Verilog语法回顾--行为模型

目录

过程赋值:

阻塞赋值:

非阻塞赋值: 

过程连续赋值:

条件语句: 

循环语句: 

过程时序控制: 

延迟控制(Delay control)

事件控制(Event control) 

命名事件:

事件or操作符: 

隐含事件列表:

电平敏感事件控制: 

赋值间时序控制:

块语句:把语句组织在一起的方法

顺序块:

并行块:

块名字:

开始和结束时间:

结构化过程:

always有关的问题:

赋值顺序错误:


参考《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

  • 11
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值