【ZYNQ Ultrascale+ MPSOC FPGA教程】第三章 Verilog基礎模塊介紹

原創聲明:

本原創教程由芯驛電子科技(上海)有限公司(ALINX)創做,版權歸本公司全部,如需轉載,需受權並註明出處。node

適用於板卡型號:

AXU2CGA/AXU2CGB/AXU3EG/AXU4EV-E/AXU4EV-P/AXU5EV-E/AXU5EV-P /AXU9EG/AXU15EG網絡

簡介

本文主要介紹verilog基礎模塊,夯實基礎,對深刻學習FPGA會有很大幫助。dom

數據類型

常量異步

整數:整數能夠用二進制b或B,八進制o或O,十進制d或D,十六進制h或H表示,例如, 8’b00001111表示8位位寬的二進制整數,4’ha表示4位位寬的十六進制整數。學習

X和Z:X表明不定值,z表明高阻值,例如,5’b00x11,第三位不定值,3’b00z表示最低位爲高阻值。spa

下劃線:在位數過長時能夠用來分割位數,提升程序可讀性,如8’b0000_1111設計

參數parameter: parameter能夠用標識符定義常量,運用時只使用標識符便可,提升可讀性及維護性,如定義parameter width = 8 ; 定義寄存器reg [width-1:0] a; 即定義了8位寬度的寄存器。3d

參數的傳遞:在一個模塊中若是有定義參數,在其餘模塊調用此模塊時能夠傳遞參數,並能夠修改參數,以下所示,在module後用#()表示。code

例如定義模塊以下調用模塊orm

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)

賦值運算符

「=」阻塞賦值,」<=」非阻塞賦值。阻塞賦值爲執行完一條賦值語句,再執行下一條,可理解爲順序執行,並且賦值是當即執行;非阻塞賦值可理解爲並行執行,不考慮順序,在always塊語句執行完成後,才進行賦值。以下面的阻塞賦值:

代碼以下:激勵文件以下

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<b,多用於判斷條件,例如:

If (a>=b) q <=1’b1 ;
else q <= 1’b0 ;表示若是a的值大於等於b的值,則q的值爲1,不然q的值爲0
  • 邏輯運算符

「&&」(兩個操做數邏輯與),」||」(兩個操做數邏輯或),」!」(單個操做數邏輯非)例如:

If (a>b && c <d) 表示條件爲a>b而且c<d; if (!a)表示條件爲a的值不爲1,也就是0。

  • 條件運算符

「?:」爲條件判斷,相似於if else,例如assign a = (i>8)?1’b1:1’b0 ;判斷i的值是否大於8,若是大於8則a的值爲1,不然爲0。

  • 位運算符

「~」按位取反,」|」按位或,」^」按位異或,」&」按位與,」^」按位同或,除了」~」只須要一個操做數外,其餘幾個都須要兩個操做數,如a&b,a|b。具體應用在後面的組合邏輯一節中有講解。

  • 移位運算符

「<<」左移位運算符,」>>」右移位運算符,如a<<1表示,向左移1位,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中以大於「>」,等於」==」,小於」<」,大於等於」>=」,小於等於」<=」,不等於」!=」表示,以大於舉例,如c= a > 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 

仿真結果以下:

總結

本文檔介紹了組合邏輯以及時序邏輯中經常使用的模塊,其中有限狀態機較爲複雜,但常常用到,但願你們可以深刻理解,在代碼中多運用,多思考,有利於快速提高水平。

相關文章
相關標籤/搜索