理論學習
上一篇介紹了常用的鎖相環IP,這一節將介紹一種較為常用的 存儲類IP核 ——ROM的使用方法。ROM是 只讀存儲器 (Read-Only Memory),顧名思義,我們只能讀出事先存放在固態中的數據,一旦寫入不能再修改或刪除,斷電不丟失。我們知道FPGA只有RAM,因此事實上在 FPGA 中通過 IP 核生成的 ROM 或 RAM掉電內容都會丟失。用 IP 核生成的 ROM 模塊只是提前添加了數據文件(.mif 或.hex 格式),在 FPGA 運行時通過數據文件給 ROM 模塊初始化,才使得 ROM 模塊 像 “真正”的掉電非易失存儲器;也正是這個原因,ROM 模塊的內容必須提前在數據文件中寫死,無法在電路中修改。
Altera推出的ROM IP核分為兩種類型:單端口ROM和雙端口ROM。對于單端口ROM提供一個讀地址端口和一個讀數據端口,只能進行讀操作;雙端口ROM提供兩個讀地址端口和兩個讀數據端口。其中不是每個端口都要用到,調用完IP核后,是可以生成其例化模塊的,到時候就可以看到我們需要控制的信號了。
ROM IP核配置
ROM要事先寫進去.mif 或.hex 格式,因此我們需要先建一個.mif文件,寫入我們想要存儲的數據。
File→New→在Memory Files下找到Memory Initialization File-選擇容量為256,位寬為8bit→選中表格的行或列右鍵可以更改進制,默認地址是十進制,存儲器是無符號十進制→手動輸入數據/復制、粘貼/利用軟件自帶的功能,直接推譯出所有數據。
軟件自帶的填充功能用法
右鍵點擊任意單元格→Custom Fill Cells→容量為256,如果起始地址為0,結束地址就為255→可以看到表格中從0-255自動填充好,由于位寬8bit,255也不會超→保存為.mif格式
創建好工程之后,還是和上一篇中一樣創建IP核,先試單端口
使用寫好的數據,瀏覽文件夾,選擇.mif格式,找到剛才保存的.mif文件導入。
和上一篇一樣,顯示了我們在仿真ROM IP核時需要的Altera仿真庫,提示我們單獨使用第三方仿真工具時需要添加altera_mf的庫
和上一篇一樣,選擇inst.v實例化文件
雙端口有少許不同
1、可以看出有兩條地址線和兩條數據線了
2、設置定義ROM存儲器大小的方式,按字數確定或按比特數確定,用默認就好
3、選擇ROM的容量,還是選擇256個數據(注意:選擇的容量需大于我們需要寫入的數據文件的數據量)
4、設置不同端口的位寬是否相同,默認是關閉,即使用相同位寬
5、設置數據位寬,這里的數據位寬設置8bit
6、存儲單元類型的選擇,按默認選擇自動
1、選擇單時鐘/雙時鐘:單時鐘是用一個時鐘控制所有寄存器,雙時鐘是輸入和輸出時鐘分別控制存儲塊的數據輸入和輸出的相關寄存器:時鐘A控制端口A的所有寄存器,時鐘B控制端口B的所有寄存器。每個端口也支持獨立的時鐘使能
2、選擇是否創建‘rden_a’和‘rden_b’讀使能信號
1、選擇是否輸出‘q_a’和‘q_b’寄存器,選擇的話就會使輸出延遲一拍
2、選擇是否為時鐘信號創建使能信號
3、選擇是否創建“aclr”異步復位信號
后面的步驟都一樣。我們以單端口為例進行設計調用,還是將生成的,qip文件添加到Files下。
設計規劃
首先我們ROM的初始化數據是0~255,每隔0.2s從0地址開始往下讀取數據顯示在數碼管上,再利用兩個按鍵信號來讀取指定地址的數據(例如按下按鍵1顯示地址為99時的數據,按下按鍵2顯示地址為199時的數據,0-255隨意指定)。每按一個按鍵就讀取一個地址的數據顯示在數碼管上。再次按下按鍵后,以當前地址繼續以0.2s的時間間隔往下讀取數據并顯示出來。
一共有5個模塊:按鍵消抖模塊(使用兩次),ROM控制模塊,IP核,數碼管動態顯示模塊,頂層模塊。實際需要做的是ROM控制模塊,頂層只是實現實例化,其他模塊可以直接調用以前的,可能要做一些修改。
剛才建立rom的ip后生成了inst.v實例化文件,由內容可以看出這個模塊出輸入輸出信號名稱,頂層模塊中關于rom的ip模塊的實例化可以直接復制這個
編寫代碼
ROM 控制模塊
讀操作是在時鐘的上升沿觸發的,而我們在調用ROM時是沒有生成讀使能的,所以在讀時鐘上升沿只要給相應的地址就能在時鐘的上升沿讀出該地址內的數據了。我們只需要控制生成讀地址即可。現在自定義的ROM IP的用法就是給從地址線輸入一個地址,ROM模塊從數據線輸出地址對應的數據。
輸入有時鐘、復位、兩個按鍵標志信號,中間信號有兩個地址標志信號,200ms計數器,輸出是8位地址。某一個按鍵按下時對應的按鍵標志信號會拉高,當檢測到某一個按鍵標志信號拉高時,對應的地址標志信號會拉高,直至下一個按鍵被按下。按一次按鍵是顯示規定地址存放的數據,再按同一個按鍵會在該地址基礎上繼續顯示下一個地址的數據,而按不同的按鍵就是顯示另一個按鍵規定的地址存放的數據。
module rom_ctrl
(
input wire sys_clk , //系統時鐘,頻率50MHz
input wire sys_rst_n , //復位信號,低有效
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計數器最大值
//reg define
reg addr_flag1 ; //特定地址1標志信號
reg addr_flag2 ; //特定地址2標志信號
reg [23:0] cnt_200ms ; //0.2s計數器
//產生特定地址1標志信號
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;
//產生特定地址2標志信號
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循環計數
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循環,其中兩個按鍵控制兩個特定地址的跳轉
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
產生addr_flag1:復位有效時addr_flag歸0;key2_flag拉高時addr_flag1歸0(因為key1與key2是互斥的,同一時間只能亮一個,所以不管key1現在是否按下去,key2一旦按下去就要以key2的地址為準了);key_flag1拉高時addr_flag1取反(因為可能存在連按兩次的情況,第一次按addr_flag1從第變高,第二次按就從高變低)。產生addr_flag1同理
200ms計數器:復位有效時歸0;計數到最大值時歸0;否則自加1
地址輸出:復位有效時addr為0;當addr為255且cnt200ms計數為最大值CNT_MAX時,addr為0(因為地址指向255后0.2ms要循環顯示地址0對應的數據);當addr_flag1拉高時addr為99;當addr_flag2拉高時addr為199;當計數器計到最大值時addr自加1
頂層模塊
實質是幾個模塊的實例化,需要注意的是key模塊使用了兩次,要實例化兩次,兩次實例化的模塊名字不能相同
之前的數碼管動態顯示的模塊框圖做一下修正
對比一下現在的模塊
1、之前的給數碼管模塊的輸入數據是data_gen這個模塊的輸出產生的,現在的data是rom的IP模塊產生的。且這個IP的輸出只有8位,而我們之前的設置的數碼管模塊data是27位,因此要補19個0能保證位數一致且對顯示沒有影響。還需要修改一處是top_seg_595模塊中實例化了data_gen,現在不需要了
2、之前的data_gen的輸出信號seg_en與seg_595_dynamic模塊的輸入信號seg_en相連,用于給數碼管顯示使能?,F在的rom_256x8模塊的實例化是系統IP自動生成的,沒有提供seg_en信號接口,需要自行設置這個信號為高電平讓他使能
頂層模塊代碼
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數據
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 ), //數碼管使能信號,高電平有效
.stcp (stcp ), //輸出數據存儲寄時鐘
.shcp (shcp ), //移位寄存器的時鐘輸入
.ds (ds )//串行數據輸入
);
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的高位和低位
數碼管動態顯示模塊:這個模塊是第三次使用了,因為之前的data是27位,現在只用了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:模擬系統時鐘,每10ns電平取反一次,周期為20ns,頻率為50MHz
always #10 sys_clk = ~sys_clk;
//重新定義參數值,縮短仿真時間仿真
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 ), //系統時鐘,頻率50MHz
.sys_rst_n (sys_rst_n ), //復位信號,低電平有效
.key (key ), //輸入按鍵信號
.stcp (stcp ), //輸出數據存儲寄時鐘
.shcp (shcp ), //移位寄存器的時鐘輸入
.ds (ds ) //串行數據輸入
);
endmodule
初始化:時鐘為高電平,復位為低電平,按鍵都為高電平表示未按下
延遲200ns后復位釋放
延遲2000ns后按下按鍵1但是模擬抖動,抖動中有200ns的按鍵是按下狀態以便識別并拉高flag
再重復模擬按下按鍵2,2,2,1,1
重新定義參數,縮短仿真時間
rom模塊實例化
波形變化
管腳分配
-
FPGA
+關注
關注
1626文章
21678瀏覽量
602037 -
鎖相環
+關注
關注
35文章
583瀏覽量
87699 -
存儲器
+關注
關注
38文章
7455瀏覽量
163623 -
ROM
+關注
關注
4文章
562瀏覽量
85679 -
IP核
+關注
關注
4文章
326瀏覽量
49434
發布評論請先 登錄
相關推薦
評論