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
    • Scoreboard Architecture: Expected vs Actual
      • Expected vs Actual: The Two-Stream Model
      • Three Streams, One Scoreboard: imp_decl and Virtual write_*
      • Env Wiring: From Block Scoreboard to End-to-End Checker
      • In-Order Matching: One Queue, Four Outcomes
      • Out-of-Order Matching: A Queue per ID
      • Debugging a Scoreboard: Silence, Shapes and Cascades
      • Scoreboards for Messy Traffic: Drops, Reorders, Splits and Merges
      • End-of-Test Checks: What Must Be Empty Before a Test Passes
    • Reference Models: Predictors, DPI-C and Built-in Comparators
    • Coverage & Closure
  • 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

‹Scoreboard Architecture: Expected vs Actual
  1. Topics
  2. UVM Verification
  3. Checking & Coverage in UVM
  4. Scoreboard Architecture: Expected vs Actual
  5. Three Streams, One Scoreboard: imp_decl and Virtual write_*

Part 8 · Checking & Coverage · Intermediate

Three Streams, One Scoreboard: imp_decl and Virtual write_*

One scoreboard, three analysis imps: what uvm_analysis_imp_decl generates, why every write_* method must be virtual, and when analysis FIFOs fit better.

On this page

    The topology table at the start of the run says sb dma_scoreboard_eot. The factory override worked. The test is dma_drain_test, which drops its objection early and relies on that scoreboard to hold the run phase open until every owed word is out. Yet the run ends early, and a debug message you put in the derived write_out never prints. Not once. The object is the right class; its methods are not being called. How can a component receive traffic and still skip the code you wrote for it?

    The idea: one mailbox per sender, and a forwarding rule you control

    A component that subscribes to an analysis port must provide a function called write. A scoreboard listens to three monitors, so it needs three different write methods, one per stream, and SystemVerilog does not allow three methods with the same name in one class. The analysis imp is the fix: a small object that owns the write the port calls, and forwards it to a method of the component. The macro uvm_analysis_imp_decl(_SUFFIX) declares an imp class that forwards to write_SUFFIX instead of write.

    Think of an office with three mail slots in the door, labelled registers, input and output. The postman (the analysis port) only knows how to push letters through a slot. Behind each slot a clerk (the imp) hands the letter to the right desk. The forwarding rule is the whole trick, and it is also where the opening bug lives: the clerk was told the desk's name when the office was built, and if the desk is not marked as replaceable, the new occupant never gets the mail.

    The picture

    Sequence diagram: the output monitor calls write on the imp; the imp calls write_out through a handle typed as the base scoreboard; if write_out is not virtual, only the base version runs; if it is virtual, the derived version runs and calls super.
    The imp holds a handle typed as the class named in its declaration. Whether the derived class's method runs is decided by ordinary virtual dispatch.

    Build it

    The declarations and the imps

    The three macros sit at package scope, before the class, in the bench. Each one expands into a class declaration, so package or file scope is the portable convention; nested class declarations are legal SystemVerilog, but not every tool is happy with a macro-generated class inside another class.

    design.sv
    1
    2
    3
    4
    // snippet: from the course DMA bench
    `uvm_analysis_imp_decl(_apb)
    `uvm_analysis_imp_decl(_in)
    `uvm_analysis_imp_decl(_out)

    Inside dma_scoreboard, each imp is declared with two parameters, the item type and the class that owns the method, and constructed in new():

    design.sv
    1
    2
    3
    4
    5
    6
    // snippet: from dma_scoreboard
    uvm_analysis_imp_apb #(apb_item,  dma_scoreboard) apb_export;
    uvm_analysis_imp_in  #(axis_item, dma_scoreboard) in_export;
    uvm_analysis_imp_out #(axis_item, dma_scoreboard) out_export;
    ...
    apb_export = new("apb_export", this);   // 'this' becomes the imp's m_imp handle

    What the macro generates

    Stripped of plumbing, uvm_analysis_imp_decl(_out) produces this. The line to look at is the last one inside write:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // snippet: roughly what uvm_analysis_imp_decl(_out) expands to
    class uvm_analysis_imp_out #(type T = int, type IMP = int) extends uvm_port_base #(uvm_tlm_if_base #(T, T));
      local IMP m_imp;                        // set from the constructor's second argument
      function new(string name, IMP imp);
        super.new(name, imp, UVM_IMPLEMENTATION, 1, 1);
        m_imp = imp;
      endfunction
      function void write(input T t);
        m_imp.write_out(t);                   // a call through a handle of type IMP
      endfunction
    endclass

    IMP is dma_scoreboard, the class written in the declaration. When the factory builds a dma_scoreboard_eot in its place, m_imp points at the derived object, but the call m_imp.write_out(t) is made through a base-typed handle. SystemVerilog then calls the derived version only if the method is virtual. That is why every write_* in the bench is declared virtual function void.

    One imp per stream, not one imp for everything

    Several ports may connect to one imp, and an analysis port may feed many imps; a second connection never replaces the first. So wiring the input and output monitors to one plain write would deliver both streams, into the same method, with nothing to say which monitor sent which item. The suffix imps keep the streams apart by construction.

    Who owns the object you receive

    The bench's monitors create a fresh item for every transaction and never touch it after write. A subscriber that stores an item can then keep the handle. dma_scoreboard goes further and stores only a computed 32-bit value, so it is immune either way. A scoreboard that stores whole items from VIP you do not control, where a monitor might reuse one object, should copy before storing; that copy is defence in depth, not the norm.

    Note

    When one port feeds several subscribers, as in_agt.ap does in the checking env (scoreboard, predictor and coverage), the subscribers are called in alphabetical order of their full names. Never write a scoreboard that depends on that order.

    When analysis FIFOs fit better

    Suffix imps (write_*)

    One uvm_tlm_analysis_fifo per stream

    Where the compare runs

    Inside the write_* call, in zero time

    In a run_phase thread that calls blocking get()

    Can it wait?

    No: write is a function

    Yes: it can wait for the partner item, a clock, or a timeout

    Typical use

    Predict on input, compare on output, as in dma_scoreboard

    Pair items that arrive at different times, latency checks, built-in comparators

    Cost

    Least code

    A thread per pairing, and FIFOs to drain at the end

    The library's comparators are built on FIFOs; they are taught, with their traps, in Built-in comparators. The FIFO mechanics are in Analysis FIFOs and debug.

    Watch it run

    With virtual methods, dma_drain_test drops its objection while the last word is still inside the DMA, the derived scoreboard holds the phase open, and every word is compared:

    Diagram
    +UVM_TESTNAME=dma_drain_test
    UVM_INFO dma_tb.sv(411) @ 375000: uvm_test_top.env.sb [SB] 6 words matched, 0 mismatched
    UVM_ERROR :    0

    What breaks: the override whose methods never ran

    This happened while the bench was being built. The first version of dma_scoreboard declared its write_* methods without virtual. dma_drain_test overrode it with dma_scoreboard_eot, which extends write_apb to learn how many words each job owes and write_out to count them down. The topology table printed the derived type, so everyone assumed the override had worked.

    It had, for the object. But every imp called m_imp.write_apb(t) and m_imp.write_out(t) through a dma_scoreboard handle, so the base methods ran and the derived ones never did. The owed count stayed at zero, phase_ready_to_end saw nothing owed, and the extension the test depended on never happened. Nothing in the log said so: no error, no warning, just a scoreboard quietly behaving like its parent.

    The fix is one keyword per method: virtual. The rule to take away is wider than scoreboards: a factory override replaces the object; whether your new code runs still depends on virtual dispatch. Any method a derived class might extend, and every method an imp or a callback calls, should be virtual in the base class from day one. Making it virtual later means editing the base class, which is exactly what the override was meant to avoid.

    Interview

    "How does one scoreboard receive transactions from three monitors?"

    Weak: "You connect all three monitors to the scoreboard's analysis export."

    Strong: "With uvm_analysis_imp_decl suffixes at package scope: each generated imp forwards write to write_apb, write_in or write_out, so the streams arrive in separate methods. I declare those methods virtual, because the imp calls them through a handle of the base scoreboard type, so a factory-overridden scoreboard's versions would otherwise never run. If the compare has to wait for something, I use an analysis FIFO per stream and a run_phase thread instead."

    Follow-ups: What happens if two monitors connect to one plain imp? (both streams arrive in the same write; nothing is overwritten) and Should the scoreboard copy what it receives? (only if it stores items and the producer might reuse them; the course's monitors publish a fresh object).

    Check yourself A colleague adds a fourth stream, interrupts, by calling uvm_analysis_imp_decl(_irq) inside the scoreboard class body. It compiles on one simulator and fails on another. What do you tell them?

    Move the macro to package or file scope, next to the other three. The macro expands to a class declaration; a nested class is legal SystemVerilog, but tool support for a macro-generated class inside another class varies, and the package-scope form is the portable convention everyone reads.

    Check yourself A derived scoreboard overrides write_out and forgets to call super.write_out(t). The base methods are virtual. What happens to the compare?

    The derived method runs instead of the base one, so the base compare never happens: no pops from expected, no matches, no mismatches. The run ends with every prediction left over (SB_LEFTOVER) and SB_EMPTY. Virtual dispatch got the right method called; calling super is what keeps the parent's behaviour.

    Check yourself Why can dma_scoreboard keep its predictions safely even if a monitor reused one item object?

    It never stores an item. write_in computes t.data ^ key and pushes a 32-bit value, and write_out reads t.data immediately inside the call. Nothing refers to the item after write returns, so later changes to that object cannot reach the queue.

    Next: the scoreboard has three inputs; something must connect them. Env Wiring wires the block scoreboard and then an end-to-end checker across two chained DMAs.

    Related topics

    • Env wiring

    • imp_decl vs FIFOs

    • Factory overrides and debug

    Previous

    127 / 280

    Next: Env Wiring: From Block Scoreboard to End-to-End Checker

    Up next

    Up nextEnv Wiring: From Block Scoreboard to End-to-End Checker

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • In-Order Matching: One Queue, Four Outcomes
    • Out-of-Order Matching: A Queue per ID
    • Debugging a Scoreboard: Silence, Shapes and Cascades