官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师

2.12 VGA显示图片

发布时间:2021-08-22   作者:admin 浏览量:
    官网连载的内容将出版成图书,并将录制视频,免费公开学习,欢迎大家留意。本连载前面是基础部分,与一般教材无异,后面是项目实践,是本连载的特色。如果你有一定的基础(能看懂verilog代码即可),那么可跳过前面部分,直接学习后面的项目实践。项目实践将有16个,从基础的闪烁灯开始,到最后是信号处理的项目,如信号发生器、FIR滤波器、插值滤波器和AD采集等。 

    本连载学习效果:不仅看能懂代码,还能知道每一行代码怎么写,怎么设计。


第二阶段  项目实践

第十二章  VGA显示图片

本文的文档编号:001700000023

本文档没有对应的视频教程

1、至简原理与应用配套的案例和PPT讲解
2本案例要实现的效果是通过VGA,以640*480的分辨率在显示器中心显示120*55的图片图片数据存在ROM IP核内。步骤性教学;

3、这是Altera和Xilinx入门学习案例文档

1 项目背景
1.1 FPGA存储器
    目前大多数FPGA都有内嵌的块RAM(Block RAM),可以将其灵活地配置成单端口RAM(DPRAM,Single Port RAM)、双端口RAM(DPRAM,Double Ports RAM)、伪双端口RAM(Pseudo DPRAM)、CAM(Content Addressable Memory)、FIFO等常用存储结构。FPGA中其实并没有专用的ROM硬件资源,实现ROM的思路是对RAM赋予初值,并保持该初值。
    Altera的器件内部提供了各种存储器模块(RAM、ROM或双口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文件。
    ROM IP核的生成方法[此处应该加上ROM的介绍,以及MIF文件的介绍。]

1.2 图片变成MIF文件的方法
    1.我们需要用到Img2Lcd软件,此软件可自行下载,无需安装即可使用。

图 430
    2.打开软件后,点击右上角“打开”按钮,选择需要转化的图片,在右侧输出数据类型下选择“c语言数组”,扫描模式为“水平扫描”,输出灰度“256色”,最大宽度和高度应大于图片的大小,由于我们示例的图片为120*60,最大宽度和高度我们选择240*240。其他选项可以不用管。
图 431
    3.之后点击“保存”,文件名为“1.c”,在点击下方保存。
图 432
    4.用gvim打开生成的文件,箭头处为数据个数,由于图片是120*60的,输出灰度为256色,所以就有7200个数据。
图 433
    5.第一行是不需要的,先删除掉,然后进入命令模式,输入“/,”,然后回车,就可以选中所有的逗号。
图 434
    6.将光标置于第一个字符,在命令模式下一次输入“q、a”,调用gvim的录制功能,然后输入“n”跳转到第一个逗号,进入编辑模式,将光标置于第一个逗号的右侧,然后回车,接着摁4下退格键删除多余的空格,在进入命令模式,输入“q”结束录制。
图 435
    7.在命令模式下输入“7198@a”,表示重复之前的录制7198次,然后回车。
图 436
    8.然后我们要删除多余的间隔,返回到第一个字符,在命令模式下输入“qa”进入录制,然后向下移动16格,刚好到没有数据的一行,输入“dd”进行删除,然后“q”退出录制。在命令模式下输入“954@a”,即可完成删除。
图 437
    9.删除掉中间的OX
图 438
    10.在命令模式下输入“:%s/0X/: /”回车将“0X”替换为冒号,输入“:%s/,/; /”回车将“,”替换为“;”。
图 439
图 440
    11.打开一个空白gvim,输入1,进入命令模式选中,输入“yy7199p”将1复制7199份,将光标至于第一个1处,进入命令模式,输入“:let i=1|g/1/s//=i/|let i=i+1”让其递增。
图 441
图 442
    12.选中1~7199,然后Ctrl+Q,进行列操作,然后Ctrl+c复制,然后回到1.c文件中,命令模式下,将光标置于第二行最前,然后Ctrl+v粘贴。
图 443
   13.将第一个数据的地址标为0,然后在最上方和结尾添加如下图所示内容。
图 444
图 445
    14.然后将“1.c”文件保存为“1.mif”格式的。


2 设计目标
    通过VGA连接线,将显示器和教学板的VGA接口相连。连接示意图如下。
图 446

分辨率

/

同步脉冲

显示后沿

显示区域

显示前沿

帧长

单位

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

    然后FPGA产生640*480分辨率(使用上表中的第一种分辨率),让显示器产生显示一幅图像。提示:显示器一般都会自适应功能,无须设置就能识别不同分辨率的图像。
    图像的内容是:在屏幕的中央显示一个明德扬的LOGO。明德扬LOGO的大小是120*60像素。除了图片之外的显示区域,则显示白色。上板效果图如下图所示(显示器不同,显示效果也会有差别,请注意)。
图 447
    本案例提供了ROM IP核文件:rom1.v。该文件的输入输出接口是:

1

2

    rom1是一个宽度为16位,深度为8192的ROM,该ROM保存了明德扬的LOGO图片,其排列方式如下。



    该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。

器件

电阻网络转换后

信号线

信号线

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。并且我们已经知道该模块有五个信号:clk、rst_n、lcd_hs、lcd_vs和lcd_rgb。为此,代码如下:

1

2

3

4

5

6

7

    module picture_new_borad (

       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,线即可,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 架构设计
    需要注意的是,输入进来的时钟clk是50MHz,而从分辨率参数表可知道,行单位的基准时钟是25 MHz。为此我们需要根据50MHz来产生一个25 MHz的时钟,然后再用于产生VGA时序。
    为了得到这个25M时钟,我们需要一个PLL。PLL可以认为是FPGA内的一个硬核,它的功能是根据输入的时钟,产生一个或多个倍频和分频后的输出时钟,同时可以调整这些输出时钟的相位、占空比等。
    例如,输入进来是50M时钟,如果我需要一个100M时钟,那么从逻辑上、代码上是不可能产生的,我们就必须用到PLL来产生了。
    整个工程的结构图如下。
图 448

    PLL的生成方式过程,请看本案例的综合工程和上板一节的内容。

3.3 VGA驱动模块设计
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。为此,代码如下:

1

2

3

4

5

6

7

    module vga_driver(

                         clk,

                         rst_n,

                         hys,

                         vys,

                         lcd_rgb

                   );

    其中clkrst_n是输入信号,hys、vys和lcd_rgb是输出信号,其中clk、rst_n、hys、vys的值是0或者1,一根线即可,lcd_rgb为16位位宽的,根据这些信息,我们补充输入输出端口定义。代码如下:

1

2

3

4

5

    input                  clk      ;

    input                  rst_n    ;

    output                 hys      ;

    output                 vys      ;   

    output [15:0]          lcd_rgb  ;


3.3.2 信号设计
    我们先设计场同步信号hys,VGA时序中的场同步信号,其时序图如下:
图 449
    hys就是一个周期性地高低变化的脉冲。我们使用的是下表中的第一种分辨率,也就是同步脉冲a的时间是96个时钟周期,而显示后沿b是48个时钟周期,显示时序c是640个时钟周期,显示前沿是16个时钟周期,一共是800个时钟周期。

分辨率

/

同步脉冲

显示后沿

显示区域

显示前沿

帧长

单位

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

    时间信号填入图中,更新后的时序图如下:
图 450
    很显然,我们需要1个计数器来产生这个时序,我们将该计数器命名为h_cnt。由于hys是不停地产生的,那么h_cnt就是不停地计数,每个时钟都要计数器,所以认为该计数器的加1条件为“1”,可写成:assign add_h_cnt = 1。从上图可知,该计数器的周期是800。综上所述,该计数器的代码如下:

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

            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;

    计数器h_cnt,那么hys信号就有了对齐的对象。从时序图可以发现, hys有两个变化点,一个是h_cnt数到96个时,由0变1;另一个是当h_cnt数到800个时,由1变0。所以,场同步信号的代码如下:

1

2

3

4

5

6

7

8

9

10

11

    always@(posedge clk or negedge rst_n)begin

        if(!rst_n)begin

            hys <= 0;

        end

        else if(add_h_cnt && h_cnt == 96 -1)begin

            hys <= 1'b1;

        end

        else if(end_h_cnt)begin

            hys <= 1'b0;

        end

    end

    接下来设计vys信号。该信号的时序图如下所示。
图 451
    vys就是一个周期性地高低变化的脉冲。我们使用的是表中的第一种分辨率,查询表可知,同步脉冲a的时间是2行的时间,而显示后沿b是33行,显示时序c是480行,显示前沿是10行,一共是525行。其中,一“行”结束,也就是h_cnt数完了。
    将时间信号填入图中,更新后的时序图如下:
图 452
    很显然,我们还需要1个计数器来产生这个时序,我们将该计数器命名为v_cnt。该计数器是用来数有多少行的,所以加1条件就是一行结束,即end_h_cnt,可写成:assign add_v_cnt = end_h_cnt。从上图可知,该计数器的周期是525。综上所述,该计数器的代码如下:

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

            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;

    有了计数器v_cnt,那么vys信号就有了对齐的对象。从时序图可以发现, vys有两个变化点,一个是v_cnt数到2个时,由0变1;一个是当h_cnt数到525个时,由1变0。所以,场同步信号的代码如下:

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_v_cnt && v_cnt == 2 - 1)begin

            vys <= 1'b1;

        end

        else if(end_v_cnt)begin

            vys <= 1'b0;

        end

    end

    最后我们还有信号需要设计,那就是lcd_rgb信号。
图 453
    我们在显示器中一共要分成两种方式显示。如上图中,以屏幕中点为中心,左右60列、上27个行、下28行显示的是图像数据;而在其他区域直接显示白色,也就是lcd_rgb等于16’b11111_111111_11111。还要注意的是,在非显示区域,lcd_rgb的值要为0,才能正确显示。我们现在要仔细区分,在什么时候分别输出上面的值。
    显示区域:(h_cnt >=(96+48)&& h_cnt <(96+48+640)),并且(v_cnt>=(2+33) && v_cnt<(2+33+480))
    图片区域:(h_cnt >=(96+48+320-60)&& h_cnt <(96+48+320+60)),并且(v_cnt>=(2+33+240-27) && v_cnt<(2+33+240+28))
    白色区域:在显示区域中,非图片区域的,就是白色区域。
    非显示区域:显示区域之外的,就是非显示区域。
    我们可以设计几个信号来表示这些区域。显示区域用valid_area=1表示,图片区域用rom_area=1表示。可得到代码如下:

1

2

3

4

5

6

7

8

    always  @(*)begin

        green_area = distance < 2500 ;

    end

 

    always  @(*)begin

        valid_area = h_cnt >=(96+48) && h_cnt <(96+48+640) && v_cnt >=(2+33) && v_cnt < (2+33+480);

    end

    有了green_area和valid_area后,设计lcd_rgb就好办了。
    非显示区域(valid_area=0),lcd_rgb输出“16’b0”;
    显示区域(valid_area)中的图片区域(rom_area=1),lcd_rgb输出为图片的像素值。但问题来了,图片的像素值哪里来?来自于ROM,我们怎么使用ROM?将这个ROM例化,下面是例化的代码。
    代码中,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。
    则可以写出代码如下:

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的信号。下面是
    由上面的表可以得到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滞后一个时钟的。这会导致什么问题呢?
图 454
    当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,让它提前一个时钟产生,而其他信号都保持不变。更新后的波形如下图。
图 455
    原来是在h_cnt=404时rom_addr的值为0,提前一个时钟后,当h_cnt=403时,rom_addr就为0,从而让rom_data也提前了一个时钟,正好解决此问题。
    所以rom_addr所对应的代码如下:

1

2

3

    always@(*)begin

        rom_addr = (h_cnt-96-48-320+60-1) + 120*(v_cnt-2-33-240+27)

    end

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

3.3.3 信号定义
    接下来定义信号类型。
    h_cnt是用always产生的信号,因此类型为reg。h_cnt计数的最大值为800,需要用10根线表示,即位宽是10位。因此代码如下:

1

    reg    [9:0]           h_cnt  ;

    add_h_cnt和end_h_cnt都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:

1

2

    wire                   add_h_cnt;

    wire                   end_h_cnt;

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

1

    reg    [9:0]           v_cnt  ;

     add_v_cnt和end_v_cnt都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1根线表示即可。因此代码如下:

1

2

    wire                   add_v_cnt;

    wire                   end_v_cnt;

    lcd_rgb是用always方式设计的,因此类型为reg。并且它的位宽是16位,16根线表示即可。因此代码如下:

1

    reg    [15:0]          lcd_rgb;

    hys和vys是用always方式设计的,因此类型为reg。并且其值是0或1,需要1根线表示即可。因此代码如下:

1

2

    reg                    hys    ;

    reg                    vys    ;

    distance是用always方式设计的,因此类型为reg。其位宽为20位,需要20根线表示。因此代码如下:


    valid_area和rom_area是用always方式设计的,因此类型为reg。并且其值是0或1,用一根线表示即可。因此代码如下:

1

2

    reg                    valid_area ;

    reg                    rom_area;

    rom_addr是用always方式设计的,因此类型为reg。其表示范围是0~6599,需要位宽为13位,需要13根线表示。因此代码如下:

1

    reg  [12:0]            distance   ;

    rom_data是例化模的输出,不是用always方式设计的,因此类型为wire。其位宽为16位,需要16根线表示。因此代码如下:

1

    reg  [15:0]            rom_data   ;

    所以整个模块的代码如下

1

2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


3.4 顶层模块设计
3.4.1 例化子模块
    例化PLL IP核的代码

1

2

3

4

    vga_pll   module_1(

                       .inclk0     (clk    ),

                       .c0         (clk_0  )

                       );

    例化驱动模块代码

1

2

3

4

5

6

7

    color module_6(  

                .clk        (clk_0  ),

                .rst_n      (rst_n  ),

                .hys        (lcd_hs ),

                .vys        (lcd_vs ),

                .lcd_rgb    (lcd_rgb)

                    );

3.4.2 信号定义

    clk_0是在例化文件中,因此类型为wire。并且其值是01,用一根线表示即可。因此代码如下:

1

    wire                    clk_0         ;

    lcd_sh和lcd_vs是在例化文件中,因此类型为wire。并且其值是0或1,用一根线表示即可。因此代码如下:

1

2

    wire         lcd_hs        ;

    wire                  lcd_vs        ;

    lcd_rgb是在例化中,因此类型为wire。它的位宽是16位的,用16根线表示即可。因此代码如下:

1

    wire [15:0]       lcd_rgb       ;

    至此,整个代码设计工作已经完成。下一步是新建工程和上板查看现象。


4 综合工程和上板
4.1 新建工程
    1.首先在d盘中创建名为“picture_new_borad”的工程文件夹,将写的代码命名为“vga_drive.v”,顶层模块名为“vga_drive”,例化文件命名为“vga_exec7.v”。图片生成的数据文件名为“1.mif”,“vga_pll.v”为时钟分频ip核,由我们提供。
图 456
图 457
图 458
    2.然后打开Quartus Ⅱ,点击File下拉列表中的New Project Wzard...新建工程选项。
图 459
    3.在出现的界面中直接点击最下方的“Next”。
图 460
    4.之后出现的是工程文件夹、工程名、顶层模块名设置界面。按照之前的命名进行填写,第一栏选择工程文件夹“picture_new_borad”,第二栏选择工程文件“vga_exec7.v”,最后一栏选择顶层模块名“vga_exec7”,然后点击”Next”。
图 461
    5.之后是文件添加界面。点击红色箭头处,在上方一栏中添加之前写的”vga_driver.v、vga_pll.v和vga_exec1.v”文件,点击右侧的“Add”按钮,之后文件还会出现在大方框中,选中它们,之后点击“Next”。
图 462
    6.器件型号选择界面。在“Device family”处选择Cyclone ⅣE,在“Available devices”处选择EP4CE15F23C8,然后点击“Next”。
图 463
    7.EDA工具界面。该页面用默认的就行,直接点击最下方“Next”。
图 464
    8.之后出现的界面是我们前面的设置的总结,确认没有错误后点击“Finish”。
图 465

4.2 生成PLL IP核

    新建工程后,就要生成PLL IP核。本节的PLL生成过程,与案例“VGA显示颜色”第四点综合工程和上板中的PLL内容一致,注意其中的地址有不同。


4.3 生成ROM IP核
    1.在界面右侧IP Catalog处搜索ROM。
图 466
    2.双击rom:1-port保存文件名,之后点击ok。
图 467
    3.在之后的界面中选择数据位宽为16位,存储深度为8192words,点击Next。
图 468
    4.在‘q’output port处取消对号选中,点击Next。
图 469
    5.然后选择文件,点击Browse,然后在出现的Select File中最下方的文件类型选择.mif格式,然后选中我们之前写的1.mif,点击Open。
图 470
    6.之后的界面中直接点击Next。
图 471
    7.取消rom1_bb.v处的箭头勾选,点击Finish。
图 472
    8.然后直接点击Yes
图 473

4.4 综合
    1.在“Project Navigator”下选中要编译的文件,点击上方工具栏中“Start Compilation”编译按钮(蓝色三角形)。
图 474
    2.编译成功后会出现以下界面。
图 475

4.5 配置管脚
图 476
    在菜单栏中,选中Assignments,然后选择Pin Planner,就会弹出配置管脚的窗口。
图 477
    在配置窗口最下方中的location一列,参考下表中最右两列配置好FPGA管脚。

器件

信号线

信号线

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

    注意注意:与其他案例不同的是,VGA案例中所有管脚的电平必须选用为LVCMOS3.3,而不能是default。如下图所示

    配置完成后,关闭Pin Planner,软件自动会保存管脚配置信息。

4.6 再次综合
图 478
    在菜单栏中,选中Processing,然后选择Start Compilation,再次对整个工程进行编译和综合。
图 479

    出现上面的界面,就说明编译综合成功。


4.7 连接开发板
    图中,下载器接入电脑USB接口,电源接入电源,vga线连接显示器,然后摁下电源开关,看到开发板灯亮。
图 480

4.8 上板
    1.双击Tasks一栏中”Program Device”。
图 481
    2.会出现如下界面,点击add file添加.sof文件, 在右侧点击“Start”,会在上方的“Progress”处显示进度。
图 482
    3.进度条中提示成功后,即可在显示器上观察到相应的现象。


技术交流QQ群:97925396
更多FPGA技术资讯:微信公众号 fpga资讯



上一篇:2.11 VGA显示圆
下一篇:1位闪烁灯设计
   拓展阅读