关注 望森FPGA 查看更多FPGA资讯
这是望森的第 6 期分享
作者 | 望森
来源 | 望森FPGA
目录
1 Always blocks (combinational) | Always过程块(组合逻辑)
2 Always blocks:(clocked) | Always过程块(时序逻辑)
4 lf statement latches | If 语句闩锁
7 Priority encoder with casez | Casez 实现的优先编码器
文中的代码都能够正常运行,请放心食用😋~
练习的官方网站是:https://hdlbits.01xz.net/
注:作者将每个练习的知识点都放在了题目和答案之后
过程块包括always, initial, task, and function块。过程块允许使用顺序语句(不能在程序之外使用)来描述电路的行为。
1 Always blocks (combinational) | Always过程块(组合逻辑)
题目:
请使用assign赋值语句以及组合always块两种方法分别构建一个 AND 门。
答案:
// synthesis verilog_input_version verilog_2001
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 块)提供了描述电路的另一种语法。
对于综合硬件,有两种类型的 always 块是相关的:
-
组合逻辑:always @(*)
-
时序逻辑:always @(posedge clk)
组合 always 块等同于赋值语句,因此总有一种方式可以以两种方式表达组合电路。选择使用哪种方式主要取决于哪种语法更方便。过程块内部的代码语法与外部的代码不同。过程块具有更丰富的语句集(例如,if-then、case),不能包含连续赋值*。(*程序连续赋值确实存在,但与连续赋值略有不同,并且不可综合。)
例如,assign 和组合 always 块描述相同的电路。两者都创建相同的组合逻辑块。每当任何输入(右侧)的值发生变化时,两者都会重新计算输出。
assign out1 = a & b | c ^ d;
always @(*) out2 = a & b | c ^ d;
对于组合式 always 块,始终使用敏感度列表 (*)。明确列出信号容易出错(如果遗漏了一个信号),并且会被忽略以进行硬件综合。
关于 wire 与 reg 的说明:assign 语句的左侧必须是网络类型(例如 wire),而过程赋值(在 always 块中)的左侧必须是变量类型(例如 reg)。这些类型(wire 与 reg)与合成的硬件无关,只是 Verilog 作为硬件模拟语言使用时遗留下来的语法。
2 Always blocks:(clocked) | Always过程块(时序逻辑)
题目:
请使用赋值语句、组合 always 块和时钟 always 块三种方式构建 XOR 门。
请注意,时钟 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 statement
assign out_assign = a ^ b;
//combinational always block
always@(*)begin
out_always_comb = a ^ b;
end
//clocked always block
always@(posedge clk)begin
out_always_ff <= a ^ b;
end
endmodule
知识点:
对于硬件综合,有两种类型的always块:
-
组合逻辑:always @(*)
-
时序逻辑:always @(posedge clk)
时序always块与组合always块一样,会创建组合逻辑块,但也会在该组合逻辑块的输出端创建一组触发器(或“寄存器”)。逻辑块的输出并非立即可见,而是在下一个(posedge clk)之后立即可见。
阻塞与非阻塞赋值
Verilog 中有三种类型的赋值:
-
连续赋值(assign x = y;)。只能在不在程序内部时使用(“始终阻塞”)。
-
程序阻塞赋值:(x = y;)。只能在程序内部使用。
-
程序非阻塞赋值:(x <= y;)。只能在程序内部使用。
在组合式 Always 块中,使用阻塞分配。在时钟式 Always 块中,使用非阻塞分配。
3 lf statement | If语句
题目:
构建一个 2 对 1 多路复用器,在 a 和 b 之间进行选择。
如果 sel_b1 和 sel_b2 都为真,则选择 b。否则,选择 a。
执行两次相同的操作,一次使用赋值语句,一次使用过程 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
assign out_assign = (sel_b1 & sel_b2) ? b : a;
//always
always@(*)begin
if (sel_b1 & sel_b2)
out_always = b;
else
out_always = a;
end
endmodule
知识点:
if 语句通常会创建一个 2 对 1 多路复用器,如果条件为真则选择一个输入,如果条件为假则选择另一个输入。
always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end
这相当于使用带有条件运算符的连续赋值:
assign out = (condition) ? x : y;
但是,程序性 if 语句提供了一种新的出错方式。只有当 out 始终被赋值时,电路才是组合的。
(意味着每个if都需要写else)
4 lf statement latches | If 语句闩锁
题目:
以下代码包含创建闩锁的错误行为。请修复错误,以便您仅在计算机确实过热时才关闭计算机,并在您到达目的地或需要加油时停止驾驶。
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 = 0;
end
endmodule
知识点:
常见错误来源:如何避免制作闩锁
设计电路时,你必须首先从电路的角度来思考:
-
我想要这个逻辑门
-
我想要一个具有这些输入并产生这些输出的组合逻辑块
-
我想要一个组合逻辑块,后面跟着一组触发器
你一定不能做的是先写代码,然后希望它能生成正确的电路。语法正确的代码不一定会产生合理的电路(组合逻辑 + 触发器)。
通常的原因是:“除了您指定的情况之外,会发生什么情况?”。Verilog 的答案是:保持输出不变。
这种“保持输出不变”的行为意味着需要记住当前状态,从而产生锁存器。组合逻辑(例如逻辑门)无法记住任何状态。请注意“Warning (10240): ... inferring latch(es)”消息。除非故意设计了锁存器,否则它几乎表示存在错误。组合电路必须在所有条件下为所有输出分配一个值。这通常意味着您始终需要 else 子句或为输出分配一个默认值。
5 Case statement | Case语句
题目:
当有大量案例时,case 语句比 if 语句更方便。
因此,在本练习中,用case 语句创建一个 6 对 1 多路复用器。当 sel 介于 0 和 5 之间时,选择相应的数据输入。否则,输出 0。数据输入和输出都是 4 位宽。
请勿设计出锁存器(参见 .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'd0 : out = data0;
3'd1 : out = data1;
3'd2 : out = data2;
3'd3 : out = data3;
3'd4 : out = data4;
3'd5 : out = data5;
default out = 0;
endcase
end
endmodule
知识点:
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
-
case 语句以 case 开头,每个“case 项”以冒号结尾。没有“switch”。
-
每个 case 项只能执行一个语句。这使得 C 中使用的“break”变得没有必要。但这意味着如果您需要多个语句,则必须使用 begin ... end。
-
允许重复(和部分重叠)的 case 项。使用第一个匹配的项。C 不允许重复的 case 项。
6 Priority encoder | 优先编码器
题目:
优先级编码器是一种组合电路,当给定一个输入位向量时,它会输出向量中第一个 1 bit的位置。
例如,给定输入 8'b10010000 的 8 位优先级编码器将输出 3'd4,因为 bit[4] 是第一个高位。
请构建一个 4 位优先级编码器,如果没有一个输入位是高位(即输入为零),则输出零。
注意,4 位数有 16 种可能的组合。
答案:
// synthesis verilog_input_version verilog_2001
module top_module (
input [3:0] in,
output reg [1:0] pos );
always@(*) begin // This is a combinational circuit
casex(in)
4'bxxx1 : pos = 2'd0;
4'bxx10 : pos = 2'd1;
4'bx100 : pos = 2'd2;
4'b1000 : pos = 2'd3;
default pos = 2'd0;
endcase
end
endmodule
知识点:
错解分析:【本题逻辑为关注第一个1以及其低位的0,忽略高位数据】
// synthesis verilog_input_version verilog_2001
module top_module (
input [3:0] in,
output reg [1:0] pos );
always@(*) begin // This is a combinational circuit
casex(in)
4'b0001 : pos = 2'd0;
4'b001x : pos = 2'd1;
4'b01xx : pos = 2'd2;
4'b1xxx : pos = 2'd3;
default pos = 2'd0;
endcase
end
endmodule
7 Priority encoder with casez | Casez 实现的优先编码器
题目:
为 8 位输入构建优先级编码器。
给定一个 8 位向量,输出应报告向量中第一个(最低有效)位为 1。如果输入向量没有高位,则报告零。
例如,输入 8'b10010000 应输出 3'd4,因为 bit[4] 是第一个高位。
答案:
// synthesis verilog_input_version verilog_2001
module top_module (
input [7:0] in,
output reg [2:0] pos );
always@(*) begin // This is a combinational circuit
casez(in)
8'bzz_zzz_zz1 : pos = 3'd0;
8'bzz_zzz_z10 : pos = 3'd1;
8'bzz_zzz_100 : pos = 3'd2;
8'bzz_zz1_000 : pos = 3'd3;
8'bzz_z10_000 : pos = 3'd4;
8'bzz_100_000 : pos = 3'd5;
8'bz1_000_000 : pos = 3'd6;
8'b10_000_000 : pos = 3'd7;
default pos = 3'd0;
endcase
end
endmodule
知识点:
从上一个练习(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 都视为无关项,两者在使用时区别不大。
-
字符 ? 是 z 的同义词。因此 2'bz0 与 2'b?0 相同
明确指定优先级行为可能比依赖案例项的排序更不容易出错。例如,如果对某些案例项进行了重新排序,以下行为仍将以相同的方式运行,因为任何位模式最多只能匹配一个案例项:
casez (in[3:0])
4'bzzz1: ...
4'bzz10: ...
4'bz100: ...
4'b1000: ...
default: ...
endcase
8 Avoiding latches | 避免闩锁
题目:
假设您正在构建一个电路来处理 PS/2 键盘的扫描码,用于游戏。根据收到的扫描码的最后两个字节,您需要指示键盘上的某个箭头键是否被按下。这涉及一个相当简单的映射,可以将其实现为包含四个 case 的 case 语句(或 if-elseif)。
您的电路有一个 16 位输入和四个输出。构建此电路以识别这四个扫描码并断言正确的输出。
答案:
// 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@(*) begin // This is a combinational circuit
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
case(scancode)
16'he06b : left = 1'd1;
16'he072 : down = 1'd1;
16'he074 : right = 1'd1;
16'he075 : up = 1'd1;
endcase
end
endmodule
知识点:
为了避免创建锁存器,必须在所有可能的条件下为所有输出分配一个值(另请参阅 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 语句覆盖了赋值。这也意味着 default: case 项变得没有必要。
- END -
微信公众号/CSDN/EETOP搜索【望森FPGA】,查看更多FPGA资讯~
相关推荐文章,点击跳转:
FPGA新手必用,Verilog HDL编程学习网站推荐 —— HDLBits
Getting Started| HDLBits启航篇!
HDLBits中文版,标准参考答案 | 2 Verilog Language 2.2 Vectors