在本篇里,我們討論 Verilog 語言的綜合問題,Verilog HDL (Hardware Description Language) 中文名為硬件描述語言,而不是硬件設計語言。這個名稱提醒我們是在描述硬件,即用代碼畫圖。
在 Verilog 語言中,always 塊是一種常用的功能模塊,也是結構最復雜的部分。筆者初學時經常為 always 語句的編寫而苦惱,不知道使用哪種賦值語句,不了解兩種賦值之間的區別。究其本質是對 always 語句的綜合一知半解,常年使用軟件思維編寫代碼所致。
現在我總結出:always 塊的綜合可以分為至少三類電路,由於歷史原因,三種電路均使用 always 關鍵字。
此外 reg 關鍵字也存在一些迷惑性,綜合器並不一定生成寄存器。
reg 在 verilog 語法中的定義為變量,它有可能是寄存器,也有可能是連線。
第一類就是組合邏輯,如代碼 1-1 和 代碼 1-2
// 1-1 組合邏輯
always @ (*)
begin
if(a>b)
q = 1;
else
q = 0;
end
代碼 1-1 是展示了一個簡單組合邏輯的 always 塊,它應該被綜合成一個一位的比較器。
// 1-2 組合邏輯,缺少敏感信號
always @ (a)
begin
if(a>b)
q = 1;
else
q = 0;
end
代碼 1-2 也是一個組合邏輯,與 1-1 不同的是,敏感信號列表中沒有 b。
我們知道,在 Verilog 語法中, always 塊的含義是一個重復執行的語句。
那么 1-2 會綜合成一個比較器:
- 當 a 發生變化時,q 發生變化
- 當 b 發生變化,由於 b 不再敏感信號列表中,所以 q 不變
這是一個徹頭徹尾的軟件思維,世界上不存在這種電路,綜合器多半會綜合一個與代碼 1-1 一樣的電路,然后報一個警告。
編寫組合邏輯的 always 塊,使用 * 代替敏感信號列表是一個簡單方便而且不容易出錯的好辦法。
第二類就是時序邏輯
// 2-1 時序邏輯
reg [1:0] q;
always @ (posedge clk)
begin
q <= q + 1'b1;
end
注意:這里使用的是阻塞賦值,我們的 q 這個時候被綜合成一個寄存器,而不是一個軟件上的變量。
代碼 2-1 是一個時序邏輯單元,它應該被綜合成一個計數器,每當時鍾的上升沿,q 自增一。綜合后的 部分 RTL 圖如下:

我們可以看到,q 通過了一個加法器,加法器是兩位的。
而關鍵的 always 塊的綜合結果如下圖所示:

我們可以看到,always 塊綜合了一個時鍾上升沿觸發的 D 觸發器。每當時鍾的上升沿,D 觸發器就把輸入 D 傳遞到另一側 Q。
綜合器就這樣完成了我們的設計意圖:D 的左側總是等於 Q+1,只有在 時鍾的上升沿,才完成值的傳遞。完整的RTL 圖如下:

// 2-2 時序邏輯,帶異步復位
reg [1:0] q;
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
q <= 2'b00;
else
q <= q + 1'b1;
end
綜合后的 RTL 圖如下:

代碼2-2綜合成一個帶有清零端的 D 觸發器,其余與 2-1 無異。
// 2-3 時序邏輯,帶同步置位
reg [1:0] q;
always @ (posedge clk or posedge set)
begin
if(set)
q <= 2'b11;
else
q <= q + 1'b1;
end

代碼 2-3 綜合成一個帶有同步置位的 D 觸發器,其余與 2-1 無異。
// 2-4 時序邏輯,帶同步置位和異步復位
reg [1:0] q;
always @ (posedge clk or posedge set or negedge rst_n)
begin
if(!rst_n)
q <= 2'b00;
else if(set)
q <= 2'b11;
else
q <= q + 1'b1;
end
RTL 圖:

可以看到多了個與門,代碼 2-4 是代碼 2-2 和 代碼 2-3 的組合結果。
當然咯,這里的綜合是指的邏輯綜合,而實際上的綜合和具體實現與邏輯無關。比如在 FPGA 芯片使用的是查找表LUT,並沒有獨立的組合邏輯。而仿真器則使用編譯方法,仿真器把 verilog 語言編譯為 x86 匯編並直接在 CPU 上運行。
好啦,本篇到這里就結束了,下一篇會深入討論綜合工具的一些其他細節。🤭
