1 項目背景 (至簡(jiǎn)設計交流群:544453837) 1.1 FPGA存儲器 目前大多數FPGA都有內嵌的塊RAM(Block RAM),可以將其靈活地配置成單端口RAM(DPRAM,Single Port RAM)、雙端口RAM(DPRAM,Double Ports RAM)、偽雙端口RAM(PseudoDPRAM)、CAM(Content AddressableMemory)、FIFO等常用存儲結構。FPGA中其實(shí)并沒(méi)有專(zhuān)用的ROM硬件資源,實(shí)現ROM的思路是對RAM賦予初值,并保持該初值。 Altera 的器件內部提供了各種存儲器模塊(RAM、ROM 或雙口 RAM),可以在設計中使用 MegaWizardPlug-In Manager,執行【Tools】|【MegaWizard Plug-InManager】菜單命令來(lái)創(chuàng )建所需要 的存儲器模塊。也可以使用 Altera 提供的宏功能模塊 LPM_ROM 來(lái)創(chuàng )建存儲器模塊。每個(gè) ROM 模塊有 CLOCK(時(shí)鐘)、address(地址)這兩個(gè)輸入信號和一個(gè) q(值)輸出信號。 ROM 在每個(gè)時(shí)鐘上升沿取出由地址信號所指定的存儲單元中的值并輸出。ROM 內的值通過(guò)加載 MIF (Memory Initialization File,存儲器初始化文件)文件來(lái)實(shí)現。 當在設計中使用了器件內部的存儲器模塊時(shí),需要對存儲器模塊進(jìn)行初始化。在Quartus 中, 可以使用兩種格式的存儲器初始化文件:Intel Hex 格式(.hex)或Altera 存儲器初始化格式(.mif)的文件。 MIF 文件是 Altera 存儲器類(lèi)器件初始化的專(zhuān)用文件格式,文件內容為地址與值的對應表,規定了 存儲器單元的初始值。 如果將要存儲于 ROM 中的內容比較少或者很有規律,可以執行【File】|【New…】菜單命令,創(chuàng ) 建 MIF 文件并編輯其內容。 如果已經(jīng)有 BMP 格式的圖片,則可以使用我們提供的 BmpToMif 這個(gè)軟件,從現有的 BMP 格式 圖片生成 MIF 文件。其使用非常簡(jiǎn)單,注意要適當調整原圖片的大小,這可以通過(guò)各種圖形編輯軟件修改,如 Windows 自帶的畫(huà)圖程序、Photoshop 等。 BmpToMif 軟件的功能有: 將 bmp 圖片轉為 mif 文件:將黑白圖片轉換為單色 mif 文件;將彩色圖片轉換為三色 mif 文件。 將二進(jìn)制文件轉為 mif 文件,如將中英文點(diǎn)陣字庫轉換為 mif 文件。 1.2 圖片變成MIF文件的方法 我們需要用到Img2Lcd軟件,此軟件可自行下載,無(wú)需安裝即可使用。 打開(kāi)軟件后,點(diǎn)擊右上角“打開(kāi)”按鈕,選擇需要轉化的圖片,在右側輸出數據類(lèi)型下選擇“c語(yǔ)言數組”,掃描模式為“水平掃描”,輸出灰度“256色”,最大寬度和高度應大于圖片的大小,由于我們示例的圖片為120*60,最大寬度和高度我們選擇240*240。其他選項可以不用管。 之后點(diǎn)擊“保存”,文件名為“1.c”,在點(diǎn)擊下方保存。 用gvim打開(kāi)生成的文件,箭頭處為數據個(gè)數,由于圖片是120*60的,輸出灰度為256色,所以就有7200個(gè)數據。 第一行是不需要的,先刪除掉,然后進(jìn)入命令模式,輸入“/,”,然后回車(chē),就可以選中所有的逗號。 將光標置于第一個(gè)字符,在命令模式下一次輸入“q、a”,調用gvim的錄制功能,然后輸入“n”跳轉到第一個(gè)逗號,進(jìn)入編輯模式,將光標置于第一個(gè)逗號的右側,然后回車(chē),接著(zhù)摁4下退格鍵刪除多余的空格,在進(jìn)入命令模式,輸入“q”結束錄制。 在命令模式下輸入“7198@a”,表示重復之前的錄制7198次,然后回車(chē)。 然后我們要刪除多余的間隔,返回到第一個(gè)字符,在命令模式下輸入“qa”進(jìn)入錄制,然后向下移動(dòng)16格,剛好到?jīng)]有數據的一行,輸入“dd”進(jìn)行刪除,然后“q”退出錄制。在命令模式下輸入“954@a”,即可完成刪除。 刪除掉中間的OX 在命令模式下輸入“:%s/0X/: /”回車(chē)將“0X”替換為冒號,輸入“:%s/,/; /”回車(chē)將“,”替換為“;”。 打開(kāi)一個(gè)空白gvim,輸入1,進(jìn)入命令模式選中,輸入“yy7199p”將1復制7199份,將光標至于第一個(gè)1處,進(jìn)入命令模式,輸入“:leti=1|g/1/s//\=i/|let i=i+1”讓其遞增。 選中1~7199,然后Ctrl+Q,進(jìn)行列操作,然后Ctrl+c復制,然后回到1.c文件中,命令模式下,將光標置于第二行最前,然后Ctrl+v粘貼。 將第一個(gè)數據的地址標為0,然后在最上方和結尾添加如下圖所示內容。 13、然后將“1.c”文件保存為“1.mif”格式的。 2 設計目標 通過(guò)VGA連接線(xiàn),將顯示器和教學(xué)板的VGA接口相連。連接示意圖如下。 然后FPGA產(chǎn)生640*480分辨率(使用上表中的第一種分辨率),讓顯示器產(chǎn)生顯示一幅圖像。提示:顯示器一般都會(huì )自適應功能,無(wú)須設置就能識別不同分辨率的圖像。 圖像的內容是:在屏幕的中央顯示一個(gè)明德?lián)P的LOGO。明德?lián)PLOGO的大小是120*60像素。除了圖片之外的顯示區域,則顯示白色。上板效果圖如下圖所示(顯示器不同,顯示效果也會(huì )有差別,請注意)。 rom1是一個(gè)寬度為16位,深度為8192的ROM,該ROM保存了明德?lián)P的LOGO圖片,其排列方式如下。 該ROM按從左往右,由上往下的順序保存了LOGO圖像每個(gè)像素的值。圖中的(y,x)表示的是第y行第x列的像素值。例如,地址0保存的是第1行第1列的像素值,地址1保存的是第1行第2列的像素值,地址119保存的是第1行第120列的像素值。接下來(lái),地址120保存的是第2行第1列的像素值,以此類(lèi)推,地址6599保存的是第55行120列的像素值。而大于6599的地址,保存的值是0,沒(méi)有意義的數字。 ROM的每一個(gè)像素,按RGB565的方式保存,也就是[15:11]表示紅基色,[10:5]表示綠基色,[4:0]表示藍基色。 3 設計實(shí)現3.1 頂層接口 新建目錄:D:\mdy_book\picture_new_borad。在該目錄中,新建一個(gè)名為picture_new_borad.v的文件,并用GVIM打開(kāi),開(kāi)始編寫(xiě)代碼。 我們要實(shí)現的功能,概括起來(lái)就是FPGA產(chǎn)生VGA時(shí)序,即控制VGA_R4~R0、VGA_G5~G0、VGA_B4~B0、VGA_HSYNC和VGA_VSYNC,讓顯示器顯示紅色。其中,VGA_HSYNC和VGA_VSYNC,FPGA可根據時(shí)序產(chǎn)生高低電平。而顏色數據,由于是固定的紅色,FPGA也能自己產(chǎn)生,不需要外部輸入圖像的數據。那么我們的FPGA工程,可以定義輸出信號hys表示行同步,用輸出信號vys表示場(chǎng)同步,定義一個(gè)16位的信號lcd_rgb,其中lcd_rgb[15:11]表示VGA_R4~0,、lcd_rgb[10:5]表示VGA_G5~0,、lcd_rgb[4:0]表示VGA_B4~0。 我們還需要時(shí)鐘信號和復位信號來(lái)進(jìn)行工程控制。 綜上所述,我們這個(gè)工程需要五個(gè)信號,時(shí)鐘clk,復位rst_n,場(chǎng)同步信號vys、行同步信號hys和RGB輸出信號lcd_rgb。 將module的名稱(chēng)定義為picture_new_borad。并且我們已經(jīng)知道該模塊有五個(gè)信號:clk、rst_n、lcd_hs、lcd_vs和lcd_rgb。為此,代碼如下: 其中clk、rst_n是輸入信號,lcd_hs、lcd_vs和lcd_rgb是輸出信號,其中clk、rst_n、lcd_hs、lcd_vs的值是0或者1,一根線(xiàn)即可,lcd_rgb為16位位寬的,根據這些信息,我們補充輸入輸出端口定義。代碼如下: 3.2 架構設計 需要注意的是,輸入進(jìn)來(lái)的時(shí)鐘clk是50MHz,而從分辨率參數表可知道,行單位的基準時(shí)鐘是25MHz。為此我們需要根據50MHz來(lái)產(chǎn)生一個(gè)25 MHz的時(shí)鐘,然后再用于產(chǎn)生VGA時(shí)序。 為了得到這個(gè)25M時(shí)鐘,我們需要一個(gè)PLL。PLL可以認為是FPGA內的一個(gè)硬核,它的功能是根據輸入的時(shí)鐘,產(chǎn)生一個(gè)或多個(gè)倍頻和分頻后的輸出時(shí)鐘,同時(shí)可以調整這些輸出時(shí)鐘的相位、占空比等。 例如,輸入進(jìn)來(lái)是50M時(shí)鐘,如果我需要一個(gè)100M時(shí)鐘,那么從邏輯上、代碼上是不可能產(chǎn)生的,我們就必須用到PLL來(lái)產(chǎn)生了。 整個(gè)工程的結構圖如下。 PLL的生成方式過(guò)程,請看本案例的綜合工程和上板一節的內容。 3.3VGA驅動(dòng)模塊設計3.3.1接口信號 在目錄:D:\mdy_book\ picture_new_borad中,建立一個(gè)rectangle.v文件,并用GVIM打開(kāi),開(kāi)始編寫(xiě)代碼。 我們新建一個(gè)GVIM文件,并且將文件保存為vga_driver.v。 我們先分析功能。要控制顯示器,讓其產(chǎn)生紅色,也就是讓FPGA控制VGA_R0~4、VGA_G0~5、VGA_B0~4、VGA_VSYNC和VGA_HSYNC信號。那么VGA驅動(dòng)模塊,可以定義輸出信號hys表示行同步,用輸出信號vys表示場(chǎng)同步,定義一個(gè)16位的信號lcd_rgb,其中lcd_rgb[15:11]表示VGA_R4~0,、lcd_rgb[10:5]表示VGA_G5~0,、lcd_rgb[4:0]表示VGA_B4~0。 同時(shí)該模塊的工作時(shí)鐘為25M,同時(shí)需要一個(gè)復位信號。 綜上所述,我們這個(gè)模塊需要五個(gè)信號,25M時(shí)鐘clk,復位rst_n,場(chǎng)同步信號vys、行同步信號hys和RGB輸出信號lcd_rgb。 將module的名稱(chēng)定義為vga_driver。并且我們已經(jīng)知道該模塊有五個(gè)信號:clk、rst_n、hys、vys和lcd_rgb。為此,代碼如下: 其中clk、rst_n是輸入信號,hys、vys和lcd_rgb是輸出信號,其中clk、rst_n、hys、vys的值是0或者1,一根線(xiàn)即可,lcd_rgb為16位位寬的,根據這些信息,我們補充輸入輸出端口定義。代碼如下: 3.3.2 信號設計 我們先設計場(chǎng)同步信號hys,VGA時(shí)序中的場(chǎng)同步信號,其時(shí)序圖如下: hys就是一個(gè)周期性地高低變化的脈沖。我們使用的是下表中的第一種分辨率,也就是同步脈沖a的時(shí)間是96個(gè)時(shí)鐘周期,而顯示后沿b是48個(gè)時(shí)鐘周期,顯示時(shí)序c是640個(gè)時(shí)鐘周期,顯示前沿是16個(gè)時(shí)鐘周期,一共是800個(gè)時(shí)鐘周期。 將時(shí)間信號填入圖中,更新后的時(shí)序圖如下: 很顯然,我們需要1個(gè)計數器來(lái)產(chǎn)生這個(gè)時(shí)序,我們將該計數器命名為h_cnt。由于hys是不停地產(chǎn)生的,那么h_cnt就是不停地計數,每個(gè)時(shí)鐘都要計數器,所以認為該計數器的加1條件為“1”,可寫(xiě)成:assign add_h_cnt = 1。從上圖可知,該計數器的周期是800。綜上所述,該計數器的代碼如下: 有了計數器h_cnt,那么hys信號就有了對齊的對象。從時(shí)序圖可以發(fā)現, hys有兩個(gè)變化點(diǎn),一個(gè)是h_cnt數到96個(gè)時(shí),由0變1;另一個(gè)是當h_cnt數到800個(gè)時(shí),由1變0。所以,場(chǎng)同步信號的代碼如下: 接下來(lái)設計vys信號。該信號的時(shí)序圖如下所示。 vys就是一個(gè)周期性地高低變化的脈沖。我們使用的是表中的第一種分辨率,查詢(xún)表可知,同步脈沖a的時(shí)間是2行的時(shí)間,而顯示后沿b是33行,顯示時(shí)序c是480行,顯示前沿是10行,一共是525行。其中,一“行”結束,也就是h_cnt數完了。 將時(shí)間信號填入圖中,更新后的時(shí)序圖如下: 很顯然,我們還需要1個(gè)計數器來(lái)產(chǎn)生這個(gè)時(shí)序,我們將該計數器命名為v_cnt。該計數器是用來(lái)數有多少行的,所以加1條件就是一行結束,即end_h_cnt,可寫(xiě)成:assignadd_v_cnt = end_h_cnt。從上圖可知,該計數器的周期是525。綜上所述,該計數器的代碼如下: 有了計數器v_cnt,那么vys信號就有了對齊的對象。從時(shí)序圖可以發(fā)現, vys有兩個(gè)變化點(diǎn),一個(gè)是v_cnt數到2個(gè)時(shí),由0變1;另一個(gè)是當h_cnt數到525個(gè)時(shí),由1變0。所以,場(chǎng)同步信號的代碼如下: 最后我們還有一個(gè)信號需要設計,那就是lcd_rgb信號。 我們在顯示器中一共要分成兩種方式顯示。如上圖中,以屏幕中點(diǎn)為中心,左右60列、上27個(gè)行、下28行顯示的是圖像數據;而在其他區域直接顯示白色,也就是lcd_rgb等于16’b11111_111111_11111。還要注意的是,在非顯示區域,lcd_rgb的值要為0,才能正確顯示。我們現在要仔細區分,在什么時(shí)候分別輸出上面的值。 |