diff --git a/dependency_support/pip_requirements.txt b/dependency_support/pip_requirements.txt index ae4ff48d4a..37082351e3 100644 --- a/dependency_support/pip_requirements.txt +++ b/dependency_support/pip_requirements.txt @@ -8,6 +8,8 @@ termcolor==1.1.0 psutil==5.7.0 portpicker==1.3.1 pyyaml==6.0.1 +git+https://github.com/cocotb/cocotb.git@a853db95b0019db6796a6803aa94304bde743e4e +cocotb_bus==0.2.1 # Note: numpy and scipy version availability seems to differ between Ubuntu # versions that we want to support (e.g. 18.04 vs 20.04), so we accept a diff --git a/xls/examples/BUILD b/xls/examples/BUILD index b5aee9b911..512396e461 100644 --- a/xls/examples/BUILD +++ b/xls/examples/BUILD @@ -641,3 +641,47 @@ filegroup( srcs = glob(["*.x"]), visibility = ["//xls:xls_internal"], ) + +xls_dslx_library( + name = "passthrough_dslx", + srcs = [ + "passthrough.x", + ], +) + +xls_dslx_verilog( + name = "passthrough_verilog", + codegen_args = { + "module_name": "passthrough", + "delay_model": "unit", + "pipeline_stages": "1", + "reset": "rst", + "use_system_verilog": "false", + "streaming_channel_data_suffix": "_data", + }, + dslx_top = "Passthrough", + library = "passthrough_dslx", + verilog_file = "passthrough.v", +) + +xls_dslx_test( + name = "passthrough_test", + dslx_test_args = { + "compare": "none", + }, + library = ":passthrough_dslx", +) + +py_test( + name = "passthrough_cocotb_test", + srcs = ["passthrough_cocotb_test.py"], + data = [ + ":passthrough_verilog", + "@com_icarus_iverilog//:iverilog", + "@com_icarus_iverilog//:vvp", + ], + deps = [ + "//xls/common:runfiles", + "//xls/simulation/cocotb:cocotb_xls", + ], +) diff --git a/xls/examples/passthrough.x b/xls/examples/passthrough.x new file mode 100644 index 0000000000..5878a33f5e --- /dev/null +++ b/xls/examples/passthrough.x @@ -0,0 +1,67 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// A simple proc that sends back the information received on +// an input channel over an output channel. + +import std + +proc Passthrough { + data_r: chan in; + data_s: chan out; + + init {()} + + config(data_r: chan in, data_s: chan out) { + (data_r, data_s) + } + + next(tok: token, state: ()) { + let (tok, data) = recv(tok, data_r); + let tok = send(tok, data_s, data); + } +} + +const NUMBER_OF_TESTED_TRANSACTIONS = u32:10; + +#[test_proc] +proc PassthroughTest { + terminator: chan out; + data_s: chan out; + data_r: chan in; + + init { u32:0 } + + config (terminator: chan out) { + let (data_s, data_r) = chan; + spawn Passthrough(data_r, data_s); + (terminator, data_s, data_r) + } + + next(tok: token, count: u32) { + let data_to_send = count; + let tok = send(tok, data_s, data_to_send); + let (tok, received_data) = recv(tok, data_r); + + trace_fmt!("send: {}, received: {}, in transaction {}", + data_to_send, received_data, count + u32:1); + + assert_eq(data_to_send, received_data); + + let do_send = (count == NUMBER_OF_TESTED_TRANSACTIONS - u32:1); + send_if(tok, terminator, do_send, true); + + count + u32:1 + } +} diff --git a/xls/examples/passthrough_cocotb_test.py b/xls/examples/passthrough_cocotb_test.py new file mode 100644 index 0000000000..4386f2f863 --- /dev/null +++ b/xls/examples/passthrough_cocotb_test.py @@ -0,0 +1,136 @@ +# Copyright 2023 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from copy import deepcopy +from dataclasses import dataclass +from pathlib import Path +from typing import Sequence, Union + +import cocotb +from cocotb.binary import BinaryValue +from cocotb.clock import Clock +from cocotb.handle import ModifiableObject, SimHandleBase +from cocotb.runner import check_results_file, get_runner +from cocotb.triggers import ClockCycles, Event, RisingEdge +from cocotb_bus.scoreboard import Scoreboard +from cocotb_xls import XLSChannel, XLSChannelDriver, XLSChannelMonitor + +from xls.common import runfiles + + +@dataclass +class SimulationData: + """Auxiliary structure used to store design-related data for the simulation""" + + clock: Clock + driver: XLSChannelDriver + monitor: XLSChannelMonitor + scoreboard: Scoreboard + data_r: XLSChannel + data_s: XLSChannel + terminate: Event + clk: ModifiableObject + rst: ModifiableObject + + +def init_sim(dut: SimHandleBase, data_to_recv: Sequence[BinaryValue]) -> SimulationData: + """Extracts all design-related data required for simulation""" + + RECV_CHANNEL = "passthrough__data_r" + SEND_CHANNEL = "passthrough__data_s" + + clock = Clock(dut.clk, 10, units="us") + + driver = XLSChannelDriver(dut, RECV_CHANNEL, dut.clk) + monitor = XLSChannelMonitor(dut, SEND_CHANNEL, dut.clk) + data_r = XLSChannel(dut, RECV_CHANNEL) + data_s = XLSChannel(dut, SEND_CHANNEL) + + scoreboard = Scoreboard(dut, fail_immediately=True) + scoreboard.add_interface(monitor, deepcopy(data_to_recv)) + + expected_packet_count = len(data_to_recv) + terminate = Event("Received the last packet of data") + + def terminate_cb(_): + if monitor.stats.received_transactions == expected_packet_count: + terminate.set() + + monitor.add_callback(terminate_cb) + + return SimulationData( + clock=clock, + driver=driver, + monitor=monitor, + scoreboard=scoreboard, + data_r=data_r, + data_s=data_s, + terminate=terminate, + clk=dut.clk, + rst=dut.rst, + ) + + +@cocotb.coroutine +async def recv(clk, send_channel): + """Cocotb coroutine that acts as a proc receiving data from a channel""" + send_channel.rdy.setimmediatevalue(0) + while True: + send_channel.rdy.value = send_channel.vld.value + await RisingEdge(clk) + + +@cocotb.coroutine +async def reset(clk, rst, cycles=1): + """Cocotb coroutine that performs the reset""" + rst.setimmediatevalue(1) + await ClockCycles(clk, cycles) + rst.value = 0 + + +@cocotb.test(timeout_time=10, timeout_unit="ms") +async def passthrough_test(dut): + """Cocotb test of the Passthrough proc""" + test_data = [BinaryValue(x, n_bits=32, bigEndian=False) for x in range(10)] + sim = init_sim(dut, test_data) + + cocotb.start_soon(sim.clock.start()) + await reset(sim.clk, sim.rst) + + cocotb.start_soon(recv(sim.clk, sim.data_s)) + await sim.driver.send(test_data) + await sim.terminate.wait() + + +if __name__ == "__main__": + runfiles._BASE_PATH = "com_icarus_iverilog" + iverilog_path = Path(runfiles.get_path("iverilog")) + vvp_path = Path(runfiles.get_path("vvp")) + os.environ["PATH"] += os.pathsep + str(iverilog_path.parent) + os.environ["PATH"] += os.pathsep + str(vvp_path.parent) + + runner = get_runner("icarus") + runner.build( + verilog_sources=["xls/examples/passthrough.v"], + hdl_toplevel="passthrough", + timescale=("1ns", "1ps"), + waves=True, + ) + results_xml = runner.test( + hdl_toplevel="passthrough", + test_module=[Path(__file__).stem], + waves=True, + ) + check_results_file(results_xml) diff --git a/xls/simulation/cocotb/BUILD b/xls/simulation/cocotb/BUILD new file mode 100644 index 0000000000..3082b5c988 --- /dev/null +++ b/xls/simulation/cocotb/BUILD @@ -0,0 +1,30 @@ +# Copyright 2023 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@xls_pip_deps//:requirements.bzl", "requirement") + +package( + default_visibility = ["//xls:xls_public"], + licenses = ["notice"], # Apache 2.0 +) + +py_library( + name = "cocotb_xls", + srcs = ["cocotb_xls.py"], + imports = ["."], + deps = [ + requirement("cocotb"), + requirement("cocotb_bus"), + ], +) diff --git a/xls/simulation/cocotb/cocotb_xls.py b/xls/simulation/cocotb/cocotb_xls.py new file mode 100644 index 0000000000..ebb1163807 --- /dev/null +++ b/xls/simulation/cocotb/cocotb_xls.py @@ -0,0 +1,80 @@ +# Copyright 2023 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, Sequence, Union + +import cocotb +from cocotb.binary import BinaryValue +from cocotb.handle import SimHandleBase +from cocotb.triggers import RisingEdge +from cocotb_bus.bus import Bus +from cocotb_bus.drivers import BusDriver +from cocotb_bus.monitors import BusMonitor + +Transaction = Union[BinaryValue, Sequence[BinaryValue]] + +XLS_CHANNEL_SIGNALS = ["data", "rdy", "vld"] +XLS_CHANNEL_OPTIONAL_SIGNALS = [] + + +class XLSChannel(Bus): + _signals = XLS_CHANNEL_SIGNALS + _optional_signals = XLS_CHANNEL_OPTIONAL_SIGNALS + + def __init__(self, entity, name, **kwargs: Any): + super().__init__(entity, name, self._signals, self._optional_signals, **kwargs) + + +class XLSChannelDriver(BusDriver): + _signals = XLS_CHANNEL_SIGNALS + _optional_signals = XLS_CHANNEL_OPTIONAL_SIGNALS + + def __init__(self, entity: SimHandleBase, name: str, clock: SimHandleBase, **kwargs: Any): + BusDriver.__init__(self, entity, name, clock, **kwargs) + + self.bus.data.setimmediatevalue(0) + self.bus.vld.setimmediatevalue(0) + + async def _driver_send(self, transaction: Transaction, sync: bool = True, **kwargs: Any) -> None: + if sync: + await RisingEdge(self.clock) + + data_to_send = (transaction if isinstance(transaction, Sequence) else [transaction]) + + for word in data_to_send: + self.bus.vld.value = 1 + self.bus.data.value = word + + while True: + await RisingEdge(self.clock) + if self.bus.rdy.value: + break + + self.bus.vld.value = 0 + + +class XLSChannelMonitor(BusMonitor): + _signals = XLS_CHANNEL_SIGNALS + _optional_signals = XLS_CHANNEL_OPTIONAL_SIGNALS + + def __init__(self, entity: SimHandleBase, name: str, clock: SimHandleBase, **kwargs: Any): + BusMonitor.__init__(self, entity, name, clock, **kwargs) + + @cocotb.coroutine + async def _monitor_recv(self) -> None: + while True: + await RisingEdge(self.clock) + if self.bus.rdy.value: + vec = self.bus.data.value + self._recv(vec)