From e38c08d5d20f34501eca52cacba374e0f7f960a3 Mon Sep 17 00:00:00 2001 From: Rice Shelley Date: Tue, 10 Feb 2026 10:57:12 -0500 Subject: [PATCH 1/4] Adding test for umi_stream --- pyproject.toml | 10 +- tests/sumi/umi_stream/surfer_config.surf.ron | 955 +++++++++++++++++++ tests/sumi/umi_stream/test_umi_stream.py | 675 +++++++++++++ tests/sumi/umi_stream/valid_ready_driver.py | 77 ++ tests/sumi/umi_stream/valid_ready_monitor.py | 32 + umi/sumi/__init__.py | 2 +- umi/sumi/umi_stream/rtl/umi_stream.v | 2 +- 7 files changed, 1748 insertions(+), 5 deletions(-) create mode 100644 tests/sumi/umi_stream/surfer_config.surf.ron create mode 100644 tests/sumi/umi_stream/test_umi_stream.py create mode 100644 tests/sumi/umi_stream/valid_ready_driver.py create mode 100644 tests/sumi/umi_stream/valid_ready_monitor.py diff --git a/pyproject.toml b/pyproject.toml index 4572491a..094130d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ urls = {Homepage = "https://github.com/zeroasiccorp/umi"} requires-python = ">= 3.9" license = {file = "LICENSE"} dependencies = [ - "siliconcompiler >= 0.35.0", + "siliconcompiler >= 0.36.5", "lambdalib >= 0.4.0, < 0.11.0" ] dynamic = ["version"] @@ -32,7 +32,10 @@ test = [ "pytest-xdist == 3.8.0", "pytest-timeout == 2.4.0", "flake8 == 7.3.0", - "switchboard-hw == 0.3.0" + "switchboard-hw == 0.3.1", + "cocotb==2.0.1", + "cocotb-bus==0.3.0", + "cocotbext-umi==0.0.2" ] [tool.check-wheel-contents] @@ -42,7 +45,8 @@ ignore = [ [tool.pytest.ini_options] markers = [ - "switchboard: this test requires switchboard to run" + "switchboard: this test requires switchboard to run", + "cocotb: this test requires cocotb to run" ] testpaths = [ "tests", diff --git a/tests/sumi/umi_stream/surfer_config.surf.ron b/tests/sumi/umi_stream/surfer_config.surf.ron new file mode 100644 index 00000000..25cfa17f --- /dev/null +++ b/tests/sumi/umi_stream/surfer_config.surf.ron @@ -0,0 +1,955 @@ +( + show_hierarchy: None, + show_menu: None, + show_ticks: None, + show_toolbar: None, + show_tooltip: None, + show_scope_tooltip: None, + show_default_timeline: None, + show_overview: None, + show_statusbar: None, + align_names_right: None, + show_variable_indices: None, + show_variable_direction: None, + show_empty_scopes: None, + show_parameters_in_scopes: None, + parameter_display_location: None, + highlight_focused: None, + fill_high_values: None, + primary_button_drag_behavior: None, + arrow_key_bindings: None, + clock_highlight_type: None, + hierarchy_style: None, + autoload_sibling_state_files: None, + autoreload_files: None, + waves: Some(( + source: File("umi_stream.vcd"), + format: Vcd, + active_scope: Some(WaveScope(( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ))), + items_tree: ( + items: [ + ( + item_ref: (1), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (2), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (3), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (4), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (5), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (6), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (7), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (8), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (9), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (10), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (11), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (12), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (13), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (14), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (15), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (16), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (17), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (18), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (19), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (24), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (20), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (21), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (22), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (23), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (25), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (26), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (27), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (28), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (29), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (30), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (31), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (34), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (32), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (33), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (35), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (36), + level: 0, + unfolded: true, + selected: false, + ), + ( + item_ref: (37), + level: 0, + unfolded: true, + selected: false, + ), + ], + ), + displayed_items: { + (22): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "usi_out_last", + id: Wellen((30)), + ), + color: None, + background_color: None, + display_name: "usi_out_last", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (36): Divider(( + color: None, + background_color: None, + name: None, + )), + (2): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_clk", + id: Wellen((10)), + ), + color: None, + background_color: None, + display_name: "umi_clk", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (17): Divider(( + color: None, + background_color: None, + name: None, + )), + (20): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "usi_out_valid", + id: Wellen((27)), + ), + color: None, + background_color: None, + display_name: "usi_out_valid", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (37): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "devicemode", + id: Wellen((1)), + ), + color: None, + background_color: None, + display_name: "devicemode", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (11): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_out_valid", + id: Wellen((32)), + ), + color: None, + background_color: None, + display_name: "umi_out_valid", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (7): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_in_srcaddr", + id: Wellen((15)), + ), + color: None, + background_color: None, + display_name: "umi_in_srcaddr [63:0]", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (33): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "s2mm_srcaddr", + id: Wellen((9)), + ), + color: None, + background_color: None, + display_name: "s2mm_srcaddr [63:0]", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (13): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_out_dstaddr", + id: Wellen((34)), + ), + color: None, + background_color: None, + display_name: "umi_out_dstaddr [63:0]", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (14): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_out_srcaddr", + id: Wellen((33)), + ), + color: None, + background_color: None, + display_name: "umi_out_srcaddr [63:0]", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (12): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_out_cmd", + id: Wellen((36)), + ), + color: None, + background_color: None, + display_name: "umi_out_cmd [31:0]", + display_name_type: Unique, + manual_name: None, + format: Some("umi"), + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (16): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_out_ready", + id: Wellen((18)), + ), + color: None, + background_color: None, + display_name: "umi_out_ready", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (28): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "usi_in_last", + id: Wellen((22)), + ), + color: None, + background_color: None, + display_name: "usi_in_last", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (18): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "usi_clk", + id: Wellen((20)), + ), + color: None, + background_color: None, + display_name: "usi_clk", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (29): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "usi_in_ready", + id: Wellen((23)), + ), + color: None, + background_color: None, + display_name: "usi_in_ready", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (10): Divider(( + color: None, + background_color: None, + name: None, + )), + (15): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_out_data", + id: Wellen((35)), + ), + color: None, + background_color: None, + display_name: "umi_out_data [255:0]", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (34): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "s2mm_cmd", + id: Wellen((6)), + ), + color: None, + background_color: None, + display_name: "s2mm_cmd [31:0]", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (23): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "usi_out_ready", + id: Wellen((26)), + ), + color: None, + background_color: None, + display_name: "usi_out_ready", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (8): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_in_data", + id: Wellen((12)), + ), + color: None, + background_color: None, + display_name: "umi_in_data [255:0]", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (27): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "usi_in_data", + id: Wellen((21)), + ), + color: None, + background_color: None, + display_name: "usi_in_data [255:0]", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (1): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_nreset", + id: Wellen((17)), + ), + color: None, + background_color: None, + display_name: "umi_nreset", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (4): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_in_valid", + id: Wellen((16)), + ), + color: None, + background_color: None, + display_name: "umi_in_valid", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (5): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_in_cmd", + id: Wellen((11)), + ), + color: None, + background_color: None, + display_name: "umi_in_cmd [31:0]", + display_name_type: Unique, + manual_name: None, + format: Some("umi"), + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (6): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_in_dstaddr", + id: Wellen((13)), + ), + color: None, + background_color: None, + display_name: "umi_in_dstaddr [63:0]", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (19): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "usi_nreset", + id: Wellen((25)), + ), + color: None, + background_color: None, + display_name: "usi_nreset", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (21): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "usi_out_data", + id: Wellen((31)), + ), + color: None, + background_color: None, + display_name: "usi_out_data [255:0]", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (24): Divider(( + color: None, + background_color: None, + name: None, + )), + (26): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "usi_in_valid", + id: Wellen((24)), + ), + color: None, + background_color: None, + display_name: "usi_in_valid", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (31): Divider(( + color: None, + background_color: None, + name: None, + )), + (32): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "s2mm_dstaddr", + id: Wellen((7)), + ), + color: None, + background_color: None, + display_name: "s2mm_dstaddr [63:0]", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (30): Divider(( + color: None, + background_color: None, + name: None, + )), + (3): Divider(( + color: None, + background_color: None, + name: None, + )), + (9): Variable(( + variable_ref: ( + path: ( + strs: [ + "umi_stream", + ], + id: Wellen((2)), + ), + name: "umi_in_ready", + id: Wellen((14)), + ), + color: None, + background_color: None, + display_name: "umi_in_ready", + display_name_type: Unique, + manual_name: None, + format: None, + field_formats: [], + height_scaling_factor: None, + analog: None, + )), + (25): Divider(( + color: None, + background_color: None, + name: None, + )), + (35): Divider(( + color: None, + background_color: None, + name: None, + )), + }, + display_item_ref_counter: 37, + viewports: [ + ( + curr_left: (-0.0013646285412315911), + curr_right: (0.015823649063907094), + target_left: (0.0), + target_right: (1.0), + move_start_left: (0.0), + move_start_right: (1.0), + move_duration: None, + move_strategy: Instant, + ), + ], + cursor: Some((1, [ + 57992321, + ])), + markers: {}, + focused_item: Some((35)), + focused_transaction: (None, None), + default_variable_name_type: Unique, + scroll_offset: 0.0, + display_variable_indices: true, + graphics: {}, + )), + drag_started: false, + drag_source_idx: None, + drag_target_idx: Some(( + before: (32), + level: 0, + )), + previous_waves: None, + count: None, + blacklisted_translators: [], + show_about: false, + show_keys: false, + show_gestures: false, + show_quick_start: false, + show_license: false, + show_performance: false, + show_logs: false, + show_cursor_window: false, + wanted_timeunit: PicoSeconds, + time_string_format: None, + show_url_entry: false, + variable_name_filter_focused: false, + variable_filter: ( + name_filter_type: Contain, + name_filter_str: "dev", + name_filter_case_insensitive: true, + include_inputs: true, + include_outputs: true, + include_inouts: true, + include_others: true, + group_by_direction: false, + ), + sidepanel_width: Some(151.59375), + ui_zoom_factor: Some(1.5), + animation_enabled: None, + use_dinotrace_style: None, + transition_value: None, +) \ No newline at end of file diff --git a/tests/sumi/umi_stream/test_umi_stream.py b/tests/sumi/umi_stream/test_umi_stream.py new file mode 100644 index 00000000..5ed19893 --- /dev/null +++ b/tests/sumi/umi_stream/test_umi_stream.py @@ -0,0 +1,675 @@ +import os +import math +import random +import copy + +import pytest + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import ClockCycles, Timer, Combine + +from cocotb_bus.drivers import BitDriver +from cocotb_bus.scoreboard import Scoreboard + +from cocotbext.umi.drivers.sumi_driver import SumiDriver +from cocotbext.umi.monitors.sumi_monitor import SumiMonitor +from cocotbext.umi.sumi import SumiCmd, SumiCmdType, SumiTransaction +from cocotbext.umi.tumi import TumiTransaction +from cocotbext.umi.utils import generators +from cocotbext.umi.utils.vrd_transaction import VRDTransaction + +from valid_ready_driver import ValidReadyDriver +from valid_ready_monitor import ValidReadyMonitor + +from siliconcompiler import Design, Sim +from siliconcompiler.flows.dvflow import DVFlow + +from siliconcompiler.tools.icarus.compile import CompileTask as IcarusCompileTask +from siliconcompiler.tools.icarus.cocotb_exec import CocotbExecTask as IcarusCocotbExecTask + +from umi.sumi.umi_stream.umi_stream import Stream + + +###################################################### +# UMI Response Compare +###################################################### + +def make_umi_compare(expected_output, scoreboard): + """Create a monitor callback that compares UMI responses on critical fields. + + The Scoreboard registers this directly as the monitor callback when + compare_fn is provided, so it must pop from expected_output itself. + """ + def check(transaction): + if not expected_output: + scoreboard.errors += 1 + scoreboard.log.error("Received UMI response but wasn't expecting any") + if scoreboard._imm: + raise AssertionError("Received UMI response but wasn't expecting any") + return + + exp = expected_output.pop(0) + errors = [] + + if int(transaction.cmd.cmd_type) != int(exp.cmd.cmd_type): + errors.append( + f"cmd_type: expected {exp.cmd.cmd_type}, got {transaction.cmd.cmd_type}" + ) + + if int(transaction.da) != int(exp.da): + errors.append( + f"da: expected 0x{int(exp.da):x}, got 0x{int(transaction.da):x}" + ) + + if exp.data is not None: + got_data = int.from_bytes(transaction.data, 'little') if transaction.data else 0 + exp_data = int.from_bytes(exp.data, 'little') + if got_data != exp_data: + errors.append( + f"data: expected 0x{exp_data:x}, got 0x{got_data:x}" + ) + + if errors: + scoreboard.errors += 1 + for e in errors: + scoreboard.log.error(f"UMI response mismatch: {e}") + if scoreboard._imm: + raise AssertionError("UMI response mismatch:\n" + "\n".join(errors)) + + return check + + +###################################################### +# Test Environment +###################################################### + +class Env: + """Reusable test environment for umi_stream tests.""" + + def __init__(self, dut): + self.dut = dut + self.dw = int(dut.DW.value) + self.aw = int(dut.AW.value) + self.cw = int(dut.CW.value) + self.data_bytes = self.dw // 8 + self.full_size = int(math.log2(self.data_bytes)) + + self.umi_driver = None + self.umi_monitor = None + self.usi_source = None + self.usi_monitor = None + + self.expected_usi_output = [] + self.expected_umi_output = [] + self.scoreboard = None + + async def assert_reset(self, nreset, period_ns=50): + nreset.value = 1 + await Timer(1, unit="step") + nreset.value = 0 + await Timer(period_ns, unit="ns") + nreset.value = 1 + await Timer(period_ns, unit="ns") + + async def setup(self, umi_valid_gen, umi_period_ns=10, usi_period_ns=12): + """Initialize signals, start clocks, apply reset, create drivers.""" + dut = self.dut + + # Initialize DUT inputs before drivers take over + dut.devicemode.value = 1 + dut.s2mm_dstaddr.value = 0 + dut.s2mm_srcaddr.value = 0 + dut.s2mm_cmd.value = 0 + dut.umi_in_valid.value = 0 + dut.umi_in_cmd.value = 0 + dut.umi_in_dstaddr.value = 0 + dut.umi_in_srcaddr.value = 0 + dut.umi_in_data.value = 0 + dut.umi_out_ready.value = 0 + dut.usi_in_valid.value = 0 + dut.usi_in_last.value = 0 + dut.usi_in_data.value = 0 + dut.usi_out_ready.value = 0 + + # Reset both clock domains + umi_reset_task = cocotb.start_soon(self.assert_reset(dut.umi_nreset, umi_period_ns*5)) + usi_reset_task = cocotb.start_soon(self.assert_reset(dut.usi_nreset, usi_period_ns*5)) + await Combine(umi_reset_task, usi_reset_task) + + # Start independent clocks for UMI and USI domains + Clock(dut.umi_clk, umi_period_ns, unit="ns").start() + await Timer(umi_period_ns * random.random(), unit="ns", round_mode="round") + Clock(dut.usi_clk, usi_period_ns, unit="ns").start() + + # Drivers + self.umi_driver = SumiDriver( + entity=dut, + name="umi_in", + clock=dut.umi_clk, + valid_generator=umi_valid_gen + ) + self.usi_source = ValidReadyDriver(entity=dut, name="usi_in", clock=dut.usi_clk) + + # Monitors (passive -- do NOT drive ready signals) + self.umi_monitor = SumiMonitor(entity=dut, name="umi_out", clock=dut.umi_clk) + self.usi_monitor = ValidReadyMonitor(entity=dut, name="usi_out", clock=dut.usi_clk) + + # Scoreboard + self.expected_usi_output = [] + self.expected_umi_output = [] + self.scoreboard = Scoreboard(dut, fail_immediately=True) + self.scoreboard.add_interface( + monitor=self.usi_monitor, + expected_output=self.expected_usi_output + ) + self.scoreboard.add_interface( + monitor=self.umi_monitor, + expected_output=self.expected_umi_output + ) + + await ClockCycles(dut.umi_clk, 5) + + +###################################################### +# Cocotb Tests +###################################################### + +@cocotb.test(timeout_time=100, timeout_unit="ms") +@cocotb.parametrize( + umi_valid_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], + umi_ready_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], + usi_ready_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], + n_ops=[int(20 * float(os.getenv("RAND_TEST_LEN_SCALER", default=1)))] +) +async def test_device_mode( + dut, + umi_valid_gen=None, + umi_ready_gen=None, + usi_ready_gen=None, + n_ops=20 +): + """Device mode: posted writes, writes with ack, and reads with data verification.""" + + env = Env(dut) + await env.setup(umi_valid_gen) + dut.devicemode.value = 1 + + # Apply backpressure on UMI response consumer + if umi_ready_gen is not None: + BitDriver(signal=dut.umi_out_ready, clk=dut.umi_clk).start(generator=umi_ready_gen) + else: + dut.umi_out_ready.value = 1 + + # Apply backpressure on USI stream consumer + if usi_ready_gen is not None: + BitDriver(signal=dut.usi_out_ready, clk=dut.usi_clk).start(generator=usi_ready_gen) + else: + dut.usi_out_ready.value = 1 + + #################################### + # Phase 1: Posted writes + #################################### + dut._log.info(f"Phase 1: {n_ops} posted writes") + max_tumi_len = 500 + for _ in range(n_ops): + + cmd_type = random.choice([ + SumiCmdType.UMI_REQ_POSTED, + SumiCmdType.UMI_REQ_WRITE, + SumiCmdType.UMI_REQ_READ + ]) + + sumi_trans = None + + if cmd_type == SumiCmdType.UMI_REQ_READ: + sumi_trans = [SumiTransaction( + cmd=SumiCmd.from_fields( + cmd_type=cmd_type, + size=env.full_size, + len=0, + eom=1 + ), + # TODO: DUT will accept any DST ADDR this seems like an issue 2 me + da=random.randint(0, (1 << len(dut.umi_in_dstaddr)) - 1), + sa=random.randint(0, (1 << len(dut.umi_in_srcaddr)) - 1), + data=random.randbytes(env.data_bytes), + addr_width=env.aw + )] + else: + # Create random tumi transaction + sumi_trans = TumiTransaction( + cmd=SumiCmd.from_fields(cmd_type=cmd_type), + da=random.randint(0, (1 << len(dut.umi_in_dstaddr)) - 1), + sa=random.randint(0, (1 << len(dut.umi_in_srcaddr)) - 1), + data=bytes(random.randbytes( + (random.randint(env.data_bytes, max_tumi_len) // env.data_bytes) * env.data_bytes + )), + ).to_sumi( + data_bus_size=env.data_bytes, + addr_width=env.aw + ) + + if cmd_type == SumiCmdType.UMI_REQ_POSTED: + for t in sumi_trans: + # Add sumi trans to expected output + env.expected_usi_output.append(VRDTransaction( + data=t.data, + last=bool(int(t.cmd.eom)) + )) + elif cmd_type == SumiCmdType.UMI_REQ_WRITE: + for t in sumi_trans: + # Add sumi trans to expected output + env.expected_usi_output.append(VRDTransaction( + data=t.data, + last=bool(int(t.cmd.eom)) + )) + # Add response to UMI expected output + resp_cmd = copy.deepcopy(t.cmd) + resp_cmd.cmd_type.from_int(SumiCmdType.UMI_RESP_WRITE) + env.expected_umi_output.append(SumiTransaction( + cmd=resp_cmd, + da=int(t.sa), + # TODO: RTL hardcodes source address (This should be fixed and a expected value should be put here) + sa=0, + data=None, + )) + elif cmd_type == SumiCmdType.UMI_REQ_READ: + for t in sumi_trans: + data = random.randbytes(env.data_bytes) + # Add response to UMI expected output + resp_cmd = copy.deepcopy(t.cmd) + resp_cmd.cmd_type.from_int(SumiCmdType.UMI_RESP_READ) + env.expected_umi_output.append(SumiTransaction( + cmd=resp_cmd, + da=int(t.sa), + # TODO: RTL hardcodes source address (This should be fixed and a expected value should be put here) + sa=0, + data=data, + )) + env.usi_source.append(VRDTransaction( + data=data, + last=bool(int(t.cmd.eom)) + )) + + # Add sumi trans to UMI driver + for t in sumi_trans: + env.umi_driver.append(t) + + while ( + len(env.expected_usi_output) != 0 + or len(env.expected_umi_output) != 0 + ): + await ClockCycles(dut.umi_clk, 1) + + raise env.scoreboard.result + + + + ##################################### + ## Phase 3: Reads from S2MM FIFO + ##################################### + #dut._log.info(f"Phase 3: {n_ops} reads") + #for i in range(n_ops): + # data = random.getrandbits(env.dw) + # srcaddr = random.getrandbits(env.aw) + + # # Queue USI data into S2MM FIFO + # env.usi_source.append(VRDTransaction( + # data=data.to_bytes(env.data_bytes, 'little'), + # last=True + # )) + # # Queue UMI read request (DUT stalls until FIFO has data) + # cmd = SumiCmd.from_fields( + # cmd_type=SumiCmdType.UMI_REQ_READ, + # size=env.full_size, + # len=0, + # eom=1 + # ) + # env.umi_driver.append(SumiTransaction( + # cmd=cmd, + # da=random.getrandbits(env.aw), + # sa=srcaddr, + # data=bytes(env.data_bytes), + # addr_width=env.aw + # )) + # env.expected_umi_output.append(SumiTransaction( + # cmd=SumiCmd.from_fields( + # cmd_type=SumiCmdType.UMI_RESP_READ, + # size=env.full_size, + # len=0, + # eom=1 + # ), + # da=srcaddr, + # sa=0, + # data=data.to_bytes(env.data_bytes, 'little'), + # addr_width=env.aw + # )) + + #while env.expected_umi_output: + # await ClockCycles(dut.umi_clk, 1) + + #await ClockCycles(dut.umi_clk, 20) + #dut._log.info("Device mode test complete") + + +#@cocotb.test(timeout_time=100, timeout_unit="ms") +#@cocotb.parametrize( +# umi_ready_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], +# usi_ready_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], +# n_ops=[int(20 * float(os.getenv("RAND_TEST_LEN_SCALER", default=1)))] +#) +async def test_nondevice_mode( + dut, + umi_ready_gen=None, + usi_ready_gen=None, + n_ops=20 +): + """Non-device (full duplex) mode: posted writes and stream-to-UMI passthrough.""" + + env = Env(dut) + await env.setup() + dut.devicemode.value = 0 + await ClockCycles(dut.umi_clk, 5) + + if umi_ready_gen is not None: + BitDriver(signal=dut.umi_out_ready, clk=dut.umi_clk).start(generator=umi_ready_gen) + else: + dut.umi_out_ready.value = 1 + + if usi_ready_gen is not None: + BitDriver(signal=dut.usi_out_ready, clk=dut.usi_clk).start(generator=usi_ready_gen) + else: + dut.usi_out_ready.value = 1 + + #################################### + # Phase 1: Posted writes to stream + #################################### + dut._log.info(f"Phase 1: {n_ops} posted writes (non-device)") + for i in range(n_ops): + data = random.getrandbits(env.dw) + eom = random.choice([0, 1]) + cmd = SumiCmd.from_fields( + cmd_type=SumiCmdType.UMI_REQ_POSTED, + size=env.full_size, + len=0, + eom=eom + ) + env.umi_driver.append(SumiTransaction( + cmd=cmd, + da=random.getrandbits(env.aw), + sa=random.getrandbits(env.aw), + data=data.to_bytes(env.data_bytes, 'little'), + addr_width=env.aw + )) + env.expected_usi_output.append(VRDTransaction( + data=data.to_bytes(env.data_bytes, 'little'), + last=bool(eom) + )) + + while env.expected_usi_output: + await ClockCycles(dut.umi_clk, 1) + + dut._log.info("Phase 1 passed: non-device posted writes") + + #################################### + # Phase 2: Stream to UMI passthrough + #################################### + dut._log.info(f"Phase 2: {n_ops} stream-to-UMI passthrough") + + # Configure S2MM control signals for passthrough + s2mm_cmd = SumiCmd.from_fields( + cmd_type=SumiCmdType.UMI_RESP_READ, + size=env.full_size, + len=0, + eom=1 + ) + s2mm_dst = random.getrandbits(env.aw) + s2mm_src = random.getrandbits(env.aw) + dut.s2mm_cmd.value = int(s2mm_cmd) + dut.s2mm_dstaddr.value = s2mm_dst + dut.s2mm_srcaddr.value = s2mm_src + + for i in range(n_ops): + data = random.getrandbits(env.dw) + env.usi_source.append(VRDTransaction( + data=data.to_bytes(env.data_bytes, 'little'), + last=True + )) + env.expected_umi_output.append(SumiTransaction( + cmd=s2mm_cmd, + da=s2mm_dst, + sa=0, + data=data.to_bytes(env.data_bytes, 'little'), + addr_width=env.aw + )) + + while env.expected_umi_output: + await ClockCycles(dut.umi_clk, 1) + + dut._log.info("Phase 2 passed: stream-to-UMI passthrough") + await ClockCycles(dut.umi_clk, 20) + raise env.scoreboard.result + + +#@cocotb.test(timeout_time=200, timeout_unit="ms") +async def test_fifo_stress(dut): + """Stress test: rapid posted writes with slow stream consumption to exercise FIFO.""" + + env = Env(dut) + await env.setup() + dut.devicemode.value = 1 + + # Slow stream consumer to force FIFO fill-up + BitDriver(signal=dut.usi_out_ready, clk=dut.usi_clk).start( + generator=generators.wave_generator() + ) + dut.umi_out_ready.value = 1 + + n = int(100 * float(os.getenv("RAND_TEST_LEN_SCALER", default=1))) + + for i in range(n): + data = random.getrandbits(env.dw) + eom = 1 if (i % 8 == 7) else 0 + cmd = SumiCmd.from_fields( + cmd_type=SumiCmdType.UMI_REQ_POSTED, + size=env.full_size, + len=0, + eom=eom + ) + env.umi_driver.append(SumiTransaction( + cmd=cmd, + da=0, + sa=0, + data=data.to_bytes(env.data_bytes, 'little'), + addr_width=env.aw + )) + env.expected_usi_output.append(VRDTransaction( + data=data.to_bytes(env.data_bytes, 'little'), + last=bool(eom) + )) + + while env.expected_usi_output: + await ClockCycles(dut.umi_clk, 1) + + await ClockCycles(dut.umi_clk, 10) + dut._log.info(f"FIFO stress test passed ({n} transactions)") + raise env.scoreboard.result + + +#@cocotb.test(timeout_time=100, timeout_unit="ms") +async def test_mixed_operations(dut): + """Interleaved device-mode operations: posted writes, ack writes, and reads.""" + + env = Env(dut) + await env.setup() + dut.devicemode.value = 1 + + # Moderate backpressure on both consumer sides + BitDriver(signal=dut.umi_out_ready, clk=dut.umi_clk).start( + generator=generators.random_toggle_generator() + ) + BitDriver(signal=dut.usi_out_ready, clk=dut.usi_clk).start( + generator=generators.random_toggle_generator() + ) + + n = int(30 * float(os.getenv("RAND_TEST_LEN_SCALER", default=1))) + dut._log.info(f"Running {n} mixed operations") + + # Pre-generate operation sequence, compute expected outputs, and queue all + for i in range(n): + op = random.choice(['posted_write', 'write_ack', 'read']) + data = random.getrandbits(env.dw) + data_bytes = data.to_bytes(env.data_bytes, 'little') + srcaddr = random.getrandbits(env.aw) + dstaddr = random.getrandbits(env.aw) + + if op == 'posted_write': + eom = random.choice([0, 1]) + cmd = SumiCmd.from_fields( + cmd_type=SumiCmdType.UMI_REQ_POSTED, + size=env.full_size, + len=0, + eom=eom + ) + env.umi_driver.append(SumiTransaction( + cmd=cmd, + da=dstaddr, + sa=srcaddr, + data=data_bytes, + addr_width=env.aw + )) + env.expected_usi_output.append(VRDTransaction( + data=data_bytes, + last=bool(eom) + )) + + elif op == 'write_ack': + cmd = SumiCmd.from_fields( + cmd_type=SumiCmdType.UMI_REQ_WRITE, + size=env.full_size, + len=0, + eom=1 + ) + env.umi_driver.append(SumiTransaction( + cmd=cmd, + da=dstaddr, + sa=srcaddr, + data=data_bytes, + addr_width=env.aw + )) + env.expected_usi_output.append(VRDTransaction( + data=data_bytes, + last=True + )) + env.expected_umi_output.append(SumiTransaction( + cmd=SumiCmd.from_fields( + cmd_type=SumiCmdType.UMI_RESP_WRITE, + size=env.full_size, + len=0, + eom=1 + ), + da=srcaddr, + sa=0, + data=None, + addr_width=env.aw + )) + + elif op == 'read': + # Queue USI data into S2MM FIFO + env.usi_source.append(VRDTransaction( + data=data_bytes, + last=True + )) + cmd = SumiCmd.from_fields( + cmd_type=SumiCmdType.UMI_REQ_READ, + size=env.full_size, + len=0, + eom=1 + ) + env.umi_driver.append(SumiTransaction( + cmd=cmd, + da=dstaddr, + sa=srcaddr, + data=bytes(env.data_bytes), + addr_width=env.aw + )) + env.expected_umi_output.append(SumiTransaction( + cmd=SumiCmd.from_fields( + cmd_type=SumiCmdType.UMI_RESP_READ, + size=env.full_size, + len=0, + eom=1 + ), + da=srcaddr, + sa=0, + data=data_bytes, + addr_width=env.aw + )) + + # Wait for all expected outputs to be consumed by scoreboards + while env.expected_usi_output or env.expected_umi_output: + await ClockCycles(dut.umi_clk, 1) + + dut._log.info(f"Mixed operations test passed ({n} ops)") + await ClockCycles(dut.umi_clk, 20) + raise env.scoreboard.result + + +###################################################### +# SiliconCompiler Design & Pytest Integration +###################################################### + +class TbDesign(Design): + + def __init__(self): + super().__init__() + + self.set_name("tb_umi_stream") + + self.set_dataroot("tb_umi_stream", __file__) + + with self.active_dataroot("tb_umi_stream"): + with self.active_fileset("testbench.cocotb"): + self.set_topmodule("umi_stream") + self.add_file("test_umi_stream.py", filetype="python") + self.add_depfileset(Stream(), "rtl") + + +def load_cocotb_test(trace=True, seed=None): + project = Sim() + project.set_design(TbDesign()) + project.add_fileset("testbench.cocotb") + + project.set_flow(DVFlow(tool="icarus-cocotb")) + + IcarusCompileTask.find_task(project).set_trace_enabled(trace) + + if seed is not None: + IcarusCocotbExecTask.find_task(project).set_cocotb_randomseed(seed) + + project.run() + project.summary() + + results = project.find_result( + step='simulate', + index='0', + directory="outputs", + filename="results.xml" + ) + if results: + print(f"\nCocotb results file: {results}") + + vcd = project.find_result( + step='simulate', + index='0', + directory="reports", + filename="tb_umi_stream.vcd" + ) + if vcd: + print(f"Waveform file: {vcd}") + + +@pytest.mark.cocotb +def test_umi_stream(): + load_cocotb_test() diff --git a/tests/sumi/umi_stream/valid_ready_driver.py b/tests/sumi/umi_stream/valid_ready_driver.py new file mode 100644 index 00000000..10561bf1 --- /dev/null +++ b/tests/sumi/umi_stream/valid_ready_driver.py @@ -0,0 +1,77 @@ +from typing import Any + +from cocotb.types import LogicArray +from cocotb.triggers import RisingEdge +from cocotb.handle import SimHandleBase + +from cocotb_bus.drivers import ValidatedBusDriver + +from cocotbext.umi.utils.vrd_transaction import VRDTransaction + + +class ValidReadyDriver(ValidatedBusDriver): + + _signals = [ + "data", + "valid", + "ready" + ] + + _optional_signals = ["strb", "len", "last"] + + def __init__( + self, + entity: SimHandleBase, + name: str, + clock: SimHandleBase, + *, + config={}, + **kwargs: Any + ): + ValidatedBusDriver.__init__(self, entity, name, clock, **kwargs) + + self.clock = clock + self.bus.valid.value = 0 + + async def _driver_send(self, transaction: VRDTransaction, sync: bool = True) -> None: + """Implementation for BusDriver. + Args: + transaction: The transaction to send. + sync: Synchronize the transfer by waiting for a rising edge. + """ + + clk_re = RisingEdge(self.clock) + + if sync: + await clk_re + + # Insert a gap where valid is low + if not self.on: + self.bus.valid.value = 0 + for _ in range(self.off): + await clk_re + + # Grab the next set of on/off values + self._next_valids() + + # Consume a valid cycle + if self.on is not True and self.on: + self.on -= 1 + + def ready() -> bool: + return bool(self.bus.ready.value) + + while True: + self.bus.valid.value = 1 + self.bus.data.value = LogicArray.from_bytes(transaction.data, byteorder="little") + if hasattr(self.bus, "strb"): + self.bus.strb.value = LogicArray(transaction.strb) + if hasattr(self.bus, "len"): + self.bus.len.value = transaction.len + if hasattr(self.bus, "last"): + self.bus.last.value = transaction.last + await clk_re + if ready(): + break + + self.bus.valid.value = 0 diff --git a/tests/sumi/umi_stream/valid_ready_monitor.py b/tests/sumi/umi_stream/valid_ready_monitor.py new file mode 100644 index 00000000..d219c2de --- /dev/null +++ b/tests/sumi/umi_stream/valid_ready_monitor.py @@ -0,0 +1,32 @@ +from cocotb.triggers import RisingEdge + +from cocotb_bus.monitors import BusMonitor + +from cocotbext.umi.utils.vrd_transaction import VRDTransaction + + +class ValidReadyMonitor(BusMonitor): + + _signals = [ + "data", + "valid", + "ready" + ] + _optional_signals = ["last"] + + def __init__(self, entity, name, clock, **kwargs): + BusMonitor.__init__(self, entity, name, clock, **kwargs) + + async def _monitor_recv(self): + clk_re = RisingEdge(self.clock) + + def valid_handshake(): + return bool(self.bus.valid.value) and bool(self.bus.ready.value) + + while True: + await clk_re + if valid_handshake(): + self._recv(VRDTransaction( + data=self.bus.data.value.to_bytes(byteorder="little"), + last=bool(self.bus.last.value) if hasattr(self.bus, "last") else None + )) diff --git a/umi/sumi/__init__.py b/umi/sumi/__init__.py index fdda0e15..e07bdb26 100644 --- a/umi/sumi/__init__.py +++ b/umi/sumi/__init__.py @@ -13,7 +13,7 @@ from .umi_pipeline.umi_pipeline import Pipeline from .umi_ram.umi_ram import RAM from .umi_regif.umi_regif import Regif -from .umi_switch.umi_stream import Stream +from .umi_stream.umi_stream import Stream from .umi_switch.umi_switch import Switch from .umi_tester.umi_tester import Tester from .umi_unpack.umi_unpack import Unpack diff --git a/umi/sumi/umi_stream/rtl/umi_stream.v b/umi/sumi/umi_stream/rtl/umi_stream.v index 11809146..69a91846 100644 --- a/umi/sumi/umi_stream/rtl/umi_stream.v +++ b/umi/sumi/umi_stream/rtl/umi_stream.v @@ -26,7 +26,7 @@ * are allowed. * ******************************************************************************/ - +`timescale 1ns/1ps module umi_stream #(parameter AW = 64, // UMI data width parameter CW = 32, // UMI command width From 87e42ef3e967a6b16b168c7c3c71c155ddf77791 Mon Sep 17 00:00:00 2001 From: Rice Shelley Date: Tue, 10 Feb 2026 14:41:19 -0500 Subject: [PATCH 2/4] Reworking umi_stream test --- tests/sumi/umi_stream/.gitignore | 1 + tests/sumi/umi_stream/icarus_cmd_file.f | 1 + tests/sumi/umi_stream/run_cocotb_sim.py | 140 ++++++ tests/sumi/umi_stream/test_umi_stream.py | 525 ++++++-------------- tests/sumi/umi_stream/verilator_cmd_file.vc | 1 + umi/sumi/umi_stream/rtl/umi_stream.v | 2 +- 6 files changed, 287 insertions(+), 383 deletions(-) create mode 100644 tests/sumi/umi_stream/.gitignore create mode 100644 tests/sumi/umi_stream/icarus_cmd_file.f create mode 100644 tests/sumi/umi_stream/run_cocotb_sim.py create mode 100644 tests/sumi/umi_stream/verilator_cmd_file.vc diff --git a/tests/sumi/umi_stream/.gitignore b/tests/sumi/umi_stream/.gitignore new file mode 100644 index 00000000..b33ddd22 --- /dev/null +++ b/tests/sumi/umi_stream/.gitignore @@ -0,0 +1 @@ +!icarus_cmd_file.f diff --git a/tests/sumi/umi_stream/icarus_cmd_file.f b/tests/sumi/umi_stream/icarus_cmd_file.f new file mode 100644 index 00000000..3e26e00a --- /dev/null +++ b/tests/sumi/umi_stream/icarus_cmd_file.f @@ -0,0 +1 @@ ++timescale+1ns/1ps diff --git a/tests/sumi/umi_stream/run_cocotb_sim.py b/tests/sumi/umi_stream/run_cocotb_sim.py new file mode 100644 index 00000000..03e58729 --- /dev/null +++ b/tests/sumi/umi_stream/run_cocotb_sim.py @@ -0,0 +1,140 @@ +from siliconcompiler import Design, Sim +from siliconcompiler.flows.dvflow import DVFlow + +from siliconcompiler.tools.icarus.compile import CompileTask as IcarusCompileTask +from siliconcompiler.tools.icarus.cocotb_exec import CocotbExecTask as IcarusCocotbExecTask + +from siliconcompiler.tools.verilator.cocotb_compile import CocotbCompileTask as VerilatorCompileTask +from siliconcompiler.tools.verilator.cocotb_exec import CocotbExecTask as VerilatorCocotbExecTask + + +class IcarusDesign(Design): + def __init__(self, design: Design): + super().__init__() + + self.set_name(f"{design.name}_icarus_sim") + + self.set_dataroot("icarus_tb", __file__) + + with self.active_dataroot("icarus_tb"): + with self.active_fileset("icarus_sim"): + self.add_file("icarus_cmd_file.f", filetype="commandfile") + self.add_depfileset(design, "testbench.cocotb") + self.set_topmodule(design.get_topmodule("testbench.cocotb")) + + +class VerilatorDesign(Design): + def __init__(self, design: Design): + super().__init__() + + self.set_name(f"{design.name}_verilator_sim") + + self.set_dataroot("verilator_tb", __file__) + + with self.active_dataroot("verilator_tb"): + with self.active_fileset("verilator_sim"): + self.add_file("verilator_cmd_file.vc", filetype="commandfile") + self.add_depfileset(design, "testbench.cocotb") + self.set_topmodule(design.get_topmodule("testbench.cocotb")) + + +def load_cocotb_test( + design: Design, + simulator="icarus", + trace=True, + seed=None +): + + if simulator == "icarus": + load_cocotb_icarus_sim(design, trace=trace, seed=seed) + elif simulator == "verilator": + load_cocotb_verilator_sim(design, trace=trace, seed=seed, trace_type="vcd") + + +def load_cocotb_icarus_sim( + design: Design, + trace=True, + seed=None +): + project = Sim() + project.set_design(IcarusDesign(design)) + project.add_fileset("icarus_sim") + project.set_flow(DVFlow(tool="icarus-cocotb")) + + IcarusCompileTask.find_task(project).set_trace_enabled(trace) + + if seed is not None: + IcarusCocotbExecTask.find_task(project).set_cocotb_randomseed(seed) + + project.run() + project.summary() + + results = project.find_result( + step='simulate', + index='0', + directory="outputs", + filename="results.xml" + ) + if results: + print(f"\nCocotb results file: {results}") + + vcd = project.find_result( + step='simulate', + index='0', + directory="reports", + filename="tb_umi_stream.vcd" + ) + if vcd: + print(f"Waveform file: {vcd}") + + +def load_cocotb_verilator_sim( + design: Design, + trace=True, + seed=None, + trace_type="vcd" +): + project = Sim() + project.set_design(VerilatorDesign(design)) + project.add_fileset("verilator_sim") + project.set_flow(DVFlow(tool="verilator-cocotb")) + + # Enable waveform tracing (must be enabled on both compile and simulate tasks) + compile_task = VerilatorCompileTask.find_task(project) + compile_task.set_verilator_trace(trace) + compile_task.set_verilator_tracetype(trace_type) + + cocotb_task = VerilatorCocotbExecTask.find_task(project) + cocotb_task.set_cocotb_trace( + enable=trace, + trace_type=trace_type + ) + + # Optionally set a random seed for reproducibility + if seed is not None: + cocotb_task.set_cocotb_randomseed(seed) + + # Run the simulation + project.run() + project.summary() + + # Find and display the results file + results = project.find_result( + step='simulate', + index='0', + directory="outputs", + filename="results.xml" + ) + if results: + print(f"\nCocotb results file: {results}") + + # Find and display the waveform file + wave_ext = trace_type if trace_type in ("vcd", "fst") else "vcd" + wave = project.find_result( + step='simulate', + index='0', + directory="reports", + filename=f"adder.{wave_ext}" + ) + if wave: + print(f"Waveform file: {wave}") diff --git a/tests/sumi/umi_stream/test_umi_stream.py b/tests/sumi/umi_stream/test_umi_stream.py index 5ed19893..386284a6 100644 --- a/tests/sumi/umi_stream/test_umi_stream.py +++ b/tests/sumi/umi_stream/test_umi_stream.py @@ -19,65 +19,13 @@ from cocotbext.umi.utils import generators from cocotbext.umi.utils.vrd_transaction import VRDTransaction -from valid_ready_driver import ValidReadyDriver -from valid_ready_monitor import ValidReadyMonitor - -from siliconcompiler import Design, Sim -from siliconcompiler.flows.dvflow import DVFlow - -from siliconcompiler.tools.icarus.compile import CompileTask as IcarusCompileTask -from siliconcompiler.tools.icarus.cocotb_exec import CocotbExecTask as IcarusCocotbExecTask +from siliconcompiler import Design from umi.sumi.umi_stream.umi_stream import Stream - -###################################################### -# UMI Response Compare -###################################################### - -def make_umi_compare(expected_output, scoreboard): - """Create a monitor callback that compares UMI responses on critical fields. - - The Scoreboard registers this directly as the monitor callback when - compare_fn is provided, so it must pop from expected_output itself. - """ - def check(transaction): - if not expected_output: - scoreboard.errors += 1 - scoreboard.log.error("Received UMI response but wasn't expecting any") - if scoreboard._imm: - raise AssertionError("Received UMI response but wasn't expecting any") - return - - exp = expected_output.pop(0) - errors = [] - - if int(transaction.cmd.cmd_type) != int(exp.cmd.cmd_type): - errors.append( - f"cmd_type: expected {exp.cmd.cmd_type}, got {transaction.cmd.cmd_type}" - ) - - if int(transaction.da) != int(exp.da): - errors.append( - f"da: expected 0x{int(exp.da):x}, got 0x{int(transaction.da):x}" - ) - - if exp.data is not None: - got_data = int.from_bytes(transaction.data, 'little') if transaction.data else 0 - exp_data = int.from_bytes(exp.data, 'little') - if got_data != exp_data: - errors.append( - f"data: expected 0x{exp_data:x}, got 0x{got_data:x}" - ) - - if errors: - scoreboard.errors += 1 - for e in errors: - scoreboard.log.error(f"UMI response mismatch: {e}") - if scoreboard._imm: - raise AssertionError("UMI response mismatch:\n" + "\n".join(errors)) - - return check +from valid_ready_driver import ValidReadyDriver +from valid_ready_monitor import ValidReadyMonitor +from run_cocotb_sim import load_cocotb_test ###################################################### @@ -112,7 +60,13 @@ async def assert_reset(self, nreset, period_ns=50): nreset.value = 1 await Timer(period_ns, unit="ns") - async def setup(self, umi_valid_gen, umi_period_ns=10, usi_period_ns=12): + async def setup( + self, + umi_valid_gen, + usi_valid_gen, + umi_period_ns=10, + usi_period_ns=12 + ): """Initialize signals, start clocks, apply reset, create drivers.""" dut = self.dut @@ -149,7 +103,12 @@ async def setup(self, umi_valid_gen, umi_period_ns=10, usi_period_ns=12): clock=dut.umi_clk, valid_generator=umi_valid_gen ) - self.usi_source = ValidReadyDriver(entity=dut, name="usi_in", clock=dut.usi_clk) + self.usi_source = ValidReadyDriver( + entity=dut, + name="usi_in", + clock=dut.usi_clk, + valid_generator=usi_valid_gen + ) # Monitors (passive -- do NOT drive ready signals) self.umi_monitor = SumiMonitor(entity=dut, name="umi_out", clock=dut.umi_clk) @@ -171,13 +130,14 @@ async def setup(self, umi_valid_gen, umi_period_ns=10, usi_period_ns=12): await ClockCycles(dut.umi_clk, 5) -###################################################### -# Cocotb Tests -###################################################### +################################################## +# Test DUT in device mode +################################################## @cocotb.test(timeout_time=100, timeout_unit="ms") @cocotb.parametrize( umi_valid_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], + usi_valid_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], umi_ready_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], usi_ready_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], n_ops=[int(20 * float(os.getenv("RAND_TEST_LEN_SCALER", default=1)))] @@ -185,14 +145,17 @@ async def setup(self, umi_valid_gen, umi_period_ns=10, usi_period_ns=12): async def test_device_mode( dut, umi_valid_gen=None, + usi_valid_gen=None, umi_ready_gen=None, usi_ready_gen=None, n_ops=20 ): """Device mode: posted writes, writes with ack, and reads with data verification.""" + max_tumi_len = 500 + env = Env(dut) - await env.setup(umi_valid_gen) + await env.setup(umi_valid_gen, usi_valid_gen) dut.devicemode.value = 1 # Apply backpressure on UMI response consumer @@ -207,13 +170,11 @@ async def test_device_mode( else: dut.usi_out_ready.value = 1 - #################################### - # Phase 1: Posted writes - #################################### - dut._log.info(f"Phase 1: {n_ops} posted writes") - max_tumi_len = 500 for _ in range(n_ops): + ############################################################## + # Generate random command type for UMI transaction + ############################################################## cmd_type = random.choice([ SumiCmdType.UMI_REQ_POSTED, SumiCmdType.UMI_REQ_WRITE, @@ -222,7 +183,14 @@ async def test_device_mode( sumi_trans = None + ############################################################## + # Generate UMI transaction based on command type + ############################################################## if cmd_type == SumiCmdType.UMI_REQ_READ: + """ + TODO: RTL can only accept reads that can responded to with a single beat of data. + This should probably be fixed. + """ sumi_trans = [SumiTransaction( cmd=SumiCmd.from_fields( cmd_type=cmd_type, @@ -250,6 +218,9 @@ async def test_device_mode( addr_width=env.aw ) + ############################################################## + # Add expected values to scoreboard based on command type + ############################################################## if cmd_type == SumiCmdType.UMI_REQ_POSTED: for t in sumi_trans: # Add sumi trans to expected output @@ -292,334 +263,152 @@ async def test_device_mode( last=bool(int(t.cmd.eom)) )) - # Add sumi trans to UMI driver + ############################################################## + # Add randomly generated UMI transaction to UMI driver + ############################################################## for t in sumi_trans: env.umi_driver.append(t) + # Wait for all expected outputs to be consumed by scoreboards while ( len(env.expected_usi_output) != 0 or len(env.expected_umi_output) != 0 ): await ClockCycles(dut.umi_clk, 1) + # Check that scoreboard did not encounter any mismatches raise env.scoreboard.result - - ##################################### - ## Phase 3: Reads from S2MM FIFO - ##################################### - #dut._log.info(f"Phase 3: {n_ops} reads") - #for i in range(n_ops): - # data = random.getrandbits(env.dw) - # srcaddr = random.getrandbits(env.aw) - - # # Queue USI data into S2MM FIFO - # env.usi_source.append(VRDTransaction( - # data=data.to_bytes(env.data_bytes, 'little'), - # last=True - # )) - # # Queue UMI read request (DUT stalls until FIFO has data) - # cmd = SumiCmd.from_fields( - # cmd_type=SumiCmdType.UMI_REQ_READ, - # size=env.full_size, - # len=0, - # eom=1 - # ) - # env.umi_driver.append(SumiTransaction( - # cmd=cmd, - # da=random.getrandbits(env.aw), - # sa=srcaddr, - # data=bytes(env.data_bytes), - # addr_width=env.aw - # )) - # env.expected_umi_output.append(SumiTransaction( - # cmd=SumiCmd.from_fields( - # cmd_type=SumiCmdType.UMI_RESP_READ, - # size=env.full_size, - # len=0, - # eom=1 - # ), - # da=srcaddr, - # sa=0, - # data=data.to_bytes(env.data_bytes, 'little'), - # addr_width=env.aw - # )) - - #while env.expected_umi_output: - # await ClockCycles(dut.umi_clk, 1) - - #await ClockCycles(dut.umi_clk, 20) - #dut._log.info("Device mode test complete") - - -#@cocotb.test(timeout_time=100, timeout_unit="ms") -#@cocotb.parametrize( -# umi_ready_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], -# usi_ready_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], -# n_ops=[int(20 * float(os.getenv("RAND_TEST_LEN_SCALER", default=1)))] -#) -async def test_nondevice_mode( +@cocotb.test(timeout_time=100, timeout_unit="ms") +@cocotb.parametrize( + umi_valid_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], + usi_valid_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], + umi_ready_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], + usi_ready_gen=[None, generators.random_toggle_generator(), generators.wave_generator()], + n_ops=[int(20 * float(os.getenv("RAND_TEST_LEN_SCALER", default=1)))] +) +async def test_non_device_mode( dut, + umi_valid_gen=None, + usi_valid_gen=None, umi_ready_gen=None, usi_ready_gen=None, n_ops=20 ): - """Non-device (full duplex) mode: posted writes and stream-to-UMI passthrough.""" + """Non-device mode: full duplex with posted writes (MM2S) and stream-to-UMI (S2MM).""" + + max_tumi_len = 500 env = Env(dut) - await env.setup() + await env.setup(umi_valid_gen, usi_valid_gen) dut.devicemode.value = 0 - await ClockCycles(dut.umi_clk, 5) + # Apply backpressure on UMI output consumer if umi_ready_gen is not None: BitDriver(signal=dut.umi_out_ready, clk=dut.umi_clk).start(generator=umi_ready_gen) else: dut.umi_out_ready.value = 1 + # Apply backpressure on USI stream consumer if usi_ready_gen is not None: BitDriver(signal=dut.usi_out_ready, clk=dut.usi_clk).start(generator=usi_ready_gen) else: dut.usi_out_ready.value = 1 - #################################### - # Phase 1: Posted writes to stream - #################################### - dut._log.info(f"Phase 1: {n_ops} posted writes (non-device)") - for i in range(n_ops): - data = random.getrandbits(env.dw) - eom = random.choice([0, 1]) - cmd = SumiCmd.from_fields( - cmd_type=SumiCmdType.UMI_REQ_POSTED, - size=env.full_size, - len=0, - eom=eom - ) - env.umi_driver.append(SumiTransaction( - cmd=cmd, - da=random.getrandbits(env.aw), - sa=random.getrandbits(env.aw), - data=data.to_bytes(env.data_bytes, 'little'), - addr_width=env.aw - )) - env.expected_usi_output.append(VRDTransaction( - data=data.to_bytes(env.data_bytes, 'little'), - last=bool(eom) - )) - - while env.expected_usi_output: - await ClockCycles(dut.umi_clk, 1) - - dut._log.info("Phase 1 passed: non-device posted writes") - - #################################### - # Phase 2: Stream to UMI passthrough - #################################### - dut._log.info(f"Phase 2: {n_ops} stream-to-UMI passthrough") - - # Configure S2MM control signals for passthrough - s2mm_cmd = SumiCmd.from_fields( - cmd_type=SumiCmdType.UMI_RESP_READ, - size=env.full_size, - len=0, - eom=1 - ) - s2mm_dst = random.getrandbits(env.aw) - s2mm_src = random.getrandbits(env.aw) - dut.s2mm_cmd.value = int(s2mm_cmd) - dut.s2mm_dstaddr.value = s2mm_dst - dut.s2mm_srcaddr.value = s2mm_src - - for i in range(n_ops): - data = random.getrandbits(env.dw) - env.usi_source.append(VRDTransaction( - data=data.to_bytes(env.data_bytes, 'little'), - last=True - )) - env.expected_umi_output.append(SumiTransaction( - cmd=s2mm_cmd, - da=s2mm_dst, - sa=0, - data=data.to_bytes(env.data_bytes, 'little'), - addr_width=env.aw - )) - - while env.expected_umi_output: - await ClockCycles(dut.umi_clk, 1) - - dut._log.info("Phase 2 passed: stream-to-UMI passthrough") - await ClockCycles(dut.umi_clk, 20) - raise env.scoreboard.result - + async def mm2s_task(): + """MM2S: Posted writes from UMI input to USI output.""" + for _ in range(n_ops): -#@cocotb.test(timeout_time=200, timeout_unit="ms") -async def test_fifo_stress(dut): - """Stress test: rapid posted writes with slow stream consumption to exercise FIFO.""" - - env = Env(dut) - await env.setup() - dut.devicemode.value = 1 - - # Slow stream consumer to force FIFO fill-up - BitDriver(signal=dut.usi_out_ready, clk=dut.usi_clk).start( - generator=generators.wave_generator() - ) - dut.umi_out_ready.value = 1 - - n = int(100 * float(os.getenv("RAND_TEST_LEN_SCALER", default=1))) - - for i in range(n): - data = random.getrandbits(env.dw) - eom = 1 if (i % 8 == 7) else 0 - cmd = SumiCmd.from_fields( - cmd_type=SumiCmdType.UMI_REQ_POSTED, - size=env.full_size, - len=0, - eom=eom - ) - env.umi_driver.append(SumiTransaction( - cmd=cmd, - da=0, - sa=0, - data=data.to_bytes(env.data_bytes, 'little'), - addr_width=env.aw - )) - env.expected_usi_output.append(VRDTransaction( - data=data.to_bytes(env.data_bytes, 'little'), - last=bool(eom) - )) - - while env.expected_usi_output: - await ClockCycles(dut.umi_clk, 1) - - await ClockCycles(dut.umi_clk, 10) - dut._log.info(f"FIFO stress test passed ({n} transactions)") - raise env.scoreboard.result + ############################################################## + # Generate random multi-beat posted write TUMI transaction + ############################################################## + sumi_trans = TumiTransaction( + cmd=SumiCmd.from_fields(cmd_type=SumiCmdType.UMI_REQ_POSTED), + da=random.randint(0, (1 << len(dut.umi_in_dstaddr)) - 1), + sa=random.randint(0, (1 << len(dut.umi_in_srcaddr)) - 1), + data=bytes(random.randbytes( + (random.randint(env.data_bytes, max_tumi_len) // env.data_bytes) * env.data_bytes + )), + ).to_sumi( + data_bus_size=env.data_bytes, + addr_width=env.aw + ) + ############################################################## + # Add expected USI output for each beat + ############################################################## + for t in sumi_trans: + env.expected_usi_output.append(VRDTransaction( + data=t.data, + last=bool(int(t.cmd.eom)) + )) -#@cocotb.test(timeout_time=100, timeout_unit="ms") -async def test_mixed_operations(dut): - """Interleaved device-mode operations: posted writes, ack writes, and reads.""" + ############################################################## + # Drive UMI posted writes + ############################################################## + for t in sumi_trans: + env.umi_driver.append(t) - env = Env(dut) - await env.setup() - dut.devicemode.value = 1 + async def s2mm_task(): + """S2MM: Stream data from USI input to UMI output with randomized s2mm fields.""" + for _ in range(n_ops): - # Moderate backpressure on both consumer sides - BitDriver(signal=dut.umi_out_ready, clk=dut.umi_clk).start( - generator=generators.random_toggle_generator() - ) - BitDriver(signal=dut.usi_out_ready, clk=dut.usi_clk).start( - generator=generators.random_toggle_generator() - ) + ############################################################## + # Randomize external s2mm cmd/addr fields per transaction + ############################################################## + s2mm_cmd = SumiCmd.from_fields( + cmd_type=SumiCmdType.UMI_REQ_POSTED, + size=random.randint(0, env.full_size), + len=random.randint(0, 255), + eom=random.randint(0, 1) + ) + s2mm_da = random.randint(0, (1 << env.aw) - 1) + s2mm_sa = random.randint(0, (1 << env.aw) - 1) - n = int(30 * float(os.getenv("RAND_TEST_LEN_SCALER", default=1))) - dut._log.info(f"Running {n} mixed operations") + dut.s2mm_cmd.value = int(s2mm_cmd) + dut.s2mm_dstaddr.value = s2mm_da + dut.s2mm_srcaddr.value = s2mm_sa - # Pre-generate operation sequence, compute expected outputs, and queue all - for i in range(n): - op = random.choice(['posted_write', 'write_ack', 'read']) - data = random.getrandbits(env.dw) - data_bytes = data.to_bytes(env.data_bytes, 'little') - srcaddr = random.getrandbits(env.aw) - dstaddr = random.getrandbits(env.aw) + ############################################################## + # Push one random USI beat into S2MM FIFO + ############################################################## + data = random.randbytes(env.data_bytes) + last = random.choice([True, False]) - if op == 'posted_write': - eom = random.choice([0, 1]) - cmd = SumiCmd.from_fields( - cmd_type=SumiCmdType.UMI_REQ_POSTED, - size=env.full_size, - len=0, - eom=eom - ) - env.umi_driver.append(SumiTransaction( - cmd=cmd, - da=dstaddr, - sa=srcaddr, - data=data_bytes, - addr_width=env.aw - )) - env.expected_usi_output.append(VRDTransaction( - data=data_bytes, - last=bool(eom) - )) + env.usi_source.append(VRDTransaction(data=data, last=last)) - elif op == 'write_ack': - cmd = SumiCmd.from_fields( - cmd_type=SumiCmdType.UMI_REQ_WRITE, - size=env.full_size, - len=0, - eom=1 - ) - env.umi_driver.append(SumiTransaction( - cmd=cmd, - da=dstaddr, - sa=srcaddr, - data=data_bytes, - addr_width=env.aw - )) - env.expected_usi_output.append(VRDTransaction( - data=data_bytes, - last=True - )) + ############################################################## + # Add expected UMI output using current s2mm field values + ############################################################## env.expected_umi_output.append(SumiTransaction( - cmd=SumiCmd.from_fields( - cmd_type=SumiCmdType.UMI_RESP_WRITE, - size=env.full_size, - len=0, - eom=1 - ), - da=srcaddr, - sa=0, - data=None, - addr_width=env.aw + cmd=s2mm_cmd, + da=s2mm_da, + sa=s2mm_sa, + data=data[:s2mm_cmd.total_bytes()], )) - elif op == 'read': - # Queue USI data into S2MM FIFO - env.usi_source.append(VRDTransaction( - data=data_bytes, - last=True - )) - cmd = SumiCmd.from_fields( - cmd_type=SumiCmdType.UMI_REQ_READ, - size=env.full_size, - len=0, - eom=1 - ) - env.umi_driver.append(SumiTransaction( - cmd=cmd, - da=dstaddr, - sa=srcaddr, - data=bytes(env.data_bytes), - addr_width=env.aw - )) - env.expected_umi_output.append(SumiTransaction( - cmd=SumiCmd.from_fields( - cmd_type=SumiCmdType.UMI_RESP_READ, - size=env.full_size, - len=0, - eom=1 - ), - da=srcaddr, - sa=0, - data=data_bytes, - addr_width=env.aw - )) + ############################################################## + # Wait for this entry to drain before changing s2mm fields + ############################################################## + while len(env.expected_umi_output) > 0: + await ClockCycles(dut.umi_clk, 1) - # Wait for all expected outputs to be consumed by scoreboards - while env.expected_usi_output or env.expected_umi_output: + mm2s = cocotb.start_soon(mm2s_task()) + s2mm = cocotb.start_soon(s2mm_task()) + await Combine(mm2s, s2mm) + + # Wait for all remaining expected outputs to be consumed by scoreboards + while ( + len(env.expected_usi_output) != 0 + or len(env.expected_umi_output) != 0 + ): await ClockCycles(dut.umi_clk, 1) - dut._log.info(f"Mixed operations test passed ({n} ops)") - await ClockCycles(dut.umi_clk, 20) + # Check that scoreboard did not encounter any mismatches raise env.scoreboard.result -###################################################### -# SiliconCompiler Design & Pytest Integration -###################################################### - class TbDesign(Design): def __init__(self): @@ -636,40 +425,12 @@ def __init__(self): self.add_depfileset(Stream(), "rtl") -def load_cocotb_test(trace=True, seed=None): - project = Sim() - project.set_design(TbDesign()) - project.add_fileset("testbench.cocotb") - - project.set_flow(DVFlow(tool="icarus-cocotb")) - - IcarusCompileTask.find_task(project).set_trace_enabled(trace) - - if seed is not None: - IcarusCocotbExecTask.find_task(project).set_cocotb_randomseed(seed) - - project.run() - project.summary() - - results = project.find_result( - step='simulate', - index='0', - directory="outputs", - filename="results.xml" - ) - if results: - print(f"\nCocotb results file: {results}") - - vcd = project.find_result( - step='simulate', - index='0', - directory="reports", - filename="tb_umi_stream.vcd" - ) - if vcd: - print(f"Waveform file: {vcd}") - - @pytest.mark.cocotb -def test_umi_stream(): - load_cocotb_test() +@pytest.mark.parametrize("simulator", ["icarus", "verilator"]) +def test_umi_stream(simulator): + load_cocotb_test( + design=TbDesign(), + simulator=simulator, + trace=True, + seed=None + ) diff --git a/tests/sumi/umi_stream/verilator_cmd_file.vc b/tests/sumi/umi_stream/verilator_cmd_file.vc new file mode 100644 index 00000000..89b77f44 --- /dev/null +++ b/tests/sumi/umi_stream/verilator_cmd_file.vc @@ -0,0 +1 @@ +--timescale 1ns/1ps diff --git a/umi/sumi/umi_stream/rtl/umi_stream.v b/umi/sumi/umi_stream/rtl/umi_stream.v index 69a91846..11809146 100644 --- a/umi/sumi/umi_stream/rtl/umi_stream.v +++ b/umi/sumi/umi_stream/rtl/umi_stream.v @@ -26,7 +26,7 @@ * are allowed. * ******************************************************************************/ -`timescale 1ns/1ps + module umi_stream #(parameter AW = 64, // UMI data width parameter CW = 32, // UMI command width From 738b4db7007b3770f739a054ced13951a2676288 Mon Sep 17 00:00:00 2001 From: Rice Shelley Date: Tue, 10 Feb 2026 16:13:56 -0500 Subject: [PATCH 3/4] Added cocotb to CI --- .github/workflows/ci.yml | 19 + tests/sumi/umi_stream/surfer_config.surf.ron | 955 ------------------- 2 files changed, 19 insertions(+), 955 deletions(-) delete mode 100644 tests/sumi/umi_stream/surfer_config.surf.ron diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a9d7daa..50d5951c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,25 @@ jobs: python3 -m pip install -e .[test] pytest -m "switchboard" -x --verbose --durations=0 + cocotb_ci: + name: "Cocotb CI" + runs-on: ubuntu-latest + container: + image: ghcr.io/zeroasiccorp/sbtest:latest + timeout-minutes: 45 + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: pytest + run: | + python3 -m venv venv + . venv/bin/activate + python3 -m pip install --upgrade pip + python3 -m pip install -e .[test] + pytest -m "cocotb" -x --verbose --durations=0 + python_ci: name: "Python + Tools CI" runs-on: ubuntu-latest diff --git a/tests/sumi/umi_stream/surfer_config.surf.ron b/tests/sumi/umi_stream/surfer_config.surf.ron deleted file mode 100644 index 25cfa17f..00000000 --- a/tests/sumi/umi_stream/surfer_config.surf.ron +++ /dev/null @@ -1,955 +0,0 @@ -( - show_hierarchy: None, - show_menu: None, - show_ticks: None, - show_toolbar: None, - show_tooltip: None, - show_scope_tooltip: None, - show_default_timeline: None, - show_overview: None, - show_statusbar: None, - align_names_right: None, - show_variable_indices: None, - show_variable_direction: None, - show_empty_scopes: None, - show_parameters_in_scopes: None, - parameter_display_location: None, - highlight_focused: None, - fill_high_values: None, - primary_button_drag_behavior: None, - arrow_key_bindings: None, - clock_highlight_type: None, - hierarchy_style: None, - autoload_sibling_state_files: None, - autoreload_files: None, - waves: Some(( - source: File("umi_stream.vcd"), - format: Vcd, - active_scope: Some(WaveScope(( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ))), - items_tree: ( - items: [ - ( - item_ref: (1), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (2), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (3), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (4), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (5), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (6), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (7), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (8), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (9), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (10), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (11), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (12), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (13), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (14), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (15), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (16), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (17), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (18), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (19), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (24), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (20), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (21), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (22), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (23), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (25), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (26), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (27), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (28), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (29), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (30), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (31), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (34), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (32), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (33), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (35), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (36), - level: 0, - unfolded: true, - selected: false, - ), - ( - item_ref: (37), - level: 0, - unfolded: true, - selected: false, - ), - ], - ), - displayed_items: { - (22): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "usi_out_last", - id: Wellen((30)), - ), - color: None, - background_color: None, - display_name: "usi_out_last", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (36): Divider(( - color: None, - background_color: None, - name: None, - )), - (2): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_clk", - id: Wellen((10)), - ), - color: None, - background_color: None, - display_name: "umi_clk", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (17): Divider(( - color: None, - background_color: None, - name: None, - )), - (20): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "usi_out_valid", - id: Wellen((27)), - ), - color: None, - background_color: None, - display_name: "usi_out_valid", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (37): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "devicemode", - id: Wellen((1)), - ), - color: None, - background_color: None, - display_name: "devicemode", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (11): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_out_valid", - id: Wellen((32)), - ), - color: None, - background_color: None, - display_name: "umi_out_valid", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (7): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_in_srcaddr", - id: Wellen((15)), - ), - color: None, - background_color: None, - display_name: "umi_in_srcaddr [63:0]", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (33): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "s2mm_srcaddr", - id: Wellen((9)), - ), - color: None, - background_color: None, - display_name: "s2mm_srcaddr [63:0]", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (13): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_out_dstaddr", - id: Wellen((34)), - ), - color: None, - background_color: None, - display_name: "umi_out_dstaddr [63:0]", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (14): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_out_srcaddr", - id: Wellen((33)), - ), - color: None, - background_color: None, - display_name: "umi_out_srcaddr [63:0]", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (12): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_out_cmd", - id: Wellen((36)), - ), - color: None, - background_color: None, - display_name: "umi_out_cmd [31:0]", - display_name_type: Unique, - manual_name: None, - format: Some("umi"), - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (16): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_out_ready", - id: Wellen((18)), - ), - color: None, - background_color: None, - display_name: "umi_out_ready", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (28): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "usi_in_last", - id: Wellen((22)), - ), - color: None, - background_color: None, - display_name: "usi_in_last", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (18): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "usi_clk", - id: Wellen((20)), - ), - color: None, - background_color: None, - display_name: "usi_clk", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (29): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "usi_in_ready", - id: Wellen((23)), - ), - color: None, - background_color: None, - display_name: "usi_in_ready", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (10): Divider(( - color: None, - background_color: None, - name: None, - )), - (15): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_out_data", - id: Wellen((35)), - ), - color: None, - background_color: None, - display_name: "umi_out_data [255:0]", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (34): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "s2mm_cmd", - id: Wellen((6)), - ), - color: None, - background_color: None, - display_name: "s2mm_cmd [31:0]", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (23): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "usi_out_ready", - id: Wellen((26)), - ), - color: None, - background_color: None, - display_name: "usi_out_ready", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (8): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_in_data", - id: Wellen((12)), - ), - color: None, - background_color: None, - display_name: "umi_in_data [255:0]", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (27): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "usi_in_data", - id: Wellen((21)), - ), - color: None, - background_color: None, - display_name: "usi_in_data [255:0]", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (1): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_nreset", - id: Wellen((17)), - ), - color: None, - background_color: None, - display_name: "umi_nreset", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (4): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_in_valid", - id: Wellen((16)), - ), - color: None, - background_color: None, - display_name: "umi_in_valid", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (5): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_in_cmd", - id: Wellen((11)), - ), - color: None, - background_color: None, - display_name: "umi_in_cmd [31:0]", - display_name_type: Unique, - manual_name: None, - format: Some("umi"), - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (6): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_in_dstaddr", - id: Wellen((13)), - ), - color: None, - background_color: None, - display_name: "umi_in_dstaddr [63:0]", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (19): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "usi_nreset", - id: Wellen((25)), - ), - color: None, - background_color: None, - display_name: "usi_nreset", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (21): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "usi_out_data", - id: Wellen((31)), - ), - color: None, - background_color: None, - display_name: "usi_out_data [255:0]", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (24): Divider(( - color: None, - background_color: None, - name: None, - )), - (26): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "usi_in_valid", - id: Wellen((24)), - ), - color: None, - background_color: None, - display_name: "usi_in_valid", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (31): Divider(( - color: None, - background_color: None, - name: None, - )), - (32): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "s2mm_dstaddr", - id: Wellen((7)), - ), - color: None, - background_color: None, - display_name: "s2mm_dstaddr [63:0]", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (30): Divider(( - color: None, - background_color: None, - name: None, - )), - (3): Divider(( - color: None, - background_color: None, - name: None, - )), - (9): Variable(( - variable_ref: ( - path: ( - strs: [ - "umi_stream", - ], - id: Wellen((2)), - ), - name: "umi_in_ready", - id: Wellen((14)), - ), - color: None, - background_color: None, - display_name: "umi_in_ready", - display_name_type: Unique, - manual_name: None, - format: None, - field_formats: [], - height_scaling_factor: None, - analog: None, - )), - (25): Divider(( - color: None, - background_color: None, - name: None, - )), - (35): Divider(( - color: None, - background_color: None, - name: None, - )), - }, - display_item_ref_counter: 37, - viewports: [ - ( - curr_left: (-0.0013646285412315911), - curr_right: (0.015823649063907094), - target_left: (0.0), - target_right: (1.0), - move_start_left: (0.0), - move_start_right: (1.0), - move_duration: None, - move_strategy: Instant, - ), - ], - cursor: Some((1, [ - 57992321, - ])), - markers: {}, - focused_item: Some((35)), - focused_transaction: (None, None), - default_variable_name_type: Unique, - scroll_offset: 0.0, - display_variable_indices: true, - graphics: {}, - )), - drag_started: false, - drag_source_idx: None, - drag_target_idx: Some(( - before: (32), - level: 0, - )), - previous_waves: None, - count: None, - blacklisted_translators: [], - show_about: false, - show_keys: false, - show_gestures: false, - show_quick_start: false, - show_license: false, - show_performance: false, - show_logs: false, - show_cursor_window: false, - wanted_timeunit: PicoSeconds, - time_string_format: None, - show_url_entry: false, - variable_name_filter_focused: false, - variable_filter: ( - name_filter_type: Contain, - name_filter_str: "dev", - name_filter_case_insensitive: true, - include_inputs: true, - include_outputs: true, - include_inouts: true, - include_others: true, - group_by_direction: false, - ), - sidepanel_width: Some(151.59375), - ui_zoom_factor: Some(1.5), - animation_enabled: None, - use_dinotrace_style: None, - transition_value: None, -) \ No newline at end of file From 7eaebb6daeb598c7e555a144fb60e840f5f99fe0 Mon Sep 17 00:00:00 2001 From: Rice Shelley Date: Tue, 10 Feb 2026 22:40:48 -0500 Subject: [PATCH 4/4] Fixed CI --- .github/workflows/ci.yml | 2 +- tests/sumi/umi_stream/test_umi_stream.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50d5951c..cb591098 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: name: "Cocotb CI" runs-on: ubuntu-latest container: - image: ghcr.io/zeroasiccorp/sbtest:latest + image: ghcr.io/siliconcompiler/sc_runner:latest timeout-minutes: 45 steps: diff --git a/tests/sumi/umi_stream/test_umi_stream.py b/tests/sumi/umi_stream/test_umi_stream.py index 386284a6..b19ee8fb 100644 --- a/tests/sumi/umi_stream/test_umi_stream.py +++ b/tests/sumi/umi_stream/test_umi_stream.py @@ -188,7 +188,7 @@ async def test_device_mode( ############################################################## if cmd_type == SumiCmdType.UMI_REQ_READ: """ - TODO: RTL can only accept reads that can responded to with a single beat of data. + TODO: RTL can only accept reads that can responded to with a single beat of data. This should probably be fixed. """ sumi_trans = [SumiTransaction( @@ -198,7 +198,7 @@ async def test_device_mode( len=0, eom=1 ), - # TODO: DUT will accept any DST ADDR this seems like an issue 2 me + # TODO: DUT will accept any DST ADDR. Do we consider this an issue? da=random.randint(0, (1 << len(dut.umi_in_dstaddr)) - 1), sa=random.randint(0, (1 << len(dut.umi_in_srcaddr)) - 1), data=random.randbytes(env.data_bytes), @@ -431,6 +431,6 @@ def test_umi_stream(simulator): load_cocotb_test( design=TbDesign(), simulator=simulator, - trace=True, + trace=False, seed=None )