声明:本文为原创作品,版权归akuei2及黑金动力社区(http://www.heijin.org)共同所有,如需转载,请注明出处http://www.cnblogs.com/kingst/
6.3 实验二十四:GUI系统
终于写到这本笔记的尾声了,在6.1章和6.2章,笔者所建立的系统都是由几个接口东拼西凑组合而成,那并非“系统建模”的主要意义,而是一个概念而已。在这一章笔者用另一种概念,一种更接近“系统建模”的实例。同时间我们也探讨“接口”对“系统建模”的重要性。
这一章我们要讨论的就是简易的“GUI系统”,GUI - 顾名思义就是“图形接口”(想知道更详细的就查维基百科吧)。在这一实验,虽然笔者不是建立非常牛的“GUI系统”,但笔者焦距的是“GUI系统”建立的基本思路。
上图是“GUI系统”的层次关系。Menu代表主目录,MenuA,MenuB 和 MenuC 代表子目录。然而每一个目录的图像都有代表的意义:
向右的流水灯效果
|
延迟400ms |
向左的流水灯效果
|
延迟200ms |
闪耀效果
|
延迟100ms |
当然,每一副图像的“箭头”也不是花瓶,图像中“箭头”表示了“目录与子目录之间切换的关系”和“同目录中不同选项切换的关系”。引一个例子来讲,“向右流水灯效果”的图像可以向右切入“延迟400ms”,然而“延迟400ms”可以向下切入“延迟200ms”。
“GUI系统”主要的功能如下:
在主目录 Menu 有3个选项,亦即“向右的流水灯效果”,“向左的流水灯效果”和“闪耀效果”。然而每一个Menu的选项,还包含各自的子目录,每一个子目录都有3个选项,亦即“延迟400ms”,“延迟200ms”,“延迟100ms”(延迟的意义上就是效果的延迟时间)。
很简单吧?但是好戏在后头。
“GUI系统”有一个经典的难题就是“目录指针”。当我们从什么目录,什么选项切换到什么目录,什么选项,该“目录指针”都要一一追踪。想到“指针”读者一定会联想到C语言的“变量指针”,“函数指针”和“结构体指针”等。
在前面笔者就强调过,Verilog HDL语言是硬件描述语言,而不是高级语言,它没有“代码的结构和特性”。但是Verilog HDL语言有一个很强大的东西,就是“位操作”,我们只要稍微的下功夫一番,就会完成“目录Flag”。
我们先假设 Menu有“三个选项”,我们可以这样作:
reg [2:0]Menu; // 建立一个寄存器表示该目录Flag
Menu[2] = 选项A的Flag // 向右的流水灯效果的选项
Menu[1] = 选线B的Flag // 向左的流水灯效果的选项
Menu[0] = 选线C的Flag // 闪耀效果的选项
假设,默认的选项是“向右的流水灯效果”,那么Menu寄存器经初始化过后的赋值是
3'b100。再假设,我从当前的“向右的流水灯效果”向下切换至“向左的流水灯效果”
Menu寄存器的值表示 2'b010;
同样的道理,我们可以为每一个Menu选线的子目录创建一个子“目录Flag”:
reg [2:0]MenuA; // MenuA 的目录Flag
reg [2:0]MenuB; // MenuB 的目录Flag
reg [2:0]MenuC; // MenuC 的目录Flag
MenuA[2] = 选项A的Flag // 向右流水灯效果的“400ms延迟”选项
MenuA[1] = 选线B的Flag // 向右流水灯效果的“200ms延迟”选项
MenuA[0] = 选线C的Flag // 向右流水灯效果的“100ms延迟”选项
MenuB[2] = 选项A的Flag // 向左流水灯效果的“400ms延迟”选项
MenuB[1] = 选线B的Flag // 向左流水灯效果的“200ms延迟”选项
MenuB[0] = 选线C的Flag // 向左流水灯效果的“100ms延迟”选项
MenuC[2] = 选项A的Flag // 闪耀效果的“400ms延迟”选项
MenuC[1] = 选线B的Flag // 闪耀效果的“200ms延迟”选项
MenuC[0] = 选线C的Flag // 闪耀效果的“100ms延迟”选项
为了更好的表达所有“目录Flag”的关系,我们可以建立一个信号 Menu_Sig 将所有“目录Flag”整合起来,成为“目录路径”:
output [11:0]Menu_Sig;
assign Menu_Sig = { Menu, MenuA, MenuB, MenuC };
Menu_Sig[11..0] | |
{ Menu, MenuA, MenuB, MenuC } | 选项 |
12'b100_000_000_000 | 向右的流水灯效果的选项 |
12'b010_000_000_000 | 向左的流水灯效果的选项 |
12'b001_000_000_000 | 闪耀效果的选项 |
12'b100_100_000_000 | 向右的流水灯效果“延迟400ms”的选项 |
12'b100_010_000_000 | 向右的流水灯效果“延迟200ms”的选项 |
12'b100_001_000_000 | 向右的流水灯效果“延迟100ms”的选项 |
12'b010_000_100_000 | 向左的流水灯效果“延迟400ms”的选项 |
12'b010_000_010_000 | 向左的流水灯效果“延迟200ms”的选项 |
12'b010_000_001_000 | 向左的流水灯效果“延迟100ms”的选项 |
12'b001_000_000_100 | 闪耀效果“延迟400ms”的选项 |
12'b001_000_000_010 | 闪耀效果“延迟200ms”的选项 |
12'b001_000_000_001 | 闪耀效果“延迟100ms”的选项 |
为了更好的表达“从什么选项切换到什么选项”或者“从什么目录切换到什么子目录”,亦即笔者就建立如上的图表。假设我进入“向右的流水灯效果“延迟400ms”的选项”那么 Menu_Sig 信号的表达会是如此:
12'b100_100_000_000
从中我们知道Menu(Menu_Sig[11:9])的A项被设置,我们知道“目录路径”从Menu的A项开始开始。然后从中我们又知道MenuA ( Menu_Sig[8:6] ) 的A项被设置,那么我们可以这样结论:“目录路径是从Menu的A项开始,然后到MenuA的A项结束”。故,从“向右的流水灯效果的选项”切入“向右的流水灯效果“延迟400ms”的选项”。
这样的设计有一个好处,就是“方便理解”。
===================================================================
讨论完了GUI系统的目录结构,接下来我们要讨论的问题就是“可配置”。“GUI系统”主要是由“上下左右”四个信号来配置。
Config_Sig[4..0] | |
分配 | 功能 |
Config_Sig[4] | Enter (保留) |
Config_Sig[3] | 上 |
Config_Sig[2] | 下 |
Config_Sig[1] | 左 |
Config_Sig[0] | 右 |
虽说Config_Sig有五位,但是GUI系统的目录切换真正被使用到的仅是Config_Sig[3..0]
Config_Sig[4] 被保留作为其他用途。
和5.8章一样 Config_Sig 中的每一位都对“高脉冲敏感”。
在这里笔者假设一个例子:“GUI系统”经初始化过后“向右流水灯效果”是默认选项。这时候笔者只有两个切换的选择:
(一)Config_Sig[2] 接收一个高脉冲,从“向右流水灯效果”选项,向下切换至“向
左流水灯效果”选项。
(二)Config_Sig[0] 接收一个高脉冲,就会切入“向右流水灯效果”的子目录选项。
笔者再假设一个情况,如果“向右流水灯效果”的“400ms延迟”作为开始选项,那么:
(一)Config_Sig[2] 接收一个高脉冲,从“向右流水灯效果”的“400ms延迟”选
项,向下切换至“向右流水灯效果”的“200ms延迟”选项。
(二)Config_Sig[1] 接收一个高脉冲,从“向右流水灯效果”的“400ms延迟”选
项(子目录)退回从“向右流水灯效果”选项(目录)。
至于目录从哪里来又切换至那里去,读者就浏览“GUI系统的目录”吧。
menu_module.v
关于menu_module.v到底要它属于“控制模块”还是“功能模块”,笔者也曾经纠结过。但是笔者还是给它定位“功能模块”,实际上这个模块的功能也很单纯,就是根据Config_Sig 信号的配置如何,就产生怎样 Menu_Sig。
menu_module.v 主要的功能就是跟踪“目录路径”而已。也就是说“GUI系统”的“目录路径”会因为Config_Sig 信号而产生变化,然而这个模块只是跟踪,然后更改 Menu_Sig。具体的功能还是直接看代码比较强。
在12~16行是核心功能中所使用的寄存器。Menu是主目录Flag的寄存器,MenuA是Menu 的A项的子目录Flag寄存器,其他的 MenuB 和 MenuC 都是大同小异。在这里有一点必须注意,在“GUI系统”初始化的时候Menu的A项作为默认选项,所以在21~25行Menu寄存器的值初始化为 3'b100。
30~50行就是主目录Menu,根据Config_Sig信号产生的结果。初头会进入步骤0,亦即主目录Menu的A项,在35行是向下切换的动作(Menu寄存器赋值为3'b010,进入步骤1),36行是切入子目录的动作(Menu寄存器清理,MenuA寄存器赋值3'b100,进入步骤3),亦即进入子目录后,子目录的A项作为默认。步骤1和2分别是 Menu的B项和C项。主目录Menu项与项之间的切换都根据“GUI系统”的“目录结构”。
步骤1(40行),如果41行成立的话,就会切换回Menu的A项(Menu寄存器赋值为3'b100, 返回步骤0)。如果42行成立,就会切换到Menu的C项(Menu寄存器赋值为,3'b001,进入步骤2)。如果43行成立,就会切入Menu的B项的子目录(MenuB寄存赋值为3'b100, 进入步骤6)亦即进入子目录后,子目录的A项作为默认。
步骤2(48行),如果49行成立的话,就会切换回Menu的B项(Menu寄存器赋值为3'b010)。如果50行成立的话,就会切入Menu的C项的子目录(MenuC寄存器赋值为3'b100,进入步骤九)亦即进入子目录后,子目录的A项作为默认。
在36行如果if条件成立的话,Menu寄存器会保存父目录的Flag,然后MenuA寄存器会设置该A项的Flag。换句话说从Menu的A项切入的话,就会进入Menu的A项的子目录MenuA的A项(MenuA的A项作为进入该目录后的默认选项)。亦即进入步骤3。步骤3~5(56~72行)是目录MenuA的选项。
步骤3(56行)就是MenuA的A项。如果58行成立就会切换至MenuA的B项(MenuA寄存器会赋值与3'b010,会进入步骤4),如果57行成立就会切出至父目录Menu,然而根据Menu的跟踪,会返回Menu的A项(MenuA寄存器清理,会返回步骤0)。
步骤4(62行)是MenuA的B项,如果63行成立会切出至父目录(MenuA寄存器清零,返回步骤0),亦即Menu的A项。如果64行成立,就会切回MenuA的A项(MenuA寄存器赋值为3'b100, 返回步骤3)。如果65行成立,会切换MenuA的C项(MenuA寄存器赋值为3'b001,进入步骤5)。
步骤5(70行)是MenuA的C项,如果71行成立的话就会切回MenuA的B项(MenuA寄存器赋值为3'b010, 返回步骤4)。如果72行成立的话就会切出至父目录(MenuA寄存器清零,返回步骤0)
Menu的B项的子目录MenuB(77~92行)和Menu的C项的子目录MenuC(97~112行),与Menu的A项的子目录MenuA(56~72行)的操作都是大同小异,笔者就不多罗嗦了(再这样写下去,笔者会患上焦急症候群 ... (⊙o⊙))。
120行是Menu_Sig 的输,该信号是由 Menu寄存器,MenuA寄存器,MenuB寄存器和MenuC寄存,按顺序结合驱动。
在这里我们可以证实一点,menu_module.v 的工作是依据Config_Sig信号对目录结构的影响来跟踪“目录路径”。然而这个“目录路径”的可视信号便是 Menu_Sig信号。
“目录模块”就如前面说所那样,它是跟踪“GUI系统”的“目录路径”,该模块只需要Config_Sig[3..0],然而随着Config_Sig[3..0]的配置,输出信号Menu_Sig,也会随着更改。
然后 Menu_Sig 信号分别驱动“页控制模块”和“LED控制模块”,我们先看左半部分:
上面的“图形”和实验二十演示(LCD接口演示实验)非常相似吧。ROM模块所拥有的空间是 8 Bits x 6144 Words,亦即这个ROM模块储存了 6 x 8 Bits x 1024 Words,也就说它包含了6副 8 Bits x 1024 Words 的图像信息。
地址0~1023 是“向右流水灯效果”的图像信息。
地址1024~2047 是“向左流水灯效果”的图像信息。
地址2048~3071 是“闪耀效果”的图像信息。
地址3072~4095 是“400ms延迟”的图像信息。
地址4096~5119 是“200ms延迟”的图像信息。
地址5120~6143 是“100ms延迟”的图像信息。
“页控制模块”的主要功能就是根据Menu_Sig 信号,从ROM模块读取不同的图像信息写入液晶接口。
假设 Menu_Sig 是 12'b100_000_000_000。那么,地址0~1023 “向右流水灯效果”的图像信息会被写入LCD接口。
page_control_module.v
page_control_module.v 的输入输出接口。
16~32行这一行代码和detect_module.v 很相识,但是我们不是要检测电平的变化,而是要检测“Menu_Sig”的变化。当Menu_Sig 产生变化的时候 上一个时间的Menu_Sig 和 下一个时间的Menu_Sig 的值是不一样,然而F1寄存器暂存下一个时间的 Menu_Sig ,F2寄存器则暂存 上一个时间的 Menu_Sig。
当我们要检测 Menu_Sig 是否发生变化的时候,可以这样表达:
if( F1 != F2 ) // Menu_Sig 发生变化
...... // 执行语句
else // Menu_Sig 没有发生变化
...... // 执行语句
在“液晶接口实验演示”中,我们知道 Z 寄存器是用来“表达图像的切换”。在42~57行是根据不同“Menu_Sig”的结果,切换不同的图像。也就是说 “不同的 Menu_Sig 值,都有不同 Z 值”。
Z值 | 图像信息 | Z值 | 图像信息 |
0 | “向右流水灯效果”图像信息 | 3 | “延迟400ms”图像信息 |
1 | “向左流水灯效果”图像信息 | 4 | “延迟200ms”图像信息 |
2 | “闪耀效果”图像信息 | 5 | “延迟100ms”图像信息 |
在 40行表示了“当Menu_Sig产生变化,就根据Menu_Sig的值,更新Z寄存器的值”。
61~87行就是该控制模块的功能。ROM模块的空间是 0 ~ 6143 , 所以驱动用的 rAddr 寄存器的位宽是 13 位(62行)。X寄存是用来计数列填充(63行),Y寄存器是行计数(64行)。
初始化的时候步骤i被初始化为 1(70行),目的是为液晶资源写入“默认选项的图像信息”,初始化的时候由于 Z值是0, 所以“向右流水灯效果”的图像信息作为默认的角色。
79行的步骤0, 是用来检测“Menu_Sig是否发生变化”,Menu_Sig 发生变化步骤i就递增以示下一个步骤(80行)。
82行步骤1是绘图操作,该85行中的 rAddr <= ( X + Y << 7 + Z << 10 )表达式,是图像信息寻址的表达式,笔者就不重复了,如果笔者有不明白的地方请复习5.7章的实验二十演示。
91~94行是该控制模块的输出驱动。
led_control_module.v
左图是LED控制模块的图形,该控制模块会根据不同的Menu_Sig 产生不同的LED效果。然而该控制模块不会像 page_control_module.v 那样,在Menu_Sig产生变化的瞬间,输出也会产生变化。每当Menu_Sig 产生变化,如果 Config_Sig[4] 没有接收一个高脉冲,是LED的输出效果是不会更新的。Config_Sig[4]在位分配的意义上正是“Enter”的效果。 说简单点,如果“Enter”没有被执行,LED的效果也没有更新。 |
第15是1ms的常量定义。在19~29行是1ms的定时器。33~41行是1ms的计数器。
45~58行是用来暂存上一个时间的Menu_Sig 和下一个时间的Menu_Sig,和page_control_module 的16~31行是同样的道理。
第62行的 Mode寄存器是用来暂存 LED的效果值。3'b100 表示向右流水灯效果,
3'b010 表示向左流水灯效果,3'b001表示闪耀效果。
63行的Delay寄存器是用来暂存延迟的值。
在70行,如果if条件成立(Menu_Sig产生变化),在72~87行 Mode的寄存器和Delay寄存器的值,会根据Menu_Sig 不同的值都会产生变化。
举个例子 12'b001_000_000_001 表示了“闪耀效果”的“延迟100ms”的选项。那么Mode的值是3'b001 和 Delay的值是 100ms。
Mode 寄存器和 Delay寄存器只是“用来暂存某值”而已。真正被用到的寄存器是 LED_Mode 和 rTimes。在初始化的情况下 LED_Mode 的初值是 3'b100,亦即“向右流水灯效果”,rTimes 的初值是 400。如果Enter键被按下(Config_Sig[4]接收一个高脉冲)LED_Mode 赋值与 Mode值,rTimes赋值与 Delay值。
108~132行是该控制模块的主要功能。在118行,会根据LED_Mode 的值产生不一样的效果。会根据不同的 rTimes值产生不一样的延迟。
gui_system.v
该组合模块和“图形”是大同小异,自己看着办吧。
实验二十四说明:
整个“GUI系统”就是 menu_module.v , page_control_module.v 和 lcd_interface.v 。该“GUI系统”在“显示”方面的设计比较简单,就是“一个事件一副图像”。此外“什么事件,产生什么效果”,这就是不主要了。
完成后的扩展图:
实验二十四结论:
看吧!这一章的实验再也不是由几个接口东平西凑成为一个系统,而是“单个系统在全部设计中占一个重要部分而已”。虽然实验二十四充其量是一个简易的“GUI系统”而已,但是这个实验中所要传达的消息也是非常的明显,就是:
“接口在系统建模中扮演的角色”此外还有“系统不同的概念”。
总结:
笔记终于写到这里,从第一章开始到第五章,所有的实验,所有的内容都是在为第六章作基础。
“什么是系统”这个问题其实笔者也是考过许多,但是“系统”这东西涉及的东西实在是太多了,由此笔者又延伸几个问题出来“什么是系统建模?”,“系统建模应该作什么?”实验二十二和二十三就是用来回答“什么是系统建模”,实验二十四则是用来回答“系统建模应该做什么”。
“系统建模”比起“基础建模”或者“封装(接口建模)”不是同一个等次的东西。因为“系统建模”的建模量不是一般的多,而是非常多。如果没有建模技巧,要完成“系统建模”是一件苦差事。
所以呀:
“系统建模”作为“低级建模”结束的一页,是再适合不过了。就如笔者在前面说所的,“前期的建模是为后期的建模作准备”。显然“系统建模”不可能是后期建模的最后一站,在“系统建模”的后面还有更后期的建模。但是那是什么,笔者也不知道甚么 ...
笔者只知道一个事实“当读者有本事走到这里,完成·明白什么是系统建模,读者就已经了解什么是低级建模”。在笔者的眼里“系统建模”是“低级建模”的综合练习,因为要清楚的表达“系统建模的结构”,读者必须掌握好“低级建模”的所有基础。无论是“代码风格”,“模块性质”,“建模结构”等,少了一样也不行。
读者呀:
是不是更上一层的明白“低级建模”的基本概念呢?一个大东西是需要许多的小东西不停的组成和不停的组合。在组合的过程要相互尊重(明白模块之间的性质),相互协调(不同性质的模块之间的调用),相互支持(一层接一层的组合)...
结束语
终于把这本笔记编辑完毕了,编辑笔记的过程真是辛酸但是又是真实。编写这本笔记的锄头笔者是重新从零开始的。说实话,笔者在编辑这本笔记之前水平很低,但是当笔者掌握了“建模技巧”之后,跳跃式的进步。读者们相不相信,就见仁见智。
话说3个月的时间说长不长说短不短,悄悄好是四份之一年,但是这一段时间对于笔者来说是绝对真实而且值得的。当这本笔记完成之际,笔者仿佛又多了解了 Verilog HDL+ FPGA的世界。Verilog HDL + FPGA的世界是深不可测,如果以笔者的话来说,笔者也仅是了解到冰山一角而已。但是这一步的踏出,笔者发现了新大陆。
好了,笔者不再罗嗦了。笔者真心的希望读者们可以借与这本笔记重新去认识 Verilog HDL + FPGA 的世界。Verilog HDL + FPGA 的世界一点也不可怕,而且多姿多彩,只是我们在学习的路上,忽然间迷失而已,只要重新思考,重新出发,就会发现这个世界的不同。
可能读者们产生这样的问题:“下一站的学习旅程,我应该选择哪里?”。笔者不能断定什么,但是笔者建议一下的几个选择:
一、了解功能仿真和验证(你会了解系统级的硬件描述语言)。
二、了解时序分析(你会了解寄存器级的世界)。
三、了解NIOS II(你会了解软核)。
四、继续走 Verilog HDL 的道路。
笔者的选择是第四点,因为笔者从这本笔记了解到 Verilog HDL 语言是很强大。笔者想更了解它 ......
最后一点就是笔者的不请之求,笔者希望这本笔记可以帮助更多人。读者们如果有顺手之力,就把它转发到每一个学习的角落。