31 if 语句与锁存器
题目描述
使用 verilog 设计电路时,应按照如下流程:
- 确定你需要的电路或逻辑门
- 确定输入输出信号,以及产生输出信号的组合逻辑块
- 确定组合逻辑块后面是否加上一组触发器。
因此,应极力避免这样的心态:试着写下一段代码,然后期待其生成正确的电路,如:
if (cpu_overheated) then shut_off_computer = 1;
if (~arrived) then keep_driving = ~gas_tank_empty;
语法正确的代码并不一定能产生功能正常的电路,一般来说都是因为不小心引入了锁存器造成的。如上述例子所示,除了指定的情况外(cpu_overheated
),还有一些其它情况,这时会发生什么?在 verilog 中,其结果就是保持不变,这意味着要记住当前状态,从而产生了锁存。
另一个例子代码与图示如下:
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
end
为消除锁存器,我们应当使组合逻辑过程块中的条件完备,即 if 语句后应加上 else 语句。
试修改上述两段代码,以消除锁存器。
输入格式
输入信号 cpu_overheated, 位宽 1bit,控制 shut_off_computer 信号。 输入信号 arrived, 位宽 1bit,控制 keep_driving 信号。 输入信号 gas_tank_empty,位宽 1bit, 作为 keep_driving 的输入信号之一。
输出格式
输出信号 shut_off_computer,位宽 1bit,要求 cpu_overheated 为真时输出 1'b1,反之输出 1'b0。 输出信号 keep_driving,位宽 1bit,要求 arrived 为假时输出 ~gas_tank_empty,反之输出 1'b0。
module top_module (
input cpu_overheated ,
output reg shut_off_computer ,
input arrived ,
input gas_tank_empty ,
output reg keep_driving
);
// Edit the code below
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1'b1;
else
shut_off_computer = 1'b0;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
else
keep_driving = ~arrived;
end
endmodule
32 case语句
题目描述
Verilog中的case语句几乎等同于if…else if…else…序列,其语法与C语言中的switch语句类似,如下例所示:
always @(*) begin // This is a combinational circuit
case (in)
1'b1: out = 1'b1; // 对应if(in>=1) 语句
1'b0: out = 1'b0; // 对应else语句
default: out = 1'bx; //使用case实现组合逻辑时,必须有default,以防出现锁存器
endcase //endcase语句表示case语句结束,两者成对使用
end
-case语句以关键字case开始,以endcase结束,两者成对出现
-case语句中每个条目只执行一条语句,如要在一个条目下进行多个赋值,需要将多条预计放在begin/end关键字之间
-case条目允许重复或部分重叠,第一个匹配到的条目有效
当有多个条目进行选择时,使用case语句比if…else…语句方便很多,本例中,使用Verilog设计六选一选择器,当sel信号在0~5时,选择对应的数据输出,否则输出0,输入输出数据位宽均为4bit。
输入格式
1个 3bit 位宽选择信号 sel 6个 4bit 位宽数据信号 data0、data1、...
输出格式
1个 4bit 位宽信号 out
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'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
33 优先编码器
题目描述
优先编码器是一种组合电路,当给定一个输入位矢量时,输出矢量中第一个 1 位的位置。例如,给定输入 8'b10010000 的 8 位优先级编码器将输出 3'd4,因为位 [4] 是高的第一位。
设计一个 4 位优先编码器电路。如果没有输入位为高电平(即输入为零),则输出为零。请注意,4 位数字有 16 种可能的组合。
输入格式
4'b1000
输出格式
2'd3
module top_module(
input [3:0] in,
output reg [1:0] pos
);
// Write your code here
always @(*)
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
endmodule
34 casez语句
题目描述
对于一个8bit输入信号的优先级编码器。例如,输入8'b10010000应该输出3'd4,因为位[4]是第一个高位。如果使用上例中的case语句,则需要包含256个条目。但如果case语句中的case项支持don-care位,我们可以将其减少到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)匹配多个条目,则选择第一个匹配项(因此4'b1111匹配第一个项,out=0,而不匹配后面的任何项)。
还有一个类似的casex,它将x和z都视为不在乎,此处不再介绍。
创建一Verilog模块,实现8bit信号输入的优先级编码器
输入格式
8'b10010000
输出格式
3'd4
module top_module (
input [7:0] in,
output reg [2:0] pos );
//code here:
always @(*) begin
casez (in[7:0])
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
35 避免锁存器
题目描述
假设您正在构建一个电路来处理游戏中PS/2键盘上的扫描码。给定接收到的扫描码的最后两个字节,您需要指示是否按下了键盘上的一个箭头键。这涉及到一个相当简单的映射,它可以实现为一个case语句(或者if…else if…),包含四个case。
您的电路有一个16位输入和四个输出,该电路识别这四个扫描码并确认正确的输出。为避免产生锁存,必须在所有四种情况和默认(default)情况下,为所有四个输出指定一个值。这可能涉及许多不必要的输入。解决这个问题的一个简单方法是在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项变得不必要。
请试着将上述代码补充完整。
输入格式
16位宽扫描码scancode
输出格式
1位宽输出left,right,up,down
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)
// Set to 1 as necessary.
16'he075 : up = 1'b1;
16'he072 : down = 1'b1;
16'he06b : left = 1'b1;
16'he074 : right = 1'b1;
default : begin
up = 1'b0;
down = 1'b0;
left = 1'b0;
right = 1'b0;
end
endcase
end
endmodule