文章目录
Alwaysblock1(组合逻辑)
由于数字电路由与导线连接的逻辑门组成,因此任何电路都可以表示为模块和赋值语句的某种组合。
有如下两种always块:
组合逻辑:always @ (*) ——等价于赋值语句 / 赋值语句(assign)的左侧必须是wire类型 / 带有敏感列表 ( * )用reg型
时序逻辑:always @ (clk) —— 变量类型reg型
练习:
使用赋值语句和组合always块构建与门。
module top_module(
input a,
input b,
output wire out_assign ,
output reg out_alwaysblock
);
//赋值语句
assign out_assign = a && b;
//always块
always @ (*) begin
out_alwaysblock = a && b;
end
endmodule
Alwaysblock2(时序逻辑)
时序always块会生成一系列的触发器(寄存器),另外输出数据不是立刻输出,而是再下一个时钟上升沿输出(posedge clk)。
Verilog三种赋值方式(阻塞和非阻塞)
- 连续赋值(assign x=y;),只能在always块外使用。
- 阻塞赋值(x=y;),只能在always块内使用。 ——组合逻辑中用
- 非阻塞赋值(x<=y;)只能在always块内使用。——时序逻辑中用
练习
使用赋值语句、组合always块和时序always块三种方式构建一个异或门。
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 @ (*) begin
out_always_comb = a ^ b;
end
always @ (posedge clk)begin
out_always_ff <= a ^ b;
end
endmodule
Always if 1
if 语句通常创建一个 2 对 1 多路复用器,如果条件为真,则选择一个输入,如果条件为 false,则选择另一个输入。格式如下:
always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end
等效于使用条件运算符的连续赋值:
assign out = (condition) ? x : y;
练习
使用 if语句 构建在 a b 之间进行选择的 2 对 1 多路复用器。如果sel_b1和sel_b2都为真,请选择 b。否则,请选择 a。
module top_module(
input a,
input b,
input sel_b1,
input sel_b2,
output wire out_assign,
output reg out_always
);
assign out_assign = (sel_b1 && sel_b2) ? b : a ;
always @ (*) begin
if(sel_b1 && sel_b2) begin
out_always = b;
end
else
out_always = a;
end
endmodule
Always if 2
避免生成锁存器
- 锁存器是一种对脉冲电平(也就是0或者1)敏感的存储单元电路,而触发器是一种对脉冲边沿(即上升沿或者下降沿)敏感的存储电路。
- 进行电路设计时,不能先写代码,后生成电路;语法正确的代码并不一定会产生合理的电路。
- 典型错误如下:
If (cpu_overheated) then shut_off_computer = 1;
当 if 语句不满足条件的时候,Verilog给出的解决方法是保持输出不变,但是组合逻辑电路是不能记录当前状态的,因此会综合出锁存器。
因此我们使用if语句或者case语句的时候,应该对所有的情况都给予对应的输出,避免锁存器的生成。
练习
下面代码造成了锁存器的生成,请进行修改:
module top_module (
input cpu_overheated,
output reg shut_off_computer,
input arrived,
input gas_tank_empty,
output reg keep_driving ); //
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
else
shut_off_computer = 0;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
else
keep_driving = 0;
end
endmodule
Always case1
Verilog 中的 case 语句几乎等效于将一个表达式与一个表达式列表进行比较的 if-elseif-else 序列。格式如下:
注意:
当有大量事例的时候用case语句更方便。
每个case事例项只能执行一个语句。因此如果需要多个语句,则必须使用 begin …end。
练习
创建一个 6 对 1 多路复用器。当 sel 介于 0 和 5 之间时,选择相应的数据输入。否则,输出 0。数据输入和输出均为4位宽。(避免锁存器的生成)
module top_module (
input [2:0] sel,
input [3:0] data0,
input [3:0] data1,
input [3:0] data2,
input [3:0] data3,
input [3:0] data4,
input [3:0] data5,
output reg [3:0] out );//
always@(*) begin // This is a combinational circuit
case(sel)
3'd0: out = data0;
3'd1: out = data1;
3'd2: out = data2;
3'd3: out = data3;
3'd4: out = data4;
3'd5: out = data5;
default: out = 4'b0;
endcase
end
endmodule
Always case2
优先编码器是一种组合电路,当给定输入位向量时,输出该向量中第一个1的位置。 例如,给定输入8’b10010000的8位优先级编码器,将输出3’d4。 bit[4] 是第一个高位——也就是看第一个高位1,出现再输入的第几位上面。
练习
构建一个4位优先级编码器。 对于此问题,如果所有输入位都不为高(即输入为零),则输出零。 请注意,一个4位数字具有16种可能的组合。
解: 看输入位的第一个高电平1 出现再第几位。
module top_module (
input [3:0] in,
output reg [1:0] pos
);
always @ (*) begin
//第一种写法:
//case(in)
//4'b0000: pos = 2'b0;
//4'b0001: pos = 2'b0;
//4'b0010: pos = 2'b1;
//4'b0011: pos = 2'b0;
//......
//default:pos = 2'b0;
//第二种写法:
case(1) //检测第一个高位所在位置 如果在输入位的0位,就输出0,以此类推
in[0]:pos = 0;
in[1]:pos = 1;
in[2]:pos = 2;
in[3]:pos = 3;
default:pos = 0;
endcase
end
endmodule
Always casez
casez 的用途:它将值 z 的位视为不关心的位(无关位)
例如,这将实现上一练习中的 4 输入优先级编码器:
always @(*) begin
casez (in[3:0])
4'bzzz1: out = 0; // in[3:1] can be anything
4'bzz1z: out = 1;
4'bz1zz: out = 2;
4'b1zzz: out = 3;
default: out = 0;
endcase
end
case语句的行为就好像是按顺序检查每个项一样(实际上,它所做的事情更像是生成一个巨大的真值表,然后进行门操作)。当某些输入匹配多种case项的时候,选择第一个匹配项,不匹配后面的任何项目。
还有类似的casex,它将x和z均无视掉,但用它来代替casez的意义不大。
符号?是z的同义词,所以2‘bz0=2’b?0
练习
用casez的方式来编写8位优先级编码器
module top_module (
input [7:0] in,
output reg [2:0] pos );
always @(*) begin
casez(in)
8'bzzzzzzz1:pos=0;
8'bzzzzzz1z:pos=1;
8'bzzzzz1zz:pos=2;
8'bzzzz1zzz:pos=3;
8'bzzz1zzzz:pos=4;
8'bzz1zzzzz:pos=5;
8'bz1zzzzzz:pos=6;
8'b1zzzzzzz:pos=7;
default:pos=0;
endcase
end
endmodule
Always nolatches
去除锁存器。
假设在构建一个电路,来处理来自 PS/2 键盘的扫描码的游戏。给定收到的最后两个字节的扫描码,根据扫描码指示是否按下了键盘上的某个箭头键。相当于一个简单的映射,可以实现为具有四个事例的case语句(或 if-elseif)。case事项如下:
为避免生成锁存器,必须位所有的可能情况分配输出值,另外还需要对输出进行赋初值的操作,这种类型的代码确保在所有可能的情况下输出都被赋值,除非case语句覆盖了赋值。这也意味着不再需要缺省的default项。如下面示例代码:
always @(*) begin
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
case (scancode)
... // Set to 1 as necessary.
endcase
end
如下进行Verilog代码编写:
module top_module (
input [15:0] scancode,
output reg left,
output reg down,
output reg right,
output reg up
);
always @ (*)begin
left = 0;
down = 0;
right = 0;
up = 0;
case(scancode)
16'he06b: left = 1;
16'he072:down=1;
16'he074:right=1;
16'he075:up=1;
endcase
end
endmodule