Skip to content

Commit 6334832

Browse files
kgugalarobertszczepanski
authored andcommitted
Add bus timer top level test
Signed-off-by: Karol Gugala <kgugala@antmicro.com>
1 parent 28f11cc commit 6334832

6 files changed

Lines changed: 322 additions & 0 deletions

File tree

verification/cocotb/noxfile.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ def i2c_target_fsm_verify(session, test_group, test_name, coverage, simulator):
303303
"test_interrupts",
304304
"test_enter_exit_hdr_mode",
305305
"test_bus_stall",
306+
"test_bus_timers",
306307
"test_target_reset",
307308
"test_ccc",
308309
"test_csr_access",
@@ -331,6 +332,7 @@ def i3c_ahb_verify(session, test_group, test_name, coverage, simulator):
331332
"test_interrupts",
332333
"test_enter_exit_hdr_mode",
333334
"test_bus_stall",
335+
"test_bus_timers",
334336
"test_target_reset",
335337
"test_ccc",
336338
"test_csr_access",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../lib_i3c_top/test_bus_timers.py
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../lib_i3c_top/test_bus_timers.py
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
"""
4+
Bus timers top-level tests.
5+
6+
Verifies that the bus_timers module correctly:
7+
- Starts the counter on STOP conditions
8+
- Resets the counter on START/RSTART conditions
9+
- Resets the counter when entering HDR mode (in_hdr_mode_i held high)
10+
"""
11+
12+
import logging
13+
import random
14+
15+
from boot import boot_init
16+
from bus2csr import int2dword
17+
from i3c_controller_fixed import I3cControllerFixed as I3cController
18+
from interface import I3CTopTestInterface
19+
20+
import cocotb
21+
from cocotb.triggers import ClockCycles
22+
23+
from common import VALID_I3C_ADDRESSES, log_seed
24+
25+
# =============================================================================
26+
# Constants
27+
# =============================================================================
28+
29+
ENTHDR0 = 0x20
30+
31+
# Small timer thresholds so tests run quickly (unit: DUT clock cycles)
32+
T_FREE = 5
33+
T_AVAL = 15
34+
T_IDLE = 30
35+
36+
# Hierarchy path to the xbus_timers instance
37+
BUS_TIMERS_PATH = (
38+
"xi3c_wrapper.i3c.xcontroller.xcontroller_standby"
39+
".xcontroller_standby_i3c.xbus_timers"
40+
)
41+
42+
# Hierarchy path to the target FSM state register
43+
FSM_STATE_PATH = (
44+
"xi3c_wrapper.i3c.xcontroller.xcontroller_standby"
45+
".xcontroller_standby_i3c.xi3c_target_fsm.state_q"
46+
)
47+
48+
FSM_STATE_IDLE = 0
49+
FSM_STATE_IN_HDR_MODE = 19
50+
51+
52+
# =============================================================================
53+
# Helpers
54+
# =============================================================================
55+
56+
async def test_setup(dut, dynamic_addr=None):
57+
"""Set up controller and top-level core interface.
58+
59+
No external target BFM is needed for bus timer tests; the DUT itself
60+
processes all bus conditions.
61+
"""
62+
cocotb.log.setLevel(logging.DEBUG)
63+
log_seed(dut)
64+
65+
i3c_controller = I3cController(
66+
sda_i=dut.bus_sda,
67+
sda_o=dut.sda_sim_ctrl_i,
68+
scl_i=dut.bus_scl,
69+
scl_o=dut.scl_sim_ctrl_i,
70+
debug_state_o=None,
71+
speed=12.5e6,
72+
)
73+
74+
dut.sda_sim_target_i.setimmediatevalue(1)
75+
dut.scl_sim_target_i.setimmediatevalue(1)
76+
77+
tb = I3CTopTestInterface(dut)
78+
await tb.setup()
79+
await ClockCycles(tb.clk, 50)
80+
await boot_init(tb, static_addr=0x5A, virtual_static_addr=0x5B,
81+
dynamic_addr=dynamic_addr)
82+
return i3c_controller, tb
83+
84+
85+
async def set_timer_thresholds(tb, t_free, t_aval, t_idle):
86+
"""Override bus timer CSR thresholds with test-specific values."""
87+
await tb.write_csr(
88+
tb.reg_map.I3C_EC.SOCMGMTIF.T_FREE_REG.base_addr, int2dword(t_free), 4)
89+
await tb.write_csr(
90+
tb.reg_map.I3C_EC.SOCMGMTIF.T_AVAL_REG.base_addr, int2dword(t_aval), 4)
91+
await tb.write_csr(
92+
tb.reg_map.I3C_EC.SOCMGMTIF.T_IDLE_REG.base_addr, int2dword(t_idle), 4)
93+
94+
95+
def _bt_sig(dut, name):
96+
"""Return a bus_timers signal handle by name, using a full cocotb path."""
97+
return getattr(dut, BUS_TIMERS_PATH + "." + name)
98+
99+
100+
def read_bus_state(dut):
101+
"""Return current bus timer outputs and counter value as a dict."""
102+
return {
103+
"counter": int(_bt_sig(dut, "bus_state_counter").value),
104+
"busy": int(_bt_sig(dut, "bus_busy_o").value),
105+
"free": int(_bt_sig(dut, "bus_free_o").value),
106+
"available": int(_bt_sig(dut, "bus_available_o").value),
107+
"idle": int(_bt_sig(dut, "bus_idle_o").value),
108+
}
109+
110+
111+
def get_fsm_state(dut):
112+
return int(getattr(dut, FSM_STATE_PATH).value)
113+
114+
115+
# =============================================================================
116+
# Tests
117+
# =============================================================================
118+
119+
@cocotb.test()
120+
async def test_bus_timers_stop_starts_counter(dut):
121+
"""STOP condition starts the bus timer counter; states advance correctly.
122+
123+
Verifies the counter transitions through busy → free → available → idle
124+
after a STOP condition is detected. Before any STOP the counter must be
125+
at 0 with bus_busy asserted.
126+
"""
127+
i3c_controller, tb = await test_setup(dut)
128+
await set_timer_thresholds(tb, T_FREE, T_AVAL, T_IDLE)
129+
130+
# Before STOP: counter=0, bus_busy=1 (count_enable has not been set yet)
131+
state = read_bus_state(dut)
132+
assert state["counter"] == 0, \
133+
f"Expected counter=0 before STOP, got {state['counter']}"
134+
assert state["busy"] == 1, \
135+
f"Expected bus_busy=1 before STOP, got {state['busy']}"
136+
assert state["free"] == 0, \
137+
f"Expected bus_free=0 before STOP, got {state['free']}"
138+
139+
# Drive START then STOP on the bus to start the timer
140+
await i3c_controller.send_start()
141+
await i3c_controller.send_stop()
142+
143+
# Wait long enough for all thresholds to be passed
144+
await ClockCycles(tb.clk, T_IDLE + 20)
145+
146+
state = read_bus_state(dut)
147+
assert state["free"] == 1, \
148+
f"Expected bus_free=1 after {T_FREE} cycles, got {state['free']}"
149+
assert state["available"] == 1, \
150+
f"Expected bus_available=1 after {T_AVAL} cycles, got {state['available']}"
151+
assert state["idle"] == 1, \
152+
f"Expected bus_idle=1 after {T_IDLE} cycles, got {state['idle']}"
153+
assert state["busy"] == 0, \
154+
f"Expected bus_busy=0 when idle, got {state['busy']}"
155+
156+
await tb.teardown()
157+
158+
159+
@cocotb.test()
160+
async def test_bus_timers_reset_on_start(dut):
161+
"""START condition resets the bus timer counter to 0.
162+
163+
After a STOP has started the counter and bus_free has been observed,
164+
a subsequent START must immediately reset the counter back to 0 and
165+
deassert all timer-state outputs.
166+
"""
167+
i3c_controller, tb = await test_setup(dut)
168+
await set_timer_thresholds(tb, T_FREE, T_AVAL, T_IDLE)
169+
170+
# Start the counter with a STOP condition
171+
await i3c_controller.send_start()
172+
await i3c_controller.send_stop()
173+
174+
# Wait until bus_free fires
175+
await ClockCycles(tb.clk, T_FREE + 10)
176+
state = read_bus_state(dut)
177+
assert state["free"] == 1, \
178+
f"Expected bus_free=1 before START reset, " \
179+
f"got {state['free']} (counter={state['counter']})"
180+
181+
# START condition resets the counter and clears count_enable
182+
await i3c_controller.send_start()
183+
await ClockCycles(tb.clk, 3)
184+
185+
state = read_bus_state(dut)
186+
assert state["counter"] == 0, \
187+
f"Expected counter=0 after START, got {state['counter']}"
188+
assert state["free"] == 0, \
189+
f"Expected bus_free=0 after START reset, got {state['free']}"
190+
assert state["available"] == 0, \
191+
f"Expected bus_available=0 after START reset, got {state['available']}"
192+
assert state["busy"] == 1, \
193+
f"Expected bus_busy=1 after START reset, got {state['busy']}"
194+
195+
# Close the open frame
196+
await i3c_controller.send_stop()
197+
await tb.teardown()
198+
199+
200+
@cocotb.test()
201+
async def test_bus_timers_reset_on_hdr_entry(dut):
202+
"""Entering HDR mode holds the bus timer counter at 0.
203+
204+
The bus_timers RTL drives reset_counter = in_hdr_mode_i, so while
205+
the target FSM is in HDR mode the counter is continuously cleared.
206+
After exiting HDR mode the counter stays at 0 until the next STOP.
207+
"""
208+
DYNAMIC_ADDR = random.choice(VALID_I3C_ADDRESSES)
209+
i3c_controller, tb = await test_setup(dut, dynamic_addr=DYNAMIC_ADDR)
210+
await set_timer_thresholds(tb, T_FREE, T_AVAL, T_IDLE)
211+
212+
# Step 1: start the counter with a STOP condition
213+
await i3c_controller.send_start()
214+
await i3c_controller.send_stop()
215+
await ClockCycles(tb.clk, T_FREE + 10)
216+
217+
state = read_bus_state(dut)
218+
assert state["free"] == 1, \
219+
f"Expected bus_free=1 before HDR entry, " \
220+
f"got {state['free']} (counter={state['counter']})"
221+
222+
# Step 2: enter HDR mode via ENTHDR0 broadcast CCC
223+
# This also drives a START condition (resetting the counter), then
224+
# asserts in_hdr_mode_i which keeps reset_counter=1 throughout HDR mode.
225+
await i3c_controller.i3c_ccc_write(
226+
ENTHDR0, broadcast_data=[], stop=False, pull_scl_low=True)
227+
await ClockCycles(tb.clk, 10)
228+
assert get_fsm_state(dut) == FSM_STATE_IN_HDR_MODE, \
229+
f"Expected FSM in HDR mode ({FSM_STATE_IN_HDR_MODE}), " \
230+
f"got {get_fsm_state(dut)}"
231+
232+
# Step 3: counter must stay at 0 while in HDR mode
233+
await ClockCycles(tb.clk, T_IDLE + 20)
234+
state = read_bus_state(dut)
235+
assert state["counter"] == 0, \
236+
f"Expected counter=0 in HDR mode, got {state['counter']}"
237+
assert state["busy"] == 1, \
238+
f"Expected bus_busy=1 in HDR mode, got {state['busy']}"
239+
assert state["free"] == 0, \
240+
f"Expected bus_free=0 in HDR mode, got {state['free']}"
241+
assert get_fsm_state(dut) == FSM_STATE_IN_HDR_MODE, \
242+
f"Expected FSM still in HDR mode, got {get_fsm_state(dut)}"
243+
244+
# Step 4: exit HDR mode; counter starts counting (STOP condition is send as a part of HDR exit)
245+
await i3c_controller.send_hdr_exit()
246+
await ClockCycles(tb.clk, 5)
247+
assert get_fsm_state(dut) == FSM_STATE_IDLE, \
248+
f"Expected FSM idle after HDR exit, got {get_fsm_state(dut)}"
249+
250+
state = read_bus_state(dut)
251+
assert state["counter"] != 0, \
252+
f"Expected counter!=0 after HDR exit (no STOP yet), got {state['counter']}"
253+
254+
assert state["free"] == 1, \
255+
f"Expected bus_free=1 after STOP following HDR exit, got {state['free']}"
256+
257+
await tb.teardown()

verification/testplan/source-maps.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,26 @@ testplans:
99
- name: "^(.*)$"
1010
source: ".*verification/cocotb/top/lib_i3c_top/test_bypass.py"
1111
cocotb_xml: ".*verification/cocotb/top/i3c_axi/test_(bypass|i3c_target)(_[0-9]+)?.xml"
12+
- name: 'CCC handling'
13+
testpoints:
14+
- name: "^(.*)$"
15+
source: "verification/cocotb/top/lib_i3c_top/test_ccc.py"
16+
- name: 'CSR access check'
17+
testpoints:
18+
- name: "^(.*)$"
19+
source: "verification/cocotb/top/lib_i3c_top/test_csr_access.py"
20+
- name: 'Bus timers top-level'
21+
testpoints:
22+
- name: "^(.*)$"
23+
source: "verification/cocotb/top/lib_i3c_top/test_bus_timers.py"
24+
- name: 'Enter and exit HDR mode'
25+
testpoints:
26+
- name: "^(.*)$"
27+
source: "verification/cocotb/top/lib_i3c_top/test_enter_exit_hdr_mode.py"
28+
- name: 'Target'
29+
testpoints:
30+
- name: "^(.*)$"
31+
source: "verification/cocotb/top/lib_i3c_top/test_i3c_target.py"
1232
- name: 'Target interrupts'
1333
testpoints:
1434
- name: "^(.*)$"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
name: Bus timers top-level
3+
testpoints:
4+
[
5+
{
6+
name: STOP condition starts the counter
7+
desc:
8+
'''
9+
Verifies that detecting a STOP condition on the I3C bus starts the
10+
bus_timers counter and that the counter progresses through the bus
11+
busy, free, available and idle states in the correct order with the
12+
expected timing.
13+
'''
14+
tests: ["bus_timers_stop_starts_counter"]
15+
tags: ["top"]
16+
}
17+
{
18+
name: START condition resets the counter
19+
desc:
20+
'''
21+
Verifies that a START condition detected after a STOP resets the
22+
bus_timers counter back to 0 and deasserts all timer-state outputs
23+
(bus_free, bus_available, bus_idle), leaving bus_busy asserted.
24+
'''
25+
tests: ["bus_timers_reset_on_start"]
26+
tags: ["top"]
27+
}
28+
{
29+
name: HDR mode entry holds the counter at 0
30+
desc:
31+
'''
32+
Verifies that entering HDR mode (in_hdr_mode_i asserted) continuously
33+
holds the bus_timers counter at 0 for the duration of HDR mode.
34+
After HDR exit the counter stays at 0 until the next STOP condition,
35+
which must then restart normal timer operation.
36+
'''
37+
tests: ["bus_timers_reset_on_hdr_entry"]
38+
tags: ["top"]
39+
}
40+
]
41+
}

0 commit comments

Comments
 (0)