Close Menu
VLSI Web
  • Home
    • About Us
    • Contact Us
    • Privacy Policy
  • Digital Design
    • Digital Circuits
    • Verilog
    • VHDL
    • System Verilog
    • UVM
  • VLSI Roles
    • RTL Design
    • Design Verification
    • Physical Design
    • DFT
    • STA
  • Interviews
  • Informative
  • VLSI Forge
Instagram LinkedIn WhatsApp Telegram
Instagram LinkedIn WhatsApp Telegram
VLSI Web
  • Home
    • About Us
    • Contact Us
    • Privacy Policy
  • Digital Design
    • Digital Circuits
    • Verilog
    • VHDL
    • System Verilog
    • UVM
  • VLSI Roles
    • RTL Design
    • Design Verification
    • Physical Design
    • DFT
    • STA
  • Interviews
  • Informative
  • VLSI Forge
VLSI Web
System Verilog

SystemVerilog Testbench Architecture: A Self-Checking Testbench Without UVM

Raju GorlaBy Raju Gorla14 July 2026Updated:11 October 2026No Comments4 Mins Read
SystemVerilog testbench architecture
Share
Facebook Twitter LinkedIn Email Telegram WhatsApp

The short answer: A good SystemVerilog testbench is layered: a generator creates randomised transactions, a driver turns them into pin activity through a virtual interface, a monitor turns pin activity back into transactions, a reference model predicts the expected results, a scoreboard compares actual with expected, and coverage records what was exercised. An environment class builds and connects these components, and each test only configures the environment and its constraints. UVM standardises exactly this structure.

Before learning UVM, I recommend building this testbench once by hand. It’s about 300 lines of SystemVerilog, and afterwards UVM stops looking like magic: every UVM class has a counterpart here. This post combines my older posts on testbench architecture, SystemVerilog for verification, advanced verification features, and debugging.

  1. The architecture
  2. The components in code
  3. The environment and the test
  4. Design choices that matter
  5. Debugging a failing test
  6. From here to UVM

Table of Contents

  • Part 1: The architecture
  • Part 2: The components in code
  • Part 3: The environment and the test
  • Part 4: Design choices that matter
  • Part 5: Debugging a failing test
  • Part 6: From here to UVM
  • FAQ
    • What are the components of a SystemVerilog testbench?
    • Do I need UVM to build a good testbench?
    • Why should the scoreboard use the monitor and not the driver?
    • How do I end a random test cleanly?

Part 1: The architecture

A layered self-checking SystemVerilog testbench: generator, driver, DUT, monitor, reference model, scoreboard and coverage inside an environment controlled by a test
Each box is a class; the arrows are mailboxes or the virtual interface.

Part 2: The components in code

class driver;
  virtual stream_if vif;
  mailbox #(txn) in;
  task run();
    forever begin
      txn t; in.get(t);
      @(vif.cb); vif.cb.valid <= 1; vif.cb.data <= t.data;
      do @(vif.cb); while (!vif.cb.ready);
      vif.cb.valid <= 0;
    end
  endtask
endclass

class monitor;
  virtual stream_if vif;
  mailbox #(txn) out;
  task run();
    forever begin
      @(vif.cb);
      if (vif.cb.valid && vif.cb.ready) begin
        txn t = new(); t.data = vif.cb.data; out.put(t);
      end
    end
  endtask
endclass

class scoreboard;
  mailbox #(txn) exp_mb, act_mb;
  int errors, checked;
  task run();
    forever begin
      txn e, a;
      exp_mb.get(e); act_mb.get(a);
      checked++;
      if (a.data !== e.data) begin
        errors++;
        $error("exp %h got %h", e.data, a.data);
      end
    end
  endtask
endclass

Part 3: The environment and the test

class env;
  generator gen; driver drv; monitor mon; ref_model rm; scoreboard sb;
  function new(virtual stream_if vif);
    mailbox #(txn) g2d = new(), g2r = new(), r2s = new(), m2s = new();
    gen = new(g2d, g2r);
    drv = new(vif, g2d);
    mon = new(vif, m2s);
    rm  = new(g2r, r2s);
    sb  = new(r2s, m2s);
  endfunction
  task run(int n);
    fork
      gen.run(n); drv.run(); mon.run(); rm.run(); sb.run();
    join_any
    wait (sb.checked == n);   // done when every transaction is checked
  endtask
endclass

program test(stream_if vif);   // or a module
  initial begin
    env e;
    e = new(vif);
    e.gen.len_max = 4;        // test-specific knob
    e.run(1000);
    $display(e.sb.errors ? "TEST FAILED" : "TEST PASSED");
    $finish;
  end
endprogram

Part 4: Design choices that matter

  • Check on the monitor side. The scoreboard compares what the DUT really produced, never what the driver intended to send.
  • Keep tests thin. A test changes constraints and configuration; it doesn’t rebuild the environment.
  • Make components reusable. A monitor that only observes an interface can be reused at block and chip level (UVM calls this a passive agent).
  • Finish cleanly. End when all expected transactions are checked, with a timeout as a safety net, and check that no expected items are left over.

Part 5: Debugging a failing test

  • Re-run with the same seed. Print the seed at the start of every run.
  • Go to the first error, not the last. Find its time and open the waveform there.
  • Decide whether the DUT, the reference model or the stimulus is wrong. Often it’s the model.
  • Add transaction-level messages with IDs, so you can follow one transaction from generator to scoreboard.
  • Turn a hard-won bug into a directed test or an assertion, so it can never come back silently.

Part 6: From here to UVM

This testbench UVM equivalent
generator + mailbox sequence + sequencer
driver, monitor uvm_driver, uvm_monitor (in a uvm_agent)
mailbox to scoreboard analysis port and uvm_tlm_analysis_fifo
env uvm_env
test program uvm_test
Passing vif in constructors uvm_config_db
wait for sb.checked objections

The design verification interview questions post covers the UVM side in more depth.

Practise this on VLSI Forge

I built VLSI Forge so you can write RTL in your browser, run it on a real simulator and check every signal in the waveform. Free, nothing to install.

Design Verification problems · Daily challenge

Previous in my SystemVerilog series: Functional coverage. Next: DPI.

FAQ

What are the components of a SystemVerilog testbench?

A generator, driver, monitor, reference model, scoreboard and coverage collector, built and connected by an environment and controlled by a test.

Do I need UVM to build a good testbench?

No. A well-structured class-based testbench like this works well for many blocks. UVM adds standardisation and reuse across teams.

Why should the scoreboard use the monitor and not the driver?

Because the monitor reports what actually happened on the DUT’s pins. Using the driver’s intent can hide bugs in the interface itself.

How do I end a random test cleanly?

Stop when the scoreboard has checked the expected number of transactions, with a timeout as a backstop, and report any leftover expected items.

Functional Verification SystemVerilog Assertions SystemVerilog Testbench Design Verification methodology
Share. Facebook Twitter LinkedIn Email Telegram WhatsApp
Previous ArticleFunctional Coverage in SystemVerilog: Covergroups, Bins and Crosses
Next Article DPI in SystemVerilog: Calling C from Your Testbench (and Back)
Raju Gorla
  • Website

Related Posts

System Verilog

Design Patterns in SystemVerilog Testbenches: Factory, Callbacks and More

16 July 2026
System Verilog

DPI in SystemVerilog: Calling C from Your Testbench (and Back)

15 July 2026
System Verilog

Functional Coverage in SystemVerilog: Covergroups, Bins and Crosses

13 July 2026
Add A Comment
Leave A Reply Cancel Reply

Topics
  • Digital Circuits
  • Informative
  • Interview Questions
  • Physical Design
  • RTL Design
  • STA
  • System Verilog
  • UVM
  • Verilog
Instagram LinkedIn WhatsApp Telegram
  • About
  • Contact
  • Privacy Policy
  • VLSI Forge
  • Courses
  • Feedback
© 2026 VLSI Web

Type above and press Enter to search. Press Esc to cancel.