理論學(xué)習(xí)
上一篇介紹了常用的鎖相環(huán)IP,這一節(jié)將介紹一種較為常用的 存儲類IP核 ——ROM的使用方法。ROM是 只讀存儲器 (Read-Only Memory),顧名思義,我們只能讀出事先存放在固態(tài)中的數(shù)據(jù),一旦寫入不能再修改或刪除,斷電不丟失。我們知道FPGA只有RAM,因此事實上在 FPGA 中通過 IP 核生成的 ROM 或 RAM掉電內(nèi)容都會丟失。用 IP 核生成的 ROM 模塊只是提前添加了數(shù)據(jù)文件(.mif 或.hex 格式),在 FPGA 運行時通過數(shù)據(jù)文件給 ROM 模塊初始化,才使得 ROM 模塊 像 “真正”的掉電非易失存儲器;也正是這個原因,ROM 模塊的內(nèi)容必須提前在數(shù)據(jù)文件中寫死,無法在電路中修改。
Altera推出的ROM IP核分為兩種類型:單端口ROM和雙端口ROM。對于單端口ROM提供一個讀地址端口和一個讀數(shù)據(jù)端口,只能進(jìn)行讀操作;雙端口ROM提供兩個讀地址端口和兩個讀數(shù)據(jù)端口。其中不是每個端口都要用到,調(diào)用完IP核后,是可以生成其例化模塊的,到時候就可以看到我們需要控制的信號了。
ROM IP核配置
ROM要事先寫進(jìn)去.mif 或.hex 格式,因此我們需要先建一個.mif文件,寫入我們想要存儲的數(shù)據(jù)。
File→New→在Memory Files下找到Memory Initialization File-選擇容量為256,位寬為8bit→選中表格的行或列右鍵可以更改進(jìn)制,默認(rèn)地址是十進(jìn)制,存儲器是無符號十進(jìn)制→手動輸入數(shù)據(jù)/復(fù)制、粘貼/利用軟件自帶的功能,直接推譯出所有數(shù)據(jù)。
軟件自帶的填充功能用法
右鍵點擊任意單元格→Custom Fill Cells→容量為256,如果起始地址為0,結(jié)束地址就為255→可以看到表格中從0-255自動填充好,由于位寬8bit,255也不會超→保存為.mif格式
創(chuàng)建好工程之后,還是和上一篇中一樣創(chuàng)建IP核,先試單端口
使用寫好的數(shù)據(jù),瀏覽文件夾,選擇.mif格式,找到剛才保存的.mif文件導(dǎo)入。
和上一篇一樣,顯示了我們在仿真ROM IP核時需要的Altera仿真庫,提示我們單獨使用第三方仿真工具時需要添加altera_mf的庫
和上一篇一樣,選擇inst.v實例化文件
雙端口有少許不同
1、可以看出有兩條地址線和兩條數(shù)據(jù)線了
2、設(shè)置定義ROM存儲器大小的方式,按字?jǐn)?shù)確定或按比特數(shù)確定,用默認(rèn)就好
3、選擇ROM的容量,還是選擇256個數(shù)據(jù)(注意:選擇的容量需大于我們需要寫入的數(shù)據(jù)文件的數(shù)據(jù)量)
4、設(shè)置不同端口的位寬是否相同,默認(rèn)是關(guān)閉,即使用相同位寬
5、設(shè)置數(shù)據(jù)位寬,這里的數(shù)據(jù)位寬設(shè)置8bit
6、存儲單元類型的選擇,按默認(rèn)選擇自動
1、選擇單時鐘/雙時鐘:單時鐘是用一個時鐘控制所有寄存器,雙時鐘是輸入和輸出時鐘分別控制存儲塊的數(shù)據(jù)輸入和輸出的相關(guān)寄存器:時鐘A控制端口A的所有寄存器,時鐘B控制端口B的所有寄存器。每個端口也支持獨立的時鐘使能
2、選擇是否創(chuàng)建‘rden_a’和‘rden_b’讀使能信號
1、選擇是否輸出‘q_a’和‘q_b’寄存器,選擇的話就會使輸出延遲一拍
2、選擇是否為時鐘信號創(chuàng)建使能信號
3、選擇是否創(chuàng)建“aclr”異步復(fù)位信號
后面的步驟都一樣。我們以單端口為例進(jìn)行設(shè)計調(diào)用,還是將生成的,qip文件添加到Files下。
設(shè)計規(guī)劃
首先我們ROM的初始化數(shù)據(jù)是0~255,每隔0.2s從0地址開始往下讀取數(shù)據(jù)顯示在數(shù)碼管上,再利用兩個按鍵信號來讀取指定地址的數(shù)據(jù)(例如按下按鍵1顯示地址為99時的數(shù)據(jù),按下按鍵2顯示地址為199時的數(shù)據(jù),0-255隨意指定)。每按一個按鍵就讀取一個地址的數(shù)據(jù)顯示在數(shù)碼管上。再次按下按鍵后,以當(dāng)前地址繼續(xù)以0.2s的時間間隔往下讀取數(shù)據(jù)并顯示出來。
一共有5個模塊:按鍵消抖模塊(使用兩次),ROM控制模塊,IP核,數(shù)碼管動態(tài)顯示模塊,頂層模塊。實際需要做的是ROM控制模塊,頂層只是實現(xiàn)實例化,其他模塊可以直接調(diào)用以前的,可能要做一些修改。
剛才建立rom的ip后生成了inst.v實例化文件,由內(nèi)容可以看出這個模塊出輸入輸出信號名稱,頂層模塊中關(guān)于rom的ip模塊的實例化可以直接復(fù)制這個
編寫代碼
ROM 控制模塊
讀操作是在時鐘的上升沿觸發(fā)的,而我們在調(diào)用ROM時是沒有生成讀使能的,所以在讀時鐘上升沿只要給相應(yīng)的地址就能在時鐘的上升沿讀出該地址內(nèi)的數(shù)據(jù)了。我們只需要控制生成讀地址即可?,F(xiàn)在自定義的ROM IP的用法就是給從地址線輸入一個地址,ROM模塊從數(shù)據(jù)線輸出地址對應(yīng)的數(shù)據(jù)。
輸入有時鐘、復(fù)位、兩個按鍵標(biāo)志信號,中間信號有兩個地址標(biāo)志信號,200ms計數(shù)器,輸出是8位地址。某一個按鍵按下時對應(yīng)的按鍵標(biāo)志信號會拉高,當(dāng)檢測到某一個按鍵標(biāo)志信號拉高時,對應(yīng)的地址標(biāo)志信號會拉高,直至下一個按鍵被按下。按一次按鍵是顯示規(guī)定地址存放的數(shù)據(jù),再按同一個按鍵會在該地址基礎(chǔ)上繼續(xù)顯示下一個地址的數(shù)據(jù),而按不同的按鍵就是顯示另一個按鍵規(guī)定的地址存放的數(shù)據(jù)。
module rom_ctrl
(
input wire sys_clk , //系統(tǒng)時鐘,頻率50MHz
input wire sys_rst_n , //復(fù)位信號,低有效
input wire key1_flag , //按鍵1消抖后有效信號
input wire key2_flag , //按鍵2消抖后有效信號
output reg [7:0] addr //輸出讀ROM地址
);
//parameter define
parameter CNT_MAX = 9_999_999; //0.2s計數(shù)器最大值
//reg define
reg addr_flag1 ; //特定地址1標(biāo)志信號
reg addr_flag2 ; //特定地址2標(biāo)志信號
reg [23:0] cnt_200ms ; //0.2s計數(shù)器
//產(chǎn)生特定地址1標(biāo)志信號
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
addr_flag1 <= 1'b0;
else if(key2_flag == 1'b1)
addr_flag1 <= 1'b0;
else if(key1_flag == 1'b1)
addr_flag1 <= ~addr_flag1;
//產(chǎn)生特定地址2標(biāo)志信號
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
addr_flag2 <= 1'b0;
else if(key1_flag == 1'b1)
addr_flag2 <= 1'b0;
else if(key2_flag == 1'b1)
addr_flag2 <= ~addr_flag2;
//0.2s循環(huán)計數(shù)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_200ms <= 24'd0;
else if(cnt_200ms == CNT_MAX)
cnt_200ms <= 24'd0;
else
cnt_200ms <= cnt_200ms + 1'b1;
//讓地址從0~255循環(huán),其中兩個按鍵控制兩個特定地址的跳轉(zhuǎn)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
addr <= 8'd0;
else if(addr == 8'd255 && cnt_200ms == CNT_MAX)
addr <= 8'd0;
else if(addr_flag1 == 1'b1)
addr <= 8'd99;
else if(addr_flag2 == 1'b1)
addr <= 8'd199;
else if(cnt_200ms == CNT_MAX)
addr <= addr + 1'b1;
endmodule
產(chǎn)生addr_flag1:復(fù)位有效時addr_flag歸0;key2_flag拉高時addr_flag1歸0(因為key1與key2是互斥的,同一時間只能亮一個,所以不管key1現(xiàn)在是否按下去,key2一旦按下去就要以key2的地址為準(zhǔn)了);key_flag1拉高時addr_flag1取反(因為可能存在連按兩次的情況,第一次按addr_flag1從第變高,第二次按就從高變低)。產(chǎn)生addr_flag1同理
200ms計數(shù)器:復(fù)位有效時歸0;計數(shù)到最大值時歸0;否則自加1
地址輸出:復(fù)位有效時addr為0;當(dāng)addr為255且cnt200ms計數(shù)為最大值CNT_MAX時,addr為0(因為地址指向255后0.2ms要循環(huán)顯示地址0對應(yīng)的數(shù)據(jù));當(dāng)addr_flag1拉高時addr為99;當(dāng)addr_flag2拉高時addr為199;當(dāng)計數(shù)器計到最大值時addr自加1
頂層模塊
實質(zhì)是幾個模塊的實例化,需要注意的是key模塊使用了兩次,要實例化兩次,兩次實例化的模塊名字不能相同
之前的數(shù)碼管動態(tài)顯示的模塊框圖做一下修正
對比一下現(xiàn)在的模塊
1、之前的給數(shù)碼管模塊的輸入數(shù)據(jù)是data_gen這個模塊的輸出產(chǎn)生的,現(xiàn)在的data是rom的IP模塊產(chǎn)生的。且這個IP的輸出只有8位,而我們之前的設(shè)置的數(shù)碼管模塊data是27位,因此要補19個0能保證位數(shù)一致且對顯示沒有影響。還需要修改一處是top_seg_595模塊中實例化了data_gen,現(xiàn)在不需要了
2、之前的data_gen的輸出信號seg_en與seg_595_dynamic模塊的輸入信號seg_en相連,用于給數(shù)碼管顯示使能。現(xiàn)在的rom_256x8模塊的實例化是系統(tǒng)IP自動生成的,沒有提供seg_en信號接口,需要自行設(shè)置這個信號為高電平讓他使能
頂層模塊代碼
module rom
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [1:0] key ,
output wire stcp ,
output wire shcp ,
output wire ds
);
//wire define
wire [7:0] addr ; //地址線
wire [7:0] rom_data ; //讀出ROM數(shù)據(jù)
wire key1_flag ; //按鍵1消抖信號
wire key2_flag ; //按鍵2消抖信號
rom_ctrl rom_ctrl_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.key1_flag (key1_flag ),
.key2_flag (key2_flag ),
.addr (addr )
);
key_filter key1_filter_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.key_in (key[0] ),
.key_flag (key1_flag )
);
key_filter key2_filter_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.key_in (key[1] ),
.key_flag (key2_flag )
);
seg_595_dynamic seg_595_dynamic_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.data ({19'd0,rom_data}),
.seg_en (1'b1 ), //數(shù)碼管使能信號,高電平有效
.stcp (stcp ), //輸出數(shù)據(jù)存儲寄時鐘
.shcp (shcp ), //移位寄存器的時鐘輸入
.ds (ds )//串行數(shù)據(jù)輸入
);
rom_256x8 rom_256x8_inst
(
.address (addr ),
.clock (sys_clk ),
.q (rom_data )
);
endmodule
rom_ctrl模塊的實例化沒有什么需要特別注意的
key_filter模塊需要注意的是這兩個模塊名不能一樣,模塊都用的是key_filter,實例化后一個叫key1_filter_inst,一個叫key2_filter_inst。rom頂層模塊中定義的Key是一個2位的變量,兩個模塊中的Key_in就分別接key的高位和低位
數(shù)碼管動態(tài)顯示模塊:這個模塊是第三次使用了,因為之前的data是27位,現(xiàn)在只用了data的其中8位,剩下19位置0
rom_256x8模塊的實例化就是rom的ip核生成的inst.v實例化文件
Testbench
`timescale 1ns/1ns
module tb_rom();
//wire define
wire stcp;
wire shcp;
wire ds ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
reg [1:0] key ;
//對sys_clk,sys_rst賦初值,并模擬按鍵抖動
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
key <= 2'b11;
#200 sys_rst_n <= 1'b1 ;
//按下按鍵key[0]
#2000 key[0] <= 1'b0;//按下按鍵
#20 key[0] <= 1'b1;//模擬抖動
#20 key[0] <= 1'b0;//模擬抖動
#20 key[0] <= 1'b1;//模擬抖動
#20 key[0] <= 1'b0;//模擬抖動
#200 key[0] <= 1'b1;//松開按鍵
#20 key[0] <= 1'b0;//模擬抖動
#20 key[0] <= 1'b1;//模擬抖動
#20 key[0] <= 1'b0;//模擬抖動
#20 key[0] <= 1'b1;//模擬抖動
//按下按鍵key[1]
#2000 key[1] <= 1'b0;//按下按鍵
#20 key[1] <= 1'b1;//模擬抖動
#20 key[1] <= 1'b0;//模擬抖動
#20 key[1] <= 1'b1;//模擬抖動
#20 key[1] <= 1'b0;//模擬抖動
#200 key[1] <= 1'b1;//松開按鍵
#20 key[1] <= 1'b0;//模擬抖動
#20 key[1] <= 1'b1;//模擬抖動
#20 key[1] <= 1'b0;//模擬抖動
#20 key[1] <= 1'b1;//模擬抖動
//按下按鍵key[1]
#2000 key[1] <= 1'b0;//按下按鍵
#20 key[1] <= 1'b1;//模擬抖動
#20 key[1] <= 1'b0;//模擬抖動
#20 key[1] <= 1'b1;//模擬抖動
#20 key[1] <= 1'b0;//模擬抖動
#200 key[1] <= 1'b1;//松開按鍵
#20 key[1] <= 1'b0;//模擬抖動
#20 key[1] <= 1'b1;//模擬抖動
#20 key[1] <= 1'b0;//模擬抖動
#20 key[1] <= 1'b1;//模擬抖動
//按下按鍵key[1]
#2000 key[1] <= 1'b0;//按下按鍵
#20 key[1] <= 1'b1;//模擬抖動
#20 key[1] <= 1'b0;//模擬抖動
#20 key[1] <= 1'b1;//模擬抖動
#20 key[1] <= 1'b0;//模擬抖動
#200 key[1] <= 1'b1;//松開按鍵
#20 key[1] <= 1'b0;//模擬抖動
#20 key[1] <= 1'b1;//模擬抖動
#20 key[1] <= 1'b0;//模擬抖動
#20 key[1] <= 1'b1;//模擬抖動
//按下按鍵key[0]
#2000 key[0] <= 1'b0;//按下按鍵
#20 key[0] <= 1'b1;//模擬抖動
#20 key[0] <= 1'b0;//模擬抖動
#20 key[0] <= 1'b1;//模擬抖動
#20 key[0] <= 1'b0;//模擬抖動
#200 key[0] <= 1'b1;//松開按鍵
#20 key[0] <= 1'b0;//模擬抖動
#20 key[0] <= 1'b1;//模擬抖動
#20 key[0] <= 1'b0;//模擬抖動
#20 key[0] <= 1'b1;//模擬抖動
//按下按鍵key[0]
#2000 key[0] <= 1'b0;//按下按鍵
#20 key[0] <= 1'b1;//模擬抖動
#20 key[0] <= 1'b0;//模擬抖動
#20 key[0] <= 1'b1;//模擬抖動
#20 key[0] <= 1'b0;//模擬抖動
#200 key[0] <= 1'b1;//松開按鍵
#20 key[0] <= 1'b0;//模擬抖動
#20 key[0] <= 1'b1;//模擬抖動
#20 key[0] <= 1'b0;//模擬抖動
#20 key[0] <= 1'b1;//模擬抖動
end
//sys_clk:模擬系統(tǒng)時鐘,每10ns電平取反一次,周期為20ns,頻率為50MHz
always #10 sys_clk = ~sys_clk;
//重新定義參數(shù)值,縮短仿真時間仿真
defparam rom_inst.key1_filter_inst.CNT_MAX = 5 ;
defparam rom_inst.key2_filter_inst.CNT_MAX = 5 ;
defparam rom_inst.rom_ctrl_inst.CNT_MAX = 99;
//---------------rom_inst--------------
rom rom_inst
(
.sys_clk (sys_clk ), //系統(tǒng)時鐘,頻率50MHz
.sys_rst_n (sys_rst_n ), //復(fù)位信號,低電平有效
.key (key ), //輸入按鍵信號
.stcp (stcp ), //輸出數(shù)據(jù)存儲寄時鐘
.shcp (shcp ), //移位寄存器的時鐘輸入
.ds (ds ) //串行數(shù)據(jù)輸入
);
endmodule
初始化:時鐘為高電平,復(fù)位為低電平,按鍵都為高電平表示未按下
延遲200ns后復(fù)位釋放
延遲2000ns后按下按鍵1但是模擬抖動,抖動中有200ns的按鍵是按下狀態(tài)以便識別并拉高flag
再重復(fù)模擬按下按鍵2,2,2,1,1
重新定義參數(shù),縮短仿真時間
rom模塊實例化
波形變化
管腳分配
-
FPGA
+關(guān)注
關(guān)注
1645文章
22026瀏覽量
617644 -
鎖相環(huán)
+關(guān)注
關(guān)注
35文章
597瀏覽量
89010 -
存儲器
+關(guān)注
關(guān)注
38文章
7644瀏覽量
167127 -
ROM
+關(guān)注
關(guān)注
4文章
578瀏覽量
87263 -
IP核
+關(guān)注
關(guān)注
4文章
338瀏覽量
50722
發(fā)布評論請先 登錄
關(guān)于FPGA IP核
FPGA的IP軟核使用技巧
FPGA IP核的相關(guān)問題
關(guān)于ip核生成的rom
【鋯石A4 FPGA試用體驗】IP核之ROM(二)創(chuàng)建ROM IP核
xilinx FPGA的FFT IP核的調(diào)用
LabVIEW FPGA CORDIC IP核的arctan使用方法
【夢翼師兄今日分享】 只讀儲存器ROM IP核的調(diào)取及應(yīng)用
使用Vivado調(diào)用ROM IP核
FPGA零基礎(chǔ)學(xué)習(xí):IP CORE 之 ROM設(shè)計
FPGA學(xué)習(xí):使用matlab和ISE 創(chuàng)建并仿真ROM IP核

FPGA學(xué)習(xí)筆記:PLL IP核的使用方法

FPGA學(xué)習(xí)筆記:RAM IP核的使用方法

評論