Skip to content

Commit 3704a86

Browse files
committed
test: add standalone croc_idma testbench with DPI-C HAL bridge
1 parent 5cf4613 commit 3704a86

12 files changed

Lines changed: 1527 additions & 6 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.bender
22
tmp
33
*.log
4+
*.so

Bender.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ sources:
6969
- rtl/test/croc_vip.sv
7070
- rtl/test/tb_croc_soc.sv
7171

72+
- target: any(simulation, verilator)
73+
files:
74+
- rtl/obi/test/obi_sim_mem.sv
75+
- rtl/test/idma/tb_croc_idma_pkg.sv
76+
- rtl/test/idma/croc_idma_drv_if.sv
77+
- rtl/test/idma/croc_idma_base.sv
78+
- rtl/test/idma/tb_croc_idma.sv
79+
7280
- target: genesys2
7381
files:
7482
- xilinx/hw/croc_xilinx.sv
@@ -117,9 +125,14 @@ vendor_package:
117125
upstream: { git: "https://github.com/pulp-platform/obi.git", rev: "6a724da5c8d6412b88b6948746e04c1adf39d017" } # newest (0.1.7+)
118126
patch_dir: "rtl/.patches/obi"
119127
exclude_from_upstream:
120-
- "src/test"
128+
- "src/test/obi_test.sv"
129+
- "src/test/obi_asserter.sv"
130+
- "src/test/tb_obi_xbar.sv"
131+
- "src/test/tb_obi_atop_resolver.sv"
132+
- "src/test/atop_golden_mem_pkg.sv"
121133
mapping:
122134
- { from: 'src/', to: '', patch_dir: 'src/' }
135+
- { from: 'src/test/obi_sim_mem.sv', to: 'test/obi_sim_mem.sv', patch_dir: 'test/' }
123136
- { from: 'include/obi', to: 'include/obi', patch_dir: 'include/' }
124137
- { from: 'Bender.yml', to: 'Bender.yml', patch_dir: '' }
125138
- { from: 'Readme.md', to: 'Readme.md', patch_dir: '' }

rtl/idma/README.md

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,105 @@
55

66
Home of the iDMA - a modular, parametrizable, and highly flexible *Data Movement Accelerator (DMA)*
77
architecture targeting a wide range of platforms from ultra-low power edge nodes to high-performance
8-
computing systems. iDMA is part of the [PULP (Parallel Ultra-Low-Power) platform](https://pulp-platform.org/),
8+
computing systems.
9+
10+
---
11+
12+
## Croc iDMA Standalone Testbench
13+
14+
The `croc_idma` wrapper (`croc_idma.sv`) can be tested standalone, without running the full Croc SoC.
15+
The testbench uses DPI-C to bridge the **same C HAL functions** used by the software SDK
16+
(`sw/lib/src/idma.c`) into SystemVerilog, driving real OBI bus transactions on the DUT.
17+
18+
### Prerequisites
19+
20+
- QuestaSim (tested with 2025.3)
21+
- GCC (host compiler, for DPI shared library)
22+
- [Bender](https://github.com/pulp-platform/bender) (dependency manager)
23+
24+
### Quick Start
25+
26+
From the repository root:
27+
28+
```sh
29+
# 1. Compile all RTL
30+
cd vsim
31+
./run_vsim.sh --flist
32+
./run_vsim.sh --build
33+
34+
# 2. Build the DPI shared library
35+
gcc -shared -fPIC -o dpi_idma.so \
36+
-I$(questa-2025.3 vsim -version 2>&1 | head -1 > /dev/null; echo /usr/pack/questa-2025.3-dz/questasim/include) \
37+
-I../rtl/test/idma/dpi \
38+
../rtl/test/idma/dpi/idma_hal_dpi.c \
39+
../rtl/test/idma/dpi/test_idma_dpi.c
40+
41+
# 3. Run the standalone iDMA testbench
42+
questa-2025.3 vsim -c tb_croc_idma -t 1ns \
43+
-sv_lib ./dpi_idma \
44+
-suppress vsim-3009 -suppress vsim-8683 -suppress vsim-8386 \
45+
-do "run -a; quit"
46+
```
47+
48+
If `QUESTA_HOME` is set, replace the `-I` path with `-I${QUESTA_HOME}/include`.
49+
50+
### What the Tests Cover
51+
52+
| # | Test | Description |
53+
|---|------|-------------|
54+
| 1 | Register read/write | Write and readback all DMA config registers + ND enable bit |
55+
| 2 | 1D memcpy (16B) | Basic 4-word copy, matches `sw/test/test_idma.c` |
56+
| 3 | 2D transfer | 2 rows of 8 bytes, equal strides |
57+
| 4 | Large 1D memcpy (256B) | Exercises burst/legalizer paths |
58+
| 5 | Back-to-back transfers | Two sequential transfers, verifies job ID increment |
59+
| 6 | Non-aligned length (7B) | Odd byte count through the hardware legalizer |
60+
| 7 | 2D scatter/gather | Different src/dst strides (32 vs 8), 4 rows |
61+
| 8 | IRQ / busy signals | Verifies `busy_o` asserted during transfer, deasserted after |
62+
63+
### Architecture
64+
65+
```
66+
test_idma_dpi.c C test (8 test cases)
67+
|
68+
idma_hal_dpi.c C HAL (same API as sw/lib/src/idma.c)
69+
|
70+
dpi_bridge.h reg_write32/reg_read32 wrappers
71+
| (DPI export tasks)
72+
tb_croc_idma.sv SV top: DPI exports → OBI driver → DUT
73+
|
74+
croc_idma_base.sv Base harness: clock, reset, DUT, 2x obi_sim_mem
75+
```
76+
77+
### File Overview
78+
79+
```
80+
rtl/test/idma/
81+
├── tb_croc_idma_pkg.sv Timing params, register offsets, memory base addresses
82+
├── croc_idma_drv_if.sv OBI config port driver (procedural tasks)
83+
├── croc_idma_base.sv Base harness (clock, DUT, read/write sim memories)
84+
├── tb_croc_idma.sv Top-level TB with DPI imports/exports
85+
└── dpi/
86+
├── dpi_bridge.h DPI-C function declarations + convenience wrappers
87+
├── idma_hal_dpi.c iDMA HAL using DPI MMIO (replaces sw/lib/src/idma.c)
88+
└── test_idma_dpi.c Test entry point called from SV
89+
```
90+
91+
### Adding New Tests
92+
93+
Add new test functions in `test_idma_dpi.c` following the existing pattern:
94+
1. Pre-load source memory with `dpi_src_mem_write8()` / `dpi_src_mem_write32()`
95+
2. Call HAL functions (`idma_memcpy`, `idma_memcpy_2d`, or individual register setters)
96+
3. Verify destination memory with `dpi_dst_mem_read8()` / `dpi_dst_mem_read32()`
97+
4. Use `CHECK_ASSERT(id, condition)` for pass/fail — the assert ID is the return code on failure
98+
5. Register the new test in `run_idma_test()`
99+
100+
Rebuild only the DPI library after changing C files (no SV recompile needed):
101+
```sh
102+
gcc -shared -fPIC -o dpi_idma.so -I${QUESTA_HOME}/include -I../rtl/test/idma/dpi \
103+
../rtl/test/idma/dpi/idma_hal_dpi.c ../rtl/test/idma/dpi/test_idma_dpi.c
104+
```
105+
106+
--- iDMA is part of the [PULP (Parallel Ultra-Low-Power) platform](https://pulp-platform.org/),
9107
where it is used as a cluster level DMA in the [Snitch Cluster](https://github.com/pulp-platform/snitch)
10108
and in the [PULP Cluster](https://github.com/pulp-platform/pulp).
11109

rtl/obi/test/obi_sim_mem.sv

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// Copyright 2023 ETH Zurich and University of Bologna.
2+
// Solderpad Hardware License, Version 0.51, see LICENSE for details.
3+
// SPDX-License-Identifier: SHL-0.51
4+
5+
// Author: Michael Rogenmoser <michaero@iis.ee.ethz.ch>
6+
7+
module obi_sim_mem import obi_pkg::*; #(
8+
parameter obi_pkg::obi_cfg_t ObiCfg = obi_pkg::ObiDefaultConfig,
9+
parameter type obi_req_t = logic,
10+
parameter type obi_rsp_t = logic,
11+
parameter type obi_r_chan_t = logic,
12+
parameter bit WarnUninitialized = 1'b0,
13+
parameter bit ClearErrOnAccess = 1'b0,
14+
parameter time ApplDelay = 0ps,
15+
parameter time AcqDelay = 0ps
16+
) (
17+
input logic clk_i,
18+
input logic rst_ni,
19+
input obi_req_t obi_req_i,
20+
output obi_rsp_t obi_rsp_o,
21+
22+
output logic mon_valid_o,
23+
output logic mon_we_o,
24+
output logic [ ObiCfg.AddrWidth-1:0] mon_addr_o,
25+
output logic [ ObiCfg.DataWidth-1:0] mon_wdata_o,
26+
output logic [ObiCfg.DataWidth/8-1:0] mon_be_o,
27+
output logic [ ObiCfg.IdWidth-1:0] mon_id_o
28+
);
29+
if (ObiCfg.OptionalCfg.UseAtop) $error("Please use an ATOP resolver before sim mem.");
30+
if (ObiCfg.Integrity) $error("Integrity not supported");
31+
if (ObiCfg.OptionalCfg.UseProt) $warning("Prot not checked!");
32+
if (ObiCfg.OptionalCfg.UseMemtype) $warning("Memtype not checked!");
33+
34+
typedef logic [ObiCfg.AddrWidth-1:0] addr_t;
35+
36+
obi_r_chan_t rsp_queue[$];
37+
38+
logic [7:0] mem[addr_t];
39+
logic rsp_ready;
40+
41+
if (ObiCfg.UseRReady) begin : gen_rready
42+
assign rsp_ready = obi_req_i.rready;
43+
end else begin : gen_no_rready
44+
assign rsp_ready = 1'b1;
45+
end
46+
47+
logic mon_valid;
48+
logic mon_we;
49+
logic [ObiCfg.AddrWidth-1:0] mon_addr;
50+
logic [ObiCfg.DataWidth-1:0] mon_wdata;
51+
logic [ObiCfg.DataWidth/8-1:0] mon_be;
52+
logic [ObiCfg.IdWidth-1:0] mon_id;
53+
54+
assign mon_we = obi_req_i.a.we;
55+
assign mon_addr = obi_req_i.a.addr;
56+
assign mon_wdata = obi_req_i.a.wdata;
57+
assign mon_be = obi_req_i.a.be;
58+
assign mon_id = obi_req_i.a.aid;
59+
60+
initial begin
61+
fork
62+
// Request Handling
63+
forever begin
64+
// Start cycle
65+
@(posedge clk_i);
66+
#(ApplDelay);
67+
// Indicate ready
68+
obi_rsp_o.gnt = 1'b1;
69+
mon_valid = 1'b0;
70+
// End of cycle
71+
#(AcqDelay-ApplDelay);
72+
// If requesting
73+
if (obi_req_i.req) begin
74+
mon_valid = 1'b1;
75+
if (obi_req_i.a.we) begin
76+
automatic obi_r_chan_t write_rsp;
77+
// write memory
78+
for (int i = 0; i < ObiCfg.DataWidth/8; i++) begin
79+
if (obi_req_i.a.be[i]) begin
80+
mem[obi_req_i.a.addr+i] = obi_req_i.a.wdata[8*i+:8];
81+
end
82+
end
83+
// write response
84+
write_rsp = 'x;
85+
write_rsp.rid = obi_req_i.a.aid;
86+
write_rsp.err = 1'b0;
87+
write_rsp.r_optional = '0;
88+
rsp_queue.push_back(write_rsp);
89+
end else begin
90+
// read response
91+
automatic obi_r_chan_t read_rsp = 'x;
92+
if (!mem.exists(obi_req_i.a.addr)) begin
93+
if (WarnUninitialized) begin
94+
$warning("%t - Access to non-initialized address at 0x%016x by ID 0x%x.",
95+
$realtime, obi_req_i.a.addr, obi_req_i.a.aid);
96+
end
97+
end else begin
98+
for (int i = 0; i < ObiCfg.DataWidth/8; i++) begin
99+
read_rsp.rdata[8*i+:8] = mem[obi_req_i.a.addr+i];
100+
end
101+
end
102+
read_rsp.rid = obi_req_i.a.aid;
103+
read_rsp.err = 1'b0;
104+
read_rsp.r_optional = '0;
105+
rsp_queue.push_back(read_rsp);
106+
end
107+
end
108+
end
109+
// Response Handling
110+
forever begin
111+
// Start cycle
112+
@(posedge clk_i);
113+
#(ApplDelay);
114+
obi_rsp_o.rvalid = 1'b0;
115+
if (rsp_queue.size() != 0) begin
116+
obi_rsp_o.r = rsp_queue[0];
117+
obi_rsp_o.rvalid = 1'b1;
118+
// End of cycle
119+
#(AcqDelay-ApplDelay);
120+
if (rsp_ready) begin
121+
void'(rsp_queue.pop_front());
122+
end
123+
end
124+
end
125+
join
126+
end
127+
128+
initial begin
129+
mon_valid_o = '0;
130+
mon_we_o = '0;
131+
mon_addr_o = '0;
132+
mon_wdata_o = '0;
133+
mon_be_o = '0;
134+
mon_id_o = '0;
135+
wait (rst_ni);
136+
forever begin
137+
@(posedge clk_i);
138+
mon_valid_o <= #(ApplDelay) mon_valid;
139+
mon_we_o <= #(ApplDelay) mon_we;
140+
mon_addr_o <= #(ApplDelay) mon_addr;
141+
mon_wdata_o <= #(ApplDelay) mon_wdata;
142+
mon_be_o <= #(ApplDelay) mon_be;
143+
mon_id_o <= #(ApplDelay) mon_id;
144+
end
145+
end
146+
endmodule
147+
148+
`include "obi/typedef.svh"
149+
`include "obi/assign.svh"
150+
151+
module obi_sim_mem_intf import obi_pkg::*; #(
152+
parameter obi_pkg::obi_cfg_t ObiCfg = obi_pkg::ObiDefaultConfig,
153+
parameter bit WarnUninitialized = 1'b0,
154+
parameter bit ClearErrOnAccess = 1'b0,
155+
parameter time ApplDelay = 0ps,
156+
parameter time AcqDelay = 0ps
157+
) (
158+
input logic clk_i,
159+
input logic rst_ni,
160+
OBI_BUS.Subordinate obi_sbr,
161+
162+
output logic mon_valid_o,
163+
output logic mon_we_o,
164+
output logic [ ObiCfg.AddrWidth-1:0] mon_addr_o,
165+
output logic [ ObiCfg.DataWidth-1:0] mon_wdata_o,
166+
output logic [ObiCfg.DataWidth/8-1:0] mon_be_o,
167+
output logic [ ObiCfg.IdWidth-1:0] mon_id_o
168+
);
169+
170+
`OBI_TYPEDEF_ALL(obi, ObiCfg)
171+
172+
obi_req_t obi_req;
173+
obi_rsp_t obi_rsp;
174+
175+
`OBI_ASSIGN_TO_REQ(obi_req, obi_sbr, ObiCfg)
176+
`OBI_ASSIGN_FROM_RSP(obi_sbr, obi_rsp, ObiCfg)
177+
178+
obi_sim_mem #(
179+
.ObiCfg (ObiCfg),
180+
.obi_req_t (obi_req_t),
181+
.obi_rsp_t (obi_rsp_t),
182+
.obi_r_chan_t (obi_r_chan_t),
183+
.WarnUninitialized(WarnUninitialized),
184+
.ClearErrOnAccess (ClearErrOnAccess),
185+
.ApplDelay (ApplDelay),
186+
.AcqDelay (AcqDelay)
187+
) i_obi_sim_mem (
188+
.clk_i,
189+
.rst_ni,
190+
.obi_req_i(obi_req),
191+
.obi_rsp_o(obi_rsp),
192+
193+
.mon_valid_o,
194+
.mon_we_o,
195+
.mon_addr_o,
196+
.mon_wdata_o,
197+
.mon_be_o,
198+
.mon_id_o
199+
);
200+
201+
endmodule

0 commit comments

Comments
 (0)