Problem 28:Always blocks(combination)(Alwaysblock1)
任何电路都可以用
module
和assign
语句组合出来。
这里介绍一种过程块,always
块,提供了一种更便捷地描述电路的方法
有两种 always
块可以综合出电路硬件的
always @(*)//综合逻辑
always @(posedge clk)//时序逻辑
- 组合
always
块相当于assign
语句
过程块内的代码与外部的 assign 代码不同。
- 过程块中,我们可以使用比较丰富的语句(if-then ,case ),但不能包含连续赋值
例如,assign
和组合和 always
块描述相同的电路,两者创造出了相同的组合逻辑电路,只要任何输入(右侧)改变值,两者都将重新计算输出
assign out1 = a & b | c ^ d;
always @(*) out2 = a & b | c ^ d;
对于组合 always 块,敏感变量列表总是使用 (*)
。
如果把所有的输入都列出来也是可以的,但容易出错的(可能少列出了一个),并且在硬件综合时会忽略您少列了一个,仍按原电路综合。 但仿真器将会按少列一个来仿真,这导致了仿真与硬件不匹配。(在 SystemVerilog 中,使用 always_comb)
使用 assign 语句和组合 always 块来构建与门。
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
output wire out_assign,
output reg out_alwaysblock
);
assign out_assign=a&b;
always @(*)
out_alwaysblock=a&b;//两种方法实现与门
endmodule
Problem 29:Always blocks(clocked)(Alwaysblock2)
对于硬件综合来说,存在两种 always 块
always @(*)//组合逻辑
always @(posedge clk)//时序逻辑
时序 always 块也会像组合 always 块一样生成一系列的组合电路,但同时在组合逻辑的输出生成了一组触发器(或寄存器)
- 通过时序 always 块的输出会在下一个时钟上升沿(
posedge clk
)后可见。
阻塞性赋值和非阻塞性赋值
在 Verilog 中有三种赋值方法:
连续赋值(assign x=y;):不能在过程块内使用;
过程阻塞性赋值(x=y;):只能在过程块中使用;
过程非阻塞性赋值(x<=y):只能在过程块内使用。
使用 assign 语句,组合 always 块和时序 always 块,这三种方式来构建异或门。
- 时序 always 块生成了与另外两个不同的电路,多了一个触发器,因此输出会有一定的延时
// synthesis verilog_input_version verilog_2001
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
- 从仿真的波形图可以看出,out_always_ff 比其他两个输出延迟了一个时钟周期,这就是非阻塞性赋值带来的。
Problem 30:If statement(Always if)
- if 语句通常对应一个二选一多路复用器,如果条件为真,则选择其中一个输入作为输出;反之如果条件为假,则选择另一个输入所谓输出。
- if 语句必须在过程块内使用。
always @(*) begin if (condition) begin out = x; end else begin out = y; end end
- 这与下面使用条件运算符连续赋值的语句是等价的:
assign out = (condition) ? x : y;
但是,过程 if 语句使用不当可能会引入新的错误,只有 out 在所有的条件下都被赋值才会生成正确的组合电路,具体的错误下一个训练才会讲到,
构建一个可以在 a 和 b 之间选择的二选一多路复用器。如果 sel_b1 和 sel_b2 都为真,输出 b,其他情况输出 a。请使用两种方法作答,一次使用 assign 赋值,一次使用 if 语句。
sel_b1 | sel_b2 | out_assign out_always |
---|---|---|
0 | 0 | a |
0 | 1 | a |
1 | 0 | a |
1 | 1 | b |
// synthesis verilog_input_version verilog_2001
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)
out_always=b;
else
out_always=a;
end
endmodule
Problem 31:If statement latches(Always if2)
常见的错误来源:如何避免引入锁存器
在设计电路时,必须首先具体考虑电路:
- 我想实现一个逻辑门;
- 我想实现一个具有输入并产生输出的组合逻辑块
- 我想实现一组组合逻辑,紧接着一组触发器
不要上来就写代码,这样往往与你想象的电路相差甚远!
语法正确的代码不一定会产生合理的电路(组合逻辑 + 触发器)。
在你用 if
语句的时候,Verilog 会在你指定情况之外保持输出不变,这意味着电路需要记住当前状态,从而产生锁存器,组合逻辑并不能记住任何状态,这就会导致错误。
Warning (10240): ... inferring latch(es)
上述这类警告通常情况下代表错误,除非锁存器是故意生成的。组合电路输出必须在所有输入的情况下都有值。这意味着必须需要 else 子句或输出默认值。
示例:以下代码包含生成锁存器的错误,请勿模仿!!!
修复错误,只有当它真的过热时才关闭计算机,真的到达目的地或者需要加油时,才停止驾驶。
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
end
修改如下:
// synthesis verilog_input_version verilog_2001
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=~arrived;
end
endmodule
Problem 32:Casestatement (Always case)
Verilog 中的 Case 语句几乎等同于将一个表达式与其他表达式列表进行比较的一系列 if-elseif-else。
语法示例:
always @(*) begin // This is a combinational circuit
case (in)//与C的switch不同
1'b1: begin
out = 1'b1; // begin-end if >1 statement
end
1'b0: out = 1'b0;
default: out = 1'bx;
endcase
end
如果存在大量的 case 项,则 case 语句比 if 语句更方便。
在本练习中,创建一个 6 选 1 的多路复用器。当 sel 介于 0 和 5 之间时,选择相应的数据输入。 其他情况输出 0。数据输入和输出均为 4 位宽。
注意:不要生成锁存器(Problem 31)
// synthesis verilog_input_version verilog_2001
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'b000:out=data0;
3'b001:out=data1;
3'b010:out=data2;
3'b011:out=data3;
3'b100:out=data4;
3'b101:out=data5;
default :out=4'b0000;//一定要用default声明不在case项里的输出,否则会生成不必要的寄存器
endcase
end
endmodule
problem 33:Priority encoder(Always case 2)
优先编码器是组合电路,当给定输入向量时,输出向量中第一个 1 的位置。例如,输入 8'b10010000 的,则优先编码器将输出 3'd4,因为位[4]是从右数第一个 1。
构建一个 4 位优先编码器,如果没有一个输入位,则输出零,
// synthesis verilog_input_version verilog_2001
module top_module (
input [3:0] in,
output reg [1:0] pos);
always@(*)
case(in)
4'b0000: pos = 2'b00;//二进制编码,看起来相对比较直观
4'b0001: pos = 2'b00;
4'b0010: pos = 2'b01;
4'b0011: pos = 2'b00;
4'b0100: pos = 2'b10;
4'b0101: pos = 2'b00;
4'b0110: pos = 2'b01;
4'b0111: pos = 2'b00;
4'b1000: pos = 2'b11;
4'b1001: pos = 2'b00;
4'b1010: pos = 2'b01;
4'b1011: pos = 2'b00;
4'b1100: pos = 2'b10;
4'b1101: pos = 2'b00;
4'b1110: pos = 2'b01;
4'b1111: pos = 2'b00;
default: pos = 2'b00;
endcase
endmodule
// synthesis verilog_input_version verilog_2001
module top_module (
input [3:0] in,
output reg [1:0] pos );
always @(*) begin
if (in[0]) pos = 0;//最省代码的写法
else if (in[1]) pos = 1;
else if (in[2]) pos = 2;
else if (in[3]) pos = 3;
else pos = 0;
end
endmodule
Problem 34:Priority encoder with casez(Always casez)
构建一个 8 输入的优先编码器,给定一个 8 位向量,输出输入向量中第一个 1 的位置。如果没有输入,则输出 0。
例如,输入 8'b10010000 应该输出 3'd4,因为位[4]是第一个出现 1 的位置。
case 项是按顺序去检查的,实际上像是生成了一个巨大的真值表和一个门,当输入匹配多个 case 项时,匹配第一个遇到的 case。
如果按照上一个去写 case 语句的话,case 语句中将有 256 个 case 项,引入 casez。
如果 case 语句中的 case 项与某些输入无关,就可以减少列出的 case 项
casez:在比较中将具有值 z 的位视为无关项。
上题可用 casez 写为
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
// synthesis verilog_input_version verilog_2001
module top_module (
input [7:0] in,
output reg [2:0] pos );
always @(*)
casez (in[7:0])
8'bzzzzzzz1:pos=3'b000;//这里casez是按照顺序来执行的
8'bzzzzzz1z:pos=3'b001;//也可以写成8‘bzzzzzz10:pos=3'b001,写逻辑比较清晰且不用看顺序
8'bzzzzz1zz:pos=3'b010;
8'bzzzz1zzz:pos=3'b011;
8'bzzz1zzzz:pos=3'b100;
8'bzz1zzzzz:pos=3'b101;
8'bz1zzzzzz:pos=3'b110;
8'b1zzzzzzz:pos=3'b111;
default :pos=3'b000;
endcase//注意这边是endcase
endmodule
Problem 35:Always nolatches(Always nolatches)
假设建立一个电路,去处理游戏 PS/2 键盘扫描码。给出接收到扫描码的最后的两个字节,您需要判断是否有按键被按下。是一个相当简单的映射,可以使用 case 语句或者 if-else 语句实现,一共有如下四种情况。
Scancode [15:0] | Arrow key |
---|---|
16'he06b | left arrow |
16'he072 | down arrow |
16'he074 | right arrow |
16'he075 | up arrow |
Anything else | none |
16 个输入,4 个输出,描述该电路,并识别这四个按键的扫描码并输出
同时为避免生成了不必要的锁存器,必须在所有条件下为所有的输出赋值(参见 Problem 31: If statement latches(Always if2) )。这可能会多打很多字,使你的代码变得冗长。 一个简单的方法是在 case 语句之前为输出分配一个“默认值”:
always @(*) begin up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0; case (scancode) ... // Set to 1 as necessary. endcase//这种代码格式可以确保“默认值”的输出,default项无关紧要了(前提是“默认值”没有被覆盖) end
- 笨办法(最全)
// synthesis verilog_input_version verilog_2001
module top_module (
input [15:0] scancode,
output reg left,
output reg down,
output reg right,
output reg up );
always@(*)
begin
up=1'b0;
down=1'b0;
left=1'b0;
right=1'b0;
case (scancode)
16'he06b: begin
up=1'b0;
down=1'b0;
left=1'b1;
right=1'b0;end
16'he072:begin
up=1'b0;
down=1'b1;
left=1'b0;
right=1'b0;end
16'he074:begin
up=1'b0;
down=1'b0;
left=1'b0;
right=1'b1;end
16'he075:begin
up=1'b1;
down=1'b0;
left=1'b0;
right=1'b0;end
default :begin
up=1'b0;
down=1'b0;
left=1'b0;
right=1'b0;end
endcase
end
endmodule
- 笨办法的简化版
module top_module (
input [15:0] scancode,
output reg left,
output reg down,
output reg right,
output reg up );
always@(*)
casez(scancode)
16'he06b: begin up = 1'b0; down = 1'b0; left = 1'b1; right = 1'b0; end
16'he072: begin up = 1'b0; down = 1'b1; left = 1'b0; right = 1'b0; end
16'he074: begin up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b1; end
16'he075: begin up = 1'b1; down = 1'b0; left = 1'b0; right = 1'b0; end
default: begin up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0; end
endcase
endmodule
- 最佳 solution
module top_module (
input [15:0] scancode,
output reg left,
output reg down,
output reg right,
output reg up );
always@(*)
begin
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
casez(scancode)
16'he06b: left = 1'b1;
16'he072: down = 1'b1;
16'he074: right = 1'b1;
16'he075: up = 1'b1;
endcase
end
endmodule
- 上面的“最佳 solution”会报告一个 case 语句没有 default 的 warning,但综合出的电路应该是和“笨办法”一样的