本例是进行四位带异步清零和同步置位的计数器的设计。
其中参考了很多不同的开发板设计。下面使用Quartus9.0对他们进行逐一编译,然后观察代码和RTL的对比,从中可以发现其相同和不同点处,并进行代码风格的优劣判断和学习。
1> 首先来看一种普通的计数器写法,写法比较通用,不论是开发板(青创,特权等同学)还是书本(各大教材)都会使用这种方式写。
module cnt4_1( //Input Ports input iclk, input irst_n, input ienable, //Output Ports output reg [3:0] ocount ); //=========================================================================== //wire, reg, parameter in the module //=========================================================================== //=========================================================================== //Logic //=========================================================================== always @ (posedge iclk or negedge irst_n) begin if (!irst_n) //0 reset ocount <= 4'b0000; else if (ienable) ocount <= ocount+1; end endmodule
红色部分是关键语句,从中可以看到,红色语句实际上是一个加法器,应该是一个组合逻辑,但是在always块里面通过非阻塞赋值来让读着能够理解其顺序含义。即在时钟上升沿,如果irst_n为高电平,并且ienable为高电平,则计数器加1.
代码的意思被很容易的理解。而把代码变成网表是通过quartus的描述理解能力(不是C语言编译器那种解释能力),在有少量逻辑表达的情况下,这种做法可以让读者顺利和快速的知道代码的含义。但是当代码量多的时候,反而不容易理解了。
2> 接下来的例子是由红星开发板提供的例程,其教程说代码风格参考了大公司内部的代码风格我认为是真实的.同样是计数器的描述:
`define UD #1 //这个用法是十分有效的,在使用modelsim的时候就可以十分逼真的感觉出寄存器的延时状态,而综合时又不会对其综合造成任何影响! module cnt4_2( //Input Ports input iclk, input irst_n, input ien, //Output Ports output reg [3:0] out4 ); //=========================================================================== //wire, reg, parameter in the module //=========================================================================== reg [3:0] out4_N; //=========================================================================== //Instance //=========================================================================== //=========================================================================== //Logic //=========================================================================== always @ (posedge iclk or negedge irst_n) begin if (!irst_n) out4 <= `UD 2'h0; else out4 <= `UD out4_N; end always @ (*) begin if (ien) out4_N = out4 + 2'h1; else out4_N = out4; end endmodule
在观察他的大量例子发现,不论什么方式,都是采用这种写法.首先增加一个reg out4_N.对于时序部分,则只做一件事,那就是如果rst_n不是低电平的话,就把out4_N 赋值给 out4.而加法器的部分和ien使能单元则放在了 红色标号的位置.这样做的目的就是为了把时序逻辑和组合逻辑从代码的角度完全隔离.这样,时序逻辑的功能就变得异常简单,仅仅是把 out4_N赋值给out4.
对于verilog来说,不论是组合逻辑还是时序逻辑,”=”号左边的reg或wire都是RTL图中器件右边的部分,而”=”号右边的reg或wire都是RTL图中器件左边的部分.
所以对于寄存器而言. out4_N 代表了寄存器的左边, 而out4 代表了寄存器的右边.反之亦然.
更加值得说明的是,Verilog和C语言不一样.它的描述最后完全是依赖编译器的理解能力,而不是读者的理解能力,不同的代码极有可能呢描述出一份完全相同的门级网表.这完全取决于quartus的理解能力.所以代码应该写的尽量让quartus更好的理解.而不是追求代码的高效和简洁.
所以将时序逻辑和组合逻辑在代码层就进行分开编写我认为是十分优秀的代码写作方式.因为在实际的RTL中,时序逻辑只有这一种功能.就是在clk来临时把左边的值赋值给右边.复杂的功能往往都是组合逻辑的功劳.
下面给出其RTL图以说明,两种代码最后都得出完全相同的RTL,所以网表最后也是完全相同的.