作者: ALINX
適用于板卡型號:
AXU2CGA/AXU2CGB/AXU3EG/AXU4EV-E/AXU4EV-P/AXU5EV-E/AXU5EV-P /AXU9EG/AXU15EG
簡介
本文主要介紹verilog基礎模塊,夯實基礎,對深入學習FPGA會有很大幫助。
數據類型
常量
整數:整數可以用二進制b或B,八進制o或O,十進制d或D,十六進制h或H表示,例如, 8’b00001111表示8位位寬的二進制整數,4’ha表示4位位寬的十六進制整數。
X和Z:X代表不定值,z代表高阻值,例如,5’b00x11,第三位不定值,3’b00z表示最低位為高阻值。
下劃線:在位數過長時可以用來分割位數,提高程序可讀性,如8’b0000_1111
參數parameter: parameter可以用標識符定義常量,運用時只使用標識符即可,提高可讀性及維護性,如定義parameter width = 8 ; 定義寄存器reg [width-1:0] a; 即定義了8位寬度的寄存器。
參數的傳遞:在一個模塊中如果有定義參數,在其他模塊調用此模塊時可以傳遞參數,并可以修改參數,如下所示,在module后用#()表示。
例如定義模塊如下調用模塊
module rom
#(
parameter depth =15,
parameter width =8
)
(
input[depth-1:0] addr ,
input[width-1:0] data ,
output result
);
endmodule
module top();
wire[31:0] addr ;
wire[15:0] data ;
wire result ;
rom
#(
.depth(32),
.width(16)
)
r1
(
.addr(addr),
.data(data),
.result(result)
);
endmodule
Parameter可以用于模塊間的參數傳遞,而localparam僅用于本模塊內使用,不能用于參數傳遞。Localparam多用于狀態機狀態的定義。
變量
變量是指程序運行時可以改變其值的量,下面主要介紹幾個常用了變量類型
1.Wire 型
Wire 類型變量,也叫網絡類型變量,用于結構實體之間的物理連接,如門與門之間,不能儲存值,用連續賦值語句assign賦值,定義為wire [n-1:0] a ; 其中n代表位寬,如定義wire a ; assign a = b ; 是將b的結點連接到連線a上。如下圖所示,兩個實體之間的連線即是wire類型變量。
2.Reg 型
Reg 類型變量,也稱為寄存器變量,可用來儲存值,必須在always語句里使用。其定義為
reg [n-1:0] a ; 表示n位位寬的寄存器,如reg [7:0] a; 表示定義8位位寬的寄存器a。如下所示定義了寄存器q,生成的電路為時序邏輯,右圖為其結構,為D觸發器。
module top(d, clk, q);
input d ;
input clk ;
outputreg q ;
always@(posedge clk)
begin
q 《= d ;
end
endmodule
也可以生成組合邏輯,如數據選擇器,敏感信號沒有時鐘,定義了reg Mux,最終生成電路為組合邏輯。
module top(a, b, c, d, sel, Mux);
input a ;
input b ;
input c ;
input d ;
input[1:0] sel ;
outputreg Mux ;
always@(sel or a or b or c or d)
begin
case(sel)
2‘b00: Mux = a ;
2’b01: Mux = b ;
2‘b10: Mux = c ;
2’b11: Mux = d ;
endcase
end
endmodule
3.Memory型
可以用memory類型來定義RAM,ROM等存儲器,其結構為reg [n-1:0] 存儲器名[m-1:0],意義為m個n位寬度的寄存器。例如,reg [7:0] ram [255:0]表示定義了256個8位寄存器,256也即是存儲器的深度,8為數據寬度。
運算符
運算符可分為以下幾類:
1. 算術運算符(+,-,*,/,%)
2. 賦值運算符(=, 3. 關系運算符(》,=, 4. 邏輯運算符(&&,||,!)
5. 條件運算符(?:)
6. 位運算符(~,|,^,&,^~)
7. 移位運算符(》)
8. 拼接運算符({ })
算術運算符
“+”(加法運算符),”-“(減法運算符),”*”(乘法運算符),”/”(除法運算符,如7/3 =2),“%”(取模運算符,也即求余數,如7%3=1,余數為1)
賦值運算符
“=”阻塞賦值,”
代碼如下:激勵文件如下
module top(din,a,b,c,clk);
input din;
input clk;
outputreg a,b,c;
always@(posedge clk)
begin
a = din;
b = a;
c = b;
end
endmodule
`timescale1 ns/1 ns
module top_tb();
reg din ;
reg clk ;
wire a,b,c ;
initial
begin
din =0;
clk =0;
forever
begin
#({$random}%100)
din =~din ;
end
end
always#10 clk =~clk ;
top t0(.din(din),.a(a),.b(b),.c(c),.clk(clk));
endmodule
可以從仿真結果看到,在clk的上升沿,a的值等于din,并立即賦給b,b的值賦給c。
如果改為非阻塞賦值,仿真結果如下,在clk上升沿,a的值沒有立即賦值給b,b為a原來的值,同樣,c為b原來的值
可以從兩者的RTL圖看出明顯不同:
阻塞賦值RTL圖非阻塞賦值RTL圖
一般情況下,在時序邏輯電路中使用非阻塞賦值,可避免仿真時出現競爭冒險現象;在組合邏輯中使用阻塞賦值,執行賦值語句后立即改變;在assign語句中必須用阻塞賦值。
關系運算符
用于表示兩個操作數之間的關系,如a》b,a If (a》=b) q else q
邏輯運算符
“&&”(兩個操作數邏輯與),”||”(兩個操作數邏輯或),”!”(單個操作數邏輯非)例如:
If (a》b && c b并且c
條件運算符
“?:”為條件判斷,類似于if else,例如assign a = (i》8)?1’b1:1’b0 ;判斷i的值是否大于8,如果大于8則a的值為1,否則為0。
位運算符
“~”按位取反,”|”按位或,”^”按位異或,”&”按位與,”^”按位同或,除了”~”只需要一個操作數外,其他幾個都需要兩個操作數,如a&b,a|b。具體應用在后面的組合邏輯一節中有講解。
移位運算符
“》”右移位運算符,如a》2,向右移兩位。
拼接運算符
“{ }”拼接運算符,將多個信號按位拼接,如{a[3:0], b[1:0]},將a的低4位,b的低2位拼接成6位數據。另外,{n{a[3:0]}}表示將n個a[3:0]拼接,{n{1’b0}}表示n位的0拼接。如{8{1’b0}}表示為8’b0000_0000.
優先級別
各種運算符的優先級別如下:
組合邏輯
本節主要介紹組合邏輯,組合邏輯電路的特點是任意時刻的輸出僅僅取決于輸入信號,輸入信號變化,輸出立即變化,不依賴于時鐘。
與門
在verilog中以“&”表示按位與,如c=a&b,真值表如下,在a和b都等于1時結果才為1,RTL表示如右圖
代碼實現如下:激勵文件如下:
module top(a, b, c);
input a ;
input b ;
output c ;
assign c = a & b ;
endmodule
`timescale1 ns/1 ns
module top_tb();
reg a ;
reg b ;
wire c ;
initial
begin
a =0;
b =0;
forever
begin
#({$random}%100)
a =~a ;
#({$random}%100)
b =~b ;
end
end
top t0(.a(a),.b(b),.c(c));
endmodule
仿真結果如下:
如果a和b的位寬大于1,例如定義input [3:0] a, input [3:0]b,那么a&b則指a與b的對應位相與。如a[0]&b[0],a[1]&b[1]。
或門
在verilog中以“|”表示按位或,如c = a|b , 真值表如下,在a和b都為0時結果才為0。
代碼實現如下:激勵文件如下
module top(a, b, c);
input a ;
input b ;
output c ;
assign c = a | b ;
endmodule
`timescale1 ns/1 ns
module top_tb();
reg a ;
reg b ;
wire c ;
initial
begin
a =0;
b =0;
forever
begin
#({$random}%100)
a =~a ;
#({$random}%100)
b =~b ;
end
end
top t0(.a(a),.b(b),.c(c));
endmodule
仿真結果如下:
同理,位寬大于1,則是按位或。
非門
在verilog中以“~”表示按位取反,如b=~a,真值表如下,b等于a的相反數。
代碼實現如下:激勵文件如下:
module top(a, b);
input a ;
output b ;
assign b =~a ;
endmodule
`timescale1 ns/1 ns
module top_tb();
reg a ;
wire b ;
initial
begin
a =0;
forever
begin
#({$random}%100)
a =~a ;
end
end
top t0(.a(a),.b(b));
endmodule
仿真結果如如下:
異或
在verilog中以“^”表示異或,如c= a^b ,真值表如下,當a和b相同時,輸出為0。
代碼實現如下:激勵文件如下:
module top(a, b, c);
input a ;
input b ;
output c ;
assign c = a ^ b ;
endmodule
`timescale1 ns/1 ns
module top_tb();
reg a ;
reg b ;
wire c ;
initial
begin
a =0;
b =0;
forever
begin
#({$random}%100)
a =~a ;
#({$random}%100)
b =~b ;
end
end
top t0(.a(a),.b(b),.c(c));
endmodule
仿真結果如下:
在verilog中以大于“》”,等于”==”,小于”=”,小于等于” b ;表示如果a大于b,那么c的值就為1,否則為0。真值表如下:
代碼實現如下:激勵文件如下:
module top(a, b, c);
input a ;
input b ;
output c ;
assign c = a 》 b ;
endmodule
`timescale1 ns/1 ns
module top_tb();
reg a ;
reg b ;
wire c ;
initial
begin
a =0;
b =0;
forever
begin
#({$random}%100)
a =~a ;
#({$random}%100)
b =~b ;
end
end
top t0(.a(a),.b(b),.c(c));
endmodule
仿真結果如下:
半加器
半加器和全加器是算術運算電路中的基本單元,由于半加器不考慮從低位來的進位,所以稱之為半加器,sum表示相加結果,count表示進位,真值表可表示如下:
可根據真值表寫出代碼如下:激勵文件如下:
module top(a, b, sum, count);
input a ;
input b ;
output sum ;
output count ;
assign sum = a ^ b ;
assign count = a & b ;
endmodule
`timescale1 ns/1 ns
module top_tb();
reg a ;
reg b ;
wire sum ;
wire count ;
initial
begin
a =0;
b =0;
forever
begin
#({$random}%100)
a =~a ;
#({$random}%100)
b =~b ;
end
end
top t0(.a(a),.b(b),
.sum(sum),.count(count));
endmodule
仿真結果如下:
全加器
而全加器需要加上低位來的進位信號cin,真值表如下:
代碼如下:激勵文件如下:
module top(cin, a, b, sum, count);
input cin ;
input a ;
input b ;
output sum ;
output count ;
assign{count,sum}= a + b + cin ;
endmodule
`timescale1 ns/1 ns
module top_tb();
reg a ;
reg b ;
reg cin ;
wire sum ;
wire count ;
initial
begin
a =0;
b =0;
cin =0;
forever
begin
#({$random}%100)
a =~a ;
#({$random}%100)
b =~b ;
#({$random}%100)
cin =~cin ;
end
end
top t0(.cin(cin),.a(a),.b(b),
.sum(sum),.count(count));
endmodule
仿真結果如下:
乘法器
乘法的表示也很簡單,利用”*”即可,如a*b,舉例代碼如下:
module top(a, b, c);
input[1:0] a ;
input[1:0] b ;
output[3:0] c ;
assign c = a * b ;
endmodule
`timescale1 ns/1 ns
module top_tb();
reg[1:0]a ;
reg[1:0]b ;
wire[3:0]c ;
initial
begin
a =0;
b =0;
forever
begin
#({$random}%100)
a =~a ;
#({$random}%100)
b =~b ;
end
end
top t0(.a(a),.b(b),.c(c));
endmodule
仿真結果如下:
數據選擇器
在verilog中經常會用到數據選擇器,通過選擇信號,選擇不同的輸入信號輸出到輸出端,如下圖真值表,四選一數據選擇器,sel[1:0]為選擇信號,a,b,c,d為輸入信號,Mux為輸出信號。
代碼如下:激勵文件如下:
module top(a, b, c, d, sel, Mux);
input a ;
input b ;
input c ;
input d ;
input[1:0] sel ;
outputreg Mux ;
always@(sel or a or b or c or d)
begin
case(sel)
2‘b00: Mux = a ;
2’b01: Mux = b ;
2‘b10: Mux = c ;
2’b11: Mux = d ;
endcase
end
endmodule
`timescale1 ns/1 ns
module top_tb();
reg a ;
reg b ;
reg c ;
reg d ;
reg[1:0] sel ;
wire Mux ;
initial
begin
a =0;
b =0;
c =0;
d =0;
forever
begin
#({$random}%100)
a ={$random}%3;
#({$random}%100)
b ={$random}%3;
#({$random}%100)
c ={$random}%3;
#({$random}%100)
d ={$random}%3;
end
end
initial
begin
sel =2‘b00;
#2000 sel =2’b01;
#2000 sel =2‘b10;
#2000 sel =2’b11;
end
top
t0(.a(a),.b(b),.c(c),.d(d),.sel(sel),
.Mux(Mux));
endmodule
仿真結果如下
3-8譯碼器
3-8譯碼器是一個很常用的器件,其真值表如下所示,根據A2,A1,A0的值,得出不同的結果。
代碼如下:激勵文件如下:
module top(addr, decoder);
input[2:0] addr ;
outputreg[7:0] decoder ;
always@(addr)
begin
case(addr)
3‘b000: decoder =8’b1111_1110;
3‘b001: decoder =8’b1111_1101;
3‘b010: decoder =8’b1111_1011;
3‘b011: decoder =8’b1111_0111;
3‘b100: decoder =8’b1110_1111;
3‘b101: decoder =8’b1101_1111;
3‘b110: decoder =8’b1011_1111;
3‘b111: decoder =8’b0111_1111;
endcase
end
endmodule
`timescale1 ns/1 ns
module top_tb();
reg[2:0] addr ;
wire[7:0] decoder ;
initial
begin
addr =3‘b000;
#2000 addr =3’b001;
#2000 addr =3‘b010;
#2000 addr =3’b011;
#2000 addr =3‘b100;
#2000 addr =3’b101;
#2000 addr =3‘b110;
#2000 addr =3’b111;
end
top
t0(.addr(addr),.decoder(decoder));
endmodule
仿真結果如下:
三態門
在FPGA使用中,經常會用到雙向IO,需要用到三態門,如bio = en? din: 1’bz ;其中en為使能信號,用于打開關閉三態門,下面的RTL圖即是實現了雙向IO,可參考代碼。激勵文件實現兩個雙向IO的對接。
module top(en, din, dout, bio);
input din ;
input en ;
output dout ;
inout bio ;
assign bio = en? din :1‘bz;
assign dout = bio ;
endmodule
`timescale1 ns/1 ns
module top_tb();
reg en0 ;
reg din0 ;
wire dout0 ;
reg en1 ;
reg din1 ;
wire dout1 ;
wire bio ;
initial
begin
din0 =0;
din1 =0;
forever
begin
#({$random}%100)
din0 =~din0 ;
#({$random}%100)
din1 =~din1 ;
end
end
initial
begin
en0 =0;
en1 =1;
#100000
en0 =1;
en1 =0;
end
top
t0(.en(en0),.din(din0),.dout(dout0),.bi
o(bio));
top
t1(.en(en1),.din(din1),.dout(dout1),.bi
o(bio));
endmodule
激勵文件結構如下圖
仿真結果如下,en0為0,en1為1時,1通道打開,雙向IO bio就等于1通道的din1,1通道向外發送數據,0通道接收數據,dout0等于bio;當en0為1,en1為0時,0通道打開,雙向IO bio就等于0通道的din0,0通道向外發送數據,1通道接收數據,dout1等于bio
時序邏輯
組合邏輯電路在邏輯功能上特點是任意時刻的輸出僅僅取決于當前時刻的輸入,與電路原來的狀態無關。而時序邏輯在邏輯功能上的特點是任意時刻的輸出不僅僅取決于當前的輸入信號,而且還取決于電路原來的狀態。下面以典型的時序邏輯分析。
D觸發器
D觸發器在時鐘的上升沿或下降沿存儲數據,輸出與時鐘跳變之前輸入信號的狀態相同。
代碼如下激勵文件如下
module top(d, clk, q);
input d ;
input clk ;
outputreg q ;
always@(posedge clk)
begin
q 《= d ;
end
endmodule
`timescale1 ns/1 ns
module top_tb();
reg d ;
reg clk ;
wire q ;
initial
begin
d =0;
clk =0;
forever
begin
#({$random}%100)
d =~d ;
end
end
always#10 clk =~clk ;
top t0(.d(d),.clk(clk),.q(q));
endmodule
RTL圖表示如下
仿真結果如下,可以看到在t0時刻時,d的值為0,則q的值也為0;在t1時刻d發生了變化,值為1,那么q相應也發生了變化,值變為1。可以看到在t0-t1之間的一個時鐘周期內,無論輸入信號d的值如何變化,q的值是保持不變的,也就是有存儲的功能,保存的值為在時鐘的跳變沿時d的值。
兩級D觸發器
軟件是按照兩級D觸發器的模型進行時序分析的,具體可以分析在同一時刻兩個D觸發器輸出的數據有何不同,其RTL圖如下:
代碼如下:激勵文件如下:
module top(d, clk, q, q1);
input d ;
input clk ;
outputreg q ;
outputreg q1 ;
always@(posedge clk)
begin
q 《= d ;
end
always@(posedge clk)
begin
q1 《= q ;
end
endmodule
`timescale1 ns/1 ns
module top_tb();
reg d ;
reg clk ;
wire q ;
wire q1 ;
initial
begin
d =0;
clk =0;
forever
begin
#({$random}%100)
d =~d ;
end
end
always#10 clk =~clk ;
top
t0(.d(d),.clk(clk),.q(q),.q1(q1));
endmodule
仿真結果如下,可以看到t0時刻,d為0,q輸出為0,t1時刻,q隨著d的數據變化而變化,而此時鐘跳變之前q的值仍為0,那么q1的值仍為0,t2時刻,時鐘跳變前q的值為1,則q1的值相應為1,q1相對于q落后一個周期。
帶異步復位的D觸發器
異步復位是指獨立于時鐘,一旦異步復位信號有效,就觸發復位操作。這個功能在寫代碼時會經常用到,用于給信號復位,初始化。其RTL圖如下:
代碼如下,注意要把異步復位信號放在敏感列表里,如果是低電平復位,即為negedge,如果是高電平復位,則是posedge
module top(d, rst, clk, q);
input d ;
input rst ;
input clk ;
outputreg q ;
always@(posedge clk ornegedge rst)
begin
if(rst ==1’b0)
q 《=0;
else
q 《= d ;
end
endmodule
`timescale1 ns/1 ns
module top_tb();
reg d ;
reg rst ;
reg clk ;
wire q ;
initial
begin
d =0;
clk =0;
forever
begin
#({$random}%100)
d =~d ;
end
end
initial
begin
rst =0;
#200 rst =1;
end
always#10 clk =~clk ;
top
t0(.d(d),.rst(rst),.clk(clk),.q(q));
endmodule
仿真結果如下,可以看到在復位信號之前,雖然輸入信號d數據有變化,但由于正處于復位狀態,輸入信號q始終為0,在復位之后q的值就正常了。
帶異步復位同步清零的D觸發器
前面講到異步復位獨立于時鐘操作,而同步清零則是同步于時鐘信號下操作的,當然也不僅限于同步清零,也可以是其他的同步操作,其RTL圖如下:
代碼如下,不同于異步復位,同步操作不能把信號放到敏感列表里
module top(d, rst, clr, clk, q);
input d ;
input rst ;
input clr ;
input clk ;
outputreg q ;
always@(posedge clk ornegedge rst)
begin
if(rst ==1‘b0)
q 《=0;
elseif(clr ==1’b1)
q 《=0;
else
q 《= d ;
end
endmodule
`timescale1 ns/1 ns
module top_tb();
reg d ;
reg rst ;
reg clr ;
reg clk ;
wire q ;
initial
begin
d =0;
clk =0;
forever
begin
#({$random}%100)
d =~d ;
end
end
initial
begin
rst =0;
clr =0;
#200 rst =1;
#200 clr =1;
#100 clr =0;
end
always#10 clk =~clk ;
top
t0(.d(d),.rst(rst),.clr(clr),.clk(clk),
.q(q));
endmodule
仿真結果如下,可以看到clr信號拉高后,q沒有立即清零,而是在下個clk上升沿之后執行清零操作,也就是clr同步于clk。
移位寄存器
移位寄存器是指在每個時鐘脈沖來時,向左或向右移動一位,由于D觸發器的特性,數據輸出同步于時鐘邊沿,其結構如下,每個時鐘來臨,每個D觸發器的輸出q等于前一個D觸發器輸出的值,從而實現移位的功能。
代碼實現:
module top(d, rst, clk, q);
input d ;
input rst ;
input clk ;
outputreg[7:0] q ;
always@(posedge clk ornegedge rst)
begin
if(rst ==1‘b0)
q 《=0;
else
q 《={q[6:0], d};//向左移位
//q 《= {d, q[7:1]} ; //向右移位
end
endmodule
激勵文件:
`timescale1 ns/1 ns
module top_tb();
reg d ;
reg rst ;
reg clk ;
wire[7:0] q ;
initial
begin
d =0;
clk =0;
forever
begin
#({$random}%100)
d =~d ;
end
end
initial
begin
rst =0;
#200 rst =1;
end
always#10 clk =~clk ;
top
t0(.d(d),.rst(rst),.clk(clk),.q(q));
endmodule
仿真結果如下,可以看到復位之后,每個clk上升沿左移一位
單口RAM
單口RAM的寫地址與讀地址共用一個地址,代碼如下,其中reg [7:0] ram [63:0]意思是定義了64個8位寬度的數據。其中定義了addr_reg,可以保持住讀地址,延遲一周期之后將數據送出。
module top
(
input[7:0] data,
input[5:0] addr,
input wr,
input clk,
output[7:0] q
);
reg[7:0] ram[63:0];//declare ram
reg[5:0] addr_reg;//addr register
always@(posedge clk)
begin
if(wr)//write
ram[addr]《= data;
addr_reg 《= addr;
end
assign q = ram[addr_reg];//read data
endmodule
`timescale1 ns/1 ns
module top_tb();
reg[7:0] data ;
reg[5:0] addr ;
reg wr ;
reg clk ;
wire[7:0] q ;
initial
begin
data =0;
addr =0;
wr =1;
clk =0;
end
always#10 clk =~clk ;
always@(posedge clk)
begin
data 《= data +1’b1;
addr 《= addr +1‘b1;
end
top t0(.data(data),
.addr(addr),
.clk(clk),
.wr(wr),
.q(q));
endmodule
仿真結果如下,可以看到q的輸出與寫入的數據一致
偽雙口RAM
偽雙口RAM的讀寫地址是獨立的,可以隨機選擇寫或讀地址,同時進行讀寫操作。代碼如下,在激勵文件中定義了en信號,在其有效時發送讀地址。
module top
(
input[7:0] data,
input[5:0] write_addr,
input[5:0] read_addr,
input wr,
input rd,
input clk,
outputreg[7:0] q
);
reg[7:0] ram[63:0];//declare ram
reg[5:0] addr_reg;//addr register
always@(posedge clk)
begin
if(wr)//write
ram[write_addr]《= data;
if(rd)//read
q 《= ram[read_addr];
end
endmodule
`timescale1 ns/1 ns
module top_tb();
reg[7:0] data ;
reg[5:0] write_addr ;
reg[5:0] read_addr ;
reg wr ;
reg clk ;
reg rd ;
wire[7:0] q ;
initial
begin
data =0;
write_addr =0;
read_addr =0;
wr =0;
rd =0;
clk =0;
#100 wr =1;
#20 rd =1;
end
always#10 clk =~clk ;
always@(posedge clk)
begin
if(wr)
begin
data 《= data +1’b1;
write_addr 《= write_addr +1‘b1;
if(rd)
read_addr 《= read_addr +1’b1;
end
end
top t0(.data(data),
.write_addr(write_addr),
.read_addr(read_addr),
.clk(clk),
.wr(wr),
.rd(rd),
.q(q));
endmodule
仿真結果如下,可以看到在rd有效時,對讀地址進行操作,讀出數據
真雙口RAM
真雙口RAM有兩套控制線,數據線,允許兩個系統對其進行讀寫操作,代碼如下:
module top
(
input[7:0] data_a, data_b,
input[5:0] addr_a, addr_b,
input wr_a, wr_b,
input rd_a, rd_b,
input clk,
outputreg[7:0] q_a, q_b
);
reg[7:0] ram[63:0];//declare ram
//Port A
always@(posedge clk)
begin
if(wr_a)//write
begin
ram[addr_a]《= data_a;
q_a 《= data_a ;
end
if(rd_a)
//read
q_a 《= ram[addr_a];
end
//Port B
always@(posedge clk)
begin
if(wr_b)//write
begin
ram[addr_b]《= data_b;
q_b 《= data_b ;
end
if(rd_b)
//read
q_b 《= ram[addr_b];
end
endmodule
`timescale1 ns/1 ns
module top_tb();
reg[7:0] data_a, data_b ;
reg[5:0] addr_a, addr_b ;
reg wr_a, wr_b ;
reg rd_a, rd_b ;
reg clk ;
wire[7:0] q_a, q_b ;
initial
begin
data_a =0;
data_b =0;
addr_a =0;
addr_b =0;
wr_a =0;
wr_b =0;
rd_a =0;
rd_b =0;
clk =0;
#100 wr_a =1;
#100 rd_b =1;
end
always#10 clk =~clk ;
always@(posedge clk)
begin
if(wr_a)
begin
data_a 《= data_a +1‘b1;
addr_a 《= addr_a +1’b1;
end
else
begin
data_a 《=0;
addr_a 《=0;
end
end
always@(posedge clk)
begin
if(rd_b)
begin
addr_b 《= addr_b +1‘b1;
end
else addr_b 《=0;
end
top
t0(.data_a(data_a),.data_b(data_b),
.addr_a(addr_a),.addr_b(addr_b
),
.wr_a(wr_a),.wr_b(wr_b),
.rd_a(rd_a),.rd_b(rd_b),
.clk(clk),
.q_a(q_a),.q_b(q_b));
endmodule
仿真結果如下
單口ROM
ROM是用來存儲數據的,可以按照下列代碼形式初始化ROM,但這種方法處理大容量的ROM就比較麻煩,建議用FPGA自帶的ROM IP核實現,并添加初始化文件。
代碼實現
moduletop
(
input[3:0] addr,
input clk,
outputreg[7:0] q
);
always@(posedge clk)
begin
case(addr)
4’d0: q 《=8‘d15;
4’d1: q 《=8‘d24;
4’d2: q 《=8‘d100;
4’d3: q 《=8‘d78;
4’d4: q 《=8‘d98;
4’d5: q 《=8‘d105;
4’d6: q 《=8‘d86;
4’d7: q 《=8‘d254;
4’d8: q 《=8‘d76;
4’d9: q 《=8‘d35;
4’d10: q 《=8‘d120;
4’d11: q 《=8‘d85;
4’d12: q 《=8‘d37;
4’d13: q 《=8‘d19;
4’d14: q 《=8‘d22;
4’d15: q 《=8‘d67;
default: q 《=8’d0;
endcase
end
endmodule
`timescale1 ns/1 ns
module top_tb();
reg[3:0] addr ;
reg clk ;
wire[7:0] q ;
initial
begin
addr =0;
clk =0;
end
always#10 clk =~clk ;
always@(posedge clk)
begin
addr 《= addr +1‘b1;
end
top t0(.addr(addr),
.clk(clk),
.q(q));
endmodule
仿真結果如下
有限狀態機
在verilog里經常會用到有限狀態機,處理相對復雜的邏輯,設定好不同的狀態,根據觸發條件跳轉到對應的狀態,在不同的狀態下做相應的處理。有限狀態機主要用到always及case語句。下面以一個四狀態的有限狀態機舉例說明。
在程序中設計了8位的移位寄存器,在Idle狀態下,判斷shift_start信號是否為高,如果為高,進入Start狀態,在Start狀態延遲100個周期,進入Run狀態,進行移位處理,如果shift_stop信號有效了,進入Stop狀態,在Stop狀態,清零q的值,再跳轉到Idle狀態。
Mealy有限狀態機,輸出不僅與當前狀態有關,也與輸入信號有關,在RTL中會與輸入信號有連接。
module top
(
input shift_start,
input shift_stop,
input rst,
input clk,
input d,
outputreg[7:0] q
);
parameter Idle =2’d0;//Idle state
parameter Start =2‘d1;//Start state
parameter Run =2’d2;//Run state
parameter Stop =2‘d3;//Stop state
reg[1:0] state ;//statement
reg[4:0] delay_cnt ;//delay counter
always@(posedge clk ornegedge rst)
begin
if(!rst)
begin
state 《= Idle ;
delay_cnt 《=0;
q 《=0;
end
else
case(state)
Idle :begin
if(shift_start)
state 《= Start ;
end
Start :begin
if(delay_cnt ==5’d99)
begin
delay_cnt 《=0;
state 《= Run ;
end
else
delay_cnt 《= delay_cnt +1‘b1;
end
Run :begin
if(shift_stop)
state 《= Stop ;
else
q 《={q[6:0], d};
end
Stop :begin
q 《=0;
state 《= Idle ;
end
default: state 《= Idle ;
endcase
end
endmodule
Moore有限狀態機,輸出只與當前狀態有關,與輸入信號無關,輸入信號只影響狀態的改變,不影響輸出,比如對delay_cnt和q的處理,只與state狀態有關。
module top
(
input shift_start,
input shift_stop,
input rst,
input clk,
input d,
outputreg[7:0] q
);
parameter Idle =2’d0;//Idle state
parameter Start =2‘d1;//Start state
parameter Run =2’d2;//Run state
parameter Stop =2‘d3;//Stop state
reg[1:0] current_state ;//statement
reg[1:0] next_state ;
reg[4:0] delay_cnt ;//delay counter
//First part: statement transition
always@(posedge clk ornegedge rst)
begin
if(!rst)
current_state 《= Idle ;
else
current_state 《= next_state ;
end
//Second part: combination logic, judge statement transition condition
always@(*)
begin
case(current_state)
Idle :begin
if(shift_start)
next_state 《= Start ;
else
next_state 《= Idle ;
end
Start :begin
if(delay_cnt ==5’d99)
next_state 《= Run ;
else
next_state 《= Start ;
end
Run :begin
if(shift_stop)
next_state 《= Stop ;
else
next_state 《= Run ;
end
Stop : next_state 《= Idle ;
default:next_state 《= Idle ;
endcase
end
//Last part: output data
always@(posedge clk ornegedge rst)
begin
if(!rst)
delay_cnt 《=0;
elseif(current_state == Start)
delay_cnt 《= delay_cnt +1‘b1;
else
delay_cnt 《=0;
end
always@(posedge clk ornegedge rst)
begin
if(!rst)
q 《=0;
elseif(current_state == Run)
q 《={q[6:0], d};
else
q 《=0;
end
endmodule
在上面兩個程序中用到了兩種方式的寫法,第一種的Mealy狀態機,采用了一段式的寫法,只用了一個always語句,所有的狀態轉移,判斷狀態轉移條件,數據輸出都在一個always語句里,缺點是如果狀態太多,會使整段程序顯的冗長。第二個Moore狀態機,采用了三段式的寫法,狀態轉移用了一個always語句,判斷狀態轉移條件是組合邏輯,采用了一個always語句,數據輸出也是單獨的 always語句,這樣寫起來比較直觀清晰,狀態很多時也不會顯得繁瑣。
Mealy有限狀態機RTL圖
Moore有限狀態機RTL圖
激勵文件如下:
`timescale1 ns/1 ns
module top_tb();
reg shift_start ;
reg shift_stop ;
reg rst ;
reg clk ;
reg d ;
wire[7:0] q ;
initial
begin
rst =0;
clk =0;
d =0;
#200 rst =1;
forever
begin
#({$random}%100)
d =~d ;
end
end
initial
begin
shift_start =0;
shift_stop =0;
#300 shift_start =1;
#1000 shift_start =0;
shift_stop =1;
#50 shift_stop =0;
end
always#10 clk =~clk ;
top t0
(
.shift_start(shift_start),
.shift_stop(shift_stop),
.rst(rst),
.clk(clk),
.d(d),
.q(q)
);
endmodule
仿真結果如下:
總結
本文檔介紹了組合邏輯以及時序邏輯中常用的模塊,其中有限狀態機較為復雜,但經常用到,希望大家能夠深入理解,在代碼中多運用,多思考,有利于快速提升水平。
審核編輯:何安
-
FPGA
+關注
關注
1629文章
21729瀏覽量
603044
發布評論請先 登錄
相關推薦
評論