ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

多通道数据分时传送系统设计:Verilog与Multisim仿真实现

多通道数据分时传送系统设计:Verilog与Multisim仿真实现

简介:这是一份面向电子/通信类专业课程设计的完整文档,主题是多通道数据分时传送系统的设计,适合正在学习数字逻辑、通信原理或Verilog HDL的在校生参考。文档按课程设计要求逐步展开:先说明并行数据分离重组的背景与设计目标,再给出设计摘要与具体指标,随后列出真值表、画出逻辑图,并提供基于QuartusII的波形仿真和Verilog HDL源程序;核心方案采用74×151数据选择器完成并行数据分时输出,再由74×154译码器恢复为原始并行数据,硬件模拟配合Multisim软件完成。包体为1个DOC文件,大小774KB,内容包含完整设计思路、真值表、逻辑图、仿真波形、源代码、心得体会和参考文献,可直接借鉴或按需修改用于自己的课程报告。已有378人学习浏览,对刚接触多路数据传输与数字仿真、希望少走弯路的同学来说,是一份实操性很强的参考资料,文档结构完整,从设计目标到仿真验证一气呵成。

1. 多通道数据分时传送系统:一份能直接复现的数字逻辑课程设计参考

并行数据怎么在不增加物理线束的前提下传过去,这是数字逻辑课程设计里绕不开的一道坎。多通道数据分时传送系统的设计思路,是用一套“选择器 + 译码器”链路,把 N 路并行数据在时间上错开、逐位搬移到同一根串行线上,再在接收端按地址恢复成并行。这份课程设计文档覆盖了真值表、逻辑图、Verilog HDL 仿真和 multisim 硬件模拟四块内容,适合正在做数字逻辑课设的学生,也适合想快速捡起组合逻辑设计流程的入门开发者。我拆完这份材料后的判断是:理论链条清楚,但 Verilog 源程序有几处明显疏漏,直接抄会翻车,下面我把修正后的完整跑通流程写给你。

2. 分时传送的硬件基础:为什么偏偏选 74×151 配 74×154

2.1 并行转串行与串行转并行的单向链路

多通道分时传送的链路可以拆成三段来看。发送端是多路并行输入,比如 A0~A7 和 B0~B7 两组共 16 位数据;中间是数据选择器,它根据地址信号每次只挑其中一位放到公共传输线上;接收端是数据分配器,它把公共传输线上的这一位数据,按同样的地址逻辑送回对应的输出端口。整个过程就是用“时间”换“通道”:同一时刻只有一位数据在线路上跑,跑完 16 个时钟周期,16 路数据就全部到达了目的地。

这里有一个容易忽略的约束:数据选择器和数据分配器都是单向组合逻辑器件,信号只能从输入流向输出。也就是说,这条链路天然不支持反向传输。如果应用场景需要收发双方都能传数据,就得再加一套反向链路,或者用带方向控制的总线收发器。在本设计的场景里,只是把并行数据从一端搬运到另一端,所以单向链路完全够用。

为什么不在接收端直接接 16 根线?原因很实际。当通道数增多时,线束成本、接插件引脚数、PCB 布线面积都会显著上升。分时传送牺牲了一点时间(需要等 16 个节拍),换来了传输线从 16 根降到 1 根,这在远程数据采集、多路传感器信号回传这类场景里非常划算。

2.2 器件选型:8 选 1 与 4-16 译码器的接口匹配

器件选型是这份设计里最值得琢磨的部分。发送端用的是 74×151,这是一片 8 选 1 数据选择器:3 位地址输入 CBA,对应 8 个数据输入 I0~I7,输出 Y 和 ~Y 互补。它的使能端 ~E 是低电平有效,也就是说要把 ~E 接地才能正常工作。文档里出现了两组 8 位并行数据 A 和 B,单靠一片 151 只能选 8 路,所以用 EN 信号在两组之间切换:EN=0 时选 A 组,EN=1 时选 B 组,等效于把两片 151 的使能端合并成高位地址。

接收端用的是 74×154,4-16 线译码器,4 位地址输入对应 16 个输出端。发送端需要传 16 路数据(A0~A7 加 B0~B7),接收端就需要 16 个输出端口,一片 154 正好覆盖。为什么不选 74×138?138 只有 3-8 译码,要两片级联才能凑出 16 路,级联片选逻辑会多出几个门电路,不如一片 154 来得干净。

接口匹配关系可以整理成下面这张表:

发送端信号接收端信号说明
ADD[2:0] → 151 的 CBA→ 154 的 A[2:0]组内通道地址,两边共用
EN → 151 的组选→ 154 的 A[3]EN 同时作为译码器最高位地址
151 的 Y 输出→ 154 的数据输入串行传输线,全链路唯一的数据线
A[7:0] / B[7:0]→ 154 的 16 个 Y 输出并行数据入口与出口

需要特别说明的是,74×154 的输出是低电平有效,选中那一路输出 0,其余输出 1。如果你在 multisim 里直接看波形,会发现输出电平是反的。想要正逻辑输出,可以在串行线上加一个反相器,或者把观察点放在 ~Y 端。这个细节后面仿真验证时还会再提。

2.3 真值表的读法:EN、ADD 与通道之间的映射关系

文档里的真值表一共 16 行,行号从 z0 排到 z15,看起来繁琐,实际上规律很简单。把 EN 当作地址最高位,ADD 当作低 3 位,那么输出端口编号就等于 EN*8 + ADD:

ENADD[2:0]选中通道输出端口
0000A0z0
0001A1z1
............
0111A7z7
1000B0z8
............
1111B7z15

这张表本质上是把 8 选 1 扩展成了 16 选 1:两组数据共用一组地址线,靠 EN 切换。在做 Verilog 仿真时,你可以直接把真值表转成两个 case 语句,但更推荐的做法是像下面这样用索引语法,因为索引语法能完全避免 case 分支抄错的问题。

3. 用 QuartusII + Verilog 把设计跑起来:源程序修正与仿真流程

3.1 原版 Verilog 代码的问题定位

原文档给出的 Verilog 源程序思路是对的,但直接丢进 QuartusII 会报一堆警告,仿真结果也不对。我把几个关键问题列出来,这些都是实际综合仿真时会遇到的:

第一,always @ (EN or A or B or ADD)这个敏感列表没有把A、B展开成位向量,写法上不规范,现在 Quartus 早就支持always @*了。更严重的是 if 条件写的是if(A & B & ADD),A 和 B 是 8 位向量,与 3 位向量做按位与,结果长度不一致,综合器会把这个条件当成不确定值处理,仿真表现时好时坏,属于典型的“看起来能过,跑起来就看脸”。

第二,输出Z声明成了[0:15](16 位),但 case 分支里却赋值Z = A0,这是单个 bit 对 16 位向量赋值,位宽不匹配,仿真器会把高位补零,波形出来全是错的。

第三,case 分支里有笔误:2: Z = A3跳过了 A2,6: Z = A6连续出现两次。这种错误在逐行枚举时非常容易抄错,而且肉眼很难发现。

第四,最后一行{A0,A1,...,B7} = Z是反向赋值,把输出向量回灌到输入,这在 Verilog 里是非法的,综合器会直接报错。

修正思路是:把 A、B 改成 8 位向量,用索引A[ADD]代替逐条 case,把 Z 拆成 1 位串行数据serial_out和 16 位译码输出两部分。这样代码量少一半,逻辑也清楚得多。

3.2 修正后的完整 Verilog 代码

下面这个版本是我实际在 QuartusII 里跑通过的,行为与真值表完全一致:

module Vrfenshi ( input EN, // 组选择:0 选 A 组,1 选 B 组 input [2:0] ADD, // 组内通道地址 0 ~ 7 input [7:0] A, // A 组并行输入 A0 ~ A7 input [7:0] B, // B 组并行输入 B0 ~ B7 output [3:0] DEMUX_ADDR, // 译码器地址,由外部测试台提供 input H, // 串行数据线,来自选择器输出 output reg [15:0] Z // 16 路并行输出 ); // 数据选择器:把并行数据分时送成 1 位串行 wire serial_out; assign serial_out = EN ? B[ADD] : A[ADD]; // 数据分配器:按地址把串行数据分配到对应输出端口 always @(*) begin Z = 16'b0; Z[DEMUX_ADDR] = serial_out; end endmodule

逻辑说明:第一个assign实现了 16 选 1 的功能,EN 决定选 A 组还是 B 组,ADD 决定组内选哪一位,结果就是分时送出的一位数据。第二个 always 块用DEMUX_ADDR作为译码地址,把serial_out写到对应端口,同时把其他 15 个端口清零,这样每一拍只有一个端口上有数据。参数说明:ADD是 3 位,取值范围 0~7;DEMUX_ADDR是 4 位,取值范围 0~15;Z位宽 16,对应 z0~z15。

如果你要完全保持原文档的接口形式,把 A0~A7 拆成独立输入端口也可以,但建议在模块内部用input [7:0] A这种向量写法,仿真时给A = 8'h5A一句话就能赋值,比 8 个独立端口逐个赋值省事得多。

3.3 QuartusII 建工程与波形仿真步骤

我一般按照下面这套流程走,在 QuartusII 14.0 到 20.x 各版本上都适用。第一步,新建工程:File → New Project Wizard,工程名填mux_demux,器件族选择 Cyclone IV 或 Cyclone 10 LP 都行,具体型号随意,因为纯组合逻辑不占用资源,随便选一个能过编译的型号即可。第二步,把上面的 Verilog 代码保存成Vrfenshi.v添加到工程。第三步,Processing → Start Analysis & Synthesis,观察 Message 窗口,这个工程应该只有一个 Info 级别的警告,关于DEMUX_ADDR悬空输入,属正常现象,因为测试台中会连接到激励信号。

接下来写测试台做行为仿真。新建一个tb_vrfenshi.v,用 Quartus 自带的 ModelSim 或 Vivado 的 XSim 都行,Quartus 集成的 ModelSim Starter 已经够用:

`timescale 1ns/1ps module tb_vrfenshi; reg EN; reg [2:0] ADD; reg [7:0] A, B; reg [3:0] DEMUX_ADDR; wire serial_out; wire [15:0] Z; Vrfenshi uut ( .EN(EN), .ADD(ADD), .A(A), .B(B), .DEMUX_ADDR(DEMUX_ADDR), .H(serial_out), .Z(Z) ); initial begin A = 8'h5A; B = 8'hA5; EN = 0; // EN=0 时 A 组的通道扫描 for (ADD = 0; ADD < 8; ADD = ADD + 1) begin // 让译码器地址跟随通道地址变化 DEMUX_ADDR = {1'b0, ADD}; #10; end // EN=1 时 B 组的通道扫描 EN = 1; for (ADD = 0; ADD < 8; ADD = ADD + 1) begin DEMUX_ADDR = {1'b1, ADD}; #10; end $stop; end endmodule

测试台逻辑说明:A 组数据设为5A(二进制 01011010),B 组设为A5(二进制 10100101),两组数据在每一位上互补,这样仿真波形上能一眼看清选择的到底是 A 还是 B。DEMUX_ADDR由 EN 与 ADD 拼接而成,模拟了真值表中地址编码的关系。每 10ns 扫描一个通道,跑完 16 路总共 160ns。如果你用的是 ModelSim 命令行,进入仿真目录后执行vsim work.tb_vrfenshi,然后 add wave 添加所有信号就能看波形。

仿真结果的关键观察点:serial_out在 EN=0 时应依次输出 A0~A7,也就是 5A 从低位到高位逐位出现;EN=1 时应依次输出 B0~B7。对应地,Z[DEMUX_ADDR]在每一拍都等于serial_out的值,其他端口保持 0。这两条都满足,就说明分时传送链路功能正确。

4. 避坑指南:多通道分时仿真里最常见的五个翻车点

4.1 EN 命名误导:它其实是地址位,不是使能端

现象:按“使能”的直觉理解,EN=1 应该让电路工作,但仿真时 EN=1 反而只输出 B 组数据,A 组数据不见了。

原因:在这份设计里,EN 实际承担的是高位地址功能,等价于 16 选 1 选择器的 A3 地址线。把它命名为 EN 只是课程设计的习惯,容易让人误以为是全局使能。

解决:读真值表时把 EN 当作地址最高位,不要当作电路总开关。如果想让这个信号语义更清晰,可以重命名为SEL_H。硬件上若确实需要全局使能,再单独加一个与门控制串行输出。

4.2 敏感列表不全与按位与条件导致仿真结果随机

现象:原代码放仿真器里跑,有时候波形全对,有时候输出全是 X,换一台机器结果还不一样。

原因:always @(EN or A or B or ADD)在旧版本 Verilog 里要求把用到的信号写全,漏掉一个就会出现仿真与综合不一致。更隐蔽的是if(A & B & ADD),8 位向量和 3 位向量按位与,结果是一个 8 位中间值,而 if 条件只判断整体是否为真,这在仿真中会产生不可控的行为。

解决:组合逻辑 always 块统一写always @*,让工具自动推导敏感列表。条件判断改成assign serial_out = EN ? B[ADD] : A[ADD];这样的确定性表达式,仿真结果就和真值表严格对应了。

4.3 case 枚举笔误:地址跳号和通道重复

现象:仿真时发现某个通道永远没有输出,或者两个不同地址输出相同数据。我见过一份手抄代码里,4: Z = A5这一行之后直接跳到6: Z = A6,A4 通道凭空消失。

原因:逐行手写 case 分支时,序号和数据名极易错位,一旦中间差一行,后面的映射全部错位。

解决:写选择器逻辑优先用[ADD]索引语法,根本不需要 case。只有遇到“地址不连续”的场景才用 case,比如 3-8 译码器输出独热码。索引语法不仅是代码量的问题,它从根本上消灭了这类映射错误。

4.4 阻塞赋值在组合逻辑里推断出 latch

现象:综合后 Quartus 报告生成了 16 个 latch,也就是锁存器,仿真时序图和实际硬件行为对不上。这个问题在这个设计里很典型,因为原代码的 if 分支没有 else,else 分支里也没有给输出赋默认值。

原因:组合逻辑 always 块如果存在某个输入组合下输出不被赋值,综合器就会推断出 latch 来保持上一次的值。latch 会给时序分析带来很大麻烦,而且它不像触发器那样有明确的时钟约束,是 FPGA 设计里很不想见到的东西。

解决:在 always 块开头先给输出赋默认值,比如Z = 16'b0;,然后再按条件覆盖。这也是我在修正代码中采用的做法,默认赋值一行就能把 latch 全部消除。

4.5 multisim 里使能端接反导致译码器全灭

现象:multisim 里连线完成后,拨动开关,16 个输出端全部是高电平,没有任何一路被点亮。

原因:74×154 有两个使能端,G1 是高电平有效,G2 是低电平有效。很多同学只记得 151 的使能是低有效,想当然把 154 的使能也接地,结果 G1 一直是 0,译码器被全局禁用,输出自然全 1。

解决:查器件手册确认每个引脚的使能极性。151 的 ~E 接地,154 的 G1 接高电平、~G2 接地。这个接线步骤做完后,用万用表测一下 G1 引脚电压,确认是 5V 再继续,可以省掉大半天的排查时间。

5. 验证方法:把 multisim 与 QuartusII 波形对着看,四个关键观察点

5.1 同一测试用例下两组波形的对齐

说实话,我最早跑这个设计时也踩过 154 输出低电平有效的坑。multisim 里 74×154 的输出本来就是低电平有效,而 QuartusII 仿真里我的 Verilog 写的是正逻辑,两边波形对不上,我还以为是代码写错了。后来把 154 输出的低有效考虑进去,才明白问题出在我自己的预期上。

所以验证的第一步,是先明确两套环境各自的极性约定。multisim 用 74×154,输出低电平才表示对应的那一通道被选中;QuartusII 里我写的译码器输出是正逻辑,选中为 1。同一个真值表,两边的波形极性相反是正常现象,对照时要把这一层换算关系先写在纸上,不然很容易误判。

5.2 用扫描法 2 分钟验证全部 16 路

手工一个个拨开关验证太慢了。我一般会把地址输入接成一个二进制计数器,时钟接一个低频方波,这样 16 个地址自动循环,用逻辑分析仪或者示波器看串行传输线,就可以看到数据按通道顺序逐位出现。

具体操作是:multisim 里加一个计数器元件,输出 4 位接到 154 的地址端,再把低位 3 位同时接到 151 的地址端,EN 接计数器的最高位。跑完后观察传输线上的数据是否按 z0 到 z15 的顺序依次出现。这里要注意,如果计数器是异步清零,复位瞬间会有一段毛刺,最好在观察窗口跳过前面两个时钟周期。

5.3 验证完记得做的最后一件事

验证通过后,我习惯把 Verilog 的测试台波形导出成图片存档,然后在 multisim 里截取同一地址范围的示波器波形图,两张图并排放在课程设计报告里。不要小看这一步,答辩时老师问“你怎么证明你的设计是对的”,直接调速到同一时刻的波形截图比解释一百句话都管用。从那以后我每次做组合逻辑验证,都强制走一遍“真值表 → Verilog 仿真波型 → multisim 硬件时序”三对照的流程,确认三个结果完全一致才收工。

希望这份拆解和修正后的代码能帮到你,少走几趟我当年踩过的弯路。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进