在笔者还没有开始写这本笔记之前,笔者和大众的新手一样,都喜欢在网络上找资料。
有一篇论文“基于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 是支持 800 x 600 x 60Hz 的显示标准(需要40Mhz的时钟源)。
在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是什么,有电子背景的同学都知道它是什么。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接口的时钟源是由全局时钟源经过倍频后输入的(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接口。所以在屏幕的显示结果上,会出现小绿人的动画。
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 的工作原理,适合任何显示接口,因为人类的肉眼对画面的“切换速度”不敏感。
文章评论(0条评论)
登录后参与讨论