Communication

UART receiver

uart_rx

Serial receiver for 8 data bits, no parity, and 1 stop bit. The line passes a two-flop synchronizer and each bit is sampled in its middle. valid pulses for one cycle with the byte; framing_error pulses instead when the stop bit is low.

Verified by Tapeo187 generic cells at the defaultsUARTserial input

Instantiate it

Paste into your design and connect the signals.

systemverilog
uart_rx #(.CLKS_PER_BIT(868)) u_uart_rx (
  .clk(clk), .rst(rst),
  .rx(uart_rx_pin),
  .data(received_byte), .valid(byte_valid), .framing_error(rx_error)
);

Parameters

NameDefaultMeaning
CLKS_PER_BIT868Clock cycles per bit: clock frequency divided by baud rate.

Ports

NameDirectionWidthMeaning
clkinput1Clock; everything changes on its rising edge.
rstinput1Synchronous reset, active high.
rxinput1Serial line (asynchronous; synchronized inside).
dataoutput8The last byte received.
validoutput1Pulses for one cycle when data holds a new byte.
framing_erroroutput1Pulses when a byte ended with a low stop bit.

Add it to a project

It is copied to ip/uart_rx/uart_rx.sv with a README (ip/uart_rx/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 (187 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_rx.sv
// UART receiver, 8N1, LSB first. The input passes a two-flop synchronizer and
// each bit is sampled in its middle. `valid` pulses for one cycle with `data`;
// `framing_error` pulses instead when the stop bit is low. Tapeo Hub (verified by Tapeo).
module uart_rx #(
  parameter integer CLKS_PER_BIT = 868  // clock frequency / baud rate
) (
  input  logic       clk,
  input  logic       rst,
  input  logic       rx,
  output logic [7:0] data,
  output logic       valid,
  output logic       framing_error
);
  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);
  localparam logic [COUNT_WIDTH-1:0] HALF_TICK = COUNT_WIDTH'((CLKS_PER_BIT - 1) / 2);

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

  always_ff @(posedge clk) begin
    if (rst) sync <= 2'b11;
    else sync <= {sync[0], rx};
  end

  always_ff @(posedge clk) begin
    if (rst) begin
      state <= IDLE;
      ticks <= '0;
      bit_index <= '0;
      shift <= '0;
      data <= '0;
      valid <= 1'b0;
      framing_error <= 1'b0;
    end else begin
      valid <= 1'b0;
      framing_error <= 1'b0;
      case (state)
        IDLE: if (!line) begin ticks <= '0; state <= START; end
        START: begin
          if (ticks == HALF_TICK) begin
            ticks <= '0;
            bit_index <= '0;
            state <= line ? IDLE : DATA;  // a glitch, not a start bit
          end else ticks <= ticks + 1'b1;
        end
        DATA: begin
          if (ticks == LAST_TICK) begin
            ticks <= '0;
            shift <= {line, shift[7:1]};
            if (bit_index == 3'd7) state <= STOP;
            else bit_index <= bit_index + 1'b1;
          end else ticks <= ticks + 1'b1;
        end
        STOP: begin
          if (ticks == LAST_TICK) begin
            ticks <= '0;
            state <= IDLE;
            if (line) begin data <= shift; valid <= 1'b1; end
            else framing_error <= 1'b1;
          end else ticks <= ticks + 1'b1;
        end
        default: state <= IDLE;
      endcase
    end
  end
endmodule

← Every block in Tapeo Hub