之前提到的議題都是在更加彈性設計電路,今天我們要介紹的是讓設計者方便撰寫 Verilog 程式碼。在 C 語言中,我們常常看見 #define
, #include
等,這些叫做「編譯器指引」(Compiler Directive),這些事又叫作程式的「預處理」。Verilog 也有相對應的 Compiler Directive ,今天我們就來介紹幾個吧!
先說說 Verilog 的 Compiler Directive 的特性,就是會由 `
這個符號來起頭,相當於 C 語言的 #
。因此 `define
, `include
, `ifdef
, `timescale
都是 Verilog 的 Compiler Directive 。
如同 C 語言的 #define
一樣,Verilog 也有對應的 Compiler Directive 叫做 `define
。這個指引的功能就是用來定義巨集 (macro) 。什麼時候會需要呢?撰寫程式時,我們一定會有時常被使用的的指令,而且這串指令的長度也許不短,那我們可以將這串指令定義為某一個單字或是一個不包含空白的字串 (e.g. getMax, myJSCounter4, ...),往後這個字串就代表這串指令。舉一些例子吧!
// definition
`define WORD_REG reg [31:0]
// using macro
`WORD_REG a;
我們定義
WORD_REG
來取代reg [31:0]
。使用時,我們為了表示 WORD_REG 為一個 macro 或是稱他是新的 Compiler Directive ,我們會以`WORD_REG
來稱呼他。
// definition
`define F $finish
// using macro
initial begin
// some code
`F;
end
我們定義
F
來取代$finish
。相同的是我們以`F
來稱呼$finish
這個 System Task ,要特別注意的是 macro 僅僅是代換掉相對應的字眼,原本該加上的結尾;
還是要補上去。
寫程式還講求 Readability ,就是可讀性。直接舉例子吧!
`TRUE
, `FALSE
來取代 1'b1
, 1'b0
。雖然直接以數字表示也沒有錯,但是可讀性相對沒有那麼高。// definition
`define TRUE 1'b1
`define FALSE 1'b0
// using in conditional statement
initial begin
if (isLoad == `TRUE)
// do something
else
// do something
end
`define NEW_STATE 1'd0
`define READY_STATE 1'd1
`define WAIT_STATE 1'd2
`define START_STATE 1'd3
`define WORD_SIZE 32
`define BYTE 8
在這裡補充關於 macro 的彈性定義,我們可以以類似函數的形式表示。如取最大值的 MAX(a, b)
,我們就可以以 macro 來呈現,程式碼如下:
// definition
`define MAX(x, y) (((x) > (y)) ? (x) : (y))
// using macro
wire [7:0] a, b, c;
assign a = 8'd5;
assign b = 8'd9;
assign c = `MAX(a, b);
在這個範例中 x, y 相當於是兩個參數,會被放入原始程式中。
小小補充:
後方的 x, y 之所以會被括號括起來,是因為我們僅僅是把這個程式碼復原,而不是當作 C 語言中的函式來處理。假設我們有一個 macro 定義為`define f(x,y) x + y
,而在使用時我們這樣呼叫assign a = f(b, c) * d
,當程式被還原時,會是b + c * d
而非(b + c) * d
,因此適當的加上括號可以減少錯誤出現。
另一個補充關於 macro 的彈性定義是 Concatenation ,中文翻譯「接合」。簡單來說就是參數可以和其他字串相接合成程式碼的一部分。我們需要透過 ``
這個符號來接合。如下的程式碼:
// definition
`define CTR_DEC(X) X``_counter
// using macro
`CTR_DEC(johnson) jsctr(clk, rst, load, in);
`CTR_DEC(ring) rctr(clk, rst, load, in);
這段程式碼被還原後會是
johnson_counter jsctr(clk, rst, load, in); ring_counter rctr(clk, rst, load, in);
平常練習時,一個 verilog 檔案足以完成我們的所有要求。但是當檔案越來越大,一個 verilog 檔案是不足以讓我們有效率的完成所有事情,這時我們就會把相關連的模組分散在不同的 verilog 檔案。那我們該如何調用其他檔案的模組呢?和 C 語言一樣,我們會使用關鍵字 include
,但是在 verilog 中,這個關鍵字應為 `include
。
使用著個關鍵字
// counter.v
`include "flipflop.v"
module ctr(...);
// some code
DFF d0(...);
endmodule
// flipflop.v
module DFF(...);
// some code
endmodule
`timescale
這個關鍵字主要是在控制延遲時間的精準度,舉例來說,#5.0167 這個延遲時間我們不一定有辦法做得那麼精確,相對應的辦法就是四捨五入到我們能處理的延遲時間。那要四捨五入到幾位,就要透過 `timescale
來定義。
先來介紹一下語法:
`timescale <time_unit>/<time_precision>
舉個例子吧!我們在 Johnson Counter 的 testbench 加上 timescale,程式碼如下:
`timescale 1ns/100ps
module Johnson_tb;
wire [4:0] in;
reg [4:0] load;
reg clk, rst;
initial begin
$dumpfile("js.vcd");
$dumpvars(0, Johnson_tb);
load = 5'b10000;
#1 clk = 1; rst = 1;
for (integer i=0; i<20; i=i+1) begin
#0.56 clk = 0; rst = 0;
#0.44 clk = 1;
end
#3 $finish;
end
johnson #(5) js(clk, rst, load, in);
endmodule
這個範例中,時間單位是 1ns ,我們可以透過時脈和波形圖的關係來確認時間單位設置成功。
想要確認時間單位是否為 100ps ,我們會將延遲時間設到比精度更小,如上述的例子,原本的 #0.56 和 #0.44 經過四捨五入的處理,分別被改為 #0.6 和 #0.4 。因此這題的時間處理就和我們原先設想的一樣!