Part 8 · Senior & Interview Prep · Intermediate

Step 2: Interface & Harness

fifo_if with clocking blocks for TB and monitor, and tb_top with clock/reset generation and DUT hookup — complete code.

Design decisions first

  • One interface carries both sides plus flags — the FIFO is one protocol, and the monitors need cross-side visibility (flags vs handshakes).

  • Two clocking blocks: drv_cb drives TB outputs with a skew past hold; mon_cb is input-only so monitors physically cannot drive.

  • The interface is parameterized by WIDTH so the same file serves every configuration in the regression.

  • rst_n stays outside the clocking blocks — reset logic needs raw asynchronous visibility.


fifo_if — complete code

systemverilog
interface fifo_if #(parameter int WIDTH = 8)
                   (input logic clk, input logic rst_n);

  // write (push) side
  logic             in_valid;
  logic             in_ready;
  logic [WIDTH-1:0] in_data;

  // read (pop) side
  logic             out_valid;
  logic             out_ready;
  logic [WIDTH-1:0] out_data;

  // status
  logic             full;
  logic             empty;

  // driver clocking: sample pre-edge, drive 2ns after the edge
  clocking drv_cb @(posedge clk);
    default input #1step output #2ns;
    output in_valid, in_data, out_ready;
    input  in_ready, out_valid, out_data, full, empty;
  endclocking

  // monitor clocking: inputs only — a monitor cannot drive, by construction
  clocking mon_cb @(posedge clk);
    default input #1step;
    input in_valid, in_ready, in_data;
    input out_valid, out_ready, out_data;
    input full, empty;
  endclocking

  modport DRV (clocking drv_cb, input rst_n);
  modport MON (clocking mon_cb, input rst_n);
endinterface

Code walkthrough

  1. input #1step — both clocking blocks sample Preponed (pre-edge) values, exactly what the DUT's flops see.

  2. output #2ns on drv_cb — drives clear of the hold window; only the three TB-driven signals are outputs.

  3. mon_cb lists every signal as input — compile-time guarantee the monitor never causes contention.

  4. Modports bundle each role's clocking block plus raw rst_n for reset-aware code.


tb_top — complete code

systemverilog
module tb_top;
  parameter int WIDTH = 8;
  parameter int DEPTH = 16;

  logic clk;
  logic rst_n;

  // clock: 100 MHz
  initial clk = 0;
  always #5ns clk = ~clk;

  // reset: assert async, deassert synchronized to the clock
  initial begin
    rst_n = 0;
    repeat (3) @(posedge clk);
    rst_n <= 1;
  end

  // the one real interface instance
  fifo_if #(.WIDTH(WIDTH)) fif (.clk(clk), .rst_n(rst_n));

  // DUT hookup
  fifo #(.WIDTH(WIDTH), .DEPTH(DEPTH)) dut (
    .clk       (clk),
    .rst_n     (rst_n),
    .in_valid  (fif.in_valid),
    .in_ready  (fif.in_ready),
    .in_data   (fif.in_data),
    .out_valid (fif.out_valid),
    .out_ready (fif.out_ready),
    .out_data  (fif.out_data),
    .full      (fif.full),
    .empty     (fif.empty)
  );

  // environment: built in later steps (gen/drv/mon/sb/cov)
  env #(.WIDTH(WIDTH), .DEPTH(DEPTH)) e;

  initial begin
    e = new(fif);                  // vif injected by constructor
    wait (rst_n);
    @(posedge clk);
    e.run();
  end

  // global watchdog — a hang becomes a report, not a lost regression slot
  initial begin
    #2ms;
    $fatal(1, "GLOBAL TIMEOUT: test did not finish");
  end
endmodule

Code walkthrough

  1. Reset asserts immediately at time 0 and deasserts with a nonblocking assign at an edge — clean, race-free release.

  2. Exactly one fifo_if instance; every component receives a virtual handle to this same instance.

  3. The env starts only after wait(rst_n) plus one edge — no stimulus into a DUT in reset.

  4. The watchdog $fatal converts every hang class into a diagnosable failure with a timestamp.

Key takeaways

  • drv_cb for driving, mon_cb (input-only) for sampling — contention is impossible by construction.

  • Parameterize the interface; one file serves the whole regression's WIDTH sweep.

  • tb_top owns clock, reset, the real interface, the DUT, the env, and the watchdog — nothing else.

Common pitfalls

  • Driving DUT inputs from raw interface signals instead of drv_cb — the races return.

  • Putting rst_n inside a clocking block — reset handling needs raw asynchronous access.

  • Forgetting the watchdog — a hung corner test silently burns the nightly regression slot.