Skip to content

Instantly share code, notes, and snippets.

@ellisgl
Created June 15, 2025 21:30
Show Gist options
  • Save ellisgl/a024220617a74cc03f2a2c1c9366444d to your computer and use it in GitHub Desktop.
Save ellisgl/a024220617a74cc03f2a2c1c9366444d to your computer and use it in GitHub Desktop.
UART / Serial String Reversal in Verilog (Tang Nano 9k)
IO_LOC "clk" 52;
IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=UP;
IO_LOC "uart_tx" 17;
IO_PORT "uart_tx" IO_TYPE=LVCMOS33;
IO_LOC "uart_rx" 18;
IO_PORT "uart_rx" IO_TYPE=LVCMOS33;
`default_nettype none
module top
#(
// 27,000,000 (27Mhz) / 115200 Baud rate
parameter DELAY_FRAMES = 234
)
(
input clk,
input uart_rx,
output uart_tx,
output reg [5:0] led
);
// UART RX signals
wire [7:0] rx_data;
wire rx_data_valid;
// UART TX signals
reg [7:0] tx_data;
reg tx_data_valid;
wire tx_busy;
// Instantiate UART RX module
uart_rx #(
.DELAY_FRAMES(DELAY_FRAMES)
) uart_rx_inst (
.clk(clk),
.uart_rx(uart_rx),
.data_out(rx_data),
.data_valid(rx_data_valid)
);
// Instantiate UART TX module
uart_tx #(
.DELAY_FRAMES(DELAY_FRAMES)
) uart_tx_inst (
.clk(clk),
.data_in(tx_data),
.data_valid(tx_data_valid),
.uart_tx(uart_tx),
.tx_busy(tx_busy)
);
// Buffer for received characters
localparam BUF_SIZE = 128;
reg [7:0] buffer [0:BUF_SIZE-1];
reg [7:0] buf_len = 0;
// State machine states
localparam STATE_RECEIVING = 2'b00;
localparam STATE_REVERSING = 2'b01;
localparam STATE_TRANSMITTING = 2'b10;
reg [1:0] state = STATE_RECEIVING;
reg [7:0] tx_index = 0;
reg [3:0] delay_counter = 0;
reg tx_started = 0;
// Main state machine
always @(posedge clk) begin
tx_data_valid <= 0; // Default
case (state)
STATE_RECEIVING: begin
if (rx_data_valid) begin
if (rx_data == 8'h0A || rx_data == 8'h0D) begin
// Newline received - start reversing
if (buf_len > 0) begin
state <= STATE_REVERSING;
end
// If buffer is empty, stay in receiving state
end else if (buf_len < BUF_SIZE) begin
// Store the character
buffer[buf_len] <= rx_data;
buf_len <= buf_len + 1;
// If buffer is full, start reversing
if (buf_len + 1 >= BUF_SIZE) begin
state <= STATE_REVERSING;
end
end
// If buffer is full and we get a non-newline, just ignore it
end
end
STATE_REVERSING: begin
// Wait for TX to be completely idle and add a small delay
if (!tx_busy) begin
if (delay_counter < 8) begin
delay_counter <= delay_counter + 1;
end else begin
state <= STATE_TRANSMITTING;
tx_index <= 0;
delay_counter <= 0;
tx_started <= 0;
end
end else begin
delay_counter <= 0; // Reset counter while TX is busy
end
end
STATE_TRANSMITTING: begin
if (tx_index < buf_len) begin
if (!tx_started && !tx_busy) begin
// Start transmitting next byte
tx_data <= buffer[buf_len - 1 - tx_index];
tx_data_valid <= 1;
tx_started <= 1;
end else if (tx_started && !tx_busy) begin
// Previous byte finished, move to next
tx_index <= tx_index + 1;
tx_started <= 0;
end
end else begin
// Done transmitting, reset for next message
state <= STATE_RECEIVING;
buf_len <= 0;
tx_index <= 0;
tx_started <= 0;
end
end
default: begin
state <= STATE_RECEIVING;
end
endcase
end
// Update LED with received data
always @(posedge clk) begin
if (rx_data_valid) begin
led <= ~rx_data[5:0];
end
end
endmodule
`default_nettype none
module uart_rx
#(
parameter DELAY_FRAMES = 234
)
(
input wire clk,
input wire uart_rx,
output reg [7:0] data_out,
output reg data_valid
);
localparam HALF_DELAY_WAIT = (DELAY_FRAMES / 2);
reg [3:0] rxState = 0;
reg [12:0] rxCounter = 0;
reg [7:0] dataIn = 0;
reg [2:0] rxBitNumber = 0;
localparam RX_STATE_IDLE = 0;
localparam RX_STATE_START_BIT = 1;
localparam RX_STATE_READ_WAIT = 2;
localparam RX_STATE_READ = 3;
localparam RX_STATE_STOP_BIT = 5;
always @(posedge clk) begin
data_valid <= 0; // Default to no valid data
case (rxState)
RX_STATE_IDLE: begin
if (uart_rx == 0) begin
rxState <= RX_STATE_START_BIT;
rxCounter <= 1;
rxBitNumber <= 0;
end
end
RX_STATE_START_BIT: begin
if (rxCounter == HALF_DELAY_WAIT) begin
rxState <= RX_STATE_READ_WAIT;
rxCounter <= 1;
end else
rxCounter <= rxCounter + 1;
end
RX_STATE_READ_WAIT: begin
rxCounter <= rxCounter + 1;
if ((rxCounter + 1) == DELAY_FRAMES) begin
rxState <= RX_STATE_READ;
end
end
RX_STATE_READ: begin
rxCounter <= 1;
dataIn <= {uart_rx, dataIn[7:1]};
rxBitNumber <= rxBitNumber + 1;
if (rxBitNumber == 3'b111)
rxState <= RX_STATE_STOP_BIT;
else
rxState <= RX_STATE_READ_WAIT;
end
RX_STATE_STOP_BIT: begin
rxCounter <= rxCounter + 1;
if ((rxCounter + 1) == DELAY_FRAMES) begin
rxState <= RX_STATE_IDLE;
rxCounter <= 0;
data_out <= dataIn;
data_valid <= 1;
end
end
endcase
end
endmodule
`default_nettype none
module uart_tx
#(
parameter DELAY_FRAMES = 234
)
(
input wire clk,
input wire [7:0] data_in,
input wire data_valid,
output reg uart_tx,
output wire tx_busy
);
reg [3:0] txState = 0;
reg [24:0] txCounter = 0;
reg [7:0] dataOut = 0;
reg [2:0] txBitNumber = 0;
localparam TX_STATE_IDLE = 0;
localparam TX_STATE_START_BIT = 1;
localparam TX_STATE_WRITE = 2;
localparam TX_STATE_STOP_BIT = 3;
assign tx_busy = (txState != TX_STATE_IDLE);
always @(posedge clk) begin
case (txState)
TX_STATE_IDLE: begin
uart_tx <= 1;
if (data_valid) begin
dataOut <= data_in;
txState <= TX_STATE_START_BIT;
txCounter <= 0;
txBitNumber <= 0;
end
end
TX_STATE_START_BIT: begin
uart_tx <= 0;
if ((txCounter + 1) == DELAY_FRAMES) begin
txState <= TX_STATE_WRITE;
txBitNumber <= 0;
txCounter <= 0;
end else
txCounter <= txCounter + 1;
end
TX_STATE_WRITE: begin
uart_tx <= dataOut[txBitNumber];
if ((txCounter + 1) == DELAY_FRAMES) begin
if (txBitNumber == 3'b111) begin
txState <= TX_STATE_STOP_BIT;
end else begin
txState <= TX_STATE_WRITE;
txBitNumber <= txBitNumber + 1;
end
txCounter <= 0;
end else
txCounter <= txCounter + 1;
end
TX_STATE_STOP_BIT: begin
uart_tx <= 1;
if ((txCounter + 1) == DELAY_FRAMES) begin
txState <= TX_STATE_IDLE;
txCounter <= 0;
end else
txCounter <= txCounter + 1;
end
default: txState <= TX_STATE_IDLE;
endcase
end
endmodule
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment