Verilog:基礎(chǔ)語法(下)

Verilog:基礎(chǔ)語法(上)

模塊與端口

  • 關(guān)鍵詞:模塊,端口饭冬,雙向端口嘀韧,PAD

結(jié)構(gòu)建模方式有 3 類描述語句: Gate(門級(jí))例化語句秤标,UDP (用戶定義原語)例化語句和 module (模塊) 例化語句绝淡。本次主要講述使用最多的模塊級(jí)例化語句。

  • 模塊
    模塊是 Verilog 中基本單元的定義形式苍姜,是與外界交互的接口牢酵。

模塊格式定義如下:

module module_name 
#(parameter_list)
(port_list) ;
              Declarations_and_Statements ;
endmodule

模塊定義必須以關(guān)鍵字 module 開始,以關(guān)鍵字 endmodule 結(jié)束衙猪。

模塊名馍乙,端口信號(hào)布近,端口聲明和可選的參數(shù)聲明等,出現(xiàn)在設(shè)計(jì)使用的 Verilog 語句(圖中 Declarations_and_Statements)之前潘拨。

模塊內(nèi)部有可選的 5 部分組成吊输,分別是變量聲明,數(shù)據(jù)流語句铁追,行為級(jí)語句季蚂,低層模塊例化及任務(wù)和函數(shù),如下圖表示琅束。這 5 部分出現(xiàn)順序扭屁、出現(xiàn)位置都是任意的。但是涩禀,各種變量都應(yīng)在使用之前聲明料滥。變量具體聲明的位置不要求,但必須保證在使用之前的位置艾船。


  • 端口
    端口是模塊與外界交互的接口葵腹。對(duì)于外部環(huán)境來說,模塊內(nèi)部是不可見的屿岂,對(duì)模塊的調(diào)用只能通過端口連接進(jìn)行践宴。

  • 端口列表

模塊的定義中包含一個(gè)可選的端口列表,一般將不帶類型爷怀、不帶位寬的信號(hào)變量羅列在模塊聲明里阻肩。下面是一個(gè) PAD 模型的端口列表:

module pad(
    DIN, OEN, PULL,
    DOUT, PAD);

一個(gè)模塊如果和外部環(huán)境沒有交互,則可以不用聲明端口列表运授。例如之前我們仿真時(shí) test.sv 文件中的 test 模塊都沒有聲明具體端口烤惊。

module test ;  //直接分號(hào)結(jié)束
    ......     //數(shù)據(jù)流或行為級(jí)描述
endmodule
  • 端口聲明

(1) 端口信號(hào)在端口列表中羅列出來以后,就可以在模塊實(shí)體中進(jìn)行聲明了吁朦。

根據(jù)端口的方向柒室,端口類型有 3 種: 輸入(input),輸出(output)和雙向端口(inout)喇完。

input伦泥、inout 類型不能聲明為 reg 數(shù)據(jù)類型,因?yàn)?reg 類型是用于保存數(shù)值的锦溪,而輸入端口只能反映與其相連的外部信號(hào)的變化,不能保存這些信號(hào)的值府怯。

output 可以聲明為 wire 或 reg 數(shù)據(jù)類型刻诊。

上述例子中 pad 模塊的端口聲明,在 module 實(shí)體中就可以表示如下:

//端口類型聲明
input        DIN, OEN ;
input [1:0]  PULL ;  //(00,01-dispull, 11-pullup, 10-pulldown)
inout        PAD ;   //pad value
output       DOUT ;  //pad load when pad configured as input

//端口數(shù)據(jù)類型聲明
wire         DIN, OEN ;
wire  [1:0]  PULL ;
wire         PAD ;
reg          DOUT ;

(2) 在 Verilog 中牺丙,端口隱式的聲明為 wire 型變量则涯,即當(dāng)端口具有 wire 屬性時(shí)复局,不用再次聲明端口類型為 wire 型。但是粟判,當(dāng)端口有 reg 屬性時(shí)亿昏,則 reg 聲明不可省略。

上述例子中的端口聲明档礁,則可以簡(jiǎn)化為:

//端口類型聲明
input        DIN, OEN ;
input [1:0]  PULL ;    
inout        PAD ;    
output       DOUT ;    
reg          DOUT ;

(3) 當(dāng)然角钩,信號(hào) DOUT 的聲明完全可以合并成一句:

output reg      DOUT ;

(4) 還有一種更簡(jiǎn)潔且常用的方法來聲明端口,即在 module 聲明時(shí)就陳列出端口及其類型呻澜。reg 型端口要么在 module 聲明時(shí)聲明递礼,要么在 module 實(shí)體中聲明,例如以下 2 種寫法是等效的羹幸。

module pad(
    input        DIN, OEN ,
    input [1:0]  PULL ,
    inout        PAD ,
    output reg   DOUT
    );
 
module pad(
    input        DIN, OEN ,
    input [1:0]  PULL ,
    inout        PAD ,
    output       DOUT
    );
 
    reg        DOUT ;

模塊例化

  • 關(guān)鍵字:例化脊髓,generate,全加器栅受,層次訪問
    在一個(gè)模塊中引用另一個(gè)模塊将硝,對(duì)其端口進(jìn)行相關(guān)連接,叫做模塊例化屏镊。模塊例化建立了描述的層次依疼。信號(hào)端口可以通過位置或名稱關(guān)聯(lián),端口連接也必須遵循一些規(guī)則闸衫。

  • 命名端口連接
    這種方法將需要例化的模塊端口與外部信號(hào)按照其名字進(jìn)行連接涛贯,端口順序隨意,可以與引用 module 的聲明端口順序不一致蔚出,只要保證端口名字與外部信號(hào)匹配即可弟翘。

下面是例化一次 1bit 全加器的例子:

full_adder1  u_adder0(
    .Ai     (a[0]),
    .Bi     (b[0]),
    .Ci     (c==1'b1 ? 1'b0 : 1'b1),
    .So     (so_bit0),
    .Co     (co_temp[0]));

如果某些輸出端口并不需要在外部連接,例化時(shí) 可以懸空不連接骄酗,甚至刪除稀余。一般來說,input 端口在例化時(shí)不能刪除趋翻,否則編譯報(bào)錯(cuò)睛琳,output 端口在例化時(shí)可以刪除踏烙。例如:

//output 端口 Co 懸空
full_adder1  u_adder0(
    .Ai     (a[0]),
    .Bi     (b[0]),
    .Ci     (c==1'b1 ? 1'b0 : 1'b1),
    .So     (so_bit0),
    .Co     ());
 
//output 端口 Co 刪除
full_adder1  u_adder0(
    .Ai     (a[0]),
    .Bi     (b[0]),
    .Ci     (c==1'b1 ? 1'b0 : 1'b1),
    .So     (so_bit0));
  • 順序端口連接
    這種方法將需要例化的模塊端口按照模塊聲明時(shí)端口的順序與外部信號(hào)進(jìn)行匹配連接,位置要嚴(yán)格保持一致黍少。例如例化一次 1bit 全加器的代碼可以改為:
full_adder1  u_adder1(
    a[1], b[1], co_temp[0], so_bit1, co_temp[1]);

雖然代碼從書寫上可能會(huì)占用相對(duì)較少的空間菩掏,但代碼可讀性降低砸紊,也不易于調(diào)試。有時(shí)候在大型的設(shè)計(jì)中可能會(huì)有很多個(gè)端口系草,端口信號(hào)的順序時(shí)不時(shí)的可能也會(huì)有所改動(dòng)廊酣,此時(shí)再利用順序端口連接進(jìn)行模塊例化,顯然是不方便的戒职。所以平時(shí)乳乌,建議采用命名端口方式對(duì)模塊進(jìn)行例化。

  • 端口連接規(guī)則
  • 輸入端口

模塊例化時(shí)其弊,從模塊外部來講仰担, input 端口可以連接 wire 或 reg 型變量。這與模塊聲明是不同的拌滋,從模塊內(nèi)部來講,input 端口必須是 wire 型變量。

  • 輸出端口

模塊例化時(shí),從模塊外部來講铸敏,output 端口必須連接 wire 型變量桩撮。這與模塊聲明是不同的,從模塊內(nèi)部來講旱爆,output 端口可以是 wire 或 reg 型變量脆烟。

  • 輸入輸出端口

模塊例化時(shí)拜鹤,從模塊外部來講,inout 端口必須連接 wire 型變量。這與模塊聲明是相同的。

  • 懸空端口

模塊例化時(shí)咙边,如果某些信號(hào)不需要與外部信號(hào)進(jìn)行連接交互市殷,我們可以將其懸空带迟,即端口例化處保留空白即可,上述例子中有提及翠语。

output 端口正常懸空時(shí)啡专,我們甚至可以在例化時(shí)將其刪除。

input 端口正常懸空時(shí)制圈,懸空信號(hào)的邏輯功能表現(xiàn)為高阻狀態(tài)(邏輯值為 z)。但是畔况,例化時(shí)一般不能將懸空的 input 端口刪除鲸鹦,否則編譯會(huì)報(bào)錯(cuò)眯停,例如:

//下述代碼編譯會(huì)報(bào)Warning
full_adder4  u_adder4(
    .a      (a),
    .b      (b),
    .c      (),
    .so     (so),
    .co     (co));
//如果模塊full_adder4有input端口c慎宾,則下述代碼編譯是會(huì)報(bào)Error
full_adder4  u_adder4(
    .a      (a),
    .b      (b),
    .so     (so),
    .co     (co));

一般來說泼舱,建議 input 端口不要做懸空處理铃诬,無其他外部連接時(shí)賦值其常量闸迷,例如:

full_adder4  u_adder4(
    .a      (a),
    .b      (b),
    .c      (1'b0),
    .so     (so),
    .co     (co));
  • 位寬匹配

當(dāng)例化端口與連續(xù)信號(hào)位寬不匹配時(shí)妖谴,端口會(huì)通過無符號(hào)數(shù)的右對(duì)齊或截?cái)喾绞竭M(jìn)行匹配挪哄。

假如在模塊 full_adder4 中,端口 a 和端口 b 的位寬都為 4bit,則下面代碼的例化結(jié)果會(huì)導(dǎo)致:

u_adder4.a = {2'bzz, a[1:0]}, u_adder4.b = b[3:0] 舰褪。
full_adder4  u_adder4(
    .a      (a[1:0]),      //input a[3:0]
    .b      (b[5:0]),      //input b[3:0]
    .c      (1'b0),
    .so     (so),
    .co     (co));

端口連續(xù)信號(hào)類型

連接端口的信號(hào)類型可以是占拍,1)標(biāo)識(shí)符折汞,2)位選擇暴备,3)部分選擇悠瞬,4)上述類型的合并,5)用于輸入端口的表達(dá)式涯捻。

當(dāng)然浅妆,信號(hào)名字可以與端口名字一樣,但他們的意義是不一樣的障癌,分別代表的是 2 個(gè)模塊內(nèi)的信號(hào)凌外。

用 generate 進(jìn)行模塊例化
當(dāng)例化多個(gè)相同的模塊時(shí),一個(gè)一個(gè)的手動(dòng)例化會(huì)比較繁瑣涛浙。用 generate 語句進(jìn)行多個(gè)模塊的重復(fù)例化康辑,可大大簡(jiǎn)化程序的編寫過程摄欲。

重復(fù)例化 4 個(gè) 1bit 全加器組成一個(gè) 4bit 全加器的代碼如下:

module full_adder4(
    input [3:0]   a ,   //adder1
    input [3:0]   b ,   //adder2
    input         c ,   //input carry bit
 
    output [3:0]  so ,  //adding result
    output        co    //output carry bit
    );
 
    wire [3:0]    co_temp ;
    //第一個(gè)例化模塊一般格式有所差異,需要單獨(dú)例化
    full_adder1  u_adder0(
        .Ai     (a[0]),
        .Bi     (b[0]),
        .Ci     (c==1'b1 ? 1'b1 : 1'b0),
        .So     (so[0]),
        .Co     (co_temp[0]));
 
    genvar        i ;
    generate
        for(i=1; i<=3; i=i+1) begin: adder_gen
        full_adder1  u_adder(
            .Ai     (a[i]),
            .Bi     (b[i]),
            .Ci     (co_temp[i-1]), //上一個(gè)全加器的溢位是下一個(gè)的進(jìn)位
            .So     (so[i]),
            .Co     (co_temp[i]));
        end
    endgenerate
 
    assign co    = co_temp[3] ;
 
endmodule
  • 層次訪問

每一個(gè)例化模塊的名字疮薇,每個(gè)模塊的信號(hào)變量等胸墙,都使用一個(gè)特定的標(biāo)識(shí)符進(jìn)行定義。在整個(gè)層次設(shè)計(jì)中按咒,每個(gè)標(biāo)識(shí)符都具有唯一的位置與名字迟隅。

Verilog 中,通過使用一連串的 . 符號(hào)對(duì)各個(gè)模塊的標(biāo)識(shí)符進(jìn)行層次分隔連接励七,就可以在任何地方通過指定完整的層次名對(duì)整個(gè)設(shè)計(jì)中的標(biāo)識(shí)符進(jìn)行訪問智袭。

層次訪問多見于仿真中。

例如掠抬,有以下層次設(shè)計(jì)吼野,則葉單元、子模塊和頂層模塊間的信號(hào)就可以相互訪問两波。

//u_n1模塊中訪問u_n3模塊信號(hào):
a = top.u_m2.u_n3.c ;

//u_n1模塊中訪問top模塊信號(hào)
if (top.p == 'b0) a = 1'b1 ;

//top模塊中訪問u_n4模塊信號(hào)
assign p = top.u_m2.u_n4.d ;

帶參數(shù)例化

  • 關(guān)鍵詞: defparam瞳步,參數(shù),例化腰奋,ram
    當(dāng)一個(gè)模塊被另一個(gè)模塊引用例化時(shí)谚攒,高層模塊可以對(duì)低層模塊的參數(shù)值進(jìn)行改寫。這樣就允許在編譯時(shí)將不同的參數(shù)傳遞給多個(gè)相同名字的模塊氛堕,而不用單獨(dú)為只有參數(shù)不同的多個(gè)模塊再新建文件。

參數(shù)覆蓋有 2 種方式:1)使用關(guān)鍵字 defparam野蝇,2)帶參數(shù)值模塊例化讼稚。

  • defparam 語句
    可以用關(guān)鍵字 defparam 通過模塊層次調(diào)用的方法,來改寫低層次模塊的參數(shù)值绕沈。

例如對(duì)一個(gè)單口地址線和數(shù)據(jù)線都是 4bit 寬度的 ram 模塊的 MASK 參數(shù)進(jìn)行改寫:

//instantiation
defparam     u_ram_4x4.MASK = 7 ;
ram_4x4    u_ram_4x4
    (
        .CLK    (clk),
        .A      (a[4-1:0]),
        .D      (d),
        .EN     (en),
        .WR     (wr),    //1 for write and 0 for read
        .Q      (q)    );

ram_4x4 的模型如下:

module  ram_4x4
    (
     input               CLK ,
     input [4-1:0]       A ,
     input [4-1:0]       D ,
     input               EN ,
     input               WR ,    //1 for write and 0 for read
     output reg [4-1:0]  Q    );
 
    parameter        MASK = 3 ;
 
    reg [4-1:0]     mem [0:(1<<4)-1] ;
    always @(posedge CLK) begin
        if (EN && WR) begin
            mem[A]  <= D & MASK;
        end
        else if (EN && !WR) begin
            Q       <= mem[A] & MASK;
        end
    end
 
endmodule

對(duì)此進(jìn)行一個(gè)簡(jiǎn)單的仿真锐想,testbench 編寫如下:

`timescale 1ns/1ns
 
module test ;
    parameter    AW = 4 ;
    parameter    DW = 4 ;
 
    reg                  clk ;
    reg [AW:0]           a ;
    reg [DW-1:0]         d ;
    reg                  en ;
    reg                  wr ;
    wire [DW-1:0]        q ;
 
    //clock generating
    always begin
        #15 ;     clk = 0 ;
        #15 ;     clk = 1 ;
    end
 
    initial begin
        a         = 10 ;
        d         = 2 ;
        en        = 'b0 ;
        wr        = 'b0 ;
        repeat(10) begin
            @(negedge clk) ;
            en     = 1'b1;
            a      = a + 1 ;
            wr     = 1'b1 ;  //write command
            d      = d + 1 ;
        end
        a         = 10 ;
        repeat(10) begin
            @(negedge clk) ;
            a      = a + 1 ;
            wr     = 1'b0 ;  //read command
        end
    end // initial begin
 
    //instantiation
    defparam     u_ram_4x4.MASK = 7 ;
    ram_4x4    u_ram_4x4
    (
        .CLK    (clk),
        .A      (a[AW-1:0]),
        .D      (d),
        .EN     (en),
        .WR     (wr),    //1 for write and 0 for read
        .Q      (q)
     );
 
    //stop simulation
    initial begin
        forever begin
            #100;
            if ($time >= 1000)  $finish ;
        end
    end
 
endmodule // test
  • 帶參數(shù)模塊例化
    第二種方法就是例化模塊時(shí),將新的參數(shù)值寫入模塊例化語句乍狐,以此來改寫原有 module 的參數(shù)值赠摇。

例如對(duì)一個(gè)地址和數(shù)據(jù)位寬都可變的 ram 模塊進(jìn)行帶參數(shù)的模塊例化:

ram #(.AW(4), .DW(4))
    u_ram
    (
        .CLK    (clk),
        .A      (a[AW-1:0]),
        .D      (d),
        .EN     (en),
        .WR     (wr),    //1 for write and 0 for read
        .Q      (q)
     );

ram 模型如下:

module  ram
    #(  parameter       AW = 2 ,
        parameter       DW = 3 )
    (
        input                   CLK ,
        input [AW-1:0]          A ,
        input [DW-1:0]          D ,
        input                   EN ,
        input                   WR ,    //1 for write and 0 for read
        output reg [DW-1:0]     Q
     );
 
    reg [DW-1:0]         mem [0:(1<<AW)-1] ;
    always @(posedge CLK) begin
        if (EN && WR) begin
            mem[A]  <= D ;
        end
        else if (EN && !WR) begin
            Q       <= mem[A] ;
        end
    end
 
endmodule

區(qū)別與建議

(1) 和模塊端口實(shí)例化一樣,帶參數(shù)例化時(shí)浅蚪,也可以不指定原有參數(shù)名字藕帜,按順序進(jìn)行參數(shù)例化,例如 u_ram 的例化可以描述為:

ram #(4, 4) u_ram (......) ;
(2) 當(dāng)然惜傲,利用 defparam 也可以改寫模塊在端口聲明時(shí)聲明的參數(shù)洽故,利用帶參數(shù)例化也可以改寫模塊實(shí)體中聲明的參數(shù)。例如 u_ram 和 u_ram_4x4 的例化分別可以描述為:

defparam     u_ram.AW = 4 ;
defparam     u_ram.DW = 4 ;
ram   u_ram(......);
ram_4x4  #(.MASK(7))    u_ram_4x4(......);

(3) 那能不能混合使用這兩種模塊參數(shù)改寫的方式呢盗誊?當(dāng)然能时甚!前提是所有參數(shù)都是模塊在端口聲明時(shí)聲明的參數(shù)或參數(shù)都是模塊實(shí)體中聲明的參數(shù)隘弊,例如 u_ram 的聲明還可以表示為(模塊實(shí)體中參數(shù)可自行實(shí)驗(yàn)驗(yàn)證):

defparam     u_ram.AW = 4 ;
ram #(.DW(4)) u_ram (......);  

(4) 那如果一個(gè)模塊中既有在模塊在端口聲明時(shí)聲明的參數(shù)共苛,又有在模塊實(shí)體中聲明的參數(shù)搔耕,那這兩種參數(shù)還能同時(shí)改寫么雏婶?例如在 ram 模塊中加入 MASK 參數(shù)姑宽,模型如下:

module  ram
    #(  parameter       AW = 2 ,
        parameter       DW = 3 )
    (
        input                   CLK ,
        input [AW-1:0]          A ,
        input [DW-1:0]          D ,
        input                   EN ,
        input                   WR ,    //1 for write and 0 for read
        output reg [DW-1:0]     Q    );
 
    parameter            MASK = 3 ;
 
    reg [DW-1:0]         mem [0:(1<<AW)-1] ;
    always @(posedge CLK) begin
        if (EN && WR) begin
            mem[A]  <= D ;
        end
        else if (EN && !WR) begin
            Q       <= mem[A] ;
        end
    end
 
endmodule

此時(shí)再用 defparam 改寫參數(shù) MASK 值時(shí)魂毁,編譯報(bào) Error:

//都采用defparam時(shí)會(huì)報(bào)Error
defparam     u_ram.AW = 4 ;
defparam     u_ram.DW = 4 ;
defparam     u_ram.MASK = 7 ;
ram   u_ram  (......);
 
//模塊實(shí)體中parameter用defparam改寫也會(huì)報(bào)Error
defparam     u_ram.MASK = 7 ;
ram #(.AW(4), .DW(4))   u_ram (......);

重點(diǎn)來了W檀痢]锪跪妥!如果你用帶參數(shù)模塊例化的方法去改寫參數(shù) MASK 的值舅列,編譯不會(huì)報(bào)錯(cuò)肌割,MASK 也將被成功改寫!

ram #(.AW(4), .DW(4), .MASK(7)) u_ram (......);
可能的解釋為帐要,在編譯器看來把敞,如果有模塊在端口聲明時(shí)的參數(shù),那么實(shí)體中的參數(shù)將視為localparam 類型榨惠,使用 defparam 將不能改寫模塊實(shí)體中聲明的參數(shù)奋早。

也可能和編譯器有關(guān)系,大家也可以在其他編譯器上實(shí)驗(yàn)赠橙。

(5)建議耽装,對(duì)已有模塊進(jìn)行例化并將其相關(guān)參數(shù)進(jìn)行改寫時(shí),不要采用 defparam 的方法期揪。除了上述缺點(diǎn)外掉奄,defparam 一般也不可綜合。

(6)而且建議凤薛,模塊在編寫時(shí)姓建,如果預(yù)知將被例化且有需要改寫的參數(shù),都將這些參數(shù)寫入到模塊端口聲明之前的地方(用關(guān)鍵字井號(hào) # 表示)缤苫。這樣的代碼格式不僅有很好的可讀性速兔,而且方便調(diào)試。

函數(shù)

  • 關(guān)鍵詞:函數(shù)活玲,大小端轉(zhuǎn)換涣狗,數(shù)碼管譯碼
    在 Verilog 中,可以利用任務(wù)(關(guān)鍵字為 task)或函數(shù)(關(guān)鍵字為 function)舒憾,將重復(fù)性的行為級(jí)設(shè)計(jì)進(jìn)行提取镀钓,并在多個(gè)地方調(diào)用,來避免重復(fù)代碼的多次編寫珍剑,使代碼更加的簡(jiǎn)潔掸宛、易懂。 函數(shù)

函數(shù)只能在模塊中定義招拙,位置任意唧瘾,并在模塊的任何地方引用措译,作用范圍也局限于此模塊。函數(shù)主要有以下幾個(gè)特點(diǎn):
1)不含有任何延遲饰序、時(shí)序或時(shí)序控制邏輯
2)至少有一個(gè)輸入變量
3)只有一個(gè)返回值领虹,且沒有輸出
4)不含有非阻塞賦值語句
5)函數(shù)可以調(diào)用其他函數(shù),但是不能調(diào)用任務(wù)
Verilog 函數(shù)聲明格式如下:

function [range-1:0]     function_id ;
input_declaration ;
 other_declaration ;
procedural_statement ;
endfunction

函數(shù)在聲明時(shí)求豫,會(huì)隱式的聲明一個(gè)寬度為 range塌衰、 名字為 function_id 的寄存器變量,函數(shù)的返回值通過這個(gè)變量進(jìn)行傳遞蝠嘉。當(dāng)該寄存器變量沒有指定位寬時(shí)最疆,默認(rèn)位寬為 1。

函數(shù)通過指明函數(shù)名與輸入變量進(jìn)行調(diào)用蚤告。函數(shù)結(jié)束時(shí)努酸,返回值被傳遞到調(diào)用處。

函數(shù)調(diào)用格式如下:

function_id(input1, input2, …);

下面用函數(shù)實(shí)現(xiàn)一個(gè)數(shù)據(jù)大小端轉(zhuǎn)換的功能杜恰。

當(dāng)輸入為 4'b0011 時(shí)获诈,輸出可為 4'b1100。例如:

module endian_rvs
    #(parameter N = 4)
        (
            input             en,     //enable control
            input [N-1:0]     a ,
            output [N-1:0]    b
    );
         
        reg [N-1:0]          b_temp ;
        always @(*) begin
        if (en) begin
                b_temp =  data_rvs(a);
            end
            else begin
                b_temp = 0 ;
            end
    end
        assign b = b_temp ;
         
    //function entity
        function [N-1:0]     data_rvs ;
            input     [N-1:0] data_in ;
            parameter         MASK = 32'h3 ;
            integer           k ;
            begin
                for(k=0; k<N; k=k+1) begin
                    data_rvs[N-k-1]  = data_in[k] ;  
                end
            end
    endfunction
         
endmodule        

函數(shù)里的參數(shù)也可以改寫心褐,例如:

defparam data_rvs.MASK = 32'd7 ;

但是仿真時(shí)發(fā)現(xiàn)舔涎,此種寫法編譯可以通過,仿真結(jié)果中逗爹,函數(shù)里的參數(shù) MASK 實(shí)際并沒有改寫成功亡嫌,仍然為 32'h3。這可能和編譯器有關(guān)掘而,有興趣的學(xué)者可以用其他 Verilog 編譯器進(jìn)行下實(shí)驗(yàn)昼伴。

函數(shù)在聲明時(shí),也可以在函數(shù)名后面加一個(gè)括號(hào)镣屹,將 input 聲明包起來。

例如上述大小端聲明函數(shù)可以表示為:

function [N-1:0]     data_rvs(
input     [N-1:0] data_in 
    ......
    ) ;
  • 常數(shù)函數(shù)

常數(shù)函數(shù)是指在仿真開始之前价涝,在編譯期間就計(jì)算出結(jié)果為常數(shù)的函數(shù)女蜈。常數(shù)函數(shù)不允許訪問全局變量或者調(diào)用系統(tǒng)函數(shù),但是可以調(diào)用另一個(gè)常數(shù)函數(shù)色瘩。

這種函數(shù)能夠用來引用復(fù)雜的值伪窖,因此可用來代替常量。

例如下面一個(gè)常量函數(shù)居兆,可以來計(jì)算模塊中地址總線的寬度:

parameter    MEM_DEPTH = 256 ;
reg  [logb2(MEM_DEPTH)-1: 0] addr ; //可得addr的寬度為8bit
 
    function integer     logb2;
    input integer     depth ;
        //256為9bit覆山,我們最終數(shù)據(jù)應(yīng)該是8,所以需depth=2時(shí)提前停止循環(huán)
    for(logb2=0; depth>1; logb2=logb2+1) begin
        depth = depth >> 1 ;
    end
endfunction
  • automatic 函數(shù)
    在 Verilog 中泥栖,一般函數(shù)的局部變量是靜態(tài)的簇宽,即函數(shù)的每次調(diào)用勋篓,函數(shù)的局部變量都會(huì)使用同一個(gè)存儲(chǔ)空間。若某個(gè)函數(shù)在兩個(gè)不同的地方同時(shí)并發(fā)的調(diào)用魏割,那么兩個(gè)函數(shù)調(diào)用行為同時(shí)對(duì)同一塊地址進(jìn)行操作譬嚣,會(huì)導(dǎo)致不確定的函數(shù)結(jié)果。

Verilog 用關(guān)鍵字 automatic 來對(duì)函數(shù)進(jìn)行說明钞它,此類函數(shù)在調(diào)用時(shí)是可以自動(dòng)分配新的內(nèi)存空間的拜银,也可以理解為是可遞歸的。因此遭垛,automatic 函數(shù)中聲明的局部變量不能通過層次命名進(jìn)行訪問尼桶,但是 automatic 函數(shù)本身可以通過層次名進(jìn)行調(diào)用。

下面用 automatic 函數(shù)锯仪,實(shí)現(xiàn)階乘計(jì)算:

wire [31:0]          results3 = factorial(4);
function automatic   integer         factorial ;
    input integer     data ;
    integer           i ;
    begin
        factorial = (data>=2)? data * factorial(data-1) : 1 ;
    end
endfunction // factorial
  • 數(shù)碼管譯碼
    每位數(shù)碼顯示端有 8 個(gè)光亮控制端(如圖中 a-g 所示)泵督,可以用來控制顯示數(shù)字 0-9 。

而數(shù)碼管有 4 個(gè)片選(如圖中 1-4)卵酪,用來控制此時(shí)哪一位數(shù)碼顯示端應(yīng)該選通幌蚊,即應(yīng)該發(fā)光。倘若在很短的時(shí)間內(nèi)溃卡,依次對(duì) 4 個(gè)數(shù)碼顯示端進(jìn)行片選發(fā)光溢豆,同時(shí)在不同片選下給予不同的光亮控制(各對(duì)應(yīng) 4 位十進(jìn)制數(shù)字),那么在肉眼不能分辨的情況下瘸羡,就達(dá)到了同時(shí)顯示 4 位十進(jìn)制數(shù)字的效果漩仙。



下面,我們用信號(hào) abcdefg 來控制光亮控制端犹赖,用信號(hào) csn 來控制片選队他,4 位 10 進(jìn)制的數(shù)字個(gè)十百千位分別用 4 個(gè) 4bit 信號(hào) single_digit, ten_digit, hundred_digit, kilo_digit 來表示,則一個(gè)數(shù)碼管的顯示設(shè)計(jì)可以描述如下:

module digital_tube
     (
      input             clk ,
      input             rstn ,
      input             en ,
 
      input [3:0]       single_digit ,
      input [3:0]       ten_digit ,
      input [3:0]       hundred_digit ,
      input [3:0]       kilo_digit ,
 
      output reg [3:0]  csn , //chip select, low-available
      output reg [6:0]  abcdefg        //light control
      );
 
    reg [1:0]            scan_r ;  //scan_ctrl
    always @ (posedge clk or negedge rstn) begin
        if(!rstn)begin
            csn            <= 4'b1111;
            abcdefg        <= 'd0;
            scan_r         <= 3'd0;
        end
        else if (en) begin
            case(scan_r)
            2'd0:begin
                scan_r    <= 3'd1;
                csn       <= 4'b0111;     //select single digit
                abcdefg   <= dt_translate(single_digit);
            end
            2'd1:begin
                scan_r    <= 3'd2;
                csn       <= 4'b1011;     //select ten digit
                abcdefg   <= dt_translate(ten_digit);
            end
            2'd2:begin
                scan_r    <= 3'd3;
                csn       <= 4'b1101;     //select hundred digit
                abcdefg   <= dt_translate(hundred_digit);
            end
            2'd3:begin
                scan_r    <= 3'd0;
                csn       <= 4'b1110;     //select kilo digit
                abcdefg   <= dt_translate(kilo_digit);
            end
            endcase
        end
    end
 
    /*------------ translate function -------*/
    function [6:0] dt_translate;
        input [3:0]   data;
        begin
        case(data)
            4'd0: dt_translate = 7'b1111110;     //number 0 -> 0x7e
            4'd1: dt_translate = 7'b0110000;     //number 1 -> 0x30
            4'd2: dt_translate = 7'b1101101;     //number 2 -> 0x6d
            4'd3: dt_translate = 7'b1111001;     //number 3 -> 0x79
            4'd4: dt_translate = 7'b0110011;     //number 4 -> 0x33
            4'd5: dt_translate = 7'b1011011;     //number 5 -> 0x5b
            4'd6: dt_translate = 7'b1011111;     //number 6 -> 0x5f
            4'd7: dt_translate = 7'b1110000;     //number 7 -> 0x70
            4'd8: dt_translate = 7'b1111111;     //number 8 -> 0x7f
            4'd9: dt_translate = 7'b1111011;     //number 9 -> 0x7b
        endcase
        end
    endfunction
 
endmodule

任務(wù)

  • 關(guān)鍵詞:任務(wù)

  • 任務(wù)與函數(shù)的區(qū)別
    和函數(shù)一樣峻村,任務(wù)(task)可以用來描述共同的代碼段麸折,并在模塊內(nèi)任意位置被調(diào)用,讓代碼更加的直觀易讀粘昨。函數(shù)一般用于組合邏輯的各種轉(zhuǎn)換和計(jì)算垢啼,而任務(wù)更像一個(gè)過程,不僅能完成函數(shù)的功能张肾,還可以包含時(shí)序控制邏輯芭析。下面對(duì)任務(wù)與函數(shù)的區(qū)別進(jìn)行概括:


  • 任務(wù)

  • 任務(wù)聲明

任務(wù)在模塊中任意位置定義,并在模塊內(nèi)任意位置引用吞瞪,作用范圍也局限于此模塊馁启。

模塊內(nèi)子程序出現(xiàn)下面任意一個(gè)條件時(shí),則必須使用任務(wù)而不能使用函數(shù)芍秆。

1)子程序中包含時(shí)序控制邏輯惯疙,例如延遲翠勉,事件控制等
2)沒有輸入變量
3)沒有輸出或輸出端的數(shù)量大于 1
Verilog 任務(wù)聲明格式如下:

task       task_id ;
    port_declaration ;
    procedural_statement ;
endtask

任務(wù)中使用關(guān)鍵字 input、output 和 inout 對(duì)端口進(jìn)行聲明螟碎。input 眉菱、inout 型端口將變量從任務(wù)外部傳遞到內(nèi)部,output掉分、inout 型端口將任務(wù)執(zhí)行完畢時(shí)的結(jié)果傳回到外部俭缓。

進(jìn)行任務(wù)的邏輯設(shè)計(jì)時(shí),可以把 input 聲明的端口變量看做 wire 型酥郭,把 output 聲明的端口變量看做 reg 型华坦。但是不需要用 reg 對(duì) output 端口再次說明。

對(duì) output 信號(hào)賦值時(shí)也不要用關(guān)鍵字 assign不从。為避免時(shí)序錯(cuò)亂惜姐,建議 output 信號(hào)采用阻塞賦值。

例如椿息,一個(gè)帶延時(shí)的異或功能 task 描述如下:

task xor_oper_iner;
    input [N-1:0]   numa;
    input [N-1:0]   numb;
    output [N-1:0]  numco ;
    //output reg [N-1:0]  numco ; //無需再注明 reg 類型歹袁,雖然注明也可能沒錯(cuò)
    #3  numco = numa ^ numb ;
    //assign #3 numco = numa ^ numb ; //不用assign,因?yàn)檩敵瞿J(rèn)是reg
endtask

任務(wù)在聲明時(shí)寝优,也可以在任務(wù)名后面加一個(gè)括號(hào)条舔,將端口聲明包起來。

上述設(shè)計(jì)可以更改為:

task xor_oper_iner(
    input [N-1:0]   numa,
    input [N-1:0]   numb,
    output [N-1:0]  numco  ) ;
    #3  numco       = numa ^ numb ;
endtask
  • 任務(wù)調(diào)用

任務(wù)可單獨(dú)作為一條語句出現(xiàn)在 initial 或 always 塊中乏矾,調(diào)用格式如下:

task_id(input1, input2, …,outpu1, output2, …);

任務(wù)調(diào)用時(shí)孟抗,端口必須按順序?qū)?yīng)。

輸入端連接的模塊內(nèi)信號(hào)可以是 wire 型钻心,也可以是 reg 型凄硼。輸出端連接的模塊內(nèi)信號(hào)要求一定是 reg 型,這點(diǎn)需要注意捷沸。

module xor_oper
    #(parameter         N = 4)
     (
      input             clk ,
      input             rstn ,
      input [N-1:0]     a ,
      input [N-1:0]     b ,
      output [N-1:0]    co  );
 
    reg [N-1:0]          co_t ;
    always @(*) begin          //任務(wù)調(diào)用
        xor_oper_iner(a, b, co_t);
    end
 
    reg [N-1:0]          co_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            co_r   <= 'b0 ;
        end
        else begin
            co_r   <= co_t ;         //數(shù)據(jù)緩存
        end
    end
    assign       co = co_r ;
 
   /*------------ task -------*/
    task xor_oper_iner;
        input [N-1:0]   numa;
        input [N-1:0]   numb;
        output [N-1:0]  numco ;
        #3  numco       = numa ^ numb ;   //阻塞賦值摊沉,易于控制時(shí)序
    endtask
 
endmodule
  • 任務(wù)操作全局變量

因?yàn)槿蝿?wù)可以看做是過程性賦值,所以任務(wù)的 output 端信號(hào)返回時(shí)間是在任務(wù)中所有語句執(zhí)行完畢之后痒给。

任務(wù)內(nèi)部變量也只有在任務(wù)中可見坯钦,如果想具體觀察任務(wù)中對(duì)變量的操作過程,需要將觀察的變量聲明在模塊之內(nèi)侈玄、任務(wù)之外,可謂之"全局變量"吟温。

例如有以下 2 種嘗試?yán)?task 產(chǎn)生時(shí)鐘的描述方式序仙。

//way1 to decirbe clk generating, not work
task clk_rvs_iner ;
        output    clk_no_rvs ;
        # 5 ;     clk_no_rvs = 0 ;
        # 5 ;     clk_no_rvs = 1 ;
endtask
reg          clk_test1 ;
always clk_rvs_iner(clk_test1);

//way2: use task to operate global varialbes to generating clk
reg          clk_test2 ;
task clk_rvs_global ;
        # 5 ;     clk_test2 = 0 ;
        # 5 ;     clk_test2 = 1 ;
endtask // clk_rvs_iner
always clk_rvs_global;
  • automatic 任務(wù)

和函數(shù)一樣,Verilog 中任務(wù)調(diào)用時(shí)的局部變量都是靜態(tài)的鲁豪∨说浚可以用關(guān)鍵字 automatic 來對(duì)任務(wù)進(jìn)行聲明律秃,那么任務(wù)調(diào)用時(shí)各存儲(chǔ)空間就可以動(dòng)態(tài)分配,每個(gè)調(diào)用的任務(wù)都各自獨(dú)立的對(duì)自己獨(dú)有的地址空間進(jìn)行操作治唤,而不影響多個(gè)相同任務(wù)調(diào)用時(shí)的并發(fā)執(zhí)行棒动。

如果一任務(wù)代碼段被 2 處及以上調(diào)用,一定要用關(guān)鍵字 automatic 聲明宾添。

當(dāng)沒有使用 automatic 聲明任務(wù)時(shí)船惨,任務(wù)被 2 次調(diào)用,可能出現(xiàn)信號(hào)間干擾缕陕,例如下面代碼描述:

task test_flag ;
        input [3:0]       cnti ;
        input             en ;
        output [3:0]      cnto ;
        if (en) cnto = cnti ;
endtask

reg          en_cnt ;
reg [3:0]    cnt_temp ;
initial begin
        en_cnt    = 1 ;
        cnt_temp  = 0 ;
        #25 ;     en_cnt = 0 ;
end
always #10 cnt_temp = cnt_temp + 1 ;

reg [3:0]             cnt1, cnt2 ;
always @(posedge clk) test_flag(2, en_cnt, cnt1);       //task(1)
always @(posedge clk) test_flag(cnt_temp, !en_cnt, cnt2);//task(2)

狀態(tài)機(jī)

  • 關(guān)鍵詞:狀態(tài)機(jī)粱锐,售賣機(jī)

有限狀態(tài)機(jī)(Finite-State Machine,F(xiàn)SM)扛邑,簡(jiǎn)稱狀態(tài)機(jī)怜浅,是表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)模型。狀態(tài)機(jī)不僅是一種電路的描述工具蔬崩,而且也是一種思想方法恶座,在電路設(shè)計(jì)的系統(tǒng)級(jí)和 RTL 級(jí)有著廣泛的應(yīng)用。

  • 狀態(tài)機(jī)類型
    Verilog 中狀態(tài)機(jī)主要用于同步時(shí)序邏輯的設(shè)計(jì)沥阳,能夠在有限個(gè)狀態(tài)之間按一定要求和規(guī)律切換時(shí)序電路的狀態(tài)跨琳。狀態(tài)的切換方向不但取決于各個(gè)輸入值,還取決于當(dāng)前所在狀態(tài)沪袭。 狀態(tài)機(jī)可分為 2 類:Moore 狀態(tài)機(jī)和 Mealy 狀態(tài)機(jī)湾宙。

  • Moore 型狀態(tài)機(jī)

Moore 型狀態(tài)機(jī)的輸出只與當(dāng)前狀態(tài)有關(guān),與當(dāng)前輸入無關(guān)冈绊。

輸出會(huì)在一個(gè)完整的時(shí)鐘周期內(nèi)保持穩(wěn)定侠鳄,即使此時(shí)輸入信號(hào)有變化,輸出也不會(huì)變化死宣。輸入對(duì)輸出的影響要到下一個(gè)時(shí)鐘周期才能反映出來伟恶。這也是 Moore 型狀態(tài)機(jī)的一個(gè)重要特點(diǎn):輸入與輸出是隔離開來的。


  • Mealy 型狀態(tài)機(jī)

Mealy 型狀態(tài)機(jī)的輸出毅该,不僅與當(dāng)前狀態(tài)有關(guān)博秫,還取決于當(dāng)前的輸入信號(hào)。

Mealy 型狀態(tài)機(jī)的輸出是在輸入信號(hào)變化以后立刻發(fā)生變化眶掌,且輸入變化可能出現(xiàn)在任何狀態(tài)的時(shí)鐘周期內(nèi)挡育。因此,同種邏輯下朴爬,Mealy 型狀態(tài)機(jī)輸出對(duì)輸入的響應(yīng)會(huì)比 Moore 型狀態(tài)機(jī)早一個(gè)時(shí)鐘周期即寒。


  • 狀態(tài)機(jī)設(shè)計(jì):3 段式(推薦)
    狀態(tài)機(jī)設(shè)計(jì)如下:

(0) 首先,根據(jù)狀態(tài)機(jī)的個(gè)數(shù)確定狀態(tài)機(jī)編碼。利用編碼給狀態(tài)寄存器賦值母赵,代碼可讀性更好逸爵。
(1) 狀態(tài)機(jī)第一段,時(shí)序邏輯凹嘲,非阻塞賦值师倔,傳遞寄存器的狀態(tài)。
(2) 狀態(tài)機(jī)第二段周蹭,組合邏輯趋艘,阻塞賦值,根據(jù)當(dāng)前狀態(tài)和當(dāng)前輸入谷醉,確定下一個(gè)狀態(tài)機(jī)的狀態(tài)致稀。
(3) 狀態(tài)機(jī)第三代,時(shí)序邏輯俱尼,非阻塞賦值抖单,因?yàn)槭?Mealy 型狀態(tài)機(jī),根據(jù)當(dāng)前狀態(tài)和當(dāng)前輸入遇八,確定輸出信號(hào)矛绘。

// vending-machine
// 2 yuan for a bottle of drink
// only 2 coins supported: 5 jiao and 1 yuan
// finish the function of selling and changing

module  vending_machine_p3  (
    input           clk ,
    input           rstn ,
    input [1:0]     coin ,     //01 for 0.5 jiao, 10 for 1 yuan

    output [1:0]    change ,
    output          sell    //output the drink
    );

    //machine state decode
    parameter            IDLE   = 3'd0 ;
    parameter            GET05  = 3'd1 ;
    parameter            GET10  = 3'd2 ;
    parameter            GET15  = 3'd3 ;

    //machine variable
    reg [2:0]            st_next ;
    reg [2:0]            st_cur ;

    //(1) state transfer
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            st_cur      <= 'b0 ;
        end
        else begin
            st_cur      <= st_next ;
        end
    end

    //(2) state switch, using block assignment for combination-logic
    //all case items need to be displayed completely    
    always @(*) begin
        //st_next = st_cur ;//如果條件選項(xiàng)考慮不全,可以賦初值消除latch
        case(st_cur)
            IDLE:
                case (coin)
                    2'b01:     st_next = GET05 ;
                    2'b10:     st_next = GET10 ;
                    default:   st_next = IDLE ;
                endcase
            GET05:
                case (coin)
                    2'b01:     st_next = GET10 ;
                    2'b10:     st_next = GET15 ;
                    default:   st_next = GET05 ;
                endcase

            GET10:
                case (coin)
                    2'b01:     st_next = GET15 ;
                    2'b10:     st_next = IDLE ;
                    default:   st_next = GET10 ;
                endcase
            GET15:
                case (coin)
                    2'b01,2'b10:
                               st_next = IDLE ;
                    default:   st_next = GET15 ;
                endcase
            default:    st_next = IDLE ;
        endcase
    end

    //(3) output logic, using non-block assignment
    reg  [1:0]   change_r ;
    reg          sell_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
        else if ((st_cur == GET15 && coin ==2'h1)
               || (st_cur == GET10 && coin ==2'd2)) begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b1 ;
        end
        else if (st_cur == GET15 && coin == 2'h2) begin
            change_r       <= 2'b1 ;
            sell_r         <= 1'b1 ;
        end
        else begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
    end
    assign       sell    = sell_r ;
    assign       change  = change_r ;

endmodule
  • Verilog 書寫規(guī)范
    在編程時(shí)多注意以下幾點(diǎn)刃永,也可以避免大多數(shù)的競(jìng)爭(zhēng)與冒險(xiǎn)問題货矮。

1)時(shí)序電路建模時(shí),用非阻塞賦值斯够。
2)組合邏輯建模時(shí)囚玫,用阻塞賦值。
3)在同一個(gè) always 塊中建立時(shí)序和組合邏輯模型時(shí)读规,用非阻塞賦值抓督。
4)在同一個(gè) always 塊中不要既使用阻塞賦值又使用非阻塞賦值。
5)不要在多個(gè) always 塊中為同一個(gè)變量賦值束亏。
6)避免 latch 產(chǎn)生铃在。
下面,對(duì)以上注意事項(xiàng)逐條分析碍遍。

1)時(shí)序電路建模時(shí)定铜,用非阻塞賦值
前面講述非阻塞賦值時(shí)就陳述過,時(shí)序電路中非阻塞賦值可以消除競(jìng)爭(zhēng)冒險(xiǎn)怕敬。

例如下面代碼描述揣炕,由于無法確定 a 與 b 阻塞賦值的操作順序,就有可能帶來競(jìng)爭(zhēng)冒險(xiǎn)东跪。

always @(posedge clk) begin
    a = b ;
    b = a ;
end

而使用非阻塞賦值時(shí)畸陡,賦值操作是同時(shí)進(jìn)行的矮烹,所以就不會(huì)帶來競(jìng)爭(zhēng)冒險(xiǎn),如以下代碼描述罩锐。

always @(posedge clk) begin
    a <= b ;
    b <= a ;
end

2)組合邏輯建模時(shí),用阻塞賦值
例如卤唉,我們想實(shí)現(xiàn) C = A&B, F=C&D 的組合邏輯功能涩惑,用非阻塞賦值語句如下。
兩條賦值語句同時(shí)賦值桑驱,F(xiàn) <= C & D 中使用的是信號(hào) C 的舊值竭恬,所以導(dǎo)致此時(shí)的邏輯是錯(cuò)誤的,F(xiàn) 的邏輯值不等于 A&B&D熬的。

而且痊硕,此時(shí)要求信號(hào) C 具有存儲(chǔ)功能,但不是時(shí)鐘驅(qū)動(dòng)押框,所以 C 可能會(huì)被綜合成鎖存器(latch)岔绸,導(dǎo)致競(jìng)爭(zhēng)冒險(xiǎn)。

always @(*) begin
    C <= A & B ;
    F <= C & D ;
end

對(duì)代碼進(jìn)行如下修改橡伞,F(xiàn) = C & D 的操作一定是在 C = A & B 之后盒揉,此時(shí) F 的邏輯值等于 A&B&D,符合設(shè)計(jì)兑徘。

always @(*) begin
    C = A & B ;
    F = C & D ;
end

3)在同一個(gè) always 塊中建立時(shí)序和組合邏輯模型時(shí)刚盈,用非阻塞賦值

雖然時(shí)序電路中可能涉及組合邏輯,但是如果賦值操作使用非阻塞賦值挂脑,仍然會(huì)導(dǎo)致如規(guī)范 1 中所涉及的類似問題藕漱。

例如在時(shí)鐘驅(qū)動(dòng)下完成一個(gè)與門的邏輯功能,代碼參考如下崭闲。

always @(posedge clk or negedge rst_n)
    if (!rst_n) begin
        q <= 1'b0;
    end
    else begin
        q <= a & b;  //即便有組合邏輯肋联,也不要寫成:q = a & b
     end
end

4)在同一個(gè) always 塊中不要既使用阻塞賦值又使用非阻塞賦值
always 涉及的組合邏輯中,既有阻塞賦值又有非阻塞賦值時(shí)镀脂,會(huì)導(dǎo)致意外的結(jié)果牺蹄,例如下面代碼描述。

此時(shí)信號(hào) C 阻塞賦值完畢以后薄翅,信號(hào) F 才會(huì)被非阻塞賦值沙兰,仿真結(jié)果可能正確。

但如果 F 信號(hào)有其他的負(fù)載翘魄,F(xiàn) 的最新值并不能馬上傳遞出去鼎天,數(shù)據(jù)有效時(shí)間還是在下一個(gè)觸發(fā)時(shí)刻。此時(shí)要求 F 具有存儲(chǔ)功能暑竟,可能會(huì)被綜合成 latch斋射,導(dǎo)致競(jìng)爭(zhēng)冒險(xiǎn)育勺。

always @(*) begin
    C = A & B ;
    F <= C & D ;
end

如下代碼描述,仿真角度看罗岖,信號(hào) C 被非阻塞賦值涧至,下一個(gè)觸發(fā)時(shí)刻才會(huì)有效。而 F = C & D 雖然是阻塞賦值桑包,但是信號(hào) C 不是阻塞賦值南蓬,所以 F 邏輯中使用的還是 C 的舊值。

always @(*) begin
    C <= A & B ;
    F = C & D ;
end

下面分析假如在時(shí)序電路里既有阻塞賦值哑了,又有非阻塞賦值會(huì)怎樣赘方,代碼如下。

假如復(fù)位端與時(shí)鐘同步弱左,那么由于復(fù)位導(dǎo)致的信號(hào) q 為 0窄陡,是在下一個(gè)時(shí)鐘周期才有效。

而如果是信號(hào) a 或 b 導(dǎo)致的 q 為 0拆火,則在當(dāng)期時(shí)鐘周期內(nèi)有效跳夭。

如果 q 還有其他負(fù)載,就會(huì)導(dǎo)致 q 的時(shí)序特別混亂榜掌,顯然不符合設(shè)計(jì)需求优妙。

always @(posedge clk or negedge rst_n)
    if (!rst_n) begin  //假設(shè)復(fù)位與時(shí)鐘同步
        q <= 1'b0;
    end
    else begin
        q = a & b;  
    end
end

需要說明的是,很多編譯器都支持這么寫憎账,上述的分析也都是建立在仿真角度上套硼。實(shí)際中如果阻塞賦值和非阻塞賦值混合編寫,綜合后的電路時(shí)序?qū)⑹清e(cuò)亂的胞皱,不利于分析調(diào)試邪意。

5)不要在多個(gè) always 塊中為同一個(gè)變量賦值
與 C 語言有所不同,Verilog 中不允許在多個(gè) always 塊中為同一個(gè)變量賦值反砌。此時(shí)信號(hào)擁有多驅(qū)動(dòng)端(Multiple Driver)雾鬼,是禁止的。當(dāng)然宴树,也不允許 assign 語句為同一個(gè)變量進(jìn)行多次連線賦值策菜。 從信號(hào)角度來講,多驅(qū)動(dòng)時(shí)酒贬,同一個(gè)信號(hào)變量在很短的時(shí)間內(nèi)進(jìn)行多次不同的賦值結(jié)果又憨,就有可能產(chǎn)生競(jìng)爭(zhēng)冒險(xiǎn)。

從語法來講锭吨,很多編譯器檢測(cè)到多驅(qū)動(dòng)時(shí)蠢莺,也會(huì)報(bào) Error。

避免 Latch

  • 關(guān)鍵詞:觸發(fā)器零如,鎖存器
  • Latch 的含義

鎖存器(Latch)躏将,是電平觸發(fā)的存儲(chǔ)單元锄弱,數(shù)據(jù)存儲(chǔ)的動(dòng)作取決于輸入時(shí)鐘(或者使能)信號(hào)的電平值。僅當(dāng)鎖存器處于使能狀態(tài)時(shí)祸憋,輸出才會(huì)隨著數(shù)據(jù)輸入發(fā)生變化会宪。

當(dāng)電平信號(hào)無效時(shí),輸出信號(hào)隨輸入信號(hào)變化蚯窥,就像通過了緩沖器狈谊;當(dāng)電平有效時(shí),輸出信號(hào)被鎖存沟沙。激勵(lì)信號(hào)的任何變化,都將直接引起鎖存器輸出狀態(tài)的改變壁榕,很有可能會(huì)因?yàn)樗矐B(tài)特性不穩(wěn)定而產(chǎn)生振蕩現(xiàn)象矛紫。

觸發(fā)器(flip-flop),是邊沿敏感的存儲(chǔ)單元牌里,數(shù)據(jù)存儲(chǔ)的動(dòng)作(狀態(tài)轉(zhuǎn)換)由某一信號(hào)的上升沿或者下降沿進(jìn)行同步的(限制存儲(chǔ)單元狀態(tài)轉(zhuǎn)換在一個(gè)很短的時(shí)間內(nèi))颊咬。

觸發(fā)器示意圖如下:


寄存器(register),在 Verilog 中用來暫時(shí)存放參與運(yùn)算的數(shù)據(jù)和運(yùn)算結(jié)果的變量牡辽。一個(gè)變量聲明為寄存器時(shí)喳篇,它既可以被綜合成觸發(fā)器,也可能被綜合成 Latch态辛,甚至是 wire 型變量麸澜。但是大多數(shù)情況下我們希望它被綜合成觸發(fā)器,但是有時(shí)候由于代碼書寫問題奏黑,它會(huì)被綜合成不期望的 Latch 結(jié)構(gòu)炊邦。

Latch 的主要危害有:

1)輸入狀態(tài)可能多次變化,容易產(chǎn)生毛刺熟史,增加了下一級(jí)電路的不確定性馁害;
2)在大部分 FPGA 的資源中,可能需要比觸發(fā)器更多的資源去實(shí)現(xiàn) Latch 結(jié)構(gòu)蹂匹;
3)鎖存器的出現(xiàn)使得靜態(tài)時(shí)序分析變得更加復(fù)雜碘菜。
Latch 多用于門控時(shí)鐘(clock gating)的控制,一般設(shè)計(jì)時(shí)限寞,我們應(yīng)當(dāng)避免 Latch 的產(chǎn)生忍啸。

  • if 結(jié)構(gòu)不完整
    組合邏輯中,不完整的 if - else 結(jié)構(gòu)昆烁,會(huì)產(chǎn)生 latch吊骤。

例如下面的模型,if 語句中缺少 else 結(jié)構(gòu)静尼,系統(tǒng)默認(rèn) else 的分支下寄存器 q 的值保持不變白粉,即具有存儲(chǔ)數(shù)據(jù)的功能传泊,所以寄存器 q 會(huì)被綜合成 latch 結(jié)構(gòu)。

module module1_latch1(
    input       data,
    input       en ,
    output reg  q) ;
   
    always @(*) begin
        if (en) q = data ;
    end

endmodule

避免此類 latch 的方法主要有 2 種鸭巴,一種是補(bǔ)全 if-else 結(jié)構(gòu)眷细,或者對(duì)信號(hào)賦初值。

例如鹃祖,上面模型中的always語句溪椎,可以改為以下兩種形式:

    // 補(bǔ)全條件分支結(jié)構(gòu)    
    always @(*) begin
        if (en)  q = data ;
        else     q = 1'b0 ;
    end

    //賦初值
    always @(*) begin
        q = 1'b0 ;
        if (en) q = data ; //如果en有效,改寫q的值恬口,否則q會(huì)保持為0
    end

但是在時(shí)序邏輯中校读,不完整的 if - else 結(jié)構(gòu),不會(huì)產(chǎn)生 latch祖能,例如下面模型歉秫。

這是因?yàn)椋琿 寄存器具有存儲(chǔ)功能养铸,且其值在時(shí)鐘的邊沿下才會(huì)改變雁芙,這正是觸發(fā)器的特性。

module module1_ff(
    input       clk ,
    input       data,
    input       en ,
    output reg  q) ;
   
    always @(posedge clk) begin
        if (en) q <= data ;
    end

endmodule

在組合邏輯中钞螟,當(dāng)條件語句中有很多條賦值語句時(shí)兔甘,每個(gè)分支條件下賦值語句的不完整也是會(huì)產(chǎn)生 latch。

其實(shí)對(duì)每個(gè)信號(hào)的邏輯拆分來看鳞滨,這也相當(dāng)于是 if-else 結(jié)構(gòu)不完整洞焙,相關(guān)寄存器信號(hào)缺少在其他條件下的賦值行為。例如:

module module1_latch11(
    input       data1,
    input       data2,
    input       en ,
    output reg  q1 ,
    output reg  q2) ;
   
    always @(*) begin
        if (en)   q1 = data1 ;
        else      q2 = data2 ;
    end

endmodule

這種情況也可以通過補(bǔ)充完整賦值語句或賦初值來避免 latch拯啦。例如:

    always @(*) begin
        //q1 = 0; q2 = 0 ; //或在這里對(duì) q1/q2 賦初值
        if (en)  begin
            q1 = data1 ;
            q2 = 1'b0 ;
        end
        else begin
            q1 = 1'b0 ;
            q2 = data2 ;
        end
    end
  • case 結(jié)構(gòu)不完整
    case 語句產(chǎn)生 Latch 的原理幾乎和 if 語句一致闽晦。在組合邏輯中,當(dāng) case 選項(xiàng)列表不全且沒有加 default 關(guān)鍵字提岔,或有多個(gè)賦值語句不完整時(shí)仙蛉,也會(huì)產(chǎn)生 Latch。例如:
module module1_latch2(
    input       data1,
    input       data2,
    input [1:0] sel ,
    output reg  q ) ;
   
    always @(*) begin
        case(sel)
            2'b00:  q = data1 ;
            2'b01:  q = data2 ;
        endcase
    end

endmodule

當(dāng)然碱蒙,消除此種 latch 的方法也是 2 種荠瘪,將 case 選項(xiàng)列表補(bǔ)充完整,或?qū)π盘?hào)賦初值赛惩。

當(dāng)然哀墓,補(bǔ)充完整 case 選項(xiàng)列表時(shí),可以羅列所有的選項(xiàng)結(jié)果喷兼,也可以用 default 關(guān)鍵字來代替其他選項(xiàng)結(jié)果篮绰。

例如,上述 always 語句有以下 2 種修改方式季惯。

    always @(*) begin
        case(sel)
            2'b00:    q = data1 ;
            2'b01:    q = data2 ;
            default:  q = 1'b0 ;
        endcase
    end

    always @(*) begin
        case(sel)
            2'b00:  q = data1 ;
            2'b01:  q = data2 ;
            2'b10, 2'b11 :  
                    q = 1'b0 ;
        endcase
    end

原信號(hào)賦值或判斷
在組合邏輯中吠各,如果一個(gè)信號(hào)的賦值源頭有其信號(hào)本身臀突,或者判斷條件中有其信號(hào)本身的邏輯,則也會(huì)產(chǎn)生 latch贾漏。因?yàn)榇藭r(shí)信號(hào)也需要具有存儲(chǔ)功能候学,但是沒有時(shí)鐘驅(qū)動(dòng)。此類問題在 if 語句纵散、case 語句梳码、問號(hào)表達(dá)式中都可能出現(xiàn),例如:

    //signal itself as a part of condition
    reg a, b ;
    always @(*) begin
        if (a & b)  a = 1'b1 ;   //a -> latch
        else a = 1'b0 ;
    end
   
    //signal itself are the assigment source
    reg        c;
    wire [1:0] sel ;
    always @(*) begin
        case(sel)
            2'b00:    c = c ;    //c -> latch
            2'b01:    c = 1'b1 ;
            default:  c = 1'b0 ;
        endcase
    end

    //signal itself as a part of condition in "? expression"
    wire      d, sel2;
    assign    d =  (sel2 && d) ? 1'b0 : 1'b1 ;  //d -> latch

避免此類 Latch 的方法伍掀,就只有一種掰茶,即在組合邏輯中避免這種寫法罢艾,信號(hào)不要給信號(hào)自己賦值来涨,且不要用賦值信號(hào)本身參與判斷條件邏輯。

例如害碾,如果不要求立刻輸出瘩例,可以將信號(hào)進(jìn)行一個(gè)時(shí)鐘周期的延時(shí)再進(jìn)行相關(guān)邏輯的組合。上述第一個(gè)產(chǎn)生 Latch 的代碼可以描述為:

    reg   a, b ;
    reg   a_r ;
   
    always (@posedge clk)
        a_r  <= a ;
       
    always @(*) begin
        if (a_r & b)  a = 1'b1 ;   //there is no latch
        else a = 1'b0 ;
    end

敏感信號(hào)列表不完整
如果組合邏輯中 always@() 塊內(nèi)敏感列表沒有列全甸各,該觸發(fā)的時(shí)候沒有觸發(fā)垛贤,那么相關(guān)寄存器還是會(huì)保存之前的輸出結(jié)果,因而會(huì)生成鎖存器趣倾。

這種情況聘惦,把敏感信號(hào)補(bǔ)全或者直接用 always@(*) 即可消除 latch。

小結(jié)
總之儒恋,為避免 latch 的產(chǎn)生善绎,在組合邏輯中,需要注意以下幾點(diǎn):

1)if-else 或 case 語句诫尽,結(jié)構(gòu)一定要完整
2)不要將賦值信號(hào)放在賦值源頭禀酱,或條件判斷中
3)敏感信號(hào)列表建議多用 always@(*)

仿真激勵(lì)

  • 關(guān)鍵詞:testbench,仿真牧嫉,文件讀寫
    Verilog 代碼設(shè)計(jì)完成后剂跟,還需要進(jìn)行重要的步驟,即邏輯功能仿真酣藻。仿真激勵(lì)文件稱之為 testbench曹洽,放在各設(shè)計(jì)模塊的頂層,以便對(duì)模塊進(jìn)行系統(tǒng)性的例化調(diào)用進(jìn)行仿真辽剧。

毫不夸張的說送淆,對(duì)于稍微復(fù)雜的 Verilog 設(shè)計(jì),如果不進(jìn)行仿真怕轿,即便是經(jīng)驗(yàn)豐富的老手偷崩,99.9999% 以上的設(shè)計(jì)都不會(huì)正常的工作辟拷。不能說仿真比設(shè)計(jì)更加的重要,但是一般來說环凿,仿真花費(fèi)的時(shí)間會(huì)比設(shè)計(jì)花費(fèi)的時(shí)間要多梧兼。有時(shí)候,考慮到各種應(yīng)用場(chǎng)景智听,testbench 的編寫也會(huì)比 Verilog 設(shè)計(jì)更加的復(fù)雜羽杰。所以,數(shù)字電路行業(yè)會(huì)具體劃分設(shè)計(jì)工程師和驗(yàn)證工程師到推。

下面考赛,對(duì) testbench 做一個(gè)簡(jiǎn)單的學(xué)習(xí)。

  • testbench 結(jié)構(gòu)劃分
    testbench 一般結(jié)構(gòu)如下:



    其實(shí) testbench 最基本的結(jié)構(gòu)包括信號(hào)聲明莉测、激勵(lì)和模塊例化颜骤。

根據(jù)設(shè)計(jì)的復(fù)雜度,需要引入時(shí)鐘和復(fù)位部分捣卤。當(dāng)然更為復(fù)雜的設(shè)計(jì)忍抽,激勵(lì)部分也會(huì)更加復(fù)雜。根據(jù)自己的驗(yàn)證需求董朝,選擇是否需要自校驗(yàn)和停止仿真部分鸠项。

當(dāng)然,復(fù)位和時(shí)鐘產(chǎn)生部分子姜,也可以看做激勵(lì)祟绊,所以它們都可以在一個(gè)語句塊中實(shí)現(xiàn)。也可以拿自校驗(yàn)的結(jié)果哥捕,作為結(jié)束仿真的條件牧抽。

  • testbench 具體分析
    1)信號(hào)聲明

testbench 模塊聲明時(shí),一般不需要聲明端口遥赚。因?yàn)榧?lì)信號(hào)一般都在 testbench 模塊內(nèi)部扬舒,沒有外部信號(hào)。

聲明的變量應(yīng)該能全部對(duì)應(yīng)被測(cè)試模塊的端口凫佛。當(dāng)然呼巴,變量不一定要與被測(cè)試模塊端口名字一樣。但是被測(cè)試模塊輸入端對(duì)應(yīng)的變量應(yīng)該聲明為 reg 型御蒲,如 clk衣赶,rstn 等,輸出端對(duì)應(yīng)的變量應(yīng)該聲明為 wire 型厚满,如 dout府瞄,dout_en。

2)時(shí)鐘生成

生成時(shí)鐘的方式有很多種,例如以下兩種生成方式也可以借鑒遵馆。

initial clk = 0 ;
always #(CYCLE_200MHz/2) clk = ~clk;

initial begin
    clk = 0 ;
    forever begin
        #(CYCLE_200MHz/2) clk = ~clk;
    end
end      

需要注意的是鲸郊,利用取反方法產(chǎn)生時(shí)鐘時(shí),一定要給 clk 寄存器賦初值货邓。

利用參數(shù)的方法去指定時(shí)間延遲時(shí)秆撮,如果延時(shí)參數(shù)為浮點(diǎn)數(shù),該參數(shù)不要聲明為 parameter 類型换况。例如實(shí)例中變量 CYCLE_200MHz 的值為 2.5职辨。如果其變量類型為 parameter,最后生成的時(shí)鐘周期很可能就是 4ns戈二。當(dāng)然舒裤,timescale 的精度也需要提高,單位和精度不能一樣觉吭,否則小數(shù)部分的時(shí)間延遲賦值也將不起作用腾供。

3)復(fù)位生成

復(fù)位邏輯比較簡(jiǎn)單,一般賦初值為 0鲜滩,再經(jīng)過一段小延遲后伴鳖,復(fù)位為 1 即可。

這里大多數(shù)的仿真都是用的低有效復(fù)位徙硅。

4)激勵(lì)部分

激勵(lì)部分該產(chǎn)生怎樣的輸入信號(hào)榜聂,是根據(jù)被測(cè)模塊的需要來設(shè)計(jì)的。
5)模塊例化

這里利用 testbench 開始聲明的信號(hào)變量闷游,對(duì)被測(cè)試模塊進(jìn)行例化連接。

6)自校驗(yàn)

如果設(shè)計(jì)比較簡(jiǎn)單贴汪,完全可以通過輸入脐往、輸出信號(hào)的波形來確定設(shè)計(jì)是否正確,此部分完全可以刪除扳埂。如果數(shù)據(jù)很多业簿,有時(shí)候拿肉眼觀察并不能對(duì)設(shè)計(jì)的正確性進(jìn)行一個(gè)有效判定。此時(shí)加入一個(gè)自校驗(yàn)?zāi)K阳懂,會(huì)是大大增加仿真的效率梅尤。

實(shí)例中,我們會(huì)在數(shù)據(jù)輸出使能 dout_en 有效時(shí)岩调,對(duì)輸出數(shù)據(jù) dout 與參考數(shù)據(jù) read_temp(激勵(lì)部分產(chǎn)生)做一個(gè)對(duì)比巷燥,并將對(duì)比結(jié)果置于信號(hào) err_cnt 中。最后我們就可以通過觀察 err_cnt 信號(hào)是否為 0 來直觀的對(duì)設(shè)計(jì)進(jìn)行判斷号枕。

當(dāng)然如實(shí)例中所示缰揪,我們也可以將數(shù)據(jù)寫入到對(duì)應(yīng)文件中,利用其他方式做對(duì)比葱淳。

7)結(jié)束仿真

如果我們不加入結(jié)束仿真部分钝腺,仿真就會(huì)無限制的運(yùn)行下去抛姑,波形太長(zhǎng)有時(shí)候并不方便分析。Verilog 中提供了系統(tǒng)任務(wù) $finish 來停止仿真艳狐。

  • 文件讀寫選項(xiàng)
    用于打開文件的系統(tǒng)任務(wù) $fopen 格式如下:
fd = $fopen("<name_of_file>", "mode")

和 C 語言類似定硝,打開方式的選項(xiàng) "mode" 意義如下:


流水線

  • 一般乘法器設(shè)計(jì)

也許有人會(huì)問,直接用乘號(hào) * 來完成 2 個(gè)數(shù)的相乘不是更快更簡(jiǎn)單嗎毫目?

如果你有這個(gè)疑問蔬啡,說明你對(duì)硬件描述語言的認(rèn)知還有所不足。就像之前所說蒜茴,Verilog 描述的是硬件電路星爪,直接用乘號(hào)完成相乘過程,編譯器在編譯的時(shí)候也會(huì)把這個(gè)乘法表達(dá)式映射成默認(rèn)的乘法器粉私,但其構(gòu)造不得而知顽腾。

例如,在 FPGA 設(shè)計(jì)中诺核,可以直接調(diào)用 IP 核來生成一個(gè)高性能的乘法器抄肖。在位寬較小的時(shí)候,一個(gè)周期內(nèi)就可以輸出結(jié)果窖杀,位寬較大時(shí)也可以流水輸出漓摩。在能滿足要求的前提下,可以謹(jǐn)慎的用 * 或直接調(diào)用 IP 來完成乘法運(yùn)算入客。

但乘法器 IP 也有很多的缺陷管毙,例如位寬的限制,未知的時(shí)序等桌硫。尤其使用乘號(hào)夭咬,會(huì)為數(shù)字設(shè)計(jì)的不確定性埋下很大的隱瞞。

很多時(shí)候铆隘,常數(shù)的乘法都會(huì)用移位相加的形式實(shí)現(xiàn)卓舵,例如:

A = A<<1 ;       //完成A * 2
A = (A<<1) + A ;   //對(duì)應(yīng)A * 3
A = (A<<3) + (A<<2) + (A<<1) + A ; //對(duì)應(yīng)A * 15

用一個(gè)移位寄存器和一個(gè)加法器就能完成乘以 3 的操作。但是乘以 15 時(shí)就需要 3 個(gè)移位寄存器和 3 個(gè)加法器(當(dāng)然乘以 15 可以用移位相減的方式)膀钠。

有時(shí)候數(shù)字電路在一個(gè)周期內(nèi)并不能夠完成多個(gè)變量同時(shí)相加的操作掏湾。所以數(shù)字設(shè)計(jì)中,最保險(xiǎn)的加法操作是同一時(shí)刻只對(duì) 2 個(gè)數(shù)據(jù)進(jìn)行加法運(yùn)算肿嘲,最差設(shè)計(jì)是同一時(shí)刻對(duì) 4 個(gè)及以上的數(shù)據(jù)進(jìn)行加法運(yùn)算融击。

如果設(shè)計(jì)中有同時(shí)對(duì) 4 個(gè)數(shù)據(jù)進(jìn)行加法運(yùn)算的操作設(shè)計(jì),那么此部分設(shè)計(jì)就會(huì)有危險(xiǎn)雳窟,可能導(dǎo)致時(shí)序不滿足砚嘴。

此時(shí),設(shè)計(jì)參數(shù)可配、時(shí)序可控的流水線式乘法器就顯得有必要了际长。

設(shè)計(jì)原理

和十進(jìn)制乘法類似耸采,計(jì)算 13 與 5 的相乘過程如下所示:


由此可知,被乘數(shù)按照乘數(shù)對(duì)應(yīng) bit 位進(jìn)行移位累計(jì)工育,便可完成相乘的過程虾宇。

假設(shè)每個(gè)周期只能完成一次累加,那么一次乘法計(jì)算時(shí)間最少的時(shí)鐘數(shù)恰好是乘數(shù)的位寬如绸。所以建議嘱朽,將位寬窄的數(shù)當(dāng)做乘數(shù),此時(shí)計(jì)算周期短怔接。

乘法器設(shè)計(jì)

考慮每次乘法運(yùn)算只能輸出一個(gè)結(jié)果(非流水線設(shè)計(jì))搪泳,設(shè)計(jì)代碼如下。

module    mult_low
    #(parameter N=4,
      parameter M=4)
     (
      input                     clk,
      input                     rstn,
      input                     data_rdy ,  //數(shù)據(jù)輸入使能
      input [N-1:0]             mult1,      //被乘數(shù)
      input [M-1:0]             mult2,      //乘數(shù)

      output                    res_rdy ,   //數(shù)據(jù)輸出使能
      output [N+M-1:0]          res         //乘法結(jié)果
      );

    //calculate counter
    reg [31:0]           cnt ;
    //乘法周期計(jì)數(shù)器
    wire [31:0]          cnt_temp = (cnt == M)? 'b0 : cnt + 1'b1 ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            cnt    <= 'b0 ;
        end
        else if (data_rdy) begin    //數(shù)據(jù)使能時(shí)開始計(jì)數(shù)
            cnt    <= cnt_temp ;
        end
        else if (cnt != 0 ) begin  //防止輸入使能端持續(xù)時(shí)間過短
            cnt    <= cnt_temp ;
        end
        else begin
            cnt    <= 'b0 ;
        end
    end

    //multiply
    reg [M-1:0]          mult2_shift ;
    reg [M+N-1:0]        mult1_shift ;
    reg [M+N-1:0]        mult1_acc ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            mult2_shift    <= 'b0 ;
            mult2_shift    <= 'b0 ;
            mult1_acc      <= 'b0 ;
        end
        else if (data_rdy && cnt=='b0) begin  //初始化
            mult1_shift    <= {{(N){1'b0}}, mult1} << 1 ;  
            mult2_shift    <= mult2 >> 1 ;  
            mult1_acc      <= mult2[0] ? {{(N){1'b0}}, mult1} : 'b0 ;
        end
        else if (cnt != M) begin
            mult1_shift    <= mult1_shift << 1 ;  //被乘數(shù)乘2
            mult2_shift    <= mult2_shift >> 1 ;  //乘數(shù)右移扼脐,方便判斷
            //判斷乘數(shù)對(duì)應(yīng)為是否為1岸军,為1則累加
            mult1_acc      <= mult2_shift[0] ? mult1_acc + mult1_shift : mult1_acc ;
        end
        else begin
            mult2_shift    <= 'b0 ;
            mult2_shift    <= 'b0 ;
            mult1_acc      <= 'b0 ;
        end
    end

    //results
    reg [M+N-1:0]        res_r ;
    reg                  res_rdy_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            res_r          <= 'b0 ;
            res_rdy_r      <= 'b0 ;
        end  
        else if (cnt == M) begin
            res_r          <= mult1_acc ;  //乘法周期結(jié)束時(shí)輸出結(jié)果
            res_rdy_r      <= 1'b1 ;
        end
        else begin
            res_r          <= 'b0 ;
            res_rdy_r      <= 'b0 ;
        end
    end

    assign res_rdy       = res_rdy_r;
    assign res           = res_r;

endmodule
  • 流水線乘法器設(shè)計(jì)
    下面對(duì)乘法執(zhí)行過程的中間狀態(tài)進(jìn)行保存,以便流水工作瓦侮,設(shè)計(jì)代碼如下艰赞。

單次累加計(jì)算過程的代碼文件如下(mult_cell.v ):

module    mult_cell
    #(parameter N=4,
      parameter M=4)
    (
      input                     clk,
      input                     rstn,
      input                     en,
      input [M+N-1:0]           mult1,      //被乘數(shù)
      input [M-1:0]             mult2,      //乘數(shù)
      input [M+N-1:0]           mult1_acci, //上次累加結(jié)果

      output reg [M+N-1:0]      mult1_o,     //被乘數(shù)移位后保存值
      output reg [M-1:0]        mult2_shift, //乘數(shù)移位后保存值
      output reg [N+M-1:0]      mult1_acco,  //當(dāng)前累加結(jié)果
      output reg                rdy );

    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            rdy            <= 'b0 ;
            mult1_o        <= 'b0 ;
            mult1_acco     <= 'b0 ;
            mult2_shift    <= 'b0 ;
        end
        else if (en) begin
            rdy            <= 1'b1 ;
            mult2_shift    <= mult2 >> 1 ;
            mult1_o        <= mult1 << 1 ;
            if (mult2[0]) begin
                //乘數(shù)對(duì)應(yīng)位為1則累加
                mult1_acco  <= mult1_acci + mult1 ;  
            end
            else begin
                mult1_acco  <= mult1_acci ; //乘數(shù)對(duì)應(yīng)位為1則保持
            end
        end
        else begin
            rdy            <= 'b0 ;
            mult1_o        <= 'b0 ;
            mult1_acco     <= 'b0 ;
            mult2_shift    <= 'b0 ;
        end
    end

endmodule

頂層例化

多次模塊例化完成多次累加,代碼文件如下(mult_man.v ):

module    mult_man
    #(parameter N=4,
      parameter M=4)
    (
      input                     clk,
      input                     rstn,
      input                     data_rdy ,
      input [N-1:0]             mult1,
      input [M-1:0]             mult2,

      output                    res_rdy ,
      output [N+M-1:0]          res );

    wire [N+M-1:0]       mult1_t [M-1:0] ;
    wire [M-1:0]         mult2_t [M-1:0] ;
    wire [N+M-1:0]       mult1_acc_t [M-1:0] ;
    wire [M-1:0]         rdy_t ;

    //第一次例化相當(dāng)于初始化肚吏,不能用 generate 語句
    mult_cell      #(.N(N), .M(M))
    u_mult_step0
    (
      .clk              (clk),
      .rstn             (rstn),
      .en               (data_rdy),
      .mult1            ({{(M){1'b0}}, mult1}),
      .mult2            (mult2),
      .mult1_acci       ({(N+M){1'b0}}),
      //output
      .mult1_acco       (mult1_acc_t[0]),
      .mult2_shift      (mult2_t[0]),
      .mult1_o          (mult1_t[0]),
      .rdy              (rdy_t[0]) );

    //多次模塊例化方妖,用 generate 語句
    genvar               i ;
    generate
        for(i=1; i<=M-1; i=i+1) begin: mult_stepx
            mult_cell      #(.N(N), .M(M))
            u_mult_step
            (
              .clk              (clk),
              .rstn             (rstn),
              .en               (rdy_t[i-1]),
              .mult1            (mult1_t[i-1]),
              .mult2            (mult2_t[i-1]),
              //上一次累加結(jié)果作為下一次累加輸入
              .mult1_acci       (mult1_acc_t[i-1]),
              //output
              .mult1_acco       (mult1_acc_t[i]),                                      
              .mult1_o          (mult1_t[i]),  //被乘數(shù)移位狀態(tài)傳遞
              .mult2_shift      (mult2_t[i]),  //乘數(shù)移位狀態(tài)傳遞
              .rdy              (rdy_t[i]) );
        end
    endgenerate

    assign res_rdy       = rdy_t[M-1];
    assign res           = mult1_acc_t[M-1];

endmodule

參考:
https://www.runoob.com/w3cnote/verilog-tutorial.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市罚攀,隨后出現(xiàn)的幾起案子党觅,更是在濱河造成了極大的恐慌,老刑警劉巖斋泄,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杯瞻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡是己,警方通過查閱死者的電腦和手機(jī)又兵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門任柜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卒废,“玉大人,你說我怎么就攤上這事宙地∷と希” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵宅粥,是天一觀的道長(zhǎng)参袱。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么抹蚀? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任剿牺,我火速辦了婚禮,結(jié)果婚禮上环壤,老公的妹妹穿的比我還像新娘晒来。我一直安慰自己,他們只是感情好郑现,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布湃崩。 她就那樣靜靜地躺著,像睡著了一般接箫。 火紅的嫁衣襯著肌膚如雪攒读。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天辛友,我揣著相機(jī)與錄音薄扁,去河邊找鬼。 笑死瞎领,一個(gè)胖子當(dāng)著我的面吹牛泌辫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播九默,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼震放,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了驼修?” 一聲冷哼從身側(cè)響起殿遂,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乙各,沒想到半個(gè)月后墨礁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耳峦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年恩静,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹲坷。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡驶乾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出循签,到底是詐尸還是另有隱情级乐,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布县匠,位于F島的核電站风科,受9級(jí)特大地震影響撒轮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贼穆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一题山、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧故痊,春花似錦臀蛛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至豫领,卻和暖如春抡柿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背等恐。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工洲劣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人课蔬。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓囱稽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親二跋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子战惊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355