Verilog基础学习二
文章目录
- Verilog基础学习二
- 一、always 块
- 1.阻塞性赋值和非阻塞性赋值
- 二、条件语句
- 1.if 语句 基本用法
- 2.避免引入锁存器
- 3.case 语句
- 4.casez 语句
- 三、归约运算符(Reduction Operators)
- 四、for循环
- Problem : Combinational for-loop: 255-bit population count
- Problem : Generate for-loop: 100-bit binary adder 2
- 五、Generate 块
- Problem : Generate for-loop: 100-digit BCD adder
- 总结
一、always 块
我们知道数字电路是由导线连接的逻辑门组成,因此任何电路都可以表示为module
和assign
语句的某种组合。但是,有时候这不是描述电路最简便的方法。过程块(比如always块)提供了一种用于替代assign语句描述电路的方法。
有两种always块是可以综合出电路硬件的:
综合逻辑:always @(*)
时序逻辑:always @(posedge clk)
1.阻塞性赋值和非阻塞性赋值
在Verilog中有以下三种赋值方法:
连续赋值(assign x=y;):不能在过程块内使用;
过程阻塞性赋值(x=y;):只能在过程块中使用;
过程费阻塞性复制(x<=y):只能在过程块内使用。
在组合always块中,使用阻塞性赋值。在时序always块中,使用非阻塞性赋值。具体为什么对设计硬件用处不大,还需要理解Verilog模拟器如何跟踪事件(译者注:的确是这样,记住组合用阻塞性,时序用非阻塞性就可以了)。不遵循此规则会导致极难发现非确定性错误,并且在仿真和综合出来的硬件之间存在差异。
示例代码如下:
module top_module(
input clk,
input a,
input b,
output wire out_assign,
output reg out_always_comb,
output reg out_always_ff );
assign out_assign = a ^ b;
always@(*) out_always_comb = a ^ b;
always@(posedge clk) out_always_ff <= a ^ b;
endmodule
二、条件语句
1.if 语句 基本用法
基本if语句:
always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end
条件(三元)运算符:
assign out = (condition) ? x : y;
condition ? if_true : if_false
2.避免引入锁存器
组合电路输出必须在所有输入的情况下都有值。这意味着必须需要else子句或着输出默认值。
3.case 语句
Verilog中的case
语句几乎等同于if-else if-else
序列,它将一个表达式与其他表达式列表进行比较。它的语法和功能与C语言中的switch
语句稍有不同:
always @(*) begin // This is a combinational circuit
case (in)
1'b1: begin
out = 1'b1; // begin-end if >1 statement
end
1'b0: out = 1'b0;
default: out = 1'bx;
endcase
end
1、case语句以case开头,每个case项以冒号结束。而switch语句没有。
2、每个case项只执行一个语句。 *这样就不需要C语言中break来跳出switch。*但这也意味着如果您需要多个语句,则必须使用begin … end。
3、case项允许重复和部分重叠,执行程序匹配到的第一个,而C语言不允许重复的case项目。
4.casez 语句
如果case语句中的case项与某些输入无关,就可以减少列出的case项。这就是casez
的用途:它在比较中将具有值z的位视为无关项(即输入01都会匹配到)。
还有一个类似的casex,将输入的x和z都视为无关。不认为casex比casez有什么特别的好处。
(作者个人感觉没必要用casex,z和x状态的问题涉及电路的基本知识)
符号"?" 是z的同义词,所以2’bz0与2’b?0相同。
always @(*) begin
casez (in[3:0])
4'bzzz1: out = 0; // in[3:1]输入什么都可以
4'bzz1z: out = 1;
4'bz1zz: out = 2;
4'b1zzz: out = 3;
default: out = 0;
endcase
end
同时为避免生成了不必要的锁存器,必须在所有条件下为所有的输出赋值。这可能会多打很多字,使你的代码变得冗长。 一个简单的方法是在case语句之前为输出分配一个“默认值”
除非case语句覆盖赋值,否则这种代码样式可确保在所有可能的情况下输出0。 这也意味着case的default项变得不必要。
提醒:always@(*)综合器会生成一个组合电路,其行为与代码描述的相同。硬件不会按顺序“执行”代码。
三、归约运算符(Reduction Operators)
归约运算符(Reduction Operators)可以对向量的每一位位进行AND,OR和XOR,产生一位输出:
&a [3:0] // AND:a[3]&a[2]&a[1]&a [0]相当于(a[3:0]== 4'hf)
|b [3:0] // OR: b[3]|b[2]|b[1]|b [0]相当于(b[3:0]!= 4'h0)
^c [2:0] // XOR:c[2]^c[1]^c[0]
四、for循环
for循环(组合always块或者generate块)在处理重复动作的时候很有用。
模块实例化时必须使用generate块
always@(*)begin
for (int i=0;i<=99;i=i+1)begin
out[i]=in[99-i];
end
end
Problem : Combinational for-loop: 255-bit population count
设计电路来计算输入矢量中 ’1‘ 的个数,题目要求建立一个255bit输入的矢量来判断输入中 ’1‘ 的个数。
module top_module(
input [254:0] in,
output [7:0] out );
integer i;
always @ (*)
begin
out = 8'b0000_0000; //**为了后面的计数累加,此处先初始化为0.**
for (i=0; i<255; i++)
begin
if(in[i] == 1'b1)
out = out + 1'b1;
else
out = out + 1'b0;
end
end
endmodule
- 循环里面的变量一定要初始化
Problem : Generate for-loop: 100-bit binary adder 2
通过实例化100个全加器来实现一个100bit的二进制加法器。该加法器有两个100bit的输入和cin,输出为sum与cout。为了鼓励大家使用实例化来完成电路设计,我们同时需要输出每个全加器的cout。
故cout[99]标志着全加器的最终进位。
- 有好多加法器需要实例化,可采用实例化数组或generate语句来实现。
考虑到for循环中只有cin与cout是变化的,每次计算中cout是本次计算的输出,也是下次计算的输入(cout就是下次计算的cin)。故我们先计算出cout[0]
和 sum[0]
。
assign cout[0] = a[0] & b[0] | a[0] & cin | b[0] & cin;
assign sum[0] = a[0] ^ b[0] ^ cin;
//一定要记得赋初值,然后再开始循环
代码如下
module top_module(
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum );
integer i;
always @(*) begin
cout[0] = a[0]&b[0] | a[0]&cin | b[0]&cin;
sum[0] = a[0] ^ b[0] ^ cin;
for(i=1;i<100;i++) begin
sum[i] = a[i] ^ b[i] ^ cout[i-1];
cout[i] = a[i]&b[i] | a[i]&cout[i-1] | b[i]&cout[i-1];
end
end
endmodule
五、Generate 块
什么是generate
语句?
生成语句可以动态的生成verilog代码,当对矢量中的多个位进行重复操作时,或者当进行多个模块的实例引用的重复操作时,或者根据参数的定义来确定程序中是否应该包含某段Verilog代码的时候,使用生成语句能大大简化程序的编写过程。
使用关键字generate
与 endgenerate
来指定范围。generate语句有generate-for、generate-if、generate-case三种语句,本题中我们使用generate-for语句。
generate-for语句:
(1) 必须有genvar
关键字定义for
语句的变量。
(2)for语句的内容必须加begin和end**(即使就一句)**。
(3)for语句必须有个名字。
//创建一个2进制转换器
Module gray2bin
#(parameter SIZE = 8)
(
input [SIZE-1:0] gray,
output [SIZE-1:0] bin
)
Genvar gi; //在generate语句中采用genvar声明
generate
for (gi=0; gi<SIZE; gi=gi+1)
begin : genbit //for语句必须有名字
assign bin[i] = ^gray[SIZE-1:gi];
end
endgenerate
endmodule
Problem : Generate for-loop: 100-digit BCD adder
本题已经提供了一个名为
bcd_fadd
的BCD一位全加器,他会添加两个BCD码和一个cin,并产生一个cout和sum。
每个bcd_fadd都是含有两个四位的输入(a,b),一个四位的输出(sum)
module bcd_fadd {
input [3:0] a,
input [3:0] b,
input cin,
output cout,
output [3:0] sum );
我们需要实例化100个bcd_fadd来实现100位的BCD进位加法器。该加法器应包含两个100bit的BCD码(包含在400bit的矢量中)和一个cin,
输出产生sum 和 cout。文章来源:https://uudwc.com/A/mnX1
module top_module(
input [399:0] a, b,
input cin,
output cout,
output [399:0] sum );
wire [399:0] cout_temp;
genvar i;
bcd_fadd inst1_bcd_fadd (
.a(a[3:0]),
.b(b[3:0]),
.cin(cin),
.cout(cout_temp[0]),
.sum(sum[3:0])
);
//与上题同理,还是先计算cout[0],我声明一个wire型的cout_temp来存放每次计算后cout的值。
//类似矢量运算中赋初值,实例的初始化也是必不可少的
generate
for(i=4; i<400; i=i+4)
begin: bcd
bcd_fadd inst_bcd_fadd(
.a(a[i+3:i]),
.b(b[i+3:i]),
.cin(cout_temp[i-4]), //上次计算输出的cout
.cout(cout_temp[i]), //本次计算输出的cout,在下次计算中变为cin
.sum(sum[i+3:i])
);
end
endgenerate
assign cout = cout_temp[396];
endmodule
总结
本次学习了always 块的用法,包含里面的if、case、casez、for语句的具体用法;学习了归约运算符(Reduction Operators)的操作,简化打字量;最后学习了Generate 块的用法,今后在实践中具体操作会有更深刻的理解。文章来源地址https://uudwc.com/A/mnX1