在 Vivado Synthesis 中使用 SystemVerilog 接口连接逻辑

什么是接口?

SystemVerilog 接口的开发旨在让设计中层级之间的连接变得更加轻松容易。 您可以把这类接口看作是多个模块共有的引脚集合。与必须在每个模块上定义多个引脚不同的是,您只需在接口中对引脚定义一次,之后只需在模块上定义接口即可。 如果稍后接口中涉及的信号被更改,则仅需更改接口即可。

这样就可以将大量信息压缩到较少代码行,但第一次写接口可能会有点困难。在第一次看别人写的接口时,也可能会很难进行解读。 本文将介绍接口的基础知识以及如何在 Vivado 中正确使用接口。

我们将把一个没有接口的小型测试用例转换为使用接口的测试用例。 这个测试用例的示例 RTL 代码将在本文的最后一节中介绍。

此原始测试用例的原理图如下所示:

原始测试用例

原始测试用例

定义接口

首先,必须先定义接口。 所需要的只是将被接口替换的多个模块共有的信号名称。 知道该列表后,接口声明如下:
interface my_int;
logic sel;
logic [9:0] data1, data2, result;
endinterface : my_int

上面的代码声明了一个名为“my_int”的接口。 它还声明了四个信号,一个称为“sel”和三个称为“data1”、“data2”和“result”的 10 位宽总线。这些是将被接口替换的模块的引脚。请注意,即使在两个模块中都使用“clk”信号,此处接口也没使用 clk 信号。 将控制信号放在接口中是可以的,但这是个人偏好的问题。本文作者更喜欢将时钟信号与接口分开。

使用接口

一旦声明了接口,就可以象使用模块的任何端口一样使用此接口。在下级模块中将使用接口替换端口,编码样式应更改如下:

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

替换后版本:
module bottom2(
my_int int1,
input clk);

请注意,与将端口声明为输入或输出不同的是,接口会被声明为“my_int”(这是给接口指定的名称)的类型, 而且还为其指定了一个实例名称“int1”。

由于下级模块的引脚已被移除,因此不能再以相同方式引用。 与直接引用引脚不同的是,他们需要基于接口名称引用。

其语法是“.
”。 例如,在原始 RTL 中,输出“result”根据“sel”输入被分配为“data1”或“data2”。

always@(posedge clk) begin
if (sel == 1)
result else
result end

现在,需要将其更改为以下内容:
always@(posedge clk) begin
if (int1.sel == 1)
int1.result else
int1.result end

在下级模块中将引脚更改为接口之后,对这些引脚的引用已被更改为引用接口,还需要修改将这些模块实例化的上级模块。

在使用接口之前,顶层的模块引脚将连接到设计中声明的信号。所以现在我们要连接接口,而不是要连接信号。首先需要做的事是声明一个相同类型的接口。
my_int int3();

上面的代码声明了一个类型为“my_int”的接口,并为其指定了一个实例名称“int3”。

和前面一样,对此接口内的信号的所有引用都需要使用“.
”语法来完成。

接下来,下级模块实例化。
bottom2 u1(int3,clk)

上面的 RTL 会对“bottom2”模块进行实例化,给它指定一个实例名称“u1”。 在“bottom2”模块中声明的接口“int1”现在与已在上一层声明的接口“int3”相关联。 进行这些更改后,设计的原理图如下所示:

转换为接口后的设计

转换为接口后的设计

添加 Modport

添加接口后,该工具已创建正确的连接,但您可能会注意到原理图看起来有点奇怪。 来自两个下级层次的“data1”和“data2”似乎正在驱动同一个网络。如果您进入这些下级模块,您会看到没有多驱动问题,因为其中一个模块将“data1”和“data2”视为输入。

原理图看起来奇怪的原因是创建的接口没有告诉工具哪些引脚在充当输入,哪些引脚在充当输出。 当工具创建连接时,并明确知道如何连接引脚,因此它先建立了连接,然后在分析行为时才找出了引脚的方向。

虽然这样可行,但还是强烈建议将接口的输入/输出信息提供给工具。这是通过使用 modport 来完成的。Modport 在接口内部进行声明,告诉工具哪些信号是输入,哪些是输出。由于不同的模块的引脚的方向各不相同,因此通常每个接口会声明多个 modport。

modport 的语法是:

例如,以下 RTL 创建了一个名为“b1”的 modport,并将 result 信号作为输出,将其他信号都作为输入信号。
modport b2 (input sel, data1, data2, output result);

然后,modport 被用于下层端口列表的接口声明中。
module bottom2 (
my_int.b2 int1,
input clk);

上面的代码告诉工具如下信息:

  • “bottom2”将使用接口“my_int”,并为其指定名为“int1”的实例名称
  • 在此接口中,result 将是输出
  • “sel”、“data1”和“data2”将是输入。
  • 更改完成后,新原理图将如下所示:

    添加 modport 后的设计

    添加 modport 后的设计

    结论

    编写本文是为了说明接口在连接具有相似信号的逻辑时的用处,但这不是接口的唯一用途。 此外,接口可以使用包括任务和功能在内的许多特性,甚至可以进行参数化。

    我们将在以后的文章中探讨其他功能。

    没有接口的原始 RTL:
    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 else
    equal end

    always@(posedge clk) begin
    if (s1 == 1) begin
    data1 end
    else begin
    data2 end
    end

    always@(posedge clk) begin
    sel 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 else
    result 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

    第一次添加接口的设计:
    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 else
    equal end

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

    always@(posedge clk) begin
    int1.sel end

    endmodule

    module bottom2 (
    my_int int1,
    input clk);

    always@(posedge clk) begin
    if (int1.sel == 1)
    int1.result else
    int1.result 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

    使用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 else
    equal end

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

    always@(posedge clk) begin
    int1.sel end

    endmodule

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

    always@(posedge clk) begin
    if (int1.sel == 1)
    int1.result else
    int1.result 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

    文章转载自:赛灵思中文社区论坛

    推荐阅读