diff options
| author | Jan Tuomi <jan@jantuomi.fi> | 2025-03-18 22:34:54 +0200 |
|---|---|---|
| committer | Jan Tuomi <jan@jantuomi.fi> | 2025-03-18 22:34:54 +0200 |
| commit | 1294b00c0941085b1abe782be65edfe90dafb04e (patch) | |
| tree | e8a7d31f328ddaf99ebe685c432ecdf291d5a4b2 | |
| parent | 182c35d5aee98ab2afa27c2e84427206edb9a9ca (diff) | |
Fix UART
| -rw-r--r-- | atk16_fpga/top.v | 179 | ||||
| -rw-r--r-- | atk16_fpga/uart_rx.v | 87 | ||||
| -rw-r--r-- | atk16_fpga/uart_tx.v | 46 |
3 files changed, 191 insertions, 121 deletions
diff --git a/atk16_fpga/top.v b/atk16_fpga/top.v index df53491..c67ade2 100644 --- a/atk16_fpga/top.v +++ b/atk16_fpga/top.v @@ -1,157 +1,94 @@ -// UART transmitter module. -// Transmits a frame: start bit (0), 8 data bits (LSB first), stop bit (1) -// at a baud rate determined by CLKS_PER_BIT (here: 10417 for 100MHz/9600) -module uart_tx ( - input clk, // system clock: 100 MHz - input rst, // synchronous reset (active high) - input start, // one-cycle pulse to start transmission - input [7:0] data, // data byte to send - output reg tx, // serial output - output reg busy // high while transmitting the byte -); - // 100e6/9600 ≈ 10417 cycles per bit. - parameter CLKS_PER_BIT = 10417; - - reg [13:0] clk_count; // counter for baud tick (14 bits is enough) - reg [ 3:0] bit_index; // counts from 0 to 9 (10 bits total: start, 8 data, stop) - reg [ 9:0] tx_frame; // complete frame: {stop bit, data[7:0], start bit} - - always @(posedge clk) begin - if (rst) begin - busy <= 1'b0; - tx <= 1'b1; // idle state is high - clk_count <= 0; - bit_index <= 0; - end else begin - if (!busy) begin - if (start) begin - // Load frame: start bit (0), 8 data bits (LSB first), stop bit (1) - tx_frame <= {1'b1, data, 1'b0}; - busy <= 1'b1; - clk_count <= 0; - bit_index <= 0; - tx <= 1'b0; // send start bit immediately - end else begin - tx <= 1'b1; // remain idle - end - end else begin - // When busy, count clocks for each bit period. - if (clk_count < CLKS_PER_BIT - 1) clk_count <= clk_count + 1; - else begin - clk_count <= 0; - bit_index <= bit_index + 1; - if (bit_index < 9) tx <= tx_frame[bit_index+1]; - else begin - busy <= 1'b0; - tx <= 1'b1; // return to idle - end - end - end - end - end -endmodule - // Top-level module for the iCE40 HX8K FPGA. // This module instantiates the UART transmitter and sends the bytes for // "Hello" and a newline character, then stops. module top ( input SYSCLK, // 100 MHz system clock - input SERIAL_RX, // Unused in this design; available as a pin - output SERIAL_TX // UART transmit output + input SERIAL_RX, // UART receive input + output SERIAL_TX, // UART transmit output + output LED1, + output LED2 ); - // Create a simple synchronous reset signal. - // Here, we use a counter that holds reset high for a few hundred cycles. - reg [7:0] reset_cnt; - reg sys_rst; - always @(posedge SYSCLK) begin - if (reset_cnt != 8'd255) begin - reset_cnt <= reset_cnt + 1; - sys_rst <= 1'b1; - end else begin - sys_rst <= 1'b0; - end - end - - // ROM for the message "Hello\n" (6 bytes) - reg [7:0] message[0:5]; - initial begin - message[0] = "H"; // ASCII 72 - message[1] = "e"; // ASCII 101 - message[2] = "l"; // ASCII 108 - message[3] = "l"; // ASCII 108 - message[4] = "o"; // ASCII 111 - message[5] = "\n"; // ASCII 10 (newline) - end - - // Pointer to index through the message. - reg [2:0] index; // One-cycle pulse to trigger transmission. - reg start_tx; + reg start_tx = 1'b0; // Data byte to be transmitted. - reg [7:0] data_to_send; + reg [7:0] data_to_send = 8'b0; // Busy flag from the transmitter. wire tx_busy; + reg [7:0] letter_a = 8'd97; + // State machine states. - localparam IDLE = 2'd0, WAIT_BUSY = 2'd1, WAIT_IDLE = 2'd2, DONE = 2'd3; - reg [1:0] state; + localparam WAIT_RX_BUSY = 3'd0; + localparam WAIT_RX_DONE = 3'd1; + localparam START_TX_1 = 3'd2; + localparam START_TX_2 = 3'd3; + localparam WAIT_TX_DONE = 3'd4; + + reg [2:0] state = WAIT_RX_BUSY; // Synchronous state machine with reset. always @(posedge SYSCLK) begin - if (sys_rst) begin - // Initialize on reset. - state <= IDLE; - index <= 0; - start_tx <= 1'b0; - data_to_send <= 8'd0; - end else begin - case (state) - IDLE: begin - start_tx <= 1'b0; - if (index < 3'd6) begin - if (!tx_busy) begin - data_to_send <= message[index]; - start_tx <= 1'b1; // trigger transmission - state <= WAIT_BUSY; - end - end else begin - state <= DONE; - end + case (state) + WAIT_RX_BUSY: begin + if (!rx_done) begin + state <= WAIT_RX_DONE; end + end - // After issuing the start pulse, wait for tx_busy to go high. - WAIT_BUSY: begin - start_tx <= 1'b0; // ensure pulse is only one cycle. - if (tx_busy) state <= WAIT_IDLE; + WAIT_RX_DONE: begin + if (rx_done) begin + state <= START_TX_1; end + end - // Wait for the transmitter to finish sending the byte. - WAIT_IDLE: begin - if (!tx_busy) begin - index <= index + 1; // move to next byte - state <= IDLE; - end + START_TX_1: begin + if (!tx_busy) begin + //data_to_send <= letter_a; + data_to_send <= data_to_receive; + start_tx <= 1'b1; // trigger transmission + state <= START_TX_2; end + end + + // After issuing the start pulse, wait for tx_busy to go high. + START_TX_2: begin + start_tx <= 1'b0; // ensure pulse is only one cycle. + state <= WAIT_TX_DONE; + end - DONE: begin - start_tx <= 1'b0; - // Remain in DONE state; no further transmissions. + // Wait for the transmitter to finish sending the byte. + WAIT_TX_DONE: begin + if (!tx_busy) begin + state <= WAIT_RX_BUSY; end + end + + default: state <= WAIT_RX_BUSY; + endcase - default: state <= IDLE; - endcase - end end // Instantiate the UART transmitter. uart_tx uart_inst ( .clk (SYSCLK), - .rst (sys_rst), // Use our synchronous reset signal. .start(start_tx), // One-cycle pulse to start transmission. .data (data_to_send), .tx (SERIAL_TX), .busy (tx_busy) ); + wire [7:0] data_to_receive; + wire rx_done; + + uart_rx uart_rx_inst ( + .clk (SYSCLK), + .rx (SERIAL_RX), + .data(data_to_receive), + .done(rx_done) + ); + + assign LED1 = data_to_receive == letter_a ? 1 : 0; + assign LED2 = LED1; + endmodule diff --git a/atk16_fpga/uart_rx.v b/atk16_fpga/uart_rx.v new file mode 100644 index 0000000..5d6129e --- /dev/null +++ b/atk16_fpga/uart_rx.v @@ -0,0 +1,87 @@ +module uart_rx ( + input wire clk, // System clock: 100 MHz + input wire rx, // Serial RX line + output reg [7:0] data, // Received data byte + output reg done // Goes high for one clock cycle when a frame is received +); + + // CLK / baudrate cycles per bit. + //parameter CLKS_PER_BIT = 10417; // 9600 baud rate + parameter CLKS_PER_BIT = 868; // 115200 baud rate + + // Define states for the state machine. + localparam STATE_IDLE = 2'd0; + localparam STATE_START = 2'd1; + localparam STATE_DATA = 2'd2; + localparam STATE_STOP = 2'd3; + + reg [ 1:0] state = STATE_IDLE; + reg [13:0] clk_count = 14'd0; + reg [ 2:0] bit_index = 3'd0; // Will count 0 to 7 for the 8 data bits. + reg [ 7:0] rx_shift_reg = 8'd0; + + // Synchronous state machine. + always @(posedge clk) begin + case (state) + STATE_IDLE: begin + done <= 1'b0; + clk_count <= 14'd0; + bit_index <= 3'd0; + if (rx == 1'b0) begin + // Detected start bit. Go to START state. + state <= STATE_START; + end else begin + state <= STATE_IDLE; + end + end + + STATE_START: begin + // Wait for half a bit period, then sample the start bit. + if (clk_count == (CLKS_PER_BIT - 1) / 2) begin + // Sample start bit: it must be 0. + if (rx == 1'b0) begin + clk_count <= 14'd0; + state <= STATE_DATA; + end else begin + // False start; return to idle. + state <= STATE_IDLE; + end + end else begin + clk_count <= clk_count + 1; + end + end + + STATE_DATA: begin + // Wait for a full bit period then sample the data bit. + if (clk_count < CLKS_PER_BIT - 1) begin + clk_count <= clk_count + 1; + end else begin + clk_count <= 14'd0; + // Sample the current data bit. + rx_shift_reg[bit_index] <= rx; + if (bit_index == 3'd7) begin + state <= STATE_STOP; + end else begin + bit_index <= bit_index + 1; + end + end + end + + STATE_STOP: begin + // Wait one bit period for the stop bit. + if (clk_count < CLKS_PER_BIT - 1) begin + clk_count <= clk_count + 1; + end else begin + clk_count <= 14'd0; + // Optionally, you could check that rx is high (stop bit). + data <= rx_shift_reg; // Latch the received data. + done <= 1'b1; // Signal that a frame has been received. + state <= STATE_IDLE; // Go back to idle for the next frame. + end + end + + default: state <= STATE_IDLE; + endcase + end + +endmodule diff --git a/atk16_fpga/uart_tx.v b/atk16_fpga/uart_tx.v new file mode 100644 index 0000000..a91c726 --- /dev/null +++ b/atk16_fpga/uart_tx.v @@ -0,0 +1,46 @@ +// UART transmitter module. +// Transmits a frame: start bit (0), 8 data bits (LSB first), stop bit (1) +// at a baud rate determined by CLKS_PER_BIT (here: 10417 for 100MHz/9600) +module uart_tx ( + input clk, // system clock: 100 MHz + input start, // one-cycle pulse to start transmission + input [7:0] data, // data byte to send + output reg tx = 1'b1, // serial TX line + output reg busy = 1'b0 // high while transmitting the byte +); + + // CLK / baudrate cycles per bit. + //parameter CLKS_PER_BIT = 10417; // 9600 baud rate + parameter CLKS_PER_BIT = 868; // 115200 baud rate + + reg [13:0] clk_count = 14'b0; // counter for baud tick (14 bits is enough) + reg [ 3:0] bit_index = 4'b0; // counts from 0 to 9 (10 bits total: start, 8 data, stop) + reg [ 9:0] tx_frame; // complete frame: {stop bit, data[7:0], start bit} + + always @(posedge clk) begin + if (!busy) begin + if (start) begin + // Load frame: start bit (0), 8 data bits (LSB first), stop bit (1) + tx_frame <= {1'b1, data, 1'b0}; + busy <= 1'b1; + clk_count <= 0; + bit_index <= 0; + tx <= 1'b0; // send start bit immediately + end else begin + tx <= 1'b1; // remain idle + end + end else begin + // When busy, count clocks for each bit period. + if (clk_count < CLKS_PER_BIT - 1) clk_count <= clk_count + 1; + else begin + clk_count <= 0; + bit_index <= bit_index + 1; + if (bit_index < 9) tx <= tx_frame[bit_index+1]; + else begin + busy <= 1'b0; + tx <= 1'b1; // return to idle + end + end + end + end +endmodule |
