VerilogHDL扫盲

以下大部分内容摘自VerilogHDL扫盲篇:

 

学习VerilogHDL语言不像学习一些高级语言,对于高级语言来说它们已经是完成品了,其外它们还有很多被隐藏的指令,这些好处无疑是减轻了学习者的负担。相反的VerilogHDL语言既是完成品,既不是完成品,就是因为它太自由了... 所以往往会让学习者感到疑惑,很疲惫和浮躁(我不学了!)。学习VerilogHDL语言需要一段过渡期的,快则半年,普通则1~2年,慢则很多年。即使经过了过渡期这也不表示已经掌握VerilogHDL语言了。所以呀朋友,希望你们可以沉住气,“欲速则不达”这是老祖先的智慧,它非常适合用在学习VerilogHDL语言的路上。VerilogHDL语言既不是顺序语言而且也非常的自由,VerilogHDL语言不像C语言那样有丰富的库支持,甚至库的概念也不适合用在它身上。但是即使它们是两个世界的居民,但是偶尔VerilogHDL语言可以在很多地方向C语言借签。相反的C语言就不能向VerilogHDL语言借签了。此外VerilogHDL语言还有两大阵列,就是综合语言和验证语言,这更是给学习者雪上加霜 太多的学习者会困惑在这两种语言的中间,所谓的困惑是思路的困惑。在这里,笔者建议先无视验证语言,先把综合语言学好(综合语言也没什么好学的,就如在前面章节笔者所举例的那样,关键字和操作符少得可怜),最重要还是掌握结构(建模)和使用规则(用法)。它们就像挥动着倚天剑和屠龙宝刀的招式,没有了这些招式倚天剑和屠龙宝刀不过是一件单纯的金属而已。至于验证语言,在未来有需要的时候再学也不迟,当阅读他人模块的时候,不要过于转牛角尖的看懂他人的思路,只要明白其中的内容就好。最重要还是如何使用自己的结构和方法去建立他人的思路,从中读者会学得更多。这一点是绝对的事实。就算现在的读者没有能力建立自己的结构也没有关系,来日方长读者有的就是时间。如果读者很钟爱笔者所建立的结构和方法,笔者很乐意也很荣欣被应用(这也是笔者写笔记的初衷)。

 

沉住气朋友,掌握VerilogHDL语言需要的不只是技术而已,最重要是那颗安静的心,安静的心会带读者乘风破浪,一方通行。此外记录笔记的习惯更为重要,向自己学习比起向他人学习更有学习的价值

 

 

一,建模是VerilogHDL语言的中心思想之二

二,时序是VerilogHDL语言的中心思想之二

 

在笔者的眼中,总结上C语言和VerilogHDL语言之间的区别会是如上的图表。关于高级语言和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的值综合。

VerilogHDL扫盲

 

更有趣得是,经过编译两段代码所生成的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++语言中的概念有所不同,然而它更接近现实中的“管理系统”(详解请看建模篇)。读者尝试想象,有没有可能一个系统的操作,没有部门,没有团队,没有小组?对,就是不可能。单文件主义恰恰好就是违反了这个简单的道理。

VerilogHDL扫盲

 

VerilogHDL扫盲

上文中所说的“没有分类”的烂漫约会,如果也“没有顺序操作”的支持... 会是一个非常糟糕的情形。因为这个系统操作(约会过程)没有结构的支持, 这个情形也反映出了单文件主义的致命缺点。笔者有一句很经典的话:“解读能力差的模块是最糟糕的”,这一句话完全迎合单文件主义下所建立的模块。

 

 

低级建模的基本单元有:功能模块,控制模块,组合模块。

� 功能模块的内容包含了最基本的动作。

� 控制模块的内容包含了动作的控制步骤。

� 组合模块的内容包含了所有功能模块和控制模块之间的组合。

 

建模层次有:基础(模块)建模,仿顺序操作建模,接口建模,系统建模。

� 基础(模块)建模的内容包含了最小功能的模块。

� 仿顺序操作建模,这一个比较特别,主要是模仿了C语言中的函数。

� 接口建模的内容包含了一个已经封装完成的模块。

� 系统建模的内容包含了一个特定功能的模块。

 

举个例子,就拿串口来作个比方:

VerilogHDL扫盲

在串口硬件模块(串口系统建模)里,分类了发送接口和接受接口。发送发送接口包含了FIFO模块,波特率产生模块和串口发送控制模块。串口接收串口包含了FIFO模块,波特率产生模块,串口接收控制模块。串口发送模块是组合了波特率产生模块和串口发送控制模块,串口接收模块是组合了波特率产生模块和串口接收控制模块。

 

串口系统建模之间的模块基本单元分类:

VerilogHDL扫盲

串口系统建模之间的层次分类:

VerilogHDL扫盲

 

在这里笔者只是简介了笔者最爱的“低级建模”的结构分类而已,事实上每一个基本单元和每一个层次都有严谨的定义。建模是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还是独立关系。

VerilogHDL扫盲

图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联系起“关系”的话,如上面的一段代码所述。又会产生怎样的时序图呢?

VerilogHDL扫盲

 

图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

1 always@(posedge clk) begin
2   e <= a & b;
3   f <= c & d;
4 end

雖然看起來是 e <= a & b; 在 f <= c & d;前面,但實際上合成電路後如下圖所示

verilog_c.gif

由上圖得知,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語言寫,事實上他們只是語法類似,但背後的思維並不一樣,唯有『心中有硬體』,才能設計出好的電路。 

 

 

 

 

原文链接: https://www.cnblogs.com/zhliao/archive/2012/04/19/2457059.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    VerilogHDL扫盲

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/48050

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月8日 下午11:56
下一篇 2023年2月8日 下午11:56

相关推荐