HDLBits——More Verilog Features
Problem 36: Conditional ternary operator(Conditional)
Requirement:
给出四个无符号数,请找到其中的最小值。
提示:
- 使用条件运算符描述一个两路的最小值电路,然后组合它来创建一个4路最小电路,可能需要一些wire变量用于表述中间结果。
- 无符号数可以使用比较运算符进行比较
Solution:
module top_module (
input [7:0] a, b, c, d,
output [7:0] min);//
// assign intermediate_result1 = compare? true: false;
wire [7:0] min1,min2;
assign min1 = (a > b) ? b : a;
assign min2 = (c > d) ? d : c;
assign min = (min1 > min2) ? min2 : min1;
endmodule
PS:条件运算符? :
是一个三元运算符,可以在一行代码上实现一个二选一,而不需要在 always 块中使用 if-else 语句,例如:
always @(posedge clk) // 一个T触发器
q <= toggle ? ~q : q;
((sel[1:0] == 2'h0) ? a : // 一个三选一MUX
(sel[1:0] == 2'h1) ? b :
c )
Timing Diagram:
Problem 37: Reduction operators(Reduction)
Requirement:
奇偶校验是常用于信道传输数据时检测错误的方法,构建一个电路,计算8位字节输入的校验位(将向该字节添加第9位)。 这里使用偶校验,即奇偶校验位是所有8个数据位的XOR。
奇偶校验:通过检验位将传输1的个数变成奇数就是奇校验,变成偶数就是偶校验。
Solution:
module top_module (
input [7:0] in,
output parity);
assign parity = ^ in;
endmodule
PS:
有时候想要构建一个输入比较多的门,对一个向量的所有位进行操作,如 a[0]&a[1]&a[2]&a[3]…,但这对于长的标量来说,这很麻烦。归约运算符(Reduction Operators),是只有一个操作数的一元运算符(类似于 ! 和 ~),可以对向量的每一位进行 AND,OR 和 XOR,产生一位输出,同理还有 &,|,~^,例如:
&a [3:0] // AND:a[3]&a[2]&a[1]&a[0]
|b [3:0] // OR: b[3]|b[2]|b[1]|b[0]
^c [2:0] // XOR:c[2]^c[1]^c[0]
! VS ~
“!”表示逻辑取反,“~”表示按位取反。当面对位宽为1时,两个操作符的作用相同。
当位宽不为1时,“~”会将变量的各个位依次取反如:a[3:0] ={1,0,0,1} , ~a ={0,1,1,0};
“!”会将变量作为一个值去做处理,非0为1:a[3:0] ={1,0,0,1} ,a=5,!a=0。a[3:0] ={0,0,0,0} ,a=0,!a=1。
Problem 38: Reduction: Even wider gates(Gates100)
Requirement:
构建具有100个输入的组合电路。
电路一共有3个输出:
Solution:
module top_module(
input [99:0] in,
output out_and,
output out_or,
output out_xor
);
assign out_and = & in;
assign out_or = | in;
assign out_xor = ^ in;
endmodule
Timing Diagram:
Problem 39: Combinational for-loop: Vector reversal2(Vector100r)
Requirement:
给了一个长度是100的向量,请把它翻转后输出。
提示:for 循环(组合 always 块或者 generate 块)在这里很有用。 这道题中,因为不需要模块实例化(必须使用 generate 块),建议使用 always 块。
Solution:
module top_module(
input [99:0] in,
output reg [99:0] out
);
always @(*) begin : name1
integer i;
for (i = 0; i<100; i=i+1) begin
out[i] = in[99-i];
end
end
endmodule
方法二:
module top_module(
input [99:0] in,
output [99:0] out
);
generate
genvar i;
begin
for (i = 0; i<100; i=i+1) begin:name2
assign out[i] = in[99-i];
end
end
endgenerate
endmodule
PS:
- 翻转指的是高低位,不是 0、1。
- 要在语句块内声明变量,必须得给块命名,即在 begin 后面 : 名字。
- 在过程语句中(initial或者always),被赋值信号需要定义成 reg 类型,否则 wire 类型。
Problem 40 Combinational for-loop: 255-bit population count
Requirement:
设计电路来计算输入矢量中 ’1‘ 的个数。
Solution:
module top_module(
input [254:0] in,
output reg [7:0] out );
always @(*) begin:name1
integer i;
out = 8'b0;
for (i = 0; i<255; i=i+1) begin
if (in[i] == 1) begin
out = out + 1;
end
else begin
out = out;
end
end
end
endmodule
不能写作 generate,会报错,我的猜测是遍历和重复还是有区别的,这里只是遍历,没有重复生成什么代码。
Timing Diagram:
Problem 41 Generate for-loop: 100-bit binary adder 2
Requirement:
通过实例化 100 个全加器来实现一个 100bit 的二进制加法器。该加法器有两个 100bit 的输入和 cin,输出为 sum 与 cout。为了鼓励大家使用实例化来完成电路设计,我们同时需要输出每个全加器的 cout, 故 cout[99] 标志着全加器的最终进位。
Solution:
module top_module(
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum );
add_1bit add_1bit_0(
.a(a[0]),
.b(b[0]),
.cin(cin),
.cout(cout[0]),
.sum(sum[0])
);
generate
genvar i;
for(i=1;i<100;i=i+1) begin:add_100
add_1bit add_1bit_i(
.a(a[i]),
.b(b[i]),
.cin(cout[i-1]),
.cout(cout[i]),
.sum(sum[i])
);
end
endgenerate
endmodule
module add_1bit (
input a,
input b,
input cin,
output sum,
output cout
);
assign cout = (a&b) | (a&cin) | (b&cin);
assign sum = a^b^cin;
endmodule
错过:cin 作为输入,由外部信号确定,不要给人家瞎赋值。
generate 语句
生成语句可以动态的生成 verilog 代码,当对矢量中的多个位进行重复操作时,或者当进行多个模块的实例引用的重复操作时,使用生成语句能大大简化程序的编写过程。generate 语句有 generate-for、generate-if、generate-case 三种语句。
generate-for语句
(1) 必须用 genvar 关键字定义 for 语句的变量。
(2)for 语句的内容必须加 begin 和 end (即使就一句)。
(3)for 语句必须有个名字,写在 begin 后面。
方法二:不例化
module top_module(
input [99:0] a, b,
input cin,
output reg [99:0] cout,
output reg [99:0] sum );
integer i;
always @(*) begin
cout[0] = (a[0] & b[0]) | (a[0] & cin) | (b[0] & cin);
sum[0] = a[0] ^ b[0] ^ cin;
for(i=1;i<100;i=i+1) begin
sum[i] = a[i] ^ b[i] ^ cout[i-1];
cout[i] = (a[i] & b[i]) | (a[i] & cout[i-1]) | (b[i] & cout[i-1]);
end
end
endmodule
注意:
-
两个 always 语句之间是并行的。
-
直接赋值会提示 always 里面只能赋值 reg 型,所以把 cout[0],sum[0] 也放进去,毕竟输入是不会变的,always 理论上只启动一次。
方法三:不用公式不例化
module top_module(
input [99:0] a, b,
input cin,
output reg [99:0] cout,
output reg [99:0] sum );
integer i;
always @(*) begin
{cout[0],sum[0]} = a[0] + b[0] + cin;
for(i=1;i<100;i=i+1) begin
{cout[i],sum[i]} = a[i] + b[i] + cout[i-1];
end
end
endmodule
Problem 42 Generate for-loop: 100-digit BCD adder
Requirement:
本题已经提供了一个名为 bcd_fadd 的 BCD 一位全加器,我们需要实例化 100 个 bcd_fadd 来实现 100 位的 BCD 进位加法器。该加法器应包含两个 100bit 的 BCD 码(400bit 的矢量)和一个 cin, 输出产生 sum 和 cout。
已知:
module bcd_fadd {
input [3:0] a,
input [3:0] b,
input cin,
output cout,
output [3:0] sum );
Solution:
module top_module(
input [399:0] a, b,
input cin,
output cout,
output [399:0] sum );
wire [99:0] cout_100;
bcd_fadd dcd_fadd_0(
.a(a[3:0]),
.b(b[3:0]),
.cin(cin),
.cout(cout_100[0]),
.sum(sum[3:0])
);
generate
genvar i;
for(i=1;i<100;i=i+1) begin : bcc_fadd
bcd_fadd dcd_fadd_i(
.a(a[4*i+3:4*i]),
.b(b[4*i+3:4*i]),
.cin(cout_100[i-1]),
.cout(cout_100[i]),
.sum(sum[4*i+3:4*i])
);
end
endgenerate
assign cout = cout_100[99];
endmodule
注意:
-
先声明一个 wire 型的 cout_temp 来存放每次计算后 cout 的值。
-
注意向量的下标是从大到小的,所以切片时也要从大到小。
-
1 位 BCD 码对应 4 位二进制,但 100 位 BCD 码存储时还是以一串二进制形式存的,需要以四为单位存、取、用。
-
全加器之间的联系:上次的进位输出是这次的进位输入,除此以外,sum 各自算各自的。
方法二:仿照上一题来一个不用例化的,我的设想是:
module top_module(
input [399:0] a, b,
input cin,
output cout,
output reg [399:0] sum );
reg [99:0] cout_100;
integer i;
always @(*) begin
{cout_100[0],sum[3:0]} = a[3:0] + b[3:0] + cin;
for(i=1;i<100;i=i+1) begin
{cout_100[i],sum[4*i+3:4*i]} = a[4*i+3:4*i] + b[4*i+3:4*i] + cout_100[i-1];
end
end
endmodule
理想很丰满,现实是 error:
[VRFC 10-1775] range must be bounded by constant expressions
切片的上下限必须都是常数才行,唉。
做了一下午,效率好低。。