In Verilog, ports are connected with wires to facilitate inter-module communication. In System Verilog, inter-module communication is achieved through interfaces, which are collections of signals.
System Verilog interface intro
So what are the benefits of using interfaces?
- In Verilog, adding a signal requires tracking down and editing every instance of the module. However, with System Verilog interfaces, you only need to modify the interface block.
- It is reusable throughout the project.
- Interface blocks allow you to use features such as parameters, variables, functional coverage, assertions, tasks, and functions.
- You can use procedural assignment and continuous assignment in the Interface block.
Let's learn the syntax first.
interface [name] ([port_list]);
[list_of_signals]
endinterfaceThe interface can describe direction information through modport and timing information through clocking block.
Connect interface with design
With Verilog design
Let's look at an example of integrating a DUT designed in Verilog.
//DUT
module counter_ud #(
parameter WIDTH = 4
) (
input wire clk
,input wire rstn
,input wire [WIDTH-1:0] load
,input wire load_en
,input wire down
,output wire rollover
,output reg [WIDTH-1:0] count
);
always @(posedge clk or negedge rstn) begin
if (!rstn) count <= 4'b0;
else if (load_en) count <= load;
else begin
if (down) count <= count - 1;
else count <= count + 1;
end
end
assign rollover = andcount;
endmodule
//Interface
interface cnt_if #(
parameter WIDTH = 4
) (
input bit clk
);
logic rstn;
logic load_en;
logic [WIDTH-1:0] load;
logic [WIDTH-1:0] count;
logic down;
logic rollover;
//initialize
initial begin
rstn = 0;
load_en = 0;
load = 4'b0;
down = 0;
end
endinterface
module tb;
//clk gen
localparam period_clk = 10;
reg clk; initial clk = 0;
always #(period_clk*0.5) clk = ~clk;
//instance
cnt_if cnt_if0 (clk);
counter_ud c0 (
.clk (cnt_if0.clk )
,.rstn (cnt_if0.rstn )
,.load (cnt_if0.load )
,.load_en (cnt_if0.load_en )
,.down (cnt_if0.down )
,.rollover (cnt_if0.rollover)
,.count (cnt_if0.count )
);
initial begin
bit load_en;
bit down;
bit [3:0] load;
@(posedge clk);
cnt_if0.rstn = 1;
//Stimulus
cnt_if0.load_en <= 1;
cnt_if0.load <= 4'h3;
cnt_if0.down <= 1;
@(posedge clk);
cnt_if0.load_en <= 0;
repeat (2) @(posedge clk);
cnt_if0.down <= 0;
//Sim. run time
repeat (7) @(posedge clk);
$finish;
end
//wave dump
initial begin
$dumpfile("dump.vcd");
$dumpvars;
end
endmoduleYou can connect each signal of the interface one by one, but you can make the code simpler by using the method below.
With System Verilog design
//DUT
module counter_ud #(
parameter WIDTH = 4
) (cnt_if _if);
always @(posedge _if.clk or negedge _if.rstn) begin
if (!_if.rstn) _if.count <= 4'b0;
else if (_if.load_en) _if.count <= _if.load;
else begin
if (_if.down) _if.count <= _if.count - 1;
else _if.count <= _if.count + 1;
end
end
assign _if.rollover = and_if.count;
endmodule
//Interface
interface cnt_if #(
parameter WIDTH = 4
) (
input bit clk
);
logic rstn;
logic load_en;
logic [WIDTH-1:0] load;
logic [WIDTH-1:0] count;
logic down;
logic rollover;
//initialize
initial begin
rstn = 0;
load_en = 0;
load = 4'b0;
down = 0;
end
endinterface
module tb;
//clk gen
localparam period_clk = 10;
reg clk; initial clk = 0;
always #(period_clk*0.5) clk = ~clk;
//instance
cnt_if cnt_if0 (clk );
counter_ud c0 (cnt_if0);
initial begin
bit load_en;
bit down;
bit [3:0] load;
@(posedge clk);
cnt_if0.rstn = 1;
//Stimulus
cnt_if0.load_en <= 1;
cnt_if0.load <= 4'h3;
cnt_if0.down <= 1;
@(posedge clk);
cnt_if0.load_en <= 0;
repeat (2) @(posedge clk);
cnt_if0.down <= 0;
//Sim. run time
repeat (7) @(posedge clk);
$finish;
end
//wave dump
initial begin
$dumpfile("dump.vcd");
$dumpvars;
end
endmoduleYou can simplify your code by integrating using an interface.
Modport
You can set specific constraints on an interface to control its direction. Let's look at an example.
//Syntax
modport [identifier] (
input [port_list],
output [port_list]
);
interface ms_if (input clk);
logic sready;
logic rstn;
logic [1:0] addr;
logic [7:0] data;
initial rstn = 0;
modport slave (
input clk
,input rstn
,input addr
,input data
,output sready
);
modport master (
input clk
,input rstn
,input sready
,output addr
,output data
);
endinterface
module master (ms_if.master mif);
always @(posedge mif.clk or negedge mif.rstn) begin
if (!mif.rstn) begin
mif.addr <= 2'b0;
mif.data <= 8'b0;
end
else begin
if (mif.sready) begin
mif.addr <= mif.addr + 1;
mif.data <= (mif.addr * 4);
end
else begin
mif.addr <= mif.addr;
mif.data <= mif.addr;
end
end
end
endmodule
module slave (ms_if.slave sif);
reg [7:0] reg_a;
reg [7:0] reg_b;
reg reg_c;
reg [3:0] reg_d;
reg dly;
reg [3:0] addr_dly;
always @(posedge sif.clk or negedge sif.rstn) begin
if (!sif.rstn) addr_dly <= 4'b0;
else addr_dly <= sif.addr;
end
always @(posedge sif.clk or negedge sif.rstn) begin
if (!sif.rstn) begin
reg_a <= 8'b0;
reg_b <= 8'b0;
reg_c <= 1'b0;
reg_d <= 4'b0;
end
else begin
case (addr_dly)
0 : reg_a <= sif.data;
1 : reg_b <= sif.data;
2 : reg_c <= sif.data;
3 : reg_d <= sif.data;
endcase
end
end
assign sif.sready = ~(sif.addr[1] and sif.addr[0]) | ~dly;
always @(posedge sif.clk or negedge sif.rstn) begin
if (!sif.rstn) dly <= 1;
else dly <= sif.sready;
end
endmodule
module dut (ms_if tif);
master m0 (tif.master);
slave s0 (tif.slave );
endmodule
module tb;
localparam period_clk = 10;
reg clk; initial clk = 0;
always #(period_clk * 0.5) clk = ~clk;
ms_if if0 (clk);
dut u_dut (if0);
initial begin
@(posedge clk)
if0.rstn = 1;
repeat (20) @(posedge clk);
$finish;
end
initial begin
$dumpfile("dump.vcd");
$dumpvars;
end
endmoduleThe ms_if interface module has a "master" modport and a "slave" modport. Clock and reset are inputs on both modports, but addr, data, and sready have opposite directions. The master and slave DUTs each use their own modports.
The reason modport is used like this is because the logic declared in the interface is basically inout, so there may be a situation where input and output conflicts due to human error in the testbench or module. In this case, an X (unknown) is displayed for the relevant net. By using modport, such conflicts can be prevented in advance.
Clocking block
The Clocking block specifies the timing and synchronization of a group of signals. Let's first look at a simple example.
clocking cb @(posedge clk);
default input #1ns output #2ns;
input from_Dut;
output to_Dut;
endclockingThe clocking block, named cb, contains an input signal, from_DUT, and an output signal, to_DUT. Skews are set to 1 ns and 2 ns, respectively, as shown in the figure below.
Input skew specifies the timing at which the signal is sampled, and output skew specifies the timing at which the signal is driven. If no default value is specified, input skew is applied at step #1 and output #0.
References: chip verify