HDLBits习题讲解_Procedures
always语句块
- 由于数字电路的实际实现,均是由基础的逻辑门搭建而成,因此任何电路都可以表示为模块和分配语句的某种组合。然而,有时这不是描述电路的最方便的方式。
always
、initial
、task
、function
为述电路提供了替代语法。
- 对于always语句块而言,考虑到硬件的可综合性,有两种always块是有意义的:
- 组合:
always @(*)
- 时序:
always @(posedge clk)
- 组合逻辑的always块和assign赋值是等价的,使用哪一种完全看哪一种更方便。always块内可有更丰富的状态,如if-then,case等,但不能有连续赋值语句assign。assign赋值语句的左边一般为wire类型,always块中左边的变量一般为reg类型。这些类型与硬件综合无关,仅是verilog语法的要求。
- 时序逻辑的always块会生成一系列的触发器,即时序敏感型,只在时钟边沿到来时,才会对输出数据进行更新,否则则会对现有数据进行保持。在Verilog语法中,过程赋值语句用于对寄存器类型(reg)进行赋值,有着两种赋值方式:阻塞赋值与非阻塞赋值。
- 阻塞赋值方式:赋值符号为“=”,如b = a; 。阻塞赋值在该语句结束时就完成赋值操作,即b的值在该赋值语句结束后立刻改变。 如果在一个块语句中,有多条阻塞赋值语句,那么在前面的赋值语句没有完成之前,后面的语句就不能执行,就像被阻塞(blocking)了一样,因此称为阻塞赋值方式。
- 非阻塞赋值方式:赋值符号为“<=”,如b <= a;。非阻塞赋值在块结束时才完成赋值操作,即b的值并不是立即就改变的,而要等到该always代码块结束时才将值赋值给目标对象。所以,若是在一个always代码块中对一个变量进行多次非阻塞赋值,则只有最后一次赋值将生效。
- 在描述组合逻辑的always块中用阻塞赋值,则综合成组合逻辑的电路结构;在描述时序逻辑的always块中用非阻塞赋值,则综合成时序逻辑的电路结构。
Always blocks (combinational)
- 题目要求,使用赋值语句和组合always块两种方式构建与门。代码如下:
module top_module(
input a,
input b,
output wire out_assign,
output reg out_alwaysblock
);
assign out_assign = a & b;
always @(*) begin
out_alwaysblock = a & b;
end
endmodule
Always blocks (clocked)
- 题目要求,使用赋值语句、组合always块和时序always块三种方式构建一个异或门。
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 @(*) begin
out_always_comb = a ^ b;
end
always @(posedge clk) begin
out_always_ff <= a ^ b;
end
endmodule
- 观察时序图,我们可以发现,
out_always_ff
的波形滞后于out_assign
和out_always_comb
一个时钟周期,这是由于在时序逻辑电路中,触发器是时钟边沿敏感型的,仅在时钟边沿到来时,才更新输出。
if-else语句
- 其格式与C语言中if-else语句类似,使用方法有以下3种。系统对表达式的值进行判断,若为0,x,z,按“假”处理;若为“1”,按“真”处理,执行指定语句。 语句可是单句,也可是多句,多句时用“begin-end”语句括起来
if(表达式) 语句1;
//////////////////////////////////
if(表达式) 语句1;
else 语句2;
/////////////////////////////////
if(表达式1) 语句1;
else if(表达式2) 语句2;
……
else if(表达式n) 语句n;
else 语句n+1;
- __使用条件语句注意事项:__在使用条件语句时,应注意列出所有条件分支,否则,编译器认为条件不满足时,会引进一个触发器保持原值。这可用于设计时序电路,例如在计数器的设计中,条件满足则加1,否则保持不变;而在组合电路设计中,应避免这种隐含触发器的存在。为避免偶然生成锁存器的错误。如果用到if语句,最好写上else项。如果用case语句,最好写上default项。遵循上面两条原则,就可以避免发生这种错误。
If statement
- 题目要求,构件一个2-1选择器,该器件的真值表如下:
sel_b1 |
sel_b2 |
out_assign |
out_wire |
0 |
0 |
a |
a |
0 |
1 |
a |
a |
1 |
0 |
a |
a |
1 |
1 |
b |
b |
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_b2&sel_b1) begin
out_always = b;
end
else begin
out_always = a;
end
end
endmodule
If statement latches
- 题目要求,找出代码中的BUG,解决下面的代码中包含的创建锁存器的不正确行为。
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
end
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 语句
- 当敏感表达式的值为值1时,执行语句1;为值2时,执行语句2;依此类推,如果敏感表达式的值与上面列出的值都不相等的话,则执行default后面的语句。
- case语句中,敏感表达式与值1~值n间的比较是一种全等比较,必须保证两者的对应位全等。 在casez语句中,如果分支表达式某些位的值为高阻z,那么对这些位的比较就不予考虑,因此只须关注其他位的比较结果。而在casex语句中,则把这种处理方式进一步扩展到对x的处理,即如果比较的双方有一方的某些位是x或z,那么这些位的比较就都不予考虑。
- case中可以有重复的case item,首次匹配的将会被执行。
- case语句的使用格式如下:
case(敏感表达式)
值1:语句1; // case分支项
值2:语句2;
……
值n:语句n;
default:语句n+1; // default语句可以省略
endcase
- __使用条件语句注意事项:__在使用条件语句时,应注意列出所有条件分支,否则,编译器认为条件不满足时,会引进一个触发器保持原值。这可用于设计时序电路,例如在计数器的设计中,条件满足则加1,否则保持不变;而在组合电路设计中,应避免这种隐含触发器的存在。为避免偶然生成锁存器的错误。如果用到if语句,最好写上else项。如果用case语句,最好写上default项。遵循上面两条原则,就可以避免发生这种错误。
Case statement
- 题目要求,使用case语句创建一个6-1多路复用器。当sel在 0 到 5 之间时,请选择相应的数据输入。否则,输出 0。数据输入和输出均为 4 位宽。
- 代码如下:
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'b0;
endcase
end
endmodule
Priority encoder
- 优先编码器是一种组合电路,当给定输入位向量时,输出该向量中第一个1的位置。 例如,给定输入
8'b10010000
的8位优先级编码器将输出3'd4
,因为bit [4]
第一个高位。
- 题目要求,构建一个4位优先级编码器。 对于此问题,如果所有输入位都不为高(即输入为零),则输出零。 请注意,一个4位数字具有16种可能的组合。
- 代码如下:
module top_module (
input [3:0] in,
output reg [1:0] pos );
always @(*) begin
case (in)
4'b0000:pos = 0;
4'b0001:pos = 0;
4'b0010:pos = 1;
4'b0011:pos = 0;
4'b0100:pos = 2;
4'b0101:pos = 0;
4'b0110:pos = 1;
4'b0111:pos = 0;
4'b1000:pos = 3;
4'b1001:pos = 0;
4'b1010:pos = 1;
4'b1011:pos = 0;
4'b1100:pos = 2;
4'b1101:pos = 0;
4'b1110:pos = 1;
4'b1111:pos = 0;
default:pos = 0;
endcase
end
endmodule
Priority encoder with casez
- 为8位输入构建优先级编码器。当给定输入位向量时,输出该向量中第一个1的位置。如果输入向量没有高位,则报告为零。 例如,给定输入8’b10010000的8位优先级编码器将输出3’d4,因为bit [4]是高的第一位。由上一个练习我们知道,case语句中将有256种case item,使用casez以后,我们可以减少需比较的case item,这就是casez的目的,在比较中,将值z的位视作无关位。
- 代码如下:
module top_module (
input [7:0] in,
output reg [2:0] pos );
always @(*) begin
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
end
endmodule
Avoiding latches
- 假设构建一个电路来处理游戏的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 |
- 一种简单的方式就是对输出先进行赋初值的操作,这种类型的代码确保在所有可能的情况下输出都被赋值,除非case语句覆盖了赋值。这也意味着不再需要缺省的default项。如下面示例代码:
always @(*) begin
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
case (scancode)
... // Set to 1 as necessary.
endcase
end
module top_module (
input [15:0] scancode,
output reg left,
output reg down,
output reg right,
output reg up );
always @(*) begin
left=0;down=0;right=0;up=0;
case(scancode)
16'he06b:left=1;
16'he072:down=1;
16'he074:right=1;
16'he075:up=1;
endcase
end
endmodule
参考:
HDLBits
HDLBits答案(4)_如何避免生成锁存器?