一般描述语言分为两个部分,亦即“综合”还有“验证”,也是俗称的综合语言还有验证语言。为了方便,笔者时常把综合语言说为“建模语言”,然而在笔者的概念之中,验证语言的价值是可有可无,为什呢?验证语言虽然给人看似“很强”,但是许多关键字用处都不大。此外,验证语言也是初学Verilog的负担,不管怎么说,笔者就是欢喜不起来...
传统流派时常说道:“建模用综合语言,仿真用验证语言”,其实这种说法是非常不负责任的,建模还有仿真之间的差异有如笔者曾说那样,前者是实际环境的建模,后者则是虚拟环境的建模,亦即实际建模还有虚拟建模。实际建模会受限与实际环境,虚拟建模则是相反的情况。由此可见,两者拥有大同的概念,不过环境却是小异。但是,又是什么原因将描述语言一分为二呢?
所谓综合,就是可以描述实际硬件的关键字,所谓不可综合就是无法描述实际硬件的关键字,首先让我们来了解一下initial这个关键字:
regCLOCK,RESET;//仿真initialCLOCK=0;initialRESET=1;reg[3:0]Reg1=4’d0;//建模.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.1.1
好奇的同学可能会问:“初始化还有复位化有什么区别?”,
真是一个好问题...初始化是编译器的活动,或者编译器给予的值,也称为初值;然而复位化则是硬件的实际活动,或者说复位结果给予的值,也称为复位值。
事实上,initial也使用与建模当中,不过如代码4.1.1所示,建模可以使用=赋值操作符省略initial这个关键字实现初始化。此外,默认下的编译器都会给予所有资源初值0。
1.always@(铭感区域)Reg1<=~Reg1;2.always@(*)Reg1=~Reg1;3.alwaysReg1<=~Reg1;4.foreverReg1<=~Reg1;.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.1.4
图4.1.1forever产生的理想时钟。
图4.1.2时钟刻度为1ps/1ps,20个时钟刻度,10个时钟。
20*5*1ps=100ps,或者10个拥有10ps的理想时钟,结果如图4.1.2所示。
如果操作forever#5CLOCK=~CLOCK一例子2执行20次,那么:
图4.1.3时钟刻度为1ns/500ps,20个时钟刻度,10个时钟。
20*5*1ns500ps=150000ps或者150ns,又或者10个拥有1.5ns的理想时钟,结果如图4.1.3所示。
当理想时钟产生成功,我们就有最基本的环境输入,接着我们就可以使用时钟沿(由低变高)触发always的铭感区域,亦即模拟建模的RTL级设计。如代码4.1.3的第10行所示,always@(posedgeCLOCK),然而第11行则表示,每一个CLOCK上升沿,Reg1都赋值4’d5,期间赋值的前面是延迟操作符——#。
图4.1.4代码4.1.3仿真结果。
,也就是5ps物理延迟,恰好是半个时钟周期。
结果如图4.1.4所示,Reg1原本应该在C0输出未来之星4’d5,很遗憾的是...经过#阻碍以后,未来值4’d5出现在C1而不是C0。#延迟操作符之所以认为验证语言,因为它可以实现物理延迟,然而#延迟操作符的使用也仅限于仿真环境(虚拟建模)而已。换之,#延迟操作符在建模环境(实际建模)是绝对无法通过,为何呢?
其二,建模环境是用来创建理想模块,结果无法描述5ps这样小气的物理延迟。
看完整体代码4.1.3我们可以这样结论道:
最后还有一个重点就是...仿真环境是否实现(模拟)物理参数,那是自由行的选择。如代码4.1.3所示,如果执行Reg1<=#54’d5操作,Reg1会经过半个周期的延迟才能输出未来值4’d5;换之,如果执行Reg1<=4’d5操作,Reg1不用经过延迟就能输出未来值4’d5。
仿真绝对不是传统流派所言那样,建模(综合)和仿真完全被分割开来,成为两种不同的平台。笔者对此无比愤怒,因为这番不负责任的强言强语,已经害死无数的初学者,笔者自然也是其中一人。为此,读者需要确切明白,建模还有仿真本是同根,差别只有概念(环境)而已。以此类推,建模所用的一切方法完全可以照搬到仿真去,这样做不仅可以大大减少学习的劳动,此外我们还可以更加有力掌握建模。
不过非常遗憾的是,权威还有主流不像笔者如此看待仿真,它们始终认为建模还有仿真应该分开。因为如此,描述语言就有综合还有验证之分,举例而言:
always#5CLOCK=~CLOCK;forever#5CLOCK=~CLOCK;.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}上面有两组产生虚拟时钟的方法,always常被认为它是属于综合语言,然而forever不管怎么看都是验证语言的东东。always关键字可以产生虚拟时钟,forever关键字也可以产生虚拟时钟,但是参考书不推荐使用always产生虚拟时钟...原因也非常荒唐,因为always有太多综合的味道,不怎么适合仿真。因此,forever就成为验证版本的always...
以此类推...实现同样作用的关键字,就有综合版本还有验证版本。所以说,学习验证语言实际上是重复学习综合语言一番...此外,好死不死,传统流派却是倾向“调试风格”仿真手段。笔者也曾经说过,调试是顺序语言的测试手段,为求单向结果。就这样,许多初学者容易产生混乱...它们的脑子好不容易在建模阶段中习惯并行思路,可是
“调试风格”的仿真手段,脑子必须180度切换至顺序模式...结果,那些来不及切换脑子的同学,脑子就会当机,导致瓶颈。
为了证明笔者不是胡说八道,笔者再举个例子:
fork...join语法书说是并行块,begin...end则是顺序块,不过这种认知也仅使用在于保守的仿真概念而已。为了吐糟权威主义还有传统流派那些有腐旧有局限的仿真概念,笔者简单举例2个不同风格的代码,但是结果都是一样,然后笔者会逐个数落它们。
1.`timescale1ps/1ps2.......3.regCLOCK;4.......5.initialCLOCK=0;6.forever#5CLOCK=~CLOCK;7.......8.begin9.#00Reg1=4’d0;//等价Reg1=4’d0;10.#10Reg1=4’d1;11.#10Reg1=4’d2;12.#10Reg1=4’d3;13.end.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.15
在此,每10个时钟刻度代表一个时钟,因此Reg1的赋值操作可以这么认为...第一时钟,Reg1赋值4’d0;第二个时钟,Reg1赋值4’d1;第三个时钟,Reg1赋值4’d2;第四个时钟Reg1赋值4’d3。
图4.1.5代码4.1.5产生的时序。
1.`timescale1ps/1ps2.......3.regCLOCK;4.......5.initialCLOCK=0;6.forever#5CLOCK=~CLOCK;7.......8.fork9.#00Reg1=4’d0;10.#10Reg1=4’d1;11.#20Reg1=4’d2;12.#30Reg1=4’d3;13.join.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.1.6
不过,此刻#延迟操作符的作用好比条件判断般,当时钟刻度为0的时候Reg1就赋值4’d0;当时钟刻度为10的时候,Reg1就赋值4’d1;当时钟刻度为20的时候,Reg1就赋值4’d2;当时钟刻度为30的时候,Reg1就赋值4’d3。笔者再强调一下,fork...join造就Reg1同时执行4种结果的赋值,不过时钟刻度作为开关条件。用建模来表示的话,代码4.1.6会是这种感觉:
图4.1.6代码4.1.6产生的时序。
解释完毕代码4.1.5还有代码4.1.6以后,笔者接着要开始数落它们了。笔者之所以看它们不顺眼,其一它们既然冷落如此重要的时钟信号,其二仿真手段太接近“调试风格”了。根据代码4.1.5还有代码4.1.6不管它们使用begin...end还是fork...join为Reg1赋值,由始至终的Reg1赋值操作始终让人觉得好似c语言那样:
reg1=0;delayps(10);reg1=1;delayps(10);reg1=2;delayps(10);reg1=3;.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}根据RTL级设计的基础,我们知道寄存器如果没有时钟源触发是不会发生操作,可见代码4.1.5还有代码4.1.6已经完全违背这个重点。对此,笔者实在无法接受...
1.`timescale1ps/1ps2.......3.regCLOCK;4.......5.initialCLOCK=0;6.forever#5CLOCK=~CLOCK;7.......8.always@(posedgeCLOCK)9.case(i)10.0:beginReg1<=4’d0;i<=i+1’b1;end11.1:beginReg1<=4’d1;i<=i+1’b1;end12.2:beginReg1<=4’d2;i<=i+1’b1;end13.3:beginReg1<=4’d3;i<=i+1’b1;end14........15.endcase.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.1.7
代码4.1.7是大伙熟悉的仿顺序操作的用法模板,i指向时钟之余,i又指向步骤,当然这些都不是重点。如代码4.1.7所示,在T0的时候(步骤0),Reg1赋值4d’0;在T1的时候(步骤1),Reg1赋值4’d1;在T2的时候(步骤2),Reg1赋值4’d2;在T3的时候(步骤3),Reg1赋值4’d3。
图4.1.7代码4.1.7产生的时序。
代码4.1.5~7之间的区别如表4.1.1所示:
表示4.1.1代码4.1.5~7之间的区别。
代码\细节
CLOCK信号
Reg1赋值控制
时序表现
时钟指向
代码4.1.5
花瓶
没有
代码4.1.6
代码4.1.7
不是花瓶
有
CLOCK信号沦落为花瓶。问题不仅而已,代码4.1.5还有代码4.1.6虽有时序图,但是时序图却没有时序表现,此外代码页没有指向时钟,这些问题的源头是以为它们忽略CLOCK信号的关系。
相反的,代码4.1.7不在视CLOCK信号为花瓶,而且还充分使用它控制Reg1的赋值操作,结果代码4.1.7所产生的时序图也包含时序表现。然而,更为重要的是...代码4.1.7有用指向工具指向时钟。
代码4.1.5~7经过一轮的搏斗以后,代码4.1.5~6虽然外表上给人简洁的假象,可是暗地里它是破烂不堪的。换之,代码4.1.7给人感觉它比前两者书写较多代码,实际上这是支撑整体的结果。笔者曾经说过结构的重要性,传统流派为了贪图早期的便利,于是无视结构的重要性,最终为后期带来许多麻烦。
但是代码4.1.5~6最为糟糕的地方是“调试风格”的仿真习惯,这是最危险也是最可怕的祸种。这种破坏性十足的危险习惯,会为精神产生无数令人绝望的切糕。因为它已经偏离RTL级设计的基础,而且代码整体也非常松散,好似摇摇欲坠的高塔,代码的数量越是增加,危险性越是提升。但是笔者所担心的问题是思维上的疲劳,初学者在建模阶段要适应并行模式已经是非常吃力了,如果“调试风格”的仿真习惯导致思维来不起切换,又或者切换过度...最终降低学习的效率。
当然,笔者之所以举例代码4.1.5~7是为了揭露权威主义还有传统流派那种破烂不堪的仿真习惯之余。此外,笔者还要强调,传统流派不过是将验证语言作为无意义的重复性学习而已。它们保守既局限的仿真思想,实际上已经扭曲验证语言原本的面膜,还有仿真实际的意义。它们这样作,一切仅仅是为了稳固自己的地位而已,然而却要牺牲千千万万的我们,实在可恶,实在可悲,实在可恨....
最后让我们来总结一下这个章节的重点:
传统流派还有权威主义为了面子,为了地位...它们死硬将“综合语言”还有“验证语言”分为两个不同平台的东西。因此,常规上,传统流派的仿真方法,实际是重复综合语言的学习而已。话虽如此,这种倾向“调试风格”的仿真习惯,不仅是违背RTL级设计的初衷,而且还180度扭曲仿真真实的意义。
代码4.1.5~7之间的比较就是最好的例子,代码4.1.7不仅可以实现代码4.1.5~6的时序结果,而且代码4.1.7的风格同时也适用于“建模(实际建模)”还有“仿真(虚拟建模)”之间,可谓是一箭双雕。笔者之所以故意举例代码4.1.5~7是为了表示,学习验证语言最为严重的误解意外,笔者还要表达初学者最容易掉入的仿真瓶颈。
传统流派是“调试风格”的仿真习惯,简单说就是一种顺序思维。换之,笔者强调的是仿真应该使用并行思维。笔者这样做是为了“建模(实际建模)”还有“仿真(虚拟建模)”共享同样的思维模式,好让初学者避免因思维切换而引起的脑袋短路问题。脑袋短路时其次的问题,天生节能主义的笔者,当然不愿因浪费精力学习重复性,而且又毫无意义的仿真。
总结完后,好奇的同学可能会问道“笔者,验证语言有很多意义不明的关键字?”,这位同学问得没错,事实却是如此。如果同学翻看官方的语法书,读者一定会看到许多陌生的验证语言,而且数量还非常可观。在这庞大的杨振语言里,其实函数性质的验证语言占据多数,例如常见的$readmemb();还有$display();。
好奇的读者可能有会问道:“笔者,我们是不是学习全部呢?”,作为初学笔者不建议....事实上,只要掌握其中几个就够用了。正如笔者前面所述那样,只有传统流派才会重复无意义的学习而已,事实上“建模”也好,“仿真”也好,我们只要换个思路即可。其实两者拥有许多共同的地方。好奇的读者又可能会不耐烦的问到:“可是,我家的叫兽却说...”,不要再可是了!详情会在后面继续....烦死了!
某天上午,笔者正在灼热又毒辣的天空下四处游走,支撑不了的笔者被逼躲到树荫下乘凉,忽然耳边有人叫道。
“小哥,天气那么炎热...要不要来杯凉爽的豆浆呢?”,豆浆小贩道。
“嗯嗯,正好口渴...老板来杯特凉特大号的!”,笔者宛如发现绿洲般兴奋道。
“小哥,久等了...这是为小哥特别准备的豆浆!”,豆浆小贩答道。
笔者二话不说,立即将豆浆往口里送去...咕噜咕噜(喝水声),怎么越喝味道越觉得奇怪,豆浆既然没有豆浆香,而且甜度未免太过了吧...这真是在豆浆吗,还是糖水?笔者不禁怀疑道。
“老板,怎么豆浆完全没有豆浆的味道?”,笔者向豆浆小贩理论道。
“小哥,别开玩笑了...小弟的豆浆无论外表怎么看都是普通豆浆呀。”,豆浆小贩答道。
“老板,你才是别开玩笑了,你自己尝尝看,这是豆浆还是糖水?”,笔者强硬道。
忽然,豆浆小贩眯着眼睛,用手将笔者拽到里边,然后用虚无的口调说道。
“小哥,小弟是卖豆浆没错,只是比例有点不同而已...”,豆浆小贩道。
“比例,怎么比例法?”,好奇心忽然驱使笔者反问道。
“5%豆浆,45%糖,50%水...“,豆浆小贩低声道。
“纳尼!老板,这种比例已经不是普通豆浆了。”,笔者惊讶道。
“嘻嘻,小哥不说,小弟也不说,有谁又会知道呢?”,豆浆小贩诡异笑道。
笔者一心想要继续理论,可是忽然出现夏天不该出现的阴风,豆浆小贩也随之消失在眼前。此刻,笔者真的吓着了...刚才那是什么?难道是天气太热导致自己看见幻觉吗?不过,嘴巴里那股不是豆浆又似豆浆的味道还残留着,难道!疙瘩忽然爬满全身,笔者一刻也不想久留此地,哪怕外边的环境依然是毒辣的酷热。
笔者踩着熟悉的回家路,眺望熟悉的建筑物,不过却埋头思考方才遇见的情况。话说,这种经历又不是第一次,笔者用不着大惊小怪,然而让笔者苦恼的是它们出现的原因。
“小弟的豆浆无论外表怎么看都是普通豆浆呀”,还有“小哥不说,小弟也不说,有谁又会知道呢?”,不知为何...豆浆小贩所言的两句话却一直漂浮在脑海中。想着想着
,嘴巴随之吐出一句领悟的“原来如此”!
读者千万别误会,上述内容并不是说灵异故事,这个小节我们还在继续谈论验证语言,但是为了营造学习气氛,笔者才借用灵异故事作为比喻。故事说道,如果豆浆没有豆浆,不管外表再怎么像极豆浆,豆浆也不是豆浆。如果不是笔者敏感,又或者豆浆小贩如果没有实话实说,笔者真的不知道那杯豆浆既然不是豆浆!
这个故事告诉我们一个非常重要的信息,如果将豆浆反应在时序的身上,我们可以这样说道:如果时序没有时序表现,不管外表再怎么像极时序,时序也不是时序。不过不同的是,时序没有豆浆小贩告诉我们这个豆浆(时序)不是豆浆(时序),除非我们细心观察。
反之,没有豆香的时序,外表看似时序,不过里边却没有所谓的启动沿,锁存沿,或者其他什么的。事实上,传统流派的仿真习惯可以比喻为不是豆浆的豆浆,没有时序表现的时序。不过,为何时序表现对于时序而言那么重要呢?其实这是见仁见智的问题,但是对于笔者来说追求至高无上的原汁原味是人类的天性。话说,除了傻子就没有人会喜欢不是豆浆的豆浆。
正如比例只有5%的豆浆已经不再是豆浆那样,不是时序的时序在某种程度上已经偏离时序的本质了。那种豆浆有喝等于没喝,或者喝了是否又会拉肚子呢?仿真虽说是模拟实际环境执行虚拟建模,不管环境是什么,时序应有的时序表现我们还是应该保留下来,不然时序就会失去意义。
一些无良的奸商会使用豆浆精粉充当豆浆的原料,豆浆精粉是可以实现豆浆味道的化学味素,我们知道化学物品吃多一定会对身体造成伤害,最坏还会致癌。同样的道理,使用太多没有时序表现的时序,最终也会扭曲我们对时序的认识。根据笔者的学习经验,时序可以分为两种,以及“理想时序”,还有“物理时序”。理想时序是时序原有的理想状态,物理时序则是时序发生在物理下的状态。不管时序理想或者物理与否,时序表现之间的差别仅是大同小异的。
话了那么多,笔者还是举例例子比较容易搞明白:
图4.2.1代码4.2.1的adder加法器。
1.`timescale1ps/1ps2.moduleadder_simulation();3.......4.initial5.begin6.#00D1=4’d1;D2=4’d1;7.#10D1=4’d2;D2=4’d2;8.#10D1=4’d3;D2=4’d3;9.#10D1=4’d4;D2=4’d4;10.end.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.2.2
图4.2.2代码4.2.2驱动代码4.2.1加法器的时序。
1.moduleadder(inputCLOCK,input[3:0]D1,D2,output[3:0]Result);2.reg[3:0]rData;3.always@(posedgeCLOCK)rData<=D1+D2;//加法模块。4.assignResult=rData;5.endmodule.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.2.3
笔者稍微将代码4.2.1的加法器改动一下,然后在第1行为他添加时钟信号之余,第3行rData的赋值方式也稍微修改一下。
图4.2.3代码4.2.3的adder模块。
图4.2.3是代码4.2.3生成的adder模块,由于该模块配载了CLOCK信号,所以综合器必须使用寄存器用暂存还有输出驱动,此刻代码4.2.3的第2行rData会有真正的形体了,不像代码4.2.1那样,rData只是单纯的描述表现而已。图4.2.3还显示,模块里边有一个相似异或的符号是加法器,它的真实身份却是代码4.2.1,不过在此它变成小弟而已。根据代码4.2.3表示,D1与D1会先经过那个像足异或符号的加法器,然后再经由时钟沿江加法结果打入寄存器当中。最后再由rData的输出Q驱动Result输出端。
1.`timescale1ps/1ps2.moduleadder_simulation();3.......4.regCLOCK;5.intialCLOCK=0;6.forever#5CLOCK=~CLOCK;7.......8.initial9.begin10.#00D1=4’d1;D2=4’d1;11.#10D1=4’d2;D2=4’d2;12.#10D1=4’d3;D2=4’d3;13.#10D1=4’d4;D2=4’d4;14.end.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.2.4
代码4.2.4是根据代码4.2.4修改以后的结果,虽然我们在第6行产生时钟,不过第10~13行还是老样子,我行我素般无视时钟,仅仅借用仿真信号驱动。
图4.2.4代码4.2.3用代码4.2.4驱动产生的时序图。
1.`timescale1ps/1ps2.moduleadder_simulation();3.......4.regCLOCK;5.intialCLOCK=0;6.forever#5CLOCK=~CLOCK;7.......8.initial9.begin10.#5D1=4’d1;D2=4’d1;11.#15D1=4’d2;D2=4’d2;12.#15D1=4’d3;D2=4’d3;13.#15D1=4’d4;D2=4’d4;14.end.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.2.5
修改代码4.2.4成为代码4.2.5是一种非常赔了夫人又折兵的补救手段...如代码4.2.5所示,笔者针对第10~13行的延迟参数做出一番修改,以及D1与D2的输入之前多了5ps延迟。
图4.2.5代码4.2.5驱动4.2.3所产生的时序。
图4.2.5是代码5.2.5驱动代码4.2.3模块所产生的时序图。如图4.2.5所示,D1与D2的输入相较图4.2.4,稍微延迟5ps(半个时钟周期)。根据常规理解,时钟沿因为处于数据D1与D2的正中央所以,数据才会有效打入寄存器。但是,代码4.2.5这种弄巧成拙的补救手段只能勉强挤出几滴时序表现而已,而且还把美丽的时序弄成丑陋的物理时序,因为信号已经失去对齐性。
Verilog是理想本质,输出的信号理应理想,所以Verilog是没有能力描述类似D1与D2这种物理延迟。坏心眼的同学可能会反驳道:“图4.2.5中,D1与D2不过只是延迟半周期而已嘛,如果使用时钟的下降沿触发不就行了嘛,笨蛋笔者!”,喷喷...这位同学想用就用吧,乱葬岗那里处很快就会发现你的尸体。原因很简单,如果D1与D2不是延迟半周期的话,你又该怎么办呢?
图4.2.6代码4.2.6驱动代码4.2.3所产生的理想时序。
这个章节,笔者想传达的信息组要有两个...其一,就是豆浆不是豆浆,时序不是时序的细节问题。时序表现好比豆浆的豆香,时序表现越丰富豆香就越浓,时序更有时序的味道。豆浆浓不浓,时序香不香其实这是非常主观的想法。举例而言,许多著名的演奏家未演奏之前,他们都会把自己关在安静中酝酿音乐情绪。此刻,如果读者不小心插身而过,读者会感觉到浓浓的特殊氛围,那就是音乐香。
再举个例子,艺术家未作画之前都会死死盯着白板,有人说他们是正在将想象具体化,又有人说他们在培养创作的感觉。此刻,如果我们插身而过,我们会感觉独特的艺术气场,艺术家称为艺术香。作为外人,我们是很难理解他们的怪异举止,然而这些怪人却相信,只要“香气”的浓度越高表现就会越好,事实是否如此只有当局者知道...
仿真的时候,如何培养或者酝酿仿真情怀,笔者认为那也是仿真的课题之一。因为仿真时基于时序,而且时序表现就是时序的“香气”,笔者始终感觉,如果时序表现越是强烈,笔者的仿真欲就会越高,仿真也会越做越起劲。笔者无法理解,那种感觉究竟是不是错觉,香气越弄,时序越有真实感,这里意指的真实感不是破烂不堪的物理时序,而是时序原有的究级理想面貌。笔者一直有一股强烈的冲动想要抓住它,每当触手可及的时候,它就会立即消失不见,原来仿真已在不知不觉的情况下完成了....
读者很可能会认为笔者是怪咖,笔者不否认,因为笔者与他们(演奏家或者艺术家)都有相似的本质,亦即追求美丽的原始冲动。笔者一直以来都强调着,心境还有心情是非常重要的...好心情就有好表现,反之亦然。传统流派造就的时序太丑陋了,看着心情也会掉到谷底...更别谈仿真了。
如果有一杯自高无上的香浓豆浆,还有一杯灌水灌过度的豆浆...请问读者会怎样选择呢?想必没有人会喜欢不是豆浆的豆浆,同样也没有人会喜欢不是时序的时序。早期,那是传统流派横行的年代,笔者因为没有选择,因此笔者才会强迫自己喝下不是豆浆的豆浆。坏东西喝多了胃壁也会穿洞,因此笔者告诫自己要好好珍惜生命。
此外,笔者也想告诉读者...类似传统流派这种仿真手段,已经停滞N年了,说得难听一点,那是适合门级仿真的仿真手段。笔者当然不是随意口吐妄言,读者随意翻开基本参考书就能验证这点,然而这种仿真手段却不适合门级以外的仿真。期间会出现,问题能容会臃肿,内容凌乱等问题。笔者曾经遇见有5级嵌套if的激励内容,说实话,笔者的蛋蛋立即就掉在地上然后昏死过去...
笔者最后还是再强调一下,味道还有喜好本来就是因人而异,有人认为传统流派是绝对正统;也有人会认为新时代就要新表现;所以不是豆浆的豆浆是否还是豆浆,都是见仁见智的问题。
验证语言拥有无数个关键字,一般都是系统函数占据多。根据官方手册,系统函数开头都有$美金的符号,紧接着会有函数名还有参数选项...常见的系统函数如表4.3.1所示:
表4.3.1
系统函数
用途
$display();
打印信息在信息显示界面
看着看着,好奇的同学可能会问道:“笔者,系统函数难道只有1个吗?”。是呀!根据笔者的习惯,常用的系统函数的确只有1个而已。好奇的同学可能又会问:“笔者,别开玩笑了!”,笔者没有开玩笑...官方手册的确记录许多系统函数,不过像夏老师那样努力的人勉强也能举例几个。换之,像笔者这种懒人自然也只有这种程度而已。
$display()函数与C语言的printf()函数非常相识,不过$display()函数稍微细腻一些,如,$display()函数常用的格式还有换码如表4.3.2所示:
表4.3.2
格式\换码
%h
输出十六进制
%d
输出十进制
%b
输出二进制
%c
输出字符
%f
输出浮点数
\n
换行
有C语言功底的同学当然对表4.3.2不会感觉陌生,除了%b以外。Verilog语言相比C语言,前者拥有更强的为操作能力,对此$display()函数也凸出这点。$display()函数的常见用法如代码4.3.1所示:
1.always@(posedgeCLOCK)2.case(i)3.0:4.beginreg1<=4’d10;i<=i+1’b1;end5.1://打印二进制格式6.begin$display(“reg1=4’b%b”,reg1);i<=i+1’b1;end7.2://打印十六进制格式8.begin$display(“reg1=4’h%h”,reg1);i<=i+1’b1;end9.3://打印十进制格式10.begin$display(“reg1=4’d%d”,reg1);i<=i+1’b1;end11.endcase.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.3.1
根据代码4.3.1所示,笔者现在步骤0为reg1赋值4’d10,然后在步骤1~3相序打印不同的输出格式,打印结果如下所示。
reg1=4’b1010reg1=4’hareg1=4’d10.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}教授使用$display()函数不是笔者真正的目的,在此读者需要仔细思考...一些系统函数如$display()函数等,它们就是怎么样的存在?然而,它们又会拥有怎样的时序表现呢?系统函数实际上是一种犯规,或者称为异存在的存有,形象点好比现实世界的超人或者孙悟空般,能力超凡,给人感觉一点也不现实。话虽如此,既然来到仿真的世界,它们再怎么犯规也要遵守最基本的时序法则。
1.always@(posedgeCLOCK)2.case(i)3.0:4.begin5.reg1<=4’d10;i<=i+1’b1;end6.$display(“reg1=4’b%b”,reg1);//打印二进制格式7.$display(“reg1=4’h%h”,reg1);//打印十六进制格式8.$display(“reg1=4’d%d”,reg1);//打印十进制格式9.end10.endcase.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.3.2
系统函数如$display()一般引发即时事件,即时事件也是无视时钟的意思。如代码4.3.2所示,是笔者基于代码4.3.1的修改。根据修改结果,原本分别需要使用3个时钟输出的操作,变成在一个时钟内完成。笔者先是在第5行使用<=操作符赋值,然后接续在第6~8行输出不同格式的结果。读者事先猜猜一下,就是会是怎样的打印信息呢?
1.always@(posedgeCLOCK)2.case(i)3.0:4.begin5.reg1=4’d10;i<=i+1’b1;end6.$display(“reg1=4’b%b”,reg1);//打印二进制格式7.$display(“reg1=4’h%h”,reg1);//打印十六进制格式8.$display(“reg1=4’d%d”,reg1);//打印十进制格式9.end10.endcase.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.3.3
代码4.3.2相较代码4.3.3内容是大同小异,除了第5行reg1的赋值操作由=改为<=。此刻再重新执行仿真就会得到以下的打印信息。
#reg1=4'b1010#reg1=4'ha#reg1=4'd10.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}啊拉!?是不是觉得很神呢,此刻由于reg1赋予即时值4’d10,所以$display()函数才得以输出0值以外的结果。代码4.3.2还有代码4.3.3告诉我们一个简单的道理,不管对方是老孙还是玉皇大帝,既然站在时序这块土地上,当然就要乖乖遵守这个世界的法则,不然就要吃不了兜着走。
此外,笔者也想透过代码4.3.2与代码4.3.3告诉读者一个信息,函数系统的本质其实是顺序语言,所以系统函数的常见用法也非常倾向“调试风格”,例如常见的参考书都会这样使用它...
1.begin2.#00reg1=4’d10;3.$display(“reg1=4’b%b”,reg1);4.#10reg1=4’d12;5.$display(“reg1=4’b%b”,reg1);6.......7.end.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.3.4
这种半吊子的仿真习惯造很容易流失时序表现,最终造就豆浆不是豆浆,时序不是时序的窘境。系统函数固然好用,但是我们也要选择最适合的用法,函数始终不是仿真的主角,它的作用宛如跑龙套般,偶尔在镜头出现几分钟就行了。换个角度而言,系统函数虽然不是主角,如果喜剧没有路人甲乙丙丁会让人觉得太假了,这就是跑龙套的重要性。同样道理,笔者偶尔也会使用打印函数输出一些信息用于教程,但是更多时候直接观察时序图的信息才是至关重要。
===================================================================
再者一些仿真语言,如for,while等循环功能。for是半阴半阳的奇怪家伙,虽然语法书上解释它是综合语言,但是它的实际行为更接近验证语言。举例而言,在建模的时候for是不建议使用的,因为它容易扰乱时钟控制,因此for的作用仅限用于初始化ram而已,如下代码4.3.5所示:
现今的集成环境如QuartusII,我们已经可以使用mif文件初始化ram,为此for的作用显得更加没有价值,所以人们将for称为建模的鹌鹑,灰心满满的for就这样消失在建模的舞台中。不知何时for在仿真光芒四射,,笔者曾在前面说过,传统流派的仿真风格是非常倾向“调试”,for自然而然就成为传统流派的爱将。
for的常见用法如代码4.3.6所示:
1.begin2.for(i=0;i<128;i=i+1)3.ram[i]=i;4.for(i=0;i<128;i=i+1)5.$display(“ram[%d]=%d”,i,ram[i]);6.for(i=0;i<128;i=i+1)7.reg1=#10ram[i];8.......9.end.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.3.6
如代码4.3.6所示,for的行为会展示顺序语言满满的既视感。对此,笔者不禁会怀疑,这样还是仿真吗?不错,for的副作用就是冲淡豆浆,好让豆浆不在是豆浆,因为代码4.3.6丁点时序香也没有,这种失去时序香的东西,已经不再是笔者梦寐以求的仿真。为此,笔者决定要复仇,誓死驱赶for,抹杀for!
建模之所以无法接受for...其一for本质是顺序语言之余,其二建模必须有顺序结构支持for才得以运作。不管怎么样,既然我们已经知晓for的秘密,就算没有for我们也是一样可以实现循环,因此仿顺序操作就诞生了。i是一件有趣的工具,它除了指向这个,指向那个以外,只要充分使用i也能实现各种循环操作。
1.always(posedgeCLOCK)2.case(i)3.4.0,1,2,3,4,5,6,7:5.beginreg1<=reg1+1’b1;i<=i+1’b1;end6.7.endcase.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.3.7
代码4.3.7是非常简单的循环操作,只有将i指向多时钟操作,如代码4.3.7所示,reg1一共递增7次。在此,代码4.3.7与代码4.3.6有明确的区别,代码4.3.6的for是无视时钟实现循环操作,换之代码4.3.7的i是根据时钟实现循环操作。相比之下,代码4.3.7才是香浓的豆浆,然而代码4.3.6仅是不是豆浆的豆浆。
好奇的朋友可能会问:“笔者,i有没有办法实现多次数的循环?”,绝对有!我的朋友...笔者曾经在章节3举例过,笔者也无妨重复解释。
1.always(posedgeCLOCK)2.case(i)3.4.0:5.if(C1==8)beginC1<=4’d0;i<=i+1’b1;end6.elsebeginreg1<=reg1+1’b1;C1<=C1+1’b1;end7.8.endcase9.10.always(posedgeCLOCK)11.case(i)12.13.0,1,2,3,4,5,6,7:14.begin15.reg1<=reg1+1’b1;16.if(C1==8-1)beginC1<=4’d0;i<=i+1’b1;end17.elseC1<=C1+1’b1;18.end19.20.endcase.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.3.8
如果i可以取代for循环,i同样可以取代while循环,因为两者适用同样的道理。笔者是豆浆的爱好者,一生都在追求浓厚豆香的豆浆,实现循环一定要伴随满满的时序表现,不然这种循环不要也罢。不过,笔者不是恶魔,笔者也不会赶尽杀绝,笔者也为for留一条生路。
在前面,笔者说过系统函数如$display()是时序的异存在,其实for也是同样的异存在。在笔者的眼中,for会引起即时事件,举例而言。
1.always@(posedgeCLOCK)2.case(i)3.4.0:5.begin//必须使用=赋值操作,配合for的即时事件6.for(x=0;x<8;x=x+1)reg1=reg1+1'b1;7.$display("reg1=4'b%b",reg1);8.i<=i+1'b1;9.end10.11.endcase.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.3.9
代码4.3.9是一种比较独特的仿真风格,此刻for被认为是即时事件的触发者,不管for愿不愿意,for都被时钟控制得死死。如代码4.3.9所示,步骤0实际上只是停留一个时钟而已,然而第6行的for既然执行8次循环操作,以致递增reg1八次。为了配合for所触发的即时事件,reg1的必须使用=赋值操作符。笔者也曾经说过,即时事件是无视时钟的事件,不管第6行的for是循环执行8次,还是9999次,它都是在一个时钟内完成。
图4.3.1代码4.3.9的仿真结果(exp14_simulation)。
代码4.3.9是笔者为for留下的仁慈,即使它脱离传统流派,它也能为美味的豆浆出一份力...此刻for可以骄傲说道:“俺是触发即时事件,俺也有时序表现!”,这种共赢局面才是我们期望的结果。作为补充笔者还是要强调一下,for对时序来说,它是异世界的存在,虽然仿真允许它们为时序出一份力。换做建模(实际建模),不管for再怎么努力,它始终无法得到认同,就算凑巧综合成功,我们也必须付出相应的代价。举例而言:
1.case(i)2.3.0,1,2,3,4,5,6,7:4.beginreg1<=reg1+1’b1;i<=i+1’b1;5.6.endcase7.8.case(j)9.10.0:11.beginfor(x=0;x<8;x=x+1’b1)reg2=reg2+1’b1;i<=i+1’b1;end12.13.endcase.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}代码4.3.10
图4.3.2过程i建模示意图。
在建模的角度上,过程i的建模十一图如图4.3.2所示,实际上它只有一个累加资源,然后利用时钟不断反馈输出,不断重复相同操作,直至满意结果为止。
图4.3.3过程j建模示意图。
相比之下,过程j使用超过1个以上的累加资源,建模示意图如图4.3.3所示。因为for触发即时事件,而且for也重复递增结果八次,所以在人眼无法触及的情况下,8个称为即时层的累加资源建立而成。输入结果每经过一个累加即时层就执行一次累加操作,直至8个即时层游走完毕。为了明确区分过程i与过程j的区别,笔者建立简单的比较表格(表格4.3.2)。
表格4.3.2过程i与过程j的区别。
过程\比较参数
时钟消耗
资源消耗
操作模式
过程i
8
少
循环操作
过程j
1
很多
即时操作
如表4.3.2所示,过程i消耗时钟多却消耗资源少;反之,过程j消耗时钟少却消耗资源多。反实际上,过程i与过程j之间的比较已经是“优化与平衡”的范畴,有时候建模必须衡量某种平衡点,如果FPGA设备的逻辑资源稀少,我们必须考虑善用时钟作为优化的手段;换之,如果操作速度作为优先,那么善用资源是一种比较适宜的优化手段。
笔者曾经说过,建模还有仿真之间的区别就在乎“物理环境”还有“虚拟环境”而已。属于虚拟环境的仿真,FPGA设备拥有无限的逻辑资源,所以for要循环多少次也没有问题,反之亦然。不管怎么样,我们还是把话题却换回来...
图4.3.4代码4.3.11,第6~10行产生的时序。
图4.3.5代码4.3.11,第12~22行产生的时序。
换之,图4.3.5是经过笔者的仿真手段所产生的时序。如图4.3.5所示,reg1在T0输出即时值8,reg2在T1输出即时值8,reg3在T2输出即时值8。在此,for不仅按照时钟工作,for也有时序表现,所以这是一杯满满豆香的豆浆。当然,重点不仅而已,亮眼的同学一定会发现,如果实际的FPGA设备有足够的逻辑资源,其实这种仿真手段也适用建模。笔者是一名贯彻信念活下去的男人,同样手段适用不同环境就是节能本质最美的绝华。
Verilog有一些比较暧昧的关键字,for就是好例子,由于它们实在太难控制了,往往会在建模(实际环境)造成巨大的破坏(使用for会消耗大量逻辑资源)。因此for却被认为是验证语言的一伙,因为虚拟的仿真环境拥有无限的资源任由它消耗。此外,还有一些完全属于验证语言的关键字,如fork...join,forever等,实际上它们是综合语言相对应的兄弟姐妹,例如:forever与always,begin...end与fork...join,至于使不使用它们是见仁见智的问题。
验证语言主要是系统函数占据多,因此许多系统函数有如$display()函数一样触发即时事件。不过系统函数它们完全属于仿真的,所以综合(建模)的时候是绝对不允许系统函数出现。虽然系统函数看似很多很强大,其实系统函数存在一定风险,正如笔者所言,由于系统函数的本质太接近顺序语言了,如果用法不妥当会很容易抹杀时序的时序表现...系统函数的关键字符是美金$。
不管怎么说,这个章节只有抛砖引玉的作为而已,更多更详细的验证语言,读者必须自行需找其它参考。验证语言的关键字,系统函数,还有预处理,由于它们比较偏向调试风格,又或者本身就是活生生的顺序语言...不过,笔者为了养成统一的思维模式(并行思维),所以不怎么喜欢它们。虽然“欢喜”还有“必要”是不同的话题,但是过多涉及它们无疑是给仿真带来极大的学习负担,因此笔者建议最小程度使用它们就好。
又一次,笔者在炎热的天空下四处跑动,已经极限的笔者正在使命寻找城市的绿洲,走着走着笔者来到一处树荫下,歇气还不及几口,旁边就有人叫道,原来是一位卖豆浆的小贩正在向笔者哈拉。
“小哥,今天的天气还真是热到过分”,豆浆小贩说道。
“是呀...我快要成为暑糕了”,笔者回答道。
“小哥,要不要来一杯清凉又美味的豆浆呢?”,豆浆小贩示意道。
“哎呀,真是雪中送炭呀老板!给我特凉特大杯哪一种!”,笔者兴奋道。
“好的,这是为小哥特地准备的豆浆。”,豆浆小贩笑道。
笔者眼也不眨一下就顺势接过豆浆小贩手中的杯子,然后立即往口里送去...
“嗯唔!?老板这是什么豆浆,实在太美味了...”,笔者惊讶道。
此刻,诡异的事情发生了,树荫下只有笔者一人而已,不知什么时候那个豆浆小贩已经凭空消失...一股时曾相似的既视感忽然穿过脑髓,笔者不禁打了一个冷颤。
“见鬼了,又是哪位豆浆小贩...”,笔者粗言道。
不过,可怖的情绪却渐渐被口里那股浓浓的豆香滋润并且消去,笔者带着满意的心情继续赶路...
激励是什么?根据笔者的理解,激励这词其实是“刺激”与“反应”的复合体,刺激表示输入,反应表示输出。所谓激励文本,就是所有“刺激与反应”的集中营...简单点说,激励文本就是实现虚拟活动,也就是仿真环境。事实上,仿真环境是笔者的主观观念,笔者认为建模(实际建模)还有仿真(虚拟建模)本是同根,只有概念(环境)不同而已。
常规观念,亦即传统流派,它们认为建模(实际建模)还有仿真(虚拟建模)是不同平台的东西,所以两者之间可以使用不同的思维,模式还有手段。传统流派认识测试文本是一个测试作用的个体,而不是一座测试作用的环境,然而这个个体是偏向“调试”(顺序)的风格还有模式。
图4.4.1传统流派,用户,模块还有激励的关系图。
激励文本由于被传统流派视为个体,所以“用户(设计者)”“模块(仿真对象)”,“激励(激励文本)”,成为一种由上之下,倒金字塔的阶层关系图。如图4.4.1所示,设计者占据最高,而且也是分量最大,可见它有所多么重要的地位,反之激励文本不仅占据最低而且分量也是最小,可想而是它的地位是多么卑贱。
形象点说,用户可以是主人A,激励文本可以是奴隶C,然而模块(仿真对象)可以是爱犬之类的宠物B。主人A除非有命令告知奴隶C,否则主人A与奴隶B平时是不相往来,说得难听一天就是主人A是宠物B的主人,宠物B是奴隶C的主人,所以主人A与奴隶C之间是没有任何纽带。
假设,主人A用千万黄金从极东买了一只宠物恐龙B回来,有一天主人A突然心血来潮想知道恐龙B的战斗表现,于是主人A会用奴隶C去测试恐龙B。无情的主人A满脑子只在乎恐龙B的战斗表现而已,至于奴隶C的死活,主人A一律没有兴趣。换句话说,传统流派认识测试文本的是一位不值钱的个体,或者是一只实验性的小白鼠。
市场上,小白鼠是廉价的科学消耗品,价值和用完即丢的抹巾一样。所以说,传统流派重视激励文本的程度是非常之低,结果它们不会花费而外的资源还有精力去维护激励文本。因为如此,激励文本内容相比模块内容(仿真对象)会是更加不堪入目的乱,乱到让人抓狂又尖叫。
图4.4.2笔者观念中的仿真环境。
根据笔者的认识,激励文本不仅不是一个廉价的个体,而是一座非常贵重的仿真环境,然而我们就是创建这个环境的神。此外,仿真还有3个必须重视的结构性,其一是仿真对象的结构性,其二是仿真环境的结构性,其三是仿真过程的结构性,三者之间的结构性简单点说:
(一)仿真对象的结构性也是笔者时常挂在嘴边“模块的结构”,如低级建模,用
法模板等。
(二)仿真过程是指基本输入,基本输出还有环境输入等内容。仿真过程所谓的结
构性是指仿真过程的用法模板。
(三)仿真环境的结构性就是激励文本的的布局,也是各种仿真过程在激励文本的
的位置。
其(一)没有什么好说,这是学习Verilog的基础,笔者已经在建模篇讲得很清楚。其(二)是其(一)的缩水,懂得其(一)自然也会晓得其(二)。其(三)是仿真的关键,笔者认为激励文本一个仿真环境,然而仿真过程还有仿真对象都是个体,仿真环境所谓的结构性是指,个体之间如何布局才能使得整体产生最大“表达能力”还有“清晰能力”。
有些同学可能会黯然笑道:”激励文本想怎样写就怎样写嘛,干嘛还要分那么多...真是爱找麻烦的笔者呀。“,这位同学有所不知了,为什么人的屁股不是长在脑袋?如果屁股长在脑袋,大小便既不是要倒立不可?人是如此,激励文本也是如此,结构的重要性不管在什么方面上都有同样的道理。
其实,很久以前笔者就一直在冷嘲热讽传统流派的粗野,传统流派不会在乎结构性这种小细节。失去结构不仅会为前期建模带来困扰,同样,后期仿真也会带来诸多不便,由此可见结构性是多么重要。所以说,笔者实在不知道那些崇拜权威还有传统流派的人们是如何大小便的...
图4.4.3仿真环境的布局(结构性)。
图4.4.3曾经出现过许多章节,然而这张图却表示笔者认为仿真环境最有效的布局方式。如图4.4.3所示,激励文本亦即仿真环境可以分为5个个体(部分),亦即环境输入,仿真对象(实例化),虚拟输入,虚拟输出还有其他。环境输入可以是最简单的时钟信号还有复位信号的模拟输出;虚拟输入有基本输入,还有反馈输入;虚拟输出可以是基本输出,还有反馈输出。至于其他则是一些补充作用。
接下来让笔者用简单的实验来讲明一切,打开exp15...
图4.4.4selfadder_funcmod产生的理想时序。
接着让我们来看看这家伙的仿真环境(激励文本)到底是如何编写的!?
为进入解释仿真结果之前,再让笔者好好说明一下仿真环境的结构性。激励文本第12~18行是环境输入的部分;第20~30行是仿真对象实例化的部分;第34~61行则是虚拟输入的部分。在此,好奇的同学会问:“虚拟输出在哪里呢?”
图4.4.5selfadder_funcmod仿真环境的布局。
如图4.4.5所示,根据该仿真环境的布局方式,CLOCK信号与RESET信号属于环境输入,Start_Sig信号与WrData信号属于虚拟输入,其中Start_Sig兼为基本输入还有反馈输入,具体内容往后继续。虚拟输出的Done_Sig还有RdData都是基本输出,而且两者都由仿真对象selfadder_module产生以后直接投射在波形图当中。
所以说,我们没有必要在激励文本中多此一举,创建多余的Done_Sig与RdData虚拟输出。此外,读者必须注意一下Start_Sig与Done_Sig实际上是一种问答信号,因此Start_Sig需要根据Done_Sig的反馈状态再一次决定拉高又或者拉低Start_Sig,所以Start_Sig除了第一次的基本输入之余,还要根据Done_Sig产生另一个虚拟输入。
46.0:47.if(Done_Sig)beginStart_Sig<=1'b0;i<=i+1'b1;end48.elsebeginWrData<=8'd8;Start_Sig<=1'b1;end.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}接着让我们稍微切换步骤0...一开始的时候,由于第47行的if条件为成立(仿真对象还没有一次性的递增操作),结果第48行的代码优先执行。此刻,基本输入WrData与Start_Sig就开始产生,Start_Sig拉高以示仿真对象使能开始工作,WrData则是递增操作所需的输入。
图4.4.6selfadder_funcmod_simulation的仿真结果(exp15)。
图4.4.6是仿真结果,其中光标C0分别指向递增模块三次性的递增操作。如图4.4.6所示,笔者为了清晰波形图,稍微分类一下信号。CLOCK与RESET是环境输入;Start_Sig与WrData是虚拟输入;Done_Sig与RdData是虚拟输出;至于i除了指向仿真对象的内部过程以外还有指向虚拟输入的激励过程。
一开始的时候(C0指向的地方),步骤0为WrData输入8并且拉高Start_Sig,然后仿真对象就会在下一个时钟沿开始工作。仿真对象用了11个时钟完成递增操作并且反馈完成信号以示一次性的操作结束。步骤0则会根据Done_Sig的结果拉低Start_Sig(反馈输入)不使能仿真是对象,然后i递增以示下一个虚拟输入的产生。虚拟输入产生3次,因此Temp会有3种基本输出,亦即64,72还有80。
在这里,有些同学可能会抓头喊道,自己无法完全解读图4.4.6的仿真结果...这是当然的,此刻笔者只是简单演示一下仿真环境的布局方式而已,我们还没有深入联系仿真结果,激励内容,还有仿真对象等,每个时钟的实际活动...简单点说,我们还没有准备好将时序活动(仿真结果)与代码(仿真对象与激励文本)之间的联系并且做出解析。所以读者不能够完全解读图4.4.6一点也不奇怪,关于这一点,笔者会在往后讲解。
不过,任性的同学可能会闹脾气道:“不管嘛!我就是要虚拟输出嘛!”,好啦好啦,别闹脾气就是了,笔者服输了,打开exp16...
图4.4.7exp16的仿真结果。
图4.4.7是添加虚拟输出以后的仿真结果,图4.4.7相比图4.4.6多了一个FlagA信号。如图4.4.7所示,FlagA在整个时序过程只会翻来覆去而已...多么无聊的家伙。
图4.4.8exp16仿真环境(激励文本)的布局。
图4.4.8相比图4.4.5,在仿真对象的下面多了一个基本输出FlagA,不过不同的是FlagA信号不是仿真对象产生的输出,而是激励文本产生的输出。
最后笔者可以这样结论道:
到目前为止,我们可能还无法看道,仿真环境经过结构性的布局之,后到底凸显那种优越?不过不管怎么样,有结构性的激励文本域没有结构性的激励文本一定存在明显的差距,因为没有结构的激励文本比起有结构的激励文本一定会非常破烂不堪(乱)。此外,这个章节笔者也经由selfadder_funcmod表示,仿真对象的结构性(模块结构),仿真过程的结构性。
仿真对象的结构性就是selfadder_funcmod模块本身的内容,那是基础中的基础。笔者在selfadder_funcmod采用仿顺序操作的用法模板,此而i指向时钟之余又指向步骤(当然也包括过程)。此外,仿真过程的结构性则是...笔者用i指向虚拟输入的产生过程,用j指向虚拟输出的产生过程。事实上,仿真过程的结构形式非常相似模块的结构,或者说是模块结构的简化版,因为两者都是采用同样的用法模板。
图4.5.1仿真环境的布局。
未进入本章之前,我们再来简单复习一下仿真环境的布局方式。如图4.5.1所示,激励文本也是仿真环境,位于顶层的当然莫属环境输入,环境输入模拟最基本也是最重要的时钟信号还有复位信号。位于环境输入的下面是仿真对象,仿真对象可以经由模块实例化而成,仿真对象也可以直接在仿真环境中建立。
接着是虚拟输入,虽然环境输入还有虚拟输入都是“输入”,不过不如环境输入有份量。
虚拟输入有两个基本分类,亦即基本输入,还有反馈输入。虚拟输入的后面是虚拟输出,虚拟输出也有两个基本分类,亦即基本输出与反馈输出。基本输出一般都是仿真对象产生的反应,很少人为添加在激励文本当中,例如实验exp15的RdData信号与Done_sig信号。不过,我们也可以自行添加在激励文本当中,例如实验exp16的FlagA就是如此。
除此之外,虚拟输出还有一种令人厌烦的反馈输出,也是这个章节要探讨的东西。其实,笔者曾经在其它章节稍微讨论过它,不过目的是用来解释i为什么要指向过过程,而没有过多深入。那么笔者再一次正式提问“什么又是反馈输出呢?”
图4.5.2仿真模型①。
应用仿真模型①的仿真对象一般都是左耳进右耳出的单纯功能,常见的例子有门级逻辑仿真。
图4.5.3仿真模型②。
图4.5.3是仿真模型②的示意图,仿真模型②相较仿真模型①,它多了更多细节。如图4.5.3所示:
(一)激励内容产生基本输入刺激仿真对象,仿真对象产生反应以后便将基本输出
投射在wave界面上。
(二)激励内容产生基本基本输入刺激仿真对象,仿真对象产生反应以后将基本输
出反馈给激励内容,然后激励内容再产生反馈输入进一步刺激仿真对象。
图4.5.3等价的仿真模型②。
图4.5.3是等价的仿真模型②,如图4.5.3所示:
(三)除了仿真对象产生基本输出以外,激励内容也产生基本输出并且投射在wave
界面。
(四)激励内容之间也可以相互刺激。
仿真模型②基于仿真模型①之后扩展更多细节,仿真模型②使用的频密性不仅比仿真模型①还高,而且仿真模型②也能兼容仿真模型①,仿真模型②的应用例子有算法模块,问答式模块等功能稍微多样化的模块。然而,悲观而言,仿真模型②拥有更多数量的信号,而且方向性也非一处,此外过程(激励内容)也很多。
图4.5.4FPGA驱动硬件。
仿真对象很多时候不仅仅是为了观察输出而已,仿真对象还必须与实际硬件发生互动。如图4.5.4所示,假设笔者为FPGA创建模块A用作驱动硬件B,其中FPGA是主机,硬件B是从机,而且FPGA又是两方访问,换句话说FPGA除了向硬件B发生写入动作意外,FPGA也会为硬件B执行读出操作。
俗语有云,无穴不来风,事出必有因,由于仿真模型①与仿真模型②无法实现上述内容,因此仿真模型③面世了。虽说仿真模型③的诞生是为了仿真实际硬件,实际上仿真模型③也兼容仿真模型②与仿真模型①。
图4.55仿真模型③。
图4.5.5是仿真模型③的示意图,相较其它仿真模型,仿真模型多了一个虚拟硬件。仿真模型①与②的仿真对象要么就是讲基本输出投射在wave界面上,要么就是将基本输出反馈给激励内容。仿真模型③,仿真对象会产生基本输出刺激虚拟硬件,虚拟硬件接受刺激以后除了直接将反应(基本输出)投射在wave界面之余,虚拟硬件也会将反应反馈给仿真对象,此刻这种输出方式称为“反馈输出”。
图4.5.6仿真模型③,虚拟硬件等价仿真对象。
如果根据等价关系去分析图4.5.5,虚拟按硬件谓是另一个仿真对象,结果如图4.5.6所示。不过,一般没有人会特意为虚拟硬件建模,并且在仿真环境当中实例化成为另一个仿真对象...好奇的同学可能会怀疑,笔者是否又在偷懒?别误会,如果虚拟硬件是功能复杂的IC的话,假设是串行储存器AT24CXX好了,试问同学是否有信息建模而成呢?答案当然是否定的,这种事情世上也只有傻子去干而已。
说来非常惭愧,笔者就是那个傻子...要建模一块虚拟硬件,说实话那种程度的活儿,会耗死笔者的小命,幸好笔者迷途知返,不然笔者早就变成一具尸体。根据节能的角度而言,我们不会为了测试实际硬件的部分功能,结果特意从轮子开始创建整个虚拟硬件,就算能力允许,本质也不允许笔者这样做,因为这种做法太没有效率了...为此,我们要测试那个功能就模拟那个功能即可。
图4.5.7仿真模型③,激励内容等价仿真对象(虚拟硬件)。
图4.5.6是另外一种等价的仿真模型③,它没有虚拟硬件,虚拟硬件也不是仿真对象,取而代之它是一段激励内容。在此,我们会用“信号”来描述虚拟硬件的部分功能,然而描述过程就写在激励内容当中,至于那个“信号”又是什么信号呢?呵呵,想知道吗?笔者就大发慈悲告诉你们吧!
从第一章至这个章节以来,笔者一直在强调“早期有很好建模,后期就有好仿真”...在建模的阶段,笔者建议使用用法模范统一建模风格之余,笔者也建议使用i指向模块的内部过程(指向步骤)。虽然这些做法在大体上是为了稳固的结构,增强内容的清晰度,还有提高模块的表达能力...然而这些所作所为却为仿真产生意想不到的正面效果,这种偶然究竟是一场意外的幸运,还是一连串“神的恶作剧”?
正如笔者曾在第三章节3.5所言那样,既然i有能力指向模块的内部过程,同样也表示i有能力标示模块的运行状态。然而,这些运行状态对仿真而言可是非常贵重的“描述材料”,我们可以用这些信息来描述虚拟硬件的部分功能。理论上的确如此,不过在此之前我们必须做足一切事先的准备,亦即规划激励文本的布局,创建可以最大程度支持仿真模型③,并且兼容仿真模型②还有仿真模型①的仿真环境。
不管怎么样,笔者还是用实验来说话吧,打开exp17:
图4.5.8哈罗功能模块的建模图。
如图4.5.8所示,假设有一个名为哈罗的功能模块,笔者用它驱动硬件C。哈罗功能模块的左方有Start_Sig信号与Done_Sig信号充当开关,右方的WrData信号与RdData信号主要是访问硬件C。
还未仿真之前,先让我们好好假设一下硬件C的基本功能:
(一)接收数据8’hAA,反馈数据8’hBB;
(二)接收数据8’hCC,反馈数据8’hDD;
(三)接收数据8’hEE,反馈数据8’hFF;
然后我们再给予上述所有信息创建仿真模型③。
图4.5.9exp17的仿真模型③。
图4.5.9就是exp17的仿真模型③,Start_Sig信号由激励内容产生,Done_Sig信号还有WrData信号则由仿真对象产生,至于RdData信号是反馈输出,它由硬件C(虚拟硬件)
产生。笔者曾在前面说过,不管硬件C的功能再怎么简单,我们都不会特意从轮子开始创建整个虚拟硬件,相反我们与采取替代又等价的手段。
图4.5.10exp17等价的仿真模型③。
如图4.5.10所示,我们采用另外一种等价的仿真模型③,其中虚拟硬件则有一段激励内容取代,此外仿真对象也将内部过程牵引出来。当我们了解这些信息以后,我们就可以开始创建仿真环境了。
第22~31行是仿真对象——哈罗功能模块的实例化;第37~53行是虚拟输入的激励内容;第57~69行则是虚拟输出的激励内容。首先让我们来瞧瞧虚拟输入的产生过程,根据图4.5.10所示,仿真对象的左边只有Start_Sig信号与Done_Sig信号而已,然而第37~53行的激励内容则是负责这些信号的基本输入还有反馈输入。
我们知道好罗功能模块有仿顺序建模的外形,因此虚拟输入程仅是在步骤0(第48行),单纯地拉高Start_Sig信号(基本输入),接着又根据Done_Sig信号的反馈结果(第47行)拉低Start_Sig信号(反馈输入)。步骤0完成后,i递增以示下一个步骤,然后虚拟输入就结束过程(第47行)。
第57~69行是虚拟输出的激励内容...在此,笔者先举例反馈输出最简单的方法。第60行是寄存器RdData的复位操作,根据图4.5.9所示,RdData是虚拟引脚给仿真对象的输入链接,可是仿真环境又没有实际的链接,结果用reg替代。第63~69行是虚拟输出的激励内容,笔者采用最简单的case...endcase用作反馈输出。根据硬件C的基本功能:
(一)如果WrData的输入数据是8’hAA,RdData就反馈输出数据8’hBB;
(二)如果WrData的输入数据是8’hCC,RdData就反馈输出数据8’hDD;
(三)如果WrData的输入数据是8’hEE,RdData就反馈输出数据8’hFF;
结果第63~69行完全对应上述要求。
图4.5.11exp17的仿真结果。
图4.5.11是hello_funcmod_simulation的仿真结果,其中就有光标C0~C4分别指向各种过程,为了清晰wave界面笔者也稍微根据环境布局分类信号,结果如图4.5.11所示。
C0指向整个激励过程的开始,首先虚拟输入会拉高Start_Sig信号,然后就停留在原步等待仿真对象反馈完成信号(注意最下面的指向信号i)。
C2指向仿真对象经由WrData信号输出第二个数据8’hCC,然而虚拟输出也再下一个时钟接收然后产生经由RdData反馈数据8’hDD。又在下一个时钟,仿真对象接收反馈数据8’hDD以后,将i递增以示下一个步骤。
C3指向仿真对象正在发送第三个数据的时候,此刻仿真对象会经由WrData信号输出数据8’hEE,虚拟输出则再下一个时钟接收数据8’hEE并且产生反馈数据8’hFF。过后,仿真对象将在下一个时钟接收反馈数据8’hFF,然后将i递增以示下一个步骤。C4则是指向仿真对象产生完成信号产生的时候,Done_Sig信号是用来通知虚拟输入一次性的操作过程已经圆满结束。
因此,虚拟输入在下一个时钟接收到Done_Sig信号以后,立即拉低Start_Sig信号(反馈输入)表示结束使能仿真对象,并且将i递增递增以示下一个步骤,此刻,指向信号i的未来值成为1。
65.8'hAA:RdData=8'hBB;66.8'hCC:RdData=8'hDD;67.8'hEE:RdData=8'hFF;.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}hello_funcmod_simulation的仿真结果大致上就是这种感觉,有些同学可能会非常好奇为什么代码的第65~67行是用=赋值操作符,而不是<=负值操作符。其实,这是笔者的小习惯,硬件在理想的状态下当然是有多快就多快将输出反馈出去...结果而言,=产生的即时值相比<=产生的未来值,即时值比较快。
这个小节,笔者除了解释3种仿真模型以外,笔者也简单举例一下仿真模型③的使用方法,此外笔者也顺便演示一下反馈输出的产生过程。不过很遗憾的是,指向信号SQ_i在此只是用来表示仿真对象的内部过程而已,并没有实际参与虚拟输出的活动。事实上,硬件C在exp17的功能要求还是非常简单的程度,所以指向信号SQ_i用不着派上用场。往后笔者会继续提高硬件C功能要求,以致虐待读者直到需要它...嘻嘻嘻。
笔者在上一个章节除了演示仿真模型③的用法以外,笔者也简单举例反馈输出的产生过程。这个章节,我们会继续未完的故事,并且好好认识一下,环境布局(仿真环境的结构性),指向信号,还有仿真模型之间是如何合作无间,以致满足虚拟硬件不断提高的功能要求。
图4.6.1exp17的仿真模型③。
图4.6.2exp17等价的仿真模型③。
前情提要,我们需要创建一座仿真环境用作测试哈罗功能模块与硬件C之间的沟通,结果如图4.6.1所示。不过,笔者不建议创建完成的虚拟硬件,为此笔者选择等价的替代方法,如图4.6.2所示,笔者仅在激励内容模拟硬件C的部分功能而已。此外,硬件C的第一次功能要求如下:
由于第一次的功能要求过于简单,所以指向信号SQ_i没有出场的机会。结果当天晚上,SQ_i跑来向笔者哭诉说它是多么期待当天的演出。无奈之下,笔者必须不断提高硬件C的功能要求,以致满足指向信号SQ_i的心愿为止。硬件C第二次的功能要求如下:
(一)第一次接收数据8’hAA,反馈数据8’hBB;
(二)第二次接收数据8’hAA,反馈数据8’hDD;
(三)第三次接收数据8’hAA,反馈数据8’hFF;
因此如此,我们需要重新修改一下exp17的内容,打开exp18:
如代码第68~80行所示,笔者应用仿顺序操作的用法模板,然后再根据步骤将反馈数据RdData按次序作出3次输出。先是步骤0检测WrData信号是否8’hAA?是就反馈数据8’hBB,然后将j递增以示下一个步骤,同样行为也发生在步骤1与步骤2的身上
。此外,笔者也事先作好保险措施,将RdData改为即时值(注意赋值操作符)。万事虽然已经具备,不过第68~80行的虚拟输出是否可以发挥预期的效果,说实在笔者真心不知道...
图4.6.3exp18的仿真结果。
图4.6.3是hello_funcmod_simulation的仿真结果。啊!我的天呀...读者看见什么嘛?如图4.6.3所示,如果Done_Sig信号没有拉高就表示激励过程失败了,亦即图4.6.3不是预期所要的仿真结果,究竟问题是发生在哪里呢?让我们一起瞧瞧吧。
C0指向整体激励的开始,此刻虚拟输入拉高Start_Sig以示仿真对象开始工作。C1指向仿真对象开始工作的时候,仿真对象现在步骤0经由WrData信号输出结果8’hAA,然后将i递增以示下一个步骤。在下一个时钟(C2指向的地方),虚拟输出接收结果并且经由RdData反馈数据8’hBB,然后将j递增以示下一个步骤。
C3指向的地方,恰好是仿真对象停留在步骤1的时候,此刻仿真对象读取RdData的过去值为8’hBB,if条件成立,然后将i递增以示下一个步骤。那么问题发生了!由于
WrData的输出结果始终是8’hAA,再同样的时刻(C3指向的地方)虚拟输出步骤2的if条件也成立了,因为它读到WrData的过去值是8’hAA,结果它经由RdData信号反馈数据8’hDD,并且将j递增以示下一个步骤。
C4指向的地方是仿真对象停在步骤2的时候,此刻仿真对象还在准备经由WrData信号更新结果而已,由于WrData之前还有之后的结果都一样,所以波形图上的WrData没有发生任何变化,不过实际上仿真对象确实已经将它更新。完后,仿真对象将i递增以示下一个步骤。糟了糟了,问题像雪球越滚越大了。
在同一个时候(C4指向的地方),虚拟输出判断WrData的过去值为8’hAA,因此反馈数据8’hFF,并且将j递增以示下一个步骤。C5指向的地方是也是仿真对象停留在步骤3的时候,此刻仿真对象还在傻傻等待接收反馈数据8’hDD,但它不知道反馈数据8’hDD老早已经跑去火星,亦即它已经错过数据8’hDD。呜呜呜...就这样,仿真对象永远都傻傻般停留在步骤3等待反馈数据8’hDD的到来...
好奇的同学可能会这样问道:“是不是仿真对象还有虚拟输出之间出现不协调的时序沟通?结果数据接收错乱了?”的确如此...好奇的同学可能又会反问道:“这起意外究竟是谁的错?仿真对象,还是虚拟输出呢?”类似问题我们不能随便妄下定论,实际上是谁都没错,是谁都有责任...
这种情况就像发生车祸一般,当务之急不是相互指着是非而是优先送急伤者才是。虚拟输出相比仿真对象,虚拟输出更像是受伤的一方,我们会优先修改虚拟输出,再者才考虑仿真对象。在此,仿真对象内部的指向信号——SQ_i它就派上用场了,前几回由于SQ_i没有出演机会,它整天都是以泪洗脸,此刻就是它大发光彩的时候,好让累积许久的能量一次性爆发出来。因此,请打开exp19:
哈罗功能模块再exp19当中没有发生任何修改,所以笔者就不重复了。
完后再让我们仿真一次看看:
图4.6.4exp19的仿真结果。
图4.6.4是exp19的仿真结果...如图4.6.4所示,Done_Sig信号可以完美输这就表示仿真结果已经符合预期的期待。C0指向的地方是整个激励过程的开始,此刻虚拟输入开始拉高Start_Sig以示使能仿真对象。在同一时刻,虚拟输出也产生反应,然后立即输出反馈数据8’hBB。
C1指向的地方是仿真对象开始操作的时候,此刻步骤对象在步骤经由WrData信号输出8’hAA。由于仿真对象还是停留在步骤0(注意SQ_i的过去值),因此虚拟输出没有产生任何变化。C2指向仿真对象停留在步骤1的时候,仿真对象检测RdData的过去值,结果满足if条件,然后将i递增以示下一个步骤。
C3指向的地方是仿真对象停留在步骤2的时候,此刻仿真对象决定输出第二次输出8’hAA,完后将i递增以示下一个步骤。再同一个时刻,虚拟输出检测SQ_i的过去值是2,结果产生反应输出反馈数据8’hDD。
C4指向的地方却是仿真对象停留在步骤3的时候,此刻它检测RdData的过去值为8’hDD,因此if条件成立,i递增以示下一个步骤。之余虚拟输出则没有什么活动发生。
C5指向的地方是仿真对象停留在步骤4的时候,此刻他决定为信号WrData输出8’hAA,然后再将i递增以示下一个步骤。再同一个时刻,虚拟输出检测SQ_i的过去值为4,它产生反应并且输出反馈数据8’hFF。
C6指向的地方是仿真对象停留在步骤4的时候,此刻它检测到RdData的过去值为8’hFF,事后将i递增以示下一个步骤。换之,此刻的虚拟输出正闲着没事干。至于C7与C8指向的地方则是仿真对象的步骤6~7,并且也是完成信号产生的步骤(注意SQ_i的过去值)。虚拟输入再C8指向的时刻检测Done_Sig的过去值是1,因此它决定拉低Start_Sig(反馈输入)不使能方针对象,然后再将i递增以示下一个步骤。
虽然虚拟输出再C8之后的时钟里还在根据SQ_i结果产生反应,不过这是不管紧要的小,因为我们已经确定在C0~C8之间,仿真对象可以正确的完成一次性的操作,这样就已经足够了。在此,读者是否渐渐发觉指向信号SQ_i是如何巧妙描述硬件C的部分功能呢?总结exp17~19的仿真结果,我们可以结论道...
除了SQ_i以外,仿真对象的使能信号Start_Sig也能成为描述虚拟硬件的原材料,就让笔者稍微扩充一下exp19的虚拟输出:
57.always@(posedgeCLOCKornegedgeRESET)58.if(!RESET)59.begin60.RdData<=8'd0;61.end62.elseif(Start_Sig)63.case(SQ_i)64.65.0:RdData=8'hBB;66.2:RdData=8'hDD;67.4:RdData=8'hFF;68.69.endcase.csharpcode,.csharpcodepre{font-size:small;color:rgba(0,0,0,1);font-family:consolas,"CourierNew",courier,monospace;background-color:rgba(255,255,255,1)}.csharpcodepre{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)}大致的感觉如上述代码所示,笔者在第62行用Start_Sig信号为虚拟输出添加一行if条件,就这样...虚拟硬件也有使能的功能了。
虽然章节4.5与4.6我们都是在谈论仿真模型还有反馈输出的故事而已,不过眼睛犀利的朋友,隐约可以察觉这一切的一切必须是仿真环境拥有结构性作为前提,此外仿真对象还有仿真过程的结构性也很重要,不然的话我们就不能轻易应用仿真模型还有实现反馈输出了。
不知不觉之间笔者已经将第四章搞定了,这个章节算是仿真的第二核心吧。第四章的一开始我们就在讨论验证语言的定义,如果没有深入一切,而且一切又是任由传统流派道听途说,结果我们就会觉得验证语言似乎很难很强大。事实上,验证语言只是占据仿真的小部分而已,而且常用的验证语言不过也是小猫两三只而已。我们真是被传统流派吓倒了。
根据笔者的认识,建模(实际建模)还有仿真(虚拟建模),本是同根,差异就有概念(环境)而已。实际上,两者可以共享同样的手段还有思维,建模虽然拥有实际的输入还有输出,不过建模会受限于实际环境。反之,仿真虽然没有物理的局限性,但是仿真必须模拟实际环境中存在的东西,如时钟信号还有复位信号就是最好的例子。
这种仿真手段不仅有自身局限性,也不怎么适合功能操作复杂的仿真对象。同样,这种仿真手段其实也是抹杀时序表现最大的凶手,因而产生豆浆不是豆浆,时序不是时序的问题。有人可能会问,时序有没有表现真的那么重要吗?时序失去如果时序表现,时序终究再也不是时序,而是一副没有意义的白纸而已,因为“精华”早已不复存在,这种感觉好似没有味道的豆浆一样。
系统函数,还有预处理占据验证语言的大多数,然而真正对仿真有用的关键字用5只手指也能表示出来。一般上笔者不怎么推荐过度学习验证语言,因为许多验证语言不仅没有实际的用处,而且又难学,此外还有验证语言是在重复综合语言的功能而已。常规上,验证语言的本质是非常接近顺序语言,为此过多使用它们很难让我们适应仿真应有的并行模式。
笔者虽然讨厌验证语言,不过我们还是需要最小程度利用它们。学习验证语言的关键就是理解它们的时序表现,换句话说就是如何使用时钟控制它们而且不然它们暴走。验证语言有没有时序表现,其实这是非常主观的问题,笔者认为有是因为笔者有追求美味豆浆的原始冲动,至于他人就见仁见智了。
此外,我们还有谈论激励文本的布局,亦即仿真环境的结构性。布局其实是一种自然艺术,好的环境布局就会产生正面发的效果,反之亦然。理解笔者的读者自然不会觉得奇怪,因为笔者是一位重视结构至死的男人,笔者认为前期有好建模,后期就有好仿真,此外笔者也一样重视仿真对象的结构性还有仿真过程的结构性。为什么笔者会如此强调结构的重要性呢?
仿真环境的结构性也好,仿真过程的结构性也好,这一切一切都是为了支撑仿真模型。
根据笔者的理解,仿真模型有3种,其一是最基础也是门级模块仿真应用最多的模型。仿真模型②相较仿真模型①拥有更大的功能扩张,期间结构性的支撑也会略显重要。最后一个仿真模型也是仿真模型③。它的诞生是为了仿真虚拟硬件,而且它也能兼容前面两个仿真模型。仿真模型③是非常讲究结构性的仿真模型,如果仿真环境,仿真对象,还有仿真过程,其中一放缺少结构都有可能失撑仿真模型③。