普通的模塊使用法:注意我們這里只實現了部分功能。。。。不是完全的讀寫模塊。。。。
module mem_core(
input logic wen,
input logic ren,
output logic mrdy=1,
input logic [7:0] addr,
input logic [7:0] mem_din, //寫進mem
output logic [7:0] mem_dout, //從mem讀出
output logic status,
input logic clk);
logic[7:0] mem [7:0];
//初始化一個mem
initial $readmemh("d:/init.txt",mem); //d:/init.txt 文件中是 @01 10 。
//或者 assign mem [2'h01]=8'b00000111; 注意這里一定要用 initial 或者assign等語句,不能直接=
task reply_read(input logic [7:0] data, integer delay);
#delay;
@(negedge clk)
mrdy=1'b0;
mem_dout=data; //從圖中可看出這兩句話幾乎同時執行。
@(negedge clk)
mrdy=1'b1;
endtask
always@(negedge ren) reply_read(mem[addr],10);
endmodule
module cpu_core(
output logic wen=1,
output logic ren=1,
input logic mrdy,
output logic [7:0] addr=0,
input logic [7:0] cpu_din,
output logic [7:0] cpu_dout,
output logic status=0,
input logic clk);
task read_memory(input logic [7:0] raddr, output logic [7:0] data);
@(posedge clk);
ren=1'b0;
addr=raddr;
@(negedge mrdy);
@(posedge clk);
data=cpu_din;
ren=1'b1;
endtask
initial begin
logic[7:0] read_data;
read_memory(2'h01, read_data);
$display("Read Result", $time,read_data);
end
endmodule
module top;
logic mrdy,wen,ren;
logic[7:0] addr,d1,d2;
wor status;
logic clk=0;
mem_core mem(.*, .mem_din(d1), .mem_dout(d2)); //采用*對同名的信號做默認連接
cpu_core cpu(.*, .cpu_din(d2), .cpu_dout(d1));
initial for(int i=0;i<=255;i++) #1 clk=!clk;
endmodule
另外,SystemVerilog引入一個重要的數據類型:interface。其主要作用有兩個:一是簡化模塊之間的連接;二是實現類和模塊之間的通信;
- 隨着復雜度的提高,模塊間互聯變得復雜,SV引入接口,代表一捆連線的結構,具有智能同步和連接功能的代碼;
接口(interface)為硬件模塊的端口提供了一個標准化的封裝方式。
用interface來封裝接口的信號和功能。interface的定
義是獨立於模塊的,通過關鍵字interface和endinterface包起來。此外,interface里面還可以
帶時鍾、斷言、方法等定義。
一個interface 也可以有input,output或是inout端口。當interface例化時,只有當變量或是線網聲明在一個interface的端口列表中才能通過名字或是位置來互連.
一種新加的和interface有關系的構造體是Modport 。它提供了module的interface端口和在特定的module中控制task和function使用的方向性信息。這些端口的方向可以在module中可以看到。
接口使用無信號的連接方式。Modport將接口中信號分組並指定方向。就像下圖中的黑色矩形塊里面一樣,黑盒,我們從外面看並不關心Modport的定義,只需要考慮clk。
interface membus(input logic clk, output wor status);
logic mrdy;
logic wen;
logic ren;
logic [7:0] addr;
logic [7:0] c2m_data;
logic [7:0] m2c_data;
task reply_read(input logic [7:0] data, integer delay);
#delay;
@(negedge clk)
mrdy=1'b0;
m2c_data=data;
@(negedge clk)
mrdy=1'b1;
endtask
//Task和function可以定義在interface中,從而允許構造更抽象級的模型
task read_memory(input logic [7:0] raddr, output logic [7:0] data);
@(posedge clk);
ren=1'b0;
addr=raddr;
@(negedge mrdy);
@(posedge clk);
data=m2c_data;
ren=1'b1;
endtask
modport master(output wen, ren, addr, c2m_data, input mrdy, m2c_data, status, read_memory);
modport slave(input wen, ren, addr, c2m_data, output mrdy, m2c_data, status, reply_read);
//控制task和function使用的方向性信息,以便在下面的module中使用
endinterface
module mem_core(membus.slave mb);
//modport只需在模塊首部指明(或者在()中),在模塊例化時不需要指明使用接口時在模塊和程序塊之外聲明接口變量;
//接口信號必須采用非阻塞值賦值來驅動。
logic[7:0] mem [7:0];
assign mem [2'h01]=8'b00000111;
assign mb.status=0;
always@(negedge mb.ren) mb.reply_read(mem[mb.addr],100);
//module可使用interface端口
endmodule
module cpu_core(membus.master mb);
assign mb.status=0;
initial begin
logic[7:0] read_data;
mb.read_memory(2'h01, read_data);
$display("Read Result", $time,read_data);
end
endmodule
module top;
wor status;
logic clk=0;
membus mb(clk,status);
mem_core mem(.mb(mb.slave));
cpu_core cpu(.mb(mb.master));
initial for(int i=0;i<=255;i++) #1 clk=!clk;
endmodule
System verilog把測試平台的代碼放在一個程序塊中,包含代碼和變量,
我總結了幾步使用interface的方法
1、 首先定義一個interface
interface arb_if(input bit clk);
logic [1:0] grant, request;
logic reset;
clocking cb @(posedge clk);
//在其中定義一個時鍾塊。供下面的測試program使用。測試program中所有使用到的信號都應該定義在其中
output request;
//注意這里的方向是測試program中所需要的方向,一般跟DUT 中的相反
input grant;
endclocking
modport TEST (clocking cb, // 使用modport,將信號分組
output reset);
modport DUT (input request, reset, clk,
output grant);
modport MONITOR (input request, grant, reset, clk);
endinterface
2、定義一個基於interface參數的設計模塊module
module arb (arb_if.DUT arbif);
//該
interface參數要實例化
reg last_winner;
reg winner;
reg [1:0] next_grant;
reg [1:0] state, nxState;
always @(posedge arbif.clk or posedge arbif.reset)
begin
。。。
end
endmodule
3、定義一個基於
interface參數的測試程序program
program automatic test (arb_if.TEST arbif);
//該
interface參數也要實例化
task reset_test();
begin
$display("Task reset_test: asserting and checking reset");
arbif.reset <= 0;
#100 arbif.reset <= 1;
//測試program中所有使用到的信號都應該調用在interface中的時鍾塊里定義的信號
arbif.cb.request <= 0;
repeat (2) @arbif.cb;
arbif.reset <= 0;
@arbif.cb; //測試program中是這樣等待時鍾邊沿的。
a0: assert (arbif.cb.grant == 2'b00);
。。。
end
endtask
task request_grant_test();
begin
。。。
end
endtask
//注意program中不允許使用always塊。
initial begin
repeat (10) @arbif.cb;
reset_test();
request_grant_test();
repeat (10) @arbif.cb;
$finish;
end
endprogram
4、‘最后使用一個頂層模塊將它們組合起來
module top;
bit clk;
always #5 clk = !clk;
arb_if arbif(clk);
//實例化一個interface
arb a1 (arbif);
//實例化一個module,參數調用上面實例化的
interface
test t1(arbif);
//實例化一個測試program,參數調用上面實例化的interface
endmodule
當然也可以隱式端口連接,值使用
.*即可。
module top;
bit clk;
always #5 clk = !clk;
arb_if arbif(.*);
arb a1 (.*);
test t1(.*);
endmodule
虛接口:虛接口是物理接口的句柄
interface 和 module是一樣的, 都是靜態的變量, 也就是在程序開始時, 內存中就有了其實例.
但是在class里使用virtual interface時之前有兩部必須提前完成:
l 定義是將接口作為一個類進行定義。
l 實例化:在RTL級的頂層中對接口進行實例化。
先定義一個接口。
interface Rx_if (input logic clk);
logic [7:0] data;
logic soc, en, clav, rclk;
clocking cb @(posedge clk);
output data, soc, clav;
input en;
endclocking : cb
modport DUT (output en, rclk,
input data, soc, clav);
modport TB (clocking cb);
endinterface : Rx_if
例如網絡交換機中DUT 的每一個通道都有一個接口。,一個Driver類可能會連接到很多接口。
我們可以在Driver類中使用
一個虛接口作為參數。
class Driver;
virtual Rx_if.TB Rx;
//想一想,如果不是虛接口,而是一個普通接口,就像一個普通模塊一樣,是一個靜態變量。比如我們在頂層模塊例化了這個接口
Rx, 那么下面所有的 實例化的 drv[i]都是對這同一個接口 Rx進行操作,這顯然不是我們想要的。
如果定義了virtual,則每個實例獨立。
...
...
endclass
然后在測試program中 創建一組虛接口
program automatic test(
Rx_if.TB Rx[4],
Tx_if.TB Tx[4],
output logic rst);
........
Driver drv[4];
//實例化了4個 Driver 對象,每個 Driver對象帶有1個實例化的虛接口
.........
initial begin
virtual Rx_if.TB vRx_t=Rx;
//創建一組虛接口,由於這里定義了virtual,所以實例化的時候可以有Rx[].
for (int i=0; i<4; i++) begin
drv[i] = new(...., vRx[i]);
end
rst <= 1;
repeat (10) @Rx[0].cb;
rst <= 0;
for (int i=0; i<4; i++) begin
drv[i].run(5, driver_done); //發送
.......
end
..........
endprogram : test
最后在頂層:
module top;
logic clk, rst;
Rx_if Rx[4] (clk);
,,,,
atm_router a1 (Rx[0], Rx[1], Rx[2], Rx[3], Tx[0], Tx[1], Tx[2], Tx[3], clk, rst);
test t1 (Rx, Tx, rst);
initial begin
clk = 0;
forever #20 clk = !clk;
end
endmodule : top
定義一個interface,且實例化多個后,如果沒有定義virtual,則在任何一個實例中修改了某個信號值,在其他實例中都會受到影響。如果定義了virtual,則每個實例獨立。如果該interface只有一個實例,可用可不用virtual,有多個實例,需要virtual。
再舉個例子:8位計數器
`timescale 1ns/1ns
interface X_if (input logic clk);
logic [7:0] din, dout;
logic reset_l, load;
clocking cb @(posedge clk);
output din, load;
input dout;
endclocking
always @cb
//接口里面也可以帶子程序,斷言,initial,always塊等代碼。
$strobe("@ : %m: dout= , din= , load= , reset= ",
$time, dout, din, load, reset_l);
modport DUT (input clk, din, reset_l, load,
output dout);
modport TB (clocking cb, output reset_l);
endinterface
// Simple 8-bit counter with load and active-low reset
`timescale 1ns/1ns
module DUT(X_if.DUT xi);
logic [7:0] count;
assign xi.dout = count; //們想要輸出的結果就是計數器
always @(posedge xi.clk or negedge xi.reset_l)
begin
if (!xi.reset_l) count = 0;
else if (xi.load) count = xi.din;
else count++;
end
endmodule
////////////////////////////////
`timescale 1ns/1ns
program automatic test();
parameter NUM_XI = 2; // Number of interface instances
typedef virtual X_if.TB vXi_t;
vXi_t vxi[NUM_XI];
//虛接口數組
class Driver;
//在測試程序中定義類
vXi_t xi;
int id;
function new(vXi_t xi, int id);
this.xi = xi;
this.id = id;
endfunction
task reset;
fork
begin
$display("@ : %m: Start reset [ ]", $time, id);
// Reset the device
xi.reset_l <= 1;
xi.cb.load <= 0;
xi.cb.din <= 0;
@(xi.cb)
xi.reset_l <= 0;
@(xi.cb)
xi.reset_l <= 1;
$display("@ : %m: End reset [ ]", $time, id);
end
join_none
endtask
task load;
fork
begin
$display("@ : %m: Start load [ ]", $time, id);
xi.cb.load <= 1;
xi.cb.din <= id + 10;
xi.cb.load <= 0;
repeat (5) @(xi.cb);
$display("@ : %m: End load [ ]", $time, id);
end
join_none
endtask
endclass
Driver driver[];
initial begin
// Connect the local virtual interfaces to the top
$display("Test.v: There are NUM_XI = interfaces", NUM_XI);
if (NUM_XI <= 0) $finish;
driver = new[NUM_XI];
//創建driver, 每個DUT 要對應一個driver
vxi = top.xi;
//XMR跨模塊連接。這種是推薦做法,就不用帶參數了program automatic test(X_if xi[NUM_XI]); 了。
//注意這里其實是把top模塊中生成的xi[]數組的句柄傳過來的
for (int i=0; i《NUM_XI; i++)
begin
driver[i] = new(vxi[i], i);
driver[i].reset;
end
foreach (driver[i])
driver[i].load;
repeat (10) @(vxi[0].cb);
$display("@ : Test completed", $time);
$finish;
end
endprogram
////////////////////////////////////////////////////////
`timescale 1ns/1ns
parameter NUM_XI = 2; // Number of interface instances
module top;
// Clock generator
bit clk;
initial forever #20 clk = !clk;
X_if xi [NUM_XI] (clk); // Instantiate N Xi interfaces
// Generate N DUT instances
generate
for (genvar i=0; i《NUM_XI; i++)
begin : dut
DUT d (xi[i]);
end
endgenerate
// Instantiate the testbench, overriding the parameter with number of instances
test tb();
endmodule : top