In SystemVerilog hierarchical modules can be connected by simple data types, complex data types (structs, unions, etc), or interfaces. The feature that I am interested in is aggregating all signals between two modules in one place which simplifies maintenance of the code.
For example in the following one change s_point's definition without changing the declarations of m1, m2, and top:
typedef struct {
logic [7:0] x;
logic [7:0] y;
} s_point; // named structure
module m1 (output s_point outPoint);
//
endmodule
module m2 (input s_point inPoint);
//
endmodule
module top ();
s_point point;
m1 m1_inst (point);
m2 m2_inst (point);
endmodule
Alternatively, this could have been done using interfaces. However, I believe using structures are easier and they are supported by more CAD tools, and they don't need to be instantiated as is the case for interfaces (although one still has to declare them in the top module).
My question is if only aggregating signals is required (i.e. no interest in interface's task, functions, modports, generic interface, internal always blocks etc) for a synthesizable design, which one is preferred?
interface
is preferred.
A struct
okay to use only when all the signals within the struct
all follow the same port direction; input
, output
, or inout wire
. It becomes challenging to use structs when driving directions become mixed. Mixed direction is a allow using the ref
keyword, however the ref
keyword is not supported by many synthesis tools, yet. inout
cannot be used because logic
is considered a variable, IEEE Std 1800-2012 § 6.5 Nets and variables. However, inout wire
can be used to cast the struct
as a net. The components of a stuct can not be assigned within an always-block and needs an assign
statement instead; just like a regular wire
.
An interface
should be grouping signals where the port direction is not consistent. Tri-states need to be defined as wire
and the variable types do not require explicit port direction when used in conjunction with always_{ff|comb|latch}
. It should also be used if the signals are part of a protocol. This way assertions can be added and it can be connected to a classes for an UVM test-bench or other SVTB environment.
Use a sturct
when only passing a explicit direction data type. Use an interface
for passing shared signals.
Imagine there is a collection of signals x
,y
,&z
, where module m_x
drives x
and reads y
&z
, module m_b
drive y
and reads x
&z
, and module m_z
drives z
and reads x
&y
. Each signals have only one driver and always_ff
can be used to guarantee this.
If we try adding a bidirectional tri-state bus
, to the mix then the struct cannot be used. A wire
resolves conflicting drivers while logic
/reg
clobber and keep the scheduler running.
Using struct
using ref
(no tri-state allowed):
typedef struct {logic [7:0] x, y, z, bus; } s_point;
module m_x_st (ref s_point point, input clk);
always_ff @(posedge clk)
point.x <= func(point.y, point.z);
//assign point.bus = (point.y!=point.z) ? 'z : point.x; // NO tir-state
endmodule
module m_y_st (ref s_point point, input clk);
always_ff @(posedge clk)
point.y <= func(point.x, point.z);
//assign point.bus = (point.x!=point.z) ? 'z : point.y; // NO tir-state
endmodule
module m_z_st (ref s_point point, input clk);
always_ff @(posedge clk)
point.z <= func(point.x, point.y);
//assign point.bus = (point.x!=point.y) ? 'z : point.z; // NO tir-state
endmodule
module top_with_st (input clk);
s_point point;
m_x_st mx_inst (point,clk);
m_y_st my_inst (point,clk);
m_z_st mz_inst (point,clk);
endmodule
Using struct
using inout wire
(nets must be driven with assign
, loses single driver guarantee):
typedef struct {logic [7:0] x, y, z, bus; } s_point;
module m_x_wst (inout wire s_point point, input clk);
logic [$size(point.x)-1:0] tmp;
assign point.x = tmp;
always_ff @(posedge clk)
tmp <= func(point.y, point.z);
assign point.bus = (point.y!=point.z) ? 'z : point.x; // tir-state
endmodule
module m_y_wst (inout wire s_point point, input clk);
logic [$size(point.y)-1:0] tmp;
assign point.y = tmp;
always_ff @(posedge clk)
tmp <= func(point.x, point.z);
assign point.bus = (point.x!=point.z) ? 'z : point.y; // tir-state
endmodule
module m_z_wst (inout wire s_point point, input clk);
logic [$size(point.z)-1:0] tmp;
assign point.z = tmp;
always_ff @(posedge clk)
tmp <= func(point.x, point.y);
assign point.bus = (point.x!=point.y) ? 'z : point.z; // tri-state
endmodule
module top_with_wst (input clk);
wire s_point point; // must have the 'wire' keyword
m_x_wst mx_inst (point,clk);
m_y_wst my_inst (point,clk);
m_z_wst mz_inst (point,clk);
endmodule
interface
(with tri-state):interface if_point;
logic [7:0] x, y, z;
wire [7:0] bus; // tri-state must be wire
endinterface
module m_x_if (if_point point, input clk);
always_ff @(posedge clk)
point.x <= func(point.y, point.z);
assign point.bus = (point.y!=point.z) ? 'z : point.x;
endmodule
module m_y_if (if_point point, input clk);
always_ff @(posedge clk)
point.y <= func(point.x, point.z);
assign point.bus = (point.x!=point.z) ? 'z : point.y;
endmodule
module m_z_if (if_point point, input clk);
always_ff @(posedge clk)
point.z <= func(point.x, point.y);
assign point.bus = (point.x!=point.y) ? 'z : point.z;
endmodule
module top_with_if (input clk);
if_point point();
m_x_if mx_inst (point,clk);
m_y_if my_inst (point,clk);
m_z_if mz_inst (point,clk);
endmodule
Running code: http://www.edaplayground.com/s/6/1150