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