赋值语句

我说这个必考,因为真会考。

阻塞赋值

使用=进行。

  • 赋值语句执行完后,块才结束;
  • b 的值在赋值语句执行完后立刻就改变的;
  • 在时序逻辑中使用时,可能会产生意想不到的结果。
1
2
3
4
5
6
reg b,c;
always @(posedge clk)
begin
b=a;//不建议使用
c=b;//不建议使用
end

有的仿真器可以综合成D触发器,但是不推荐。

三段式状态机 中,描述次态逻辑和输出逻辑的电路必须使用阻塞赋值,表明为组合电路。

状态机

非阻塞赋值

使用<=进行赋值。

  • 在语句块中,上面语句所赋的变量值不能立即就为下面的语句所用;
  • 块结束后才能完成这次赋值操作,而所赋的变量值是上次赋值得到的;
  • 在编写可综合的时序逻辑模块时,这是最常用的赋值方法。

例:下面代码会发生什么?

1
2
3
4
5
always @(posedge clk)
begin
b<=a;
a<=b;
end
答案

这俩会交换位置。因为:

  • 在时钟沿到来前,两个表达式并行运算,已经确定好结果;
  • 时钟沿到来时,a和b被赋值。

块语句

顺序块

使用begin...end的块是顺序块,通常用来标识顺序执行的语句。

  • 块内的语句是按顺序执行的,即只有上面一条语句执行完后下面的语句才能执行。
  • 每条语句的延迟时间是相对于前一条语句的仿真时间而言的。
  • 直到最后一条语句执行完,程序流程控制才跳出该语句块。
1
2
3
4
5
begin [:block_id
{declaration}
]
procedural_statement(s);
end

如果在块内用到了临时变量,则必须填写block_id!

并行块

  • 块内语句是同时执行的,即程序流程控制一进入到该并行块,块内语句则开始同时并行地执行。
  • 块内每条语句的延迟时间是相对于程序流程控制进入到块内的仿真时间的。
  • 延迟时间是用来给赋值语句提供执行时序的。
  • 当按时间时序排序在最后的语句执行完后或一个 disable 语句执行时,程序流程控制跳出该程序块。
1
2
3
4
5
fork [:block_id
{declaration}
]
procedural_statement(s);
join

生成块

这一块很少用到,考的话就是冷门点。留个概念就行,平时写代码也可以强迫自己刻意去用(虽然印象中确实用不到)

generate/endgenerate

生成语句可以动态地生成 Verilog 代码。这一声明语句方便了参数化模块的生成。当对矢量中的多个位进行重复操作时,或者当进行多个模块的实例引用的重复操作时,或者在根据参数的定义来确定程序中是否应该包括某段 Verilog 代码的时候,使用生成语句能够大大简化程序的编写过程。

生成实例可以是以下的一个或多种类型:

  • 模块
  • 用户定义原语
  • 门级原语
  • 连续赋值语句
  • initial 和 always 块

不允许出现在生成范围之中的模块项声明:

  • 参数、局部参数
  • 输入、输出和输入/输出声明
  • 指定块

举个栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//本模块生成两条N位总线变量的按位异或
module bitwise_xor(out,i0,i1);
//参数声明语句。参数可以重新定义
parameter N=32;//缺省的总线位宽为32位
//端口声明语句
output [N-1:0] out;
input [N-1:0] i0,i1;
//声明一个临时循环变量。
//该变量只用于生成块的循环计算。
//Verilog仿真时该变量在设计中并不存在
genvar j;
//用一个单循环生成按位异或的异或门(xor)
generate
for(j=0;j<N;j=j+1)
begin: xor_loop
xor g1(out[j],i0[j],i1[j]);
end//在生成块内部结束循环
endgenerate//结束生成块
endmodule

//另外一种编写形式
//异或门可以用always块来替代
reg [N-1:0] out;
generate
for(j=0;j<N;j=j+1)
begin:bit
always@(i0[j]ori1[j])
out[j]=i0[j]^i0[j];
end
endgenerate

生成块用的时候,记得给begin块起名字!

生成块的本质是使用循环内的一条语句来代替多条重复的 Verilog 语句,简化用户的编程。

关键词genvar用于声明生成变量,生成变量只能用在生成块之中;在确立后的仿真代码中,生成变量是不存在的。

和if..else还有case的连携

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/本模块生成N位的加法器
module adder(co,sum,a0,a1,ci);
parameter N=4;//缺省的总线位宽为4
output [N-1:0]sum;
output co;
input [N-1:0]a0,a1;
input ci;
//根据总线的位宽,调用(实例引用)相应的加法器
//参数N在调用(实例引用)时可以重新定义,调用(实例引用)
//不同位宽的加法器是根据不同的N来决定的。
generate
case(N)
//当N=1,或2时分别选用位宽为1位或2位的加法器
1: adder_1bit adder1(co,sum,a0,a1,ci);//1位的加法器
2: adder_2bit adder2(co,sum,a0,a1,ci);//2位的加法器
//缺省的情况下选用位宽为N位的超前进位加法器
default: adder_cla#(N)adder3(co,sum,a0,a1,ci);
endcase
endgenerate//生成块的结束
endmodule

块语句的特点

嵌套:块可以嵌套使用, 顺序块和并行块能够混合在一起使用

1
2
3
4
5
6
7
8
9
initial
begin
x = 1'b0;
fork
#5 y = 1'b1 ;
#10 z = {x, y};
join
#20 w = {y, x};
end

命名块:块可以具有自己的名字

禁用块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//从矢量标志寄存器的低有效位开始查找第一个值为1的位
reg [15:0] flag;
integer i; //用于计数的整数
initial
begin
flag = 16'b 0010_0000_0000_0000;
i = 0;
begin: block1 //while循环声明中的主模块是命名块block1
while(i < 16)
begin
if (flag[i])
begin
$display("Encountered a TRUE bit at element number %d", i);
disable block1; // 在标志寄存器中找到了值为真(1)的位,
禁用block1
end
i = i + 1;
end
end
end

为什么要加块名?

  • 可以在块内定义局部变量,即只在块内使用的变量。
  • 可以允许块被其他语句调用,如 disable 语句。
  • 在 Verilog 语言里,所有的变量都是静态的,即所有的变量都只有一个唯一的存储地址,因此进入或跳出块并不影响存储在变量内的值。基于以上原因,块名就提供了一个在任何仿真时刻确认变量值的方法。

条件语句

if...else

没啥好说的,记得带上()begin...end就行。

只能写在过程块里!

如果嵌套使用,必须带上begin...end

case

记得写default,不写可以,多写了不行。多个default语句是非法的。

同样只能用于过程块。

  • 每一个 case 分项的分支表达式的值必须互不相同,否则就会出现问题,即对表达式的同一个值,将出现多种执行方案,产生矛盾。
  • 执行完 case 分项后的语句,则跳出该 case 语句结构,终止 case 语句的执行。
  • 在用 case 语句表达式进行比较的过程中,只有当信号的对应位的值能明确进行比较时,比较才能成功。因此,要注意详细说明 case 分项的分支表达式的值。
  • case 语句的所有表达式值的位宽必须相等,只有这样,控制表达式和分支表达式才能进行对应位的比较。一个经常犯的错误是用ˈbx , ˈbz 来替代 nˈbx , nˈbz,这样写是不对的,因为信号 x , z 的默认宽度是机器的字节宽度,通常是 32 位(此处 n 是 case 控制表达式的位宽)。

循环语句

在 VerilogHDL 中存在着 4 种类型的循环语句,用来控制执行语句的执行次数。

  • forever 语句:连续的执行语句
  • repeat 语句:连续执行一条语句 n 次
  • while 语句:执行一条语句直到某个条件不满足。如果一开始条件即不满足(为假),则语句一次也不能被执行
  • for 语句:通过以下 3 个步骤来决定语句的循环执行
    • 给控制循环次数的变量赋初值
    • 判定控制循环的表达式的值,如为假,则跳出循环语句;如为真,则执行指定的语句后,转到下一步
    • 执行一条赋值语句来修正控制循环变量次数的变量值,然后返回上一步

forever

forever必须和时间控制一起使用!否则会陷入无法逃脱的死循环.....

1
2
3
4
5
6
// 生成周期为20的时钟
initial
begin
clock=0;
#5 forever #10 clock=~clock;
end

repeat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 乘法器
parameter size=8, longsize=16;
reg[size:1] opa, opb;
reg [longsize:1] result;
begin: mult
reg[longsize:1] shift_opa, shift_opb;
shift_opa = opa; shift_opb = opb; result = 0;
repeat(size)
begin
if (shift_opb[1])
result = result + shift_opa;
shift_opa = shift_opa<<1;
shift_opb = shift_opb>>1;
end
end

while

1
2
3
4
5
6
7
8
9
10
11
12
// 对rega中有多少个1进行统计
begin: countls
reg [7:0] tempreg;
count=0;
tempreg = rega;
while (tempreg)
begin
if (tempreg[0])
count = count+1;
tempreg = tempreg>>1;
end
end

for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 上面用 repeat 实现的乘法器,用 for 也可以
parameter size = 8, longsize = 16;
reg[size:1] opa, opb;
reg[longsize:1] result;
begin: mult
integer bindex;
result= 0;
for( bindex=1; bindex<= size; bindex= bindex+1)
if(opb[bindex])
result = result + (opa<<( bindex-1));
end

// 在 for 语句中
// 循环变量增值表达式可以不必是一般的常规加法或减法表达式
begin: count1s
reg [7:0] tempreg;
count=0;
for(tempreg=rega; tempreg; tempreg=tempreg>>1)
if (tempreg[0])
count = count+1;
end