
The tt_um_nuatlabs_uart design is a full-duplex, 8N1 (8 data bits, no parity, 1 stop bit) Universal Asynchronous Receiver-Transmitter (UART) peripheral hardened on the 130nm IHP SG13G2 BiCMOS process via Tiny Tapeout. It fits into a single standard tile (1x1) and features a runtime time-multiplexed bidirectional data bus that allows full 8-bit parallel data transfer using Tiny Tapeout's 8 shared GPIO pins (uio).
+---------------------------------------+
| tt_um_nuatlabs_uart |
| |
ui_in[0] (rxd) -->|--> [2-Stage Sync] --> [UART RX] |
| | |
| rx_data |
| | |
ui_in[1] (tx_st)->|---------------------> [UART TX] |
| | |
| txd -------->|--> uo_out[0] (txd)
| tx_busy ------>|--> uo_out[1] (tx_busy)
| rx_valid ----->|--> uo_out[2] (rx_valid)
| |
ui_in[2] -------->|-- [Bus Multiplexer & Dir Control] |
(uio_load_mode) | | ^ |
| v | |
| tx_data_bus rx_data |
| | | |
+-------|----------------------|--------+
v |
uio[7:0] (Input) uio[7:0] (Output)
UART Transmitter (uart_tx):
ST_IDLE, ST_START, ST_DATA, ST_STOP).tx_data from the uio_in bus upon detecting a single-cycle high pulse on ui_in[1] (tx_start).uo_out[1] (tx_busy) high throughout transmission.uo_out[0] (txd).BAUD_DIV = f_clk / baud_rate.UART Receiver (uart_rx):
ui_in[0] (rxd) passes through a 2-stage flip-flop synchronizer (rxd_sync1, rxd_sync2) to eliminate metastability risks.BAUD_DIV / 2), providing maximum noise immunity and tolerance against clock jitter.rx_data and asserts uo_out[2] (rx_valid) for exactly one clock cycle.Time-Multiplexed Bidirectional Data Bus (uio[7:0]):
ui_in[2] (uio_load_mode) to dynamically govern the direction of the bidirectional uio bank:
uio_load_mode = 1): Sets uio_oe = 8'h00 (input mode). The external host or microcontroller presents the 8-bit byte to transmit on uio_in[7:0].uio_load_mode = 0): Sets uio_oe = 8'hFF (output mode). The chip drives the last successfully received byte onto uio_out[7:0].The hardware divider is parameterized via BAUD_DIV = f_clk / baud_rate:
BAUD_DIV is defaulted to 4 for rapid, deterministic simulation in Cocotb.Run the automated test suite from the repository root:
cd test
make clean
make SIM=icarus
The testbench validates 3 comprehensive scenarios:
test_uart_tx_shape: Checks bit timing and serial frame shape: start bit (0), 8 data bits (LSB-first), stop bit (1), and active duration of tx_busy.test_uart_loopback_single_byte: Emulates an external jumper wire by dynamically copying uo_out[0] (txd) to ui_in[0] (rxd) cycle-by-cycle, verifying full transmission, reception, and reconstruction.test_uart_loopback_multiple_bytes: Stress tests back-to-back transmissions across boundary and alternating values (0x00, 0xFF, 0x55, 0xAA, 0x81, 0x7E).uo[0] (txd) and pin ui[0] (rxd).ui[2] (uio_load_mode) to 1 (input mode).uio[7:0] to the desired 8-bit test byte (e.g., 0xA5 / 10100101).ui[1] (tx_start) high then low.uo[1] (tx_busy) LED toggle high while transmitting.uo[2] (rx_valid) LED pulse when reception completes.ui[2] (uio_load_mode) to 0 (output mode).uio[7:0] will display the received byte, matching the byte sent in step 3.ui[0] (rxd)uo[0] (txd) -> PC RXDuo[0] to ui[0].| # | Input | Output | Bidirectional |
|---|---|---|---|
| 0 | rxd (serial input) | txd (serial output) | tx_data[0] (in) / rx_data[0] (out) |
| 1 | tx_start (transmit start pulse) | tx_busy (transmitter busy flag) | tx_data[1] (in) / rx_data[1] (out) |
| 2 | uio_load_mode (1=load tx_data, 0=show rx_data) | rx_valid (receiver valid pulse) | tx_data[2] (in) / rx_data[2] (out) |
| 3 | tx_data[3] (in) / rx_data[3] (out) | ||
| 4 | tx_data[4] (in) / rx_data[4] (out) | ||
| 5 | tx_data[5] (in) / rx_data[5] (out) | ||
| 6 | tx_data[6] (in) / rx_data[6] (out) | ||
| 7 | tx_data[7] (in) / rx_data[7] (out) |