设计:4 位 2:1 多路选择器(MUX)
本节尝试向页面添加了互动组件与 SVG,如有显示异常,可在评论反馈或直接联系作者。
认识 4 位 2:1 多路选择器
多路选择器(Multiplexer,MUX)的作用是:从多个输入中选择一个,传递到输出。
其接口抽象为:
| 信号 | 位宽 | 作用 |
|---|---|---|
a |
4 位 | 输入 A |
b |
4 位 | 输入 B |
sel |
1 位 | 选择信号 |
y |
4 位 | 输出 |
当 sel=0 时,输出应选择 a;当 sel=1 时,输出应选择 b。
需要注意的是,MUX 不需要时钟激发。这种组合逻辑电路没有时钟输入也能工作。
| 类型 | 特征 | 示例 | 工作方式 |
|---|---|---|---|
| 组合逻辑 | 当前输入决定输出 | 多路选择器、加法器、译码器 | 输入变化后,输出随之变化,不需要时钟沿触发。 |
| 时序逻辑 | 保存状态 | D 触发器、计数器、状态机 | 通常在时钟沿更新寄存器保存的状态。 |
真实硬件中的组合逻辑并非零延迟,输出需要经过传播时间才稳定。但它不需要等待特定的时钟沿。
用 SystemVerilog 实现
接下来亲自实现一下端口列表。创建 src/mux2.sv。
注意:与 Verilog 不同,SystemVerilog 使用
logic类型代替wire和reg。详见上节对比。
logic可以用于组合逻辑输出,也可以用于时序逻辑中的寄存器变量,具体取决于驱动方式。
module mux2 (
input logic [3:0] a,
input logic [3:0] b,
input logic sel,
output logic [3:0] y
);
endmodule
现在开始实现输出逻辑,可以选择使用三目条件或者组合 always_comb 块。初学时此处使用三目条件(sel ? b : a)实现:
assign y = sel ? b : a;
SystemVerilog 使用 assign 语句为组合逻辑赋值。sel ? b : a 表示如果 sel 为真(1),则输出 b,否则输出 a。
assign 是连续赋值。只要 sel、a 或 b 发生变化,y 就会根据表达式重新计算,不需要时钟。
为了更加符合工程项目结构,建立 tests/tb_mux2.sv 测试文件以编写 testbench。
注意:此处的测试与之前所配置的 Python 测试不同,SystemVerilog 的 testbench 用于入门、时序逻辑、基础模块,Python 测试则适用于大量随机测试、复杂验证、自动化。现在建立新的测试项目时需要修改
justfile,添加新的测试目标。test-sv FILE TEST: mkdir -p build verilator --binary --timing --top-module {{TEST}} -Mdir build/vobj src/{{FILE}}.sv tests/{{TEST}}.sv ./build/vobj/V{{TEST}}
先回答一个设计问题
假设我们测试:
a = 4'b1010;
b = 4'b0111;
sel = 1'b1;
在 testbench 中写测试与其它语言写集成测试类似。我们可以使用 if 判断输出是否符合预期,并在不符合时打印错误信息。
对于 if (y !== 4'b0111) 需要理解,!== 是 SystemVerilog 中的不等于(严格)运算符,它不仅比较数值,还比较位的有效性(0、1、X、Z)。在测试中使用 !== 可以确保输出与预期完全匹配,包括未定义或高阻态的情况。上述示例中 0b1010 与 0b0111 在 sel = 1 时,输出应为 0b0111,如果输出不符合预期,测试将打印错误信息。
现在开始编写。
module tb_mux2;
logic [3:0] a;
logic [3:0] b;
logic sel;
logic [3:0] y;
mux2 dut (
.a (a),
.b (b),
.sel(sel),
.y (y)
);
initial begin
// Setting input.
a = 4'b1010;
b = 4'b0111;
sel = 1'b1;
// Waiting for stability.
#1;
if (y !== b) $fatal(1, "Test failed!");
$display("Test passed!");
$finish;
end
endmodule
创建测试模块需要注意:
module tb_mux2;定义测试模块不需要端口,因为它是顶层测试模块。mux2 dut (...);实例化被测试的模块mux2,并将信号连接到测试模块的逻辑变量。initial begin ... end块在仿真开始时执行一次,设置输入信号、等待传播延迟,并检查输出是否符合预期。#1;表示等待 1 个时间单位(仿真时间),确保组合逻辑有足够时间稳定输出。$fatal(1, "Test failed!");在测试失败时终止仿真并打印错误信息。1是错误代码,表示仿真以错误状态退出。$display("Test passed!");在测试通过时打印成功信息。$finish;结束仿真。
然后运行测试:
just test-sv mux2 tb_mux2
返回 Test passed! 表示测试通过。
试着改变输入 sel 为 0,并修改预期输出为 a,再次运行测试,确保测试覆盖了所有情况。
module tb_mux2;
logic [3:0] a;
logic [3:0] b;
logic sel;
logic [3:0] y;
mux2 dut (
.a (a),
.b (b),
.sel(sel),
.y (y)
);
initial begin
// Setting input.
a = 4'b1010;
b = 4'b0111;
sel = 1'b1;
// Waiting for stability.
#1;
if (y !== b) $fatal(1, "Test failed! sel = 1.");
sel = 1'b0;
#1;
if (y !== a) $fatal(1, "Test failed! sel = 0.");
$display("Test passed!");
$finish;
end
endmodule
运行测试,应当能够通过。
现在,回到刚刚的测试。为什么在 sel 发生变化后 y 也会立即发生变化?
因为 MUX 不需要等待时钟沿,输入变化后,输出就会随组合逻辑的传播而更新。
导出波形
接下来,试试再添加一组测试在刚刚的 tb_mux2.sv 中:
- 将
a设为4'b0000 - 将
b设为4'b1111 - 设置
sel = 0,等待#1,检查输出 - 再设置
sel = 1,等待#1,检查输出
// Setting input
a = 4'b0000;
b = 4'b1111;
sel = 1'b0;
#1;
// Checking output.
if (y !== a) $fatal(1, "Test failed! sel = 0.");
sel = 1'b1;
#1;
if (y !== b) $fatal(1, "Test failed! sel = 1.");
运行测试:
just test-sv mux2 tb_mux2
测试通过。
接下来在 initial begin 后面添加:
$dumpfile("build/mux2.vcd");
$dumpvars(0, tb_mux2);
这两个系统任务分别用于:
$dumpfile:指定波形文件的路径。$dumpvars:记录测试平台及其内部的信号变化。
然后再编辑 justfile,给 verilator 添加 --trace 参数:
test-sv FILE TEST:
mkdir -p build
verilator --binary --trace --timing --top-module {{TEST}} -Mdir build/vobj src/{{FILE}}.sv tests/{{TEST}}.sv
./build/vobj/V{{TEST}}
再次运行测试,然后得到 build/mux2.vcd 文件。使用波形查看器 GTKWave打开该文件。
打开这些数据:
这里能够看到第一帧(0ps)的 sel 为 1,输出 y 为 7 = b。
第二帧(1ps)sel 为 0,输出为 a = A。
第三帧(2ps)sel 为 0,输出为 a = 0。
第四帧(3ps)sel 为 1,输出为 b = F。
至此,4 位 2:1 多路选择器的设计、实现、测试和波形导出都完成了。




