你的位置:EETOP 赛灵思(Xilinx) 社区 >> >> 全部 >> 详细内容

Verilog HDL建模技巧 :低级建模 仿顺序操作

发布者:jackzhang 时间:2010-06-22 18:55:12

第1章“低级建模”的思路
首先,我将用一个简单的例子来说明一下,“低级建模”的最基本思路:
(一)利用C语言驱动八位发光二极管:

我们以流水灯作为例子,因为它是最经典的实验。假设我要实现流水灯效果,那么我只要建立一个简单的“流水灯函数”函数,“Flashing”。

如果要实现自左向右或者自右向左发光的流水灯,可以使用C语言创建两个简单的函数,“Flashing_To_Right”和“Flashing_To_Left”。

假设我要实现流水灯效果:
1. 永远自右向左发亮。
2. 永远自左向右发亮。
3. 流水灯永远跑来跑去。
那么可以这样写:

对于C语言来说,这些任务都非常的简单。几乎是入门级的实验,但是将这些实验带入到V语言的环境下。确实一个记得思考的问题。

(二)利用V语言驱动八位发光二极管:

我们先看一段非常傻瓜的一段代码:

没错,上面是实现流水灯的代码。如果我说我要求:“ 自左向右循环3次,自右向左循环5次,然后自左向右一次,自右向左一次,然后自左向右循环30次 ”。当你听到这样的要求,你可能会崩溃.... 如果按照上面的写法,你会写得很长很长。

相比之下,C语言要实现以上的要求,根本就是“小儿科”的功夫。

给自己5分钟的思考,想想我到底要表达什么?

在C语言上,有“顺序操作”或者“泛型编程”的概念。从上述的代码中,for循环利用i变量,控制循环次数,然后调用3次“Flashing_To_Right()”函数。相反的V语言是“并行操作”的概念,类似的方法完全行不通。这就是新手们常常遇到的问题。

方法行不通,但是不代表思路不行。“低级建模”最基本的思路就是“仿顺序操作”。“低级建模”不是什么困难的东西,它只是一中的“手段”而已,只要你了解它的基本构思,它会成为很有用的工具。

第2章“低级建模”的结构

2.1“低级建模”的基本结构

从一个管理系统看来,“低级建模”会是一个从上直下层次的一个概念。

从上面的示意图可以看出,老板是最顶级的,而员工是最低级的。老板从上头发号,然后经经理呀,领导呀,最后苦力集团就开始动工了。当完工的时候,一一向上报告。低级建模的概念类似如此。除了老板以外,所有经理,领导,员工的“集合”称为“低级建模”。而老板是“独立模块”,因为老板不受命令,而且也不用报告。而多个“功能模块”的集合称为“组织模块”,如上面示意图中的“永远垂死的苦力集团”

2.2“低级建模”的准则

根据上文和上示意图的分析,“低级建模”基本上有以下几个准则:

1. 有“组织模块”和“功能模块”之分。
2. “低级建模”中的“功能模块”均称为“低级功能模块”。
3. “低级功能模块”有如特点:有开始信号,完成信号,一个模块只有一个功能。
4. 多个“低级功能模块”组织起来称为“组织模块”。

注意点:功能模块又分成“低级功能模块”和“非低级功能模块”?这话何解呢?

从上面的示意图中可以分辨出“低级功能模块”和“非低级功能模块”。“低级功能模块”包含“开始信号”和“完成信号”而“非低级功能模块”则没有(就是这样简单而已)。

2.3“开始信号”和“完成信号”的作用

先看看以下一段代码:

上面的代码可以分成两个部分,一部分是“独立模块”和另一部分是“低级功能模块”。独立模块只有一个则打工模块有三个,而且每一个打工模块仅包含一个功能而已“打扫”,“洗厕所”和“跑腿”....

注意: 独立模块不属于低级建模。

假设老板有一系列的命令要发号:打扫 ==> 洗厕所 ==> 跑腿

当“负责打扫”的“低级功能模块”收到老板的第一号命令“打扫”时,该模块从“待命状态”变成“打扫状态”,此时老板可以睡一觉或者干其他的活儿。该模块便开始“执行打扫任务”,当该模块“打扫完毕”后,就给老板“报告”,然后返回“待命状态”。故老板听到“打扫完成”报告后,就给下一个“低功能模块”发下一号命令 ...

在“低级建模”的结构上,为了使不同层次的“低级功能模块”可以协调的工作,“开始信号”和“完成信号”扮演着很重要的角色。在现实中,如果 “打扫 ==> 洗厕所 ==> 跑腿”是一个有“次序的三部曲”,那么老板不可能要员工颠倒次序来干活儿 。老板得按次序,一个一个的命令员工干活。除此之外老板也不可能实时监督员工的工作状况,做老板真的很辛苦,除了“发号”以外,还要干很多事情,所以员工的“完成报告”在某种程度上可以减轻老板的活儿(使编程更简单),毕竟老板也是人,他也有疲惫的时候。

接下来的话题便是:“每一个低功能模块仅包含一个功能”。

虽然在现实中,确实存在“全能的人类”打扫,洗厕所,跑腿等技能全都集于一身。但是“低级建模”的准则必须遵守。你尝试想象一下,如果一个“低级功能模块”,包含了如上的工作 “打扫 ==> 洗厕所 ==> 跑腿” 或者更多,即不是要把代码写得很长很长 ...

所以呀,“低级建模”的准则有它一定的“重要性”(在日后的深入中,你会慢慢了解的)。

2.4 组织的概念

“组织模块”在“低级建模”中,非常的重要。它不但简化对多个“低级功能模块”的调用,而且也解决了“两义性”或者“多义性”的问题。

你尝试想象一下:如果有多个打工仔,散落在不同的地方。当老板要发号的时候,既不是非常不方便。同样的,在模块化设计中,设计者往往为了使使用更简单,常常都会使用“顶层模块”将多个模块“封装”到一个模块中,亦即将复杂的东西“隐藏”了起来,只剩下简单“接口”而已。这样的做法是为了使该模块可以容易被使用。

然而在“低级建模”的设计中,“模块化的组织”更有“层次感”。为了使“上一层模块”可以很方便调用“下一层组织模块”。“低级建模”的设计常常将一组或者一个“组织模块+低级功能模块”,“低级功能模块+低级功能模块”,“组织模块+组织模块”组织起来。虽然感觉上会有一种“杂乱感”,但是实际运用起来,真的非常方便。

如上面的示意图中,3个员工被组织了起来,然后3个员工的组合又和领导组织了起来。故这样的组织方法因层次关系,如此类推,最后会有两个“大组织”

组织1 = { 经理 => 领导 => 3个用工 }
组织2 = { 经理 => 3个员工 }

“低级建模”的“组织”结果会是示意图中所示。除老板意外,大家都有自己的“组织”。

假设老板要命了员工干活,那么老板只要命令任意一个经理就行。

至于“二义性”或者“多义性”的问题,后面会讨论到。

第3章“低级功能模块”的模板结构

3.1 模板基本结构

从上述中,模板的基本结构有以下的特征:

1) Start_Sig 和 Done_Sig是固定的。
2) 寄存器i用于控制次序步骤。
3) 最后两个i步骤用于产生完成信号。
4) i 等于 0 的时候,多半都是用于初始化动作(选择性)。

正如准则的要求,“开始信号”和“完成信号”都是必须的。“开始信号”可以视为“片选信号”而“完成信号”如字面上的意思。寄存器i有一个重要的功能,它指向任何下一个步骤,而通常所编写的格式如下:

除此之外该模板还引入了 “task - endtask”。目的是为了提升和 “结构性” 。新手们应该知道,使用V语言如果没有良好的编程风格,代码的 “可读性” 是非常的糟糕。

在这里我先简单复习一下,“task - endtask” 的用法 :

上面的两个写法都是等价的。如果模块是小功能,那么左边的写法很适合。但是一旦模块的功能很复杂,那么右边的写法会凸显出优势。

3.2 建议

为什么需要模板结构?

创建代码的工作往往都是一次性,为了供人参考,或者为日后“升级”的打算。我们不得不养成好的“编程风格”,这也是许多参考书上提出的重点之一。而“模板”便是一种已经制定好的“编程风格”,故这样会简化了编程风格上的问题,只要加以修改,便会完整一个有“结构”和“有风格”的代码。

为什么“低级功能模块”的步骤,需要一个计数寄存器来指向呢?

其实这个问题我也考虑了很久,因为是“仿顺序操作”的关系,故人类对“1, 2, 3 ... ”类似的次序(步骤)有更直接的效果。而且也很好的为代码扩展。

编写“低级功能模块”时,必须遵守笔者提议的模板结构吗?

模板的结构只是一个参考而已。该“模板”结论是我经过不同风格的编程,得出“最通用”的结果。当然你可以无视我的规定,完全自定义自己的模板结构。但是有一点请注意,必须以“解读性”为优先考虑。因为好的代码不是在“执行效率”上,而是“可维护”和“被解读”。

第4章 续流水灯实验——“低级建模”实例

4.1建立2个“低级功能模块”

“自左向右循环3次,自右向左循环5次,然后自左向右一次,自右向左一次,然后自左向右循环30次”
假设这是实验的要求, 首先我们先建立两个“低级功能模块”。一个名为flashing_to_left 和 flashing_to_right 。

两个模块几乎是一模一样,不同的地方只是在 53行而已。虽然代码都很简单,但是还是稍微关心 45 ~ 61 行的代码,因为是功能的核心部分。正如模板结构般,在第45行,如果Start_Sig 不处于高电平,那么该模块根本无法执行功能。计数寄存器i使用与指向下一个步骤,正如在第49行,当rData 被初始化过后,i指向下一个步骤。

在51行到53行之间,是移位操作,每一次的移位动作都需要200个时钟周期。移位操作一共有8个步骤。最后该模块产生一个“高脉冲”以表示“完成”,然而i也被复位为0。

仿真效果如下

(第一行是输出Q,第二行是Start_Sig,第三行是完成信号)

( 最后一行是完成信号 )

注意最后一行,当i 计数从1~8过后,就产生一个完成信号,而完成信号需要两个时钟周期的时间。

完成创建2个“低级功能模块”以后,为了使日后调用方便,必须封装起来。“低级功能模块”的封装工作会比较麻烦,因为“低级功能模块”有“复用连线”的现象,换一句话说,会出现“两义性”或者“多义性”的问题 ......

4.2 “低级功能模块”封装 和“两义性”或者“多义性”的问题

如上的示意图。当我们要封装2个“低级功能模块”在一次,如果两个“低级功能模块”使用同一个输入(重用输入连线)或者同一个输出(重用输出连线)就会出现两义性的问题。为了解决这个问题,使用“多路选择器”便可以。

为了解决多“两义性”或者“多义性”的问题多路选择器常常被使用。如上述代码中在45~52行(从第27,第40行引出“连线”)“Q_U1”和“Q_U2”被if控制着输出。这样的写法是最优化的,生成的RTL图也非常的整洁。

当然还有其他的写法:

虽然如上的写法和,第45到52行的写法相比,更为简洁,而且生成的 RTL图也一样。但是这样的写法有如下的弱点:

1. 解读性很差。
2. 只能解决两义性的问题而已。
所以不怎么推荐使用。
还有一中更糟糕的写法:

虽然该写法的解读性很高效果也一样,但是却很浪费资源。生成的RTL图如下:

RTL图

和上述两个写法相比,它可差多了,所以不推荐使用。
/**********************************************************/

接下来,我们来分析上面的代码。

“always @ ( * )”这样的写法在Verilog HDL 2001 中已经被支持(好像是这个版本)。在敏感包中的“*”,可以理解为“任何状况都有效”。

而“else rQ = 3'dx”,这行不能被省略掉。不要问我为什么,这是V语言的编程规则。你尝试注释掉后,再编译看看,你会发现会很多“Warning”。

还有一点请注意,因为“Q_U1”和“Q_U2”是连线的关系,所以必须使用寄存器来驱动。如上的代码中“rQ”便是扮演这样的角色。如果该寄存器被省略了,会出现编译错误。

总结:

“两义性”或者“多义性”的问题,不仅在“低级建模”中出现,日常的建模也会出现它们的踪影,然而“低级建模”出现的频率比较高罢了。“多路选择器”在设计的时候应该经多方面的考虑,亦即取得最平衡的效果。

4. 3 建立“产生效果”的 “低级功能模块”

首先,我们先回顾一下“低级建模”的准则。低级建模的准则如下:

1. 有“组织模块”和“功能模块”之分。
2. “低级建模”中的“功能模块”均称为“低级功能模块”。
3. “低级功能模块”有如特点:有开始信号,完成信号,一个模块只有一个工能
4. 多个“低级功能模块”组织起来称为“组织模块”。

在上一章节,我们建立了两个“低级功能模块”,各个模块都有“开始信号”,“完成信号”,和“单一的功能”(准则2, 3),然后将两个“低级功能模块”组织了起来(准则1,4),

“ 自左向右循环3次,自右向左循环5次,然后自左向右一次,自右向左一次,然后自左向右循环30次”
这一章节,我们要从上一章节的基础上,建立另一个“低级功能模块”,用来产生如上的效果。

然后再将它与之前已经组织好的模块,再一次组织起来。

概念图如下:

“产生效果的模块”代码如下:

这个effect_module由于使用模板的关系,多以编程格式与flashing_to_right/left 大同小异。但是核心功能部分就稍微复杂一些。

第23行和24行,声明用的寄存器“isRight”和“isLeft”分别对应flashing_to_right 和 flashing_to_left 的“Start_Sig”。
在40行,第1~3, 9 i步骤,执行“Flash_Right”任务。在56~62行,巧妙的利用“开始信号”和“完成信号”来执行“flashing_to_right”模块。正如下代码:

一开始 “isRight" 的初始值为0,所以会进入 else语句,然后“isRight”被设置为1。当“isRight"为1时,“Right_Start_Sig”被拉高(第88行),换一句话说就是“使能flashing_to_right ”模块。

在“flashing_to_right”模块执行完成之前(产生完成信号),i步骤不变。当检查到“完成信号”,i会指向下一个步骤,然后isRight设置为0(关闭flashing_to_right模块)。

即使是Flash_Left 任务,还是其他什么任务。只要涉及到“开始信号”和“完成信号”,都可以使用这样的写法,这是“低级功能模块”的编程好处。

在第30行至50行之间,这个功能模块产生的效果,如i指向的步骤那样,0时初始化,1~3,9时执行“flashing_to_right”功能,然后 4~8,10 时执行“flashing_to_left”功能等等......

最后会产生如下的效果:

“ 自左向右循环3次,自右向左循环5次,然后自左向右一次,自右向左一次,然后自左向右循环30次”
在最后两个步骤,产生完成信号。

就这样,一个符合“低级建模”准则的“低级功能模块”就完成了。这个模块只是“产生控制次序而已”,还不是完成品。在下一章,当将“effect_module”和“flashing_module”组织过后,就会成为完成品。

4. 4 : 再一次“组织”起来:

这一章,我们要讲 effect_module 和 flashing_module 组织起来,然后命名为“done”

代码如下:

这是第二层的组织了,到这里基本上我们已经完成如下图的效果

下面是仿真的载图:

总结:

实际上“低级建模”不是一个完美的东西,它本身就存在很多缺陷,如:建模量很大等...

除此之外,“低级建模”在“时序”上还是很弱,如VGA的驱动程式,它真的有点力不从心(理论上是这样,我没有更多的时间测试其他程式了)。

正如我在书语上所说的,它是针对新手提出的“一种建模”思路。主要是针对“仿顺序操作”的执行概念,其余的还有“编程风格”,“建模模块”等在设计思路上的扩展。

最新课程

  • 深入浅出玩儿转FPGA

    本视频基于Xilinx公司的Artix-7FPGA器件以及各种丰富的入门和进阶外设,提供了一些典型的工程实例,帮助读者从FPGA基础知识、逻辑设计概念

  • 从零开始大战FPGA基础篇

    本课程为“从零开始大战FPGA”系列课程的基础篇。课程通俗易懂、逻辑性强、示例丰富,课程中尤其强调在设计过程中对“时序”和“逻辑”的把控,以及硬件描述语言与硬件电路相对应的“

  • Verilog基础及典型数字

    课程中首先会给大家讲解在企业中一般数字电路从算法到流片这整个过程中会涉及到哪些流程,都分别使用什么工具,以及其中每个流程都分别做了