Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • SystemVerilog Language Foundations34
  • Object-Oriented SystemVerilog for Verification33
  • Constraint Randomization — Interview-Critical Deep Dive56
  • SystemVerilog Assertions — Interview-Critical Deep Dive54
  • Functional Coverage in SystemVerilog28
  • Testbench Architecture in SystemVerilog32
  • Advanced Language & Integration30
    • DPI-C: Calling C from SystemVerilog
      • DPI Imports
      • Exports & Context Calls
      • Passing Arrays & Structs
      • C Reference Models
      • DPI Pitfalls & Debug
    • bind: Attaching Verification to RTL
    • Macros, Defines & Compilation
    • Plusargs, Files & Runtime Configuration
    • Simulation Performance & TB Memory
    • RTL/TB Boundaries, Races & Timing
  • Senior & Interview Preparation29
Loading lesson

‹DPI-C: Calling C from SystemVerilog
  1. Topics
  2. SystemVerilog
  3. Advanced Language & Integration
  4. DPI-C: Calling C from SystemVerilog
  5. DPI Imports

Part 7 · Advanced & Integration · Intermediate

DPI Imports

Import C functions into SystemVerilog with pure and context qualifiers and 2-state type mapping.

On this page

    Function and task imports

    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.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 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.

    c
    1
    2
    3
    4
    5
    6
    7
    8
    // 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);
    }
    Check yourself The SV import names the function 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.

    Pure and context

    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

    pure

    No side effects, result depends only on arguments

    Math helpers, CRC, format converters

    context

    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

    Common pitfall

    Marking a function pure when it keeps static state or performs I/O lets the simulator cache repeated calls. Later calls then return stale results with no warning.

    Check yourself A C helper computes a CRC from its arguments only, while another helper reports errors back into SV. Which qualifier fits each?

    The CRC helper is pure. The reporting helper is context, since only a context import establishes the scope that exports need.

    Types at the boundary

    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

    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 (input)

    const char*

    Simulator owns the memory

    string (output, inout)

    const char**

    C points it at a buffer that C keeps alive

    chandle

    void*

    Opaque C pointer

    bit

    svBit

    2-state, 1 bit

    logic

    svLogic

    4-state, 1 bit

    packed bit vector

    svBitVecVal array

    2-state vector

    packed logic vector

    svLogicVecVal array

    4-state, aval and bval pairs

    unpacked array

    svOpenArrayHandle

    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.

    Testbench

    DPI call directions across the shared object.
    SV calls C through imports; C calls back through exports. The shared object links the two sides.

    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.

    tb.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 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
    endmodule

    The C half of the round trip needs its C half linked through DPI-C, so it is not run in this course's checks.

    c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 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.

    checksum_fold.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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

    Interview

    Expect to explain when an imported task is legal and when pure caching is safe. A task import matters only when C must call a blocking exported SV task, and pure is safe only for side-effect-free code.

    Related topics

    • Exports & Context Calls

    Previous

    238 / 296

    Next: Exports & Context Calls

    Up next

    Up nextExports & Context Calls

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Passing Arrays & Structs
    • C Reference Models
    • DPI Pitfalls & Debug