下列答案不唯一!
目录
1、Always blocks (combinational)
1、Always blocks (combinational)
由于数字电路由用导线连接的逻辑门组成,因此任何电路都可以表示为一些模块和赋值语句的组合。然而,有时候这并不是描述电路最简便的方式。
对于硬件的设计,存在两种类型的always块:
组合:always@ (*)
时序:always@(posedge clk)
组合逻辑的always块和assign赋值是等价的,因此总有一种方法可以用两种方式表达组合电路,使用哪一种语法择主要是看哪种语法更方便。
例如,分配和组合总是块描述相同的电路。两者都创建了相同的组合逻辑。当任何输入(右边)改变值时,两者都将重新计算输出。
提供下列模板:
assign out1 = a & b | c ^ d;
always @(*) out2 = a & b | c ^ d;
关于wire和reg的注意事项:assign赋值语句的左边一般为wire类型,always块中左边的变量一般为reg类型。这仅是Verilog语法的要求。
题目练习:
使用赋值语句和组合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
2、Always blocks (clocked)
与组合always块一样,时序always块创建一个组合逻辑blob,但也在组合逻辑blob的输出处创建一组触发器(或“寄存器”)。逻辑blob的输出不是立即可见的,而是在下一个(posedge clk)之后立即可见的。
阻塞与非阻塞:
在Verilog中有三种类型的赋值:连续的赋值(assign x = y;)。只能在过程块之外使用("always block")。
过程阻塞赋值:(x = y;)。只能在过程中使用。
过程非阻塞赋值:(x <= y;)只能在过程中使用。
在组合逻辑的always块中(always @(*))使用阻塞赋值语句,
在时序逻辑的always块中(always @(posedge clk))使用非阻塞赋值语句。
题目练习:
使用赋值语句、组合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
3、If statement
如下所示,if语句创建一个2选1的多路器,当条件为真时选择一个输入,当条件为假时选择另一个输入。
always @(*) begin if (condition) begin out = x; end else begin out = y; end end
上述也相当于在条件运算符中使用连续赋值:
assign out = (condition) ? x : y;
题目练习:
构建一个2选1的多路选择器,在a和b之间进行选择。如果sel_b1和sel_b2都为真,则选择b。否则,选择a。重复执行两次,一次使用assign语句,一次使用过程中if语句。
代码:
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_b2, sel_b1} == 2'b11) ? b : a;
always@(*)begin
if({sel_b2, sel_b1} == 2'b11)
out_always = b;
else
out_always = a;
end
endmodule
//方法二
/*
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 == 1 ? b : a;
always@(*) begin
if(sel_b1 & sel_b2 == 1) begin
out_always = b;
end
else begin
out_always = a;
end
end
endmodule
*/
4、If statement latches
一个常见的问题:如何避免锁存器的产生。当设计电路时,我们必须首先从电路的角度考虑,不应该是先写代码,然后希望它产生一个合适的电路。
- If (cpu_overheated) then shut_off_computer = 1;
- If (~arrived) then keep_driving = ~gas_tank_empty;
语法上正确的代码并不意味着设计成的电路也是合理的。例如上述示例,如果if条件不满足,输出如何变化呢?而Verilog给出的解决方法就是保持输出不变。因为组合逻辑电路不能记录当前的状态,所以就会产生锁存器。
所以当我们使用if语句或者case语句时,我们必须考虑到所有情况并给对应情况的输出进行赋值,就意味着我们要为else或者default中的输出赋值。
题目练习:
找BUG,解决下面的代码中包含的创建锁存器的不正确行为。
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;
endalways @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
endendmodule
代码:
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) & (~gas_tank_empty))
keep_driving = 1;
else begin
keep_driving = 0;
end
end
endmodule
5、Case statement
Verilog中的Case语句几乎等价于一个if-elseif-else序列
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
题目练习:
如果案例数量较多,案例陈述比if陈述更方便。因此,在这个练习中,创建一个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(...)
endendmodule
代码:
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是因为input[2:0] sel
3'b001 : out = data1;
3'b010 : out = data2;
3'b011 : out = data3;
3'b100 : out = data4;
3'b101 : out = data5;
default : out = 0;
endcase
end
endmodule
6、Priority encoder
优先编码器是一种组合电路,当给定一个输入位向量时,输出该向量中前1位的位置。例如,给定输入8'b10010000的8位优先级编码器将输出3'd4,因为位[4]是高的第一个位。
构建一个4位优先编码器。对于这个问题,如果没有一个输入位是高的(即输入为零),输出为零。请注意,4位数字有16种可能的组合。
module top_module (
input [3:0] in,
output reg [1:0] pos );
always@(*) begin
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;
endcase
end
endmodule
7、Priority encoder with casez
根据前面的练习, case语句中有256个case。如果case语句中的case项不考虑bit位,我们可以减少(减少到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
还有一种类似的情况,casex将x和z都视为无关紧要。我看不出用它来对付casez有什么用。
显式指定优先级行为可能比依赖于用例项的排序更不容易出错。例如,如果某些case项被重新排序,下面的代码仍然会以同样的方式运行,因为任何位模式最多只能匹配一个case项:
casez (in[3:0])
4'bzzz1: ...
4'bzz10: ...
4'bz100: ...
4'b1000: ...
default: ...
endcase
题目练习:
为8位输入构建优先级编码器。给定一个8位向量,输出应该报告向量中第一个(最低有效)位为1的位。如果输入向量没有高的位,则报告为零。例如,输入8'b10010000应该输出3'd4,因为位[4]是高的第一个位。
module top_module (
input [7:0] in,
output reg [2:0] pos );
always@(*) begin
casez(in)
8'bzzzzzzz1 : pos = 3'b000;
8'bzzzzzz10 : pos = 3'b001;
8'bzzzzz100 : pos = 3'b010;
8'bzzzz1000 : pos = 3'b011;
8'bzzz10000 : pos = 3'b100;
8'bzz100000 : pos = 3'b101;
8'bz1000000 : pos = 3'b110;
8'b10000000 : pos = 3'b111;
default: pos = 3'b000;
endcase
end
endmodule
8、Avoiding latches
假设你正在构建一个电路来处理来自PS/2键盘的游戏扫描代码。给定接收到的扫描码的最后两个字节,您需要指出是否按了键盘上的一个方向键。这涉及到一个相当简单的映射,它可以用一个case语句(或if-elseif)实现,有四个case。
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位输入和四个输出。建立这个电路,识别这四个扫描码,并断言正确的输出。
为了避免创建锁存,所有输出都必须在所有可能的条件下赋值(参见4、If statement latches)。仅仅有一个默认情况是不够的。必须为所有四种情况下的所有四种输出以及默认情况下的输出分配一个值。这可能涉及很多不必要的输入。一个简单的方法是在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)。
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