Vivado使用技巧(28):支援的Verilog語法
複雜的電路設計通常使用自頂向下的設計方法,設計過程中的不同階段需要不同的設計規格。比如架構設計階段,需要模組框圖或演算法狀態機(ASM)圖表這方面的設計說明。一個框圖或演算法的實現與暫存器(reg)和連線(wire)息息相關。Verilog便具有將ASM圖表和電路框圖用計算機語言表達的能力,本文將講述Vivado綜合支援的Verilog硬體描述語言。
Verilog提供了行為化和結構化兩方面的語言結構,描述設計物件時可以選擇高層次或低層次的抽象等級。使用Verilog設計硬體時,可以將其視作並行處理和物件導向程式設計。Vivado綜合支援IEEE 1364標準。Vivado綜合對Verilog的支援可以用最有效的方式描述整體電路和各個模組。綜合會為每個模組選擇最佳的綜合流程,將高層次的行為級或低層次的結構級轉換為門級網表。
本文將介紹Vivado綜合支援的所有Verilog語法。
1.可變部分選擇
除了用兩個明確的值限定選擇邊界外(如assign out = data[8:2]),還可以使用變數從向量中選擇一組bit。設定一個起始點和擷取的寬度,起始點可以動態變化。示例如下:
reg [3:0] data;
reg [3:0] select;
wire [7:0] byte = data[select +: 8]; //+、-表示從起始點開始增加或減少
2.結構化Verilog
Verilog可以進行多個程式碼塊設計,並按一定的設計層次組合起來。下面給出於此相關的重要概念:
- 元件(Component):結構化設計中的一個基本塊;
- 申明(Declaration):元件與外部交流的資訊;
- 主體(Body):元件內部的行為或結構;
- 埠(Port):元件的I/O;
- 訊號(Signal):元件與元件之間的連線;
一個元件用常見的模組(module)來表示。元件之間的連線由例項化(instantiation)宣告實現。例項化宣告規定一個元件在另外一個元件或電路中的例項,賦予識別符號,並用關係列表設定訊號與埠之間的聯絡。
除了自己設計的元件外,結構化Verilog還支援例項化預定義的原語:邏輯閘、暫存器、Xilinx特定的原語(如CLKDLL、BUFG)。這些原語都定義在Xilinx Verilog庫檔案unisim_comp.v中。邏輯閘原語包括AND、OR、XOR、NAND、NOR、NOT。例項化這些邏輯閘來搭建更大的邏輯電路,示例如下:
//實現2輸入或非邏輯功能
module build_xor
(
input a, b,
output c
);
wire a_not, b_not;
//每個例項必須有不同的例項化名稱
not a_inv (a_not, a);
not b_inv (b_not, b);
and a1 (x, a_not, b);
and a2 (y, b_not, a);
or out (c, x, y);
endmodule
3.Verilog引數
引數化程式碼提高了可讀性和程式碼緊湊型、容易維護和再使用。一個Verilog引數(parameter)就是一個常數(不支援字串),且例項化引數化模組時可以改寫引數值。下面給出示例:
//Verilog引數控制例項化塊暫存器的寬度
module myreg #(parameter SIZE = 1)
(
input clk, clken,
input [SIZE-1:0]d,
output reg [SIZE-1:0]q
);
always @(posedge clk)
if (clken) q <= d;
endmodule
//頂層模組
module test #(parameter SIZE = 8)
(
input clk, clken,
input [SIZE-1:0] di,
output [SIZE-1:0] do
);
myreg #SIZE inst_reg (clk, clken, di, do);
endmodule
4.Verilog使用限制
在Vivado綜合中用到的Verilog語法有如下3點限制:
大小寫敏感:Verilog是一種大小寫敏感的語言,但在Vivado中,只有例項和訊號名稱會區分大小寫。如果兩個module名稱只有大小寫不同,綜合時會報錯。儘管如此,也不推薦僅用大小寫區分兩個不同的物件,在混合語言工程中可能會引起意料之外的問題。
阻塞和非阻塞賦值:不要混合使用阻塞和非阻塞賦值。儘管綜合時可能不會報錯,但在模擬時會出現錯誤。下面給出兩個錯誤例子:
//同一訊號不要混用阻塞和非阻塞賦值
always @(in1)
if (in2) out1 = in1;
else out1 <= in2;
//同一訊號的不同bit不要混用
if (in2) begin
out1[0] = 1'b0;
out1[1] <= in1;
end
else begin
out1[0] = in2;
out1[1] <= 1'b1;
end
- 整數處理:某些情況下,Vivado綜合器處理整數時與其它綜合工具方法不同,因此必須使用特定的程式碼編寫方式。在Case語句或拼接語句中,使用未定義大小的整數都會導致無法預料的結果。下面給出例子:
//case語句
reg [2:0] condition1;
always @(condition1) begin
case(condition1)
4 : data_out = 2; //生成錯誤結果
3'd4 : data_out = 2; //正常工作
endcase
end
//拼接語句
reg [31:0] temp;
assign temp = 4'b1111 % 2; //未確定位寬的運算用臨時訊號儲存
assign dout = {12/3,temp,din}; //12/3運算位寬不確定,結果錯誤
5.Verilog構造和系統任務
Vivado綜合支援的Verilog構造與系統任務包括:
整數、實數、assign(有限制)、deassign(有限制)、repeat語法(重複值必須是常數)、for語法(範圍必須是靜態的)、disable(不能用於for迴圈和repeat迴圈)、module定義、defparam、例項陣列、`default_nettype、`define、`ifdef、`ifndef、`elsif、`include、`file、`line、$fclose、$fgets、$fopen、$fscanf、$readmemb、$readmemh、$signed、$unsigned、$floor(僅用於引數)、$ceil(僅用於引數)。
Vivado綜合不支援和會忽視的的Verilog構造和系統任務包括:
字串、網路型別(tri0、tri1、trireg)、驅動強度、實數和實時暫存器、命名事件、事件(@)、延遲(#)、force、release、forever語法、wait、並行塊、設定塊、macromodule定義、層次結構名稱、`celldefine、`endcelldefine、`resetall、`timescale、`unconnected_drive、`nounconnected_drive、`uselib、$display、$fdisplay、$finish、$fwrite、$monitor、$random、$stop、$strobe、$time、$write、$clog2(僅SystemVerilog支援)、$rtoi、$itor、all others。
介紹其中幾個非常常用的系統任務:
- $signed和$unsigned可以強制規定輸入資料為帶符號數或無符號數,並作為返回值,不用管之前的符號。
- $readmemb和$readmemh可以用於初始化塊儲存器,兩者分別用2進位制和16進製表示。如“$readmemb(“ram.data”, ram, 0, 7)”;。
6.Verilog原語
Vivado支援上文列出的Verilog門級原語,但不支援上拉下拉、驅動強度和延遲、原語矩陣這些型別的門級原語。也不支援如下轉換級原語:cmos、nmos、pmos、rcmos、rnmos、rpmos、rtran、rtranif0、rtranif1、tran、tranif0、tranif1。
例項化門級原語的示例如下:
gate_type instance_name (output, inputs); //語法模板
and U1 (out, in1, in2);
bufif1 U2 (triout, data, trienable);
7.行為級Verilog
行為級Verilog中的變數都申明為整數,資料型別可以是reg(程式塊中賦值)、wire(連續賦值)和integer(會被轉換為暫存器型別)。所有變數的預設位寬為1bit,稱作標量(scalar);定義的N bits位寬變數稱作向量(Vector)。reg和wire可以定義為帶符號數signed或無符號數unsigned。變數的每個bit可以是如下值:1(邏輯1)、0(邏輯0)、x(未知邏輯值)、z(高阻)。
reg [3:0] arb_priority;
wire [31:0] arb_request;
wire signed [8:0] arb_signed;
暫存器在定義時可以初始化,初始值是一個常數或引數,不能是函式或任務的呼叫。在全域性復位或上電時,Vivado綜合會將初始化值作為暫存器的輸出(作為暫存器的INIT屬性值)。而且,該初始值與本地復位是相互獨立的。
//定義時初始化暫存器
reg arb_onebit = 1'b0;
reg [3:0] arb_priority = 4'b1011;
//本地置位/復位
always @(posedge clk)
if (rst) arb_onebit <= 1'b0;
Verilog支援定義wire和reg的陣列,支援一位陣列和二維陣列,但每次從陣列中選擇的元素不能超過一個,陣列也不能作為任務或函式的傳遞引數。陣列的定義示例如下:
//有32個元素的陣列,每個元素4bits位寬
reg [3:0] mem_array [31:0];
//包含64個8bits位寬元素的陣列
wire [7:0] mem_array [63:0];
//包含256*16個8bits位寬wire元素的二維陣列
wire [63:0] array2 [0:255][0:15];
//包含256*8個64bits位寬reg元素的二維陣列
reg [63:0] array2 [255:0][7:0];
Vivado支援的所有表示式列在下表中:
符號 | 表示式 |
---|---|
{} | 拼接運算子 |
{{}} | 複製運算子 |
+, -, , /, %, * | 加、減、乘、除、求餘、求冪 |
>, <, >=, <= | 關係運算 |
! | 邏輯取反 |
&& | 邏輯與 |
|| | 邏輯或 |
==, != | 邏輯相等,邏輯不等 |
=== | 條件相等 |
!== | 條件不等 |
~ | 按位取反 |
& | 按位與 |
| | 按位或 |
^ | 按位異或 |
~^, ^~ | 按位等價(異或非) |
~& | 與非運算 |
~| | 或非運算 |
<<, >> | 左移,右移 |
<<<,>>> | 帶符號左移,帶符號右移 |
?: | 條件表示式 |
or, ‘,’ | 事件或(如用於敏感列表) |
其中“===”和“!==”在綜合時與“==”和“!=”功能相同,沒有任何差別。但在模擬中,可以用來判斷變數是否與’x’和’z’是否相等。下表給出常用操作符的運算結果,以供查閱。
initial和always是兩個程式塊,每個塊內部組織了一些語法宣告,用begin和end表示範圍。塊內部的語法宣告按順序執行。綜合時只會處理always塊,會忽略initial塊。
8.模組module
Verilog中描述元件(component)的方法便是模組(module),模組必須申明與例項化。模組申明包括模組名稱、電路I/O埠列表、定義功能的主體,並以endmodule結束。
每個電路I/O埠要有名稱、埠模式(input、output、inout),如果埠是陣列型別還要有範圍資訊。下面給出兩種模組申明方法的示例:
//方法1,老版本Verilog
module example (A, B, O);
input A, B;
output O;
assign O = A & B;
endmodule
//方法2,推薦用法
module example
(
input A, B,
output O
);
assign O = A & B;
endmodule
例項化模組時,要定義一個例項化名稱和一個埠關係表。列表要規定例項與頂層模組之間如何連線,列表中的每一個元素將模組申明中的一個形式埠(port)和頂層模組中的實際網路(net)連線在一起。下面給出一個例項化上述模組的例子:
module top
(
input A, B, C,
output O
);
wire tmp;
example inst_example (.A(A), .B(B), .O(tmp));
assign O = tmp | C;
endmodule
Vivado綜合支援兩種連續賦值方式(只適用於wire和三態資料型別),用簡潔的方式完成組合邏輯賦值,但是綜合時會忽略連續賦值中的延遲和強度定義。顯式連續賦值用assign關鍵詞開頭,緊跟一個已經申明過的網路:“wire mysignal; assign mysignal = select ? b : a;”。隱式連續賦值在申明時便完成賦值:“wire misignal = a | b;”。
9.過程賦值
如上所述,wire和三態型別要用連續賦值,reg型別變數則需要用過程賦值,藉助always塊、任務(task)、函式(function)實現。學習Verilog難免會遇到阻塞賦值和非阻塞賦值的概念,但其實在設計中只需要明白阻塞賦值(=)用於模擬;非阻塞賦值(<=)用於設計中的過程賦值即可。
always塊中的組合邏輯由Verilog時間控制語句有效地建模。其中,延遲時間控制語句[#]僅用於模擬,綜合時會忽略;組合邏輯建模主要由事件控制時間控制語句[@]實現。
每個always塊都有一個敏感列表,列在“always @”後面的括號中。如果敏感列表中一個訊號的相關事件發生(值變化或邊沿到來),就會啟用該always塊。在always塊中,如果訊號沒有在if或case的所有分支中明確地賦值,綜合會產生一個鎖存器保持之前的值。一個程式塊中可以使用如下語句:
[1].if-else語句:
使用true和false條件來執行語句,執行多條語句要使用begin…end關鍵詞。
[2].case語句:
比較表示式和分支的值,比較順序按照編寫分支的順序進行,執行第一個匹配的分支。如果沒有匹配項則執行default分支。case語句中不要使用未指定位寬大小的整數,否則可能會產生錯誤結果。
casez將分支的任意bit位上的z值視作不關心;casex將分支的任意bit位上的x值視作不關心。casez和casex中不關心的bit用‘?’代替。下面給出一個使用case的示例程式碼:
module mux4
(
input [1:0] sel,
input [1:0] a, b, c, d,
output reg [1:0] outmux
);
always @ *
case(sel)
2'b00 : outmux = a;
2'b01 : outmux = b;
2'b10 : outmux = c;
2'b11 : outmux = d;
endcase
endmodule
上述程式碼在評估輸入值時,按照一定的優先順序順序進行。如果希望能並行地處理這個過程,使用paralled_case屬性,將case語句替換為“(* paralled_case *)” case(sel)”。
[3].For語句與Repeat語句:
使用迴圈可以完成一些重複性工作。For迴圈的邊界必須是常數,停止迴圈條件需要使用>、<、>=、<=四種運算子。使用“var = var +或- step”來控制執行下一輪運算,var為迴圈變數,step是一個常數值。
使用repeat語句,重複次數也必須是常數值。
[4].While迴圈:
While的測試表示式可以是任意合法的Verilog表示式。為了避免造成無限迴圈,可以使用-loop_iteration_limit選項。該語法很少使用,下面給出一個示例程式碼:
parameter P = 4;
always @(ID_complete)
begin : UNIDENTIFIED
integer i;
reg found;
unidentified = 0;
i = 0;
found = 0;
while (!found && (i < P))
begin
found = !ID_complete[i];
unidentified[i] = !ID_complete[i];
i = i + 1;
end
end
[5].順序always塊:
always塊可以描述帶有順序性的電路,敏感列表中需要包含如下邊沿觸發事件(上升沿posedge或下降沿negedge):必須有一個時鐘事件、可選的置位/復位事件。如果不需要非同步訊號,always塊模板如下:
always @(posedge CLK)
begin
//同步部分
end
如果需要非同步控制訊號,always塊模板如下:
always @(posedge CLK or posedge ACTRL1 or à )
begin
if (ACTRL1)
//非同步部分
else
//同步部分
end
下面給出四個不同觸發方式的順序always塊示例程式碼:
//上升沿觸發時鐘控制的8bits暫存器
module seq1
(
input [7:0]DI,
input CLK,
output reg [7:0] DO
);
always @(posedge CLK)
DO <= DI ;
endmodule
//新增一個高電平有效非同步復位訊號
module seq1
(
input [7:0]DI,
input CLK, ARST,
output reg [7:0] DO
);
always @(posedge CLK or posedge ARST)
if (ARST == 1'b1) DO <= 8'h00;
else DO <= DI ;
endmodule
//再新增一個低電平有效非同步置位訊號
module seq1
(
input [7:0]DI,
input CLK, ARST, ASET
output reg [7:0] DO
);
always @(posedge CLK or posedge ARST or negedge ASET)
if (ARST == 1'b1) DO <= 8'h00;
else if (ASET == 1'b1) DO <= 8'hFF;
else DO <= DI ;
endmodule
//不使用非同步控制邏輯,使用同步復位
module seq1
(
input [7:0]DI,
input CLK, SRST,
output reg [7:0] DO
);
always @(posedge CLK)
if (SRST == 1'b1) DO <= 8'h00;
else DO <= DI ;
endmodule
最後再補充一些與賦值有關的內容。如果表示式左邊位寬大於右邊的位寬,賦值時需要在高位填充:
- 如果表示式右邊為無符號數,則高位補0;
- 如果表示式右邊為帶符號數,則高位補符號位;
- 如果表示式右邊的最高位為x或z,則無論該數為無符號數還是帶符號數,高位都補充為x或z。
10.任務與函式
如果設計中要多次使用重複的程式碼,可以使用任務task和函式function來減少程式碼量,提升可維護性。任務和函式必須在模組中申明和使用,函式頭只包含輸入引數,任務頭包含輸入、輸出和雙向引數。函式的返回值可以申明為無符號數或帶符號數,函式內容與always塊類似。下面分別給出一個函式和任務的示例程式碼:
//函式function使用示例
module test
(
input [3:0] A, B,
input CIN,
output [3:0] S,
output COUT
);
wire [1:0] S0, S1, S2, S3;
function signed [1:0] ADD;
input A, B, CIN;
reg S, COUT;
begin
S = A ^ B ^ CIN;
COUT = (A&B) | (A&CIN) | (B&CIN);
ADD = {COUT, S};
end
endfunction
assign S0 = ADD (A[0], B[0], CIN),
S1 = ADD (A[1], B[1], S0[1]),
S2 = ADD (A[2], B[2], S1[1]),
S3 = ADD (A[3], B[3], S2[1]),
S = {S3[0], S2[0], S1[0], S0[0]},
COUT = S3[1];
endmodule
//任務task使用示例
module test
(
input [3:0] A, B,
input CIN,
output [3:0] S,
output COUT
);
reg [1:0] S0, S1, S2, S3;
task ADD;
input A, B, CIN;
output [1:0] C;
reg [1:0] C;
reg S, COUT;
begin
S = A ^ B ^ CIN;
COUT = (A&B) | (A&CIN) | (B&CIN);
C = {COUT, S};
end
endtask
always @(A or B or CIN)
begin
ADD (A[0], B[0], CIN, S0);
ADD (A[1], B[1], S0[1], S1);
ADD (A[2], B[2], S1[1], S2);
ADD (A[3], B[3], S2[1], S3);
S = {S3[0], S2[0], S1[0], S0[0]};
COUT = S3[1];
end
endmodule
Verilog還支援遞迴任務和遞迴函式,要使用automatic關鍵詞申明。遞迴次數由-recursion_iteration_limit選項設定,預設為64,以避免無限遞迴。下面給出一個計算階乘的遞迴函式的例子。
function automatic [31:0] fac;
input [15:0] n;
if (n == 1) fac = 1;
else fac = n * fac(n-1);
endfunction
Vivado綜合支援函式呼叫來計算常數值,將其稱之為常數函式。下面給出一個使用常數函式的例子:
module test #(parameter ADDRWIDTH = 8, DATAWIDTH = 4)
(
input clk, we,
input [ADDRWIDTH-1:0] a,
input [DATAWIDTH-1:0] di,
output [DATAWIDTH-1:0] do
);
function integer getSize;
input addrwidth;
begin
getSize = 2**addrwidth;
end
endfunction
reg [DATAWIDTH-1:0] ram [getSize(ADDRWIDTH)-1:0];
always @(posedge clk)
if (we) ram[a] <= di;
assign do = ram[a];
endmodule
Verilog中的常數可以用2進位制、8進位制、10進位制和16進製表示,沒有明確表示時預設為10進位制。如下面4’b1010、4’o12、4’d10、4’ha表示同一個數。
11.Verilog巨集
Verilog可以像這樣定義巨集“`define TESTEQ1 4’b1101”。定義的巨集可以用在後面的程式碼中,如“if (request == `TESTEQ1)”。使用`ifdef和`endif可以檢測是否定義了某個巨集,相當於條件編譯。如果`ifedf呼叫的巨集被定義過,則內部的程式碼將會編譯;如果巨集沒有定義,則會編譯`else中的程式碼。`else不是必須的,但必須有`endif。
使用巨集可以在不修改原始碼的情況下修改設計,在IP核生成和流程測試中很有用。下面給出兩個使用巨集的例子:
//示例1
'define myzero 0
assign mysig = 'myzero;
//示例2,條件編譯
'ifdef MYVAR
module if_MYVAR_is_declared;
...
endmodule
'else
module if_MYVAR_is_not_declared;
...
endmodule
'endif
12.Include檔案
Verilog可以將原始碼分散在多個檔案中,當需要引用另一個檔案中的程式碼時,可以使用如下語句:“`include <path/file-to-be-included>”。該程式碼可以將指定檔案的內容全部插入到當前檔案的`include行中。Vivado首先會在指定路徑中查詢,如果沒有找到則會在-include_dirs選項設定的目錄中查詢。可以同時使用多個`include語句。
13.Generate語法
Verilog的註釋和C++語言相同,支援單行註釋和多行註釋,這裡不再舉例。最後再說說常用的Generate語法。使用generate可以簡化程式碼編寫工作,generate…endgenerate中的內容再RTL分析階段會被轉換為對應的電路。
使用generate語法可以建立原語或模組例項、initial或always程式塊、連續賦值、網路和變數申明、引數重定義、任務或函式定義。Vivado支援全部三種generate語法:generate迴圈(generate-for)、generate條件(generate-if-else)和generate情況(generate-case)。
[1]. generate-for
使用generate-for主要用來建立多個例項化,與for迴圈用法基本相同,但必須使用genvar變數,且begin語句必須有一個單獨的命名。下面給出一個示例程式碼:
generate genvar i;
for (i=0; i<=7; i=i+1)
begin : for_name
adder add (a[8*i+7 : 8*i], b[8*i+7 : 8*i], ci[i], sum_for[8*i+7 : 8*i],
c0_or[i+1]);
end
endgenerate
[2]. generate-if-else
主要用來控制生成哪一個物件,每一個分支用begin…end限定,begin語句必須有一個單獨的命名。下面給出一個示例程式碼:
//根據資料位寬選擇不同的乘法器實現方式
generate
if (IF_WIDTH < 10)
begin : if_name
multiplier_imp1 # (IF_WIDTH) u1 (a, b, sum_if);
end
else
begin : else_name
multiplier_imp2 # (IF_WIDTH) u2 (a, b, sum_if);
end
endgenerate
[3]. generate-case
主要用來控制在哪種條件下生成哪個物件。case的每一個分支用begin…end限定,begin語句必須有一個單獨的命名。下面給出一個示例程式碼:
//根據資料位寬選擇不同的加法器實現方式
generate
case (WIDTH)
1:
begin : case1_name
adder #(WIDTH*8) x1 (a, b, ci, sum_case, c0_case);
end
2:
begin : case2_name
adder #(WIDTH*4) x2 (a, b, ci, sum_case, c0_case);
end
default:
begin : d_case_name
adder x3 (a, b, ci, sum_case, c0_case);
end
endcase
endgenerate
相關文章
- Vivado使用技巧(19):使用Vivado Simulator
- Vivado使用技巧(27):RAM編寫技巧
- Vivado使用技巧(26):HDL編寫技巧
- Vivado使用技巧(17):建立IBIS模型模型
- 常識:Verilog語法-generate-for
- 常識:Verilog語法-取模
- 01-Verilog基本語法元素
- Vivado使用技巧(5):屬性編輯器的使用
- Vivado使用技巧(31):時鐘的約束方法
- Vivado使用技巧(21):模擬中的Debug特性
- VIVADO vhdl verilog 實現矩陣運算矩陣
- Vivado使用技巧(6):Messages視窗管理
- Vivado使用技巧(29):約束功能概述
- Vivado使用技巧(20):Waveform功能詳解ORM
- Vivado使用技巧(18):模擬功能概述
- Vivado使用技巧(33):時序異常
- Vivado使用技巧(25):Block Synthesis技術BloC
- Vivado使用技巧(9):COE檔案使用方法
- Verilog RTL優化策略(一):推薦使用assign語法替代if-else和case語法優化
- Vivado使用技巧(32):IO延遲的約束方法
- Vivado使用技巧(4):查詢功能詳解
- Vivado使用技巧(3):Force Up-to-Date功能
- Vivado使用技巧(11):設定FPGA配置模式FPGA模式
- Vivado使用技巧(8):Core Container打包IP核AI
- Vivado使用技巧(34):路徑分割現象
- Vivado使用技巧(30):使用時序約束嚮導
- Vivado使用技巧(16):SSN轉換噪聲分析
- Vivado使用技巧(14):IO規劃方法詳解
- Vivado使用技巧(23):綜合執行與OOC
- Vivado使用技巧(22):綜合策略與設定的選擇
- Vivado使用技巧(15):DRC設計規則檢查
- Vivado使用技巧(13):CSV檔案定義IO Ports
- 11/28日語法(what a 和 how a)
- Vivado使用技巧(7):使用IP核自帶Testbench進行模擬
- Vivado使用技巧(10):編輯與改寫IP核原始檔
- Vivado使用技巧(24):HDL/XDC中設定綜合屬性
- C++語法小技巧C++
- Vivado使用技巧(12):設定DCI與內部參考電壓