基于BASYS 3 FPGA開發(fā)板及Verilog、Matlab、Photoshop實(shí)現(xiàn)VGA接顯示屏的動(dòng)圖顯示

項(xiàng)目成果
見投稿視頻
項(xiàng)目設(shè)計(jì)
注:由于開發(fā)板存儲(chǔ)量較小,采用純黑白顯示以減小數(shù)據(jù)量,實(shí)際上彩色顯示設(shè)計(jì)方式是完全相同的。
使用的軟件
Vivado 2019.1
VSCode(僅用作輔助Vivado編寫Verilog程序)
MATLAB R2022a (9.12.0.1884302) - Academic Use
Adobe Photoshop CC 2018
使用的硬件
BASYS 3 FPGA開發(fā)板
COMPAQ 顯示器
VGA-VGA轉(zhuǎn)接線等
用戶端設(shè)計(jì)
利用VGA-VGA線將BASYS3 FPGA開發(fā)板接入顯示屏,并在顯示屏上顯示動(dòng)態(tài)gif(取自《Bad Apple!!》中開頭的博麗靈夢(mèng),共5幀,幀率為7fps)

開發(fā)板最右側(cè)的開關(guān)為電源開關(guān),可以實(shí)現(xiàn)顯示屏開啟并顯示動(dòng)態(tài)圖片,或顯示屏關(guān)閉并動(dòng)態(tài)圖片復(fù)位至初始的操作,其上方配有電源指示燈
最左側(cè)自左向右五盞LED燈為流水燈,分別順序?qū)?yīng)gif的五幀,表示當(dāng)前顯示的是第幾幀
中間按鈕為pause鍵,按下可以實(shí)現(xiàn)暫停功能,即停在當(dāng)前幀,放手即繼續(xù)
上側(cè)按鈕為reset鍵,按下可以實(shí)現(xiàn)回到第一幀,并停在第一幀,放手即繼續(xù)

程序設(shè)計(jì)
采用三模塊并行設(shè)計(jì),即Display(vga顯示模塊)、Player(播放器模塊)與Storage(數(shù)據(jù)調(diào)用模塊)

其中的Storage模塊中調(diào)用了開發(fā)板的BRAM(Block RAM)IP核,用于存儲(chǔ)圖片數(shù)據(jù)
BRAM IP核數(shù)據(jù)來自.coe初始化文件,此文件利用Matlab程序?qū)?bmp(24位圖)圖像解碼處理后生成
VGA顯示模塊(頂層模塊):vga_bad_apple_display.v
設(shè)置好VGA顯示標(biāo)準(zhǔn)的行掃描與列掃描參數(shù)、播放相關(guān)參數(shù),方便后續(xù)調(diào)用
接入開發(fā)板系統(tǒng)時(shí)鐘100MHz,用作各模塊進(jìn)一步分頻處理使用
接入switch開關(guān),用于開閉電源
接入pause和reset按鈕,將其接入播放器模塊,用于暫停與復(fù)位(其中reset信號(hào)將開關(guān)與按鈕的信號(hào)結(jié)合后再接入Player模塊,因?yàn)閮烧叨伎梢蕴峁?fù)位信號(hào))
采用640*480、12位RGB的VGA顯示標(biāo)準(zhǔn),使用25MHz時(shí)鐘進(jìn)行行、場(chǎng)掃描,并輸出行、場(chǎng)同步信號(hào),以及RGB數(shù)據(jù)
將行、場(chǎng)掃描的位置(即當(dāng)前像素位置)傳給數(shù)據(jù)調(diào)用模塊,請(qǐng)求色彩信息
獲取數(shù)據(jù)調(diào)用模塊返回的的色彩信息,用于輸出至VGA接口的RGB數(shù)據(jù)
將switch、reset及幀位置、幀速度反映在六盞LED燈
播放器模塊:vga_bad_apple_player.v
設(shè)置好動(dòng)圖顯示的幀率、幀數(shù)相關(guān)參數(shù),方便后續(xù)調(diào)用
從頂層模塊接入100MHz時(shí)鐘信號(hào),用作生成幀率(幀切換速度)所需時(shí)鐘
從頂層模塊接入pause信號(hào),控制幀計(jì)數(shù)(切換至第幾幀)暫停與繼續(xù)
從頂層模塊接入處理后的reset信號(hào),控制幀復(fù)位
向數(shù)據(jù)調(diào)用模塊輸出幀編號(hào),確定VGA顯示模塊調(diào)用的是數(shù)據(jù)調(diào)用模塊中哪一幀的數(shù)據(jù)
數(shù)據(jù)調(diào)用模塊:vga_bad_apple_storage.v
設(shè)置好動(dòng)圖顯示的長、寬及幀數(shù)參數(shù),方便后續(xù)調(diào)用
從VGA顯示模塊和播放器模塊接入需要的像素位置、幀編號(hào)信息,生成所需要的數(shù)據(jù)地址
從頂層模塊接入100MHz時(shí)鐘信號(hào),再接入BRAM
根據(jù)數(shù)據(jù)地址從BRAM中調(diào)取數(shù)據(jù),并確定調(diào)出的數(shù)據(jù)中的第幾位為所需要的對(duì)應(yīng)的色彩信息
向VGA顯示模塊返回當(dāng)前需要顯示的色彩信息
IP核BRAM模塊:bad_apple_bram.xci
利用IP核生成一個(gè)用于存儲(chǔ)數(shù)據(jù)的BRAM模塊,位寬為2bit,位深為768000bit(是由于BRAM的位深至多為1048576bit,不然最方便調(diào)用的方式為位寬為1bit且位深為1536000bit)
將模塊設(shè)置為always enable(永遠(yuǎn)工作)
利用.coe文件初始化此存儲(chǔ)模塊(本質(zhì)即存入圖片數(shù)據(jù))
從數(shù)據(jù)調(diào)用模塊接入100MHz系統(tǒng)時(shí)鐘
將wea端(讀寫使能端,當(dāng)置0則只讀,當(dāng)置1則只寫)置0
將dina端(數(shù)據(jù)寫入端)置0
從數(shù)據(jù)調(diào)用模塊接入所需數(shù)據(jù)地址
向數(shù)據(jù)調(diào)用模塊傳回對(duì)應(yīng)的位寬為2bit的數(shù)據(jù)
其他設(shè)計(jì)
利用Matlab解碼.bmp(24位圖片文件)生成.coe(二進(jìn)制數(shù)據(jù)文件)用于BRAM的初始化
參考了CSDN上的代碼,程序讀取了.bmp文件中每一像素點(diǎn)的24位RGB值,削去每個(gè)R、G、B值的后四位,生成每個(gè)像素點(diǎn)的12位RGB值,并以16進(jìn)制形式打印在.coe文件中,同時(shí)顯示所打印的圖片
由于本項(xiàng)目只需要像素點(diǎn)的黑白值,且要求位寬為2位,故將代碼做部分改動(dòng),使得若像素點(diǎn)RGB值為0,則打印0,否則打印1,并且每逢偶數(shù)個(gè)像素點(diǎn)打印一個(gè)逗號(hào)并換行,并在最后生成一個(gè)分號(hào)而不是逗號(hào),作為終止標(biāo)志
由于項(xiàng)目幀數(shù)較少,采用5幀分別打印在5個(gè)文件,再將5個(gè)文件合并這一方式生成最終的.coe文件
利用Photoshop將.gif文件轉(zhuǎn)化為只含純黑白像素點(diǎn)的各幀的.bmp(24位圖)文件
直接使用Photoshop打開.gif文件,發(fā)現(xiàn)各幀自動(dòng)按順序分離在各圖層,此時(shí)的各幀依然有灰色像素點(diǎn)

選取所需要的幀圖層重新命名,用于區(qū)分其他不需要的幀圖層
將所需處理的幀圖層可視,其他不可視
在左上方“圖像”菜單中選擇“調(diào)整”中的“閾值”
調(diào)整“閾值”的數(shù)值,至看起來輪廓較為流暢,并記下這一數(shù)值
確定后,放大圖片至像素點(diǎn)清晰可見,可以看到只有黑像素和白像素

對(duì)于所有所需幀圖層進(jìn)行相應(yīng)操作后,點(diǎn)擊左上角的“文件”菜單,在其中找到“導(dǎo)出”,選擇”將圖層導(dǎo)出至文件“,并選擇.bmp24位圖格式與需要導(dǎo)出的文件夾
Photoshop會(huì)自行進(jìn)行操作,完畢后會(huì)提示導(dǎo)出完畢

項(xiàng)目思路整理與思考
注:后文中可能會(huì)出現(xiàn)以下人物
Starskyer:我的室友,同樣選擇榮譽(yù)課程的、一同想好嘗試做動(dòng)圖顯示的好友
Yosame:我的好友,是一位計(jì)算機(jī)學(xué)院的大佬
關(guān)于最終呈現(xiàn)方式的不斷改變
最開始嘗試完成的是彩色圖片,并且希望完成的是彩色的動(dòng)圖顯示
但是將一幀圖片以簡單粗暴的方式導(dǎo)入進(jìn)代碼中后發(fā)現(xiàn)占用了99%的LUT空間,并且完成從零開始綜合、實(shí)現(xiàn)、生成比特流總共要花掉二十多分鐘的時(shí)間
在這之后,思考了各種各樣的辦法:
關(guān)于直接將數(shù)據(jù)粘貼在代碼中
最初的那張無聲鈴鹿的《うまぴょい伝説》使用了13萬行代碼,跑了20分鐘終于成功顯示了出來

在后來的《Bad Apple》中,一開始也嘗試使用30萬行代碼作為顯示數(shù)據(jù),但是無論是在我自己的電腦上還是在Starskyer的電腦上都跑不出來(跑了一個(gè)半小時(shí)還卡在第一步)
所以這個(gè)方案:駁回
關(guān)于開發(fā)板的BRAM IP核作為讀寫過渡存儲(chǔ)器
由于“將數(shù)據(jù)直接粘貼進(jìn)代碼”這一方式不可行,因此我們只能讀取BRAM IP核,而不能寫入,這就導(dǎo)致BRAM變成了一個(gè)ROM,因此只能把它初始化
關(guān)于開發(fā)板的Flash
這一方面確實(shí)有相關(guān)的資料,但是由于過程非常復(fù)雜,沒有探索下去
關(guān)于開發(fā)板的USB接口
當(dāng)跳線帽決定開發(fā)板的編程模式為“USB”時(shí),USB存儲(chǔ)設(shè)備才會(huì)發(fā)揮作用,并且只不過是找到其中的編程文件,并對(duì)開發(fā)板進(jìn)行編程
當(dāng)跳線帽不在“USB”模式時(shí),USB存儲(chǔ)設(shè)備只能作為一個(gè)PS/2接口使用,即只能識(shí)別鍵盤和鼠標(biāo)
因此如果是想通過USB口外接存儲(chǔ)設(shè)備供開發(fā)板提取數(shù)據(jù)的話,應(yīng)該是辦不到的
關(guān)于Yosame提到的擬合方法
Yosame提到說對(duì)于動(dòng)圖的顯示可以采用每個(gè)像素點(diǎn)擬合一個(gè)函數(shù)的形式,減小數(shù)據(jù)存儲(chǔ)量
我非常喜歡這個(gè)辦法,因?yàn)檫@相當(dāng)于是只需要一幀的數(shù)據(jù),而將后續(xù)的數(shù)據(jù)所需的存儲(chǔ)量以擬合的運(yùn)算量這一形式呈現(xiàn)在函數(shù)之中了,這正是一種很棒的減小數(shù)據(jù)存儲(chǔ)量的辦法
但是由于時(shí)間有限,一方面我們毫無頭緒如何去做這個(gè)事,另一方面我們也沒有時(shí)間或者算力去完成這一工作
最終選擇:開發(fā)板的BRAM IP核作為只讀存儲(chǔ)器
我們前面提到將BRAM作為讀寫過渡存儲(chǔ)器難以實(shí)現(xiàn),但是我們?nèi)绻纱鄬⑵渥兂梢粋€(gè)ROM,然后減小一定的數(shù)據(jù)量,是可以實(shí)現(xiàn)弱一些的功能的
可以說是無奈之舉了吧
本BRAM可裝載的最大數(shù)據(jù)量是1800Kbits,而一張640480的圖片便包含307.2Kbits,因此要么使用小彩圖,要么使用大的黑白圖,而且都是在幀數(shù)很少的情況下,因此Starskyer選擇了160120的4幀彩色動(dòng)圖,而我選擇了640*480的5幀黑白動(dòng)圖
使用BRAM IP核的優(yōu)點(diǎn)在于,程序跑的非???,實(shí)際數(shù)據(jù)量卻和直接粘貼入代碼相當(dāng),一般來說大約2~3分鐘就可以生成好比特流了
關(guān)于代碼的不斷改進(jìn)
單模塊
最最開始使用的是單模塊設(shè)計(jì),原因是是從東大實(shí)驗(yàn)書上借鑒下來的
Vivado本身也會(huì)提醒你“單模塊設(shè)計(jì)不利于層次結(jié)構(gòu)設(shè)計(jì)”,另一方面確實(shí)所有東西混在一起很難看也很混亂
最混亂的還屬時(shí)序的always塊,在靜態(tài)圖片顯示中還體現(xiàn)不出來,但是一旦到了動(dòng)圖顯示,各種時(shí)鐘交雜,會(huì)非?;靵y
三模塊初設(shè)計(jì)
其實(shí)最初也沒太想清楚,姑且使用了串行結(jié)構(gòu)來實(shí)現(xiàn)

到項(xiàng)目收工的前一天晚上,我的代碼還是基于這張圖的,那天晚上結(jié)果已經(jīng)出來了,和最后的結(jié)果只是差幾個(gè)組合電路設(shè)計(jì)的不同
問題最大的是Player模塊,其實(shí)當(dāng)時(shí)就有點(diǎn)感覺奇怪了,怎么這么多線接進(jìn)去又原封不動(dòng)接出來,這也太浪費(fèi)資源了
項(xiàng)目收工當(dāng)天早上,我開始給我的代碼系統(tǒng)性地加注釋,在工程中慢慢把各種沒用的輸入輸出接口給刪掉了
三模塊最終設(shè)計(jì)
刪完以后的結(jié)構(gòu)長這樣

說實(shí)話非常清爽,本來的Player模塊雜七雜八要接了靠十根線,最后只剩下三根線,reset這根線還是我最后在檢查組合電路設(shè)計(jì)的時(shí)候加上去的
一開始我是沒有接入reset按鈕的,但是后來我發(fā)現(xiàn)我把switch和reset完全搞混了,因此加入了reset按鍵,一個(gè)負(fù)責(zé)控制總開關(guān),一個(gè)負(fù)責(zé)控制播放器的開關(guān)
關(guān)于仿真與測(cè)試
說實(shí)話,并不是很想用Simulation文件來進(jìn)行仿真與測(cè)試,因?yàn)榉浅?fù)雜并且很難看到直觀結(jié)果
因此更多是使用直接改程序的方式來進(jìn)行測(cè)試
實(shí)際使用的比如說是先不輸入數(shù)據(jù),而是把模塊的數(shù)據(jù)接入一個(gè)簡單的選擇器,設(shè)置第一幀到第五幀分別是從小到大的方塊等等,這樣很直觀,也幫助我找出了問題并改正
再有就是利用LED燈的閃爍來查看,這一設(shè)計(jì)幫助我找出了兩次幀編號(hào)變量設(shè)置的問題(一次是將bit數(shù)當(dāng)成總數(shù)使用了,一次是使用了1位變量接周期為5的計(jì)數(shù)器了)
關(guān)于圖片解碼
我一開始只是看到在粘貼過來的matlab程序中詳細(xì)寫了各種注釋,了解到是取用的24位RGB中各自的前5、6、5位作為16位RGB,或是前4、4、4位作為12位RGB,并沒有深入思考
后來在與Starskyer的討論之中恍然大悟,原來取前幾位是做除法的意思,那就沒問題了
但是我在顯示這塊依然有兩個(gè)完全不明白的點(diǎn)
為什么電腦上顯示的640*480的清晰程度和顯示屏顯示出來的感覺完全不一樣?
有沒有一種可能,是顯示器更大了?這個(gè)我感覺不太可能,因?yàn)檫B顯示出來的圖片本身都有些邊緣崩壞
因此我強(qiáng)烈懷疑是顯示器自己對(duì)于像素信號(hào)進(jìn)行了處理,但是這塊我的了解并不多
為什么使用一模一樣的程序解碼出來的圖片在顯示屏上顯示出來只有第一幀的邊緣時(shí)崩壞的,而后面4幀邊緣都是很順滑的


這方面我進(jìn)行了一定的探索,包括重新用Photoshop對(duì)圖片進(jìn)行處理,以及改變那特定第一幀的選取,還有在Matlab代碼里改變黑白閾值這些嘗試,都沒能解決這一問題
因此承上一個(gè)我問題,我估計(jì)大概率還是這個(gè)顯示屏自行對(duì)信號(hào)進(jìn)行了處理,然而這點(diǎn)也是存疑的,因?yàn)闉槭裁粗挥械谝粠幚韷牧?,后面都是好的?/p>
總體而言對(duì)于這個(gè)問題還是毫無想法
項(xiàng)目感想
是真的很有意思,把一張自己想要顯示的圖顯示到屏幕上,甚至還可以是動(dòng)圖,確實(shí)是很有成就感的事情
在完成自己的想法的過程中,在網(wǎng)上查了各種資料,與好友、學(xué)長、助教老師等等身邊的人進(jìn)行了各種討論,也都非常有益,學(xué)到了非常多東西
還有我爸非常支持我,直接讓我從家里搬來臺(tái)式機(jī)的顯示屏,并且提供了VGA-VGA線和電源線,直接解決了我們寢室的顯示屏缺位問題,可以說是我們順利做項(xiàng)目的先決條件,在做的過程中也常常同我討論各種問題,也都是很有益的過程
當(dāng)迷霧漸漸撥開,最后發(fā)現(xiàn)其實(shí)一切根本沒想象中的那么難,我整個(gè)項(xiàng)目總的代碼量不過230行左右,但是可以說很搞腦子,而且在探索軟件、硬件方面需要下很多功夫,不像別的同學(xué)設(shè)計(jì)游戲、設(shè)計(jì)交通燈、設(shè)計(jì)計(jì)時(shí)器,可能更多是在設(shè)計(jì)方面需要下功夫,側(cè)重點(diǎn)不同
在代碼編寫的過程中,也慢慢形成了自己的代碼風(fēng)格,一方面體現(xiàn)了一些自己的個(gè)性,另一方面也讓自己看著很愉悅
這只是榮譽(yù)課程最終項(xiàng)目的先導(dǎo),最后的壓軸:RISC CPU設(shè)計(jì)還在等著我們,感覺可以拿出干勁好好研究研究了!