An import declaration gives SystemVerilog a prototype for a C function. Imported functions return in zero simulation time.
Imported tasks may consume time only when they call an exported SV task that blocks. The C code itself never waits on simulation events.
The next listing 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)
// Function import: zero-time, returns a value
import "DPI-C" function int compute_crc(input int unsigned data,
input int unsigned poly);
// Task import: returns int on the C side (0, or 1 if disabled),
// may call exported SV tasks
import "DPI-C" task run_c_model(input int n_cycles);
// Renaming: SV name differs from C symbol
import "DPI-C" c_checksum = function int checksum(input byte data[]);
// void function
import "DPI-C" function void log_to_c(input string msg);The rename form lets the SV name differ from the C symbol. The C definition must use the C symbol, with an open-array handle for array ports.
The next listing shows the correct C signature for the renamed import. 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"
/* Matches: import "DPI-C" c_checksum = function int checksum(input byte data[]); */
int c_checksum(const svOpenArrayHandle h)
{
return (int)svSize(h, 1);
}checksum but the C object exports c_checksum. Which import form links them, and what C type carries the array?The rename form import "DPI-C" c_checksum = function int checksum(input byte data[]). The array arrives as const svOpenArrayHandle.
The pure qualifier promises the result depends only on the inputs. The context qualifier promises the opposite: the function needs its SV call site, because it will call exported SV functions.
Qualifier | Promise | Typical use |
|---|---|---|
| No side effects, result depends only on arguments | Math helpers, CRC, format converters |
| Needs call-site scope, may call exported SV code | Any callback into SV |
none | Side effects allowed, no calls back into SV | File utilities, logging, stateful models |
The CRC helper is pure. The reporting helper is context, since only a context import establishes the scope that exports need.
DPI defines a fixed mapping between SV types and C types in the standard header svdpi.h. Two-state SV types map to plain C integer types.
SV type | C type | Notes |
|---|---|---|
|
| 2-state, 8 bit |
|
| 2-state, 16 bit |
|
| 2-state, 32 bit |
|
| 2-state, 64 bit |
|
| |
|
| |
|
| Simulator owns the memory |
|
| C points it at a buffer that C keeps alive |
|
| Opaque C pointer |
|
| 2-state, 1 bit |
|
| 4-state, 1 bit |
packed |
| 2-state vector |
packed |
| 4-state, aval and bval pairs |
unpacked array |
| Open array, queried at run time |
packed struct | passed as vector | Bit layout preserved, not a C struct |
Prefer 2-state types such as int, byte, and bit [31:0] at the DPI boundary. A C model has no meaningful interpretation of X, so four-state values are resolved in SV before crossing.
SV byte is signed while C char signedness is implementation-defined. Portable C code converts through unsigned char at the boundary, as the CRC loop in a later lesson does.
The next two listings show a complete checksum round trip. 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 compiled and linked; the course runner builds SV only (Verilator 5.048 links DPI-C, Icarus 13 has no DPI)
// dpi_pkg.sv
package dpi_pkg;
import "DPI-C" pure function int unsigned
checksum_add(input int unsigned acc, input int unsigned data);
endpackage
module tb;
import dpi_pkg::*;
int unsigned csum;
initial begin
csum = 32'hFFFF_FFFF;
csum = checksum_add(csum, 32'hDEAD_BEEF);
csum = checksum_add(csum, 32'h0000_1234);
$display("checksum = %h", csum);
end
endmoduleThe C half of the round trip 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")
// checksum.c — compile: gcc -shared -fPIC -o checksum.so checksum.c
#include "svdpi.h"
unsigned int checksum_add(unsigned int acc, unsigned int data)
{
/* simple Fletcher-style fold; pure: no static state */
acc ^= data;
acc = (acc << 7) | (acc >> 25);
return acc;
}The import is pure, so the simulator may cache identical calls.
int unsigned maps to unsigned int, with no svdpi types needed for plain scalars.
The C function name matches the SV import name, or the rename form is used.
C is compiled with -fPIC into a shared object that the simulator loads with its -sv_lib switch.
The next listing proves the same fold in plain SystemVerilog. It runs in this course's checks.
timeunit 1ns; timeprecision 1ps;
module checksum_fold (
input logic [31:0] acc,
input logic [31:0] data,
output logic [31:0] csum
);
logic [31:0] mixed;
always_comb begin
mixed = acc ^ data;
csum = (mixed << 7) | (mixed >> 25);
end
endmodule
module tb_checksum_fold;
logic [31:0] acc, data, csum;
logic [31:0] expv;
int bad = 0;
checksum_fold dut (.*);
initial begin
acc = 32'hFFFF_FFFF; data = 32'hDEAD_BEEF; #1;
expv = ((acc ^ data) << 7) | ((acc ^ data) >> 25);
if (csum !== expv) bad++;
acc = csum; data = 32'h0000_1234; #1;
expv = ((acc ^ data) << 7) | ((acc ^ data) >> 25);
if (csum !== expv) bad++;
acc = 32'hFFFF_FFFF; data = 32'hDEAD_BEEF; #1;
if (csum !== (((32'hFFFF_FFFF ^ 32'hDEAD_BEEF) << 7) | ((32'hFFFF_FFFF ^ 32'hDEAD_BEEF) >> 25))) bad++;
if (bad == 0) $display("PASS: checksum fold matches the reference for 3 checks");
else $fatal(1, "FAIL: checksum fold gave a wrong value");
$finish;
end
endmodule