仿真好比吸烟一样,两者都要使用健康来交保 ... 爱惜生命也好,节能主义也罢,我们都应该尽量减低仿真使用频率。仿真除了存在恐怖的绝望切糕以外,仿真也存在不可仿真对象,那么什么又是不可仿真对象呢?不可仿真对象不是没有仿真的意义,而是仿真起来会不断蹉跎青春,浪费无谓的精力。
我们知道仿真的本意是将仿真对象,激励内容,还有时序结果联系以后并且做出解析 ... 然而,不可仿真对象则是妨碍联系过程,让仿真信息解析工作无法有效执行。那么,到底有存在多少不可仿真对象呢?根据笔者的理解,不可仿真对象有以下3个:
(一)超傻模块
(二)超烦模块
(三)超乱模块
其一,超傻模块是指功能过度简单,例如小功能模块或者小组合逻辑。小功能模块虽然也是功能模块,但它不仅用时简短(大概有1~3个时钟),而且功能也非常单纯,典型的例子有,加码器,移位寄存器等。如果超傻模块作为个体建模,模块内容一般都是清晰直观,稍看一眼就立即知道大意。
1. module encode_module( input CLOCK, input [3:0]NumSig, output[7:0]SMGCode ); 2. 3. reg [7:0]rSMG; 4. always @ ( posedge CLOCK ) 5. case( NumSig ) 6. 4'd0 : rSMG <= 8'b1100_0000; 7. 4'd1 : rSMG <= 8'b1111_1001; 8. 4'd2 : rSMG <= 8'b1010_0100; 9. 4'd3 : rSMG <= 8'b1011_0000; 10. 4'd4 : rSMG <= 8'b1001_1001; 11. 4'd5 : rSMG <= 8'b1001_0010; 12. 4'd6 : rSMG <= 8'b1000_0010; 13. 4'd7 : rSMG <= 8'b1111_1000; 14. 4'd8 : rSMG <= 8'b1000_0000; 15. 4'd9 : rSMG <= 8'b1001_0000; 16. endcase 17. 18. assign SMGCode = rSMG; 19. 20. endmodule
代码6.1.1
代码6.1.1是加码器的典型例子 ... 如代码6.1所示,这是一个数码管的加码模块,第1行的NumSig是输入信息,SMGCode则是加码信息;第5的case ... endcase 会根据 NumSig 的输入,为 rSMG 赋予不同的加码信息。如第6行所示,NumSig输入为 4’d0,操作将加码信息8'b1100_0000赋予 rSMG,然后再由 rSMG 驱动 SMGCode (第18行)。
图6.1.1 想象的理想时序图。
至于代码6.1,我们只要善用想象力稍微脑补一下,时序图就会出现在脑海当中。如图6.1.1所示,那是笔者脑补以后的时序结果 ... 假设NumSig在T0的未来值是 4’d0,那么加码模块会在T1读取该过去值,并且经由SMGCode输出为未来值 8’b1100_0000。其它NumSig也以此类推。
小组合逻辑,一般是指小功能的组合逻辑,小组合逻辑与小功能模块虽然大伙的都是超傻的同伴,但是前者没有时钟源。笔者曾经说过,组合逻辑的时序表现是即时事件,而且即时事件也无视时钟,结果我们用不着脑补时序图。小组合逻辑的典型例子有Verilog自带的运算符,如 *,/,% 等之余,还有一些常用的选择器也是。
1. module devide_module( input [7:0]A,B, output [15:0]Result ); 2. 3. reg [15:0]rResult; 4. always @ (*) rResult = A * B; 5. 6. assign Result = rResult; 7. 8. endmodule
代码6.1.2
代码6.1.2乘法器的简单例子,第4行除了使用 always @ (*) 声明组合逻辑以外,第4行业使用 Verilog 自带的相乘运算符执行 A*B操作,然后将结果赋予 rResult寄存器。最后再由该寄存器驱动 Result出入端(第6行)。代码6.1.2基本上已经傻到不能再傻了,因为内容不管怎么看都是太直接了,所以用不着脑补时序图。
1. module xx_basemod ( output [7:0]WrData ); 2. 3. wire [7:0]Data_U1; 4. a_module U1 5. ( 6. ... 7. .StartSig( StartSig_U1 ), 8. .Data( Data_U1 ) 9. ) 10. 11. wire [7:0]Data_U2; 12. a_module U2 13. ( 14. ... 15. .StartSig( StartSig_U2 ), 16. .Data( Data_U2 ) 17. ) 18. 19. reg [7:0]rData; 20. always @ ( * ) 21. if( StartSig_U1 ) rData = Data_U1; 22. else if( StartSig_U2 ) rData = Data_U2; 23. else rData = 8’dx; 24. 25. assign WrData = rData; 26. 27. endmodule
代码6.1.3
代码6.1.3则是选择器的典型例子,笔者先是在第4~9行,还有第12~17行实例化同样的 a_module 为 U1与U2,然而第8行,还有第16行的 Data输出都争用同一个WrData输出端(第1行),此刻两仪性的问题发生了。为此,笔者在20~23行建立一个选择器来协调 U1~U2公用同一个输出端。
如代码6.1.3所示,第20行使用 always @ (*)声明并且创建组合逻辑,而且组合逻辑也是触发即时事件,因此第21~23行的操作同样也是无法脑补时序图。不过代码20~23的内容早已非常清晰的告诉我们,如果 U1 使能,输出端WrData就交由U1使用(第21行),反之亦然。
超傻模块作为个体而言,模块内容足以清晰直至刺激我们的想象力,任我们想忘也忘不了。在此,有些同学可能会疑惑道,超傻模块实际上便没有阻碍仿真的联系工作,换之,仿真超傻模块反更简单 ... 为何笔者要将它们列为不可仿真对象呢?嗯 ... 这种感觉还比老师询问读者一加一的结果,读者是将结果心算出来呢?还是用纸加铅笔计算出来呢?又或者使用计算器计算出来呢?不用说,答案当然是心算出来。
不管仿真对象再怎么简单,仿真本来就是麻烦的工作,尤其是是仿真的准备工作更加猥琐耗时 ... 我们不仅要预设仿真,我们也要建立仿真项目,再者就是创建激励文本。试问我们有多少青春这样去蹉跎呢?超傻模块最大的罪状就是消耗我们的青春,结果它必须死!必须成为不可仿真对象!
“小哥,吃饱了吗?”
“小哥,身体还好吗?”
“小哥,天气转凉了 ... ”
隔壁的小花三五四次总是重复一样的问候,然而这些关心话,笔者曾经认为烦心又吵死人 ... 如今离家许久,笔者渐渐地开始怀念小花的唠叨了。人会啰嗦,模块同样也会啰嗦,这些啰嗦的模块笔者称为超烦模块。好奇的同学可能会疑惑,模块又不会说话,那来唠叨呢?这位同学有所不知了,超烦模块并不指意模块会涛涛不绝说废话,而是模块让人觉得“烦心”才是重点。
烦心?没错,就是烦心。试问读者,除了唠叨以外还有什么东西会让人觉得烦心呢?笔者认为,等候女人是最烦心的事情 ... 笔者始终不理解,为何女人都会无止境照镜子?换装会犹豫不决?一个好好的约会,笔者每次都要用上小时去等待她们 ... 期间,笔者会不断思考,女人这种生物究竟用有什么结构创建的?
除了等候女人以外,等候模块也会让笔者觉得十分烦心 ... 一般上,超烦模块都有一处共同点,亦即内容夹带计数器(定时器)。虽说等候计数没有等候女人那么可怕,然而等候计数期间,我们分分秒秒都在掉血,时间还有精力无意间就这样浪费掉了。为了更好理解超烦模块的破坏力,请打开 exp26 ...
1. `timescale 1 ns/ 1 ns 2. module exp26_simulation(); 3. 4. reg CLOCK; 5. reg RESET; 6. 7. /***********************************/ 8. 9. initial 10. begin 11. RESET = 0; #10; RESET = 1; 12. CLOCK = 1; forever #10 CLOCK = ~CLOCK; 13. end 14. 15. /***********************************/ 16. 17. parameter T100MS = 24'd500_000; 18. 19. reg [3:0]i; 20. reg [23:0]C1; 21. reg [3:0]LED; 22. 23. always @ ( posedge CLOCK or negedge RESET ) 24. if( !RESET ) 25. begin 26. i <= 4'd0; 27. C1 <= 24'd0; 28. LED <= 4'd0; 29. end 30. else 31. case( i ) 32. 33. 0: 34. if( C1 == T100MS ) begin C1 <= 24'd0; i <= i + 1'b1; end 35. else begin C1 <= C1 + 1'b1; LED <= 4'b0001; end 36. 37. 1: 38. if( C1 == T100MS ) begin C1 <= 24'd0; i <= i + 1'b1; end 39. else begin C1 <= C1 + 1'b1; LED <= 4'b0010; end 40. 41. 2: 42. if( C1 == T100MS ) begin C1 <= 24'd0; i <= i + 1'b1; end 43. else begin C1 <= C1 + 1'b1; LED <= 4'b0100; end 44. 45. 3: 46. if( C1 == T100MS ) begin C1 <= 24'd0; i <= 4'd0; end 47. else begin C1 <= C1 + 1'b1; LED <= 4'b1000; end 48. 49. endcase 50. 51. /***********************************/ 52. 53. endmodule
exp26是无伤大雅的流水灯实验,假设笔者想要模拟50Mhz的实际时钟 ... 笔者先在第1行将时钟刻度设置为 1ns/1ns。50Mhz有20ns的时钟周期,因此第12行的CLOCK寄存器每隔10个时钟刻度翻转一次。假设间隔是100ms,于是笔者在第17行声明100ms的常量为 24'd500_000。
第19~21行是相关的寄存器,i为指向步骤,C1为计数器,LED寄存器为模拟LED效果。第31~49是流水灯的操作,步骤0先将LED设置为 4’b0001,然后延迟100ms;步骤1设置LED为4’b0010,然后延迟100ms;步骤2设置LED为4’b0100,然后延迟100ms
;步骤3设置LED为4’b1000,然后延迟100ms;最后再重新返回步骤0。
图6.2.1 exp26的仿真结果。
读者没有看错,图6.2.1是exp26的仿真结果,不过笔者中途放弃等候了 ... 因为实在是太烦心了。如图6.2.1所示,我们可以看见无间断的 CLOCK信号与C1计数,然而我们却看不见C1的实际计数内容,真是哦买狗!
图6.2.2 将wave界面放大几十倍。
为了看见C1的计数内容的话,笔者估计将wave界面放大,话虽如此 ... 如图6.2.2所示,那是笔者将wave界面放大几十倍以后的结果,细心的同学一定会发现,光标C0并没有准确指向时钟。为了调整C0指向的地方,笔者必须不停拖动滚动轴,或者不停缩小再放大 ... 做着做着:
”气死人了!受够了,实在烦心死了!不玩了!不玩了!exp26不仿真了!“
结果笔者开始自暴自弃了 ... 在此,想必读者也是身同感受。没错,这就是超烦模块惊人的破坏力,它会让人们觉得烦心,然后自暴自弃,感受绝望。笔者曾经说过,时钟用量是一种直接性的变数,亦即时钟用量越长,意外的可能性就会越高,而且可能性也会指向上身,甚至海量化仿真信息。换句话说,超烦模块是切糕的兄弟,它助长切糕肆虐。
除此之外,模块愈烦时钟用量愈多,仿真愈加吃尽内存,直至拖慢整架计算机,甚至当机。根据笔者的经历,笔者的老机器最的极限是1ms的最大仿真时间,如果最大仿真时间超过1ms,计算器就会立即罢工。机器罢工还是小事,真正让笔者忧心的是过长的wave界面 ...
如图6.2.1所示,过长的wave界面会大大降低仿真效率 ... 期间,指向工作不仅会变得困难起来,而且还要不停滑动鼠标中间将wave界面拉来拉去,缩小又放大。一个简单的流失灯实验,笔者至少花费了1个小时在瞎搞,最终得到一个烦心的结果 ... 说实在,滑鼠,手指,还有青春都在发疼。试问我们有多少青春可以这样去蹉跎?超烦模块最大的罪状就是贱踏我们的青春,结果它必须死!必须成为不可仿真对象!
读者千万别小看可爱漂亮的女人,她们外表越好看,打扮起来越花时间,实际等候会发挥无与伦比的破坏力,摧毁我们的青春。话虽如此,作为男人有时候也不得不等待,但也不能过度牺牲小我。为此,我们必须拿出绅士的风范,告诉她们“我最爱真实的妳”。如此一来,问题就能巧妙解决。超烦模块就是如此,我们不能无视它,也又不能直视它,为此我们必须拿出绅士的手段。
超烦模块一般都是计数器(定时器)在里边不停灌水才会导致的悲剧。反过来问,我们究竟是为了什么去仿真计数器(定时器),答案就是为了实现精密控时。精密控时究竟是什么,相比有些同学可能会觉得困惑?简单而言,好比exp26我们都必须为每个步骤计数5000000个时钟以致实现100ms的间隔时间。
精密控时一般有两种手段实现,其一是笔者爱用的整合技巧,整合技巧所拥有的独特代码风格,实际上是针对控时所设计 ... 不过很遗憾的是,笔者并不打算在这里解释整合技巧,取而代之笔者会举例另一种更加直接的方法,那就是不等例缩放。
图6.2.3 等例缩放。
假设笔者是一名黑铁技工,然而有一位客人要求读者为它建立一副高为36英寸(3英尺)
,长为120英寸(10英尺)的招牌A。笔者首先必须为客人提供招牌A的软设计,由于36“ × 120”的面积实在太大,笔者的老机器无法承受。因此,笔者创建10:1等例缩放的招牌B供客人参考,结果如图6.2.3所示。
等例缩放是一种日常的解决手段,目的是为了节能 ... 看见“节能”两个字,笔者仿佛触电般两眼又发光了。没错!等例缩放是一种节能的解决手段,而且仿真也能应用 ... 但是仿真软件不及其它设计软件,简单点击几下鼠标就能等例缩放。结果,仿真软件只能人为实现不等例缩放。不等例缩放也是一种破坏性的手段,所以执行之前,我们必须准备好副本才行。
代开exp27,它是exp26的副本 ... 接下来,我们便要用它实现不等例缩放。
1. `timescale 1 ps/ 1 ps 2. module exp27_simulation(); 3. 4. reg CLOCK; 5. reg RESET; 6. 7. /***********************************/ 8. 9. initial 10. begin 11. RESET = 0; #10; RESET = 1; 12. CLOCK = 1; forever #5 CLOCK = ~CLOCK; 13. end 14. 15. /***********************************/ 16. 17. parameter T100MS = 24'd5; 18. 19. reg [3:0]i; 20. reg [23:0]C1; 21. reg [3:0]LED; 22. 23. always @ ( posedge CLOCK or negedge RESET ) 24. if( !RESET ) 25. begin 26. i <= 4'd0; 27. C1 <= 24'd0; 28. LED <= 4'd0; 29. end 30. else 31. case( i ) 32. 33. 0: 34. if( C1 == T100MS ) begin C1 <= 24'd0; i <= i + 1'b1; end 35. else begin C1 <= C1 + 1'b1; LED <= 4'b0001; end 36. 37. 1: 38. if( C1 == T100MS ) begin C1 <= 24'd0; i <= i + 1'b1; end 39. else begin C1 <= C1 + 1'b1; LED <= 4'b0010; end 40. 41. 2: 42. if( C1 == T100MS ) begin C1 <= 24'd0; i <= i + 1'b1; end 43. else begin C1 <= C1 + 1'b1; LED <= 4'b0100; end 44. 45. 3: 46. if( C1 == T100MS ) begin C1 <= 24'd0; i <= 4'd0; end 47. else begin C1 <= C1 + 1'b1; LED <= 4'b1000; end 48. 49. endcase 50. 51. /***********************************/ 52. 53. endmodule
exp27由于是不等例缩放的关系,所以笔者用不着严格遵守模拟实际的情况。笔者先将始终刻度设置为1ps/1ps(第1行),接着将第12行的 CLOCK寄存器设置为每隔5个时钟刻度翻转一下,亦即模拟10ps周期的时钟信号。最后,笔者再将第17行的常量声明设置为24’d5。余下的内容与exp27一模一样。
图6.2.4 exp27的仿真结果。
如图6.2.4所示,这是exp27的仿真结果,其中光标C0~C2分别指向步骤0~1的停留时间 ... 根据现实结果,步骤0~1分别停留60ps,或者说6个时钟,同样的结果也发生在步骤2~3的身上。为此,我们开始纠错,设计的本意是要每个步骤停留5个时钟,亦即50ps的时间才对,由此可见步骤0~3多停留一个步骤。但是有时什么原因导致步骤多停留一个时钟呢(60ps)?答案是 ... 步骤与步骤之前的切换多用了1个时钟。
31. case( i ) 32. 33. 0: 34. if( C1 == T100MS -1) begin C1 <= 24'd0; i <= i + 1'b1; end 35. else begin C1 <= C1 + 1'b1; LED <= 4'b0001; end 36. 37. 1: 38. if( C1 == T100MS -1) begin C1 <= 24'd0; i <= i + 1'b1; end 39. else begin C1 <= C1 + 1'b1; LED <= 4'b0010; end 40. 41. 2: 42. if( C1 == T100MS -1) begin C1 <= 24'd0; i <= i + 1'b1; end 43. else begin C1 <= C1 + 1'b1; LED <= 4'b0100; end 44. 45. 3: 46. if( C1 == T100MS -1) begin C1 <= 24'd0; i <= 4'd0; end 47. else begin C1 <= C1 + 1'b1; LED <= 4'b1000; end 48. 49. endcase
exp28 是纠错以后的结果 ... 代码31~39所示,第34,42,42,还有46行,笔者都添加 -1就少步骤逗留的时间。
图6.2.5 exp28 的仿真结果。
图6.2.5是exp28的仿真结果。如图6.2.5所示,光标C0~C4分别指向步骤0~3的停留时间,每个光标也有50ps的间隔时间,亦即每个步骤都停留5个时钟,因此我们可以断定 exp28是预想所要的仿真结果。为此,我么可以将 exp28的纠错手段往 exp26照搬即可。这样一来,我们就用不着使劲滚动滑鼠中间,无尽缩放wave界面又拖来拖去,然后带着轻松的心境完成仿真。
曾经何时笔者也犯过同样的错误 ... 计数器灌水过多导致仿真又长又臭,可是笔者当时也没有其它解决的手段,唯一的选择就是成为勇者硬闯而已,结果每次都成为破烂回来。如果当时有白骆驼引导笔者,想必笔者也不会选择勇者这条道路。为了避免后人重蹈笔者的后尘,笔者希望读者好好知晓 ... 超烦模块其一点也不可爱,还不如说超可怕!但是小花是例外,她虽然喜欢重复同样的问候,但她绝不会让人等候多时,也没有自恋的怪癖。
“臭小鬼,又把房间弄乱了!赶紧給老娘收拾干净,不然晚饭吃空气!”
笔者很懒,房间自然也很乱,所以才会常常惹怒母亲大人。模块内容好比房间一样,如果不努力去维护它,内容会像滚雪球那样越写越乱,直至不敢入目,类似的模块笔者称为超乱模块。超乱模块是不可仿真对象的一种,我们知道仿真的本意就将仿真对象,激励内容,还有时序结果联系起来并且做出解析。如果仿真对象(模块内容)无法解读,那么解析仿真信息也无从谈起。
超乱模块的例子有:无结构模块,还有官方插件模块。无结构模块就是自由结构的模块,Verilog是一门自由的语言,模块自由到没有结构其实一点也不奇怪。笔者年轻的时候建模很Free Style,结果模块像果冻般软绵绵地 ... 越往上建模,感觉越是给人一种“要倒了!要倒了!“ 的危机感。完后,笔者回头一看,哦买狗!乱糟糟的内容,让笔者吃不尽热狗。
“内容很乱!内容超乱”,笔者震撼道。
模块内容凌乱是后期工作的定时炸弹,它任何时候会炸飞一切,而且一瞬足以让我们的努力灰飞烟灭。为了避免悲剧发生,笔者开始Verilog的旅程,直至遇见它 ... 是它告诉笔者结构的重要性,它也说过没有结构,模块就是一只史莱姆而已,又弱又惨。为此,如何为模块注入结构——这门学问,成为了今天的建模技巧。
一般上,超乱模块的内容,除了设计者以外,其他人是没有办法读懂的 ... 作为契机,超乱模块就成为了保护商业秘密最好的手段,官方插件模块就是如此。笔者曾经认为官方老大是一位不私指教的好人,所以官方插件模块理应也是平易近人。但笔者打开内容来看的那刻瞬间,蛋蛋就立即掉到地上 .... 那是什么东西!?比笔者的房间还乱!?
图6.3.1 创建官方插件模块1Port RAM
如图6.3.1所示,假设笔者经由集成环境(Quartus II)的Mega Wizard 创建官方插件模块 1 Port RAM(作图)。模块创建完毕以后,相关的 ram.v 文件就会出现在指定的目录下(右图) ... 然后再将 ram.v打开浏览。
1. `timescale 1 ps / 1 ps 2. // synopsys translate_on 3. module ram ( address, clock, data, wren, q); 4. 5. input [7:0] address; 6. input clock; 7. input [7:0] data; 8. input wren; 9. output [7:0] q; 10. 11. `ifndef ALTERA_RESERVED_QIS 12. // synopsys translate_off 13. `endif 14. tri1 clock; 15. `ifndef ALTERA_RESERVED_QIS 16. // synopsys translate_on 17. `endif 18. 19. wire [7:0] sub_wire0; 20. wire [7:0] q = sub_wire0[7:0]; 21. 22. altsyncram altsyncram_component ( 23. .address_a (address), 24. .clock0 (clock), 25. .data_a (data), 26. .wren_a (wren), 27. .q_a (sub_wire0), 28. .aclr0 (1'b0), 29. .aclr1 (1'b0), 30. .address_b (1'b1), 31. .addressstall_a (1'b0), 32. .addressstall_b (1'b0), 33. .byteena_a (1'b1), 34. .byteena_b (1'b1), 35. .clock1 (1'b1), 36. .clocken0 (1'b1), 37. .clocken1 (1'b1), 38. .clocken2 (1'b1), 39. .clocken3 (1'b1), 40. .data_b (1'b1), 41. .eccstatus (), 42. .q_b (), 43. .rden_a (1'b1), 44. .rden_b (1'b1), 45. .wren_b (1'b0)); 46. defparam 47. altsyncram_component.clock_enable_input_a = "BYPASS", 48. altsyncram_component.clock_enable_output_a = "BYPASS", 49. altsyncram_component.intended_device_family = "Cyclone IV GX", 50. altsyncram_component.lpm_hint = "ENABLE_RUNTIME_MOD=NO", 51. altsyncram_component.lpm_type = "altsyncram", 52. altsyncram_component.numwords_a = 256, 53. altsyncram_component.operation_mode = "SINGLE_PORT", 54. altsyncram_component.outdata_aclr_a = "NONE", 55. altsyncram_component.outdata_reg_a = "UNREGISTERED", 56. altsyncram_component.power_up_uninitialized = "FALSE", 57. altsyncram_component.read_during_write_mode_port_a = "NEW_DATA_NO_NBE_READ", 58. altsyncram_component.widthad_a = 8, 59. altsyncram_component.width_a = 8, 60. altsyncram_component.width_byteena_a = 1; 61. 62. endmodule
读者看见什么吗?蛋蛋是否在发疼呢?没错,我们会看见一堆意义不明的关键字,具体意义千万别问笔者,笔者早已放弃思考了。除此之外,官方插件模块也不能随意仿真,想要仿真家伙就要给老大交钱!交钱?这句话是什么意思?就是字面上的意思 ...
图6.3.1 仿真库界面,设计库与资源库。
我们曾在第二章学过,Modelsim除了wave界面以外还有许多形形色色的界面,仿真库界面就是其中一个。如图6.3.1所示,仿真库又可以分为设计库还有资源库,设计库是仿真的临时空间(也称为工作库);反之,资源库则是存储官方插件模块所需的仿真资源。默认下,Modelsim只为我们提供标准库(Standard Library),标准库可以支持的程度也只有HDL官方指定的范畴而已。
然而,官方插件模块的内容却远远超过HDL官方所指定的范畴,用ram.v 为例的话,如:什么NEW_DATA_NO_NBE_READ?什么 altsyncram?这些关键字的背后都是不可告人的商业秘密。结果而言,Modelsim为了支持Altera官方的插件模块,Modelsim必须拥有Altera官方自定义的仿真库不可。
图.6.3.2 支持RAM的资源库。
如图6.3.2所示,支持RAM所属的资源库是altera_mf,因为 ram.v用 Verilog所创建,所以资源库选为 altera_mf_ver。笔者曾经疑问过,Altera-modelsim AE与Altera-modelsim SE之间究竟有什么区别?后来发现SE除了免费以外,SE也有行数限制,仿真库(资源库)也不如AE丰富。世界真是现实呀 ... 笔者不尽感叹道,不过,作为初学 SE已经够用了。
除此之外,还有一些琐碎的细节读者需要稍微注意,即手动编译官方插件模块相比自动编译多了一项操作。exp29是自动编译的仿真项目,基本上都是一键搞定。相比之下,exp30是手动编译的仿真项目,如果按照往常的步骤,Modelsim会狠狠警告我们“altsyncram是什么!?”,然后返回一个 error loading design错误。可恶!身为软件竟敢给老子嚣张!
* Error: (vsim-3033) .../Experiment06/exp30/ram.v(85): Instantiation of 'altsyncram' failed. The design unit was not found.
# Error loading design
为了教训嚣张的 Modelsim,我们必须告诉它什么叫做恐惧!
①Start Simulation 界面。 |
② 选择Library,为Search Libraries First 添加资源库。 |
③选择 altera_mf_ver 资源库。 |
④资源库添加完毕。 |
图6.3.3 手动添加资源库。
如图6.3.3所示,这是手动将资源库添加至设计库的过程。首先按照步骤① 打开Start Simulation 窗口;接着按照② 选择Libraries,然后为点击 Search Libraries First 的 Add按钮添加资源库;随之会弹出如3所示 Select Library的小窗口,按↓搜索 altera_mf_ver 资源库然后点击OK生效;完后,altera_mf_ver资源库会添加在 Search Libraries First的列表下。
图6.3.4 手动添加资源库。
再者也可利用 Design,选择 exp30_simulation,点击OK便启动仿真,接续的操作和往常一样。接下来,让我们悄悄 exp30究竟拥有什么激励内容?
1. `timescale 1 ps/ 1 ps 2. module exp30_simulation(); 3. 4. reg CLOCK; 5. reg RESET; 6. reg RAM_WrEn; 7. reg [7:0] RAM_Addr,RAM_WrData; 8. wire [7:0]RAM_RdData; 9. 10. /***********************************/ 11. 12. initial 13. begin 14. RESET = 0; #10; RESET = 1; 15. CLOCK = 1; forever #5 CLOCK = ~CLOCK; 16. end 17. 18. /***********************************/ 19. 20. ram U1 21. ( 22. .address ( RAM_Addr ), 23. .clock ( CLOCK ), 24. .data ( RAM_WrData ), 25. .wren ( RAM_WrEn ), 26. .q ( RAM_RdData ) 27. ); 28. 29. reg [3:0]i; 30. 31. always @ ( posedge CLOCK or negedge RESET ) 32. if( !RESET ) 33. begin 34. i <= 4'd0; 35. RAM_WrEn <= 1'b0; 36. RAM_Addr <= 8'd0; 37. RAM_WrData <= 8'd0; 38. end 39. else 40. case( i ) 41. 42. 0: 43. begin RAM_WrEn <= 1'b1; RAM_Addr <= 8'd0; RAM_WrData <= 8'hAA; i <= i + 1'b1; end 44. 45. 1: 46. begin RAM_WrEn <= 1'b0; i <= i + 1'b1; end 47. 48. 2: 49. i <= i; 50. 51. endcase 52. 53. /***********************************/ 54. 55. endmodule
exp30代码行第20~27行是仿真对象的实例化,其中第31~51行是虚拟输入。虚拟输入在步骤0为RAM_WrEn赋值1,RAM_Addr赋值 8’d0,RAM_WrData赋值 8’hAA,然后i递增以示下一个步骤。步骤1仅为 RAM_WrEn 清零,接着将i递增以示下一个步骤。
图6.3.5 exp30仿真结果。
如图6.3.5所示,C0指向虚拟输入的步骤0,C1指向虚拟输入的步骤1 ... 期间,虚拟输入再T0的时候,RAM_Addr输出未来值 8’d0,RAM_WrData则输出未来值 8’haa,RAM_WrEn输出未来值1’b1。下一刻,亦即T1的时候,仿真对象检测RAM_WrEn的过去值为1,并且决定读取 RAM_WrData的过去值8’hAA至地址8’d0,然后再经由 RAM_RdData输出未来值 8’haa。
根据仿真结果显示,RAM_WrEn充当写入数据的锁匙,拉高有效。那么问题来了!为什么仿真对象好死不死,写入数据必须拉高 RAM_WrEn才行呢?我们知道仿真对象是官方插件模块,内容必须隐藏不可。如此一来,解析仿真信息工作会因为仿真对象无法解读而发生中断,因为我们无法理解,为何写入数据不得不拉高 RAM_WrEn?最终仿真也无法继续下去。这种感觉好比大号上到一半,忽然有人按铃,实在令人讨厌无比!
我们知道官方插件模块是官方的产品,也是商业秘密 ... 官方没有理由公开内容,但是官方又不得不公开,官方别扭许久后,最后想到一个好办法!作为弥补手段,官方为用户创建厚厚的鸟文手册供参考。即使内容看不懂,我们也不能追究官方的责任 ... 说实话,这招四两拨千斤实在太厉害了!
图6.3.6 官方提供的 Single port RAM图形。
图6.3.6是官方为我们提供的 Single port RAM 图形,话虽如此 ... 实际上除了data,address,wren,inclock,outclock,还有q信号以外,基本上笔者都不认识,想必同学也一定会看到抓狂。
图6.3.7 官方提供的出入端说明(部分载图)。
当然,用心的官方也为我们提供详细的出入端说明,结果如图6.3.7所示。由于内容实在太多,笔者只是部分载图而已,如 data 信号,它是输入端,选择性,送入数据的信号;address信号,它是输入端,固定性,送入地址的型号;byteena信号,输入端,选择性,用来遮盖部分写数据位。官方所提供的输入端说明虽然详细,但是详细不代表清晰,也不一定具体,这种感觉好比走马看花似的,内容都是模模糊糊,尽是让人焦急又觉得心烦。
图6.3.8 官方提供的时序图(部分载图)。
除此之外,用心的官方担心我们都不懂应用,也会为我们提供时序结果。如图6.3.8所示,那是部分时序结果(由于内容实在太多笔者也懒惰一一载图了),大致的意思是指某某信号有什么用途又该怎么使用 ... 想必有些同学一定会看到满头雾水,不知道它在讲什么,结果愈看脑袋愈要爆炸般发疼。
在某种程度上来说,官方的确已经尽心尽力为用户提供这个,提供那个,然而最关键的内容始终不见踪影。这种感觉好比给钱要狗狗自己去买食物般实在是太趣味了!聪明的狗狗说不定会自行拿钱买好料吃 ... 换之,如果是刚出生不久的狗仔,基本上都不知道什么是钱?结果又怎样晓得自行拿钱买吃呢?搞不定会错当把钱当成食物吃掉 ...
笔者曾是狗仔,现在也是狗仔!不管怎么样,这个问题确实困扰笔者许久,笔者认为仿真对象左右了仿真效果,如果仿真对象无法解读效果也会大打折扣!不过,官方竟也不是狼心狗肺,铁石心肠,经过多次别扭以后,虽然它们不能透露确切的内容,取而代之它们却公开另外的等价内容。
1. module single_port_ram
2. (
3. input [7:0] data,
4. input [7:0] addr,
5. input we, clk,
6. output [7:0] q
7. );
8. reg [7:0] ram[255:0];
9. reg [7:0] addr_reg;
10.
11. always @ (posedge clk)
12. begin
13. if (we)
14. ram[addr] <= data;
15. addr_reg <= addr;
16. end
17.
18. assign q = ram[addr_reg];
19.
20. endmodule
代码6.3.1
如代码6.3.1所示,一眼望去就知道这是官方所提供的等价内容,而且还是精简版。事实上,这是官方最大的让步,我们做人不应该得寸进尺,应该学会见好就收。第3~6行是相关的出入端声明;第8行声明位宽为8深度为256的RAM,第9行则是用来寄存地址的寄存器 addr_reg;其中第13~14行才是关键,if(we) ram[addr] <= data这段代码表示,将数据写入ram必须拉高 we不可。
图6.3.5的仿真对象写入数据之所以不得不拉高 RAM_WrEn,原来是这行代码在作怪,原来如此,原来这样,这样一来所有谜题都豁然开来,真相大白!但是仔细一想,我们为了仿真这样一个简单的RAM模块,必须大费周章浪解读手册,又分析时序,又寻找等价内容 ... 经过一番波折以后,结果却换来一只小小的成果而已。一心爱着节能的笔者,实在是无法接受这份事实!因为官方插件模块妨碍我们来不及讴歌青春 ... 结果它必须死,必须成为不可仿真对象。
不管太阳再怎么毒辣,即使资料再怎么难消化,只要一杯特凉的豆浆送入口中,笔者也能取回冷静的心境。喝完一杯豆浆以后,笔者望着天花板开始思考,是谁规定模块非仿真不可?然而模块为什么又要仿真呢?思考之际,笔者随手翻看参考书,然后遇见这样一张流程图(Concept Flow Chart)。
图6.4.1 仿真流程图。
如图6.4.1所示,左图是参考书的流程图,那是非常正规的仿真流程,即建模失败继续返回建模;建模成功推向仿真;仿真失败就返回建模,或者再次仿真;仿真成功则往下继续推。左图表面上总是给人一种绝对有理的感觉,我们很难找到任何反驳的理由,结果不得不俯首称臣,屈服与它。但是反过的话,左图好似命令我们这样干那样干而已,却从来不说理由,其中成功的定义是什么?失败的定义又是什么?
小小的气愤之后,笔者随笔画了图6.4.1的右图。首先,建模还有仿真必须是同一个等级,示意建模还有仿真都有相同的根却有不同的概念。紧接着,有问题的双箭头示意仿真是用来测试有问题的模块,反之没有问题的模块则不用仿真 ... 想到这里,笔者开始停顿了一下。
“有问题的模块 ... 没有问题的模块 ...”,笔者嘟囔道。
“官方插件模块有问题吗?”,笔者继续嘟囔道。
没错,官方不可能发布有问题的插件模块,官方插件模块既然没有问题,我们为什么又要仿真呢?难道嫌青春太寂寞了吗?但是,官方插件模块最大的问题就是很难驾驭,虽然手册有详细的图形,出入端说明,还有时序结果,但是对于初学者而言,那些内容有如天书般实在很难理解。除非给钱,不然官方根本没有义务一一为我们解释清楚。
除此之外,从自动思想的角度而言,没有问题的模块就是一只完整的个体,但问题是官方插件模块是没有明显的特征,因为内容被隐藏起来,结果我们无法留下深刻的印象。在此我们有几个解决手段:
(一)解读手册;
(二)寻找替代;
虽说,驾驭官方插件模块最好的方法就是解读手册,然而关键是如何看懂时序图。为了实现这一点,除了提升基础能力以外,就是不停实验还有不停测试而已,笔者实在想不到其它好办法。官方插件模块虽然好用,只要我们习得驾驭的窍门,它们便会成为便利的家伙,对于那些喜欢快餐的朋友更是臭味相投。
这个世界上有两种懒人,一种是越懒越进步的人,另一种是越懒越退步的人。笔者作为一个懒人,虽然喜欢节能,但却不不怎么喜欢便利,因为贪图便利会让人退步。我们知道快餐虽然便利,但是吃久了就会伤害身体,同样的道理也适用建模。有些人会因为过度依赖官方插件模块,结果不仅失去建模能力,逐渐也会放弃思考,最后沦落为一只笨重又痴肥的懒家伙。因此,相较解读手册这个解决手段,笔者比较倾向寻找替代这个解决手段。
1. module single_port_ram
2. (
3. input [7:0] data,
4. input [7:0] addr,
5. input we, clk,
6. output [7:0] q
7. );
8. reg [7:0] ram[255:0];
9. reg [7:0] addr_reg;
10.
11. always @ (posedge clk)
12. begin
13. if (we)
14. ram[addr] <= data;
15. addr_reg <= addr;
16. end
17.
18. assign q = ram[addr_reg];
19.
20. endmodule
代码6.4.1
代码6.4.1是官方为我们提供的等价内容,我们可以基于代码6.4.1进一步修改让它,最终会成为更加直观更实用的模块。
1. module ram 2. ( 3. input CLOCK,RESET, 4. input RAM_WrEn, 5. input [7:0] RAM_Addr, 6. input [7:0] RAM_WrData, 7. output [7:0] RAM_RdData 8. ); 9. reg [7:0] ram[255:0]; 10. reg [7:0] rData; 11. 12. always @ (posedge CLOCK or negedge RESET) 13. if( !RESET ) 14. rData <= 8’d0; 15. else if( RAM_WrEn ) 16. ram[ RAM_Addr ] <= RAM_WrData; 17. else 18. rData <= ram[ RAM_Addr ]; 19. 20. assign RAM_RdData = rData; 21. 22. endmodule
代码6.4.2
代码6.4.2是笔者简单修改以后的RAM模块,第3~7行是出入段的声明,基本上和代码6.4.1差不多,只是命名方法比较强调个体存在,为了是进一步加深个体印象。第10行是rData寄存器是用来暂存读取结果。至于第12~20行却有很大的改变,代码6.4.1原本使用 rAddr寄存器暂存地址数据,然后再用 ram 驱动 q 输出端。
相较之下,代码6.4.2不但没有暂存地址数据,而是直接应用地址数据选择ram的储存范围(第16行)。此外,代码6.4.2则使用 rData寄存器暂存 ram的读出结果(第18行),再用rData寄存器驱动 RAM_RdData 输出端(第20行)。在此,有些同学可能不明白代码6.4.2的修改意义,其实代码6.4.2这样做是为了避免“写时读”的问题。有关写时读的详细内容,读者自己参考手册吧,关键字是 Read During Write。
接着,请打开 exp31 ... exp31相比exp29~30,最大的差别就是exp31使用了自建的ram,然而exp29~30则是使用官方插件模块。
1. `timescale 1 ps/ 1 ps 2. module exp31_simulation(); 3. 4. reg CLOCK; 5. reg RESET; 6. reg RAM_WrEn; 7. reg [7:0] RAM_Addr,RAM_WrData; 8. wire [7:0]RAM_RdData; 9. 10. /***********************************/ 11. 12. initial 13. begin 14. RESET = 0; #10; RESET = 1; 15. CLOCK = 1; forever #5 CLOCK = ~CLOCK; 16. end 17. 18. /***********************************/ 19. 20. ram U1 21. ( 22. .RAM_Addr ( RAM_Addr ), 23. .CLOCK ( CLOCK ), 24. .RESET( RESET ), 25. .RAM_WrData( RAM_WrData ), 26. .RAM_WrEn ( RAM_WrEn ), 27. .RAM_RdData ( RAM_RdData ) 28. ); 29. 30. /***************************************/ 31. 32. reg [3:0]i; 33. 34. always @ ( posedge CLOCK or negedge RESET ) 35. if( !RESET ) 36. begin 37. i <= 4'd0; 38. RAM_WrEn <= 1'b0; 39. RAM_Addr <= 8'd0; 40. RAM_WrData <= 8'd0; 41. end 42. else 43. case( i ) 44. 45. 0: 46. begin RAM_WrEn <= 1'b1; RAM_Addr <= 8'd0; RAM_WrData <= 8'hAA; i <= i + 1'b1; end 47. 48. 1: 49. begin RAM_WrEn <= 1'b1; RAM_Addr <= 8'd1; RAM_WrData <= 8'hBB; i <= i + 1'b1; end 50. 51. 2: 52. begin RAM_WrEn <= 1'b1; RAM_Addr <= 8'd2; RAM_WrData <= 8'hCC; i <= i + 1'b1; end 53. 54. 3: 55. begin RAM_WrEn <= 1'b0; RAM_Addr <= 8'd0; i <= i + 1'b1; end 56. 57. 4: 58. begin RAM_WrEn <= 1'b0; RAM_Addr <= 8'd1; i <= i + 1'b1; end 59. 60. 5: 61. begin RAM_WrEn <= 1'b0; RAM_Addr <= 8'd2; i <= i + 1'b1; end 62. 63. 6: 64. i <= i; 65. 66. endcase 67. 68. /***********************************/ 69. 70. endmodule
如代码 exp31_simulation所示,第4~8行是仿真所需的模拟寄存器与连线;第12~16行则是产生环境输入;第20~28行是自建ram的仿真对象;第34~66行则是虚拟输入。步骤0~2分别为 ram 写入:地址0数据8’hAA,地址1数据8’hBB,地址2数据8’hCC。步骤3~5分别则是从 ram 哪里读取地址0~2的数据。
图6.4.2 exp31的仿真结果。
图6.4.2是exp31的仿真结果,其中光标C0~C6分别指向虚拟输入的步骤0~6也是T0~T6。
虚拟输入在T0~T6之间的操作:
T0,为仿真对象发送相关的未来值,WrEn为1,Addr为0,WrData为 8’hAA;
T1,为仿真对象发送相关的未来值,WrEn为1,Addr为1,WrData为 8’hBB;
T2,为仿真对象发送相关的未来值,WrEn为1,Addr为2,WrData为 8’hCC;
T3,为仿真对象发送相关的未来值,WrEn为0,Addr为0;
T4,为仿真对象发送相关的未来值,WrEn为0,Addr为1;
T5,为仿真对象发送相关的未来值,WrEn为0,Addr为2;
T6,无所事事。
仿真对象在T0~T6之间的操作:
T0,无所事事。
T1,读取相关的过去值,WeEn为1以示写操作,将数据8’hAA,存入地址0;
T2,读取相关的过去值,WeEn为1以示写操作,将数据8’hBB,存入地址1;
T3,读取相关的过去值,WeEn为1以示写操作,将数据8’hCC,存入地址2;
T4,WrEn过去值为0以示读操作,将地址过去值0指向的数据8’hAA,输出为未来值;
T5,WrEn过去值为0以示读操作,将地址过去值1指向的数据8’hBB,输出为未来值;
T6,WrEn过去值为0以示读操作,将地址过去值2指向的数据8’hCC,输出为未来值;
上述的内容表示了:
exp31自建ram的功能,相较官方插件模块ram,两者之间没有什么差别。但是自建ram有明显的优势,亦即内容不仅公开而且也清晰,解析仿真信息没有中断的问题,因为仿真对象如何按照刺激产生反应都一一表明。这种感觉好比顺利上完大号般实在爽快无比,期间也忍不住想唱快乐天堂:“告诉你一个神秘的地方~♬”。
站在主动思想的角度而言,公开内容表示个体炫耀特征,再加上清晰的内容,个体无疑强化了自己的存在感 ... 强调自生对个体模块来说,没有什么事情比这个更重要的。此外,我们也用不着考虑(仿真)资源库的问题。
图6.4.3 自定义官方插件模块。
除了这些琐碎的事情以外,官方插件模块还有一个问题,亦即自定义的自由度不大。如图6.4.3所示,我么可以透过集成环境自定义官方插件模块,如输出端q启用寄存器,或者启用rden读使能等 ... 然而,官方插件模块愈是启用更多复选功能,官方插件模块愈是越难驾驭,特征还有存在感也会愈来愈模糊。官方插件模块虽然可以自定义,但是自定义的自由度也是有限的,不能超过官方指定的范围。
举例笔者是完全的新手,笔者觉得 ram模块的时序很难控制,为此笔者需要自定义ram,为它加入问答信号,让它变得更加容易控制。
1. module ram 2. ( 3. input CLOCK,RESET, 4. input RAM_WrEn, 5. input [7:0] RAM_Addr, 6. input [7:0] RAM_WrData, 7. input RAM_RdEn, 8. output [7:0] RAM_RdData, 9. output RAM_DnSig 10. ); 11. reg [7:0] ram[255:0]; 12. reg [7:0] rData; 13. reg [3:0]i; 14. reg isDone; 15. 16. always @ (posedge CLOCK or negedge RESET) 17. if( !RESET ) 18. begin 19. rData <= 8'd0; 20. i <= 2'd0; 21. isDone <= 1'b0; 22. end 23. else if( RAM_WrEn ) 24. case( i ) 25. 26. 0: 27. begin ram[ RAM_Addr ] <= RAM_WrData; i <= i + 1'b1; end 28. 29. 1: 30. begin isDone <= 1'b1; i <= i + 1'b1; end 31. 32. 2: 33. begin isDone <= 1'b0; i <= 2'd0; end 34. 35. endcase 36. else if( RAM_RdEn ) 37. case( i ) 38. 39. 0: 40. begin rData <= ram[ RAM_Addr ]; i <= i + 1'b1; end 41. 42. 1: 43. begin isDone <= 1'b1; i <= i + 1'b1; end 44. 45. 2: 46. begin isDone <= 1'b0; i <= 2'd0; end 47. 48. endcase 49. 50. assign RAM_RdData = rData; 51. assign RAM_DnSig = isDone; 52. 53. endmodule
代码6.4.3
代码6.4.3是加入问答信号的ram模块,第3~9行是相关的出入端声明,其中多添加了RAM_RdEn与RAM_DnSig;第11~14行是相关的寄存器声明,i用来指向步骤,isDone用来反馈完成信号;第23~35行是ram的写操作,RAM_WrEn作为使能信号;第36~48行则是ram的读操作,RAM_RdEn作为使能信号。第51的RAM_DnSig输出端则用 isDone寄存器来驱动。
1. `timescale 1 ps/ 1 ps 2. module exp32_simulation(); 3. 4. reg CLOCK; 5. reg RESET; 6. reg RAM_WrEn; 7. reg [7:0] RAM_Addr,RAM_WrData; 8. reg RAM_RdEn; 9. wire [7:0]RAM_RdData; 10. wire RAM_DnSig; 11. 12. /***********************************/ 13. 14. initial 15. begin 16. RESET = 0; #10; RESET = 1; 17. CLOCK = 1; forever #5 CLOCK = ~CLOCK; 18. end 19. 20. /***********************************/ 21. 22. ram U1 23. ( 24. .CLOCK ( CLOCK ), 25. .RESET( RESET ), 26. .RAM_WrEn ( RAM_WrEn ), 27. .RAM_Addr ( RAM_Addr ), 28. .RAM_WrData( RAM_WrData ), 29. .RAM_RdEn ( RAM_RdEn ), 30. .RAM_RdData ( RAM_RdData ), 31. .RAM_DnSig( RAM_DnSig ) 32. ); 33. 34. /***************************************/ 35. 36. reg [3:0]i; 37. 38. always @ ( posedge CLOCK or negedge RESET ) 39. if( !RESET ) 40. begin 41. i <= 4'd0; 42. RAM_WrEn <= 1'b0; 43. RAM_RdEn <= 1'b0; 44. RAM_Addr <= 8'd0; 45. RAM_WrData <= 8'd0; 46. end 47. else 48. case( i ) 49. 50. 0: 51. if( RAM_DnSig ) begin RAM_WrEn <= 1'b0; i <= i + 1'b1; end 52. else begin RAM_WrEn <= 1'b1; RAM_Addr <= 8'd0; RAM_WrData <= 8'hAA; end 53. 54. 1: 55. if( RAM_DnSig ) begin RAM_WrEn <= 1'b0; i <= i + 1'b1; end 56. else begin RAM_WrEn <= 1'b1; RAM_Addr <= 8'd1; RAM_WrData <= 8'hBB; end 57. 58. 2: 59. if( RAM_DnSig ) begin RAM_WrEn <= 1'b0; i <= i + 1'b1; end 60. else begin RAM_WrEn <= 1'b1; RAM_Addr <= 8'd2; RAM_WrData <= 8'hCC; end 61. 62. 3: 63. if( RAM_DnSig ) begin RAM_RdEn <= 1'b0; i <= i + 1'b1; end 64. else begin RAM_RdEn <= 1'b1; RAM_Addr <= 8'd0; end 65. 66. 4: 67. if( RAM_DnSig ) begin RAM_RdEn <= 1'b0; i <= i + 1'b1; end 68. else begin RAM_RdEn <= 1'b1; RAM_Addr <= 8'd1; end 69. 70. 5: 71. if( RAM_DnSig ) begin RAM_RdEn <= 1'b0; i <= i + 1'b1; end 72. else begin RAM_RdEn <= 1'b1; RAM_Addr <= 8'd2; end 73. 74. 6: 75. i <= i; 76. 77. endcase 78. 79. /***********************************/ 80. 81. endmodule
exp32基本上与exp31差不了多少,除了第8行RAM_RdEn寄存器,还有第10行RAM_DnSig连线。第38~77行是虚拟输入,由于仿真对象应用了问答信号,所以控制仿真对象也会变得非常简单。步骤0~2接续为仿真对象写入数据8’hAA,8’hBB,8’hCC在地址0~2。步骤3~5分别接续从仿真对象的地址0~2读出数据8’hAA,8’hBB,8’hCC。
图6.4.4 exp32的仿真结果。
图6.4.4是exp32的仿真结果,光标C0~C5分别指向3个写操作3个读操作的完成信号。
期间:
C0指向的地方是虚拟输入为仿真对象写入数据8’hAA在地址0;
C1指向的地方是虚拟输入为仿真对象写入数据8’hBB在地址1;
C2指向的地方是虚拟输入为仿真对象写入数据8’hCC在地址2;
C3指向的地方是虚拟输入从仿真对象的地址0读出数据8’hAA;
C4指向的地方是虚拟输入从仿真对象的地址1读出数据8’hBB;
C5指向的地方是虚拟输入从仿真对象的地址2读出数据8’hCC;
无可否认,仿真对象应用问答信号以后变得更加容易控制,期间我们用不着考虑时序的是否精密,不过作为代价,exp32用了更长的时间进行读写操作。同学可能会好奇道,官方插件模块是否也能这样,应用问答信号执行呢?答案虽然是可以,但是过程比较猥琐。我们知道官方插件信号不能直接修改内容,除非经过集成环境 ... 为此,首先我们需要在顶层模块的身上将官方插件模块实例化,然后才能自定义。
1. module ram_top 2. ( 3. input CLOCK,RESET, 4. input RAM_WrEn, 5. input [7:0] RAM_Addr, 6. input [7:0] RAM_WrData, 7. input RAM_RdEn, 8. output [7:0] RAM_RdData, 9. output RAM_DnSig 10. ); 11. wire [7:0] wRAM_RdData; 12. ram U1 13. ( 14. .address ( RAM_Addr ), 15. .clock ( CLOCK ), 16. .data ( RAM_WrData ), 17. .wren ( RAM_WrEn ), 18. .q ( RAM_RdData ) 19. ); 20. 21. reg [3:0]i; 22. reg isDone; 23. 24. always @ (posedge CLOCK or negedge RESET) 25. if( !RESET ) 26. begin 27. i <= 2'd0; 28. isDone <= 1'b0; 29. end 30. else if( RAM_WrEn ) 31. case( i ) 32. 33. 0: 34. begin isDone <= 1'b1; i <= i + 1'b1; end 35. 36. 1: 37. begin isDone <= 1'b0; i <= 2'd0; end 38. 39. endcase 40. else if( RAM_RdEn ) 41. case( i ) 42. 43. 0: 44. begin isDone <= 1'b1; i <= i + 1'b1; end 45. 46. 1: 47. begin isDone <= 1'b0; i <= 2'd0; end 48. 49. endcase 50. 51. assign RAM_DnSig = isDone; 52. 53. endmodule
代码6.4.4
大致的感觉入代码6.4.4所示。笔者现在顶层模块实例化ram模块(第11~19行),然后再加入问答机制(第30~49行)。代码6.4.4相比代码6.4.3,读写操作都少了一个步骤,但却发挥一样的功能。仔细比较一下代码6.4.3还有代码6.4.4,我们会发现代码6.4.3的自定义会比较直接也比较省力。
最后,笔者可以这样总结道:
超乱模块是不可仿真对象之一,因为仿真对象解读不可,结果阻碍了仿真对象,激励内容,还有时序结果之间的联系,仿真信息的解析工作随之也糟中断。超乱模块的典型例子有:失去结构的模块,还有官方插件模块 ... 前者可以注入结构解决问题,后者则是比较麻烦一点。由于官方插件模块是隐藏实际内容,而且模块内容也有许多意义不明的关键字,为了执行仿真,我们必须做好以下准备:
(一)加入资源库;
(二)解读手册,寻找等价内容,然后再摸索大概的模块内容;
如果能找到等价内容,那当然是最好的事情 ... 我们可以基于等价内容再自行创建同样功能的模块用来替代官方插件模块,否则我们必须硬啃手册,进一步摸索隐藏内容才行。
玩过RPG游戏的同学一定知晓,勇者基本上都不能可能一次通过BOSS战役,除非那是臭游戏,没有做好平衡微调。勇者必须用死亡换取BOSS的信息,经过数次累积以后才能完全攻略BOSS。游戏中,勇者可以无限重生,但是反比玩家,游戏时间越长精神就会越差 ...
同样的事情也发生在仿真的身上,BOSS战役好比仿真,我们好比玩家,期间仿真会经历无数次失败,然后有用的信息就会累积越多,直至仿真成功。仿真虽然可以无数次重复,但是我们的精力,时间,还有忍耐力是有限的,仿真时间越是长久,我们越是疲倦,仿真效率越是低下 ... 如果这种轮回不停重复,最终会成为噩梦般的恶性循环。这种依靠死亡换来的成功,笔者称为被动设计。
“被动”意指,我们终被仿真牵着鼻子走,而不是我们引导仿真前进。传统流派也好,常规也罢,基本上都是采用被动设计的仿真手段 ... 从小到大,笔者都是一幅弱身子,笔者实在无法忍受仿真的折腾,笔者每经一场战役,笔者越是感觉生命仿佛短了几年。某天,笔者忽然察觉不妙,如果总是被仿真拖着鼻子走下去,笔者总有一天一定会成为人干。为此,笔者开始思考如何夺取仿真的主导权。
期间,笔者反问自己,为何总是被仿真牵着鼻子走呢?首先,我们知道Modelsim是用来播放可视化时序信息 ... 换句话说,失去仿真我们好比失去一对窥视时序的眼睛,变成时序盲子。不管如何,仿真始终都是他人的眼睛,我们必须借用别人的眼睛走路,状况极为被动。笔者随之又思考道,我们又不是没长眼睛,只是眼睛看不清楚时序而已?
“为什么,我们的眼睛看不清楚时序?无论是肉眼还是心眼 ... ”笔者感叹道。
事实上,我们的眼睛不是看不清楚时序,而是时序本身被一层模糊烟雾遮盖罢了,为了看清时序,我们必须将其去除以致明朗化。为此,笔者发现“提高模块的表达能力”还有“清晰模块内容”是取回主导权的关键。不过,笔者感觉只有这些还是物所不足,
笔者需要一个思想,一个可以连贯一切的思想,那就是主动思想。
主动思想的副作用是个体拥有清晰的内容,我们用不着仿真也能脑补个体的时序图。然而,主动思想的主作用是,用清晰的个体内容留下强烈的局部印象,然后深深映在想象当中。无数局部印象不停填充以后,最终整体的时序图会脑补完成。结果而言,想象力便成为最便捷的仿真工具,我们可以一边建模一边在脑补时序。
这种想象的仿真工具虽然没有形体但它至少是我们的眼睛,让我们摆脱被动 ... 此外,想象力也是自由的,只要脑补能力足够强大(想象力丰富)这个工具基本上是无所不能,因此它才是最强的仿真工具。不过作为前提条件,我们必须运用技巧将信息,深刻地映入脑海当中。这种应用主动思想的设计技巧,笔者称为主动设计。
接下来,让笔者用简单的例子来演示主动设计的优劣点。此外,读者也可以当成是最后综合复习。
图6.5.1 exp33建模图。
图6.5.1 是 exp33的建模图,其中包裹 ram模块,控制模块,还有spi功能模块。ram模块用来储存信息,spi功能模块用来发送串行数据,控制模块则是两者之间的协调。exp33用意很简单,控制模块先从ram模块读取数据,然后再经由spi功能模块发送出去。接下来,让我们来瞧瞧它们的具体内容。
1. module ram 2. ( 3. input CLOCK,RESET, 4. input RAM_WrEn, 5. input [7:0] RAM_Addr, 6. input [7:0] RAM_WrData, 7. output [7:0] RAM_RdData 8. ); 9. reg [7:0] ram[255:0]; 10. reg [7:0] rData; 11. 12. initial begin 13. ram[1] = 8'b1100_0011; 14. ram[2] = 8'b1000_0001; 15. end 16. 17. always @ (posedge CLOCK or negedge RESET) 18. if( !RESET ) 19. rData <= 8'd0; 20. else if( RAM_WrEn ) 21. ram[ RAM_Addr ] <= RAM_WrData; 22. else 23. rData <= ram[ RAM_Addr ]; 24. 25. assign RAM_RdData = rData; 26. 27. endmodule
上述代码是ram模块,笔者直接向exp31借用,然而第12~14则是初始化ram的写法之一,为了后续的仿真作准备。除此之外我们也可以用for来初始化ram。
reg [9:0]x;
for( x = 0; x < 256; x = x + 1’b1 )
ram[x] <= x;
图6.5.2 SPI的写操作时序(主机)。
SPI传输协议是应用较为普遍的传输协议之一,SPI传输协议一般有分主机与从机,主机是控制SCL的一方,反之亦然。SPI都是SCL下降设置数据(发送数据),SCL上升沿锁存数据(读取数据),不管写操作还是读操作都是从数据的最高位开始。如图6.5.2所示,这是从主机视角看去的写操作,其中发送写数据是 8’b11001100,因此SDO在T0~T1拉高,T2~T3拉低,T4~T5拉高,T6~T7拉低。
在这之前笔者先假定 spi_funcmod使用50Mhz的时钟源,然而SCL的频率是1Mhz,因此:
(1/1Mhz) / (1/50Mhz) = 50
50Mhz时钟源计数50次等于1个SCL时钟,半周期是25。不过,笔者认为计数25下的水分太多,为此笔者将它不等例缩放为5。接下来,让我们悄悄 spi_funcmod具体的内容:
1. module spi_funcmod 2. ( 3. input CLOCK,RESET, 4. input SPI_StartSig, 5. output SPI_DoneSig, 6. input [7:0] SPI_WrData, 7. output SCL,SDO 8. ); 9. parameter T1US = 8'd5; // (1/1Mhz) / (1/50Mhz) = 50; 10. 11. reg [4:0]i; 12. reg [7:0]C1; 13. reg rSDO,rSCL; 14. reg isDone; 15. 16. always @ (posedge CLOCK or negedge RESET) 17. if( !RESET ) 18. begin 19. i <= 5'd0; 20. C1 <= 8'd0; 21. {rSDO, rSCL} <= 2'b01; 22. isDone <= 1'b0; 23. end 24. else if( SPI_StartSig ) 25. case( i ) 26. 27. 0,2,4,6,8,10,12,14: 28. if( C1 == T1US -1) begin C1 <= 8'd0; i <= i + 1'b1; end 29. else begin C1 <= C1 + 1'b1; rSCL <= 1'b0; rSDO <= SPI_WrData[7-(i>>1)]; end 30. 31. 1,3,5,7,9,11,13,15: 32. if( C1 == T1US -1) begin C1 <= 8'd0; i <= i + 1'b1; end 33. else begin C1 <= C1 + 1'b1; rSCL <= 1'b1; end 34. 35. 16: 36. begin isDone <= 1'b1; i <= i + 1'b1; end 37. 38. 17: 39. begin isDone <= 1'b0; i <= 5'd0; end 40. 41. endcase 42. 43. assign SCL = rSCL; 44. assign SDO = rSDO; 45. assign SPI_DoneSig = isDone; 46. 47. endmodule
第3~7行是相关的出入端声明;第9行是T1US的常量定义,但是笔者将它不等例缩放为5;第11~14是相关的寄存器声明;第16~41行是核心操作,SPI传输协议默认下SCL时钟都是拉高,因此第21的复位操作 rSCL为1。第24行表示该操作必须使能 SPI_StartSig 不可。第27~29是用来拉低SCL和设置数据(输出数据)的步骤,占有8个步骤。
第29行的SPI_WrData[7-(i>>1)]表示输出数据从最高位开始。第31~33行是用来拉高SCL而已,也是占有8个步骤。步骤16~17则是用来反馈完成信号。之余第43~45行则是相关的输出驱动。笔者之前也说过,本实验是采用主动设计,主动设计是非常强调个体,而且内容的清晰程度足以让我们脑补时序,甚至留下深刻印象。一般上,笔者为了节能是不做仿真的,不过为了讲明主动设计,我们还是悄作仿真吧。
1. `timescale 1 ps/ 1 ps 2. module spi_funcmod_simulation(); 3. 4. reg CLOCK; 5. reg RESET; 6. reg SPI_StartSig; 7. reg [7:0]SPI_WrData; 8. wire SPI_DoneSig; 9. wire SCL,SDO; 10. 11. /***********************************/ 12. 13. initial 14. begin 15. RESET = 0; #10; RESET = 1; 16. CLOCK = 1; forever #5 CLOCK = ~CLOCK; 17. end 18. 19. /***********************************/ 20. 21. spi_funcmod U1 22. ( 23. .CLOCK ( CLOCK ), 24. .RESET( RESET ), 25. .SPI_StartSig( SPI_StartSig ), 26. .SPI_DoneSig( SPI_DoneSig ), 27. .SPI_WrData( SPI_WrData ), 28. .SCL ( SCL ), 29. .SDO ( SDO ) 30. ); 31. 32. /***************************************/ 33. 34. reg [3:0]i; 35. 36. always @ ( posedge CLOCK or negedge RESET ) 37. if( !RESET ) 38. begin 39. i <= 4'd0; 40. SPI_StartSig <= 1'b0; 41. SPI_WrData <= 8'd0; 42. end 43. else 44. case( i ) 45. 46. 0: 47. if( SPI_DoneSig ) begin SPI_StartSig <= 1'b0; i <= i + 1'b1; end 48. else begin SPI_StartSig <= 1'b1; SPI_WrData <= 8'b1001_1001; end 49. 50. 1: 51. i <= i; 52. 53. endcase 54. 55. /***********************************/ 56. 57. endmodule
spi_funcmod.vt 是 spi_funcmod.v 的个体仿真环境,也是所谓的局部仿真。因为是不等例缩放的缘故,笔者用不着再现真实的时钟周期。第21~30行是仿真对象的实例化,第36~53行是虚拟输入的激励内容,其中步骤0是为仿真对象写入数据8’b1001_1001。
图6.5.3 spi_funcmod 的仿真结果。
图6.5.3是 spi_funcmod的仿真结果,其中光标C0~C7分别指向SCL的8次下降沿。
C0之际,SD0输出未来值1;
C1之际,SD0输出未来值0;
C2之际,SD0输出未来值0;
C3之际,SD0输出未来值1;
C4之际,SD0输出未来值1;
C5之际,SD0输出未来值0;
C6之际,SD0输出未来值0;
C7之际,SD0输出未来值1;
由于仿真对象应用了问答沟通,结果仿真对象变得容易控制了。C8指向的地方是仿真对象完成一次性的操作,并且返回完成信号以示一次性的写操作已经完成。
1. module ctrlmod 2. ( 3. input CLOCK,RESET, 4. 5. output [7:0]RAM_Addr, 6. input [7:0]RAM_RdData, 7. 8. output SPI_StartSig, 9. input SPI_DoneSig, 10. output [7:0] SPI_WrData 11. ); 12. reg [3:0]i; 13. reg [7:0]rAddr,rData; 14. reg isSPI; 15. 16. always @ (posedge CLOCK or negedge RESET) 17. if( !RESET ) 18. begin 19. i <= 4'd0; 20. { rAddr, rData } <= { 8'd0,8'd0 }; 21. isSPI <= 1'b0; 22. end 23. else 24. case( i ) 25. 26. 0,1: 27. begin rAddr <= 8'd1; i <= i + 1'b1; end 28. 29. 2: 30. begin rData <= RAM_RdData; i <= i + 1'b1; end 31. 32. 3: 33. if( SPI_DoneSig ) begin isSPI <= 1'b0; i <= i + 1'b1; end 34. else begin isSPI <= 1'b1; end 35. 36. 4,5: 37. begin rAddr <= 8'd2; i <= i + 1'b1; end 38. 39. 6: 40. begin rData <= RAM_RdData; i <= i + 1'b1; end 41. 42. 7: 43. if( SPI_DoneSig ) begin isSPI <= 1'b0; i <= i + 1'b1; end 44. else begin isSPI <= 1'b1; end 45. 46. 8: 47. i <= i; 48. 49. endcase 50. 51. assign RAM_Addr = rAddr; 52. assign SPI_StartSig = isSPI; 53. assign SPI_WrData = rData; 54. 55. endmodule
控制模块是低级建模当中比较重要的模块,控制模块一般都不用仿真,因为它的责任只是协调和发号而已。另一方面,控制模块也是仿真的切入口。第3~10行是相关的出入端声明;第12~14行是相关的寄存请声明;第16~49行是控制模块的核心操作。根据
exp33的实验要求,控制模块的功能是从ram读取数据,然后再将数据送往spi功能模块发送出去。
它首先在步骤0为RAM_rAddr赋值为1,亦即从ram的地址1读取数据;步骤1是给足ram有充分的时间读出数据;步骤2则将读出的数据赋给rData寄存器;步骤3是启动spi功能模块将方才读到的数据发送出去。步骤4~7则是重复步骤0~3的操作不过是发送数据不同而已。
图6.5.4 笔者脑补的时序图。
步骤0~2,还有步骤4~7假设它们分别指向时钟时钟T0~T2,T3~T5。i成功指向时钟以后,内容会无比清晰,此刻笔者就能自己能脑补时序,如图6.5.4所示,那是笔者脑补的时序图,过程如下:
l T0之际,它为ram发送地址8’d1;
l T1之际, 此刻它在发呆。同一时刻,ram接收地址数据,接着发送数据8’b1100_0011;
l T2之际,它将数据8’b1100_0011在rData寄存器里。
l T4之际,它为ram发送地址8’d2;
l T5之际, 此刻它在发呆。同一时刻,ram接收地址数据,接着发送数据8’b1000_0001;
l T6之际,它将数据数据8’b1000_0001在rData寄存器里。
就这样,我们可以省下许多不必要的仿真时间。
1. `timescale 1 ps/ 1 ps 2. module exp33_simulation(); 3. 4. reg CLOCK; 5. reg RESET; 6. 7. /***********************************/ 8. 9. initial 10. begin 11. RESET = 0; #10; RESET = 1; 12. CLOCK = 1; forever #5 CLOCK = ~CLOCK; 13. end 14. 15. /***********************************/ 16. 17. wire [7:0]RAM_Addr,RAM_RdData; 18. 19. ram U1 20. ( 21. .RAM_Addr ( RAM_Addr ), 22. .CLOCK ( CLOCK ), 23. .RESET( RESET ), 24. .RAM_WrData( ), 25. .RAM_WrEn ( ), 26. .RAM_RdData ( RAM_RdData ) 27. ); 28. 29. wire SPI_StartSig, SPI_DoneSig; 30. wire [7:0]SPI_WrData; 31. 32. ctrlmod U2 33. ( 34. .CLOCK( CLOCK ), 35. .RESET( RESET ), 36. .RAM_Addr( RAM_Addr ), 37. .RAM_RdData( RAM_RdData ), 38. .SPI_StartSig( SPI_StartSig ), 39. .SPI_DoneSig( SPI_DoneSig ), 40. .SPI_WrData( SPI_WrData ) 41. ); 42. 43. spi_funcmod U3 44. ( 45. .CLOCK ( CLOCK ), 46. .RESET( RESET ), 47. .SPI_StartSig( SPI_StartSig ), 48. .SPI_DoneSig( SPI_DoneSig ), 49. .SPI_WrData( SPI_WrData ), 50. .SCL ( SCL ), 51. .SDO ( SDO ) 52. ); 53. 54. /***************************************/ 55. 56. endmodule
exp33是该实验的仿真环境,如果按照平常的建模习惯,笔者一定事先建立组合模块将 ram模块,控制模块,还有spi功能模块组合起来再仿真。但是,根据主动设计而言,上述3个模块基本上已有清晰的内容,强烈的印象,还有脑补的时序,已经足够完事了,如果读者有足够的信心,读者可以无视再仿真。不过世事也有万一的时候,小心能驶万年船,为求安心多作一下仿真也好。
如exp33所示,笔者按照 exp33的建模图将3个模块组合起来。其中,第24~25行的RAM_WrEn与 RAM_WrData信号,笔者有意将它们放空,因为exp33没有它们的出场机会。
图6.5.5 exp33的仿真结果。
图6.5.5是exp33的仿真结果,其中光标C0~C1指向SPI的第一次写操作,C2~C3则指向SPI的第二次写操作。根据exp33的实验要求,控制模块经由 RAM_Addr信号为ram模块写入地址,接着ram模块便会吐出相对应的数据,然后再由控制模块将数据送入spi功能模块,最终以SPI的传输方式发送出去。
如图6.5.5所示,第一次操作是读出地址1的数据 8’b1100_0011,并且按照SPI的传输方式发送出去。第二次操作则是读出地址2的数据 8’b1000_0001,再由SPI方式发送出去。图6.5.5的时序结果相较笔者脑补的时序图,两者极为接近,虽然仿真工具显示的时序结果非常具体,不过这是借用它人眼睛看到的景象,颇为被动 ... 反之,想象力建立的时序结果虽然抽象,不过那是心眼看到的景象,较为主动。
图6.5.6 exp33的比喻图。
笔者为了强调主动设计,笔者故意瞎搞一下exp33。如图6.5.6所示,ram模块比喻为茉莉花,控制模块比喻为蜜蜂,SPI功能模块比喻为蜜蜂窝。茉莉花的功能是生产花蜜,蜜蜂的工作是从茉莉花哪里采集花蜜以后,再囤积在蜜蜂窝里,至于蜜蜂窝是用来生产蜂蜜的工厂。
如果我们站在整体视角去观察事物,我们自然会认为蜂蜜生产必须依靠茉莉花,蜜蜂,还有蜜蜂窝三者之间的合作才能顺利完成,期间三者保持亲密的关系。整体视角一般也称为神的视角,神为了掌握所有,神必必须接受所有个体情况之余,神还要了解个体之间的交流状况。不管我们的脑袋能力再怎么好,我们始终没有神般的吞吐量,一口吃完整块切糕根本就是痴心妄想。
除此之外,大口大口进食原本就是一种坏习惯,我们分分钟都会消化不良照成反胃。如果读者坚持要用神的视角去观察一切,笔者建议最好挑食一点,只选自己在意的部分。偏食虽说也是一种不好的进食习惯,营养容易失衡,仿真也是理解一块一块,不过那也是没有主动设计的情况下而已。主动设计本来就是个体视角东西,整体视角只是用来看爽 ... 啊不!是补助性质。
如果我们站在个体视角去观察事物,我们会发现茉莉花尽管生产花蜜,蜜蜂尽管采蜜和囤积,蜜蜂窝尽管生产蜂蜜而已,期间三者都保持独立又陌生的关系。个体视角是一种,先将大切糕分为无数小切糕,然后再逐步消化的方法。因为我们没有神一般的能耐掌握一切,余下我们只能掌握局部信息,然后再运用想象力将所有局部信息结合起来,成为一副完成的巨大的信息。个体视角不仅可以减轻左脑的负担,个体视角也让我们更加集中处理事情。
个体视角好比人类当下的社会现象,人与人之间只有陌生与自私,不过仔细一想,那是个体尽全力活下去的证据。二十一世纪的今天,忙碌的生活早已让人们流失许多闲情,为了造就更好的生活条件,个体必须使劲思考自己,个体必须使劲做好自己,不然这个社会也不会正常运作。嗨 ... 说着说着,叹气声也不经意流了出来,话题太沉重了,如果这个世界真有恶魔的话,麻烦落下一颗陨石来纠正一切吧 ... 拜托了。
图6.6.1 exp34的建模图。
笔者在上一个小节为主动设计举例一个简单的例子,然而例子实在太简单了,为了继续刺激读者成长,这一回笔者稍微提高难度。如图6.6.1所示,那是exp34的建模图,模块之间除了更多信号以外,exp34也多了一块虚拟硬件,读者是不是觉得很刺激呢?控制模块与RAM模块之间多了 RAM_WrEn信号还有 RAM_WrData信号,即表示控制模块不仅读取RAM的数据,它也为RAM模块写数据。
控制模块与SPI功能模块之间的变化,除了SPI_StartSig信号多了1位之余,两者之间也增加了SPI_RdData信号。至于SPI功能模块不再单单发送数据,它也要接收来至虚拟硬件反馈的数据。为此我们开始思考,读写RAM之前已经做过实验,所以没有什么难题。此外,控制模块与SPI功能模块之间也应用了问答信号,所以控制的问题也不大,不过SPI功能模块需要多添加一项读功能了。最后就是exp34多了虚拟硬件,亦即多了反馈输出,结果必须应用仿真模型③。
嗯,做好一切思考准备以后,我们就可以开工了。
1. module ram 2. ( 3. input CLOCK,RESET, 4. input RAM_WrEn, 5. input [7:0] RAM_Addr, 6. input [7:0] RAM_WrData, 7. output [7:0] RAM_RdData 8. ); 9. reg [7:0] ram[255:0]; 10. reg [7:0] rData; 11. 12. initial begin 13. ram[1] = 8'b1100_0011; 14. ram[2] = 8'b0000_0000; 15. end 16. 17. always @ (posedge CLOCK or negedge RESET) 18. if( !RESET ) 19. rData <= 8'd0; 20. else if( RAM_WrEn ) 21. ram[ RAM_Addr ] <= RAM_WrData; 22. else 23. rData <= ram[ RAM_Addr ]; 24. 25. assign RAM_RdData = rData; 26. 27. endmodule
ram模块修改的地方不多,除了第14行的初始化以外,笔者将ram[2]的空间清零,目的是用来储存虚拟硬件反馈回来的数据。
图6.6.2 SPI的读写时序。
笔者曾经说过,SPI传输协议有主机(掌握SCL一方)与从机之分,而且SPI传输协议在SCL的下降沿设置数据,SCL的上升沿锁存数据。如图6.6.2所示,上图是SPI写操作,下图是SPI的读操作,期间SDI的输入数据是 8’b1010_1010,因此每次SCL上升沿读取数据,SPI_RdData最高位道最低分持续发生变化,直至T7为止,SPI_RdData最终输出数据8’b1010_1010。
1. module spi_funcmod 2. ( 3. input CLOCK,RESET, 4. input [1:0]SPI_StartSig, 5. output SPI_DoneSig, 6. input [7:0] SPI_WrData, 7. output [7:0] SPI_RdData, 8. output SCL,SDO, 9. input SDI, 10. 11. output [4:0]SQ_i 12. ); 13. parameter T1US = 8'd5; // (1/1Mhz) / (1/50Mhz) = 50; 50/25/5 14. 15. reg [4:0]i; 16. reg [7:0]C1; 17. reg [7:0]rData; 18. reg rSDO,rSCL; 19. reg isDone; 20. 21. always @ (posedge CLOCK or negedge RESET) 22. if( !RESET ) 23. begin 24. i <= 5'd0; 25. C1 <= 8'd0; 26. rData <= 8'd0; 27. {rSDO, rSCL} <= 2'b01; 28. isDone <= 1'b0; 29. end 30. else if( SPI_StartSig[1] ) 31. case( i ) 32. 33. 0,2,4,6,8,10,12,14: 34. if( C1 == T1US -1) begin C1 <= 8'd0; i <= i + 1'b1; end 35. else begin C1 <= C1 + 1'b1; rSCL <= 1'b0; rSDO <= SPI_WrData[7-(i>>1)]; end 36. 37. 1,3,5,7,9,11,13,15: 38. if( C1 == T1US -1) begin C1 <= 8'd0; i <= i + 1'b1; end 39. else begin C1 <= C1 + 1'b1; rSCL <= 1'b1; end 40. 41. 16: 42. begin isDone <= 1'b1; i <= i + 1'b1; end 43. 44. 17: 45. begin isDone <= 1'b0; i <= 5'd0; end 46. 47. endcase 48. else if( SPI_StartSig[0] ) 49. case( i ) 50. 51. 0,2,4,6,8,10,12,14: 52. if( C1 == T1US -1) begin C1 <= 8'd0; i <= i + 1'b1; end 53. else begin C1 <= C1 + 1'b1; rSCL <= 1'b0; end 54. 55. 1,3,5,7,9,11,13,15: 56. if( C1 == T1US -1) begin C1 <= 8'd0; i <= i + 1'b1; end 57. else begin C1 <= C1 + 1'b1; rSCL <= 1'b1; rData[7-(i>>1)] <= SDI; end 58. 59. 16: 60. begin isDone <= 1'b1; i <= i + 1'b1; end 61. 62. 17: 63. begin isDone <= 1'b0; i <= 5'd0; end 64. 65. endcase 66. 67. assign SCL = rSCL; 68. assign SDO = rSDO; 69. assign SPI_RdData = rData; 70. assign SPI_DoneSig = isDone; 71. assign SQ_i = i; 72. 73. endmodule
exp34的spi功能模块相较 exp33的spi功能模块,它多了一个读操作,如代码行48~65所示。SPI_StartSig[1]使能表示启动写操作(第30行),SPI_StartSig[0]使能则表示启动读操作(第48行)。SPI输出数据与读取数据最大的差别就是SCL的时钟沿而已,由于下降沿对读操作没有什么作用,因此步骤0~14(偶数)只是单纯拉低SCL而已。
反之,上升沿对读操作来说却非常重要,步骤1~15(奇数)除了拉高时钟以外,第57行的读取操作rData[7-(i>>1)] <= SDI表示从数据最高位开始。此外,exp34拥有虚拟硬件,因此笔者将内部的指向信号i引出(第71行),以用来描述虚拟硬件的部分功能。
1. `timescale 1 ps/ 1 ps 2. module spi_funcmod_simulation(); 3. 4. reg CLOCK; 5. reg RESET; 6. 7. reg [1:0]SPI_StartSig; 8. reg [7:0]SPI_WrData; 9. wire SPI_DoneSig; 10. wire [7:0]SPI_RdData; 11. wire [4:0]SQ_i; 12. 13. reg SDI; 14. wire SCL,SDO; 15. 16. /***********************************/ 17. 18. initial 19. begin 20. RESET = 0; #10; RESET = 1; 21. CLOCK = 1; forever #5 CLOCK = ~CLOCK; 22. end 23. 24. /***********************************/ 25. 26. spi_funcmod U1 27. ( 28. .CLOCK ( CLOCK ), 29. .RESET( RESET ), 30. .SPI_StartSig( SPI_StartSig ), 31. .SPI_DoneSig( SPI_DoneSig ), 32. .SPI_WrData( SPI_WrData ), 33. .SPI_RdData( SPI_RdData ), 34. .SCL ( SCL ), 35. .SDO ( SDO ), 36. .SDI( SDI ), 37. .SQ_i( SQ_i ) 38. ); 39. 40. /***************************************/ 41. 42. reg [3:0]i; 43. 44. always @ ( posedge CLOCK or negedge RESET ) 45. if( !RESET ) 46. begin 47. i <= 4'd0; 48. SPI_StartSig <= 1'b0; 49. SPI_WrData <= 8'd0; 50. end 51. else 52. case( i ) 53. 54. 0: 55. if( SPI_DoneSig ) begin SPI_StartSig[1] <= 1'b0; i <= i + 1'b1; end 56. else begin SPI_StartSig[1] <= 1'b1; SPI_WrData <= 8'b1001_1001; end 57. 58. 1: 59. if( SPI_DoneSig ) begin SPI_StartSig[0] <= 1'b0; i <= i + 1'b1; end 60. else begin SPI_StartSig[0] <= 1'b1; end 61. 62. 2: 63. i <= i; 64. 65. endcase 66. 67. /***********************************/ 68. 69. always @ ( posedge CLOCK or negedge RESET ) 70. if( !RESET ) 71. begin 72. SDI <= 1'b0; 73. end 74. else if( SPI_StartSig[0] ) 75. case( SQ_i ) 76. 77. 0: SDI = 1'b1; 78. 2: SDI = 1'b0; 79. 4: SDI = 1'b1; 80. 6: SDI = 1'b0; 81. 8: SDI = 1'b1; 82. 10: SDI = 1'b0; 83. 12: SDI = 1'b1; 84. 14: SDI = 1'b0; 85. 86. endcase 87. 88. endmodule
上述代码是用来实现局部仿真,第44~65行是虚拟输入,第69~86行则是虚拟输出,它相较exp33多了虚拟输出这段激励内容。虚拟输入,除了步骤0除了使能仿真对象写入数据 8’b1001_1001以外,步骤1也使能仿真对象读取数据。期间,第69~86行的虚拟输出则描述虚拟硬件的部分功能,虚拟输出借用 SPI_StartSig信号之余,它也借用 SQ_i信号,期间反馈输出的数据是 8’b1010_1010,而且反馈输出也是按照 SQ_i指向的过程作出反应。
SQ_i指向过程0~14(偶数)分别是SCL信号的上升沿,根据SPI传输协议,SCL上升沿则是设置数据。
图6.6.3 spi_funcmod 的局部仿真结果。
图6.6.3是 spi_funcmod的局部仿真结果,光标C0~C1指向的范围是写操作,C2~C3指向的范围是读操作。如图6.6.3所示,仿真对象的写数据是 8’b1001_1001,虚拟输出反馈的数据则是 8’b1010_1010。C3指向的地方也是仿真对象完成读操作的时候,此刻请注意SPI_RdData的结果,它是8’b1010_1010 ... 这个情况表示读操作,还有反馈输出的激励过程都顺利完成。
1. module ctrlmod 2. ( 3. input CLOCK,RESET, 4. 5. output [7:0]RAM_Addr, 6. input [7:0]RAM_RdData, 7. output RAM_WrEn, 8. output [7:0]RAM_WrData, 9. 10. output [1:0]SPI_StartSig, 11. input SPI_DoneSig, 12. output [7:0] SPI_WrData, 13. input [7:0] SPI_RdData 14. ); 15. reg [3:0]i; 16. reg [7:0]rAddr,rData; 17. reg [1:0]isSPI; 18. reg isRAM; 19. 20. always @ (posedge CLOCK or negedge RESET) 21. if( !RESET ) 22. begin 23. i <= 4'd0; 24. { rAddr, rData } <= { 8'd0,8'd0 }; 25. isSPI <= 2'b00; 26. isRAM <= 1'b0; 27. end 28. else 29. case( i ) 30. 31. 0,1: 32. begin rAddr <= 8'd1; i <= i + 1'b1; end 33. 34. 2: 35. begin rData <= RAM_RdData; i <= i + 1'b1; end 36. 37. 3: 38. if( SPI_DoneSig ) begin isSPI[1] <= 1'b0; i <= i + 1'b1; end 39. else begin isSPI[1] <= 1'b1; end 40. 41. 4: 42. if( SPI_DoneSig ) begin isSPI[0] <= 1'b0; i <= i + 1'b1; end 43. else begin isSPI[0] <= 1'b1; end 44. 45. 5: 46. begin rAddr <= 8'd2; isRAM <= 1'b1; i <= i + 1'b1; end 47. 48. 6: 49. begin isRAM <= 1'b0; i <= i + 1'b1; end 50. 51. 7: 52. i <= i; 53. 54. endcase 55. 56. assign RAM_Addr = rAddr; 57. assign RAM_WrEn = isRAM; 58. assign RAM_WrData = SPI_RdData; 59. assign SPI_StartSig = isSPI; 60. assign SPI_WrData = rData; 61. 62. endmodule
exp34的控制模块相较exp33的控制模块,它则多了步骤4的SPI读操作(第41~43行),然后就是步骤5~6的ram写操作(第45~48行)。控制模块现在在步骤0~2从ram模块的地址1哪里读取数据,然后在步骤3将其送入SPI功能模块并且发送出去。时候,它又从SPI功能模块哪里读来数据,然后再步骤5~6将其写入ram模块的地址2当中。注意,RAM_WrData直接由SPI_RdData驱动(第58行)。
图6.6.4 笔者脑补的时序图。
假设步骤0~2,还有步骤5~6,它们分别指向T0~T2,T5~T6。如图6.6.4所示,那是笔者脑补的时序图,左图是从ram哪里读出数据,右图则是将数据写入ram哪里。根据右图的现实结果,控制模块在T5的时候,将RAM_WrEn拉高,并且输出地址8’d2,还有数据8’b1010_1010,然后ram则会在T6将数据8’b1010_1010存入地址8’d2。
1. `timescale 1 ps/ 1 ps 2. module exp34_simulation(); 3. 4. reg CLOCK; 5. reg RESET; 6. 7. /***********************************/ 8. 9. initial 10. begin 11. RESET = 0; #10; RESET = 1; 12. CLOCK = 1; forever #5 CLOCK = ~CLOCK; 13. end 14. 15. /***********************************/ 16. 17. wire [7:0]RAM_Addr,RAM_RdData; 18. wire RAM_WrEn; 19. wire [7:0]RAM_WrData; 20. 21. ram U1 22. ( 23. .RAM_Addr ( RAM_Addr ), 24. .CLOCK ( CLOCK ), 25. .RESET( RESET ), 26. .RAM_WrData( RAM_WrData ), 27. .RAM_WrEn ( RAM_WrEn ), 28. .RAM_RdData ( RAM_RdData ) 29. ); 30. 31. wire [1:0]SPI_StartSig; 32. wire SPI_DoneSig; 33. wire [7:0]SPI_WrData; 34. wire [7:0]SPI_RdData; 35. 36. ctrlmod U2 37. ( 38. .CLOCK( CLOCK ), 39. .RESET( RESET ), 40. .RAM_Addr( RAM_Addr ), 41. .RAM_RdData( RAM_RdData ), 42. .RAM_WrEn( RAM_WrEn ), 43. .RAM_WrData( RAM_WrData ), 44. .SPI_StartSig( SPI_StartSig ), 45. .SPI_DoneSig( SPI_DoneSig ), 46. .SPI_WrData( SPI_WrData ), 47. .SPI_RdData( SPI_RdData ) 48. ); 49. 50. reg SDI; 51. wire [4:0]SQ_i; 52. 53. spi_funcmod U3 54. ( 55. .CLOCK ( CLOCK ), 56. .RESET( RESET ), 57. .SPI_StartSig( SPI_StartSig ), 58. .SPI_DoneSig( SPI_DoneSig ), 59. .SPI_WrData( SPI_WrData ), 60. .SPI_RdData( SPI_RdData ), 61. .SCL ( SCL ), 62. .SDO ( SDO ), 63. .SDI( SDI ), 64. .SQ_i( SQ_i ) 65. ); 66. 67. /***************************************/ 68. 69. always @ ( posedge CLOCK or negedge RESET ) 70. if( !RESET ) 71. begin 72. SDI <= 1'b0; 73. end 74. else if( SPI_StartSig[0] ) 75. case( SQ_i ) 76. 77. 0: SDI = 1'b1; 78. 2: SDI = 1'b0; 79. 4: SDI = 1'b1; 80. 6: SDI = 1'b0; 81. 8: SDI = 1'b1; 82. 10: SDI = 1'b0; 83. 12: SDI = 1'b1; 84. 14: SDI = 1'b0; 85. 86. endcase 87. 88. endmodule
exp34相较exp33之间的不同之处,除了3个模块之间组合连线以外(第17~65行),exp34也多了反馈输出(第69~86行),其它的内容读者自己看着办吧。
图6.6.5 exp34的仿真结果。
图6.6.5是exp34的仿真结果,初次见面的朋友可能会吓到,还以为遇见切糕。没错,exp34的仿真结果,在某种程度上来说已经是如假包换的切糕。不过在此之间,我们已经做好许多准备了,所以这块切糕一点也不可怕。光标C0~C1指向的地方是SPI的写操作,光标C2~C3指向的地方则是 SPI的读操作。
图6.6.5基本上是一种拼凑完毕的时序结果而已,全程内容笔者就不详细说了,反之让我们来解析一写小细节吧。U2/i是用来指向控制模块的内部过程,SQ_i则是用来指向spi功能模块的过程,此外SQ_i还有 SPI_StartSig也用来描述虚拟硬件。当控制模块执行读操作的时候(C2指向的地方),SPI_StartSig的结果是2’b01,虚拟输出也因此开始执行。
虚拟输出根据 SQ_i指向的过程,一步步为SDO拉高又拉低,结果输出8’b1010_1010的串行数据。紧接着C3指向的地方正好是读操作的结束之际,控制模块接下来会将刚才读出的数据写入ram模块的地址2当中。
exp34相较exp33虽然稍微刺激一点,然而采用主动设计之后exp34也不会难到那里去,因为再大的切糕我们也可以分成好几份消化。如果读者反问笔者,有没有闲情去脑补整个exp34的时序,笔者则会笑笑说不。主动设计本来是用来偷懒 ... 啊不!是,用作局部仿真造就整体仿真的设计技巧。笔者脑袋的处理能力不仅有限,而且吞吐量也不够,所以笔者只会执行局部仿真,或者脑补局部时序而已,其余任由想象力填充。
接着,让我们来好好思考一下,这个章节所学到的东西。如果我们按照传统手段去仿真exp34,或许很多同学会感觉这是一件杀死不偿命的苦差事,笔者对此举四肢赞同。理由很简单,传统手段是一种硬塞硬啃的仿真方法,那些承受能力稍差的同学(笔者也是)
一般都会支持不了,然后患上绝望的仿真癌。曾经何时笔者也饱受仿真癌的折磨,所幸有它打救笔者。
为此,笔者才考虑另外的仿真手法。首先是尽量过滤不必要的信息,然后再将整体有规划地切分为数个个体,期间我们必须做好各种早期准备。接着,再借用自动思想,为个体留下强烈的局部印象,让它深深刻入那记忆之中。事后,我们便可以脑补这个,脑补那个了,然而要不要脑补整体的仿真内容,还是将无数的局部仿真内容结合起来,完全是读者的自由。相对之下,笔者比较喜欢玩拼图,将局部的仿真信息一块一块拼凑起来形成一幅整体的仿真信息。
如果读者感觉不安或者没有信心,读者也可以将脑补的时序图与是实际的时序图进行比较。读者在意哪里就比较哪里,为了避免反脑,读者千万别妄想自己可以吞下整副时序图哦,稍微偏食一点也没关系的。最后笔者奉劝道,做人做事最怕就是三心两意,如果读者选择主动思想,读者必须放手传统流派,让它成为过去,不再回头,然后成为潇洒又勇敢的男人。
完成 exp33~34的作业以后,笔者将其交给师兄,不一会师兄露出惊讶的神情,然后急忙呼唤另一位师兄来检查作业。师兄们在商讨期间,笔者用眼角偷瞄周围 ... 这里除了笔者以外,还有它班的同学,然而数量不超过10个人,想必它们也是那场噩梦的幸存者吧?它们的身上再也感觉不到常人的气场,应该说它们给人感觉越来越像师兄了,让人觉得非常不舒服。
笔者曾经听过一战期间,斯特勒秘密建立了一只特殊部队,这个部队专门聚集战场的死神。它们虽然怪异,不过它们都是生存率最高的士兵,也是传说的不死军队,那种异于常人的生存率也只能使用奇迹来形容而已。传统流派是不是也使用相似的方法聚集同伴呢?不知为何,笔者浑身忽然觉得恶寒起来。
突如其来“喂”的一声,打破笔者的胡思乱想,笔者立即将意识放在对方的身上。一位年逾40的男人正缓缓靠过来,那副似曾相识的面貌,不禁让笔者回忆那起不愉快经历。没错,那位男人正是资深师兄。无形的压力驱使身体站了起来,笔者注视它,它也注视笔者,然而在场所有视线也聚集在这里 ... 就这样,大眼瞪小眼一段时间以后,命运的轮盘开始转动了。
“我的师弟,首先恭喜通过考验,老夫实在非常高兴”,资深师兄客套道。
“是 ... 是吗?”,笔者板着脸答道。
“老夫怎么想都不明白,我的师弟是如何活下来呢?”,资深师兄突然问道。
“ ... 啊!?”,笔者哑然道。
根据笔者的认识,通过考验的人只有啃掉切糕这条选择而已,然而笔者却被切糕啃掉,不过又奇迹生还下来,然后又将切糕解决掉。那种状况对传统流派而言,根本毫无先例的,师兄的问题毫无疑问是在质疑笔者“为什么像你这种弱小的蠢货,不仅活了下来,还漂亮解决切糕呢?”很明显,师兄正在怀疑疑笔者在考验期间出猫。这个问题好比导火线般,不小心将笔者的按钮启动,压抑许久的感情终于爆发了!
“啊哈哈哈!啊哈哈哈!”,笔者狂笑道。
“传统流派唷 ... 你到底有多丑陋?,笔者对着众人叫喊道。
“可恶,小鬼你说谁丑陋!!!”,师兄A愤怒道。
“难道还有谁吗?”,笔者故意加重语气道。
“蠢货!是不是活得不耐烦了!”,师兄B愤怒道。
几名师兄因为经受不了笔者的羞辱,激动地破口大骂道,其中一名师兄还举起拳头冲向笔者。没错“暴力”这就是它们的真面目,来吧!笔者才不怕暴力!此刻,资深师兄忽然举起右手将对方拦住 ... 有一瞬,笔者在隐约种听见轮盘的转动声,那股声音好似下定决意一般,越滚越慢,越滚越慢 ...
“丑陋嘛?呵呵,真有意思!”,资深师兄笑道。
“我的师弟,为什么那样觉得?”,资深师兄又继续道。
“哼!只要细心观察,任谁都会觉得!”,笔者反驳道。
“有谁这样觉得吗?是你?是你?还是你?”,资深师兄有意将矛头指向其他师兄。
“没 ... 没有!”,师兄A口吃道。
“不,不是那样的!”,师兄B心虚道。
“那个 ...!那个”,师兄C颤栗道。
“看吧 ... 我的师弟,没有人这样认为”,资深师兄阴笑道。
可恶,这不就是赤裸裸的恐吓吗?它们都畏惧资深师兄,这种情况好比笔者恐惧切糕一样,无法反抗的绝对压力。想着想着,笔者忽然觉得身体有点不停使唤 ... 不,那是颤抖,笔者打从心底也在畏惧。灵魂的左天使说:不服从就会死!右恶魔则说:不屈服,去报复!此刻,理智还有感情正在发生激烈的斗争 ... 头好疼,感觉快要爆炸了,拜托不要再吵了!笔者紧闭双眼,使劲压住太阳穴祈求着。
“孩子,别忘了,白骆驼始终在身边”
温馨的声音忽然响起,笔者不经回忆在梦中出现的它 ... 吵闹声渐渐消去,黑暗中笔者也看见同辈们的笑颜,它们不停为笔者加油打气,告知笔者不能退缩!好温柔呀,好窝心呀,一股前所未有的勇气正逐渐填补心中的懦弱,白骆驼的身影引导笔者踏出人生的决意。此刻,命运的轮盘已经停止转动。
“勇气,已经足够了 ...“,笔者小声道。
“怎么了,我的师弟?”,资深师兄问道。
“各位师兄,谢谢大家一直以来的暴力!小弟决定离开!”,笔者决意道。
“什么!?”,资深师兄惊讶道。
“臭小鬼!想走人吗!?”,师兄A叫喊道。
“想来就来,想走就走,以为这里是慈善堂吗?”,师兄B叫喊道。
“不知道也不想知道!总之,各位多保重!”,笔者坚持道。
“可恶,别自说自话 ... ”,师兄C叫喊道。
笔者无视师兄们的叫喊,转身后便立即离开 ... 吧嗒,吧嗒,鞋子与地板的摩擦声也融入背后的叫喊声,然而那些杂音,随着距离的拉长也逐渐消去。笔者抵达门口之际,背后传来有力的叫声,如果没听错的话,那是资深师兄的声音,从刚才开始它就一直保持沉默。
“慢着,我的师弟 ... ”,资深师兄阻止道。
“......”,笔者用沉默回应道。
“再踏出门口一步,您再也不是师弟,而是永远被追缉的敌人!”,资深师兄大声道。
“最后一刻还在恐吓对方,传统流派你究竟有多丑陋!”,笔者小声嘀咕道。
笔者头也不回,顺着命运之风向前一方通行,背后仅留下恶意的视线 ...
不知不觉,笔者又来到山崖边,笔者顺着崖口正缓缓前进着,然而这次不是寻求解脱才来到这里。笔者在崖口一角忽然停下脚步,百感交集的感情也随之涌上心头 ... 这个地方曾是命运转折点,也是一处契机之地,笔者曾经“绝望”来到这里;笔者也曾经“希望”离开这里;然而,现在又因为回忆来到这里 ....
笔者用怀念的眼光注视远处,哪里曾是解脱的天堂,哪里也曾是希望之光升起的地方。如今仔细一看,哪里不过是一处毫无止境的地平线而已。人真是奇怪的生物,同样的事情会因为不同的心情看到不同的结果,笔者不禁发出自嘲的“呵呵”声。再享受一会风景吧 ... 笔者自言自语道。
时间宛如蒸汽般消失在不觉不觉之中,眼见夕阳已经西下,鸟儿也不断发出“吱吱”的归巢声,示意笔者“离开的时候”到了。走吧!笔者带着决意的步伐往家乡出发去。笔者意识到,从今以后,前方会有数之不尽的切糕挡道,背后也有暴力的家伙在追缉。不过,笔者也不是独自一个人默默上路,因为看不见的远处,它无时无刻都在守候笔者,所以笔者不害怕也不寂寞。
笔者认为,盲从才是最可怕的东西,我们宛如丧尸般失去自我,任由别人牵动鼻子前进,不管对方是传统流派还是仿真,结果就像温水煮青蛙,死在不知不觉当中。在未知的土地上,独立又主动的思考非常重要,因为那是唯一保护自己的工具 ... 建模也好,仿真也好,它们都是未知的土地,然而学习就是在未知的土地上冒险。
“读者,您也是不是这样认为呢?”
第六章终于写完了,这个章节的内容比较单调,除了讨论不可仿真模块以外,就是经过综合练习认识主动设计的优劣之处。青春是宝贵的东西,我们不应该任由仿真蹉跎,然而蹉跎青春的三大杀手,它们就是超傻模块,超烦模块,还有超乱模块等不可仿真对象。真可怕,真可怕!
超傻模块物以其名一样,是再也简单不了的家伙,它不仅用时少,而且内容页也非常单纯,基本上都是望上一眼便立即知晓它的用意。根据主动思想,越是简单模块,模块内容越清晰,作为一快个体而言,它已经拥有非常强烈的特征印象,所以我们用不着特意仿真也没问题。但是我们无法阻止寂寞的家伙去仿真它们。
除了超傻模块以外,也有超烦模块这个不可仿真对象。超烦模块并不表示模块它会吱吱喳喳说个不停 ... 换之,它会故意让我们等候,直至我们感觉不耐烦。千万别认为超烦模块是可爱的女人,实际等候起来会产生无尽的破坏力!试问各位绅士,大伙已经浪费多少时间在等候异性呢?超烦模块会拉长wave界面,吃尽内存空间,拖慢计算器,让仿真变成更加耗时。
最后一位不可仿真对象就是超乱模块,超乱意指内容不明的模块内容,一般是指没有结构的模块,还有官方插件模块。官方插件模块不是不可仿真,而是仿真起来超级麻烦,因为官方插件模块是隐藏内容,所以我们必须经过各种途径去摸索才能知晓模块内容。模块如果隐藏内容,其一就是驾驭的问题,其二就是阻碍仿真信息的解析工作,因为仿真对象未能解读。
最后的综合实验基本上都是演示主动设计的使用方法。笔者需要强调,主动设计不是东西,主动设计更像技巧这种无形的东西。主动设计是一种讲求个体造就整体的设计技巧,它基于主动思想,相比整体情况它更在乎个体的状况。主动设计有许多内容,然而较为注目就是局部仿真,想象力仿真,还有想象力填充等奇怪的东西。
读者必须知道modelsim等仿真工具始终是它人的眼睛,借用它人的力量比哭死爹娘更困难,而且也非常被动 ... 为此,笔者才会尝试运用想象力去执行仿真。期间,读者可能会觉得可笑,认为笔者是不是妄想过头了?不管怎么样,主动设计正逐渐成为笔者的建模,还有仿真习惯 ... 像笔者这样不能一口吞掉切糕的弱小家伙,主动设计会将切糕平均划分以后,再一口一口吃掉。
最后的最后,笔者也写上自己的故事,虽然内容经过武侠化还有夸张化,不过感情还有决意却是如假包换的真东西。这种感觉好比独行侠游走江湖一般,为了明天它必须步步为营,还有拥有一份不可动摇的自我。
、