声明:本文为原创作品,版权归akuei2及黑金动力社区(http://www.heijin.org)共同所有,如需转载,请注明出处http://www.cnblogs.com/kingst/
5.6 实验十九:VGA封装
在笔者还没有开始写这本笔记之前,笔者和大众的新手一样,都喜欢在网络上找资料。
有一篇论文“基于FPGA的VGA接口”中的实验,笔者很感兴趣,但是论文始终是论文,论文的东西都是用来毕业,瞎了笔者的狗眼。笔者就在那个时候开始突发奇想:“有没有什么的办法,以最小的条件,来实验该论文中的内容呢?”
在此刻,阅读这章笔记的时候,读者是否对“封装”或者“接口”有一个概念了吧?
同样的事实,VGA模块也需要封装成为VGA接口。
上图是组合模块 vga_interface.v ,里面包含了“同步模块”(用于配置显示标准,和驱动 VGA ),“ VGA 控制模块 ”(用于控制图像信息),还有一个双向 RAM 模块。是不是觉得很疑惑:为什么多了一个 RAM 模块出来。
在实验九中, VGA 控制模块读取的图片信息是来自 ROM 模块。相反的实验十九的图像信息是来自 RAM 。在完成 VGA 接口之前,我们必须设定一些参数。
图像分辨率 | 16 x 16 |
图像颜色 | 点阵 |
图像显示位置 | X = 3 ,Y = 2 |
显示标准 | 800 x 600 x 60Hz |
显示标准,关于这个参数,在 3.4 章中有详细的介绍,这里就重复了。图像分辨率,说简单点就是一副图像信息的大小。图像颜色,点阵的意思就是黑和白。图像显示位置,是指一副图信息开始显示的位置, X = 3 Y = 2 ,表示图像在屏幕的坐标( 3, 2 )显示。
sync_module.v
在29行的if条件,就是表示开始显示位置 Y, 和39行的开始显示位置 X。
我们知道图像信息是16 x 16。所以,29行的if条件成立范围是在 Row_Addr_Sig > 1 ~ Row_Addr_Sig < 17。同样的在39行的if条件成立范围在 Column_Addr_Sig > 2 ~ Column_Addr_Sig < 19。
在 24 行和 34 行的寄存器 m 和 n ,是用来列寻址和行寻址。所以它们必须从“开始显示位置”开始计数。
在 46~54 行的 isSize 标志寄存器是用来确定“一副图像的显示框”,亦即“图像显示有效范围”。在 51~52 行的 if 条件指定了该 isSize 表示寄存器被拉高的时候,也就是说在 Row_Addr_Sig > 1 ~ Row_Addr_Sig < 17 和 Column_Addr_Sig > 2 ~ Column_Addr_Sig < 19 ,分辨率为 16 x 16 的图像时“显示有效”的。
59 行表示 m 行寻址 等价于 Ram_Addr 地址。
61~63 行,是列寻址,同是点阵操作。由于该 VGA 接口的颜色支持参数是“黑白”的缘故,所以 61~63 行都是同样的点阵操作。
ram_module.v
RAM 是什么,有电子背景的同学都知道它是什么。 RAM 和 ROM 不同的是, RAM 支持访问(读和写操作),然而 ROM 只支持读操作。一般上 RAM 有分为单端口,双向端口,三向端口。双向端口,有为分为真双向端口( True ),和假双向端口( Simple ),真双向端口有写入时钟和读取时钟,然而假双向端口读写共用一个时钟。为了方便 vga_interface.v 的建模,在这里我们使用假双向端口 RAM 。
在这里,我们先要考虑 vga_interface.v 支持的图像分辨率,亦即 16 x 16 。所以RAM所需要的储存空间是 16Bits x 16 Words。RAM和 FIFO一样,要访问RAM的时候都需要拉高 xx_En_Sig 信号。由于RAM 包含 16 Bits 所以 Write_Data 和 Read_Data, 皆是16位的位宽。当然,16 Words 表示了 xx_Addr_Sig 是 4位的位宽。
一个普遍存在与双向端口 RAM 的问题是“同时读写冲突”。一般的物理双向端口 RAM 都内置仲裁逻辑。“仲裁逻辑”的设计方法是比较复杂,我们使用另一种方法,就是“访问优先级”。
在这里,我们可以这样定义:写操作的优先级高于读操作。
所以呀,使用 QuartusII 自建的双向端口 RAM 不怎么适合。换一句话,我们必须手动创建包含“访问优先级”的“假双向端口 RAM ”。
第1~11行是RAM模块的输入输出口。在15行,声明了 16Bits x 16 Words 的储存器。
如果以Altera的FPGA为例,尝试回想一下,FPGA都内置了偏上资源 m4k。当我声明储存器的时候,我们可以指定它由 m4k 组成。
(* ram_tyle = m4k *)
当然你也可以指定储存器由逻辑资源组成:
(* ram_tyle = logic *)
在前面,我已经说过:“双向端口的RAM存在访问冲突的问题”。当QuartusII 在综合的时候,由于 m4k 资源本身的特性,并不适合“写时读”(read-during-write),亦即“访问冲突”。虽然,我们手动为RAM模块添加了访问优先级,可以避免“访问冲突”的问题。但是Quartus II 的综合器是一个大笨蛋,你没有提示它:“不用关心m4k资源的访问冲突问题”,综合器是不知道的。因此:
(* ram_style = no_rw_check *)
还有一点就是关于储存器初始化的问题:
还记得在建立ROM的时候,笔者都为ROM建立一个 .mif ,然后对ROM初始化。同样的道理,RAM储存器在创建的时候也必须初始化。该RAM模块初始化的信息,是实验十之五之中的“第一副小绿人”。
(* ram_init_file = ram_initial_file.mif *)
21~27行是RAM模块的访问优先级逻辑。在24行表示了写操作比读操作拥有更高的优先级。当 Write_En_Sig 拉高的时候,对RAM储存器执行写操作。一旦 Write_En_Sig 被拉低 读操作作为RAM储存器的默认状态。
有一重点必须注意就是rData寄存器是用来暂存读数据。为了避免“当写操作执行时Read_Data 失去驱动源”。
vga_interface.v
VGA 接口的时钟源是由全局时钟源经过倍频后输入的( 9 行)。
实验十九说明:
读者是不是觉得实验十九和实验九相比,简直是莫名其妙对吧?在这里笔者稍微区分一下实验九和实验十九的不同。实验九顾名思义就是VGA显示模块,它所使用的显示方法是同步的。换句话说,也就是图片信息处理和VGA显示驱动都是在同样的时间下。当然也可以这样想,图片信息是又VGA模块本身提供的,又或者图片信息早已固定存在。
实验十九的 VGA 接口 , 恰好是与实验九的 VGA 显示模块相反。它所使用的显示方法是异步的。也就是说,图像信息处理和 VGA 显示驱动是在不同的时间下完成。看得简单点就是,图像信息是由外部提供。
在实验十九中的 VGA 接口,图像信息是暂存在 RAM 模块里,而且 RAM 模块里边的图像信息是由上一层模块写入。读者可能会产生这样一个问题:“假设 vga_control_module.v 在读取 RAM ,地址 0 的信息的时候,生一层模块同时对 RAM ,地址 0 执行写操作 ... ”。
事实上,由于访问优先级逻辑的关系,当发生“访问冲突”的时候,vga_control_module.v读取的是 上一次的rData信息。
读者可能又会问:“当发生访问冲突的时候, rData 暂存的数据当然不是当前的显示数据,图像显示既不是出现错误? ”。 你知道吗 ? LCD 显示技术为了消除 LCD 显示残影的问题,在指定间隔时的间里,都会插入诺干“黑帧”(全黑图像信息)。如果 LCD 插入“黑帧”,已不是屏幕忽然间全黑,然后又恢复,又全黑,又恢复 .... 呵呵!事实上,人体的视觉是很蛋疼和迟钝的,这些“短暂”的“黑帧”人眼是察觉不出来。这也使得 LCD 消除残影的方法。
同样的道理,以 800 x 600 x 60Hz 为显示标准。如果在 1 秒内出现 1~16 个残缺的帧,人眼是不会察觉到,因为该显示标准时每秒 60 帧图像。用动画的话来说,每秒 8 帧产生拖尾效果,每秒 24 帧产生专业动画效果,每秒 40 帧人眼是“瞬间”的效果。
完成后的扩展图:
实验十九结论:
实验九和实验十九最大的差别就是图像信息处理的方法。前者是由模块内部提供的,后者是由上一层模块写入。此外实验十九当 vga_control_module.v 对 ram_module.v 读取信息的时候,如果在同一个时间,上一层模块对 vga_interface.v 的 ram_module.v 执行写操作,故会发生“访问冲突”。为了避免这个问题,笔者对 ram_module.v 加入了访问优先级逻辑,访问优先级定义为:写操作优先级高于读操作。
还有有关“实验十九说明”中提及种种“显示问题”会在“实验十九演示”中演示。
实验十九演示:小绿人请加油
上图的 vga_interface_demo.v 组合模块表示了,控制程序从 ROM 模块读取图片信息,然后写入 VGA 接口。该 ROM 模块是基于实验九之五的 greenman_rom_module.v ,里边包含了 6 副 16 x 16 的图片信息。控制程序,每隔 250ms 写入不同的信息至 VGA 接口。所以在屏幕的显示结果上,会出现小绿人的动画。
vga_interface_demo.v
16~20 行实例化了 pll 模块, pll 模块将全局时钟倍频至 40Mhz 。 24 行是 1ms 常量的声明,然而 28~50 行建立了 1ms 定时器( 28~38 行)和 ms 级计数器( 42~50 行)。 54~69 行表示了 6 副图像信息在 ROM 模块的起始地址。在 73~104 行就是该组合模块的核心功能,在 93~95 行,表示了 - 当步骤为 0 , 2 , 4 , 6 , 8 , 10 时,对 vga_interface.v 的 RAM 模块写入不同的图像信息。在 97~99 行是 250ms 的延迟,亦即每一副图像信息的停留时间。
109~116 行实例化了 greenman_rom_module.v 。在 120~132 行实例化了 vga_interface.v 。
在一开始的时候 vga_interface.v 内部的RAM模块会执行初始化该储存信息,亦即作为默认图像信息。当 vga_interface_demo.v 进入步骤0,在同一时间,在62行会对 Y寄存器赋值与ROM模块的第一副图像信息的起始地址,亦即地址0。在94~95行 isWrite被拉高,以示对vga_interface.v 的RAM模块写入使能(123行)。
Y 寄存器作为图像信息在 ROM 模块的起始地址, X 寄存器作为每一副图像信息的行寻址地址,然而 rAddr 作为 x 寄存器和 y 寄存器的总和,同时也是作为 ROM 模块的地址信号( 114 行)。
在 94 行中, x 会递增至 0~15 ,已对一副图像信息的行寻址( Addr 寄存器的 前 4 位 [3..0] ,同时作为 vga_interface.v 的写入地址 -124 行)。还有一点必须注意的是, vga_interface.v 的写入数据是由 ROM 模块的 Rom_Data 驱动( 125 行)。直到 x 等于 16 ( 95 行) , 亦即一幅图像的行寻址已经结束, rAddr , X , 和 isWrite 寄存器被赋予 0 值。 i 递增以示下一个步骤。
在97~99行,步骤 1, 3, 5, 7, 9, 11 是延迟250ms的操作。rTimes 寄存作为言辞 ms 的计数值(98行),rTime 赋值与 250,亦即是延迟 250ms。当isCount被拉高的时候(99行),定时器和计数器都会开始工作(28~50行)。直到ms级的计数计数到250为止(98行),isCount被拉低, i递增以示下一个步骤。
上述步骤的执行大致如下:
(一)写入第0副图像信息至 vga_interface.v ,延迟250ms。
(二)写入第1副图像信息至 vga_interface.v ,延迟250ms。
(三)写入第2副图像信息至 vga_interface.v ,延迟250ms。
(四)写入第3副图像信息至 vga_interface.v ,延迟250ms。
(五)写入第4副图像信息至 vga_interface.v ,延迟250ms。
(六)写入第5副图像信息至 vga_interface.v ,延迟250ms。
(七)重复执行步骤1~6。
实验十九演示说明:
在演示的显示效果中,你会看到小绿人的动画由六副图像信息,每隔 250ms 不停的循环和切换而产生。实际上,每秒 60 帧的图像信息中,有几幅图像是“崩坏”,由于人类的视觉迟钝的关系察觉不出来。
实验十九演示结论:
不知道读者还记得“接口的定义”吗?就是“最后的工程”和“独立性”。在实验十九演示中, VGA 接口是一个独立的个体,它以每秒显示 60 帧图像信息而工作。当我们对 VGA 接口调用的时候,我们不需要顾及 VGA 接口的内部是如何工作,反之我们只要关心如何对 VGA 接口 写入图像信息即可。如实验十九演示中那样,为了实现小绿人动画,我们在上一层模块中,每隔 250ms 写入不同的图像信息。
和在实验九之五不同的是,我们为了实现小绿人的动画效果,必须考虑由 sync_module.v 输出的 Frame_Sig 信号。以计数 Frame_Sig 信号的方式去实现小绿人动画。类似的方法大大的压缩了灵活性。
原理上, vga_interface.v 的工作原理,适合任何显示接口,因为人类的肉眼对画面的“切换速度”不敏感。