Part 7 · Advanced & Integration · Intermediate

DPI Imports

import "DPI-C" function/task syntax, pure and context qualifiers, SV-to-C type mapping, and svdpi.h.

Import syntax: functions and tasks

An import "DPI-C" declaration gives SystemVerilog a prototype for a C function. Imported functions must return in zero simulation time — they map to ordinary C functions. Imported tasks may consume time only in the limited sense that they can call exported SV tasks that block; the C code itself cannot wait on simulation events directly.

systemverilog
// Function import: zero-time, returns a value
import "DPI-C" function int compute_crc(input int unsigned data,
                                        input int unsigned poly);

// Task import: void on the C side, 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);

pure and context qualifiers

Two optional qualifiers tell the simulator what the C function is allowed to do, which controls both correctness and optimization. pure promises the result depends only on the inputs — no side effects, no static state, no file I/O — so the simulator may cache or reorder calls. context promises the opposite: the function needs to know its SV call site, because it will call exported SV functions or use scope-sensitive svdpi APIs.

  • pure — side-effect free, result depends only on arguments; simulator may cache results and skip repeated calls. Math helpers, CRC, format converters.

  • context — the C code calls exported SV functions/tasks or uses svGetScope/svSetScope; the simulator must maintain call-site scope. Required for any callback into SV.

  • (no qualifier) — default: side effects allowed (file I/O, static state), but no calls back into SV. Most utility imports live here.


Type mapping: SV to C

DPI defines a fixed mapping between SV types and C types, declared in the standard header svdpi.h. Two-state SV types map to plain C integer types; four-state types map to svLogic and packed vectors to svLogicVecVal arrays that carry the X/Z encoding.

diagram
SV TYPE                  C TYPE (svdpi.h)         NOTES
  ─────────────────────────────────────────────────────────────────
  byte                     char                     2-state, 8 bit
  shortint                 short int                2-state, 16 bit
  int                      int                      2-state, 32 bit
  longint                  long long                2-state, 64 bit
  real                     double
  shortreal                float
  string                   const char*              simulator owns memory
  chandle                  void*                    opaque C pointer
  bit                      svBit (unsigned char)    2-state, 1 bit
  logic / reg              svLogic (unsigned char)  4-state, 1 bit
  bit  [N:0] (packed)      svBitVecVal array        2-state vector
  logic [N:0] (packed)     svLogicVecVal array      4-state, aval/bval pairs
  unpacked array []        svOpenArrayHandle        open array (see later)
  struct (packed)          passed as vector         bit layout preserved

Prefer 2-state types (int, byte, bit [31:0]) at the DPI boundary. Four-state values force the C side to decode aval/bval pairs, and a C model has no meaningful interpretation of X anyway — resolve X-ness in SV before crossing.


Worked example: checksum import

A minimal but complete round trip: SV passes a data word and an accumulator to C, C returns the updated checksum. The C file is compiled into a shared object the simulator loads at startup.

SV side

systemverilog
// 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
endmodule

C side

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;
}

Walkthrough

  1. The import is declared pure — no side effects, so the simulator may cache identical calls.

  2. int unsigned maps to unsigned int; no svdpi types needed for plain scalars.

  3. The C function name must match the SV import name (or use the rename form).

  4. Compile C with -fPIC into a shared object; load with the simulator's -sv_lib or equivalent switch.

Key takeaways

  • import "DPI-C" function for zero-time calls; task imports only matter when C must call blocking exported SV tasks.

  • pure enables caching; context is mandatory when C calls back into SV — default is in between.

  • Keep the boundary 2-state: int/byte/bit map to plain C types, logic forces aval/bval decoding.

  • svdpi.h is the standard header — it defines svBit, svLogic, vector value types, and open-array APIs.

Common pitfalls

  • Marking a function pure when it has static state or I/O — simulator caching silently returns stale results.

  • Passing logic vectors when bit would do — C side must decode 4-state aval/bval pairs for no benefit.

  • Mismatched prototypes between SV import and C definition — often links fine, then corrupts the stack at runtime.

  • Assuming an imported task can wait on SV events — C code cannot block; only exported SV tasks it calls can.