Communication

UART transmitter

uart_tx

Serial transmitter with 8 data bits, no parity, and 1 stop bit, least significant bit first. Pulse start for one cycle while busy is low to send data; done pulses after the stop bit. Pair it with uart_rx for a full UART, or connect tx to a board's USB-serial pin.

Verified by Tapeo124 generic cells at the defaultsUARTserial output

Instantiate it

Paste into your design and connect the signals.

systemverilog
uart_tx #(.CLKS_PER_BIT(868)) u_uart_tx (
  .clk(clk), .rst(rst),
  .start(send), .data(byte_to_send),
  .tx(uart_tx_pin), .busy(tx_busy), .done(tx_done)
);

Parameters

NameDefaultMeaning
CLKS_PER_BIT868Clock cycles per bit: clock frequency divided by baud rate (100 MHz / 115200 = 868).

Ports

NameDirectionWidthMeaning
clkinput1Clock; everything changes on its rising edge.
rstinput1Synchronous reset, active high.
startinput1Pulse for one cycle to send data (ignored while busy).
datainput8The byte to send, read when start is high.
txoutput1Serial line; idles high.
busyoutput1High while a byte is being sent.
doneoutput1Pulses for one cycle after the stop bit.

Add it to a project

It is copied to ip/uart_tx/uart_tx.sv with a README (ip/uart_tx/README.md). When your design needs it, the Architect (new designs) or the Architect (changes) suggests it and asks before using it. Or add it yourself:

How it is verified

Before every release Tapeo checks this block: its self-checking testbench passes in Icarus Verilog, Verilator -Wall reports nothing, and Yosys synthesizes it (124 generic cells at the default parameters). That shows it works for the tested cases; your copy is part of your project, and your own testbench checks it in your design.

Source

uart_tx.sv
// UART transmitter, 8 data bits, no parity, 1 stop bit (8N1), LSB first.
// Pulse `start` for one cycle while `busy` is low to send `data`; `done`
// pulses for one cycle after the stop bit. Tapeo Hub (verified by Tapeo).
module uart_tx #(
  parameter integer CLKS_PER_BIT = 868  // clock frequency / baud rate, for example 100 MHz / 115200
) (
  input  logic       clk,
  input  logic       rst,
  input  logic       start,
  input  logic [7:0] data,
  output logic       tx,
  output logic       busy,
  output logic       done
);
  localparam integer COUNT_WIDTH = (CLKS_PER_BIT > 1) ? $clog2(CLKS_PER_BIT) : 1;
  localparam logic [COUNT_WIDTH-1:0] LAST_TICK = COUNT_WIDTH'(CLKS_PER_BIT - 1);

  typedef enum logic [1:0] { IDLE, START, DATA, STOP } state_t;
  state_t state;
  logic [COUNT_WIDTH-1:0] ticks;
  logic [2:0] bit_index;
  logic [7:0] shift;

  always_ff @(posedge clk) begin
    if (rst) begin
      state <= IDLE;
      ticks <= '0;
      bit_index <= '0;
      shift <= '0;
      tx <= 1'b1;
      done <= 1'b0;
    end else begin
      done <= 1'b0;
      case (state)
        IDLE: begin
          tx <= 1'b1;
          if (start) begin
            shift <= data;
            ticks <= '0;
            tx <= 1'b0;
            state <= START;
          end
        end
        START: begin
          if (ticks == LAST_TICK) begin
            ticks <= '0;
            bit_index <= '0;
            tx <= shift[0];
            state <= DATA;
          end else ticks <= ticks + 1'b1;
        end
        DATA: begin
          if (ticks == LAST_TICK) begin
            ticks <= '0;
            if (bit_index == 3'd7) begin
              tx <= 1'b1;
              state <= STOP;
            end else begin
              bit_index <= bit_index + 1'b1;
              tx <= shift[bit_index + 1'b1];
            end
          end else ticks <= ticks + 1'b1;
        end
        STOP: begin
          if (ticks == LAST_TICK) begin
            ticks <= '0;
            done <= 1'b1;
            state <= IDLE;
          end else ticks <= ticks + 1'b1;
        end
        default: state <= IDLE;
      endcase
    end
  end

  assign busy = (state != IDLE);
endmodule

← Every block in Tapeo Hub