HDLBits答案(4)_如何避免生成锁存器
HDLBits_Verilog Language_Procedures
组合逻辑的always块
tips:考虑到硬件的可综合性,有两种always块是有意义的:
- 组合:
always @(*)
- 时序:
always @(posedge clk)
组合逻辑的always块和assign赋值是等价的,使用哪一种完全看哪一种更方便。always块内可有更丰富的状态,如if-then,case等,但不能有连续赋值语句assign。
如果在always块内指定了特定的信号,但没有的话,和always(*)综合结果相同,此时功能仿真结果和硬件结果就有所差异。
assign赋值语句的左边一般为wire类型,always块中左边的变量一般为reg类型。这些类型与硬件综合无关,仅是verilog语法的要求。
题目描述:
使用赋值语句和组合always块两种方式构建与门。
Solution:
1 | module top_module( |
时序逻辑的always块
tips:时序always块可像组合always块那样生成电路,同时也会生成一系列的触发器,寄存器等,因为输出要等到下个时钟延才能输出。
阻塞赋值 vs 非阻塞赋值
verilog中有三种赋值方式:
- 连续赋值(assign x=y;),只能在always块外使用。
- 阻塞赋值(x=y;),只能在always块内使用。
- 非阻塞赋值(x<=y;)只能在always块内使用。
在组合逻辑的always块中(always @(*))使用阻塞赋值语句;
在时序逻辑的always块中(always @(posedge clk))使用非阻塞赋值语句。
题目描述:
使用赋值语句、组合always块和时序always块三种方式构建一个异或门。
Solution:
1 | module top_module( |
IF选择器
tips:一个if语句会产生一个2选1的数据选择器,需注意的是,并不是if选择的那路数据才被实现成电路模式,而是if和else两路都被实现为电路形式然后用选择器选择输出。
if的两种方式:
1 | always @(*) begin |
1 | assign out = (condition) ? x : y; |
题目描述:
2-1选择器
sel_b1 | sel_b2 | out_assign/out_always |
---|---|---|
0 | 0 | a |
0 | 1 | a |
1 | 0 | a |
1 | 1 | b |
Solution:
1 | module top_module( |
IF中锁存器问题
tips:如何避免在使用if语句时生成锁存器?
tip:锁存器与触发器的区别?
- 锁存器是一种对脉冲电平(也就是0或者1)敏感的存储单元电路,而触发器是一种对脉冲边沿(即上升沿或者下降沿)敏感的存储电路。
当我们在设计电路时,不能直接先写成代码然后期望它直接生成为合适的电路,如下典型错误所示:
- If (cpu_overheated) then shut_off_computer = 1;
- If (~arrived) then keep_driving = ~gas_tank_empty;
语法上正确的代码并不意味着设计成的电路也是合理的。我们来思考这么一个问题,如上图的错误示例,如果if条件不满足,输出如何变化呢?Verilog给出的解决方法是:保持输出不变。因为组合逻辑电路不能记录当前的状态,所以就会综合出锁存器。
所以当我们使用if语句或者case语句时,我们必须考虑到所有情况并给对应情况的输出进行赋值,就意味着我们要为else或者default中的输出赋值。
题目描述:找BUG,解决下面的代码中包含的创建锁存的不正确行为。
1 | always @(*) begin |
Solution:
1 | module top_module ( |
Case语句
tips:在Verilog中,case语句与if-elseif-else相近,与c语言中的switch差别较大,示例如下:
1 | always @(*) begin // This is a combinational circuit |
- case语句以case开始,每个case的选项以分号结束。
- 每个case的选项中只能执行一个statement,所以就无需break语句。但如果我们想在一个case选项中执行多个statement,就需要使用
begin...end
- case中可以有重复的case item,首次匹配的将会被执行。
题目描述:6-1数据选择器
Be careful of inferring latches!(需添加Default)
Solution:
1 | module top_module ( |
优先编码器
题目描述:优先编码器是一种组合电路,当给定输入位向量时,输出该向量中第一个1的位置。 例如,给定输入8’b10010000的8位优先级编码器将输出3’d4,因为bit [4]是高的第一位。
构建一个4位优先级编码器。 对于此问题,如果所有输入位都不为高(即输入为零),则输出零。 请注意,一个4位数字具有16种可能的组合。
Solution:
1、根据惯性思维使用case语法,列出每一种情况,然后列出其对应的输出。
1 | case(input) |
2、如果按上述思路来写,那么更多位的优先编码器如何实现呢?其实有更简单的方法,目前既不用casex也不用casez,这两位要在后面出场。来看另一种思路:
1 | module top_module ( |
根据上面所说的case的性质,case中可以有重复的case item,但首次匹配的才会被执行。再看上面的case语句,即从低到高位去比较in中是否有数据位为1,下面即使有重复为1的也只会执行首次匹配的操作。如此,N位优先编码器只需要N个case分支即可,大大简化代码量。
casez实现优先编码器
题目描述:
为8位输入构建优先级编码器。当给定输入位向量时,输出该向量中第一个1的位置。如果输入向量没有高位,则报告为零。 例如,给定输入8’b10010000的8位优先级编码器将输出3’d4,因为bit [4]是高的第一位。
tips:由上一个练习我们知道,case语句中将有256种case item,使用casez以后,我们可以减少需比较的case item,这就是casez的目的,在比较中,将值z的位视作无关位。
所以上题的另一种解法为:
1 | always @(*) begin |
case语句的行为就好像是按顺序检查每个项一样(实际上,它所做的事情更像是生成一个巨大的真值表,然后进行门操作)。注意某些输入(例如4’b1111)是如何匹配多个case项的。选择第一个匹配项(因此4’b1111匹配第一个项目,out = 0,但不匹配后面的任何项目)。
- 还有类似的
casex
,它将x和z均无视掉。 - 符号?是z的同义词,所以2‘bz0=2’b?0
Solution:
1 | module top_module ( |
该题也可以按Priority encoder中的解法2来写,复杂度甚至更低。
去锁存器
题目描述:
假设构建一个电路来处理游戏的PS/2键盘上的扫描代码。对于收到的最后两个字节的扫描码,我们需要指示是否按下了键盘上的一个方向键。这涉及到一个相当简单的映射,它可以实现为一个case语句(或if-elseif),包含四个case。
电路有一个16位输入和四个输出。建立能识别这四种扫描码并正确输出的电路。
Scancode[15:0] | Arrow key |
---|---|
16’he06b | left arrow |
16’he072 | down arrow |
16’he074 | right arrow |
16’he075 | up arrow |
Anything else | none |
tips:为避免生成锁存器,所有的输入情况必须要被考虑到。但仅有一个简单的default是不够的,我们必须在case item和default中为4个输出进行赋值,这会导致很多不必要的代码编写。
一种简单的方式就是对输出先进行赋初值的操作,这种类型的代码确保在所有可能的情况下输出都被赋值,除非case语句覆盖了赋值。这也意味着不再需要缺省的default项。如下面示例代码:
1 | always @(*) begin |
Solution:
1 | module top_module ( |
总结:
学习了组合和时序两种always块,并知晓了两种类型的always块中变量的类型即赋值方式。何时使用wire与reg,何时用阻塞和非阻塞赋值。
学习了如何在组合逻辑中使用if和case语句时避免生成锁存器:
将所有状态均考虑到并为其输出赋值,考虑不全可用else/default。
在case之前为所有输出赋初值,这样可以不用使用default,除非满足case item进行覆盖赋值,否则仍保持初值。
时序逻辑下不会生成锁存器。
学习了case语句的妙用,以及使用casez忽略无关位,更简便的实现优先编码器。