Skip to content

Commit 10b0a1c

Browse files
committed
examples: Add a simple passthrough example that uses cocotb for validation
This commit adds a simple DSLX module that sends back the information received on the input channel. The example contains tests written in DSLX to verify the IR, as well as tests that use Cocotb framework to validate behavior of the generated Verilog sources. Internal-tag: [#46586] Signed-off-by: Robert Winkler <rwinkler@antmicro.com>
1 parent 4b09202 commit 10b0a1c

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed

xls/examples/BUILD

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,3 +641,47 @@ filegroup(
641641
srcs = glob(["*.x"]),
642642
visibility = ["//xls:xls_internal"],
643643
)
644+
645+
xls_dslx_library(
646+
name = "passthrough_dslx",
647+
srcs = [
648+
"passthrough.x",
649+
],
650+
)
651+
652+
xls_dslx_verilog(
653+
name = "passthrough_verilog",
654+
codegen_args = {
655+
"module_name": "passthrough",
656+
"delay_model": "unit",
657+
"pipeline_stages": "1",
658+
"reset": "rst",
659+
"use_system_verilog": "false",
660+
"streaming_channel_data_suffix": "_data",
661+
},
662+
dslx_top = "Passthrough",
663+
library = "passthrough_dslx",
664+
verilog_file = "passthrough.v",
665+
)
666+
667+
xls_dslx_test(
668+
name = "passthrough_test",
669+
dslx_test_args = {
670+
"compare": "none",
671+
},
672+
library = ":passthrough_dslx",
673+
)
674+
675+
py_test(
676+
name = "passthrough_cocotb_test",
677+
srcs = ["passthrough_cocotb_test.py"],
678+
data = [
679+
":passthrough_verilog",
680+
"@com_icarus_iverilog//:iverilog",
681+
"@com_icarus_iverilog//:vvp",
682+
],
683+
deps = [
684+
"//xls/common:runfiles",
685+
"//xls/simulation/cocotb:cocotb_xls",
686+
],
687+
)

xls/examples/passthrough.x

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2023 The XLS Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// A simple proc that sends back the information received on
16+
// an input channel over an output channel.
17+
18+
import std
19+
20+
proc Passthrough {
21+
data_r: chan<u32> in;
22+
data_s: chan<u32> out;
23+
24+
init {()}
25+
26+
config(data_r: chan<u32> in, data_s: chan<u32> out) {
27+
(data_r, data_s)
28+
}
29+
30+
next(tok: token, state: ()) {
31+
let (tok, data) = recv(tok, data_r);
32+
let tok = send(tok, data_s, data);
33+
}
34+
}
35+
36+
const NUMBER_OF_TESTED_TRANSACTIONS = u32:10;
37+
38+
#[test_proc]
39+
proc PassthroughTest {
40+
terminator: chan<bool> out;
41+
data_s: chan<u32> out;
42+
data_r: chan<u32> in;
43+
44+
init { u32:0 }
45+
46+
config (terminator: chan<bool> out) {
47+
let (data_s, data_r) = chan<u32>;
48+
spawn Passthrough(data_r, data_s);
49+
(terminator, data_s, data_r)
50+
}
51+
52+
next(tok: token, count: u32) {
53+
let data_to_send = count;
54+
let tok = send(tok, data_s, data_to_send);
55+
let (tok, received_data) = recv(tok, data_r);
56+
57+
trace_fmt!("send: {}, received: {}, in transaction {}",
58+
data_to_send, received_data, count + u32:1);
59+
60+
assert_eq(data_to_send, received_data);
61+
62+
let do_send = (count == NUMBER_OF_TESTED_TRANSACTIONS - u32:1);
63+
send_if(tok, terminator, do_send, true);
64+
65+
count + u32:1
66+
}
67+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Copyright 2023 The XLS Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
from copy import deepcopy
17+
from dataclasses import dataclass
18+
from pathlib import Path
19+
from typing import Sequence, Union
20+
21+
import cocotb
22+
from cocotb.binary import BinaryValue
23+
from cocotb.clock import Clock
24+
from cocotb.handle import ModifiableObject, SimHandleBase
25+
from cocotb.runner import check_results_file, get_runner
26+
from cocotb.triggers import ClockCycles, Event, RisingEdge
27+
from cocotb_bus.scoreboard import Scoreboard
28+
from cocotb_xls import XLSChannel, XLSChannelDriver, XLSChannelMonitor
29+
30+
from xls.common import runfiles
31+
32+
33+
@dataclass
34+
class SimulationData:
35+
"""Auxiliary structure used to store design-related data for the simulation"""
36+
37+
clock: Clock
38+
driver: XLSChannelDriver
39+
monitor: XLSChannelMonitor
40+
scoreboard: Scoreboard
41+
data_r: XLSChannel
42+
data_s: XLSChannel
43+
terminate: Event
44+
clk: ModifiableObject
45+
rst: ModifiableObject
46+
47+
48+
def init_sim(dut: SimHandleBase, data_to_recv: Sequence[BinaryValue]) -> SimulationData:
49+
"""Extracts all design-related data required for simulation"""
50+
51+
RECV_CHANNEL = "passthrough__data_r"
52+
SEND_CHANNEL = "passthrough__data_s"
53+
54+
clock = Clock(dut.clk, 10, units="us")
55+
56+
driver = XLSChannelDriver(dut, RECV_CHANNEL, dut.clk)
57+
monitor = XLSChannelMonitor(dut, SEND_CHANNEL, dut.clk)
58+
data_r = XLSChannel(dut, RECV_CHANNEL)
59+
data_s = XLSChannel(dut, SEND_CHANNEL)
60+
61+
scoreboard = Scoreboard(dut, fail_immediately=True)
62+
scoreboard.add_interface(monitor, deepcopy(data_to_recv))
63+
64+
expected_packet_count = len(data_to_recv)
65+
terminate = Event("Received the last packet of data")
66+
67+
def terminate_cb(_):
68+
if monitor.stats.received_transactions == expected_packet_count:
69+
terminate.set()
70+
71+
monitor.add_callback(terminate_cb)
72+
73+
return SimulationData(
74+
clock=clock,
75+
driver=driver,
76+
monitor=monitor,
77+
scoreboard=scoreboard,
78+
data_r=data_r,
79+
data_s=data_s,
80+
terminate=terminate,
81+
clk=dut.clk,
82+
rst=dut.rst,
83+
)
84+
85+
86+
@cocotb.coroutine
87+
async def recv(clk, send_channel):
88+
"""Cocotb coroutine that acts as a proc receiving data from a channel"""
89+
send_channel.rdy.setimmediatevalue(0)
90+
while True:
91+
send_channel.rdy.value = send_channel.vld.value
92+
await RisingEdge(clk)
93+
94+
95+
@cocotb.coroutine
96+
async def reset(clk, rst, cycles=1):
97+
"""Cocotb coroutine that performs the reset"""
98+
rst.setimmediatevalue(1)
99+
await ClockCycles(clk, cycles)
100+
rst.value = 0
101+
102+
103+
@cocotb.test(timeout_time=10, timeout_unit="ms")
104+
async def passthrough_test(dut):
105+
"""Cocotb test of the Passthrough proc"""
106+
test_data = [BinaryValue(x, n_bits=32, bigEndian=False) for x in range(10)]
107+
sim = init_sim(dut, test_data)
108+
109+
cocotb.start_soon(sim.clock.start())
110+
await reset(sim.clk, sim.rst)
111+
112+
cocotb.start_soon(recv(sim.clk, sim.data_s))
113+
await sim.driver.send(test_data)
114+
await sim.terminate.wait()
115+
116+
117+
if __name__ == "__main__":
118+
runfiles._BASE_PATH = "com_icarus_iverilog"
119+
iverilog_path = Path(runfiles.get_path("iverilog"))
120+
vvp_path = Path(runfiles.get_path("vvp"))
121+
os.environ["PATH"] += os.pathsep + str(iverilog_path.parent)
122+
os.environ["PATH"] += os.pathsep + str(vvp_path.parent)
123+
124+
runner = get_runner("icarus")
125+
runner.build(
126+
verilog_sources=["xls/examples/passthrough.v"],
127+
hdl_toplevel="passthrough",
128+
timescale=("1ns", "1ps"),
129+
waves=True,
130+
)
131+
results_xml = runner.test(
132+
hdl_toplevel="passthrough",
133+
test_module=[Path(__file__).stem],
134+
waves=True,
135+
)
136+
check_results_file(results_xml)

0 commit comments

Comments
 (0)