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
    • What Is UVM?
      • Why UVM Exists: History and Goals
      • What UVM Standardizes (and What You Own)
      • Components vs Objects: Hierarchy, Lifetime, and Phases
      • Objects That Copy, Compare and Print
      • The Stimulus, Check, and Coverage Triangle
      • Your First UVM Testbench
      • The DMA Bench: Full Source, Part 1
      • The DMA Bench: Full Source, Part 2
      • The DMA Bench: Full Source, Part 3
    • Testbench Architecture and Environment Composition
  • 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
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹What Is UVM?
  1. Topics
  2. UVM Verification
  3. UVM Foundations
  4. What Is UVM?
  5. Your First UVM Testbench

Part 1 · Foundations · Intermediate

Your First UVM Testbench

Build, run and break a complete UVM testbench for the course's DMA: agents, a predicting scoreboard and a clean end of test.

On this page

    Your first week on a new DV team, someone hands you a regression log. Two hundred tests, all green. Then a designer asks a simple question: how do you know the DMA actually moved the right data? You open one passing test and find it programs the registers, waits, and ends. Nothing ever compared an output word against anything. The test was green because it never checked anything.

    This lesson builds the opposite: a complete, small UVM testbench that drives a real design, predicts what should come out, checks every word, and ends only when the work is done. You will read every part of it, run it, and then break it on purpose. Every later lesson in this course adds to this same bench, so the time you spend here pays off all the way to the senior chapters.

    The design you will verify

    The course uses one small design throughout: a stream DMA. Software programs it over APB (a simple register bus), then data words flow in on one AXI-Stream port and out on another. On the way through, each word is XORed with a key register, so the output is never just a copy of the input. When the job finishes, the DMA raises an interrupt.

    Offset

    Register

    Fields

    0x00

    CTRL

    bit 0 GO (write 1 to start), bit 1 IRQ_EN

    0x04

    STATUS

    bit 0 BUSY (read-only), bit 1 DONE and bit 2 ERR (write 1 to clear)

    0x08

    LEN

    bits 15:0, number of words to move

    0x0C

    KEY

    every word is XORed with this value

    0x10

    COUNT

    words moved so far (read-only)

    Three interfaces, three kinds of traffic: register accesses that wait for a ready signal, a data stream with valid/ready handshakes, and a level-sensitive interrupt. That mix is why the DMA is a good teaching design: almost every UVM topic, from reactive agents to the register layer, has a natural home in it.

    The idea: separate who drives, who watches and who judges

    A UVM testbench splits the work the way a well-run kitchen does. The test is the head chef deciding what is cooked today. Sequences are the recipes: they describe stimulus as transactions ("write 0xA5A5_0F0F to KEY") without caring about pins. A driver is the cook who turns each recipe step into wiggling signals at the right clock edges. A monitor is the person at the pass who watches every plate that leaves, without touching it, and reports what it saw. The scoreboard is the critic who knows what each plate should look like and complains when one is wrong.

    The reason for the split is reuse and trust. A monitor that never drives can be reused at chip level where nothing in the testbench is allowed to drive that bus. A scoreboard that only listens to monitors judges what really happened on the pins, not what the stimulus intended to happen. When those roles blur, you get the green-but-checking-nothing test from the opening.

    The picture

    Block diagram: a test containing an env with an active APB agent, an active input stream agent and a passive output stream agent, all monitors feeding one scoreboard.
    The base DMA bench inside dma_env, built by dma_smoke_test. Drivers push stimulus into the DUT; three monitors report what they saw; the scoreboard predicts each output word and compares.

    Read the arrows. Stimulus flows left to right through drivers. Observations flow from monitors into the scoreboard. No arrow goes from a sequence or driver to the scoreboard: the critic never asks the cook what was intended.

    Build it, one layer at a time

    Interfaces and clocking blocks: where the bench meets the pins

    Classes cannot hold wires, so each bus is bundled into a SystemVerilog interface and the classes get a virtual interface handle to it. Timing is fixed by two rules. Monitors sample through a clocking block, mon_cb, whose #1step input skew reads every signal just before the clock edge: exactly the value the DUT samples on that edge. Drivers wait on @(posedge clk) and drive with nonblocking assignments, so everything they change lands after the edge, the way a flip-flop output would. Together the two rules remove a whole family of races where the testbench and the design disagree about which value was on the bus.

    apb_if.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet: from the course DMA bench
    interface apb_if (input logic clk, input logic rst_n);
      logic [7:0]  paddr;
      logic        psel, penable, pwrite;
      logic [31:0] pwdata, prdata;
      logic        pready, pslverr;
    
      // Monitors sample through mon_cb: #1step reads each signal just before the clock
      // edge, which is exactly the value the DUT samples on that edge. Drivers drive with
      // nonblocking assignments right after @(posedge clk) (see apb_driver).
      clocking mon_cb @(posedge clk);
        default input #1step;
        input paddr, psel, penable, pwrite, pwdata, prdata, pready, pslverr;
      endclocking
    endinterface

    The transaction: what one APB access means

    A sequence item is the transaction a sequence hands to a driver. It holds the fields of one access and nothing about timing. rdata and slverr are not random: the driver fills them in on the way back. The uvm_object_utils macro registers the class with the factory, which is what later lets a test swap in an error-injecting item without editing this file.

    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
    // snippet: from the course DMA bench
    class apb_item extends uvm_sequence_item;
      rand bit [7:0]  addr;
      rand bit        write;
      rand bit [31:0] data;     // write data
      bit      [31:0] rdata;    // filled by the driver on reads
      bit             slverr;   // filled by the driver
      constraint word_aligned_c { addr[1:0] == 2'b00; }
    
      `uvm_object_utils(apb_item)
      function new(string name = "apb_item");
        super.new(name);
      endfunction
    
      function string convert2string();
        return write ? $sformatf("WR addr=%02h data=%08h%s", addr, data, slverr ? " SLVERR" : "")
                     : $sformatf("RD addr=%02h rdata=%08h%s", addr, rdata, slverr ? " SLVERR" : "");
      endfunction
    
      function void do_copy(uvm_object rhs);
        apb_item r;
        super.do_copy(rhs);
        $cast(r, rhs);
        addr = r.addr; write = r.write; data = r.data; rdata = r.rdata; slverr = r.slverr;
      endfunction
    endclass

    The driver: turning a transaction into an APB transfer

    The driver pulls one item at a time (get_next_item), drives it, and reports completion (item_done). APB has two phases: a setup clock with PSEL high and PENABLE low, then an access phase with PENABLE high that lasts until the slave raises PREADY. Read data is valid in the cycle PREADY is high, so that is where the driver captures it.

    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
    // snippet: from the course DMA bench
    class apb_driver extends uvm_driver #(apb_item);
      `uvm_component_utils(apb_driver)
      virtual apb_if vif;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
          `uvm_fatal("NOVIF", "apb_if not set for this driver")
      endfunction
    
      task run_phase(uvm_phase phase);
        vif.psel    <= 1'b0;
        vif.penable <= 1'b0;
        @(posedge vif.rst_n);
        forever begin
          seq_item_port.get_next_item(req);
          drive(req);
          seq_item_port.item_done();     // rdata travels back inside req itself
        end
      endtask
    
      // Setup phase: PSEL=1, PENABLE=0 for one clock. Access phase: PENABLE=1, held
      // until the slave answers PREADY=1; read data is valid in that cycle.
      // Every wait is on the same event, @(posedge vif.clk), and every drive is a
      // nonblocking assignment, so a value read right after the edge is the value
      // from before it: the one the DUT sampled.
      task drive(apb_item it);
        @(posedge vif.clk);
        vif.paddr   <= it.addr;
        vif.pwrite  <= it.write;
        vif.pwdata  <= it.data;
        vif.psel    <= 1'b1;
        vif.penable <= 1'b0;
        @(posedge vif.clk);
        vif.penable <= 1'b1;
        do @(posedge vif.clk); while (!vif.pready);
        it.rdata  = vif.prdata;
        it.slverr = vif.pslverr;
        vif.psel    <= 1'b0;
        vif.penable <= 1'b0;
      endtask
    endclass

    Common pitfall

    Notice item_done() takes no argument. The read data travels back inside the same req object the sequence created, so the sequence reads it after finish_item returns. Passing item_done(req) also queues a response object. If the sequence never calls get_response(), the 9th one overflows the default response queue of 8: UVM 1.2 reports an error, and the IEEE 1800.2 reference library drops it silently unless the report is switched on.

    The monitor: a fresh object for every transfer

    The monitor watches the bus and recognises a completed transfer on the clock edge where PSEL, PENABLE and PREADY are all high. For each one it creates a new item and broadcasts it on an analysis port, which can have any number of listeners. Creating a new object every time matters: subscribers often store what they receive, and a monitor that reuses one handle would quietly rewrite every stored copy on the next transfer.

    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
    // snippet: from the course DMA bench
    class apb_monitor extends uvm_monitor;
      `uvm_component_utils(apb_monitor)
      virtual apb_if vif;
      uvm_analysis_port #(apb_item) ap;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
        ap = new("ap", this);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
          `uvm_fatal("NOVIF", "apb_if not set for this monitor")
      endfunction
    
      // A transfer completes on the edge where PSEL, PENABLE and PREADY are all 1.
      task run_phase(uvm_phase phase);
        forever begin
          @(vif.mon_cb);
          if (vif.mon_cb.psel && vif.mon_cb.penable && vif.mon_cb.pready) begin
            apb_item it = apb_item::type_id::create("it");   // a fresh object per transfer
            it.addr   = vif.mon_cb.paddr;
            it.write  = vif.mon_cb.pwrite;
            it.data   = vif.mon_cb.pwdata;
            it.rdata  = vif.mon_cb.prdata;
            it.slverr = vif.mon_cb.pslverr;
            `uvm_info("APB_MON", it.convert2string(), UVM_HIGH)
            ap.write(it);
          end
        end
      endtask
    endclass

    The agent: one reusable box per interface

    The agent packages a sequencer, a driver and a monitor for one interface. Its is_active setting decides whether it drives: an active agent builds all three; a passive agent builds only the monitor. The output stream uses a passive copy of the same AXI-Stream agent, because the DUT drives that port and the bench must only watch.

    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
    // snippet: from the course DMA bench
    class apb_agent extends uvm_agent;
      `uvm_component_utils(apb_agent)
      apb_sequencer sqr;
      apb_driver    drv;
      apb_monitor   mon;
      uvm_analysis_port #(apb_item) ap;   // the agent's public output
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);         // reads is_active from config_db
        mon = apb_monitor::type_id::create("mon", this);
        if (get_is_active() == UVM_ACTIVE) begin
          sqr = apb_sequencer::type_id::create("sqr", this);
          drv = apb_driver::type_id::create("drv", this);
        end
      endfunction
    
      function void connect_phase(uvm_phase phase);
        ap = mon.ap;
        if (get_is_active() == UVM_ACTIVE)
          drv.seq_item_port.connect(sqr.seq_item_export);
      endfunction
    endclass

    The scoreboard: predict, then compare

    Here is the part the green-but-checking-nothing test was missing. The scoreboard listens to three monitors. APB writes to KEY update its copy of the key. Every input word becomes a prediction, word ^ key, pushed onto a queue. Every output word pops the oldest prediction and compares. At the end, check_phase fails the test if predictions were never matched, or if nothing was compared at all.

    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

    The three uvm_analysis_imp_decl macros create three differently named write methods (write_apb, write_in, write_out) so one class can receive three streams of traffic.

    The environment and the test

    The env builds the agents and the scoreboard and wires each monitor's port to a scoreboard input. The test owns the scenario: program KEY and LEN, start the job with the interrupt enabled, send one packet, wait for the interrupt, then check and clear STATUS. It raises an objection for the whole time it is busy. UVM's run phase ends only when every objection has been dropped, which is how the simulator knows the test is finished rather than idle.

    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
    // snippet: from the course DMA bench
    // Program KEY and LEN, start a job with the interrupt enabled, stream one
    // packet of LEN words, wait for the interrupt, then check and clear STATUS.
    class dma_smoke_test extends dma_base_test;
      `uvm_component_utils(dma_smoke_test)
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      task run_phase(uvm_phase phase);
        bit [31:0] status, key = 32'hA5A5_0F0F;
        int unsigned n_words = 8;
        axis_pkt_seq pkt = axis_pkt_seq::type_id::create("pkt");
    
        phase.raise_objection(this, "smoke test running");
        reg_write(KEY, key);
        reg_write(LEN, n_words);
        reg_write(CTRL, 32'h3);                  // IRQ_EN | GO
    
        if (!pkt.randomize() with { n == n_words; })
          `uvm_fatal("RAND", "axis_pkt_seq randomize failed")
        pkt.start(env.in_agt.sqr);
    
        wait (irq_vif.irq === 1'b1);
        reg_read(STATUS, status);
        if (status[1] !== 1'b1) `uvm_error("STATUS", $sformatf("DONE not set: STATUS=%08h", status))
        reg_write(STATUS, 32'h2);                // write 1 to clear DONE
        reg_read(STATUS, status);
        if (status[2:0] !== 3'b000) `uvm_error("STATUS", $sformatf("STATUS not clear: %08h", status))
        #100ns;                                  // let the last output word drain
        phase.drop_objection(this, "smoke test done");
      endtask
    endclass

    The top module

    The top module makes the clock and reset, instantiates the interfaces and the DUT, puts each virtual interface into uvm_config_db for the components that need it, and calls run_test. A +UVM_TESTNAME= plusarg on the command line overrides the name given to run_test, which is how one compiled bench runs a whole regression of different tests.

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // snippet: from the course DMA bench
    module top;
      import uvm_pkg::*;
      import dma_pkg::*;
    
      logic clk = 0;
      always #5 clk = ~clk;                        // 100 MHz
      rst_if rst (clk);
      wire rst_n = rst.rst_n;
      initial begin
        rst.rst_n = 1'b0;
        repeat (4) @(posedge clk);
        rst.rst_n <= 1'b1;
      end
    
      apb_if  apb  (clk, rst_n);
      axis_if s_ax (clk, rst_n);
      axis_if m_ax (clk, rst_n);
      irq_if  irqi (clk);
    
      // The output side accepts every beat unless a test adds a reactive slave
      // (axis_sink_agent), whose driver then drives TREADY.
      initial m_ax.tready = 1'b1;
    
      dma_dut dut (
        .clk, .rst_n,
        .paddr(apb.paddr), .psel(apb.psel), .penable(apb.penable), .pwrite(apb.pwrite),
        .pwdata(apb.pwdata), .prdata(apb.prdata), .pready(apb.pready), .pslverr(apb.pslverr),
        .s_tdata(s_ax.tdata), .s_tvalid(s_ax.tvalid), .s_tlast(s_ax.tlast), .s_tready(s_ax.tready),
        .m_tdata(m_ax.tdata), .m_tvalid(m_ax.tvalid), .m_tlast(m_ax.tlast), .m_tready(m_ax.tready),
        .irq(irqi.irq)
      );
    
      initial begin
        uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.env.apb.*", "vif", apb);
        uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.in_agt.*", "vif", s_ax);
        uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.out_agt.*", "vif", m_ax);
        uvm_config_db#(virtual irq_if)::set(null, "uvm_test_top", "irq_vif", irqi);
        uvm_config_db#(virtual irq_if)::set(null, "uvm_test_top.env.irq.*", "vif", irqi);
        uvm_config_db#(virtual rst_if)::set(null, "uvm_test_top*", "rst_vif", rst);
        run_test("dma_smoke_test");
      end
    endmodule

    Watch it run

    The two complete files, the design and the testbench, are on The DMA bench: full source, ready to paste into EDA Playground. Choose UVM 1.2 under Libraries and a simulator such as Aldec Riviera-PRO, and run. This exact bench has been built and run with Verilator and the open-source UVM library, so the listing is known to work.

    The end of the log should look like this (the paths and the time unit vary by simulator):

    Diagram
    UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatched
    
    --- UVM Report Summary ---
    ** Report counts by severity
    UVM_ERROR :    0
    UVM_FATAL :    0
    • 8 words matched means every output word was predicted and compared. That number is your proof the test checked something.

    • UVM_ERROR : 0 and UVM_FATAL : 0 is the pass condition. Regression scripts grep for exactly these lines.

    • The topology table printed at the start (from print_topology) shows out_agt with only a monitor: it really is passive.

    What breaks: three ways to fool yourself

    The bug that looks like success. A team once lost a week to a DMA that occasionally corrupted one word in a long transfer. Their scoreboard compared output words against the input words from the stimulus sequence, not the monitored input, and they had forgotten the XOR. Every compare failed on day one, so someone "fixed" the scoreboard to compare against the output monitor's own previous value. The tests went green and stayed green through the corruption. The fix was the structure in this lesson: predict from the observed input and the observed register write, and compare against the observed output.

    Try it on this bench. Change the DUT so word 6 uses ~key instead of key, rerun, and you get exactly one error:

    Diagram
    UVM_ERROR dma_tb.sv(396) @ ...: uvm_test_top.env.sb [SB_MISMATCH] expected 52c7d685, got ad38297a
    UVM_INFO dma_tb.sv(411) @ ...: uvm_test_top.env.sb [SB] 7 words matched, 1 mismatched
    • Remove the objection from run_phase, and the test ends at time 0. No traffic, no compares, and check_phase reports SB_EMPTY. Without that check the test would pass.

    • Drop the objection right after `pkt.start()` without waiting for the interrupt, and the last output words may still be inside the DUT when the run phase ends. SB_LEFTOVER catches it.

    • Make the output agent active by mistake, and its driver fights the DUT for the bus. A passive agent for DUT-driven ports is not a style choice.

    Interview

    "Walk me through a UVM testbench you've built."

    A weak answer lists class names: "there's a test, an env, agents with drivers and monitors, and a scoreboard." Every candidate can say that.

    A strong answer follows the data and names the decisions: "Stimulus is transactions from sequences; drivers own pin timing through clocking blocks. Monitors are passive and broadcast a new object per transfer, so the same agent works passive at SoC level. The scoreboard predicts from observed inputs and the observed register state, never from the sequence, and check_phase fails if anything is left unmatched or nothing was compared. End of test is one objection in the test, held until the DUT's done interrupt."

    Expect the follow-ups: How do you know your test checked anything? (match counts and an empty-compare check) and What changes when this block goes into a chip? (stimulus agents go passive, the block scoreboard keeps checking from monitors).

    Check yourself The output-side agent is created from the same class as the input-side agent. What stops it from driving the DUT's output port?

    The env sets is_active to UVM_PASSIVE for out_agt in config_db before creating it. The agent's build_phase calls super.build_phase(), which reads that setting, and then builds only the monitor. With no driver there is nothing to drive the port.

    Check yourself A colleague moves the KEY prediction into the stimulus sequence: "we know the key we wrote, so pass it straight to the scoreboard." What is lost?

    The scoreboard would now trust the intent, not the bus. If the APB write never reached the DUT (a wrong address, a SLVERR, a driver bug), the sequence's key and the DUT's key differ, and the scoreboard would blame the data path for a register-path bug, or miss it entirely. Predicting from the APB monitor checks that the write really happened.

    Check yourself Why does dma_scoreboard fail the test when zero words were compared, even though nothing mismatched?

    A test that compared nothing proves nothing. Zero compares usually means the stimulus never ran, the monitors were not connected, or the test ended too early. Treating it as an error turns a silent false pass into a visible failure.

    Next: the bench works, but every component is hard-wired. In Testbench architecture you will see why the four-layer split exists, and how the same agent serves block, subsystem and chip benches without a single edit.

    Related topics

    • Testbench architecture

    • Objections and end of test

    • Scoreboard architecture

    • Agent architecture

    Previous

    10 / 280

    Next: The DMA Bench: Full Source, Part 1

    Up next

    Up nextThe DMA Bench: Full Source, Part 1

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why UVM Exists: History and Goals
    • What UVM Standardizes (and What You Own)
    • Components vs Objects: Hierarchy, Lifetime, and Phases