diff --git a/index.html b/index.html index e7bdd9dc..c0427c64 100644 --- a/index.html +++ b/index.html @@ -80,9 +80,11 @@ ga('send', 'pageview'); diff --git a/presets/verilog/font_cp437_8x8.v b/presets/verilog/font_cp437_8x8.v index 8856a8f9..f6f2a01d 100644 --- a/presets/verilog/font_cp437_8x8.v +++ b/presets/verilog/font_cp437_8x8.v @@ -1,3 +1,6 @@ +`ifndef FONT_CP437_H +`define FONT_CP437_H + // PC font (code page 437) module font_cp437_8x8(addr, data); @@ -270,3 +273,5 @@ module font_cp437_8x8(addr, data); }; end endmodule + +`endif diff --git a/presets/verilog/maze_game.v b/presets/verilog/maze_game.v new file mode 100644 index 00000000..2e67354c --- /dev/null +++ b/presets/verilog/maze_game.v @@ -0,0 +1,126 @@ +`include "hvsync_generator.v" +`include "font_cp437_8x8.v" +`include "ram.v" +`include "tile_renderer.v" +`include "sprite_scanline_renderer.v" +`include "lfsr.v" +`include "sound_generator.v" +`include "cpu8.v" +`include "cpu16.v" + +module maze_game_top(clk, reset, hsync, vsync, rgb); + + input clk, reset; + output hsync, vsync; + output [3:0] rgb; + + wire display_on; + wire [8:0] hpos; + wire [8:0] vpos; + + // video RAM bus + wire [7:0] vram_read; + reg [7:0] vram_write = 0; + reg vram_writeenable = 0; + + // multiplex sprite and tile RAM + wire sprite_ram_select = (vpos == 256); + reg [15:0] tile_ram_addr; + reg [6:0] sprite_ram_addr; + + // tile and sprite ROM + wire [10:0] tile_rom_addr; + wire [7:0] tile_rom_data; + wire [15:0] sprite_rom_addr; + wire [15:0] sprite_rom_data; + + // gfx outputs + wire [3:0] tile_rgb; + wire [3:0] sprite_rgb; + + hvsync_generator hvsync_gen( + .clk(clk), + .reset(reset), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + // video RAM (16k) + RAM #(14,8) vram( + .clk(clk), + .dout(vram_read), + .din(vram_write), + .addr(sprite_ram_select + ? {7'b0111111, sprite_ram_addr} + : tile_ram_addr[13:0]), + .we(vram_writeenable) + ); + + tile_renderer tile_gen( + .clk(clk), + .reset(reset), + .hpos(hpos), + .vpos(vpos), + .display_on(display_on), + .ram_addr(tile_ram_addr), + .ram_read(vram_read), + .rom_addr(tile_rom_addr), + .rom_data(tile_rom_data), + .rgb(tile_rgb) + ); + + sprite_scanline_renderer ssr( + .clk(clk), + .reset(reset), + .hpos(hpos), + .vpos(vpos), + .ram_addr(sprite_ram_addr), + .ram_data(vram_read), + .rom_addr(sprite_rom_addr), + .rom_data(sprite_rom_data), + .rgb(sprite_rgb) + ); + + font_cp437_8x8 tile_rom( + .addr(tile_rom_addr), + .data(tile_rom_data) + ); + + example_bitmap_rom bitmap_rom( + .addr(sprite_rom_addr), + .data(sprite_rom_data) + ); + + assign rgb = display_on + ? (sprite_rgb>0 ? sprite_rgb : tile_rgb) + : 0; + + // CPU RAM (32k x 16 bits) + RAM #(15,16) mram( + .clk(clk), + .dout(cpuram_read), + .din(cpuram_write), + .addr(cpuram_addr[14:0]), + .we(cpuram_writeenable) + ); + + reg [15:0] cpuram_read; + reg [15:0] cpuram_write; + reg [15:0] cpuram_addr; + reg cpuram_writeenable; + wire busy; + + CPU16 cpu( + .clk(clk), + .reset(reset), + .hold(0), + .busy(busy), + .address(cpuram_addr), + .data_in(cpuram_read), + .data_out(cpuram_write), + .write(cpuram_writeenable)); + +endmodule diff --git a/presets/verilog/ram.v b/presets/verilog/ram.v index 399ff938..302a0cee 100644 --- a/presets/verilog/ram.v +++ b/presets/verilog/ram.v @@ -1,3 +1,6 @@ +`ifndef RAM_H +`define RAM_H + `include "hvsync_generator.v" module RAM(clk, addr, din, dout, we); @@ -21,3 +24,4 @@ module RAM(clk, addr, din, dout, we); endmodule +`endif diff --git a/presets/verilog/sprite_scanline_renderer.v b/presets/verilog/sprite_scanline_renderer.v index 54a0e5cf..4251ba05 100644 --- a/presets/verilog/sprite_scanline_renderer.v +++ b/presets/verilog/sprite_scanline_renderer.v @@ -1,4 +1,5 @@ `include "hvsync_generator.v" +`include "ram.v" module example_bitmap_rom(addr, data); @@ -31,6 +32,7 @@ module example_bitmap_rom(addr, data); endmodule module sprite_scanline_renderer(clk, reset, hpos, vpos, rgb, + ram_addr, ram_data, rom_addr, rom_data); parameter NB = 5; @@ -43,7 +45,9 @@ module sprite_scanline_renderer(clk, reset, hpos, vpos, rgb, input [8:0] hpos; input [8:0] vpos; output [3:0] rgb; - + + output [NB+1:0] ram_addr; + input [7:0] ram_data; output [15:0] rom_addr; input [15:0] rom_data; @@ -66,7 +70,7 @@ module sprite_scanline_renderer(clk, reset, hpos, vpos, rgb, wire [8:0] read_bufidx = {vpos[0], hpos[7:0]}; reg [15:0] out_bitmap; reg [7:0] out_attr; - wire [NB-1:0] move_index = hpos[NB-1:0]; + wire [NB-1:0] load_index = hpos[NB+2:3]; /* 0: read sprite_ypos[i] @@ -85,10 +89,25 @@ module sprite_scanline_renderer(clk, reset, hpos, vpos, rgb, // reset every frame, don't draw vpos >= 256 if (reset || vpos[8]) begin - // wiggle sprites randomly once per frame - if (vpos == 256 && hpos < N) begin - sprite_xpos[move_index] <= sprite_xpos[move_index] + 8'(($random&3)-1); - sprite_ypos[move_index] <= sprite_ypos[move_index] + 8'(($random&3)-1); + // load sprites from RAM on line 260 + // 8 cycles per sprite + if (vpos == 260 && hpos < N*8) begin + case (hpos[2:0]) + 0: begin + ram_addr <= {load_index, 2'b00}; + end + 2: begin + sprite_xpos[load_index] <= ram_data; + ram_addr <= {load_index, 2'b01}; + end + 4: begin + sprite_ypos[load_index] <= ram_data; + ram_addr <= {load_index, 2'b10}; + end + 6: begin + sprite_attr[load_index] <= ram_data; + end + endcase end end else if (hpos < N*2) begin k <= 0; @@ -183,14 +202,45 @@ module test_scanline_render_top(clk, reset, hsync, vsync, rgb); .data(rom_data) ); + wire [6:0] ram_addr; + wire [7:0] ram_read; + reg [7:0] ram_write; + reg ram_we; + + // 128-byte RAM + RAM #(7,8) ram( + .clk(clk), + .addr(ram_addr), + .dout(ram_read), + .din(ram_write), + .we(ram_we) + ); + sprite_scanline_renderer ssr( .clk(clk), .reset(reset), .hpos(hpos), .vpos(vpos), .rgb(rgb), + .ram_addr(ram_addr), + .ram_data(ram_read), .rom_addr(rom_addr), .rom_data(rom_data) ); + always @(posedge clk) begin + // wiggle sprites randomly once per frame + if (vpos == 256) begin + ram_addr <= hpos[8:2]; + // 4 clocks per read/write cycle + if (!hpos[1]) begin + ram_we <= 0; + end else begin + ram_we <= 1; + ram_write <= ram_read + 8'(($random&3)-1); + end + end else + ram_we <= 0; + end + endmodule diff --git a/presets/verilog/tile_renderer.v b/presets/verilog/tile_renderer.v new file mode 100644 index 00000000..b25873ab --- /dev/null +++ b/presets/verilog/tile_renderer.v @@ -0,0 +1,126 @@ +`include "hvsync_generator.v" +`include "font_cp437_8x8.v" +`include "ram.v" + +module tile_renderer(clk, reset, hpos, vpos, display_on, + rgb, + ram_addr, ram_read, + rom_addr, rom_data); + + input clk, reset; + input [8:0] hpos; + input [8:0] vpos; + input display_on; + output [3:0] rgb; + + output reg [15:0] ram_addr; + input [7:0] ram_read; + + output [10:0] rom_addr; + input [7:0] rom_data; + + reg [7:0] page_base = 0; // page table base (8 bits) + reg [15:0] row_base; // row table base (16 bits) + + wire [4:0] row = vpos[7:3]; // 5-bit row, vpos / 8 + wire [4:0] col = hpos[7:3]; // 5-bit column, hpos / 8 + wire [2:0] yofs = vpos[2:0]; // scanline of cell (0-7) + wire [2:0] xofs = hpos[2:0]; // which pixel to draw (0-7) + + reg [7:0] char; + reg [7:0] attr; + reg [7:0] next_char; + reg [7:0] next_attr; + + // tile ROM address + assign rom_addr = {char, yofs}; + + // lookup char and attr + always @(posedge clk) + if (hpos[8]) begin + case (hpos[7:0]) + // read row_base from page table (2 bytes) + // TODO: why 2 cycles? + 0: ram_addr <= {page_base, row, 3'b000}; + 2: row_base[7:0] <= ram_read; + 3: ram_addr <= {page_base, row, 3'b001}; + 5: row_base[15:8] <= ram_read; + endcase + end else begin + case (hpos[2:0]) + 0: ram_addr <= row_base + 16'(col); + 2: next_char <= ram_read; + 3: ram_addr <= row_base + 16'(col) + 32; + 5: next_attr <= ram_read; + 7: begin + char <= next_char; + attr <= next_attr; + end + endcase + end + + // extract bit from ROM output + assign rgb = display_on + ? (rom_data[~xofs] ? attr[3:0] : attr[7:4]) + : 0; + +endmodule + +module test_tilerender_top(clk, reset, hsync, vsync, rgb); + + input clk, reset; + output hsync, vsync; + output [3:0] rgb; + + wire display_on; + wire [8:0] hpos; + wire [8:0] vpos; + + reg [15:0] ram_addr; + wire [7:0] ram_read; + reg [7:0] ram_write = 0; + reg ram_writeenable = 0; + + wire [10:0] rom_addr; + wire [7:0] rom_data; + + hvsync_generator hvsync_gen( + .clk(clk), + .reset(reset), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + // RAM + RAM #(16,8) ram( + .clk(clk), + .dout(ram_read), + .din(ram_write), + .addr(ram_addr), + .we(ram_writeenable) + ); + + tile_renderer tile_gen( + .clk(clk), + .reset(reset), + .hpos(hpos), + .vpos(vpos), + .display_on(display_on), + .ram_addr(ram_addr), + .ram_read(ram_read), + .rom_addr(rom_addr), + .rom_data(rom_data), + .rgb(rgb) + ); + + // tile ROM + font_cp437_8x8 tile_rom( + .addr(rom_addr), + .data(rom_data) + ); + + +endmodule diff --git a/src/platform/verilog.js b/src/platform/verilog.js index 0c5b56ba..fbaa5b4e 100644 --- a/src/platform/verilog.js +++ b/src/platform/verilog.js @@ -21,7 +21,9 @@ var VERILOG_PRESETS = [ {id:'cpu8.v', name:'Simple 8-Bit CPU'}, {id:'racing_game_cpu.v', name:'Racing Game with CPU'}, {id:'framebuffer.v', name:'Frame Buffer'}, + {id:'tile_renderer.v', name:'Tile Renderer'}, {id:'sprite_scanline_renderer.v', name:'Sprite Scanline Renderer'}, + {id:'maze_game.v', name:'Maze Game'}, ]; var VERILOG_KEYCODE_MAP = makeKeycodeMap([ @@ -186,8 +188,8 @@ var VerilogPlatform = function(mainElement, options) { var self = this; var video, audio; var useAudio = false; - var videoWidth = 256+20; - var videoHeight = 240+16; + var videoWidth = 292; + var videoHeight = 256; var maxVideoLines = 262+40; // vertical hold var idata, timer, timerCallback; var gen; @@ -204,6 +206,7 @@ var VerilogPlatform = function(mainElement, options) { var scope_index_offset = 0; var scope_max_y = 0; var scope_y_top = 0; + var scope_a = 0; // used for transitions var scopeWidth = videoWidth; var scopeHeight = videoHeight; var scopeImageData; @@ -276,9 +279,9 @@ var VerilogPlatform = function(mainElement, options) { } } - var framex=videoWidth-10; + var framex=0; var framey=0; - var frameidx=videoWidth-10; + var frameidx=0; var framehsync=false; var framevsync=false; @@ -306,14 +309,14 @@ var VerilogPlatform = function(mainElement, options) { gen.vpaddle = framey > paddle_y ? 1 : 0; } if (framey > maxVideoLines || gen.vsync) { - var wasvsync = framevsync; framevsync = true; framey = 0; - framex = videoWidth-10; - frameidx = framex; - if (sync && !wasvsync) return; // exit when vsync starts + framex = 0; + frameidx = 0; } else { + var wasvsync = framevsync; framevsync = false; + if (sync && wasvsync) return; // exit when vsync ends } } } @@ -333,24 +336,27 @@ var VerilogPlatform = function(mainElement, options) { } // paint into frame, synched with vsync if full speed var sync = fps > 45; - var trace = fps < 0.1; + var trace = fps < 0.02; updateVideoFrameCycles(cyclesPerFrame * fps/60 + 1, sync, trace); //if (trace) displayTraceBuffer(); updateInspectionFrame(); - if (trace) { + if (scope_a > 0.01) { video.getContext().fillStyle = "black"; - video.getContext().fillRect(0, 0, videoWidth, videoHeight/3); - video.updateFrame(0, -framey+videoHeight/6, 0, 0, videoWidth, videoHeight); + video.getContext().fillRect(0, 0, videoWidth, videoHeight); + var vidyoffset = Math.round(scope_a*(-framey+videoHeight/6)); + video.updateFrame(0, vidyoffset, 0, 0, videoWidth, videoHeight); + video.getContext().fillStyle = "white"; + video.getContext().fillRect(framex, framey+vidyoffset, 1, 1); scope_index_offset = (trace_index - trace_signals.length*scopeWidth + trace_buffer.length) % trace_buffer.length; scope_x_offset = 0; updateScopeOverlay(trace_signals); - var k = 0.1; - scope_y_top = k*videoHeight/3 + scope_y_top*(1-k); } else { video.updateFrame(); scope_index_offset = 0; - scope_y_top = videoHeight; } + // smooth transition + scope_a = scope_a * 0.9 + (trace?1.0:0.0) * 0.1; + scope_y_top = (1 - scope_a*0.7) * videoHeight - (1 - scope_a) * scope_y_offset; updateInspectionPostFrame(); self.restartDebugState(); gen.__unreset(); @@ -631,7 +637,7 @@ var VerilogPlatform = function(mainElement, options) { this.reset = function() { gen.__reset(); trace_index = scope_x_offset = 0; - trace_buffer.fill(0); + if (trace_buffer) trace_buffer.fill(0); dirty = true; if (video) video.setRotate(gen.rotate ? -90 : 0); } diff --git a/src/ui.js b/src/ui.js index 0e5b90e6..b56da0e8 100644 --- a/src/ui.js +++ b/src/ui.js @@ -1252,6 +1252,14 @@ function _fasterFrameRate() { setFrameRateUI(fps); } +function _slowestFrameRate() { + setFrameRateUI(60/65536); +} + +function _fastestFrameRate() { + setFrameRateUI(60); +} + function setupDebugControls(){ $("#dbg_reset").click(resetAndDebug); $("#dbg_pause").click(pause); @@ -1305,6 +1313,8 @@ function setupDebugControls(){ $("#speed_bar").show(); $("#dbg_slower").click(_slowerFrameRate); $("#dbg_faster").click(_fasterFrameRate); + $("#dbg_slowest").click(_slowestFrameRate); + $("#dbg_fastest").click(_fastestFrameRate); } updateDebugWindows(); }