仿真的扫盲文

声明:本文为黑金动力社区(http://www.heijin.org)原创教程,如需转载请注明出处,谢谢!


黑金动力社区2013年原创教程连载计划:

http://www.cnblogs.com/alinx/p/3362790.html


《FPGA那些事儿—Modelsim仿真技巧》REV1.0 PDF下载地址:

http://www.heijin.org/forum.php?mod=viewthread&tid=22419&extra=page%3D1


前 言

第一章 仿真的扫盲文   

1.1 Modelsim 是电视机

1.2 仿真和调试

1.3 理想与物理

1.4 综合与验证

1.5 激励文本(Testbench)   

1.6 仿真流程

1.7 建模的切糕

1.8 仿真的切糕

1.9 自动思想的简介

1.10 不可仿真对象的简介

总结


笔者一直以来都在纠结,自己是否要为仿真编辑相关的教程呢?一般而言,Modelsim等价仿真已经成为大众的常识,但是学习仿真是否学习Modelsim,笔者则是一直保持保留的态度。笔者认为,仿真是Modelsim,但是Modelsim不是仿真,严格来讲Modelsim只是仿真所需的工具而已,又或者说Modelsim只是学习仿真的一部小插曲而已。除此之外,笔者也认为仿真可以是验证语言,但是验证语言却不是仿真,因为验证语言只是仿真的一小部分而已,事实上仿真也不一定需要验证语言。

常规告诉笔者,仿真一定要学习Modelsim还有验证语言,亦即Modelsim除了学习操作软件以外,我们还要熟悉 TCL命令(Tool Command Language)。此外,学习验证语言除了掌握部分关键字以外,还要记忆熟悉大量的系统函数,还有预处理。年轻的笔者,因为年少无知就这样上当了,最后笔者因为承受不了那巨大的学习负担,结果自爆了。

经过惨痛的经历以后,笔者重新思考“仿真是什么?”,仿真难道是常规口中说过的东西吗?还是其它呢?苦思冥想后,笔者终于悟道“仿真既是虚拟建模”这一概念。虚拟建模还有实际建模除了概念(环境)的差别以外,两者其实是同样的东西。换句话说,一套用在实际建模的习惯,也能应用在仿真的身上。

按照这条线索继续思考,笔者发现仿真其实是复合体,其中包括建模,时序等各种基础知识。换言之,仿真不仅需要一定程度的基础,仿真不能按照常规去理解,不然脑袋会短路。期间,笔者发现愈多细节,那压抑不了的求知欲也就愈烧愈旺盛,就这样日夜颠倒研究一段时间以后,笔者终于遇见仿真的关键,亦即个体仿真与整体仿真之间的差异。

常规的参考书一般都是讨论个体仿真而已,然而它们不曾涉及整体仿真。一个过多模块其中的仿真对象好比一块大切糕,压倒性的仿真信息会让我们喘不过起来,为此笔者开始找寻解决方法。后来笔者又发现到,早期建模会严重影响仿真的表现,如果笔者不规则分化整体模块,仿真很容易会变得一团糟,而且模块也会失去连接性。

笔者愈是深入研究仿真,愈是发现以往不曾遇见的细节问题,然而这些细节问题也未曾出现在任何一本参考书的身上。渐渐地,笔者开始认识,那些所谓的权威还有常规,从根本上只是外表好看的纸老虎而已,细节的涉及程度完全不行。笔者非常后悔,为什么自己会浪费那么多时间在它们的身上。可恶的常规!快把笔者的青春还回来! 所以说,常规什么的最讨厌了,最好统统都给我爆炸去吧!呜咕,过多怨气实在一言难尽,欲知详情,读者自己看书去吧 …

第一章 仿真的扫盲文

1.1 Modelsim 是电视机

如果笔者提问Modelsim 是为何物?想必同学们都会认为“Modelsim就是仿真”这种等价的关系。草草而言,该想法只是美丽的误会而已,笔者眼中Modelsim 只是类似电视机的工具。我们知道电视机除了播放功能以外,它甚么也不是。换之,身为用户的我们,使用电视机就要学会开关和调节节目,而不是去研究构造和功能原理。

Modelsim成功播放波形图以后自然可以功成身退。笔者一直以来都无法理解,为甚么会有那么多同学特别纠结 Modelsim 的五脏六腑,笔者不知要佩服他们的挑战精神才好,还是要讽刺他们皮痒才好呢?因为信心满满到的他们,最终都会被Modelsim搞到变成破烂回来,结果实在惨不忍睹。笔者也不是有意中伤他们,因为笔者也是经验者。

Modelsim 有各种各样的版本,除了官方的默认版本以外,还有第二方的自定义版本,如Altera Modelsim SE或者Altera Modelsim PE。以上是 Altera 公司自定义的两个版本, SE 是 Start Edition 亦即入门版本,AE是 Altera Edition,亦即是付费版本。

许多同学都认为 AE一定比 SE 更强更加好用。逻辑上的确如此,不过 AE 与 SE之间宛如 24寸 与 19寸液晶显示器的大小差别而已。基本上SE已经足够应付一切仿真应用了,此外SE还有许多功能根本排不上用场。婆婆曾说过做人要节俭,东西够用就好,过多就是浪费,所以笔者告诫读者不要过度纠结 AE,如果读者硬要自寻烦恼的话,那么后果请自负。

笔者知道自己很烦,最后还是要再强调一下,目前同学们只要把 Modelsim当成电视机就好,然而 Modelsim除了学习开关之余,还有就是调节节目而已,至于详细的用法,往后我们会慢慢接触到。

1.2 仿真和调试

曾笔者还没讲述“仿真”之前,让我们在根本上先理解仿真和调试的区别。仿真这词的同义虽然接近调试可是仿真却不等于调试。调试用来观察结果,仿真则是观察过程。仿真有“功能仿真”和“行为仿真”两个专业分类,对此调试也有“下线调试”和“上线调试”两个专业分类,让笔者用表格来说明:

表格1.2.1 仿真与调试的对应关系。

并行语言

顺序语言

仿真

调试

功能仿真

下线调试

行为仿真

上线调试

那么,什么是上线调试与下线调试呢?上线调试最为常见的方法就是将程序下载到开发板,然后再观察开发板的结果是否与预期一样,如发出“哔哔哔”声的程序,下去开发板后蜂鸣器发出“哔哔哔”的话,那么程序就合格。此外,上线调试还有较为细腻的方法,就是利用专用的上位程序或者集成环境序同步测试开发板,详细情形笔者就不谈了,有玩过单片机的朋友一定略知一二。

下线调试就是隔着开发板在电脑上模拟程序的测试结果,让笔者用例子详细说明吧。

1.    main()
2.    {
3.        int varA = 1;
4.        printf( "%d", varA );
5.    }

.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, “Courier New”, courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }

代码1.2.1

代码1.2.1大意是指,第3行声明整型变量varA然后赋予初值16,然后再第4行使用打印函数——printf 输出 varA的储存结果。假设varA的储存结果1对应LED点亮,那0对应LED消灭。如代码1.2.1所示,它会告诉我们LED最终会点亮,因为varA被赋予初值1。

所谓下线调试就是在没有开发板的情况下,利用模拟环境取得假想结果,假想结果再经由大脑进一步脑补(反映)实际的情况。如代码1.2.1所示,当我们在集成环境(IDE)按下 <F5> 或者 <F6> 的测试热键,varA的输出结果“1”就会在集成环境的信息窗口显示出来。再来经由我们自己脑补道:“varA的输出结果是1,那么开发板的LED是点亮的”。

到目前为止,笔者也只是大概讲述一下“调试”的内容而已,事实上“仿真”的内容比起“调试”还要麻烦许多,如果读者要确确实实理解调试与仿真之前的实际区别,读者就必须从语言的本质开始理解。

在笔者的眼中,一些高级语言如 C, C++, Java 等都视为顺序语言,此外还有古老的Basic 和汇编也是一样。为什么笔者之将它们称为顺序语言呢?原因很单纯,因为这些语言是按着顺序的步骤在执行操作,举个例子:

1.    main() 
2.    {
3.        varA = 1;
4.        varB = 2;
5.        varC = 3;
6.    }

.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, “Courier New”, courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }

代码1.2.2

代码1.2.2的3~5行是varA,varB与varC的赋值操作,先是varA赋予1值,然后varB再赋予2值,最后varC赋予3值。在此之间,varB的赋值操作需要等待 varA的赋值操作完毕之后才能执行,同样varC 的赋值操作需要等待 varB赋值完毕之后才被执行。varA,varB与varC之间的赋值操作延迟也称为”步骤差“。

故名思议,顺序语言会让我们普遍将代码以步骤的单位去认为,如果步骤1不完成执行,步骤2就无法继续,其它以此类推。笔者再将代码1.2.2换成另一个形式看看:

1.    main() 
2.    {
3.        varA = 1; varB = 2; varC = 3;
4.    }

.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, “Courier New”, courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }

代码1.2.3

代码1.2.3是将代码1.2.2的3行集为1行,然后我们会这样解读代码: ”第3行,varA赋予1值,varB赋予2值,varC赋予3值。“,笔者再顽皮一点,继续胡搞代码1.2.3:

1.    main() 
2.    {
3.        varC = 3; varB = 2; varA = 1; 
4.    }

.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, “Courier New”, courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }

代码1.2.4

代码1.2.4是将代码1.2.3的赋值操来回颠倒,然后我们会这样解读代码: ”第3行,varC赋予3值,varB赋予2值,varA赋予1值。“,无论笔者怎样胡搞varA,varB与varC的位置,我们都无法割舍步骤这个单位去解读代码中 varA 与 varB 还有 varC的赋值过程。因为只要失去“步骤”我们就会迷失解读的方向,这是顺序语言的特征,我们也可以说“步骤就是支撑整体顺序语言最基本的结构”。

在此读者必须理解,“平常我们就是太习以为常使用步骤这个单位去解读代码,不知不觉习惯就成为理所当然”,但是事实却好相反,这种想法还有这种思路也仅限于“调试”这个框架而已。此外,读者还必须理解“步骤”只是可视的宏观单位而已,然而不可视的微观单位却是“指令”。假设varA = 1的赋值操作,可视步骤也只有1个,但是在隐藏中,varA = 1这样简单的赋值操作到底需N个指令完成,完全取决与编译器的编译质量。

对于我们这些只会认为肉眼看见才是事实的小白而言,指令就像不存在与人界的幽灵般,时而增多时而减少,尽是虚幻也难以捉摸。除此之外,处理指令所需的时钟,也会根据指令的版本,处理器的工艺等因素产生改变。讲白点,调试只会输出步骤的结果却无视指令的内容,它也会无视时钟的消耗数。

因此读者必须理解,调试一般离不开顺序语言,宏观上顺序语言需由由步骤这个可视单位支撑。微观上,步骤则是由无数的指令在后面支撑着,然而指令的内容还有时钟的消耗数都是隐藏内容。总结说,调试只在乎步骤在表面上产生的结果而已,又或者说是追求单向结果。

笔者一直以来都在不停思考,为什么Verilog HDL 不使用“调试”而是“仿真”这词呢?有些朋友可能会认为笔者有点过于钻牛角尖了,对此笔者不敢否认,但是那种违和感时时刻刻都在折磨笔者,笔者也十分焦急想将它揪出来。我们知道“调试”与顺序语言有切不断的关系,然而“仿真”却与并行语言有着强烈的羁绊。

所谓的并行语言有如VHDL或者Verilog HDL 等各种描述语言,描述语言不像顺序语言有步骤支撑整体的顺序结构,结果描述语言显得结构自由,甚至称为没有结构的地步。顺序语言每一个关键字都在暗示处理器的处理行为,然而描述语言每一个关键字只是描述手段,我们用它在白纸上绘出我们想要的“形状”。

顺序语言与并行语言之间最大的差异就是,顺序语言每一个时钟只能执行一个步骤(如果这个步骤在一个时钟内完成的话),然而并行语言可以在一个时钟内执行千千万万个步骤,上限是没有尽头的。顺序语言之所以不关心时钟,那是因为时钟不仅隐藏而且还无法控制。反之,并行语言的时钟不仅开放也能控制。

仿真有“功能仿真”还有“行为仿真”两大分类,如表格1.2.1所示。功能仿真与行为仿真的差别,即前者是下线,后者则是上线。一般所谓的仿真就是“功能仿真”,而不是行为仿真,为了避免浑浊,往下内容笔者皆用“仿真”来表示“功能仿真”。至于行为仿真已经超出本书的范围,怒不解释。

仿真是一件很麻烦的事情,而且“仿真”也不像“调试”那样,只要轻松按下 <F5> 或者 <F6> 等调试热键,信息就会像水一样,花啦啦地打印出来。换之,执行仿真之前必须经历许多准备工作,如创建仿真对象,编辑仿真环境,仿真之间必我们必须一边追踪过程,一边解析信息。

笔者曾说过调试是追求单向结果,如果用仿真来比拟调试,调试是用来断定某个信号在某个时钟的某个结果而已,如:信号B在时钟T10的结果是逻辑0,又或者数据C在时钟T12的结果是8’hAA。反之,仿真是追求多向过程,亦即N个信号在所有个时钟发生里什么结果,如:模块A,有信号A与数据B,而且模块A的一次性操作需要耗时10个时钟,那么仿真会用来观察信号A与数据B在10个时钟内的结果变化。

笔者曾被仿真杀死过许多次,如果不是它在笔者最绝望的时候拉笔者一把,如今笔者就是一只怨气十足的冤魂了。学习仿真就像处于金庸所描述的江湖般,那里存在许多各种帮门流派,其中一种称为传统流派,也是网络流传已久的右翼硬派,门徒最多,死人也是最多。初落平阳的笔者为了寻求照应,就这样糊里糊涂加入其中。

期间笔者至少死过十余来次,最后终于支持不住,一心来到崖边寻求解脱。那是笔者的人生当中最黑暗的一刻,就在跳下的瞬间,一直温柔却有力的右手揪主笔者,然后让笔者感动不已的声音传遍全身:“孩子,千万别做傻事,明天总有希望!”,此刻是笔者最痛哭的一次。

为了寻找仿真之道,为了知晓那份违和感 … 经过七七四十九天的闭关以后,歪道终于开窍,所有问题自然迎刃而解。歪道是什么?那是完全背离传统的知识,它像毒瘾一般让人深入无法自拔,甚至落沦魔鬼。歪道宛如不懂善恶的恶魔一般,只会给予方法却不会承担后果,这是沾染歪道的唯一风险。

1.3 理想与物理

理想与物理就像梦想与现实之间的关系 … 我们知道现实世界(物理)是充满病痛,鄙视,欺骗还有杀戮等各种悲剧的复合空间,然而传统流派就是基于它们。笔者第一次踏入传统流派的大门,一股恶寒经由脚根直达脊椎,全身也不禁颤栗起来。反之,理想想世界却是现实的相反,那里不存在任何悲剧。

闭关期间,笔者曾经穿梭诸神逗留的乌托邦,眼前出现的一切不经让笔者目睁口呆。在那里,生命都有黄金比例的结构,大伙都是协调相处,啊!多么理想的世界 … 对!这就是笔者向往的世界,也是仿真应该演变的方向,而不是那坑坑爸爸的物理世界。我们知道时序就是寄存器还有组合逻辑产生的活动(信号),传统流派强调时序应该接近物理,亦即物理时序,反之笔者强调时序是理想完美,亦即理想时序。

clip_image002

图1.3.1 理想世界与物理世界的寄存器。

图1.3.1显示有两个世界的居民,左图是理想世界,右图是物理世界。理想世界是非常整洁的世界,不像物理世界存在许多物理元素如: Tco/Tsu/Th 等寄存器特性以外,还有 tPath等物理延迟。初学的朋友可能会问:什么是寄存器特性?什么又是物理延迟?,朋友可以将它们想象为阻碍寄存器沟通的障碍。

clip_image004

图1.3.2 理想时序与物理时序。

图1.3.3是寄存器之间的活动记录(沟通记录),亦即时序,也是该世界唯一可视的信息。左图是理想世界产生的理想时序,右图是物理世界产生的物理时序。根据左图所示,寄存器1在T1的时候向寄存器2发送(启动)数据,接着寄存器2便在T2接收(锁存)并且输出。根据右图的表现,寄存器1在T1向寄存器2发送数据,可是数据遭受Tco的妨碍之余,还被Tpath拖后腿,最终数据在T4被寄存器2读取,不过寄存器2在读取之间还要考虑 Tsu与Th是否满足。

左图是非常整洁又高效的沟通过程,然而右图是沟通过程非常烦乱。试问读者,哪一个时序图更加顺眼更加容易解读?答案理想是理想时序,不是吗?在此,心机重的读者可能会反驳道:“叫兽说过时序不能能缺少物理元素,如果物理元素被吃掉,时序还是时序吗?”可怜的读者,叫兽就是风水师,骗人骗到祖宗十八代也不奇怪。

首先我们必须理解,所谓仿真就是在虚拟的环境下运行模块,测试功能是否按照预期执行。为此,我们为何不取最理想的结果呢? 举例而言,假设模块A,要求T1拉高输出,然后T2拉低输出。为此,仿真仅是单纯地观察它是在T1拉高输出,然后在T2拉低输出——这是理想状态。而不是观察模块A在1ps拉高的电平是多少V,2ps拉低电平多少V——这是物理状态。

再者,笔者也强调过,Modelsim只是一台电视机而已,功能就是播放波形(时序),这种情形宛如读者看电视,要享受当然选择高清节目,而不是走色的崩坏节目。理想时序就是高清节目,物理时序就是崩坏节目,仿真就是享受高清节目这么一回事,读者能理解吗?

在此,有些同学可能会不安道:“物理时序该怎么办?物理元素该怎么办?”。为此,先让笔者帮忙消除不安:

1) Tco/Tsu/Th 寄存器特性,或者 Tpath 等物理延迟,理想时序都会无视。

2) Modelsim只是一台电视机而已,它可以播放理想时序也可以播放物理时序,不过没有傻子会喜欢崩坏的节目。

3) 物理时序Modelsim 虽然可以播放但是无法解决。此外,各大FPGA厂商早已经为物理时序准备好各种仿真和纠正的工具,如TimeQuest。

4) 笔者也准备好物理时序的教程。

读者用不着担心理想时序多难学习,事实上理想时序相较物理时序更加容易掌握。再者,笔者爱用的建模技巧,仿真技巧,整合技巧,甚至静态时序分析,都有应用理想时序。

理想时序作为设计是非常重要的概念,尤其是仿真的环节上,它不仅可以减少仿真的难度,也可以减轻激励文本的编辑工作,还有内容的解读。

它曾说过:“理想的开始就是成功的一切”,这句话暗喻心情的重要性,理想或者美丽的东西会舒缓心情,结果高产。反之,瑕疵还有丑陋的东西会搞坏心情,结果难产。这种感觉好比新年图新,什么都是新,华人相信新东西除了示意好开始之外还有圆满和理想的含义,因为新东西没有肮脏和瑕疵。

1.4 综合与验证

描述语言专业分类有综合语言和验证语言,一般认为综合语言用来设计,验证语言用来仿真,说实说那是放屁!笔者还记得第一天学艺的时候,师兄当下给笔者递过一本厚厚的书籍,封面写着“验证语言”,笔者随意一翻,蛋蛋立即落在地上。因为内容仅是意义不明的关键字还有语法。

师兄最后还说道:“今天给老子啃完,不然明天把你干掉 …. ”

许多新手曾是那样,综合语言还没有掌握又要立即学习验证语言,不然仿真就无法开工。老实说,别开本大爷的玩笑了!拜托了,笔者是人不是吸尘机,不可能在短时间内吸收那么多东西。学习和恋爱一样,不能同时一脚踏两船,不然进度会进入两头不到岸的窘境 …. 直到最后,不管综合语言还是验证语言,半桶水的程度也没有达到。

此外,验证语言也会随着年份拉长,内容也会不断增加,如Verilog 1993 进化到 Verilog 2001(据说未来还会继续增长)。只要稍微打开手册一看,我们立即就会发现验证语言占满全量的4/5,这点无疑是一起厚重的压力。闭关期间,笔者一直苦思冥想:“仿真的定义是什么?为什么仿真离不开验证语言呢?”不知不觉,笔者的意识再度穿梭诸神逗留的乌托邦。

它告诉笔者:“物理世界有资源却有法则束缚,理想世界没有资源也没有法则束缚 ,真是一言惊醒梦中人。仿真是利用虚拟的环境取得假想的结果。然而,这个虚拟环境,假想空间,没有所谓的物理限制,如:理想的FPGA有数不尽的逻辑资源,开发板要什么硬件就有什么硬件。但是这个虚拟环境却没有实际的资源,如时钟信号什么的。为此,我们需要利用验证语言产生虚拟的时钟信号。

上面的内容告诉我们一个非常重要的信息,亦即仿真也是建模,不过是虚拟建模,它虽然不会局限于硬件,但是需要模拟实际资源,为此需要用到验证语言。为此,笔者得到这样一个问题,如果验证语言可以用来描述虚拟的资源,为什么综合语言不能用来描述虚拟的资源呢?。

实际上,仿真只要最小利用验证语言而已,例如产生时钟信号什么的,之余其余的描述工作,我们都可以交由综合语言去搞定。这是笔者最活跃的仿真思想,把仿真当成建模来玩。反之,传统流派的仿真概念好像被堵塞的臭水沟一样非常死非常臭,仿真和建模有绝对的分割线划开,建模就是综合,仿真就是验证,两者没有深切的关系。反之笔者却认为,仿真与建模不仅关系深切,而且两者之间只有概念的差别而已,即一个是虚拟建模,一个则是实际建模。

1.5 激励文本(Testbench)

激励文本或称激励文件,英文名为 Testbench,常见的后缀名有 .vt 与 .tb。笔者曾问过师兄, 激励文件是什么,师兄却怒吼道: ”激励文件就是激励文件啦,怎么!想死吗!?“,这是传统流派给予的回答。根据笔者的妄想,激励文本宛如绘出虚拟世界的一张白纸,亦即仿真环境。然而,我们就是创建这个环境的神明.

身为神明,我们有5项重任:

1) 产生环境输入。

2) 建立仿真对象。

3) 产生虚拟输入。

4) 产生虚拟输出。

5) 观察世界,更正世界。

clip_image006

图1.5.1 神明的任务之一。

如图1.5.1所示,那是仿真环境最基本的的概念。首先是环境输入,环境输入一般则是模块所要的时钟信号还有复位信号,它们都是最基本的需要,这种感觉好比水源,空气等 … 任一缺少仿真环境也无法成立。仿真对象好比居住在仿真环境的生物,它一般先在集成环境建模,然后实例化在激励文本当中,我们当然也可以直接在激励文本中描述仿真对象。

虚拟输入又指刺激,这种感觉好比生物的生存危机,如外敌如入侵,资源干枯等。仿真对象除了需要环境输入,仿真对象也要虚拟输入刺激才行。虚拟输入又分为基本输入与反馈输入,基本输入可以视为第一刺激,反馈输入则是第二刺激,形象点说:假设外星人入侵地球,此为人类的第一刺激。事情发生以后,人类不仅没有团结,而且还出现叛徒,这是人类的第二刺激。

除了,环境输入还有虚拟输入以外,激励文本还有虚拟输出。虚拟输出又指反应,这种感觉好比生物对应危机的反应。虚拟输出也有基本输出与反馈输出之分,它们也称为第一反应与第二反应。这种感觉好比外星人入侵地球以后,有些人会绝望,有些人会反抗,有些人宁愿成为走狗,此为人类的第一反应。为了解决那些叛徒,联合国实现全名监控,此为人类的第二反应。

clip_image008

图1.5.2 神的任务之二。

仿真环境对神来说不过是心血来潮的实验场所而已,神闲来无事创建了仿真环境A,不久之后生物B便诞生。神为了刺激生物B进化,神召唤外星人攻击生物B,此刻生物B会出现各种抉择。如图1.5.2所示,那是仿真环境的演化过程,也是仿真环境的运动,亦即时序。作为神,我们的眼睛“全能之眼”时常处在高处窥视一切,如果觉得那里不顺眼就插手哪里。

例如神觉得外星人入侵太无趣了,于是顺便召唤陨石下来(更动虚拟输入) … 又或者神觉得生物B太弱了,然后强化它们(更动仿真对象)。再假设神觉得演化步伐太慢了,结果神加快时间的流失(更动环境输入)。在此,读者可能会觉得这个神太可恶了,把生物当成玩具来玩 … 嘛,别激动朋友,仿真本来就是那么一回事。

笔者说过,仿真既是虚拟建模,为此笔者开始自问:“激励文本是不是也要结构?”。答案是肯定的,激励文本有两种结构性,其一是布局的结构性,还有激励内容的结构性。

clip_image009

图1.5.3 布局的结构性。

如图1.5.3所示,那是笔者根据习惯,然后为激励文本所建立的布局结构。环境输入一般都是置于激励文本的最顶端,余下是仿真对象的实例化,接下则是虚拟输入还有虚拟输出,最后就是其它。好奇的同学可能会怀疑布局结构的重要性,这位同学试想一下,如果世界万物失去结构会是怎样的场景呢?是不是无法想象呢?根据笔者的认识,激励文本之所以需要布局结构,其一是为了维护激励文本内容,其二是为了节能。

除了布局结构以外,激励文本还有激励内容的结构性。笔者一般都将虚拟输入还有虚拟输出称为激励内容又或者激励过程。那是因为虚拟输入还有虚拟输出原本就是一组操作,而且操作都是经由无数步骤组成。笔者曾经说过,描述语言是自由结构的语言,步骤又是顺序操作的单位,默认下它是没有结构它的,为此笔者应用了低级建模的用法模板。

1.    reg [3:0]i;
2.    
3.        always @ ( posedge CLOCK or negedge RESET )
4.            if( !RESET )
5.                begin
6.                    i <= 4\'d0;
7.                    Start_Sig <= 1\'b0;
8.                    WrData <= 8\'d0;
9.                end                
10.              else 
11.                  case( i )
12.                    
13.                         0: 
14.                         if( Done_Sig ) begin Start_Sig <= 1\'b0; i <= i + 1\'b1; end
15.                         else begin WrData <= 8\'d8; Start_Sig <= 1\'b1; end
16.                         
17.                         1:
18.                         if( Done_Sig ) begin Start_Sig <= 1\'b0; i <= i + 1\'b1; end
19.                         else begin WrData <= 8\'d9; Start_Sig <= 1\'b1; end
20.                         
21.                         2:
22.                         if( Done_Sig ) begin Start_Sig <= 1\'b0; i <= i + 1\'b1; end
23.                         else begin WrData <= 8\'d10; Start_Sig <= 1\'b1; end
24.                         
25.                         3:
26.                         begin i <= i; end
27.                    
28.                    endcase
29.                    
30.        /***********************************/

.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, “Courier New”, courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }

 

代码1.5.1

如代码1.5.1所示,那是虚拟输入应用用法模板以后的例子。其中我们可以看见步骤i指向步骤,指向操作。用法模板除了为激励内容提供最基本的顺序结构以外,用法模板还会帮助我们节能。因为,如果仿真对象还有激励内容都有相同的用法模板,那么两者之间都能应用相同的思路,还有相同的习惯。

1.6 仿真流程

clip_image011

图1.6.1 仿真流程图。

为了帮助小白扫盲,笔者绘出简单明了的仿真流程图。如图1.6.1所示,里边拥有许多流程与分支,然而一切流程与分支都起源于“集成环境”。接下来,让我们从“集成环境”开始,然后来了解各个流程与分支。

流程1

1) 集成环境生成软模块,也是俗称的建模。

2) 软模块经过综合工具成为硬模块,也是俗称的综合或者编译。

3) 硬模块生成以后便下载到开发板观察输出,如果输出结果不理想,就返回步骤1。

软模块和硬模型都是笔者的专用语,软模型是意思是指综合之前或者下载到开发板之前的理想模块,没有实际的逻辑资源。反之,硬模型意思是指物理模块,拥有实际的逻辑资源。流程1也称为上线调试,亦即典型的调试方法,只要结果不符合预期,步骤就会返回开始,然后重复流程。该调试方式虽为最笨但也是最管用,不管调试对象是什么都适合。

流程2

1) 集成环境生成软模型。

2) 软模型经过仿真工具编译成为仿真对象。

3) 创建激励文本。

4) 激励文本作用仿真对象经由仿真工具输出波形(理想时序图)。

流程2是仿真最基本的仿真步骤,流程2相较常规的仿真流程,只有细节上的大同小异而已。在此有些读者可能会觉得疑惑,软模块与仿真对象都是理想模块,两者之间的差异究竟在哪里?理论上来说,软模块是未经加工的生肉,而且本质理想。换之,仿真对象是经过仿真工具加工过的正肉,本质也是理想。

流程2之后会产生3条通往不同分支。

流程2,分支1(仿真结果符合预期):

1) 仿真结果符合预期以后,流程会返回集成环境。

2) 软模型经过综合成为硬模型。

3) 硬模型下载到开发板观察输出,如果输出结果不理想返回步骤1。

流程2,分支1基本上是流程1的翻版,亦即仿真结果符合预期,但不等于实际效果是否理想,所以需要进一步将软模块综合成为硬模块,再下载到开发板观察输出是否达到预想效果?如果是,流程结束;如果不是,重复流程1。

流程2,分支2(仿真结果不符合预期,仿真对象有问题,返回集成环境):

1) 仿真结果不符合预期以后,流程返回集成环境更正软模型。

2) 更正以后的软模型,再经过仿真工具编译成为仿真对象。

3) 仿真对象经由仿真工具输出波形(理想时序图)。

流程2,分支3(仿真结果不符合预期,仿真对象有问题,返回仿真工具):

1) 仿真结果不符合预期以后,流程返回仿真工具更正软模型。

2) 更正以后的软模型,再经过仿真工具编译成为仿真对象。

3) 仿真对象经由仿真工具输出波形(理想时序图)。

流程2,分支2与3直接的差距就是第二次更正的软模型是经过集成环境还是仿真工具。在此,可能会有同学觉得疑惑它们之间的差异何在?集成环境拥有较强的更正能力,但也更加耗时耗力;相反,仿真工具的更正能力虽然不及集成环境,但是耗时耗力相对较小。不管选择哪一种分支,都是见仁见智的事情。

此外,还有一个关键点,一些仿真对象可能会携带官方插件模块,许多时候仿真工具都对官方插件模块的支持不怎么友善,主要问题是编译手段还有仿真库的问题。安全起见,那些携带官方插件模块的仿真对象返回集成环境会比较妥当。

流程2,分支4(仿真结果不符合预期,激励文本有问题):

1) 仿真结果不符合预期以后,流程重返更正激励文本。

2) 更正以后的激励文本再作用仿真对象输出波形图(理想时序图)。

流程2,分支4是激励文本有问题,除了最基本的语法错误以外,读者还要注意一下。仿真工具的编译器比较别扭,必须遵守先声明后调用这个规则。相比之下,集成环境的编译器比较醒目,声明还有调用的次序上下颠倒也没有问题。所以说,集成环境编译成功并不代表仿真工具一定编译成功,其中一定出现声明调用的次序问题。

总结来说,图1.6.1只是自定义的仿真流程而已,实际流程会因人而异。图1.6.1是笔者的经验总结,也是笔者的想象力爆发。它曾说过:“流程会根据平衡发生变化 … ”,这句话足让笔者深思许久,流程充其量只是便利的指南而已,活物不是跟死流程的机械人,所以笔者非常建议,流程看看以笑笑就好,不要过度纠结。

1.7 建模的切糕

切糕是什么?切糕是梦幻般的硬通货,传说投资切糕比起金银还有房地产更实在。市价好的时候可以换钱,灾难来的时候可以充饥,此外也有切糕达人一夜巨富的故事 … 啊哈哈,以上纯属恶搞而已。切糕是新疆的传统食物,既是玛仁糖,也是体积庞大的饼干,模块好比切糕,其实这种比喻一点也不夸张,还不如说再适合不过。一个系统模块的份量,差不多是一座200公斤重的切糕.。

clip_image013

图1.7.1 传统建模,单文件(单模块),多文件(多模块)。

传统建模有单模块与多模块之分。如图1.7.1所示,假设有四个模块A,B,C与D,左图是多模块建模,模块A与模块B各有独自的 .v文件,模块C与模块D则共用一个 .v文件。右图则是单模块建模,也是笔者常常讽刺的单文件主义,这里所有模块共处于一室。传统建模都有一贯的致命缺点,就是没有结构性可言。接下来,让笔者逐个分析它们的缺点吧。

首先是单模块建模,也是初学者最常犯的问题,所有模块都强挤在一起,虽然在编辑方面有过人之处,作为代价,单模块建模却为后期工作带来许多麻烦。单模块建模宛如所有代码集于一身的Main函数一样,不过别忘了,并行语言不是顺序语言,它没有“步骤”这个最小的单位去支撑。此外,单模块建模不仅拖累模块的表达能力,单模块建模也不适合仿真。这种建模过度集中的情况,最终会演变成一块大切糕。

仿真还没有开始之前,我们的心情就被切糕一样大的模块搞砸了,200公斤绝对不是普通人可以应付的重量,到头来,我们也只能傻乎乎看着它而已。所以说,要一口气搞定切糕一样大的模块绝非易事,于是人们开始动脑思考对策 … 如果整座切糕无法处理,我们是否可以分块处理恶?就这样“多模块”的思想开始流行起来。

传统的多模块建模如图1.7.1左图所示,模块A,B,C,D分别分散在各别的.v文件里,模块虽然有分化,但是模块却没有规则分化。这种感觉好比没有规则切整座切糕,结果每块小切糕都高矮不一。人类是一种自欺欺人的生物,人们以为只要分散整座切糕,压力就会不复存在,可是事实确是如此嘛?在此之前,先听笔者讲个故事:

某天,小明不幸在森林里迷路,衰到极点的小明被一只饿狼追杀,小明只有不停向前逃命而已。忽然间,眼前的状况把小明愣住了,因为眼前出现2条以上的分叉路,就在小明犹豫的一瞬间,饿狼就把小明推到了,可怜的小明死因却是一瞬的犹豫。可能读者会吐糟小明笨,干嘛停下犹豫,总之先跑再说 … 旁人总是在旁冷言冷语,因为它们不是当事者也无法知晓小明,同时它们也忽略一个重要的关键,那就是“小明当下的状态”。

小明之所以失去冷静,是因为焦急,为什么焦急,因为被饿狼追赶,然后最该死的关键是突如其来的分叉路,它害死小明出现片刻的犹豫。当我们在执行仿真的时候,我们的处境就好比小明,不 … 应该说是比小明更糟,因为小明起码被一只饿狼追杀而已。一只饿狼的好比一个过程在运行。

并行语言发生问题绝不仅一处,换句话说Verilog绝非一个时间执行一个过程,而是同一个时间有无数过程在同步执行。这种情况好比N只饿狼同时在追赶读者,普通人类根本无法同时应付N只饿狼。再者,无规则分散模块会将仿真搞成像迷宫一样,如果读者同时被N只饿狼在迷宫里追杀,想必读者的结局比小明更加惨不忍睹。为什么呢?原因很简单,无规则分散模块会将无数过程搞成错综复杂,在此犹豫的时间一定会多过小明,人一旦犹豫就会忘记如何前进。

曾经何时,笔者也像小明一样,同时被N只饿狼追赶,然而无规则分散模块的后果,仿真相似迷宫一样让足笔者琢磨不定,失去方寸,找不到仿真的切入口。仿真从模块A开始?还是从模块B?除此之外,激励文本的编辑工作也非常混乱。模块A与模块B应该共享一个激励文本?还是模块A与模块B拥有各自的激励文本呢?想着想着,就觉得头好疼,蛋蛋好不舒服。

圣者说过:“胡乱分散就是混乱”,不规则分散模块会导致模块变成迷宫,然后丢失仿真的方向。既然如此,我们还不如不分散为好,可是整座模块的压力实在太大了,不分散又不行。结果分也不是!不分也不是!左右为难,还不如死去算了!冷静点我的朋友,切糕一定要分,不过要有规则还有标准。

clip_image015

图1.8.1 低级建模的基本模块。

这种标准就是“功能”。模块按功能分化,这种感觉好比切糕按公斤划分。模块除了按“功能”量化以外,模块也根据按照“功能”类化。如图1.8.1所示,那是低级建模的基本模块,分别有控制模块,和功能模块。控制模块有正方形的外观,属于“控制类”;功能模块有长方形的外观,属于“功能类”。无论是哪一种模块,它们都遵守低级建模的准则,亦即“一个模块,只有一个功能”而已。

假设一套进食动作有以下几个步骤:

1) 举起食物

2) 张开嘴巴

3) 送入嘴中

4) 闭上嘴巴

5) 咬碎食物

6) 吞入肚子

一套完成的进食动作,基本上由6个步骤组成,每个步骤可以视为“功能类”。然而,左边的数字1~6却可是视为“控制类”。

clip_image017

图1.8.2 用餐模块化。

一套进食动作按照“功能”分化以后,结果如图1.8.2所示。每一个模块仅有一个功能类,如:举起食物,张开嘴巴,送入肚子,闭上嘴巴,压碎食物,送入肚子都是拥有一个模块”。至于用餐步骤,它属于“控制类”,它也有一个模块。

如果一座大切糕可以品均又有规划,分散成为每块小切糕的话,那么每块小切糕好比独立的个体一样 … 这种感觉好比整座切糕被分散成为无数可以承受的最小份量,最后再由耐心将全部慢慢吃掉。模块有规则分化以后,功能的复杂度也会跟着简化,形象点说就是一座复杂的大迷宫,无数分散成为简单的小迷宫,如此一来仿真就不容易迷路了。此外,控制模块也好比迷宫的入口一样,一切从它开始。为此,我们可以这样说:有规则分化模块除了可以简化迷宫以外,它也为仿真建立入口。

除此之外,该准则还有一个好处就是分散功能,分散过程,举例而言:一个大功能好比10只饿狼的狼群,面对它们我们不仅没有胜算,而且压力也很大了。不过,大功能经过分散以后,10只饿狼的狼群也会因此分散成,我们虽然不能一次单挑10只饿狼,但是我们可以10次单挑1只饿狼。

总结来说,有规划分散模块除了上述的好处以外,有规划分散模块也会为人带来好心情。这种感觉好比切糕太大我们会觉得反胃,切糕切成稀巴烂我们也会觉得恶心,不管哪一种情况都会影响我们进食的心情,仿真也是一样的道理。还有一点读者必须好好记住,建模还有仿真有千丝万缕切不断的关系,前期有好的建模,后期就有轻松的仿真。

1.8 仿真的切糕

笔者曾在1.8小节当中说过,单模块或者没有规则的多模块建模会将仿真搞成迷宫,让仿真失去方向,最糟还会搞坏心情。反之,有规则的多模块建模,不仅会简化迷宫也会给仿真前带来好心情。切糕除了出现在建模以外,切糕也会出现在仿真之前还有仿真之间。不过切糕又会以什么形式出现在仿真当中呢?这个问题就让笔者来慢慢长谈吧,好让读者有个深刻的认识。

clip_image009[1]

图1.8.1 激励文本。

笔者曾在小节1.5介绍过激励文本的作用。根据笔者的习惯,激励文本可以按着图1.8.1所示的次序插入相关激励内容。处于激励文本的顶端是环境输入,其次就是仿真对象。仿真对象一般都是先在集合环境里完成建模,然后再经由激励文本将其实例化成为仿真对象,如:

function_module.v
module function1( input CLK, input RESET, input SigD, output SigQ );
...
endmodule

function_module.vt
module function1_simulation();
function1 U1( .CLK(CLK), .RESET(RESET), .Sig(SigD), .SigQ(SigQ) );  // 仿真对象实例化,出入端声明
...
endmodule

.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, “Courier New”, courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }

代码1.8.1

笔者先在集成环境里创建名为function1的模块,为了仿真function1模块,笔者必须事先建立仿真环境。为此,笔者建立一个名为function1_simulation的仿真环境名,然后将 function1实例化为U1,过程如代码1.8.1所示。

根据代码1.8.1所示,function1有4个出入端,亦即 CLK信号,RESET信号,SigD信号,还有SigQ信号。在此,读者尝试想象一下,像整座像切糕一样大的模块到底有多少出入端呢?答案是肯定的,亦即非常多,而且还多到不可想象。过多的出入端无疑会拉长激励文本,结果导致激励文本成为臃肿的大肥,这是切糕出现在仿真中的第一种形式。

根据笔者的经历,20几个出入端还算是小切糕的程度而已,随着建模层次不断提升,出入端的数量也会不断增加,一个系统模块有50来个出入端也是非常普遍的事情。分散模块就是了预防这个问题,逻辑而言,模块愈是分散,出入端的数量理应也会分散,而且实例化也会变的更加轻松,激励文本因此也会变得更加苗条。浏览苗条的激励文本,好比我们观察苗条的美女一般,骨干的美丽还有完美的曲线(简洁直观的内容),不仅令我们流失压力,也让我们的心情变得愈加愉快,情绪越来越有干劲。

知晓切糕的第一种形式以后,接下来让我们寻找切糕的第二种形式。

==================================================================

出入端过多除了拉长激励文本以外,它也会影响激励过程的编辑工作。激励文本有两种激励内容,亦即虚拟输入还有虚拟输出,尤其是虚拟输入,出入端的影响会是更重。

clip_image019

图1.8.2 虚拟输入。

  reg [3:0]i;
2.        
3.        always @( posedge CLK or negedge RSTn )
4.            if( !RSTn )
5.                      begin
6.                      Pin_In = 1\'b1;
7.                      i <= 4\'d0;
8.                  end
9.             else 
10.                 case( i )
11.                  
12.                        0,1:
13.                        begin Pin_In <= 1\'b1; i <= i + 1\'b1; end
14.                         
15.                        2:
16.                        begin Pin_In <= 1\'b0; i <= i + 1\'b1; end
17.                        .....
18.                  
19.                  endcase

.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, “Courier New”, courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }

代码1.8.2 虚拟输入。

虚拟输入有基本输入与反馈输入,图1.8.2还有代码1.8.2则是基本输入最简单的例子。

如图1.8.2所示,激励内容经由 Pin_In产生基本输入刺激仿真对象。

clip_image021

图1.8.3 反馈输入的假想图。

1.        reg [3:0]i;

2.        always @ ( posedge CLK or negedge RSTn )
3.            if( !RSTn )
4.                  begin
5.                          i <= 4\'d0;
6.                         A <= 8\'d0;
7.                         B <= 8\'d0;
8.                         Start_Sig <= 1\'b0;
9.                end    
10.              else 
11.                  case( i )
12.                
13.                          0:
14.                         if( Done_Sig ) begin Start_Sig <= 1\'b0; i <= i + 1\'b1; end
15.                         else begin A <= 8\'d2; B <= 8\'d4; Start_Sig <= 1\'b1; end
16.                                 
17.                 endcase

.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, “Courier New”, courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }

代码1.8.3 虚拟输入。

图1.8.3还有代码1.8.4则是反馈输入简单的例子。首先,仿真对象会根据基本输入的刺激产生相关的反应,即基本输出。如代码1.8.3所示,在步骤0当中,A赋值 8\’d2, B赋值8\’d4,Start_Sig 则拉高,这是基本输入。仿真对象完成操作以后,它会经由 Done_Sig 反馈完成信号,这是基本输出。虚拟输入接收Done_Sig以后,便会拉低 Start_Sig,这是反馈输入。

眼睛犀利的同学想必已经猜想到切糕处的第二种形式了吧!没错,仿真对象的功能越是复杂,出入端的数量就越多,期间激励内容的编辑工作不仅会变得繁重,而且激励文本也会越来越臃肿,内容变得非常杂乱。当简单的内容剧增到某种程度以后也会变成枯燥乏味,这种感觉好比读者被老师罚写名字,假设读者有“王一二”般的简单名字,假设老师罚写500次,就算名字再怎么简单,读者也会哭死。激励内容的编辑工作也是同样的道理。

笔者相信解读时序信息是最头疼问题 … 但是,让真正笔者担心是没完没了的时序。我们知晓顺序语言如果当前的步骤无法奏效,那么问题一定是上一个步骤有地方弄错了,顺序语言好比单行通道一样,就算通道再长问题再多,只要努力的走下去,黎明一定会出现。

红苹果敲响牛顿的头,让他发现万有引力,不过野心勃勃的牛顿预想挑战万有定律,牛顿最后发现万物只能按照时间演变下去,这种感觉好比我们走在又长又暗的单行通道里,单调即无趣。牛顿虽然克服万有定律,但是他的人生观宛如牛顿力学一般非常消极,因为人生只能按照时间一直走下去而已 … 这种情况,我们称为单向。

牛顿死后几个世纪,量子邪说也随之兴起,量子力学之所以称为邪说是因为主意完全违背牛顿力学,举例来说:一只猫将其关在盒子里始终不打开,根据邪说,世界很可能会分支成为两条不同演化的世界线,亦即并行世界。世界有可能走向猫已死亡的世界线A,世界也有可能走向猫还活着的世界线B,其中猫死亡与否是造就世界线A与B的变数。

这种情况,我们称为多向。

仿真可是单向,也是多向。多向的仿真好比量子邪说般,信号亦即变数,仿真对象的功能越多,信号的数量理应也是一样多,结果变数也会变多。变数会衍生分支,无数的变数最终会衍生数之不尽的分支,从而海量化仿真信息。

clip_image023

图1.8.4 ps2接口模块的建模图。

为了给予读者一个感知的认识笔者就用一张建模图来解释。如图1.8.4所示,那是一幅ps2接口的建模图,ps2接口差不多有一座小切糕的级别,它有3个功能模块,1个控制模块,信号的数量总和有10,亦即:PS2_CLK_Pin_In,PS2_Data_Pin_In,PS2_Data,PS2_Done_Sig,FIFO_Write_Data,Write_Req_Sig,Full_Sig,FIFO_Read_Data,Read_Req_Sig,还有 Empty_Sig。

ps2接口模块的激励过程大致如下:

我们必须先产生2个主要的虚拟输入,亦即 PS2_CLK_Pin 和 PS2_Data_Pin_In 。电平检测模块接收输入就会产生输出 H2L_Sig 刺激 ps2解码模块,ps2解码在PS2_Data_Pin_In 还有 H2L_Sig 双双的刺激下就会产生PS2_Data 还有 PS2_Done_Sig 的输出进一步刺激 PS2控制模块。

PS2控制模块接收刺激之后,会经过 FIFO_Write_Data 和 Write_Req_Sig 等输出刺激 FIFO模块,FIFO模块也会使用 Full_Sig 反馈 PS2控制模块。最后 FIFO模块会经由顶层信号 Read_Req_Sig 刺激接而输出 FIFO_Read_Data 还有 Empty_Sig 等顶层信号。

读者千万别忘了,上述流程只是简单的文字描述而已,此外上述流程仅讲述一条成功的流向而已。单文字流程,笔者的头脑已经快要发疼了,假设仿真流程既不是文字描述,也不仅一条的话 … 没错,在实际的仿真当中,流程不仅没有成功的保证,流程也不可能只有一条而已,此外信息也不是文字描述,而是错综复杂的信号。多向仿真说过,信号就是变数,信号越多变数越多,变数所衍生的流程也会越多,许多流程的信息相乘以后会产生令人窒息的信息量。

没错,这就是糕的第三形式,为了让读者深刻体验切糕的威力,笔者继续用图1.8.5来举。假设有两条信号H2L_Sig与PS2_Data_In刺激ps2解码模块,此刻会产生以下几个可能性:

可能性一:H2L_Sig 先刺激 ps2解码模块,然后PS2_Data_In 再刺ps2激解码模块;

可能性二:PS2_Data_In 先刺激 ps2 解码模块,然后 H2L_Sig 再刺激 ps2 解码模块;

可能性三:H2L_Sig 与 PS2_Data_In 同时刺激ps2解码模块;

假设1个可能性仅为1条流程,然后一条流程需要仿真1次。上述有3个可能性表示有3条流程,因此需要仿真3次。读者请仔细一想,上述3条流程仅是ps2解码模块的局部情况而已,试问ps2接口有几个模块?结果又会产生多少可能性呢?为了取得正确的结果,我们又要仿真多少次呢?没错,这就是仿真最可怕的地方,亦即无限可能性,海量仿真信息!

在此,读者的蛋蛋是不是开始在发颤呢?人不仅时间有限,而且精力也不多,庄子一直劝告我们,“别用有限的身体去追求无限的目标”。当模块过度集中以后,出入端增加仅是小事一桩,真正让人觉得害怕的是,无数信号相乘以后做照成的仿真信息。为了避免仿真变成悲剧,我们必须想尽办法分散模块,分散功能,分散信号,控制变数,减少流程 …

最后,让我们来总结一下,这个小节究竟有几种形式的切糕?

l 仿真对象的功能越复杂,出入端越多,激励文本越臃肿。

l 仿真对象的功能越复杂,出入端越多,激励内容越混乱。

l 仿真对象的功能越复杂,信号的数量越多,变数也会越多。

切糕除了小节1.8的介绍以外,还有本节的3种形式。切糕之所以给人绝望是因为天生俱来的压倒性份量 … 描述语言不同与高级语言,那些许多烦人又猥琐的底层工作高级语言都交由后台处理,我们只要关心代码的正确性即可。反之,描述语言的后台能力很弱,结果许多底层工作必须交由我们自己承担,但是人的精力是有限的 …

最后笔者再次强调,仿真真正最可怕的地方就是数之不尽的流程,还有解析不玩的海量信息。世界线还有平行世界虽然听起来感觉非常浪漫,可是浪漫的背后往往都是无限残酷,这种感觉好比 Stein:Gate 的男主,为了拯救青梅竹马,却无法避免世界线的收束,结果无数次目睹她的死亡。为了避免仿真发生悲剧,我们应该仅可能减少功能数量还有变数,以最小的力气引导仿真流程走向预想的途径。

有些同学可能还在幻想自己成为勇者挑战恶龙然后征服所有世界线,但是笔者还是告诫读者千万不要过分高估自己的承受能力,就算读者有再好的吞吐量,吃了第一块大切糕,绝对没有能耐吃第二块大切糕,这点笔者可以拍胸保证,因为笔者就是过来人。如今,笔者已经放弃当初作为勇者的身份,因为笔者发觉自己的身心已经被切糕搞到破烂不堪。

1.9 自动思想的简介

传统流派虽然手段强硬而且暴力,不过它们相信气势可以闯出一片天,是典型自信的主攻派,然而,笔者本质柔弱还有节能,宛如不停往下游走的水资源,是典型怕事的撤退派。面对宛如切糕一样大的模块,传统流派有可能会选择消耗战,誓死吃掉整座切糕。换之,笔者没有那种自信,也没有那种胆量,所以笔者选择分化切糕到最小数量,然后再逐个吃掉。

笔者当然晓得过度分散模块的缺点,亦即重复性的工作量,举例而言:假设一块切糕有10个功能,切糕经过分散以后成为10块小切糕,因此笔者必须重复十次仿真的工作。很不巧这是马死落地走的最佳方法,鬼叫笔者的承受能力差,无法一口吃掉切糕。此外,模块过度分散也会导致仿真失去整体性与连接性,亦即我们只能看见个体的局部仿真状况,而不是整体的仿真状况。这个问题让足笔者纠结过一阵子 …

师兄曾说过:“仿真是允许强者淘汰弱者的残酷世界! ”,不管笔者怎样否定传统流派,这是唯一一句受到笔者赞同的话,仿真本来是挑战一块又一块的切糕,那些承受能力差的弱者,根本没有仿真的资格。这句话听起来,虽然让人觉得不舒服,然而这就是现实。不是仿真拒绝我们,而是弱小让我们失格。

笔者是弱者,所以笔者没有能耐承受大切糕,为此必须将大切糕分化为无数小切糕。然而大切糕经过分化以后就会失去连接性与集中性,原先的完整性就会受到破坏。笔者只要一天找不到连接小切糕的关键,回复切糕的完整性,笔者不仅没有资格仿真,笔者也没有资格去挑战切糕 …

为了寻找那份关键的东西,笔者苦思冥想了好久,想着想着 … 意识不知不觉中又来到诸神逗留的乌托邦,然而它却在那里等待笔者。

“在这里,生命不受负责也不受管理,每个生命既是个体也是整体”,它说道。

“既是个体也是整体的生命,这不矛盾吗?”,笔者反驳道。

它摇摇头,然后指向前方继续说道。

“那里有一处花田,花草虫蚁都在生活着,生存压力让它们无瑕顾及其它,这是个体表现。从旁观望,无数个体存造就眼前的花田,这是整体表现 … 理解了吗,孩子? ”

clip_image025

图1.9.1 主动设计概念。

神的话永远都是充满寓意,为了了解它的话语,我们必须换个角度去思考问题。如图1.9.1所示,假设有甲乙丙丁四只家伙在交谈,小丁因为不擅长交流所以被排斥在外。如果站在局外者的角度观察这起交谈,小丁则会认为这起谈话必须甲乙丙三人无间断合作才能完成。反观,如果站在当局者的角度去观察这起交谈的话,事实上小甲只说想说,小乙只听想听,然而小丙边听边说,每个人只作自己想作的而已。

假设小丁不小心错过交谈内容却又想了解详情 … 此刻,如果小丁是传统流派的门徒,它认为最佳方法莫过再现现场还有执行实时监控。换之,如果小丁换作笔者,它会认为实时监控和再现现场太耗神耗力了,此外小丁还要同时接受三人份的谈话信息。所以小丁选择逐个拜访小甲,小丙,还有小乙 … 小丁了解片断的详情后,然后将无数片断信息都交由想象力去结合。

并行语言是一门偏硬的语言,由于本质限制的关系,它无法实现顺序语言所拥有的函数调用还有传递参数等能力,不过并行语言可以建立仿顺序结构的效仿顺序操作。但是,没有步骤的弱点,导致并行语言不适合过多内容集于一身,因为会降低解读能力,所以多模块建模就流行起来。低级建模也是多模块建模之一,不过是有准则的多模块建模。多模块建模却有一个缺点,亦即模块分化以后,模块会失去集中性与连接性。

为此,建模图的作用就非常重要了,建模图不仅可视化模块之间的关系,也能加强个体整体的存在感,过后经由想象力再现整体效果。建模阶段,个体模块只做想做,只听想听,不用顾忌其他。完后,再运用想象力联系所有个体情况,整体情况就会出现在脑海当中。这种思想笔者称为主动思想,应用这种思想的技巧也称为主动设计。

人类有两种记忆能力,左脑拥有清晰记忆也擅长处理具体的信息;右脑拥有模糊激励也擅长处理抽象的信息(想象)。主动思想的作用就是将左脑的记忆压力,还有处理压力分担给右脑,好让用脑保持平衡而不是一面倒。人类的头脑是一件不可思议的工具,右脑虽然没有左脑的强劲分析能力,但是右脑的想象能力比起左脑可优秀许多。例如记忆陌生人的面貌,如果用左脑记忆的话,左脑竟可能会多收集更多的面部数据,但是面部信息远远超越左脑的承受能力,所以用不了多久,左脑就会当机。

反之右脑只会收集少数面部特征,其余细节任由想象力填充,亦即模糊能力。假设将两张相似的明星脸交由电脑对照,不管两张脸给人的感觉再怎么相识,电脑始终会给予否定的答案。如果将明星脸交托左脑分析,左脑也会给予相同的否定,因为左脑和电脑不会给予“好像,似乎,相识”等暧昧的回应,所以说明星脸是右脑引起的浪漫现象。

根据图1.9.1的谈话情况,小丁在不知不觉之间发挥了主动思想的能力。首先它无意识记忆甲乙丙最有特征性的谈话内容,假设甲在这次谈话中说了,“海边”,乙说了“美女”,丙则说了“BBQ”。小丁经由脑补以后,小丁会认为甲乙丙在谈论“美女在海边BBQ”,又或者“美女在海边被BBQ”。主动思想除了应用在日常生活种以外,主动思想也能应用在仿真当中。

当我们将整体模块分化为无数个体模块以后,为了应用主动思想充当个体模块之间的连线,为此个体模块必须露出特征,给人留下强烈的局部印象。这种感觉好比笔者记忆美女A的脸庞,不过笔者很懒,所以笔者不会记忆所有脸部细节,为此笔者会记忆她怜人的泪痣,性感的粉唇,还有可爱的娃娃头。最后在笔者的记忆中,美女A的脸庞除了泪痣,嘴唇,还有娃娃头特别清晰以外,其它细节怎样也无所谓。

仿真也有同样的道理,如果个体模块有强烈的特征,经过仿真以后,它就会成为清晰的局部信息,然后深深烙印在我们的记忆中。无数清晰的局部信息刻入记忆以后,再由想象力将所有细节结合起来,一幅完整的整体信息就会出现在我们的脑海当中。换句话说,我们利用想象力充当个体模块之间的黏糊剂,但是作为前提,个体模块必须清晰,而且印象要非常强烈,不然的话我们作不到脑补时序。

1.10 不可仿真对象的简介

这个世界上存在一些危及生命的仿真对象,例如切糕级别的模块(功能过多的模块),但是还有一些仿真对象比切糕更危险,笔者称为不可仿真对象 … 不可仿真对象实际上不是不能仿真,而是没有仿真意义,或者阻碍仿真。根据笔者的认识,不可仿真对象有三种,亦即“超乱模块”,“超烦模块”还有“超傻模块”。

超乱模块顾名思义就是模块内容过度杂乱以致无法解读,说白点就是解读能力极差的模块。这类模块好比,官方插件模块,还有就是失去结构的模块。官方为了保护商业秘密,结果会故意搅乱模块的内容,这是一种非常恶心的行为,不过它们起码也做好善尾的工作,确保插件模块即时健全,还有附加说明书,所以它们情有可原。

仿真本来就是处在虚拟的环境下模拟模块的功能状态,如果模块内容本身也无法确切表达,仿真就会失去原来意义。此外,仿真也是用来测试不健全的模块块,然而官方插件模块不仅健全,而且还有许多资料支持。仔细一想,我们为何还要浪费无谓的精力在它们的身上呢?为了节能,除非是特殊的情况,否则笔者是不会仿真官方插件模块。

”超烦模块“是指用时过于沉长的模块,让人等到烦心。这个问题一般主要是由计数器或者定时器灌水过多引起的悲剧。仿真的最小单位不是现实中1秒或者1ms等物理时间,而是N个时钟。例如典型的流水灯实验,流水间隔至少都是100~1000ms之间,结果灌水会是非常严重。

根据笔者的惨痛经历,20Mhz为例的仿真时钟,仿真时间一般不推荐操作超过1秒,1ms~10ms之间最为理想,过长的仿真时间会拉长wave界面。曾经何时笔者也是傻子一名,为了验证正确的计时结果,笔者常常在仿真界面上拖来拖去,实在累人 … 天生节能的笔者绝对不能容许这种滑稽的事情发生在自己身上,于是笔者左思右想最终想到整合技巧。整合技巧有各种作用,其中一项功能就是精密控时,任何长时间计时都能在代码上搞定,而不是经由仿真。

最后还有一个不可仿真对象就是“超傻模块”,故名思议超傻模块是指功能过度简单明了,这种功能简单到一眼就能辨出是园是扁,难道我们还会大费周章,敲锣打鼓去仿真它吗?当然不会啦。超傻模块典型例子有:多路选择器,加码,解码等小组合逻辑还有小功能模块。

最后让笔者来个简单的总结吧:

超乱模块:模块内容极乱,解读不能,例子:官方插件模块,无结构的模块。

超烦模块:模块内容水分,极度费神,例子:定时器,计数器。

超傻模块:模块内容单纯,浪费气力,例子:多路选择器,输出器等小组合逻辑。

总结:

第一章虽然尽是毫无里头的扫盲文,不过扫盲文的作用究竟有多大读者应该心中有数。

“仿真有建模三倍的负担”这句话一点也夸张 … 建模亦即实际建模,除了建模还是建模;然而,仿真除了建模以外,还有创建激励还有解读时序信息等猥琐的工作要我们去干。说实话,同时兼学建模和仿真很容易两头不到岸,这也是学习最忌遇见的窘境。笔者希望读者可以透过扫盲文做好最起码的思想准备。

仿真是什么?虽然在字面上“调试”还有“仿真”相较有同义的嫌疑,不过“调试”不等价“仿真”,“调试”为求单向结果,换之“仿真”为求多向过程。简单说,调试是顺序语言的测试手段,然而仿真是并行语言的测试手段,后者相较前者不仅不单纯,而且让人匪夷所思甚至难以捉摸。

调试与仿真之间也是顺序语言与并行语言之间的思路问题,错用思路宛如遇上一堵杀人不偿命的隐形巨墙。笔者已经无数目睹同学FQ不成反被摔死,实在可怜 … 为什么他们要模仿飞蛾寻求绝命之火呢?事实上这些悲剧,传统流派实在功不可没,它们手段强硬而且暴力,强迫学习从来不说明,无辜之人才会接续遭殃。

“仿真结果一定是物理时序!“

“仿真需要验证语言!“

“仿真和建模是两回事!“

“我们才是正道!“

正是这些花言巧语骗尽傻子卖猪仔,曾经何时笔者也是一只傻傻上当的猪仔 … 解脱不遂之后,笔者才渐渐领悟,传统流派只会传授蜻蜓点水的表面功夫,许多重要细节都会潇洒带过。仿真不当容易受伤,不过仿真真正让人恐惧的是——切糕。切糕是隐藏在仿真背后的绝望,切糕会改变形态躲在黑暗处,然后无时无刻等待时机扑食希望。切糕一般是指压倒性的工作量还有信息量,主要是仿真手段不当所产生的副产物。传统流派不知真傻还是装傻,那么大的切糕它们既然可以视而不见?

笔者被切糕施虐的惨痛的经历最终浓缩成为这本书。这个世界(仿真)太大了,传统手段只会沦落切糕的猎物,为求生存异常手段是必须的。这本书是这个世界(仿真)的地图,也是这个世界(仿真)的生存手册 … 此外,这本书也能给予出发前的心里准备。读者好好切记,这个世界(仿真)是由并行法则支配的空间,以往的顺序手段已经一去不返,心存半点也会引来悲剧。

“仿真是会死人的学习 … ”,笔者道。

版权声明:本文为alinx原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/alinx/p/3367573.html