Verilog多路选择器

HDLBits链接


定义

多路选择器(Multiplexer,简称MUX)是数字系统中最常用的电路之一。简单来说,它就像一个单刀多掷开关——根据选择信号的不同,从多个输入中选一个送到输出端。

让我们用更正式的语言来定义:

多路选择器是一个多输入、单输出的组合逻辑电路。它可以根据地址码(选择码)的不同,从多个输入数据流中选取一个,让其输出到公共的输出端。

打个生活中的比方:多路选择器就像一个电梯的楼层选择器——你按下想去的楼层按钮(选择信号),电梯就会把你带到对应的楼层(选择对应的输入)。


部分练习题

让我们通过一系列的题目来掌握多路选择器的设计。


题目1:2-to-1多路选择器(1位宽)

题目描述1:实现一个位宽为1的2-to-1多路选择器。当 sel = 0,选择 a;当 sel = 1,选择 b

Solution1

1
2
3
4
5
6
7
8
9
module top_module(
input a,
input b,
input sel,
output out
);
// 三目运算符:条件 ? 条件成立选这个 : 条件不成立选这个
assign out = sel ? b : a;
endmodule

小贴士:2选1多路选择器用三目运算符写起来最简洁!


题目2:2-to-1多路选择器(100位宽)

题目描述2:实现一个位宽为100的2-to-1多路选择器。当 sel = 0,选择 a;当 sel = 1,选择 b

Solution2

1
2
3
4
5
6
7
8
9
module top_module(
input [99:0] a,
input [99:0] b,
input sel,
output [99:0] out
);
// 三目运算符同样适用于向量!
assign out = sel ? b : a;
endmodule

看到了吗?和1位的情况几乎一模一样,只是位宽变了而已!这就是Verilog的方便之处——向量操作可以大大简化代码。


题目3:9-to-1多路选择器(16位宽)

题目描述3:创建一个位宽为16的9-to-1多路选择器。

  • sel = 0 选择 a
  • sel = 1 选择 b
  • ……
  • sel = 8 选择 i

对于未使用的 sel 值(sel = 9~15),将所有输出位设置为1。

Solution3

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 [15:0] a, b, c, d, e, f, g, h, i,
input [3:0] sel,
output reg [15:0] out
);
always @(*) begin
case(sel)
4'd0: out = a;
4'd1: out = b;
4'd2: out = c;
4'd3: out = d;
4'd4: out = e;
4'd5: out = f;
4'd6: out = g;
4'd7: out = h;
4'd8: out = i;
default: out = 16'hffff; // 未使用的情况:全1
endcase
end

endmodule

小贴士:选择分支超过2个时,用case语句比嵌套if-else或三目运算符更清晰!


题目4:256-to-1多路选择器(1位宽)

题目描述4:创建一个位宽为1的256-to-1多路选择器。256个输入都被打包成一个256位的输入向量。

  • sel = 0 表示选择 in[0]
  • sel = 1 表示选择 in[1]
  • ……

Solution4

1
2
3
4
5
6
7
8
module top_module(
input [255:0] in,
input [7:0] sel,
output out
);
// 直接用sel作为索引!是不是很简单?
assign out = in[sel];
endmodule

Verilog允许直接用变量作为向量的索引,这在设计多路选择器时特别方便!


题目5:256-to-1多路选择器(4位宽)

题目描述5:创建一个位宽为4的256-to-1多路选择器。256个4位输入都被打包成一个1024位的输入向量。

  • sel = 0 选择 in[3:0]
  • sel = 1 选择 in[7:4]
  • sel = 2 选择 in[11:8]
  • ……

这个题目稍微有点挑战性!因为每个输入是4位,我们需要选择连续的4位。

这时候就需要用到Verilog的一个特殊语法:+:-:操作符

先看一下这两个操作符的用法:

1
2
3
4
data[0 +: 8]  // 等价于 data[7:0]
data[15 +: 2] // 等价于 data[16:15]
data[7 -: 8] // 等价于 data[7:0]
data[15 -: 2] // 等价于 data[15:14]

格式说明:

  • 起始位置 +: 位宽:从起始位置开始,向下(索引增大的方向)选指定位宽
  • 起始位置 -: 位宽:从起始位置开始,向上(索引减小的方向)选指定位宽

Solution5

1
2
3
4
5
6
7
8
module top_module(
input [1023:0] in,
input [7:0] sel,
output [3:0] out
);
// sel*4是起始位置,然后向下选4位
assign out = in[sel*4 +: 4];
endmodule

是不是很优雅?用+:操作符一行就搞定了!


入门者避坑指南

多路选择器虽然简单,但初学者也容易踩一些坑:


错误1:没有覆盖所有情况

错误表现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module bad_example (
input [1:0] sel,
input [3:0] a, b, c,
output reg [3:0] y
);
always @(*) begin
case(sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
// 漏掉了sel == 2'b11的情况!
endcase
end
endmodule

错误原因:
这会生成锁存器!因为当sel是2’b11时,y保持原值不变。

正确做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module good_example (
input [1:0] sel,
input [3:0] a, b, c,
output reg [3:0] y
);
always @(*) begin
case(sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
2'b11: y = 4'd0; // 加上default情况
endcase
end
endmodule

或者用先赋初值的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module good_example2 (
input [1:0] sel,
input [3:0] a, b, c,
output reg [3:0] y
);
always @(*) begin
y = 4'd0; // 先赋初值
case(sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
// 不需要default了
endcase
end
endmodule

调试技巧:

  • 写case语句时,先把default写上,然后再填其他分支
  • 或者,在always块开头先给所有输出赋初值

错误2:索引越界

错误表现:

1
2
3
4
5
6
7
8
module bad_example (
input [99:0] in,
input [7:0] sel,
output out
);
// sel是8位,可能的值是0-255,但in只有100位!
assign out = in[sel];
endmodule

错误原因:
当sel >= 100时,索引越界了!虽然有些仿真器和综合工具可能会处理,但这是不安全的。

正确做法:

1
2
3
4
5
6
7
8
module good_example (
input [99:0] in,
input [7:0] sel,
output out
);
// 先检查sel是否在有效范围内
assign out = (sel < 8'd100) ? in[sel] : 1'b0;
endmodule

或者:

1
2
3
4
5
6
7
8
9
10
11
12
module good_example2 (
input [99:0] in,
input [7:0] sel,
output reg out
);
always @(*) begin
if (sel < 8'd100)
out = in[sel];
else
out = 1'b0;
end
endmodule

错误3:误用+:-:

错误表现:

1
2
3
4
5
6
7
8
module bad_example (
input [1023:0] in,
input [7:0] sel,
output [3:0] out
);
// 错误!`+:`的右边应该是位宽,不是结束位置
assign out = in[sel*4 +: sel*4 + 3];
endmodule

错误原因:
+:-:的第二个操作数是位宽,不是结束位置!

正确做法:

1
2
3
4
5
6
7
8
module good_example (
input [1023:0] in,
input [7:0] sel,
output [3:0] out
);
// 正确:sel*4是起始位置,4是位宽
assign out = in[sel*4 +: 4];
endmodule

调试技巧:

  • 记住:+:-:后面跟的是位宽,不是结束索引
  • 如果记不住,可以用传统的方式,但要注意位宽是否固定

错误4:选择方式不恰当

错误表现:

1
2
3
4
5
6
7
8
9
10
module bad_example (
input [1:0] sel,
input [3:0] a, b, c, d,
output [3:0] y
);
// 2选1用三目运算符没问题,但4选1还嵌套就太乱了
assign y = (sel == 2'd0) ? a :
(sel == 2'd1) ? b :
(sel == 2'd2) ? c : d;
endmodule

虽然语法上没问题,但可读性很差!

正确做法:

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

调试技巧:

  • 2选1:推荐用三目运算符
  • 3选1及以上:推荐用case语句
  • 如果有优先级:用if-else

错误5:位宽不匹配

错误表现:

1
2
3
4
5
6
7
8
module bad_example (
input [3:0] a, b,
input sel,
output [2:0] y
);
// 错误!a和b是4位,y是3位,位宽不匹配
assign y = sel ? b : a;
endmodule

错误原因:
虽然有些工具会自动截断高位,但这可能导致意外的结果。最好显式指定位宽匹配。

正确做法:

1
2
3
4
5
6
7
8
module good_example (
input [3:0] a, b,
input sel,
output [2:0] y
);
// 显式取低3位
assign y = sel ? b[2:0] : a[2:0];
endmodule

调试技巧:

  • 确保选择器的输入和输出位宽一致
  • 如果位宽不同,显式说明如何处理(截断、补零等)

本章小结

这一章我们学习了多路选择器的设计:

  1. 多路选择器的定义:根据选择信号,从多个输入中选一个输出
  2. 几种实现方式
    • 2选1:三目运算符最简洁 sel ? b : a
    • 多选一:case语句最清晰
    • 索引选择:直接用变量作为索引 in[sel]
  3. 高级技巧+:-:操作符,用于选择连续的多位
  4. 注意事项
    • 覆盖所有选择情况,避免生成锁存器
    • 注意索引不要越界
    • 选择合适的实现方式,保证代码可读性

多路选择器是数字设计中最基础、最常用的模块之一,一定要熟练掌握!