Simulator compatibility updates

This commit is contained in:
Alex Mykyta
2023-10-22 20:43:34 -07:00
parent d689bb7077
commit b5b1ba790e
29 changed files with 244 additions and 111 deletions

View File

@@ -1,7 +1,7 @@
def pytest_addoption(parser):
parser.addoption(
"--sim-tool",
choices=["questa", "xilinx", "stub", "skip", "auto"],
choices=["questa", "xsim", "stub", "skip", "auto"],
default="auto",
help="""
Select the simulator to use.
@@ -12,6 +12,29 @@ def pytest_addoption(parser):
"""
)
parser.addoption(
"--gui",
default=False,
action="store_true",
help=""",
Launch sim tool in GUI mode
Only use this option when running a single test
"""
)
parser.addoption(
"--rerun",
default=False,
action="store_true",
help=""",
Re-run simulation in-place without re-exporting regblock
Useful if hand-editing a testcase interactively.
"""
)
parser.addoption(
"--synth-tool",
choices=["vivado", "skip", "auto"],

View File

@@ -49,6 +49,13 @@ class BaseTestCase(unittest.TestCase):
def _load_request(self, request):
self.request = request
@property
def rerun(self) -> bool:
"""
Re-run wothout deleting and re-generating prior output directory.
"""
return self.request.config.getoption("--rerun")
def get_testcase_dir(self) -> str:
class_dir = os.path.dirname(inspect.getfile(self.__class__))
return class_dir
@@ -114,6 +121,9 @@ class BaseTestCase(unittest.TestCase):
)
def setUp(self) -> None:
if self.rerun:
return
# Create fresh build dir
run_dir = self.get_run_dir()
if os.path.exists(run_dir):

View File

@@ -95,59 +95,129 @@ interface axi4lite_intf_driver #(
@cb;
end
semaphore txn_aw_mutex = new(1);
semaphore txn_w_mutex = new(1);
semaphore txn_b_mutex = new(1);
semaphore txn_ar_mutex = new(1);
semaphore txn_r_mutex = new(1);
//--------------------------------------------------------------------------
typedef struct {
logic [1:0] bresp;
} write_response_t;
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1);
bit w_before_aw;
w_before_aw = $urandom_range(1,0);
class write_request_t;
mailbox #(write_response_t) response_mbx;
logic [ADDR_WIDTH-1:0] addr;
logic [DATA_WIDTH-1:0] data;
logic [DATA_WIDTH/8-1:0] strb;
function new();
this.response_mbx = new();
endfunction
endclass
fork
begin
txn_aw_mutex.get();
mailbox #(write_request_t) aw_mbx = new();
mailbox #(write_request_t) w_mbx = new();
write_request_t write_queue[$];
// Issue AW transfers
initial forever begin
write_request_t req;
aw_mbx.get(req);
##0;
if(w_before_aw) repeat($urandom_range(2,0)) @cb;
repeat($urandom_range(2,0)) @cb;
cb.AWVALID <= '1;
cb.AWADDR <= addr;
cb.AWADDR <= req.addr;
cb.AWPROT <= '0;
@(cb);
while(cb.AWREADY !== 1'b1) @(cb);
cb.AWVALID <= '0;
txn_aw_mutex.put();
end
begin
txn_w_mutex.get();
// Issue W transfers
initial forever begin
write_request_t req;
w_mbx.get(req);
##0;
if(!w_before_aw) repeat($urandom_range(2,0)) @cb;
repeat($urandom_range(2,0)) @cb;
cb.WVALID <= '1;
cb.WDATA <= data;
cb.WSTRB <= strb;
cb.WDATA <= req.data;
cb.WSTRB <= req.strb;
@(cb);
while(cb.WREADY !== 1'b1) @(cb);
cb.WVALID <= '0;
cb.WSTRB <= '0;
txn_w_mutex.put();
end
begin
txn_b_mutex.get();
// Listen for R responses
initial forever begin
@cb;
while(!(cb.BREADY === 1'b1 && cb.BVALID === 1'b1)) @(cb);
assert(!$isunknown(cb.BRESP)) else $error("Read from 0x%0x returned X's on BRESP", addr);
txn_b_mutex.put();
while(rst || !(cb.BREADY === 1'b1 && cb.BVALID === 1'b1)) @cb;
if(write_queue.size() != 0) begin
// Can match this response with an existing request.
// Send response to requestor
write_request_t req;
write_response_t resp;
req = write_queue.pop_front();
resp.bresp = cb.BRESP;
req.response_mbx.put(resp);
end else begin
$error("Got unmatched write response");
end
join
end
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1);
write_request_t req;
write_response_t resp;
req = new();
req.addr = addr;
req.data = data;
req.strb = strb;
aw_mbx.put(req);
w_mbx.put(req);
write_queue.push_back(req);
// Wait for response
req.response_mbx.get(resp);
assert(!$isunknown(resp.bresp)) else $error("Read from 0x%0x returned X's on BRESP", addr);
endtask
task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
//--------------------------------------------------------------------------
typedef struct {
logic [DATA_WIDTH-1: 0] rdata;
logic [1:0] rresp;
} read_response_t;
class read_request_t;
mailbox #(read_response_t) response_mbx;
function new();
this.response_mbx = new();
endfunction
endclass
semaphore txn_ar_mutex = new(1);
read_request_t read_queue[$];
// Listen for R responses
initial forever begin
@cb;
while(rst || !(cb.RREADY === 1'b1 && cb.RVALID === 1'b1)) @cb;
if(read_queue.size() != 0) begin
// Can match this response with an existing request.
// Send response to requestor
read_request_t req;
read_response_t resp;
req = read_queue.pop_front();
resp.rdata = cb.RDATA;
resp.rresp = cb.RRESP;
req.response_mbx.put(resp);
end else begin
$error("Got unmatched read response");
end
end
task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
read_request_t req;
read_response_t resp;
fork
begin
txn_ar_mutex.get();
// Issue read request
##0;
cb.ARVALID <= '1;
cb.ARADDR <= addr;
@@ -155,19 +225,18 @@ interface axi4lite_intf_driver #(
@(cb);
while(cb.ARREADY !== 1'b1) @(cb);
cb.ARVALID <= '0;
txn_ar_mutex.put();
end
begin
txn_r_mutex.get();
@cb;
while(!(cb.RREADY === 1'b1 && cb.RVALID === 1'b1)) @(cb);
assert(!$isunknown(cb.RDATA)) else $error("Read from 0x%0x returned X's on RDATA", addr);
assert(!$isunknown(cb.RRESP)) else $error("Read from 0x%0x returned X's on RRESP", addr);
data = cb.RDATA;
txn_r_mutex.put();
end
join
// Push new request into queue
req = new();
read_queue.push_back(req);
txn_ar_mutex.put();
// Wait for response
req.response_mbx.get(resp);
assert(!$isunknown(resp.rdata)) else $error("Read from 0x%0x returned X's on RDATA", addr);
assert(!$isunknown(resp.rresp)) else $error("Read from 0x%0x returned X's on RRESP", addr);
data = resp.rdata;
endtask
task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
@@ -177,6 +246,7 @@ interface axi4lite_intf_driver #(
assert(data == expected_data) else $error("Read from 0x%x returned 0x%x. Expected 0x%x", addr, data, expected_data);
endtask
//--------------------------------------------------------------------------
initial begin
reset();
end

View File

@@ -14,7 +14,7 @@ module external_block #(
output logic [WIDTH-1:0] rd_data,
output logic wr_ack
);
timeunit 1ns;
timeunit 1ps;
timeprecision 1ps;
localparam ADDR_SHIFT = $clog2(WIDTH/8);

View File

@@ -13,7 +13,7 @@ module external_reg #(
output logic [WIDTH-1:0] rd_data,
output logic wr_ack
);
timeunit 1ns;
timeunit 1ps;
timeprecision 1ps;
logic [SUBWORDS-1:0][WIDTH-1:0] value;
@@ -69,6 +69,7 @@ initial begin
#1ns;
if(!rst && req) begin
$info("got request");
if(req_is_wr) do_write(req, wr_data, wr_biten);
else do_read(req);
end

View File

@@ -76,6 +76,7 @@ class SimTestCase(BaseTestCase):
super().setUp()
# Create testbench from template
if not self.rerun:
self._generate_tb()
simulator = simulator_cls(self)

View File

@@ -3,13 +3,13 @@ import functools
from .base import Simulator
from .questa import Questa
from .xilinx import Xilinx
from .xilinx import XilinxXSIM
from .stub import StubSimulator
ALL_SIMULATORS: List[Simulator]
ALL_SIMULATORS = [
Questa,
Xilinx,
XilinxXSIM,
StubSimulator,
]

View File

@@ -13,6 +13,10 @@ class Simulator:
def __init__(self, testcase: 'SimTestCase' = None) -> None:
self.testcase = testcase
@property
def gui_mode(self) -> bool:
return self.testcase.request.config.getoption("--gui")
@property
def tb_files(self) -> List[str]:
files = []

View File

@@ -48,14 +48,21 @@ class Questa(Simulator):
"vsim", "-quiet",
"-voptargs=+acc",
"-msgmode", "both",
"-do", "set WildcardFilter [lsearch -not -all -inline $WildcardFilter Memory]",
"-do", "log -r /*;",
"-do", "run -all; exit;",
"-c",
"-l", "%s.log" % test_name,
"-wlf", "%s.wlf" % test_name,
"tb",
"-do", "set WildcardFilter [lsearch -not -all -inline $WildcardFilter Memory]",
"-do", "log -r /*;",
]
if self.gui_mode:
cmd.append("-i")
else:
cmd.extend([
"-do", "run -all; exit;",
"-c",
])
for plusarg in plusargs:
cmd.append("+" + plusarg)
subprocess.run(cmd, check=True)

View File

@@ -5,17 +5,17 @@ import shutil
from .base import Simulator
class Xilinx(Simulator):
class XilinxXSIM(Simulator):
"""
Don't bother using the Xilinx simulator... Its buggy and extraordinarily slow.
Avoid using the Xilinx simulator... Its buggy and extraordinarily slow.
As observed in v2023.2:
- clocking block assignments do not seem to actually simulate correctly.
assignment statements get ignored or the values get mangled.
- Streaming operators have all sorts of limitations.
Keeping this here in case someday it works better...
- Clocking block assignments to struct members do not simulate correctly.
assignment statements get lost.
https://support.xilinx.com/s/question/0D54U00007ZIGfXSAX/xsim-bug-xsim-does-not-simulate-struct-assignments-in-clocking-blocks-correctly?language=en_US
- Streaming bit-swap within a conditional returns a corrupted value
https://support.xilinx.com/s/question/0D54U00007ZIIBPSA5/xsim-bug-xsim-corrupts-value-of-signal-that-is-bitswapped-within-a-conditional-operator?language=en_US
"""
name = "xilinx"
name = "xsim"
@classmethod
def is_installed(cls) -> bool:
@@ -30,7 +30,7 @@ class Xilinx(Simulator):
"xvlog", "--sv",
"--log", "compile.log",
"--include", os.path.join(os.path.dirname(__file__), ".."),
"--define", "XSIM",
"--define", "XILINX_XSIM",
]
cmd.extend(self.tb_files)
subprocess.run(cmd, check=True)
@@ -38,7 +38,7 @@ class Xilinx(Simulator):
cmd = [
"xelab",
"--log", "elaborate.log",
"--timescale", "1ns/1ps",
"--timescale", "1ps/1ps",
"--debug", "all",
"tb",
]
@@ -50,13 +50,17 @@ class Xilinx(Simulator):
test_name = self.testcase.request.node.name
# call vsim
cmd = [
"xsim",
"--R",
# call xsim
cmd = ["xsim"]
if self.gui_mode:
cmd.append("--gui")
else:
cmd.append("-R")
cmd.extend([
"--log", "%s.log" % test_name,
"tb",
]
])
for plusarg in plusargs:
cmd.append("--testplusarg")

View File

@@ -1,8 +1,10 @@
{% sv_line_anchor %}
module tb;
timeunit 1ns;
timeunit 10ps;
timeprecision 1ps;
`define bitswap(x) ($bits(x))'({<<{x}})
logic rst = '1;
logic clk = '0;
initial forever begin

View File

@@ -1,6 +1,6 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
incompatible_sim_tools = {"xilinx"}
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
def test_dut(self):
self.run_test()

View File

@@ -1,6 +1,6 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
incompatible_sim_tools = {"xilinx"}
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
def test_dut(self):
self.run_test()

View File

@@ -145,8 +145,8 @@
.req(hwif_out.wide_ro_reg.req),
.req_is_wr(hwif_out.wide_ro_reg.req_is_wr),
.wr_data(64'b0),
.wr_biten(64'b0),
.wr_data(32'b0),
.wr_biten(32'b0),
.rd_ack(hwif_in.wide_ro_reg.rd_ack),
.rd_data(hwif_in.wide_ro_reg.rd_data),
.wr_ack()

View File

@@ -1,5 +1,6 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
def test_dut(self):
self.run_test()

View File

@@ -1,5 +1,6 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
def test_dut(self):
self.run_test()

View File

@@ -1,5 +1,6 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
def test_dut(self):
self.run_test()

View File

@@ -24,8 +24,8 @@
disable fork;
cpuif.write('h0, 'd0);
force dut.field_storage.r1.f1.value[3] = 1'b1;
release dut.field_storage.r1.f1.value[3];
assign dut.field_storage.r1.f1.value = 16'd1;
deassign dut.field_storage.r1.f1.value;
@cb;
@cb;
assert(cb.parity_error == 1'b1);

View File

@@ -36,7 +36,7 @@
wait fork;
// Mix read/writes
for(int i=0; i<64; i++) begin
for(int i=0; i<8; i++) begin
fork
automatic int i_fk = i;
begin

View File

@@ -1,5 +1,6 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
def test_dut(self):
self.run_test()

View File

@@ -1,5 +1,6 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
def test_dut(self):
self.run_test()

View File

@@ -1,5 +1,6 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
def test_dut(self):
self.run_test()

View File

@@ -71,13 +71,13 @@
cpuif.assert_read('h3000, 'h4DEAB000);
// rw_reg_lsb0
`ifndef XSIM
// Xilinx simulator has poor support for streaming operators. Skip
`ifndef XILINX_XSIM
// Skip due to xsim bug simulating internal RTL - bitswap inside conditional corrupts data
cpuif.assert_read('h3004, 0);
cpuif.write('h3004, 'h4DEAB000);
@cb;
assert({<<{cb.hwif_out.rw_reg_lsb0.f1.value}} == 8'hAB);
assert({<<{cb.hwif_out.rw_reg_lsb0.f2.value}} == 11'h4DE);
assert(`bitswap(cb.hwif_out.rw_reg_lsb0.f1.value) == 8'hAB);
assert(`bitswap(cb.hwif_out.rw_reg_lsb0.f2.value) == 11'h4DE);
cpuif.assert_read('h3004, 'h4DEAB000);
`endif
{% endblock %}

View File

@@ -1,5 +1,6 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
def test_dut(self):
self.run_test()

View File

@@ -1,5 +1,6 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
def test_dut(self):
self.run_test()

View File

@@ -50,10 +50,10 @@
cpuif.write('h14, 'h9ABC);
cpuif.write('h16, 'hDEF1);
@cb;
assert({<<{cb.hwif_out.rw_reg1_lsb0.f1.value}} == 8'h34);
assert({<<{cb.hwif_out.rw_reg1_lsb0.f2.value}} == 3'h1);
assert({<<{cb.hwif_out.rw_reg1_lsb0.f3.value}} == 1'h1);
assert({<<{cb.hwif_out.rw_reg1_lsb0.f4.value}} == 8'h9A);
assert(`bitswap(cb.hwif_out.rw_reg1_lsb0.f1.value) == 8'h34);
assert(`bitswap(cb.hwif_out.rw_reg1_lsb0.f2.value) == 3'h1);
assert(`bitswap(cb.hwif_out.rw_reg1_lsb0.f3.value) == 1'h1);
assert(`bitswap(cb.hwif_out.rw_reg1_lsb0.f4.value) == 8'h9A);
cpuif.assert_read('h10, 'h1034);
cpuif.assert_read('h12, 'h0000);
cpuif.assert_read('h14, 'h9A10);
@@ -67,8 +67,8 @@
cpuif.write('h1C, 'h9ABC);
cpuif.write('h1E, 'hDEF1);
@cb;
assert({<<{cb.hwif_out.rw_reg2_lsb0.f1.value}} == 4'h8);
assert({<<{cb.hwif_out.rw_reg2_lsb0.f2.value}} == 16'hDEF1);
assert(`bitswap(cb.hwif_out.rw_reg2_lsb0.f1.value) == 4'h8);
assert(`bitswap(cb.hwif_out.rw_reg2_lsb0.f2.value) == 16'hDEF1);
cpuif.assert_read('h18, 'h0000);
cpuif.assert_read('h1A, 'h0008);
cpuif.assert_read('h1C, 'h0000);

View File

@@ -1,5 +1,6 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
def test_dut(self):
self.run_test()

View File

@@ -69,7 +69,7 @@
assert(cb.hwif_out.reg1_msb0.f1.value == 0);
cpuif.write('hE, 'hDEF1);
@cb; @cb;
assert({<<{cb.hwif_out.reg1_msb0.f1.value}} == 64'hDEF19ABC56781234);
assert(`bitswap(cb.hwif_out.reg1_msb0.f1.value) == 64'hDEF19ABC56781234);
cpuif.assert_read('h8, 'h1234);
cpuif.assert_read('hA, 'h5678);
cpuif.assert_read('hC, 'h9ABC);
@@ -104,8 +104,8 @@
assert(cb.hwif_out.reg2_msb0.f2.value == 0);
cpuif.write('h16, 'hAA12);
@cb; @cb;
assert({<<{cb.hwif_out.reg2_msb0.f1.value}} == 12'h234);
assert({<<{cb.hwif_out.reg2_msb0.f2.value}} == 4'h1);
assert(`bitswap(cb.hwif_out.reg2_msb0.f1.value) == 12'h234);
assert(`bitswap(cb.hwif_out.reg2_msb0.f2.value) == 4'h1);
cpuif.assert_read('h14, 'h3400);
cpuif.assert_read('h16, 'h0012);
@@ -281,7 +281,7 @@
cpuif.assert_read('hA, 'h0200);
cpuif.assert_read('hC, 'h0070);
cpuif.assert_read('hE, 'h0002);
assert({<<{cb.hwif_out.reg1_msb0.f1.value}} == 'h0002_0070_0200_A000);
assert(`bitswap(cb.hwif_out.reg1_msb0.f1.value) == 'h0002_0070_0200_A000);
// Check that strobes are cumulative
cpuif.write('h8, 'h0030, 'h00F0);
@@ -297,7 +297,7 @@
cpuif.assert_read('hA, 'h0278);
cpuif.assert_read('hC, 'hA07D);
cpuif.assert_read('hE, 'hAF02);
assert({<<{cb.hwif_out.reg1_msb0.f1.value}} == 'hAF02_A07D_0278_A230);
assert(`bitswap(cb.hwif_out.reg1_msb0.f1.value) == 'hAF02_A07D_0278_A230);
{% endblock %}

View File

@@ -2,6 +2,8 @@ from ..lib.sim_testcase import SimTestCase
from ..lib.cpuifs.passthrough import Passthrough
class Test(SimTestCase):
incompatible_sim_tools = {"xsim"} # due to cb struct assignment bug
cpuif = Passthrough() # test with bit strobes
def test_dut(self):