verilog HDL入門
特點
- 類C語言
- 並行執行
- 硬件描述
設計流程: 自頂向下
前提:懂C語言和簡單的數電知識
簡單體驗
語法很類似C語言,同時不難看出描述的是一個多路選擇器
module muxtwo (out, a, b, s1);
input a,b,s1;
output out;
reg out;
always @(s1 or a or b)
if(!s1) out = a;
else out = b;
endmodule
注意
- 沒考慮時延問題
- 沒有說明如果輸入a或b是三態的(高阻時)輸出應該是什么。
一 入門例子
例1.
多路選擇器
描述一個多路選擇器,控制信號sel,輸入信號in0、in1, 輸出信號out
module mux(out, in0, in1, sel);
parameter N=8;
output[N:1] out;
input[N:1] in0, in1;
input sel;
assign out=sel?in1:in0;
endmodule
例2.
4位二進制加法計數器(帶同步清0)
進位輸出:當q為最大值(15)且cin=1時,cout=1;否則cout=0
module counter(q, cout, reset, cin, clk);
parameter N=4;
input reset, cin, clk;
output cout, q;
reg[N:1] q; //寄存器型有保持功能
//邏輯功能
always @(posedge clk) //時鍾上升沿執行
begin
if (reset) q<=0;
else
q<=q+cin
end
assign cout=&q && cin; //&q是語法糖,相當於q[1]&...q[N]
endmodule
二 數據類型
空白符
忽略
注釋符
// 和 /**/
注釋最好是英文,EDA對中文不友好
數值
0/1/x/z
x : 不確定
z : 高阻態
整數
/*
format : +/-<size>'<base_format><number>
size的確定是一個好的編程習慣
*/
8'b1001101 //位寬為8位的二進制數
8'ha6 //位寬為8位的十六進制數
4'b1x_01 //下划線只是方便閱讀
//wrong eg.
4'd-4 //補碼表示 或 在位寬表達式前面加一個減號
3' b001 //空格
(4+4)'b11
//高級建模使用
integer idata; //一個整型數據對象
//idata 位寬由機器和編譯器決定, 是有符號數
idata = -12;
idata[7:0] = 8'ha2
integer iram[7:0]; //數組
實數
/*
雖然parameter可以定義常量,parameter pi=3.14
但是verilog本身不能識別小數,不能直接運算小數(需要技巧)
*/
//小數點兩邊必須有數字
2.7
5.2e8 //520.0
//wrong eg.
6.
.3e5
//建模使用
real idata;
物理數據類型
物理數據類型:連線型、寄存器型和存儲器類型
標記符 | 名稱 | 類型 |
---|---|---|
supply | 電源級驅動 | 驅動 |
strong | 強驅動 | 驅動 |
pull | 上拉級驅動 | 驅動 |
large | 大容性 | 存儲 |
weak | 弱驅動 | 驅動 |
mediam | 中性驅動 | 存儲 |
small | 小容性 | 存儲 |
highz | 高容性 | 高阻 |
信號強度:表示數字電路中不同強度的驅動源,解決不同驅動強度存在下的賦值沖突。
簡單理解就是,表格里的驅動源,從上到下強度依次減弱,supply相當於短路,highz相當於斷路。
連線型
連線型數據類型 | 功能說明 |
---|---|
wire, tri | 標准連續(缺省默認類型) |
tri1 | 上拉電阻 |
tri0 | 下拉電阻 |
supply1 | 電源線,高電平1 |
supply0 | 電源線,低電平0 |
... ... | |
寄存器型
與連線型數據的區別:reg型數據保存最后一次賦值,而wire型數據需要持續的驅動。reg默認初始值為不定值x, 且默認無符號。
//有符號寫法
reg signed[3:0] rega;
rega = -2; //rega值為1110(14)
存儲器類型
可以理解為寄存器的組合(二維寄存器)
reg[7:0] memory[63:0]; //寬度為8,深度64
memory[0] = 8'h12;
memory = 12; //error
向量與標量
//vector 默認
wire vectored[7:0] v_data;
assign a = v_data[0];
assign b = v_data[5:3];
assign v_data[1] = 1'b1;
//scalar
wire scalared[7:0] s_data;
assign a = v_data[0]; //error
字符串
字符串變量其實就是寄存器變量,由""括起。
module string_test;
reg[19*8:1] str;
initial
begin
str = "I love verilog HDL!";
$display("str is :%H",str);
end
endmodule
時間型數據對象
time idata;
time iram[7:0];
參數
parameter H=1;
defparam H=2;
//僅限於當前模塊的參數定義
localparam real PI = 3.14;
三 操作符
賦值
= 與 <= : https://www.cnblogs.com/friedCoder/articles/12257385.html
等式
邏輯相等‘’與邏輯全等‘=’
(1)、邏輯相等:兩個操作數逐位比較,如果兩個進行比較的位是不定態‘x’或者高阻態’z’,則輸出x
$displayb ( 4’b0011 == 4’b1010 ); // 0
$displayb ( 4’b0011 != 4’b1x10 ); // 1
$displayb ( 4’b1010 == 4’b1x10 ); // x
$displayb ( 4’b1x10 == 4’b1x10 ); // x
$displayb ( 4’b1z10 == 4’b1z10 ); // x
(2)、邏輯全等
$displayb ( 4’b01zx === 4’b01zx ); // 1
$displayb ( 4’b01zx !== 4’b01zx ); // 0
$displayb ( 4’b01zx === 4’b00zx ); // 0
$displayb ( 4’b01zx !== 4’b11zx ); // 1
在進行全等運算時,對不定態與高阻態也要進行比較,當兩個操作數完全一致時,其結果才為1,否則為0
比較
對於<、>之類的操作符,操作數中只要有一位為x或z,結果都為x
拼接
起拼接的作用 如 a = {b[5],b[4:0]}
意思為 b的最高位和b的低五位拼接起來,組成的a為6位
縮位
~^ : 位取同或操作
四 順序語句
//下面2個並行賦值
assign p_sum_one = data1 + data2;
assign p_sum_two = data1 + data3;
always @(data1, data2, data3);
begin
//下面2個順序賦值
assign s_sum_one = data1 + data2;
assign s_sum_one = data1 + data2;
end
//但是實際生成電路后,會發現這4個加法器是並行執行
if 條件語句
if 條件
順序語句1;
elsif 條件
順序語句2;
end if
case 條件語句
//要求匹配完全一致,包括x和z
casez語句:忽略值為z的位
casex語句:忽略值為z或x位
repeat語句
repeat(8)
begin
順序語句;
end
forever
永遠執行
五 自定義原語 UDP
原語只能有一個標量輸出(0,1或x),可以有多個標量輸入(0,1,z或x,這里x和z同等看待)。
六 任務和函數
verilog的任務 = C語言的函數
task cal_num_one;
input[15:0] data;
output[4:0] num;
integer i, j;
begin
i = 0;
for(j=0; j<16; j++)
if(data[j])
i = i + 1;
num = i[4:0];
end
endtask
若在函數定義中沒有指定函數值得取值范圍和類型,則函數默認返回1位二進制數。返回值的類型可以是real,integer,time或者realtime之一。通過關鍵詞signed可以把返回值聲明為帶符號值。
function [automatic] [signed]
[range or type] function_id;
input_declaration
other_declaration
statements
endfunction
//eg.
function [7:0] expected_led;
input [7:0] swt;
begin
expected_led[0] = ~swt[0];
expected_led[1] = swt[1] & ~swt[2];
expected_led[3] = swt[2] & swt[3];
expected_led[2] = expected_led[1] | expected_led[3];
expected_led[7:4] = swt[7:4];
end
endfunction
任務調用
端口連接信號的順序必須與任務定義時的端口順序一致。由於任務內的語句是順序執行,所以輸出信號的類型必須是寄存器類型。
函數
函數只能返回一個信號,即只有一個輸出信號(函數名就是輸出信號);且函數不能調用任務;也不能帶時序控制。
系統任務函數
七 其它語法
阻塞與非阻塞
- 兩者都能實現組合電路建模
- 如果always進程中的敏感信號列表包括了所有賦值操作符右邊的信號,則兩者沒區別。
- 如果always進程中的敏感信號列表沒有包括所有賦值操作符右邊的信號,則兩者有差別,至少在ModelSim中的仿真有差別。
- always進程中的敏感信號列表沒有包括所有賦值操作符右邊的信號時,多條阻塞賦值語句書寫順序是有先后講究的。
- 因此,推薦組合電路使用阻塞賦值,並且always敏感列表包括所有賦值操作符右邊的信號,即使用always@(*)。時序電路推薦非阻塞語句。
//3個寄存器
always@(posedge clk)
begin
temp_one <= a;
temp_two <= temp_one;
y_r <= temp_two;
end
//1個寄存器
always@(posedge clk)
begin
temp_one = a;
temp_two = temp_one;
y_r = temp_two; //最后相當於y_r = a
end
//3個寄存器
always@(posedge clk)
begin
y_r = temp_two;
temp_two = temp_one;
temp_one = a;
end
預編譯指令
類似`define
時延
assign #2 a = b;
//b計算好后,延時2個時間單位送給a
事件
上升沿,下降沿;電平事件