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
      • Who Calls, Who Implements: Ports, Exports and Imps
      • connect() Rules: Which Way Each Link Points
      • put, get, peek and transport: Flow-Controlled TLM
      • Analysis Broadcast: One write(), Many Listeners
      • TLM1 Channels and Port Cardinality
      • Analysis Connectivity Patterns: imp_decl vs FIFOs
      • Connection Triage and FIFO Sizing: A Debug Playbook
      • The TLM Lessons' Bench File: Full Source
    • TLM 2.0 Sockets
  • 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 1: Ports, Exports and Analysis
  1. Topics
  2. UVM Verification
  3. Transaction-Level Modeling (TLM)
  4. TLM 1: Ports, Exports and Analysis
  5. Analysis Broadcast: One write(), Many Listeners

Part 4 · TLM & Analysis · Intermediate

Analysis Broadcast: One write(), Many Listeners

How an analysis port reaches every subscriber in zero time, the order it calls them in, fan-out and fan-in, the DMA scoreboard's three uvm_analysis_imp_decl imps, and the factory override whose write methods never ran.

On this page

    The interviewer draws a monitor with two subscribers, a coverage collector and a scoreboard, and asks: in what order do they receive the write? "In the order you connected them," you say. Here is the stream monitor of the DMA bench with two subscribers: the scoreboard sb connected first, and a second, FIFO-based scoreboard fsb (built in imp_decl vs FIFOs; for now it is just another subscriber) connected after it. The library's own list of where the port's writes go:

    Diagram
    Resolved implementation list:
      0: uvm_test_top.env.fsb.in_fifo.analysis_export (uvm_analysis_imp)
      1: uvm_test_top.env.sb.in_export (uvm_analysis_imp_in)

    fsb is called first, although it was connected second. So what does decide the order, what else does a single write() promise, and how does the bench's scoreboard take three streams at once?

    The idea: a radio broadcast, not a phone call

    An analysis port is a publisher. Its write(t) is a void function: it hands the same transaction handle to every connected imp, one after another, in the caller's own thread, in zero simulated time, and returns. There is no return value, no acknowledgement and no backpressure. A subscriber is any component that owns an analysis imp and implements write(). It is a radio broadcast: the station transmits whether ten radios are on or none, and no radio can ask it to slow down.

    That design is deliberate. A monitor reports what the DUT did; it must never wait for a checker, or the testbench would stop sampling the bus. The price is that subscribers must be quick inside write(), and must not change the shared object (each subscriber gets the same handle, which is also why a monitor creates a fresh object per transaction, as in your first testbench).

    Note

    Two rules, stated once for the course. Order: an analysis port calls its subscribers' write() in alphabetical order of the subscribers' full names (the imp list is an associative array keyed by name). Do not depend on the order. Fan-out and fan-in: an analysis export or imp can have many ports connected to it, and an analysis port can fan out to many imps. A second connection never overwrites the first.

    The order rule explains the opening log: uvm_test_top.env.fsb... sorts before uvm_test_top.env.sb.... Rename sb to a_sb and the order flips. A checker that only works if the coverage collector has already seen the item, or has not yet, is a checker that breaks when someone renames a component.

    The picture: three monitors, one scoreboard, three methods

    Three monitor ports each connect to one of three scoreboard imps; each imp calls write_apb, write_in or write_out on the scoreboard.
    Each imp_decl suffix creates a new imp class whose write() calls a differently named method on the scoreboard. All three calls run in the monitors' threads, at the moment each monitor samples.

    Build it: the bench's scoreboard, line by line

    Three imp classes from one macro

    A component can implement only one method called write(). To receive three streams, the bench declares three imp classes with ` uvm_analysis_imp_decl ``. Each call of the macro generates a class like this one (from the library's macro, trimmed):

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // snippet: what `uvm_analysis_imp_decl(_out) generates
    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
      // constructor as in uvm_analysis_imp
      function void write(input T t);
        m_imp.write_out(t);            // the suffix becomes part of the method name
      endfunction
    endclass

    The macros sit at package level, outside the class, because they declare classes. Use them once per suffix per package: two packages may each declare _in, but one package cannot declare it twice.

    The scoreboard

    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    // snippet: from the course DMA bench
    // Predicts every output word from the input word and the KEY register the
    // DUT was programmed with, then compares in order. The write_* methods are
    // virtual so a derived scoreboard (a factory override) can extend them.
    class dma_scoreboard extends uvm_scoreboard;
      `uvm_component_utils(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;
    
      bit [31:0]   key;
      bit [31:0]   expected[$];
      int unsigned n_match, n_mismatch;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
        apb_export = new("apb_export", this);
        in_export  = new("in_export", this);
        out_export = new("out_export", this);
      endfunction
    
      virtual function void write_apb(apb_item t);
        if (t.write && !t.slverr && t.addr == KEY) key = t.data;
      endfunction
    
      virtual function void write_in(axis_item t);
        expected.push_back(t.data ^ key);
      endfunction
    
      virtual function void write_out(axis_item t);
        bit [31:0] exp;
        if (expected.size() == 0) begin
          `uvm_error("SB_UNEXP", $sformatf("output word %08h with nothing expected", t.data))
          n_mismatch++;
          return;
        end
        exp = expected.pop_front();
        if (t.data !== exp) begin
          `uvm_error("SB_MISMATCH", $sformatf("expected %08h, got %08h", exp, t.data))
          n_mismatch++;
        end else begin
          n_match++;
        end
      endfunction
    
      function void check_phase(uvm_phase phase);
        if (expected.size() != 0)
          `uvm_error("SB_LEFTOVER", $sformatf("%0d expected word(s) never came out", expected.size()))
        if (n_match == 0)
          `uvm_error("SB_EMPTY", "no words were compared: the test checked nothing")
      endfunction
    
      function void report_phase(uvm_phase phase);
        `uvm_info("SB", $sformatf("%0d words matched, %0d mismatched", n_match, n_mismatch), UVM_LOW)
      endfunction
    endclass
    • Each imp is declared with the scoreboard class as its IMP parameter and constructed with this.

    • write_apb tracks the KEY register from observed writes; write_in turns every observed input word into a prediction; write_out compares. All three finish in zero time and never block.

    • The three methods are virtual. That one word is war story 2, below.

    Order across streams

    Within one stream, items arrive in the order the monitor wrote them. Across streams there is no promise: if the APB monitor and the input monitor both sample on the same clock edge, which write() runs first depends on the simulator's scheduling of the two monitor threads. The DMA scoreboard is safe because the DUT gives it slack: KEY is written several clocks before any data, and an output word always appears at least one clock after its input word. A scoreboard for a DUT without that slack must not assume an order between streams; imp_decl vs FIFOs discusses the options.

    Fan-in: many ports into one imp

    Two APB agents in a subsystem env could both connect to sb.apb_export; the imp accepts any number of ports and each write simply lands in write_apb. What the scoreboard can then no longer tell is which agent the item came from. If that matters, give each source its own suffix, or carry the source in the item.

    Watch it run

    The opening list came from dma_fifo_sb_test in tlm_tb.sv: the smoke test with an env that adds a second, FIFO-based scoreboard fsb and connects every monitor port to both. The full debug_connected_to() output, and that second scoreboard, are in imp_decl vs FIFOs. Both scoreboards received every word, because a second connection is added to the port's list rather than replacing the first:

    Diagram
    UVM_INFO tlm_tb.sv(332) @ 655000: uvm_test_top.env.fsb [FSB] 8 words matched, 0 mismatched
    UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatched

    What breaks: the override whose methods never ran (war story 2)

    This bug happened while the bench was being built. dma_drain_test overrides the scoreboard 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 printed sb dma_scoreboard_eot, so the override had worked. Yet the owed count stayed at zero and the run ended with a word still inside the DMA. The new write_apb and write_out never ran.

    At that time the base class declared its methods without virtual:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    // bad-example: the base scoreboard as first written
    class dma_scoreboard extends uvm_scoreboard;
      uvm_analysis_imp_out #(axis_item, dma_scoreboard) out_export;
      // ...
      function void write_out(axis_item t);        // not virtual
        // compare
      endfunction
    endclass

    Look at the imp class again. m_imp is declared as IMP, and IMP is dma_scoreboard, the class named in the declaration. The factory built a dma_scoreboard_eot, and m_imp pointed at it, but m_imp.write_out(t) was resolved through a dma_scoreboard handle. For a non-virtual method, SystemVerilog picks the method from the handle's type, so the base write_out ran every time. The fix is the one in the bench: declare every write_* method virtual in any scoreboard that may be extended or overridden, and call super.write_out(t) in the derived version.

    The factory side of this story, and why an override replaces the object, not the calls, is told in Override timing, safety and debug.

    Interview

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

    A weak answer: "it has three analysis imps." Three plain uvm_analysis_imps would all call the same write().

    A strong answer: "` uvm_analysis_imp_decl(_apb) `` generates a new imp class whose write() calls write_apb() on the class named in its IMP parameter. I declare one per stream, construct each with this, and connect each monitor's port to its imp. The writes run in the monitors' threads in zero time, so the methods must not block, and I make them virtual so a derived scoreboard can extend them; the imp calls through a base-type handle. Within a stream order is preserved; across streams it is not, and I never depend on the order between subscribers on one port, which is alphabetical by full name."

    Expect the follow-ups: What if a write method needs to wait for something? (move the work to a thread, for example behind an analysis FIFO) and Can an analysis export fan out? (yes, to any number of imps).

    Check yourself A coverage collector on the input monitor's port normalizes t.data in place inside its write(). The scoreboard is on the same port. Does the scoreboard see the original or the normalized value?

    It depends on the alphabetical order of the two subscribers' full names, because both get the same handle and are called one after the other. That makes the scoreboard's result depend on component names, which is the bug. A subscriber must never modify the received object; it clones first if it needs a changed copy.

    Check yourself The derived scoreboard declares virtual function void write_out(axis_item t), but the base class's write_out is not virtual. Does the override work?

    No. Virtual dispatch is decided by the method in the handle's class. The imp calls through a dma_scoreboard handle, and in that class write_out is not virtual, so the base version runs. virtual must be on the base declaration.

    Check yourself Two monitors connect to the same sb.apb_export. Is the second connection an error, and does it replace the first?

    Neither. An imp accepts any number of ports, and each connection is added, never replaced. Both monitors' items arrive in write_apb, which cannot tell them apart unless the item carries its source or each monitor gets its own suffix.

    Next: every port in this lesson had a home. In TLM1 channels and port cardinality you will see which ports fail the run when they are left unconnected, and the ready-made FIFOs and channels that sit between producers and consumers.

    Related topics

    • imp_decl vs FIFOs

    • Override timing, safety and debug

    • Scoreboard architecture

    • Your first UVM testbench

    Previous

    63 / 280

    Next: TLM1 Channels and Port Cardinality

    Up next

    Up nextTLM1 Channels and Port Cardinality

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Who Calls, Who Implements: Ports, Exports and Imps
    • connect() Rules: Which Way Each Link Points
    • Analysis Connectivity Patterns: imp_decl vs FIFOs