明德扬肖老师 发表于 2020-6-4 15:22:30

【FPGA至简设计原理与应用】书籍连载18 第三篇 FPGA至简设计项目 第九章 VGA显示矩阵

温馨提示:明德扬2023推出了全新课程——逻辑设计基本功修炼课,降低学习FPGA门槛的同时,增加了学习的趣味性,并组织了考试赢积分活动(点击→了解课程详情)http://www.mdy-edu.com/ffkc/415.html,感兴趣请联系易老师:13112063618(微信同步)
大家好,近期我们会连载《FPGA至简设计原理与应用》一书,有兴趣的同学可以学习,也希望大家可以对我们的书提出宝贵的意见和建议。
《FPGA至简设计原理与应用》书籍连载索引目录
http://www.fpgabbs.cn/forum.php?mod=viewthread&tid=989


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

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





第三篇 FPGA至简设计项目实践

第九章 VGA显示矩阵


本文档编号:001700000021
需要看对应的视频,请点击视频编号:001700000442
1、至间原理与应用配套的案例和PPT讲解
2、本设计需要通过VGA线将显示器和开发板进行连接,FPGA在连接成功后产生640*480分辨率,刷新频率为60Hz的VGA时序,使得显示器产生显示一幅完整的矩阵图像。这幅矩阵图像即为显示屏边缘上显示一个宽为20像素的红色边框,在屏幕的中央显示一个长为150像素、高为100像素的绿色矩形。步骤性教
3、Altera和Xilinx入门学习案例文档

第1节 项目背景

VGA的显示背景与原理在上一章“VGA显示颜色”的案例分享中已经有了比较详细的解释,这里就不再进行展开讲解,如果还有不理解的读者朋友可以回到上一章中的项目背景部分进行阅读学习。
第2节 设计目标
上一章实现了VGA显示颜色的设计,这一章中将进行更高一级的设计学习——VGA显示矩阵图像。按照至简设计法的思路,在进行设计之前首先明确设计目标。明确了设计目标后,后续的每一步操作都是围绕设计目标进行展开。如果没有牢记设计目标就开始动手进行实践操作,最终的作品也是东拼西凑的产物,一旦在设计过程中出现了问题就需要花费大量的精力进行寻找修复。建议初学者在开始学习时就养成良好的设计习惯,才能在后续的职业生涯中受益。
本设计需要通过VGA连接线将显示器和开发板进行连接,FPGA在连接成功后产生640*480分辨率,刷新频率为60Hz的VGA时序,使得显示器产生显示一幅完整的矩阵图像。这幅矩阵图像即为显示屏边缘上显示一个宽为20像素的红色边框,在屏幕的中央显示一个长为150像素、高为100像素的绿色矩形。显示器一般都具有分辨率自适应功能,无须设置就能识别不同分辨率的图像。本设计的相应参数参见表3.9-1中的第一行。其中,行的单位为“基准时钟”,即频率为25MHz、周期为40ns的时钟,列的单位则为“行”,请读者朋友们一定要注意区分。表3.9–1 VGA常用分辨率
分辨率行/列同步脉冲显示后沿显示区域显示前沿帧长单位
640*480/60Hz行964864016800基准时钟
列23348010525行
800*600/72Hz行12064800561040基准时钟
列62360037666行
800*600/60Hz行12888800401056基准时钟
列4236001628行
1024*768/60Hz行1361601024241344基准时钟
列6297683806行

设计完成后,通过VGA连接线将显示器和开发板的VGA接口相连,连接示意图如下:图3.9-1教学板连接示意图
上板后显示器展示效果图如下图所示,不同的显示器会有一定的色差,需要以实际显示情况为主。可以看到显示屏边缘上显示一个红色边框(边框宽为20像素),在屏幕的中央显示一个绿色矩形(矩形长为150像素,高为100像素),实现了矩阵图像。想要观看连接后演示视频效果的读者朋友可以登陆至简设计法官网学习:www.mdy-edu.com/xxxx。图3.9-2VGA显示矩阵效果图
第3节 设计实现确定了设计目标后本书会逐步分析讲解工程的制作步骤。建议初学者认真学习每一步,因为这里分享给同学们的不仅仅是案例,还有在操作过程中的一些设计理念及原理。当然本书也会分享一些至简设计法的设计技巧,最终希望每一位读者都可以具备独立设计工程的能力。当然已经拥有扎实的功底、只是想要根据步骤完成项目的读者朋友们可以跳过此部分,直接进入第五节中的简略版操作步骤分享。
3.1 顶层接口
新建目录:D:\mdy_book\rec_exec1,在该目录中新建一个名为rec_exec1.v的文件。用GVIM打开后开始编写代码。这里再次强调,初学者一定要按照本书提供的文件路径以及文件名进行设置,避免后面出现未知错误。
分析设计目标可知,本设计中FPGA产生VGA时序,即控制VGA_R4~R0、VGA_G5~G0、VGA_B4~B0、VGA_HSYNC和VGA_VSYNC,从而使显示器呈红色、绿色的矩阵显示。其中,FPGA可根据时序产生高低电平从而控制VGA_HSYNC和VGA_VSYNC。而颜色数据,由于本设计需要的显示的颜色数据是固定的红色和绿色,可通过FPGA自身产生而不需要外部输入图像的数据。那么在FPGA的工程设计中可以定义输出信号hys表示行同步,定义输出信号vys表示场同步,定义一个16位的信号lcd_rgb进行RGB输出,其中lcd_rgb表示VGA_R4~0、lcd_rgb表示VGA_G5~0、lcd_rgb表示VGA_B4~0。当然,本设计中还需要时钟信号clk和复位信号rst_n来进行工程控制。
综上所述,本工程需要五个信号:时钟信号clk,复位信号rst_n,场同步信号vys、行同步信号hys和RGB输出信号lcd_rgb。信号和硬件的对应关系如下表所示。
表3.9 - 2信号和管脚关系
器件电阻网络转换后信号线信号线FPGA管脚FPGA工程信号
CN1VGA_REDVGA_R4E11lcd_rgb
VGA_R3C10lcd_rgb
VGA_R2D10lcd_rgb
VGA_R1E9lcd_rgb
VGA_R0E10lcd_rgb
VGA_GREENVGA_G5D15lcd_rgb
VGA_G4C17lcd_rgb
VGA_G3C19lcd_rgb
VGA_G2E12lcd_rgb
VGA_G1C13lcd_rgb
VGA_G0E15lcd_rgb
VGA_BLUEVGA_B4D13lcd_rgb
VGA_B3E13lcd_rgb
VGA_B2D17lcd_rgb
VGA_B1E16lcd_rgb
VGA_B0C15lcd_rgb
VGA_HSYNCVGA_HSYNCC20hys
VGA_VSYNCVGA_VSYNCD20vys
X1
SYS_CLKG1clk
K1
SYS_RSTAB12rst_n

通过以上分析可以写出顶层信号代码。将module的名称定义为color_exec1,已经知道该模块有五个信号:clk、rst_n、lcd_hs、lcd_vs和lcd_rgb,将与外部相连接的输入/输出信号列出,从而实现信号与管脚的连接。具体顶层代码如下:
1234567module rec_rect1(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位。根据以上分析补充输入输出端口定义,其具体代码如下:
12345input                   clk;input                   rst_n;output                  lcd_hs;output                  lcd_vs;output          lcd_rgb       ;


3.2 信号设计
分析设计目标可知,首先需要设计行同步信号hys,其时序图表示如下:图3.9-3VGA行同步信号时序
根据时序图可以看到,hys就是一个周期性进行高低变化的脉冲。根据设计目标可知图像分辨率选定为640*480,因此使用下表中的640*480分辨率的相应参数。即同步脉冲a的时间是96个基准时钟,而显示后沿b的时间是48个基准时钟周期,显示时序c的时间是640个基准时钟,显示前沿的时间是16个基准时钟,共计800个基准时钟(800=96+48+640+16)。
表3.9–1 VGA常用分辨率
分辨率行/列同步脉冲显示后沿显示区域显示前沿帧长单位
640*480/60Hz行964864016800基准时钟
列23348010525行
800*600/72Hz行12064800561040基准时钟
列62360037666行
800*600/60Hz行12888800401056基准时钟
列4236001628行
1024*768/60Hz行1361601024241344基准时钟
列6297683806行

这里需要注意,一个基准时钟是40ns,而至简设计法开发板的时钟周期是20ns,因此基于至简设计法开发板的VGA工程设计中,采用2个时钟时间代表一个基准时钟时间。在图中补充对应的时间信号,带有时间信号的时序图如下图所示。图3.9-4带时间信息的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条件和计数数量后开始进行代码编写。相信各位往常都是一行行输入代码,但是至简设计法有一个小技巧,可以为大家编写代码省去不少时间,并且一定程度上降低了代码的出错率。至简设计法将日常代码中常用到的固定部分做成了模板,进行代码编程时可以调用相应模板后根据逻辑输入对应设计的变量将代码补充完整。这里就可以用模板编写计数器代码,感受一下这个炫酷的功能。
打开GVIM,在命令模式下输入“:Mdyjsq”,点击回车,就调出了对应模板,如下图所示。随后再将本案例中的变量填到模板里面,就可以得到完整正确的计数器代码。图3.9-5至简设计法调用计数器代码模板
补充完整后得到计数基准时间的计数器cnt0代码如下。
12345678910111213always @(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;    endend assign add_cnt0 = 1;assign end_cnt0 = add_cnt0 && cnt0== 2 -1;

接着讨论用于计数hys长度的计数器cnt1。根据设计目标可知一行需要800个基准时钟,因此其计数数量为800。前文设计中已经确定一个基准时钟可以用end_cnt0表示,因此计数器cnt1的加1条件为“end_cnt0”,可写成:assign add_cnt1 = end_cnt0。继续调用至简设计法模板,在命令模式下输入“:Mdyjsq”,点击回车调出对应模板后将“add_cnt1”和“end_cnt1”补充完整,得到该计数器的代码如下:
12345678910111213always @(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;    endend 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”,调用至简设计法模板,将模板补充完整后得到场同步信号的代码如下:
1234567891011always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginhys<= 0;    end    else if(add_cnt1 && cnt1==96-1)beginhys<= 1;    end    else if(end_cnt1)beginhys<= 0;    endend

接下来讨论vys信号的设计,根据设计目标可以得到VGA场同步信号的时序图如下所示:图3.9-6VGA场同步信号时序
可以看出vys也是一个周期性地进行高低变化的脉冲。本设计中图像分辨率选定为640*480,因此使用表3.9- 1中的640*480分辨率的相应参数。同步脉冲a的时间是2行的时间,显示后沿b的时间是33行,显示时序c的时间是480行,显示前沿的时间是10行,共计525行(525=2+33+480+10)。这里需要注意:行的单位为“基准时钟”,前文设计中使用计数器cnt0表示一个基准时钟,cnt1表示一行。由于场同步信号是的单位是“行”,因此可以使用cnt1来辅助进行场同步信号的表示,即cnt1计数结束则代表一“行”结束。
在场同步信号中补充时间信号,得到带有时间信号的时序图如下所示。图3.9-7带时间信息的VGA场同步时序
很显然,若要产生这一时序还需要另1个计数器,将该计数器命名为cnt2。该计数器的作用是计数行的数量,所以cnt2的加1条件为一行结束,即end_cnt1,可写成:assign add_cnt2 = end_cnt1。从上图可知,该计数器的计数数量为525。综上所述得到该计数器的代码如下所示:
12345678910111213always @(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;    endend assign add_cnt2 = end_cnt1;assign end_cnt2 = add_cnt2 && cnt2==525-1 ;

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

图3.9-8VGA显示矩阵效果图
最后还需要设计lcd_rgb信号。从设计目标可知显示器中一共需要显示三种颜色:红色、绿色和白色。上一章节中列出了设计使用颜色的相应信号值,当lcd_rgb等于16’b11111_000000_00000时表示红色;lcd_rgb等于16’b00000_111111_00000时表示绿色;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))。
在显示区域中包含了红色区域、绿色区域和白色区域。下面按照VGA显示矩阵效果图将其翻译为代码表示。
代码编写时可以将红色区域看为单独的四个长方形,因此红色区域可表示为:
(cnt1>=(96+48)&&cnt1<(96+48+20)) 或者(cnt1>=(96+48+620)&&cnt1<(96+48+640)) 或者(cnt2>= (2+33) &&cnt2<(2+33+20)) 或者          (cnt2>= (2+33+460) &&cnt2<(2+33+480))此时,lcd_rgb的输出为“16’b11111_000000_00000”;绿色区域可以看做一个完整的长方形,绿色区域可表示为:(cnt1>=(96+48+320-75)&&cnt1<(96+48+320+75)) 并且(cnt2>= (2+33+240-50) &&cnt2<(2+33+240+50)) 此时,lcd_rgb的输出为“16’b00000_111111_00000”;
本项目一共需要显示三种颜色,绿色和红色区域已经表示出来了,那么白色区域即为显示区域中非红色区域并且非绿色区域的部分,此时lcd_rgb输出“16’b11111_111111_11111”。综上所述,可以写出lcd_rgb的代码如下:
12345678910111213141516171819always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginlcd_rgb<= 16'h0;    end    else if(cnt1 >=(96+48) && cnt1 <(96+48+640) && cnt2 >=(2+33) && cnt2 < (2+33+480))begin //显示区域if((cnt1 >= (96+48) && cnt1 < (96+48+20)) ||                 (cnt1 >= (96+48+620) && cnt1 < (96+48+640)) ||                 (cnt2 >= (2+33) && cnt2 < (2+33+20)) ||                 (cnt2 >= (2+33+460) && cnt2 < (2+33+480)))begin//红色区域lcd_rgb<= 16'b11111_000000_00000;      end      else if((cnt1 >= (96+48+320-75) && cnt1 < (96+48+320+75)) && (cnt2 >= (2+33+240-50) && cnt2 < (2+33+240+50)))begin//绿色区域lcd_rgb<= 16'b00000_111111_00000;      end      else begin//白色区域lcd_rgb<= 16'b11111_111111_11111;      end    end    else beginlcd_rgb<= 0;    endend

至此,主体程序已经完成。
3.3 信号定义
接下来将module补充完整,首先来定义信号类型。再次强调,在进行reg和wire的判断的时候,总容易存在多余的联想,比如认为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根线表示即可。
编辑模式下输入“Reg1”“Wire1”可调用至简设计法模板,补充完整后得到代码如下:
1reg             cnt0;wire                   add_cnt0;wire                   end_cnt0;

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

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

cnt2是用always产生的信号,因此类型为reg。cnt2计数的最大值为525,需要用10根线表示,即位宽是10位。其具体代码如下:
1reg             cnt2;

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

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

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

至此,整个代码的设计工作已经完成。回顾一下本工程的整个代码设计,可以发现一共需要3个计数器,这里同样分享一个至简设计法代码模板,在“GVIM”中使用快捷命令“Jsq3”可以调出3个计数器的模板,调出的“Jsq3”模板如下图所示,补充完整后可以得到完整的计数器代码。图3.9-10至简设计法调用3个计数器模板
最终得到整个工程的代码如下:
12 module color_exec1(clk,rst_n,lcd_hs,lcd_vs,lcd_rgb    ); input                   clk;input                   rst_n;output                  lcd_hs;output                  lcd_vs;output          lcd_rgb       ; reg                cnt0;wire                   add_cnt0;wire                   end_cnt0; reg                cnt1;wire                   add_cnt1;wire                   end_cnt1;reg                cnt2;wire                   add_cnt2;wire                   end_cnt2;reg             lcd_rgb; reg                  hys;reg                  vys; 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;    endend assign add_cnt0 = 1;assign end_cnt0 = add_cnt0 && cnt0== 2 -1; 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;    endend assign add_cnt1 = end_cnt0;assign end_cnt1 = add_cnt1 && cnt1==800-1 ; 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;    endend assign add_cnt2 = end_cnt1;assign end_cnt2 = add_cnt2 && cnt2==525-1 ; always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginhys<= 0;    end    else if(add_cnt1 && cnt1==96-1)beginhys<= 1;    end    else if(end_cnt1)beginhys<= 0;    endend always@(posedge clk or negedge rst_n)begin    if(!rst_n)beginvys<= 1'b0;    end    else if(add_cnt2 && cnt2 == 2 - 1)beginvys<= 1'b1;    end    else if(end_cnt2)beginvys<= 1'b0;    endend always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginlcd_rgb<= 16'h0;    end    else if(cnt1 >=(96+48) && cnt1 <(96+48+640) && cnt2 >=(2+33) && cnt2 < (2+33+480))begin //显示区域if((cnt1 >= (96+48) && cnt1 < (96+48+20)) ||                 (cnt1 >= (96+48+620) && cnt1 < (96+48+640)) ||                 (cnt2 >= (2+33) && cnt2 < (2+33+20)) ||                 (cnt2 >= (2+33+460) && cnt2 < (2+33+480)))begin//红色区域lcd_rgb<= 16'b11111_000000_00000;      end      else if((cnt1 >= (96+48+320-75) && cnt1 < (96+48+320+75)) && (cnt2 >= (2+33+240-50) && cnt2 < (2+33+240+50)))begin//绿色区域lcd_rgb<= 16'b00000_111111_00000;      end      else begin//白色区域lcd_rgb<= 16'b11111_111111_11111;      end    end    else beginlcd_rgb<= 0;    endendendmodule

下一步是新建工程和上板查看现象。

第4节 综合与上板4.1 新建工程
打开软件Quartus Ⅱ,点击“File”下拉列表中的New Project Wzard...新建工程选项,如下图所示。图3.9-11Quartus新建工程
随后会出现Quartus新建工程介绍,如下图所示,直接点击“Next”。图3.9-12Quartus新建工程介绍
此时会出现工程文件夹、工程名、顶层模块名设置界面,如图3.9- 13所示。设置目录为:D:/mdy_book/rec_exec1,工程名和顶层名为rec_exec1。再次强调,为了避免初学者在后续操作中发生程序跳出未知错误的问题,强烈建议设置的文件目录和工程名称与本书保持一致。设置完成后点击“Next”。图3.9-13QUARTUS新建工程设置名称
新建工程类型设置如下图所示,选择“Empty project”,然后点击“Next”。图3.9-14QUARTUS新建工程类型
接下来进行文件添加,其界面如下图所示。点击右侧的“Add”按钮,选择之前写好的“rec_exec1.v”文件,可以看到界面下方会显示出文件,之后点击“Next”。图3.9-15QUARTUS添加文件
图3.9-16为芯片选择页面,选择“Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”,之后点击“Next”。图3.9-16QUARTUS选择芯片型号
图3.9- 17为QUARTUS设置工具界面,不必做任何修改,直接点击“Next”。图3.9-17QUARTUS设置工具界面
下图为QUARTUS新建工程汇总界面,可以看到新建工程的汇总情况,点击“Finish”完成新建工程。图3.9-18QUARTUS新建工程汇总界面
4.2 综合
新建工程步骤完成后,就会出现如下图所示的 QUARTUS新建工程后界面。图3.9-19QUARTUS新建工程后界面
点击编译按钮,可以对整个工程进行编译。编译成功的界面如下图所示。图3.9-20QUARTUS编译后界面
4.3 配置管脚
下面需要对相应管脚进行配置。如下图所示,在菜单栏中选中“Assignments”,然后选择“Pin Planner”,随后就会弹出配置管脚的窗口。图3.9-21QUARTUS配置管脚选项
在配置窗口最下方中的“location”一列,参考信号和管脚关系,按照表3.9- 2中最右两列配置好FPGA管脚,配置管理来源参见管脚配置环节,最终配置的结果如图3.9-22。配置完成后,关闭Pin Planner,软件自动会保存管脚配置信息。
表3.9 - 2信号和管脚关系
器件信号线信号线FPGA管脚内部信号
U6,U7SEG_ESEG0Y6seg_ment
SEG_DPSEG1W6未用到
SEG_GSEG2Y7seg_ment
SEG_FSEG3W7seg_ment
SEG_DSEG4P3seg_ment
SEG_CSEG5P4seg_ment
SEG_BSEG6R5seg_ment
SEG_ASEG7T3seg_ment
DIG1DIG_EN1T4seg_sel
DIG2DIG_EN2V4seg_sel
DIG3DIG_EN3V3seg_sel
DIG4DIG_EN4Y3seg_sel
DIG5DIG_EN5Y8seg_sel
DIG6DIG_EN6W8seg_sel
DIG7DIG_EN7W10seg_sel
DIG8DIG_EN8Y10seg_sel
X1
SYS_CLKG1clk
K1
SYS_RSTAB12rst_n
图3.9-22 QUARTUS配置管脚4.4 再次综合
再次打开“QUARTUS”软件,在菜单栏中选中“Processing”,然后选择“Start Compilation”,再次对整个工程进行编译和综合,如图3.9- 23所示。图3.9-23QUARTUS编译选项
当出现如下图所示的QUARTUS编译成功标志则说明编译综合成功。图3.9-24QUARTUS编译成功标志
4.5 连接开发板
完成编译后开始进行上板调试操作,按照下图的方式将下载器接入电脑USB接口,接上开发板电源,将开发板的VGA口连接到一台显示器上,然后按下下方蓝色开关,硬件连接完毕。图3.9-25开发板连接图
4.6 上板
打开QUARTUS界面,单击界面中的“file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml9492\wps69.jpg”,则会弹出配置界面。在界面中点击“add file”添加“.sof”文件后点击“Start”,会在“Progress”出现显示进度。图3.9-26QUARTUS界面
QUARTUS下载程序界面如下图所示,当进度条到100%提示成功后,即可在显示器上观察到相应的现象。图3.9-27QUARTUS下载程序界面
进度条提示成功后,如果操作无误此时可以在显示器上看到红色区域外框与绿色区域结合的矩阵显示。如果没有显示成功,就需要返回检查一下连接是否到位,代码是否编写正确。如果无法自己完成错误排查的话,可以重新按照步骤操作一遍,相信一定会达到想要的效果。
第5节 简化版步骤分享
这里依旧会分享简化版的步骤,方便掌握基础原理后进行反复操作复习。
5.1 设计实现5.1.1 顶层接口
新建目录:D:\mdy_book\rec_exec1。在该目录中,新建一个名为rec_exec1.v的文件,并用GVIM打开,开始编写代码。
确定顶层信号,信号和管脚的对应关系见表3.9- 2。
表3.9 –2 信号和管脚关系
器件电阻网络转换后信号线信号线FPGA管脚FPGA工程信号
CN1VGA_REDVGA_R4E11lcd_rgb
VGA_R3C10lcd_rgb
VGA_R2D10lcd_rgb
VGA_R1E9lcd_rgb
VGA_R0E10lcd_rgb
VGA_GREENVGA_G5D15lcd_rgb
VGA_G4C17lcd_rgb
VGA_G3C19lcd_rgb
VGA_G2E12lcd_rgb
VGA_G1C13lcd_rgb
VGA_G0E15lcd_rgb
VGA_BLUEVGA_B4D13lcd_rgb
VGA_B3E13lcd_rgb
VGA_B2D17lcd_rgb
VGA_B1E16lcd_rgb
VGA_B0C15lcd_rgb
VGA_HSYNCVGA_HSYNCC20hys
VGA_VSYNCVGA_VSYNCD20vys
X1
SYS_CLKG1clk
K1
SYS_RSTAB12rst_n

写出顶层信号代码:
1234567module rec_rect1(clk,rst_n,lcd_hs,lcd_vs,lcd_rgb    );

声明输入输出属性:
12345input                   clk;input                   rst_n;output                  lcd_hs;output                  lcd_vs;output          lcd_rgb       ;


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

分辨率行/列同步脉冲显示后沿显示区域显示前沿帧长单位
640*480/60Hz行964864016800基准时钟
列23348010525行
800*600/72Hz行12064800561040基准时钟
列62360037666行
800*600/60Hz行12888800401056基准时钟
列4236001628行
1024*768/60Hz行1361601024241344基准时钟
列6297683806行
表3.9–1 VGA常用分辨率
分析设计目标可得VGA行同步信号,其时序图如下所示。图3.9-3VGA行同步信号时序设计计数器架构,表示计数基准时间的计数器cnt0代码如下:
12345678910111213always @(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;    endend assign add_cnt0 = 1;assign end_cnt0 = add_cnt0 && cnt0== 2 -1;

表示计数hys长度的计数器cnt1代码如下:
12345678910111213always @(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;    endend assign add_cnt1 = end_cnt0;assign end_cnt1 = add_cnt1 && cnt1==800-1 ;


设计行同步信号的代码如下:
1234567891011always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginhys<= 0;    end    else if(add_cnt1 && cnt1==96-1)beginhys<= 1;    end    else if(end_cnt1)beginhys<= 0;    endend


设计VGA场同步时序计数器cnt2代码如下:
12345678910111213always @(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;    endend assign add_cnt2 = end_cnt1;assign end_cnt2 = add_cnt2 && cnt2==525-1 ;

设计场同步信号的代码如下:
1234567891011always@(posedge clk or negedge rst_n)begin    if(!rst_n)beginvys<= 1'b0;    end    else if(add_cnt2 && cnt2 == 2 - 1)beginvys<= 1'b1;    end    else if(end_cnt2)beginvys<= 1'b0;    endend


设计lcd_rgb信号代码如下:
12345678910111213141516171819always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginlcd_rgb<= 16'h0;    end    else if(cnt1 >=(96+48) && cnt1 <(96+48+640) && cnt2 >=(2+33) && cnt2 < (2+33+480))begin //显示区域if((cnt1 >= (96+48) && cnt1 < (96+48+20)) ||                 (cnt1 >= (96+48+620) && cnt1 < (96+48+640)) ||                 (cnt2 >= (2+33) && cnt2 < (2+33+20)) ||                 (cnt2 >= (2+33+460) && cnt2 < (2+33+480)))begin//红色区域lcd_rgb<= 16'b11111_000000_00000;      end      else if((cnt1 >= (96+48+320-75) && cnt1 < (96+48+320+75)) && (cnt2 >= (2+33+240-50) && cnt2 < (2+33+240+50)))begin//绿色区域lcd_rgb<= 16'b00000_111111_00000;      end      else begin//白色区域lcd_rgb<= 16'b11111_111111_11111;      end    end    else beginlcd_rgb<= 0;    endend

至此,主体程序已经完成,接下来将module补充完整。
5.1.3 信号定义
首先定义信号类型,cnt0、add_cnt0 和 end_cnt0的信号定义如下:
1reg             cnt0;wire                   add_cnt0;wire                   end_cnt0;

cnt1的信号定义如下:
1reg             cnt1;

add_cnt1和end_cnt1的信号定义如下:
12wire                   add_cnt1;wire                   end_cnt1;

cnt2的信号定义如下:
1reg             cnt2;

add_cnt2和end_cnt2的信号定义如下:
12wire                   add_cnt2;wire                   end_cnt2;

lcd_rgb的信号定义如下:
1reg              lcd_rgb;

hys和vys代码如下:
12reg                  hys;reg                  vys;

至此,整个代码的设计工作已经完成。最终得到完整的设计代码如下:
12 module color_exec1(clk,rst_n,lcd_hs,lcd_vs,lcd_rgb    ); input                   clk;input                   rst_n;output                  lcd_hs;output                  lcd_vs;output          lcd_rgb       ; reg                cnt0;wire                   add_cnt0;wire                   end_cnt0; reg                cnt1;wire                   add_cnt1;wire                   end_cnt1;reg                cnt2;wire                   add_cnt2;wire                   end_cnt2;reg             lcd_rgb; reg                  hys;reg                  vys; 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;    endend assign add_cnt0 = 1;assign end_cnt0 = add_cnt0 && cnt0== 2 -1; 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;    endend assign add_cnt1 = end_cnt0;assign end_cnt1 = add_cnt1 && cnt1==800-1 ; 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;    endend assign add_cnt2 = end_cnt1;assign end_cnt2 = add_cnt2 && cnt2==525-1 ; always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginhys<= 0;    end    else if(add_cnt1 && cnt1==96-1)beginhys<= 1;    end    else if(end_cnt1)beginhys<= 0;    endend always@(posedge clk or negedge rst_n)begin    if(!rst_n)beginvys<= 1'b0;    end    else if(add_cnt2 && cnt2 == 2 - 1)beginvys<= 1'b1;    end    else if(end_cnt2)beginvys<= 1'b0;    endend always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginlcd_rgb<= 16'h0;    end    else if(cnt1 >=(96+48) && cnt1 <(96+48+640) && cnt2 >=(2+33) && cnt2 < (2+33+480))begin //显示区域if((cnt1 >= (96+48) && cnt1 < (96+48+20)) ||                 (cnt1 >= (96+48+620) && cnt1 < (96+48+640)) ||                 (cnt2 >= (2+33) && cnt2 < (2+33+20)) ||                 (cnt2 >= (2+33+460) && cnt2 < (2+33+480)))begin//红色区域lcd_rgb<= 16'b11111_000000_00000;      end      else if((cnt1 >= (96+48+320-75) && cnt1 < (96+48+320+75)) && (cnt2 >= (2+33+240-50) && cnt2 < (2+33+240+50)))begin//绿色区域lcd_rgb<= 16'b00000_111111_00000;      end      else begin//白色区域lcd_rgb<= 16'b11111_111111_11111;      end    end    else beginlcd_rgb<= 0;    endendendmodule

下一步是新建工程和上板查看现象。
5.2 综合与上板5.2.1 新建工程
打开软件Quartus Ⅱ,点击“File”下拉列表中的New Project Wzard...新建工程选项。图3.9-11Quartus新建工程
直接点击“Next”。图3.9-12Quartus新建工程介绍
此时出现的是工程文件夹、工程名、顶层模块名设置界面(目录为:D:/mdy_book/rec_exec1,工程名和顶层名为rec_exec1),完成设置后点击“Next”。图3.9-13 QUARTUS新建工程设置名称
选择“Empty project”后点击“Next”。图3.9-14 QUARTUS新建工程类型
点击右侧的“Add”按钮,选择“color_exec1.v”文件后点击“Next”,完成文件添加。图3.9-15 QUARTUS添加文件
对芯片型号进行选择,在“Device family”选项中选择“Cyclone ⅣE”,“Available devices”选项中选择“EP4CE15F23C8”,随后点击“Next”。图3.9-16 QUARTUS选择芯片型号
直接点击“Next”。图3.9-17 QUARTUS设置工具界面
点击“Finish”,完成新建工程。图3.9-18 QUARTUS新建工程汇总界面
5.2.2 综合
新建工程后界面如下图所示,点击“编译”。图3.9-19 QUARTUS新建工程后界面
编译成功如下图所示。图3.9-20 QUARTUS编译后界面
5.2.3 配置管脚
进行管脚配置,在菜单栏中点击“Assignments”后点击“Pin Planner”,此时会弹出配置管脚的窗口。图3.9-21 QUARTUS配置管脚选项
在配置窗口“location”根据信号和管脚关系配置管脚,配置完成关闭“Pin Planner”即可自动保存配置信息。图3.9-22 QUARTUS配置管脚
5.2.4 再次综合
再次打开“QUARTUS”软件,在菜单栏中选择“Processing”,随后点击“Start Compilation”再次进行综合。图3.9-23 QUARTUS编译选项
出现 QUARTUS 编译成功标志时表示此次编译成功。图3.9-24 QUARTUS编译成功标志
5.2.5 连接开发板
下载器接入电脑 USB 接口,将开发板接上电源,开发板的VGA口连接到一台显示器上后按下蓝色开关。图3.9-25开发板连接图
5.2.6 上板
打开 QUARTUS 界面后单击“”图标:图3.9-26 QUARTUS界面
点击“add file”,添加.sof文件,完成添加后点击“Start”,在“Progress”中会显示进度,当进度条显示“100%”为成功,可观察显示器现象。如果此时开发板连接的显示器显示出了设计目标中需要的画面则代表设计成功。图3.9-27 QUARTUS下载程序界面
第6节 扩展练习至此,VGA显示矩阵设计已经完成,相信同学们已经可以完全掌握这一设计。那么在掌握这项工程后可以多做一些思考,尝试在工程原理不变的基础上进行一定的数据调整,试着改变图像显示区域或者改变显示颜色等参数,挑战一下独立完成多个设计。也欢迎有更多思路和想法的同学前往至简设计法论坛上进行交流讨论。















页: [1]
查看完整版本: 【FPGA至简设计原理与应用】书籍连载18 第三篇 FPGA至简设计项目 第九章 VGA显示矩阵