Skip to content

Commit 08d5408

Browse files
committed
Updated cocotb tests and added them to CI
1 parent 1ac49ba commit 08d5408

File tree

6 files changed

+138
-106
lines changed

6 files changed

+138
-106
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ jobs:
7878
7979
eda_ci:
8080
name: Run EDA
81-
if: false
8281
runs-on: ubuntu-latest
8382
container: ghcr.io/siliconcompiler/sc_runner:latest
8483
steps:
@@ -90,10 +89,6 @@ jobs:
9089
. .venv/bin/activate
9190
9291
pip install --upgrade pip
93-
pip install .[dev]
94-
95-
# change running directory
96-
mkdir testrun
97-
cd testrun
92+
pip install -e .[dev]
9893
99-
pytest $GITHUB_WORKSPACE -m "eda"
94+
pytest -n logical -m "eda" --verbose

lambdalib/ramlib/la_asyncfifo/testbench/la_asyncfifo.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from cocotb.triggers import RisingEdge
33
from cocotb_bus.bus import Bus
44
from cocotb.queue import Queue
5-
from cocotb.binary import BinaryValue
5+
from cocotb.types import LogicArray
66

77

88
class LaAsyncFifoWrBus(Bus):
@@ -133,7 +133,7 @@ def pause(self):
133133
def resume(self):
134134
self._pause = False
135135

136-
async def read(self) -> BinaryValue:
136+
async def read(self) -> LogicArray:
137137
return await self.queue.get()
138138

139139
async def _run(self):
Lines changed: 49 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
import random
2+
import string
23
from decimal import Decimal
34

4-
import siliconcompiler
5-
65
import cocotb
76
from cocotb.clock import Clock
87
from cocotb.triggers import ClockCycles, Timer, Combine
9-
from cocotb.regression import TestFactory
10-
from cocotb import utils
118

12-
from lambdalib import ramlib
139
from lambdalib.utils._tb_common import (
1410
run_cocotb,
15-
drive_reset,
11+
do_reset,
1612
random_bool_generator
1713
)
18-
from lambdalib.ramlib.la_asyncfifo.testbench.la_asyncfifo import (
14+
from .la_asyncfifo import (
1915
LaAsyncFifoWrBus,
2016
LaAsyncFifoRdBus,
2117
LaAsyncFifoSource,
@@ -30,7 +26,13 @@ def bursty_en_gen(burst_len=20):
3026
yield en_state
3127

3228

33-
@cocotb.test()
29+
def random_decimal(max: int, min: int, decimal_places=2) -> Decimal:
30+
prefix = str(random.randint(min, max))
31+
suffix = ''.join(random.choice(string.digits) for _ in range(decimal_places))
32+
return Decimal(prefix + "." + suffix)
33+
34+
35+
@cocotb.test(timeout_time=100, timeout_unit="ms")
3436
async def test_almost_full(dut):
3537

3638
wr_clk_period_ns = 10.0
@@ -51,14 +53,14 @@ async def test_almost_full(dut):
5153

5254
# Reset DUT
5355
await Combine(
54-
cocotb.start_soon(drive_reset(dut.wr_nreset)),
55-
cocotb.start_soon(drive_reset(dut.rd_nreset))
56+
cocotb.start_soon(do_reset(dut.wr_nreset, time_ns=wr_clk_period_ns*4)),
57+
cocotb.start_soon(do_reset(dut.rd_nreset, time_ns=rd_clk_period_ns*4))
5658
)
5759

58-
await cocotb.start(Clock(dut.wr_clk, wr_clk_period_ns, units="ns").start())
60+
Clock(dut.wr_clk, wr_clk_period_ns, units="ns").start()
5961
# Randomize phase shift between clocks
6062
await Timer(wr_clk_period_ns * random.random(), "ns", round_mode="round")
61-
await cocotb.start(Clock(dut.rd_clk, rd_clk_period_ns, units="ns").start())
63+
Clock(dut.rd_clk, rd_clk_period_ns, units="ns").start()
6264

6365
almost_full_level = int(dut.AFULLFINAL.value)
6466

@@ -85,6 +87,22 @@ async def test_almost_full(dut):
8587
assert dut.wr_almost_full.value == 0
8688

8789

90+
# Generate sets of tests based on the different permutations of the possible arguments to fifo_test
91+
MAX_PERIOD_NS = 10
92+
MIN_PERIOD_NS = 1
93+
# Generate random clk period to test between min and max
94+
RAND_WR_CLK_PERIOD_NS, RAND_RD_CLK_PERIOD_NS = [
95+
random_decimal(MAX_PERIOD_NS, MIN_PERIOD_NS) for _ in range(2)
96+
]
97+
98+
99+
@cocotb.test(timeout_time=100, timeout_unit="ms")
100+
@cocotb.parametrize(
101+
wr_clk_period_ns=[MIN_PERIOD_NS, RAND_WR_CLK_PERIOD_NS, MAX_PERIOD_NS],
102+
rd_clk_period_ns=[MIN_PERIOD_NS, RAND_RD_CLK_PERIOD_NS, MAX_PERIOD_NS],
103+
wr_en_generator=[None, random_bool_generator, bursty_en_gen],
104+
rd_en_generator=[None, random_bool_generator, bursty_en_gen]
105+
)
88106
async def fifo_rd_wr_test(
89107
dut,
90108
wr_clk_period_ns=10.0,
@@ -98,25 +116,27 @@ async def fifo_rd_wr_test(
98116
clock=dut.wr_clk,
99117
reset=dut.wr_nreset
100118
)
101-
fifo_source.set_wr_en_generator(random_bool_generator())
119+
if wr_en_generator:
120+
fifo_source.set_wr_en_generator(wr_en_generator())
102121

103122
fifo_sink = LaAsyncFifoSink(
104123
bus=LaAsyncFifoRdBus.from_prefix(dut, ""),
105124
clock=dut.rd_clk,
106125
reset=dut.rd_nreset
107126
)
108-
fifo_sink.set_rd_en_generator(random_bool_generator())
127+
if rd_en_generator:
128+
fifo_sink.set_rd_en_generator(rd_en_generator())
109129

110130
# Reset DUT
111131
await Combine(
112-
cocotb.start_soon(drive_reset(dut.wr_nreset)),
113-
cocotb.start_soon(drive_reset(dut.rd_nreset))
132+
cocotb.start_soon(do_reset(dut.wr_nreset, time_ns=wr_clk_period_ns*4)),
133+
cocotb.start_soon(do_reset(dut.rd_nreset, time_ns=rd_clk_period_ns*4))
114134
)
115135

116-
await cocotb.start(Clock(dut.wr_clk, wr_clk_period_ns, units="ns").start())
136+
Clock(dut.wr_clk, wr_clk_period_ns, units="ns").start()
117137
# Randomize phase shift between clocks
118-
await Timer(wr_clk_period_ns * random.random(), "ns", round_mode="round")
119-
await cocotb.start(Clock(dut.rd_clk, rd_clk_period_ns, units="ns").start())
138+
await Timer(wr_clk_period_ns * Decimal(random.random()), "ns", round_mode="round")
139+
Clock(dut.rd_clk, rd_clk_period_ns, units="ns").start()
120140

121141
await ClockCycles(dut.wr_clk, 3)
122142

@@ -133,55 +153,25 @@ async def fifo_rd_wr_test(
133153
await ClockCycles(dut.wr_clk, 10)
134154

135155

136-
# Generate sets of tests based on the different permutations of the possible arguments to fifo_test
137-
MAX_PERIOD_NS = 10.0
138-
MIN_PERIOD_NS = 1.0
139-
# Generate random clk period to test between min and max
140-
RAND_WR_CLK_PERIOD_NS, RAND_RD_CLK_PERIOD_NS = [utils.get_time_from_sim_steps(
141-
# Time step must be even for cocotb clock driver
142-
steps=utils.get_sim_steps(
143-
time=Decimal(MIN_PERIOD_NS) + (
144-
Decimal(MAX_PERIOD_NS - MIN_PERIOD_NS)
145-
* Decimal(random.random()).quantize(Decimal("0.00"))
146-
),
147-
units="ns",
148-
round_mode="round"
149-
) & ~1,
150-
units="ns"
151-
) for _ in range(0, 2)]
152-
153-
# Factory to automatically generate a set of tests based on the different permutations
154-
# of the provided test arguments
155-
tf = TestFactory(fifo_rd_wr_test)
156-
tf.add_option('wr_clk_period_ns', [MIN_PERIOD_NS, RAND_WR_CLK_PERIOD_NS, MAX_PERIOD_NS])
157-
tf.add_option('rd_clk_period_ns', [MIN_PERIOD_NS, RAND_RD_CLK_PERIOD_NS, MAX_PERIOD_NS])
158-
tf.add_option('wr_en_generator', [None, random_bool_generator, bursty_en_gen])
159-
tf.add_option('rd_en_generator', [None, random_bool_generator, bursty_en_gen])
160-
tf.generate_tests()
161-
162-
163-
def test_la_asyncfifo():
164-
chip = siliconcompiler.Chip("la_asyncfifo")
165-
166-
# TODO: should not be needed?
167-
chip.input("ramlib/la_asyncfifo/rtl/la_asyncfifo.v", package='lambdalib')
168-
chip.use(ramlib)
156+
def load_cocotb_test():
157+
from siliconcompiler import Sim
158+
from lambdalib.ramlib import Asyncfifo
159+
160+
project = Sim(Asyncfifo())
161+
project.add_fileset("rtl")
169162

170163
for depth in [2, 4, 8]:
171-
test_module_name = "lambdalib.ramlib.tests.tb_la_asyncfifo"
164+
test_module_name = __name__
172165
test_name = f"{test_module_name}_depth_{depth}"
173166
tests_failed = run_cocotb(
174-
chip=chip,
167+
project=project,
175168
test_module_name=test_module_name,
176169
timescale=("1ns", "1ps"),
177170
parameters={
178171
"DW": 32,
179172
"DEPTH": depth
180173
},
181-
output_dir_name=test_name
174+
output_dir_name=test_name,
175+
waves=False
182176
)
183177
assert (tests_failed == 0), f"Error test {test_name} failed!"
184-
185-
186-
if __name__ == "__main__":
187-
test_la_asyncfifo()

lambdalib/utils/_tb_common.py

Lines changed: 75 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,115 @@
11
import os
2-
32
import random
43
from pathlib import Path
4+
from typing import List, Tuple, Optional, Mapping, Union
5+
6+
from siliconcompiler import Sim
57

68
from cocotb.triggers import Timer
7-
from cocotb.runner import get_runner, get_results
9+
from cocotb.handle import SimHandleBase
10+
11+
from cocotb_tools.runner import get_runner
12+
from cocotb_tools.check_results import get_results
813

9-
import siliconcompiler
10-
from siliconcompiler.tools.slang import elaborate
14+
15+
async def do_reset(
16+
reset: SimHandleBase,
17+
time_ns: int,
18+
active_level: bool = False):
19+
"""Perform a async reset"""
20+
reset.value = not active_level
21+
await Timer(1, unit="step")
22+
reset.value = active_level
23+
await Timer(time_ns, "ns")
24+
reset.value = not active_level
25+
await Timer(1, unit="step")
1126

1227

1328
def run_cocotb(
14-
chip: siliconcompiler.Chip,
15-
test_module_name,
16-
output_dir_name=None,
17-
simulator_name="icarus",
18-
timescale=None,
19-
parameters=None):
20-
21-
# Use surelog to pickle Verilog sources
22-
flow = "cocotb_flow"
23-
chip.node(flow, "import", elaborate)
24-
chip.set("option", "flow", flow)
25-
assert chip.run()
26-
27-
pickled_verilog = chip.find_result("v", "import")
28-
assert pickled_verilog, "Could not locate pickled verilog"
29+
project: Sim,
30+
test_module_name: str,
31+
output_dir_name: Optional[str] = None,
32+
simulator_name: str = "icarus",
33+
build_args: Optional[List] = None,
34+
timescale: Optional[Tuple[str, str]] = None,
35+
parameters: Optional[Mapping[str, object]] = None,
36+
seed: Optional[Union[str, int]] = None,
37+
waves: bool = True):
38+
"""Launch cocotb given a SC Project"""
39+
40+
if parameters is None:
41+
parameters = {}
42+
43+
if build_args is None:
44+
build_args = []
2945

3046
if output_dir_name is None:
3147
output_dir_name = test_module_name
3248

33-
top_level_dir = os.getcwd()
34-
build_dir = Path(chip.getbuilddir()) / output_dir_name
49+
pytest_current_test = os.getenv("PYTEST_CURRENT_TEST", None)
50+
51+
rootpath = Path(__file__).resolve().parent.parent
52+
top_level_dir = rootpath
53+
build_dir = rootpath / "build" / output_dir_name
3554
test_dir = None
3655

3756
results_xml = None
38-
# Need to check if we are running inside of pytest. See link below.
39-
# https://github.com/cocotb/cocotb/blob/d883ce914063c3601455d10a40f459fffa22d8f2/cocotb/runner.py#L313
40-
if not os.getenv("PYTEST_CURRENT_TEST", None):
57+
if not pytest_current_test:
4158
results_xml = build_dir / "results.xml"
4259
test_dir = top_level_dir
4360

61+
# Get top level module name
62+
top_lvl_module_name = None
63+
main_filesets = project.get("option", "fileset")
64+
if main_filesets and len(main_filesets) != 0:
65+
main_fileset = main_filesets[0]
66+
top_lvl_module_name = project.design.get_topmodule(
67+
fileset=main_fileset
68+
)
69+
70+
filesets = project.get_filesets()
71+
idirs = []
72+
defines = []
73+
for lib, fileset in filesets:
74+
idirs.extend(lib.find_files("fileset", fileset, "idir"))
75+
defines.extend(lib.get("fileset", fileset, "define"))
76+
77+
sources = []
78+
for lib, fileset in filesets:
79+
for value in lib.get_file(fileset=fileset, filetype="systemverilog"):
80+
sources.append(value)
81+
for lib, fileset in filesets:
82+
for value in lib.get_file(fileset=fileset, filetype="verilog"):
83+
sources.append(value)
84+
4485
# Build HDL in chosen simulator
4586
runner = get_runner(simulator_name)
4687
runner.build(
47-
sources=[pickled_verilog],
48-
hdl_toplevel=chip.design,
49-
waves=True,
88+
sources=sources,
89+
includes=idirs,
90+
hdl_toplevel=top_lvl_module_name,
91+
build_args=build_args,
92+
waves=waves,
5093
timescale=timescale,
5194
build_dir=build_dir,
5295
parameters=parameters
5396
)
97+
5498
# Run test
5599
_, tests_failed = get_results(runner.test(
56-
hdl_toplevel=chip.top(),
100+
hdl_toplevel=top_lvl_module_name,
57101
test_module=test_module_name,
58102
test_dir=test_dir,
103+
test_args=build_args,
59104
results_xml=results_xml,
60105
build_dir=build_dir,
61-
timescale=timescale,
62-
waves=True
106+
seed=seed,
107+
waves=waves
63108
))
64109

65110
return tests_failed
66111

67112

68-
async def drive_reset(reset, reset_time_in_ns=100, active_level=False):
69-
reset.value = 1 if active_level else 0
70-
await Timer(reset_time_in_ns, units="ns")
71-
reset.value = 0 if active_level else 1
72-
73-
74113
def random_bool_generator():
75114
while True:
76115
yield (random.randint(0, 1) == 1)

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ version = {attr = "lambdalib.__version__"}
3131
dev = [
3232
"flake8 == 7.3.0",
3333
"pytest == 8.4.2",
34+
"pytest-xdist==3.8.0",
3435
"pytest-timeout == 2.4.0",
35-
"cocotb == 2.0.0",
36+
"cocotb @ git+https://github.com/cocotb/cocotb.git@770d72214362cb366097267873839f50c69e417e",
3637
"cocotb-bus @ git+https://github.com/cocotb/cocotb-bus.git@c53c6024391fa02263969b02a0e0de5a22974cfc"
3738
]
3839

@@ -43,4 +44,4 @@ markers = [
4344
testpaths = [
4445
"tests"
4546
]
46-
timeout = "60"
47+
timeout = "300"

tests/test_cocotb_runner.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import pytest
2+
from lambdalib.ramlib.la_asyncfifo.testbench import tb_la_asyncfifo
3+
4+
5+
@pytest.mark.eda
6+
def test_la_asyncfifo():
7+
tb_la_asyncfifo.load_cocotb_test()

0 commit comments

Comments
 (0)