commit 59f7d5db2e922c4f1c3acf1d776bfca5abb2bcfb Author: John Bintz Date: Sat Jul 6 14:51:29 2024 -0400 initial commit diff --git a/Go_Board_Constraints.pcf b/Go_Board_Constraints.pcf new file mode 100644 index 0000000..3f23d45 --- /dev/null +++ b/Go_Board_Constraints.pcf @@ -0,0 +1,71 @@ +# ############################################################################## + +# iCEcube PCF + +# Version: 2014.12.27052 + +# File Generated: Apr 27 2015 09:46:33 + +# Family & Device: iCE40HX1K + +# Package: VQ100 + +# ############################################################################## + +### Main FPGA Clock +set_io i_Clk 15 + +### LED Pins: +set_io o_LED_1 56 +set_io o_LED_2 57 +set_io o_LED_3 59 +set_io o_LED_4 60 + +## Push-Button Switches +set_io i_Switch_1 53 +set_io i_Switch_2 51 +set_io i_Switch_3 54 +set_io i_Switch_4 52 + +### 7 Segment Outputs +set_io o_Segment1_A 3 +set_io o_Segment1_B 4 +set_io o_Segment1_C 93 +set_io o_Segment1_D 91 +set_io o_Segment1_E 90 +set_io o_Segment1_F 1 +set_io o_Segment1_G 2 +set_io o_Segment2_A 100 +set_io o_Segment2_B 99 +set_io o_Segment2_C 97 +set_io o_Segment2_D 95 +set_io o_Segment2_E 94 +set_io o_Segment2_F 8 +set_io o_Segment2_G 96 + +## UART Outputs +set_io i_UART_RX 73 +set_io o_UART_TX 74 + +## VGA Outputs +set_io o_VGA_HSync 26 +set_io o_VGA_VSync 27 +set_io o_VGA_Red_0 36 +set_io o_VGA_Red_1 37 +set_io o_VGA_Red_2 40 +set_io o_VGA_Grn_0 29 +set_io o_VGA_Grn_1 30 +set_io o_VGA_Grn_2 33 +set_io o_VGA_Blu_0 28 +set_io o_VGA_Blu_1 41 +set_io o_VGA_Blu_2 42 + +## PMOD Signals +set_io io_PMOD_1 65 +set_io io_PMOD_2 64 +set_io io_PMOD_3 63 +set_io io_PMOD_4 62 +set_io io_PMOD_7 78 +set_io io_PMOD_8 79 +set_io io_PMOD_9 80 +set_io io_PMOD_10 81 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..90bfbdb --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +CONSTRAINTS = ../Go_Board_Constraints.pcf +LED_TOGGLE = pong_runner.bin + +install: $(LED_TOGGLE) + iceprog $(LED_TOGGLE) + +.PHONY: test +test: + iverilog -o test uart_rx_test.v uart_rx.v + vvp test + gtkwave test.vcd + +.SUFFIXES: .v .json .bitstream .bin + +.v.json: + yosys -q -p "hierarchy -top PongRunner; synth_ice40 -json $*.json" $*.v vga_sync_pulse_generator.v vga_current_beam_position.v vga_add_porches_to_output.v uart_rx.v uart_tx.v pong.v pong_ball.v pong_paddle.v debounce_filter.v + +.json.bitstream: + #nextpnr-ice40 --hx1k --freq 25 --pcf $(CONSTRAINTS) --json $*.json --package vq100 --asc $*.bitstream + nextpnr-ice40 --hx1k --freq 25 --pcf $(CONSTRAINTS) --json $*.json --package vq100 --asc $*.bitstream + +.bitstream.bin: + icepack $*.bitstream $*.bin diff --git a/README.md b/README.md new file mode 100644 index 0000000..7e4a5b4 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Go Board code reworking + +This code is a reworking of the code for the [Go Board](https://nandland.com/the-go-board/) +FPGA beginner's board. + +The original code comes from two sources: + +* [Nandland Go Board tutorials](https://nandland.com/download-and-install-the-fpga-tools-and-drivers/) +* [Getting Started with FPGAs repo](https://github.com/nandland/getting-started-with-fpgas/) + +Where possible I tried to: + +* use more consistent variable and constant names +* fix missing inferred wires (the Pong project had a lot of these) +* avoid run-on lines of code +* avoid one-liner conditional bodies, opting for `begin...end` blocks everywhere + +All of these can be built with [Yosys](https://github.com/YosysHQ/yosys) +and tested with [Icarus Verilog](https://github.com/steveicarus/iverilog). The `Makefile` is one I've been dragging along across all projects. It can fire off both a build-and-install to the Go Board, as well as run the test suite and open up [GTKWave](https://gtkwave.sourceforge.net/) +so you can inspect the signals. You do not need Lattive iCECube2 and you will have a hard time +getting it to run on modern Linux anyway! + +`sipo_shift_register_test.sv` shows how to create your own `assert` for Icarus Verilog, +taken from an idea from here: https://stackoverflow.com/a/13906120 + +Have fun. + +John diff --git a/binary_to_7_segment.v b/binary_to_7_segment.v new file mode 100644 index 0000000..f783a4f --- /dev/null +++ b/binary_to_7_segment.v @@ -0,0 +1,44 @@ +module Binary_to_7_Segment( + input i_Clk, + input [$clog2(15)-1:0] i_Number, + output o_SegA, + output o_SegB, + output o_SegC, + output o_SegD, + output o_SegE, + output o_SegF, + output o_SegG +); + +reg [6:0] r_HexEncoding; +always @(posedge i_Clk) begin + case (i_Number) + 0: r_HexEncoding <= 7'b1111110; + 1: r_HexEncoding <= 7'b0110000; + 2: r_HexEncoding <= 7'b1101101; + 3: r_HexEncoding <= 7'b1111001; + 4: r_HexEncoding <= 7'b0110011; + 5: r_HexEncoding <= 7'b1011011; + 6: r_HexEncoding <= 7'b1011111; + 7: r_HexEncoding <= 7'b1110000; + 8: r_HexEncoding <= 7'b1111111; + 9: r_HexEncoding <= 7'b1111011; + 10: r_HexEncoding <= 7'b1110111; + 11: r_HexEncoding <= 7'b0011111; + 12: r_HexEncoding <= 7'b1001110; + 13: r_HexEncoding <= 7'b0111101; + 14: r_HexEncoding <= 7'b1001111; + 15: r_HexEncoding <= 7'b1000111; + default: r_HexEncoding <= 7'b0000000; + endcase +end + +assign o_SegA = r_HexEncoding[6]; +assign o_SegB = r_HexEncoding[5]; +assign o_SegC = r_HexEncoding[4]; +assign o_SegD = r_HexEncoding[3]; +assign o_SegE = r_HexEncoding[2]; +assign o_SegF = r_HexEncoding[1]; +assign o_SegG = r_HexEncoding[0]; + +endmodule diff --git a/count_and_toggle.v b/count_and_toggle.v new file mode 100644 index 0000000..adebfc8 --- /dev/null +++ b/count_and_toggle.v @@ -0,0 +1,22 @@ +module Count_And_Toggle ( + input i_Clk, + input i_Enable, + output reg o_Toggle +); + parameter COUNT_LIMIT = 10; + + reg [$clog2(COUNT_LIMIT - 1):0] r_Counter; + + always @(posedge i_Clk) begin + if (i_Enable == 1) begin + if (r_Counter == COUNT_LIMIT - 1) begin + o_Toggle <= !o_Toggle; + r_Counter <= 0; + end else begin + r_Counter <= r_Counter + 1; + end + end else begin + o_Toggle <= 0; + end + end +endmodule diff --git a/debounce_filter.v b/debounce_filter.v new file mode 100644 index 0000000..8979105 --- /dev/null +++ b/debounce_filter.v @@ -0,0 +1,30 @@ +module Debounce_Filter +#(parameter DEBOUNCE_LIMIT = 20) ( + input i_Clk, + input i_Bouncy, + output o_Debounced +); + +reg [$clog2(DEBOUNCE_LIMIT)-1:0] r_Count = 0; +reg r_State = 1'b0; + +always @(posedge i_Clk) +begin + if (i_Bouncy !== r_State && r_Count < DEBOUNCE_LIMIT - 1) + begin + r_Count <= r_Count + 1; + end + else if (r_Count == DEBOUNCE_LIMIT - 1) + begin + r_State <= i_Bouncy; + r_Count <= 0; + end + else + begin + r_Count <= 0; + end +end + +assign o_Debounced = r_State; + +endmodule diff --git a/debounce_project_top.v b/debounce_project_top.v new file mode 100644 index 0000000..e14bfae --- /dev/null +++ b/debounce_project_top.v @@ -0,0 +1,19 @@ +module Debounce_Project_Top( + input i_Clk, + input i_Switch_1, + output o_LED_1 +); + wire w_Debounced_Switch; + + Debounce_Filter #(.DEBOUNCE_LIMIT(250000)) Debounce_Inst ( + .i_Clk(i_Clk), + .i_Bouncy(i_Switch_1), + .o_Debounced(w_Debounced_Switch) + ); + + LED_Toggle_Project LED_Toggle_Inst ( + .i_Clk(i_Clk), + .i_Switch_1(w_Debounced_Switch), + .o_LED_1(o_LED_1) + ); +endmodule diff --git a/demux_1_to_4.v b/demux_1_to_4.v new file mode 100644 index 0000000..a878b30 --- /dev/null +++ b/demux_1_to_4.v @@ -0,0 +1,14 @@ +module Demux_1_to_4( + input i_Data, + input i_Sel0, + input i_Sel1, + output o_Data0, + output o_Data1, + output o_Data2, + output o_Data3 +); + assign o_Data0 = i_Data & !i_Sel0 & !i_Sel1; + assign o_Data1 = i_Data & i_Sel0 & !i_Sel1; + assign o_Data2 = i_Data & !i_Sel0 & i_Sel1; + assign o_Data3 = i_Data & i_Sel0 & i_Sel1; +endmodule diff --git a/fifo.v b/fifo.v new file mode 100644 index 0000000..4ef9a5f --- /dev/null +++ b/fifo.v @@ -0,0 +1,180 @@ +module FIFO( + input i_Clock, + + input i_writeDataReady_Flash, + input [WIDTH_BITS-1:0] i_writeData, + input [$clog2(DEPTH_WORDS)-1:0] i_almostFullLevel, + output o_almostFull, + output o_Full, + + input i_doRead_Flash, + output reg o_readDataReady, + output reg [WIDTH_BITS-1:0] o_readData, + input [$clog2(DEPTH_WORDS)-1:0] i_almostEmptyLevel, + output o_almostEmpty, + output o_Empty, + + input i_Reset_Low +); + parameter WIDTH_BITS = 8; + parameter DEPTH_WORDS = 16; + + reg [WIDTH_BITS-1:0] r_RAM [DEPTH_WORDS-1:0]; + reg [$clog2(DEPTH_WORDS)-1:0] r_writeAddress; + reg [$clog2(DEPTH_WORDS)-1:0] r_readAddress; + reg [$clog2(DEPTH_WORDS)-1:0] r_Count; + + always @(posedge i_Clock or negedge i_Reset_Low) begin + if (~i_Reset_Low) begin + r_writeAddress <= 0; + r_readAddress <= 0; + r_Count <= 0; + end else begin + if (i_writeDataReady_Flash) begin + r_RAM[r_writeAddress] <= i_writeData; + + if (r_writeAddress == DEPTH_WORDS - 1) begin + r_writeAddress <= 0; + end else begin + r_writeAddress <= r_writeAddress + 1; + end + end + + if (i_doRead_Flash) begin + o_readData <= r_RAM[r_readAddress]; + o_readDataReady <= i_doRead_Flash; + + if (r_readAddress == DEPTH_WORDS - 1) begin + r_readAddress <= 0; + end else begin + r_readAddress <= r_readAddress + 1; + end + end + + if (i_doRead_Flash & ~i_writeDataReady_Flash) begin + if (r_Count != 0) begin + r_Count <= r_Count - 1; + end + end else if (i_writeDataReady_Flash & ~i_doRead_Flash) begin + if (r_Count != DEPTH_WORDS) begin + r_Count <= r_Count + 1; + end + end + end + end + + assign o_Full = ( + r_Count == DEPTH_WORDS + ) || ( + r_Count == DEPTH_WORDS-1 && + i_writeDataReady_Flash && + !i_doRead_Flash + ); + assign o_Empty = (r_Count == 0); + + assign o_almostFull = (r_Count > DEPTH_WORDS - i_almostFullLevel); + assign o_almostEmpty = (r_Count < i_almostEmptyLevel); +endmodule + +module TestFIFO(); + reg r_Clk = 0; + + always #1 r_Clk <= !r_Clk; + + reg r_Reset_Low = 0; + + reg r_writeDataReady_Flash = 0; + reg [7:0] r_writeData = 50; + reg [3:0] r_almostFullLevel = 4; + + wire w_almostFull; + wire w_Full; + + reg r_doReadFlash = 0; + wire w_readDataReady; + wire [7:0] w_readData; + reg [3:0] r_almostEmptyLevel = 4; + wire w_almostEmpty; + wire w_Empty; + + FIFO myFifo( + .i_Clock(r_Clk), + .i_Reset_Low(r_Reset_Low), + + .i_writeDataReady_Flash(r_writeDataReady_Flash), + .i_writeData(r_writeData), + .i_almostFullLevel(r_almostFullLevel), + .o_almostFull(w_almostFull), + .o_Full(w_Full), + + .i_doRead_Flash(r_doReadFlash), + .o_readDataReady(w_readDataReady), + .o_readData(w_readData), + .i_almostEmptyLevel(r_almostEmptyLevel), + .o_almostEmpty(w_almostEmpty), + .o_Empty(w_Empty) + ); + + initial begin + $dumpfile("test.vcd"); + $dumpvars; + + #2; + + r_Reset_Low <= 1; + + #2; + + r_writeDataReady_Flash <= 1; + + #2; + + r_writeDataReady_Flash <= 0; + + #2; + + r_writeData = 69; + r_writeDataReady_Flash <= 1; + #2; + r_writeDataReady_Flash <= 0; + #2; + + r_writeDataReady_Flash <= 1; + #2; + r_writeDataReady_Flash <= 0; + #2; + + r_writeDataReady_Flash <= 1; + #2; + r_writeDataReady_Flash <= 0; + #2; + + r_writeDataReady_Flash <= 1; + #2; + r_writeDataReady_Flash <= 0; + #2; + + r_writeDataReady_Flash <= 1; + #2; + r_writeDataReady_Flash <= 0; + #2; + + r_doReadFlash <= 1; + + #2; + + r_doReadFlash <= 0; + + #2; + + r_doReadFlash <= 1; + + #2; + + r_doReadFlash <= 0; + + #2; + + $finish; + end +endmodule diff --git a/led_toggle.v b/led_toggle.v new file mode 100644 index 0000000..9971c14 --- /dev/null +++ b/led_toggle.v @@ -0,0 +1,20 @@ +module LED_Toggle_Project( + input i_Clk, + input i_Switch_1, + output o_LED_1 +); + reg r_LED_1 = 1'b0; + reg r_Switch_1 = 1'b0; + + always @(posedge i_Clk) + begin + r_Switch_1 <= i_Switch_1; + + if (i_Switch_1 == 1'b0 && r_Switch_1 == 1'b1) + begin + r_LED_1 <= ~r_LED_1; + end + end + + assign o_LED_1 = r_LED_1; +endmodule diff --git a/lfsr_22.v b/lfsr_22.v new file mode 100644 index 0000000..8b3099e --- /dev/null +++ b/lfsr_22.v @@ -0,0 +1,18 @@ +module LFSR( + input i_Clk, + output [SIZE-1:0] o_LFSR_Data, + output o_LFSR_Done +); + parameter SIZE = 22; + + reg [SIZE-1:0] r_LFSR; + wire w_XNOR; + + always @(posedge i_Clk) begin + r_LFSR <= {r_LFSR[SIZE-2:0], w_XNOR}; + end + + assign w_XNOR = r_LFSR[SIZE-1] ^~ r_LFSR[SIZE-2]; + assign o_LFSR_Done = (r_LFSR == 0); + assign o_LFSR_Data = r_LFSR; +endmodule diff --git a/pong.v b/pong.v new file mode 100644 index 0000000..2069994 --- /dev/null +++ b/pong.v @@ -0,0 +1,181 @@ +module Pong( + input i_Clk, + input i_HSync, + input i_VSync, + + input i_GameStart, + + input i_Player1_Up, + input i_Player1_Down, + input i_Player2_Up, + input i_Player2_Down, + + output reg o_HSync, + output reg o_VSync, + output [2:0] o_Red, + output [2:0] o_Green, + output [2:0] o_Blue +); + parameter TOTAL_COLUMNS = 800; + parameter TOTAL_ROWS = 525; + + parameter ACTIVE_COLUMNS = 640; + parameter ACTIVE_ROWS = 480; + + localparam SCREEN_WIDTH_CELLS = 40; + localparam SCREEN_HEIGHT_CELLS = 30; + localparam SCORE_LIMIT = 9; + localparam PADDLE_HEIGHT = 6; + localparam PADDLE_PLAYER1_X = 0; + localparam PADDLE_PLAYER2_X = SCREEN_WIDTH_CELLS - 1; + + localparam STATE_IDLE = 0; + localparam STATE_RUNNING = 1; + localparam STATE_P1_WINNER = 2; + localparam STATE_P2_WINNER = 3; + localparam STATE_CLEANUP = 4; + + reg [2:0] r_currentState = STATE_IDLE; + + wire w_HSync, w_VSync; + wire [9:0] w_X, w_Y; + + wire w_P1_DoDrawPaddle, w_P2_DoDrawPaddle; + wire [5:0] w_P1_PaddleY, w_P2_PaddleY; + wire w_DoDrawBall, w_DrawAny; + wire [5:0] w_BallX, w_BallY; + wire w_IsGameActive; + + reg [3:0] r_P1_Score = 0; + reg [3:0] r_P2_Score = 0; + + wire [5:0] w_CellX, w_CellY; + + VGA_Current_Beam_Position #( + .TOTAL_COLUMNS(TOTAL_COLUMNS), + .TOTAL_ROWS(TOTAL_ROWS) + ) CurrentPosition ( + .i_Clk(i_Clk), + .i_HSync(i_HSync), + .i_VSync(i_VSync), + .o_HSync(w_HSync), + .o_VSync(w_VSync), + .o_X(w_X), + .o_Y(w_Y) + ); + + always @(posedge i_Clk) begin + o_HSync <= w_HSync; + o_VSync <= w_VSync; + end + + // right shift 4 + assign w_CellX = w_X[9:4]; + assign w_CellY = w_Y[9:4]; + + // paddles + + PongPaddle #( + .X_POS_CELLS(PADDLE_PLAYER1_X), + .SCREEN_HEIGHT_CELLS(SCREEN_HEIGHT_CELLS), + .PADDLE_HEIGHT_CELLS(PADDLE_HEIGHT) + ) P1_Paddle ( + .i_Clk(i_Clk), + .i_CellX(w_CellX), + .i_CellY(w_CellY), + .i_PaddleUp(i_Player1_Up), + .i_PaddleDown(i_Player1_Down), + .o_DoDrawPaddle(w_P1_DoDrawPaddle), + .o_PaddleY(w_P1_PaddleY) + ); + + PongPaddle #( + .X_POS_CELLS(PADDLE_PLAYER2_X), + .SCREEN_HEIGHT_CELLS(SCREEN_HEIGHT_CELLS), + .PADDLE_HEIGHT_CELLS(PADDLE_HEIGHT) + ) P2_Paddle ( + .i_Clk(i_Clk), + .i_CellX(w_CellX), + .i_CellY(w_CellY), + .i_PaddleUp(i_Player2_Up), + .i_PaddleDown(i_Player2_Down), + .o_DoDrawPaddle(w_P2_DoDrawPaddle), + .o_PaddleY(w_P2_PaddleY) + ); + + PongBall #( + .SCREEN_WIDTH_CELLS(SCREEN_WIDTH_CELLS), + .SCREEN_HEIGHT_CELLS(SCREEN_HEIGHT_CELLS) + ) Ball ( + .i_Clk(i_Clk), + .i_IsGameActive(w_IsGameActive), + .i_CellX(w_CellX), + .i_CellY(w_CellY), + .o_DoDrawBall(w_DoDrawBall), + .o_BallX(w_BallX), + .o_BallY(w_BallY) + ); + + always @(posedge i_Clk) begin + case (r_currentState) + STATE_IDLE: + begin + if (i_GameStart) begin + r_currentState <= STATE_RUNNING; + end + end + + STATE_RUNNING: + begin + if ( + w_BallX == 0 && ( + w_BallY < w_P1_PaddleY || + w_BallY > w_P1_PaddleY + PADDLE_HEIGHT + ) + ) begin + r_currentState <= STATE_P1_WINNER; + end else if ( + w_BallX == SCREEN_WIDTH_CELLS - 1 && ( + w_BallY < w_P2_PaddleY || + w_BallY > w_P2_PaddleY + PADDLE_HEIGHT + ) + ) begin + r_currentState <= STATE_P2_WINNER; + end + end + + STATE_P1_WINNER: + begin + if (r_P1_Score == SCORE_LIMIT - 1) begin + r_P1_Score <= 0; + end else begin + r_P1_Score <= r_P1_Score + 1; + r_currentState <= STATE_CLEANUP; + end + end + + STATE_P2_WINNER: + begin + if (r_P2_Score == SCORE_LIMIT - 1) begin + r_P2_Score <= 0; + end else begin + r_P2_Score <= r_P2_Score + 1; + r_currentState <= STATE_CLEANUP; + end + end + + STATE_CLEANUP: + begin + r_currentState <= STATE_IDLE; + end + endcase + end + + assign w_IsGameActive = (r_currentState == STATE_RUNNING) ? 1 : 0; + + assign w_DrawAny = w_DoDrawBall | w_P1_DoDrawPaddle | w_P2_DoDrawPaddle; + + assign o_Red = w_DrawAny ? 7 : 0; + assign o_Green = w_DrawAny ? 7 : 0; + assign o_Blue = w_DrawAny ? 7 : 0; +endmodule diff --git a/pong_ball.v b/pong_ball.v new file mode 100644 index 0000000..c5e5483 --- /dev/null +++ b/pong_ball.v @@ -0,0 +1,70 @@ +module PongBall ( + input i_Clk, + input i_IsGameActive, + input [5:0] i_CellX, + input [5:0] i_CellY, + + output reg o_DoDrawBall, + output reg [5:0] o_BallX, + output reg [5:0] o_BallY +); + parameter SCREEN_WIDTH_CELLS = 40; + parameter SCREEN_HEIGHT_CELLS = 30; + + parameter CLOCKS_PER_SECOND = 25000000; + parameter BALL_MOVE_TIME_MS = 50; + parameter BALL_SPEED = 1250000; + + reg [$clog2(BALL_SPEED)-1:0] r_ballMoveCount; + + reg [5:0] r_PrevBallX; + reg [5:0] r_PrevBallY; + + always @(posedge i_Clk) begin + if (!i_IsGameActive) begin + o_BallX <= SCREEN_WIDTH_CELLS / 2; + o_BallY <= SCREEN_HEIGHT_CELLS / 2; + r_PrevBallX <= SCREEN_WIDTH_CELLS / 2 + 1; + r_PrevBallY <= SCREEN_HEIGHT_CELLS / 2 - 2; + end else begin + if (r_ballMoveCount < BALL_SPEED) begin + r_ballMoveCount <= r_ballMoveCount + 1; + end else begin + r_ballMoveCount <= 0; + + r_PrevBallX <= o_BallX; + r_PrevBallY <= o_BallY; + + if ( + // at right edge, travelling right + (r_PrevBallX < o_BallX && o_BallX == SCREEN_WIDTH_CELLS - 1) || + // not at left edge + (o_BallX < r_PrevBallX && o_BallX != 0) + ) begin + o_BallX <= o_BallX - 1; + end else begin + o_BallX <= o_BallX + 1; + end + + if ( + // at bottom edge, travelling up + (r_PrevBallY < o_BallY && o_BallY == SCREEN_HEIGHT_CELLS - 1) || + // not at left edge + (o_BallY < r_PrevBallY && o_BallY != 0) + ) begin + o_BallY <= o_BallY - 1; + end else begin + o_BallY <= o_BallY + 1; + end + end + end + end + + always @(posedge i_Clk) begin + if (i_CellX == o_BallX && i_CellY == o_BallY) begin + o_DoDrawBall <= 1; + end else begin + o_DoDrawBall <= 0; + end + end +endmodule diff --git a/pong_paddle.v b/pong_paddle.v new file mode 100644 index 0000000..7a5a2fa --- /dev/null +++ b/pong_paddle.v @@ -0,0 +1,59 @@ +/** +* Paddles maintain their own location on the screen +*/ +module PongPaddle ( + input i_Clk, + input [5:0] i_CellX, + input [5:0] i_CellY, + input i_PaddleUp, + input i_PaddleDown, + + output reg o_DoDrawPaddle, + output reg [5:0] o_PaddleY +); + parameter X_POS_CELLS = 0; + parameter PADDLE_HEIGHT_CELLS = 6; + parameter SCREEN_HEIGHT_CELLS = 30; + + parameter CLOCKS_PER_SECOND = 25000000; + parameter PADDLE_MOVE_TIME_MS = 50; + // omg i can't get this + parameter integer PADDLE_SPEED = 1250000; + + reg [$clog2(PADDLE_SPEED)-1:0] r_paddleMoveCount; + wire w_onlyOneButtonDown = i_PaddleUp ^ i_PaddleDown; + + always @(posedge i_Clk) begin + // handle end of move delay + if (w_onlyOneButtonDown) begin + if (r_paddleMoveCount == PADDLE_SPEED) begin + r_paddleMoveCount <= 0; + end else begin + r_paddleMoveCount <= r_paddleMoveCount + 1; + end + end + + if ( + i_PaddleUp && + r_paddleMoveCount == PADDLE_SPEED && + o_PaddleY > 0 + ) begin + o_PaddleY <= o_PaddleY - 1; + end else if ( + i_PaddleDown && + r_paddleMoveCount == PADDLE_SPEED && + o_PaddleY < SCREEN_HEIGHT_CELLS - PADDLE_HEIGHT_CELLS - 1 + ) begin + o_PaddleY <= o_PaddleY + 1; + end + + if ( + i_CellX == X_POS_CELLS && + i_CellY >= o_PaddleY && + i_CellY <= o_PaddleY + PADDLE_HEIGHT_CELLS) begin + o_DoDrawPaddle <= 1; + end else begin + o_DoDrawPaddle <= 0; + end + end +endmodule diff --git a/pong_runner.v b/pong_runner.v new file mode 100644 index 0000000..4b0f558 --- /dev/null +++ b/pong_runner.v @@ -0,0 +1,142 @@ +module PongRunner ( + input i_Clk, + input i_UART_RX, + + input i_Switch_1, + input i_Switch_2, + input i_Switch_3, + input i_Switch_4, + + output o_VGA_HSync, + output o_VGA_VSync, + output o_VGA_Red_0, + output o_VGA_Red_1, + output o_VGA_Red_2, + output o_VGA_Grn_0, + output o_VGA_Grn_1, + output o_VGA_Grn_2, + output o_VGA_Blu_0, + output o_VGA_Blu_1, + output o_VGA_Blu_2 +); + localparam TOTAL_COLUMNS = 800; + localparam TOTAL_ROWS = 525; + + localparam ACTIVE_COLUMNS = 640; + localparam ACTIVE_ROWS = 480; + + localparam VIDEO_WIDTH = 3; + + wire [VIDEO_WIDTH-1:0] w_Red_FromPong, w_Red_FromPorchSync; + wire [VIDEO_WIDTH-1:0] w_Green_FromPong, w_Green_FromPorchSync; + wire [VIDEO_WIDTH-1:0] w_Blue_FromPong, w_Blue_FromPorchSync; + + wire w_dataReady; + wire w_HSync_Start, w_VSync_Start; + wire w_HSync_FromPong, w_VSync_FromPong; + wire w_HSync_FromPorchSync, w_VSync_FromPorchSync; + wire w_Switch_1_Debounced, + w_Switch_2_Debounced, + w_Switch_3_Debounced, + w_Switch_4_Debounced; + + UART_RX #( + .CLOCKS_PER_SECOND(25000000), + .BAUD_RATE(115200) + ) MyUART_RX ( + .i_Clk(i_Clk), + .i_ReceiveBit(i_UART_RX), + .o_DataReady(w_dataReady), + .o_DataByte() + ); + + VGA_Sync_Pulse_Generator #( + .TOTAL_COLUMNS(TOTAL_COLUMNS), + .TOTAL_ROWS(TOTAL_ROWS), + .ACTIVE_COLUMNS(ACTIVE_COLUMNS), + .ACTIVE_ROWS(ACTIVE_ROWS) + ) SyncGenerator ( + .i_Clk(i_Clk), + .o_HSync(w_HSync_Start), + .o_VSync(w_VSync_Start), + .o_rawX(), + .o_rawY() + ); + + Debounce_Filter #( + .DEBOUNCE_LIMIT(25000000 / 25000) + ) Debounce_Switch_1 ( + .i_Clk(i_Clk), + .i_Bouncy(i_Switch_1), + .o_Debounced(w_Switch_1_Debounced) + ); + + Debounce_Filter #( + .DEBOUNCE_LIMIT(25000000 / 25000) + ) Debounce_Switch_2 ( + .i_Clk(i_Clk), + .i_Bouncy(i_Switch_2), + .o_Debounced(w_Switch_2_Debounced) + ); + + Debounce_Filter #( + .DEBOUNCE_LIMIT(25000000 / 25000) + ) Debounce_Switch_3 ( + .i_Clk(i_Clk), + .i_Bouncy(i_Switch_3), + .o_Debounced(w_Switch_3_Debounced) + ); + + Debounce_Filter #( + .DEBOUNCE_LIMIT(25000000 / 25000) + ) Debounce_Switch_4 ( + .i_Clk(i_Clk), + .i_Bouncy(i_Switch_4), + .o_Debounced(w_Switch_4_Debounced) + ); + + Pong #() MyPong ( + .i_Clk(i_Clk), + .i_HSync(w_HSync_Start), + .i_VSync(w_VSync_Start), + .i_GameStart(w_dataReady), + .i_Player1_Up(w_Switch_1_Debounced), + .i_Player1_Down(w_Switch_2_Debounced), + .i_Player2_Up(w_Switch_3_Debounced), + .i_Player2_Down(w_Switch_4_Debounced), + .o_HSync(w_HSync_FromPong), + .o_VSync(w_VSync_FromPong), + .o_Red(w_Red_FromPong), + .o_Green(w_Green_FromPong), + .o_Blue(w_Blue_FromPong) + ); + + VGA_Add_Porches_To_Output #() PorchOutput ( + .i_Clk(i_Clk), + .i_HSync(w_HSync_FromPong), + .i_VSync(w_VSync_FromPong), + .i_Red(w_Red_FromPong), + .i_Green(w_Green_FromPong), + .i_Blue(w_Blue_FromPong), + .o_HSync(w_HSync_FromPorchSync), + .o_VSync(w_VSync_FromPorchSync), + .o_Red(w_Red_FromPorchSync), + .o_Green(w_Green_FromPorchSync), + .o_Blue(w_Blue_FromPorchSync) + ); + + assign o_VGA_HSync = w_HSync_FromPorchSync; + assign o_VGA_VSync = w_VSync_FromPorchSync; + + assign o_VGA_Red_0 = w_Red_FromPorchSync[0]; + assign o_VGA_Red_1 = w_Red_FromPorchSync[1]; + assign o_VGA_Red_2 = w_Red_FromPorchSync[2]; + + assign o_VGA_Grn_0 = w_Green_FromPorchSync[0]; + assign o_VGA_Grn_1 = w_Green_FromPorchSync[1]; + assign o_VGA_Grn_2 = w_Green_FromPorchSync[2]; + + assign o_VGA_Blu_0 = w_Blue_FromPorchSync[0]; + assign o_VGA_Blu_1 = w_Blue_FromPorchSync[1]; + assign o_VGA_Blu_2 = w_Blue_FromPorchSync[2]; +endmodule diff --git a/ram.v b/ram.v new file mode 100644 index 0000000..e11e3ea --- /dev/null +++ b/ram.v @@ -0,0 +1,77 @@ +module RAM( + input i_writeClock, + input [$clog2(DEPTH_WORDS - 1):0] i_writeAddress, + input [WIDTH_BITS-1:0] i_writeData, + input i_writeDataValid, + + input i_readClock, + input [$clog2(DEPTH_WORDS - 1):0] i_readAddress, + input i_doRead, + output reg o_dataValid, + output reg [WIDTH_BITS-1:0] o_readData +); + parameter WIDTH_BITS = 8; + parameter DEPTH_WORDS = 16; + + reg [WIDTH_BITS-1:0] r_Memory[DEPTH_WORDS-1:0]; + + always @(posedge i_writeClock) begin + if (i_writeDataValid) begin + r_Memory[i_writeAddress] <= i_writeData; + end + end + + always @(posedge i_readClock) begin + o_readData <= r_Memory[i_readAddress]; + o_dataValid <= i_doRead; + end +endmodule + +module TestRAM(); + reg r_Clk = 0; + + always #2 r_Clk <= !r_Clk; + + reg [4:0] r_writeAddress = 1; + reg [7:0] r_writeData = 50; + reg r_writeDataValid = 0; + + reg [4:0] r_readAddress = 1; + wire [7:0] w_readData; + reg r_doRead = 0; + wire w_dataValid; + + RAM myRam ( + .i_writeClock(r_Clk), + .i_writeAddress(r_writeAddress), + .i_writeData(r_writeData), + .i_writeDataValid(r_writeDataValid), + + .i_readClock(r_Clk), + .i_readAddress(r_readAddress), + .i_doRead(r_doRead), + .o_dataValid(w_dataValid), + .o_readData(w_readData) + ); + + initial begin + $dumpfile("test.vcd"); + $dumpvars; + + #2; + + r_writeDataValid <= 1; + + #2; + + r_writeDataValid <= 0; + + #2; + + r_doRead <= 1; + + #2; + + $finish; + end +endmodule diff --git a/simon.v b/simon.v new file mode 100644 index 0000000..3b70729 --- /dev/null +++ b/simon.v @@ -0,0 +1,103 @@ +module Simon( + input i_Clk, + + input i_Switch_1, + input i_Switch_2, + input i_Switch_3, + input i_Switch_4, + + output o_LED_1, + output o_LED_2, + output o_LED_3, + output o_LED_4, + + output o_Segment2_A, + output o_Segment2_B, + output o_Segment2_C, + output o_Segment2_D, + output o_Segment2_E, + output o_Segment2_F, + output o_Segment2_G +); + localparam GAME_LIMIT = 7; + localparam CLKS_PER_SECOND = 25000000; + localparam integer DEBOUNCE_LIMIT = CLKS_PER_SECOND * 0.001; + + wire w_Switch_1, w_Switch_2, w_Switch_3, w_Switch_4; + wire [3:0] w_Score; + wire w_Segment2_A, + w_Segment2_B, + w_Segment2_C, + w_Segment2_D, + w_Segment2_E, + w_Segment2_F, + w_Segment2_G; + + Debounce_Filter #( + .DEBOUNCE_LIMIT(DEBOUNCE_LIMIT) + ) Debounce_Switch_1 ( + .i_Clk(i_Clk), + .i_Bouncy(i_Switch_1), + .o_Debounced(w_Switch_1) + ); + + Debounce_Filter #( + .DEBOUNCE_LIMIT(DEBOUNCE_LIMIT) + ) Debounce_Switch_2 ( + .i_Clk(i_Clk), + .i_Bouncy(i_Switch_2), + .o_Debounced(w_Switch_2) + ); + + Debounce_Filter #( + .DEBOUNCE_LIMIT(DEBOUNCE_LIMIT) + ) Debounce_Switch_3 ( + .i_Clk(i_Clk), + .i_Bouncy(i_Switch_3), + .o_Debounced(w_Switch_3) + ); + + Debounce_Filter #( + .DEBOUNCE_LIMIT(DEBOUNCE_LIMIT) + ) Debounce_Switch_4 ( + .i_Clk(i_Clk), + .i_Bouncy(i_Switch_4), + .o_Debounced(w_Switch_4) + ); + + Simon_Game #( + .CLKS_PER_SECOND(CLKS_PER_SECOND), + .GAME_LIMIT(GAME_LIMIT) + ) Simon_Game_Instance ( + .i_Clk(i_Clk), + .i_Switch_1(w_Switch_1), + .i_Switch_2(w_Switch_2), + .i_Switch_3(w_Switch_3), + .i_Switch_4(w_Switch_4), + .o_Score(w_Score), + .o_LED_1(o_LED_1), + .o_LED_2(o_LED_2), + .o_LED_3(o_LED_3), + .o_LED_4(o_LED_4) + ); + + Binary_to_7_Segment Scoreboard ( + .i_Clk(i_Clk), + .i_Number(w_Score), + .o_SegA(w_Segment2_A), + .o_SegB(w_Segment2_B), + .o_SegC(w_Segment2_C), + .o_SegD(w_Segment2_D), + .o_SegE(w_Segment2_E), + .o_SegF(w_Segment2_F), + .o_SegG(w_Segment2_G) + ); + + assign o_Segment2_A = !w_Segment2_A; + assign o_Segment2_B = !w_Segment2_B; + assign o_Segment2_C = !w_Segment2_C; + assign o_Segment2_D = !w_Segment2_D; + assign o_Segment2_E = !w_Segment2_E; + assign o_Segment2_F = !w_Segment2_F; + assign o_Segment2_G = !w_Segment2_G; +endmodule diff --git a/simon_game.v b/simon_game.v new file mode 100644 index 0000000..cce51ed --- /dev/null +++ b/simon_game.v @@ -0,0 +1,208 @@ +module Simon_Game ( + input i_Clk, + + input i_Switch_1, + input i_Switch_2, + input i_Switch_3, + input i_Switch_4, + + output reg [3:0] o_Score, + + output o_LED_1, + output o_LED_2, + output o_LED_3, + output o_LED_4 +); + parameter CLKS_PER_SECOND = 25000000; + parameter GAME_LIMIT = 6; + + localparam START = 3'd0; + localparam PATTERN_OFF = 3'd1; + localparam PATTERN_SHOW = 3'd2; + localparam WAIT_PLAYER = 3'd3; + localparam INCREMENT_SCORE = 3'd4; + localparam PLAYER_LOST = 3'd5; + localparam PLAYER_WON = 3'd6; + + reg [2:0] r_currentState; + + // count and toggle results + wire w_IncomingDelay; + reg r_DelayCompleted; + wire w_EnableCounter; + + // user input handling + reg r_Switch_1, r_Switch_2, r_Switch_3, r_Switch_4; + reg r_Button_notDown; + reg [1:0] r_currentButtonDown; + + // game pattern & construction + reg [1:0] r_LEDPattern [0:10]; + wire [21:0] w_LFSR_Data; + reg [$clog2(GAME_LIMIT)-1:0] r_currentLEDPattern; + + always @(posedge i_Clk) begin + if (i_Switch_1 & i_Switch_2) begin + r_currentState <= START; + end else begin + case (r_currentState) + START: + begin + if (!i_Switch_1 & !i_Switch_2 & r_Button_notDown) begin + o_Score <= 0; + r_currentLEDPattern <= 0; + r_currentState <= PATTERN_OFF; + end + end + + PATTERN_OFF: + begin + if (!w_IncomingDelay & r_DelayCompleted) begin + r_currentState <= PATTERN_SHOW; + end + end + + PATTERN_SHOW: + begin + if (!w_IncomingDelay & r_DelayCompleted) begin + // this limits the number of + if (o_Score == r_currentLEDPattern) begin + r_currentLEDPattern <= 0; + r_currentState <= WAIT_PLAYER; + end else begin + r_currentLEDPattern <= r_currentLEDPattern + 1; + r_currentState <= PATTERN_OFF; + end + end + end + + WAIT_PLAYER: + begin + if (r_Button_notDown) begin + if ( + r_LEDPattern[r_currentLEDPattern] == r_currentButtonDown && + o_Score == r_currentLEDPattern + ) begin + r_currentLEDPattern <= 0; + r_currentState <= INCREMENT_SCORE; + end else if ( + r_LEDPattern[r_currentLEDPattern] != r_currentButtonDown + ) begin + r_currentState <= PLAYER_LOST; + end else begin + r_currentLEDPattern <= r_currentLEDPattern + 1; + end + end + end + + INCREMENT_SCORE: + begin + o_Score <= o_Score + 1; + if (o_Score == GAME_LIMIT - 1) begin + r_currentState <= PLAYER_WON; + end else begin + r_currentState <= PATTERN_OFF; + end + end + + PLAYER_WON: + begin + o_Score <= 4'hA; + end + + PLAYER_LOST: + begin + o_Score <= 4'hF; + end + + default: + begin + r_currentState <= START; + end + endcase + end + end + + always @(posedge i_Clk) begin + if (r_currentState == START) begin + r_LEDPattern[0] <= w_LFSR_Data[1:0]; + r_LEDPattern[1] <= w_LFSR_Data[3:2]; + r_LEDPattern[2] <= w_LFSR_Data[5:4]; + r_LEDPattern[3] <= w_LFSR_Data[7:6]; + r_LEDPattern[4] <= w_LFSR_Data[9:8]; + r_LEDPattern[5] <= w_LFSR_Data[11:10]; + r_LEDPattern[6] <= w_LFSR_Data[13:12]; + r_LEDPattern[7] <= w_LFSR_Data[15:14]; + r_LEDPattern[8] <= w_LFSR_Data[17:16]; + r_LEDPattern[9] <= w_LFSR_Data[19:18]; + r_LEDPattern[10] <= w_LFSR_Data[21:20]; + end + end + + assign o_LED_1 = ( + (r_currentState == PATTERN_SHOW && r_LEDPattern[r_currentLEDPattern] == 0) ? + 1 : + i_Switch_1 + ); + + assign o_LED_2 = ( + (r_currentState == PATTERN_SHOW && r_LEDPattern[r_currentLEDPattern] == 1) ? + 1 : + i_Switch_2 + ); + + assign o_LED_3 = ( + (r_currentState == PATTERN_SHOW && r_LEDPattern[r_currentLEDPattern] == 2) ? + 1 : + i_Switch_3 + ); + + assign o_LED_4 = ( + (r_currentState == PATTERN_SHOW && r_LEDPattern[r_currentLEDPattern] == 3) ? + 1 : + i_Switch_4 + ); + + always @(posedge i_Clk) begin + r_DelayCompleted <= w_IncomingDelay; + r_Switch_1 <= i_Switch_1; + r_Switch_2 <= i_Switch_2; + r_Switch_3 <= i_Switch_3; + r_Switch_4 <= i_Switch_4; + + if (r_Switch_1 && !i_Switch_1) begin + r_Button_notDown <= 1; + r_currentButtonDown <= 0; + end else if (r_Switch_2 && !i_Switch_2) begin + r_Button_notDown <= 1; + r_currentButtonDown <= 1; + end else if (r_Switch_3 && !i_Switch_3) begin + r_Button_notDown <= 1; + r_currentButtonDown <= 2; + end else if (r_Switch_4 && !i_Switch_4) begin + r_Button_notDown <= 1; + r_currentButtonDown <= 3; + end else begin + r_Button_notDown <= 0; + r_currentButtonDown <= 0; + end + end + + assign w_EnableCounter = (r_currentState == PATTERN_SHOW || r_currentState == PATTERN_OFF); + + Count_And_Toggle #( + .COUNT_LIMIT(CLKS_PER_SECOND/4) + ) Counter ( + .i_Clk(i_Clk), + .i_Enable(w_EnableCounter), + .o_Toggle(w_IncomingDelay) + ); + + LFSR #( + .SIZE(22) + ) MyLFSR ( + .i_Clk(i_Clk), + .o_LFSR_Data(w_LFSR_Data), + .o_LFSR_Done() + ); +endmodule diff --git a/sipo_shift_register_test.sv b/sipo_shift_register_test.sv new file mode 100644 index 0000000..2e9fe05 --- /dev/null +++ b/sipo_shift_register_test.sv @@ -0,0 +1,104 @@ +module SiPo_ShiftRegister_4( + input i_Clk, + input i_Latch, + input i_Data, + input i_Reset, + output o_Data0, + output o_Data1, + output o_Data2, + output o_Data3 + ); + reg [3:0] r_Data; + reg [3:0] r_Incoming; + reg [2:0] r_Pos; + reg r_didLatch; + + always @ (posedge i_Clk & i_Reset) begin + r_Data <= 0; + r_Incoming <= 0; + r_Pos <= 0; + r_didLatch <= 0; + end + + always @ (posedge i_Clk) begin + if (i_Latch & !r_didLatch) begin + r_Incoming[0] <= i_Data; + r_Incoming[3:1] <= r_Incoming[2:0]; + r_Pos <= r_Pos + 1; + r_didLatch <= 1; + end + + if (r_didLatch & !i_Latch) begin + r_didLatch <= 0; + + if (r_Pos == 4) begin + r_Data[3:0] <= r_Incoming[3:0]; + r_Incoming <= 0; + r_Pos <= 0; + end + end + end + + assign o_Data0 = r_Data[0]; + assign o_Data1 = r_Data[1]; + assign o_Data2 = r_Data[2]; + assign o_Data3 = r_Data[3]; +endmodule + +module Test_SIPO(); + task assert(input condition); + if (!condition) $error; + endtask + + reg r_Data = 1'b0, r_Clk = 1'b0, r_Latch = 1'b0, r_Reset = 1'b0; + wire w_Data0, w_Data1, w_Data2, w_Data3; + + always #2 r_Clk <= !r_Clk; + + SiPo_ShiftRegister_4 UUT( + .i_Clk(r_Clk), + .i_Data(r_Data), + .i_Latch(r_Latch), + .i_Reset(r_Reset), + .o_Data0(w_Data0), + .o_Data1(w_Data1), + .o_Data2(w_Data2), + .o_Data3(w_Data3) + ); + + initial begin + $dumpfile("test.vcd"); + $dumpvars; + + r_Data <= 1'b1; + r_Reset <= 1'b1; + @(posedge r_Clk); + + r_Reset <= 1'b0; + @(posedge r_Clk); + + r_Latch <= 1'b1; + @(posedge r_Clk); + r_Latch <= 1'b0; + @(posedge r_Clk); + + r_Data <= 0; + r_Latch <= 1'b1; + @(posedge r_Clk); + r_Latch <= 1'b0; + @(posedge r_Clk); + r_Latch <= 1'b1; + @(posedge r_Clk); + r_Latch <= 1'b0; + @(posedge r_Clk); + r_Data <= 1; + r_Latch <= 1'b1; + @(posedge r_Clk); + r_Latch <= 1'b0; + @(posedge r_Clk); + + #5; + + $finish(); + end +endmodule diff --git a/uart_rx.v b/uart_rx.v new file mode 100644 index 0000000..713cf59 --- /dev/null +++ b/uart_rx.v @@ -0,0 +1,100 @@ +module UART_RX ( + input i_Clk, + input i_ReceiveBit, + output o_DataReady, + output [7:0] o_DataByte +); + parameter CLOCKS_PER_SECOND = 25000000; + parameter BAUD_RATE = 115200; + localparam integer CLOCKS_PER_BIT = CLOCKS_PER_SECOND / BAUD_RATE; + localparam integer HALF_CLOCKS_PER_BIT = (CLOCKS_PER_BIT - 1) / 2; + + localparam IDLE = 0; + localparam VERIFY_START_BIT = 1; + localparam READ_DATA_BITS = 2; + localparam WAIT_FOR_STOP_BIT = 3; + localparam CLEANUP = 4; + + // golang-style: lowercase is private, uppercase is public + reg [$clog2(CLEANUP)-1:0] r_currentState = IDLE; + + reg r_dataReady = 0; + reg [$clog2(CLOCKS_PER_BIT)-1:0] r_clockCount = 0; + reg [$clog2(8)-1:0] r_bitIndex = 0; + reg [7:0] r_receivedByte = 0; + + always @(posedge i_Clk) begin + case (r_currentState) + IDLE: + begin + r_dataReady <= 0; + r_clockCount <= 0; + r_bitIndex <= 0; + + if (i_ReceiveBit == 0) begin + r_currentState <= VERIFY_START_BIT; + end + end + + // Check the middle of the start bit to make sure it's still low + VERIFY_START_BIT: + begin + if (r_clockCount == HALF_CLOCKS_PER_BIT) begin + // still low, let's go + if (i_ReceiveBit == 0) begin + r_clockCount <= 0; + r_currentState <= READ_DATA_BITS; + end else begin + r_currentState <= IDLE; + end + end else begin + r_clockCount <= r_clockCount + 1; + end + end + + // note that we are still in the halfway points of the signal at this + // point. we are sampling the very middles. + READ_DATA_BITS: + begin + if (r_clockCount < CLOCKS_PER_BIT - 1) begin + r_clockCount <= r_clockCount + 1; + end else begin + r_clockCount <= 0; + r_receivedByte[r_bitIndex] <= i_ReceiveBit; + + if (r_bitIndex == 7) begin + r_bitIndex <= 0; + r_currentState <= WAIT_FOR_STOP_BIT; + end else begin + r_bitIndex <= r_bitIndex + 1; + end + end + end + + WAIT_FOR_STOP_BIT: + begin + if (r_clockCount < CLOCKS_PER_BIT - 1) begin + r_clockCount <= r_clockCount + 1; + end else begin + r_dataReady <= 1; + r_clockCount <= 0; + r_currentState <= CLEANUP; + end + end + + CLEANUP: + begin + r_currentState <= IDLE; + r_dataReady <= 0; + end + + default: + begin + r_currentState <= IDLE; + end + endcase + end + + assign o_DataByte = r_receivedByte; + assign o_DataReady = r_dataReady; +endmodule diff --git a/uart_tx.v b/uart_tx.v new file mode 100644 index 0000000..79856c1 --- /dev/null +++ b/uart_tx.v @@ -0,0 +1,98 @@ +module UART_TX ( + input i_Reset_Low, + input i_Clk, + input i_TransmitReady, + input [7:0] i_TransmitByte, + + output reg o_Active, + output reg o_Output, + output reg o_Done +); + parameter CLOCKS_PER_SECOND = 25000000; + parameter BAUD_RATE = 115200; + localparam integer CLOCKS_PER_BIT = CLOCKS_PER_SECOND / BAUD_RATE; + + localparam IDLE = 0; + localparam SEND_START_BIT = 1; + localparam SEND_DATA_BITS = 2; + localparam SEND_STOP_BIT = 3; + + reg [$clog2(SEND_STOP_BIT)-1:0] r_currentState = IDLE; + reg [$clog2(CLOCKS_PER_BIT)-1:0] r_clockCount = 0; + reg [$clog2(8)-1:0] r_bitIndex = 0; + reg [7:0] r_transmitData; + + always @(posedge i_Clk or negedge i_Reset_Low) begin + if (!i_Reset_Low) begin + r_currentState <= IDLE; + end else begin + o_Done <= 0; + + case (r_currentState) + IDLE: + begin + // Drive line high for idle + o_Output <= 1; + r_clockCount <= 0; + r_bitIndex <= 0; + + if (i_TransmitReady) begin + o_Active <= 1; + r_transmitData <= i_TransmitByte; + r_currentState <= SEND_START_BIT; + end + end + + SEND_START_BIT: + begin + o_Output <= 0; + + if (r_clockCount < CLOCKS_PER_BIT - 1) begin + r_clockCount <= r_clockCount + 1; + end else begin + r_clockCount <= 0; + r_currentState <= SEND_DATA_BITS; + end + end + + SEND_DATA_BITS: + begin + o_Output <= r_transmitData[r_bitIndex]; + + if (r_clockCount < CLOCKS_PER_BIT - 1) begin + r_clockCount <= r_clockCount + 1; + end else begin + r_clockCount <= 0; + + if (r_bitIndex == 7) begin + r_bitIndex <= 0; + r_currentState <= SEND_STOP_BIT; + end else begin + r_bitIndex <= r_bitIndex + 1; + end + end + end + + SEND_STOP_BIT: + begin + o_Output <= 1; + + if (r_clockCount < CLOCKS_PER_BIT - 1) begin + r_clockCount <= r_clockCount + 1; + end else begin + o_Done <= 1; + r_clockCount <= 0; + r_currentState <= IDLE; + o_Output <= 0; + end + end + + default: + begin + r_currentState <= IDLE; + end + endcase + end + end +endmodule + diff --git a/vga_add_porches_to_output.v b/vga_add_porches_to_output.v new file mode 100644 index 0000000..c85f270 --- /dev/null +++ b/vga_add_porches_to_output.v @@ -0,0 +1,91 @@ +/** +* The output is buffered as the front/back porches are added, so watch your +* timing for other stuff. +*/ +module VGA_Add_Porches_To_Output( + input i_Clk, + input i_HSync, + input i_VSync, + input [VIDEO_WIDTH-1:0] i_Red, + input [VIDEO_WIDTH-1:0] i_Green, + input [VIDEO_WIDTH-1:0] i_Blue, + + output reg o_HSync, + output reg o_VSync, + output reg [VIDEO_WIDTH-1:0] o_Red, + output reg [VIDEO_WIDTH-1:0] o_Green, + output reg [VIDEO_WIDTH-1:0] o_Blue +); + parameter VIDEO_WIDTH = 3; + + parameter TOTAL_COLUMNS = 800; + parameter TOTAL_ROWS = 525; + + parameter ACTIVE_COLUMNS = 640; + parameter ACTIVE_ROWS = 480; + + //parameter FRONT_PORCH_COUNT_X = 18; + parameter FRONT_PORCH_COUNT_X = 15; + //parameter FRONT_PORCH_COUNT_Y = 10; + parameter FRONT_PORCH_COUNT_Y = 10; + //parameter BACK_PORCH_COUNT_X = 50; + parameter BACK_PORCH_COUNT_X = 53; + //parameter BACK_PORCH_COUNT_Y = 33; + parameter BACK_PORCH_COUNT_Y = 33; + + wire w_HSync, w_VSync; + + wire [$clog2(TOTAL_COLUMNS)-1:0] w_X; + wire [$clog2(TOTAL_ROWS)-1:0] w_Y; + + reg [VIDEO_WIDTH-1:0] r_Red = 0; + reg [VIDEO_WIDTH-1:0] r_Green = 0; + reg [VIDEO_WIDTH-1:0] r_Blue = 0; + + VGA_Current_Beam_Position #( + .TOTAL_COLUMNS(TOTAL_COLUMNS), + .TOTAL_ROWS(TOTAL_ROWS) + ) CurrentPosition ( + .i_Clk(i_Clk), + .i_HSync(i_HSync), + .i_VSync(i_VSync), + .o_HSync(w_HSync), + .o_VSync(w_VSync), + .o_X(w_X), + .o_Y(w_Y) + ); + + // https://web.mit.edu/6.111/www/s2004/NEWKIT/vga.shtml + always @(posedge i_Clk) begin + if ( + (w_X < FRONT_PORCH_COUNT_X + ACTIVE_COLUMNS) || + (w_X > TOTAL_COLUMNS - BACK_PORCH_COUNT_X - 1) + ) begin + o_HSync <= 1; + end else begin + o_HSync <= w_HSync; + end + + if ( + (w_Y < FRONT_PORCH_COUNT_Y + ACTIVE_ROWS) || + (w_Y > TOTAL_ROWS - BACK_PORCH_COUNT_Y - 1) + ) begin + o_VSync <= 1; + end else begin + o_VSync <= w_VSync; + end + end + + // The process of going through Current Beam Position and then + // the porch alignment above delays the video signal by two clock ticks. + // Do the same input -> wire -> output as above. + always @(posedge i_Clk) begin + r_Red <= i_Red; + r_Green <= i_Green; + r_Blue <= i_Blue; + + o_Red <= r_Red; + o_Green <= r_Green; + o_Blue <= r_Blue; + end +endmodule diff --git a/vga_current_beam_position.v b/vga_current_beam_position.v new file mode 100644 index 0000000..f203e5c --- /dev/null +++ b/vga_current_beam_position.v @@ -0,0 +1,47 @@ +// Keys off of HSync and VSync signals to get the actual electron +// beam position + +module VGA_Current_Beam_Position( + input i_Clk, + input i_HSync, + input i_VSync, + + output reg o_HSync = 0, + output reg o_VSync = 0, + + output reg [$clog2(TOTAL_COLUMNS)-1:0] o_X, + output reg [$clog2(TOTAL_ROWS)-1:0] o_Y +); + parameter TOTAL_COLUMNS = 800; + parameter TOTAL_ROWS = 525; + + wire w_frameStart; + + // forward these flip-flop style. use these instead of what + // comes out of sync pulse generator! + always @(posedge i_Clk) begin + o_VSync <= i_VSync; + o_HSync <= i_HSync; + end + + always @(posedge i_Clk) begin + if (w_frameStart == 1) begin + o_X <= 0; + o_Y <= 0; + end else begin + if (o_X == TOTAL_COLUMNS - 1) begin + o_X <= 0; + if (o_Y == TOTAL_ROWS - 1) begin + o_Y <= 0; + end else begin + o_Y <= o_Y + 1; + end + end else begin + o_X <= o_X + 1; + end + end + end + + // rising vsync == new frame + assign w_frameStart = (~o_VSync & i_VSync); +endmodule diff --git a/vga_pattern_generator.v b/vga_pattern_generator.v new file mode 100644 index 0000000..64e53ce --- /dev/null +++ b/vga_pattern_generator.v @@ -0,0 +1,189 @@ +module VGA_Pattern_Generator( + input i_Clk, + input [3:0] i_Pattern, + input i_HSync, + input i_VSync, + + output reg o_HSync = 0, + output reg o_VSync = 0, + output reg [VIDEO_WIDTH-1:0] o_Red, + output reg [VIDEO_WIDTH-1:0] o_Green, + output reg [VIDEO_WIDTH-1:0] o_Blue +); + // Go Board only supports 3 bits per channel. That's even less than the + // Amiga! + parameter VIDEO_WIDTH = 3; + + parameter TOTAL_COLUMNS = 800; + parameter TOTAL_ROWS = 525; + + parameter ACTIVE_COLUMNS = 640; + parameter ACTIVE_ROWS = 480; + + localparam COLOR_BAR_WIDTH = ACTIVE_COLUMNS / 8; + + wire w_HSync, w_VSync; + + wire [VIDEO_WIDTH-1:0] w_Red[0:15]; + wire [VIDEO_WIDTH-1:0] w_Green[0:15]; + wire [VIDEO_WIDTH-1:0] w_Blue[0:15]; + + wire [$clog2(TOTAL_COLUMNS)-1:0] w_X; + wire [$clog2(TOTAL_ROWS)-1:0] w_Y; + + VGA_Current_Beam_Position #( + .TOTAL_COLUMNS(TOTAL_COLUMNS), + .TOTAL_ROWS(TOTAL_ROWS) + ) CurrentPosition ( + .i_Clk(i_Clk), + .i_HSync(i_HSync), + .i_VSync(i_VSync), + .o_HSync(w_HSync), + .o_VSync(w_VSync), + .o_X(w_X), + .o_Y(w_Y) + ); + + // all these pattern operations take one clock tick + // so delay sync signals by that much + always @(posedge i_Clk) begin + o_HSync <= w_HSync; + o_VSync <= w_VSync; + end + + // none more black + assign w_Red[0] = 0; + assign w_Green[0] = 0; + assign w_Blue[0] = 0; + + // red + assign w_Red[1] = ( + (w_X < ACTIVE_COLUMNS && w_Y < ACTIVE_ROWS) ? + {VIDEO_WIDTH{1'b1}} : + 0 + ); + assign w_Green[1] = 0; + assign w_Blue[1] = 0; + + // green + assign w_Red[2] = 0; + assign w_Green[2] = ( + (w_X < ACTIVE_COLUMNS && w_Y < ACTIVE_ROWS) ? + {VIDEO_WIDTH{1'b1}} : + 0 + ); + assign w_Blue[2] = 0; + + // blue + assign w_Red[3] = 0; + assign w_Green[3] = 0; + assign w_Blue[3] = ( + (w_X < ACTIVE_COLUMNS && w_Y < ACTIVE_ROWS) ? + {VIDEO_WIDTH{1'b1}} : + 0 + ); + + // checkerboard + // cell size is 32 pixels + assign w_Red[4] = (w_X[5] ^ w_Y[5]) ? {VIDEO_WIDTH{1'b1}} : 0; + assign w_Green[4] = w_Red[4]; + assign w_Blue[4] = w_Red[4]; + + wire [2:0] w_colorBarSelect; + + // color bars + // yes this is the hardware way to do this b/c multi/div + assign w_colorBarSelect = + w_X < COLOR_BAR_WIDTH ? 0 : + w_X < COLOR_BAR_WIDTH * 2 ? 1 : + w_X < COLOR_BAR_WIDTH * 3 ? 2 : + w_X < COLOR_BAR_WIDTH * 4 ? 3 : + w_X < COLOR_BAR_WIDTH * 5 ? 4 : + w_X < COLOR_BAR_WIDTH * 6 ? 5 : + w_X < COLOR_BAR_WIDTH * 7 ? 6 :7; + + assign w_Red[5] = ( + w_colorBarSelect == 4 || + w_colorBarSelect == 5 || + w_colorBarSelect == 6 || + w_colorBarSelect == 7 + ) ? {VIDEO_WIDTH{1'b1}} : 0; + + assign w_Green[5] = ( + w_colorBarSelect == 2 || + w_colorBarSelect == 3 || + w_colorBarSelect == 6 || + w_colorBarSelect == 7 + ) ? {VIDEO_WIDTH{1'b1}} : 0; + + assign w_Blue[5] = ( + w_colorBarSelect == 1 || + w_colorBarSelect == 3 || + w_colorBarSelect == 5 || + w_colorBarSelect == 7 + ) ? {VIDEO_WIDTH{1'b1}} : 0; + + // border + + localparam BORDER_WIDTH = 2; + + assign w_Red[6] = ( + w_X < BORDER_WIDTH || w_X >= ACTIVE_COLUMNS - BORDER_WIDTH || + w_Y < BORDER_WIDTH || w_Y >= ACTIVE_ROWS - BORDER_WIDTH + ) ? {VIDEO_WIDTH{1'b1}} : 0; + assign w_Green[6] = 0; + assign w_Blue[6] = 0; + + always @(posedge i_Clk) begin + case (i_Pattern) + 0: + begin + o_Red <= w_Red[0]; + o_Green <= w_Green[0]; + o_Blue <= w_Blue[0]; + end + 1: + begin + o_Red <= w_Red[1]; + o_Green <= w_Green[1]; + o_Blue <= w_Blue[1]; + end + 2: + begin + o_Red <= w_Red[2]; + o_Green <= w_Green[2]; + o_Blue <= w_Blue[2]; + end + 3: + begin + o_Red <= w_Red[3]; + o_Green <= w_Green[3]; + o_Blue <= w_Blue[3]; + end + 4: + begin + o_Red <= w_Red[4]; + o_Green <= w_Green[4]; + o_Blue <= w_Blue[4]; + end + 5: + begin + o_Red <= w_Red[5]; + o_Green <= w_Green[5]; + o_Blue <= w_Blue[5]; + end + 6: + begin + o_Red <= w_Red[6]; + o_Green <= w_Green[6]; + o_Blue <= w_Blue[6]; + end + default: + begin + o_Red <= w_Red[0]; + o_Green <= w_Green[0]; + o_Blue <= w_Blue[0]; + end + endcase + end +endmodule diff --git a/vga_sync_pulse_generator.v b/vga_sync_pulse_generator.v new file mode 100644 index 0000000..61d5efb --- /dev/null +++ b/vga_sync_pulse_generator.v @@ -0,0 +1,34 @@ +// Remember, this "magically works" on the Go Board. +// If you're using a different clock speed, you need to +// do a different thing here. +module VGA_Sync_Pulse_Generator ( + input i_Clk, + output o_HSync, + output o_VSync, + + output reg [$clog2(TOTAL_COLUMNS)-1:0] o_rawX, + output reg [$clog2(TOTAL_ROWS)-1:0] o_rawY +); + // remember overscan + parameter ACTIVE_COLUMNS = 640; + parameter ACTIVE_ROWS = 480; + + parameter TOTAL_COLUMNS = 800; + parameter TOTAL_ROWS = 525; + + always @(posedge i_Clk) begin + if (o_rawX == TOTAL_COLUMNS - 1) begin + o_rawX <= 0; + if (o_rawY == TOTAL_ROWS - 1) begin + o_rawY <= 0; + end else begin + o_rawY <= o_rawY + 1; + end + end else begin + o_rawX <= o_rawX + 1; + end + end + + assign o_HSync = (o_rawX < ACTIVE_COLUMNS) ? 1 : 0; + assign o_VSync = (o_rawY < ACTIVE_ROWS) ? 1 : 0; +endmodule diff --git a/vga_tester.v b/vga_tester.v new file mode 100644 index 0000000..7c57da2 --- /dev/null +++ b/vga_tester.v @@ -0,0 +1,219 @@ +module VGA_Tester( + input i_Clk, + + output o_VGA_HSync, + output o_VGA_VSync, + output o_VGA_Red_0, + output o_VGA_Red_1, + output o_VGA_Red_2, + output o_VGA_Grn_0, + output o_VGA_Grn_1, + output o_VGA_Grn_2, + output o_VGA_Blu_0, + output o_VGA_Blu_1, + output o_VGA_Blu_2, + + input i_UART_RX, + output o_UART_TX, + + output o_Segment1_A, + output o_Segment1_B, + output o_Segment1_C, + output o_Segment1_D, + output o_Segment1_E, + output o_Segment1_F, + output o_Segment1_G, + output o_Segment2_A, + output o_Segment2_B, + output o_Segment2_C, + output o_Segment2_D, + output o_Segment2_E, + output o_Segment2_F, + output o_Segment2_G +); + localparam TOTAL_COLUMNS = 800; + localparam TOTAL_ROWS = 525; + + localparam ACTIVE_COLUMNS = 640; + localparam ACTIVE_ROWS = 480; + + localparam VIDEO_WIDTH = 3; + + // vga + wire w_HSync_Start, w_VSync_Start; + wire w_HSync_FromTestPattern, w_VSync_FromTestPattern; + wire w_HSync_FromPorchSync, w_VSync_FromPorchSync; + + wire [VIDEO_WIDTH-1:0] w_Red_FromTestPattern; + wire [VIDEO_WIDTH-1:0] w_Red_FromPorchSync; + wire [VIDEO_WIDTH-1:0] w_Green_FromTestPattern; + wire [VIDEO_WIDTH-1:0] w_Green_FromPorchSync; + wire [VIDEO_WIDTH-1:0] w_Blue_FromTestPattern; + wire [VIDEO_WIDTH-1:0] w_Blue_FromPorchSync; + + reg [3:0] r_currentPattern = 6; + + // uart + wire w_dataReady; + wire [7:0] w_dataByte; + wire w_transmitActive, w_transmitSerial; + + wire w_segment1_A, w_segment2_A; + wire w_segment1_B, w_segment2_B; + wire w_segment1_C, w_segment2_C; + wire w_segment1_D, w_segment2_D; + wire w_segment1_E, w_segment2_E; + wire w_segment1_F, w_segment2_F; + wire w_segment1_G, w_segment2_G; + + reg r_resetTransmit = 1; + + // accept incoming data + + UART_RX #( + .CLOCKS_PER_SECOND(25000000), + .BAUD_RATE(115200) + ) MyUART_RX ( + .i_Clk(i_Clk), + .i_ReceiveBit(i_UART_RX), + .o_DataReady(w_dataReady), + .o_DataByte(w_dataByte) + ); + + UART_TX #( + .CLOCKS_PER_SECOND(25000000), + .BAUD_RATE(115200) + ) MyUART_TX ( + .i_Clk(i_Clk), + .i_Reset_Low(r_resetTransmit), + .i_TransmitReady(w_dataReady), + .i_TransmitByte(w_dataByte), + .o_Active(w_transmitActive), + .o_Output(w_transmitSerial), + .o_Done() + ); + + assign o_UART_TX = w_transmitActive ? w_transmitSerial : 1; + + Binary_to_7_Segment Segment_1 ( + .i_Clk(i_Clk), + .i_Number(w_dataByte[7:4]), + .o_SegA(w_segment1_A), + .o_SegB(w_segment1_B), + .o_SegC(w_segment1_C), + .o_SegD(w_segment1_D), + .o_SegE(w_segment1_E), + .o_SegF(w_segment1_F), + .o_SegG(w_segment1_G) + ); + + assign o_Segment1_A = !w_segment1_A; + assign o_Segment1_B = !w_segment1_B; + assign o_Segment1_C = !w_segment1_C; + assign o_Segment1_D = !w_segment1_D; + assign o_Segment1_E = !w_segment1_E; + assign o_Segment1_F = !w_segment1_F; + assign o_Segment1_G = !w_segment1_G; + + Binary_to_7_Segment Segment_2 ( + .i_Clk(i_Clk), + .i_Number(w_dataByte[3:0]), + .o_SegA(w_segment2_A), + .o_SegB(w_segment2_B), + .o_SegC(w_segment2_C), + .o_SegD(w_segment2_D), + .o_SegE(w_segment2_E), + .o_SegF(w_segment2_F), + .o_SegG(w_segment2_G) + ); + + assign o_Segment2_A = !w_segment2_A; + assign o_Segment2_B = !w_segment2_B; + assign o_Segment2_C = !w_segment2_C; + assign o_Segment2_D = !w_segment2_D; + assign o_Segment2_E = !w_segment2_E; + assign o_Segment2_F = !w_segment2_F; + assign o_Segment2_G = !w_segment2_G; + + always @(posedge i_Clk) begin + if (w_dataReady) begin + r_currentPattern <= w_dataByte[3:0]; + end + end + + // show the video + + VGA_Sync_Pulse_Generator #( + .TOTAL_COLUMNS(TOTAL_COLUMNS), + .TOTAL_ROWS(TOTAL_ROWS), + .ACTIVE_COLUMNS(ACTIVE_COLUMNS), + .ACTIVE_ROWS(ACTIVE_ROWS) + ) SyncGenerator ( + .i_Clk(i_Clk), + .o_HSync(w_HSync_Start), + .o_VSync(w_VSync_Start), + .o_rawX(), + .o_rawY() + ); + + VGA_Pattern_Generator #( + .VIDEO_WIDTH(VIDEO_WIDTH), + .TOTAL_COLUMNS(TOTAL_COLUMNS), + .TOTAL_ROWS(TOTAL_ROWS), + .ACTIVE_COLUMNS(ACTIVE_COLUMNS), + .ACTIVE_ROWS(ACTIVE_ROWS) + ) PatternGenerator ( + .i_Clk(i_Clk), + .i_Pattern(r_currentPattern), + .i_HSync(w_HSync_Start), + .i_VSync(w_VSync_Start), + + .o_HSync(w_HSync_FromTestPattern), + .i_Clk(i_Clk), + .i_Pattern(r_currentPattern), + .i_HSync(w_HSync_Start), + .i_VSync(w_VSync_Start), + + .o_HSync(w_HSync_FromTestPattern), + .o_VSync(w_VSync_FromTestPattern), + .o_Red(w_Red_FromTestPattern), + .o_Green(w_Green_FromTestPattern), + .o_Blue(w_Blue_FromTestPattern) + ); + + VGA_Add_Porches_To_Output #( + .VIDEO_WIDTH(VIDEO_WIDTH), + .TOTAL_COLUMNS(TOTAL_COLUMNS), + .TOTAL_ROWS(TOTAL_ROWS), + .ACTIVE_COLUMNS(ACTIVE_COLUMNS), + .ACTIVE_ROWS(ACTIVE_ROWS) + ) PorchBuilder ( + .i_Clk(i_Clk), + .i_HSync(w_HSync_FromTestPattern), + .i_VSync(w_VSync_FromTestPattern), + .i_Red(w_Red_FromTestPattern), + .i_Green(w_Green_FromTestPattern), + .i_Blue(w_Blue_FromTestPattern), + + .o_HSync(w_HSync_FromPorchSync), + .o_VSync(w_VSync_FromPorchSync), + .o_Red(w_Red_FromPorchSync), + .o_Green(w_Green_FromPorchSync), + .o_Blue(w_Blue_FromPorchSync) + ); + + assign o_VGA_HSync = w_HSync_FromPorchSync; + assign o_VGA_VSync = w_VSync_FromPorchSync; + + assign o_VGA_Red_0 = w_Red_FromPorchSync[0]; + assign o_VGA_Red_1 = w_Red_FromPorchSync[1]; + assign o_VGA_Red_2 = w_Red_FromPorchSync[2]; + + assign o_VGA_Grn_0 = w_Green_FromPorchSync[0]; + assign o_VGA_Grn_1 = w_Green_FromPorchSync[1]; + assign o_VGA_Grn_2 = w_Green_FromPorchSync[2]; + + assign o_VGA_Blu_0 = w_Blue_FromPorchSync[0]; + assign o_VGA_Blu_1 = w_Blue_FromPorchSync[1]; + assign o_VGA_Blu_2 = w_Blue_FromPorchSync[2]; +endmodule