Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/axil_ram/testbench.sv
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ module testbench (
.ADDR_WIDTH (ADDR_WIDTH))
sb_axil_m_i (
.clk (clk),
.reset (1'b0),
.reset (~nreset),

.m_axil_awaddr (s_axil_awaddr),
.m_axil_awprot (s_axil_awprot),
Expand Down
17 changes: 17 additions & 0 deletions examples/axil_reset/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright (c) 2024 Zero ASIC Corporation
# This code is licensed under Apache License 2.0 (see LICENSE for details)

.PHONY: verilator
verilator:
./test.py --tool verilator

.PHONY: icarus
icarus:
./test.py --tool icarus

.PHONY: clean
clean:
rm -f queue-* *.q
rm -f *.vcd *.fst *.fst.hier
rm -rf obj_dir build
rm -f *.o *.vpi
20 changes: 20 additions & 0 deletions examples/axil_reset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# axil_reset

Regression test for [issue #275](https://github.com/zeroasiccorp/switchboard/issues/275).

The DUT (`axil_reset_check.sv`) models a simple AXI-Lite register interface whose
handshake logic ignores reset, so all three `ready` signals are high from time
zero -- including while the design is still in reset. It counts every transaction
accepted while `rst` was asserted and returns that count for any read.

A transactor that drives transactions without regard to reset gets them accepted
before the design is ready. In a real DUT those transactions are silently dropped
and the simulation hangs waiting for a response; here the count is non-zero and
the test fails instead.

Run with:

```console
make verilator
make icarus
```
121 changes: 121 additions & 0 deletions examples/axil_reset/axil_reset_check.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// AXI-Lite subordinate that models the class of DUT described in
// https://github.com/zeroasiccorp/switchboard/issues/275: a "simple axi lite
// register interface" whose handshake logic ignores reset, so "ready" is high
// by default -- including while the design is still in reset.
//
// A transactor that drives transactions without regard to reset will have them
// accepted before the design is ready. In a real DUT those transactions are
// silently dropped and the simulation hangs waiting for a response that never
// comes. To keep the failure observable rather than fatal, this model still
// responds to every transaction, and counts the ones that were accepted while
// reset was asserted. The count is readable over the same AXI-Lite port, so
// the testbench can assert that it is zero.

// Copyright (c) 2026 Zero ASIC Corporation
// This code is licensed under Apache License 2.0 (see LICENSE for details)

`default_nettype none

module axil_reset_check #(
parameter DATA_WIDTH = 32,
parameter ADDR_WIDTH = 8,
parameter STRB_WIDTH = (DATA_WIDTH/8)
) (
input wire clk,
input wire rst,

// AXI-Lite subordinate interface
input wire [ADDR_WIDTH-1:0] s_axil_awaddr,
input wire [2:0] s_axil_awprot,
input wire s_axil_awvalid,
output wire s_axil_awready,
input wire [DATA_WIDTH-1:0] s_axil_wdata,
input wire [STRB_WIDTH-1:0] s_axil_wstrb,
input wire s_axil_wvalid,
output wire s_axil_wready,
output wire [1:0] s_axil_bresp,
output reg s_axil_bvalid = 1'b0,
input wire s_axil_bready,
input wire [ADDR_WIDTH-1:0] s_axil_araddr,
input wire [2:0] s_axil_arprot,
input wire s_axil_arvalid,
output wire s_axil_arready,
output reg [DATA_WIDTH-1:0] s_axil_rdata = 'b0,
output wire [1:0] s_axil_rresp,
output reg s_axil_rvalid = 1'b0,
input wire s_axil_rready
);
// unused inputs
/* verilator lint_off UNUSEDSIGNAL */
wire [ADDR_WIDTH-1:0] unused_awaddr = s_axil_awaddr;
wire [2:0] unused_awprot = s_axil_awprot;
wire [DATA_WIDTH-1:0] unused_wdata = s_axil_wdata;
wire [STRB_WIDTH-1:0] unused_wstrb = s_axil_wstrb;
wire [ADDR_WIDTH-1:0] unused_araddr = s_axil_araddr;
wire [2:0] unused_arprot = s_axil_arprot;
/* verilator lint_on UNUSEDSIGNAL */

assign s_axil_bresp = 2'b00;
assign s_axil_rresp = 2'b00;

// the write halves are tracked independently, and "ready" is simply the
// absence of anything outstanding. nothing here is gated on rst, which is
// the whole point of the model: at time zero all three ready signals are
// high, so a transactor that ignores reset gets its transactions accepted.

reg aw_pending = 1'b0;
reg w_pending = 1'b0;

assign s_axil_awready = !aw_pending;
assign s_axil_wready = !w_pending;
assign s_axil_arready = !s_axil_rvalid;

wire aw_accepted = s_axil_awvalid && s_axil_awready;
wire w_accepted = s_axil_wvalid && s_axil_wready;
wire ar_accepted = s_axil_arvalid && s_axil_arready;

// count transactions accepted while reset was asserted. deliberately not
// cleared by rst -- it is a record of what happened during reset.
localparam CNT_WIDTH = 8;

reg [CNT_WIDTH-1:0] rst_xacts = 'b0;

always @(posedge clk) begin
if (rst && (aw_accepted || ar_accepted)) begin
if (rst_xacts != {CNT_WIDTH{1'b1}}) begin
rst_xacts <= rst_xacts + 1'b1;
end
end
end

always @(posedge clk) begin
// write address / data
if (aw_accepted) begin
aw_pending <= 1'b1;
end

if (w_accepted) begin
w_pending <= 1'b1;
end

// issue the write response once both halves have arrived
if (aw_pending && w_pending && !s_axil_bvalid) begin
s_axil_bvalid <= 1'b1;
aw_pending <= 1'b0;
w_pending <= 1'b0;
end else if (s_axil_bvalid && s_axil_bready) begin
s_axil_bvalid <= 1'b0;
end

// every read returns the number of transactions accepted during reset
if (ar_accepted) begin
s_axil_rvalid <= 1'b1;
s_axil_rdata <= {{(DATA_WIDTH-CNT_WIDTH){1'b0}}, rst_xacts};
end else if (s_axil_rvalid && s_axil_rready) begin
s_axil_rvalid <= 1'b0;
end
end

endmodule

`default_nettype wire
107 changes: 107 additions & 0 deletions examples/axil_reset/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env python3

# Regression test for https://github.com/zeroasiccorp/switchboard/issues/275:
# transactors must not drive transactions into a DUT that is still in reset.

# Copyright (c) 2026 Zero ASIC Corporation
# This code is licensed under Apache License 2.0 (see LICENSE for details)

import sys

import numpy as np

from siliconcompiler import Design

from switchboard import SbDut
from switchboard.verilog.sim.switchboard_sim import SwitchboardSim


def main():
# build the simulator
dut = build_testbench()

# launch the simulation
dut.simulate()

axil = dut.intfs['s_axil']

# the DUT counts every transaction accepted while it was in reset, and
# returns that count for any read. drive some traffic first, so that a
# transactor which ignores reset has something to get wrong.
for addr in range(0, 4 * dut.args.n, 4):
axil.write(addr % 256, np.uint32(addr))

rst_xacts = int(axil.read(0, np.uint32))

print(f'Transactions accepted during reset: {rst_xacts}')

if rst_xacts == 0:
print("PASS!")
sys.exit(0)
else:
print(f'FAIL: {rst_xacts} transaction(s) were driven into the DUT while'
' it was still in reset')
sys.exit(1)


class AxilResetCheck(Design):

def __init__(self):
super().__init__("axil_reset_check")

top_module = "axil_reset_check"

self.set_dataroot("axil_reset", __file__)

with self.active_fileset('rtl'):
self.set_topmodule(top_module)
self.add_depfileset(SwitchboardSim())
self.add_file("axil_reset_check.sv")

with self.active_fileset('verilator'):
self.set_topmodule(top_module)
self.add_depfileset(self, "rtl")

with self.active_fileset('icarus'):
self.set_topmodule(top_module)
self.add_depfileset(self, "rtl")


def build_testbench():
dw = 32
aw = 8

parameters = dict(
DATA_WIDTH=dw,
ADDR_WIDTH=aw
)

interfaces = {
's_axil': dict(type='axil', dw=dw, aw=aw, direction='subordinate')
}

# a long reset makes the failure deterministic: without the fix the
# transactor drives a transaction on essentially the first clock edge
resets = [dict(name='rst', delay=8)]

extra_args = {
'-n': dict(type=int, default=16, help='Number of writes to perform.')
}

dut = SbDut(
design=AxilResetCheck(),
cmdline=True,
autowrap=True,
parameters=parameters,
interfaces=interfaces,
resets=resets,
extra_args=extra_args
)

dut.build()

return dut


if __name__ == '__main__':
main()
58 changes: 58 additions & 0 deletions examples/test_autowrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python

# Copyright (c) 2026 Zero ASIC Corporation
# This code is licensed under Apache License 2.0 (see LICENSE for details)

import pytest

from switchboard.autowrap import autowrap


def wrap(tmp_path, interfaces, resets):
filename = autowrap(
instances={'dut': 'dut'},
parameters={'dut': {}},
interfaces={'dut': interfaces},
clocks={'dut': ['clk']},
resets={'dut': resets},
tieoffs={'dut': {}},
filename=tmp_path / 'testbench.sv'
)

return filename.read_text()


# the transactors have to be held in reset for as long as any part of the design
# is in reset. otherwise transactions are driven into a DUT that hasn't come out
# of reset yet, which hangs the simulation if the DUT holds its "ready" signals
# high during reset. see https://github.com/zeroasiccorp/switchboard/issues/275

@pytest.mark.parametrize('name,intf,macro', [
('sb_in', dict(type='sb', dw=32, direction='input'), 'QUEUE_TO_SB_SIM'),
('sb_out', dict(type='sb', dw=32, direction='output'), 'SB_TO_QUEUE_SIM'),
('umi_in', dict(type='umi', dw=32, cw=32, aw=64, direction='input'), 'QUEUE_TO_UMI_SIM'),
('umi_out', dict(type='umi', dw=32, cw=32, aw=64, direction='output'), 'UMI_TO_QUEUE_SIM'),
('s_axi', dict(type='axi', dw=32, aw=16, idw=8, direction='subordinate'), 'SB_AXI_M'),
('s_axil', dict(type='axil', dw=32, aw=16, direction='subordinate'), 'SB_AXIL_M'),
])
def test_transactor_held_in_reset(tmp_path, name, intf, macro):
text = wrap(tmp_path, {name: intf}, [dict(name='rst', delay=8)])

line = next(line for line in text.splitlines() if macro in line)

# the transactor reset is the last bit of the reset vector to be de-asserted,
# making it the logical OR of every reset driven into the design
assert line.rstrip().endswith('rstvec[8]);'), line

# the reset vector has to be declared before the transactors that use it
assert text.index('rstvec = ') < text.index(macro)


def test_transactor_reset_tied_off_without_resets(tmp_path):
text = wrap(tmp_path, {'s_axil': dict(type='axil', dw=32, aw=16,
direction='subordinate')}, [])

assert 'rstvec' not in text

line = next(line for line in text.splitlines() if 'SB_AXIL_M' in line)
assert line.rstrip().endswith("1'b0);"), line
2 changes: 2 additions & 0 deletions examples/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
@pytest.mark.parametrize('path,expected,target', [
['axil', 'PASS!', 'icarus'],
['axil', 'PASS!', 'verilator'],
['axil_reset', 'PASS!', 'icarus'],
['axil_reset', 'PASS!', 'verilator'],
# ['minimal', 'PASS!', 'icarus'],
# ['minimal', 'PASS!', 'verilator'],
['network', None, 'verilator'],
Expand Down
Loading