diff --git a/Makefile b/Makefile index a2a6505..27e9832 100644 --- a/Makefile +++ b/Makefile @@ -142,6 +142,7 @@ MODULES += phy_core MODULES += pmd_dp83223 MODULES += pmd_dp83223_rx MODULES += scramble +MODULES += uart_tx MODULES += wb_mux .PHONY: test diff --git a/README.adoc b/README.adoc index e313cda..ee06fff 100644 --- a/README.adoc +++ b/README.adoc @@ -200,6 +200,11 @@ nebulously-specified, and limited-in-number iCE40 PLLs. This module implements a scrambler as described in ANSI X3.264-1995 section 7.1.1. +=== `uart_tx` + +A standard UART transmit module, accepting AXI-stream. 8n1 only. Supports +115,200 and 4,000,000 baud. + === `wb_mux` This implements a simple Wishbone mux, allowing a single master to access diff --git a/rtl/uart_tx.v b/rtl/uart_tx.v new file mode 100644 index 0000000..6e35d81 --- /dev/null +++ b/rtl/uart_tx.v @@ -0,0 +1,74 @@ +// 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_tx ( + input clk, + + input [7:0] data, + output reg ready, + input valid, + + output reg tx, + + /* Run at 4M for testing */ + input high_speed +); + + /* 1085 cycles, for 115200 baud with a 125 MHz clock */ + parameter SLOW_VALUE = 11'h78c; + /* 31 cycles, for 4M baud with a 125 MHz clock */ + parameter FAST_VALUE = 11'h68e; + + reg [7:0] data_last; + reg valid_last, ready_next; + reg [10:0] lfsr, lfsr_next; + reg [3:0] counter, counter_next; + reg [8:0] bits, bits_next; + + initial begin + ready = 1'b1; + valid_last = 1'b0; + bits = 9'h1ff; + end + + always @(*) begin + tx = bits[0]; + + ready_next = ready; + counter_next = counter; + lfsr_next = { lfsr[9:0], lfsr[10] ^ lfsr[8] }; + bits_next = bits; + + if (&lfsr) begin + if (counter) + counter_next = counter - 1; + else + ready_next = 1; + lfsr_next = high_speed ? FAST_VALUE : SLOW_VALUE; + bits_next = { 1'b1, bits[8:1] }; + end + + if (valid_last && ready) begin + ready_next = 0; + counter_next = 9; + lfsr_next = high_speed ? FAST_VALUE : SLOW_VALUE; + bits_next = { data_last, 1'b0 }; + end + end + + always @(posedge clk) begin + data_last <= data; + ready <= ready_next; + valid_last <= valid; + counter <= counter_next; + lfsr <= lfsr_next; + bits <= bits_next; + end + +endmodule diff --git a/tb/axis_replay_buffer.py b/tb/axis_replay_buffer.py index 2fa740b..d1b34a6 100644 --- a/tb/axis_replay_buffer.py +++ b/tb/axis_replay_buffer.py @@ -25,7 +25,8 @@ async def send_packet(signals, packet, ratio=1, last_extra=0): else: signals['data'].value = val signals['valid'].value = 1 - signals['last'].value = last + if 'last' in signals: + signals['last'].value = last await RisingEdge(signals['clk']) while True: await FallingEdge(signals['clk']) diff --git a/tb/uart_tx.py b/tb/uart_tx.py new file mode 100644 index 0000000..f2275ba --- /dev/null +++ b/tb/uart_tx.py @@ -0,0 +1,56 @@ +# 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 send_packet + +BAUD = 4e6 +BIT_STEPS = get_sim_steps(1 / BAUD, 'sec', round_mode='round') + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_tx(uart): + uart.clk.value = BinaryValue('Z') + uart.valid.value = 0 + uart.high_speed.value = BAUD == 4e6 + + await Timer(1) + await cocotb.start(Clock(uart.clk, 8, units='ns').start()) + await FallingEdge(uart.clk) + + msg = b"Hello" + + await cocotb.start(send_packet({ + 'clk': uart.clk, + 'data': uart.data, + 'valid': uart.valid, + 'ready': uart.ready, + }, msg)) + + async def getchar(): + while not uart.tx.value: + await FallingEdge(uart.clk) + while uart.tx.value: + await FallingEdge(uart.clk) + await Timer(BIT_STEPS // 2) + + result = 0 + for _ in range(8): + await Timer(BIT_STEPS) + result >>= 1 + result |= 0x80 if uart.tx.value else 0 + + return result + + then = get_sim_time() + for c in msg: + assert c == await getchar() + now = get_sim_time() + + expected = BIT_STEPS * (10 * len(msg) - 1.5) + actual = now - then + assert abs(actual - expected) / expected < 0.01