以下大部分内容摘自VerilogHDL扫盲篇:
学习VerilogHDL语言不像学习一些高级语言,对于高级语言来说它们已经是完成品了,其外它们还有很多被隐藏的指令,这些好处无疑是减轻了学习者的负担。相反的VerilogHDL语言既是完成品,既不是完成品,就是因为它太自由了... 所以往往会让学习者感到疑惑,很疲惫和浮躁(我不学了!)。学习VerilogHDL语言需要一段过渡期的,快则半年,普通则1~2年,慢则很多年。即使经过了过渡期这也不表示已经掌握VerilogHDL语言了。所以呀朋友,希望你们可以沉住气,“欲速则不达”这是老祖先的智慧,它非常适合用在学习VerilogHDL语言的路上。VerilogHDL语言既不是顺序语言而且也非常的自由,VerilogHDL语言不像C语言那样有丰富的库支持,甚至库的概念也不适合用在它身上。但是即使它们是两个世界的居民,但是偶尔VerilogHDL语言可以在很多地方向C语言借签。相反的C语言就不能向VerilogHDL语言借签了。此外VerilogHDL语言还有两大阵列,就是综合语言和验证语言,这更是给学习者雪上加霜 太多的学习者会困惑在这两种语言的中间,所谓的困惑是思路的困惑。在这里,笔者建议先无视验证语言,先把综合语言学好(综合语言也没什么好学的,就如在前面章节笔者所举例的那样,关键字和操作符少得可怜),最重要还是掌握结构(建模)和使用规则(用法)。它们就像挥动着倚天剑和屠龙宝刀的招式,没有了这些招式倚天剑和屠龙宝刀不过是一件单纯的金属而已。至于验证语言,在未来有需要的时候再学也不迟,当阅读他人模块的时候,不要过于转牛角尖的看懂他人的思路,只要明白其中的内容就好。最重要还是如何使用自己的结构和方法去建立他人的思路,从中读者会学得更多。这一点是绝对的事实。就算现在的读者没有能力建立自己的结构也没有关系,来日方长读者有的就是时间。如果读者很钟爱笔者所建立的结构和方法,笔者很乐意也很荣欣被应用(这也是笔者写笔记的初衷)。
沉住气朋友,掌握VerilogHDL语言需要的不只是技术而已,最重要是那颗安静的心,安静的心会带读者乘风破浪,一方通行。此外记录笔记的习惯更为重要,向自己学习比起向他人学习更有学习的价值
一,建模是VerilogHDL语言的中心思想之二
二,时序是VerilogHDL语言的中心思想之二
在笔者的眼中,总结上C语言和VerilogHDL语言之间的区别会是如上的图表。关于高级语言和VerilogHDL语言区别的内容笔者讨论到这里就好了,读者不要过于深入区分谁是谁,谁又不是谁,如此纠结对学习没有任何好处,更多认识,当读者们深入以后就会自然了解。
我们知道驱使(RTL级)模块行动的最小单位就是时钟,即时发生延迟也只是延迟一个时钟,又或者2~3个时钟,绝对不会出现什么延迟2ns~3ns(物理路径延迟)等的问题。
在笔者的眼中“时序”的思想非常简单,就是如何读懂波形图,如何把波形图和模块的内容作联系。
例子1-reg 和wire的尴尬:
reg 和wire如果站在RTL级建模的角度上,reg 就是寄存器,作用是用来暂存内容,而且也提供操作空间;wire就是连线,作用仅此而已。但是站在组合逻辑级建模上reg和wire已经是傻傻分不清楚了
举个例子:
moduleomg_module(output[7:0]Q);
wire[3:0]A=4'd0;
wire[3:0]B=4'd2;
wire[3:0]C=4'd3;
assignQ=A+B+C;
endmodule
以上的一段代码,请问wire的作用是连线还是寄存内容了?呵呵,笔者没有说这样的使用方法有错呀。在这里笔者只是提出一个有趣的例子而已,对于一些初学者来说可能会非常的疑惑,尤其是那些习惯“面向对象”思想的人们...
moduleomg_module(output[7:0]Q);
reg[3:0]A;
reg[3:0]B;
reg[3:0]C;
always@(*)
begin
A=4'd0;
B=4'd2;
C=4'd3;
end
assignQ=A+B+C;
endmodule
如果笔者换成这样写法的话,是不是觉得更有道理呢?always(*) 的使用暗示了这是一个组合逻辑,然而寄存器A的值是4'd0,寄存器B的值是4'd2,寄存器C的值是4'd3,Q的驱动输出是寄存器A,B,C的值综合。
更有趣得是,经过编译两段代码所生成的RTL视图都一样!?(⊙o⊙)?... 在这里,笔者是要告诉读者一个信息,当我们建模的时候“解读能力”的优先级往往都高过“内容精简性”。要一度过于“贪小便宜”而把内容的解读能力跨下了,虽然这两个代码都没有错,而且结果一致。但是一个潜在的问题是,我们人类都是习惯把“东西分类”以便管理和理解,既然wire在字面上都是“连线”的意思了,就干脆把它当“连线”来使用...
例子2-always@() 的多样性
1.always@(posedgeCLKornegedgeRSTn)// 当CLK和RSTn变化的时候
2.always@(*) // 什么时候都变化, 亦即默认为组合逻辑
3.always@(A) // 当A变化的时候
always@() 的用法很多,但是用得最多的就是第1个和第2个。
always@(posedgeCLKornegedgeRSTn)
if(!RSTn)
begin
i<=4'd0;
.......
end
else
case(i)
0:
.......
endcase
上面一段代码是笔者最爱的“基于仿顺序操作想法”的基本“用法模板”,在后期里它可是大展拳脚。
always@(*)A=4'd9; // 常数赋值
always@(*) // 选择器
if(Start_Sig[0])rQ=U1_Q;
elseif(Start_Sig[1])rQ=U2_Q;
elseQ=1'bx;
至于第二种always@() 的用法,笔者估计它是用得最“泛滥”了,尤其是在组合逻辑建模里,不过笔者使用的比较不多, 除了“常数赋值”和“输出选择器”以外。
最后的第一种always@()的用法,基本上只要懂得上面两种always@() 用法以后,自然而然也会了。
例子3- 最头疼的=和<=赋值
基本上要搞懂这两个赋值操作符号的作用,就必须把“时序”的概念搞懂先。
宏观上,如同参考书中所说的一样;微观上,在时序中“=”是引发“即时事件”,“<=”则是引发“时间点事件”
例子4- 要慎用的*/%数学运算符
当读者使用*/ 和%的数学运算符的时候,笔者请你们再三的三思(九思?呵呵!),因为使用它们的代价很大。如果读者所使用的FPGA有内嵌硬件乘法器又或者除法器的话,那么这些乘法器和除法器就会被消耗。相反的,如果读者说使用的FPGA是没有包含这些东西的话,资源逻辑的消耗是很大的。一般上,如果是为了求出*2 *4 *8 *16又或者/2 /4 /8 /16 笔者建议使用位操作的运算符,亦即<<和>>,它们也可以求出同样的结果。如果想要求出的结果是不在2N范围之内的话,读者还是求与其他的方法... 只有在用尽办法的时候才使用它们。至于%的数学运算符,它真的是一个罪恶,没有可以替代的第三方法....在这里笔者要强调一下,笔者不是说不可以使用它们,笔者只是建议如果可以的话不要过度依赖它们,它们尽是一些逻辑资源的大食怪,不把资源吃光才怪。
在这里,笔者再强调一下HDL语言是一种非常自由的语言,它没有固定的结构,也没有固定的使用方法。参考书在乎“如何使用HDL语言去完成设计”,然而笔者比较在乎“如何有效使用HDL语言”。这两者之间都没有问题,不过是笔者的笔记比较偏向学习的口味,没有参考书那样死沉沉的气氛,比较愉快和轻松
话句话说,VerilogHDL语言可以应用的地方只适合“逻辑和底层设计”.... 不不不,这是天大的误会。就这样,随之又产生“C语言驱动的东西,既然用VerilogHDL语言去驱动”类似的声音。是谁规定C语言可以驱动的东西,VerilongHDL语言就不能驱动?相反的,C语言可以驱动的东西,如果读者也能使用VerilogHDL语言去驱动,那么这才是真正的学习。之所以会产生如此的声音,就如笔者在前几章节讲述的那样:“VerilogHDL语言的结构自由,使用方法也自由,自由到好像没有一样”很多的设计都不包含结构和使用方法,只要设计可以发挥预期般的效果就Okay~如果读者明白了这个简单的道理,读者自然会明白自定义VerilogHDL语言的结构和使用方法是非常的重要和基础。VerilogHDL语言的建模不是越复杂就越伟大,反之越直接的建模才是学习的方向。在这里,听笔者说:“当你放下偏见,你才可以接触到真理”,这简单的智慧在哪里都行得通,学习VerilogHDL语言也是这样一回事。
笔者来说说为什么单文件主义远离了VerilogHDL语言的本质呢?VerilogHDL语言,本质上是并行而且又有“面向对象”的味道。但是这“面向对象”的概念和C++语言中的概念有所不同,然而它更接近现实中的“管理系统”(详解请看建模篇)。读者尝试想象,有没有可能一个系统的操作,没有部门,没有团队,没有小组?对,就是不可能。单文件主义恰恰好就是违反了这个简单的道理。
上文中所说的“没有分类”的烂漫约会,如果也“没有顺序操作”的支持... 会是一个非常糟糕的情形。因为这个系统操作(约会过程)没有结构的支持, 这个情形也反映出了单文件主义的致命缺点。笔者有一句很经典的话:“解读能力差的模块是最糟糕的”,这一句话完全迎合单文件主义下所建立的模块。
低级建模的基本单元有:功能模块,控制模块,组合模块。
� 功能模块的内容包含了最基本的动作。
� 控制模块的内容包含了动作的控制步骤。
� 组合模块的内容包含了所有功能模块和控制模块之间的组合。
建模层次有:基础(模块)建模,仿顺序操作建模,接口建模,系统建模。
� 基础(模块)建模的内容包含了最小功能的模块。
� 仿顺序操作建模,这一个比较特别,主要是模仿了C语言中的函数。
� 接口建模的内容包含了一个已经封装完成的模块。
� 系统建模的内容包含了一个特定功能的模块。
举个例子,就拿串口来作个比方:
在串口硬件模块(串口系统建模)里,分类了发送接口和接受接口。发送发送接口包含了FIFO模块,波特率产生模块和串口发送控制模块。串口接收串口包含了FIFO模块,波特率产生模块,串口接收控制模块。串口发送模块是组合了波特率产生模块和串口发送控制模块,串口接收模块是组合了波特率产生模块和串口接收控制模块。
串口系统建模之间的模块基本单元分类:
串口系统建模之间的层次分类:
在这里笔者只是简介了笔者最爱的“低级建模”的结构分类而已,事实上每一个基本单元和每一个层次都有严谨的定义。建模是VerilogHDL语言的结构,越庞大的设计建模所带来的后期影响是读者/笔者远远都猜想不到的。故建模对于VerilogHDL语言来说是非常重要的基础。VerilogHDL语言的结构是自由的,然而笔者的“低级建模”是笔者自定义的结构而已。当然读者也可以建立自己的结构。
仿顺序操作是什么?就是利用VerilogHDL语言自身的特质去模仿一些顺序语言如C语言,故称为“仿顺序操作”
always@(posedgeCLKornegedgeRSTn)
if(!RSTn)
begin
i<=4'd0;
....
end
else
case(i)
0:
begin....i<=i+1'b1;end
1:
......
endcase
就以流水灯来说话吧:
always@(posedgeCLKornegedgeRSTn)
if(!RSTn)
begin
i<=4'd0;
rLED<=4'b0000;
end
else
case(i)
0:
beginrLED<=4'b0001;i<=i+1'b1;end
1:
if(Timer==T10MS)i<=i+1'b1;end
2:
beginrLED<=4'b0010;i<=i+1'b1;end
3:
if(Timer==T10MS)i<=i+1'b1;end
4:
beginrLED<=4'b0100;i<=i+1'b1;end
5:
if(Timer==T10MS)i<=i+1'b1;end
6:
beginrLED<=4'b1000;i<=i+1'b1;end
7:
if(Timer==T10MS)i<=4'd0;end
endcase
在case... endcase 之间,步骤i 等于0,2,4,6的时候是更新LED(流水操作),步骤i等于1,3,5,7的时候是延迟10ms。
此外,还可以把步骤i当成简单的循环。上面一段代码表达了,当步骤i等于0,2,4,6的时候就更新rLED,。反之,当步骤i等于1,3,5,7的时候就延迟10ms。在步骤i等于8的时候是步骤返回操作。
always@(posedgeCLKornegedgeRSTn)
if(!RSTn)
begin
i<=4'd0;
rLED<=4'b0001;
end
else
case(i)
0:
beginrLED<={rLED[0],rLED[3:1]};i<=i+1'b1;end
1
if(Timer==T10MS)i<=i-1'b1;end
endcase
又或者更进一步的压缩步骤,使得代码更直接而且更节省资源。在上面一段代码当中,只有两个步骤,亦即步骤0和1。步骤0是更新rLED,步骤1是延迟10ms,然而这两个步骤之间交互交替使而产生流水灯效果
当然,步骤i的用法不仅而已,如果把“时序”的概念引入的话:
always@(posedgeCLKornegedgeRSTn)
if(!RSTn)
begin
i<=4'd0;
Mper<=8'd0;
Mcand<=8'd0;
Sum<=8'd0;
end
else
case(i)
0:
beginMper<=Mper_Sig;Mcand<=Mcand_Sig;Sum<=8'd0i<=i+1'b1;end
1
if(Mcand==0)i<=i+1'b1;
elseSum<=Sum+Mper;Mcand<=Mcand-1'b1;end
......
endcase
以上一段代码是简单的乘法运算,Mper是乘数的暂存器,Mcand是被乘数的暂存器,Sum是累加空间。当步骤i等于0的时候初始化相关的寄存器,在步骤i等于1的时候执行乘法操作... 在这里读者也可这样说:“在T0的时候初始化相关的寄存器,在接下来的时钟执行乘法操作... (有点闷,看不太懂,实力还不够(⊙o⊙))
总结之下,这个用法可以伸缩的范围非常之大。除外,它所带来的好处也非常之多:
� 提供了VerilogHDL语言顺序操作的支持。
� 提高了模块的表达能力。
� 提供了仿顺序操作·建模的结构基础。
但是它也带来一些限制:
� 不推荐嵌套case...endcase 和if。
� 该用法不推荐出现过多在同一个模块中。
这些限制是笔者标记下来的,这之间和“低级建模”有多少关系。当然,如果读者不遵守的话也没有问题。显然这个用法也不是万能的,尤其是一些紧密的RTL级建模,如:VGA驱动,它就显得无用武之地了。事实上这个用法到目前为止,笔者还在不停的研究当中,越深入学习它,就越发现它的潜能很深...
认识RTL级设计(建模)
用凡人的话来说CLK代表了一个模块的心跳节拍,这个心跳节拍提供模块可以消耗的动力。但是CLK信号真正可以被模块所用到不是它的高电平又或者低电平,而是上升沿(低电平到高电平的变化)和下降沿(高电平到低电平的变化)。
always@(posedgeCLK)
......
对于RTL级设计来说CLK是模块的心跳,没有心跳模块就不能活动,没有心跳就没有时序图。换另一句话说,构成RTL级最基本的设计需要“寄存器”为最小的建模单位,然后再加上模块可以活动的CLK信号。
always@(posedgeCLK)
Counter1<=Counter1+1'b1;
always@(posedgeCLK1/2)
Counter2<=Counter2+1'b1;
上面有一段代码是Counter1+CLK和Counter2+CLK1/2促成最简单的RTL级设计。Counter1在每一个CLK时钟内递增,然而Counter2在每一个CLK1/2时钟内递增。到目前为止Counter1和Counter2还是独立关系。
图0.13b是Counter1+CLK和Counter2+CLK1/2产生的时序图。在这里Counter2所使用的频率是Counter1的一半。在每一个CLK的上升沿Counter1都递增,然而在每一个CLK1/2的上升沿Counter2都递增
always@(posedgeCLK)
Counter1<=Counter1+1'b1;
always@(posedgeCLK1/2)
Counter2<=Counter1;
假设笔者把Counter1和Counter2联系起“关系”的话,如上面的一段代码所述。又会产生怎样的时序图呢?
图0.13c是Counter1和Counter2建立关系以后多产生的时序图。在CLK是T0的时候,CLK_1/2也是在T0。由于在T0之前Counter1什么也没有,所以Counter什么也读不到(一般上0为复位值)。在CLK_1/2是T1,Counter2尝试读取Counter1的“过去值”,结果Counter2读到值2,所以在CLK_1/2的T1,Counter2的“未来值”是2。类似的事情也发生在CLK_1/2的T2,T3和T4的时候。在这里读者先不用管“过去值”和“未来值”的定义,这是笔者在时序篇里的专用词。读者需要焦距的是,每一次Counter1成功递增是发生在CLK的上升沿,然而Counter2每一次成功读取Counter1的值都是发生在CLK_1/2的上升沿。换句话说,CLK的上升沿是触发Counter1递增,CLK_1/2的上升沿是触发Counter读取Counter1的过去值。以上的内容就是RTL级设计最基本的思想。
至于组合逻辑级设计呢?在VerilogHDL语言中,如果我们把VerilogHDL语言看成是理想的语言,那么组合逻辑就可以直接无视被CLK的影响,因为组合逻辑取得的是即时的结果。假设读者不把VerilogHDL语言看成是“理想”的话,组合逻辑会产生“物理”上的延迟。但是笔者还是建议读者把VerilogHDL语言看成是一个理想的工具为好。换另一个角度来看的话,C语言和VerilogHDL语言都是工具,难道C语言会产生“物理”上的延迟吗?此外这样的想法对于VerilogHDL语言的设计会带来很大的好处,尤其是看懂波形图(时序图)哪一环,效果会更加明显。
原文:http://www.cnblogs.com/oomusou/archive/2008/06/17/c_verilog_mental_thinking.html
Abstract
Verilog由於在語法上向C靠攏,若熟悉C語言,學Verilog倍感親切,但也由於語法類似,若把Verilog當成C語言來思考,怎很難抓到硬體的精神。
Introduction
Verilog有3點思維與C語言不一樣
1.軟體是循序的,而硬體是並行的
C語言是一行一行的執行,就算組合語言也是一樣,或許你會說threading,但在微觀下仍是循序地執行。但硬體電路就不一樣,電路只要一插上電,所有電路就同時工作。
如以下的Verilog
2 e <= a & b;
3 f <= c & d;
4 end
雖然看起來是 e <= a & b; 在 f <= c & d;前面,但實際上合成電路後如下圖所示
由上圖得知,e和f並沒有先後之分,是並行的。
2.硬體要循序,要靠clock和FSM
或許你會說,『我的演算法就是要循序一步一步的做,如C語言那樣,那怎麼辦?』,若Verilog要這樣,就得靠clock並且搭配FSM,當一個state完成後,進入下一個state,這樣就能依照clock的進行,而達成循序的要求。
3.Verilog程式碼沒有先後之分
除了blocking assignment有先後執行順序,而nonblocking assignment同時執行外,Verilog的程式沒有前後順序之分,所以才稱為硬體『描述』語言,而非硬體『程式』語言,先寫的不代表先執行,後寫的也不代表後執行,只是代表硬體的架構的描述,也就是說,將原來的電路圖,變成文字描述而已。
4.多用RTL Viewer和ModelSim觀察自己寫的code
Verilog寫法小小的差異,合成出來的硬體就可能有天壤之別,多用RTL Viewer觀察合成出來的硬體是否和自己預期的一樣,並多用ModelSim觀察跑出來的波形,這樣會增加你對Verilog的掌握度。
Conclusion
很多人學了Verilog,還是把它當C語言寫,事實上他們只是語法類似,但背後的思維並不一樣,唯有『心中有硬體』,才能設計出好的電路。