VGA显示图片
1.1 FPGA存储器
目前大多数FPGA都有内嵌的块RAM(Block RAM),可以将其灵活地配置成单端口RAM(DPRAM,Single Port RAM)、双端口RAM(DPRAM,Double Ports RAM)、伪双端口RAM(PseudoDPRAM)、CAM(Content AddressableMemory)、FIFO等常用存储结构。FPGA中其实并没有专用的ROM硬件资源,实现ROM的思路是对RAM赋予初值,并保持该初值。
Altera 的器件内部提供了各种存储器模块(RAM、ROM 或双口 RAM),可以在设计中使用 MegaWizardPlug-In Manager,执行【Tools】|【MegaWizard Plug-InManager】菜单命令来创建所需要 的存储器模块。也可以使用 Altera 提供的宏功能模块 LPM_ROM 来创建存储器模块。每个 ROM 模块有 CLOCK(时钟)、address(地址)这两个输入信号和一个 q(值)输出信号。 ROM 在每个时钟上升沿取出由地址信号所指定的存储单元中的值并输出。ROM 内的值通过加载 MIF (Memory Initialization File,存储器初始化文件)文件来实现。
当在设计中使用了器件内部的存储器模块时,需要对存储器模块进行初始化。在Quartus 中, 可以使用两种格式的存储器初始化文件:Intel Hex 格式(.hex)或Altera 存储器初始化格式(.mif)的文件。 MIF 文件是 Altera 存储器类器件初始化的专用文件格式,文件内容为地址与值的对应表,规定了 存储器单元的初始值。
如果将要存储于 ROM 中的内容比较少或者很有规律,可以执行【File】|【New…】菜单命令,创 建 MIF 文件并编辑其内容。 如果已经有 BMP 格式的图片,则可以使用我们提供的 BmpToMif 这个软件,从现有的 BMP 格式 图片生成 MIF 文件。其使用非常简单,注意要适当调整原图片的大小,这可以通过各种图形编辑软件修改,如 Windows 自带的画图程序、Photoshop 等。 BmpToMif 软件的功能有: 将 bmp 图片转为 mif 文件:将黑白图片转换为单色 mif 文件;将彩色图片转换为三色 mif 文件。
将二进制文件转为 mif 文件,如将中英文点阵字库转换为 mif 文件。
1.2 图片变成MIF文件的方法
我们需要用到Img2Lcd软件,此软件可自行下载,无需安装即可使用。
![235a7af3a211bd4637ac36d3139ce850.png](https://i-blog.csdnimg.cn/blog_migrate/cb97f1655ff90c9c521b2654b7a77a1e.jpeg)
打开软件后,点击右上角“打开”按钮,选择需要转化的图片,在右侧输出数据类型下选择“c语言数组”,扫描模式为“水平扫描”,输出灰度“256色”,最大宽度和高度应大于图片的大小,由于我们示例的图片为120*60,最大宽度和高度我们选择240*240。其他选项可以不用管。
![03e80ea0f8c478f805cab144c878ce60.png](https://i-blog.csdnimg.cn/blog_migrate/47e7c129ac2f5ead55e3deb266f3198d.jpeg)
之后点击“保存”,文件名为“1.c”,在点击下方保存。
![c5632cfdbfdbc5231987395f94f086ce.png](https://i-blog.csdnimg.cn/blog_migrate/909ffd5d9ce014625aea23c9dd0d82d1.jpeg)
用gvim打开生成的文件,箭头处为数据个数,由于图片是120*60的,输出灰度为256色,所以就有7200个数据。
![c5b390f8ffb402f2e3caced4b04574ab.png](https://i-blog.csdnimg.cn/blog_migrate/07bcfc92f2bc7635ea92f1206145537c.jpeg)
第一行是不需要的,先删除掉,然后进入命令模式,输入“/,”,然后回车,就可以选中所有的逗号。
![b9d01466f5e0e40ec316a1979d548163.png](https://i-blog.csdnimg.cn/blog_migrate/382c152170570fa0d162bfcbbca5c3eb.jpeg)
将光标置于第一个字符,在命令模式下一次输入“q、a”,调用gvim的录制功能,然后输入“n”跳转到第一个逗号,进入编辑模式,将光标置于第一个逗号的右侧,然后回车,接着摁4下退格键删除多余的空格,在进入命令模式,输入“q”结束录制。
![037f7bdc84553119dc51a408d6be86ba.png](https://i-blog.csdnimg.cn/blog_migrate/77782b47fcfe77b0231329d3498216fa.jpeg)
在命令模式下输入“7198@a”,表示重复之前的录制7198次,然后回车。
![d3252e2cf45e84e6e8eec5755bca861f.png](https://i-blog.csdnimg.cn/blog_migrate/35d849f6ecade5b42150cd8cb1e93fdd.jpeg)
然后我们要删除多余的间隔,返回到第一个字符,在命令模式下输入“qa”进入录制,然后向下移动16格,刚好到没有数据的一行,输入“dd”进行删除,然后“q”退出录制。在命令模式下输入“954@a”,即可完成删除。
![bb91d5517183309269d5c242882595ba.png](https://i-blog.csdnimg.cn/blog_migrate/d847f5adfa517d2f19e0270428b18f0a.jpeg)
删除掉中间的OX
![3e3ed26d7187990a2632e7283fba0624.png](https://i-blog.csdnimg.cn/blog_migrate/f5566ae040f8c193317207d1cf109427.jpeg)
在命令模式下输入“:%s/0X/: /”回车将“0X”替换为冒号,输入“:%s/,/; /”回车将“,”替换为“;”。
![55057f4679ebba20bf1783e7ac10cef4.png](https://i-blog.csdnimg.cn/blog_migrate/25b4352d439a1514aa96fc44108320c5.jpeg)
![f0685e419daa5ce3bc3f12e7e49f50d6.png](https://i-blog.csdnimg.cn/blog_migrate/1a34bfdb6ef12fc91731f9971b27944c.jpeg)
打开一个空白gvim,输入1,进入命令模式选中,输入“yy7199p”将1复制7199份,将光标至于第一个1处,进入命令模式,输入“:leti=1|g/1/s//=i/|let i=i+1”让其递增。
![f794227a0f0320cd2f095a62054e11d5.png](https://i-blog.csdnimg.cn/blog_migrate/70c265fb3cea663ab448538143eea94c.jpeg)
![b46c6ef5c4c3cfc68befa7b5ce6a651f.png](https://i-blog.csdnimg.cn/blog_migrate/b354ecdc07c0f3a31528d2eadad221b6.jpeg)
选中1~7199,然后Ctrl+Q,进行列操作,然后Ctrl+c复制,然后回到1.c文件中,命令模式下,将光标置于第二行最前,然后Ctrl+v粘贴。
![1777f5a7f06dbe582d33420fb5cbba98.png](https://i-blog.csdnimg.cn/blog_migrate/5937a6a972667db574aa89eaa1a518ce.jpeg)
将第一个数据的地址标为0,然后在最上方和结尾添加如下图所示内容。
![90b8cbc88c687f6a343a6741641d8ea9.png](https://i-blog.csdnimg.cn/blog_migrate/72e7d5c2e17797a7dbff2eb47ff18d7b.jpeg)
![724fe8f0e1fcdfebba3fc56c9362ea8d.png](https://i-blog.csdnimg.cn/blog_migrate/3b2cdea2baf6411165521cad86662ead.jpeg)
13、然后将“1.c”文件保存为“1.mif”格式的。
2 设计目标
通过VGA连接线,将显示器和教学板的VGA接口相连。连接示意图如下。
![0b7a8242a557642c67066dfa157271e7.png](https://i-blog.csdnimg.cn/blog_migrate/99b041cffb0d62d6478f9951d312f4a9.jpeg)
![17848c826cec8e3fe30bcca2a8e34dc8.png](https://i-blog.csdnimg.cn/blog_migrate/e194eb373450316cce7a52b001964af1.jpeg)
然后FPGA产生640*480分辨率(使用上表中的第一种分辨率),让显示器产生显示一幅图像。提示:显示器一般都会自适应功能,无须设置就能识别不同分辨率的图像。
图像的内容是:在屏幕的中央显示一个明德扬的LOGO。明德扬LOGO的大小是120*60像素。除了图片之外的显示区域,则显示白色。上板效果图如下图所示(显示器不同,显示效果也会有差别,请注意)。
![a30b5188db193a88a8bcec83ee6b097c.png](https://i-blog.csdnimg.cn/blog_migrate/bcf39f02339d8d15ae54f3f113f5f1ae.jpeg)
rom1是一个宽度为16位,深度为8192的ROM,该ROM保存了明德扬的LOGO图片,其排列方式如下。
![a5c5168416fec015b4ab15c24975d856.png](https://i-blog.csdnimg.cn/blog_migrate/573a2fa5f1adeb45ecf778e903f0b6d6.jpeg)
该ROM按从左往右,由上往下的顺序保存了LOGO图像每个像素的值。图中的(y,x)表示的是第y行第x列的像素值。例如,地址0保存的是第1行第1列的像素值,地址1保存的是第1行第2列的像素值,地址119保存的是第1行第120列的像素值。接下来,地址120保存的是第2行第1列的像素值,以此类推,地址6599保存的是第55行120列的像素值。而大于6599的地址,保存的值是0,没有意义的数字。
ROM的每一个像素,按RGB565的方式保存,也就是[15:11]表示红基色,[10:5]表示绿基色,[4:0]表示蓝基色。
3 设计实现
3.1 顶层接口
新建目录:D:mdy_bookpicture_new_borad。在该目录中,新建一个名为picture_new_borad.v的文件,并用GVIM打开,开始编写代码。
我们要实现的功能,概括起来就是FPGA产生VGA时序,即控制VGA_R4~R0、VGA_G5~G0、VGA_B4~B0、VGA_HSYNC和VGA_VSYNC,让显示器显示红色。其中,VGA_HSYNC和VGA_VSYNC,FPGA可根据时序产生高低电平。而颜色数据,由于是固定的红色,FPGA也能自己产生,不需要外部输入图像的数据。那么我们的FPGA工程,可以定义输出信号hys表示行同步,用输出信号vys表示场同步,定义一个16位的信号lcd_rgb,其中lcd_rgb[15:11]表示VGA_R4~0,、lcd_rgb[10:5]表示VGA_G5~0,、lcd_rgb[4:0]表示VGA_B4~0。
我们还需要时钟信号和复位信号来进行工程控制。
综上所述,我们这个工程需要五个信号,时钟clk,复位rst_n,场同步信号vys、行同步信号hys和RGB输出信号lcd_rgb。
![3da2025319631b383a948c63aad37122.png](https://i-blog.csdnimg.cn/blog_migrate/5d45d9ed7cea83df40e6dc65ff02b511.jpeg)
将module的名称定义为picture_new_borad。并且我们已经知道该模块有五个信号:clk、rst_n、lcd_hs、lcd_vs和lcd_rgb。为此,代码如下:
![00540eb230e039ab9c9cd91f24e105ba.png](https://i-blog.csdnimg.cn/blog_migrate/1fe067ce8805ab56fb944204bbd1d14d.jpeg)
其中clk、rst_n是输入信号,lcd_hs、lcd_vs和lcd_rgb是输出信号,其中clk、rst_n、lcd_hs、lcd_vs的值是0或者1,一根线即可,lcd_rgb为16位位宽的,根据这些信息,我们补充输入输出端口定义。代码如下:
![0df4dfa8bfa9a1ca78ad8d5dc3e05d0b.png](https://i-blog.csdnimg.cn/blog_migrate/ffcdfbf42b0bce608d0756342955d503.jpeg)
3.2 架构设计
需要注意的是,输入进来的时钟clk是50MHz,而从分辨率参数表可知道,行单位的基准时钟是25MHz。为此我们需要根据50MHz来产生一个25 MHz的时钟,然后再用于产生VGA时序。
为了得到这个25M时钟,我们需要一个PLL。PLL可以认为是FPGA内的一个硬核,它的功能是根据输入的时钟,产生一个或多个倍频和分频后的输出时钟,同时可以调整这些输出时钟的相位、占空比等。
例如,输入进来是50M时钟,如果我需要一个100M时钟,那么从逻辑上、代码上是不可能产生的,我们就必须用到PLL来产生了。
整个工程的结构图如下。
![8410c16f8176279589cbe34844d2fcf0.png](https://i-blog.csdnimg.cn/blog_migrate/e7dcdf21ae6a58a3fe678c4c9407589f.jpeg)
PLL的生成方式过程,请看本案例的综合工程和上板一节的内容。
3.3VGA驱动模块设计
3.3.1接口信号
在目录:D:mdy_book picture_new_borad中,建立一个rectangle.v文件,并用GVIM打开,开始编写代码。
我们新建一个GVIM文件,并且将文件保存为vga_driver.v。
我们先分析功能。要控制显示器,让其产生红色,也就是让FPGA控制VGA_R0~4、VGA_G0~5、VGA_B0~4、VGA_VSYNC和VGA_HSYNC信号。那么VGA驱动模块,可以定义输出信号hys表示行同步,用输出信号vys表示场同步,定义一个16位的信号lcd_rgb,其中lcd_rgb[15:11]表示VGA_R4~0,、lcd_rgb[10:5]表示VGA_G5~0,、lcd_rgb[4:0]表示VGA_B4~0。
同时该模块的工作时钟为25M,同时需要一个复位信号。
综上所述,我们这个模块需要五个信号,25M时钟clk,复位rst_n,场同步信号vys、行同步信号hys和RGB输出信号lcd_rgb。
将module的名称定义为vga_driver。并且我们已经知道该模块有五个信号:clk、rst_n、hys、vys和lcd_rgb。为此,代码如下:
![4ccb32a33288af7371e85704a875c327.png](https://i-blog.csdnimg.cn/blog_migrate/b3d230171001445d7f1f37791ed5ab2a.jpeg)
其中clk、rst_n是输入信号,hys、vys和lcd_rgb是输出信号,其中clk、rst_n、hys、vys的值是0或者1,一根线即可,lcd_rgb为16位位宽的,根据这些信息,我们补充输入输出端口定义。代码如下:
![f304cb3187ac7729cf4da3e2f12aab81.png](https://i-blog.csdnimg.cn/blog_migrate/fc83ebdb5fcb944264a76eae4ecf715f.jpeg)
3.3.2 信号设计
我们先设计场同步信号hys,VGA时序中的场同步信号,其时序图如下:
![6e658a686cf9456a02f50dc94047004e.png](https://i-blog.csdnimg.cn/blog_migrate/10d0aea71f36c05d5f2774bdbfd82aa6.jpeg)
hys就是一个周期性地高低变化的脉冲。我们使用的是下表中的第一种分辨率,也就是同步脉冲a的时间是96个时钟周期,而显示后沿b是48个时钟周期,显示时序c是640个时钟周期,显示前沿是16个时钟周期,一共是800个时钟周期。
![93d1d35e63c08009caa476b668f7ea92.png](https://i-blog.csdnimg.cn/blog_migrate/8d5243263dea8ce1caef43dfaa8f0221.jpeg)
将时间信号填入图中,更新后的时序图如下:
![4d336102c68569cac3146003b2560fdf.png](https://i-blog.csdnimg.cn/blog_migrate/12a26a04e105dc795578222872f97f8a.jpeg)
很显然,我们需要1个计数器来产生这个时序,我们将该计数器命名为h_cnt。由于hys是不停地产生的,那么h_cnt就是不停地计数,每个时钟都要计数器,所以认为该计数器的加1条件为“1”,可写成:assign add_h_cnt = 1。从上图可知,该计数器的周期是800。综上所述,该计数器的代码如下:
![731a2bed3750268388c0613e697a84e2.png](https://i-blog.csdnimg.cn/blog_migrate/4bad43fde0533b3fdf42c45f852d57a2.jpeg)
有了计数器h_cnt,那么hys信号就有了对齐的对象。从时序图可以发现, hys有两个变化点,一个是h_cnt数到96个时,由0变1;另一个是当h_cnt数到800个时,由1变0。所以,场同步信号的代码如下:
![7a76c6c256e5b0226f0aa568afc44477.png](https://i-blog.csdnimg.cn/blog_migrate/9b1a6e641eaa1e6fe5068cdbf2b240f1.jpeg)
接下来设计vys信号。该信号的时序图如下所示。
![6879930b11f98d71ee20adebfc12e2e9.png](https://i-blog.csdnimg.cn/blog_migrate/f27a6e35ebc218c4179693cc4503aae6.jpeg)
vys就是一个周期性地高低变化的脉冲。我们使用的是表中的第一种分辨率,查询表可知,同步脉冲a的时间是2行的时间,而显示后沿b是33行,显示时序c是480行,显示前沿是10行,一共是525行。其中,一“行”结束,也就是h_cnt数完了。
将时间信号填入图中,更新后的时序图如下:
![89d3112f464b26c2b080dafa9ce86a18.png](https://i-blog.csdnimg.cn/blog_migrate/e294ad5f98d610bd921d1790fba24854.jpeg)
很显然,我们还需要1个计数器来产生这个时序,我们将该计数器命名为v_cnt。该计数器是用来数有多少行的,所以加1条件就是一行结束,即end_h_cnt,可写成:assignadd_v_cnt = end_h_cnt。从上图可知,该计数器的周期是525。综上所述,该计数器的代码如下:
![43df9fa2368a7c0f00ae7eb5a8d588ee.png](https://i-blog.csdnimg.cn/blog_migrate/5cf7dd54d6e41d58a91a3609fad7e28e.jpeg)
有了计数器v_cnt,那么vys信号就有了对齐的对象。从时序图可以发现, vys有两个变化点,一个是v_cnt数到2个时,由0变1;另一个是当h_cnt数到525个时,由1变0。所以,场同步信号的代码如下:
![673d39b3721942a8dc1e683a04948715.png](https://i-blog.csdnimg.cn/blog_migrate/55cde372f0e9548aee29bf08d60ccea8.jpeg)
最后我们还有一个信号需要设计,那就是lcd_rgb信号。
![520ecab820762b40230b46b60d02f5d5.png](https://i-blog.csdnimg.cn/blog_migrate/45e63da608427dbd5344af607406ce14.jpeg)
我们在显示器中一共要分成两种方式显示。如上图中,以屏幕中点为中心,左右60列、上27个行、下28行显示的是图像数据;而在其他区域直接显示白色,也就是lcd_rgb等于16’b11111_111111_11111。还要注意的是,在非显示区域,lcd_rgb的值要为0,才能正确显示。我们现在要仔细区分,在什么时候分别输出上面的值。
显示区域:(h_cnt >=(96+48)&& h_cnt <</span>(96+48+640)),并且(v_cnt>=(2+33) && v_cnt<</span>(2+33+480))
图片区域:(h_cnt >=(96+48+320-60)&& h_cnt<</span>(96+48+320+60)),并且(v_cnt>=(2+33+240-27)&& v_cnt<</span>(2+33+240+28))
白色区域:在显示区域中,非图片区域的,就是白色区域。
非显示区域:显示区域之外的,就是非显示区域。
我们可以设计几个信号来表示这些区域。显示区域用valid_area=1表示,图片区域用rom_area=1表示。可得到代码如下:
![7adc9abea826981f6deea7d8b1dc9ed7.png](https://i-blog.csdnimg.cn/blog_migrate/3ea4ed297ba596b3e4129c43b13745b6.jpeg)
有了green_area和valid_area后,设计lcd_rgb就好办了。
非显示区域(valid_area=0),lcd_rgb输出“16’b0”;
显示区域(valid_area)中的图片区域(rom_area=1),lcd_rgb输出为图片的像素值。但问题来了,图片的像素值哪里来?来自于ROM,我们怎么使用ROM?将这个ROM例化,下面是例化的代码。
![6ea248d7ff6bdf7c881344538b661d6a.png](https://i-blog.csdnimg.cn/blog_migrate/6d4c493b8de931e753475e217868198f.jpeg)
代码中,address、clock和q是rom1内部的信号,而rom_addr、clk和rom_data是VGA驱动模块的信号。上面的例化意思是,将rom1的信号address连到VGA驱动模块的rom_addr信号上;将rom1的信号clock连到VGA驱动模块的clk信号上;将rom1的信号q连到VGA驱动模块的rom_data信号上。想象一下,现在要在一台电视机内部要安装一个电路板。这个电路板的自己命名的接口有address、clock和q。这台电视机里面有三种线,分别是rom_addr,clk和rom_data。我们把rom_addr插到电路板address接口上,把clk插到电路板的clock接口上,把rom_data插到电路板的q接口上,这样就完成了安装。
我们知道,通过控制ROM的地址,就能让ROM输出对应地址的数据。ROM输出的信号是rom_data。所以,显示区域(valid_area)中的图片区域(rom_area=1),lcd_rgb输出为图片的像素值,也就是rom_data的值,即lcd_rgb=rom_data。至于如何控制地址rom_addr,先不考虑,继续完成lcd_rgb信号设计。
显示区域(valid_area)中的非图片区域(rom_area=0),lcd_rgb输出白色16’h11111_111111_11111。
则可以写出代码如下:
![c6a8f748e9f7bb249ef5ae6e96f9f4c1.png](https://i-blog.csdnimg.cn/blog_migrate/9fdab0bb6ba43475a808393bff7d502b.jpeg)
最后我们再来设计rom_addr的信号。下面是
![954062948e221869561b321ac02835a6.png](https://i-blog.csdnimg.cn/blog_migrate/d8c43ffd196b5488e66ccc06aa53f994.jpeg)
由上面的表可以得到rom_addr与h_cnt和v_cnt的关系:rom_addr = (h_cnt-96-48-320+60) + 120*(v_cnt-2-33-240+27)。
但我们要注意到ROM的时序,rom_data会比rom_addr滞后一个时钟的。这会导致什么问题呢?
![e93694f2f8ca33b1100e85bd0e35f5d6.png](https://i-blog.csdnimg.cn/blog_migrate/2830023fd644be4ca1c68ed0c1086c68.jpeg)
当h_cnt=404并且v_cnt=248时,此时是图片的第一个像素点。由于rom_addr是由组合逻辑产生的,所以在第6个时钟时rom_addr=0,而地址0所对应的像素值,要在第7个时钟才会在rom_data输出。lcd_rgb是时序产生的,它在第7个时钟上升沿,采样rom_data的值并输出,很显示,此时lcd_rgb并不是输出地址0所对应的像素值。
遇到此种问题时,我们就需要调整时序。其中一种方法是是调整rom_addr,让它提前一个时钟产生,而其他信号都保持不变。更新后的波形如下图。
![9b4d7599d7092471fa42a28a0dff95fc.png](https://i-blog.csdnimg.cn/blog_migrate/fd0fa8c456c9412b7665c2e5bcd9a4b3.jpeg)
原来是在h_cnt=404时rom_addr的值为0,提前一个时钟后,当h_cnt=403时,rom_addr就为0,从而让rom_data也提前了一个时钟,正好解决此问题。
所以rom_addr所对应的代码如下:
![db903edb29441a6ce445c248157a0b52.png](https://i-blog.csdnimg.cn/blog_migrate/ca78cbf16aedb5e7accc6c379e59ff92.jpeg)
此次,主体程序已经完成。接下来是将module补充完整。
3.3.3 信号定义
接下来定义信号类型。
h_cnt是用always产生的信号,因此类型为reg。h_cnt计数的最大值为800,需要用10根线表示,即位宽是10位。因此代码如下:
![db0d744557e7a212740be5f28b595e7c.png](https://i-blog.csdnimg.cn/blog_migrate/1c66fd5878ce1d954e5b3574afb9670b.jpeg)
add_h_cnt和end_h_cnt都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:
![7a70524f7f03e3356e325c74df99b182.png](https://i-blog.csdnimg.cn/blog_migrate/15e46d48e4fde741867a743b17b4f1bb.jpeg)
v_cnt是用always产生的信号,因此类型为reg。v_cnt计数的最大值为525,需要用10根线表示,即位宽是10位。因此代码如下:
![b3636bebcbe32e14b8f022e7597e2f4d.png](https://i-blog.csdnimg.cn/blog_migrate/7c0bc5f2a1a82adc4bf6ee300d25067e.jpeg)
add_v_cnt和end_v_cnt都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1根线表示即可。因此代码如下
![f0305d35016f4d8fd76a47a017b8d7ce.png](https://i-blog.csdnimg.cn/blog_migrate/75200869b6bc2bf4c2c21761875984d6.jpeg)
lcd_rgb是用always方式设计的,因此类型为reg。并且它的位宽是16位,16根线表示即可。因此代码如下:
![b0362e965e130bdbc602c4d097c28958.png](https://i-blog.csdnimg.cn/blog_migrate/2be1f5a58bfbde627875d7b3ba7a07c6.jpeg)
distance是用always方式设计的,因此类型为reg。其位宽为20位,需要20根线表示。因此代码如下:
![11edaa27b37272c1f9389896696373d9.png](https://i-blog.csdnimg.cn/blog_migrate/3410382212e96c47cae9dc5233a0a199.jpeg)
distance是用always方式设计的,因此类型为reg。其位宽为20位,需要20根线表示。因此代码如下:
![179176359396a729eda4b9b7572a26f2.png](https://i-blog.csdnimg.cn/blog_migrate/dc3d23d6494586fc97977660900f3dc5.jpeg)
valid_area和rom_area是用always方式设计的,因此类型为reg。并且其值是0或1,用一根线表示即可。因此代码如下:
![d648d2dfa3ec5d7d235be3cba6ed2816.png](https://i-blog.csdnimg.cn/blog_migrate/1e1ade42cfd0961c968aa313b9e3f7a8.jpeg)
rom_addr是用always方式设计的,因此类型为reg。其表示范围是0~6599,需要位宽为13位,需要13根线表示。因此代码如下:
![96c815c108c294e60f3912fd99266a1a.png](https://i-blog.csdnimg.cn/blog_migrate/4e1b012b4af65896f73f83ffb00acb4c.jpeg)
rom_data是例化模块的输出,不是用always方式设计的,因此类型为wire。其位宽为16位,需要16根线表示。因此代码如下:
3.4 顶层模块设计
3.4.1例化子模块
例化PLL IP核的代码
![f410b4f9669cca88ca1e559c4be5c31c.png](https://i-blog.csdnimg.cn/blog_migrate/4b13f5e2b143437c5ca18c3c3e53b225.jpeg)
例化驱动模块的代码。
![cca8c3999514a636615c5606731bc120.png](https://i-blog.csdnimg.cn/blog_migrate/b86a76c62415931043234681a01f7928.jpeg)
3.4.2 信号定义
clk_0是在例化文件中,因此类型为wire。并且其值是0或1,用一根线表示即可。因此代码如下:
![312365dc4e45c5fe324cac09c732f6d9.png](https://i-blog.csdnimg.cn/blog_migrate/e069d5b04a5040917b97da09834bf6d6.jpeg)
lcd_sh和lcd_vs是在例化文件中,因此类型为wire。并且其值是0或1,用一根线表示即可。因此代码如下:
![ad156e987a69b07d8fcc274576b4fa7c.png](https://i-blog.csdnimg.cn/blog_migrate/2a45b3cf270b58085254fa8e6136468b.jpeg)
lcd_rgb是在例化文件中,因此类型为wire。它的位宽是16位的,用16根线表示即可。因此代码如下:
![1b52890d03462481aa6e8930e577db94.png](https://i-blog.csdnimg.cn/blog_migrate/bda72ec965411535b9fcd23f3e72702e.jpeg)
4 综合工程和上板
4.1 新建工程
首先在d盘中创建名为“picture_new_borad”的工程文件夹,将写的代码命名为“vga_drive.v”,顶层模块名为“vga_drive”,例化文件命名为“vga_exec7.v”。图片生成的数据文件名为“1.mif”,“vga_pll.v”为时钟分频ip核,由我们提供。
![716745bd0989011ab432914156c2458f.png](https://i-blog.csdnimg.cn/blog_migrate/4285ef0b14ace8635b5717bc38a1c7af.jpeg)
![ed76965d027beeac93dc2c26e8664a1e.png](https://i-blog.csdnimg.cn/blog_migrate/07594303c72828feabf70b2e8fa02adc.jpeg)
![7ec228dc7e94174d7ab5b55c645561d6.png](https://i-blog.csdnimg.cn/blog_migrate/28f109ba0530726c6cd65eca9276aa1e.jpeg)
然后打开Quartus Ⅱ,点击File下拉列表中的New Project Wzard...新建工程选项。
![d41f014a0219132d5d74a203a0365ea6.png](https://i-blog.csdnimg.cn/blog_migrate/c22b42c906f33cbaa3bda46369fdd3dc.jpeg)
3.在出现的界面中直接点击最下方的“Next”。
![73892555817ae278a779e029364de216.png](https://i-blog.csdnimg.cn/blog_migrate/523bfdec81347b42876451d32158d0f1.jpeg)
4.之后出现的是工程文件夹、工程名、顶层模块名设置界面。按照之前的命名进行填写,第一栏选择工程文件夹“picture_new_borad”,第二栏选择工程文件“vga_exec7.v”,最后一栏选择顶层模块名“vga_exec7”,然后点击”Next”。
![4d27e0c1a7835f9b5112c86a418a72cb.png](https://i-blog.csdnimg.cn/blog_migrate/6d8704fb24f1506e9301512dfb4e26f3.jpeg)
5.之后是文件添加界面。点击红色箭头处,在上方一栏中添加之前写的”vga_driver.v、vga_pll.v和vga_exec1.v”文件,点击右侧的“Add”按钮,之后文件还会出现在大方框中,选中它们,之后点击“Next”。
![b0e351e922c2b3bb07d009347b51260b.png](https://i-blog.csdnimg.cn/blog_migrate/9be44fa796455b553d450df24a205a24.jpeg)
器件型号选择界面。在“Device family”处选择Cyclone ⅣE,在“Available devices”处选择EP4CE15F23C8,然后点击“Next”。
![63e28b92e446dc827ede92aff0ee1438.png](https://i-blog.csdnimg.cn/blog_migrate/4466d40f9be625ccfa8544bfb164fd8c.jpeg)
EDA工具界面。该页面用默认的就行,直接点击最下方“Next”。
![ccec467e8bb07f5008d844955cde19e2.png](https://i-blog.csdnimg.cn/blog_migrate/88135721b312b2f021a0263f68163575.jpeg)
8.之后出现的界面是我们前面的设置的总结,确认没有错误后点击“Finish”。
![27927e4cafed074835ca265fa25d0cee.png](https://i-blog.csdnimg.cn/blog_migrate/6229df74beef3716f9ba7d55688ca1bc.jpeg)
4.2 生成PLL IP核
新建工程后,就要生成PLL IP核。本节的PLL生成过程,与案例“VGA显示颜色”第四点综合工程和上板中的PLL内容一致,注意其中的地址有不同。
4.3 生成ROM IP核
1、在界面右侧IP Catalog处搜索ROM。
![4559f17af76ddc93a994440094975682.png](https://i-blog.csdnimg.cn/blog_migrate/16ba96f8828da39829f98f5bdf211211.jpeg)
2、双击rom:1-port保存文件名,之后点击ok。
![3de234f77eb76c61ef86a4ba29f9f8c9.png](https://i-blog.csdnimg.cn/blog_migrate/aecfa206a2dd380661fd121c312cb98f.jpeg)
4、在之后的界面中选择数据位宽为16位,存储深度为8192words,点击Next。
![066fa61f06a2a2b338304338e6ec57ce.png](https://i-blog.csdnimg.cn/blog_migrate/1f87a794733dc338f3db3295695f35b0.jpeg)
在‘q’output port处取消对号选中,点击Next。
![bc7e16e3c0c2365b82d5683ad8933896.png](https://i-blog.csdnimg.cn/blog_migrate/c5900e3eb5bb396098c511d6b5300c30.jpeg)
5、然后选择文件,点击Browse,然后在出现的Select File中最下方的文件类型选择.mif格式,然后选中我们之前写的1.mif,点击Open。
![53e7d86d6562fd3a7cb80146f56cd6b7.png](https://i-blog.csdnimg.cn/blog_migrate/3179f1c29413d836be75efadf3131a80.jpeg)
6、之后的界面中直接点击Next。
![778d282d9972da8a77ca09e47da07aef.png](https://i-blog.csdnimg.cn/blog_migrate/848a5bd2759c337590a503661d7cdee1.jpeg)
取消rom1_bb.v处的箭头勾选,点击Finish。
![01fde3525fd8898f12feb826a1728b7b.png](https://i-blog.csdnimg.cn/blog_migrate/48276d78f16b9dfba2e6acb4df886bb7.jpeg)
8、然后直接点击Yes
![85715bd2194f6982918509ecffabd857.png](https://i-blog.csdnimg.cn/blog_migrate/c325821cee3f48b0841300019b911cd0.jpeg)
4.4 综合
1.在“Project Navigator”下选中要编译的文件,点击上方工具栏中“StartCompilation”编译按钮(蓝色三角形)。
![9e74ec2158ca720f99ad809279bb7ca0.png](https://i-blog.csdnimg.cn/blog_migrate/e7fc0c4b688ab9d436b36e1927837430.jpeg)
2.编译成功后会出现以下界面。
![a026f3916bf76a4ee4c8000e9676d434.png](https://i-blog.csdnimg.cn/blog_migrate/8d7bb5a36347c05b7353284b9663b72b.jpeg)
4.5 配置管脚
![ec04d2c47d6eee0483caa589e0f378fd.png](https://i-blog.csdnimg.cn/blog_migrate/e39ecaa7d7c5d65a12eeb67d79134e76.jpeg)
在菜单栏中,选中Assignments,然后选择Pin Planner,就会弹出配置管脚的窗口。
![22fbbd694af5a50723a425ee21a2fb9b.png](https://i-blog.csdnimg.cn/blog_migrate/06ef8c52a0c5d4d53f439b56d525feea.jpeg)
在配置窗口最下方中的location一列,参考下表中最右两列配置好FPGA管脚。
![73f1196933694661d7ad9ec632c40d29.png](https://i-blog.csdnimg.cn/blog_migrate/039d13faa5398b8c58a417d8e4160404.jpeg)
配置完成后,关闭Pin Planner,软件自动会保存管脚配置信息。
4.6 再次综合
![3cdc0c65f478dd37a1425b2cad7a87aa.png](https://i-blog.csdnimg.cn/blog_migrate/36b6b92e448d04d21bd5758f4920bd64.jpeg)
在菜单栏中,选中Processing,然后选择Start Compilation,再次对整个工程进行编译和综合。
![51aaf9c3358b6116576d20f151019a98.png](https://i-blog.csdnimg.cn/blog_migrate/d3a8bb3d35c0db2ad444e2467f6b7aca.jpeg)
出现上面的界面,就说明编译综合成功。
4.7 连接开发板
图中,下载器接入电脑USB接口,电源接入电源,vga线连接显示器,然后摁下电源开关,看到开发板灯亮。
![4637a056723be7194c48efe3af65870a.png](https://i-blog.csdnimg.cn/blog_migrate/02df849b95b1e59ca961314e26e181ad.jpeg)
4.8 上板
1.双击Tasks一栏中”Program Device”。
![922453d9b7fe70c55d293ffd94e1effb.png](https://i-blog.csdnimg.cn/blog_migrate/04bbabc422280ea7362ca23be965d0d5.jpeg)
2.会出现如下界面,点击add file添加.sof文件, 在右侧点击“Start”,会在上方的“Progress”处显示进度。
![843bcb9341a0ba19ec5955cd94bff48a.png](https://i-blog.csdnimg.cn/blog_migrate/f1321b2393fad024e403e16449cec31c.jpeg)
3.进度条中提示成功后,即可在显示器上观察到相应的现象
如果你觉得有用的话,就请你回个贴或者赞,证明我的付出没有白费,大家都不容易,你想要FPGA的更多资料,QQ:328908175,让们共同学习。