一
Verliog語(yǔ)法基礎(chǔ)
基本的語(yǔ)法略過(guò),主要想寫(xiě)一些關(guān)于框架,規(guī)范,技術(shù)難點(diǎn)的博文,這樣對(duì)于我們養(yǎng)成好的編碼習(xí)慣是有好處的,就定這樣一個(gè)flag吧.希望大家可以一起好好學(xué)習(xí),共同進(jìn)步.
接口時(shí)序設(shè)計(jì)規(guī)范
模塊和模塊之間的通過(guò)模塊的接口實(shí)現(xiàn)關(guān)聯(lián), 因此規(guī)范的時(shí)序設(shè)計(jì), 對(duì)于程序設(shè)計(jì)的過(guò)程, 以及程序的維護(hù), 團(tuán)隊(duì)之間的溝通都是非常必要的。
命名規(guī)則
1、 頂層文件
對(duì)象+功能+top
比如:video_oneline_top
2、 邏輯控制文件
介于頂層和驅(qū)動(dòng)層文件之間
對(duì)象+ctr
比如:ddr_ctr.v
3、 驅(qū)動(dòng)程序命名
對(duì)象+功能+dri
比如:lcd_dri.v、 uart_rxd_dri.v
4、 參數(shù)文件命名
對(duì)象+para
比如:lcd_para.v
5、 模塊接口命名:文件名+u
比如 lcd_dir lcd_dir_u(........)
6、 模塊接口命名:特征名+文件名+u
比如 mcb_read c3_mcb_read_u
7、 程序注釋說(shuō)明
/*****************************************************************/
// Company:
// Engineer:
// WEB:
// BBS:
// Create Date: 0750 07/31/2019
// Design Name: FPGA STREAM
// Module Name: FPGA_USB
// Project Name: FPGA STREAM
// Target Devices: XC6SLX16-FTG256/XC6SLX25-FTG256 Mis603
// Tool versions: ISE14.7
// Description: CY7C68013A SLAVE FIFO comunication with fpga
// Revision: V1.0
// Additional Comments:
//1) _i input
//2) _o output
//3) _n activ low
//4) _dg debug signal
//5) _r delay or register
//6) _s state mechine
/*****************************************************************/
8、 端口注釋 input Video_vs_i,//輸入場(chǎng)同步入
9、 信號(hào)命名 命名總體規(guī)則:對(duì)象+功能(+極性) +特性
10、 時(shí)鐘信號(hào) 對(duì)象+功能+特性 比如:phy_txclk_i、 sys_50mhz_i
11、 復(fù)位信號(hào) 對(duì)象+功能+極性+特性 比如:phy_rst_n_i、 sys_rst_n_i
12、 延遲信號(hào) 對(duì)象+功能+特性 1+特征 2 比如:fram_sync_i_r0、 fram_sync_i_r1
13、 特定功能計(jì)數(shù)器
對(duì)象+cnt 比如:line_cnt、 div_cnt0、 div_cnt1 功能+cnt 比如:wr_cnt、 rd_cnt 對(duì)象+功能+cnt 比如:fifo_wr_cnt、 mcb_wr_cnt、 mem_wr_cnt 對(duì)象+對(duì)象+cnt 比如:video_line_cnt、 video_fram_cnt
14、 一般計(jì)數(shù)器 cnt+序號(hào) 用于不容易混淆的計(jì)數(shù) 比如:cnt0、 cnt1、 cnt2
15、 時(shí)序同步信號(hào) 對(duì)象+功能+特性 比如:line_sync_i、 fram_sysc_i
16、 使能信號(hào) 功能+en 比如:wr_en、 rd_en 對(duì)象+功能+en 比如:fifo_wr_en、 mcb_wr_en
Verilog 最最基礎(chǔ)語(yǔ)法
C 語(yǔ)言和 Verilog 的關(guān)鍵詞和結(jié)構(gòu)對(duì)比:
C 語(yǔ)言和 Verilog 運(yùn)算符對(duì)比:
關(guān)鍵字
信號(hào)部分:input 關(guān)鍵詞, 模塊的輸入信號(hào), 比如 input Clk, Clk 是外面關(guān)鍵輸入的時(shí)鐘信號(hào);
output 關(guān)鍵詞, 模塊的輸出信號(hào), 比如 output[3:0]Led; 這個(gè)地方正好是一組輸出信號(hào)。其中[3:0]表示 0~3 共 4 路信號(hào)。
inout 模塊輸入輸出雙向信號(hào)。這種類(lèi)型, 我們的例子 24LC02 中有使用。數(shù)總線(xiàn)的通信中, 這種信號(hào)被廣泛應(yīng)用;
wire 關(guān)鍵詞, 線(xiàn)信號(hào)。例如:wire C1_Clk; 其中 C1_Clk 就是 wire 類(lèi)型的信號(hào);
線(xiàn)信號(hào),三態(tài)類(lèi)型, 我們一般常用的線(xiàn)信號(hào)類(lèi)型有input,output,inout,wire;
reg 關(guān)鍵詞, 寄存器。和線(xiàn)信號(hào)不同, 它可以在 always 中被賦值, 經(jīng)常用于時(shí)序邏輯中。比如 reg[3:0]Led;表示了一組寄存器。
結(jié)構(gòu)部分:
module()… endmodule代表一個(gè)模塊, 我們的代碼寫(xiě)在這個(gè)兩個(gè)關(guān)鍵字中間
always@()括號(hào)里面是敏感信號(hào)。這里的 always@(posedge Clk)敏感信號(hào)是 posedge Clk 含義是在上升沿的時(shí)候有效, 敏感信號(hào)還可以 negedge Clk 含義是下降沿的時(shí)候有效, 這種形式一般時(shí)序邏輯都會(huì)用到。還可以是*這個(gè)一符號(hào), 如果是一個(gè)*則表示一直是敏感的, 一般用于組合邏輯。
assign 用來(lái)給 output,inout 以及 wire 這些類(lèi)型進(jìn)行連線(xiàn)。assign 相當(dāng)于一條連線(xiàn), 將表達(dá)式右邊的電路直接通過(guò) wire(線(xiàn))連接到左邊, 左邊信號(hào)必須是 wire 型(output 和 inout 屬于 wire 型) 。當(dāng)右邊變化了左邊立馬變化, 方便用來(lái)描述簡(jiǎn)單的組合邏輯。
符號(hào)部分:
這里重點(diǎn)講解一下“<=” 賦值符號(hào),非阻塞賦值?,“=” 阻塞賦值,“{}”
“<=” 賦值符號(hào), 非阻塞賦值, 在一個(gè) always 模塊中, 所有語(yǔ)句一起更新。它也可以表示小于等于, 具體是什么含義編譯環(huán)境根據(jù)當(dāng)前編程環(huán)境判斷, 如果“<=” 是用在一個(gè) if 判斷里如:if(a <= 10);當(dāng)然就表示小于等于了。
“=” 阻塞賦值, 或者給信號(hào)賦值, 如果在 always 模塊中, 這條語(yǔ)句被立刻執(zhí)行。阻塞賦值和非阻塞賦值將再后面詳細(xì)舉例說(shuō)明。
“{} ” 在 Verilog 中表示拼接符, {a,b}這個(gè)的含義是將括號(hào)內(nèi)的數(shù)按位并在一起, 比如:{1001,1110}表示的是 10011110。拼接是 Verilog 相對(duì)于其他語(yǔ)言的一大優(yōu)勢(shì), 在以后的編程中請(qǐng)慢慢體會(huì)。
參數(shù)部分:
parameter
parameter a = 180;//十進(jìn)制, 默認(rèn)分配長(zhǎng)度 32bit(編譯器默認(rèn))parameter a = 8’d180;//十進(jìn)制parameter a = 8’haa; //十六進(jìn)制parameter a = 8’b1010_1010; //二進(jìn)制
預(yù)處理命令
`include file1.v`define X = 1;`deine Y;`ifdef YZ=1;`elseZ=0;`endif
Verilog 中數(shù)值表示的方式
如果我們要表示一個(gè)十進(jìn)制是 180 的數(shù)值, 在 Verilog 中的表示方法如下:
二進(jìn)制:8’ b1010_1010; //其中“_” 是為了容易觀(guān)察位數(shù), 可有可無(wú)。
十進(jìn)制:8’ d180;
16 進(jìn)制:8’ hAA;
講到這里,具備這些基礎(chǔ)知識(shí),需要通過(guò)代碼來(lái)學(xué)習(xí)Veriog 語(yǔ)言。最后, 筆者提一點(diǎn)建議, 學(xué)習(xí) Verilog 多看別人寫(xiě)的優(yōu)秀的代碼, 多看官方提供的代碼和文檔。其中官方提供的代碼, 很多時(shí)候代表了最新的用法, 或者推薦的用法。讀者學(xué)習(xí), 首先把最最基礎(chǔ)的掌握好, 這樣, 在項(xiàng)目中遇到了問(wèn)題, 也能快速學(xué)習(xí), 快速解決。
對(duì)于理論知識(shí)的學(xué)習(xí), 沒(méi)必要一開(kāi)始就研究得那么深刻, 只是搞理論學(xué)習(xí), 對(duì)于學(xué)習(xí)Verilog 語(yǔ)言, 或者 FPGA 開(kāi)發(fā)是不實(shí)際的, 要聯(lián)系理論和實(shí)踐結(jié)合。多仿真, 多驗(yàn)證, 多問(wèn)題, 多學(xué)習(xí), 多改進(jìn).
三
淺談狀態(tài)機(jī)
01. 前言
狀態(tài)機(jī)是FPGA設(shè)計(jì)中一種非常重要、非常根基的設(shè)計(jì)思想,堪稱(chēng)FPGA的靈魂,貫穿FPGA設(shè)計(jì)的始終。
02.狀態(tài)機(jī)簡(jiǎn)介
什么是狀態(tài)機(jī):狀態(tài)機(jī)通過(guò)不同的狀態(tài)遷移來(lái)完成特定的邏輯操作(時(shí)序操作)狀態(tài)機(jī)是許多數(shù)字系統(tǒng)的核心部件, 是一類(lèi)重要的時(shí)序邏輯電路。通常包括三個(gè)部分:
-
下一個(gè)狀態(tài)的邏輯電路
-
存儲(chǔ)狀態(tài)機(jī)當(dāng)前狀態(tài)的時(shí)序邏輯電路
-
輸出組合邏輯電路
03. 狀態(tài)機(jī)分類(lèi)
通常, 狀態(tài)機(jī)的狀態(tài)數(shù)量有限, 稱(chēng)為有限狀態(tài)機(jī)(FSM) 。由于狀態(tài)機(jī)所有觸發(fā)器的時(shí)鐘由同一脈沖邊沿觸發(fā), 故也稱(chēng)之為同步狀態(tài)機(jī)。
根據(jù)狀態(tài)機(jī)的輸出信號(hào)是否與電路的輸入有關(guān)分為 Mealy 型狀態(tài)機(jī)和 Moore 型狀態(tài)機(jī)
3.1,Mealy 型狀態(tài)機(jī)
電路的輸出信號(hào)不僅與電路當(dāng)前狀態(tài)有關(guān), 還與電路的輸入有關(guān)
3.2,Moore 型狀態(tài)機(jī)
電路的輸出僅僅與各觸發(fā)器的狀態(tài), 不受電路輸入信號(hào)影響或無(wú)輸入
狀態(tài)機(jī)的狀態(tài)轉(zhuǎn)移圖, 通常也可根據(jù)輸入和內(nèi)部條件畫(huà)出。一般來(lái)說(shuō), 狀態(tài)機(jī)的設(shè)計(jì)包含下列設(shè)計(jì)步驟:
-
根據(jù)需求和設(shè)計(jì)原則, 確定是 Moore 型還是 Mealy 型狀態(tài)機(jī);
-
分析狀態(tài)機(jī)的所有狀態(tài), 對(duì)每一狀態(tài)選擇合適的編碼方式, 進(jìn)行編碼;
-
根據(jù)狀態(tài)轉(zhuǎn)移關(guān)系和輸出繪出狀態(tài)轉(zhuǎn)移圖;
-
構(gòu)建合適的狀態(tài)機(jī)結(jié)構(gòu), 對(duì)狀態(tài)機(jī)進(jìn)行硬件描述。
04. 狀態(tài)機(jī)描述
狀態(tài)機(jī)的描述通常有三種方法, 稱(chēng)為一段式狀態(tài)機(jī), 二段式狀態(tài)機(jī)和三段式狀態(tài)機(jī)。
狀態(tài)機(jī)的描述通常包含以下四部分:
-
利用參數(shù)定義語(yǔ)句 parameter 描述狀態(tài)機(jī)各個(gè)狀態(tài)名稱(chēng), 即狀態(tài)編碼。狀態(tài)編碼通常有很多方法包含自然二進(jìn)制編碼, One-hot 編碼,格雷編碼碼等;
-
用時(shí)序的 always 塊描述狀態(tài)觸發(fā)器實(shí)現(xiàn)狀態(tài)存儲(chǔ);
-
使用敏感表和 case 語(yǔ)句(也采用 if-else 等價(jià)語(yǔ)句) 描述狀態(tài)轉(zhuǎn)換邏輯;
-
描述狀態(tài)機(jī)的輸出邏輯。
下面根據(jù)狀態(tài)機(jī)的三種方法來(lái)具體說(shuō)明
4.1,一段式狀態(tài)機(jī)
module detect_1(
input clk_i,
input rst_n_i,
output out_o
);
reg out_r;
//狀態(tài)聲明和狀態(tài)編碼
reg [1:0] state;
parameter [1:0] S0=2‘b00;
parameter [1:0] S1=2’b01;
parameter [1:0] S2=2‘b10;
parameter [1:0] S3=2’b11;
always@(posedge clk_i)
begin
if(!rst_n_i)begin
state《=0;
out_r《=1‘b0;
end
else
case(state)
S0 :
begin
out_r《=1’b0;
state《= S1;
end
S1 :
begin
out_r《=1‘b1;
state《= S2;
end
S2 :
begin
out_r《=1’b0;
state《= S3;
end
S3 :
begin
out_r《=1‘b1;
end
endcase
end
assign out_o=out_r;
endmodul
一段式狀態(tài)機(jī)是應(yīng)該避免使用的, 該寫(xiě)法僅僅適用于非常簡(jiǎn)單的狀態(tài)機(jī)設(shè)計(jì)。
4.2,兩段式狀態(tài)機(jī)
module detect_2(
input clk_i,
input rst_n_i,
output out_o
);
reg out_r;
//狀態(tài)聲明和狀態(tài)編碼
reg [1:0] Current_state;
reg [1:0] Next_state;
parameter [1:0] S0=2‘b00;
parameter [1:0] S1=2’b01;
parameter [1:0] S2=2‘b10;
parameter [1:0] S3=2’b11;
//時(shí)序邏輯:描述狀態(tài)轉(zhuǎn)換
always@(posedge clk_i)
begin
if(!rst_n_i)
Current_state《=0;
else
Current_state《=Next_state;
end
//組合邏輯:描述下一狀態(tài)和輸出
always@(*)
begin
out_r=1‘b0;
case(Current_state)
S0 :
begin
out_r=1’b0;
Next_state= S1;
end
S1 :
begin
out_r=1‘b1;
Next_state= S2;
end
S2 :
begin
out_r=1’b0;
Next_state= S3;
end
S3 :
begin
out_r=1‘b1;
Next_state=Next_state;
end
endcase
end
assign out_o = out_r;
endmodule
兩段式狀態(tài)機(jī)采用兩個(gè) always 模塊實(shí)現(xiàn)狀態(tài)機(jī)的功能, 其中一個(gè) always 采用同步時(shí)序邏輯描述狀態(tài)轉(zhuǎn)移, 另一個(gè) always 采用組合邏輯來(lái)判斷狀態(tài)條件轉(zhuǎn)移。
4.3,三段式狀態(tài)機(jī)
module detect_3(
input clk_i,
input rst_n_i,
output out_o
);
reg out_r;
//狀態(tài)聲明和狀態(tài)編碼
reg [1:0] Current_state;
reg [1:0] Next_state;
parameter [1:0] S0=2‘b00;
parameter [1:0] S1=2’b01;
parameter [1:0] S2=2‘b10;
parameter [1:0] S3=2’b11;
//時(shí)序邏輯:描述狀態(tài)轉(zhuǎn)換
always@(posedge clk_i)
begin
if(!rst_n_i)
Current_state《=0;
else
Current_state《=Next_state;
end
//組合邏輯:描述下一狀態(tài)
always@(*)
begin
case(Current_state)
S0:
Next_state = S1;
S1:
Next_state = S2;
S2:
Next_state = S3;
S3:
begin
Next_state = Next_state;
end
default :
Next_state = S0;
endcase
end
//輸出邏輯: 讓輸出 out, 經(jīng)過(guò)寄存器 out_r 鎖存后輸出, 消除毛刺
always@(posedge clk_i)
begin
if(!rst_n_i)
out_r《=1‘b0;
else
begin
case(Current_state)
S0,S2:
out_r《=1’b0;
S1,S3:
out_r《=1‘b1;
default :
out_r《=out_r;
endcase
end
end
assign out_o=out_r;
endmodule
三段式狀態(tài)機(jī)在第一個(gè) always 模塊采用同步時(shí)序邏輯方式描述狀態(tài)轉(zhuǎn)移, 第二個(gè)always 模塊采用組合邏輯方式描述狀態(tài)轉(zhuǎn)移規(guī)律, 第三個(gè) always 描述電路的輸出。通常讓輸出信號(hào)經(jīng)過(guò)寄存器緩存之后再輸出, 消除電路毛刺。
05. 狀態(tài)機(jī)優(yōu)缺點(diǎn)
1、一段式狀態(tài)機(jī):只涉及時(shí)序電路,沒(méi)有競(jìng)爭(zhēng)與冒險(xiǎn),同時(shí)消耗邏輯比較少。
但是如果狀態(tài)非常多,一段式狀態(tài)機(jī)顯得比較臃腫,不利于維護(hù)。
2、兩段式狀態(tài)機(jī):當(dāng)一個(gè)模塊采用時(shí)序(狀態(tài)轉(zhuǎn)移),一個(gè)模塊采用組合時(shí)候(狀態(tài)機(jī)輸出),組合邏輯電路容易造成競(jìng)爭(zhēng)與冒險(xiǎn);當(dāng)兩個(gè)模塊都采用時(shí)序,可以避免競(jìng)爭(zhēng)與冒險(xiǎn)的存在,但是整個(gè)狀態(tài)機(jī)的時(shí)序上會(huì)延時(shí)一個(gè)周期。
兩段式狀態(tài)機(jī)是推薦的狀態(tài)機(jī)設(shè)計(jì)方法。
3、三段式狀態(tài)機(jī):三段式狀態(tài)機(jī)在狀態(tài)轉(zhuǎn)移時(shí)采用組合邏輯電路+格雷碼,避免了組合邏輯的競(jìng)爭(zhēng)與冒險(xiǎn);狀態(tài)機(jī)輸出采用了同步寄存器輸出,也可以避免組合邏輯電路的競(jìng)爭(zhēng)與冒險(xiǎn);采用這兩種方法極大的降低了競(jìng)爭(zhēng)冒險(xiǎn)。并且在狀態(tài)機(jī)的采用這種組合邏輯電路+次態(tài)寄存器輸出,避免了兩段式狀態(tài)機(jī)的延時(shí)一個(gè)周期(三段式狀態(tài)機(jī)在上一狀態(tài)中根據(jù)輸入條件判斷當(dāng)前狀態(tài)的輸出,從而在不插入額外時(shí)鐘節(jié)拍的前提下,實(shí)現(xiàn)寄存器的輸出)。
三段式狀態(tài)機(jī)也是比較推崇的,主要是由于維護(hù)方便, 組合邏輯與時(shí)序邏輯完全獨(dú)立。
06. 總結(jié)
靈活選擇狀態(tài)機(jī),不一定要拘泥理論,怎樣方便怎樣來(lái)
07.擴(kuò)展
四段式不是指三個(gè)always代碼,而是四段程序。使用四段式的寫(xiě)法,可參照明德?lián)PGVIM特色指令Ztj產(chǎn)生的狀態(tài)機(jī)模板。
明·德·揚(yáng)四段式狀態(tài)機(jī)符合一次只考慮一個(gè)因素的設(shè)計(jì)理念。
-
第一段代碼,照抄格式,完全不用想其他的。
-
第二段代碼,只考慮狀態(tài)之間的跳轉(zhuǎn),也就是說(shuō)各個(gè)狀態(tài)機(jī)之間跳轉(zhuǎn)關(guān)系。
-
第三段代碼,只考慮跳轉(zhuǎn)條件。
-
第四段,每個(gè)信號(hào)逐個(gè)設(shè)計(jì)。
四
Test bench文件結(jié)構(gòu)一覽無(wú)余
01,前言
Verilog測(cè)試平臺(tái)是一個(gè)例化的待測(cè)(MUT)模塊,重要的是給它施加激勵(lì)并觀(guān)測(cè)其輸出。邏輯模塊與其對(duì)應(yīng)的測(cè)試平臺(tái)共同組成仿真模型,應(yīng)用這個(gè)模型可以測(cè)試該模塊能否符合自己的設(shè)計(jì)要求。
編寫(xiě)TESTBENCH的目的是為了對(duì)使用硬件描述語(yǔ)言設(shè)計(jì)的電路進(jìn)行仿真驗(yàn)證,測(cè)試設(shè)計(jì)電路的功能、性能與設(shè)計(jì)的預(yù)期是否相符。通常,編寫(xiě)測(cè)試文件的過(guò)程如下:
-
產(chǎn)生模擬激勵(lì)(波形);
-
將產(chǎn)生的激勵(lì)加入到被測(cè)試模塊中并觀(guān)察其響應(yīng);
-
將輸出響應(yīng)與期望值相比較。
02,完成的Test bench文件結(jié)構(gòu)
通常,一個(gè)完整的測(cè)試文件其結(jié)構(gòu)為
-
module Test_bench();//通常無(wú)輸入無(wú)輸出
-
信號(hào)或變量聲明定義
-
邏輯設(shè)計(jì)中輸入對(duì)應(yīng)reg型
-
邏輯設(shè)計(jì)中輸出對(duì)應(yīng)wire型
-
使用initial或always語(yǔ)句產(chǎn)生激勵(lì)
-
例化待測(cè)試模塊
-
監(jiān)控和比較輸出響應(yīng)
-
endmodule
03,時(shí)鐘激勵(lì)設(shè)計(jì)
下面列舉出一些常用的封裝子程序, 這些是常用的寫(xiě)法, 在很多應(yīng)用中都能用到。
3.1,時(shí)鐘激勵(lì)產(chǎn)生方法一
50%占空比時(shí)鐘
parameterClockPeriod=10;initialbeginclk_i=0;forever#(ClockPeriod/2)clk_i=~clk_i;end
3.2,時(shí)鐘激勵(lì)產(chǎn)生方法二
50%占空比時(shí)鐘
initialbeginclk_i=0;always#(ClockPeriod/2)clk_i=~clk_i;end
3.3,時(shí)鐘激勵(lì)產(chǎn)生方法三:
產(chǎn)生固定數(shù)量的時(shí)鐘脈沖
initialbeginclk_i=0;repeat(6)#(ClockPeriod/2)clk_i=~clk_i;end
3.4,時(shí)鐘激勵(lì)產(chǎn)生方法四
產(chǎn)生非占空比為50%的時(shí)鐘
initialbeginclk_i=0;foreverbegin#((ClockPeriod/2)-2)clk_i=0;#((ClockPeriod/2)+2)clk_i=1;end04,復(fù)位信號(hào)設(shè)計(jì)
4.1,復(fù)位信號(hào)產(chǎn)生方法一
異步復(fù)位
initialbeginrst_n_i=1;#100;rst_n_i=0;#100;rst_n_i=1;end
4.2,復(fù)位信號(hào)產(chǎn)生方法二
同步復(fù)位
initialbeginrst_n_i=1;@(negedgeclk_i)rst_n_i=0;#100;//固定時(shí)間復(fù)位repeat(10)@(negedgeclk_i);//固定周期數(shù)復(fù)位@(negedgeclk_i)rst_n_i=1;end
4.3復(fù)位信號(hào)產(chǎn)生方法三
復(fù)位任務(wù)封裝
taskreset;input[31:0]reset_time;//復(fù)位時(shí)間可調(diào),輸入復(fù)位時(shí)間RST_ING=0;//復(fù)位方式可調(diào),低電平或高電平beginrst_n=RST_ING;//復(fù)位中#reset_time;//復(fù)位時(shí)間rst_n_i=~RST_ING;//撤銷(xiāo)復(fù)位,復(fù)位結(jié)束endendtask05,雙向信號(hào)設(shè)計(jì)
5.1,雙向信號(hào)描述一
inout在testbench中定義為wire型變量
//為雙向端口設(shè)置中間變量inout_reg作為inout的輸出寄存,其中inout變//量定義為wire型,使用輸出使能控制傳輸方向//inoutbir_port;wirebir_port;regbir_port_reg;regbi_port_oe;assignbi_port=bi_port_oe?bir_port_reg:1'bz;
5.2雙向信號(hào)描述二
強(qiáng)制force
//當(dāng)雙向端口作為輸出口時(shí),不需要對(duì)其進(jìn)行初始化,而只需開(kāi)通三態(tài)門(mén)//當(dāng)雙向端口作為輸入時(shí),只需要對(duì)其初始化并關(guān)閉三態(tài)門(mén),初始化賦值需//使用wire型數(shù)據(jù),通過(guò)force命令來(lái)對(duì)雙向端口進(jìn)行輸入賦值//assigndinout=(!en)din:16'hz;完成雙向賦值initialbeginforcedinout=20;#200forcedinout=dinout-1;end06,特殊信號(hào)設(shè)計(jì)
6.1特殊激勵(lì)信號(hào)產(chǎn)生描述一
輸入信號(hào)任務(wù)封裝
taski_data;input[7:0]dut_data;begin@(posedgedata_en);send_data=0;@(posedgedata_en);send_data=dut_data[0];@(posedgedata_en);send_data=dut_data[1];@(posedgedata_en);send_data=dut_data[2];@(posedgedata_en);send_data=dut_data[3];@(posedgedata_en);send_data=dut_data[4];@(posedgedata_en);send_data=dut_data[5];@(posedgedata_en);send_data=dut_data[6];@(posedgedata_en);send_data=dut_data[7];@(posedgedata_en);send_data=1;#100;endendtask//調(diào)用方法:i_data(8'hXX);
6.2特殊激勵(lì)信號(hào)產(chǎn)生描述二
多輸入信號(hào)任務(wù)封裝
taskmore_input;input[7:0]a;input[7:0]b;input[31:0]times;output[8:0]c;beginrepeat(times)//等待times個(gè)時(shí)鐘上升沿@(posedgeclk_i)c=a+b;//時(shí)鐘上升沿a,b相加endendtask//調(diào)用方法:more_input(x,y,t,z);//按聲明順序
6.3,特殊激勵(lì)信號(hào)產(chǎn)生描述三
輸入信號(hào)產(chǎn)生,一次SRAM寫(xiě)信號(hào)產(chǎn)生
initialbegincs_n=1;//片選無(wú)效wr_n=1;//寫(xiě)使能無(wú)效rd_n=1;//讀使能無(wú)效addr=8'hxx;//地址無(wú)效data=8'hzz;//數(shù)據(jù)無(wú)效#100;cs_n=0;//片選有效wr_n=0;//寫(xiě)使能有效addr=8'hF1;//寫(xiě)入地址data=8'h2C;//寫(xiě)入數(shù)據(jù)#100;cs_n=1;wr_n=1;#10;addr=8'hxx;data=8'hzz;end
Testbench中@與wait
//@使用沿觸發(fā)//wait語(yǔ)句都是使用電平觸發(fā)initialbeginstart=1'b1;wait(en=1'b1);#10;start=1'b0;end
07,仿真控制語(yǔ)句及系統(tǒng)任務(wù)描述
7.1,仿真控制語(yǔ)句及系統(tǒng)任務(wù)描述
$stop//停止運(yùn)行仿真,modelsim中可繼續(xù)仿真$stop(n)//帶參數(shù)系統(tǒng)任務(wù),根據(jù)參數(shù)0,1或2不同,輸出仿真信息$finish//結(jié)束運(yùn)行仿真,不可繼續(xù)仿真$finish(n)//帶參數(shù)系統(tǒng)任務(wù),根據(jù)參數(shù)0,1或2不同,輸出仿真信息//0:不輸出任何信息//1:輸出當(dāng)前仿真時(shí)刻和位置//2:輸出當(dāng)前仿真時(shí)刻、位置和仿真過(guò)程中用到的memory以及CPU時(shí)間的統(tǒng)計(jì)$random//產(chǎn)生隨機(jī)數(shù)$random%n//產(chǎn)生范圍-n到n之間的隨機(jī)數(shù){$random}%n//產(chǎn)生范圍0到n之間的隨機(jī)數(shù)?
7.2,仿真終端顯示描述
$monitor//仿真打印輸出,大印出仿真過(guò)程中的變量,使其終端顯示/*$monitor($time,,,"clk=%dreset=%dout=%d",clk,reset,out);*/$display//終端打印字符串,顯示仿真結(jié)果等/*$display(”Simulationstart!");$display(”Attime%t,inputis%b%b%b,outputis%b",$time,a,b,en,z);*/$time//返回64位整型時(shí)間$stime//返回32位整型時(shí)間$realtime//實(shí)行實(shí)型模擬時(shí)間
7.3文本輸入方式
$readmemb/$readmemh//激勵(lì)具有復(fù)雜的數(shù)據(jù)結(jié)構(gòu)//verilog提供了讀入文本的系統(tǒng)函數(shù)$readmemb/$readmemh("<數(shù)據(jù)文件名>",<存儲(chǔ)器名>);$readmemb/$readmemh("<數(shù)據(jù)文件名>",<存儲(chǔ)器名>,<起始地址>);$readmemb/$readmemh("<數(shù)據(jù)文件名>",<存儲(chǔ)器名>,<起始地址>,<結(jié)束地址>);$readmemb:/*讀取二進(jìn)制數(shù)據(jù),讀取文件內(nèi)容只能包含:空白位置,注釋行,二進(jìn)制數(shù)數(shù)據(jù)中不能包含位寬說(shuō)明和格式說(shuō)明,每個(gè)數(shù)字必須是二進(jìn)制數(shù)字。*/$readmemh:/*讀取十六進(jìn)制數(shù)據(jù),讀取文件內(nèi)容只能包含:空白位置,注釋行,十六進(jìn)制數(shù)數(shù)據(jù)中不能包含位寬說(shuō)明和格式說(shuō)明,每個(gè)數(shù)字必須是十六進(jìn)制數(shù)字。*//*當(dāng)?shù)刂烦霈F(xiàn)在數(shù)據(jù)文件中,格式為@hh...h,地址與數(shù)字之間不允許空白位置,可出現(xiàn)多個(gè)地址*/modulereg[7:0]memory[0:3];//聲明8個(gè)8位存儲(chǔ)單元integeri;initialbegin$readmemh("mem.dat",memory);//讀取系統(tǒng)文件到存儲(chǔ)器中的給定地址//顯示此時(shí)存儲(chǔ)器內(nèi)容for(i=0;i<4;i=i+1)$display("Memory[%d]=%h",i,memory[i]);endendmodule
?
/*mem.dat文件內(nèi)容
@001ABCD@003A1*/
//仿真輸出為
Memory[0]=xx;Memory[1]=AB;Memory[2]=CD;Memory[3]=A1;
08,總結(jié)
一個(gè)完整的設(shè)計(jì),除了好的功能描述代碼,對(duì)于程序的仿真驗(yàn)證是必不可少的。學(xué)會(huì)如何去驗(yàn)證自己所寫(xiě)的程序,即如何調(diào)試自己的程序是一件非常重要的事情。而RTL邏輯設(shè)計(jì)中,學(xué)會(huì)根據(jù)硬件邏輯來(lái)寫(xiě)測(cè)試程序,即Testbench是尤其重要的。
五
【很重要】Testbenth前仿真全過(guò)程
01. 前言
在FPGA 高手養(yǎng)成記-Test bench文件結(jié)構(gòu)一覽無(wú)余只是簡(jiǎn)單的例舉了常用的 testbench 寫(xiě)法,在工程應(yīng)用中基本能夠滿(mǎn)足我們需求, 至于其他更為復(fù)雜的 testbench 寫(xiě)法, 大家可參考其他書(shū)籍或資料。
testbench沒(méi)有像RTL代碼設(shè)計(jì)那樣嚴(yán)謹(jǐn),我們可以在符合語(yǔ)法規(guī)則的前提下,隨意編寫(xiě)我們的測(cè)試文件,有些在RTL代碼中不可綜合的語(yǔ)句,我們可以在testbench中實(shí)現(xiàn)。大體流程如下:
02.測(cè)試模塊設(shè)計(jì)
要測(cè)試我們的cpu需要ROM和RAM模塊,這就需要我們先做好這兩個(gè)模塊
這里定義了一個(gè) 1024 x 8 的RAM
再定義一個(gè)8192x 8 的ROM
ROM和RAM都還沒(méi)有裝入數(shù)據(jù),等會(huì)我們會(huì)調(diào)用函數(shù)給他們裝數(shù)據(jù),接下來(lái)是地址譯碼器,來(lái)控制ROM和RAM的打開(kāi)與關(guān)閉。
各模塊建立好之后我們就開(kāi)始仿真了。
03.仿真
這次教學(xué)我們用的是modelsim SE 10.0 版本進(jìn)行教學(xué),直接先在quartus II中建一個(gè).v文件將其保存在原來(lái)的工程文件目錄中,并命名為cpu_top.v,直接在這里寫(xiě)測(cè)試代碼
下面大家可以來(lái)完成cpu 的仿真過(guò)程了
3.1,模塊包含
首先,我們需要將我們剛寫(xiě)好的那幾個(gè)模塊包含進(jìn)去,即CPU模塊,ROM模塊,RAM模塊,地址譯碼器模塊,并寫(xiě)好時(shí)間測(cè)量度,見(jiàn)下圖
3.2,定義頂層模塊
由于我們的設(shè)計(jì)只有兩個(gè)輸入,即時(shí)鐘模塊和復(fù)位模塊,凡是輸入信號(hào)在testbench中統(tǒng)一定義成reg型變量,凡是輸出或者雙向輸入輸出信號(hào)統(tǒng)一定義成wire型變量,我們的設(shè)計(jì)只有輸入沒(méi)有輸出,故只定義輸入和連線(xiàn)即可
下圖便是我們要組成的測(cè)試頂層模塊圖,我們定義的wire型變量,實(shí)際就是我們頂層模塊中,模塊模塊與模塊間的連線(xiàn)。而這些連線(xiàn)就是我們cpu的輸出,這樣我們就可以用我們的測(cè)試模塊來(lái)測(cè)試我們的cpu是否能正確工作
3.3. 元件例化
就是將各個(gè)模塊連接起來(lái)即可,這里就不做太多的說(shuō)明了,因?yàn)橐郧岸紝?xiě)過(guò)很多次了
3.4.測(cè)試激勵(lì)的書(shū)寫(xiě)
先寫(xiě)好時(shí)鐘產(chǎn)生模塊和復(fù)位模塊.并將復(fù)位模塊用task任務(wù)封裝,這樣我們?cè)跍y(cè)試過(guò)程中就可以隨時(shí)調(diào)用復(fù)位任務(wù)進(jìn)行復(fù)位
時(shí)鐘為50Mhz,復(fù)位時(shí)間為20ns
然后,我們?cè)儆胻ask封裝我們需要的模塊,我們來(lái)想一下,上電后,CPU會(huì)從ROM中讀兩個(gè)時(shí)鐘周期的數(shù)據(jù)是吧,但是我們的ROM現(xiàn)在還是空的,所以我們需要一個(gè)任務(wù)是往ROM中裝入程序,給ROM中裝數(shù)據(jù)我們可以用系統(tǒng)函數(shù)$readmemb,即打開(kāi)一個(gè)文件,并將其中的數(shù)據(jù)送到我們之前定義的ROM中去
而test1.pro文件是需要我們自己定義的,我們可以在quartusII中再新建一個(gè).v文件,在里面寫(xiě)上我們自己定義的程序,并將其保存為.pro文件即可,至于寫(xiě)什么程序,是我們隨便定義的.
裝完ROM和RAM的數(shù)據(jù)之后,按說(shuō)就可以了進(jìn)行波形仿真了,因?yàn)閏pu是自動(dòng)讀取數(shù)據(jù)的,下面我們先來(lái)做第一步仿真,我先把之后的代碼注釋掉,大家先看沒(méi)有被注釋掉的代碼
里面都是我們之前封裝好的函數(shù),剛開(kāi)始進(jìn)行復(fù)位,然后進(jìn)行第一步測(cè)試,之后停止,將其保存之后,并默認(rèn)為用其打開(kāi),打開(kāi)后見(jiàn)下圖
然后,file——new——library——ok即建好一個(gè)庫(kù)
點(diǎn)擊左上角的編譯按鈕,將我們之前寫(xiě)好的所有.v文件全部都編譯進(jìn)去
看到transcript一欄顯示編譯成功后即可,若沒(méi)有transcript一欄,可以選擇菜單中的view——transcript即可,若顯示有紅色錯(cuò)誤,那就請(qǐng)讀者按照它的要求進(jìn)行修改代碼,這說(shuō)明你的代碼有問(wèn)題,一般是連接問(wèn)題
3.5,波形仿真
編譯成功后,雙擊cpu_top就可以開(kāi)始波形仿真了
進(jìn)入仿真頁(yè)面后,我們右擊cpu模塊將其加入至波形
大家先看兩個(gè)圖,等會(huì)結(jié)合這兩個(gè)圖給大家細(xì)細(xì)講解仿真過(guò)程
我們先來(lái)看第一個(gè)過(guò)程
上電后,cpu先從ROM中讀回兩個(gè)周期的數(shù)據(jù),是從ROM的0地址開(kāi)始的,再對(duì)比我們之前定義好的ROM,數(shù)據(jù)讀取正確,讀回的數(shù)據(jù)的前三位是111,即指令碼JMP,后13位003c為地址碼,JMP指令是將讀回的數(shù)據(jù)作為新的地址碼來(lái)讀取相應(yīng)地址的數(shù)據(jù)。那么,下一步,cpu應(yīng)該是從ROM的003c地址處讀數(shù)據(jù)才對(duì),再看一下波形
對(duì)比波形后可知,cpu正好是從003c處讀取數(shù)據(jù),讀到的數(shù)據(jù)指令碼位111即JMP,地址碼位0006,再到ROM的0006地址處看
這次讀回的指令碼位101,即LDA,也就是說(shuō)將后13位地址碼對(duì)應(yīng)的RAM中的數(shù)據(jù)讀回,送到累加器中,想一下,這時(shí)的RAM應(yīng)該是打開(kāi)的,而且雙向輸入輸出口的數(shù)據(jù)總線(xiàn)上應(yīng)該是來(lái)自RAM的8位數(shù)據(jù),由于ROM0006地址處的地址碼為1800是13位的,而RAM的地址是9位的,因此實(shí)際上我們從RAM中讀回的數(shù)據(jù)是從RAM的0地址讀回的,即我們之前給RAM寫(xiě)好的0000_0000,再看一下波形
正如我們所想的一樣,數(shù)據(jù)總線(xiàn)上是0000_0000,RAM是打開(kāi)的,地址為1800
就這樣,讀者可以自己再試一下,看看我們的cpu是不是按照我們之前給他的程序運(yùn)行的,在這里我就不再給大家一一介紹了
雖然波形仿真很直觀(guān),但是看久了就會(huì)令人眼花繚亂,尤其是數(shù)據(jù)很多的時(shí)候,我們只能看其中一部分,不能講所有數(shù)據(jù)看完整,這時(shí)候我們單單是用波形來(lái)仿真就遠(yuǎn)遠(yuǎn)不夠了,下面介紹用系統(tǒng)任務(wù)仿真的過(guò)程
3.6,系統(tǒng)任務(wù)仿真
再回到我們的代碼,注釋掉了一些代碼吧,我們把那些代碼給加上,以其中一個(gè)過(guò)程為例
假設(shè)讀回的指令碼位101,即LDA,如果我在fentch_8的高電平期間且在cpu輸出地址為奇數(shù)的時(shí)候記錄一下此時(shí)的時(shí)間、指令、地址、目的地址、數(shù)據(jù)的話(huà)就可以不用看波形,讓電腦來(lái)幫助我們來(lái)分析了,因此作如下處理
這里我延時(shí)60ns,是因?yàn)榈谝淮斡涗浀臅r(shí)候數(shù)據(jù)總線(xiàn)上還沒(méi)有數(shù)據(jù),只有延時(shí)一會(huì)才會(huì)有數(shù)據(jù),即上面那張波形圖右邊那根黃色的線(xiàn)處記錄一下數(shù)據(jù),并將其顯示。我們也可以加上一下標(biāo)注,來(lái)幫助我們觀(guān)察
這樣我們?cè)賮?lái)仿真的時(shí)候就不用看波形了,直接打開(kāi)transcript一欄觀(guān)察記錄即可
這樣便可以為我們省下大量的仿真時(shí)間
04. 總結(jié)
這里提出以下幾點(diǎn)建議供大家參考:
? 封裝有用且常用的 testbench, testbench 中可以使用 task 或 function 對(duì)代碼進(jìn)行封裝, 下次利用時(shí)靈活調(diào)用即可;
? 如果待測(cè)試文件中存在雙向信號(hào)(inout)需要注意, 需要一個(gè) reg 變量來(lái)表示輸入, 一個(gè) wire 變量表示輸出;
? 單個(gè) initial 語(yǔ)句不要太復(fù)雜,可分開(kāi)寫(xiě)成多個(gè) initial 語(yǔ)句, 便于閱讀和修改;
? Testbench 說(shuō)到底是依賴(lài) PC 軟件平臺(tái), 必須與自身設(shè)計(jì)的硬件功能相搭配。
六
串行口通信電路設(shè)計(jì)
1、頂層模塊
寫(xiě)程序都一樣,不能多有的程序都寫(xiě)在一個(gè)模塊里,那樣看起來(lái)很麻煩,出了錯(cuò)誤也不好維護(hù),對(duì)于一些小的程序我們可以寫(xiě)在一個(gè)模塊里,但程序一旦復(fù)雜起來(lái)還是要懂得模塊化編程的,對(duì)于頂層模塊,最好是只寫(xiě)接口就好了,例如:
這段代碼中,rx_232是我們的底層模塊名,后面跟著的那個(gè)rx呢是我們自己取的名字,是任意的。后面的一大串呢就是接口,為了直觀(guān)呢,建議大家采用我的這種寫(xiě)法,看上去比較清楚明白,括號(hào)里面的接口是我們頂層文件的接口,括號(hào)外面的是我們調(diào)用底層模塊的接口,這些接口要一一對(duì)應(yīng)正確才能保證數(shù)據(jù)之間的傳輸。
在頂層模塊中,我們只定義了數(shù)據(jù)輸入接口,用來(lái)接收數(shù)據(jù),數(shù)據(jù)輸出接口,用于發(fā)送數(shù)據(jù),時(shí)鐘接口,和復(fù)位接口。這四個(gè)接口是有輸入輸出關(guān)系的,對(duì)于其他的接口,是屬于我們整個(gè)模塊內(nèi)部的接口,是模塊與模塊之間的接口,既非輸入,也非輸出,相當(dāng)于一根導(dǎo)線(xiàn)一樣,所以我們把他們定義成wire型變量
2、波特率選擇模塊
單片機(jī)或者計(jì)算機(jī)在串口通信時(shí)的傳輸速率用波特率表示,9600bps表示的就是每秒鐘傳送9600位的數(shù)據(jù),這里之所以計(jì)數(shù)到5027,在這里算一下。
1秒傳送9600位,那么傳送一位的時(shí)間就可以算出,即1s=1000_000_000ns,所以傳送一位數(shù)據(jù)需要1000_000_000/9600=
104166ns,而我們的時(shí)鐘周期為20ns,因此需要計(jì)數(shù)到104166/20=5028個(gè)時(shí)鐘周期
下面是串口通信時(shí)序圖
我再來(lái)解釋一下這個(gè)圖吧,我當(dāng)時(shí)學(xué)單片機(jī)的時(shí)候還真是沒(méi)怎么重視這張圖,只知道只要一個(gè)指令就可以發(fā)送,沒(méi)有真正搞清楚是怎么發(fā)送和接受的,那就在這里復(fù)習(xí)一下吧,計(jì)算機(jī)和單片機(jī)之間進(jìn)行通信,這里用的是rs232通信方式,即通信之前,計(jì)算機(jī)和單片機(jī)之前要設(shè)定好相同的波特率,只有波特率相同了才能進(jìn)行通信。
其次,計(jì)算機(jī)發(fā)送數(shù)據(jù)時(shí)要先發(fā)送一個(gè)起始位,一般是低電平,后面跟著的是8位數(shù)據(jù)位,奇偶校驗(yàn)位,停止位等,當(dāng)起始位低電平信號(hào)傳送到我們的接收端口時(shí),在接收模塊中會(huì)發(fā)送一個(gè)命令給波特率時(shí)鐘計(jì)數(shù)器,開(kāi)始計(jì)時(shí),計(jì)時(shí)到一半的時(shí)候會(huì)產(chǎn)生一個(gè)采樣高脈沖信號(hào),當(dāng)接收模塊檢測(cè)到這個(gè)高脈沖之后就會(huì)將數(shù)據(jù)存到寄存器中,當(dāng)檢測(cè)到第11個(gè)脈沖信號(hào)時(shí),也就是代表一幀的數(shù)據(jù)接收完畢,發(fā)送模塊就給波特率選擇模塊發(fā)送一個(gè)停止信號(hào)告訴它停止計(jì)時(shí)。
同時(shí),當(dāng)數(shù)據(jù)接收完畢之后也會(huì)產(chǎn)生一個(gè)信號(hào)告訴發(fā)送模塊,信號(hào)已經(jīng)接收完畢,準(zhǔn)備發(fā)送,這個(gè)時(shí)候發(fā)送模塊再給波特率計(jì)時(shí)模塊發(fā)送一個(gè)信號(hào)開(kāi)始計(jì)時(shí),計(jì)數(shù)到某一位的中間時(shí)產(chǎn)生一個(gè)采樣信號(hào),當(dāng)發(fā)送模塊檢測(cè)到采樣信號(hào)之后就將寄存器里的數(shù)據(jù)送到發(fā)送端,每次只送一位,這樣就實(shí)現(xiàn)了數(shù)據(jù)的接收與發(fā)送。
下面是波特率計(jì)時(shí)模塊的主要程序部分
3、數(shù)據(jù)接收模塊
在接收模塊中,為了準(zhǔn)確的檢測(cè)計(jì)算機(jī)發(fā)送來(lái)的數(shù)據(jù)起始位的那個(gè)低電平信號(hào),用到了邊沿脈沖檢測(cè)法,可以有效的避免毛刺現(xiàn)象帶來(lái)的問(wèn)題
下面是發(fā)送部分的主要程序段
4、數(shù)據(jù)發(fā)送模塊
發(fā)送模塊原理上和接受模塊是一樣的,不同點(diǎn)就是接收模塊通過(guò)邊沿檢測(cè)法檢測(cè)起始位低電平信號(hào)來(lái)啟動(dòng)接收數(shù)據(jù),而發(fā)送模塊是通過(guò)檢測(cè)數(shù)據(jù)發(fā)送完畢后,我們認(rèn)為得置一個(gè)低電平信號(hào),發(fā)送模塊通過(guò)檢測(cè)這個(gè)低電平信號(hào)來(lái)啟動(dòng)發(fā)送。見(jiàn)下圖
下面是生成的RTL視圖
下面是測(cè)試結(jié)果
七
手把手解析時(shí)序邏輯乘法器代碼
下面是一段16位乘法器的代碼,大家可以先瀏覽一下,之后我再做詳細(xì)解釋
module mux16(clk,rst_n,start,ain,bin,yout,done);input clk; //芯片的時(shí)鐘信號(hào)。input rst_n; //低電平復(fù)位、清零信號(hào)。定義為0表示芯片復(fù)位;定義為1表示復(fù)位信號(hào)無(wú)效。input start; //芯片使能信號(hào)。定義為0表示信號(hào)無(wú)效;定義為1表示芯片讀入輸入管腳得乘數(shù)和被乘數(shù),并將乘積復(fù)位清零。input[15:0] ain; //輸入a(被乘數(shù)),其數(shù)據(jù)位寬為16bit.input[15:0] bin; //輸入b(乘數(shù)),其數(shù)據(jù)位寬為16bit.output[31:0] yout; //乘積輸出,其數(shù)據(jù)位寬為32bit.output done; //芯片輸出標(biāo)志信號(hào)。定義為1表示乘法運(yùn)算完成.reg[15:0] areg; //乘數(shù)a寄存器reg[15:0] breg; //乘數(shù)b寄存器reg[31:0] yout_r; //乘積寄存器reg done_r;reg[4:0] i; //移位次數(shù)寄存器//------------------------------------------------//數(shù)據(jù)位控制always @(posedge clk or negedge rst_n)if(!rst_n) i <= 5'd0;else if(start && i < 5'd17) i <= i+1'b1;else if(!start) i <= 5'd0;//------------------------------------------------//乘法運(yùn)算完成標(biāo)志信號(hào)產(chǎn)生always @(posedge clk or negedge rst_n)if(!rst_n) done_r <= 1'b0;else if(i == 5'd16) done_r <= 1'b1; //乘法運(yùn)算完成標(biāo)志else if(i == 5'd17) done_r <= 1'b0; //標(biāo)志位撤銷(xiāo)assign done = done_r;//------------------------------------------------//專(zhuān)用寄存器進(jìn)行移位累加運(yùn)算always @(posedge clk or negedge rst_n) beginif(!rst_n) beginareg <= 16'h0000;breg <= 16'h0000;yout_r <= 32'h00000000;endelse if(start) begin //啟動(dòng)運(yùn)算if(i == 5'd0) begin //鎖存乘數(shù)、被乘數(shù)areg <= ain;breg <= bin;endelse if(i > 5'd0 && i < 5'd16) beginif(areg[i-1]) yout_r = {1'b0,yout[30:15]+breg,yout_r[14:1]}; //累加并移位else yout_r <= yout_r>>1; //移位不累加endelse if(i == 5'd16 && areg[15]) yout_r[31:16] <= yout_r[31:16]+breg; //累加不移位endendassign?yout?=?yout_r;endmodule
要理解這段代碼,首先要弄明白幾個(gè)點(diǎn)。
1、我們通常寫(xiě)的十進(jìn)制的乘法豎式,同樣適用于二進(jìn)制。下面我們就以這個(gè)算式為例:1011 x 0111 =0100_1101。
2、兩個(gè)16位的數(shù)相乘,結(jié)果是32位的,沒(méi)有32位要在高位補(bǔ)零。
3、計(jì)算兩個(gè)16位的數(shù)相乘需要移位15次。
例如:
1 0 1 1
x 0 1 1 1
------------------------------------
1 0 1 1
1 0 1 1
1 0 1 1
0 0 0 0
------------------------------------
1 0 0 1 1 0 1
前三次計(jì)算是移位的,最后一次沒(méi)有移位
4、兩個(gè)16位的數(shù)相加,結(jié)果是17位的,不夠17位最高位補(bǔ)零。
例如語(yǔ)句yout[30:15]+breg,結(jié)果是17位的。
知道了這些,我們就開(kāi)始看代碼了
1)、接口部分注釋寫(xiě)的很清楚,這里就不提了
2)、數(shù)據(jù)位控制部分
always @(posedge clk or negedge rst_n)if(!rst_n) i <= 5'd0;else if(start && i < 5'd17) i <= i+1'b1;else if(!start) i <= 5'd0;
當(dāng)start為1時(shí),芯片讀入兩個(gè)數(shù),此時(shí)開(kāi)始計(jì)數(shù),計(jì)數(shù)16次,乘法運(yùn)算開(kāi)始
3)、乘法運(yùn)算完成標(biāo)志信號(hào)產(chǎn)生
always @(posedge clk or negedge rst_n)if(!rst_n) done_r <= 1'b0;else if(i == 5'd16) done_r <= 1'b1; //乘法運(yùn)算完成標(biāo)志else if(i == 5'd17) done_r <= 1'b0; //標(biāo)志位撤銷(xiāo)assign done = done_r;
這部分也很好理解
4)、專(zhuān)用寄存器進(jìn)行移位累加運(yùn)算
這里為了簡(jiǎn)單,就用15到18位代替15到30位
以上部分是最主要的計(jì)算部分,其他地方相對(duì)來(lái)說(shuō)還比較簡(jiǎn)單,例如當(dāng)乘數(shù)某一位為0時(shí),不用累加,直接右移,當(dāng)i計(jì)數(shù)到16時(shí),此時(shí)就不用再移位了,可以直接用位數(shù)表示,直接累加即可。
下面是仿真圖
八
基于FIFO的串口發(fā)送機(jī)設(shè)計(jì)全流程
首先來(lái)解釋一下FIFO的含義,F(xiàn)IFO就是First Input First Output的縮寫(xiě),就是先入先出的意思,按照我的理解就是,先進(jìn)去的數(shù)據(jù)先出,例如一個(gè)數(shù)組的高位先進(jìn),那么讀出來(lái)的時(shí)候也就高位先出。下面是百度百科的解釋。
FIFO一般用于不同時(shí)鐘域之間的數(shù)據(jù)傳輸,比如FIFO的一端是AD數(shù)據(jù)采集,另一端是計(jì)算機(jī)的PCI總線(xiàn),假設(shè)其AD采集的速率為16位 100K SPS,那么每秒的數(shù)據(jù)量為100K×16bit=1.6Mbps,而PCI總線(xiàn)的速度為33MHz,總線(xiàn)寬度32bit,其最大傳輸速率為1056Mbps,在兩個(gè)不同的時(shí)鐘域間就可以采用FIFO來(lái)作為數(shù)據(jù)緩沖。另外對(duì)于不同寬度的數(shù)據(jù)接口也可以用FIFO,例如單片機(jī)為8位數(shù)據(jù)輸出,而DSP可能是16位數(shù)據(jù)輸入,在單片機(jī)與DSP連接時(shí)就可以使用FIFO來(lái)達(dá)到數(shù)據(jù)匹配的目的。
我們將這三個(gè)模塊分別定義為dataoutput塊,fifo_ctrl塊和uart_ctrl塊。現(xiàn)在考慮連線(xiàn),具體到每一根連線(xiàn),這樣根據(jù)圖來(lái)寫(xiě)代碼要比直接用腦子構(gòu)圖要方便的多。三個(gè)模塊,先考慮時(shí)鐘和復(fù)位信號(hào)線(xiàn),三個(gè)模塊都有,然后,數(shù)據(jù)產(chǎn)生模塊要將產(chǎn)生的數(shù)據(jù)發(fā)給FIFO模塊,所以要有數(shù)據(jù)寫(xiě)入線(xiàn),我們定義它為wr-datain,數(shù)據(jù)寫(xiě)入FIFO塊后總要輸出,這些數(shù)據(jù)就是我們要發(fā)送的數(shù)據(jù),所以定義輸出數(shù)據(jù)線(xiàn)tx_data,先不管FIFO,我們?cè)賮?lái)定義數(shù)據(jù)發(fā)送模塊的連線(xiàn),數(shù)據(jù)發(fā)送總要有個(gè)啟動(dòng)信號(hào),所以我們定義變量tx_start,之后,還要有一個(gè)輸出端給PC機(jī),我們定義這個(gè)輸出端位rs232。
對(duì)于FIFO模塊的例化過(guò)程很簡(jiǎn)單就不做過(guò)多的說(shuō)明,只把接口說(shuō)一下,F(xiàn)IFO模塊除了時(shí)鐘,復(fù)位信號(hào)外,還有數(shù)據(jù)輸入端口,這個(gè)端口要和之前的數(shù)據(jù)產(chǎn)生模塊的數(shù)據(jù)輸出端口相連,還有寫(xiě)請(qǐng)求端口,高電平有效,數(shù)據(jù)發(fā)送模塊每隔1秒鐘產(chǎn)生一個(gè)16位的數(shù)據(jù),并發(fā)送寫(xiě)請(qǐng)求命令給FIFO,還有讀請(qǐng)求命令,高電平有效數(shù)據(jù)發(fā)送模塊在發(fā)送數(shù)據(jù)時(shí)要發(fā)送一個(gè)讀請(qǐng)求給FIFO,從中讀取數(shù)據(jù)后再發(fā)送給PC機(jī),還有空信號(hào)empty,只要檢測(cè)到FIFO中有數(shù)據(jù),empty就為低電平,我們可用這個(gè)信號(hào)來(lái)啟動(dòng)數(shù)據(jù)發(fā)送模塊。這樣一來(lái),我們的整體框架就出來(lái)了有了這個(gè)整體框架,再寫(xiě)代碼就容易多了。
下面是RTL視圖
按照這個(gè)框架,先把接口定義出來(lái),中間的連線(xiàn)用wire型
設(shè)計(jì)完端口之后我們就來(lái)設(shè)計(jì)底層模塊,先設(shè)計(jì)數(shù)據(jù)產(chǎn)生模塊dataoutput,這個(gè)部分主要是產(chǎn)生數(shù)據(jù),可用一個(gè)分頻電路實(shí)現(xiàn)每1s發(fā)送一次的數(shù)據(jù),產(chǎn)生這16位數(shù)據(jù)的時(shí)候,需要16個(gè)時(shí)鐘,每個(gè)時(shí)鐘數(shù)據(jù)自加1,總體來(lái)說(shuō)比較簡(jiǎn)單
寫(xiě)完一個(gè)模塊之后養(yǎng)成好習(xí)慣,馬上把端口例化
數(shù)據(jù)產(chǎn)生以后就要進(jìn)入緩沖器FIFO,由于這段代碼我們是調(diào)用的,所以只要例化接口就好了,只需要將產(chǎn)生的fifo_ctrl_inst文件中例化好的代碼拷貝粘貼就好
最后我們要寫(xiě)數(shù)據(jù)發(fā)送部分,之前已經(jīng)講過(guò),數(shù)據(jù)發(fā)送部分還要包括兩個(gè)子模塊,一個(gè)是波特率匹配模塊,一個(gè)是發(fā)送模塊,既然又包括兩個(gè)子模塊,那么我們還要構(gòu)建一個(gè)框圖
按照之前的例子,當(dāng)FIFO當(dāng)中有數(shù)據(jù)時(shí)empty就會(huì)拉低,我們把它取反后送給發(fā)送模塊,告訴發(fā)送模塊準(zhǔn)備發(fā)送,這樣,發(fā)送模塊就會(huì)產(chǎn)生一個(gè)波特率計(jì)數(shù)器啟動(dòng)信號(hào)bps_start給波特率匹配模塊,波特率匹配模塊收到信號(hào)后立馬開(kāi)始匹配計(jì)數(shù),并產(chǎn)生采集信號(hào),將采集信號(hào)傳給發(fā)送模塊,發(fā)送模塊根據(jù)采集信號(hào),將數(shù)據(jù)一位一位發(fā)送出去。知道了這個(gè)原理之后,我們構(gòu)建起這樣一個(gè)框架
根據(jù)這個(gè)框圖,我們定義端口和線(xiàn)
定義完端口之后,開(kāi)始寫(xiě)發(fā)送模塊,用邊沿脈沖檢測(cè)法檢測(cè)啟動(dòng)信號(hào)tx_start信號(hào)的上升沿來(lái)啟動(dòng)發(fā)送部分,波特率配置模塊具體代碼在前面也文章中有給出,就不在說(shuō)明,寫(xiě)完之后例化端口,這兩個(gè)模塊作為數(shù)據(jù)發(fā)送模塊的子模塊,要在數(shù)據(jù)發(fā)送模塊下例化
這樣一來(lái),我們整個(gè)設(shè)計(jì)就完成了,看上去很簡(jiǎn)單,但是從我自己實(shí)踐的角度來(lái)說(shuō)還是有點(diǎn)挑戰(zhàn)的,包括中間出現(xiàn)的各種問(wèn)題,下面就來(lái)分享一下我在做這個(gè)設(shè)計(jì)時(shí)遇到的問(wèn)題
1.例化問(wèn)題
在例化端口時(shí),要注意括號(hào)里面的才是本層模塊的端口,也就是說(shuō)在本層模塊上面已經(jīng)定義過(guò)的變量,括號(hào)外面的才是被調(diào)用模塊的端口,也在下層模塊的頂部被聲明,我在寫(xiě)這段程序的時(shí)候?qū)⒍哳嵉沽?,?dǎo)致連線(xiàn)不成功,最終是通過(guò)查看RTL視圖知道了哪根線(xiàn)有問(wèn)題才修改成功的
2.同一個(gè)變量不能在多個(gè)always語(yǔ)句中被賦值
我們可能習(xí)慣這么寫(xiě)
那么,num的值在其他always語(yǔ)句中就不允許再被賦值或者清零,我在寫(xiě)的時(shí)候在其他always語(yǔ)句中將num 清零了,導(dǎo)致編譯不成功
3.定義變量之前不要出現(xiàn)該變量,即使后面又定義了
例如,我先進(jìn)行num的運(yùn)算,之后再定義num,reg [3:0] num,這樣寫(xiě)的話(huà)雖然編譯沒(méi)有錯(cuò)誤,但是在調(diào)用modelsim仿真的時(shí)候它會(huì)出現(xiàn)編譯錯(cuò)誤,所以為了規(guī)范,不要這樣寫(xiě)
4. 在邊沿脈沖檢測(cè)的時(shí)候,習(xí)慣于檢測(cè)下降沿,而這里是檢測(cè)tx_en 的上升沿,所以我在復(fù)位清零的時(shí)候錯(cuò)誤的將兩級(jí)寄存器賦值為0,實(shí)際上在檢測(cè)上升沿時(shí)要對(duì)兩級(jí)寄存器復(fù)位時(shí)置一,再把最后一級(jí)寄存器取反后與上一級(jí)相與。
5.在發(fā)送數(shù)據(jù)部分,由于受到上次寫(xiě)接收部分程序的影響,沒(méi)有將起始位發(fā)送出去,因?yàn)樵诮邮詹糠郑遣恍枰邮掌鹗嘉坏?,是從第一位開(kāi)始,而在發(fā)送部分只有先發(fā)送起始位才能和上位機(jī)握手通信,還有在發(fā)送完數(shù)據(jù)后要發(fā)送停止位,其他情況下都發(fā)送高電平來(lái)阻止通信的進(jìn)行
6.最后一個(gè)問(wèn)題是最棘手的問(wèn)題,我找了好大一半天也沒(méi)發(fā)現(xiàn),最后還是根據(jù)源代碼找出來(lái)的,不過(guò)我還是不知道將這兩條語(yǔ)句顛倒了對(duì)程序有什么影響,只知道顛倒后數(shù)據(jù)會(huì)一直在發(fā)送,不會(huì)像預(yù)設(shè)一樣,每隔一秒發(fā)送一次,至今還是搞不清楚,希望大神指點(diǎn)迷津
總結(jié)
語(yǔ)法上的錯(cuò)誤到不至于太難,寫(xiě)的多了就不會(huì)出錯(cuò)了,關(guān)鍵是邏輯上的錯(cuò)誤很隱蔽,也很難發(fā)現(xiàn),可以通過(guò)RTL視圖來(lái)檢測(cè)連線(xiàn)上是否正確,還可以借助仿真工具。
審核編輯 :李倩
-
FPGA
+關(guān)注
關(guān)注
1625文章
21636瀏覽量
601315 -
寄存器
+關(guān)注
關(guān)注
31文章
5301瀏覽量
119862
原文標(biāo)題:工程師深度:FPGA 高手養(yǎng)成記
文章出處:【微信號(hào):zhuyandz,微信公眾號(hào):FPGA之家】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論