|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
x
本帖最后由 johnny1209 于 2012-3-18 15:27 编辑
题记:
要记住,你们在学校里所学到的那些奇妙的东西,都是多少代人的工作成绩,都是由世界上每个国家里的热忱的努力和无尽的劳动所产生的。这一切都作为遗产交到你们手里,使你们可以领受它,尊重它,增进它,并且有朝一日又忠实地转交给你们地孩子们。这样我们这些总是要死的人,就在我们共同创造的不朽事物中得到了永生。
——爱因斯坦
前言:
如果说C语言是思想的提炼的话,那Verilog则是思想+实体的提炼。一个复杂的数字电路,Verilog可以像“庖丁解牛”手上的那把钢刀,几下子就把这电路理清楚了。从C语言跳到Verilog,让我感受到Verilog化腐朽为神奇的思维之美。
理解一门语言背后的思维,才能体会到语言真正的美。
——本文试图提炼一些Verilog语言里让人心动的语法设计。
一、nets&variable(之前为Registers)
所有的数字电路中的物理连线、数据存储和传输单元不外乎4个逻辑值状态——0、1、X或x(不确定或未知的逻辑状态)、Z或z(高阻态)。故不像软件语言,数字电路的所有数据类型都是在上述4类逻辑状态中取值。
故Verilog的变量只分为两种数据类型(不可综合的其他变量都未列出),为分清楚足够分清楚两种类型,在中文文献里没找到太好的解释,只好拿来英文了:
- Nets
- represent structural connections between components. - Registers
- represent variables used to store data.
|
|
也就是说,Nets表示数字电路中的连线,如门与门之间的连接,net型不存储数值(trireg型除外)。net型必须由驱动来驱动(门或者连续赋值语句),如果没有驱动连接到net,它的值为高阻态(Z);而Register是用来存储数据的变量。一个register存储从一个赋值语句到下一个赋值语句的值。reg型数据的默认值为未知状态(X)。
1) net型:wire、tri;
net型相当于硬件电路中的各种物理连接,其特点是输出的值随输入的值而变。
2)variable型:reg、integer。
variable型变量必须放在过程语句(如initial、always)中,通过过程赋值语句赋值;在always、initial等过程块内被赋值的信号也必须定义成Variable型。
这里需注意的是reg型变量不一定对应这硬件上的寄存器(n bit)或触发器(1 bit)。在综合时,综合器会根据具体情况来确定将其映射成寄存器还是映射为连线。(所以可以这样理解:之所以要定义reg变量,只是因为在过程语句要用到这些变量。不能因为看到reg而片面理解其综合后的结果。)
integer型变量多用于表示循环变量,这里不用多说。
二、wire和reg的赋值
这里继续把wire和reg的赋值分清楚。
Verilog的赋值分为连续赋值和过程赋值。连续赋值用于数据流行为建模,多用于组合逻辑电路;过程赋值用于顺序行为建模,用于顺序行为建模。
连续赋值等号右边操作数发生变化就需要执行(上电便一直执行),而过程赋值语句只是执行一次,注意这里的一次是指:在initial块中,过程性赋值只顺序执行一次,而在always块中,每一次满足always的条件时,都要顺序执行一次该always块中的语句。
分清连续赋值和过程赋值才能理解连续赋值是针对net型来用,过程赋值是针对reg型来用。
对于reg的过程赋值,又有2种赋值方式:非阻塞赋值和阻塞赋值。阻塞赋值在该语句结束时就立即完成赋值操作,它在initial或always块内是顺序执行的;
例1:
always@(A)
begin
B=A
C=B
D=C
end
在这个always块内,当A的值发生变化的时候,会先将A的值赋给B,然后紧跟着把B的值赋给C,接着将C的值赋给D。如果我们没有设定每一个语句的延迟的话,在仿真时我们会看到B、C、D是同时被赋值的。
例2:
always@(posedge CK)
begin
M=A&B;
Y = M | C;
end
该电路综合出的电路如下:
这里always块内综合出了组合逻辑和时序器件,这并不是我们所希望的。一个好的coding style需要很清楚地区分二者,在专业的设计领域,只允许always@(posedge clk)这样的语句综合出D触发器。此外阻塞式赋值还可能会有竞争或者组合反馈的危险(是大忌)。这需要绝对避免。来看看2个例子:
例3:
always@(posedge clk)
begin
//此语句会产生竞争
B=A;
A=B;
end
always@(in1, in2, in3)
begin
//此语句会产生组合反馈的情况
temp1 = (in1 | in2) & in3;
temp2 = temp1 & in2;
in3 = temp2;
end
非阻塞赋值在整个过程块结束时才完成赋值操作,在initial或者always内的非阻塞式赋值语句,并不因为其位置而有先后执行的差别。
例4:
initial
begin
a<=1; // a<=1和b<=3是同时被执行的
b<=3; [url=]//并非执行完a,才执行b[/url]的赋值
end
例5:
always@(posedge clk)
begin
M<=A&B;
Y<=M | C;
end
其综合后的电路图如下图:
由上可知:对于非阻塞式赋值,综合后的电路会对reg型生成D触发器保存原来的结果。
(这里说一个题外话,为啥Verilog要这么费劲把数据类型分成wire和reg,为何不直接用variable来表示得了。我想这不单是用来区分组合逻辑电路和时序逻辑电路。更是为综合考虑,即综合时是否在变量前增加触发器或寄存器,是随着输入值时刻变化,还是对变量输出可控。)
综合举例:
1)对wire型连续赋值(基本RS触发器):(注意wire型的赋值位置)
module rs_ff(r, s, q, qn);
input r, s;
output q, qn;
assign qn=~(r & q);
assign q = ~(s & qn);
endmodule
2)reg过程赋值(阻塞赋值):(注意reg型的赋值位置)
module mux2_1_block(out, a, b, sel);
input a, b, sel;
output reg out;
always@(a, b, sel)
begin
if(sel == 0)
out = a;
else
out = b;
end
endmodule
3) reg过程赋值(非阻塞赋值):见上面的例子。
三、综述
像C语言的设计围绕编译来考虑一样,Verilog的设计也是围绕着综合来考虑。但相对于C语言,可以说Verilog的设计更严格。从net型和variable型变量就可见一斑。变量不同,对应的综合后的电路就不同;对variable来说,赋值不同,对应的综合后电路也不同。
我们将阻塞赋值语句的always过程块写成下面的形式。
always @(event-expression)
begin
< LHS1 = RHS1 assignments > //阻塞赋值语句1
< LHS2 = RHS2 assignments> //阻塞赋值语句2
.
.
.
end
同样非阻塞赋值语句的always进程块写出下面的形式:
always @(event-expression)
begin
<LHS1<=RHS1 assignments > // 非阻塞赋值语句1
<LHS2<=RHS2 assignments > // 非阻塞赋值语句2
.
.
.
end
其中LHS指赋值符号左边的变量或表达式, RHS指赋值符号右端的变量或表达式。阻塞赋值“=”与非阻塞赋值“<=”的本质区别在于:非阻塞赋值语句右端的表达式计算完后并不立即给左端,而是同时启动下一条语句继续执行,即同时计算RHS1,RHS2,计算完后,在进程结束时,同时分别赋值给左端变量LHS1,LHS2。
而阻塞赋值语句在每个右端表达式计算完后,立即赋值给左端变量。即赋值语句LHS1 = RHS1执行完后LHS1是立即更新的,同时只有LHS1 = RHS1执行完后才可执行语句LHS2=RHS2,以此类推。
下面是阻塞语句和非阻塞语句的几条原则:
(1)当用always块来描述组合逻辑时,既可以用阻塞赋值,也可以采用非阻塞赋值,最好才用阻塞赋值;
(2)设计时序逻辑电路,尽量使用非阻塞赋值方式;
(3)描述锁存器,尽量使用非阻塞赋值;
(4)若在同一个always过程块中既为组合逻辑建模,又为时序逻辑建模,最好使用非阻塞赋值方式;
(5)在同一个always过程块中,不要混合使用两种赋值方式;
(6)不能在两个或两个以上的always过程中对同一变量赋值,这样会引发冲突。
看到这里我想可以大话一下两种赋值:阻塞赋值就像工厂一条流水线,各赋值语句是上下游的关系,必须等上游的工序走完才能走到下游。非阻塞赋值像工厂的多条并行的流水线,每条流水线先生产自己的半成品,然后再把半成品加工成成品(赋值到LHS1)。
参考:《数字系统设计与Verilog HDL(第四版)》王金明著
《精通Verilog HDL:IC设计核心技术实例详解》
Verilog online help:http://verilog.renerta.com/mobile/index.html |
|
|
|