"How would you verify this?" is the core of a principal interview. The interviewer wants structure, priorities and the checks that matter most, not a list of every component. A good answer has the same shape every time.
Clarify the design: parameters, protocols, what is configurable, what is in scope.
Risks: name the three or four features most likely to hide bugs, and say why.
Environment: drivers, monitors, reference model, where the checks live.
Checks: what is compared end to end, what is checked with assertions, what is audited at end of test.
Stimulus and coverage: what must be reached, and how you prove it was reached.
Closure: what "done" means, and what runs beyond simulation.
Spend most of the time on risks and checks. They show judgment; an environment block diagram does not.
Assume M masters and S slaves, AXI4, a configurable address map, and ID widening (the interconnect appends master bits to each ID so responses can be routed back).
Step | Answer |
|---|---|
Risks | Address decode and the default slave; response routing by widened ID; ordering for the same ID; write data following the write address order; arbitration fairness; bursts and the 4 KB boundary rule. |
Environment | An active master agent per master port and a responder per slave port, each with a passive monitor. A routing predictor maps each request to the expected slave from the address map. |
Checks | Per-path scoreboards: every request appears at exactly one slave (or the default slave with a DECERR response), unchanged apart from the ID. Responses return to the right master with the original ID. Within one ID and direction, order is kept; across IDs, any order is legal. Protocol assertions on every port: VALID stays high until READY, payload stable while waiting. |
Stimulus | Many outstanding transactions; the same ID sent to two different slaves; all masters hammering one slave; random back-pressure on every channel; unmapped addresses. |
Coverage | Master by slave cross; outstanding depth per port; same-ID-to-different-slaves cases; back-pressure on each channel; DECERR per master. |
Closure | All paths exercised with high outstanding depth, zero leaked IDs after drain, fairness bounds checked, plus a formal pass on the decoder and protocol assertions if available. |
Follow-up questions the interviewer is likely to ask:
"Master 0 sends ID 3 to slave A, then ID 3 to slave B. Slave B answers first. What should happen?" The interconnect must keep same-ID order. It either stalls the second request until the first completes, or buffers B's response. Your scoreboard must check per-ID order across slaves.
"How does your scoreboard handle out-of-order responses?" A queue of expected items per ID: in order within a queue, any order across queues.
"How do you know you did not lose a transaction?" Count outstanding items per ID at the end of the test, after a drain, and fail on any leftover.
Assume a set-associative, write-back data cache with a memory-side bus and a CPU-side request port.
Step | Answer |
|---|---|
Risks | Tag compare and aliasing; replacement and dirty eviction; hit-under-miss and requests to a line that is being filled; write-back of partial lines; flush and invalidate while busy. |
Environment | A CPU-side driver, a memory-side responder with random latency, and a flat memory model as the reference. |
Checks | Every read returns the value a flat memory would return. Never model the cache's internals in the checker; model the memory the CPU believes in. After a flush, memory contents equal the flat model. Assertions: no two ways valid with the same tag in one set; a dirty line is written back before it is replaced. |
Stimulus | A small working set, so lines are reused; addresses that map to the same set, to force conflicts and evictions; back-to-back accesses to a line being filled. |
Coverage | Hit, miss, eviction clean or dirty, per way; conflicts per set; hits during an outstanding miss; flush while busy. |
Closure | All cache states and transitions reached, zero mismatches against the flat memory, and a final memory compare after flush in every test. |
The core idea fits in a small bench. A 4-line direct-mapped, write-through cache is checked against a flat 256-byte array. Half the addresses are chosen to collide in the same line, because that is where tag bugs live.
// Direct-mapped, write-through, no-write-allocate cache in front of a 256-byte memory.
module dm_cache (
input logic clk,
input logic rst_n,
input logic req,
input logic we,
input logic [7:0] addr,
input logic [7:0] wdata,
output logic [7:0] rdata,
output logic hit_q
);
logic [7:0] mem [256];
logic [7:0] line [4];
logic [5:0] tag [4];
logic valid [4];
wire [1:0] idx = addr[1:0];
wire [5:0] t = addr[7:2];
wire hit = valid[idx] && (tag[idx] == t);
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin
for (int i = 0; i < 4; i++) valid[i] <= 1'b0;
for (int i = 0; i < 256; i++) mem[i] <= 8'h00;
hit_q <= 1'b0;
end else if (req) begin
hit_q <= hit;
if (we) begin
mem[addr] <= wdata; // write-through
if (hit) line[idx] <= wdata; // keep the cached copy current
end else if (hit) begin
rdata <= line[idx];
end else begin // read miss: fill the line
rdata <= mem[addr];
line[idx] <= mem[addr];
tag[idx] <= t;
valid[idx] <= 1'b1;
end
end
endmodule
It passes with 210 hits and 1090 misses over 1300 reads. Two mutants fail it: comparing only the low 3 tag bits (tag[idx][2:0] == t[2:0]) returns stale data for aliased addresses (read c0: got 24, flat memory has 00), and removing the write-hit update of the cached line returns old data after a write. Restrict the bench to the small working set (addresses 0 to 15) and the tag mutant passes: those tags differ in their low bits, so the partial compare never confuses them. Reuse and aliasing are separate stimulus goals, and a cache bench needs both.
Follow-up questions:
"Why not check the hit/miss decision in the scoreboard?" Because the spec defines data, not hits. A cycle-accurate cache model copies the RTL and copies its bugs. Check performance separately, with counters and bounds.
"How would you find a bug in the replacement policy?" Data is still correct with a bad policy, so use a white-box assertion or a model of the policy, and cover each way being chosen as victim.
"What changes with coherence?" The flat memory becomes a global memory with ordering rules; you add snoop agents and check that no two caches hold a line in conflicting states.
Assume multiple channels, descriptor chains in memory (scatter-gather), an AXI master port, and an interrupt per channel.
Step | Answer |
|---|---|
Risks | Byte counts and unaligned starts and ends; bursts split at 4 KB boundaries; descriptor parsing and chain termination; channel arbitration; abort and error mid-transfer; interrupt and status accuracy. |
Environment | A register driver for the programming interface, a memory model behind the AXI port that can inject latency and error responses, and a descriptor generator that writes chains into that memory. |
Checks | After each transfer the destination equals the source, byte for byte. Guard bands: no byte outside the destination range changed. Every AXI burst is legal and none crosses 4 KB. Status and interrupts match a transfer-level model; one interrupt per completion, none extra. |
Stimulus | Random lengths including 0, 1 and maximum; every start and end alignment; long chains; several channels at once; aborts at random points; slave error responses. |
Coverage | Alignment of start and end crossed with length class; 4 KB crossings; chain lengths; abort per state; error response per channel; concurrent channels. |
Closure | Zero data or guard-band mismatches, all alignment and boundary bins hit, every abort state reached, and a software-driven test at SoC level for the interrupt and driver flow. |
Follow-up questions:
"How do you catch a DMA that writes one byte too many?" Guard bands: fill the bytes around every destination buffer with a known pattern and check them after the transfer. A destination-only compare misses an overrun.
"How do you test abort?" Abort at a random cycle, then check that no write happened after the abort was accepted, that status reports the bytes done, and that the channel can start a new transfer.
"What would you run outside simulation?" Formal on the burst-splitting logic (4 KB and alignment rules suit formal well), and an SoC test with the real driver on emulation.
Because the cache is meant to be invisible: the CPU must see the same data as with no cache. A flat memory is a simple, independent reference. A cache model would repeat the design's structure and could share its bugs, and it would break on legal changes such as a different replacement order.
The response-routing check in the scoreboard. It must record which master issued each widened ID, so it can map the returning ID to the master that must receive it.