【可综合SV】Connecting Modules and Interfaces
Connecting Modules and Interfaces
SystemVerilog Connecting Modules and Interfaces structures that are supported by Vivado Synthesis
支持的模块连接方式
Vivado 合成支持以下四种实例化和连接模块的方式:
- by ordered list(顺序列表)
- by name(SystemVerilog新增)
- by named ports (命名端口)
- by wildcard ports (通配符端口)(SystemVerilog新增)
四种方式的具体体现如下例
module module_connect(
input int a, b,
output int r1,
output int sum,
output int r4,
output logic [15:0] r2
adder #(.delay (5.0)) i1 (a, b, r1); // 32-bit 2-state adder : ordered list
adder #(.delay (5.0)) i3 (.a, .b, .sum); // 32-bit 2-state adder : name
adder #(.dtype (logic[15:0])) i2 (a, b, r2);// 16 bit 4-state adder : named ports
adder #(.delay (5.0)) i4 (.*, .sum(r4)); // 32-bit 2-state adder : wildcard ports
endmodule
module adder #(parameter type dtype = int, parameter realtime delay = 4) (
input dtype a, b,
output dtype sum
assign sum = a + b;
endmodule
其中使用名字
.name
或通配符
.*
来连接模块可以很大程度减少连接错误或者遗漏。主要是由于:
- 所连接的线网(net)必须是显式声明的 。点名和点星方式本身并不会隐含一个线网,这可防止传统Verilog中隐式线网有时的网表错误。
- 端口长度(size)与线网必须相同 。传统Verilog允许连接的长度不匹配,然而通常这都是由错误或者丢失的线网导致的。
- 针对点星方法,必须显示声明未连接的端口。 传统Verilog会将模块实例中任何未显式列出的端口都推断为不连接的。然而没有连接的端口通常都是无意的编码错误。点星方式要求显式列出所有未连接端口。 需注意的是,点名并没有这种额外检查 。
这里插入Xilinx提供的SV的使用若干建议:
55194 - Vivado Synthesis - SystemVerilog 的 Vivado Synthesis 最佳实践是什么? (xilinx.com)
文中提及:不要混合使用 2 状态和 4 状态类型,使用
.*
减少模块连接错误。下面补充基本状态类型:
无符号 | 有符号 | |
---|---|---|
2状态 | bit | byte(8-bit),shortint(16-bit),int(32-bit),longint(64-bit) |
4状态 | logic,reg,net-type | integer(32-bit) |
net-type主要可以分成5类,一般前两类是可综合,后三类用于仿真:
类型 | 信号 | 说明 | |
---|---|---|---|
1 | Wire and Tri nets | wire,Tri | wire可用于被单个门或连续赋值驱动,Tri可用于多个驱动器驱动一个net |
2 | Supply nets | supply0,supply1 | 用来对电路中的电源建模,这些net有supply强度 |
3 | Wired nets | wand,triand,wor,trior | 用于对wired 逻辑配置建模 |
4 | Trireg net | trireg | Trireg存储一个值,被用来对充电存储建模 |
5 | Tri0 and tri1 nets | Tri0,tri1 | 对具有上拉电阻和下拉电阻的net建模 |
可参考:
使用接口的好处
这段传统Verilog代码片段:
module top;
wire sig1, sig2, sig3, sig4;
wire clock, reset;
mod_a u1 ( .sig1(sig1),
.sig2(sig2),
.sig3(sig3),
.sig4(sig4),
.clk(clock),
.rstN(reset) );
mod_b u2 (.sig1(sig1),
.sig2(sig2),
.sig3(sig3),
.sig4(sig4),
.clk(clock),
.rstN(reset) );
endmodule
module mod_a ( input sig1, sig2,
input clk, rstN,
output sig3, sig4);
endmodule
module mod_b ( input sig3, sig4,
input clk, rstN,
output sig1, sig2);
endmodule
上述例子中将信号多次例化或引用,一旦设计发生了更改,比如在两个模块实例间需要添加一个额外信号,或者是改变了端口长度(size),我们就要在该代码分别进行修改,甚至很可能要在不同的源文件中进行修改。
SystemVerilog的接口(interface)允许我们将信号声明信息封装于一个地方。下例同上例一样,只是用了interface。现在如果要在两个模块间添加一个信号,或事更改向量长度(size)的话,就只需要修改一处代码了。
interface intf_1;
wire sig1, sig2, sig3, sig4;
endinterface: intf_1
module top;
wire clock, reset;
intf_1 i1();
mod_a u1 (.a1(i1), .clk(clock), .rstN(reset) );
mod_b u2 (.b1(i1), .clk(clock), .rstN(reset) );
endmodule: top
module mod_a ( interface a1,
input clk, rstN);
endmodule: mod_a
module mod_b ( intf_1 b1,
input clk, rstN);
endmodule: mod_b
通用接口端口(general interface port)
看上例中的mod_a,它的端口a1被声明为了interface端口类型(port type),而非传统的input,output或inout端口方向。这被称为“ 通用接口端口(general interface port) ”。将任意接口定义的实例(instance of any interface definition)连到通用接口端口都是合法的。
注意: 通用接口端口是可综合的,但带有通用接口端口的模块 不可作为elaboration的top module 。
特定类型接口端口(type-specific interface port)
在上例的mod_b中,接口端口b1被声明为了intf_1端口类型,这被称为“ 特定类型接口端口(type-specific interface port) ”。只有将intf_1接口的实例连接于该特定类型接口端口才是合法的。
建议 |
---|
在模块设计方案中仅使用特定类型接口端口。设计方案所期待的是接口端口内的特定信号。特定类型接口端口能确保所预期的接口,以及该接口内的信号,能被连到该口。 |
如何使用接口
接口是模块之间特定通信的一种方式。接口由一组网表(nets)和变量(variables)组合在一起,用于在模块之间建立连接。Vivado 合成支持以下接口结构:
- Interface definition ports
- Interface data type declarations
- Interface modport definitions
- Interface tasks and functions; must be fully automatic
- Interface procedural code(always blocks); must follow synthesis rules
- Parameterized Interfaces
上文中提到的 fully automatic 应该说的是tasks和functions不能被 static 修饰,经过测试在 task 内部使用 static 修饰的全局变量也能通过综合:
static logic a = 1;
interface if;
logic b;
task tif (output b);
b = a;
endtask
endinterface
另外在前文最佳实践中提及:
Functions are automatic by default.
首先可以创建一个头文件用于放置使用的接口,例如 intf.sv, 在需要调用的模块前包括即可
`include "intf.sv"
module top(
endmodule
Interface modport definitions
创建好头文件后,在其中定义需要使用的接口。下面这个例子中,展示了支持结构中的 definition ports、 data type declarations、modport definitions 和 Parameterized Interfaces
interface randomnum_intf # (parameter N = 4) // Parameterized Interfaces
(input clk); // definition ports
// data type declarations
logic request, ready;
logic [N-1:0] value;
logic newport;
// modport definitions
modport randomnum_p (input request, output ready, value);
modport controller_p (input value, ready, output request);
endinterface
这里着重介绍一下 modport。使用 modport 可以将接口进行分组(可以是部分信号),并指定端口的方向。那么定义好的接口如何在模块中声明和使用:
`include "intf.sv"
module randomnum(
input rst,
randomnum_intf.randomnum_p int2, // modport 声明
randomnum_intf int3 // inout
always_ff @(posedge int3.clk)
if(rst)
int2.ready <= int3.request;
int2.ready <= int2.request;
对于没有使用 modport指定端口方向的信号(如
newport
),或者在声明时没有使用modport名,将使用点对点的无信号方向的连接方式,在综合时会被作为 inout接口
[Synth 8-330] inout connections inferred for interface port 'randomnum_intf' with no modport
Interface tasks and functions
下面这个例子展示了支持结构中的 tasks and functions
interface display_intf;
logic won, lost;
logic [4:0] user_total, fpga_total;
task digi_display (input won, lost,
input [4:0] user_total, fpga_total,
output [7:0] left_half1, left_half0, right_half1, right_half0
case(user_total)
5'b00000: {left_half1, left_half0} = {8'b00000011,8'b00000010};//00
5'b11111: {left_half1, left_half0} = {8'b00001101,8'b10011110};//31
endcase
if(won || lost)
case(fpga_total)
5'b00000: {right_half1, right_half0} = {8'b00000011,8'b00000011};//00
5'b11111: {right_half1, right_half0} = {8'b00001101,8'b10011111};//31
endcase
{right_half1, right_half0} = {8'b11111111,8'b11111111};
endtask
endinterface
特别需要注意的是,虽然 task 中使用了端口方向,但是接口中端口方向仍然是 inout
[Synth 8-330] inout connections inferred for interface port 'display_intf' with no modport
那么如何在模块中使用接口定义的 task :
`include "intf.sv"
module display(
input clk, rst,
display_intf int1, // 声明接口
logic [7:0] left_half1, left_half0, right_half1, right_half0;
//task call
always_comb
int1.digi_display (.won(int1.won), .lost(int1.lost),
.user_total(int1.user_total), .fpga_total(int1.fpga_total),
.* );
用过组合逻辑对接口中的 task 进行驱动。
Top Level
上面两个例子主要说明了在模块中如何使用接口,这些模块最终会被放置在顶层模块下,而在顶层模块中我们需要对接口进行声明:
`include "intf.sv"
module top(input clk);
randomnum_intf #(.N(4)) int3(.clk(clk)); // 接口例化