Verilog教程
這個教程寫的很好,可以多看看。本篇還沒整理完。
一、Verilog簡介
什么是FPGA?一種可通過編程來修改其邏輯功能的數(shù)字集成電路(芯片)
與單片機的區(qū)別?對單片機編程并不改變其地電路的內(nèi)部結(jié)構(gòu),只是根據(jù)要求實現(xiàn)的功能來編寫運行的程序(指令)。舉例:單片機就兩個uart,但是我想用4個uart,單片機就沒辦法了。
什么是HDL?(hardware description language)硬件描述語言,用于描述數(shù)字電路結(jié)構(gòu)和功能的語言。
Verilog和C的區(qū)別?Verilog是硬件描述語言,在編譯下載到FPGA之后,會生成電路,所以Verilog是并行運行的。C語言是軟件編程語言,編譯下載到單片機之后,是存儲器中的一組指令。而單片機處理軟件指令需要取指、譯碼、執(zhí)行,這個過程是串行執(zhí)行的。
二、Verilog基礎語法
2.1基礎知識
2.1.1 邏輯值
0:邏輯0,邏輯假。
1:邏輯1,邏輯真。
X:未知,高或低。
Z:高阻態(tài),外部沒有激勵信號,是一個懸空狀態(tài)。
2.1.2 數(shù)值表示
二進制(b)、八進制(o)、十進制(d)、十六進制(h)。
舉例:
8'b10101010
16'h10c8_fc9a
//_可以增加可讀性
//直接寫數(shù)字,默認十進制。
//不指定位寬,編譯器自動分配
//負數(shù),按位取反加一,符號位在最高位。
-15 = -5'b10001
//科學計數(shù)法
1.2e4 //12000
//字符串
reg[15*8-1:0] string;
initial begin
string = "Hello World!";
end
2.1.3 標識符
用于定義模塊名、端口名、信號名。
字母、數(shù)字、$符號、_下劃線。第一個字符必須是字母或者下劃線。
嚴格區(qū)分大小寫。
不建議大小寫混合使用。
普通內(nèi)部信號建議全部小寫。
2.2 數(shù)據(jù)類型
2.2.1?線網(wǎng)類型wire
- 線網(wǎng)類型便是結(jié)構(gòu)實體之間的物理連線。不能存儲值,它的值由驅(qū)動的元件所決定。
- 驅(qū)動線網(wǎng)類型變量的元件有門、連續(xù)賦值語句、assign等。
- 如果沒有驅(qū)動元件連接到線網(wǎng)類型的變量上,則該變量就是高阻態(tài)。
2.2.2?寄存器類型reg
- 數(shù)據(jù)存儲單元,默認初始值是不定值x。未寫位寬的時候默認32位。無符號數(shù)。
- reg類型數(shù)據(jù)只能在always和initial中賦值。
- 如果該過程語句描述的是時序邏輯,即always語句帶有時鐘信號,則該寄存器變量對應為觸發(fā)器。
- 如果該過程語句描述的是組合邏輯,即always語句不帶有時鐘信號,則該寄存器變量對應為硬件連線。
2.2.3 向量
- 位寬大于1,wire和reg可以聲明為向量的形式:
wire[3:0] add;
reg[7:0] data;
- [bit+: width]?: 從起始 bit 位開始遞增,位寬為 width。
- [bit-: width]?: 從起始 bit 位開始遞減,位寬為 width。
- 向量組合需要{}括起來。
2.2.4 數(shù)組
- <數(shù)組名>[<下標>]
reg[7:0] counter[3:0];//3個8bit數(shù)組
wire data[7:0][3:0];二維數(shù)組
reg[31:0] data_bits[7:0][2:0][3:0];三維數(shù)組
//賦值
counter[3] = 4'hF ; //將數(shù)組counter中第4個元素的值賦值為4bit 十六進制數(shù)F,等效于counter[3][3:0] = 4'hF,即可省略寬度;
assign data[0][1] = 1'b1; //將數(shù)組data的第1行第2列的元素賦值為1,這里不能省略第二個訪問標號,即 assign data[0] = 1'b1; 是非法的。
2.2.5?整數(shù),實數(shù),時間
- 整數(shù)(integer):32bit,有符號數(shù)。可以用來定義for循環(huán)的i。
- 實數(shù)(real):默認值為0。
- 時間(time):64bit,通過調(diào)用系統(tǒng)函數(shù) $time 獲取當前仿真時間。
2.2.6?參數(shù)類型localparam、parameter
常量,類似#define??梢砸淮味x多個參數(shù),參數(shù)與參數(shù)之間需要用逗號隔開。每個參數(shù)定義的右邊必須是一個常數(shù)表達式。
#
1.參數(shù)的傳遞
模塊定義的時候傳入?yún)?shù),模塊實例化的時候傳入?yún)?shù)。
2.時序仿真中的延時
//延時2.5個時間單位后執(zhí)行sys_clk_i信號的翻轉(zhuǎn)
always #2.5 sys_clk_i = ~sys_clk_i;
2.3操作符
操作符 | 操作符合 | 優(yōu)先級 | 說明 |
單目運算 | + - ! ~ | 最高 | |
乘、除、取模 | * / % | ||
加減 | + - | ||
移位 | << ?>> <<< >>> | 注意:邏輯右移時,左邊高位會補 0;而算術(shù)右移時,左邊高位會補充符號位。左移(<<),右移(>>),算術(shù)左移(<<<),算術(shù)右移(>>>) | |
關系 | <? <=? >? >= | ||
等價 | ==? !=? ===? !=== | ||
歸約 | & ~& | 對這個操作數(shù)逐位進行操作 | |
^ ~^ | |||
|?~| | |||
邏輯 | && | ||
|| | |||
條件 | ?: | 最低 |
拼接運算符:{}。{a,b[3:0]}
2.3.1優(yōu)先級
2.4編譯指令
1.`define,`undef,`ifdef,`elsif,`else,`endif同C語言
2.`include同C語言
3.`timescale
用于定義時延、仿真的單位和精度
`timescale time_unit / time_precision
- time_unit 表示時間單位,time_precision 表示時間精度,
- 單位 s(秒),ms(毫秒),us(微妙),ns(納秒),ps(皮秒)和 fs(飛秒)。
- 時間單位≥時間精度。
- 編譯過程中,`timescale會影響后面的模塊中的時延值。直到遇到另一個`timescale或者`resetall。
- 沒有默認的`timescale,沒有指定的情況下,會繼承前面編譯模塊的`timescale參數(shù),可能導致設計出錯。
- 一個設計多個模塊都有`timescale時,時延單位不受影響,但是時延精度會換算成最小時延精度。
- 如果有并行子模塊,子模塊間的 `timescale 并不會相互影響。
- 時間精度設置是會影響仿真時間的。時間精度越小,仿真時占用內(nèi)存越多,實際使用的仿真時間就越長。所以如果沒有必要,應盡量將時間精度設置的大一些。
- 如果延時時間的最小位數(shù)小于時間精度,將會四舍五入。例如時間單位為10ns,精度為1ns,#1.04表示延時1.04個時間單位=1.04x10ns=10.4ns,但精度無法表示0.1ns,#1.04≈10ns
4.`default_nettype
//將沒有被聲明的連線定義為線網(wǎng)類型
`default_nettype wand
//將沒有被聲明的連線定義為線與類型
`default_nettype none
5.`resetall
該編譯器指令將所有的編譯指令重新設置為缺省值。使用該指令可以防止`timescale傳遞。
6.`celldefine, `endcelldefine
將模塊標記為單元模塊。
三、程序框架
3.1Verilog注釋
//、/**/、
3.2Verilog關鍵字
3.3Verilog程序框架
模塊的結(jié)構(gòu)
一個模塊由兩部分組成:一部分描述接口,一部分描述邏輯功能。
端口定義、IO說明、內(nèi)部信號聲明、功能定義。
模塊的調(diào)用
四、高級知識點
4.1結(jié)構(gòu)語句
1.?initial
- 只執(zhí)行一次。
- 常用于測試文件的編寫,用來產(chǎn)生仿真測試信號(激勵信號),或者用于對存儲器變量賦初值。
2. always
- 一直不斷地重復活動。
- 但是只有和一定的時間控制結(jié)合在一起才有作用。
- always時間控制可以是沿觸發(fā)或者電平觸發(fā)。敏感列表。
- 沿觸發(fā):多個信號中間要用or連接。(posedge,negedge)
- 電平觸發(fā):(*)
4.2賦值語句
always 時序邏輯塊中多用非阻塞賦值,always 組合邏輯塊中多用阻塞賦值。
1.阻塞賦值(與C語言一樣)
b = a;
描述組合邏輯時,用阻塞賦值。
2.非阻塞賦值(并行同時賦值)
b <= a;
只能用于對寄存器類型的變量進行賦值,只能用于initial和always中。
描述時序邏輯時,用非阻塞賦值。
注意:在同一個always塊中不要既用非阻塞賦值又要阻塞賦值,不允許在多個always塊中對同一個變量進行賦值。
組合邏輯
任意時刻輸出僅僅取決于該時刻的輸入,與電路原來的狀態(tài)無關。
時序邏輯
任意時刻的輸出不僅取決于當時的輸入信號,而且還取決于電路原來的狀態(tài)。或者說還與之前的輸入有關,因此時序邏輯必須具備記憶功能。
4.3 時序控制
- 時延
常規(guī)時延:該語句需要等待一定時間,然后將計算結(jié)果賦值給目標信號。
#delay a = b;
或者
#delay;
a = b;
內(nèi)嵌時延:該語句先將計算結(jié)果保存,然后等待一定的時間后賦值給目標信號。
a = #delay b;
- 邊沿觸發(fā)事件控制
一般事件控制:用@表示
//在信號clk上升沿時刻,執(zhí)行q<=d,正邊沿D觸發(fā)器模型
always @(posedge clk) q <= d ;
//在信號clk下降沿時刻,執(zhí)行q<=d,負邊沿D觸發(fā)器模型
always @(negedge clk) q <= d ;
命名事件控制:用戶可以聲明 event(事件)類型的變量,并觸發(fā)該變量來識別該事件是否發(fā)生。
命名事件用關鍵字 event 來聲明,觸發(fā)信號用 -> 表示。
event ? ? start_receiving ;
always @( posedge clk_samp) begin
? ? ? ? -> start_receiving ; ? ? ? //采樣時鐘上升沿作為時間觸發(fā)時刻
end
?
always @(start_receiving) begin
? ? data_buf = {data_if[0], data_if[1]} ; //觸發(fā)時刻,對多維數(shù)據(jù)整合
end
敏感列表:
當多個信號或事件中任意一個發(fā)生變化都能夠觸發(fā)語句的執(zhí)行時,
用關鍵字 or 連接多個事件或信號。這些事件或信號組成的列表稱為"敏感列表"。
or 也可以用逗號 , 來代替。
當敏感列表中變量較多時,@*或@(*)。
- 電平敏感事件控制
initial begin
? ? wait (start_enable) ; ? ? ?//等待 start 信號
? ? forever begin
? ? ? ? //start信號使能后,在clk_samp上升沿,對數(shù)據(jù)進行整合
? ? ? ? @(posedge clk_samp) ?;
? ? ? ? data_buf = {data_if[0], data_if[1]} ; ? ? ?
? ? end
end
語句塊
順序塊:順序執(zhí)行,非阻塞賦值除外。
begin
end
并行塊:并行執(zhí)行,阻塞賦值也并行執(zhí)行。
fork
join
命名塊
命名塊的禁用:disable
4.4 條件語句
條件語句必須在過程塊(initial、always)中使用。
if-else
0,x,z按假處理。
if和else后面可以用begin end包含多個語句。
case
位寬必須相等。
casez,不用考慮表達式中的高阻值。
casex,不用考慮高阻值z和不定值x。
4.5 函數(shù)
- 只能在模塊中定義,位置任意,并在模塊的任何地方引用,作用范圍也局限于此模塊
- 不含有任何延遲、時序或時序控制邏輯
- 至少有一個輸入變量
- 只有一個返回值,且沒有輸出
- 不含有非阻塞賦值語句
- 函數(shù)可以調(diào)用其他函數(shù),但是不能調(diào)用任務
//聲明
function [range-1:0] function_id ;
input_declaration ;
other_declaration ;
procedural_statement ;
endfunction
//調(diào)用
function_id(input1, input2, …);
4.6 任務
- 任務的輸入輸出可以沒有或者多個,且端口聲明可以為 inout 型。
- 不能出現(xiàn)initial和always過程塊。但可以包含其他時序控制,如延時語句。
- 任務可以調(diào)用函數(shù)和任務。
- 任務可以作為一條單獨的語句出現(xiàn)語句塊中。
4.5.1 任務聲明
任務在模塊中任意位置定義,并在模塊內(nèi)任意位置引用,作用范圍也局限于此模塊。
模塊內(nèi)子程序出現(xiàn)下面任意一個條件時,則必須使用任務而不能使用函數(shù)。
- 1)子程序中包含時序控制邏輯,例如延遲,事件控制等
- 2)沒有輸入變量
- 3)沒有輸出或輸出端的數(shù)量大于 1
對 output 信號賦值時也不要用關鍵字 assign。為避免時序錯亂,建議 output 信號采用阻塞賦值。
task task_id ;
port_declaration ;
procedural_statement ;
endtask
task xor_oper_iner(
? ? input [N-1:0] ? numa,
? ? input [N-1:0] ? numb,
? ? output [N-1:0] ?numco ?) ;
? ? #3 ?numco ? ? ? = numa ^ numb ;
endtask
4.5.2 任務調(diào)用
task_id(input1, input2, …,outpu1, output2, …);
輸入端連接的模塊內(nèi)信號可以是 wire 型,也可以是 reg 型。輸出端連接的模塊內(nèi)信號要求一定是 reg 型。
4.7 狀態(tài)機
4.7.1狀態(tài)機概念FSM
在有限個狀態(tài)之間按一定規(guī)律轉(zhuǎn)換的時序電路
4.7.2狀態(tài)機模型
狀態(tài)寄存器由一組觸發(fā)器組成,用來記憶狀態(tài)機當前所處的狀態(tài),狀態(tài)的改變只發(fā)生在時鐘的跳變沿。
4.7.3狀態(tài)機設計
狀態(tài)空間定義
狀態(tài)跳轉(zhuǎn)(時序邏輯)
下個狀態(tài)判斷(組合邏輯)
各個狀態(tài)下的動作
競爭和冒險
競爭:在組合邏輯電路中,不同路徑的輸入信號傳輸?shù)酵粋€門電路時,到達時間可能會不同,這種情況稱為競爭。
冒險:競爭的存在導致輸出信號需要一段時間才能達到期望值,這一段時間可能產(chǎn)生錯誤的輸出。
判斷方法
代數(shù)法:例如:Y=A+A`或Y=AA`,A的狀態(tài)改變,會導致電路存在競爭冒險。
卡諾圖法:卡諾圖是首尾相鄰的。
?消除方法:
6.4 Verilog 競爭與冒險 | 菜鳥教程 (runoob.com)
- 增加濾波電容,濾除窄脈沖
- 修改邏輯,增加冗余量:增加卡諾圈
- 使用時鐘同步電路,利用觸發(fā)器進行打拍延遲
- 采用格雷碼計數(shù)器
Verilog書寫規(guī)范
- 時序電路建模時,用非阻塞賦值。
- 組合邏輯建模時,用阻塞賦值。
- 在同一個 always 塊中建立時序和組合邏輯模型時,用非阻塞賦值。
- 在同一個 always 塊中不要既使用阻塞賦值又使用非阻塞賦值。
- 不要在多個 always 塊中為同一個變量賦值。
- 避免 latch (鎖存器)產(chǎn)生。
Latch 的主要危害有:
- 輸入狀態(tài)可能多次變化,容易產(chǎn)生毛刺,增加了下一級電路的不確定性;
- 在大部分 FPGA 的資源中,可能需要比觸發(fā)器更多的資源去實現(xiàn) Latch 結(jié)構(gòu);
- 鎖存器的出現(xiàn)使得靜態(tài)時序分析變得更加復雜。
組合邏輯中,if結(jié)構(gòu)不完整(時序邏輯不會產(chǎn)生latch)
- 補全if-else結(jié)構(gòu)
- 對信號賦初值
組合邏輯中,case結(jié)構(gòu)不完整(case選項列表不全且沒有加default,或多個賦值語句不完整)
- 補全case選項列表
- 對信號賦初值
原信號賦值或判讀
- 避免這種寫法,使用臨時變量,延時一個時鐘周期進行賦值。
敏感信號列表不完整(always@()塊中敏感列表沒有列全)
- 多用always@(*)
teachbench(仿真激勵文件)
1.信號聲明
2.時鐘生成:時延、將時鐘分頻。
initial clk = 0 ;
always #(CYCLE_200MHz/2) clk = ~clk;
initial begin
? ? clk = 0 ;
? ? forever begin
? ? ? ? #(CYCLE_200MHz/2) clk = ~clk;
? ? end
end
3.復位生成:一般賦初值為 0,再經(jīng)過一段小延遲后,復位為 1 即可。
4.激勵部分
5.模塊例化
6.自校驗文章來源:http://www.zghlxwxcb.cn/news/detail-841538.html
7.結(jié)束仿真:系統(tǒng)任務 $finish 來停止仿真。停止仿真之前,可以將自校驗的結(jié)果,通過系統(tǒng)任務 $display 在終端進行顯示。文章來源地址http://www.zghlxwxcb.cn/news/detail-841538.html
到了這里,關于Verilog(未完待續(xù))的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!