Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
    • SystemVerilog Data Types
    • Arrays: Packed, Dynamic, Queues & Associative
    • Procedural Code & Scheduling
    • Operators, Expressions & Width Rules
    • Packages, Typedefs & Compilation
    • Interfaces, Modports & Clocking Blocks
      • Interface Basics
      • Modports
      • Clocking Blocks
      • Interface BFM
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Interfaces, Modports & Clocking Blocks
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Language Foundations
  4. Interfaces, Modports & Clocking Blocks
  5. Interface Basics

Part 1 · Language Foundations · Intermediate

Interface Basics

Declare a bus once as an interface, share one instance, and prefer named ports.

On this page

    Why this matters

    A wide bus was wired by hand through four levels of hierarchy, and one level swapped two signals. An interface makes the bus one port, so there is nothing to swap.

    After this lesson you can: declare an interface, instantiate it once and connect it to several modules, and choose named or generic interface ports.

    One bundle instead of seven ports

    A bus protocol is one logical thing. Classic Verilog shreds it into individual ports, repeated on every module and wired by hand at each level.

    Each repetition risks a swapped, mis-sized, or forgotten signal. An interface restores the protocol as one named object.

    dut_old.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // snippet: separate ports before the bundle, one object after it
    // BEFORE: every signal a separate port, repeated everywhere
    module dut_old (
      input  logic        clk, rst_n,
      input  logic        req,
      output logic        gnt,
      input  logic [31:0] addr,
      inout  wire  [63:0] data,
      output logic        err
    );
      // ...
    endmodule
    
    // AFTER: the protocol is one named thing
    interface bus_if (input logic clk, input logic rst_n);
      logic        req;
      logic        gnt;
      logic [31:0] addr;
      wire  [63:0] data;
      logic        err;
    endinterface
    
    module dut (bus_if bus);          // one port
      assign bus.gnt = bus.req;       // dot into the bundle
    endmodule

    Instantiate once, share everywhere

    An interface is instantiated like a module. The instance elaborates into real nets that live at the instantiation point.

    Each connected module declares a port of the interface type and reaches members with dot notation. Clocks and resets enter through the interface ports, so later clocking blocks can use them.

    One instance holds the nets; every module connected to it sees the same signals.

    The raw drives in the testbench below are scaffolding: they change inputs between edges with # delays. Against a clocked DUT, a blocking drive at the sampling edge would race, so real testbenches drive and sample through a clocking block (Clocking Blocks).

    Named ports before generic ports

    A port declared as plain interface accepts any interface with the members the module uses. That flexibility defers type checking to the elaboration of each connection.

    Named ports stay the default. Reserve generic ports for deliberately polymorphic utilities such as monitors.

    err_counter.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // snippet: a generic utility port beside the preferred named port
    // generic: accepts ANY interface with .clk and .err
    module err_counter (interface i);
      int count;
      always @(posedge i.clk)
        if (i.err) count++;
    endmodule
    
    // named (preferred): checked against bus_if at compile time
    module bus_monitor (bus_if bus);
      always @(posedge bus.clk)
        if (bus.req && bus.gnt)
          $display("[%0t] grant addr=%h", $time, bus.addr);
    endmodule

    The monitor still samples on the raw clock; clocking discipline arrives in a later lesson. One name collision to file away: interface class is an object-oriented abstract type, unrelated to the hardware interface in this topic.

    Common mistakes

    Common pitfall

    One bus_if per module compiles cleanly and communicates never. Two instances are two separate copies of the nets, so share a single instance instead.

    Interview

    “Named or generic interface port?” Named ports check at compile time. Generic ports defer checking to elaboration and suit polymorphic utilities only.

    Check yourself Why does sharing one bus_if instance remove cross-wiring bugs?

    The signals travel together through one port. There is no per-signal list left to swap, mis-size, or forget.

    Testbench

    Predict first The bench below sets bus.req to 1. A second instance, other, is connected to nothing. Does bus.gnt follow, and does other.gnt?

    bus.gnt goes to 1, because the DUT is connected to bus. other.gnt never changes: nothing drives that copy.

    The full bundle plus a testbench that proves the sharing.

    bus_if.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    interface bus_if (input logic clk, input logic rst_n);
      logic        req;
      logic        gnt;
      logic [31:0] addr;
      wire  [63:0] data;
      logic        err;
    endinterface
    
    module dut (bus_if bus);
      assign bus.gnt = bus.req;
    endmodule
    
    module tb_if_basic;
    
      timeunit 1ns; timeprecision 1ps;
      logic clk = 0;
      logic rst_n = 1;
      bus_if bus (.clk(clk), .rst_n(rst_n));      // shared instance
      bus_if other (.clk(clk), .rst_n(rst_n));    // separate copy
      dut u_dut (.bus(bus));
      int errors = 0;
    
      initial begin
        bus.req = 1'b0; bus.addr = 32'h0000_1000; #1;
        if (bus.gnt !== 1'b0) errors++;
        bus.req = 1'b1; #1;
        if (bus.gnt !== 1'b1) errors++;            // same instance: gnt follows req
        if (bus.addr !== 32'h0000_1000) errors++;  // address is visible too
        if (other.req === 1'b1) errors++;          // separate copy never sees req
        if (other.gnt === 1'b1) errors++;
        if (errors != 0) $fatal(1, "FAIL: %0d checks differed", errors);
        $display("PASS: one shared instance communicates and a second copy stays isolated");
        $finish;
      end
    endmodule
    Terminal
    PASS: one shared instance communicates and a second copy stays isolated

    Project step

    Declare pkt_if, PacketFIFO's bus: valid, ready, data[7:0] and last, with a src modport for the side that sends bytes and a snk modport for the side that receives them. Connect a sink that is always ready and send it the Part 0 packet.

    One way to write it:

    pkt_if.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    // PacketFIFO's bus: one byte per beat, valid/ready handshake, last on the final byte.
    interface pkt_if (input logic clk, input logic rst_n);
      logic       valid, ready, last;
      logic [7:0] data;
      modport src (output valid, data, last, input ready, clk, rst_n);   // sends bytes
      modport snk (input valid, data, last, clk, rst_n, output ready);   // receives bytes
    endinterface
    
    // A sink that is always ready and counts the beats it accepts.
    module pkt_sink (pkt_if.snk p);
      int beats = 0;
      bit saw_last = 0;
      assign p.ready = 1'b1;
      always @(posedge p.clk)
        if (p.rst_n && p.valid && p.ready) begin
          beats <= beats + 1;
          if (p.last) saw_last <= 1;
        end
    endmodule
    
    module tb_pkt_if;
      logic clk = 0, rst_n = 0;
      always #5 clk = ~clk;
      pkt_if   bus (.clk(clk), .rst_n(rst_n));    // one shared instance
      pkt_sink u_snk (.p(bus));
      logic [7:0] pkt [0:3];
      int errors = 0;
      initial begin
        pkt[0] = 8'h22; pkt[1] = 8'hA5; pkt[2] = 8'h3C; pkt[3] = 8'hBB;
        bus.valid = 0; bus.last = 0; bus.data = 0;
        @(negedge clk) rst_n = 1;
        foreach (pkt[i]) begin                     // raw drives between edges for now
          @(negedge clk);
          bus.valid = 1;
          bus.data  = pkt[i];
          bus.last  = (i == 3);
        end
        @(negedge clk) bus.valid = 0;
        if (u_snk.beats != 4 || !u_snk.saw_last) errors++;
        if (errors == 0) $display("PASS: the sink accepted %0d beats through pkt_if and saw last", u_snk.beats);
        else $fatal(1, "FAIL: sink saw %0d beats, last=%0b", u_snk.beats, u_snk.saw_last);
        $finish;
      end
    endmodule
    Terminal
    PASS: the sink accepted 4 beats through pkt_if and saw last

    Remember

    • An interface instance holds real nets; every module connected to it sees them.

    • Two instances are two separate copies: share one.

    • Named interface ports are checked at compile time; generic ones are not.

    Rule of thumb: one bus, one instance, one port per module.

    How this was checked

    Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 cannot run these benches: it rejects interface ports.

    Related topics

    • Next: Modports

    • Interfaces and modports intro (RTL course)

    Previous

    31 / 259

    Next: Modports

    Up next

    Up nextModports

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Clocking Blocks
    • Interface BFM