diff --git a/fpga/examples/.gitignore b/fpga/examples/.gitignore new file mode 100644 index 00000000..e48fc88a --- /dev/null +++ b/fpga/examples/.gitignore @@ -0,0 +1,6 @@ +*.asc +*.bin +*.blif +*.log +*.out +*.vcd diff --git a/fpga/examples/Makefile b/fpga/examples/Makefile new file mode 100644 index 00000000..a0c901c4 --- /dev/null +++ b/fpga/examples/Makefile @@ -0,0 +1,22 @@ + +%.asc: %.v icestick.pcf + yosys -p "synth_ice40 -blif $*.blif" $*.v | tee $*.log + arachne-pnr -d 1k -p icestick.pcf $*.blif -o $*.asc + +%.bin: %.asc + icetime -c 12 -d hx1k $*.asc + icepack $*.asc $*.bin + +%.prog: %.bin + iceprog $< + +%.hex: %.asm + node ../../src/tools/jsasm.js < $< > $@ + +%.vlog: %.asc + icebox_vlog $*.asc > $*.vlog + +%.vcd: %.vlog test.vlog + iverilog -o $*.out -v /usr/share/yosys/ice40/cells_sim.v $< test.vlog + vvp $*.out + diff --git a/fpga/examples/ball_paddle.v b/fpga/examples/ball_paddle.v new file mode 100644 index 00000000..d1ccc761 --- /dev/null +++ b/fpga/examples/ball_paddle.v @@ -0,0 +1,247 @@ + +`include "hvsync_generator.v" +`include "digits10.v" +`include "scoreboard.v" + +/* +A brick-smashing ball-and-paddle game. +*/ + +module ball_paddle_top(clk, reset, hpaddle, out); + + input clk; + input reset; + input hpaddle; + wire hsync, vsync; + output [1:0] out; + + wire display_on; + wire [8:0] hpos; + wire [8:0] vpos; + + reg [8:0] paddle_pos; // paddle X position + + reg [8:0] ball_x; // ball X position + reg [8:0] ball_y; // ball Y position + reg ball_dir_x; // ball X direction (0=left, 1=right) + reg ball_speed_x; // ball speed (0=1 pixel/frame, 1=2 pixels/frame) + reg ball_dir_y; // ball Y direction (0=up, 1=down) + + reg brick_array [0:BRICKS_H*BRICKS_V-1]; // 16*8 = 128 bits + + wire [3:0] score0; // score right digit + wire [3:0] score1; // score left digit + wire [3:0] lives; // # lives remaining + reg incscore; // incscore signal + reg declives = 0; // TODO + + localparam BRICKS_H = 16; // # of bricks across + localparam BRICKS_V = 8; // # of bricks down + + localparam BALL_DIR_LEFT = 0; + localparam BALL_DIR_RIGHT = 1; + localparam BALL_DIR_DOWN = 1; + localparam BALL_DIR_UP = 0; + + localparam PADDLE_WIDTH = 31; // horizontal paddle size + localparam BALL_SIZE = 6; // square ball size + + reg clk2; + always @(posedge clk) begin + clk2 <= !clk2; + end + + hvsync_generator #(256,60,40,25) hvsync_gen( + .clk(clk2), + .reset(reset), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + // scoreboard + wire score_gfx; // output from score generator + player_stats stats(.reset(reset), + .score0(score0), .score1(score1), .incscore(incscore), + .lives(lives), .declives(declives)); + scoreboard_generator score_gen( + .score0(score0), .score1(score1), .lives(lives), + .vpos(vpos), .hpos(hpos), + .board_gfx(score_gfx)); + + wire [5:0] hcell = hpos[8:3]; // horizontal brick index + wire [5:0] vcell = vpos[8:3]; // vertical brick index + wire lr_border = hcell==0 || hcell==31; // along horizontal border? + + // TODO: unsigned compare doesn't work in JS + wire [8:0] paddle_rel_x = ((hpos-paddle_pos) & 9'h1ff); + + // player paddle graphics signal + wire paddle_gfx = (vcell == 28) && (paddle_rel_x < PADDLE_WIDTH); + + // difference between ball position and video beam + wire [8:0] ball_rel_x = (hpos - ball_x); + wire [8:0] ball_rel_y = (vpos - ball_y); + + // ball graphics signal + wire ball_gfx = ball_rel_x < BALL_SIZE + && ball_rel_y < BALL_SIZE; + + reg main_gfx; // main graphics signal (bricks and borders) + reg brick_present; // 1 when we are drawing a brick + reg [6:0] brick_index;// index into array of current brick + // brick graphics signal + wire brick_gfx = lr_border || (brick_present && vpos[2:0] != 0 && hpos[3:1] != 4); + + // scan bricks: compute brick_index and brick_present flag + always @(posedge clk2) + // see if we are scanning brick area + if (vpos[8:6] == 1 && !lr_border) + begin + // every 16th pixel, starting at 8 + if (hpos[3:0] == 8) begin + // compute brick index + brick_index <= {vpos[5:3], hpos[7:4]}; + end + // every 17th pixel + else if (hpos[3:0] == 9) begin + // load brick bit from array + brick_present <= brick_array[brick_index]; + end + end else begin + brick_present <= 0; + end + + // only works when paddle at bottom of screen! + // (we don't want to mess w/ paddle position during visible portion) + always @(posedge hsync) + if (!hpaddle) + paddle_pos <= vpos; + + // 1 when ball signal intersects main (brick + border) signal + wire ball_pixel_collide = main_gfx & ball_gfx; + + /* verilator lint_off MULTIDRIVEN */ + reg ball_collide_paddle = 0; + reg [3:0] ball_collide_bits = 0; + /* verilator lint_on MULTIDRIVEN */ + + // compute ball collisions with paddle and playfield + always @(posedge clk2) + if (ball_pixel_collide) begin + // did we collide w/ paddle? + if (paddle_gfx) begin + ball_collide_paddle <= 1; + end + // ball has 4 collision quadrants + if (!ball_rel_x[2] & !ball_rel_y[2]) ball_collide_bits[0] <= 1; + if (ball_rel_x[2] & !ball_rel_y[2]) ball_collide_bits[1] <= 1; + if (!ball_rel_x[2] & ball_rel_y[2]) ball_collide_bits[2] <= 1; + if (ball_rel_x[2] & ball_rel_y[2]) ball_collide_bits[3] <= 1; + end + + // compute ball collisions with brick and increment score + always @(posedge clk2) + if (ball_pixel_collide && brick_present) begin + brick_array[brick_index] <= 0; + incscore <= 1; // increment score + end else begin + incscore <= 0; // reset incscore + end + + // computes position of ball in relation to center of paddle + wire signed [8:0] ball_paddle_dx = ball_x - paddle_pos + 8; + + // ball bounce: determine new velocity/direction + always @(posedge vsync or posedge reset) + begin + if (reset) begin + ball_dir_y <= BALL_DIR_DOWN; + end else + // ball collided with paddle? + if (ball_collide_paddle) begin + // bounces upward off of paddle + ball_dir_y <= BALL_DIR_UP; + // which side of paddle, left/right? + ball_dir_x <= (ball_paddle_dx < 20) ? BALL_DIR_LEFT : BALL_DIR_RIGHT; + // hitting with edge of paddle makes it fast + ball_speed_x <= ball_collide_bits[3:0] != 4'b1100; + end else begin + // collided with playfield + // TODO: can still slip through corners + // compute left/right bounce + casez (ball_collide_bits[3:0]) + 4'b01?1: ball_dir_x <= BALL_DIR_RIGHT; // left edge/corner + 4'b1101: ball_dir_x <= BALL_DIR_RIGHT; // left corner + 4'b101?: ball_dir_x <= BALL_DIR_LEFT; // right edge/corner + 4'b1110: ball_dir_x <= BALL_DIR_LEFT; // right corner + default: ; + endcase + // compute top/bottom bounce + casez (ball_collide_bits[3:0]) + 4'b1011: ball_dir_y <= BALL_DIR_DOWN; + 4'b0111: ball_dir_y <= BALL_DIR_DOWN; + 4'b001?: ball_dir_y <= BALL_DIR_DOWN; + 4'b0001: ball_dir_y <= BALL_DIR_DOWN; + 4'b0100: ball_dir_y <= BALL_DIR_UP; + 4'b1?00: ball_dir_y <= BALL_DIR_UP; + 4'b1101: ball_dir_y <= BALL_DIR_UP; + 4'b1110: ball_dir_y <= BALL_DIR_UP; + default: ; + endcase + end + ball_collide_bits <= 0; // clear all collide bits for frame + ball_collide_paddle <= 0; + end + + // ball motion: update ball position + always @(negedge vsync or posedge reset) + begin + if (reset) begin + // reset ball position to top center + ball_x <= 128; + ball_y <= 180; + end else begin + // move ball horizontal and vertical position + if (ball_dir_x == BALL_DIR_RIGHT) + ball_x <= ball_x + (ball_speed_x?1:0) + 1; + else + ball_x <= ball_x - (ball_speed_x?1:0) - 1; + ball_y <= ball_y + (ball_dir_y==BALL_DIR_DOWN?1:-1); + end + end + + // compute main_gfx + always @(*) + begin + case (vpos[8:3]) + 0: main_gfx = score_gfx; // scoreboard + 1: main_gfx = score_gfx; + 2: main_gfx = score_gfx; + 3: main_gfx = 0; + 4: main_gfx = 1; // top border + 8: main_gfx = brick_gfx; // 1st brick row + 9: main_gfx = brick_gfx; // ... + 10: main_gfx = brick_gfx; + 11: main_gfx = brick_gfx; + 12: main_gfx = brick_gfx; + 13: main_gfx = brick_gfx; + 14: main_gfx = brick_gfx; + 15: main_gfx = brick_gfx; // 8th brick row + 28: main_gfx = paddle_gfx | lr_border; // paddle + 29: main_gfx = hpos[0] ^ vpos[0]; // bottom border + default: main_gfx = lr_border; // left/right borders + endcase + end + + // combine signals to RGB output + wire r = display_on && (ball_gfx | paddle_gfx); + wire g = display_on && (main_gfx | ball_gfx); + wire b = display_on && (ball_gfx | brick_present); + + assign out = (hsync||vsync) ? 0 : (1+g+(r|b)); + +endmodule + diff --git a/presets/verilog/ram2.v b/fpga/examples/blktest.v similarity index 53% rename from presets/verilog/ram2.v rename to fpga/examples/blktest.v index e4187872..4b40b0bc 100644 --- a/presets/verilog/ram2.v +++ b/fpga/examples/blktest.v @@ -1,25 +1,12 @@ `include "hvsync_generator.v" `include "digits10.v" - -module RAM_1KB_tri(clk, addr, data, we); - input clk; - input [9:0] addr; - inout [7:0] data; - input we; - - reg [7:0] mem [1023:0]; - assign data = !we ? mem[addr] : 8'bz; - always @(posedge clk) begin - if (we) - mem[addr] <= data; - end -endmodule +`include "ram.v" +`include "lfsr.v" module test_ram2_top( input clk, reset, - output hsync, vsync, - output [2:0] rgb + output [1:0] out ); wire display_on; wire [8:0] hpos; @@ -27,19 +14,34 @@ module test_ram2_top( reg ram_writeenable = 0; wire [9:0] ram_addr = {row,col}; - wire [7:0] ram_data = ram_writeenable ? ram_write : 8'bz; - wire [7:0] ram_read = ram_writeenable ? 8'bz : ram_data; reg [7:0] ram_write; + reg [7:0] ram_read; + reg [7:0] ram_write; + reg [7:0] rand; - RAM_1KB_tri ram( - .clk(clk), - .data(ram_data), + reg clk2; + + always @(posedge clk) begin + clk2 <= !clk2; + end + + RAM_sync ram( + .clk(clk2), + .din(ram_write), + .dout(ram_read), .addr(ram_addr), .we(ram_writeenable) ); - hvsync_generator hvsync_gen( - .clk(clk), + LFSR lfsr( + .clk(clk2), + .reset(reset), + .enable(!reset), + .lfsr(rand) + ); + + hvsync_generator #(256,60,40,25) hvsync_gen( + .clk(clk2), .reset(reset), .hsync(hsync), .vsync(vsync), @@ -51,22 +53,22 @@ module test_ram2_top( wire [4:0] row = vpos[7:3]; wire [4:0] col = hpos[7:3]; wire [3:0] digit = ram_read[3:0]; + wire [2:0] xofs = hpos[2:0]; wire [2:0] yofs = vpos[2:0]; - wire [4:0] bits; + wire [7:0] bits; // TODO? - digits10_array numbers( + digits10_case numbers( .digit(digit), .yofs(yofs), .bits(bits) ); - wire r = display_on && 0; - wire g = display_on && bits[hpos[2:0] ^ 3'b111]; - wire b = display_on && 0; - assign rgb = {b,g,r}; - - always @(posedge clk) - if (display_on && vpos[2:0] == 7) + wire g = display_on && bits[xofs ^ 3'b111]; + + assign out = (hsync||vsync) ? 0 : (1+g+g); + + always @(posedge clk2) + if (display_on && vpos[2:0] == 7 && rand[0]) case (hpos[2:0]) 6: begin ram_write <= ram_read + 1; diff --git a/fpga/examples/cp437.hex b/fpga/examples/cp437.hex new file mode 100644 index 00000000..5353e78e --- /dev/null +++ b/fpga/examples/cp437.hex @@ -0,0 +1,2048 @@ +00 +00 +00 +00 +00 +00 +00 +00 //0 +7e +81 +a5 +81 +bd +99 +81 +7e //1 +7e +ff +db +ff +c3 +e7 +ff +7e //2 +6c +fe +fe +fe +7c +38 +10 +00 //3 +10 +38 +7c +fe +7c +38 +10 +00 //4 +38 +7c +38 +fe +fe +d6 +10 +38 //5 +10 +10 +38 +7c +fe +7c +10 +38 //6 +00 +00 +18 +3c +3c +18 +00 +00 //7 +ff +ff +e7 +c3 +c3 +e7 +ff +ff //8 +00 +3c +66 +42 +42 +66 +3c +00 //9 +ff +c3 +99 +bd +bd +99 +c3 +ff //10 +0f +07 +0f +7d +cc +cc +cc +78 //11 +3c +66 +66 +66 +3c +18 +7e +18 //12 +3f +33 +3f +30 +30 +70 +f0 +e0 //13 +7f +63 +7f +63 +63 +67 +e6 +c0 //14 +99 +5a +3c +e7 +e7 +3c +5a +99 //15 +80 +e0 +f8 +fe +f8 +e0 +80 +00 //16 +02 +0e +3e +fe +3e +0e +02 +00 //17 +18 +3c +7e +18 +18 +7e +3c +18 //18 +66 +66 +66 +66 +66 +00 +66 +00 //19 +7f +db +db +7b +1b +1b +1b +00 //20 +7e +c3 +78 +cc +cc +78 +8c +f8 //21 +00 +00 +00 +00 +7e +7e +7e +00 //22 +18 +3c +7e +18 +7e +3c +18 +ff //23 +18 +3c +7e +18 +18 +18 +18 +00 //24 +18 +18 +18 +18 +7e +3c +18 +00 //25 +00 +18 +0c +fe +0c +18 +00 +00 //26 +00 +30 +60 +fe +60 +30 +00 +00 //27 +00 +00 +c0 +c0 +c0 +fe +00 +00 //28 +00 +24 +66 +ff +66 +24 +00 +00 //29 +00 +18 +3c +7e +ff +ff +00 +00 //30 +00 +ff +ff +7e +3c +18 +00 +00 //31 +00 +00 +00 +00 +00 +00 +00 +00 //32 +30 +78 +78 +30 +30 +00 +30 +00 //33 +6c +6c +6c +00 +00 +00 +00 +00 //34 +6c +6c +fe +6c +fe +6c +6c +00 //35 +30 +7c +c0 +78 +0c +f8 +30 +00 //36 +00 +c6 +cc +18 +30 +66 +c6 +00 //37 +38 +6c +38 +76 +dc +cc +76 +00 //38 +60 +60 +c0 +00 +00 +00 +00 +00 //39 +18 +30 +60 +60 +60 +30 +18 +00 //40 +60 +30 +18 +18 +18 +30 +60 +00 //41 +00 +66 +3c +ff +3c +66 +00 +00 //42 +00 +30 +30 +fc +30 +30 +00 +00 //43 +00 +00 +00 +00 +00 +70 +30 +60 //44 +00 +00 +00 +fc +00 +00 +00 +00 //45 +00 +00 +00 +00 +00 +30 +30 +00 //46 +06 +0c +18 +30 +60 +c0 +80 +00 //47 +78 +cc +dc +fc +ec +cc +78 +00 //48 +30 +f0 +30 +30 +30 +30 +fc +00 //49 +78 +cc +0c +38 +60 +cc +fc +00 //50 +78 +cc +0c +38 +0c +cc +78 +00 //51 +1c +3c +6c +cc +fe +0c +0c +00 //52 +fc +c0 +f8 +0c +0c +cc +78 +00 //53 +38 +60 +c0 +f8 +cc +cc +78 +00 //54 +fc +cc +0c +18 +30 +60 +60 +00 //55 +78 +cc +cc +78 +cc +cc +78 +00 //56 +78 +cc +cc +7c +0c +18 +70 +00 //57 +00 +00 +30 +30 +00 +30 +30 +00 //58 +00 +00 +30 +30 +00 +70 +30 +60 //59 +18 +30 +60 +c0 +60 +30 +18 +00 //60 +00 +00 +fc +00 +fc +00 +00 +00 //61 +60 +30 +18 +0c +18 +30 +60 +00 //62 +78 +cc +0c +18 +30 +00 +30 +00 //63 +7c +c6 +de +de +de +c0 +78 +00 //64 +30 +78 +cc +cc +fc +cc +cc +00 //65 +fc +66 +66 +7c +66 +66 +fc +00 //66 +3c +66 +c0 +c0 +c0 +66 +3c +00 //67 +fc +6c +66 +66 +66 +6c +fc +00 //68 +fe +62 +68 +78 +68 +62 +fe +00 //69 +fe +62 +68 +78 +68 +60 +f0 +00 //70 +3c +66 +c0 +c0 +ce +66 +3e +00 //71 +cc +cc +cc +fc +cc +cc +cc +00 //72 +78 +30 +30 +30 +30 +30 +78 +00 //73 +1e +0c +0c +0c +cc +cc +78 +00 //74 +e6 +66 +6c +78 +6c +66 +e6 +00 //75 +f0 +60 +60 +60 +62 +66 +fe +00 //76 +c6 +ee +fe +d6 +c6 +c6 +c6 +00 //77 +c6 +e6 +f6 +de +ce +c6 +c6 +00 //78 +38 +6c +c6 +c6 +c6 +6c +38 +00 //79 +fc +66 +66 +7c +60 +60 +f0 +00 //80 +78 +cc +cc +cc +dc +78 +1c +00 //81 +fc +66 +66 +7c +78 +6c +e6 +00 //82 +78 +cc +e0 +38 +1c +cc +78 +00 //83 +fc +b4 +30 +30 +30 +30 +78 +00 //84 +cc +cc +cc +cc +cc +cc +fc +00 //85 +cc +cc +cc +cc +cc +78 +30 +00 //86 +c6 +c6 +c6 +d6 +fe +ee +c6 +00 //87 +c6 +c6 +6c +38 +6c +c6 +c6 +00 //88 +cc +cc +cc +78 +30 +30 +78 +00 //89 +fe +cc +98 +30 +62 +c6 +fe +00 //90 +78 +60 +60 +60 +60 +60 +78 +00 //91 +c0 +60 +30 +18 +0c +06 +02 +00 //92 +78 +18 +18 +18 +18 +18 +78 +00 //93 +10 +38 +6c +c6 +00 +00 +00 +00 //94 +00 +00 +00 +00 +00 +00 +00 +ff //95 +30 +30 +18 +00 +00 +00 +00 +00 //96 +00 +00 +78 +0c +7c +cc +76 +00 //97 +e0 +60 +7c +66 +66 +66 +bc +00 //98 +00 +00 +78 +cc +c0 +cc +78 +00 //99 +1c +0c +0c +7c +cc +cc +76 +00 //100 +00 +00 +78 +cc +fc +c0 +78 +00 //101 +38 +6c +60 +f0 +60 +60 +f0 +00 //102 +00 +00 +76 +cc +cc +7c +0c +f8 //103 +e0 +60 +6c +76 +66 +66 +e6 +00 //104 +30 +00 +70 +30 +30 +30 +78 +00 //105 +18 +00 +78 +18 +18 +18 +d8 +70 //106 +e0 +60 +66 +6c +78 +6c +e6 +00 //107 +70 +30 +30 +30 +30 +30 +78 +00 //108 +00 +00 +ec +fe +d6 +c6 +c6 +00 //109 +00 +00 +f8 +cc +cc +cc +cc +00 //110 +00 +00 +78 +cc +cc +cc +78 +00 //111 +00 +00 +dc +66 +66 +7c +60 +f0 //112 +00 +00 +76 +cc +cc +7c +0c +1e //113 +00 +00 +d8 +6c +6c +60 +f0 +00 //114 +00 +00 +7c +c0 +78 +0c +f8 +00 //115 +10 +30 +7c +30 +30 +34 +18 +00 //116 +00 +00 +cc +cc +cc +cc +76 +00 //117 +00 +00 +cc +cc +cc +78 +30 +00 //118 +00 +00 +c6 +c6 +d6 +fe +6c +00 //119 +00 +00 +c6 +6c +38 +6c +c6 +00 //120 +00 +00 +cc +cc +cc +7c +0c +f8 //121 +00 +00 +fc +98 +30 +64 +fc +00 //122 +1c +30 +30 +e0 +30 +30 +1c +00 //123 +18 +18 +18 +00 +18 +18 +18 +00 //124 +e0 +30 +30 +1c +30 +30 +e0 +00 //125 +76 +dc +00 +00 +00 +00 +00 +00 //126 +10 +38 +6c +c6 +c6 +c6 +fe +00 //127 +78 +cc +c0 +cc +78 +18 +0c +78 //128 +00 +cc +00 +cc +cc +cc +7e +00 //129 +1c +00 +78 +cc +fc +c0 +78 +00 //130 +7e +c3 +3c +06 +3e +66 +3f +00 //131 +cc +00 +78 +0c +7c +cc +7e +00 //132 +e0 +00 +78 +0c +7c +cc +7e +00 //133 +30 +30 +78 +0c +7c +cc +7e +00 //134 +00 +00 +7c +c0 +c0 +7c +06 +3c //135 +7e +c3 +3c +66 +7e +60 +3c +00 //136 +cc +00 +78 +cc +fc +c0 +78 +00 //137 +e0 +00 +78 +cc +fc +c0 +78 +00 //138 +cc +00 +70 +30 +30 +30 +78 +00 //139 +7c +c6 +38 +18 +18 +18 +3c +00 //140 +e0 +00 +70 +30 +30 +30 +78 +00 //141 +cc +30 +78 +cc +cc +fc +cc +00 //142 +30 +30 +00 +78 +cc +fc +cc +00 //143 +1c +00 +fc +60 +78 +60 +fc +00 //144 +00 +00 +7f +0c +7f +cc +7f +00 //145 +3e +6c +cc +fe +cc +cc +ce +00 //146 +78 +cc +00 +78 +cc +cc +78 +00 //147 +00 +cc +00 +78 +cc +cc +78 +00 //148 +00 +e0 +00 +78 +cc +cc +78 +00 //149 +78 +cc +00 +cc +cc +cc +7e +00 //150 +00 +e0 +00 +cc +cc +cc +7e +00 //151 +00 +cc +00 +cc +cc +fc +0c +f8 //152 +c6 +38 +7c +c6 +c6 +7c +38 +00 //153 +cc +00 +cc +cc +cc +cc +78 +00 //154 +18 +18 +7e +c0 +c0 +7e +18 +18 //155 +38 +6c +64 +f0 +60 +e6 +fc +00 //156 +cc +cc +78 +fc +30 +fc +30 +00 //157 +f0 +d8 +d8 +f4 +cc +de +cc +0e //158 +0e +1b +18 +7e +18 +18 +d8 +70 //159 +1c +00 +78 +0c +7c +cc +7e +00 //160 +38 +00 +70 +30 +30 +30 +78 +00 //161 +00 +1c +00 +78 +cc +cc +78 +00 //162 +00 +1c +00 +cc +cc +cc +7e +00 //163 +00 +f8 +00 +f8 +cc +cc +cc +00 //164 +fc +00 +cc +ec +fc +dc +cc +00 //165 +3c +6c +6c +3e +00 +7e +00 +00 //166 +3c +66 +66 +3c +00 +7e +00 +00 //167 +30 +00 +30 +60 +c0 +cc +78 +00 //168 +00 +00 +00 +fc +c0 +c0 +00 +00 //169 +00 +00 +00 +fc +0c +0c +00 +00 //170 +c6 +cc +d8 +3e +63 +ce +98 +1f //171 +c6 +cc +d8 +f3 +67 +cf +9f +03 //172 +00 +18 +00 +18 +18 +3c +3c +18 //173 +00 +33 +66 +cc +66 +33 +00 +00 //174 +00 +cc +66 +33 +66 +cc +00 +00 //175 +22 +88 +22 +88 +22 +88 +22 +88 //176 +55 +aa +55 +aa +55 +aa +55 +aa //177 +dd +77 +dd +77 +dd +77 +dd +77 //178 +18 +18 +18 +18 +18 +18 +18 +18 //179 +18 +18 +18 +18 +f8 +18 +18 +18 //180 +18 +18 +f8 +18 +f8 +18 +18 +18 //181 +36 +36 +36 +36 +f6 +36 +36 +36 //182 +00 +00 +00 +00 +fe +36 +36 +36 //183 +00 +00 +f8 +18 +f8 +18 +18 +18 //184 +36 +36 +f6 +06 +f6 +36 +36 +36 //185 +36 +36 +36 +36 +36 +36 +36 +36 //186 +00 +00 +fe +06 +f6 +36 +36 +36 //187 +36 +36 +f6 +06 +fe +00 +00 +00 //188 +36 +36 +36 +36 +fe +00 +00 +00 //189 +18 +18 +f8 +18 +f8 +00 +00 +00 //190 +00 +00 +00 +00 +f8 +18 +18 +18 //191 +18 +18 +18 +18 +1f +00 +00 +00 //192 +18 +18 +18 +18 +ff +00 +00 +00 //193 +00 +00 +00 +00 +ff +18 +18 +18 //194 +18 +18 +18 +18 +1f +18 +18 +18 //195 +00 +00 +00 +00 +ff +00 +00 +00 //196 +18 +18 +18 +18 +ff +18 +18 +18 //197 +18 +18 +1f +18 +1f +18 +18 +18 //198 +36 +36 +36 +36 +37 +36 +36 +36 //199 +36 +36 +37 +30 +3f +00 +00 +00 //200 +00 +00 +3f +30 +37 +36 +36 +36 //201 +36 +36 +f7 +00 +ff +00 +00 +00 //202 +00 +00 +ff +00 +f7 +36 +36 +36 //203 +36 +36 +37 +30 +37 +36 +36 +36 //204 +00 +00 +ff +00 +ff +00 +00 +00 //205 +36 +36 +f7 +00 +f7 +36 +36 +36 //206 +18 +18 +ff +00 +ff +00 +00 +00 //207 +36 +36 +36 +36 +ff +00 +00 +00 //208 +00 +00 +ff +00 +ff +18 +18 +18 //209 +00 +00 +00 +00 +ff +36 +36 +36 //210 +36 +36 +36 +36 +3f +00 +00 +00 //211 +18 +18 +1f +18 +1f +00 +00 +00 //212 +00 +00 +1f +18 +1f +18 +18 +18 //213 +00 +00 +00 +00 +3f +36 +36 +36 //214 +36 +36 +36 +36 +f7 +36 +36 +36 //215 +18 +18 +ff +00 +ff +18 +18 +18 //216 +18 +18 +18 +18 +f8 +00 +00 +00 //217 +00 +00 +00 +00 +1f +18 +18 +18 //218 +ff +ff +ff +ff +ff +ff +ff +ff //219 +00 +00 +00 +00 +ff +ff +ff +ff //220 +f0 +f0 +f0 +f0 +f0 +f0 +f0 +f0 //221 +0f +0f +0f +0f +0f +0f +0f +0f //222 +ff +ff +ff +ff +00 +00 +00 +00 //223 +00 +00 +76 +dc +c8 +dc +76 +00 //224 +00 +78 +cc +f8 +cc +f8 +c0 +c0 //225 +00 +fe +c6 +c0 +c0 +c0 +c0 +00 //226 +00 +fe +6c +6c +6c +6c +6c +00 //227 +fe +66 +30 +18 +30 +66 +fe +00 //228 +00 +00 +7e +cc +cc +cc +78 +00 //229 +00 +66 +66 +66 +66 +7c +60 +c0 //230 +00 +76 +dc +18 +18 +18 +18 +00 //231 +fc +30 +78 +cc +cc +78 +30 +fc //232 +38 +6c +c6 +fe +c6 +6c +38 +00 //233 +38 +6c +c6 +c6 +6c +6c +ee +00 //234 +1c +30 +18 +7c +cc +cc +78 +00 //235 +00 +00 +7e +db +db +7e +00 +00 //236 +06 +0c +7e +db +db +7e +60 +c0 //237 +3c +60 +c0 +fc +c0 +60 +3c +00 //238 +78 +cc +cc +cc +cc +cc +cc +00 //239 +00 +fc +00 +fc +00 +fc +00 +00 //240 +30 +30 +fc +30 +30 +00 +fc +00 //241 +60 +30 +18 +30 +60 +00 +fc +00 //242 +18 +30 +60 +30 +18 +00 +fc +00 //243 +0e +1b +1b +18 +18 +18 +18 +18 //244 +18 +18 +18 +18 +18 +d8 +d8 +70 //245 +30 +30 +00 +fc +00 +30 +30 +00 //246 +00 +72 +9c +00 +72 +9c +00 +00 //247 +38 +6c +6c +38 +00 +00 +00 +00 //248 +00 +00 +00 +18 +18 +00 +00 +00 //249 +00 +00 +00 +00 +18 +00 +00 +00 //250 +0f +0c +0c +0c +ec +6c +3c +1c //251 +78 +6c +6c +6c +6c +00 +00 +00 //252 +78 +0c +38 +60 +7c +00 +00 +00 //253 +00 +00 +3c +3c +3c +3c +00 +00 //254 +00 +00 +00 +00 +00 +00 +00 +00 //255 diff --git a/fpga/examples/cpu8.v b/fpga/examples/cpu8.v new file mode 100644 index 00000000..27dd2d2c --- /dev/null +++ b/fpga/examples/cpu8.v @@ -0,0 +1,232 @@ + +`ifndef ALU_H +`define ALU_H + +// ALU operations +`define OP_ZERO 4'h0 +`define OP_LOAD_A 4'h1 +`define OP_INC 4'h2 +`define OP_DEC 4'h3 +`define OP_ASL 4'h4 +`define OP_LSR 4'h5 +`define OP_ROL 4'h6 +`define OP_ROR 4'h7 +`define OP_OR 4'h8 +`define OP_AND 4'h9 +`define OP_XOR 4'ha +`define OP_LOAD_B 4'hb +`define OP_ADD 4'hc +`define OP_SUB 4'hd +`define OP_ADC 4'he +`define OP_SBB 4'hf + +// ALU module +module ALU(A, B, carry, aluop, Y); + + parameter N = 8; // default width = 8 bits + input [N-1:0] A; // A input + input [N-1:0] B; // B input + input carry; // carry input + input [3:0] aluop; // alu operation + output [N:0] Y; // Y output + carry + + always @(*) + case (aluop) + // unary operations + `OP_ZERO: Y = 0; + `OP_LOAD_A: Y = {1'b0, A}; + `OP_INC: Y = A + 1; + `OP_DEC: Y = A - 1; + // unary operations that generate and/or use carry + `OP_ASL: Y = {A, 1'b0}; + `OP_LSR: Y = {A[0], 1'b0, A[N-1:1]}; + `OP_ROL: Y = {A, carry}; + `OP_ROR: Y = {A[0], carry, A[N-1:1]}; + // binary operations + `OP_OR: Y = {1'b0, A | B}; + `OP_AND: Y = {1'b0, A & B}; + `OP_XOR: Y = {1'b0, A ^ B}; + `OP_LOAD_B: Y = {1'b0, B}; + // binary operations that generate and/or use carry + `OP_ADD: Y = A + B; + `OP_SUB: Y = A - B; + `OP_ADC: Y = A + B + (carry?1:0); + `OP_SBB: Y = A - B - (carry?1:0); + endcase + +endmodule + +/* +Bits Description + +00ddaaaa A @ B -> dest +01ddaaaa A @ immediate -> dest +11ddaaaa A @ read [B] -> dest +10000001 swap A <-> B +1001nnnn A -> write [nnnn] +1010tttt conditional branch + + dd = destination (00=A, 01=B, 10=IP, 11=none) +aaaa = ALU operation (@ operator) +nnnn = 4-bit constant +tttt = flags test for conditional branch +*/ + +// destinations for COMPUTE instructions +`define DEST_A 2'b00 +`define DEST_B 2'b01 +`define DEST_IP 2'b10 +`define DEST_NOP 2'b11 + +// instruction macros +`define I_COMPUTE(dest,op) { 2'b00, (dest), (op) } +`define I_COMPUTE_IMM(dest,op) { 2'b01, (dest), (op) } +`define I_COMPUTE_READB(dest,op) { 2'b11, (dest), (op) } +`define I_CONST_IMM_A { 2'b01, `DEST_A, `OP_LOAD_B } +`define I_CONST_IMM_B { 2'b01, `DEST_B, `OP_LOAD_B } +`define I_JUMP_IMM { 2'b01, `DEST_IP, `OP_LOAD_B } +`define I_STORE_A(addr) { 4'b1001, (addr) } +`define I_BRANCH_IF(zv,zu,cv,cu) { 4'b1010, (zv), (zu), (cv), (cu) } +`define I_CLEAR_CARRY { 8'b10001000 } +`define I_SWAP_AB { 8'b10000001 } +`define I_RESET { 8'b10111111 } +// convenience macros +`define I_ZERO_A `I_COMPUTE(`DEST_A, `OP_ZERO) +`define I_ZERO_B `I_COMPUTE(`DEST_B, `OP_ZERO) +`define I_BRANCH_IF_CARRY(carry) `I_BRANCH_IF(1'b0, 1'b0, carry, 1'b1) +`define I_BRANCH_IF_ZERO(zero) `I_BRANCH_IF(zero, 1'b1, 1'b0, 1'b0) +`define I_CLEAR_ZERO `I_COMPUTE(`DEST_NOP, `OP_ZERO) + +module CPU(clk, reset, address, data_in, data_out, write); + + input clk; + input reset; + output [7:0] address; + input [7:0] data_in; + output [7:0] data_out; + output write; + + reg [7:0] IP; + reg [7:0] A, B; + reg [8:0] Y; + reg [2:0] state; + + reg carry; + reg zero; + wire [1:0] flags = { zero, carry }; + + reg [7:0] opcode; + wire [3:0] aluop = opcode[3:0]; + wire [1:0] opdest = opcode[5:4]; + wire B_or_data = opcode[6]; + + localparam S_RESET = 0; + localparam S_SELECT = 1; + localparam S_DECODE = 2; + localparam S_COMPUTE = 3; + localparam S_READ_IP = 4; + + ALU alu( + .A(A), + .B(B_or_data ? data_in : B), + .Y(Y), + .aluop(aluop), + .carry(carry)); + + always @(posedge clk) + if (reset) begin + state <= 0; + write <= 0; + end else begin + case (state) + // state 0: reset + S_RESET: begin + IP <= 8'h80; + write <= 0; + state <= S_SELECT; + end + // state 1: select opcode address + S_SELECT: begin + address <= IP; + IP <= IP + 1; + write <= 0; + state <= S_DECODE; + end + // state 2: read/decode opcode + S_DECODE: begin + opcode <= data_in; // (only use opcode next cycle) + casez (data_in) + // ALU A + B -> dest + 8'b00??????: begin + state <= S_COMPUTE; + end + // ALU A + immediate -> dest + 8'b01??????: begin + address <= IP; + IP <= IP + 1; + state <= S_COMPUTE; + end + // ALU A + read [B] -> dest + 8'b11??????: begin + address <= B; + state <= S_COMPUTE; + end + // A -> write [nnnn] + 8'b1001????: begin + address <= {4'b0, data_in[3:0]}; + data_out <= A; + write <= 1; + state <= S_SELECT; + end + // swap A,B + 8'b10000001: begin + A <= B; + B <= A; + state <= S_SELECT; + end + // conditional branch + 8'b1010????: begin + if ( + (data_in[0] && (data_in[1] == carry)) || + (data_in[2] && (data_in[3] == zero))) + begin + address <= IP; + state <= S_READ_IP; + end else begin + state <= S_SELECT; + end + IP <= IP + 1; // skip immediate + end + // fall-through RESET + default: begin + state <= S_RESET; // reset + end + endcase + end + // state 3: compute ALU op and flags + S_COMPUTE: begin + // transfer ALU output to destination + case (opdest) + `DEST_A: A <= Y[7:0]; + `DEST_B: B <= Y[7:0]; + `DEST_IP: IP <= Y[7:0]; + `DEST_NOP: ; + endcase + // set carry for certain operations (4-7,12-15) + if (aluop[2]) carry <= Y[8]; + // set zero flag + zero <= ~|Y[7:0]; + // repeat CPU loop + state <= S_SELECT; + end + // state 4: read new IP from memory (immediate mode) + S_READ_IP: begin + IP <= data_in; + state <= S_SELECT; + end + endcase + end + +endmodule + +`endif diff --git a/fpga/examples/crttest.v b/fpga/examples/crttest.v new file mode 100644 index 00000000..7a328eaf --- /dev/null +++ b/fpga/examples/crttest.v @@ -0,0 +1,52 @@ + +`include "hvsync_generator.v" + +/* +A simple test pattern using the hvsync_generator module. + +12000000/15734/2 +381.33977373840091521545 +381-256-23-7 = 95 +*/ + +module test_hvsync_top(clk, reset, out, led); + + input clk, reset; + output [1:0] out; + wire hsync; + wire vsync; + wire [8:0] hpos; + wire [8:0] vpos; + reg [5:0] frame; + output led; + + reg clk2; + always @(posedge clk) begin + clk2 <= !clk2; + end + + hvsync_generator #(256,60,40,25) hvsync_gen( + .clk(clk2), + .reset(reset), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + wire r = display_on && (((hpos&7)==0) || (((vpos+frame)&7)==0)); + wire g = display_on && vpos[4]; + wire b = display_on && hpos[4]; + + assign out = (hsync||vsync) ? 0 : (1+g+(r|b)); + + always @(posedge vsync) begin + frame <= frame + 1; + end + + assign led = frame[5]; + +endmodule + +// TODO: PWM grey scales diff --git a/fpga/examples/digits10.v b/fpga/examples/digits10.v new file mode 100644 index 00000000..60f2bf1f --- /dev/null +++ b/fpga/examples/digits10.v @@ -0,0 +1,207 @@ + +`ifndef DIGITS10_H +`define DIGITS10_H + +`include "hvsync_generator.v" + +/* +ROM module with 5x5 bitmaps for the digits 0-9. + +digits10_case - Uses the case statement. +digits10_array - Uses an array and initial block. + +These two modules are functionally equivalent. +*/ + +// module for 10-digit bitmap ROM +module digits10_case(digit, yofs, bits); + + input [3:0] digit; // digit 0-9 + input [2:0] yofs; // vertical offset (0-4) + output reg [4:0] bits; // output (5 bits) + + // combine {digit,yofs} into single ROM address + wire [6:0] caseexpr = {digit,yofs}; + + always @(*) + case (caseexpr)/*{w:5,h:5,count:10}*/ + 7'o00: bits = 5'b11111; + 7'o01: bits = 5'b10001; + 7'o02: bits = 5'b10001; + 7'o03: bits = 5'b10001; + 7'o04: bits = 5'b11111; + + 7'o10: bits = 5'b01100; + 7'o11: bits = 5'b00100; + 7'o12: bits = 5'b00100; + 7'o13: bits = 5'b00100; + 7'o14: bits = 5'b11111; + + 7'o20: bits = 5'b11111; + 7'o21: bits = 5'b00001; + 7'o22: bits = 5'b11111; + 7'o23: bits = 5'b10000; + 7'o24: bits = 5'b11111; + + 7'o30: bits = 5'b11111; + 7'o31: bits = 5'b00001; + 7'o32: bits = 5'b11111; + 7'o33: bits = 5'b00001; + 7'o34: bits = 5'b11111; + + 7'o40: bits = 5'b10001; + 7'o41: bits = 5'b10001; + 7'o42: bits = 5'b11111; + 7'o43: bits = 5'b00001; + 7'o44: bits = 5'b00001; + + 7'o50: bits = 5'b11111; + 7'o51: bits = 5'b10000; + 7'o52: bits = 5'b11111; + 7'o53: bits = 5'b00001; + 7'o54: bits = 5'b11111; + + 7'o60: bits = 5'b11111; + 7'o61: bits = 5'b10000; + 7'o62: bits = 5'b11111; + 7'o63: bits = 5'b10001; + 7'o64: bits = 5'b11111; + + 7'o70: bits = 5'b11111; + 7'o71: bits = 5'b00001; + 7'o72: bits = 5'b00001; + 7'o73: bits = 5'b00001; + 7'o74: bits = 5'b00001; + + 7'o100: bits = 5'b11111; + 7'o101: bits = 5'b10001; + 7'o102: bits = 5'b11111; + 7'o103: bits = 5'b10001; + 7'o104: bits = 5'b11111; + + 7'o110: bits = 5'b11111; + 7'o111: bits = 5'b10001; + 7'o112: bits = 5'b11111; + 7'o113: bits = 5'b00001; + 7'o114: bits = 5'b11111; + + default: bits = 0; + endcase +endmodule + +module digits10_array(digit, yofs, bits); + + input [3:0] digit; // digit 0-9 + input [2:0] yofs; // vertical offset (0-4) + output [4:0] bits; // output (5 bits) + + reg [4:0] bitarray[16][5]; // ROM array (16 x 5 x 5 bits) + + assign bits = bitarray[digit][yofs]; // assign module output + + integer i,j; + + initial begin/*{w:5,h:5,count:10}*/ + bitarray[0][0] = 5'b11111; + bitarray[0][1] = 5'b10001; + bitarray[0][2] = 5'b10001; + bitarray[0][3] = 5'b10001; + bitarray[0][4] = 5'b11111; + + bitarray[1][0] = 5'b01100; + bitarray[1][1] = 5'b00100; + bitarray[1][2] = 5'b00100; + bitarray[1][3] = 5'b00100; + bitarray[1][4] = 5'b11111; + + bitarray[2][0] = 5'b11111; + bitarray[2][1] = 5'b00001; + bitarray[2][2] = 5'b11111; + bitarray[2][3] = 5'b10000; + bitarray[2][4] = 5'b11111; + + bitarray[3][0] = 5'b11111; + bitarray[3][1] = 5'b00001; + bitarray[3][2] = 5'b11111; + bitarray[3][3] = 5'b00001; + bitarray[3][4] = 5'b11111; + + bitarray[4][0] = 5'b10001; + bitarray[4][1] = 5'b10001; + bitarray[4][2] = 5'b11111; + bitarray[4][3] = 5'b00001; + bitarray[4][4] = 5'b00001; + + bitarray[5][0] = 5'b11111; + bitarray[5][1] = 5'b10000; + bitarray[5][2] = 5'b11111; + bitarray[5][3] = 5'b00001; + bitarray[5][4] = 5'b11111; + + bitarray[6][0] = 5'b11111; + bitarray[6][1] = 5'b10000; + bitarray[6][2] = 5'b11111; + bitarray[6][3] = 5'b10001; + bitarray[6][4] = 5'b11111; + + bitarray[7][0] = 5'b11111; + bitarray[7][1] = 5'b00001; + bitarray[7][2] = 5'b00001; + bitarray[7][3] = 5'b00001; + bitarray[7][4] = 5'b00001; + + bitarray[8][0] = 5'b11111; + bitarray[8][1] = 5'b10001; + bitarray[8][2] = 5'b11111; + bitarray[8][3] = 5'b10001; + bitarray[8][4] = 5'b11111; + + bitarray[9][0] = 5'b11111; + bitarray[9][1] = 5'b10001; + bitarray[9][2] = 5'b11111; + bitarray[9][3] = 5'b00001; + bitarray[9][4] = 5'b11111; + + end +endmodule + +// test module +module test_numbers_top(clk, reset, hsync, vsync, rgb); + + input clk, reset; + output hsync, vsync; + output [2:0] rgb; + + wire display_on; + wire [8:0] hpos; + wire [8:0] vpos; + + hvsync_generator hvsync_gen( + .clk(clk), + .reset(reset), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + wire [3:0] digit = hpos[7:4]; + wire [2:0] xofs = hpos[3:1]; + wire [2:0] yofs = vpos[3:1]; + wire [4:0] bits; + + digits10_array numbers( + .digit(digit), + .yofs(yofs), + .bits(bits) + ); + + wire r = display_on && 0; + wire g = display_on && bits[xofs ^ 3'b111]; + wire b = display_on && 0; + assign rgb = {b,g,r}; + +endmodule + +`endif diff --git a/fpga/examples/font_cp437_8x8.v b/fpga/examples/font_cp437_8x8.v new file mode 100644 index 00000000..85abd1b7 --- /dev/null +++ b/fpga/examples/font_cp437_8x8.v @@ -0,0 +1,21 @@ + +`ifndef FONT_CP437_H +`define FONT_CP437_H + +// PC font (code page 437) + +module font_cp437_8x8(addr, data); + + input [10:0] addr; + output [7:0] data; + + reg [7:0] bitarray[0:2047]; + + assign data = bitarray[addr]; + + initial begin/*{w:8,h:8,bpp:1,count:256}*/ + $readmemh("cp437.hex", bitarray); + end +endmodule + +`endif diff --git a/fpga/examples/hvsync_generator.v b/fpga/examples/hvsync_generator.v new file mode 100644 index 00000000..24587616 --- /dev/null +++ b/fpga/examples/hvsync_generator.v @@ -0,0 +1,69 @@ + +`ifndef HVSYNC_GENERATOR_H +`define HVSYNC_GENERATOR_H + +/* +Video sync generator, used to drive a simulated CRT. +To use: +- Wire the hsync and vsync signals to top level outputs +- Add a 3-bit (or more) "rgb" output to the top level +*/ + +module hvsync_generator(clk, reset, hsync, vsync, display_on, hpos, vpos); + + input clk; + input reset; + output reg hsync, vsync; + output display_on; + output reg [8:0] hpos; + output reg [8:0] vpos; + + // declarations for TV-simulator sync parameters + // horizontal constants + parameter H_DISPLAY = 256; // horizontal display width + parameter H_BACK = 23; // horizontal left border (back porch) + parameter H_FRONT = 7; // horizontal right border (front porch) + parameter H_SYNC = 23; // horizontal sync width + // vertical constants + parameter V_DISPLAY = 240; // vertical display height + parameter V_TOP = 5; // vertical top border + parameter V_BOTTOM = 14; // vertical bottom border + parameter V_SYNC = 3; // vertical sync # lines + // derived constants + parameter H_SYNC_START = H_DISPLAY + H_FRONT; + parameter H_SYNC_END = H_DISPLAY + H_FRONT + H_SYNC - 1; + parameter H_MAX = H_DISPLAY + H_BACK + H_FRONT + H_SYNC - 1; + parameter V_SYNC_START = V_DISPLAY + V_BOTTOM; + parameter V_SYNC_END = V_DISPLAY + V_BOTTOM + V_SYNC - 1; + parameter V_MAX = V_DISPLAY + V_TOP + V_BOTTOM + V_SYNC - 1; + + wire hmaxxed = (hpos == H_MAX) || reset; // set when hpos is maximum + wire vmaxxed = (vpos == V_MAX) || reset; // set when vpos is maximum + + // horizontal position counter + always @(posedge clk) + begin + hsync <= (hpos>=H_SYNC_START && hpos<=H_SYNC_END); + if(hmaxxed) + hpos <= 0; + else + hpos <= hpos + 1; + end + + // vertical position counter + always @(posedge clk) + begin + vsync <= (vpos>=V_SYNC_START && vpos<=V_SYNC_END); + if(hmaxxed) + if (vmaxxed) + vpos <= 0; + else + vpos <= vpos + 1; + end + + // display_on is set when beam is in "safe" visible frame + assign display_on = (hpos A +; ldb #IN_FLAGS ; addr of IN_FLAGS -> B +; and none,[B] ; read B, AND with A +; bz DisplayLoop ; loop until paddle flag set + ldb #IN_VPOS + mov A,[B] ; load vertical position -> A + sta PLAYER_X ; store player x position +; wait for vsync + lda #F_VSYNC + ldb #IN_FLAGS +WaitForVsyncOn: + and none,[B] + bz WaitForVsyncOn ; wait until VSYNC on +WaitForVsyncOff: + and none,[B] + bnz WaitForVsyncOff ; wait until VSYNC off +; check collision + lda #F_COLLIDE + ldb #IN_FLAGS + and none,[B] ; collision flag set? + bz NoCollision ; skip ahead if not + lda #16 + sta SPEED ; speed = 16 +NoCollision: +; update speed + ldb #SPEED + mov A,[B] ; speed -> A + inc A ; increment speed + bz MaxSpeed ; speed wraps to 0? + sta SPEED ; no, store speed +MaxSpeed: + mov A,[B] ; reload speed -> A + lsr A + lsr A + lsr A + lsr A ; divide speed by 16 +; add to lo byte of track pos + ldb #TRACKPOS_LO + add B,[B] ; B <- speed/16 + trackpos_lo + swapab ; swap A <-> B + sta TRACKPOS_LO ; A -> trackpos_lo + swapab ; swap A <-> B again + bcc NoCarry ; carry flag from earlier add op +; add to hi byte of track pos + ldb #TRACKPOS_HI + mov B,[B] ; B <- trackpos_hi + inc b ; increment B + swapab ; swap A <-> B + sta TRACKPOS_HI ; A -> trackpos_hi + swapab ; swap A <-> B again +NoCarry: +; update enemy vert pos + ldb #ENEMY_Y + add A,[B] + sta ENEMY_Y ; enemy_y = enemy_y + speed/16 +; update enemy horiz pos + ldb #ENEMY_X + mov A,[B] + ldb #ENEMY_DIR + add A,[B] + sta ENEMY_X ; enemy_x = enemy_x + enemy_dir + sub A,#64 + and A,#127 ; A <- (enemy_x-64) & 127 + bnz SkipXReverse ; skip if enemy_x is in range +; load ENEMY_DIR and negate + zero A + sub A,[B] + sta ENEMY_DIR ; enemy_dir = -enemy_dir +SkipXReverse: +; back to display loop + jmp DisplayLoop diff --git a/fpga/examples/racing.hex b/fpga/examples/racing.hex new file mode 100644 index 00000000..a49f3f7d --- /dev/null +++ b/fpga/examples/racing.hex @@ -0,0 +1,128 @@ +4b +80 +92 +94 +95 +4b +b4 +93 +0 +97 +2 +96 +5b +41 +cb +92 +4b +10 +5b +42 +f9 +ac +94 +f9 +a4 +97 +4b +20 +5b +42 +f9 +ac +a4 +4b +10 +97 +5b +7 +cb +2 +ac +ab +97 +cb +5 +5 +5 +5 +5b +8 +dc +81 +98 +81 +a1 +bf +5b +9 +db +12 +81 +99 +81 +5b +5 +cc +95 +5b +4 +cb +5b +6 +cc +94 +4d +40 +49 +7f +a4 +d3 +0 +cd +96 +6b +8c +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/fpga/examples/racing_game.v b/fpga/examples/racing_game.v new file mode 100644 index 00000000..7bc6fc88 --- /dev/null +++ b/fpga/examples/racing_game.v @@ -0,0 +1,158 @@ + +`include "hvsync_generator.v" +`include "sprite_bitmap.v" +`include "sprite_renderer.v" + +/* +A simple racing game with two sprites and a scrolling playfield. +This version does not use a CPU; all logic is straight Verilog. +*/ + +module racing_game_top(clk, reset, out, hpaddle, vpaddle); + + input clk, reset; + input hpaddle, vpaddle; + output [1:0] out; + wire hsync, vsync; + wire display_on; + wire [8:0] hpos; + wire [8:0] vpos; + + // player car position (set at VSYNC) + reg [7:0] player_x; + reg [7:0] player_y; + // paddle position (set continuously during frame) + reg [7:0] paddle_x; + reg [7:0] paddle_y; + // enemy car position + reg [7:0] enemy_x = 128; + reg [7:0] enemy_y = 128; + // enemy car direction, 1=right, 0=left + reg enemy_dir = 0; + + reg [15:0] track_pos = 0; // player position along track (16 bits) + reg [7:0] speed = 31; // player velocity along track (8 bits) + + reg clk2; + always @(posedge clk) begin + clk2 <= !clk2; + end + + // video sync generator + hvsync_generator #(256,60,40,25) hvsync_gen( + .clk(clk2), + .reset(reset), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + // set paddle registers + always @(posedge hsync) + begin + if (!hpaddle) paddle_x <= vpos[7:0]; + if (!vpaddle) paddle_y <= vpos[7:0]; + end + + + // select player or enemy access to ROM + wire player_load = (hpos >= 256) && (hpos < 260); + wire enemy_load = (hpos >= 260); + // wire up car sprite ROM + // multiplex between player and enemy ROM address + wire [3:0] player_sprite_yofs; + wire [3:0] enemy_sprite_yofs; + wire [3:0] car_sprite_yofs = player_load ? player_sprite_yofs : enemy_sprite_yofs; + wire [7:0] car_sprite_bits; + car_bitmap car( + .yofs(car_sprite_yofs), + .bits(car_sprite_bits)); + + // signals for player sprite generator + wire player_vstart = {1'b0,player_y} == vpos; + wire player_hstart = {1'b0,player_x} == hpos; + wire player_gfx; + wire player_is_drawing; + + // signals for enemy sprite generator + wire enemy_vstart = {1'b0,enemy_y} == vpos; + wire enemy_hstart = {1'b0,enemy_x} == hpos; + wire enemy_gfx; + wire enemy_is_drawing; + + // player sprite generator + sprite_renderer player_renderer( + .clk(clk2), + .reset(reset), + .vstart(player_vstart), + .load(player_load), + .hstart(player_hstart), + .rom_addr(player_sprite_yofs), + .rom_bits(car_sprite_bits), + .gfx(player_gfx), + .in_progress(player_is_drawing)); + + // enemy sprite generator + sprite_renderer enemy_renderer( + .clk(clk2), + .reset(reset), + .vstart(enemy_vstart), + .load(enemy_load), + .hstart(enemy_hstart), + .rom_addr(enemy_sprite_yofs), + .rom_bits(car_sprite_bits), + .gfx(enemy_gfx), + .in_progress(enemy_is_drawing)); + + // signals for enemy bouncing off left/right borders + wire enemy_hit_left = (enemy_x == 64); + wire enemy_hit_right = (enemy_x == 192); + wire enemy_hit_edge = enemy_hit_left || enemy_hit_right; + + // update player, enemy, track counters + // runs once per frame + always @(posedge vsync) + begin + player_x <= 64; //paddle_x; + player_y <= 180; + track_pos <= track_pos + {11'b0,speed[7:4]}; + enemy_y <= enemy_y + {3'b0, speed[7:4]}; + if (enemy_hit_edge) + enemy_dir <= !enemy_dir; + if (enemy_dir ^ enemy_hit_edge) + enemy_x <= enemy_x + 1; + else + enemy_x <= enemy_x - 1; + // collision check? + if (frame_collision) + speed <= 16; + else if (speed < ~paddle_y) + speed <= speed + 1; + else if (speed > 16) + speed <= speed - 1; + end + + // set to 1 when player collides with enemy or track + reg frame_collision; + + always @(posedge clk2) + if (player_gfx && (enemy_gfx || track_gfx)) + frame_collision <= 1; + else if (vsync) + frame_collision <= 0; + + // track graphics signals + wire track_offside = (hpos[7:5]==0) || (hpos[7:5]==7); + wire track_shoulder = (hpos[7:3]==3) || (hpos[7:3]==28); + wire track_gfx = (vpos[5:1]!=track_pos[5:1]) && track_offside; + + // combine signals for RGB output + wire r = display_on && (enemy_gfx || track_shoulder); + wire g = display_on && (player_gfx || track_gfx); + wire b = display_on && (player_gfx || track_shoulder); + + assign out = (hsync||vsync) ? 0 : (1+g+(r|b)); + +endmodule diff --git a/fpga/examples/racing_game_cpu.v b/fpga/examples/racing_game_cpu.v new file mode 100644 index 00000000..a98fb196 --- /dev/null +++ b/fpga/examples/racing_game_cpu.v @@ -0,0 +1,169 @@ + +`include "hvsync_generator.v" +`include "sprite_bitmap.v" +`include "sprite_renderer.v" +`include "cpu8.v" + +/* +A simple racing game with two sprites and a scrolling playfield. +This version uses the 8-bit CPU. +*/ + +// uncomment to see scope view +//`define DEBUG + +module racing_game_top(clk, reset, out, hpaddle, vpaddle); + + input clk, reset; + input hpaddle, vpaddle; + output [1:0] out; + wire hsync, vsync; + wire display_on; + wire [8:0] hpos; + wire [8:0] vpos; + + parameter PADDLE_X = 0; // paddle X coordinate + parameter PADDLE_Y = 1; // paddle Y coordinate + parameter PLAYER_X = 2; // player X coordinate + parameter PLAYER_Y = 3; // player Y coordinate + parameter ENEMY_X = 4; // enemy X coordinate + parameter ENEMY_Y = 5; // enemy Y coordinate + parameter ENEMY_DIR = 6; // enemy direction (1, -1) + parameter SPEED = 7; // player speed + parameter TRACKPOS_LO = 8; // track position (lo byte) + parameter TRACKPOS_HI = 9; // track position (hi byte) + + parameter IN_HPOS = 8'h40; // CRT horizontal position + parameter IN_VPOS = 8'h41; // CRT vertical position + // flags: [0, 0, collision, vsync, hsync, vpaddle, hpaddle, display_on] + parameter IN_FLAGS = 8'h42; + + reg [7:0] ram[0:15]; // 16 bytes of RAM + reg [7:0] rom[0:127]; // 128 bytes of ROM + + wire [7:0] address_bus; // CPU address bus + reg [7:0] to_cpu; // data bus to CPU + wire [7:0] from_cpu; // data bus from CPU + wire write_enable; // write enable bit from CPU + + reg clk2; + always @(posedge clk) begin + clk2 <= !clk2; + end + + // 8-bit CPU module + CPU cpu(.clk(clk2), + .reset(reset), + .address(address_bus), + .data_in(to_cpu), + .data_out(from_cpu), + .write(write_enable)); + + // RAM write from CPU + always @(posedge clk2) + if (write_enable) + ram[address_bus[5:0]] <= from_cpu; + + // RAM read from CPU + always @(*) + casez (address_bus) + // RAM + 8'b00??????: to_cpu = ram[address_bus[5:0]]; + // special read registers + IN_HPOS: to_cpu = hpos[7:0]; + IN_VPOS: to_cpu = vpos[7:0]; + IN_FLAGS: to_cpu = {2'b0, frame_collision, + vsync, hsync, vpaddle, hpaddle, display_on}; + // ROM + 8'b1???????: to_cpu = rom[address_bus[6:0]]; + default: to_cpu = 8'bxxxxxxxx; + endcase + + // video sync generator + hvsync_generator #(256,60,40,25) hvsync_gen( + .clk(clk2), + .reset(reset), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + // flags for player sprite renderer module + wire player_vstart = {1'b0,ram[PLAYER_Y]} == vpos; + wire player_hstart = {1'b0,ram[PLAYER_X]} == hpos; + wire player_gfx; + wire player_is_drawing; + + // flags for enemy sprite renderer module + wire enemy_vstart = {1'b0,ram[ENEMY_Y]} == vpos; + wire enemy_hstart = {1'b0,ram[ENEMY_X]} == hpos; + wire enemy_gfx; + wire enemy_is_drawing; + + // select player or enemy access to ROM + wire player_load = (hpos >= 256) && (hpos < 260); + wire enemy_load = (hpos >= 260); + // wire up car sprite ROM + // multiplex between player and enemy ROM address + wire [3:0] player_sprite_yofs; + wire [3:0] enemy_sprite_yofs; + wire [3:0] car_sprite_yofs = player_load ? player_sprite_yofs : enemy_sprite_yofs; + wire [7:0] car_sprite_bits; + car_bitmap car( + .yofs(car_sprite_yofs), + .bits(car_sprite_bits)); + + // player sprite renderer + sprite_renderer player_renderer( + .clk(clk2), + .reset(reset), + .vstart(player_vstart), + .hstart(player_hstart), + .load(player_load), + .rom_addr(player_sprite_yofs), + .rom_bits(car_sprite_bits), + .gfx(player_gfx), + .in_progress(player_is_drawing)); + + // enemy sprite renderer + sprite_renderer enemy_renderer( + .clk(clk2), + .reset(reset), + .vstart(enemy_vstart), + .hstart(enemy_hstart), + .load(enemy_load), + .rom_addr(enemy_sprite_yofs), + .rom_bits(car_sprite_bits), + .gfx(enemy_gfx), + .in_progress(enemy_is_drawing)); + + // collision detection logic + reg frame_collision; + always @(posedge clk2) + if (player_gfx && (enemy_gfx || track_gfx)) + frame_collision <= 1; + else if (vpos==0) + frame_collision <= 0; + + // track graphics + wire track_offside = (hpos[7:5]==0) || (hpos[7:5]==7); + wire track_shoulder = (hpos[7:3]==3) || (hpos[7:3]==28); + wire track_gfx = (vpos[5:1]!=ram[TRACKPOS_LO][5:1]) && track_offside; + + // RGB output + wire r = display_on && (player_gfx || enemy_gfx || track_shoulder); + wire g = display_on && (player_gfx || track_gfx); + wire b = display_on && (enemy_gfx || track_shoulder); + + assign out = (hsync||vsync) ? 0 : (1+g+(r|b)); + + //////////// CPU program code + + initial begin + $readmemh("racing.hex", rom); + end + +endmodule + diff --git a/fpga/examples/ram.v b/fpga/examples/ram.v new file mode 100644 index 00000000..182fca54 --- /dev/null +++ b/fpga/examples/ram.v @@ -0,0 +1,80 @@ + +`ifndef RAM_H +`define RAM_H + +/* +RAM_sync - Synchronous RAM module. +RAM_async - Asynchronous RAM module. +RAM_async_tristate - Async RAM module with bidirectional data bus. + +Module parameters: + +A - number of address bits (default = 10) +D - number of data bits (default = 8) +*/ + +module RAM_sync(clk, addr, din, dout, we); + + parameter A = 10; // # of address bits + parameter D = 8; // # of data bits + + input clk; // clock + input [A-1:0] addr; // address + input [D-1:0] din; // data input + output [D-1:0] dout; // data output + input we; // write enable + + reg [D-1:0] mem [0:(1<>1][15:8] : bitarray[addr>>1][7:0]; + + initial begin/*{w:16,h:16,bpw:16,count:5}*/ + bitarray['h00] = 16'b11110000000; + bitarray['h01] = 16'b11110000000; + bitarray['h02] = 16'b1100000000; + bitarray['h03] = 16'b1100000000; + bitarray['h04] = 16'b111101101111000; + bitarray['h05] = 16'b111101101111000; + bitarray['h06] = 16'b111111111111000; + bitarray['h07] = 16'b111111111111000; + bitarray['h08] = 16'b111111111111000; + bitarray['h09] = 16'b111111111111000; + bitarray['h0a] = 16'b111111111111000; + bitarray['h0b] = 16'b111100001111000; + bitarray['h0c] = 16'b111100001111000; + bitarray['h0d] = 16'b0; + bitarray['h0e] = 16'b0; + bitarray['h0f] = 16'b0; + + bitarray['h10] = 16'b111000000000; + bitarray['h11] = 16'b1111000000000; + bitarray['h12] = 16'b1111000000000; + bitarray['h13] = 16'b11000000000; + bitarray['h14] = 16'b11101110000; + bitarray['h15] = 16'b1101110000; + bitarray['h16] = 16'b111101111110000; + bitarray['h17] = 16'b111101111111000; + bitarray['h18] = 16'b111111111111000; + bitarray['h19] = 16'b11111111111000; + bitarray['h1a] = 16'b11111111111100; + bitarray['h1b] = 16'b11111111111100; + bitarray['h1c] = 16'b11111001111100; + bitarray['h1d] = 16'b1111001110000; + bitarray['h1e] = 16'b1111000000000; + bitarray['h1f] = 16'b1100000000000; + + bitarray['h20] = 16'b0; + bitarray['h21] = 16'b0; + bitarray['h22] = 16'b11000011000000; + bitarray['h23] = 16'b111000111100000; + bitarray['h24] = 16'b111101111110000; + bitarray['h25] = 16'b1110111111000; + bitarray['h26] = 16'b111111111100; + bitarray['h27] = 16'b11111111110; + bitarray['h28] = 16'b11011111111110; + bitarray['h29] = 16'b111111111111100; + bitarray['h2a] = 16'b111111111001000; + bitarray['h2b] = 16'b11111110000000; + bitarray['h2c] = 16'b1111100000000; + bitarray['h2d] = 16'b111110000000; + bitarray['h2e] = 16'b11110000000; + bitarray['h2f] = 16'b1100000000; + + bitarray['h30] = 16'b0; + bitarray['h31] = 16'b0; + bitarray['h32] = 16'b110000000; + bitarray['h33] = 16'b100001111000000; + bitarray['h34] = 16'b1110001111110000; + bitarray['h35] = 16'b1111010111111100; + bitarray['h36] = 16'b1111111111111111; + bitarray['h37] = 16'b1111111111111; + bitarray['h38] = 16'b11111111110; + bitarray['h39] = 16'b101111111110; + bitarray['h3a] = 16'b1111111101100; + bitarray['h3b] = 16'b11111111000000; + bitarray['h3c] = 16'b1111111100000; + bitarray['h3d] = 16'b11111110000; + bitarray['h3e] = 16'b111100000; + bitarray['h3f] = 16'b1100000; + + bitarray['h40] = 16'b0; + bitarray['h41] = 16'b0; + bitarray['h42] = 16'b0; + bitarray['h43] = 16'b111111111000; + bitarray['h44] = 16'b111111111000; + bitarray['h45] = 16'b111111111000; + bitarray['h46] = 16'b111111111000; + bitarray['h47] = 16'b1100001111100000; + bitarray['h48] = 16'b1111111111100000; + bitarray['h49] = 16'b1111111111100000; + bitarray['h4a] = 16'b1100001111100000; + bitarray['h4b] = 16'b111111111000; + bitarray['h4c] = 16'b111111111000; + bitarray['h4d] = 16'b111111111000; + bitarray['h4e] = 16'b111111111000; + bitarray['h4f] = 16'b0; + end +endmodule + +// 16x16 sprite renderer that supports rotation +module sprite_renderer2(clk, vstart, load, hstart, rom_addr, rom_bits, + hmirror, vmirror, + gfx, busy); + + input clk, vstart, load, hstart; + input hmirror, vmirror; + output [4:0] rom_addr; + input [7:0] rom_bits; + output gfx; + output busy; + + assign busy = state != WAIT_FOR_VSTART; + + reg [2:0] state; + reg [3:0] ycount; + reg [3:0] xcount; + + reg [15:0] outbits; + + localparam WAIT_FOR_VSTART = 0; + localparam WAIT_FOR_LOAD = 1; + localparam LOAD1_SETUP = 2; + localparam LOAD1_FETCH = 3; + localparam LOAD2_SETUP = 4; + localparam LOAD2_FETCH = 5; + localparam WAIT_FOR_HSTART = 6; + localparam DRAW = 7; + + always @(posedge clk) + begin + case (state) + WAIT_FOR_VSTART: begin + ycount <= 0; + // set a default value (blank) for pixel output + // note: multiple non-blocking assignments are vendor-specific + gfx <= 0; + if (vstart) state <= WAIT_FOR_LOAD; + end + WAIT_FOR_LOAD: begin + xcount <= 0; + gfx <= 0; + if (load) state <= LOAD1_SETUP; + end + LOAD1_SETUP: begin + rom_addr <= {vmirror?~ycount:ycount, 1'b0}; + state <= LOAD1_FETCH; + end + LOAD1_FETCH: begin + outbits[7:0] <= rom_bits; + state <= LOAD2_SETUP; + end + LOAD2_SETUP: begin + rom_addr <= {vmirror?~ycount:ycount, 1'b1}; + state <= LOAD2_FETCH; + end + LOAD2_FETCH: begin + outbits[15:8] <= rom_bits; + state <= WAIT_FOR_HSTART; + end + WAIT_FOR_HSTART: begin + if (hstart) state <= DRAW; + end + DRAW: begin + // mirror graphics left/right + gfx <= outbits[hmirror ? ~xcount[3:0] : xcount[3:0]]; + xcount <= xcount + 1; + if (xcount == 15) begin // pre-increment value + ycount <= ycount + 1; + if (ycount == 15) // pre-increment value + state <= WAIT_FOR_VSTART; // done drawing sprite + else + state <= WAIT_FOR_LOAD; // done drawing this scanline + end + end + endcase + end + +endmodule + +// converts 0..15 rotation value to bitmap index / mirror bits +module rotation_selector(rotation, bitmap_num, hmirror, vmirror); + + input [3:0] rotation; // angle (0..15) + output [2:0] bitmap_num; // bitmap index (0..4) + output hmirror, vmirror; // horiz & vert mirror bits + + always @(*) + case (rotation[3:2]) // 4 quadrants + 0: begin // 0..3 -> 0..3 + bitmap_num = {1'b0, rotation[1:0]}; + hmirror = 0; + vmirror = 0; + end + 1: begin // 4..7 -> 4..1 + bitmap_num = -rotation[2:0]; + hmirror = 0; + vmirror = 1; + end + 2: begin // 8-11 -> 0..3 + bitmap_num = {1'b0, rotation[1:0]}; + hmirror = 1; + vmirror = 1; + end + 3: begin // 12-15 -> 4..1 + bitmap_num = -rotation[2:0]; + hmirror = 1; + vmirror = 0; + end + endcase + +endmodule + +// tank controller module -- handles rendering and movement +module tank_controller(clk, reset, hpos, vpos, hsync, vsync, + sprite_addr, sprite_bits, gfx, + playfield, + switch_left, switch_right, switch_up); + + input clk; + input reset; + input hsync; + input vsync; + input [8:0] hpos; + input [8:0] vpos; + output [7:0] sprite_addr; + input [7:0] sprite_bits; + output gfx; + input playfield; + input switch_left, switch_right, switch_up; + + parameter initial_x = 128; + parameter initial_y = 120; + parameter initial_rot = 0; + + wire hmirror, vmirror; + wire busy; + wire collision_gfx = gfx && playfield; + + reg [11:0] player_x_fixed; + wire [7:0] player_x = player_x_fixed[11:4]; + wire [3:0] player_x_frac = player_x_fixed[3:0]; + + reg [11:0] player_y_fixed; + wire [7:0] player_y = player_y_fixed[11:4]; + wire [3:0] player_y_frac = player_y_fixed[3:0]; + + reg [3:0] player_rot; + reg [3:0] player_speed; + reg [3:0] frame = 0; + + wire vstart = {1'b0,player_y} == vpos; + wire hstart = {1'b0,player_x} == hpos; + + sprite_renderer2 renderer( + .clk(clk), + .vstart(vstart), + .load(hsync), + .hstart(hstart), + .hmirror(hmirror), + .vmirror(vmirror), + .rom_addr(sprite_addr[4:0]), + .rom_bits(sprite_bits), + .gfx(gfx), + .busy(busy)); + + rotation_selector rotsel( + .rotation(player_rot), + .bitmap_num(sprite_addr[7:5]), + .hmirror(hmirror), + .vmirror(vmirror)); + + always @(posedge vsync or posedge reset) + begin + if (reset) begin + player_rot <= initial_rot; + player_speed <= 0; + end else begin + frame <= frame + 1; // increment frame counter + if (frame[0]) begin // only update every other frame + if (switch_left) + player_rot <= player_rot - 1; // turn left + else if (switch_right) + player_rot <= player_rot + 1; // turn right + if (switch_up) begin + if (player_speed != 15) // max accel + player_speed <= player_speed + 1; + end else + player_speed <= 0; // stop + end + end + end + + // set if collision; cleared at vsync + reg collision_detected; + + always @(posedge clk) + if (vstart) + collision_detected <= 0; + else if (collision_gfx) + collision_detected <= 1; + + // sine lookup (4 bits input, 4 signed bits output) + function signed [3:0] sin_16x4; + input [3:0] in; // input angle 0..15 + integer y; + case (in[1:0]) // 4 values per quadrant + 0: y = 0; + 1: y = 3; + 2: y = 5; + 3: y = 6; + endcase + case (in[3:2]) // 4 quadrants + 0: sin_16x4 = 4'(y); + 1: sin_16x4 = 4'(7-y); + 2: sin_16x4 = 4'(-y); + 3: sin_16x4 = 4'(y-7); + endcase + endfunction + + always @(posedge hsync or posedge reset) + if (reset) begin + // set initial position + player_x_fixed <= initial_x << 4; + player_y_fixed <= initial_y << 4; + end else begin + // collision detected? move backwards + if (collision_detected && vpos[3:1] == 0) begin + if (vpos[0]) + player_x_fixed <= player_x_fixed + 12'(sin_16x4(player_rot+8)); + else + player_y_fixed <= player_y_fixed - 12'(sin_16x4(player_rot+12)); + end else + // forward movement + if (vpos < 9'(player_speed)) begin + if (vpos[0]) + player_x_fixed <= player_x_fixed + 12'(sin_16x4(player_rot)); + else + player_y_fixed <= player_y_fixed - 12'(sin_16x4(player_rot+4)); + end + end + +endmodule + diff --git a/fpga/examples/sprite_scanline_renderer.v b/fpga/examples/sprite_scanline_renderer.v new file mode 100644 index 00000000..039f70b0 --- /dev/null +++ b/fpga/examples/sprite_scanline_renderer.v @@ -0,0 +1,183 @@ + +`include "hvsync_generator.v" +`include "ram.v" + +/* +sprite_scanline_renderer - Module that renders multiple + sprites whose attributes are fetched from shared RAM, + and whose bitmaps are stored in ROM. Made to be paired + with the FEMTO-16 CPU. +*/ + +module example_bitmap_rom(addr, data); + + input [15:0] addr; + output [15:0] data; + + reg [15:0] bitarray[0:255]; + + assign data = bitarray[addr & 15]; + + initial begin/*{w:16,h:16,bpw:16,count:1}*/ + bitarray['h00] = 16'b11110000000; + bitarray['h01] = 16'b100001000000; + bitarray['h02] = 16'b1111111100000; + bitarray['h03] = 16'b1111111100000; + bitarray['h04] = 16'b11110000000; + bitarray['h05] = 16'b11111111110000; + bitarray['h06] = 16'b111100001111000; + bitarray['h07] = 16'b1111101101111100; + bitarray['h08] = 16'b1101100001101111; + bitarray['h09] = 16'b1101111111100110; + bitarray['h0a] = 16'b1001111111100000; + bitarray['h0b] = 16'b1111111100000; + bitarray['h0c] = 16'b1110011100000; + bitarray['h0d] = 16'b1100001100000; + bitarray['h0e] = 16'b1100001100000; + bitarray['h0f] = 16'b11100001110000; + end + +endmodule + +module sprite_scanline_renderer(clk, reset, hpos, vpos, rgb, + ram_addr, ram_data, ram_busy, + rom_addr, rom_data); + + parameter NB = 5; // 2^NB == number of sprites + parameter MB = 3; // 2^MB == slots per scanline + + localparam N = 1<= 256 + if (reset || vpos[8]) begin + // load sprites from RAM on line 260 + // 8 cycles per sprite + // do first sprite twice b/c CPU might still be busy + if (vpos == 260 && hpos < N*2+8) begin + ram_busy <= 1; + case (hpos[0]) + 0: begin + ram_addr <= {load_index, 1'b0}; + // load X and Y position (2 cycles ago) + sprite_xpos[load_index] <= ram_data[7:0]; + sprite_ypos[load_index] <= ram_data[15:8]; + end + 1: begin + ram_addr <= {load_index, 1'b1}; + // load attribute (2 cycles ago) + sprite_attr[load_index] <= ram_data[7:0]; + end + endcase + end + end else if (hpos < N*2) begin + // setup vars for next phase + k <= 0; + romload <= 0; + // select the sprites that will appear in this scanline + case (hpos[0]) + // compute Y offset of sprite relative to scanline + 0: z <= 8'(vpos - sprite_ypos[i]); + 1: begin + // sprite is active if Y offset is 0..15 + if (z < 16) begin + line_xpos[j] <= sprite_xpos[i]; // save X pos + line_yofs[j] <= z; // save Y offset + line_attr[j] <= sprite_attr[i]; // save attr + line_active[j] <= 1; // mark sprite active + j <= j + 1; // inc counter + end + i <= i + 1; // inc main array counter + end + endcase + end else if (hpos < N*2+M*18) begin + // setup vars for next phase + j <= 0; + // if sprite shift register is empty, load new sprite + if (out_bitmap == 0) begin + case (romload) + 0: begin + // set ROM address and fetch bitmap + rom_addr <= {4'b0, line_attr[k][7:4], line_yofs[k]}; + end + 1: begin + // load scanline buffer offset to write + write_ofs <= {~vpos[0], line_xpos[k]}; + // fetch 0 if sprite is inactive + out_bitmap <= line_active[k] ? rom_data : 0; + // load attribute for sprite + out_attr <= line_attr[k]; + // disable sprite for next scanline + line_active[k] <= 0; + // go to next sprite in 2ndary buffer + k <= k + 1; + end + endcase + romload <= !romload; + end else begin + // write color to scanline buffer if low bit == 1 + if (out_bitmap[0]) + scanline[write_ofs] <= out_attr[3:0]; + // shift bits right + out_bitmap <= out_bitmap >> 1; + // increment to next scanline pixel + write_ofs <= write_ofs + 1; + end + end else begin + // clear counters + i <= 0; + end + + // read and clear buffer + rgb <= scanline[read_bufidx]; + scanline[read_bufidx] <= 0; + end + +endmodule + diff --git a/fpga/examples/spritetest.v b/fpga/examples/spritetest.v new file mode 100644 index 00000000..04d70592 --- /dev/null +++ b/fpga/examples/spritetest.v @@ -0,0 +1,77 @@ + +`include "hvsync_generator.v" +`include "sprite_bitmap.v" +`include "sprite_renderer.v" + +/* +A simple racing game with two sprites and a scrolling playfield. +This version does not use a CPU; all logic is straight Verilog. +*/ + +module racing_game_top(clk, reset, out); + + input clk, reset; + output [1:0] out; + + wire hsync, vsync; + wire display_on; + wire [8:0] hpos; + wire [8:0] vpos; + + // player car position (set at VSYNC) + reg [7:0] player_x; + reg [7:0] player_y; + + reg clk2; + always @(posedge clk) begin + clk2 <= !clk2; + end + + // video sync generator + hvsync_generator #(256,60,40,25) hvsync_gen( + .clk(clk2), + .reset(reset), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + // select player or enemy access to ROM + wire player_load = (hpos >= 256); + // wire up car sprite ROM + wire [3:0] car_sprite_yofs; + wire [7:0] car_sprite_bits; + car_bitmap car( + .yofs(car_sprite_yofs), + .bits(car_sprite_bits)); + + // signals for player sprite generator + wire player_vstart = player_y == vpos; + wire player_hstart = player_x == hpos; + wire player_gfx; + wire player_is_drawing; + + // player sprite generator + sprite_renderer player_renderer( + .clk(clk2), + .reset(reset), + .vstart(player_vstart), + .load(player_load), + .hstart(player_hstart), + .rom_addr(car_sprite_yofs), + .rom_bits(car_sprite_bits), + .gfx(player_gfx), + .in_progress(player_is_drawing)); + + // runs once per frame + always @(posedge vsync) + begin + player_x <= player_x + 1; + player_y <= player_y + 1; + end + + assign out = (hsync||vsync) ? 0 : display_on ? (1+player_gfx+(player_vstart|player_hstart|player_is_drawing)) : 1; + +endmodule diff --git a/fpga/examples/starfield.v b/fpga/examples/starfield.v new file mode 100644 index 00000000..b07b178d --- /dev/null +++ b/fpga/examples/starfield.v @@ -0,0 +1,47 @@ + +`include "hvsync_generator.v" +`include "lfsr.v" + +/* +Scrolling starfield generator using a period (2^16-1) LFSR. +*/ + +module starfield_top(clk, reset, out); + + input clk, reset; + output [1:0] out; + wire hsync, vsync; + wire display_on; + wire [8:0] hpos; + wire [8:0] vpos; + wire [15:0] lfsr; + + reg clk2; + always @(posedge clk) begin + clk2 <= !clk2; + end + + hvsync_generator #(256,60,40,25) hvsync_gen( + .clk(clk2), + .reset(reset), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + wire star_enable = !hpos[8] & !vpos[8]; + + // LFSR with period = 2^16-1 = 256*256-1 + LFSR #(16,16'b1000000001011,0) lfsr_gen( + .clk(clk2), + .reset(reset), + .enable(star_enable), + .lfsr(lfsr)); + + wire star_on = &lfsr[15:9]; + + assign out = (hsync||vsync) ? 0 : star_on ? (1+lfsr[1]+lfsr[2]) : 1; + +endmodule diff --git a/fpga/examples/test.vlog b/fpga/examples/test.vlog new file mode 100644 index 00000000..ceaa5516 --- /dev/null +++ b/fpga/examples/test.vlog @@ -0,0 +1,34 @@ + +module test; + +reg clk; +reg reset; +reg hpaddle; +reg vpaddle; +wire out0; +wire out1; + +chip chip( + .io_7_0_0(clk), + .io_0_8_1(reset), + .io_13_4_0(hpaddle), + .io_13_3_1(vpaddle), + .io_13_6_0(out0), + .io_13_4_1(out1) +); + +always #2 clk = !clk; + +initial begin + $dumpfile("racing_game_cpu.vcd"); + $dumpvars(0,test); + + #1 clk = 0; + #5 reset = 1; + #10 reset = 0; + + #100000 $finish(); +end + +endmodule + diff --git a/fpga/examples/test_hvsync.v b/fpga/examples/test_hvsync.v new file mode 100644 index 00000000..a3445ce8 --- /dev/null +++ b/fpga/examples/test_hvsync.v @@ -0,0 +1,32 @@ + +`include "hvsync_generator.v" + +/* +A simple test pattern using the hvsync_generator module. +*/ + +module test_hvsync_top(clk, reset, hsync, vsync, rgb); + + input clk, reset; + output hsync, vsync; + output [2:0] rgb; + wire display_on; + wire [8:0] hpos; + wire [8:0] vpos; + + hvsync_generator hvsync_gen( + .clk(clk), + .reset(0), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + wire r = display_on && (((hpos&7)==0) || ((vpos&7)==0)); + wire g = display_on && vpos[4]; + wire b = display_on && hpos[4]; + assign rgb = {b,g,r}; + +endmodule diff --git a/fpga/examples/tile_renderer.v b/fpga/examples/tile_renderer.v new file mode 100644 index 00000000..113fa151 --- /dev/null +++ b/fpga/examples/tile_renderer.v @@ -0,0 +1,89 @@ + +/* +Displays a 32x30 grid of 8x8 tiles, whose attributes are + fetched from RAM, and whose bitmap patterns are in ROM. +*/ + +module tile_renderer(clk, reset, hpos, vpos, + rgb, + ram_addr, ram_read, ram_busy, + rom_addr, rom_data); + + input clk, reset; + input [8:0] hpos; + input [8:0] vpos; + output [3:0] rgb; + + // start loading cells from RAM at this hpos value + // first column read will be ((HLOAD-2) % 32) + parameter HLOAD = 272; + + output reg [15:0] ram_addr; + input [15:0] ram_read; + output reg ram_busy; + + output [10:0] rom_addr; + input [7:0] rom_data; + + reg [7:0] page_base = 8'h7e; // page table base (8 bits) + reg [15:0] row_base; // row table base (16 bits) + reg [4:0] row; + //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 [15:0] cur_cell; + wire [7:0] cur_char = cur_cell[7:0]; + wire [7:0] cur_attr = cur_cell[15:8]; + + // tile ROM address + assign rom_addr = {cur_char, yofs}; + + reg [15:0] row_buffer[0:31]; + + // lookup char and attr + always @(posedge clk) begin + // reset row to 0 when last row displayed + if (vpos == 248) begin + row <= 0; + end + // time to read a row? + if (vpos[2:0] == 7) begin + // read row_base from page table (2 bytes) + case (hpos) + // assert busy 5 cycles before first RAM read + HLOAD-8: ram_busy <= 1; + // read page base for row + HLOAD-3: ram_addr <= {page_base, 3'b000, row}; + HLOAD-1: row_base <= ram_read; + // deassert BUSY and increment row counter + HLOAD+34: begin + ram_busy <= 0; + row <= row + 1; + end + endcase + // load row of tile data from RAM + // (last two twice) + if (hpos >= HLOAD && hpos < HLOAD+34) begin + ram_addr <= row_base + hpos[4:0]; + row_buffer[hpos[4:0] - 5'd2] <= ram_read; + end + end + // latch character data + if (hpos < 256) begin + case (hpos[2:0]) + 7: begin + cur_cell <= row_buffer[col+1]; + end + endcase + end else if (hpos == 308) begin + cur_cell <= row_buffer[0]; + end + end + + // extract bit from ROM output + assign rgb = rom_data[~xofs] ? cur_attr[3:0] : cur_attr[7:4]; + +endmodule + diff --git a/fpga/examples/tiletest.v b/fpga/examples/tiletest.v new file mode 100644 index 00000000..ab9e2cc3 --- /dev/null +++ b/fpga/examples/tiletest.v @@ -0,0 +1,68 @@ + +`include "hvsync_generator.v" +`include "font_cp437_8x8.v" +`include "ram.v" +`include "tile_renderer.v" + +module test_tilerender_top(clk, reset, out); + + input clk, reset; + output [1:0] out; + wire hsync, vsync; + + wire display_on; + wire [8:0] hpos; + wire [8:0] vpos; + + reg [15:0] ram_addr; + wire [15:0] ram_read; + reg [15:0] ram_write = 0; + reg ram_writeenable = 0; + + wire [10:0] rom_addr; + wire [7:0] rom_data; + wire ram_busy; + + hvsync_generator #(256,60,40,25) hvsync_gen( + .clk(clk), + .reset(reset), + .hsync(hsync), + .vsync(vsync), + .display_on(display_on), + .hpos(hpos), + .vpos(vpos) + ); + + // RAM + RAM_sync #(10,16) ram( + .clk(clk), + .dout(ram_read), + .din(ram_write), + .addr(ram_addr), + .we(ram_writeenable) + ); + + wire [3:0] rgb_tile; + + tile_renderer tile_gen( + .clk(clk), + .reset(reset), + .hpos(hpos), + .vpos(vpos), + .ram_addr(ram_addr), + .ram_read(ram_read), + .ram_busy(ram_busy), + .rom_addr(rom_addr), + .rom_data(rom_data), + .rgb(rgb_tile) + ); + + assign out = (hsync||vsync) ? 0 : (1+rgb_tile[0]+rgb_tile[1]); + + // tile ROM + font_cp437_8x8 tile_rom( + .addr(rom_addr), + .data(rom_data) + ); + +endmodule diff --git a/presets/verilog/ram1.v b/presets/verilog/chardisplay.v similarity index 100% rename from presets/verilog/ram1.v rename to presets/verilog/chardisplay.v diff --git a/presets/verilog/digits10.v b/presets/verilog/digits10.v index aa12829f..0a1903e3 100644 --- a/presets/verilog/digits10.v +++ b/presets/verilog/digits10.v @@ -95,7 +95,7 @@ module digits10_array(digit, yofs, bits); input [2:0] yofs; // vertical offset (0-4) output [4:0] bits; // output (5 bits) - reg [4:0] bitarray[16][5]; // ROM array (16 x 5 x 5 bits) + reg [4:0] bitarray[0:15][0:4]; // ROM array (16 x 5 x 5 bits) assign bits = bitarray[digit][yofs]; // assign module output diff --git a/presets/verilog/racing_game.v b/presets/verilog/racing_game.v index 51bec445..5588b529 100644 --- a/presets/verilog/racing_game.v +++ b/presets/verilog/racing_game.v @@ -97,7 +97,7 @@ module racing_game_top(clk, hsync, vsync, rgb, hpaddle, vpaddle); .rom_addr(enemy_sprite_yofs), .rom_bits(car_sprite_bits), .gfx(enemy_gfx), - .in_progress(player_is_drawing)); + .in_progress(enemy_is_drawing)); // signals for enemy bouncing off left/right borders wire enemy_hit_left = (enemy_x == 64); diff --git a/presets/verilog/racing_game_cpu.v b/presets/verilog/racing_game_cpu.v index 5ba945c5..5d133939 100644 --- a/presets/verilog/racing_game_cpu.v +++ b/presets/verilog/racing_game_cpu.v @@ -9,21 +9,7 @@ A simple racing game with two sprites and a scrolling playfield. This version uses the 8-bit CPU. */ -// uncomment to see scope view -//`define DEBUG - -module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle, - address_bus, to_cpu, from_cpu, write_enable -`ifdef DEBUG - , output [7:0] A - , output [7:0] B - , output [7:0] IP - , output carry - , output zero -`else - , rgb -`endif -); +module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle, rgb); input clk, reset; input hpaddle, vpaddle; @@ -31,16 +17,7 @@ module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle, wire display_on; wire [8:0] hpos; wire [8:0] vpos; -`ifdef DEBUG - wire [2:0] rgb; - assign IP = cpu.IP; - assign A = cpu.A; - assign B = cpu.B; - assign carry = cpu.carry; - assign zero = cpu.zero; -`else output [3:0] rgb; -`endif parameter PADDLE_X = 0; // paddle X coordinate parameter PADDLE_Y = 1; // paddle Y coordinate @@ -58,13 +35,13 @@ module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle, // flags: [0, 0, collision, vsync, hsync, vpaddle, hpaddle, display_on] parameter IN_FLAGS = 8'h42; - reg [7:0] ram[64]; // 64 bytes of RAM - reg [7:0] rom[128]; // 128 bytes of ROM + reg [7:0] ram[0:15]; // 16 bytes of RAM + reg [7:0] rom[0:127]; // 128 bytes of ROM - output wire [7:0] address_bus; // CPU address bus - output reg [7:0] to_cpu; // data bus to CPU - output wire [7:0] from_cpu; // data bus from CPU - output wire write_enable; // write enable bit from CPU + wire [7:0] address_bus; // CPU address bus + reg [7:0] to_cpu; // data bus to CPU + wire [7:0] from_cpu; // data bus from CPU + wire write_enable; // write enable bit from CPU // 8-bit CPU module CPU cpu(.clk(clk), @@ -77,13 +54,13 @@ module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle, // RAM write from CPU always @(posedge clk) if (write_enable) - ram[address_bus[5:0]] <= from_cpu; + ram[address_bus[3:0]] <= from_cpu; // RAM read from CPU always @(*) casez (address_bus) // RAM - 8'b00??????: to_cpu = ram[address_bus[5:0]]; + 8'b00??????: to_cpu = ram[address_bus[3:0]]; // special read registers IN_HPOS: to_cpu = hpos[7:0]; IN_VPOS: to_cpu = vpos[7:0]; @@ -150,7 +127,7 @@ module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle, .rom_addr(enemy_sprite_yofs), .rom_bits(car_sprite_bits), .gfx(enemy_gfx), - .in_progress(player_is_drawing)); + .in_progress(enemy_is_drawing)); // collision detection logic reg frame_collision; diff --git a/presets/verilog/ram.v b/presets/verilog/ram.v index 093b3d7c..26f04d24 100644 --- a/presets/verilog/ram.v +++ b/presets/verilog/ram.v @@ -24,7 +24,7 @@ module RAM_sync(clk, addr, din, dout, we); output [D-1:0] dout; // data output input we; // write enable - reg [D-1:0] mem [1<