明德扬论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

微信扫一扫,快捷登录!

查看: 631|回复: 0

【FPGA至简设计原理与应用】书籍连载20 第三篇 FPGA至简设计项目 第十一章VGA显示图片

[复制链接]
发表于 2020-6-11 15:29:49 | 显示全部楼层 |阅读模式

马上注册,看完整文章,学更多FPGA知识。

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
大家好,近期我们会连载《FPGA至简设计原理与应用》一书,有兴趣的同学可以学习,也希望大家可以对我们的书提出宝贵的意见和建议。

《FPGA
至简设计原理与应用》书籍连载索引目录
http://www.fpgabbs.cn/forum.php?mod=viewthread&tid=989


读过的朋友可积极在贴后留言,书籍正式出版时,我们会从留言者中挑选20位幸运读者,幸运读者可获潘老师亲笔签名书籍一本。

注:手机浏览可能格式会乱,建议用电脑端进行浏览。





第十一章 VGA显示图片第1节 项目背景


1.1 IP核概述
IP核(Intellectual Property core)指的是知识产权核或知识产权模块,其是具有特定电路功能的硬件描述语言程序,在EDA技术开发中具有十分重要的地位。美国著名的Dataquest咨询公司将半导体产业的IP定义为“用于ASICFPGA中的预先设计好的电路功能模块”。

在数字电路中IP(知识产权)核非常常用,其将FIR滤波器、SDRAM控制器、PCI接口等比较复杂的功能模块设计成可修改参数的模块,这些模块会像应用程序一样公开给设计者们使用。在进行复杂的工程中如果需要这些技术,设计者就可以调用对应IP核后补充工程所需参数,就可将其运用到设计中。随着CPLD/FPGA的规模越来越大,设计也变得越来越复杂,设计者的主要任务则是在规定的时间周期内完成复杂的设计。而IP核的调用可以避免重复劳动,从而大大减轻工程师的负担,因此使用IP核成为了一大发展趋势,与此同时,IP核的重用大大缩短了产品上市时间。

利用IP核设计的电子系统具有引用方便的特点且很易于修改基本元件的功能。一般来说,具有复杂功能和商业价值的IP核具有知识产权,尽管目前IP核的市场活动还不规范,但是仍有许多集成电路设计公司从事IP核的设计、开发和营销工作。

IP核具有三种不同的存在形式:HDL语言形式,网表形式、版图形式。IP内核可以在不同的硬件描述级实现,由此产生了三类IP内核:软核、固核和硬核。这种分类主要依据产品交付的方式,而这三种IP内核实现方法也各具特色。

软核是用VHDL等硬件描述语言描述的功能块,但其并不涉及用哪些具体电路元件来实现这些功能。软IP通常是以硬件描述语言HDL源文件的形式出现,其应用开发过程与普通HDL的设计十分相似,只是所需的开发硬软件环境比较昂贵。软IP的设计周期短,设计投入少,且不涉及物理实现,因此为后续的设计留有较大的发挥空间,同时也增大了IP的灵活性和适应性。其主要缺点是软IP在一定程度上使后续工序无法适应整体设计,从而需要一定程度的修正,在性能上也不可能获得全面的优化。软核是以源代码的形式提供,尽管源代码可以采用加密方法,但其知识产权保护问题依然不容忽视。

硬核提供设计阶段最终阶段产品:掩膜。这种硬核以经过完全的布局布线的网表形式提供,既具有可预见性,同时还可以针对特定工艺或购买商进行功耗和尺寸上的优化。尽管由于缺乏灵活性而导致硬核可移植性差,但由于其无须提供寄存器转移级(RTL)文件,因而更易于实现IP保护。

固核则是软核和硬核的折中。目前应用于FPGAIP内核大多数均为软核,其有助于用户调节参数并增强可复用性。软核通常以加密形式提供,这样一来用户无法获取实际的 RTL,但其布局和布线十分灵活。在这些加密的软核中,如果对内核进行了参数化,那么通过头文件或图形用户接口(GUI)用户可以方便地进行参数操作。而对于那些对时序要求严格的内核(PCI接口内核),可预布线特定信号或分配特定的布线资源从而满足其时序要求,这一部分的内核就可归类为固核。由于内核是预先设计的代码模块,其建立(setup)、保持时间和握手信号都可能是固定的,因此其它电路的设计时都必须考虑如何与该内核正确地进行接口。如果内核具有固定布局或部分固定的布局,那么这还可能影响其它电路的布局。

1.2 ROM IP
目前现有的大多数FPGA都有内嵌块RAM(Block RAM),可以将其灵活地配置成单端口RAM(DPRAMSingle Port RAM)、双端口RAM(DPRAMDouble Ports RAM)、伪双端口RAM(Pseudo DPRAM)CAM(Content Addressable Memory)FIFO等常用的存储结构。其实在FPGA中并没有专用的ROM硬件资源,实现ROM的思路是对RAM赋予初值后保持该初值。

Altera 的器件内部提供了各种存储器模块(RAMROM 或双口 RAM),可以在设计中使用MegaWizard Plug-In Manager,执行“Tools”后利用“MegaWizard Plug-In Manager”菜单命令来创建所需要的存储器模块。此外,也可以使用 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.3 图片转成初始化文件
上文中提到过:在创建mif文件时,若已经存在bmp格式图片则可以使用软件工具BmpToMif来将bmp的图片转换为mif文件。下面介绍其具体操作,BmpToMif的软件界面如下图所示。
图片1.png
3.11-1BMP2MIF软件界面

点击打开图片后选择一幅bmp格式的图片。这里需要注意的是,由于受开发板上FPGA资源的限制,图片的大小不能超过320*240。如果图片大小不是320*240,建议使用“画图”或者“photoshop”等软件将图片大小更改为320*240

bmp图片转换为mif文件的过程如下:

1. 点击“图像”选项卡;
2. 点击“打开图片”后选择一幅图片,本案例中选择一幅大小为320*240、格式为bmp的图片,文件名为“mdy_logo.bmp”;
3. 在“颜色类型”中根据需要选择“黑白”或“彩色”,本案例中将选用黑白模式;
4. 若为黑白图,在“黑白选项”中选择“黑色为1”或“白色为1”,注意如果是黑白图片,生成ROM要选择字长为“1”;
5. 若为彩色图,在“彩色选项”中选择“单一mif文件”或“三个mif文件”。如果是“单一mif文件”,则生成ROM要选择字长为3,每个字按红绿蓝表示各个分量;如果是“三个mif文件”,会生成三个mif文件,每个文件分别对应于红绿蓝分量,生成ROM要选择字长为“1”;
6. “颜色界值”用于颜色的界定。若为黑白图,当图中的颜色红绿蓝三个分量均小于或等于对应界值时转换为黑,否则为白;若为彩色图,当图中的颜色红、绿或蓝分量小于等于对应界值时该分量转换为0,否则为1
7. 点击“预览”可以查看转换颜色后的效果;
8. 点击“生成mif文件”,可以得到所需文件。

本设计将提供已经生成好的mif文件:mdy_logo.mif,可以直接使用。


1.4 生成ROM IP
打开Quartus软件后首先应新建工程,新建工程的方法请看本章的4.1部分,这里需要注意目录路径、工程名和顶层模块名要与本书设定一致。在“Quartus”界面右边“IP Catalog”窗口中,输入“rom”,然后双击“ROM:1-PORT”,就会弹出ROM的路径设置界面。
图片2.png
3.11-2IP Catalog中查找ROM IP

在文件名设置页面中输入“D:\mdy_book\picture_new_borad\rom1.v”,选择“Verilog”类型,此时会生成一个文件名为room1.v,类型为verilogROM代码,点击“OK”后会跳到ROM IP核的设置界面。
图片3.png
3.11-3设置ROM IP核文件名界面

按下图所示进行设置,需要注意,输出信号q的位宽选择为1,深度选择为65536。其它保持为默认,点击“Next”。
图片4.png
3.11-4ROM IP核设置界面1

在下图将“’q’ output port”选项不勾选,其它保持默认,点击“Next”。
图片5.png
3.11-5ROM IP核设置界面2

选择ROM初始化文件的界面如下图所示,选择本书提供的文件“D:/mdy_book/picture_new_borad/rom1.mif”后直接点击Finish,完成ROM文件的设置并生成ROM的代码“rom1.v”。
图片6.png
3.11-6ROM IP核设置界面3

约一分钟左右后在“D:/mdy_book/picture_new_borad/”目录下就可以看到生成的rom1.v”文件,如下图所示。但该代码的RTL部分不可见,同学们只能看到其顶层接口,可以看出该模块只有三个信号,时钟clock16位的地址address1位输出数据q
图片7.png
3.11-7ROM IP核的输入输出接口

第2节 设计目标
学会生成ROM IP核后来带领同学们进行新项目的设计。按照至简设计法的思路,在进行设计之前首先应明确设计目标。明确了设计目标后,后续的每一步操作都会围绕设计目标进行展开。如果没有牢记设计目标就开始动手进行实践操作,最终的作品也只是东拼西凑的产物。这种状态下,一旦在设计过程中出现了问题就需要花费大量的精力进行寻找修复。只有在开始学习时就养成良好的设计习惯,才能在后续的职业生涯中受益。因此建议一定要确定设计目标再进行后面部分的学习。

前面几章中已经带领同学们学习了通过VGA显示颜色和形状的工程,这一章中在之前设计的基础上增强项目的难度,引入一个新的概念。前几章的设计中显示的图像都是基于FPGA本身可以产生的三基色完成的,本次设计会在显示颜色的同时在显示器上产生一幅图像。

本设计需要通过VGA连接线将显示器和开发板进行连接,FPGA在连接成功后产生640*480分辨率、刷新频率为60HzVGA时序,使得显示器屏幕的中央显示一个至简设计法的LOGO除图片之外的显示区域则显示为白色。显示的至简设计法LOGO大小为120*60像素。

显示器一般都具有分辨率自适应功能,无须特殊设置就能识别不同分辨率的图像。本设计相应参数参见表3.11-1中的第一行,这里的VGA常用分辨率的对应时序参数并不是本书随意设定的,而是国际通用标准,每个关于VGA的设计工程都需要遵守这一标准。其中,行的单位为“基准时钟”,即频率为25MHz、周期为40ns的时钟,列的单位则为“行”,请读者朋友们一定要区分好。

3.11 - 1 VGA常用分辨率
分辨率
/
同步脉冲
显示后沿
显示区域
显示前沿
帧长
单位
640*480
/60Hz
96
48
640
16
800
基准时钟
2
33
480
10
525
800*600
/72Hz
120
64
800
56
1040
基准时钟
6
23
600
37
666
800*600
/60Hz
128
88
800
40
1056
基准时钟
4
23
600
1
628
1024*768
/60Hz
136
160
1024
24
1344
基准时钟
6
29
768
3
806

本案例中提供了ROM IP核文件:rom1.v。该文件的输入输出接口是:
1
2
rom1 (
[size=9.5000pt]        address,
[size=9.5000pt]        clock,
[size=9.5000pt]        q);

rom1是一个宽度为16位,深度为8192ROM,该ROM中已经保存了至简设计法的LOGO图片,并且与VGA扫描路径相同,该ROM也是按照从左往右,由上往下的顺序保存了LOGO图像每个像素的值。图中(y,x)表示的是第y行第x列的像素值。例如:

地址0保存的是第1行第1列的像素值,即(0,0);
地址1保存的是第1行第2列的像素值,即(0,1);
地址2保存的是第1行第3列的像素值,即(0,2);
地址119保存的是第1行第120列的像素值,即(0,119)
地址120保存的是第2行第1列的像素值,即(2,0);
以此类推,地址6599保存的是第55120列的像素值,即(54,119);
而大于6599的地址,保存的值为0,其数字没有意义。

RGB565的方式保存ROM的每一个像素,即[15:11]表示红基色,[10:5]表示绿基色,[4:0]表示蓝基色。

综上可知,该ROM的排列方式如下图所示:
3.11-8ROM存储图片的方式


设计完成后,通过VGA连接线将显示器和教学板的VGA接口相连。连接示意图如下所示:
图片9.png
3.11-9教学板连接示意图

上板后显示器展示效果图如下图所示,不同的显示器会有一定的色差,请读者朋友们以实际情况为主。可以看到屏幕中央有一个120*60的至简设计法LOGO,其余显示区域为白色。想要观看连接后的演示视频效果,可以登陆至简设计法官网学习:www.mdy-edu.com/xxxx
图片10.png

3.11-10VGA显示图片效果图

第3节 设计实现
确定了设计目标后,本书会逐步分析讲解工程的制作步骤。建议初学者认真学习每一步,因为这里分享给同学们的不仅仅是案例,还有在操作过程中的一些设计理念及原理。当然本书也会分享一些至简设计法的设计技巧,最终希望每一位读者都可以具备独立设计工程的能力。当然已经拥有扎实的功底、只是想要根据步骤完成项目的读者朋友们可以跳过此部分,直接进入第五节中的简略版操作步骤分享。

3.1 顶层接口
新建目录:D:\mdy_book\picture_new_borad,并在该目录中,新建一个名为picture_new_borad.v的文件。用GVIM打开后开始编写代码,这里再次强调,初学者一定要按照本书提供的文件路径以及文件名进行设置,避免后面出现未知错误。

首先来确定顶层信号,分析设计目标可知FPGA产生VGA时序,即控制VGA_R4~R0VGA_G5~G0VGA_B4~B0VGA_HSYNCVGA_VSYNC从而使显示器显示图像。其中,FPGA可根据时序产生高低电平从而控制VGA_HSYNCVGA_VSYNC。本设计中显示的一部分是FPGA自身可以产生的白色图像,另一部分的LOGO图片数据需要先转成mif的初始化文件,再利用初始化文件生成ROM IP核,设计时例化此IP核,控制IP核的地址从而读取到图片数据。

在本工程设计中可以定义输出信号hys表示行同步,定义输出信号vys表示场同步,定义一个16位的信号lcd_rgb用于RGB输出,其中lcd_rgb[15:11]表示VGA_R4~0lcd_rgb[10:5]表示VGA_G5~0lcd_rgb[4:0]表示VGA_B4~0。当然,设计中还需要时钟信号clk和复位信号rst_n来进行工程控制。

综上所述,本工程一共需要五个信号,时钟信号clk,复位信号rst_n,场同步信号vys、行同步信号hysRGB输出信号lcd_rgb。信号与硬件的对应关系如下表所示。

3.11 –2 信号和管脚关系
器件
电阻网络转换后
信号线
信号线
FPGA管脚
FPGA工程信号
CN1
VGA_RED
VGA_R4
E11
lcd_rgb[15]
VGA_R3
C10
lcd_rgb[14]
VGA_R2
D10
lcd_rgb[13]
VGA_R1
E9
lcd_rgb[12]
VGA_R0
E10
lcd_rgb[11]
VGA_GREEN
VGA_G5
D15
lcd_rgb[10]
VGA_G4
C17
lcd_rgb[9]
VGA_G3
C19
lcd_rgb[8]
VGA_G2
E12
lcd_rgb[7]
VGA_G1
C13
lcd_rgb[6]
VGA_G0
E15
lcd_rgb[5]
VGA_BLUE
VGA_B4
D13
lcd_rgb[4]
VGA_B3
E13
lcd_rgb[3]
VGA_B2
D17
lcd_rgb[2]
VGA_B1
E16
lcd_rgb[1]
VGA_B0
C15
lcd_rgb[0]
VGA_HSYNC
VGA_HSYNC
C20
hys
VGA_VSYNC
VGA_VSYNC
D20
vys
X1

SYS_CLK
G1
clk
K1

SYS_RST
AB12
rst_n

通过以上分析写出顶层信号代码。将module的名称定义为picture_new_borad。已经知道该模块有五个信号:clkrst_nlcd_hslcd_vslcd_rgb。将与外部相连接的输入/输出信号列出,从而实现信号与管脚的连接。具体顶层代码如下:
1
2
3
4
5
6
7
module picture_new_borad (
clk  ,
rst_n  ,
lcd_hs  ,
lcd_vs  ,
lcd_rgb
    );

随后声明信号的输入输出属性。这里需要声明这一信号对于FPGA来说,属于输入信号还是输出信号。如果是输入信号,则声明其为input,如果是输出信号,则声明其为ouput。在本设计中,由于clk是外部的晶振输入给FPGA的,因此在FPGA中clk是输入信号input;同样地,rst_n是外部按键输送给FPGA的,在FPGA中同样为输入信号input;lcd_hs、lcd_vs和lcd_rgb是FPGA输出给显示器的,因此其为输出信号output,并且其中clk、rst_n、lcd_hs、lcd_vs的值都是0或者1,用一根线表示即可,lcd_rgb位宽为16位。根据以上分析补充输入输出端口定义,其具体代码如下:
1
2
3
4
5
input                   clk  ;
input                   rst_n  ;
output                  lcd_hs  ;
output                  lcd_vs  ;
output  [15:0]          lcd_rgb       ;

3.2 信号设计
分析设计目标可知,首先需要设计行同步信号hys,其时序图表示如下:

图片11.png
3.11-11VGA行同步时序

根据时序图可以看到,hys就是一个周期性地高低变化的脉冲。根据设计目标可知图像分辨率选定为640*480,因此使用下表中的640*480分辨率的相关参数。即同步脉冲a的时间是96个基准时钟,显示后沿b的时间是48个基准时钟周期,显示时序c的时间是640个基准时钟,显示前沿的时间是16个基准时钟,共计800个基准时钟(800=96+48+640+16)。

3.11 - 1 VGA常用分辨率
分辨率
/
同步脉冲
显示后沿
显示区域
显示前沿
帧长
单位
640*480
/60Hz
96
48
640
16
800
基准时钟
2
33
480
10
525
800*600
/72Hz
120
64
800
56
1040
基准时钟
6
23
600
37
666
800*600
/60Hz
128
88
800
40
1056
基准时钟
4
23
600
1
628
1024*768
/60Hz
136
160
1024
24
1344
基准时钟
6
29
768
3
806

这里需要注意,一个基准时钟是40ns,而至简设计法开发板的时钟周期是20ns,因此基于至简设计法开发板的VGA工程设计中,采用2个时钟时间代表一个基准时钟时间。在图中补充对应的时间信息,带有时间信息的时序图如下:
图片12.png

3.11-12带时间信息的VGA行同步时序

根据至简设计法的理论,分析波形图和设计目标可以得到本设计的计数器架构:本设计需要使用2个计数器,其中一个计数器cnt0用来计数基准时间,另一个计数器cnt1用来计数hys的行长度。

先来讨论用于计数基准时间的cnt0。至简设计法的计数器只考虑两个因素:加1条件和计数数量,只要可以确定相应逻辑,就能完成计数器代码设计。首先确定计数器cnt0的加1条件:由于该计数器在不停地计数,永远不停止,因此可以认为其加1条件是始终有效的,可写成:assign add_cnt0==1。

这里可能会有读者会提出疑问:加1条件的概念是什么?这里以停车位来进行比喻,一般情况下对每个停车位置会进行对应编号,但是如果某个位置上放置了一块石头无法作为停车位时,该位置就不能获得对应的编号。反之则可以认为停车位编号的加1条件就是:对应位置上没有石头,其可以继续的进行编号,即assign add_cnt0 = 没有石头。因此如果在设计中计数器一直没有阻碍地进行计数工作,就可以认为加1条件是一直有效的。

接下来确定计数器cnt0的计数数量,前文分析中可知2个时钟周期等于1个基准时钟,因此计数器cnt0的计数数量是2。

确定好了加1条件和计数数量后开始进行代码编写。相信各位往常都是一行行输入代码,但是至简设计法有一个小技巧,可以为大家编写代码省去不少时间,并且一定程度上降低了代码的出错率。至简设计法将日常代码中常用到的固定部分做成了模板,进行代码编程时可以调用相应模板后根据逻辑输入对应设计的变量将代码补充完整。这里就可以用模板编写计数器代码,感受一下这个炫酷的功能。

在命令模式下输入“:Mdyjsq”,点击回车,就调出了对应模板,如下图所示。随后再将本案例中的变量填到模板里面,就可以得到完整正确的计数器代码。
图片13.png

3.11-13至简设计法调用计数器代码模板

补充完整后得到计数基准时间的计数器cnt0代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end
[size=9.5000pt]
assign add_cnt0 = [size=9.5000pt]1[size=9.5000pt];
assign end_cnt0 = add_cnt0 && cnt0== 2 -1[size=9.5000pt];

接着讨论用于计数hys长度的计数器cnt1。根据设计目标可以知道一行占有800个基准时钟,因此其计数数量为800。前文设计中已经确定了一个基准时钟可以用end_cnt0表示,因此计数器cnt1的加1条件为“end_cnt0”,可写成:assign add_cnt1 = end_cnt0。此时继续调用至简设计法模板,在命令模式下输入“:Mdyjsq”后点击回车,调出对应模板并将“add_cnt1”和“end_cnt1”补充完整,得到该计数器的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end
[size=9.5000pt]
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==800-1 ;

确定了计数器cnt0和cnt1后hys信号的设计就有了对齐的对象。从时序图可以发现,hys有两个变化点,一个是cnt1数到96个基准时钟时,同步脉冲a结束,信号由0变1出现一个上升沿;另一个是当cnt1数到800个基准时钟时,信号由1变0出现下降沿。下面将其翻译成代码,在编辑模式下输入“Shixu2”,调用至简设计法模板,然后将模板补充完整后得到场同步信号的代码如下:
1
2
3
4
5
6
7
8
9
10
11
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
hys<= 0;
    end
    else if(add_cnt1 && cnt1==96-1)begin
hys<= 1;
    end
    else if(end_cnt1)begin
hys<= 0;
    end
end

接下来讨论vys信号的设计。根据设计目标可以得到VGA场同步信号的时序图如下所示:
图片14.png

3.11-14VGA场同步时序

可以看出vys就是一个周期性地进行高低变化的脉冲。本设计中图像分辨率选定为640*480,因此使用表3.10- 1中的640*480分辨率的相应参数。查询表可知:同步脉冲a的时间是2行,显示后沿b的时间是33行,显示时序c的时间是480行,显示前沿的时间是10行,共计525行。此处需要注意行的单位为“基准时钟”,前文设计中已经使用计数器cnt0表示一个基准时钟,cnt1表示一行;场同步信号是的单位是“行”,因此设计中可以使用cnt1来辅助表示场同步信号,即cnt1计数结束则代表一“行”结束。

在场同步信号中补充时间信息,得到带有时间信息的时序图如下所示。
图片15.png

3.11-15带时间信息的VGA场同步时序

分析时序图可以发现若要产生这一时序还需要另1个计数器,可将产生这一时序的计数器命名为cnt2。前文强调过vys的单位是行,而该计数器是用来计数行的数量,因此其加1条件就是一行结束。前文中使用cnt1来计数一行,因此计数器cnt2的加1条件一行结束即“end_cnt1”,可写成:assign add_cnt2 = end_cnt1。分析上面的时序图可以知道,该计数器的计数数量为525。下面继续调用至简设计法模板将其翻译为代码表示,在命令模式下输入“:Mdyjsq”,点击回车,调出对应模板后将“add_cnt1”和“end_cnt1”补充完整,得到该计数器的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt2 <= 0;
    end
    else if(add_cnt2)begin
        if(end_cnt2)
            cnt2 <= 0;
        else
            cnt2 <= cnt2 + 1;
    end
end
[size=9.5000pt]
assign add_cnt2 = end_cnt1;
assign end_cnt2 = add_cnt2 && cnt2==525-1 ;

确定了计数器cnt2,则vys信号的设计就有了对齐的对象。从时序图可以发现:vys有两个变化点,一个是cnt2数到2个时,信号值由01;另一个是当cnt2数到525个时,信号值由10。下面将vys信号翻译成代码,在编辑模式下输入“Shixu2”调用至简设计法模板后将其补充完整,得到场同步信号的代码如下:
1
2
3
4
5
6
7
8
9
10
11
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
vys<= 1'b0;
    end
    else if(add_cnt2 && cnt2 == 2 - 1)begin
vys<= 1'b1;
    end
    else if(end_cnt2)begin
vys<= 1'b0;
    end
end
3.11-16VGA显示图片区域

最后还需要完成lcd_rgb信号的设计。从设计目标可知要在显示器中一共要完成两种方式的显示。如上图所示,显示器需要显示一幅外部输入的图片,其他区域显示为白色。外部输入图片大小为120*55,以屏幕最中心为中间点,中心点左方和右方均为120/2=60列,上方55/2≈27行,下方55-27=28行,这一范围是显示图像数据的区域,而在其它区域内直接显示白色,这时lcd_rgb等于16’b11111_111111_11111。这里一定要注意,设计目标需要在“显示区域”才能进行颜色赋值,在非显示区域,将lcd_rgb的值要为0。

确定VGA背景的讲解部分有说明过显示区域如何确定,场同步信号处于显示区域且行同步信号也处于显示区域时才是真正的显示区域,而其他区域中红、绿、蓝基色都应赋值为低电平时,从而实现VGA颜色显示。结合VGA时序可以知道行同步信号的显示区域为同步脉冲和显示后沿之后,并且在显示前沿之前,转化为代码表示即为cnt1>=(96+48)&&cnt1<(96+48+640);场同步信号的显示区域也为同步脉冲和显示后沿之后,且在显示前沿之前,转化为代码表示即为cnt2>=(2+33) &&cnt2<(2+33+480)。行同步信号和场同步信号同时处于显示区域才是显示器的实际显示区域,因此显示器显示区域转化为代码表示即为(cnt1>=(96+48)&&cnt1<(96+48+640)),并且(cnt2>=(2+33) &&cnt2<(2+33+480))。

通过前文分析可知图片的显示区域为中间点左方60列,右方60列,上方27行,下方28行。确定显示区域后可以得到:
显示区域:其中间点为cnt1=96+48+640/2=464cnt2=2+33+480/2=275,因而可以确定图片区域的范围,转化为代码表示即(cnt1>=96+48+320-60&&cnt1<96+48+320+60),并且(cnt2>=(2+33+240-27) &&cnt2<2+33+240+28)

白色区域:在显示区域中,非图片区域的就是白色区域,此时lcd_rgb输出“16’b11111_111111_11111”;
非显示区域:显示区域之外的就是非显示区域,非显示区域lcd_rgb要为低电平,即输出“16’b0”。

综上所述,可以设计几个信号来表示这些区域,定义显示区域用valid_area=1表示,图片区域用rom_area=1表示。可得到代码如下:
1
2
3
4
5
6
7
8
always  @(*)begin
valid_area = cnt1>=(96+48) &&cnt1<(96+48+640) &&cnt2>=(2+33) &&cnt2< (2+33+480);
end
always  @(*)begin
rom_area= cnt1>=(96+48+320-60) &&cnt1<(96+48+320+60) &&(cnt2>=(2+33+240-27)&&cnt2<2+33+240+28;
end

确定了rom_areavalid_area后,lcd_rgb的设计非常容易。通过前文分析可知:在显示区域(valid_area=1)中的图片区域(rom_area=1),lcd_rgb输出为图片的像素值。有读者此时可能会有疑问:图片的像素值从哪里来呢?在第一节中讲解了可以将图片数据转换为mif的初始化文件并利用该初始化文件生成ROM IP核,通过控制该ROMIP核的地址就能读取到图片数据。下面将具体介绍其操作方法。

首先将该ROM例化,所谓IP核的例化,就是将IP核的接口与工程进行连接。举个例子,现在要在一台电视机内部要安装一个电路板。电路板上的接口有addressclockq。同样电视机里面有三种线,分别是rom_addrclkrom_data。需要把rom_addr插到电路板address接口上,把clk插到电路板的clock接口上,把rom_data插到电路板的q接口上才能完成了安装。同样地,在rom1内部有三个信号分别是addressclockq;因此VGA驱动模块也有对应的三个信号rom_addrclkrom_data。例化就是将rom1的信号address连到VGA驱动模块的rom_addr信号上;将rom1的信号clock连到VGA驱动模块的clk信号上;将rom1的信号q连到VGA驱动模块的rom_data信号上。

在本设计中例化IP核的代码如下:
1
2
3
4
rom1 u_fpga_rom(
[size=9.5000pt]        .address (rom_addr),
[size=9.5000pt]        .clock   (clk     ),
[size=9.5000pt]        .q       (rom_data));

前文中讲解过可以通过控制ROM的地址令ROM输出对应地址的数据。ROM输出的信号是rom_data,因此在显示区域(valid_area=1)中的图片区域(rom_area=1),lcd_rgb输出图片的像素值,也就是rom_data的值,即当rom_area=1时,lcd_rgb=rom_data

在显示区域(valid_area=1)中的非图片区域(rom_area=0),lcd_rgb输出白色16’h11111_111111_11111

综上所述,lcd_rgb信号设计代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
lcd_rgb<= 16'h0;
    end
    else if(valid_area)begin
        if(rom_area)begin
lcd_rgb<= rom_data;
        end
        else begin
lcd_rgb<= 16'b11111_111111_11111;
        end
    end
    else begin
lcd_rgb<= 0;
    end
end


最后来设计控制地址的rom_addr信号。下表是本设计中cnt1cnt2对应的rom_addr值,可以得出rom_addrcnt1cnt2的关系,即rom_addr = (cnt1-96-48-320+60) + 120*(cnt2-2-33-240+27)
3.11- 3 cnt1cnt2对应的rom_addr值

需要注意的是ROM的时序中,rom_data会比rom_addr滞后一个时钟,如下图所示。下面请同学们来思考一下这样会出现什么问题呢?
图片17.png
3.11-17ROM时序问题

在这种情况下,当cnt1=404并且cnt2=248时的位置为图片的第一个像素点。由于rom_addr是由组合逻辑产生的,所以在第6个时钟时rom_addr的值为0,但地址0所对应的像素值在第7个时钟时才会在rom_data输出。由于lcd_rgb是时序产生的,它在第7个时钟上升沿,才会采样rom_data的值并输出,可以看出此时lcd_rgb并不是输出地址0所对应的像素值。

在设计中遇到此种问题时,就需要进行时序调整。此时,最简单的办法是调整rom_addr的时序,让它提前一个时钟产生,而其它信号保持不变。更新后的波形如下图所示:
图片18.png
3.11-18调整ROM相关时序

调整前,当cnt1=404rom_addr的值为0,调整rom_addr将其提前一个时钟后,变为当cnt1=403时,rom_addr就为0,从而使得rom_data也提前了一个时钟,这样一来就完美的解决了时钟滞后的问题。

因此rom_addr所对应的代码如下:
1
2
3
always@(*)begin
rom_addr = (cnt1-96-48-320+60-1) + 120*(cnt2-2-33-240+27)
end

至此,主体程序已经完成。


3.3 信号定义
接下来将module补充完整,首先来定义信号类型。再次强调,在进行regwire的判断的时候,总容易存在多余的联想,比如认为reg就是寄存器,wire是线;或者认为reg会综合成寄存器,wire不会综合成寄存器。但是这些其实和reg型还是wire型的确定都并无关系,在进行信号类型的判断时不需要做任何的联想,只要记住一个规则“用always实现的是reg型,其他都是wire”就可以了。

cnt0是用always产生的信号,因此类型为reg。cnt0计数的最大值为1,需要用1根线表示,即位宽是1位。

add_cnt0和end_cnt0都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可,即位宽为1。

打开GVIM,在编辑模式下输入“Reg1”“Wire1”可调用至简设计法相应模板,补充完整后得到代码如下:
1
reg    [0:0]           cnt0  ;
wire                   add_cnt0;
wire                   end_cnt0;

cnt1是用always产生的信号,因此类型为regcnt1计数的最大值为800,那如何确定该值对应的位宽是多少呢?至简设计法在这里分享一个非常实用的技巧,打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入进去,就会获得对应的信号位宽。利用这一方法将cnt1的最大计数器800输入到计算器中,如下图所示,可以看出其位宽为10。本设计的数位比较小,这种方法在后续遇到比较大的数字时会方便很多,也不容易出错。
图片19.png

3.11-19通过计算器获取信号位宽

add_cnt1和end_cnt1都是用assign方式设计的,因此类型为wire,并且其值是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到cnt1、add_cnt1和end_cnt1的代码如下:
1
reg    [9:0]           cnt1  ;
wire                   add_cnt1;
wire                   end_cnt1;

cnt2是用always产生的信号,因此类型为reg。cnt2计数的最大值为525,需要用10根线表示,即位宽是10位。

add_cnt2和end_cnt2都是用assign方式设计的,因此类型为wire。并且其值是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到cnt2、add_cnt2和end_cnt2的代码如下:
reg    [9:0]            cnt2  ;
wire                   add_cnt2;
wire                   end_cnt2;

lcd_rgb是用always方式设计的,因此类型为reg。其位宽是16位,16根线表示即可。编辑模式下输入“Reg16”调用至简设计法模板,补充完整后得到代码如下:
1
reg    [15:0]          lcd_rgb;

hys和vys是用always方式设计的,因此类型为reg。并且其值是0或1,需要1根线表示即可。编辑模式下输入“Reg1”调用至简设计法模板,补充完整后得到代码如下:
1
2
reg                    hys  ;
reg                    vys  ;

valid_arearom_area是用always方式设计的,因此类型为reg。并且其值是01,用一根线表示即可。编辑模式下输入“Reg1”调用至简设计法模板,补充完整后得到代码如下:
1
2
reg                    valid_area ;
reg                    rom_area;

rom_addr是用always方式设计的,因此类型为reg。其表示范围是0~6599,最大值为6599,使用计算器得出需要13根线表示,即位宽为13位,因此代码如下:
1
reg  [12:0]            distance   ;

rom_data是例化模块的输出,不是用always方式设计的,因此类型为wire。其位宽为16位,需要16根线表示,编辑模式下输入“Reg16”调用至简设计法模板,补充完整后得到代码如下:
1
reg  [15:0]            rom_data   ;

至此,整个代码的设计工作已经完成。回顾一下本工程的整个代码设计,可以发现一共需要3个计数器,这里同样分享一个至简设计法代码模板,在“GVIM”中使用快捷命令“Jsq3”可以调出3个计数器的模板,调出的Jsq3”模板如下图所示,补充完整后可以得到完整的计数器代码。
图片20.png

3.11-20至简设计法调用3个计数器模板

最终得到整个工程的代码如下:
1
2
module vga_driver(
[size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        clk  ,
[size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        rst_n  ,
[size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        hys  ,
[size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        vys  ,
[size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        lcd_rgb
               );
[size=9.5000pt]
parameter     PICTURE_W    =   16 ;
parameter     ROW_W        =   10;
[size=9.5000pt]
parameter   X0_INIT = 273;
parameter   X1_INIT = 373;
parameter   Y0_INIT = 202;
parameter   Y1_INIT = 282;
parameter   X_GAP   = 5  ;
parameter   Y_GAP   = 4  ;
parameter   CNT_TIME= 300;
[size=9.5000pt]
parameter     TIME_1S      = 25000000;
parameter    X_CENT = 323;
parameter    Y_CENT = 242;
parameter    X_PRE_CENT = X_CENT + 141;
parameter    Y_PRE_CENT = Y_CENT +  32;
[size=9.5000pt]
parameter     WHITE        = 16'b11111_111111_11111;
parameter     RED          = 16'b11111_000000_00000;
parameter     GREEN        = 16'b00000_111111_00000;
parameter     BLUE         = 16'b00000_000000_11111;
parameter     YELLOW       = 16'b11111_111111_00000;
parameter     PURPLE       = 16'b01111_000000_10000;
parameter     CYAN         = 16'b01111_111111_11111;
parameter     PINK         = 16'b11111_011111_01111;
parameter     BLACK        = 16'b00000_000000_00000;
[size=9.5000pt]
input                  clk  ;
input                  rst_n  ;
[size=9.5000pt]
output                 hys  ;
output                 vys  ;
[size=9.5000pt]
[size=9.5000pt]
output [PICTURE_W-1:0] lcd_rgb;
reg    [PICTURE_W-1:0] lcd_rgb;
[size=9.5000pt]
reg    [ROW_W-1:0]     h_cnt;
reg    [ROW_W-1:0]     v_cnt;
reg                    hys;
reg                    hys_tmp;
reg                    vys;
reg                    vys_tmp;
reg                    valid_area ;
[size=9.5000pt]
reg[19:0]              distance   ;
reg                    rom_area;
reg[12:0]              rom_addr;
wire[7:0]              rom_data;
[size=9.5000pt]
[size=9.5000pt]
wire        add_h_cnt ;
wire        end_h_cnt ;
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
h_cnt<= 0;
    end
    else if(add_h_cnt) begin
        if(end_h_cnt)
h_cnt<= 0;
        else
h_cnt<= h_cnt+1 ;
   end
end
assign add_h_cnt = 1;
assign end_h_cnt = add_h_cnt&&h_cnt == 800-1 ;
[size=9.5000pt]
[size=9.5000pt]
wire        add_v_cnt ;
wire        end_v_cnt ;
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
v_cnt<= 0;
    end
    else if(add_v_cnt) begin
        if(end_v_cnt)
v_cnt<= 0;
        else
v_cnt<= v_cnt+1 ;
   end
end
assign add_v_cnt = end_h_cnt;
assign end_v_cnt = add_v_cnt&&v_cnt == 525-1 ;
[size=9.5000pt]
[size=9.5000pt]
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
hys_tmp<= 0;
    end
    else if(add_h_cnt&&h_cnt==96-1)begin
hys_tmp<= 1'b1;
    end
    else if(end_h_cnt)begin
hys_tmp<= 1'b0;
    end
end
[size=9.5000pt]
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
hys<= 1'b1;
    end
    else begin
hys<= hys_tmp;
    end
end
[size=9.5000pt]
[size=9.5000pt]        
[size=9.5000pt]
[size=9.5000pt]
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
vys_tmp<= 1'b0;
    end
    else if(v_cnt<10'd2)begin
vys_tmp<= 1'b0;
    end
    else begin
vys_tmp<= 1'b1;
    end
end   
[size=9.5000pt]
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
vys<= 1'b0;
    end
    else begin
vys<= vys_tmp;
    end
end
[size=9.5000pt]
[size=9.5000pt]
always  @(*)begin
valid_area = h_cnt>=141 &&h_cnt<=786 &&v_cnt>=32 &&v_cnt<=515;
end
[size=9.5000pt]
[size=9.5000pt]
always  @(*)begin
rom_area = h_cnt>=((323-60) + 141) &&h_cnt< ((323+60) + 141) &&v_cnt>= ((242-27)+32) &&v_cnt< ((242+28) +32);
end
[size=9.5000pt]
[size=9.5000pt]
[size=9.5000pt]
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
lcd_rgb<= 0;
    end
    else if(valid_area)begin
        if(rom_area)
lcd_rgb<= {rom_data[7:5],2'b11,rom_data[4:2],3'b111,rom_data[1:0],3'b111};
        else begin
lcd_rgb<= 16'b11111_111111_11111;
        end
    end
    else begin
lcd_rgb<= 0;
    end
end
[size=9.5000pt]
[size=9.5000pt]
always  @(*)begin
rom_addr = (h_cnt-((323-60)+141)) + 120*(v_cnt-((242-27)+32));
end
[size=9.5000pt]
[size=9.5000pt]

fpga_romu_fpga_rom(
[size=9.5000pt]        .address (rom_addr),
[size=9.5000pt]        .clock   (clk     ),
[size=9.5000pt]        .q       (rom_data));

endmodule


第4节 综合工程和上板4.1 新建工程
打开软件Quartus Ⅱ,点击“File”下拉列表中的New Project Wzard...新建工程选项,如下图所示。
图片21.png

3.11-21Quartus新建工程

随后会出现Quartus新建工程介绍,如下图所示,直接点击“Next”。
图片22.png

3.11-22Quartus新建工程介绍

此时出现的是工程文件夹、工程名、顶层模块名设置界面,如图3.11- 23所示。设置目录为:D:/mdy_book/picture_new_borad,工程名和顶层名为picture_new_borad。再次强调,为了避免初学者在后续操作中发生程序跳出未知错误的问题,强烈建议设置的文件目录和工程名称与本书保持一致。设置完成后点击“Next”。
23.png

3.11-23QUARTUS新建工程设置名称

新建工程类型设置如下图所示,选择“Empty project”,然后点击“Next”。
24.png

3.11-24QUARTUS新建工程类型

接下来进行文件添加,其界面如下图所示。点击右侧的“Add”按钮,选择之前写好的“picture_new_borad.v”文件,可以看到界面下方会显示出文件,之后点击“Next”。
25.png

3.11-25QUARTUS添加文件

3.11- 25为芯片选择页面,选择“Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”,之后点击“Next”。
26.png

3.11-26QUARTUS选择芯片型号

3.11- 27QUARTUS设置工具界面,不必做任何修改,直接点击“Next”。
27.png

3.11-27QUARTUS设置工具界面

下图为 QUARTUS新建工程汇总界面,可以看到新建工程的汇总情况,点击“Finish”,完成新建工程。
file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml5188\wps81.jpg
3.11-28QUARTUS新建工程汇总界面

4.2 综合
新建工程步骤完成后,就会出现如下图所示的QUARTUS新建工程后界面。
29.png

3.11-29QUARTUS新建工程后界面

点击编译按钮,可以对整个工程进行编译。编译成功的界面,如下图所示。
30.png

3.11-30QUARTUS编译后界面

4.3 配置管脚
下面需要对相应管脚进行配置。如下图所示,在菜单栏中选中“Assignments”,然后选择“Pin Planner”,随后就会弹出配置管脚的窗口。
31.png

3.11-31QUARTUS配置管脚选项

在配置窗口最下方中的“location”一列,参考信号和管脚关系,按照下表中最右两列配置好FPGA管脚,配置管理来源参见管脚配置环节,最终配置的结果如图3.11-32。配置完成后,关闭Pin Planner”,软件自动会保存管脚配置信息。

3.11 - 2信号和管脚关系
器件
信号线
信号线
FPGA管脚
内部信号
U6,U7
SEG_E
SEG0
Y6
seg_ment[2]
SEG_DP
SEG1
W6
未用到
SEG_G
SEG2
Y7
seg_ment[0]
SEG_F
SEG3
W7
seg_ment[1]
SEG_D
SEG4
P3
seg_ment[3]
SEG_C
SEG5
P4
seg_ment[4]
SEG_B
SEG6
R5
seg_ment[5]
SEG_A
SEG7
T3
seg_ment[6]
DIG1
DIG_EN1
T4
seg_sel[0]
DIG2
DIG_EN2
V4
seg_sel[1]
DIG3
DIG_EN3
V3
seg_sel[2]
DIG4
DIG_EN4
Y3
seg_sel[3]
DIG5
DIG_EN5
Y8
seg_sel[4]
DIG6
DIG_EN6
W8
seg_sel[5]
DIG7
DIG_EN7
W10
seg_sel[6]
DIG8
DIG_EN8
Y10
seg_sel[7]
X1

SYS_CLK
G1
clk
K1

SYS_RST
AB12
rst_n

32.png

3.11-32 QUARTUS配置管脚

4.4 再次综合
再次打开“QUARTUS”软件,在菜单栏中,选中“Processing”,然后选择“Start Compilation”,再次对整个工程进行编译和综合,如图3.11- 33所示。
33.png

3.11-33QUARTUS编译选项

当出现如下图所示的 QUARTUS编译成功标志,就说明编译综合成功。
34.png

3.11-34QUARTUS编译成功标志


4.5 连接开发板
完成编译后开始进行上板调试操作,按照下图的方式将下载器接入电脑USB接口,接上开发板电源,将开发板的VGA口连接到一台显示器上,然后按下下方蓝色开关,硬件连接完毕。
35.png

3.11-35开发板连接图

4.6 上板
打开QUARTUS界面,单击界面中的“file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml5188\wps89.jpg”,则会弹出配置界面。在界面中点击“add file”添加“.sof”文件后点击“Start”,会在“Progress”出现显示进度。
36.png

3.11-36QUARTUS界面

QUARTUS下载程序界面如下图所示,当进度条到100%提示成功后,即可在显示器上观察到相应的现象。
37.png

3.11-37QUARTUS下载程序界面

进度条提示成功后,如果操作无误此时可以在显示器上中心看到一个至简设计法LOGO,其他显示区域为白色。如果没有显示成功,就需要返回检查一下连接是否到位,代码是否编写正确。如果无法自己完成错误排查的话,可以重新按照步骤操作一遍,相信一定会达到想要的效果。

第5节 简化版步骤分享
这里依旧会分享简化版的步骤,方便掌握基础原理后进行反复操作复习。

5.1 设计实现5.1.1 顶层接口
新建目录:D:\mdy_book\picture_new_borad在该目录中,新建一个名为picture_new_borad.v的文件,用GVIM打开后开始编写代码。

确定顶层信号,信号和管脚的对应关系见表3.11- 2。

3.11 - 2信号和管脚关系
器件
电阻网络转换后
信号线
信号线
FPGA管脚
FPGA工程信号
CN1
VGA_RED
VGA_R4
E11
lcd_rgb[15]
VGA_R3
C10
lcd_rgb[14]
VGA_R2
D10
lcd_rgb[13]
VGA_R1
E9
lcd_rgb[12]
VGA_R0
E10
lcd_rgb[11]
VGA_GREEN
VGA_G5
D15
lcd_rgb[10]
VGA_G4
C17
lcd_rgb[9]
VGA_G3
C19
lcd_rgb[8]
VGA_G2
E12
lcd_rgb[7]
VGA_G1
C13
lcd_rgb[6]
VGA_G0
E15
lcd_rgb[5]
VGA_BLUE
VGA_B4
D13
lcd_rgb[4]
VGA_B3
E13
lcd_rgb[3]
VGA_B2
D17
lcd_rgb[2]
VGA_B1
E16
lcd_rgb[1]
VGA_B0
C15
lcd_rgb[0]
VGA_HSYNC
VGA_HSYNC
C20
hys
VGA_VSYNC
VGA_VSYNC
D20
vys
X1

SYS_CLK
G1
clk
K1

SYS_RST
AB12
rst_n

写出顶层信号代码:
1
2
3
4
5
6
7
module picture_new_borad (
clk  ,
rst_n  ,
lcd_hs  ,
lcd_vs  ,
lcd_rgb
    );

声明输入输出属性:
1
2
3
4
5
input                   clk  ;
input                   rst_n  ;
output                  lcd_hs  ;
output                  lcd_vs  ;
output  [15:0]          lcd_rgb       ;

5.1.2 信号设计
首先进行架构设计。设计目标中确定显示器中需要显示640*480分辨率的图像,因此使用下表中的第一种分辨率。

3.11-1 VGA常用分辨率
分辨率
/
同步脉冲
显示后沿
显示区域
显示前沿
帧长
单位
640*480
/60Hz
96
48
640
16
800
基准时钟
2
33
480
10
525
800*600
/72Hz
120
64
800
56
1040
基准时钟
6
23
600
37
666
800*600
/60Hz
128
88
800
40
1056
基准时钟
4
23
600
1
628
1024*768
/60Hz
136
160
1024
24
1344
基准时钟
6
29
768
3
806

分析设计目标可得VGA时序中的行同步信号,其时序图如下所示:

3.11-12带时间信息的VGA行同步时序

设计计数器架构,表示计数基准时间的计数器cnt0代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end
[size=9.5000pt]
assign add_cnt0 = [size=9.5000pt]1[size=9.5000pt];
assign end_cnt0 = add_cnt0 && cnt0== 2 -1[size=9.5000pt];

表示计数hys长度的计数器cnt1代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end
[size=9.5000pt]
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==800-1 ;

设计行同步信号的代码如下:
1
2
3
4
5
6
7
8
9
10
11
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
hys<= 0;
    end
    else if(add_cnt1 && cnt1==96-1)begin
hys<= 1;
    end
    else if(end_cnt1)begin
hys<= 0;
    end
end


设计VGA场同步时序计数器cnt2代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt2 <= 0;
    end
    else if(add_cnt2)begin
        if(end_cnt2)
            cnt2 <= 0;
        else
            cnt2 <= cnt2 + 1;
    end
end
[size=9.5000pt]
assign add_cnt2 = end_cnt1;
assign end_cnt2 = add_cnt2 && cnt2==525-1 ;

设计场同步信号的代码如下:
1
2
3
4
5
6
7
8
9
10
11
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
vys<= 1'b0;
    end
    else if(add_cnt2 && cnt2 == 2 - 1)begin
vys<= 1'b1;
    end
    else if(end_cnt2)begin
vys<= 1'b0;
    end
end

设计lcd_rgb信号代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
lcd_rgb<= 16'h0;
    end
    else if(valid_area)begin
        if(rom_area)begin
lcd_rgb<= rom_data;
        end
        else begin
lcd_rgb<= 16'b11111_111111_11111;
        end
    end
    else begin
lcd_rgb<= 0;
    end
end

设计rom_addr的信号:
1
2
3
always@(*)begin
rom_addr = (cnt1-96-48-320+60-1) + 120*(cnt2-2-33-240+27)
end

至此,主体程序已经完成,接下来将module补充完整。

5.1.3 信号定义
接下来定义信号类型,cnt0的信号定义如下:
1
reg    [9:0]           cnt1  ;

add_cnt1end_cnt1的信号定义如下:
1
2
wire                   add_cnt1;
wire                   end_cnt1;

cnt2的信号定义如下:
1
reg    [9:0]           cnt2  ;

add_cnt2end_cnt2的信号定义如下:
1
2
wire                   add_cnt2;
wire                   end_cnt2;

lcd_rgb的信号定义如下:
1
reg    [15:0]          lcd_rgb;

hysvys的信号定义如下:
1
2
reg                    hys  ;
reg                    vys  ;

valid_arearom_area的信号定义如下:
1
2
reg                    valid_area ;
reg                    rom_area;

rom_addr的信号定义如下:
1
reg  [12:0]            distance   ;

rom_dat的信号定义如下:
1
reg  [15:0]            rom_data   ;

至此,整个代码的设计工作已经完成。最终得到完整的设计代码如下:
1
2
module vga_driver(
[size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        clk  ,
[size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        rst_n  ,
[size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        hys  ,
[size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        vys  ,
[size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        [size=9.5000pt]        lcd_rgb
               );
[size=9.5000pt]
parameter     PICTURE_W    =   16 ;
parameter     ROW_W        =   10;
[size=9.5000pt]
parameter   X0_INIT = 273;
parameter   X1_INIT = 373;
parameter   Y0_INIT = 202;
parameter   Y1_INIT = 282;
parameter   X_GAP   = 5  ;
parameter   Y_GAP   = 4  ;
parameter   CNT_TIME= 300;
[size=9.5000pt]
parameter     TIME_1S      = 25000000;
parameter    X_CENT = 323;
parameter    Y_CENT = 242;
parameter    X_PRE_CENT = X_CENT + 141;
parameter    Y_PRE_CENT = Y_CENT +  32;
[size=9.5000pt]
parameter     WHITE        = 16'b11111_111111_11111;
parameter     RED          = 16'b11111_000000_00000;
parameter     GREEN        = 16'b00000_111111_00000;
parameter     BLUE         = 16'b00000_000000_11111;
parameter     YELLOW       = 16'b11111_111111_00000;
parameter     PURPLE       = 16'b01111_000000_10000;
parameter     CYAN         = 16'b01111_111111_11111;
parameter     PINK         = 16'b11111_011111_01111;
parameter     BLACK        = 16'b00000_000000_00000;
[size=9.5000pt]
input                  clk  ;
input                  rst_n  ;
[size=9.5000pt]
output                 hys  ;
output                 vys  ;
[size=9.5000pt]
[size=9.5000pt]
output [PICTURE_W-1:0] lcd_rgb;
reg    [PICTURE_W-1:0] lcd_rgb;
[size=9.5000pt]
reg    [ROW_W-1:0]     h_cnt;
reg    [ROW_W-1:0]     v_cnt;
reg                    hys;
reg                    hys_tmp;
reg                    vys;
reg                    vys_tmp;
reg                    valid_area ;
[size=9.5000pt]
reg[19:0]              distance   ;
reg                    rom_area;
reg[12:0]              rom_addr;
wire[7:0]              rom_data;
[size=9.5000pt]
[size=9.5000pt]
wire        add_h_cnt ;
wire        end_h_cnt ;
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
h_cnt<= 0;
    end
    else if(add_h_cnt) begin
        if(end_h_cnt)
h_cnt<= 0;
        else
h_cnt<= h_cnt+1 ;
   end
end
assign add_h_cnt = 1;
assign end_h_cnt = add_h_cnt&&h_cnt == 800-1 ;
[size=9.5000pt]
[size=9.5000pt]
wire        add_v_cnt ;
wire        end_v_cnt ;
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
v_cnt<= 0;
    end
    else if(add_v_cnt) begin
        if(end_v_cnt)
v_cnt<= 0;
        else
v_cnt<= v_cnt+1 ;
   end
end
assign add_v_cnt = end_h_cnt;
assign end_v_cnt = add_v_cnt&&v_cnt == 525-1 ;
[size=9.5000pt]
[size=9.5000pt]
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
hys_tmp<= 0;
    end
    else if(add_h_cnt&&h_cnt==96-1)begin
hys_tmp<= 1'b1;
    end
    else if(end_h_cnt)begin
hys_tmp<= 1'b0;
    end
end
[size=9.5000pt]
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
hys<= 1'b1;
    end
    else begin
hys<= hys_tmp;
    end
end
[size=9.5000pt]
[size=9.5000pt]        
[size=9.5000pt]
[size=9.5000pt]
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
vys_tmp<= 1'b0;
    end
    else if(v_cnt<10'd2)begin
vys_tmp<= 1'b0;
    end
    else begin
vys_tmp<= 1'b1;
    end
end   
[size=9.5000pt]
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
vys<= 1'b0;
    end
    else begin
vys<= vys_tmp;
    end
end
[size=9.5000pt]
[size=9.5000pt]
always@(*)begin
valid_area = h_cnt>=141 &&h_cnt<=786 &&v_cnt>=32 &&v_cnt<=515;
end
[size=9.5000pt]
[size=9.5000pt]
always@(*)begin
rom_area = h_cnt>=((323-60) + 141) &&h_cnt< ((323+60) + 141) &&v_cnt>= ((242-27)+32) &&v_cnt< ((242+28) +32);
end
[size=9.5000pt]
[size=9.5000pt]
[size=9.5000pt]
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
lcd_rgb<= 0;
    end
    else if(valid_area)begin
        if(rom_area)
lcd_rgb<= {rom_data[7:5],2'b11,rom_data[4:2],3'b111,rom_data[1:0],3'b111};
        else begin
lcd_rgb<= 16'b11111_111111_11111;
        end
    end
    else begin
lcd_rgb<= 0;
    end
end
[size=9.5000pt]
[size=9.5000pt]
always  @(*)begin
rom_addr = (h_cnt-((323-60)+141)) + 120*(v_cnt-((242-27)+32));
end
[size=9.5000pt]
[size=9.5000pt]
[size=9.5000pt]
fpga_romu_fpga_rom(
[size=9.5000pt]        .address (rom_addr),
[size=9.5000pt]        .clock   (clk     ),
[size=9.5000pt]        .q       (rom_data));

endmodule

5.2 综合工程和上板5.2.1 新建工程
下一步新建工程和上板查看现象。打开软件Quartus Ⅱ,点击“File”下拉列表中的New Project Wzard...新建工程选项。
图片21.png

3.11-21Quartus新建工程

直接点击“Next”。
图片22.png

3.11-22 Quartus新建工程介绍

此时会出现的是工程文件夹、工程名、顶层模块名设置界面(目录为:D:/mdy_book/picture_new_borad,工程名和顶层名为picture_new_borad),完成设置后点击“Next”。
23.png

3.11-23 QUARTUS新建工程设置名称

选择“Empty project”后点击“Next”。
24.png

3.11-24 QUARTUS新建工程类型

点击右侧的“Add”按钮后选择“picture_new_borad.v”文件后点击“Next”,完成文件添加。
25.png

3.11-25 QUARTUS添加文件

对芯片型号进行选择,在“Device family”选项中选择“Cyclone ⅣE”,“Available devices”选项下选择“EP4CE15F23C8”随后点击“Next”。
26.png

3.11-26 QUARTUS选择芯片型号

直接点击“Next”。
27.png
3.11-27 QUARTUS设置工具界面

点击“Finish”,完成新建工程。
file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml5188\wps100.jpg
3.11-28QUARTUS新建工程汇总界面

5.2.2 综合
新建工程后界面如下图所示,点击“编译”。
29.png

3.11-29 QUARTUS新建工程后界面

编译成功如下图所示。
30.png

3.11-30 QUARTUS编译后界面


5.2.3 配置管脚
进行管脚配置,在菜单栏中点击“Assignments”后点击“Pin Planner”,此时会弹出配置管脚的窗口。
31.png

3.11-31 QUARTUS配置管脚选项

在配置窗口“location”根据信号和管脚关系配置管脚,配置完成关闭“Pin Planner”即可自动保存配置信息。
32.png

3.11-32 QUARTUS配置管脚


5.2.4 再次综合
再次打开“QUARTUS”软件,在菜单栏中选择“Processing”,随后点击“Start Compilation”再次进行综合。
33.png

3.11-33 QUARTUS编译选项

出现 QUARTUS 编译成功标志时表示此次编译成功。
34.png

3.11-34 QUARTUS编译成功标志


5.2.5 连接开发板
下载器接入电脑 USB 接口,将开发板接上电源,开发板的VGA口连接到一台显示器上后按下蓝色开关。
35.png

3.11-35开发板连接图


5.2.6 上板
打开 QUARTUS 界面后单击 111111111.png ”图标。
36.png

3.11-36 QUARTUS界面

点击“add file”,添加.sof文件,完成添加后点击“Start”,在“Progress”会显示进度,当进度条显示“100%”为成功,可观察显示器现象。如果此时可以看到开发板连接的显示器显示出了设计目标需要的画面,即代表设计成功。
37.png
3.11-37 QUARTUS下载程序界面

第6节 扩展练习
至此,VGA显示图像设计已经完成,相信同学们已经可以完全掌握这一设计。那么在掌握这项工程后可以多做一些思考,尝试在工程原理不变的基础上进行一定的数据调整,试着改变图像显示区域或者改变显示图像内容,挑战一下独立完成多个设计。也欢迎有更多思路和想法的读者前往至简设计法论坛上跟进行交流讨论。





上一篇:【FPGA至简设计原理与应用】书籍连载19 第三篇 FPGA至简设计项目 第十章 VGA显示圆
下一篇:【FPGA至简设计原理与应用】书籍连载21 第三篇FPGA至简设计项目 第十二章信号发生器
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


QQ|手机版|小黑屋|MDYBBS ( 粤ICP备16061416号-1

GMT+8, 2020-9-24 06:43 , Processed in 0.638743 second(s), 16 queries , File On.

Powered by Discuz! X3.4

本论坛由广州健飞通信有限公司所有

© 2001-2019 Comsenz Inc.

快速回复 返回顶部 返回列表