Part 6 · Testbench Architecture · Intermediate
The Layered Testbench
Signal, command, functional, scenario, and test layers — responsibilities, boundaries, and why flat testbenches fail.
The five layers
The classic layered testbench (popularized by Spear's SystemVerilog for Verification ) stacks five layers, each speaking a different level of abstraction. Lower layers know about pins and cycles; upper layers know about scenarios and intent. Each layer only talks to its immediate neighbors.
THE LAYER STACK
┌─────────────────────────────────────────────────────────────┐
│ TEST LAYER "run 500 random writes then read back" │
│ picks knobs, constraints, scenario mix │
├─────────────────────────────────────────────────────────────┤
│ SCENARIO LAYER generator: streams of related transactions │
│ "write burst then read burst at same addr" │
├─────────────────────────────────────────────────────────────┤
│ FUNCTIONAL LAYER driver/monitor/scoreboard: one transaction │
│ at a time, checks exp vs act │
├─────────────────────────────────────────────────────────────┤
│ COMMAND LAYER pin-level protocol: assert valid, wait │
│ ready, sample on clocking-block edge │
├─────────────────────────────────────────────────────────────┤
│ SIGNAL LAYER interface + clocking block + DUT pins │
└─────────────────────────────────────────────────────────────┘
Abstraction rises going up: cycles → transactions → scenarios → intentResponsibilities per layer
Signal layer — the interface and clocking blocks: the only place pin names exist.
Command layer — driver/monitor BFM code that translates one transaction into cycle-accurate pin activity (and back).
Functional layer — scoreboard and reference model: per-transaction correctness, no pin knowledge.
Scenario layer — generators producing meaningful transaction streams, not just single items.
Test layer — selects scenarios, overrides constraints, sets knob values; contains zero protocol code.
Why layering enables reuse
Each boundary is a swap point. Because only the signal and command layers know pin widths and timing, you can retarget everything above them with no edits.
// Transaction is parameterized once; layers above the command
// layer never mention widths again.
class bus_txn #(int AW = 16, int DW = 32);
rand bit [AW-1:0] addr;
rand bit [DW-1:0] data;
rand bit write;
endclass
// Swap the DUT from 32-bit to 64-bit data:
// - signal layer: interface DW parameter changes
// - command layer: driver assigns vif.cb.wdata <= tr.data (unchanged code)
// - functional/scenario/test layers: untouched
typedef bus_txn #(.AW(16), .DW(64)) bus_txn64;Concrete reuse wins
Swap DUT widths — only the interface parameter and transaction typedef change.
Reuse a scenario — the same write-then-read-back generator runs against a register file, a FIFO, or a memory controller given a matching driver.
Replace random generation with a file-replay generator — driver, monitor, scoreboard never notice.
Run the same scoreboard against RTL and a gate-level netlist — only the signal layer rebinding changes.
Anti-example: the flat testbench
Here is the testbench every layered architecture replaced. It works for a homework assignment and fails everywhere else.
// FLAT TB — stimulus, driving, and checking tangled in one block
module tb_flat;
logic clk = 0, rst_n;
logic [15:0] addr; logic [31:0] wdata, rdata;
logic valid, write, ready;
always #5 clk = ~clk;
dut u_dut (.*);
initial begin
rst_n = 0; valid = 0; #20 rst_n = 1;
// test 1: hard-coded write then read, inline "check"
@(posedge clk); addr = 16'h10; wdata = 32'hDEAD; write = 1; valid = 1;
@(posedge clk); while (!ready) @(posedge clk);
valid = 0;
@(posedge clk); addr = 16'h10; write = 0; valid = 1;
@(posedge clk); while (!ready) @(posedge clk);
if (rdata !== 32'hDEAD) $display("FAIL?"); // maybe checked, maybe stale
valid = 0;
// test 2: copy-paste of test 1 with different numbers...
$finish;
end
endmoduleFailure modes
No randomization — only the values the author thought of get tested.
Checking is inline and timing-fragile — rdata sampled at the wrong cycle silently passes.
Adding a second master means duplicating the whole block — no concurrency story.
Changing the protocol handshake means editing every test — stimulus and driving are fused.
Pass/fail is a human reading the log; regressions cannot consume it.
Interview angle
When asked “why do we layer testbenches?” , do not answer “because UVM does it.” Answer with swap points: each layer boundary is a place you can replace one piece without touching the others, and name a concrete example — retargeting data width, or replaying a failing transaction stream through an unchanged driver.
Key takeaways
Five layers: signal, command, functional, scenario, test — abstraction rises going up.
Pin names and timing live only in the bottom two layers; intent lives only at the top.
Every layer boundary is a reuse swap point — that is the whole justification.
Flat testbenches fuse all five layers into one block; they fail at scale, concurrency, and regression.
Common pitfalls
Letting the generator wait on clock edges — scenario layer leaking into the command layer.
Scoreboard reading DUT pins directly — functional layer bypassing the monitor.
Test classes containing protocol timing code — test layer doing the command layer's job.
Layering for its own sake on a 50-line DUT block test — judgment matters; layers earn their cost at scale.