Verilog語法之十三:編譯預處理
本文首發於微信公眾號“花螞蟻”,想要學習FPGA及Verilog的同學可以關注一下。
Verilog HDL語言和C語言一樣也提供了編譯預處理的功能。“編譯預處理”是Verilog HDL編譯系統的一個組成部分。
Verilog HDL語言允許在程式中使用幾種特殊的命令(它們不是一般的語句)。Verilog HDL編譯系統通常先對這些特殊的命令進行“預處理”,然後將預處理的結果和源程式一起在進行通常的編譯處理。
在Verilog HDL語言中,為了和一般的語句相區別,這些預處理命令以符號“ `”開頭(注意這個符號是不同於單引號“ '”的)。這些預處理命令的有效作用範圍為定義命令之後到本檔案結束或到其它命令定義替代該命令之處。Verilog HDL提供了以下預編譯命令:
`accelerate,`autoexpand_vectornets,`celldefine,`default_nettype,`define,`else,`endcelldefine,`endif,`endprotect,`endprotected,`expand_vectornets,`ifdef,`include,`noaccelerate,`noexpand_vectornets,`noremove_gatenames,`noremove_netnames,`nounconnected_drive,`protect,`protecte,`remove_gatenames,`remove_netnames,`reset,`timescale,`unconnected_drive
在這一小節裡只對常用的`define、`include、`timescale進行介紹,其餘的請查閱參考書。
1.巨集定義 `define
用一個指定的識別符號(即名字)來代表一個字串,它的一般形式為:
`define 識別符號(巨集名) 字串(巨集內容)
如:
`define signal string
它的作用是指定用識別符號signal來代替string這個字串,在編譯預處理時,把程式中在該命令以後所有的signal都替換成string。
這種方法使使用者能以一個簡單的名字代替一個長的字串,也可以用一個有含義的名字來代替沒有含義的數字和符號,因此把這個識別符號(名字)稱為“巨集名”,在編譯預處理時將巨集名替換成字串的過程稱為“巨集展開”。`define是巨集定義命令。
[例1]:
`define WORDSIZE 8
module
reg[1:`WORDSIZE] data; //這相當於定義 reg[1:8] data;
關於巨集定義的八點說明:
1) 巨集名可以用大寫字母表示,也可以用小寫字母表示。建議使用大寫字母,以與變數名相區別。
2) `define命令可以出現在模組定義裡面,也可以出現在模組定義外面。巨集名的有效範圍為定義命令之後到原檔案結束。通常,`define命令寫在模組定義的外面,作為程式的一部分,在此程式內有效。
3)在引用已定義的巨集名時,必須在巨集名的前面加上符號“`”,表示該名字是一個經過巨集定義的名字。
4) 使用巨集名代替一個字串,可以減少程式中重複書寫某些字串的工作量。而且記住一個巨集名要比記住一個無規律的字串容易,這樣在讀程式時能立即知道它的含義,當需要改變某一個變數時,可以只改變 `define命令列,一改全改。如例1中,先定義WORDSIZE代表常量8,這時暫存器data是一個8位的暫存器。如果需要改變暫存器的大小,只需把該命令列改為:`define WORDSIZE 16。這樣暫存器data則變為一個16位的暫存器。由此可見使用巨集定義,可以提高程式的可移植性和可讀性。
5) 巨集定義是用巨集名代替一個字串,也就是作簡單的置換,不作語法檢查。預處理時照樣代入,不管含義是否正確。只有在編譯已被巨集展開後的源程式時才報錯。
6)巨集定義不是Verilog HDL語句,不必在行末加分號。如果加了分號會連分號一起進行置換。如:
[例2]:
module test;
reg a, b, c, d, e, out;
`define expression a+b+c+d;
assign out = `expression + e;
...
endmodule
經過巨集展開以後,該語句為:
assign out = a+b+c+d;+e;
顯然出現語法錯誤。
7) 在進行巨集定義時,可以引用已定義的巨集名,可以層層置換。如:
[例3]:
module test;
reg a, b, c;
wire out;
`define aa a + b
`define cc c + `aa
assign out = `cc;
endmodule
這樣經過巨集展開以後,assign語句為
assign out = c + a + b;
8) 巨集名和巨集內容必須在同一行中進行宣告。如果在巨集內容中包含有註釋行,註釋行不會作為被置換的內容。如:
[例4]:
module
`define typ_nand nand #5 //define a nand with typical delay
`typ_nand g121(q21,n10,n11);
………
endmodule
經過巨集展開以後,該語句為:
nand #5 g121(q21,n10,n11);
巨集內容可以是空格,在這種情況下,巨集內容被定義為空的。當引用這個巨集名時,不會有內容被置換。
注意:組成巨集內容的字串不能夠被以下的語句記號分隔開的。
- · 註釋行
- · 數字
- · 字串
- · 確認符
- · 關鍵詞
- · 雙目和三目字元運算子
如下面的巨集定義宣告和引用是非法的。
`define first_half "start of string
$display(`first_half end of string");
注意在使用巨集定義時要注意以下情況:
1) 對於某些 EDA軟體,在編寫源程式時,如使用和預處理命令名相同的巨集名會發生衝突,因此建議不要使用和預處理命令名相同的巨集名。
2)巨集名可以是普通的識別符號(變數名)。例如signal_name 和 'signal_name的意義是不同的。但是這樣容易引起混淆,建議不要這樣使用。
2.“檔案包含”處理`include
所謂“檔案包含”處理是一個原始檔可以將另外一個原始檔的全部內容包含進來,即將另外的檔案包含到本檔案之中。Verilog HDL語言提供了`include命令用來實現“檔案包含”的操作。其一般形式為:
`include “檔名”
上圖表示“檔案包含”的含意。圖(a)為檔案File1.v,它有一個`include "File2.v"命令,然後還有其它的內容(以A表示)。圖(b)為另一個檔案File2.v,檔案的內容以B表示。在編譯預處理時,要對`include命令進行“檔案包含”預處理:將File2.v的全部內容複製插入到 `include "File2.v"命令出現的地方,即File2.v 被包含到File1.v中,得到圖(c)所示的結果。
在接著往下進行的編譯中,將“包含”以後的File1.v作為一個原始檔單位進行編譯。
“檔案包含”命令是很有用的,它可以節省程式設計人員的重複勞動。可以將一些常用的巨集定義命令或任務(task)組成一個檔案,然後用`include命令將這些巨集定義包含到自己所寫的原始檔中,相當於工業上的標準元件拿來使用。另外在編寫Verilog HDL原始檔時,一個原始檔可能經常要用到另外幾個原始檔中的模組,遇到這種情況即可用`include命令將所需模組的原始檔包含進來。
[例1]:
(1)檔案aaa.v
module aaa(a,b,out);
input a, b;
output out;
wire out;
assign out = a^b;
endmodule
(2)檔案 bbb.v
`include "aaa.v"
module bbb(c,d,e,out);
input c,d,e;
output out;
wire out_a;
wire out;
aaa aaa(.a(c),.b(d),.out(out_a));
assign out=e&out_a;
endmodule
在上面的例子中,檔案bbb.v用到了檔案aaa.v中的模組aaa的例項器件,通過“檔案包含”處理來呼叫。模組aaa實際上是作為模組bbb的子模組來被呼叫的。在經過編譯預處理後,檔案bbb.v實際相當於下面的程式檔案bbb.v:
module aaa(a,b,out);
input a, b;
output out;
wire out;
assign out = a ^ b;
endmodule
module bbb( c, d, e, out);
input c, d, e;
output out;
wire out_a;
wire out;
aaa aaa(.a(c),.b(d),.out(out_a));
assign out= e & out_a;
endmodule
關於“檔案包含”處理的四點說明:
1) 一個`include命令只能指定一個被包含的檔案,如果要包含n個檔案,要用n個`include命令。
2) `include命令可以出現在Verilog HDL源程式的任何地方,被包含檔名可以是相對路徑名,也可以是絕對路徑名。例如:'include"parts/count.v"
3) 可以將多個`include命令寫在一行,在`include命令列,只可以出空格和註釋行。例如下面的寫法是合法的。
'include "fileB" 'include "fileC" //including fileB and fileC
4) 如果檔案1包含檔案2,而檔案2要用到檔案3的內容,則可以在檔案1用兩個`include命令分別包含檔案2和檔案3,而且檔案3應出現在檔案2之前。例如在下面的例子中,即在file1.v中定義:
`include"file3.v"
`include"file2.v"
module test(a,b,out);
input[1:`size2] a, b;
output[1:`size2] out;
wire[1:`size2] out;
assign out= a+b;
endmodule
file2.v的內容為:
`define size2 `size1+1
.
.
.
file3.v的內容為:
`define size1 4
.
.
.
這樣,file1.v和file2.v都可以用到file3.v的內容。在file2.v中不必再用 `include "file3.v"了。
5) 在一個被包含檔案中又可以包含另一個被包含檔案,即檔案包含是可以巢狀的。例如上面的問題也可以這樣處理,見下圖,
它的作用和下圖的作用是相同的。
3.時間尺度 `timescale
`timescale命令用來說明跟在該命令後的模組的時間單位和時間精度。使用`timescale命令可以在同一個設計裡包含採用了不同的時間單位的模組。
例如,一個設計中包含了兩個模組,其中一個模組的時間延遲單位為ns,另一個模組的時間延遲單位為ps。EDA工具仍然可以對這個設計進行模擬測試。
`timescale 命令的格式如下:
`timescale<時間單位>/<時間精度>
在這條命令中,時間單位參量是用來定義模組中模擬時間和延遲時間的基準單位的。時間精度參量是用來宣告該模組的模擬時間的精確程度的,該參量被用來對延遲時間值進行取整操作(模擬前),因此該參量又可以被稱為取整精度。
如果在同一個程式設計裡,存在多個`timescale命令,則用最小的時間精度值來決定模擬的時間單位。另外時間精度至少要和時間單位一樣精確,時間精度值不能大於時間單位值。
在`timescale命令中,用於說明時間單位和時間精度參量值的數字必須是整數,其有效數字為1、10、100,單位為秒(s)、毫秒(ms)、微秒(us)、納秒(ns)、皮秒(ps)、毫皮秒(fs)。這幾種單位的意義說明見下表。
下面舉例說明`timescale命令的用法。
[例1]:
`timescale 1ns/1ps
在這個命令之後,模組中所有的時間值都表示是1ns的整數倍。這是因為在`timescale命令中,定義了時間單位是1ns。模組中的延遲時間可表達為帶三位小數的實型數,因為 `timescale命令定義時間精度為1ps.
[例2]:
`timescale 10us/100ns
在這個例子中,`timescale命令定義後,模組中時間值均為10us的整數倍。因為`timesacle 命令定義的時間單位是10us。延遲時間的最小分辨度為十分之一微秒(100ns),即延遲時間可表達為帶一位小數的實型數。
例3:
`timescale 10ns/1ns
module test;
reg set;
parameter d=1.55;
initial
begin
#d set=0;
#d set=1;
end
endmodule
在這個例子中,`timescale命令定義了模組test的時間單位為10ns、時間精度為1ns。因此在模組test中,所有的時間值應為10ns的整數倍,且以1ns為時間精度。這樣經過取整操作,存在引數d中的延遲時間實際是16ns(即1.6×10ns),這意味著在模擬時刻為16ns時暫存器set被賦值0,在模擬時刻為32ns時暫存器set被賦值1。模擬時刻值是按照以下的步驟來計算的。
1) 根據時間精度,引數d值被從1.55取整為1.6。
2) 因為時間單位是10ns,時間精度是1ns,所以延遲時間#d作為時間單位的整數倍為16ns。
3) EDA工具預定在模擬時刻為16ns的時候給暫存器set賦值0(即語句 #d set=0;執行時刻),在模擬時刻為32ns的時候給暫存器set賦值1(即語句 #d set=1;執行時刻),
注意:如果在同一個設計裡,多個模組中用到的時間單位不同,需要用到以下的時間結構。
1) 用`timescale命令來宣告本模組中所用到的時間單位和時間精度。
2) 用系統任務$printtimescale來輸出顯示一個模組的時間單位和時間精度。
3) 用系統函式$time和$realtime及%t格式宣告來輸出顯示EDA工具記錄的時間資訊。
4.條件編譯命令`ifdef、`else、`endif
一般情況下,Verilog HDL源程式中所有的行都將參加編譯。但是有時希望對其中的一部分內容只有在滿足條件才進行編譯,也就是對一部分內容指定編譯的條件,這就是“條件編譯”。有時,希望當滿足條件時對一組語句進行編譯,而當條件不滿足是則編譯另一部分。
條件編譯命令有以下幾種形式:
1)
`ifdef 巨集名 (識別符號)
程式段1
`else
程式段2
`endif
它的作用是當巨集名已經被定義過(用`define命令定義),則對程式段1進行編譯,程式段2將被忽略;否則編譯程式段2,程式段1被忽略。其中`else部分可以沒有,即:
2)
`ifdef 巨集名 (識別符號)
程式段1
`endif
這裡的 “巨集名” 是一個Verilog HDL的識別符號,“程式段”可以是Verilog HDL語句組,也可以是命令列。這些命令可以出現在源程式的任何地方。
注意:被忽略掉不進行編譯的程式段部分也要符合Verilog HDL程式的語法規則。
通常在Verilog HDL程式中用到`ifdef、`else、`endif編譯命令的情況有以下幾種:
- · 選擇一個模組的不同代表部分。
- · 選擇不同的時序或結構資訊。
- · 對不同的EDA工具,選擇不同的激勵。
本文首發於微信公眾號“花螞蟻”,想要學習FPGA及Verilog的同學可以關注一下。