Always blocks
基础定义
由于数字电路是由用导线连接的逻辑门组成的,因此任何电路都可以表示为模块和赋值语句的某种组合。然而,有时这不是最方便的方式来描述电路。过程(以always块为例)为描述电路提供了另一种语法。
对于综合硬件,两种类型的 always 块是相关的:
Combinational:始终为 @( * )
Clocked:总是 @(posedge clk)
Combinational always blocks are equivalent to assign statements, thus there is always a way to express a combinational circuit both ways.
The syntax for code inside a procedural block is different from code that is outside.
例如,赋值和组合总是块描述相同的电路。两者都创建了相同的组合逻辑。每当任何输入(右侧)改变值时,两者都将重新计算输出。
对于组合always块,始终使用( * )的灵敏度列表。显式列出信号很容易出错(如果遗漏了一个),并且在硬件合成时被忽略。如果显式地指定了灵敏度列表而错过了一个信号,则合成硬件的行为仍然会像指定了( * )一样,但模拟不会也不会匹配硬件的行为。(在SystemVerilog中,使用always_comb。)
关于wire和reg的说明:assign语句的左侧必须是net type(例如,wire),而procedure赋值语句的左侧(在always块中)必须是variable type(例如,reg)。这些types(wire vs. reg)与硬件合成无关,只是Verilog作为硬件模拟语言使用时遗留下来的语法。
练习: Alwaysblock1
Build an AND gate using both an assign statement and a combinational always block.
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
阻塞赋值 vs 非阻塞赋值
Blocking vs. Non-Blocking Assignment
There are 3 types of assignments in Verilog:
- Continuous assignments (assign x = y;). Can only be used when not inside a procedure (“always block”).
- Procedural blocking assignment: (x = y;). Can only be used inside a procedure.
- Procedural non-blocking assignment: (x <= y;). Can only be used inside a procedure.
In a combinational always block, use blocking assignments. In a clocked always block, use non-blocking assignments.
练习:Alwaysblock2
Build an XOR gate three ways, using an assign statement, a combinational always block, and a clocked always block. Note that the clocked always block produces a different circuit from the other two: There is a flip-flop so the output is delayed.
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; //clock always block, 要用非阻塞赋值,即 <=endmodule
Statements
if statement
An if statement usually creates a 2-to-1 multiplexer, selecting one input if the condition is true, and the other input if the condition is false.
always @(*) beginif (condition) beginout = x;endelse beginout = y;end
end
This is equivalent to using a continuous assignment with a conditional operator:
assign out = (condition) ? x : y;
However, the procedural if statement provides a new way to make mistakes. The circuit is combinational only if out is always assigned a value.
对上面这句话进行解释:
使用过程化(procedural)的 if 语句可能会引入错误。如果 out 在某些情况下没有被赋值,就可能导致合成的电路不是一个真正的组合逻辑电路。
组合逻辑电路(combinational logic) 需要确保输出在所有可能的情况下都有明确的赋值。如果 if 语句的所有可能路径中,某些情况下 out 没有被赋值,那么综合工具可能会推导出一个锁存器(latch) 来存储 out 的值,而不是纯粹的组合逻辑。这可能会导致意外的时序问题。
示例错误代码:
always @(*) begin if (condition) out = x; end // 如果 condition 为 false,out 没有赋值
//
在上面的代码中,如果 condition == 0,那么 out 没有被赋值,这就可能导致 out 被综合成锁存器,而不是简单的组合逻辑。
正确的做法: 确保 out 在所有情况下都有赋值,例如:
always @(*) begin if (condition) out = x; else out = y; // 确保 out 总是有值 end
或者,使用更简洁的连续赋值(assign):
assign out = (condition) ? x : y;
这样可以确保 out 在任何情况下都有值,保证是纯组合逻辑,不会意外生成锁存器。
练习:Always if
Build a 2-to-1 mux that chooses between a and b. Choose b if both sel_b1 and sel_b2 are true. Otherwise, choose a. Do the same twice, once using assign statements and once using a procedural if statement.
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 @(*)beginif(sel_b1&sel_b2)beginout_always = b;endelse beginout_always = a;endendendmodule
If statement latches
When designing circuits, you must think first in terms of circuits:
设计电路时,必须首先考虑电路:
- I want this logic gate 我想要这个逻辑门
- I want a combinational blob of logic that has these inputs and produces these outputs 我想要一个具有这些输入并产生这些输出的组合逻辑块
- I want a combinational blob of logic followed by a set of flip-flops我想要一组组合的逻辑后跟一组触发器(flip-flops)
What you must not do is write the code first, then hope it generates a proper circuit.
一定不要做的是先编写代码,然后希望它生成正确的电路。
Syntactically-correct code does not necessarily result in a reasonable circuit (combinational logic + flip-flops). The usual reason is: “What happens in the cases other than those you specified?”. Verilog’s answer is: Keep the outputs unchanged.
语法正确的代码不一定会导致电路合理(组合逻辑+触发器)。通常的原因是:“在您指定的情况以外的情况下会发生什么?”。 Verilog的答案是:保持输出不变。
This behaviour of “keep outputs unchanged” means the current state needs to be remembered, and thus produces a latch. Combinational logic (e.g., logic gates) cannot remember any state.
这种“保持输出不变”的行为意味着需要记住当前状态,从而产生一个锁存器。组合逻辑(例如逻辑门)无法记住任何状态。
注意警告:Warning (10240): ... inferring latch(es)" messages.
Unless the latch was intentional, it almost always indicates a bug. Combinational circuits must have a value assigned to all outputs under all conditions. This usually means you always need else clauses or a default value assigned to the outputs.
除非有意使用锁存器,否则它几乎总是表示错误。在所有条件下,组合回路必须为所有输出分配一个值。这通常意味着您总是需要else子句或一个默认值分配给输出。
练习:Always if2
The following code contains incorrect behaviour that creates a latch. Fix the bugs so that you will shut off the computer only if it’s really overheated, and stop driving if you’ve arrived at your destination or you need to refuel.
always @(*) beginif (cpu_overheated)shut_off_computer = 1;
endalways @(*) beginif (~arrived)keep_driving = ~gas_tank_empty;
end
Solution
module top_module (input cpu_overheated,output reg shut_off_computer,input arrived,input gas_tank_empty,output reg keep_driving ); //always @(*) beginif (cpu_overheated)shut_off_computer = 1;elseshut_off_computer = 0;endalways @(*) beginif (arrived | gas_tank_empty)keep_driving = 0;else keep_driving = 1;endendmodule
case statement
Case statements in Verilog are nearly equivalent to a sequence of if-elseif-else that compares one expression to a list of others. Its syntax and functionality differs from the switch statement in C.
语句:case(条件) 语句 endcase
always @(*) begin // This is a combinational circuitcase (in)1'b1: begin out = 1'b1; // begin-end if >1 statementend1'b0: out = 1'b0;default: out = 1'bx;endcase
end
- The case statement begins with case and each “case item” ends with a colon.
- Each case item can execute exactly one statement. This means that if you need more than one statement, you must use begin … end.
- Duplicate (and partially overlapping) case items are permitted.
练习:Always case
Case statements are more convenient than if statements if there are a large number of cases. So, in this exercise, create a 6-to-1 multiplexer. When sel is between 0 and 5, choose the corresponding data input. Otherwise, output 0. The data inputs and outputs are all 4 bits wide.
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 circuitcase(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 = 0;endcaseendendmodule
Priority
priority encoder 优先编码器
A priority encoder is a combinational circuit that, when given an input bit vector, outputs the position of the first 1 bit in the vector.
For example, a 8-bit priority encoder given the input 8’b10010000 would output 3’d4, because bit[4] is first bit that is high.
输入向量分析:
位索引: 7 6 5 4 3 2 1 0
值: 1 0 0 1 0 0 0 0
这里的优先级编码器会从最低位(索引 0)开始向高位扫描,寻找第一个 1。
优先级编码器的工作过程:
- 从最低位(索引 0)开始扫描,检查每一位的值。
- 当检测到第一个值为 1 的位时,停止扫描,并输出该位的索引。
- 在上述例子中,第一个 1 出现在索引 4,因此输出是 3’d4(3 位二进制表示的 4)。
当多个输入信号同时有效时,优先编码器会根据预设的优先级规则,识别出最高优先级的信号,并输出对应的二进制码。
练习:Always case2
Build a 4-bit priority encoder. For this problem, if none of the input bits are high (i.e., input is zero), output zero. Note that a 4-bit number has 16 possible combinations.
module top_module (input [3:0] in,output reg [1:0] pos );always @(*) begin // Combinational always blockcase (in)4'h0: pos = 2'h0; 4'h1: pos = 2'h0;4'h2: pos = 2'h1;4'h3: pos = 2'h0;4'h4: pos = 2'h2;4'h5: pos = 2'h0;4'h6: pos = 2'h1;4'h7: pos = 2'h0;4'h8: pos = 2'h3;4'h9: pos = 2'h0;4'ha: pos = 2'h1;4'hb: pos = 2'h0;4'hc: pos = 2'h2;4'hd: pos = 2'h0;4'he: pos = 2'h1;4'hf: pos = 2'h0;default: pos = 2'b0; endcaseendendmodule
priority encoder–casez
From the previous exercise (always_case2), there would be 256 cases in the case statement. We can reduce this (down to 9 cases) if the case items in the case statement supported don’t-care bits. This is what casez is for: It treats bits that have the value z as don’t-care in the comparison.
练习:Always casez
module top_module (input [7:0] in,output reg [2:0] pos );always @(*) begincasez(in) 8'bzzzzzzz1:pos=0;8'bzzzzzz10:pos=1;8'bzzzzz100:pos=2;8'bzzzz1000:pos=3;8'bzzz10000:pos=4;8'bzz100000:pos=5;8'bz1000000:pos=6;8'b10000000:pos=7;default:pos=0;endcaseend
endmodule
Avoid latches
练习:Always nolatches
Suppose you’re building a circuit to process scancodes from a PS/2 keyboard for a game. Given the last two bytes of scancodes received, you need to indicate whether one of the arrow keys on the keyboard have been pressed. This involves a fairly simple mapping, which can be implemented as a case statement (or if-elseif) with four cases.
Your circuit has one 16-bit input, and four outputs. Build this circuit that recognizes these four scancodes and asserts the correct output.
To avoid creating latches, all outputs must be assigned a value in all possible conditions (See also always_if2). Simply having a default case is not enough. You must assign a value to all four outputs in all four cases and the default case. This can involve a lot of unnecessary typing. One easy way around this is to assign a “default value” to the outputs before the case statement:
always @(*) beginup = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;case (scancode)... // Set to 1 as necessary.endcase
end
This style of code ensures the outputs are assigned a value (of 0) in all possible cases unless the case statement overrides the assignment. This also means that a default: case item becomes unnecessary.
Reminder: The logic synthesizer generates a combinational circuit that behaves equivalently to what the code describes. Hardware does not “execute” the lines of code in sequence.
module top_module (input [15:0] scancode,output reg left,output reg down,output reg right,output reg up ); always @(*) beginup = 1'b0;down = 1'b0;left = 1'b0;right = 1'b0;case (scancode)16'he06b: left=1;16'he072: down=1;16'he074: right=1;16'he075: up=1;endcaseendendmodule