最近跟着老师学习Verilog,做了中科大的练习题,将答案记录一下
Q28always过程块_组合逻辑
题目描述
所有的数字电路都是由逻辑门和连线构成的,因此理论上来说都可以通过模块的连接和assign语句进行描述,然而在很多情况下这并不是最方便的一种方式,过程块提供了一种更加方便的描述方式,always过程块便是其中最常用的一种。
对于可综合电路(即能转化成实际电路的verilog描述方式,与之相对的是不可综合电路,多用于电路仿真,不能转换成实际电路),有两种always块的语法形式:
-组合逻辑电路:always@()
-时序逻辑电路:always@(posedge clk)
组合逻辑电路的always块与assign语句等效,用户描述组合逻辑电路时,可根据便利性选择其中一种方式使用。两者生成的硬件电路一般是等效的,但在语法规则上稍有不同:
-assign语句只能对一个信号进行赋值,always块内可对多个信号进行赋值
-assign语句中被赋值信号为wire类型,always块内被赋值信号需定义为reg类型
-always块内支持更加丰富的语法,如使用if…else..、case等适合实现交复杂的组合逻辑
例如下述两条语句是等效的(out1需定义为wire类型,out2需定义为reg类型,但这仅仅是语法上的要求,生成的电路并没有区别):
assign out1 = a & b | c ^ d;
always @() out2 = a & b | c ^ d;
其对应的电路图如下所示:
always语句后的括号内放的是敏感变量列表,对于上例来说,可以写成always @(a,b,c,d) out2 = a & b | c ^ d,但为了简单起见,我们一般都用符号*代替。
试创建一verilog模块,实现一与门,分别用assign语句和always块实现。
输入格式
1位的a,1位的b
输出格式
1位的out_assign,1位的out_alwaysblock
示例波形
代码编辑
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
Q29always过程块_时序逻辑
题目描述
通过前例已经了解到,对于可综合电路,有两种always块的语法形式:
- 组合逻辑电路:
always@(*)
- 时序逻辑电路:
always@(posedge clk)
用always描述的时序逻辑电路,除了像组合逻辑always块那样生成组合逻辑电路外,还会生成一组触发器(或称寄存器),用于寄存组合逻辑的输出。寄存器的输出只有在时钟的上升沿时(posedge clk
)才会更新,其余时刻均保持不变。
阻塞赋值和非阻塞赋值:
在Verilog中,有三种赋值方式,分别为:
- 连续赋值(如
assign x = y;
),该赋值方式只能用于过程块(如always块)之外 - 阻塞赋值(如
x = y;
),该赋值方式只能用在过程块(如always@(*)
)内 - 非阻塞赋值(如
x <= y;
),该赋值方式只能用在过程块内(如always@(posedge clk)
)
在设计Verilog模块时,请遵循以下原则:
- 在组合逻辑的always块内采用阻塞赋值
- 时序逻辑的always块内采用非阻塞赋值
违背这一原则将可能导致难以发现的电路错误,且可能导致仿真与综合的不一致,请用户切记。至于为何这样,初学者可以不必理会,简单理解为verilog语法规范性要求即可。
创建一verilog电路,分别采用上述三种赋值方式实现异或门电路,如下图所示:
Hint
- always块内被赋值的信号都应定义成reg类型
- always块内,组合逻辑采用阻塞赋值(
a = b
),时序逻辑采用非阻塞赋值(a <= b
) - always语句括号内是敏感变量列表,时序逻辑是边沿敏感的,
posedge clk
表示的是clk信号的上升沿,此外,还可以是negedge clk
,表示clk信号的下降沿。
输入格式
一位线网型变量clk,a, b。clk为时钟,a,b为输入
输出格式
一位线网型变量out_assign,out_always_comb,out_always_ff。out_assign为a,b连续赋值得到的结果。out_always_comb为a,b阻塞赋值得到的结果。out_always_ff为a,b非阻塞赋值得到的结果
示例波形
代码
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
Q30if…else…语句
题目描述
if语句用于过程块内部,其对应的电路是二选一的选择器,
以下述代码为例:
always@(*)
begin
if(condition) out = x;
else out = y;
end
上述代码与下面的assign语句完全等效:
assign out = (condition) ? x : y;
试创建一Verilog模块,分别采用assing语句和过程块内的if语句实现下述选择器电路:
Hint:
1.if…else…可以嵌套使用,如
if …
else if…
else if…
else…
2.使用if语句描述组合逻辑时,务必加上else语句,以免产生锁存器(数字电路设计中应尽力避免产生锁存器)
3.本题两个输出信号波形其实是完全一致的,原则上是为了训练大家采用assign和过程块内的if语句使用,所以希望大家能够两种方式都各自尝试一下
输入格式
信号a, b, 选择信号sel_b1, sel_b2
输出格式
通过assign语句选择的信号out_assign 通过if语句选择的信号out_always
示例波形
代码
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_b1 & sel_b2)
out_always<=b;
else
out_always<=a;
end
//用户编辑到此为止
endmodule
Q31if 语句与锁存器
题目描述
使用 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 = 1'b0;
end
endmodule
Q32case语句
题目描述
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'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
Q33优先编码器
题目描述
优先编码器是一种组合电路,当给定一个输入位矢量时,输出矢量中第一个 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@(*)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;
default: pos = 2'b00;
endcase
end
endmodule
Q34casez语句
题目描述
对于一个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 );
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
Q35避免锁存器
题目描述
假设您正在构建一个电路来处理游戏中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;
casez(scancode)
16'he06b:left = 1'b1;
16'he072:down = 1'b1;
16'he074:right = 1'b1;
16'he075:up = 1'b1;
endcase
end
endmodule
Q36条件运算符
题目描述
Verilog中有一个跟C语言中类似的三目条件运算符( ? : ),其语法格式为:
(condition ? if_true : if_false)
该表达式可以用于为其它信号赋值,例如:signal = condition ? if_true : if_false;
该语句等同于:
if(condition) signal = if_true;
else signal = if_false;
因涉及到3个操作数,并且能实现条件运算的功能,因此称为三目条件运算符。
下面是几个使用该运算符的例子:
(0 ? 3 : 5) // 条件为假,因此表达式的值为5
(sel ? b : a) // 二选一选择器
always @(posedge clk) // 触发器
q <= toggle ? ~q : q;
always @(*) // 有限状态机(FSM)
case (state)
A: next = w ? B : A;
B: next = w ? A : B;
endcase
assign out = ena ? q : 1'bz; // 三态门
((sel[1:0] == 2'h0) ? a : (sel[1:0] == 2'h1) ? b : c ) //嵌套使用
试设计一计算最小值功能的Verilog模块,给定四个无符号数,求最小值。提示:
1. 可以综合使用比较运算符(< or >)和条件运算符(? :)。
2. 有必要的话,可以定义中心变量
输入格式
8bit a,b,c,d
输出格式
8bit min, 为 a,b,c,d的最小值
示例波形
代码
module top_module (
input [7:0] a, b, c, d,
output [7:0] min);
wire [7:0] minab, mincd;
assign minab=a<b?a:b;
assign mincd=c<d?c:d;
assign min=mincd<minab?mincd:minab;
endmodule
Q37归约运算符
题目描述
您已经熟悉了两个值之间的位运算,例如a&b或a^b。有时,您需要创建一个对一个向量的所有位进行操作的逻辑门,如(a[0]&a[1]&a[2]&a[3]…),如果向量很长的话,也会很麻烦。归约运算符可以对向量的位进行AND、OR和XOR运算,产生一位输出:
& a[3:0] // AND: a[3]&a[2]&a[1]&a[0]. Equivalent to (a[3:0] == 4'hf)
| b[3:0] // OR: b[3]|b[2]|b[1]|b[0]. Equivalent to (b[3:0] != 4'h0)
^ c[2:0] // XOR: c[2]^c[1]^c[0]
这些是只有一个操作数的单目运算符(类似于NOT运算符!和~)。您还可以反转这些门的输出来创建NAND、NOR和XNOR门,例如(~&d[7:0])。
当传输数据使用的是一个不完美的渠道时,经常使用奇偶校验作为一种简单的方法来检测错误。创建一个将为8位字节计算奇偶校验位的电路(这将在字节中添加第9位)。我们将使用“偶数”奇偶校验,其中奇偶校验位只是所有8个数据位的异或。
那么,让我们进入正题:
输入格式
8位输入信号in
输出格式
1位输出信号parity=^in,可以直接通过归约运算符完成。 是的,你没有看错,真的有用的题干大概就这么一句……
示例波形
代码
module top_module (
input [7:0] in,
output parity);
assign parity=^in;
endmodule
Q38D触发器
题目描述
D触发器是一种在时钟信号的边缘(通常是上升沿)存储位并定期更新的电路。在Verilog中,时序逻辑电路描述(一般语法为:always@(posedge clk))都会生成组合逻辑块加D触发器的电路结构。
D触发器是最简单的“组合逻辑后跟一个触发器”形式,其中组合逻辑部分只是一根导线。
创建verilog模块,实现一个D触发器。
输入格式
时钟clk,1位的d
输出格式
1位的q
示例波形
代码
module top_module (
input clk, // Clocks are used in sequential circuits
input d,
output reg q );//
// Use a clocked always block
// copy d to q at every positive edge of clk
// Clocked always blocks should use non-blocking assignments
always@(posedge clk)begin
q<=d;
end
endmodule
Q39寄存器
题目描述
寄存器在本质上来说就是触发器,我们一般将由多个触发器构成的向量信号成为寄存器。试设计一个在时钟上升沿出发的8bit位宽的寄存器。
输入格式
一位宽线网型变量clk,时钟信号; 八位宽线网型变量d,寄存器输入信号;
输出格式
八位宽寄存器型变量q,寄存器输出信号
代码
module top_module (
input clk,
input [7:0] d,
output reg [7:0] q
);
// 请用户在下方编辑代码
always@(posedge clk)begin
q<=d;
end
//用户编辑到此为止
endmodule
Q40有复位功能的寄存器
题目描述
创建一个带有复位信号(reset)的8bit位宽的寄存器,在触发信号(clk)的上升沿,当复位信号为高电平时,寄存器输出0,否则与输入信号d同步。
Hint:
在同一个always进程块中,同一触发信号只能使用一种边沿,即上升沿和下降沿不可同时使用。(例如:always@(posedge clk or negedge clk),这种写法是错误的)
输入格式
时钟信号clk, 同步复位信号reset, 8bit宽信号输入d
输出格式
8bit宽信号输出q
代码
module top_module (
input clk,
input reset, // Synchronous reset
input [7:0] d,
output reg [7:0] q
);
always@(posedge clk)begin
if(reset)
q<=0;
else
q<=d;
end
endmodule
Q41下降沿触发的寄存器
题目描述
在时序逻辑电路中,敏感变量不但可以是触发信号的上升沿(posedge
),也可以是下降沿(negedge
),试创建 8bit 位宽的寄存器,所有 DFF 都应该由 clk 的下降沿(负边缘)触发。同时采用高电平有效的同步复位方式,复位值为 0x34
而不是零。
输入格式
输入信号 clk,时钟信号。 输入信号 reset,复位信号,高电平有效(复位)。 输入信号 d,位宽 8bit,任意数据信号。
输出格式
输出信号 q,位宽 8bit,在时钟下降沿取输入信号 d 的值,若复位信号有效则复位为值 0x34,复位方式为同步复位。
代码
module top_module (
input clk ,
input reset ,
input [7:0] d ,
output reg [7:0] q
);
// Write your code here
always@(negedge clk)begin
if(reset)
q<=8'he0x34;
else
q<=d;
end
endmodule
Q42异步复位的寄存器
题目描述
在前面的例子中,复位信号只能在触发信号的触发边沿才能起作用,也就是说如果没有触发边沿就无法对电路复位,这大大限制了复位的功能,因此还有一种经常使用的复位方式,称为异步复位。
为了使复位信号不依赖于时钟边沿,则复位信号也应该放在always进程块的敏感变量列表中。复位信号高电平有效和低电平有效在编码时稍有不同,对于高电平有效的复位信号来说,可以写成如下形式:
always@(posedge clk or posedge reset)
begin
if(reset) …
else …
end
如果是低电平有效的复位信号,则应写成如下形式:
always@(posedge clk or negedge reset)
begin
if(~reset) …
else …
end
试创建Verilog模块,实现一个时钟上升沿触发的,支持高电平有效的异步复位功能的8bit寄存器,寄存器复位值为0。
输入格式
2个 1bit 位宽的信号 clk、areset 1个 8bit 位宽的信号 d
输出格式
1个 8bit 位宽的信号 q
代码
module top_module (
input clk,
input areset, // active high asynchronous reset
input [7:0] d,
output reg [7:0] q
);
// Write your code here
always@(posedge clk or posedge areset)begin
if(areset)
q<=0;
else
q<=d;
end
endmodule
Q43带使能的寄存器
题目描述
在前面的电路中,寄存器输出端 q 在每个时钟的上升沿都会更新一次,但有时候我们可能需要使输出端保持不变,这时就需要加入使能信号,创建一 16bit 位宽(2byte)的寄存器,其中每字节都由一个使能信号控制,使能为 0 时,输出保持不变,使能为 1 时更新 q。时钟上升沿触发,同步复位,复位低电平有效,复位值为 0。
输入格式
任意有效的 16bit 位宽数据
输出格式
每个字节都由一个使能信号控制:使能为0时输出保持不变,使能为1时更新q
示例波形
代码
module top_module(
input clk,
input resetn,
input [1:0] byteena,
input [15:0] d,
output reg [15:0] q
);
// Write your code here
always@(posedge clk)begin
if(~resetn)
q<=0;
else
casez(byteena)
2'b10:q[15:8]<=d[15:8];
2'b11:q[15:0]<=d[15:0];
2'b01:q[7:0]<=d[7:0];
endcase
end
endmodule
Q44触发器+逻辑门
题目描述
编写verilog代码,实现下图所示的电路功能
输入格式
1
输出格式
1
代码
module top_module (
input clk,
input in,
output reg out);
always@(posedge clk)begin
out=out^in;
end
endmodule
Q45寄存器+逻辑门
题目描述
编写Verilog代码,实现下图所示的电路功能,假设所有D触发器的初始复位值为0
输入格式
clk,x
输出格式
z
代码
module top_module (
input clk,
input x,
output z
);
reg q1,q2,q3;
initial
begin
q1 = 0;
q2 = 0;
q3 =0;
end
always@(posedge clk)
begin
q1 <= q1 ^ x;
q2 <= (~q2) & x;
q3 <= (~q3) | x;
end
assign z = ~(q1 ^ q2 ^ q3);
endmodule
Q46上升沿检测
题目描述
在实际应用中,我们经常需要对某个信号的边沿进行检测,并以此作为后续动作的触发信号(例如电脑键盘的某个按键被按下或者被松开,在电路中则对应的是电平的变化)。
设计一个电路,包含clk信号、1bit输入信号in和1bit输出信号out,当in信号从0变为1时(相对于clk,该信号变化频率很慢),out信号在in信号上升沿附近输出1个时钟周期的高电平脉冲,其余时刻都为0,如下图所示
提示:如果在top_module中定义了reg,且out的结果与reg相关,可以想象,在仿真的开始阶段,reg的值是未定义的,那么out的波形也是未定义的,为了避免此情况的error, 可以使用initial初始化这些reg, 使初始的out 为0
输入格式
时钟clk, 被检测信号1bit in
输出格式
检测结果 1bit out
代码
module top_module (
input clk,
input in,
output out
);
reg q1,q2;
initial
begin
q1 = 0;
q2 = 0;
end
always@(posedge clk)begin
q2 <= q1;
q1 <= in;
end
assign out = q1 & ~q2;
endmodule
Q47双边沿检测
题目描述
根据上升沿检测的思路,设计一双边沿检测电路,即在输入信号的上升沿和下降沿附近时刻,各输出一个高电平脉冲,如下图所示
输入格式
1位时钟信号clk和1位输入信号in
输出格式
一位信号out
代码
module top_module (
input clk,
input in,
output out
);
reg q1,q2;
always@(posedge clk )begin
q2 <= q1;
q1 <= in;
end
assign out =(q1 & ~q2)|(~q1 & q2);
endmodule