目录
参考《Verilog 编程艺术》魏家明著
Verilog行为模型包含有控制仿真和操作变量的过程语句,它们包含在过程快内。每个过程块都有一个与他相联系的活动流。
活动从initial 和 always开始,每个initial和always都开始各自的活动流。所有活动流都是并发的,用于模拟硬件固有的并发行为。
例子:
module behave();
reg a, b;
initial begin
a = 1'b1;
b = 1'b0;
end
always begin
#50 a = ~a;
end
always begin
#100 b = ~b;
end
endmodule
所有由initial和always定义的活动流在仿真0时刻同时开始,initial只执行一次,always重复执行。
过程赋值:
过程赋值用于修改reg,integer,time,real和realtime类型的变量,过程赋值和连续赋值有很大的不同。
1/连续赋值驱动线网,当输入发生变化时就计算并更改线网
2/过程赋值更改变量,在包含它的过程流的控制下更改变量
Verilog 有两种过程赋值,阻塞赋值和非阻塞赋值。
阻塞赋值:
在顺序块内,阻塞赋值语句必须在它的后续语句执行之前执行。但是在并行块(fork join)内,阻塞赋值语句不能阻止它的后续语句的执行。
variable_lvalue = [delay_or_event_control] expression;
说明:
1.= 是阻塞赋值的操作符。
2.delay_or_event_control是可选的赋值间时序操作,既可以是delay_control(例如 #6),也可以是event_control(例如 @(posedge clk))。
3.当LHS使用到了变量(例如数组的索引),那么此变量的值就是在执行赋值语句时刻的值。
4.=也被过程连续赋值和连续赋值使用
阻塞赋值例子:
rega = 0;
rega[3] = 1;
rega[3:5] = 7;
mema[address] = 8'hff;
{carry, acc} = rega + regb;
a = #5 b;
a = @(posedge clk) b;
a = repeat(3) @(posedge clk) b;
非阻塞赋值:
能够在不阻塞过程流的情况下允许赋值调度。当需要对几个变量在同一个 time-step赋值,而且不用考虑赋值语句的顺序和依赖关系时,就可以使用非阻塞赋值。
variable_lvalue <= [delay_or_event_control] expression;
说明:
1.非阻塞赋值和阻塞赋值语法一样,只不过<=是非阻塞赋值的操作符
2.delay_or_event_control是可选的赋值间时序操作,既可以是delay_control(例如 #6),也可以是event_control(例如 @(posedge clk))。
3.当LHS使用到了变量(例如数组的索引),那么此变量的值就是在执行赋值语句时刻的值。
4.注意:<=也是比较操作符,要根据其出现的位置判断是做非阻塞赋值,还是比较操作。
5.不像阻塞赋值中的事件或延迟控制,非阻塞赋值不能阻塞过程流。在非阻塞赋值计算RHS和调度LHS的更改时,它不会阻塞同一块内后续语句的执行。
6.非阻塞赋值的执行分为两步,
1)在执行非阻塞赋值时,仿真器计算RHS,然后把更改的RHS调度到非阻塞赋值更改队列(NBAU_EQ)的尾部。
2)在time-step的最后,仿真器激活NBAU_EQ时,更改每个赋值语句的LHS
例子:a,b交换
module evaluates2(
output out
);
reg a, b, c;
initial begin
a = 0;
b = 1;
c = 0;
end
always c = #5 ~c;
always @(posedge clk) begin
a <= b;
b <= a;
end
endmodule
过程连续赋值:
是允许用表达式对变量和线网连续驱动的过程赋值,包括assign/deassign语句和force/release语句。
LHS可以是reg 和 net,不能是reg的位选和域选。
例子:assign 和 deassign可以用来模拟D触发器的异步复位/置位
module dff(
output reg q,
input d, clear, preset, clock
);
always @(clear or preset) begin
if (!clear) assign q = 0;
else if (!preset) assign q = 1;
else deassign q;
end
always @(posedge clock) begin
q <= d;
end
endmodule
如果clear或preset为0,那么输出q保持为0或1,posedge clock对q没影响。当clear和preset都为1时,取消assign过程连续赋值,然后posedge clock对q有影响。
条件语句:
与c语言的if语句一样。
if (index > 0) begin
if (rega > regb)
result = rega;
else
result = regb;
end
循环语句:
Verilog有4中循环语句:
1.forever:持续不断的执行,就是死循环
2.repeat:执行括号内表达式指定的循环次数,如果表达式是x或z,就不执行
3.while:与c语言的while循环一样,当括号内表达式为true时就执行,否则不进入循环或跳出循环
4.for:与c语言的for一样。
通常:
1.forever,用在需要死循环的地方,例如生成时钟
2.repeat,可以不用定义循环变量,直接使用。
3.while,循环中的判断条件可以很简单,也可以很复杂
4.for,常用于固定的次数或可变次数的循环,要定义一个循环变量。
例子:
initial begin
clk <= 0;
forever # (PERIOD / 2.0) clk = ~clk;
end
repear (3) @(posedge clk);
begin : counts
reg [7;0] tempreg;
counr = 0;
tempreg = rega;
while (tempreg) begin
if (tempreg[0])
count = count + 1;
tempreg = tempreg >> 1;
end
end
for循环中,如果循环个数是变量的时候,那么任何综合工具都综合不出来,这是因为硬件规模必须是有限的,固定的。当综合工具遇到循环变量时,就把它们展开成若干条顺序执行的语句,然后再综合出电路。若循环个数是常数,则展开的语句数是确定的,所以可以综合;若循环个数是变量,则展开的语句数是不确定的,对应的硬件电路数量也不能确定,所以无法综合。
disable语句:
再命名块中使用,当disable执行的时候,命名块就被终止执行,所以它可以用于停止块,退出循环或退出task和function。
例子:
module test;
integer i;
initial begin
begin : break_block
for (i = 0; i < 10; i = i + 1) begin : continue_block
if (i == 5) disable continue_block;
if (i == 8) disable break_block;
$display("i= %-d", i)
end
end
end
endmodule
过程时序控制:
延迟控制:直到过了指定的时间延迟,才允许后续语句的执行。延迟控制表示从开始遭遇语句到最后执行语句之间的时间。由 # 引入
事件控制:直到某些仿真事件发生,才允许后续语句的执行。仿真时间既可以是线网值或变量值的变化,也可以是由其他过程触发的命名时间。由 @ 引入
延迟控制(Delay control)
延迟控制后面的语句要根据指定的延迟时间推迟执行。规则:
1/如果延迟表达式是x或z,那么就当作0延迟
2/如果延迟表达式是负数,那么就把它解释成同样位长的无符号整数。
3/specify parameters也可用再延迟表达式中,这些参数再做SDF反标时会被替换掉。
例子:
#10 rega = regb;
#d rega = regb;
#((d+e)/2) rega = regb;
#regr regr = rege + 1
事件控制(Event control)
过程语句的执行可以和线网,变量变化或命名事件的发生同步,事件控制的规则如下:
1/线网或变量的变化可以当作触发事件使用,称为隐含事件
2/事件可以基于方向的变化,包括posedge和negedge
3/negedge:the transition form 1 to x z or 0,and from x or z to 0
4/posedge:the transition form 0 to x z or 1,and from x or z to 1
5/当表达式的值发生变化时,就检测到隐含事件。但是当表达式内操作数发生变化,但表达式的结果没变时,就检测不到隐含事件。
例子:
@r rega = regb;
@(posedge clock) rega = regb;
forever @(negedge clock) rega = regb;
命名事件:
必须先声明后使用。命名事件是要明确触发的,用于控制过程语句的执行。用户可以声明event事件类型变量,并触发该变量来识别该事件是否发生
触发信号用 -> 表示。
事件or操作符:
逻辑或在一起的多个事件用于表示:这些多个事件中只要任意事件发生,那么就可以触发后续语句的之心。or或‘,’被当作事件逻辑或操作符,它们的作用是一样的。
例子:使用逻辑或操作符
@(trig or enable) rega = regb;
@(posedge clk_a or posedge clk_b or trig) rega = regb;
always @(a, b, c, d, e)
隐含事件列表:
事件列表不全会引起bug,使用@(*)可以自动建立隐含事件列表,可以消除由事件列表不全引起的bug。
always @(*)
y = (a & b) | (c & d);
equ to always @(a or b or c or d)
电平敏感事件控制:
过程语句可以通过wait语句延迟执行,直到等待的条件变为true,wait语句是一种特殊形式的事件控制。wait语句的本质是电平敏感的,而基本事件控制其实是边沿敏感的。
wait语句计算条件值,如果是false,那么后续语句就被阻塞,直到条件位true
例子:
begin
wait (!enble) #10 a = b;
#10 c = d;
end
如果进入块时enable是1,wait语句就会推迟后续语句的执行,直到enable变成1.如果进入块时enable是0,那么再延迟10个时间单位后就执行a=b,不会有附加延迟。
赋值间时序控制:
赋值间的延迟控制和事件控制包含在赋值语句里面,以不同的方式执行活动流。
赋值间延迟控制和事件控制会推迟LHS的更改,但是RHS是在延迟之前计算,而不是再延迟之后。
例子:同时对a和b采样和更新值,会导致竞争条件
fork
#5 a = b;
#5 b = a;
join
通过赋值间时序控制可以避免竞争,因为赋值间延迟使a和b的值在延迟前被计算,然后a和b的值在延迟后被更改为新值
fork
a = #5 b;
b = #5 a;
join
fork
a = @(posedge clk) b;
c = @(posedge clk) d;
join
a <= repeat (5) @(posedge clk) data;
在这个例子中,在执行语句时,data被计算并保存到临时存储中,过了5个posedge clk 之后,a才被更改为data的值。
块语句:把语句组织在一起的方法
顺序块:
位于begin和end之间的语句会按照指定的顺序执行。
特性:
1.语句按顺序执行,执行一条之后,再执行下一条
2.每条语句的延迟值是相对于它前面语句完成时的仿真时间
3.在最后一条语句执行完后,控制就从对应的顺序块离开
并行块:
位于fork和join之间的语句会并发执行。
特性:
1.所有语句并发执行
2.每条语句的延迟值都是相对于进入并行块时的仿真时间
3.可以使用延迟控制,从而为赋值提供time-ording
4.在最后一条time-ording语句执行完后,控制就从对应的并行块离开
块名字:
顺序快和并行块可以被命名,就是在begin或fork后面加上“: name_of_block”
1.允许为块声明local variables,parameters,named events
2.允许其他地方引用块。例如,使用disable语句终止命名块
所有块内声明的变量都是静态的,既所有local变量都有自己单独的存储空间,进入和离开块都不会影响保存在变量中的值。
开始和结束时间:
对于顺序块,开始时间是在执行第一条语句时,结束时间是在最后一条语句执行完
对于并行块,开始时间对所有语句是相同的,结束时间是在最后的time-ording语句执行完
顺序块和并行块可以互相包含。整个块已经执行完成,块后面的语句才能执行下去。
结构化过程:
Verilog中的过程要用以下4种语句表示:initial,always,task和function
1.initial和always在仿真开始的时候被使能
2.initial块只执行一次,当它里面的语句都执行完后,initial块就停止
3.always块要重复执行,只有仿真结束,always才停止
4.在initial和always块之间,没有执行顺序的要求
5.模块内对initial块和always块的数量没有限制。
注意:在写综合代码时,当always块包含异步行为时,例如always@(posedge clk or negedge reset),那么此always块只能包含一个独立的if块。但是当always块只包含同步行为,例如always@(posedge clk),那么此always块就可以包含多个独立的if块。
always有关的问题:
1.综合工具对没有posedge和negedge的always推导出组合逻辑或latch逻辑
2.对于组合always块,组合逻辑是从块中的逻辑推导出来的,与敏感列表没有任何关系,但是综合工具会检查敏感列表是否完整,如果综合工具发现敏感列表不完整,就发出警告;可能导致前后仿真不一致
3.当敏感列表不包含edge表达式,通常生成组合逻辑,但是如果输出变量没有在每个分支上都赋值,就会出现latch,所以设计者要注意检查是否生成了不想要的latch
4.当敏感列表中有edge表达式的时候,就不能在出现non-edge表达式。如果在敏感列表中把edge和non-edge混杂在一起,综合工具就会报告error
5.在always敏感列表中存在但没有使用的信号不会导致前后仿真不一致,但这些额外的信号会使前后仿真运行变慢。
赋值顺序错误:
always块中前仿真赋值是按照顺序执行的,如果变量在被赋值之前使用,那么就导致错误顺序的赋值,因为变量将保持上一次always块执行时的值。
在code2a中,temp在赋值之前就被调用,那么上次always执行时的赋值给temp的值就被用于计算变量的赋值。在下一行temp被赋值一个对应于本次always执行的新值。前仿真时temp就像一个latch,值被保留用于下一次计算,然后在综合时,综合工具起是按照temo = c & d;在前面考虑的,不会生成latch,这就导致前后仿真不一致。code2b给出了正确的编码顺序。
module code2a (
output reg o,
input a, b, c, d
);
reg temp;
always @(*) begin
o = a & b | temp;
temp = c & d;
end
endmodule
module code2b (
output reg o,
input a, b, c, d
);
reg temp;
always @(*) begin
temp = c & d;
o = a & b | temp;
end
endmodule