FPGA杂记5——格雷码转换设计
在此感謝 特權(quán)同學(xué),接下來寫到的FPGA雜記基本上源于他的B站講解視頻,同時這是他的淘寶商鋪鏈接,感興趣的同學(xué)也可以在B站中看到他更多的一些視頻學(xué)習(xí)資料,一起學(xué)習(xí)一起成長
1. 顯示和監(jiān)視任務(wù)
1.1 顯示任務(wù) $display 以及 $write
兩者的語法結(jié)構(gòu)大概都是:任務(wù)名(“可選字符串+格式”,信號列表)
- 任務(wù)名包括$displayb(二進(jìn)制)、 $displayh(十六進(jìn)制)、 $displayo(八進(jìn)制)以及 $write的其他任務(wù)名
- 可選字符串+格式包括%h(16進(jìn)制輸出)、%c(ASCII輸出)、%b(二進(jìn)制輸出)、%s(字符串輸出)、%t(以當(dāng)前時間格式輸出顯示)等等
- display和write的區(qū)別在于display輸出后會自動換行
%0d,加個0會使得輸出的數(shù)據(jù)前面的零或者空格刪掉(一般發(fā)生在輸出要求位數(shù)大于實際輸出位數(shù)的情況下)例如,32位的數(shù)據(jù)以十進(jìn)制顯示為65,用顯示任務(wù)不加0的結(jié)果是:____(空格)65,但若是要求為%0d,那么輸出結(jié)果就是65
1.2 監(jiān)視任務(wù)$monitor
$monitor任務(wù)在聲明后默認(rèn)開啟,只有在其運(yùn)行期間調(diào)用系統(tǒng)任務(wù) $monitoroff 則會關(guān)閉監(jiān)視任務(wù)知道系統(tǒng)再次調(diào)用系統(tǒng)任務(wù) $monitoron后將重新開啟監(jiān)視
以上兩種主要在testbench中調(diào)用任務(wù),方便在測試中驗證自己的設(shè)計、邏輯功能的實現(xiàn)
2. 4位格雷碼計數(shù)器設(shè)計
2.1 設(shè)計實現(xiàn)
設(shè)計目標(biāo):以1S為一個計數(shù)周期(注意不是系統(tǒng)時鐘),計數(shù)值從0一直往上疊加,并將計數(shù)值不斷轉(zhuǎn)換成格雷碼輸出
`timescale 1ns/1ps module vlg_design(input i_clk, //時鐘為50MHz,20nsinput i_rst_n,output[3:0] o_gray); / //1s定時計數(shù) localparam TIMER_1S_MAX_CNT = 32'd1_000_000_000/20; //50MHZ的時鐘在1s內(nèi)的最大周期數(shù),1S標(biāo)志位reg[31:0] r_cnt;always @(posedge i_clk)if(!i_rst_n) r_cnt <= 'b0;else if(r_cnt < (TIMER_1S_MAX_CNT - 1)) r_cnt <= r_cnt + 'b1;else r_cnt <= 'b0;// //1s計數(shù)器 reg[3:0] r_second;always @(posedge i_clk)if(!i_rst_n) r_second <= 'b0;else if(r_cnt == (TIMER_1S_MAX_CNT-1)) r_second <= r_second + 'b1;else ;/ //r_second譯碼為格雷碼輸出 reg[3:0] r_gray;always @(posedge i_clk) if(!i_rst_n) r_gray <= 'b0;else begincase(r_second)4'b0000: r_gray <= 4'b0000;4'b0001: r_gray <= 4'b0001;4'b0010: r_gray <= 4'b0011;4'b0011: r_gray <= 4'b0010;4'b0100: r_gray <= 4'b0110;4'b0101: r_gray <= 4'b0111;4'b0110: r_gray <= 4'b0101;4'b0111: r_gray <= 4'b0100;4'b1000: r_gray <= 4'b1100;4'b1001: r_gray <= 4'b1101;4'b1010: r_gray <= 4'b1111;4'b1011: r_gray <= 4'b1110;4'b1100: r_gray <= 4'b1010;4'b1101: r_gray <= 4'b1011;4'b1110: r_gray <= 4'b1001;4'b1111: r_gray <= 4'b1000;default: ;endcase endassign o_gray = r_gray; endmodule看整個運(yùn)行過程,各部分都會定義一個寄存器暫時儲存輸出結(jié)果,最后再進(jìn)行賦值操作
2.2 測試腳本testbench
`timescale 1ns/1ps module testbench_top(); //參數(shù)定義 `define CLK_PERIORD 20 //時鐘周期設(shè)置為20ns(50MHz) //接口申明 reg clk; reg rst_n; wire[3:0] o_gray; //對被測試的設(shè)計進(jìn)行例化 vlg_design uut_vlg_design(.i_clk(clk),.i_rst_n(rst_n),.o_gray(o_gray)); //復(fù)位和時鐘產(chǎn)生 //時鐘和復(fù)位初始化、復(fù)位產(chǎn)生 initial beginclk <= 0;rst_n <= 0;#1000;rst_n <= 1; end//時鐘產(chǎn)生 always #(`CLK_PERIORD/2) clk = ~clk; //測試激勵產(chǎn)生 integer i; initial begin@(posedge rst_n); //等待復(fù)位完成$monitor("o_gray is %b at %0dns",o_gray,$time);$stop; endendmodule2.3 測試結(jié)果
3. 基于查找表的8位格雷碼轉(zhuǎn)換
3.1 查找表LUT
查找表是相當(dāng)于預(yù)先存儲好結(jié)果的數(shù)據(jù)表,免去了運(yùn)算過程,從另外一種角度來說相當(dāng)于ROM。我們接下來利用IP的ROM加入查找表,通過VIVADO創(chuàng)建出ROM的初始化COE文件用于存放LUT的數(shù)據(jù)
3.2 ROM初始化COE文件
COE文件用于對ROM作初始化賦值,具體格式如下
memory_initialization_radix = 位數(shù)(數(shù)據(jù)格式);memory_initialization_vector = 初始化數(shù)據(jù); //查找表數(shù)據(jù)COE文件制作以及使用ROM流程:
- 打開vivado工程文件點擊IP Catalog選擇生成ROM(以Block Memory Generator為例)
- 將打開的ROM的Memory Type改成Single Port ROM
- 在Port A Options里面將位寬與數(shù)據(jù)個數(shù)改成相應(yīng)的數(shù)據(jù),同時使能always
- 在Other Options中導(dǎo)入COE文件,這里由于沒有COE文件可以在線創(chuàng)建一個即可
- 然后點擊OK會彈出一個generate的窗口直接生成即可
- 回到vivado界面的Sources里面的IP Sources中查看你所配置IP的Instantiation Template中的原語 .veo 文件,并將原語用于你所設(shè)計的verilog代碼中
這里要注意一下,只要在IP中勾選了Primitives Output Register,那么就意味著你會延時兩個時鐘周期輸出數(shù)據(jù),在Summary中也可查看
3.3 設(shè)計代碼.v
`timescale 1ns/1ps module vlg_design(input i_clk, input i_rst_n,input i_en,input[7:0] i_data,output o_vld, //有效信號output[7:0] o_gray);// //o_vld是i_en兩個時鐘周期的延時 reg[1:0] r_vld;always@(posedge i_clk)if(!i_rst_n) r_vld <= 'b00;else r_vld <= {r_vld[0],i_en};assign o_vld = r_vld[1];//IP ROM_LUT blk_mem_gen_0 uut_blk_mem_gen_0 (.clka(i_clk), // input wire clka.addra(i_data), // input wire [7 : 0] addra.douta(o_gray) // output wire [7 : 0] douta ); endmodule3.4 測試腳本
`timescale 1ns/1ps module testbench_top(); //參數(shù)定義 `define CLK_PERIORD 20 //時鐘周期設(shè)置為20ns(50MHz) //接口申明 reg clk; reg rst_n; reg i_en; reg[7:0] i_data; wire o_vld; wire[7:0] o_gray; //對被測試的設(shè)計進(jìn)行例化 vlg_design uut_vlg_design(.i_clk(clk),.i_rst_n(rst_n),.i_en(i_en),.i_data(i_data),.o_vld(o_vld),.o_gray(o_gray)); //復(fù)位和時鐘產(chǎn)生 //時鐘和復(fù)位初始化、復(fù)位產(chǎn)生 initial beginclk <= 0;rst_n <= 0;#1000;rst_n <= 1; end //時鐘產(chǎn)生 always #(`CLK_PERIORD/2) clk = ~clk; //測試激勵產(chǎn)生 initial begini_en <= 'b0;i_data <= 'b0;@(posedge rst_n); //等待復(fù)位完成@(posedge clk);i_en <= 'b1;i_data <= 'b0;@(posedge clk);repeat(255) begin //循環(huán)疊加256次i_data <= i_data + 1;@(posedge clk);endi_en <= 'b0;i_data <= 'b0;#1000;$stop; end// //實時顯示 always@(posedge clk) beginif(o_vld) $display("%b",o_gray);else ; endendmodule4. 格雷碼轉(zhuǎn)換的快速算法
4.1 算法介紹
- 二進(jìn)制碼(自然數(shù))的最高位是MSB,最低位是0
- 二進(jìn)制碼的MSB位直接賦值給格雷碼的MSB位
- 對于次高位(MSB-1)位到最低位0,使用二進(jìn)制碼相鄰位異或的方式獲取格雷碼
- 若二進(jìn)制碼字的第i位與第i+1位相同,則第 i 位的格雷碼為0
- 若二進(jìn)制碼字的第i位與第i+1位不同,則第 i 位的格雷碼為1
舉個例子:4位的二進(jìn)制碼1010轉(zhuǎn)換格雷碼(不通過LUT,直接通過RTL實現(xiàn))
| Bit3 | 取MSB位 | 1 |
| Bit2 | 1 xor 0 | 1 |
| Bit1 | 0 xor 1 | 1 |
| Bit0 | 1 xor 0 | 1 |
最終輸出結(jié)果:1111
4.2 模塊設(shè)計
`timescale 1ns/1ps module vlg_design #(parameter MSB = 7) //參數(shù)定義(input i_clk,input i_rst_n,input i_en,input[MSB:0] i_data,output reg o_vld,output reg[MSB:0] o_gray);always @(posedge i_clk) if(!i_rst_n) o_vld <= 'b0;else o_vld <= i_en; //快速轉(zhuǎn)換設(shè)計 always @(posedge i_clk)o_gray[MSB] <= i_data[MSB];genvar i; generatefor(i=MSB-1;i>=0;i=i-1) beginalways @(posedge i_clk) begino_gray[i] <= i_data[i] ^ i_data[i+1];endend endgenerateendmodule4.3 測試腳本
`timescale 1ns/1ps module testbench_top(); //參數(shù)定義 `define CLK_PERIORD 10 //時鐘周期設(shè)置為10ns(100MHz) parameter GRAY_MSB = 7; //定義參數(shù) //接口申明 reg clk; reg rst_n; reg i_en; reg[GRAY_MSB:0] i_data; wire o_vld; wire[GRAY_MSB:0] o_gray; //對被測試的設(shè)計進(jìn)行例化 vlg_design #(.MSB(GRAY_MSB))uut_vlg_design(.i_clk(clk),.i_rst_n(rst_n),.i_en(i_en),.i_data(i_data),.o_vld(o_vld),.o_gray(o_gray)); //復(fù)位和時鐘產(chǎn)生 //時鐘和復(fù)位初始化、復(fù)位產(chǎn)生 initial beginclk <= 0;rst_n <= 0;#1000;rst_n <= 1; end //時鐘產(chǎn)生 always #(`CLK_PERIORD/2) clk = ~clk; //測試激勵產(chǎn)生 initial begini_en <= 'b0;i_data <= 'b0;$display("The value of GRAY_MSB is %0d",GRAY_MSB); @(posedge rst_n); //等待復(fù)位完成@(posedge clk);i_en <= 'b1;i_data <= 'b0;repeat(2**(GRAY_MSB+1)-1) begin //一共有(MSB+1)位,相當(dāng)于計數(shù)值最大為2^(MSB+1)-1@(posedge clk);i_en <= 'b1;i_data <= i_data + 1;end@(posedge clk);i_en <='b0;$stop; endalways @(posedge clk)if(o_vld) $display("%b",o_gray);else ;endmodule4.4 測試結(jié)果
腳本顯示(modelsim):
測試波形:
總結(jié)
以上是生活随笔為你收集整理的FPGA杂记5——格雷码转换设计的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Django】django使用原生SQ
- 下一篇: Django的信号机制详解