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
    • TLM 1: Ports, Exports and Analysis
    • TLM 2.0 Sockets
      • Initiator and Target Sockets
      • Blocking Transport
      • Non-Blocking Transport
      • Temporal Decoupling
      • TLM 2.0 Generic Payload: Extensions and Status
  • 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
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹TLM 2.0 Sockets
  1. Topics
  2. UVM Verification
  3. Transaction-Level Modeling (TLM)
  4. TLM 2.0 Sockets
  5. Temporal Decoupling

Part 4 · TLM & Analysis · Intermediate

Temporal Decoupling

Running an initiator ahead of simulation time with a local offset and a quantum, loosely versus approximately timed models, why UVM-SV has no quantum keeper, and when TLM 2.0 fits better than TLM 1.

On this page

    A firmware team boots software on a virtual platform that includes your DMA's TLM model. Booting takes four hours. Profiling shows the simulator spends most of its time switching between processes: every 10 ns register access makes the initiator wait, which wakes the scheduler, which runs everything else, thousands of times per simulated microsecond. The model's timing is correct; it is just being honoured far too often. How do TLM 2.0 models go fast without lying about time?

    The idea: keep a local clock, synchronize only now and then

    Temporal decoupling lets an initiator run ahead of simulation time. Instead of waiting after every transfer, it adds each annotated delay to a local time offset and keeps going, with $time standing still. Only when the offset reaches a quantum does it synchronize: wait out the whole offset at once and reset it. Between synchronizations, the initiator's local time is $time + offset. It is like a traveller who keeps a running total of expenses on paper and settles with the bank once a week, instead of walking to the bank after every coffee.

    The quantum is the accuracy dial. A small quantum keeps every process close to real simulation time; a large one runs fast but lets one initiator get far ahead of another, which can hide ordering bugs between them. In SystemC TLM, a global quantum and a tlm_quantumkeeper utility manage this for every initiator. UVM-SV has neither. A decoupled UVM initiator keeps its own offset and its own quantum, as the model below does.

    The picture

    Sequence diagram: the initiator makes three transfers, each adding 10 ns to its local offset while simulation time stays at 0; when the offset passes the 25 ns quantum it waits 30 ns once and resets the offset.
    Three annotated transfers cost no simulation time; one synchronization spends all of it. The scheduler wakes once instead of three times.

    Build it: an initiator that keeps its own offset

    The model below is plain SystemVerilog, not the UVM library, so it can run anywhere. It shows the mechanism a decoupled UVM initiator implements around b_transport: accumulate, compare with the quantum, synchronize once.

    design.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
    class decoupled_init;
      time local_off;
      time quantum;
      int unsigned syncs;
      function new(time q);
        quantum = q;
        local_off = 0;
        syncs = 0;
      endfunction
      // After a transfer that annotated ann: add it to the offset, report whether to sync.
      function bit xfer(time ann);
        local_off = local_off + ann;
        return (local_off >= quantum);
      endfunction
      // Synchronize: wait out the whole offset at once, then reset it.
      task do_sync();
        syncs++;
        #(local_off);
        local_off = 0;
      endtask
    endclass
    
    class q_mem;
      bit [31:0] mem[bit [31:0]];
      function void write(bit [31:0] a, bit [31:0] d);
        mem[a] = d;
      endfunction
      function bit [31:0] read(bit [31:0] a);
        if (mem.exists(a) != 0) return mem[a];
        return '0;
      endfunction
    endclass
    

    In a UVM initiator, the same logic wraps the socket call. Pass the current offset in as the delay, so an annotating target knows how far ahead the initiator already is; read back the new offset; and wait only when it reaches the quantum, or when the initiator must interact with something in real simulation time, such as waiting for an RTL interrupt:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // snippet: a decoupled initiator around b_transport
    time quantum = 1us;
    time offset  = 0;                         // local time = $time + offset
    
    task xfer(uvm_tlm_generic_payload gp);
      uvm_tlm_time delay = new("delay");
      delay.incr(offset, 1ns);                // tell the target how far ahead we are
      isock.b_transport(gp, delay);           // an annotating target adds its latency
      offset = delay.get_realtime(1ns);       // our new local offset
      if (offset >= quantum) sync();
    endtask
    
    task sync();                              // also call before touching real-time signals
      #(offset);
      offset = 0;
    endtask

    Everything a quantum keeper would do in SystemC is in those few lines: the offset, the comparison and the synchronization. What UVM cannot give you is a global quantum shared by every initiator; if several decoupled initiators must stay within the same window, they read one quantum from a shared configuration object.

    Watch it run

    Diagram
    PASS: 3 transfers caused 1 quantum sync and all readbacks matched
    • $time is still 0 after three transfers: none of them consumed simulation time.

    • The third transfer pushed the offset to 30 ns, past the 25 ns quantum, so exactly one synchronization was needed.

    • After the single do_sync, $time is 30: the whole offset was spent at once. The data was correct throughout, because decoupling changes when time is accounted, not what the transfers do.

    Loosely timed, approximately timed, or TLM 1?

    Style

    Interface

    Timing points per transfer

    Use it for

    Loosely timed (LT)

    b_transport, usually with decoupling

    Two: call and return, plus the annotation

    Virtual platforms and software bring-up, where speed matters most

    Approximately timed (AT)

    nb_transport phases

    Up to four, one per phase

    Performance studies that must show pipelining and arbitration

    TLM 1

    put, get, transport, analysis

    None built in

    Custom transaction types, flow control, and broadcast inside a UVM bench

    Many platforms mix the two: loosely timed for the processor and memory path that software exercises constantly, approximately timed only for the interconnect or the block under study. In SystemC one target socket carries both interfaces, so a model can serve b_transport and nb_transport_fw on the same socket; in UVM-SV the blocking and non-blocking sockets are separate classes, so such a model needs two sockets or an adapter. For DV work, TLM 2.0 mostly appears at the boundary with SystemC: a reference model or virtual platform connected through a co-simulation layer such as UVM Connect or UVM-SystemC. Inside a UVM bench, analysis ports and TLM 1 FIFOs remain the normal plumbing.

    What breaks: the initiator that got too far ahead

    A virtual platform ran two decoupled initiators, a CPU model and the DMA model, each with a 1 µs quantum. The firmware started a DMA job and then polled a status register. In the platform, the CPU's polling loop could run a full microsecond of local time ahead of the DMA's progress before either synchronized, so the CPU saw busy for far longer than on silicon, and a timeout in the firmware, tuned on the platform, was much too generous for the chip. Worse, a race in which the firmware cleared DONE just as the DMA set it never showed up on the platform, because the two models never interleaved finely enough. Reducing the quantum for that phase of the boot, and synchronizing explicitly before every status poll, exposed the race. A large quantum is a speed setting, never a correctness guarantee.

    Interview

    "What is temporal decoupling, and what does UVM give you for it?"

    A weak answer: "it lets TLM models run without a clock."

    A strong answer: "An initiator runs ahead of simulation time, adding each annotated delay to a local offset instead of waiting, and synchronizes only when the offset reaches a quantum, so the scheduler wakes far less often. The quantum trades speed for accuracy: too large and initiators drift apart and races disappear. SystemC provides a global quantum and tlm_quantumkeeper; UVM-SV provides only the delay argument, so a decoupled UVM initiator keeps its own offset and quantum."

    Expect the follow-ups: When must a decoupled initiator synchronize early? (before interacting with anything in real simulation time) and LT or AT for a performance model? (AT, if pipelining matters).

    Check yourself The quantum is 25 ns and three transfers annotate 10 ns each. When does the initiator synchronize, and how far does $time move?

    After the third transfer, when the offset reaches 30 ns and passes the quantum. $time then moves by the full 30 ns in one wait, and the offset resets to 0.

    Check yourself A decoupled initiator must wait for the DUT's interrupt line before continuing. What must it do first?

    Synchronize: wait out its current local offset so that simulation time catches up with its local time. Otherwise it would sample the interrupt at a moment that is earlier than its own notion of now.

    Check yourself Halving the quantum made a virtual-platform boot 30% slower and exposed a firmware race. Which change would you keep for the nightly software regression, and which for the DV sign-off runs?

    Keep the large quantum for software regressions, where throughput matters and the software has already been checked against finer timing. Use the small quantum, or explicit synchronization before status polls, for runs whose purpose is to find interaction bugs between initiators. The quantum is a per-purpose setting, not a single correct value.

    One more habit makes decoupled models safe to debug: log the local time, $time + offset, rather than $time. Messages stamped with simulation time alone show three transfers happening at the same instant, which reads like a bug when it is only decoupling at work.

    Next: every transfer in this topic carried a generic payload. Generic payload: extensions and status looks inside it: every field, every status code, and how to carry data the standard fields do not have.

    Related topics

    • Blocking transport

    • Non-blocking transport

    • Generic payload: extensions and status

    • Mailbox communication

    Previous

    71 / 280

    Next: TLM 2.0 Generic Payload: Extensions and Status

    Up next

    Up nextTLM 2.0 Generic Payload: Extensions and Status

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Initiator and Target Sockets
    • Blocking Transport