From a549fca957ffc21b5a016e277b1cdd006ee3f697 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Tue, 28 Feb 2023 22:19:22 -0500 Subject: [PATCH] Add UART receive module Add the recieve half of the UART. It's more or less the inverse of the transmit half, except we manage the state explicitly. I originally did this in hopes that yosys would recode the FSM, but it doesn't like the subtraction in the D* states. I left in the async reset anyway since it reduces the LUT count. Signed-off-by: Sean Anderson --- Makefile | 1 + README.adoc | 6 +++ rtl/uart_rx.v | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++ tb/uart_rx.py | 80 +++++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 rtl/uart_rx.v create mode 100644 tb/uart_rx.py diff --git a/Makefile b/Makefile index 27e9832..d3cd15e 100644 --- a/Makefile +++ b/Makefile @@ -143,6 +143,7 @@ MODULES += pmd_dp83223 MODULES += pmd_dp83223_rx MODULES += scramble MODULES += uart_tx +MODULES += uart_rx MODULES += wb_mux .PHONY: test diff --git a/README.adoc b/README.adoc index ee06fff..28d2ef5 100644 --- a/README.adoc +++ b/README.adoc @@ -205,6 +205,12 @@ This module implements a scrambler as described in ANSI X3.264-1995 section A standard UART transmit module, accepting AXI-stream. 8n1 only. Supports 115,200 and 4,000,000 baud. +=== `uart_rx` + +A standard UART receive module, outputting AXI-stream. 8n1 only. Supports +115,200 and 4,000,000 baud. Properly detects breaks as (single) frame errors, +and ignores runt start bits. + === `wb_mux` This implements a simple Wishbone mux, allowing a single master to access diff --git a/rtl/uart_rx.v b/rtl/uart_rx.v new file mode 100644 index 0000000..0aa2511 --- /dev/null +++ b/rtl/uart_rx.v @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: AGPL-3.0-Only +/* + * Copyright (C) 2022 Sean Anderson + * + * 8n1@115200; no one uses anything else (and neither do I) + */ + +`include "common.vh" + +module uart_rx ( + input clk, + input rst, + + output reg [7:0] data, + input ready, + output reg valid, + + input rx, + + /* Run at 4M for testing */ + input high_speed, + /* No one ready */ + output reg overflow, + /* Missing stop bit */ + output reg frame_error +); + + /* 1085 cycles, for 115200 baud with a 125 MHz clock */ + parameter SLOW_FULL = 11'h78c; + parameter SLOW_HALF = 11'h202; + /* 31 cycles, for 4M baud with a 125 MHz clock */ + parameter FAST_FULL = 11'h68e; + parameter FAST_HALF = 11'h34c; + + localparam ERROR = 11; + localparam IDLE = 10; + localparam START = 9; + localparam D0 = 8; + localparam D1 = 7; + localparam D2 = 6; + localparam D3 = 5; + localparam D4 = 4; + localparam D5 = 3; + localparam D6 = 2; + localparam D7 = 1; + localparam STOP = 0; + + reg [3:0] state, state_next; + reg [10:0] lfsr, lfsr_next; + reg [7:0] bits, bits_next, data_next; + reg ready_last; + reg valid_next, overflow_next, frame_error_next; + + always @(*) begin + state_next = state; + lfsr_next = { lfsr[9:0], lfsr[10] ^ lfsr[8] }; + bits_next = bits; + data_next = data; + valid_next = valid && !ready_last; + overflow_next = 0; + frame_error_next = 0; + + case (state) + IDLE: if (!rx) begin + state_next = START; + lfsr_next = high_speed ? FAST_HALF : SLOW_HALF; + end + START: if (&lfsr) begin + state_next = rx ? IDLE : D0; + lfsr_next = high_speed ? FAST_FULL : SLOW_FULL; + end + D0, D1, D2, D3, D4, D5, D6, D7: if (&lfsr) begin + lfsr_next = high_speed ? FAST_FULL : SLOW_FULL; + bits_next = { rx, bits[7:1] }; + state_next = state - 1; + end + STOP: if (&lfsr) begin + lfsr_next = high_speed ? FAST_FULL : SLOW_FULL; + if (rx) begin + state_next = IDLE; + if (valid_next) + overflow_next = 1; + else + data_next = bits; + valid_next = 1; + end else begin + frame_error_next = 1; + state_next = ERROR; + end + end + ERROR: if (&lfsr) begin + lfsr_next = high_speed ? FAST_FULL : SLOW_FULL; + if (rx) + state_next = IDLE; + end + endcase + end + + always @(posedge clk) begin + lfsr <= lfsr_next; + bits <= bits_next; + data <= data_next; + end + + always @(posedge clk, posedge rst) begin + if (rst) begin + state <= IDLE; + valid <= 0; + ready_last <= 0; + overflow <= 0; + frame_error <= 0; + end else begin + state <= state_next; + valid <= valid_next; + ready_last <= ready; + overflow <= overflow_next; + frame_error <= frame_error_next; + end + end + +endmodule diff --git a/tb/uart_rx.py b/tb/uart_rx.py new file mode 100644 index 0000000..f924251 --- /dev/null +++ b/tb/uart_rx.py @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: AGPL-3.0-Only +# Copyright (C) 2023 Sean Anderson + +import cocotb +from cocotb.binary import BinaryValue +from cocotb.clock import Clock +from cocotb.triggers import FallingEdge, Timer +from cocotb.utils import get_sim_time, get_sim_steps + +from .axis_replay_buffer import recv_packet + +BAUD = 4e6 +BIT_STEPS = get_sim_steps(1 / BAUD, 'sec', round_mode='round') + +def as_bits(c): + for _ in range(8): + yield c & 1 + c >>= 1 + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_rx(uart): + uart.clk.value = BinaryValue('Z') + uart.rst.value = 1 + uart.ready.value = 1 + uart.rx.value = 1 + uart.high_speed.value = 1 + + await Timer(1) + uart.rst.value = 0 + await cocotb.start(Clock(uart.clk, 8, units='ns').start()) + await FallingEdge(uart.clk) + + async def putchar(c): + for bit in (0, *as_bits(c), 1): + uart.rx.value = bit + await Timer(BIT_STEPS) + + msg = b"Hell\0" + signals = { + 'clk': uart.clk, + 'data': uart.data, + 'valid': uart.valid, + 'ready': uart.ready, + } + + await cocotb.start(recv_packet(signals, msg)) + for c in msg: + await putchar(c) + + overflows = 0 + frame_errors = 0 + + async def count_errors(): + nonlocal overflows + nonlocal frame_errors + while True: + await FallingEdge(uart.clk) + overflows += uart.overflow.value + frame_errors += uart.frame_error.value + + monitor = await cocotb.start(count_errors()) + + uart.rx.value = 0 + await Timer(BIT_STEPS * 20) + + uart.rx.value = 1 + await Timer(BIT_STEPS) + + assert frame_errors == 1 + + uart.ready.value = 0 + await putchar(0xFF) + await putchar(0) + + assert overflows == 1 + + uart.ready.value = 1 + await recv_packet(signals, (0xFF,)) + + monitor.kill()