0.10 单文件主义
单文件主义对于新手来说, 某个程度上它是一个“伟大的主义” 但是又有很多人会受限这个“伟大的主义”。单文件主义就是,所有内容的设计都是在一个模块之内完成,这一点,有点像 C 语言中 main 那样,所有动作都在 main()函数中完成。单文件主义是新手都要经过的, 当游走一段时间以后, 慢慢的我们会发现这个主义的局限性。 我们想要越过“它”,但是又不知道要如何往哪个方向 … 这就是很多新手都会遇见的“瓶颈”。
在这里, 笔者告诉读者们, 能多快就有多快远离单文件主义。 单文件主义基本上已是远离 Verilog HDL 语言的本质了,这是一个很危险的陷阱,如果读者不小心陷入,在接下来的学习路上是没有任何帮助的。 这是一个事实, 而不是笔记自身的想法。 会养成单文件主义,多半的原因还是来源于参考书,这真的使笔者很无语 …..
笔者来说说为什么单文件主义远离了 Verilog HDL 语言的本质呢? Verilog HDL 语言, 本质上是并行而且又有“面向对象” 的味道。 但是这“面向对象” 的概念和 C++语言中的概念有所不同, 然而它更接近现实中的“管理系统”(详解请看建模篇)。 读者尝试想象,
有没有可能一个系统的操作, 没有部门, 没有团队, 没有小组?对, 就是不可能。 单文件主义恰恰好就是违反了这个简单的道理。
普通人类在理解上, 都喜欢把东西分类, 然后方便于管理。 读者可以想象一个没有分类的系统, 内容究竟到底有多乱吗?是一塌糊涂的乱! 所以单文件主义的建模, 最大的缺陷就是一个字“乱”,内容真的很乱。如果 Verilog HDL 语言是顺序语言的话,结果还不至于那样糟糕,相反的, Verilog HDL 语言却是并行性质的语言 ….
上文中所说的“没有分类” 的烂漫约会,如果也“没有顺序操作” 的支持 … 会是一个
非常糟糕的情形。 因为这个系统操作(约会过程) 没有结构的支持 , 这个情形也反映出
了单文件主义的致命缺点。笔者有一句很经典的话: “解读能力差的模块是最糟糕的”,
这一句话完全迎合单文件主义下所建立的模块。
0.11 Verilog HDL 语言结构简介
在前面,笔者已经说过 Verilog HDL 语言从出生以来,它的结构是非常自由,可以说自由到没有结构。 所以学习 Verilog HDL 语言第一步就是要学会建立结构, 建立结构用另一句话说就是学习建模。同样,笔者也说过 Verilog HDL 语言我们需要自定义自己的结构,在这里就拿笔者最爱的“低级建模” 来做一些简单的扫盲。
低级建模被网友称为“自底向上” 的设计方法,其实“自底向上” 还是“自顶向下” 都无所谓啦,最重要就是建模的思路。
低级建模的基本单元有:功能模块,控制模块,组合模块。
� 功能模块的内容包含了最基本的动作。
� 控制模块的内容包含了动作的控制步骤。
� 组合模块的内容包含了所有功能模块和控制模块之间的组合。
建模层次有:基础(模块)建模,仿顺序操作建模,接口建模,系统建模。
� 基础(模块)建模的内容包含了最小功能的模块。
� 仿顺序操作建模,这一个比较特别,主要是模仿了 C 语言中的函数。
� 接口建模的内容包含了一个已经封装完成的模块。
� 系统建模的内容包含了一个特定功能的模块。
举个例子,就拿串口来作个比方:
在串口硬件模块(串口系统建模) 里, 分类了发送接口和接受接口。 发送发送接口包含了 FIFO 模块, 波特率产生模块和串口发送控制模块。 串口接收串口包含了 FIFO 模块,波特率产生模块, 串口接收控制模块。 串口发送模块是组合了波特率产生模块和串口发送控制模块,串口接收模块是组合了波特率产生模块和串口接收控制模块。
串口系统建模之间的模块基本单元分类:
在这里笔者只是简介了笔者最爱的“低级建模” 的结构分类而已, 事实上每一个基本单元和每一个层次都有严谨的定义。建模是 Verilog HDL 语言的结构,越庞大的设计建模所带来的后期影响是读者/笔者远远都猜想不到的。 故建模对于 Verilog HDL 语言来说是非常重要的基础。
好了笔者就不多话了, 有关“低级建模” 的笔记笔者已经老早准备好了。 笔者在这里只是简单的表述一下 Verilog HDL 语言结构的概念而已, 然而笔者需要再强调一下: Verilog HDL 语言的结构是自由的,然而笔者的“低级建模” 是笔者自定义的结构而已。当然读者也可以建立自己的结构。
0.12 Verilog HDL 语言使用规则(方法)简介
“一种设计,超过 10 个编辑风格” 这一句话不仅表示了一种设计有多种结构(很多设计都是单文件主义, 或者不存在结构), 而且也代表了设计中所使用的不同规则(方法)。
但是不是所有设计都有固定的使用方法,这一点读者可以诚实的问自己 : “自己的设计是否有一套的使用方法呢? ” 好吧~笔者就不故意为难了。
Verilog HDL 语言是一个非常自由的语言, 故被使用最多的方法就是“自由方法”(没有-方法) 呵呵~这是事实。 当自定义自己的方法的时候, 读者要问自己, 自己所使用的方法是“注重什么”。换做笔者,笔者注重“模块解读能力” 又或者说有一套方法可以提高模块的表达能力。故 Verilog HDL 语言的结构就是其中一环了,但是这还不够,当我们再开始设计的时候,我们还需要一套的“用法模板”。
在这里笔者向读者们推荐吧“基于仿顺序操作” 的“用法模板”(到目前为止,笔者还没有给它固定的命名,如果读者有什么好想法就告诉笔者吧 )。仿顺序操作是什么?就是利用 Verilog HDL 语言自身的特质去模仿一些顺序语言如 C 语言, 故称为“仿顺序操作”。
仿顺序操作的思想是非常有潜力的, 但是在建模篇里笔者仅是以“步骤” 作为这个用法的入门认识。 当读者对建模和时序有一定的认识以后, 读者会进一步发现这个思想非常的“变态”(潜力的另一极端用词)。 作为入门, 仿顺序操作用法都是以“步骤” 作为基本单位,这个概念和 C 语言的步骤没什么两样。
“步骤” 也可以用“简易状态机” 来理解, 但是“状态机” 的概念太死了, 笔者不怎么喜欢,还是使用“步骤” 来称呼比较情切:
always @ ( posedge CLK or negedge RSTn )
if( !RSTn )
begin
i <= 4'd0;
....
end
else
case( i )
0:
begin .... i <= i + 1'b1; end
1:
......
endcase
i 代表了“步骤的指向” 至于 case … endcase 之间是一个完成工作的经过。 笔者举个简
单的实例,就以流水灯来说话吧:
always @ ( posedge CLK or negedge RSTn )
if( !RSTn )
begin
i <= 4'd0;
rLED <= 4'b0000;
end
else
case( i )
0:
begin rLED <= 4'b0001; i <=i + 1'b1; end
1:
if( Timer == T10MS ) i <= i + 1'b1; end
2:
begin rLED <= 4'b0010; i <=i + 1'b1; end
3:
if( Timer == T10MS ) i <= i + 1'b1; end
4:
begin rLED <= 4'b0100; i <=i + 1'b1; end
5:
if( Timer == T10MS ) i <= i + 1'b1; end
6:
begin rLED <= 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。
always @ ( posedge CLK or negedge RSTn )
if( !RSTn )
begin
i <= 4'd0;
rLED <= 4'b0000;
end
else
case( i )
0,2,4,6:
begin rLED <= { rLED[0], rLED[3:1] }; i <=i + 1'b1; end
1,3,5,7:
if( Timer == T10MS ) i <= i + 1'b1; end
8:
begin i <= 4'd0; end
endcase
此外, 还可以把步骤 i 当成简单的循环。 上面一段代码表达了, 当步骤 i 等于 0,2,4,6 的
时候就更新 rLED,。反之,当步骤 i 等于 1,3,5,7 的时候就延迟 10ms。在步骤 i 等于 8
的时候是步骤返回操作。
always @ ( posedge CLK or negedge RSTn )
if( !RSTn )
begin
i <= 4'd0;
rLED <= 4'b0001;
end
else
case( i )
0:
begin rLED <= {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 @ ( posedge CLK or negedge RSTn )
if( !RSTn )
begin
i <= 4'd0;
Mper <= 8'd0;
Mcand <= 8'd0;
Sum <= 8'd0;
end
else
case( i )
0:
begin Mper <= Mper_Sig; Mcand <= Mcand_Sig ; Sum <= 8'd0i <=i + 1'b1; end
1
if( Mcand == 0 ) i <= i + 1'b1;
else Sum <= Sum + Mper; Mcand <= Mcand - 1'b1; end
......
endcase
以上一段代码是简单的乘法运算, Mper 是乘数的暂存器, Mcand 是被乘数的暂存器,Sum 是累加空间。当步骤 i 等于 0 的时候初始化相关的寄存器,在步骤 i 等于 1 的时候执行乘法操作 … 在这里读者也可这样说: “在 T0 的时候初始化相关的寄存器, 在接下来的时钟执行乘法操作 … ”
总结之下,这个用法可以伸缩的范围非常之大。除外,它所带来的好处也非常之多:
� 提供了 Verilog HDL 语言顺序操作的支持。
� 提高了模块的表达能力。
� 提供了仿顺序操作· 建模的结构基础。。
但是它也带来一些限制:
� 不推荐嵌套 case … endcase 和 if。
� 该用法不推荐出现过多在同一个模块中。
这些限制是笔者标记下来的, 这之间和“低级建模” 有多少关系。 当然, 如果读者不遵
守的话也没有问题。 显然这个用法也不是万能的, 尤其是一些紧密的 RTL 级建模, 如:
VGA 驱动,它就显得无用武之地了。事实上这个用法到目前为止,笔者还在不停的研
究当中,越深入学习它,就越发现它的潜能很深 …
好了,之一章节笔者就介绍到那么多,自定义使用规则(用法)对于 Verilog HDL 语言
来说是非常自由但也非常重要,没有固定的使用方法,模块的解读能力会大打折扣。 这
些简单的道理,也只有当读者不停深入学习 Verilog HDL 语言的时候自然就会明白。
0.13 认识 RTL 级设计(建模)
笔者差不多也要结束这一章 Verilog HDL 语言的扫盲文了,在结束之前笔者或多或少都有责任加强读者们对 RTL 级设计(建模) 的认识。 在前面, 笔者曾经说过 RTL 级建模最受注意的特征就是“时钟” 亦即 CLK 信号, 要明白 CLK 的定义笔者就要乘坐时光机回到读学院的时候。
用凡人的话来说 CLK 代表了一个模块的心跳节拍,这个心跳节拍提供模块可以消耗的动力。但是 CLK 信号真正可以被模块所用到不是它的高电平又或者低电平,而是上升沿(低电平到高电平的变化)和下降沿(高电平到低电平的变化)。
always @ ( posedge CLK )
......
在上面一段代码中的 always @ ( posedge CLK ) 表达了 always @ () 以下的内容在每一个 CLK 的上升沿发成操作:
假设有一串长长而且连续的 CLK 就会产生最基本的时序图(没有 CLK 基本上是没有时序图),如图 0.13a 所示。
对于 RTL 级设计来说 CLK 是模块的心跳, 没有心跳模块就不能活动, 没有心跳就没有时序图。 换另一句话说, 构成 RTL 级最基本的设计需要“寄存器” 为最小的建模单位,然后再加上模块可以活动的 CLK 信号。
always @ ( posedge CLK )
Counter1 <= Counter1 + 1'b1;
always @ ( posedge CLK1/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 @ ( posedge CLK )
Counter1 <= Counter1 + 1'b1;
always @ ( posedge CLK1/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 级设计最基本的思想。
至于组合逻辑级设计呢?在 Verilog HDL 语言中,如果我们把 Verilog HDL 语言看成是理想的语言,那么组合逻辑就可以直接无视被 CLK 的影响,因为组合逻辑取得的是即时的结果。
举个简单的组合逻辑级的设计:
always @ ( * )
Sum = A + B + C
这是一个由组合逻辑所组成的简单 3 路加法器。
上面的图标表示了3路加法器求得的即时结果 … 在这里CLK信号对于它来说已经再也不重要了。
假设读者不把 Verilog HDL 语言看成是“理想” 的话, 组合逻辑会产生“物理” 上的延迟。但是笔者还是建议读者把 Verilog HDL 语言看成是一个理想的工具为好。换另一个角度来看的话, C 语言和 Verilog HDL 语言都是工具, 难道 C 语言会产生“物理” 上的延迟吗?此外这样的想法对于 Verilog HDL 语言的设计会带来很大的好处,尤其是看懂
波形图(时序图)哪一环,效果会更加明显。
0.14 过渡中,沉住气!朋友!
到了这一章节笔者不知不觉又寂寞了,基本上有关 Verilog HDL 语言的扫盲文需要告一段落了。 学习 Verilog HDL 语言不像学习一些高级语言, 对于高级语言来说它们已经是完成品了,其外它们还有很多被隐藏的指令,这些好处无疑是减轻了学习者的负担。 相反的 Verilog HDL 语言既是完成品,既不是完成品,就是因为它太自由了 … 所以往往会让学习者感到疑惑,很疲惫和浮躁(我不学了!)。
学习 Verilog HDL 语言需要一段过渡期的,快则半年,普通则 1~2 年,慢则很多年。即使经过了过渡期这也不表示已经掌握 Verilog HDL 语言了。 所以呀朋友, 希望你们可以沉住气, “欲速则不达” 这是老祖先的智慧,它非常适合用在学习 Verilog HDL 语言的路上。 Verilog HDL 语言可以延长到的范围完完全全超过参考书的内容,对于笔者来说也看不到尽头。
那些有学习单片机经验的朋友, 最好不让学习单片机的思想主宰了你。 就如笔者在前面所说的那样, Verilog HDL 语言既不是顺序语言而且也非常的自由, Verilog HDL 语言不像 C 语言那样有丰富的库支持, 甚至库的概念也不适合用在它身上。 但是即使它们是两个世界的居民, 但是偶尔 Verilog HDL 语言可以在很多地方向 C 语言借签。 相反的 C 语
言就不能向 Verilog HDL 语言借签了。
此外 Verilog HDL 语言还有两大阵列,就是综合语言和验证语言,这更是给学习者雪上加霜。太多的学习者会困惑在这两种语言的中间,所谓的困惑是思路的困惑。在这里,笔者建议先无视验证语言, 先把综合语言学好(综合语言也没有什么好学的, 就如在前面章节笔者所举例的那样, 关键字和操作符少得可怜), 最重要还是掌握结构(建模)和使用规则(用法)。它们就像挥动着倚天剑和屠龙宝刀的招式,没有了这些招式倚天剑和屠龙宝刀不过是一件单纯的金属而已。 至于验证语言, 在未来有需要的时候再学也不迟。
当阅读他人模块的时候, 不要过于转牛角尖的看懂他人的思路, 只要明白其中的内容就好。最重要还是如何使用自己的结构和方法去建立他人的思路,从中读者会学得更多。这一点是绝对的事实。 就算现在的读者没有能力建立自己的结构也没有关系, 来日方长读者有的就是时间。 如果读者很钟爱笔者所建立的结构和方法, 笔者很乐意也很荣欣被应用(这也是笔者写笔记的初衷)。
最后的最后还是那么一句话,沉住气朋友,掌握 Verilog HDL 语言需要的不只是技术而已, 最重要是那颗安静的心, 安静的心会带读者乘风破浪, 一方通行。 此外记录笔记的习惯更为重要, 向自己学习比起向他人学习更有学习的价值。 如果有时间的话, 就坐下来研究研究 Verilog HDL 语言这个怪家伙吧。
总结:
这一章终于到总结了, 从第一章节到最后一个章节, 从最简单的问题:“什么是 HDL 语言? ”, 直到一些困惑度极高的问题: “Verilog HDL 语言的结构? ”,“Verilog HDL 语言的使用规则(用法)? ”,甚至学习 Verilog HDL 语言该保持的心情,笔者也不放过。
不过这短短的二十几页, 应该足够达到扫盲的作用了吧?虽然这一章的扫盲文, 效果不足覆盖所有言内容, 但是只要有效的开了一个入门的口子, 对于接下来的路就简单许多。
人们常说: “入门,入门”,学习要从入门开始,如果门都没有,又要如何进入呢?
笔者学习 Verilog HDL 语言也有一段时间了,对于入门的心得多少都有。所以笔者觉得比起如何入门,先知道个大概来得更重要。有一句笑话说过 : “既然你要入门,你至少需要知道,你要进入那一扇门的形状?圆的还是方的?要是不清楚,就算有门在哪儿,你也进不了门, 结果“进入门的门都没有” …… ” 所以说, 扫盲的作用是把目标建立出大概的轮廓 … 不然真的进入门的门都没有~啊哈哈哈!好了好了,笔者也不多话了, 是时候画上句号了。