並行塊
在測試中經常會用到 fork...join
塊。使用並行塊能表示以同一個時間起點算起的多個時間的運行,並行的執行復雜的過程結構,如循環或任務。
eg
module inilne_tb;
reg [7:0] data_bus;
initial fork
data_bus=8'b00;
#10 data_bus=8'h45;
//以下的兩個repeat開始執行時間不同,但可以並行同時執行
#20 repeat(10) #10 data_bus=data_bus+1;
#25 repeat(5) #20 data_bus=data_bus<<1;
#140 data_bus=8'h0f;
join
endmodule
0時刻對data_bus賦初始值
10個單位時間之後對data_bus重新賦值
從20單位時間開始,每10個單位時間數據自加
從25單位時間開始,沒20個單位時間數據左移,與上一條指令並行執行
140單位時間再賦值
建立時鐘
雖然有時候在設計中會包含時鐘,但時鐘通常在測試模塊中。可以使用門級和行爲級建立時鐘模型。行爲描述一般使用的人較多。
簡單的對稱方波
reg clk;
always begin
#peroid/2 clk=0;
#peroid/2 clk=1;
end
簡單帶延遲的對稱方波時鐘
reg clk;
initial begin
clk=0;
#(peroid)
forever
#(peroid/2) clk=!clk;
end
不規則形
reg clk;
initial begin
#(peroid+1) clk=1;
#(peroid/2-1)
forever begin
#(peroid/4) clk=0;
#(3*peroid/4) clk=1;
end
end
將會產生一個帶延遲的,佔空比不爲1,同時投脈衝不規則的時鐘。
Verilog高級結構
task
一般用於編寫測試模塊或者行爲描述的模塊。其中可以包含時間控制(如: #delays,@,wait);也可以包含input、output、inout端口定義和參數;同時也可以調用其他任務或函數。
module bus_ctrl_tb;
reg[7:0] data;
reg data_valid,data_rd;
cpu ul(data_valid,data,data_in);
initial begin
//在模塊中調用task
cpu_driver(8'b0000_0000)
cpu_driver(8'b0000_0000)
cpu_driver(8'b0000_0000)
end
//定義task
task cpu_driver;
input [7:0] data_in;
begin
#30 data_valid=1;
wait(data_rd==1);
#20 data=data_in;
wait(data_rd==0);
#20 data=8'hzz;
#30 data_valid=0;
end
endtask
endmodule
在測試模塊中使用任務可以提高程序代碼的效率,可以用任務把多次重複的操作包裝起來。
同時要注意的是,模塊的任務中,用於定時控制的信號,例如clk絕對不能作爲任務的輸入。因爲輸入值只想任務內部傳遞第一次,而定時控制一般來講絕對不止一次傳遞控制。
不要在程序的不同部分同時調用一個任務。這是因爲任務只有一組本地變量,同一時刻調用兩次相同的任務將會導致錯誤。這種情況同時發生在使用定時控制的任務中。
parameter MAX_BITS=8;
reg[MAX_BITS:1] D;
task reverse_bits;
//雙向總線端口被當做寄存器類型
inout [7:0] data;
integer K;
for(K=0;K<MAX_BITS;K=K+1)
reverse_bits[MAX_BITS-(K+1)]=data[K];
endtask
always @ (posedge clk)
reverse_bits(D);
上面的代碼可以看出,在task定義過程中,有直接使用reverse_bits[MAX_BITS-(K+1)]=data[K];
,也就是說,在Verilog中與函數一樣,task、function調用都是直接將參數代替函數名直接改變。上面的調用 reverse_bits(D)
等價於:
inout [7:0] data;
integer K;
for(K=0;K<MAX_BITS;K=K+1)
D[MAX_BITS-(K+1)]=data[K];
任務只含有一個雙向總線(inout)端口和一個內部變量,沒有其他輸入端口、輸出端口和定時控制,沒有引用模塊變量。
在任務調用時候,任務的輸入變量(端口)在任務內部被當做寄存器類型變量處理(D)。
module mult(clk,a,b,out,en_mult);
input clk,en_mult;
input [3:0]a,b;
output [7:0] out;
reg[15:0] out;
always @ (posedge clk)
//任務調用
multme(a,b,out);
task multme;
input [3:0] xme,tome;
output [7:0] result;
wait(en_mult)
result=xme*tome;
endtask
endmodule
模塊中定義的任務含有輸入,輸出,時間控制和一個內部變量,並且引用了一個本模塊的變量。任務調用時候參數的順序應該與任務定義聲明的變量順序相同。
function
函數不能包含定時控制,但是可以在包含定時控制的過程塊中調用函數。
在模塊中,使用名爲func的函數時,是將它作爲名爲func的寄存器類型變量來處理。
module orand(a,b,c,d,e,out);
input [&;0] a,b,c,d,e;
output[7:0] out;
reg[7:0] out;
always @(a,b,c,d,e);
out=func(a,b,c,d,e);
function [7:0] func;
input [7:0] a,b,c,d,e;
if(e==1)
func=(a|b)&(c|d);
else
func=0;
endfunction
endmodule
不包含任何定時控制語句
至少一個輸入,不能含有任何輸出和總線口
只返回一個值,值的變量名與函數名同名,數據類型默認爲reg
傳遞給函數的參數順序與函數輸入口聲明的順序相同
函數定義必須在模塊定義內
函數不能調用任務(因爲任務可能包含時間控制),反之可以
雖然函數只能返回一個值,但是他的返回值可以直接賦給一個由多個子信號拼購成的信號變量。
{o1,o2,o3,o4}=func(a,b,c,d,e)
在函數定義時,如果在函數名之前定義了位寬,則函數就可以返回多bit構成的矢量。如果定義函數的語句比較多時,可以使用begin...end
塊。
函數名前面的位寬代表了返回值(一般就是以函數名爲名的reg)的位寬。
若把函數定義爲整型、實型或時間類型,就可以返回相應類型的數據。可以在任何類型的表達式中調用函數。
module checksub(neg,in_a,in_b);
output neg;
input a,b;
reg neg;
function integer subtr;
input [7:0] in_a,in_b;
subtr=in_a-in_b;
endfunction
always @(a or b)
begin if(subtr(a,b)<0)
neg=1;
else
neg=0;
else
endmodule