前例中的加法器成为串行进位加法器,只有等前一级的加法器运算结束产生进位位之后,下一级加法器才能利用进位位进行计算,因此电路延时会随加法器串联级数的增加而线性增加,这使得电路计算速度大大降低。设每一级全加器的延时为t,则32bit加法器的延时则为:32t。
为降低电路整体延时,我们可以按下图进行设计:
我们将电路分为两段,每段实现16bit的加法,为了使高16位与低16位同时进行运算,我们采用两个add16对高位进行计算,区别在于进位位分别为0和1,最终通过低16位加法器的输出进位作为选择控制信号,选择高16位的运算结果。这样,32bit加法器的延时就变为:16t+tmux2 ≈16t,延时降低了接近一倍,这种以空间(增加电路)换时间(提高速度)的做法,在数字电路设计中经常使用。
请创建Verilog模块,实现上图中的电路结构,其中add16不需要用户编写,其声明如下:
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
assign {cout,sum} = a + b + cin;
endmodule
输入格式
32bit a, 32bit b
输出格式
32bit sum 为 a 与 b 的和
注意:在always模块中,需要用reg
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
assign {cout,sum} = a + b + cin;
endmodule
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire cin,cin1,cin2,cout;
wire [15:0] sum0, sum1, sum2;
reg [15:0] sum_total;
assign cin = 0;
assign cin1 = 0;
assign cin2 = 1;
add16 add0 (a[15:0], b[15:0], cin, sum0[15:0], cout);
add16 add1 (a[31:16], b[31:16], cin1, sum1[15:0],);
add16 add2 (a[31:16], b[31:16], cin2, sum2[15:0],);
always @(*)
begin
case(cout)
1'b0: sum_total = sum1;
1'b1: sum_total = sum2;
endcase
end
assign sum = {sum_total,sum0};
endmodule
题目描述
通过对加法器进行改造,可以支持加、减两种运算。我们知道,电路中有符号数通常使用补码表示,如-b其补码为:~b + 1(按位取反然后加1)。因此,对于减法算式a-b,可以理解为a+(-b) = a+(~b+1)= a + (~b) +1,因此对于减法运算,可以将加法器进行如下改造实现:
实现减法运算时,首先通过32bit的异或门,将信号b按位取反,同时将输入进位位置1,实现加法运算时,b保持不变,输入进位位置0。
其中add16模块代码如下,用户可直接调用:
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
assign {cout,sum} = a + b + cin;
endmodule
请创建Verilog模块,实现上述电路功能。
输入格式
32位的a,b,以及一个1位信号sub,sub为1时为减法,sub为0时为加法
输出格式
32位信号sum 注:我想你读到这里的时候,一定跟我一样想着直接用sum=(sub==0?a+b:a-b)逃课了,但是请老老实实地按题目要求分高位低位取补码相加哦~~~
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
assign {cout,sum} = a + b + cin;
endmodule
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);
wire[31:0] b_reverse;
wire cout1,cout2;
// 等价于先对输入求反码,然后再加1。
// 最终的结果是一个可以做两种操作(即加法和减法)的电路:(a + b + 0)和(a + ~b + 1)。
assign b_reverse = b^{32{sub}};
add16 inst1(a[15:0], b_reverse[15:0], sub, sum[15:0], cout1);
add16 inst2(a[31:16], b_reverse[31:16], cout1, sum[31:16], cout2);
endmodule
题目描述
所有的数字电路都是由逻辑门和连线构成的,因此理论上来说都可以通过模块的连接和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 statement
assign out_assign = a & b;
//combinational always block
always @(*) begin
out_alwaysblock = a & b;
end
endmodule
题目描述
通过前例已经了解到,对于可综合电路,有两种always块的语法形式:
always@(*)
always@(posedge clk)
用always描述的时序逻辑电路,除了像组合逻辑always块那样生成组合逻辑电路外,还会生成一组触发器(或称寄存器),用于寄存组合逻辑的输出。寄存器的输出只有在时钟的上升沿时(posedge clk
)才会更新,其余时刻均保持不变。
阻塞赋值和非阻塞赋值:
在Verilog中,有三种赋值方式,分别为:
assign x = y;
),该赋值方式只能用于过程块(如always块)之外x = y;
),该赋值方式只能用在过程块(如always@(*)
)内x <= y;
),该赋值方式只能用在过程块内(如always@(posedge clk)
)在设计Verilog模块时,请遵循以下原则:
违背这一原则将可能导致难以发现的电路错误,且可能导致仿真与综合的不一致,请用户切记。至于为何这样,初学者可以不必理会,简单理解为verilog语法规范性要求即可。
创建一verilog电路,分别采用上述三种赋值方式实现异或门电路,如下图所示:
Hint
a = b
),时序逻辑采用非阻塞赋值(a <= b
)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)
out_always_ff <= a ^ b;
//用户编辑到此为止
endmodule
题目描述
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) begin
out_always = b;
end
else begin
out_always = a;
end
end
//用户编辑到此为止
endmodule