HDLBits刷题合集—5 Procedures
HDLBits-26 Alwaysblock1
Problem Statement
由于数字电路是由与线相连的逻辑门组成的,所以任何电路都可以表示为模块和赋值语句的组合。然而,有时这并不是描述电路最方便的方式。过程(以always块为例)提供了描述电路的另一种语法。
有两种类型的always块是与可综合硬件相关的:
组合逻辑: always @(*)
时序逻辑: always @(posedge clk)
always块等价于赋值语句,因此总是有两种方式来表达一个组合电路。选择使用哪个方法主要是哪个语法更方便的问题。过程块内部代码的语法与外部代码不同。过程块有更丰富的语句集(例如if-then, case),不能包含连续的赋值,但也引入了许多新的不直观的出错方法。
例如,赋值语句和组合always块描述相同的电路。两者都创建了相同的组合逻辑。当任何输入(右侧)更改值时,两者都将重新计算输出。
assign out1 = a & b | c ^ d;
always @(*) out2 = a & b | c ^ d;
使用赋值语句和组合always块来构建与门。
代码如下:
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
output wire out_assign,
output reg out_alwaysblock
);
//assign statement
assign out_assign = a & b;
//combinational always block
always@(*) begin
out_alwaysblock = a & b;
end
ndmodule
HDLBits-27 Alwaysblock2
Problem Statement
时序always块创建一组组合逻辑像组合always块一样,但也在组合逻辑输出处创建一组触发器(或“寄存器”)。不像逻辑的输出是立即可见的,它的输出而是在下一个(posedge clk)之后才可见。
阻塞 VS 非阻塞赋值
在Verilog中存在三种赋值:
连续赋值(assign x = y;):不能在过程中使用(“always块”)。
过程阻塞赋值(x = y;):只能在过程中使用。
过程非阻塞赋值(x <= y;):只能在过程中使用。
在组合always块中,使用阻塞性赋值。在时序always块中,使用非阻塞性赋值。完全理解是为什么对硬件设计用处不大,还需要理解Verilog模拟器如何跟踪事件不遵循这一规则就很难发现仿真和综合硬件之间既不确定又不同的错误。
使用赋值语句、组合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
HDLBits-28 Always if
Problem Statement
if语句通常创建一个2选1的多路复用器,如果条件为真,则选择一个输入;如果条件为假,则选择另一个输入。
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。实现两次,一次用赋值语句,一次用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) begin
out_always = b;
end
else begin
out_always = a;
end
end
endmodule
HDLBits-29 Always if2
Problem Statement
常见的错误来源:如何避免产生锁存器?
在设计电路时,关于电路必须首先考虑:
我想要这个逻辑门
我想要一个逻辑组合块,它有一些输入,产生这一些输出
我想要一个逻辑组合块,后面带有触发器
你千万不能先写代码,然后希望它能产生一个合适的电路。
If (cpu_overheated) then shut_off_computer = 1;
If (~arrived) then keep_driving = ~gas_tank_empty;
语法正确的代码不一定会产生合理的电路(组合逻辑+触发器)。通常的原因是:“除了您指定的那些情况之外,在其他情况下会发生什么?”Verilog的答案是:保持输出不变(产生锁存)。
这种“保持输出不变的行为”意味着需要记住当前状态,从而产生一个锁存器。组合逻辑(例如,逻辑门)不能记住任何状态。注意“Warning (10240): … inferring latch(es)”信息。除非锁存是故意的,否则它几乎总是指出一个bug。组合电路必须在所有条件下都有一个值分配给输出。这通常意味着你总是需要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
HDLBits-30 Always case
Problem Statement
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。
擦色条件允许重复(和部分重叠),但使用第一个匹配的。C语言中不允许重复。
如果在有许多种的情况下,case语句比if语句更方便。因此,在这个练习中,创建一个6对1的多路复用器。当sel在0到5之间时,选择相应的数据输入。否则,输出0。数据输入和输出都是4位宽。
代码如下:
// 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;
endcase
end
endmodule
HDLBits-31 Always case2
Problem Statement
优先级编码器是一种组合电路,当给定一个输入位向量时,输出向量中第一个1位的位置。例如,给定输入8’b10010000的8位优先级编码器将输出3’d4,因为位[4]是第一个高的位。
构建一个4位优先级编码器。如果没有一个输入位是高的(例如输入为零),那么输出为零。注意,一个4位数字有16种可能的组合。
代码如下:
// 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
HDLBits-32 Always casez
Problem Statement
构建一个8位输入优先级编码器。给定一个8位向量,输出应该指出向量中的第一个是1的位。如果输入向量没有1,则输出0。例如,输入8’b10010000应该输出3’d4,因为位[4]是第一个高(为“1”)的位。
根据前面的练习(Always_case2), case语句中有256种情况。如果case语句中的case项支持“不关心”位,我们可以将其减少(减少到9种情况)。这就是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语句的行为就像按顺序检查每一项一样(实际上,它做的事情更像是生成一个巨大的真值表,而不是生成门)。注意某些输入(例如,4’b1111)如何匹配多个case项。选择第一个匹配项(因此4’b1111匹配第一个项out = 0,但不匹配后面的任何项)。
还有一个类似的casex,它把x和z都当作“不关心”。我看不出它比casez有更多的用处。
符号?是z的同义词,所以2’bz0和2’b?0相同。
代码如下:
// synthesis verilog_input_version verilog_2001
module top_module (
input [7:0] in,
output reg [2:0] pos );
always @(*)
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
endmodule
HDLBits-33 Always nolatches
Problem Statement
假设你正在为一个游戏构建一个从PS/2键盘处理扫描代码的电路。给定接收到的最后两个字节的扫描代码,你需要指出键盘上的方向键是否已被按下。这涉及到一个相当简单的映射,它可以实现为包含四种情况的case语句(或if-elseif)。
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位的输入和四个输出。建立这个电路,识别这四个扫描代码,并产生正确的输出。
为了避免创建锁存,所有输出必须在所有可能的条件下分配一个值(请参阅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
end
这种风格的代码确保输出在所有可能的情况下都被赋值(0),除非case语句重写赋值。这也意味着默认的:case项变得没有必要。
代码如下:
// 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 @(*)
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
endmodule
Note
新手一枚,主要分享博客,记录学习过程,后期参考大佬代码或思想会一一列出。欢迎大家批评指正!