UPGRADE YOUR BROWSER

We have detected your current browser version is not the latest one. Xilinx.com uses the latest web technologies to bring you the best online experience possible. Please upgrade to a Xilinx.com supported browser:Chrome, Firefox, Internet Explorer 11, Safari. Thank you!

cancel
Showing results for 
Search instead for 
Did you mean: 

Using SystemVerilog interfaces to connect logic in Vivado Synthesis

Xilinx Employee
Xilinx Employee
4 0 197

What are Interfaces?

SystemVerilog interfaces were developed to allow for easier connectivity between blocks in your design.  One way to think of them is as collections of pins that are common to many blocks. Instead of having to define many pins on each module, they are defined once in an interface, and then the interface is defined on the module instead of pins.  If the set of signals is later changed, only the interface needs to change.

This allows a lot of information to be condensed into a smaller amount of lines, but it can be a little difficult to write for the first time. It can also be difficult to interpret an interface written by someone else when you are looking at it for the first time.  This article will explain the basics of interfaces and how to correctly write them in Vivado.

We will be converting a small testcase without interfaces into one that uses interfaces.  The example RTL stages for this will be in the last section of the article. 

The schematic for the original testcase appears as follows:

image.pngOriginal testcaseDefining the Interface

In order to proceed, the interface must first be defined.  All that is needed are the names of the signals that are common to the multiple modules that will be replaced by the interface.  Once that list is known, the interface is declared as follows:

interface my_int;
     logic sel;
     logic [9:0] data1, data2, result;
endinterface : my_int

The code above has declared an interface called "my_int".  It has also declared four signals, one bit called "sel", and three 10 bit buses called  data1, data2, and result. These are the pins of the modules that will be replaced by the interface. Note that even though the "clk" signal is used in both modules, the interface is not using the clk signal.  Putting control signals in an interface will work, but it is a matter of personal preference. This author prefers to leave the clock signals separate from the interface.

Using the Interface

Once the interface has been declared, it can be used like any port of a module. For lower level modules where the ports will be replaced with the interface, the coding style should be changed as follows:

Orginal version:

module bottom2(
		input clk,
		input sel,
		input [9:0] data1, data2,
		output logic [9:0] result);

New Version:

module bottom2(
	my_int int1,
	input clk);

Note that instead of declaring the port as an input or output, it is declared as type "my_int" which was the name given to the interface.  It has also been given the instance name of int1.

Because the pins for the lower level have been removed, they can no longer be referenced the same way.  Instead of referencing the pins directly, they need to be referenced in the context of the interface.

The syntax for this is "<int_name>.<pin_name>".  For example, in the original RTL, the output "result" was assigned either data1 or data2 depending on the sel input.

always@(posedge clk) begin
if (sel == 1)
result <= data1;
else
result <= data2;
end

Now, it needs to be changed to the following:

always@(posedge clk) begin
if (int1.sel == 1)
	int1.result <= int1.data1;
else
	int1.result <= int1.data2;
end

After the lower levels have been modified to change the pins to interfaces, and references to those pins have been changed to reference the interface, the upper level that instantiates those modules also needs to be modified.

Before using interfaces, the module pins at the top level would have been connected to signals that were declared in the design. So now, instead of connecting signals, we will connect interfaces. The first thing that needs to be done is to declare an interface of the same type.

my_int int3();

The code above declares an interface of type "my_int" and gives it an instance name of "int3".

As before, all references to the signals inside this interface need to be done with the "<interface_name>.<pin_name>" syntax.

Next, the lower level is instantiated.

bottom2 u1(int3,clk)

The RTL above instantiates the bottom2 module giving it an instance name of u1.  The interface "int1" that was declared in the bottom2 module is now associated with the interface "int3" that has been declared in the upper level.  After making these changes, the schematic for the design will look like the following:

 

image.pngDesign after converting to interfaces

Adding Modports

After adding the interface, the tool has created the correct connections, but you might notice that the schematic looks a little odd.  The data1 and data2 lines from both lower levels appear to be driving the same net. If you tunnel down into those modules, you will see that there is no multiple driver issue as one of those modules treats data1 and data2 as an input.

The reason that the schematic looks odd is that the interface created did not tell the tool which pins were acting as inputs and which were acting as outputs.  When it created the connections, the tool did not know exactly how to hook up the pins, so it made the connections and later when the behavior was analyzed, the tool figured out the direction of the pins.

While this will work, it is highly recommended that input/output information for the interface be given to the tool. This is accomplished through the use of modports. Modports are declared inside of interfaces and they tell the tool which signals are inputs and which are outputs. Because different modules will have pins with different directions, multiple modports per interface are usually declared.

The syntax for a modport is:

modport <name> (<input.output> <pin name>, <input/output> <pin name>....);

For example, the following RTL creates a modport called b1, and makes the result signal an output and the other signals all inputs.

modport b2 (input sel, data1, data2, output result);

The modport is then used in the declaration of the interface in the lower level port list.

module bottom2 (
     my_int.b2 int1,
     input clk);

The code above tells the tool the following:

  • bottom2 is going to use the interface my_int, and to give it an instance name called "int1"
  • In this interface, the result will be an output
  • sel, data1, and data2 will be inputs.

Once the changes are complete, the new schematic will look like the following:

image.pngDesign after adding modports

Conclusion

This article was written to show how useful Interfaces can be in hooking up logic with similar signals, but this is not the only use for interfaces.  In addition, interfaces can use many features including tasks and functions, and can even be parametrized. 

We will explore these other features in future articles.

Original RTL with no interfaces:

module bottom1 (
input clk,
input [9:0] d1, d2,
input s1,
input [9:0] result,
output logic sel,
output logic [9:0] data1, data2,
output logic equal);

always@(posedge clk) begin
if (d1 == d2)
	equal <= 1;
else
	equal <= 0;
end

always@(posedge clk) begin
     if (s1 == 1) begin
        data1 <= d1;
	end
     else begin
	   data2 <= d2;
	end
end

always@(posedge clk) begin
     sel <= ^result;
end

endmodule
		 
module bottom2 (
input clk,
input sel,
input [9:0] data1, data2,
output logic [9:0] result );

always@(posedge clk) begin
if (sel == 1)
result <= data1;
else
result <= data2;
end

endmodule


module top (
input clk,
input s1,
input [9:0] d1, d2,
output my_sel,
output equal);

logic [9:0] data1, data2, result;
logic sel;

assign my_sel = sel;

bottom1 u0 (.clk(clk), .d1(d1), .d2(d2), .s1(s1), .result(result), .sel(sel), .data1(data1), .data2(data2), .equal(equal));
bottom2 u1 (.clk(clk), .sel(sel), .data1(data1), .data2(data2), .result(result));



endmodule

Design with first attempt at an interface:

interface my_int;
    logic sel;
    logic [9:0] data1, data2, result;
    
endinterface : my_int

module bottom1 (
my_int int1,
input clk,
input [9:0] d1, d2,
input s1,
output logic equal);

always@(posedge clk) begin
if (d1 == d2)
	equal <= 1;
else
	equal <= 0;
end

always@(posedge clk) begin
     if (s1 == 1) begin
        int1.data1 <= d1;
	end
     else begin
	   int1.data2 <= d2;
	end
end

always@(posedge clk) begin
     int1.sel <= ^int1.result;
end

endmodule
		 
module bottom2 (
my_int int1,
input clk);

always@(posedge clk) begin
if (int1.sel == 1)
	int1.result <= int1.data1;
else
	int1.result <= int1.data2;
end

endmodule


module top (
input clk,
input s1,
input [9:0] d1, d2,
output my_sel,
output equal);

logic [9:0] data1, data2, result;
logic sel;

my_int int3();

assign my_sel = int3.sel;

bottom1 u0 (int3, clk, d1, d2, s1, equal);
bottom2 u1 (int3, clk);



endmodule

Design with use of modports:

interface my_int;
    logic sel;
    logic [9:0] data1, data2, result;
    
    modport b1 (input result, output sel, data1, data2);
    modport b2 (input sel, data1, data2, output result);
endinterface : my_int

module bottom1 (
my_int.b1 int1,
input clk,
input [9:0] d1, d2,
input s1,
output logic equal);

always@(posedge clk) begin
if (d1 == d2)
	equal <= 1;
else
	equal <= 0;
end

always@(posedge clk) begin
     if (s1 == 1) begin
        int1.data1 <= d1;
	end
     else begin
	   int1.data2 <= d2;
	end
end

always@(posedge clk) begin
     int1.sel <= ^int1.result;
end

endmodule
		 
module bottom2 (
my_int.b2 int1,
input clk);

always@(posedge clk) begin
if (int1.sel == 1)
	int1.result <= int1.data1;
else
	int1.result <= int1.data2;
end

endmodule


module top (
input clk,
input s1,
input [9:0] d1, d2,
output my_sel,
output equal);

logic [9:0] data1, data2, result;
logic sel;

my_int int3();

assign my_sel = int3.sel;

bottom1 u0 (int3, clk, d1, d2, s1, equal);
bottom2 u1 (int3, clk);



endmodule