数字电路模拟程序作业报告
数字电路模拟程序作业报告
一、前言
本报告围绕“数字电路模拟程序”系列三次作业展开总结。该系列题目由南昌航空大学蔡轲老师设计,核心目标是使用 Java 实现一个能够解析、建模并模拟数字电路运行的程序。三次作业在功能上层层递进:
作业1(基础逻辑门):实现与门、或门、非门、异或门、同或门五种基础元件的电路模拟,重点在于输入解析、连接关系建立与拓扑排序计算。
作业2(组合电路元件):在作业1基础上新增三态门、译码器、数据选择器、数据分配器等复杂元件,并引入控制引脚概念,输出格式也更复杂。
作业3(子电路与异常检测):进一步增加子电路(Sub-circuit)嵌套机制,以及连接信息的异常输入检测,要求程序具备更强的鲁棒性与模块化能力。
二、设计与分析
以下分别对三次作业的源码进行结构分析,并结合类设计、算法选择与实现思路进行说明。
2.1作业1:基础逻辑门模拟
作业1采用面向过程 + 哈希表的方式实现。核心数据结构包括:
Map<String, Integer> pinValue:记录每个引脚当前的电平值。
Map<String, List
Map<String, Gate> gateInfo:存储每个逻辑门的类型与输入引脚数量。
TreeMap<Integer, String>:按编号排序存储各类门实例,便于最终按序输出。
计算顺序通过拓扑排序(Kahn算法)确定。程序先根据连接关系建立有向图:若门A的输出连接到门B的输入,则存在边 A → B。拓扑排序后,按顺序遍历每个门,检查其所有输入引脚是否已有有效值,若齐全则调用 calculate() 计算输出。
关键设计点:使用正则表达式 Pattern 解析元件名(如 A(2)1、X5),将引脚统一抽象为 元件名-引脚号 的字符串键,简化了连接关系的存储与查找。
2.2 作业2:组合电路元件扩展
作业2引入了面向对象 + 抽象类的设计。定义抽象类 Component,所有具体门电路(AndGate、OrGate、NotGate、XorGate、XnorGate、TriState、Decoder、Mux、Demux)均继承自该类,并实现 compute() 与 printOutput() 方法。
这种设计的优势在于:
可扩展性强:新增元件类型只需添加新的子类,无需修改主逻辑。
职责分离清晰:每个元件独立管理自己的引脚列表、计算逻辑与输出格式。
统一排序输出:通过 TYPE_ORDER 列表与编号比较器,一键实现题目要求的输出顺序。
此外,作业2的输入处理采用即时传播策略:读取连接信息时,若源引脚已有值,则直接写入目标引脚。最终再对所有组件调用 compute() 进行统一计算与输出。
2.3 作业3:子电路与异常检测
作业3是整个系列中复杂度最高的一次迭代。源码在作业2的 OO 基础上,进一步引入以下核心设计:
Circuit 类:抽象表示“电路”,区分主电路与子电路(sub 标志)。包含输入列表、输出集合、连接列表等。
Conn 类:封装一条连接信息的原始字符串与解析后的引脚列表,便于异常检测时回显原始输入。
Gate / GatePin 类:更精细地描述门电路属性,支持前缀命名(子电路嵌套时自动添加前缀)。
异常检测模块(validate())是作业3的亮点。它对每条连接信息进行多维度校验:
统计源/目标引脚数量,检测 more than one input 与 none input。
检测连接中是否没有输出目标(none output)。
检查输入输出顺序是否写反(input and output sequence error)。
使用 Map<String, String> driven 检测同一输入引脚是否被多个输出驱动(input signal conflict)。
子电路机制通过递归展开(build())实现。程序先解析所有子电路定义,在主电路构建时,遇到子电路引用即递归展开其内部连接,并为所有门实例添加前缀(如 C2-N1),从而避免命名冲突。
模拟计算采用迭代松弛(Iterative Relaxation):循环传播信号值并计算门输出,直到没有新值产生为止。这种方式天然支持多级级联与部分反馈场景。
三、踩坑心得
在三次作业的编码与提交过程中,遇到并解决了若干典型问题,以下按类别进行总结。
3.1 引脚解析与命名歧义
作业1初期,正则表达式对元件名的匹配不够严谨,导致 A(2)1-1 与 A(2)1-10 被错误解析。修复方式是将 pinPattern 精确为 (A-Z?\d+)-(\d+),确保括号与编号的完整捕获。
3.2 拓扑排序的边界情况
作业1中,若电路存在未连接任何输出的孤立门,或输入信号未覆盖所有必要引脚,拓扑排序后这些门仍会出现在序列中,但计算时应被跳过。源码通过 if (!valid) continue; 实现:当某输入引脚无有效值时,直接忽略该门输出,这与题目“忽略该元件”的要求一致。
3.3 控制引脚与多输出格式
作业2的译码器(Decoder)与数据分配器(Demux)输出格式与基础门完全不同。译码器输出的是“哪一路输出为0”的索引,而分配器需输出一串包含 - 的状态字符串。初期曾混淆两者的引脚编号规则,后对照题目重新梳理:
译码器 M(3)1:控制引脚 0/1/2(S1/S2/S3),输入引脚 3/4/5(A0/A1/A2),输出引脚 613(Y0Y7)。
数据分配器 F(2)1:控制引脚 0/1(A/B),输入引脚 2(D),输出引脚 36(W0W3)。
3.4 子电路前缀与命名冲突
作业3中,子电路内部元件编号可能与主电路或其他子电路重复。例如子电路 C1 和 C2 都包含 N1。源码通过 prefix + base 的方式生成全局唯一名(如 C1-N1、C2-N1),并在 build() 中使用 built 集合防止重复展开,有效解决了命名冲突。
3.5 异常优先级与单条处理
题目要求:若一条连接信息同时触发多种异常,按固定优先级只输出最高优先级的一条;若多条连接都有异常,只处理最前面的一条。源码在 validate() 中按顺序执行检测,并在发现异常时立即 return,同时在主流程中第一个异常即终止后续模拟,完全符合题意。
四、改进建议
基于三次作业的实现经验,以下从代码结构、算法效率与可维护性角度提出改进建议。
4.1 引入真正的组合模式(Composite Pattern)
作业3虽然实现了子电路,但 Circuit 与 Gate 的层级关系仍显松散。可进一步抽象出统一的 Component 接口,让 LeafGate(基础门)与 CompositeCircuit(子电路)实现同一接口。这样主电路无需区分“原子元件”与“子电路”,统一调用 compute() 即可,架构更优雅。
4.2 使用事件驱动或观察者模式优化信号传播
当前作业3采用全局迭代松弛,每次循环遍历所有边和门,时间复杂度为 O(k * (E + V)),k 为迭代轮数。对于大规模电路,可引入事件队列:仅当某引脚值发生变化时,才将其下游门加入待计算队列。这种“事件驱动”方式可将平均复杂度降至 O(E + V)。
4.3 异常信息的结构化与国际化
当前异常信息以硬编码字符串形式返回,不利于后续维护或多语言扩展。建议定义枚举类 ErrorCode,将异常类型、优先级、模板消息分离,主逻辑只返回错误码,由专门的 ErrorFormatter 生成最终输出。
4.4 单元测试覆盖
三次作业均依赖标准输入输出进行黑盒测试,调试成本较高。建议为每个门电路、每种异常场景编写 JUnit 单元测试,尤其是边界条件(如输入不全、控制引脚无效、空连接等),可显著提升代码可信度与迭代效率。
4.5 配置化引脚定义
目前各元件的引脚编号规则(控制/输入/输出的顺序与数量)分散在代码各处。可提取为配置文件或注解,使元件定义与计算逻辑解耦,新增元件时只需修改配置而无需改动核心代码。
五、总结
通过“数字电路模拟程序”系列三次作业,从基础逻辑门到复杂组合电路,再到子电路与异常检测,逐步构建了一个功能较完整的数字电路模拟器。整个过程中,有以下几方面收获:
架构演进意识:从面向过程到面向对象,再到组合模式与递归展开,深刻体会到良好架构对需求迭代的支撑作用。
算法与数据结构的应用:拓扑排序、哈希表、迭代松弛等经典技术在实际问题中的灵活运用。
边界条件与鲁棒性:异常检测模块让我认识到,生产级代码不仅要“跑得通”,更要“防得住”。
模块化与可扩展性:通过抽象类与多态,实现了元件类型的“即插即用”,为后续增加时序电路(如D触发器、JK触发器)打下了基础。
需要进一步学习与研究的方向包括:时序电路的时钟同步机制、更高效的电路仿真算法(如事件驱动仿真)、以及形式化验证方法在数字电路中的应用。这些知识将帮助我在后续作业(如果有时序电路迭代)中更好地应对挑战。
六、类图
六、类图设计
为了更清晰地展现三次作业的代码架构演进,以下分别绘制了三个版本的类图,并对其设计理念进行解读。
6.1 作业1 类图
作业1采用面向过程编程,核心是一个主类 Main 配合内部静态类 Gate。所有数据(引脚值、连接关系、门信息等)以静态哈希表形式集中存放。拓扑排序通过 Kahn 算法实现,输出时按五个 TreeMap(A/O/N/X/Y)依次遍历。这种架构在元件种类少、逻辑简单的场景下足够使用,但扩展新元件类型时需修改多处代码。
作业1类图

6.2 作业2 类图
作业2全面转向面向对象设计,定义了抽象类 Component,九种具体元件各自独立成类。每种元件内部管理自己的引脚列表,输出格式也由各子类自行控制。主类 Main2 通过多态统一调用 compute() 和 printOutput(),新增元件类型时仅需添加新子类。这种设计显著提升了代码的可扩展性与可维护性,是面向对象编程优势的典型体现。
作业2类图

图2:作业2 组合电路元件 - 类图
6.3 作业3 类图
作业3在作业2基础上,进一步引入 Circuit、Conn、Gate、GatePin 四个核心类。Circuit 统一描述主电路与子电路,支持递归展开。Conn 封装原始连接字符串以支持异常回显。Gate 和 GatePin 精细化管理门实例与引脚解析。主类 Main3 通过 validate() 进行五类异常检测,build() 递归构建全局连接图,simulate() 采用迭代松弛算法传播信号。整体架构已具备组合模式的雏形,为时序电路和更复杂场景的扩展奠定了基础。

作业3类图
图3:作业3 子电路与异常检测 - 类图
从三个类图的演变可以看出,代码架构随着需求复杂度增加而逐步演进:从扁平化的静态方法调用,到多态化的面向对象设计,再到支持递归嵌套和异常处理的复合架构。这一过程充分体现了软件架构的可演化性——好的架构不是一次设计出来的,而是在迭代中逐步完善的。

浙公网安备 33010602011771号