黑金Xilinx FPGA学习笔记(一)verilogHDL扫盲文-(1)

verilog简介
HDL 顾名思义Hardware Description Languag
verilog HDL 语言的语法和格式都比较随便,它没有 VDL HDL 语言那么严谨,因此受到了广泛的应用。
0.3RTL级和组合逻辑级
笔者的眼中 Verilog HDL 语言建立的硬件模块可以
分为有时钟源和无时钟源。有时钟源的意思是需要时钟信号作为操作最基本消耗单位, 硬件模块才能执行。 无时钟源的意思就是不需要消费时钟信号, 硬件模块也可以被执行。 无时钟源最好的例子就是由组合逻辑建立的硬件模块, 一些典型的设计如: 硬件乘法器, 硬件除法器等基本上都是由一些复杂的逻辑门组合, 故它们根本就不需要时钟来操作, 或者说它们只需要一个步骤又或者只需要一个时钟就可以完成任务。

由此就有 RTL 级和组合逻辑级的建模之分。 基本上 RTL 级建模的基本单元会是“寄存器”, 然而组合逻辑级的建模基本单元就是逻辑门。 单单用几行的文字来讲述 RTL 级和组合逻辑级的建模实在太抽象了,笔者聚个简单的例子

module Add_Module( input [7:0]A, input [7:0]B, input [7:0]C, Output[15:0]);
    wire [7:0]_a = A;
    wire [7:0]_b = B;
    wire [7:0]_c = C
    assign Output = _a + _b + _c;
endmodul

这是一个简单的组合逻辑级,建模而成的 3 路加法器。

module Add_module
( input CLK, input RSTn, input [7:0]A, input [7:0]B, input [7:0]C, Output[15:0]) ;
    reg [15:0]rTemp;
    alawys @ ( posedge CLK negedge RSTn )
        if( !RSTn )
            rTemp <= 4'0;
        else
            rTemp <= A + B + C;
    assign Output = rTemp;
endmodul

这是一个简单的 RTL 级,建模而成的 3 路加法器。
根据上面两个例子的三路加法器, 一个是由组合逻辑级建模而成, 另外一个则是由 RTL级建模而成。 组合逻辑级建模给人最直接的印象就是模块都不带“时钟信号”, 反之 RTL级建模的最大特征性,就是模块都会伴随“时钟信号”。当然,笔者不可能仅以“时钟信号” 来区分组合逻辑级建模和 RTL 级建模。
在实际的学习中过于在意区分“什么是组合逻辑级建模,什么是 RTL 级建模” 是对学习没有任何帮助的(不知道为什么很多参考书都很习注重区分), 凡是有关 Verilog HDL语言的建模它们都被需要。话虽如此,但是在这一本笔记里笔者还是比较注重 RTL 级
的建模。
0.4 Verilog HDL 语言真的那么难掌握?
Verilog HDL 语言容易入门但是不容易掌握,估计这是所有学习 Verilog HDL 语言人们的心声。 其实要掌握 Verilog HDL 语言是很简单的, 笔者的秘诀就是“掌握 Verilog HDL语言的思想”。 在笔者眼中 Verilog HDL 语言的思想有两种, 一种是建模和另一种是时序。建模是 Verilog HDL 语言的结构或者说是它的地基,然而时序是所有模块的活动记录。这些概念,目前的读者不明白也不要紧,笔者已经分为两个笔记来详谈了它们了。
笔者相信很多接触 FPGA 的朋友之前都有接触过单片机,在不知不觉之中,自然而然
学习单片机的想法就主宰了学习 FPGA。 笔者也是过来人, 这样的心情笔者非常的了解。
当我们再学习单片机的时候, 很多人估计都是直接以 C 语言入门吧?笔者就先说说单片Verilog HDL 的礼物 - Verilog HDL 扫盲文
笔者博客 : http://blog.ednchina.com/akuei2 黑金动力社区 : http://www.heijin.org
6
机这个硬件吧: 它们都是各大产商的产品, 一些基本的硬件资源老早就已经嵌入在这个小小的单片机当中。 余下, 尤其说学习单片机还不如说我们学习如何控制单片机的寄存器更为贴切。 一些硬件的发生, 使用者可以完全不用知道, 我们只要懂得如何配置控制该硬件的寄存器就可以了。
相比之下 FPGA 可以称为赤裸裸的乐高积木, 如果读者要实现串口, 读者就要自行建立串口硬件模块。 要建立一个串口硬件模块, 首先需要明白串口的操作原理, 然后根据需要自定义和修改原理的发生。 最后还要考虑这个串口硬件模块如何被控制?是否独立化它(见接口建模)?这些就是建模思路。
在软件方面, 我们可以用 C 语言去配置单片机的寄存器和编辑单片机操作的逻辑。 但是有一点请读者不要忘了, C 语言也是一个产品, 它和 Verilog HDL 语言不同 C 语言在出
生之后它就有自己的结构和自己一套的使用规则。 最后根据单片机的产商需要, C 语言还可以进一步被自定义, 这种现象不难看见, 学习 c51 和学习 AVR 基本上就有两套的C 语言用法。
反之 Verilog HDL 语言虽然有语法, 但是 Verilog HDL 语言就没有自身的结构也没有自己一套的用法。 在网上这种现象很普遍, 读者会看到五花八门, 百花齐放, 各种各样的模块内容。 很多时候, 这些模块的内容只有设计者自己看懂而已, 别人估计要花上几倍的精力才可以搞明白“它在干嘛”。
为此, 如果要把 Verilog HDL 语言掌握好, 第一要提供 Verilog HDL 语言的结构, 第二要提供 Verilog HDL 语言一套用法(使用规则)。前者笔者是用建模来解决,后者笔者用“仿顺序操作” 的想法来补充。
0.5 高级语言和 Verilog HDL 语言的区
一些高级语言如 C 语言,组成操作行为的基本单位是“步骤”,举个例子:

步骤 1 sum1 = a + b + c;
步骤 2 sum2 = d + e + f

在步骤 1 sum1 赋值与 a + b + c,然而在步骤 2 sum2 赋值与 d + e + f; 在使用者眼中,
C 语言只要两个步骤就可以把操作完成。 反之在使用者不可见之下, 步骤 1 和步骤 2 总
和所生成的指令估计会超过 8 个, 然而单片机一个时钟只能执行一个指令而已, 亦即要
完成步骤 1 和步骤 2 至少需要超过 8 个时钟才能完成。
对于使用者来说, 他们只是关心步骤而已, 反而不会过于关心“一共被执行了多少指令”
和“消耗了多少个时钟”。相反的对于 Verilog HDL 语言来说,尤其是 RTL 级的建模,
时钟代表了模块执行所要消耗的单位。

case(i)
0:
begin Sum1<=a+b+c;Sum2<=d+e+f;i<+i+1'b1;end

在一个时钟之内 Sum1 和 Sum2 同时赋值与 a+b+c 与 d+e+f。

case( i )
0:
begin Sum1 <= a + b + c; i <= i + 1'b1; end
1:
begin Sum2 <= d + e + f; i <= i + 1'b1; end

在第一个时钟之内 Sum1 赋值与 a+b+c;在第二个时钟之内 Sum2 赋值与 d+e+f;
在上面两段简单的代码之中, Sum1 和 Sum2 可以在一个时钟内求得,又或者 Sum1 和
Sum2 可以分开两个时钟之内完成赋值。换句话说,理论上 Verilog HDL 语言在一个时
钟之内可以完成“很多很多很多” 的操作又可以完成单一操作, 因为它的操作是由逻辑
资源组成的, 只要 FPGA 的逻辑资源允许的话, 读者要在一个时钟内完成 1 万个 1 亿个操作都没有问题。这个事实也暴露了 Verilog HDL 语言是有并行的性质。

比较属性 C 语言 Verilog HDL 语言
最小单位 指令 逻辑资源
单个时钟可以执行的操作 一个指令(简单指令) 理论上是无限个操作
(并行操作的性质)
结构 有固定的结构 没有固定结构
使用规则 有固定的使用规则 没有规定使用规则
强度 软 偏
在笔者的眼中, 总结上 C 语言和 Verilog HDL 语言之间的区别会是如上的图表。 关于高
级语言和 Verilog HDL 语言区别的内容笔者讨论到这里就好了,读者不要过于深入区分谁是谁, 谁又不是谁, 如此纠结对学习没有任何好处, 更多认识, 当读者们深入以后就会自然了解。
0.6 什么是 Verilog HDL 语言的时序?

时序是 Verilog HDL 语言的中心思想之二,在死板的教科书上时序图等于逻辑波形图,在某种程度上这样的解释没有任何错误。 但是, 这种的解释太过武断了, 类似“鬈发的小孩都是顽皮的小孩(笔者是鬈发小孩)” 的解释。时序也可以称为是“模块的活动记录”, 又或者说: 把每一个时钟中的模块活动记录都链接起来的话, 就会形成俗称的“波形图”。
从另一个角度来看的话, 在长长的时序图里, 包含了模块的活动规则, 模块的沟通记录等一些贵重的信息。 老实说, 有关 Verilog HDL 语言与时序的概念, 新手确实很难掌握。因为传统的印象中“时序” 给人的概念是发生在物理上的, 既有延迟, 又有亚太事件(亚
稳态) …. 等。虽然这也是时序的“一番” 概念,但是这是发生在物理上而已,然而发生在 Verilog HDL 语言身上的时序是“理想”的, 是不存在任何瑕疵。 我们知道驱使(RTL级) 模块行动的最小单位就是时钟, 即时发生延迟也只是延迟一个时钟, 又或者 2~3 个
时钟,绝对不会出现什么延迟 2ns~3ns (物理路径延迟)等的问题。
在笔者的眼中“时序” 的思想非常简单, 就是如何读懂波形图, 如何把波形图和模块的内容作联系。但是很遗憾,在这一本笔记里笔者只是详谈建模而不讨论任何和“时序”
有关的话题。 原因很简单, 就是“建模是 HDL 语言的所有” 如果读者都搞不懂建模到底是什么一回事,模块的内容估计都会“惨不忍睹”。就算读者搞明白什么是时序(在不懂建模的概念之下)结局是什么意思也没有,这一点笔者不会骗你的 …
取而代之笔者用“步骤” 作为笔记的中心 … 嗯~还有一点, 笔者所设计的实例都是直接下载到开发板, 运行并且观察结果。 如果读者在仿真上遇到什么问题, 千万不要来找笔
者噢,呵呵~(如果读者无法很好掌握“时序” 的概念,仿真会非常不好使的。)
更多有关“时序” 的话题, 笔者已经准备在另一本笔记里了。 所以, 笔者真的真的很有诚意的求求你们,先把建模学好再谈什么仿真 …
0.7 Verilog HDL 的综合语言
Verilog HDL 语言有两个部分, 是综合语言和验证语言。 有关验证语言笔者就不谈了 …说了都感觉恶心, 它已经伤了笔者很多的心。 至于综合语言也就是建模最常用的, 在这
里笔者比较习惯称为它们建模语言。
综合语言常用的关键字不多,笔者就随便举例

属性 关键字
模块建立 module … endmodule
输入输出声明 input , output , inout
判断 if … elseif … else
case() … endcase
资源声明 reg , wire
基本操作用 always@() , posedge , negedge, assign , begin … end
常量声明 paramete
至于综合语言常用的操作符也不多,笔者也随便举例:
属性 操作符
赋值 <= , =
逻辑判断 > , < , <= , >=, != , ==
下标 []
位操作 {}, <<, >>
数学运算 +, - , *, /, %
其他 ?:(三目), *(声明组合逻辑用)

当读者看了笔者“随便举例” 的综合语言以后, 是不是会傻眼?不要怀疑, 只要懂这些
东西就已经足够了。事实上真正使得读者们产生疑惑感的是读者们手上用的参考书。 这
些砖家叫兽有的没的, 为了“权威” 什么事情都干, 笔者没有损毁他们的意思, 事实上
很多参考书都是参考来又参考去, 内容都是换汤不换药。 更恶心的是这些参考书还添加
了许多看不懂的内容,买的人往往都是最伤神和伤身的(钱包也一样伤 )。不过也有一
个例外就是夏教授写的书确实很经典。

我们先谈谈几个比较有趣的关键字和操作符:

例子 1 - reg 和 wire 的尴尬:
reg 和 wire 如果站在 RTL 级建模的角度上, reg 就是寄存器,作用是用来暂存内容,而且也提供操作空间; wire 就是连线, 作用仅此而已。 但是站在组合逻辑级建模上 reg和 wire 已经是傻傻分不清楚了,举个例子:

module omg_module ( output [7:0]Q );
    wire [3:0]A = 4'd0;
    wire [3:0]B = 4'd2;
    wire [3:0]C = 4'd3;
    assign Q = A + B + C;
endmodule

以上的一段代码,请问 wire 的作用是连线还是寄存内容了?呵呵,笔者没有说这样的
使用方法有错呀。 在这里笔者只是提出一个有趣的例子而已, 对于一些初学者来说可能
会非常的疑惑,尤其是那些习惯“面向对象” 思想的人们 …

module omg_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
assign Q = A + B + C;
endmodule

如果笔者换成这样写法的话,是不是觉得更有道理呢? always ( * ) 的使用暗示了这是一个组合逻辑, 然而寄存器 A 的值是 4’d0, 寄存器 B 的值是 4’d2,寄存器 C 的值是4’d3, Q 的驱动输出是寄存器 A,B,C 的值综合。
黑金Xilinx FPGA学习笔记(一)verilogHDL扫盲文-(1)_第1张图片

更有趣得是,经过编译两段代码所生成的 RTL 视图都一样!? (⊙o⊙)? … 在这里,笔者是要告诉读者一个信息, 当我们建模的时候“解读能力” 的优先级往往都高过“内容精简性”。不要一度过于“贪小便宜” 而把内容的解读能力跨下了,虽然这两个代码都没有错,而且结果一致。但是一个潜在的问题是,我们人类都是习惯把“东西分类” 以便管理和理解,既然 wire 在字面上都是“连线” 的意思了,就干脆把它当“连线” 来
使用 …

例子 2 - always @ () 的多样性
1. always @ (posedge CLK or negedge RSTn) // 当 CLK 和 RSTn 变化的时候
2. always @ ( * ) // 什么时候都变化, 亦即默认为组合逻辑
3. always @ ( A ) // 当 A 变化的时候
always @ () 的用法很多,但是用得最多的就是第 1 个和第 2 个。
关于第一个用法, 表示了在 always @ ()之下的动作, 对每一个 CLK 的上升沿或者 RSTn
的下降沿都被执行。笔者给出一些比较简单的例子:

always @ ( posedge CLK or negedge RSTn )
if( !RSTn )
Counter <= 8'd0;
else
Counter <= Counter + 1'b1;

这是一个简单的计数器实例, 当 RSTn 产生下降沿 Counter 就清零, 反之在每一个 CLK的上升沿 Counter 就递增。 always @() 这样的用法也给 RTL 级建模提供了基础,此外我们也可以经过自定义产生出另类的用法。

always @ ( posedge CLK or negedge RSTn )
    if( !RSTn )
    begin
        i <= 4'd0;
        .......
    end
    else
        case( i )
            0:
            .......
    endcase

上面一段代码是笔者最爱的“基于仿顺序操作想法” 的基本“用法模板”,在后期里它可是大展拳脚。

always @ ( * ) A = 4'd9; // 常数赋值

always @ ( * ) // 选择器
    if( Start_Sig[0] ) rQ = U1_Q;
    else if ( Start_Sig[1] ) rQ = U2_Q;
    else Q = 1'bx;

至于第二种 always @ () 的用法, 笔者估计它是用得最“泛滥” 了, 尤其是在组合逻辑
建模里,不过笔者使用的比较不多, 除了“常数赋值” 和“输出选择器” 以外。
最后的第一种 always @()的用法,基本上只要懂得上面两种 always @ () 用法以后,自
然而然也会了。
例子 3 - 最头疼的 = 和 <= 赋值
基本上要搞懂这两个赋值操作符号的作用,就必须把“时序” 的概念搞懂先。一般上,参考书只是告诉我们一个是非阻塞, 一个是非阻塞 …. 说实话, 当笔者把厚厚的参考书吃完以后,笔者完全还搞不懂究竟参考书都在说什么(砖家叫兽万岁 )。如果从笔者的角度去理解的话, 只有在时序的活动中才可以很清楚的看清它们的区别。 宏观上, 如同参考书中所说的一样; 微观上, 在时序中“=” 是引发“即时事件” ,“<=” 则是引发“时间点事件”。
不过很可惜的是,在建模篇里笔者没有过多的讨论它们。一律有关 RTL 级活动的使用“<=” ,一律有关组合逻辑级的活动都使用“=”。如果笔者在建模篇里过度涉及它们,笔记的目的就会本末倒置, 这一点请读者们见谅。 笔者已经在时序篇里准备好来讨论它们了。
例子 4 - 要慎用的 * / % 数学运算符
当读者使用 * / 和 % 的数学运算符的时候,笔者请你们再三的三思(九思?呵呵!),因为使用它们的代价很大。 如果读者所使用的 FPGA 有内嵌硬件乘法器又或者除法器的话, 那么这些乘法器和除法器就会被消耗。 相反的, 如果读者说使用的 FPGA 是没有包含这些东西的话,资源逻辑的消耗是很大的。一般上, 如果是为了求出 *2 *4 *8 *16 又或者 /2 /4 /8 /16 笔者建议使用位操作的运算符, 亦即 << 和 >> , 它们也可以求出同样的结果。 如果想要求出的结果是不在 2N 范围之内的话, 读者还是求与其他的方法 … 只有在用尽办法的时候才使用它们。 至于 %的数学运算符,它真的是一个罪恶,没有可以替代的第三方法 ….
在这里笔者要强调一下,笔者不是说不可以使用它们,笔者只是建议如果可以的话不要过度依赖它们,它们尽是一些逻辑资源的大食怪,不把资源吃光才怪。
0.8 关于参考书和读者的笔记
无论读者是浏览参考书也好, 笔者的笔记也好, 还是他人大大的笔记也好, 不要过度把HDL 语言看得太死, HDL 语言不适合这一套的。但是一些新手,尤其是上路一段时间的新手, 笔者非常建议能静下心来好好阅读笔记中的每一章节。 笔者不是按“项目的味道” 去编辑笔记,而是按“学习的味道” 去编辑笔记,内容都是在围绕“Verilog HDL语言是什么?如何用好它? ” 之类的重点。
0.9 不要带偏见去学习 Verilog HDL 语言
某个声音:
“C 语言驱动的东西,既然用 Verilog HDL 语言去驱动!?省了吧 …”
“Verilog HDL 语言能不能像 C 语言这样调用子程序? ”
“Verilog HDL 很像 C 语言 …”
“你怎么可以违背参考书 …. 怎么不使用状态机? ”
上述的声音很容易在初学 Verilog HDL 语言的时候常常被听到。 笔者还记得笔者在初学Verilog HDL 语言的时候,印象最深刻的一句话就是“Verilog HDL 语言很像 C 语言, 但是不要把 Verilog HDL 语言当成 C 语言”。这一句话一直在后期的学习中,给笔者许多的灵感。
在许多本笔记中,笔者常常说 C 语言和 Verilog HDL 语言是两个世界的居民,但是笔者又时不时借签 C 语言,这其中真的充满了很多矛盾 … 很多新手都说 C 语言和Verilog HDL 语言既然是不同的东西, 自然而然 Verilog HDL 语言的作为和 C 语言的作为是 180° 相冲的。 话句话说, Verilog HDL 语言可以应用的地方只适合“逻辑和底层设计” …. 不不不,这是天大的误会。
就这样,随之又产生“C 语言驱动的东西,既然用 Verilog HDL 语言去驱动” 类似的声音。是谁规定 C 语言可以驱动的东西, Verilong HDL 语言就不能驱动?相反的, C 语言可以驱动的东西, 如果读者也能使用 Verilog HDL 语言去驱动, 那么这才是真正的学习。之所以会产生如此的声音,就如笔者在前几章节讲述的那样:
“Verilog HDL 语言的结构自由,使用方法也自由,自由到好像没有一样”
很多的设计都不包含结构和使用方法,只要设计可以发挥预期般的效果就 Okay ~ 如果读者明白了这个简单的道理,读者自然会明白自定义 Verilog HDL 语言的结构和使用方法是非常的重要和基础。 很可惜呀, 这一切的问题都要归咎于参考书, 因为参考书从来不考虑这一点, 傻乎乎的读者就这样遭殃了。 事实上 Verilog HDL 语言可以实现如同 C语言那样调用子程序.. 但是问题就在于没有结构和没有使用方法, 所以才会感觉困难而已。
笔者非常非常的建议, 不要把参考书当着神来膜拜, 即使是最常用的状态机, 也有非常多的缺陷。这些缺陷在后期建模的时候,会如显的突出 … 代码臃肿和解读能力下降等问题。不知道大伙有没有看过如同“蜘蛛网” 的状态机的关系图?呵呵 … 参考书所说的一切都不是绝对的,它们可供参考,但是不可供 “迷信”。读者要使用状态机也好,还是不使用状态机也好,完全是自由。
Verilog HDL 语言的建模不是越复杂就越伟大,反之越直接的建模才是学习的方向。在这里, 听笔者说: “当你放下偏见, 你才可以接触到真理”, 这简单的智慧在哪里都行得通,学习 Verilog HDL 语言也是这样一回事。“放下一切对 Verilog HDL 语言学习的偏见吧,阿门” ~ 呵呵!

你可能感兴趣的:(FPGA,verilog)