diff --git a/build.tcl b/build.tcl index 8096b62..ab4e095 100644 --- a/build.tcl +++ b/build.tcl @@ -17,6 +17,7 @@ if {$argc == 2} { if {$dev eq "nano20k"} { set_device GW2AR-LV18QN88C8/I7 -device_version C add_file src/nano20k/config.v + add_file -type verilog "src/snes2hdmi_nano.v" add_file -type cst "src/nano20k/snestang.cst" add_file -type verilog "src/nano20k/gowin_pll_hdmi.v" add_file -type verilog "src/nano20k/gowin_pll_snes.v" @@ -34,6 +35,7 @@ if {$dev eq "nano20k"} { } else { error "Unknown controller $controller" } + add_file -type verilog "src/snes2hdmi.v" add_file -type verilog "src/primer25k/gowin_pll_27.v" add_file -type verilog "src/primer25k/gowin_pll_hdmi.v" add_file -type verilog "src/primer25k/gowin_pll_snes.v" @@ -50,6 +52,7 @@ if {$dev eq "nano20k"} { } else { error "Unknown controller $controller" } + add_file -type verilog "src/snes2hdmi.v" add_file -type verilog "src/primer25k/gowin_pll_27.v" add_file -type verilog "src/primer25k/gowin_pll_hdmi.v" add_file -type verilog "src/primer25k/gowin_pll_snes.v" @@ -66,6 +69,7 @@ if {$dev eq "nano20k"} { } else { error "Unknown controller $controller" } + add_file -type verilog "src/snes2hdmi.v" add_file -type verilog "src/mega138k/gowin_pll_27.v" add_file -type verilog "src/mega138k/gowin_pll_hdmi.v" add_file -type verilog "src/mega138k/gowin_pll_snes.v" @@ -125,7 +129,6 @@ add_file -type verilog "src/ppuoam.v" add_file -type verilog "src/smc_parser.v" add_file -type verilog "src/smp.v" add_file -type verilog "src/snes.v" -add_file -type verilog "src/snes2hdmi.v" add_file -type verilog "src/controller_adapter.sv" add_file -type verilog "src/controller_ds2.sv" add_file -type verilog "src/controller_snes.v" diff --git a/snestang_nano20k.gprj b/snestang_nano20k.gprj index 36bdb80..1ee89c4 100644 --- a/snestang_nano20k.gprj +++ b/snestang_nano20k.gprj @@ -60,7 +60,8 @@ - + + diff --git a/src/iosys/iosys.v b/src/iosys/iosys.v index 9c42a25..2ee3ae8 100644 --- a/src/iosys/iosys.v +++ b/src/iosys/iosys.v @@ -43,8 +43,8 @@ module iosys #( // OSD display interface output overlay, - input [10:0] overlay_x, // 720p - input [9:0] overlay_y, + input [7:0] overlay_x, // 0-255 + input [7:0] overlay_y, // 0-223 output [14:0] overlay_color, // BGR5 input [11:0] joy1, // joystick 1: (R L X A RT LT DN UP START SELECT Y B) input [11:0] joy2, // joystick 2 @@ -219,7 +219,7 @@ picorv32 #( // text display @ 0x0200_0000 textdisp #(.COLOR_LOGO(COLOR_LOGO)) disp ( .clk(clk), .hclk(hclk), .resetn(resetn), - .overlay_x(overlay_x), .overlay_y(overlay_y), .overlay_color(overlay_color), + .x(overlay_x), .y(overlay_y), .color(overlay_color), .reg_char_we(textdisp_reg_char_sel ? mem_wstrb : 4'b0), .reg_char_di(mem_wdata) ); diff --git a/src/iosys/textdisp.v b/src/iosys/textdisp.v index 7c048cf..562b6a4 100644 --- a/src/iosys/textdisp.v +++ b/src/iosys/textdisp.v @@ -2,18 +2,19 @@ // to print characters to the display. module textdisp( - input clk, // main logic clock - input hclk, // hdmi clock - input resetn, + input clk, // main logic clock + input hclk, // hdmi clock + input resetn, - input [10:0] overlay_x, - input [9:0] overlay_y, - output reg [14:0] overlay_color, + input [7:0] x, // 0 - 255 + input [7:0] y, // 0 - 223 + output reg [14:0] color, // pixel color, NOTE: 2-cycle latency + // we need 1 cycle to fetch character and 1 cycle to fetch font byte // PicoRV32 I/O interface. Every write updates one character // [23:16]: x, [15:8]: y, [7-0]: character to print - input [3:0] reg_char_we, - input [31:0] reg_char_di + input [3:0] reg_char_we, + input [31:0] reg_char_di ); // BGR @@ -21,7 +22,12 @@ localparam [14:0] COLOR_BACK = 15'b00000_00000_00000; localparam [14:0] COLOR_TEXT = 15'b10000_11111_11111; // yellow //localparam [14:0] COLOR_TEXT = 15'b11000_11000_11000; // white localparam [14:0] COLOR_CURSOR = 15'b10000_11000_11111; // orange -parameter [14:0] COLOR_LOGO = 15'b00000_10101_00000; // green +// parameter [14:0] COLOR_LOGO = 15'b00000_10101_00000; // green +parameter [14:0] COLOR_LOGO = 15'b00000_10011_11111; + +// 72x14 pixels 1bpp logo +localparam LOGO_X = 128-36; +localparam LOGO_Y = 201; // // Pixel output logic for characters and logo: @@ -31,9 +37,7 @@ parameter [14:0] COLOR_LOGO = 15'b00000_10101_00000; // green // reg [10:0] mem_addr_b; reg [7:0] mem_do_b; -reg [1:0] mem_cnt; reg is_cursor; -reg [2:0] xoff, yoff; reg [14:0] overlay_color_buf; wire [1:0] cmd = reg_char_di[31:24]; @@ -56,111 +60,64 @@ gowin_dpb_menu menu_mem ( .dinb(), .doutb(mem_do_b) ); -localparam overlay_x_start = 256 - 3; -reg [7:0] px; // = (overlay_x - overlay_x_start) / 3; -reg [7:0] py; // = (overlay_y - 24) / 3; -reg [9:0] overlay_y_r; -reg [1:0] y_cnt; - -// 72x14 pixels 1bpp logo -localparam LOGO_X = 128-36; -localparam LOGO_Y = 201; - reg [6:0] logo_addr; reg [2:0] logo_xoff; reg logo_active; - -always @* begin - case (mem_cnt) - 2'd0: mem_addr_b <= {1'b0, py[7:3], px[7:3]}; // fetch next character - 2'd1: mem_addr_b <= logo_active ? - {4'b0111, logo_addr} : // fetch logo byte - {1'b1, mem_do_b[7] ? 7'h3F : mem_do_b[6:0], yoff}; - // fetch font byte (? for non-ASCII chars) - default: mem_addr_b <= 0; +reg [14:0] color_buf; +reg [7:0] x_r; + +// Rendering state machine +reg [2:0] state; +localparam MAIN = 0; // default state and fetching character +localparam FETCH_FONT = 1; // fetch font byte +localparam FETCH_LOGO = 2; // fetch logo byte +localparam OUTPUT = 3; // output new pixel and fetch character + +always @* begin // address and output logic + color = color_buf; + case (state) + MAIN: mem_addr_b = {1'b0, y[7:3], x[7:3]}; + FETCH_FONT: mem_addr_b = {1'b1, mem_do_b[7] ? 7'h3F : mem_do_b[6:0], y[2:0]}; + FETCH_LOGO: mem_addr_b = {4'b0111, logo_addr}; + OUTPUT: begin + mem_addr_b = {1'b0, y[7:3], x[7:3]}; + if (logo_active) + color = mem_do_b[logo_xoff] ? COLOR_LOGO : COLOR_BACK; + else + color = mem_do_b[x[2:0]] ? (is_cursor ? COLOR_CURSOR : COLOR_TEXT) : COLOR_BACK; + end + default: mem_addr_b = 0; endcase end -reg py_logo_active; - -always @(posedge hclk) begin - if (py == LOGO_Y) - py_logo_active <= 1; - if (py == LOGO_Y + 14) - py_logo_active <= 0; - - if (px >= LOGO_X && px < LOGO_X+71 && py_logo_active) - logo_active <= 1; - else - logo_active <= 0; -end - -always @(posedge hclk) begin - - // mem_cnt: cycle counter (0,1,2) - mem_cnt <= mem_cnt + 2'd1; - if (mem_cnt == 2'd2) begin - mem_cnt <= 0; - px <= px + 8'd1; - end - if (overlay_x == overlay_x_start-1) begin - mem_cnt <= 0; - px <= 0; - end - overlay_y_r <= overlay_y; - if (overlay_y != overlay_y_r) begin - if (overlay_y == 10'd24) begin - py <= 0; - y_cnt <= 0; - end else begin - y_cnt <= y_cnt + 2'd1; - if (y_cnt == 2'd2) begin - py <= py + 10'd1; - y_cnt <= 0; +always @(posedge hclk) begin // actual state machine + reg [7:0] logo_x, logo_y; + + x_r <= x; + + case (state) + MAIN, OUTPUT: begin + state <= MAIN; + if (state == OUTPUT) color_buf <= color; + if (x[0] != x_r[0]) begin // moved to new pixel + if (x >= LOGO_X && x < LOGO_X+72 && y >= LOGO_Y && y < LOGO_Y+14) begin + state <= FETCH_LOGO; + logo_active <= 1; + end else begin + state <= FETCH_FONT; + logo_active <= 0; end end - end - - // address generation - case (mem_cnt) - 2'd0: begin - reg [6:0] logo_x; - reg [3:0] logo_y; - - // output color from last cycle - overlay_color <= overlay_color_buf; - - // for next character - is_cursor <= px[7:3] == 0; - xoff <= px[2:0]; - yoff <= py[2:0]; - - logo_x = px - LOGO_X; - logo_y = py - LOGO_Y; - // logo_addr <= logo_y * 9 + logo_x[6:3]; + logo_x = x - LOGO_X; + logo_y = y - LOGO_Y; logo_addr <= {logo_y, 3'b0} + logo_y + logo_x[6:3]; logo_xoff <= logo_x[2:0]; - - // mem_do_b is character after cycle 0 - end - 2'd1: begin - // bram fetches font or logo byte to mem_do_b - end - 2'd2: begin - // compute output color - overlay_color_buf <= COLOR_BACK; - if (logo_active) begin - if (mem_do_b[logo_xoff]) - overlay_color_buf <= COLOR_LOGO; - else - overlay_color_buf <= COLOR_BACK; - end else if (mem_do_b[xoff]) begin - if (is_cursor) - overlay_color_buf <= COLOR_CURSOR; - else - overlay_color_buf <= COLOR_TEXT; - end + is_cursor <= x[7:3] == 0; end + + FETCH_FONT, FETCH_LOGO: state <= OUTPUT; + + default: ; endcase end diff --git a/src/snes2hdmi.v b/src/snes2hdmi.v index b871a47..aa78343 100644 --- a/src/snes2hdmi.v +++ b/src/snes2hdmi.v @@ -16,8 +16,8 @@ module snes2hdmi ( input [8:0] ys, // {field,y} input overlay, - output [10:0] overlay_x, - output [9:0] overlay_y, + output [7:0] overlay_x, + output [7:0] overlay_y, input [14:0] overlay_color, input [15:0] audio_l, @@ -72,13 +72,13 @@ module snes2hdmi ( // BRAM line buffer for 16 lines. Each pixel is 5:5:5 RGB // Each BRAM block holds 4 lines. So 16 lines needs 4 blocks. // - localparam BUF_WIDTH = 4; // 2 ^ BUF_WIDTH lines + localparam BUF_WIDTH = 5; // 2 ^ BUF_WIDTH lines localparam BUF_SIZE = 1 << BUF_WIDTH; reg [14:0] mem [0:BUF_SIZE*256-1]; - reg [11:0] mem_portA_addr; + reg [BUF_WIDTH+8-1:0] mem_portA_addr; reg [14:0] mem_portA_wdata; reg mem_portA_we; - wire [11:0] mem_portB_addr; + wire [BUF_WIDTH+8-1:0] mem_portB_addr; reg [14:0] mem_portB_rdata; reg mem_writeto = 1'b0; // current line we are writing to. we read from the other line @@ -91,15 +91,18 @@ module snes2hdmi ( reg sync_done = 1'b0; reg hdmi_first_line; reg pause_sync; // pause for even number of cycles for PPU sdram access to stay in sync + reg hdmi_first_line_r, hdmi_first_line_rr; always @(posedge clk) begin + hdmi_first_line_r <= hdmi_first_line; + hdmi_first_line_rr <= hdmi_first_line_r; if (pause_snes_for_frame_sync) pause_sync <= ~pause_sync; if (~sync_done) begin if (~pause_snes_for_frame_sync) begin - if (ys[7:0] == 8'd2 && snes_refresh) begin // halt SNES during snes dram refresh on line 2 + if (ys[7:0] == 8'd1 && snes_refresh) begin // halt SNES after first line is buffered pause_snes_for_frame_sync <= 1'b1; pause_sync <= 0; end - end else if (hdmi_first_line && pause_sync) begin // HDMI frame start, now resume SNES + end else if (hdmi_first_line_rr && pause_sync) begin // HDMI frame start, now resume SNES pause_snes_for_frame_sync <= 1'b0; sync_done <= 1'b1; end @@ -108,7 +111,7 @@ module snes2hdmi ( if (ys[7:0] == 8'd200) sync_done <= 1'b0; // reset sync_done for next frame end always @(posedge clk_pixel) begin - if (cy == 10'd24 && cx >= 11'd256 && cx < 11'd356) // first 100 pixels of first line + if (cy == 0 && cx >= 11'd160) hdmi_first_line <= 1; else hdmi_first_line <= 0; @@ -202,43 +205,77 @@ module snes2hdmi ( // // Video // - assign active = cx >= 11'd256 && cx < 11'd1024 && cy >= 10'd24 && cy < 10'd696; - wire [10:0] x0 = cx - 11'd256; - wire [9:0] y0 = cy - 10'd24; - - // synthesizer takes care of integer const division with a few ALUs and no DSP usage - assign x = (cx - 256) / 3; - assign y = (cy - 24) / 3; - // another way that works but uses more resources - // 0.3333 = 0.0101010101 binary -// assign x = (x0 + (x0 >> 2) + (x0 >> 4) + (x0 >> 6) + (x0 >> 8)) >> 2; - // assign y = (y0 + (y0 >> 2) + (y0 >> 4) + (y0 >> 6) + (y0 >> 8)) >> 2; - assign mem_portB_addr = {y[BUF_WIDTH-1:0], x}; - reg [23:0] rgb; - - assign overlay_x = cx; - assign overlay_y = cy; + reg [23:0] rgb; // actual RGB output + reg active; + reg [7:0] xx; // scaled-down pixel position + reg [7:0] yy; + reg [10:0] xcnt; + reg [10:0] ycnt; // fractional scaling counters + reg [9:0] cy_r; + assign mem_portB_addr = {yy[BUF_WIDTH-1:0], xx}; + assign overlay_x = xx; + assign overlay_y = yy; + localparam XSTART = (1280 - 960) / 2; // 960:720 = 4:3 + localparam XSTOP = (1280 + 960) / 2; + + // address calculation + // Assume the video occupies fully on the Y direction, we are upscaling the video by `720/height`. + // xcnt and ycnt are fractional scaling counters. + always @(posedge clk_pixel) begin + reg active_t; + reg [10:0] xcnt_next; + reg [10:0] ycnt_next; + xcnt_next = xcnt + 256; + ycnt_next = ycnt + 224; + + active_t = 0; + if (cx == XSTART - 1) begin + active_t = 1; + active <= 1; + end else if (cx == XSTOP - 1) begin + active_t = 0; + active <= 0; + end - // calc rgb value to hdmi - always @(posedge clk_pixel) r_active <= active; - - always @* begin - if (r_active) -// if (y < 1) // XXX: for debug purposes, show a gradient on the top -// rgb <= {x, x, x}; -// else - if (~overlay) - rgb <= {mem_portB_rdata[4:0], 3'b0, mem_portB_rdata[9:5], 3'b0, mem_portB_rdata[14:10], 3'b0}; - else begin -// if (overlay_color == 0) -// rgb <= {2'b0, mem_portB_rdata[4:0], 3'b0, mem_portB_rdata[9:5], 3'b0, mem_portB_rdata[14:10], 1'b0}; -// else - rgb <= {overlay_color[4:0], 3'b0, overlay_color[9:5], 3'b0, overlay_color[14:10], 3'b0}; + if (active_t | active) begin // increment xx + xcnt <= xcnt_next; + if (xcnt_next >= 960) begin + xcnt <= xcnt_next - 960; + xx <= xx + 1; end - else - rgb <= 24'h303030; // show a grey background + end + + cy_r <= cy; + if (cy[0] != cy_r[0]) begin // increment yy at new lines + ycnt <= ycnt_next; + if (ycnt_next >= 720) begin + ycnt <= ycnt_next - 720; + yy <= yy + 1; + end + end + + if (cx == 0) begin + xx <= 0; + xcnt <= 0; + end + + if (cy == 0) begin + yy <= 0; + ycnt <= 0; + end + end + // calc rgb value to hdmi + always @(posedge clk_pixel) begin + if (active) begin + if (overlay) + rgb <= {overlay_color[4:0],3'b0,overlay_color[9:5],3'b0,overlay_color[14:10],3'b0}; // BGR5 to RGB8 + else + rgb <= {mem_portB_rdata[4:0], 3'b0, mem_portB_rdata[9:5], 3'b0, mem_portB_rdata[14:10], 3'b0}; + end else + rgb <= 24'h303030; + end // HDMI output. logic[2:0] tmds; diff --git a/src/snes2hdmi_nano.v b/src/snes2hdmi_nano.v new file mode 100644 index 0000000..2decb45 --- /dev/null +++ b/src/snes2hdmi_nano.v @@ -0,0 +1,288 @@ +// NES video and sound to HDMI converter +// nand2mario, 2022.9 +// This is a special version for tang nano 20k as it uses less BRAM (4 vs the normal 8) + +`timescale 1ns / 1ps + +module snes2hdmi ( + input clk, // snes clock + input resetn, + + // snes video and audio signals + input dotclk, + input hblank, + input vblank, + input [14:0] rgb5, + input [8:0] xs, // {x,dotclk} + input [8:0] ys, // {field,y} + + input overlay, + output reg [7:0] overlay_x, + output reg [7:0] overlay_y, + input [14:0] overlay_color, + + input [15:0] audio_l, + input [15:0] audio_r, + input audio_ready, + output audio_en, + + // frame-sync pause happens during snes_refresh + input snes_refresh, + + // video clocks + input clk_pixel, + input clk_5x_pixel, + input locked, + + output reg pause_snes_for_frame_sync, + + // output [7:0] led, + + // output signals + output tmds_clk_n, + output tmds_clk_p, + output [2:0] tmds_d_n, + output [2:0] tmds_d_p +); + + localparam FRAMEWIDTH = 1280; // 720P + localparam FRAMEHEIGHT = 720; + localparam TOTALWIDTH = 1650; + localparam TOTALHEIGHT = 750; + localparam SCALE = 5; + localparam VIDEOID = 4; + localparam VIDEO_REFRESH = 60.0; + + localparam IDIV_SEL_X5 = 3; + localparam FBDIV_SEL_X5 = 54; + localparam ODIV_SEL_X5 = 2; + localparam DUTYDA_SEL_X5 = "1000"; + localparam DYN_SDIV_SEL_X5 = 2; + + localparam CLKFRQ = 74250; + + localparam AUDIO_BIT_WIDTH = 16; + + // video stuff + wire [9:0] cy, frameHeight; + wire [10:0] cx, frameWidth; + reg active, r_active; + wire [7:0] x, y; // SNES pixel position + + // + // BRAM line buffer for 16 lines. Each pixel is 5:5:5 RGB + // Each BRAM block holds 4 lines. So 16 lines needs 4 blocks. + // + localparam BUF_WIDTH = 4; // 2 ^ BUF_WIDTH lines + localparam BUF_SIZE = 1 << BUF_WIDTH; + reg [14:0] mem [0:BUF_SIZE*256-1]; + reg [11:0] mem_portA_addr; + reg [14:0] mem_portA_wdata; + reg mem_portA_we; + wire [11:0] mem_portB_addr; + reg [14:0] mem_portB_rdata; + reg mem_writeto = 1'b0; // current line we are writing to. we read from the other line + + // We need to do a bit of synchronization as the SNES and HDMI run on separate clocks and they + // do not align perfectly. + // - Let SNES execute a bit faster (0.5%) than the HDMI stack. This way we always have enough pixels. + // - For each frame, we let the SNES run until the first line is rendered, and written to line buffer. + // Then we pause the SNES and wait (through pause_snes_for_frame_sync) for HDMI to catch up. + // - Once HDMI started the frame, start SNES again. + reg sync_done = 1'b0; + reg hdmi_first_line; + reg pause_sync; // pause for even number of cycles for PPU sdram access to stay in sync + always @(posedge clk) begin + if (pause_snes_for_frame_sync) pause_sync <= ~pause_sync; + if (~sync_done) begin + if (~pause_snes_for_frame_sync) begin + if (ys[7:0] == 8'd2 && snes_refresh) begin // halt SNES during snes dram refresh on line 2 + pause_snes_for_frame_sync <= 1'b1; + pause_sync <= 0; + end + end else if (hdmi_first_line && pause_sync) begin // HDMI frame start, now resume SNES + pause_snes_for_frame_sync <= 1'b0; + sync_done <= 1'b1; + end + end else + pause_snes_for_frame_sync <= 1'b0; + if (ys[7:0] == 8'd200) sync_done <= 1'b0; // reset sync_done for next frame + end + always @(posedge clk_pixel) begin + if (cy == 10'd24 && cx >= 11'd256 && cx < 11'd356) // first 100 pixels of first line + hdmi_first_line <= 1; + else + hdmi_first_line <= 0; + end + + // BRAM port A read/write + always_ff @(posedge clk) begin + if (mem_portA_we) begin + mem[mem_portA_addr] <= mem_portA_wdata; + end + end + + // BRAM port B read + always_ff @(posedge clk_pixel) begin + mem_portB_rdata <= mem[mem_portB_addr]; + end + + integer j; + initial begin + // On power-up, fill line buffer with a gradient + for (j = 0; j < 512; j=j+1) begin + mem[j][14:10] = j; + mem[j][9:5] = j; + mem[j][4:0] = j; + end + end + + // + // Data input + // + reg r_dotclk, r_vblank, r_hblank; + always @(posedge clk) begin + r_dotclk <= dotclk; + r_vblank <= vblank; + mem_portA_we <= 1'b0; + if (dotclk && ~r_dotclk && ~hblank && ~vblank && ys[7:0] < 224) begin // on posedge of dotclk, read a pixel + mem_portA_addr <= {ys[BUF_WIDTH-1:0], xs[8:1]}; + mem_portA_wdata <= rgb5; + mem_portA_we <= 1'b1; + end + end + + always @(posedge clk) begin + r_hblank <= hblank; + if (hblank & ~r_hblank) begin // flip active line on hblank + mem_writeto <= ~mem_writeto; + end + end + + + // HDMI TX audio - send 32K sampling rate audio + // Hoarse sound issue: https://github.com/nand2mario/snestang/issues/16 + // SNES average framerate is 60.09881. So we need to pause the SNES a bit every frame to ensure 60 fps HDMI. + // For sound, that translate to about 8 samples pause time per frame. So we run our DSP a bit faster to + // buffer enough samples before the SNES pause. + localparam AUDIO_OUT_RATE = 32000; + localparam AUDIO_DELAY = CLKFRQ * 1000 / AUDIO_OUT_RATE / 2; + reg [15:0] audio_sample_word [1:0]; + reg [$clog2(AUDIO_DELAY)-1:0] audio_divider; + reg clk_audio; + reg audio_rinc; + wire audio_full, audio_empty; + wire [31:0] audio_sample; + + always @(posedge clk_pixel) begin + if (resetn) begin + audio_rinc <= 0; + if (audio_divider != AUDIO_DELAY - 1) + audio_divider <= audio_divider + 1; + else begin + audio_divider <= 0; + clk_audio = ~clk_audio; // generate audio clock @ 32Khz + // output audio sample on posedge clk_audio + if (!clk_audio && !audio_empty) begin + {audio_sample_word[0], audio_sample_word[1]} <= audio_sample; + audio_rinc <= 1'b1; + end + end + end + end + + // Audio sample FIFO for 16 samples + dual_clk_fifo #(.DATESIZE(32), .ADDRSIZE(4)) audio_fifo ( + .clk(clk), .wrst_n(1'b1), + .winc(audio_ready), .wdata({audio_l, audio_r}), .wfull(audio_full), + .rclk(clk_pixel), .rrst_n(1'b1), + .rinc(audio_rinc), .rdata(audio_sample), .rempty(audio_empty), + .almost_full(), .almost_empty() + ); + + // + // Video + // + assign active = cx >= 11'd256 && cx < 11'd1024 && cy >= 10'd24 && cy < 10'd696; + wire [10:0] x0 = cx - 11'd256; + wire [9:0] y0 = cy - 10'd24; + + // synthesizer takes care of integer const division with a few ALUs and no DSP usage + assign x = (cx - 256) / 3; + assign y = (cy - 24) / 3; + // another way that works but uses more resources + // 0.3333 = 0.0101010101 binary +// assign x = (x0 + (x0 >> 2) + (x0 >> 4) + (x0 >> 6) + (x0 >> 8)) >> 2; + // assign y = (y0 + (y0 >> 2) + (y0 >> 4) + (y0 >> 6) + (y0 >> 8)) >> 2; + assign mem_portB_addr = {y[BUF_WIDTH-1:0], x}; + reg [23:0] rgb; + + reg [1:0] overlay_cnt; + + always @(posedge clk_pixel) begin + if (cx == 0) begin + overlay_x <= 0; + overlay_cnt <= 0; + end + if (cx >= 256) begin + overlay_cnt <= overlay_cnt == 2 ? 0 : overlay_cnt + 1; + if (overlay_cnt == 1) + overlay_x <= overlay_x + 1; + end + overlay_y <= y; + end + + // calc rgb value to hdmi + always @(posedge clk_pixel) r_active <= active; + + always @* begin + if (r_active) +// if (y < 1) // XXX: for debug purposes, show a gradient on the top +// rgb <= {x, x, x}; +// else + if (~overlay) + rgb <= {mem_portB_rdata[4:0], 3'b0, mem_portB_rdata[9:5], 3'b0, mem_portB_rdata[14:10], 3'b0}; + else begin +// if (overlay_color == 0) +// rgb <= {2'b0, mem_portB_rdata[4:0], 3'b0, mem_portB_rdata[9:5], 3'b0, mem_portB_rdata[14:10], 1'b0}; +// else + rgb <= {overlay_color[4:0], 3'b0, overlay_color[9:5], 3'b0, overlay_color[14:10], 3'b0}; + end + else + rgb <= 24'h303030; // show a grey background + end + + + // HDMI output. + logic[2:0] tmds; + + hdmi #( .VIDEO_ID_CODE(VIDEOID), + .DVI_OUTPUT(0), + .VIDEO_REFRESH_RATE(VIDEO_REFRESH), + .IT_CONTENT(1), + .AUDIO_RATE(AUDIO_OUT_RATE), + .AUDIO_BIT_WIDTH(AUDIO_BIT_WIDTH), + .START_X(0), + .START_Y(0) ) + + hdmi( .clk_pixel_x5(clk_5x_pixel), + .clk_pixel(clk_pixel), + .clk_audio(clk_audio), + .rgb(rgb), + .reset( ~resetn ), + .audio_sample_word(audio_sample_word), + .tmds(tmds), + .tmds_clock(tmdsClk), + .cx(cx), + .cy(cy), + .frame_width( frameWidth ), + .frame_height( frameHeight ) ); + + // Gowin LVDS output buffer + ELVDS_OBUF tmds_bufds [3:0] ( + .I({clk_pixel, tmds}), + .O({tmds_clk_p, tmds_d_p}), + .OB({tmds_clk_n, tmds_d_n}) + ); + +endmodule diff --git a/src/snestang_top.v b/src/snestang_top.v index 9168710..6322b85 100644 --- a/src/snestang_top.v +++ b/src/snestang_top.v @@ -629,8 +629,8 @@ assign snes_joy1_di[1] = 0; // P3 assign snes_joy2_di[1] = 0; // P4 wire [14:0] overlay_color; -wire [10:0] overlay_x; -wire [9:0] overlay_y; +wire [7:0] overlay_x; +wire [7:0] overlay_y; wire [7:0] dbg_dat_out_loader;