Systemverilog interface/modport 簡介&使用方法

Share on:

本文內容採用創用 CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款授權.

在新的 SystemVerilog 標準中,引入了 interfacemodport 這樣的語法。本文章中將會討論這兩者的用法、限制以及突破限制的方法。

如下方所示,這是一個 Verilog struct 的語法:

1typedef struct {
2    parameter DATA_SIZE = 8;
3    parameter ADDR_SIZE = 4;
4    logic [DATA_SIZE-1:0] addr;
5    logic [ADDR_SIZE-1:0] data;
6} SramStruct;
然而,對於 Verilog 來說,只是 struct 是不夠的,主要的原因是因為 Verilog 的信號有方向性 (input/output),一個 struct 可能就同時有兩個方向性的信號,必須有額外的方法來定義信號方向性。

Interface and modport

於是在 Verilog 標準中,就有了 interface 來取代 struct 的功能,然後再用 modport 加以描述方向性。例如在 SRAM 中,我們會用一個位置 addr 跟一個資料 data ,來表示一個我們要把資料寫入 SRAM 或是從 SRAM 讀出。為了定義不同的信號方向,我們可以這樣使用 interface/modport

 1interface SramInterface();
 2    parameter DATA_SIZE = 8;
 3    parameter ADDR_SIZE = 4;
 4    logic [DATA_SIZE-1:0] addr;
 5    logic [ADDR_SIZE-1:0] data;
 6    modport Read__Slave (input  addr, output data, input  en);
 7    modport Write_Slave (input  addr, input  data, input  en);
 8    modport Read__Master(output addr, input  data, output en);
 9    modport Write_Master(output addr, output data, output en);
10endinterface
如此一來,一個 two-port SRAM module(一讀一寫)可以這樣定義 IO:
1module SRAM(clk, rin, win);
2    input clk;
3    SramInterface.Read__Slave rin;
4    SramInterface.Write_Slave win;
而使用這個 SRAM 可以被這樣使用:
1module Top();
2   MemoryInterface read_port, write_port;
3   SRAM sram(clk, read_port, write_port);
4   initial read_port.addr = 4'h9;
5endmodule
如以一來,便省去了大量宣告&接線的程式碼。

Interface/modport 限制

即使有這麼多的好處,作者仍認為 interface/modport 在實用上有很多限制,其最大的原因是在現在的語法規範中,雖然可以透過 parameter 產生不同大小的 interface,其運作方式導致其缺乏的防錯機制。

舉例而言,在上面的例子中:

1module SRAM(clk, rin, win);
2    input clk;
3    SramInterface.Read__Slave rin;
4    SramInterface.Write_Slave win;
rin/win 這兩個 interface 只能根據上層呼叫方式自動推導,來決定兩個的 interfaceparameter。換句話說,如果上層使用了:
1module Top();
2   MemoryInterface#(8,4) read__port;
3   MemoryInterface#(4,8) write_port;
4   SRAM sram(clk, read__port, write_port);
5   initial read_port.addr = 4'h9;
6endmodule
那就會產生大小不同的 interface,這對 nLint 等等工具來說也不好檢查。問題就出在 Verilog 語法上,一個 module 中無法先給 parameter,再用他來決定 interface。也就是說,這樣的語法不存在的:
1module SRAM(clk, rin, win);
2    parameter D = 8;
3    parameter A = 4;
4    input clk;
5    SramInterface#(D,A).Read__Slave rin;
6    SramInterface#(D,A).Write_Slave win;
在現行語法下,我們只能可以通過存取兩個 interfaceparameter 來確認 parameter 是正確的:
1module SRAM(clk, rin, win);
2    input clk;
3    SramInterface.Read__Slave rin;
4    SramInterface.Write_Slave win;
5    assert(rin.DATA_SIZE == win.DATA_SIZE);
6    assert(rin.ADDR_SIZE == win.ADDR_SIZE);

用 macro 來當替代方案

基於上述這麼多限制,加上目前仍有許多 EDA tool 支援度不佳,作者不建議使用 interface 功能。

於是,這邊來討論一種用 define 來達成類似的效果的方法。這個方法中,macro 展開之後,甚至可以完全相容於舊 Verilog 語法,並且對於使用 module 的人來說,其需要寫的程式碼跟 interface 一樣多:

 1`define SramInterface#_Logic(A,D,name)\
 2   logic [A-1:0] name``_addr;\
 3   logic [D-1:0] name``_data;
 4`define SramInterface#_Name(A,D,name)\
 5   name``_addr,\
 6   name``_data,
 7`define SramInterface#_Read__Slave(A,D,name)\
 8   input [A-1:0] name``_addr;\
 9   input [D-1:0] name``_data;
10`define SramInterface_Write_Slave (A,D,name)\
11   input [A-1:0] name``_addr;\
12   output logic [D-1:0] name``_data;
13`define SramInterface_Read__Master(A,D,name)\
14   output logic [A-1:0] name``_addr;\
15   output logic [D-1:0] name``_data;
16`define SramInterface_Write_Master(A,D,name)\
17   output logic [A-1:0] name``_addr;\
18   input [D-1:0] name``_data;
19`define SramInterface#_Connect(port_name,id_name)\
20    port_name``_addr(id_name``_addr),\
21   .port_name``_data(id_name``_data)
這邊兩個撇號是把字串串起來的意思,跟 C++ macro 裡面的雙井號功能一樣。
 1module SRAM(clk, `SramInterface_Name(mif_input));
 2    parameter A = 4;
 3    parameter D = 8;
 4    input clk;
 5    `SramInterface_Read__Slave(D, A, rin);
 6    `SramInterface_Write_Slave(D, A, win);
 7endmodule
 8
 9module Top();
10    `SramInterface_Logic read__port;
11    `SramInterface_Logic write_port;
12    SRAM sram(
13       .clk(clk),
14       `SramInterface_Connect(read_port, rin),
15       `SramInterface_Connect(write_port,win)
16    );
17   initial mif_addr = 4'h9;
18endmodule