An open array argument lets one C function accept SV arrays of any size. On the C side the argument arrives as an opaque handle.
The svdpi API queries the handle geometry and element pointers at run time.
The next two listings show one import serving two packet sizes. They need their C halves linked through DPI-C, so they are not run in this course's checks.
// not-run: needs its C half linked, and Verilator 5.048 rejects dynamic-array actuals for open arrays (Icarus 13 has no DPI)
// Open array import: works for any length byte array
import "DPI-C" function int unsigned
crc32_buf(input byte data[]);
module tb_crc_demo;
byte pkt4[] = '{8'h11, 8'h22, 8'h33, 8'h44};
byte pkt2[] = '{8'hAA, 8'hBB};
initial begin
$display("crc4 = %h", crc32_buf(pkt4)); // same import,
$display("crc2 = %h", crc32_buf(pkt2)); // different sizes
end
endmoduleThe C half walks the handle with low and high bounds. It needs its C half linked through DPI-C, so it is not run in this course's checks.
// not-run: C half of a DPI pair; the course runner builds SV only (Verilator 5.048 compiles it as C++, so wrap it in extern "C")
// crc.c
#include "svdpi.h"
unsigned int crc32_buf(const svOpenArrayHandle data)
{
unsigned int crc = 0xFFFFFFFFu;
int lo = svLow(data, 1); /* lowest index, dim 1 */
int hi = svHigh(data, 1); /* highest index */
for (int i = lo; i <= hi; i++) {
char *el = (char *)svGetArrElemPtr1(data, i);
crc ^= (unsigned char)(*el);
for (int b = 0; b < 8; b++)
crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1u)));
}
return crc ^ 0xFFFFFFFFu;
}svSize, svLow, and svHigh report the element count and index bounds of one dimension.
svGetArrElemPtr1, 2, and 3 return a pointer to one element of a 1, 2, or 3-dimensional array.
svGetArrayPtr returns a whole-block pointer only when the layout is C-contiguous, and returns NULL otherwise, so the caller checks for NULL before use.
Element access is by pointer. Writes through the pointer update the SV array for inout and output arguments.
The svLow and svHigh bounds of the dimension. The loop runs from low to high inclusive and never assumes a fixed length.
A packed struct crosses DPI as a plain bit vector, not a C struct. The C side sees the SV bit layout and unpacks fields deliberately.
The next listing maps a 32-bit header onto one vector word. It needs its C half linked through DPI-C, so it is not run in this course's checks.
// not-run: needs its C half compiled and linked; the course runner builds SV only (Verilator 5.048 links DPI-C, Icarus 13 has no DPI)
typedef struct packed {
bit [7:0] opcode;
bit [15:0] addr;
bit [7:0] len;
} hdr_t; // 32 bits total -> one svBitVecVal word
import "DPI-C" function void decode_hdr(input hdr_t h);
// C signature: void decode_hdr(const svBitVecVal* h);
// h[0] bits [31:24]=opcode, [23:8]=addr, [7:0]=len (SV packing order)Input strings are simulator-owned and valid only for the duration of the call. Copy the bytes when longer storage is needed.
The next listing returns a string from a buffer that outlives the call. It needs its C half linked through DPI-C, so it is not run in this course's checks.
// not-run: C half of a DPI pair; the course runner builds SV only (Verilator 5.048 compiles it as C++, so wrap it in extern "C")
#include <stdio.h>
const char* model_version(void)
{
static char buf[32]; /* outlives the call */
snprintf(buf, sizeof buf, "model-%d.%d", 2, 7);
return buf; /* SV copies it on return */
}OWNERSHIP RULES AT THE BOUNDARY
Who allocated it? Who frees it? Lifetime of pointer
-------------------------------------------------------------------
SV array passed to C simulator duration of the call
(svOpenArrayHandle) — never store the ptr
string input to C simulator duration of the call
(const char*) — copy bytes to keep
C heap returned via C (provide a until C frees it;
chandle free import!) SV must call free fn
C static buffer C (static) until next call that
returned as string overwrites the buffer
RULE: never free across the boundary; never store a pointer
whose lifetime is "duration of the call".Crashes at the boundary are almost always ownership bugs. Each side frees only what it allocated.
The next listing drives a packet to a C model through caller-allocated buffers. It needs its C half linked through DPI-C, so it is not run in this course's checks.
// not-run: needs its C half linked, and Verilator 5.048 rejects dynamic-array actuals for open arrays (Icarus 13 has no DPI)
// SV drives a packet to a C model and reads back the response.
import "DPI-C" function int
model_process(input byte req[], output byte rsp[]);
task automatic send_pkt(input byte req[]);
byte rsp[];
int n;
rsp = new[256]; // SV allocates the output buffer
n = model_process(req, rsp); // C fills rsp, returns count
if (n < 0) $fatal(1, "request longer than the rsp buffer");
rsp = new[n](rsp); // shrink to actual size
endtaskThe C half fills the caller buffer and returns the count. It needs its C half linked through DPI-C, so it is not run in this course's checks.
// not-run: C half of a DPI pair; the course runner builds SV only (Verilator 5.048 compiles it as C++, so wrap it in extern "C")
#include "svdpi.h"
int model_process(const svOpenArrayHandle req, svOpenArrayHandle rsp)
{
int n_req = svSize(req, 1);
int cap = svSize(rsp, 1); /* size of the caller's buffer */
int n_out = 0;
if (n_req > cap) return -1; /* never write past rsp */
for (int i = 0; i < n_req; i++) {
char *in = (char *)svGetArrElemPtr1(req, svLow(req,1) + i);
char *out = (char *)svGetArrElemPtr1(rsp, svLow(rsp,1) + n_out);
*out = (char)(*in ^ 0x5A); /* model the transform */
n_out++;
}
return n_out; /* SV side shrinks rsp to this length */
}The next listing proves the byte transform and the header unpacking in plain SystemVerilog. It runs in this course's checks.
module tb_bytes_struct;
logic [7:0] req [0:3];
logic [7:0] rsp [0:3];
logic [7:0] expv [0:3];
logic [31:0] hdr;
logic [7:0] opcode;
logic [15:0] addr;
logic [7:0] len;
int bad;
int i;
initial begin
bad = 0;
req[0] = 8'h11; req[1] = 8'h22; req[2] = 8'h33; req[3] = 8'h44;
expv[0] = 8'h4B; expv[1] = 8'h78; expv[2] = 8'h69; expv[3] = 8'h1E;
for (i = 0; i < 4; i++) rsp[i] = req[i] ^ 8'h5A;
for (i = 0; i < 4; i++) if (rsp[i] !== expv[i]) bad++;
hdr = {8'hA5, 16'h1234, 8'h07};
opcode = hdr[31:24]; addr = hdr[23:8]; len = hdr[7:0];
if (opcode !== 8'hA5) bad++;
if (addr !== 16'h1234) bad++;
if (len !== 8'h07) bad++;
if (bad == 0) $display("PASS: byte transform matched on 4 bytes and header gave 3 fields");
else $fatal(1, "FAIL: byte transform gave a wrong value");
$finish;
end
endmoduleThe SV side, since it allocated the buffer. Each side frees only its own allocations, so C never frees it.