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

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
    • Simulation for Sign-off
    • Safety, Acceleration and Models
      • Functional Safety: Fault Injection and Diagnostic Coverage
      • Emulation and FPGA Prototyping: When the Simulator Is Too Slow
      • Multi-Language Benches: SystemC, C/C++ and Python
      • Mixed-Signal Verification with UVM
    • Beyond Simulation
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Safety, Acceleration and Models
  1. Topics
  2. UVM Verification
  3. Verification in Industry Practice
  4. Safety, Acceleration and Models
  5. Multi-Language Benches: SystemC, C/C++ and Python

Part 12 · Industry Practice · Intermediate

Multi-Language Benches: SystemC, C/C++ and Python

SystemC TLM-2.0 models next to UVM, C/C++ models through DPI with callbacks, C tests on the embedded CPU, and how cocotb and UVM coexist, with a guide for choosing per block.

On this page

    Three deliveries land in the same week. The architects send a SystemC TLM-2.0 model of the whole subsystem. The algorithm team sends a C++ library that computes the expected output of the DMA's new compression mode. The firmware team wants to run their boot code on the CPU in the RTL and see the DMA move data. And a new hire asks why the team down the hall verifies its blocks in Python. Your bench is UVM. Which of these does it connect to, how, and who decides?

    The idea: UVM owns the testbench, other languages own the knowledge

    UVM is good at structure: stimulus, checking, coverage, configuration and reuse. It is not where an algorithm's truth lives. That truth is usually written first by someone else, in the language they work in: architects in SystemC, algorithm engineers in C++ or Python, firmware engineers in C. Rewriting it in SystemVerilog creates a second copy that drifts from the first. So the usual answer is to call the original, and the engineering question is only how the call crosses the language boundary.

    Think of an embassy. Your bench is the home country; each other language is a foreign one with its own laws. A bridge such as DPI is the embassy: a narrow, well-defined place where things cross, with rules about what may pass (simple data types, who owns memory, who may block). Most multi-language trouble comes from treating the border as if it were not there.

    The picture: four ways in

    Flowchart: a UVM bench with sequences, scoreboard and a mailbox monitor; a C/C++ model feeds the scoreboard over DPI; a SystemC model connects to sequences through a TLM bridge; a C test on the DUT CPU reports through a mailbox; a cocotb bench shares models with the C side.
    C and C++ models are called through DPI; SystemC models connect through a TLM bridge; firmware runs on the DUT's own CPU and reports through memory; Python usually runs as its own bench and shares models rather than components.

    Build it: C/C++ through DPI, in both directions

    Calling a C reference model from SystemVerilog is covered with the course bench in DPI-C reference models: dma_predictor_dpi calls dma_c_model and agrees with the SystemVerilog predictor on 14 of 14 words. At project scale three more things matter. First, the C side sometimes needs to call back into SystemVerilog: to fetch data on demand, to log through the UVM report server, or to wait for simulated time. That is a DPI export, and the import that triggers it must be declared context. Second, models with state need one instance per DUT instance, usually held as a chandle returned by an init function. Third, a C++ model needs extern "C" on every function SystemVerilog sees, or the link fails on a mangled name (the course bench hit exactly this link error when its C model was first added).

    A small complete example: the C model pulls its input words from SystemVerilog one call at a time, then reports back through an exported SV function.

    top.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
    // not-run: needs model.c compiled with it (verilator --binary top.sv model.c)
    module top;
      // The C side calls back into SV: export makes an SV function callable from C.
      export "DPI-C" function sv_get_word;
      export "DPI-C" function sv_note;
      // context: the C function may call exported SV functions.
      import "DPI-C" context function int unsigned c_dma_job(int unsigned key, int unsigned len);
    
      int unsigned mem [16];
      int unsigned n_notes;
    
      function int unsigned sv_get_word(int unsigned idx);
        return mem[idx];
      endfunction
    
      function void sv_note(string msg);
        n_notes++;
        $display("[SV] note from C: %s", msg);
      endfunction
    
      initial begin
        int unsigned sum, exp;
        foreach (mem[i]) mem[i] = 32'h1000_0000 + i;
        sum = c_dma_job(32'hA5A5_0F0F, 5);
        exp = 0;
        for (int i = 0; i < 5; i++) exp += mem[i] ^ 32'hA5A5_0F0F;
        $display("[SV] C model checksum %08h, SV expects %08h: %s, %0d note(s)",
                 sum, exp, sum == exp ? "PASS" : "FAIL", n_notes);
        $finish;
      end
    endmodule
    c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <stdio.h>
    #include "svdpi.h"
    #ifdef __cplusplus
    extern "C" {
    #endif
    /* Exported from SV (the prototypes Verilator also writes to Vtop__Dpi.h). */
    extern unsigned int sv_get_word(unsigned int idx);
    extern void sv_note(const char *msg);
    
    /* A C model that pulls its input words from the testbench, one call per word. */
    unsigned int c_dma_job(unsigned int key, unsigned int len) {
      unsigned int sum = 0;
      char msg[64];
      for (unsigned int i = 0; i < len; i++) sum += sv_get_word(i) ^ key;
      snprintf(msg, sizeof msg, "job of %u words, key %08x", len, key);
      sv_note(msg);
      return sum;
    }
    #ifdef __cplusplus
    }
    #endif
    Diagram
    $ verilator --binary top.sv model.c --top-module top && ./obj_dir/Vtop
    [SV] note from C: job of 5 words, key a5a50f0f
    [SV] C model checksum 8c394b41, SV expects 8c394b41: PASS, 1 note(s)

    Two details make this work. The import is context, which tells the simulator the C function may call back into SystemVerilog; without it, calling an export is undefined. And the exported functions are functions, so they take no simulated time. If the C side must wait for the design ("run 100 clocks, then read"), export an SV task instead and import the C routine as a context task; the C call then blocks while simulated time advances, which only works on threads the simulator controls.

    SystemC TLM-2.0 models and UVM

    Architecture teams often build a virtual prototype in SystemC with TLM-2.0 sockets and the generic payload: the same transport concepts that UVM's own TLM-2.0 classes copy (see Sockets and the generic payload). Connecting the two means passing a generic payload across the SV/SystemC border. Simulators that run both languages in one kernel provide this through their own TLM bridge; UVM Connect, an open-source library originally from Mentor (now Siemens EDA), does it by registering each SystemVerilog port or socket and each SystemC socket under a shared string name and pairing them. Uses you will see:

    • The SystemC model as the reference model. UVM monitors send observed transactions to it; it returns predictions for the scoreboard. One model serves architecture, software and DV.

    • The SystemC model as the stimulus source. A software stack running on the virtual prototype issues transactions that UVM drivers turn into pin activity on the RTL.

    • Swapping a block. In a subsystem, one block runs as RTL while its neighbours stay as fast TLM models, so the RTL block sees realistic traffic early.

    The hard parts are not the bridge. They are timing (a loosely timed model has no clock; its transactions carry approximate delays, and checking must not compare cycle timing against it) and ownership (a TLM-2.0 payload is passed by reference and may be reused by its initiator, so copy what you keep, the same rule as for UVM monitors).

    Software-driven tests: C on the DUT's own CPU

    When the SoC has a processor, many system-level tests are C programs compiled for that CPU, loaded into the memory model (for example with $readmemh on the RAM, or through a backdoor), and run on the RTL. The firmware does what real software will do: program the DMA, start it, take the interrupt. UVM stays in charge of the environment around it: external interfaces, scoreboards on the buses, and the verdict. The two sides talk through a mailbox: a reserved address that the C code writes to ("test passed", "test failed with code 3", a character for printf) and that a UVM monitor on the memory bus decodes into messages and objections. Synchronization works the same way in reverse: UVM writes a flag the C program polls.

    This is also where Portable Stimulus (Accellera PSS) appears: one abstract scenario model from which a tool generates both UVM sequences for block level and C tests for the embedded CPU at SoC level, so a scenario written once runs at both levels.

    Python and cocotb next to UVM

    cocotb runs testbenches written in Python against a standard HDL simulator (open-source ones such as Icarus and Verilator, and the commercial ones), driving and sampling signals through the simulator's VPI/VHPI interface with coroutines. Teams use it because Python is quick to write and its libraries (numpy, scipy) are often where the reference algorithm already exists. pyuvm implements UVM's classes in Python on top of cocotb for teams that want the structure without SystemVerilog. The listing below shows the shape of a cocotb test for the DMA's stream path.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # not-run: needs cocotb and a simulator
    import cocotb
    from cocotb.clock import Clock
    from cocotb.triggers import RisingEdge
    
    @cocotb.test()
    async def xor_path(dut):
        cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
        key, word = 0xA5A50F0F, 0x12345678
        # ... program KEY over APB and start a one-word job, then:
        while not (dut.m_tvalid.value and dut.m_tready.value):
            await RisingEdge(dut.clk)
        assert int(dut.m_tdata.value) == word ^ key, "output word mismatch"

    In real companies, cocotb and UVM usually coexist by block, not inside one bench. Small blocks, algorithm-heavy datapaths and FPGA teams often use cocotb; blocks that need reusable VIP, a register model, commercial coverage closure or integration into a UVM SoC environment stay in UVM. What crosses between them is models: the same C or Python reference used by both benches. Mixing Python components into a running UVM environment is possible through DPI and an embedded interpreter, but it is rare and fragile, so treat it as a last resort.

    Situation

    A good fit

    Reusable protocol VIP, register model, SoC integration

    UVM in SystemVerilog

    Bit-exact algorithm already written in C/C++

    UVM with the model called through DPI

    Architecture model in SystemC already exists

    UVM with a TLM bridge, or the model as the reference

    SoC scenarios that real software will perform

    C tests on the CPU, with a UVM environment around them (or PSS for both levels)

    Small block, fast bring-up, Python-native team or models

    cocotb (or pyuvm)

    What breaks: the model that was right and the bench that was wrong

    A team called a C++ image-filter model through DPI and saw rare mismatches only in long regressions. The DUT was blamed for two weeks. The cause was on the border: the SystemVerilog side passed an open array to C, and the C++ model kept the pointer to use on the next call. The DPI standard says such a pointer is valid only for the duration of the call; the simulator later reused that memory, so the model sometimes read the next frame's pixels. The fix was to copy the data inside the call. The general rule: anything that crosses DPI (arrays, strings, handles) belongs to the side that created it, for as long as that side says, and a model that wants to keep data must copy it.

    Interview

    "The architecture team gives you a C++ model. How do you use it in your UVM bench?"

    A weak answer: "I'd rewrite it in SystemVerilog so it fits the bench."

    A strong answer: "I call it, through DPI, inside a predictor component so the rest of the bench sees ordinary transactions. The C++ side gets extern "C" wrappers, a chandle per instance if it has state, and copies of any array data it keeps. I compile it into the simulation or a shared library, and I check the wrapper against a few directed cases before trusting it. I agree a version and an owner with the architects, because a model change is a spec change. If it needs simulated time, I export SV tasks rather than letting C spin."

    Follow-ups: What does `context` on a DPI import mean? (the C function may call exported SV functions or tasks). When would you pick a SystemC bridge instead of DPI? (when the model is already TLM-2.0 SystemC with sockets, or when it must run as a timed component with its own processes).

    Check yourself A C model called through DPI needs to wait 50 clock cycles in the middle of a computation, and someone writes a busy loop in C that polls a signal. What happens, and what should they do?

    The simulator is blocked inside the C call, so simulated time never advances and the signal never changes: the run hangs. The C side must hand control back to the simulator: export an SV task that waits the cycles, import the C routine as a context task, and call the exported task from C. Better still, restructure the model so SystemVerilog calls it once per step.

    Check yourself Your SoC test is a C program on the embedded CPU. It finishes, but the UVM test hangs until the timeout. What link is probably missing?

    The end of the C program must reach UVM through the mailbox: the program writes a pass or fail code to the reserved address, and a UVM monitor on that bus decodes it and drops the test's objection (or reports a failure). If the monitor never sees the write (wrong address, a cache holding the write, or no monitor at all), the objection stays raised and the run times out.

    Check yourself A block team wants to switch from UVM to cocotb. Their block uses the shared APB VIP and the SoC team reuses their environment. What do you advise?

    Keep the block-level UVM environment, because other teams reuse it and it uses shared VIP and a register model that cocotb would have to duplicate. If the attraction is Python reference models, share those with the UVM bench through DPI or a C export. cocotb is a better fit for a new standalone block or a Python-native team, not for a block whose environment is part of a larger UVM system.

    Next: SystemC, C and Python are all still digital. Mixed-Signal Verification with UVM deals with the parts of the chip whose values are voltages.

    Related topics

    • Up: Safety, Acceleration and Models

    • DPI-C reference models

    • Sockets and the generic payload

    Previous

    243 / 280

    Next: Mixed-Signal Verification with UVM

    Up next

    Up nextMixed-Signal Verification with UVM

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Functional Safety: Fault Injection and Diagnostic Coverage