转载自:知乎
本系列文章将和读者一起巡礼数字逻辑在线学习网站 HDLBits 的教程与习题,并附上解答和一些作者个人的理解,相信无论是想 7 分钟精通 Verilog,还是对 Verilog 和数电知识查漏补缺的同学,都能从中有所收获。
附上传送门:Always if - HDLBits
Problem 30: If statement(Always if)
if语句通常对应一个二选一多路复用器,如果条件为真,则选择其中一个输入作为输出;反之如果条件为假,则选择另一个输入所谓输出。if语句必须在过程块内使用。
下面给出了一个基本的if语句和其综合出来的电路。
always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end
这与下面使用条件运算符连续赋值的语句是等价的:
assign out = (condition) ? x : y;
但是,过程if语句使用不当可能会引入新的错误,只有out在所有的条件下都被赋值才会生成正确的组合电路,具体的错误下一个训练才会讲到,
牛刀小试
构建一个可以在a和b之间选择的二选一多路复用器。如果sel\_b1和sel\_b2都为真,输出b,其他情况输出a。请使用两种方法作答,一次使用assign赋值,一次使用if语句。
解答与分析
// 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 :a;
always@(*)
if(sel_b1&sel_b2)
out_always = b;
else
out_always = a;
endmodule
Problem 31: If statement latches(Always if2)
常见的错误来源:如何避免引入锁存器
在设计电路时,必须首先具体考虑电路:
1、我想实现一个逻辑门;
2、我想实现一个具有输入并产生输出的组合逻辑块;
3、我想实现一组组合逻辑,紧接着一组触发器。
不要上来就写代码,这样往往与你想象的电路相差很远。
if (cpu_overheated) then shut_off_computer = 1;
if (~arrived) then keep_driving = ~gas_tank_empty;
除了你指定的情况以外,会发生些什么,答案是什么也不会发生,输出保持不变。而这往往就导致了电路的错误,所以说语法正确的代码不一定能产生合理的电路(组合逻辑+触发器)。
输出保持不变,这就意味着电路需要记住当前状态,从而产生锁存器。组合逻辑(比如逻辑门)不能记住任何状态。
Warning (10240): ... inferring latch(es)
上述这类警告通常情况下代表错误,除非锁存器是故意生成的。组合电路输出必须在所有输入的情况下都有值。这意味着必须需要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
Problem 32: Casestatement(Always case)
Verilog中的case语句几乎等同于if-else if-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
1、case语句以case开头,每个case项以冒号结束。而switch语句没有。
2、每个case项只执行一个语句。 这样就不需要C语言中break来跳出switch。但这也意味着如果您需要多个语句,则必须使用begin ... end。
3、case项允许重复和部分重叠,执行程序匹配到的第一个,而C语言不允许重复的case项目。
牛刀小试
如果存在大量的case项,则case语句比if语句更方便。 因此,在本练习中,创建一个6选1的多路复用器。当sel介于0和5之间时,选择相应的数据输入。 其他情况输出0。数据输入和输出均为4位宽。
注意:不要生成锁存器(详见:Problem 31: If statement latches(Always if2))。
解答与分析
// 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
注意这里一定要用default声明一下不在case项里的输出,否则会生成不必要的寄存器,影响电路的功能。
Problem 33: Priority encoder(Always case2)
优先编码器是组合电路,当给定输入时,输出输入向量中的右边第一个1的位置。例如,输入8'b10010000的,则优先编码器将输出3'd4,因为位[4]是从右数第一个1。
牛刀小试
构建一个4位优先编码器。如果没有输入均为零,则输出零。请注意,4位数字有16种输入发的可能。
小提示:使用十六进制(4'hb)或十进制(4'd11)与二进制(4'b1011)相比可以节省打字量。
解答与分析
这是二进制编码的写法,看起来相对直观一点。
// 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
作者提供了16进制的编码的写法,他觉得这样打字打的少,2333333。
module top_module (
input [3:0] in,
output reg [1:0] pos
);
always @(*) begin // Combinational always block
case (in)
4'h0: pos = 2'h0; // I like hexadecimal because it saves typing.
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; // Default case is not strictly necessary because all 16 combinations are covered.
endcase
end
endmodule
当然,有更简单的写法,那就看下一题吧。
不过还是先谈谈优先译码器,可能在学数字电路的时候优先译码器是左边第一个为0的数字,在本题中是右面第一个。两者都可以叫做优先译码器,出现的第一个数字把后面的数字屏蔽掉了,第一个数字具有较高的优先级,所以叫做优先译码器。
与优先译码器相对应的简单译码器,简单译码器的输入要求只能有一个1。出现多个1的时候视不同情况有不同的处理方式。
Problem 34: Priority encoder with casez(Always casez)
牛刀小试
构建一个8输入的优先编码器。给定一个8位向量,输出输入向量中左数第一个1的位置。如果输入均为0,则输出零。例如,输入8'b10010000应该输出3'd4,因为位[4]是第一个出现1的位置。
如果还按上一个练习(Problem 33: Priority encoder(Always case2))写case语句的话,case语句中将有256个case项。如果case语句中的case项与某些输入无关,就可以减少列出的case项(在本题中减少到9个)。这就是casez的用途:它在比较中将具有值z的位视为无关项(即输入01都会匹配到)。
例如:下面的代码就是上一个联系中的4输入优先编码器:
always @(*) begin
casez (in[3:0])
4'bzzz1: out = 0; // in[3:1]输入什么都可以
4'bzz1z: out = 1;
4'bz1zz: out = 2;
4'b1zzz: out = 3;
default: out = 0;
endcase
end
case项是按顺序检查的(实际上,它更像是生成一个巨大的真值表然后生成超大的门)。注意有输入(例如,4'b1111)匹配多个case项。选择第一个匹配(因此4'b1111匹配第一个case项,out = 0)。
还有一个类似的casex,将输入的x和z都视为无关。不认为casex比casez有什么特别的好处。(作者个人感觉没必要用casex,z和x状态的问题涉及电路的基本知识)
符号"?" 是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
嗯,用casez就方便多了。。。
推荐阅读
- HDLBits:在线学习Verilog(六 · Problem 25-29)
- HDLBits:在线学习Verilog( 五 · Problem 20-24)
- HDLBits:在线学习 Verilog (四 · Problem 15-19)
关注此系列,请关注专栏FPGA的逻辑