Generate实例化模块

HDLBits链接


三目运算符

Verilog中有跟C语言类似的三目运算符,这可以用于在一行中根据条件选择两个值中的一个,而不用在组合always块中使用if-then。

语法很简单:

1
条件 ? 值1 : 值2

就像生活中做选择题:如果条件成立,选值1;否则选值2。

下面给出一些示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(0 ? 3 : 5)     // 结果是5,因为条件是false
(sel ? b : a) // 一个2选1多路选择器

always @(posedge clk) // T触发器
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; // 三态缓冲器

// 3选1多路选择器的嵌套写法
((sel[1:0] == 2'h0) ? a :
(sel[1:0] == 2'h1) ? b :
c )

题目:四个数找最小值

题目描述:给定4个无符号数,求最小值。无符号数可以用比较运算符(a < b)进行比较。

Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module top_module (
input [7:0] a, b, c, d,
output [7:0] min
);
wire [7:0] mintemp1;
wire [7:0] mintemp2;

// 第一步:a和b比较,取较小的
assign mintemp1 = (a < b) ? a : b;
// 第二步:c和第一步结果比较
assign mintemp2 = (c < mintemp1) ? c : mintemp1;
// 第三步:d和第二步结果比较,得到最小值
assign min = (d < mintemp2) ? d : mintemp2;

endmodule

小贴士:三目运算符可以嵌套使用,但要注意可读性。如果嵌套太多层,用if-else或者case语句可能更清晰。


缩位运算符

缩位运算符可以对向量的各个位进行与、或和异或操作,产生1位的输出。这就像把向量的所有位”缩”成一位,所以叫缩位运算符。

1
2
3
& a[3:0]     // 与缩位: a[3] & a[2] & a[1] & a[0],等价于(a[3:0] == 4'hf)
| b[3:0] // 或缩位: b[3] | b[2] | b[1] | b[0],等价于(b[3:0] != 4'h0)
^ c[2:0] // 异或缩位: c[2] ^ c[1] ^ c[0]

我们可以通过翻转上述缩位运算的输出来获得与非、或非和同或缩位运算的输出。


题目1:奇偶校验

题目描述1:奇偶校验经常被用作传输数据时检测错误的简单方法。创建一个电路,为一个8位字节计算一个奇偶校验位(它将在1个字节的基础上增加1位)。我们将使用”偶数”奇偶校验,其中奇偶校验位只是所有8位数据位的异或。

Solution 1

1
2
3
4
5
6
7
module top_module (
input [7:0] in,
output parity
);
// 异或缩位:所有位异或在一起
assign parity = ^ in;
endmodule

题目2:100位输入的缩位运算

题目描述2:构建一个组合电路,包括100个输入,in[99:0]

3个输出:

  • out_and:100个位的与运算输出
  • out_or:100个位的或运算输出
  • out_xor:100个位的异或运算输出

Solution 2

1
2
3
4
5
6
7
8
9
10
11
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

for循环的组合逻辑:向量顺序翻转

题目描述:给定一个100位的输入向量,翻转它的位顺序。

Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
module top_module(
input [99:0] in,
output [99:0] out
);
integer i;

always @(*) begin
for(i = 0; i < 100; i = i + 1) begin
// in[0] -> out[99], in[1] -> out[98], ..., in[99] -> out[0]
out[i] = in[99 - i];
end
end
endmodule

小贴士:在组合逻辑的always块中使用for循环是可以综合的!但要注意循环次数必须是固定的(编译时就能确定)。


for循环的组合逻辑:255位数统计1的个数

题目描述:”计数”电路对输入向量中的1进行计数。为一个255位输入向量建立一个”计数”电路。

Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module top_module (
input [254:0] in,
output [7:0] out
);
integer i;

always @(*) begin
// 先给输出赋初值
out = 8'd0;

for(i = 0; i < 255; i = i + 1) begin
// 如果in[i]是1,就加1
out = out + in[i];
end
end
endmodule

插叙:锁存器的危害

在继续讲Generate之前,让我们再回顾一下为什么要避免锁存器。之前提到过,这里再总结一下:

锁存器会对电路产生哪些影响呢?

  1. 锁存器对毛刺敏感,无异步复位端,不能让芯片在上电时处在确定的状态;
  2. 锁存器会使静态时序分析变得很复杂,不利于设计的可重用。

所以,在ASIC设计中,除了CPU高速电路,或者RAM这种对面积很敏感的电路,一般不提倡用锁存器。


循环生成语句

如果说for循环是让软件代码重复执行多次,那么Generate语句就是让硬件代码重复生成多次!

Generate语句常用于编写可配置的、可综合的RTL设计结构。它可用于创建模块的多个实例化,或者有条件的实例化代码块。

generate循环的语法与for循环语句的语法很相似。但是在使用时必须先在genvar声明中声明循环中使用的索引变量名,然后才能使用它。

genvar声明的索引变量被用作整数用来判断generate循环。genvar声明可以是generate结构的内部或外部区域,并且相同的循环索引变量可以在多个generate循环中,只要这些循环不嵌套。

注意:genvar只有在建模的时候才会出现,在仿真时就已经消失了!

Verilog中generate循环中的generate块可以命名也可以不命名。如果已命名,则会创建一个generate块实例数组。如果未命名,则有些仿真工具会出现警告,因此,最好始终对它们进行命名


题目1:100位行波进位加法器

题目描述1:通过实例化100个全加法器,创建一个100位行波进位加法器。

Solution 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module top_module(
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum
);
genvar i; // genvar类型,用于generate循环

generate
for(i = 0; i < 100; i = i + 1) begin: adder // 给generate块命名
if(i == 0) begin
// 第一个全加器:进位输入是cin
assign {cout[0], sum[0]} = a[0] + b[0] + cin;
end
else begin
// 其他全加器:进位输入来自上一位的cout
assign {cout[i], sum[i]} = a[i] + b[i] + cout[i - 1];
end
end
endgenerate
endmodule

题目2:100位BCD行波进位加法器

题目描述2

为您提供了一个名为bcd_fadd的BCD一位加法器,它的输入为两个BCD数字和进位输入信号,并生成求和输出和进位输出信号。

1
2
3
4
5
6
7
8
9
module bcd_fadd (
input [3:0] a,
input [3:0] b,
input cin,
output cout,
output [3:0] sum
);
// 内部实现...
endmodule

实例化bcd_fadd的100个副本,以创建一个100位的BCD行波进位加法器。

Solution 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module top_module(
input [399:0] a, b,
input cin,
output cout,
output [399:0] sum
);
wire [99:0] cout_temp; // 中间进位信号
genvar i;

generate
for(i = 0; i < 100; i = i + 1) begin: bcd_fadd
if(i == 0)
// 第一位:进位输入是cin
bcd_fadd bcd_inst(
.a(a[3:0]),
.b(b[3:0]),
.cin(cin),
.cout(cout_temp[0]),
.sum(sum[3:0])
);
else
// 其他位:进位输入来自上一位
bcd_fadd bcd_inst(
.a(a[4*i + 3 : 4*i]),
.b(b[4*i + 3 : 4*i]),
.cin(cout_temp[i - 1]),
.cout(cout_temp[i]),
.sum(sum[4*i + 3 : 4*i])
);
end
endgenerate

// 最后的进位输出
assign cout = cout_temp[99];
endmodule

入门者避坑指南

在这一章,我们学习了很多实用的设计技巧,初学者在使用这些技巧时容易犯以下错误:


错误1:generate循环中使用普通变量而非genvar

错误表现:

1
2
3
4
5
6
7
8
9
10
11
12
module bad_example (
input [9:0] a, b,
output [9:0] y
);
integer i; // 错误!应该用genvar

generate
for(i = 0; i < 10; i = i + 1) begin: my_loop
assign y[i] = a[i] & b[i];
end
endgenerate
endmodule

错误原因:
generate循环的索引变量必须是genvar类型,不能是integer。genvar是专门用于generate语句的类型,它在编译时就会被处理掉,不会出现在实际硬件中。

正确做法:

1
2
3
4
5
6
7
8
9
10
11
12
module good_example (
input [9:0] a, b,
output [9:0] y
);
genvar i; // 正确:使用genvar

generate
for(i = 0; i < 10; i = i + 1) begin: my_loop
assign y[i] = a[i] & b[i];
end
endgenerate
endmodule

调试技巧:

  • 记住:generate循环用genvar,always块里的for循环用integer

错误2:generate块没有命名

错误表现:

1
2
3
4
5
6
7
8
9
10
11
12
module bad_example (
input [9:0] a, b,
output [9:0] y
);
genvar i;

generate
for(i = 0; i < 10; i = i + 1) begin // 错误!没有命名
assign y[i] = a[i] & b[i];
end
endgenerate
endmodule

错误原因:
虽然有些工具可能接受没有命名的generate块,但这不是好的编码风格。有些仿真工具会发出警告,而且在调试时很难引用generate块内部的信号。

正确做法:

1
2
3
4
5
6
7
8
9
10
11
12
module good_example (
input [9:0] a, b,
output [9:0] y
);
genvar i;

generate
for(i = 0; i < 10; i = i + 1) begin: my_loop // 加上名字
assign y[i] = a[i] & b[i];
end
endgenerate
endmodule

调试技巧:

  • 给generate块起个有意义的名字,这样在波形查看器中也更容易找到对应的信号

错误3:组合逻辑for循环中忘记给变量赋初值

错误表现:

1
2
3
4
5
6
7
8
9
10
11
12
13
module bad_example (
input [3:0] in,
output [2:0] out
);
integer i;

always @(*) begin
// 错误!out没有赋初值
for(i = 0; i < 4; i = i + 1) begin
out = out + in[i];
end
end
endmodule

错误原因:
out在第一次循环时使用了它的旧值,这会导致生成锁存器!

正确做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
module good_example (
input [3:0] in,
output [2:0] out
);
integer i;

always @(*) begin
out = 3'd0; // 先赋初值!
for(i = 0; i < 4; i = i + 1) begin
out = out + in[i];
end
end
endmodule

调试技巧:

  • 在always块开头,先给所有要赋值的变量赋初值!这是避免锁存器的万能良药。

错误4:三目运算符嵌套过深

错误表现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module bad_example (
input [2:0] sel,
input [7:0] a, b, c, d, e, f, g, h,
output [7:0] y
);
// 错误!嵌套太深了,难以阅读
assign y = (sel == 3'd0) ? a :
(sel == 3'd1) ? b :
(sel == 3'd2) ? c :
(sel == 3'd3) ? d :
(sel == 3'd4) ? e :
(sel == 3'd5) ? f :
(sel == 3'd6) ? g : h;
endmodule

错误原因:
虽然语法上没问题,但嵌套太深的三目运算符可读性很差,容易出错。

正确做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module good_example (
input [2:0] sel,
input [7:0] a, b, c, d, e, f, g, h,
output reg [7:0] y
);
always @(*) begin
case(sel)
3'd0: y = a;
3'd1: y = b;
3'd2: y = c;
3'd3: y = d;
3'd4: y = e;
3'd5: y = f;
3'd6: y = g;
3'd7: y = h;
default: y = 8'd0;
endcase
end
endmodule

调试技巧:

  • 如果选择分支超过3个,考虑用case语句代替三目运算符
  • 代码首先是给人看的,其次才是给编译器看的!

错误5:generate循环的边界错误

错误表现:

1
2
3
4
5
6
7
8
9
10
11
12
13
module bad_example (
input [99:0] a,
output [99:0] b
);
genvar i;

generate
// 错误!应该是i < 100,不是i <= 100
for(i = 0; i <= 100; i = i + 1) begin: my_loop
assign b[i] = ~a[i];
end
endgenerate
endmodule

错误原因:
向量是[99:0],只有100位,索引到100就越界了!

正确做法:

1
2
3
4
5
6
7
8
9
10
11
12
module good_example (
input [99:0] a,
output [99:0] b
);
genvar i;

generate
for(i = 0; i < 100; i = i + 1) begin: my_loop
assign b[i] = ~a[i];
end
endgenerate
endmodule

调试技巧:

  • 仔细检查向量的范围和循环的边界
  • 向量[N:0]有N+1位,循环应该从0到N

本章小结

这一章我们学习了很多实用的设计技巧:

  1. 三目运算符条件 ? 值1 : 值2,适合简单的2选1选择
  2. 缩位运算符&|^,把向量缩成1位
  3. 组合逻辑中的for循环:可以综合!注意循环次数要固定,还要记得给变量赋初值
  4. Generate语句:硬件代码的”复制粘贴”,用于批量生成模块或逻辑
    • 必须用genvar声明索引变量
    • 最好给generate块命名
    • 可以配合if-else进行条件生成

这些技巧可以在设计过程中大大简化工作量,让代码更简洁、更易维护!