Part 1 · Language Foundations · Intermediate

Modports

Direction views for DUT vs TB, modports in port declarations, and the limits of what modports enforce.

Direction is relative — modports make it explicit

A raw interface has no notion of direction: req is just a variable, and both the DUT and the testbench could legally drive it. But direction is the essence of a protocol — the requester drives req and samples gnt; the arbiter does the reverse. A modport ("module port") declares a named directional view of the interface's signals. The same physical net appears as input in one modport and output in another, because direction is always relative to the component looking at the bus. Modports add no hardware and no new nets — they are compile-time lenses that let the compiler reject a module driving a signal its view declares as input.

systemverilog
interface bus_if (input logic clk);
  logic        req, gnt;
  logic [31:0] addr;
  logic [63:0] rdata;

  // the SAME nets, two opposite views:
  modport dut (
    input  req, addr,      // DUT receives requests
    output gnt, rdata,     // DUT produces grant + read data
    input  clk
  );
  modport tb (
    output req, addr,      // TB produces requests
    input  gnt, rdata,     // TB samples responses
    input  clk
  );
endinterface
diagram
ONE NET, TWO VIEWS

                 physical net: req
        ┌────────────────────────────────┐
        │                                │
   modport tb                       modport dut
   (output req)                     (input req)
        │                                │
   tb_driver may DRIVE req     dut may only SAMPLE req
   tb_driver reads gnt         dut drives gnt
        │                                │
        └── direction is relative to the viewer ──┘

Using modports at connection points

A module selects its view by qualifying the port type: bus_if.dut. Alternatively the selection can happen at the instantiation site by connecting bus.dut to a plain interface port — the port-declaration style is preferred because the module then documents and enforces its own role. Once a modport view is in force, a procedural assignment to an input-view signal is a compile error: the wrong-direction bug that used to surface as an X-fight at runtime now fails before simulation starts.

systemverilog
// view chosen in the port declaration (preferred)
module dut (bus_if.dut bus);
  always_ff @(posedge bus.clk) begin
    bus.gnt <= bus.req;          // OK: gnt is output in dut view
    // bus.req <= 1'b0;          // COMPILE ERROR: req is input here
  end
endmodule

module tb_driver (bus_if.tb bus);
  task automatic send(logic [31:0] a);
    bus.req  <= 1'b1;            // OK: tb view owns req
    bus.addr <= a;
    @(posedge bus.clk iff bus.gnt);
    bus.req  <= 1'b0;
  endtask
endmodule

module tb_top;
  logic clk = 0;  always #5 clk = ~clk;
  bus_if bus (.clk(clk));
  dut       u_dut (.bus(bus));   // module already declared .dut view
  tb_driver u_drv (.bus(bus));
endmodule

What modports do not enforce

Modports are weaker than newcomers assume, and interviewers probe exactly this edge. First, they restrict access through the modport only: code that receives the whole interface (or a hierarchical reference like tb_top.bus.req) bypasses the view entirely. Second, enforcement strength varies — the LRM requires errors for illegal procedural drives, but tools differ on continuous assignments and on checks through virtual interface modport handles, so treat modports as documentation plus best-effort checking, not a security boundary. Third, modports do not create drivers or resolve contention: two components with output views of the same logic variable are still a multiple-driver problem. What modports also carry: a clocking block can be exported through a modport (modport tb (clocking cb);), which is the lesson that follows, and import task entries can expose interface methods to a view.

Interview angle

  • "Do modports create new signals?" — no; they are named directional views over the same nets.

  • "Can a module bypass a modport?" — yes, via the unqualified interface or hierarchical references; modports check the lens, not the nets.

  • "DUT vs TB modports?" — mirror images of each other; direction is relative to the viewer.

Key takeaways

  • Modports declare per-role direction views; the dut and tb views are mirror images over the same nets.

  • Prefer selecting the view in the module's port declaration — the module enforces its own role.

  • Wrong-direction drives become compile errors instead of runtime X-fights.

  • Modports restrict access through themselves only — they are lenses, not locks, and they do not arbitrate drivers.

Common pitfalls

  • Assuming a modport prevents all access to a signal — hierarchical references sail straight past it.

  • Declaring every signal inout-ish in one catch-all modport — compiles everywhere, checks nothing.

  • Two output views of one logic variable — modports do not solve multiple-driver contention.

  • Relying on uniform tool enforcement for virtual-interface modport handles — behavior varies; do not lean on it.