verilogHDL学习笔记2–组合逻辑与时序逻辑
一、组合逻辑
1.wire 和 reg
reg:寄存器类型,通常是对存储单元的描述,在下一个触发机制到来之前保留原值,用always描述
wire:线网型类型,相当于实际的连接线,变量的值随时发生变化,用assign连接(多条assign的赋值语句之间互相独立,并行执行)
2.always语句
always @(敏感事件)begin
程序语句
end
例子:
always @(a or b or d or sel)begin
if(sel==0)
c = a + b;
else c = a + d;
end
注意:“ * ” 可以指“程序语句”中所有的条件信号(推荐写法),即 a、b、d、sel(不包括 c)
3.数字进制
<位宽>’<基数><数值>,如 4’b1011。
位宽:描述常量所含位数的十进制整数,是可选项。
基数:进制,是 b,B,d,D,o,O,h 或者 H,缺省默认为十进制数
数值:数值未写完整时,高 位补 0。注:如果基数为 b 或 B,可以 是 0,1,x,X,z 或 Z。其他进制不能有x 或 z
4.运算符
4.1 加减运算:
assign C=A + B;
assign C=A - B;
总结运算步骤如下: 1. 根据“人的常识”,预计结果的最大最小值,从而确定结果的信号位宽。 2. 将加数、减数等数据,位宽扩展成结果位宽一致。 3. 按二进制加减法进行计算。
4.2 逻辑运算:
与 &&
或 ||
非 !
assign a = 4’b0111 && 4’b1000;
assign b = 4’b0111 || 4’b1000;
assign c = !4’b0111;
结果为 a 为逻辑真,b 为逻辑真,c 为逻辑假。
4.3 判断逻辑
使用心得1:逻辑运算符两边对应的是 1 比特信号
其中 a 和 b 都是多比特信号,表示两个多比特信号进行逻辑与。
wire[3:0] a, b; 当 a 不等于 0 并且 b 不等于 0 时,d 的值为 1。
assign d = a &&b ; 不等于 0 就是表示逻辑真,等于 0 就表示逻辑假
assign d = (a!=0) && (b!=0) ; 等价于这种方式
使用心得2:多是用括号,优先级
优先级:
逻辑运算符中“&&”和“||”的优先级低于算数运算符;“!”的优先级高于双目逻辑运算符。
(a < b)&&(c > d)可写成:a < b && c > d
(a = = b)| |(c = = d)可写成:a = = b | | c = = d
(!a)| |(a > b)可写成:!a | | a > b
逻辑运算符**“&&”和“||”的优先级低于算数运算符,!的优先级高于双目逻辑运算符**
将逻辑与“&&”和按位与“&”进行对比可以看出,逻辑与运算符的运算只有逻辑真或逻辑假两 种结果,即 1 或 0;而“&”是位运算符,用于两个多位宽数据操作。
assign a = 4’b0111 && 4’b1000;
assign b = 4’b0111 || 4’b1000;
assign c = !4’b0111;
assign d = 4’b0111 & 4’b1000;
assign e = 4’b0111 | 4’b1000;
assign f = ~4’b0111;
a=1’b1,b=1’b1,c=1’b0,d=4’b000,e=4’b1111,f=4’b1000。
4.4 逻辑运算符
|| :逻辑或A B一个当中一个为1则为1
| :按位或 对应比特位相或
~^ ^~ :二元异或非即同或
~非 、 &与 、 | 或 、^ 异或 都是按位操作
4.5 关系运算符
大于>、 小于< 、大于等于>=、小于等于<=、 ==逻辑相等 !=逻辑不等于
4.6 移位运算符
“<<” 左移位运算符、 “>>” 右移位运算符
wire[3:0] a; assign a = 4’b0111<< 2;
结果:a = 4’b1100。
左移操作属于逻辑移位,需要用 0 来填补移出的空位,即在低位补 0。左移 n 位,就要补 n 个 0。
**左移操作是不消耗逻辑资源.
左移操作的操作数可以是常数,也可以是信号。同样,左移操作的移位数、常数也可以是 信号。
右移运算同理
通过左移乘法运算,利用右移实现除法运算,利用左移位产生独热码
例如:a*2,等价于 a<<1;
assign b = a*67;
assign c = (a<<6) +(a<<1) +a;
b 和 c 都可以实现 a*67,但第 1 行消耗了一个乘法,
而第 2 行则只用到两个加法 器,从而节省了资源。
独热码在设计时非常有用,可以用来表示状态机的状态使状态机更健壮,也可以用于多选一的电 路中,表示选择其中的一个。 利用左移位操作,可以方便地产生独热码,例如产生 4’b0010,可以是 4’b1 << 1。类似地,也 可以产生 1 个比特为 0,其他为 1 的码制。例如产生 4’b1011,可以是~(4’b1 <<2)。
利用左移操作, 还可以产生其他需要的数字结果: 例如,产生 5’b00111,可以是(5’b1<<3)-1。 例如,产生 5’b11100,可以是~((5’b1<<2)-1)。
4.7 条件运算符
4.8 三目运算符
always@(*)begin
r =s ? t : u ;
end
表达式中 s 如果为真,则把 t 赋值给 r;如果 s 为假,则把 u 赋值给 r
可以使用if-else来替代
5 if语句
建议: 1、条件表达式需用括号括起来
2、若为 if - if 语句,请使用块语句 begin — end
if(Clk)
begin if(Reset)
Q = 0;
else
Q = D;
end
6.case语句
可以在 1 个分支中定义多个分支项,且这些值不需要互斥。缺省分支覆盖所有没有被分支表达式覆盖的其他分支
书写建议: case 语句的缺省项必须写,防止产生锁存器。
case (HEX)
4'b0001 : LED = 7'b1111001; // 1
4'b0010 : LED = 7'b0100100; // 2
4'b0011 : LED = 7'b0110000; // 3
4'b0100 : LED = 7'b0011001; // 4
endcase
7.选择语句
例 1:vect[7 +: 3];
其中,起始位置为 7,+代表着升序,宽度为 3。即从 7 开始向比 7 大的方向数 3 个数。其等价 形式为:vect[7 +: 3]==vect[7 : 9]。
例 2:vect[9 -: 4];
其中,起始位置为 9,-代表着降序,宽度为 4。即从 9 开始向比 9 小的方向数 4 个数。其等价 形式为:vect[9 -: 4]==vect[9 : 6]。
在实际使用的过程中该语法最常用的形式是将 a 作为一个可变的数来使用。
当 cnt0 时,将 din[7:0]赋值给 data[15:8];当 cnt1 时将 din[7:0]赋值给 data[7:0]
在设计的时候便可以写成:data[15-8cnt -: 8] <= din[7:0](此时需要将 15-8cnt 视为一个整 体 a,其会随着 cnt 变化而发生变化),这样一来就完成了对代码的精简工作。
选择语句的硬件电路结构如下图所示,其本质上是一个选择器。当 cnt0 时,选中 data[15:8] 的锁存器,将 din[7:0]赋值给 data[15:8],而 data[7:0]的锁存器保持输出不变;当 cnt1 时,选中 d ata[7:0]的锁存器,将 din[7:0]赋值给 data[7:0],而 data[15:8]的锁存器保持输出不变。
经验总结
if 语句每个分支之间是有优先级的,综合得到的电路是类似级联的结构。case 语句每个分支是平等 的,综合得到的电路则是一个多路选择器。因此,多个 if else-if 语句综合得到的逻辑电路延时有可能 会比 case 语句稍大。
if语句综合后的RTL
module if_case(
input [1:0] en,
input [1:0] a,
output reg q
);
always @(*)begin
if(en[0]==1)
q = a[0];
else if(en[1]==1)
q = a[1];
else
q = 0;
end
endmodule
从上图中所示的 RTL 图可以看到,此电路中含有两个二选一多路选择器,并且右边的优先级要 高于左边(因为 q 的值是直接与右边的二选一选择器连接),当 en[0]不为 1 时才会继续判断 en[1]。 也就是说,在 if 语句下综合出的电路含有优先级。
case语句综合后的RTL
always @(*)begin
case(en)
2'b01: q = a[0];
2'b10: q = a[1];
default:q = 0;
endcase
end
使用 case 语句编写的逻辑代码综合出的电路是并行的,没有优先级,不影响电路的运行速度。
但在FPGA中映射后的电路没有差别。
拼接运算符
二、时序逻辑
- always语句
同步复位的时序逻辑和异步复位的时序逻辑。在同步复位的时序逻 辑中复位不是立即有效,而在时钟上升沿时复位才有效。其代码结构如下:
always@(posedge clk) begin
if(rst_n==1’b0)
代码语句;
else begin
代码语句;
end
end
在异步复位的时序逻辑中复位立即有效,与时钟无关。其代码结构如下:
always@(posedge clk or negedge rst_n) begin
if(rst_n==1’b0)
代码语句;
else begin
代码语句;
end
end
- D 触发器代码
该段代码总是在“时钟 clk 上升沿或者复位 rst_n 下降沿” 的时候执行一次。具体执行方法如下: 1. 如果复位 rst_n=0,则 q 的值为 0; 2. 如果复位 rst_n=1,则将(a+d)的结果赋给 q(注意,前提条件是时钟上升沿的时候)。 假设用信号 c 表示 a+d 的结果,则第 2 点可改为:如果复位 rst_n=1,则将 c 的值赋给 q(注意, 前提条件是时钟上升沿的时刻)。很明显这是一个 D 触发器,输入信号为 d,输出为 q,时钟为 clk, 复位为 rst_n
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
q <= 0;
end
else begin
q <= d;
end
end
- 阻塞赋值和非阻塞赋值
阻塞赋值使用 **“=”语句;非阻塞赋值使用“<=”**语句
阻塞赋值:在一个“begin…end”的多行赋值语句,先执行当前行的赋值语句,再执行下一行 的赋值语句。
非阻塞赋值:在一个“begin…end”的多行赋值语句,在同一时间内同时赋值。
begin
c = a;
d = c + a;
end
begin
c <= a;
d <= c + a;
end
上面两个例子中,1 到 4 行部分是阻塞赋值,程序会先执行第 2 行,得到结果后再执行第 3 行。 6 至 9 行这一段是非阻塞赋值,第 7 行和第 8 行的赋值语句是同时执行的。