Each layer speaks one level of abstraction. Lower layers know pins and cycles, and upper layers know scenarios and intent.
Layer | Responsibility | Knows |
|---|---|---|
Signal | Interface, clocking blocks, DUT pins | Pin names and timing |
Command | Driver and monitor translate one transaction to pin activity and back | The handshake protocol |
Functional | Scoreboard and reference model check each transaction | Expected versus actual data |
Scenario | Generator produces streams of related transactions | Stories, not single items |
Test | Selects scenarios, sets knob values | Intent, no protocol code |
A layer talks only to its neighbours. That restriction is what keeps pieces replaceable.
Only the signal and command layers name pins. Intent lives only in the test layer, which holds scenario selection and knob values and no protocol code.
Because only the bottom two layers know widths and timing, everything above them survives a DUT retarget. The transaction type is parameterized once, and upper layers never mention widths again.
// snippet
// One transaction type for every width; 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
// Retargeting 32-bit data to 64-bit data changes three places
// as one parameter set: the interface DW parameter, this
// typedef, and the driver and DUT widths together. Changing
// only the typedef silently truncates data against a 32-bit
// interface, so the three always move as one.
typedef bus_txn #(.AW(16), .DW(64)) bus_txn64;Swap DUT widths with one parameter set, as above.
Reuse a scenario such as write-then-read-back against a register file, a FIFO, or a memory controller behind a matching driver.
Replace random generation with file replay: driver, monitor, and scoreboard never notice.
Run one scoreboard against RTL and against a gate-level netlist with only the signal layer rebound.
This is the testbench every layered architecture replaced. It works for a homework assignment and fails everywhere else.
// bad-example
// 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
endmoduleFlat habit | Why it fails at scale |
|---|---|
Hard-coded values | Only the values the author thought of get tested. |
Inline checking | Data sampled at the wrong cycle passes silently. |
One block per master | A second master duplicates everything, with no concurrency story. |
Fused stimulus and driving | A handshake change means editing every test. |
Human-read logs | Regressions cannot consume a verdict nobody printed. |
A module-level smoke test that writes four addresses and reads them back through DUT pins. It is deliberately flat: one block holds stimulus, driving and checking. It only shows the pin activity the layers must produce; the class layers appear in the next lessons.
`timescale 1ns/1ps
module flat_dut (
input logic clk,
input logic rst_n,
input logic valid,
input logic write,
input logic [3:0] addr,
input logic [31:0] wdata,
output logic ready,
output logic [31:0] rdata
);
logic [31:0] mem [0:15];
assign ready = 1'b1;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) rdata <= 32'h0;
else if (valid && write) begin
mem[addr] <= wdata;
rdata <= 32'h0;
end
else if (valid && !write) rdata <= mem[addr];
end
endmodule
rdata right after the ready loop. What makes that check fragile?Stimulus, driving, and checking share one block with no stated sampling rule. A one-cycle shift in the DUT response turns the check stale, and nothing in the structure would flag the shift.