if语句出现锁存器
错误的常见来源:如何避免出现锁存器
设计电路时,你必须首先想到在电路方面:
- 我想要这个逻辑门
- 我想要一个具有这些输入并产生这些输出的逻辑组合块
- 我想要一个逻辑组合块,然后是一组触发器
你不能做的是先写代码,然后希望它生成一个合适的电路。If (cpu_overheated) then shut_off_computer = 1;If (~arrived) then keep_driving = ~gas_tank_empty;语法正确的代码不一定会产生合理的电路(组合逻辑 + 触发器)。通常的原因是:“在您指定的情况之外的情况下会发生什么?”。Verilog 的答案是:保持输出不变。
这种“保持输出不变”的行为意味着需要记住当前状态,从而产生一个锁存器。组合逻辑(例如,逻辑门)无法记住任何状态。注意警告(10240):…推断闩锁(es)“消息。除非闩锁是故意的,否则它几乎总是表明存在错误。组合电路必须在所有条件下为所有输出分配一个值。这通常意味着您总是需要else子句或分配给输出的默认值。
练习
以下代码包含创建闩锁的错误行为。修复错误,以便您仅在计算机确实过热时才关闭计算机,并在到达目的地或需要加油时停止驾驶。
always @(*) begin if (cpu_overheated) shut_off_computer = 1; end always @(*) begin if (~arrived) keep_driving = ~gas_tank_empty; end
Module Declaration
// 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 );
答案
// 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 = 0; end endmodule
case语句
Verilog 中的 Case 语句几乎等同于一系列 if-elseif-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
- case 语句以case开头,每个“case item”以冒号结尾。没有“switch”。
- 每个 case 项只能执行一个语句。这使得 C 中使用的“break”变得不必要。但这意味着如果您需要多个语句,则必须使用begin … end。
- 允许重复(和部分重叠)的case项。使用第一个匹配的。C 不允许重复的 case 项。
练习
如果有大量case项,Case 语句比 if 语句更方便。因此,在本练习中,创建一个 6 对 1 多路复用器。当sel在0到5之间时,选择对应的数据输入。否则,输出 0。数据输入和输出均为 4 位宽。
Module Declaration
// 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 );
答案
// 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) 0:out = data0; 1:out = data1; 2:out = data2; 3:out = data3; 4:out = data4; 5:out = data5; default:out = 4'b0000; endcase end endmodule
case语句2
甲优先级编码器是一个组合电路,给定一个输入位向量时,输出第一的位置1中的向量位。例如,给定输入8’b100 1 0000的 8 位优先级编码器将输出3’d4,因为 bit[4] 是第一个高位。
练习
构建一个 4 位优先级编码器。对于这个问题,如果没有一个输入位为高(即输入为零),则输出零。请注意,一个 4 位数字有 16 种可能的组合。
Module Declaration
// synthesis verilog_input_version verilog_2001 module top_module ( input [3:0] in, output reg [1:0] pos );
答案
// synthesis verilog_input_version verilog_2001 module top_module ( input [3:0] in, output reg [1:0] pos ); always@(*) begin // This is a combinational circuit case(in) 0:pos = 0; 1:pos = 0; 2:pos = 1; 3:pos = 0; 4:pos = 2; 5:pos = 0; 6:pos = 1; 7:pos = 0; 8:pos = 3; 9:pos = 0; 10:pos = 1; 11:pos = 0; 12:pos = 2; 13:pos = 0; 14:pos = 1; 15:pos = 0; default:pos = 4'b0000; endcase end endmodule
casez语句
从上一个练习,case 语句中有 256 个 case。如果 case 语句中的 case 项支持 don’t-care 位,我们可以减少这种情况(减少到 9 个 case)。这就是z 的情况:它在比较中将具有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 语句的行为就像是按顺序检查每个项目(实际上,它的作用更像是生成一个巨大的真值表然后制作门)。请注意某些输入(例如4’b1111)如何匹配多个 case 项。选择第一个匹配项(因此4’b1111匹配第一项out = 0,但不匹配后面的任何项)。
还有一个类似的casex将x和z 都视为无关紧要。我认为在casez上使用它没有多大意义。
? 是z的同义词。所以2’bz0和2’b?0是一样的。
练习
为 8 位输入构建一个优先编码器。给定一个 8 位向量,输出应报告向量中的第一个位1。如果输入向量没有高位,则报告零。例如,输入8’b100 1 0000应该输出3’d4,因为 bit[4] 是第一个高位。
Module Declaration
// synthesis verilog_input_version verilog_2001 module top_module ( input [7:0] in, output reg [2:0] pos );
答案
// synthesis verilog_input_version verilog_2001 module top_module ( input [7:0] in, output reg [2:0] pos ); always @(*) begin casez (in) 8'bzzzz_zzz1: pos = 0; 8'bzzzz_zz1z: pos = 1; 8'bzzzz_z1zz: pos = 2; 8'bzzzz_1zzz: pos = 3; 8'bzzz1_zzzz: pos = 4; 8'bzz1z_zzzz: pos = 5; 8'bz1zz_zzzz: pos = 6; 8'b1zzz_zzzz: pos = 7; default: pos = 0; endcase end endmodule
避免锁存器
假设您正在构建一个电路来处理来自游戏的 PS/2 键盘的扫描码。给定接收到的最后两个字节的扫描码,您需要指出是否按下了键盘上的箭头键之一。这涉及一个相当简单的映射,它可以实现为具有四个 case 的 case 语句(或 if-elseif)。
您的电路有一个 16 位输入和四个输出。构建这个电路来识别这四个扫描码并断言正确的输出。
为避免创建锁存器,必须在所有可能的条件下为所有输出分配一个值。仅仅有一个默认情况是不够的。您必须在所有四种情况和默认情况下为所有四个输出分配一个值。这可能涉及许多不必要的打字。解决此问题的一种简单方法是在 case 语句之前为输出分配一个“默认值” :
always @(*) begin up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0; case (scancode) ... // Set to 1 as necessary. endcase end
这种代码风格确保在所有可能的情况下为输出分配一个值(0),除非 case 语句覆盖分配。这也意味着default: case 项变得不必要了。逻辑合成器生成一个组合电路,其行为与代码描述的相同。硬件不会按顺序“执行”代码行。
Module Declaration
// 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 );
练习答案
// 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: left = 1'b1; 16'he072: down = 1'b1; 16'he074:right = 1'b1; 16'he075: up = 1'b1; endcase end endmodule