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. Emulation and FPGA Prototyping: When the Simulator Is Too Slow

Part 12 · Industry Practice · Intermediate

Emulation and FPGA Prototyping: When the Simulator Is Too Slow

Speed ranges, split HVL/HDL testbenches, transaction-level links (SCE-MI), what UVM code must change for the box, hybrid emulation and trigger-based debug.

On this page

    The SoC team wants to boot the operating system on the RTL before tape-out. Someone does the arithmetic: the boot needs a few billion clock cycles, and the full-chip simulation runs at a few hundred cycles a second. That is months of wall-clock time for one boot. Meanwhile your UVM bench drives the chip's DMA through a driver full of @(posedge vif.clk) and the occasional #2ns. The project buys time on an emulator. What part of your bench can go into the box, and what has to change?

    The idea: move the clocked world into hardware, keep the thinking on the host

    An event-driven simulator evaluates the design one event at a time on a CPU, so it slows down as the design grows. An emulator maps the design onto special hardware (arrays of custom processors or of FPGAs, depending on the vendor) that evaluates the whole design every clock in parallel. An FPGA prototype goes further: the design is synthesized and placed onto FPGAs much like a real chip, and runs faster still, with less visibility. The speeds differ by orders of magnitude, and that is the reason all three exist:

    Platform

    Typical speed on a large SoC

    Visibility

    Turnaround after an RTL change

    Best for

    RTL simulation

    Hz to kHz

    Every signal, any time

    Minutes

    Block and subsystem DV, debug, coverage closure

    Emulation

    Hundreds of kHz to a few MHz

    Full, within capture windows

    Hours

    Full-chip tests, OS boot, HW/SW integration, long scenarios, power and performance runs

    FPGA prototype

    Several MHz to tens of MHz

    Limited: chosen probes

    Hours to a day; bring-up takes weeks

    Software development, real-world interfaces, demos

    The speeds are orders of magnitude, not benchmarks: real numbers depend on design size, the platform and, above all, on how often the box has to stop and talk to the host. That last point drives every change to the testbench. Think of a factory (the box) and a head office (the host). The factory is fast as long as it is not on the phone. A testbench that calls head office every clock for permission to wiggle a pin makes the factory as slow as the phone line.

    The picture: a split testbench

    Flowchart: host side with test, sequences, proxy driver, proxy monitor and scoreboard; box side with driver BFM, DUT, monitor BFM and clocks; the proxy driver calls the driver BFM and the monitor BFM calls the proxy monitor, once per transaction.
    The UVM classes stay on the host. Everything with a clock moves into the box. The two halves exchange whole transactions through function and task calls, never individual pin changes.

    This is the split HVL/HDL testbench (also called a dual-top or two-kingdom bench). The HDL side holds the DUT, the clock and reset generation, and BFMs (bus functional models): interfaces whose tasks turn one transaction into pin activity, written in a synthesizable style the emulator compiler accepts. In practice that rules out, on the HDL side, classes, randomization, # delays and wait on time, dynamic arrays and queues, and $display-driven logic; communication with the HVL side goes only through the vendor's transactor channel (SCE-MI pipes, or DPI function calls the compiler supports at the boundary). The HVL side holds every UVM class. A UVM driver becomes a thin proxy: it gets an item and calls a BFM task with its fields. A monitor becomes a proxy that receives completed transactions from a BFM call.

    The link between the halves is standardized by Accellera's SCE-MI (Standard Co-Emulation Modeling Interface). Its function-based use model reuses DPI-style calls in both directions; its pipe-based model streams transactions through buffers so the box does not stop for every one. Each vendor adds its own transactor libraries and compilers on top, but the rules for the HDL side are the same everywhere.

    Build it: a BFM and its proxy

    The model below is the pattern in miniature, in plain SystemVerilog so it runs anywhere. axis_bfm is the HDL side: one clocked task per beat and a monitor that pushes each completed beat out with a zero-time, one-way call. stream_proxy is what the UVM driver would be: it never touches a pin.

    axis_bfm.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
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    // A split testbench in miniature. The HDL side (interface + top) holds everything clocked;
    // the HVL side (package + class) is untimed and talks to it only through task and function calls.
    package hvl_pkg;
      int unsigned seen[$];
      // Inbound, one-way and zero-time: the HDL monitor calls this; it never blocks.
      function automatic void mon_write(input logic [31:0] w);
        seen.push_back(w);
      endfunction
    endpackage
    
    // HDL side: the BFM. Clocked tasks only (no # delays, no classes, no hierarchical references).
    interface axis_bfm (input logic clk);
      logic [31:0] tdata;
      logic        tvalid = 1'b0;
      logic        tready;
    
      // One whole beat per call: the call boundary is a transaction, not a pin wiggle.
      task automatic send(input logic [31:0] w);
        tdata  <= w;
        tvalid <= 1'b1;
        do @(posedge clk); while (!tready);
        tvalid <= 1'b0;
      endtask
    
      // The monitor lives on the HDL side too, and pushes finished transactions out.
      always @(posedge clk) if (tvalid && tready) hvl_pkg::mon_write(tdata);
    endinterface
    
    // HVL side: the proxy a UVM driver would be. It only calls the BFM's task.
    class stream_proxy;
      virtual axis_bfm vif;
      function new(virtual axis_bfm vif);
        this.vif = vif;
      endfunction
      task send_pkt(int unsigned n, logic [31:0] base);
        for (int unsigned i = 0; i < n; i++) vif.send(base + i);
      endtask
    endclass
    
    module top;
      logic clk = 1'b0;
      always #5 clk = ~clk;              // in a box, the emulator's clock generator replaces this line
      axis_bfm bfm (clk);
    
      // A sink that stalls every third cycle, so the BFM must wait on TREADY.
      int unsigned cyc = 0;
      always @(posedge clk) cyc <= cyc + 1;
      assign bfm.tready = (cyc % 3 != 2);
    
      initial begin
        stream_proxy p = new(bfm);
        p.send_pkt(5, 32'hC0DE_0000);
        @(posedge clk);
        $display("HVL saw %0d beats, first %08h, last %08h", hvl_pkg::seen.size(),
                 hvl_pkg::seen[0], hvl_pkg::seen[hvl_pkg::seen.size() - 1]);
        if (hvl_pkg::seen.size() == 5 && hvl_pkg::seen[4] == 32'hC0DE_0004) $display("PASS");
        else $display("FAIL");
        $finish;
      end
    endmodule
    Diagram
    HVL saw 5 beats, first c0de0000, last c0de0004
    PASS

    Five beats arrive in order even though the sink stalls every third cycle; the proxy never knew about the stall. That is the property you want: all timing knowledge lives in the BFM. The course bench's agents already lean this way, and Interface abstraction shows the abstract-BFM variant (a class handle instead of a virtual interface) that some emulation flows prefer.

    What UVM code has to change

    In a simulation-only bench

    In an emulation-ready bench

    Why

    Driver wiggles pins through vif every clock

    Driver calls one BFM task per transaction

    Each host call stops the box; fewer, bigger calls keep it fast

    #2ns delays in drivers and sequences

    Waits counted in clocks, inside the BFM

    The box has no notion of absolute delay except its clocks

    Clocking blocks and #1step sampling in classes

    Sampling on clock edges inside the BFM

    The compiler maps clocked logic; the class side is untimed

    Hierarchical references (top.dut.u_core.state)

    Signals brought out through ports, interfaces or the vendor's probe mechanism

    Cross-boundary references are not synthesizable

    Monitor builds an item per beat on the host

    BFM packs the beat and sends it in one one-way call

    Inbound calls should be zero-time and non-blocking

    Clock and reset from always #5

    Clock generation inside the HDL top, in the form the tool supports

    Clocks belong to the box

    The checking and stimulus layers, which are most of the bench, do not change. Sequences, the scoreboard, the coverage model and the register layer all work on transactions, so a well-layered bench moves with only its drivers and monitors rewritten. Teams that know emulation is coming write the split from day one, so the same bench runs in simulation and in the box.

    Hybrid emulation and debug

    Even an emulator is slow for a CPU booting an operating system. Hybrid emulation keeps the processor subsystem as a fast instruction-level model in a virtual platform on the host (often SystemC TLM) and puts the rest of the SoC in the box, linked by transactors. Software boots on the fast model, then drives the real RTL of the blocks under test. The trade is accuracy: the CPU is no longer cycle-accurate RTL, so CPU-timing bugs need a pure-RTL run later.

    Debug changes too. A box runs too fast to record everything forever. You set triggers (a signal value, a transaction, an assertion firing) and the platform captures a window of full visibility around it, which you then open in a waveform viewer. Emulators keep that capture deep and flexible; FPGA prototypes see only the probes compiled in, so a missing probe means a new compile. A good habit is to reproduce a box failure as a short simulation from a saved state, where every signal is visible.

    What breaks: the call that ate the speed-up

    A team moved their bench to an emulator and measured a speed-up of almost nothing. The profile showed the box idle most of the time, waiting on the host. The culprit was a monitor that called back to the host on every clock edge, valid or not, to let a class decide whether a beat had happened, and a driver that returned to the host between the address and data phases of every access. Each call forced the box and the host to synchronize. Moving the valid check and the whole two-phase access into the BFM, so the host saw one call per completed transaction, gave them the expected speed. The rule to remember: count host calls per DUT clock; the closer to zero, the faster the box.

    Interview

    "Your UVM bench has to run on an emulator. What do you change?"

    A weak answer: "The emulator runs SystemVerilog, so it should just work, maybe a bit of cleanup."

    A strong answer: "I split it. The DUT, clocks, reset and BFMs go to the HDL top, written synthesizably: clocked tasks with no # delays, no classes and no hierarchical references. The UVM side keeps sequences, scoreboards, coverage and the register model, and its drivers and monitors become proxies that call BFM tasks once per transaction; inbound calls are one-way and zero-time. Then I measure host calls per DUT clock, because synchronization, not the DUT, decides the speed. Done well, the same split bench runs in simulation too, so tests are developed in simulation and moved to the box for long runs."

    Follow-ups: When would you pick an FPGA prototype over emulation? (software development at higher speed, real interfaces, when visibility and fast recompiles matter less). What is hybrid emulation for? (booting software on a fast CPU model while the rest of the SoC runs as RTL in the box).

    Check yourself Your driver waits #3ns after each APB access to meet a hold time on a model. It works in simulation. What happens when you try to compile the bench for emulation, and what is the fix?

    The delay is in the HVL class, which is untimed, or in an interface the emulator must synthesize; either way absolute delays are not supported in the box. The fix is to express the wait in clocks inside the BFM (repeat (n) @(posedge clk)), or to remove it if it only existed to work around a race, which clocked BFMs avoid.

    Check yourself A monitor in your split bench calls a host function every clock to report the bus state, and the class decides whether a transfer completed. Functionally it is correct. What is wrong with it?

    Every call synchronizes the box with the host, so the emulator runs at host-call speed instead of hardware speed. Move the completion detection (valid and ready, or PSEL/PENABLE/PREADY) into the BFM and call the host once per completed transfer with the packed fields.

    Check yourself Which parts of the course DMA bench would change if it moved to an emulator, and which would not?

    The drivers and monitors of the APB, stream and interrupt agents would become proxies plus BFMs in the interfaces, and the top would generate clocks and reset in the tool's supported form. The sequences, dma_scoreboard, the predictor, the coverage model, the register model and the tests stay as they are, because they only see transactions.

    Next: the box runs RTL, but the host side often runs models written by other teams in other languages. Multi-Language Benches shows how SystemC, C++ and Python plug into a UVM flow.

    Related topics

    • Up: Safety, Acceleration and Models

    • Interface abstraction

    • TLM-2.0 temporal decoupling

    Previous

    242 / 280

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

    Up next

    Up nextMulti-Language Benches: SystemC, C/C++ and Python

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Mixed-Signal Verification with UVM