Skip to content

Commit 4cea2fe

Browse files
committed
UART Cocotb testbench intial work
1 parent 07b55f4 commit 4cea2fe

4 files changed

Lines changed: 378 additions & 25 deletions

File tree

src/tt_um_utoss_riscv.sv

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ module tt_um_utoss_riscv (
5252
)
5353
u_uart (
5454
.clk ( clk )
55-
, .rst ( rst_n )
55+
, .rst ( ~rst_n )
5656
, .i_data_s ( uart_tx_data )
5757
, .i_valid_s ( uart_tx_valid )
5858
, .o_ready_s ( uart_tx_ready )
@@ -75,7 +75,7 @@ module tt_um_utoss_riscv (
7575

7676
uart_bus_master u_master (
7777
.clk ( clk )
78-
, .rst ( rst_n )
78+
, .rst ( ~rst_n )
7979
, .rx_data ( uart_rx_data )
8080
, .rx_valid ( uart_rx_valid )
8181
, .rx_ready ( uart_rx_ready )
@@ -91,7 +91,7 @@ module tt_um_utoss_riscv (
9191
, .hold_core ( hold_core )
9292
);
9393

94-
wire core_reset = rst_n | hold_core;
94+
wire core_reset = ~rst_n | hold_core;
9595

9696
addr_t bus_addr;
9797
data_t bus_write_data;
@@ -112,7 +112,7 @@ module tt_um_utoss_riscv (
112112

113113
utoss_riscv core
114114
( .clk ( clk )
115-
, .reset ( ~core_reset )
115+
, .reset ( core_reset )
116116
, .memory__address ( core_addr )
117117
, .memory__write_data ( core_write_data )
118118
, .memory__write_enable( core_write_enable )

test/Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ SIM ?= icarus
66
FST ?= -fst # Use more efficient FST format
77
TOPLEVEL_LANG ?= verilog
88
SRC_DIR = $(PWD)/../src
9-
PROJECT_SOURCES = tt_um_utoss_riscv.sv MA.sv utoss-risc-v/src/ALU_ALUdecoder/ALU.sv utoss-risc-v/src/ALU_ALUdecoder/ALUdecoder.sv utoss-risc-v/src/ControlFSM.sv utoss-risc-v/src/fetch.sv utoss-risc-v/src/Instruction_Decode/Instruction_Decode.sv utoss-risc-v/src/Instruction_Decode/MemoryLoader.sv utoss-risc-v/src/Instruction_Decode/registerFile.v utoss-risc-v/src/params.svh utoss-risc-v/src/types.svh utoss-risc-v/src/utils.svh utoss-risc-v/src/utoss_riscv.sv
9+
PROJECT_SOURCES = tt_um_utoss_riscv.sv MA.sv utoss-risc-v/src/ALU_ALUdecoder/ALU.sv utoss-risc-v/src/ALU_ALUdecoder/ALUdecoder.sv utoss-risc-v/src/ControlFSM.sv utoss-risc-v/src/fetch.sv utoss-risc-v/src/Instruction_Decode/Instruction_Decode.sv utoss-risc-v/src/Instruction_Decode/MemoryLoader.sv utoss-risc-v/src/Instruction_Decode/RegisterFile.sv utoss-risc-v/src/params.svh utoss-risc-v/src/types.svh utoss-risc-v/src/utils.svh utoss-risc-v/src/uart.sv utoss-risc-v/src/uart_tx.sv utoss-risc-v/src/uart_rx.sv utoss-risc-v/src/uart_bus_master.sv utoss-risc-v/src/utoss_riscv.sv
1010
COMPILE_ARGS += -DUTOSS_RISCV_HARDENING
1111

1212
ifneq ($(GATES),yes)
1313

1414
# RTL simulation:
1515
SIM_BUILD = sim_build/rtl
1616
VERILOG_SOURCES += $(addprefix $(SRC_DIR)/,$(PROJECT_SOURCES))
17+
ifeq ($(SIM),verilator)
18+
COMPILE_ARGS += --timing -Wno-fatal -Wno-WIDTHTRUNC -Wno-WIDTHEXPAND
19+
endif
1720

1821
else
1922

test/test.py

Lines changed: 252 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,268 @@
1-
# SPDX-FileCopyrightText: © 2024 Tiny Tapeout
2-
# SPDX-License-Identifier: Apache-2.0
1+
"""Cocotb integration tests for the UART-to-RISC-V core bridge.
2+
3+
The top-level tt_um_utoss_riscv module exposes a UART command interface that
4+
can halt and run the core, write and read memory, and read architectural
5+
registers. These tests exercise that protocol end-to-end.
6+
"""
37

48
import cocotb
59
from cocotb.clock import Clock
6-
from cocotb.triggers import ClockCycles
10+
from cocotb.triggers import ClockCycles, ReadOnly, RisingEdge
711

812

9-
@cocotb.test()
10-
async def test_project(dut):
11-
dut._log.info("Start")
13+
CLK_HZ = 50_000_000
14+
BAUD = 115_200
15+
CLOCKS_PER_BIT = CLK_HZ // BAUD
1216

13-
# Set the clock period to 10 us (100 KHz)
14-
clock = Clock(dut.clk, 10, unit="us")
15-
cocotb.start_soon(clock.start())
17+
UART_SOF = 0xA5
18+
UART_RESP_SOF = 0x5A
19+
20+
CMD_WR32 = 0x10
21+
CMD_RD32 = 0x11
22+
CMD_RUN = 0x12
23+
CMD_HALT = 0x13
24+
CMD_RDREG = 0x14
25+
26+
R_ACK = 0x90
27+
R_RD = 0x91
28+
R_RDREG = 0x92
29+
30+
STATUS_OK = 0x00
31+
32+
33+
def encode_i_type(imm, rs1, funct3, rd, opcode=0x13):
34+
imm = imm & 0xFFF
35+
return ((imm & 0xFFF) << 20) | ((rs1 & 0x1F) << 15) | ((funct3 & 0x7) << 12) | ((rd & 0x1F) << 7) | (opcode & 0x7F)
36+
37+
38+
def encode_r_type(funct7, rs2, rs1, funct3, rd, opcode=0x33):
39+
return ((funct7 & 0x7F) << 25) | ((rs2 & 0x1F) << 20) | ((rs1 & 0x1F) << 15) | ((funct3 & 0x7) << 12) | ((rd & 0x1F) << 7) | (opcode & 0x7F)
40+
41+
42+
def encode_s_type(imm, rs2, rs1, funct3, opcode=0x23):
43+
imm = imm & 0xFFF
44+
imm_hi = (imm >> 5) & 0x7F
45+
imm_lo = imm & 0x1F
46+
return ((imm_hi & 0x7F) << 25) | ((rs2 & 0x1F) << 20) | ((rs1 & 0x1F) << 15) | ((funct3 & 0x7) << 12) | ((imm_lo & 0x1F) << 7) | (opcode & 0x7F)
47+
48+
49+
def encode_j_type(imm, rd, opcode=0x6F):
50+
imm = imm & 0x1FFFFF
51+
bit20 = (imm >> 20) & 0x1
52+
bit10_1 = (imm >> 1) & 0x3FF
53+
bit11 = (imm >> 11) & 0x1
54+
bit19_12 = (imm >> 12) & 0xFF
55+
return (
56+
(bit20 << 31)
57+
| (bit19_12 << 12)
58+
| (bit11 << 20)
59+
| (bit10_1 << 21)
60+
| ((rd & 0x1F) << 7)
61+
| (opcode & 0x7F)
62+
)
63+
64+
65+
def word_to_bytes(value):
66+
return [
67+
value & 0xFF,
68+
(value >> 8) & 0xFF,
69+
(value >> 16) & 0xFF,
70+
(value >> 24) & 0xFF,
71+
]
72+
73+
74+
def checksum(bytes_):
75+
value = 0
76+
for byte in bytes_:
77+
value ^= byte & 0xFF
78+
return value & 0xFF
79+
80+
81+
class UartBridge:
82+
def __init__(self, dut):
83+
self.dut = dut
84+
self.rx_buffer = []
85+
86+
def set_rxd_idle(self):
87+
self.dut.ui_in.value = 0x08
88+
89+
def set_rxd(self, level):
90+
self.dut.ui_in.value = 0x08 if level else 0x00
91+
92+
async def send_byte(self, value):
93+
self.set_rxd(1)
94+
await ClockCycles(self.dut.clk, CLOCKS_PER_BIT)
95+
96+
self.set_rxd(0)
97+
await ClockCycles(self.dut.clk, CLOCKS_PER_BIT)
98+
99+
for bit_index in range(8):
100+
self.set_rxd((value >> bit_index) & 1)
101+
await ClockCycles(self.dut.clk, CLOCKS_PER_BIT)
102+
103+
self.set_rxd(1)
104+
await ClockCycles(self.dut.clk, CLOCKS_PER_BIT)
105+
106+
async def monitor_tx(self):
107+
previous_bit = 1
108+
109+
while True:
110+
await RisingEdge(self.dut.clk)
111+
await ReadOnly()
112+
113+
current_bit = (int(self.dut.uo_out.value) >> 4) & 1
114+
if previous_bit == 1 and current_bit == 0:
115+
await ClockCycles(self.dut.clk, CLOCKS_PER_BIT + (CLOCKS_PER_BIT // 2))
116+
117+
value = 0
118+
for bit_index in range(8):
119+
current_bit = (int(self.dut.uo_out.value) >> 4) & 1
120+
value |= current_bit << bit_index
121+
await ClockCycles(self.dut.clk, CLOCKS_PER_BIT)
122+
123+
await ClockCycles(self.dut.clk, CLOCKS_PER_BIT)
124+
stop_bit = (int(self.dut.uo_out.value) >> 4) & 1
125+
assert stop_bit == 1, "UART stop bit was not high"
126+
self.rx_buffer.append(value)
16127

17-
# Reset
18-
dut._log.info("Reset")
128+
previous_bit = current_bit
129+
130+
async def recv_byte(self):
131+
while not self.rx_buffer:
132+
await RisingEdge(self.dut.clk)
133+
134+
return self.rx_buffer.pop(0)
135+
136+
async def transact(self, payload, expected_response_len):
137+
for byte in [UART_SOF, *payload]:
138+
await self.send_byte(byte)
139+
140+
response = []
141+
for _ in range(expected_response_len):
142+
response.append(await self.recv_byte())
143+
return response
144+
145+
async def halt_core(self):
146+
response = await self.transact([CMD_HALT, CMD_HALT], 4)
147+
self._check_ack(response, CMD_HALT)
148+
return response
149+
150+
async def run_core(self):
151+
response = await self.transact([CMD_RUN, CMD_RUN], 4)
152+
self._check_ack(response, CMD_RUN)
153+
return response
154+
155+
async def write32(self, addr, value):
156+
payload = [CMD_WR32]
157+
payload.extend(word_to_bytes(addr))
158+
payload.extend(word_to_bytes(value))
159+
payload.append(checksum(payload))
160+
response = await self.transact(payload, 4)
161+
self._check_ack(response, CMD_WR32)
162+
163+
async def read32(self, addr):
164+
payload = [CMD_RD32]
165+
payload.extend(word_to_bytes(addr))
166+
payload.append(checksum(payload))
167+
response = await self.transact(payload, 7)
168+
self._check_read(response, R_RD)
169+
return self._bytes_to_word(response[2:6])
170+
171+
async def read_reg(self, reg_index):
172+
payload = [CMD_RDREG, reg_index & 0x1F]
173+
payload.append(checksum(payload))
174+
response = await self.transact(payload, 7)
175+
self._check_read(response, R_RDREG)
176+
return self._bytes_to_word(response[2:6])
177+
178+
def _check_ack(self, response, command):
179+
assert response[0] == UART_RESP_SOF, f"Expected response SOF 0x{UART_RESP_SOF:02x}"
180+
assert response[1] == R_ACK, f"Expected ACK response 0x{R_ACK:02x}"
181+
assert response[2] == STATUS_OK, f"Expected OK status, got 0x{response[2]:02x}"
182+
assert response[3] == (R_ACK ^ STATUS_OK), "ACK checksum mismatch"
183+
184+
def _check_read(self, response, response_type):
185+
assert response[0] == UART_RESP_SOF, f"Expected response SOF 0x{UART_RESP_SOF:02x}"
186+
assert response[1] == response_type, f"Expected response type 0x{response_type:02x}"
187+
assert response[6] == checksum(response[1:6]), "Read checksum mismatch"
188+
189+
@staticmethod
190+
def _bytes_to_word(data_bytes):
191+
return (
192+
(data_bytes[0] & 0xFF)
193+
| ((data_bytes[1] & 0xFF) << 8)
194+
| ((data_bytes[2] & 0xFF) << 16)
195+
| ((data_bytes[3] & 0xFF) << 24)
196+
)
197+
198+
199+
async def reset_dut(dut):
19200
dut.ena.value = 1
20-
dut.ui_in.value = 0
21201
dut.uio_in.value = 0
202+
dut.ui_in.value = 0x08
22203
dut.rst_n.value = 0
23204
await ClockCycles(dut.clk, 10)
24205
dut.rst_n.value = 1
206+
await ClockCycles(dut.clk, 10)
207+
208+
209+
@cocotb.test()
210+
async def test_uart_core_bridge(dut):
211+
dut._log.info("Starting UART-to-core integration test")
212+
213+
clock = Clock(dut.clk, 20, unit="ns")
214+
cocotb.start_soon(clock.start())
215+
216+
await reset_dut(dut)
217+
218+
bridge = UartBridge(dut)
219+
cocotb.start_soon(bridge.monitor_tx())
220+
design = dut.dut
221+
222+
assert int(design.u_master.hold_core.value) == 1, "Core should start held after reset"
223+
assert (int(dut.uo_out.value) & 0x10) != 0, "UART TX should idle high after reset"
224+
225+
dut._log.info("Halting core through UART")
226+
await bridge.halt_core()
227+
assert int(design.u_master.hold_core.value) == 1, "HALT should keep the core held"
228+
229+
program = [
230+
encode_i_type(32, 0, 0x0, 1), # addi x1, x0, 32
231+
encode_i_type(0x123, 0, 0x0, 2), # addi x2, x0, 0x123
232+
encode_s_type(0, 2, 1, 0x2), # sw x2, 0(x1)
233+
encode_i_type(0, 1, 0x2, 3, 0x03), # lw x3, 0(x1)
234+
encode_r_type(0x00, 3, 2, 0x0, 4), # add x4, x2, x3
235+
encode_j_type(0, 0), # jal x0, 0
236+
]
237+
238+
for index, instruction in enumerate(program):
239+
address = index * 4
240+
dut._log.info("Writing instruction 0x%08x to address 0x%08x", instruction, address)
241+
await bridge.write32(address, instruction)
242+
243+
dut._log.info("Verifying instruction memory via UART readback")
244+
fetched = await bridge.read32(0)
245+
assert fetched == program[0], f"Instruction readback mismatch: 0x{fetched:08x} != 0x{program[0]:08x}"
246+
247+
dut._log.info("Releasing core through UART RUN command")
248+
await bridge.run_core()
249+
assert int(design.u_master.hold_core.value) == 0, "RUN should release the core"
250+
251+
await ClockCycles(dut.clk, 1200)
252+
253+
dut._log.info("Re-halt the core after execution")
254+
await bridge.halt_core()
255+
assert int(design.u_master.hold_core.value) == 1, "HALT should reassert the hold signal"
25256

26-
dut._log.info("Test project behavior")
257+
reg_x3 = await bridge.read_reg(3)
258+
reg_x4 = await bridge.read_reg(4)
259+
mem_word = await bridge.read32(32)
27260

28-
# Wait for one clock cycle to see the output values
29-
await ClockCycles(dut.clk, 100)
261+
assert reg_x3 == 0x123, f"x3 mismatch: expected 0x00000123, got 0x{reg_x3:08x}"
262+
assert reg_x4 == 0x246, f"x4 mismatch: expected 0x00000246, got 0x{reg_x4:08x}"
263+
assert mem_word == 0x123, f"Memory mismatch: expected 0x00000123, got 0x{mem_word:08x}"
30264

31-
# The following assersion is just an example of how to check the output values.
32-
# Change it to match the actual expected output of your module:
33-
assert dut.uo_out.value != 0
265+
pc = int(design.core.dbg_pc.value)
266+
assert pc != 0, "Program counter did not advance after RUN"
34267

35-
# Keep testing the module by changing the input values, waiting for
36-
# one or more clock cycles, and asserting the expected output values.
268+
dut._log.info("UART/core bridge test completed successfully")

0 commit comments

Comments
 (0)