ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

从逻辑门到CPU:手搓一台基础计算机的完整搭建指南

从逻辑门到CPU:手搓一台基础计算机的完整搭建指南 这期内容不写业务代码也不调大模型而是回到计算机最底层如果手上只有与门、或门、非门、异或门这些基础逻辑门怎么把一台能执行指令的计算机搭出来答案是可以而且这正是“从0手搓CPU”系列前半段的关键转折点。前面几集大概率已经验证过单个门电路的输入输出关系到了第 11 集核心问题变成这些门电路怎样级联成运算器、寄存器、程序计数器和控制单元最后再用一条时钟线让整台机器自动跑程序。先说结论这不依赖什么高端显卡也没有夸张的显存需求。真正吃资源的不是电脑配置而是你把几十个模块连在一起之后的排错耐心。如果走仿真路线普通电脑启动 Logisim 或 Verilog 仿真器就能跑如果走实物路线面包板加 74LS 系列逻辑门芯片、跳线、按键、LED 和 5V 电源就是主要材料。文章后面会按“模块搭建 - 整体连线 - 功能测试 - 问题排查”的顺序把这台基础逻辑门计算机拆开讲清楚。1. 核心能力速览“从逻辑门手搓计算机”不是一个软件项目而是一套完整的数字系统设计路线。它要解决的核心问题是用最底层的布尔逻辑单元构造出一个能取指、译码、执行、写回的存储程序计算机。能力项说明主题用基础逻辑门搭建一台可以执行指令的计算机原型核心功能搭建加法器、寄存器、ALU、程序计数器、控制器、存储器接口推荐路线Logisim/Verilog 仿真先行再上 FPGA 或 74LS 分立芯片硬件门槛仿真普通电脑 Java 运行环境实物面包板 5V 电源 74LS/4000 系列芯片是否需要 GPU不需要纯数字逻辑/CPU 仿真即可启动方式图形化工程打开、命令行仿真、FPGA 综合烧写对外接口地址总线、数据总线、读写控制信号、时钟信号批量任务可连续执行一段指令序列也就是程序适合读者学计算机组成、CPU 设计、数字逻辑的开发者或学生这集真正值得关注的点不是某个复杂算法而是“原来一台计算机可以由这些不起眼的小门拼出来”。2. 适用场景与使用边界这个项目最适合的人是正在学计算机组成原理、数字逻辑、CPU 设计或者对“程序到底怎么在硬件里跑”感到好奇的开发者。它能帮你把抽象的总线、寄存器、指令周期变成一屏可以观察的电路图或一块可以点亮的实验板。它能解决的问题很具体一是搞懂组合逻辑和时序逻辑的区别比如加法器属于组合逻辑寄存器属于时序逻辑二是搞懂指令从内存取出来之后控制单元如何让 ALU 算数、让寄存器存数三是搞懂一个简单程序为什么能按照地址连续执行。也要说清楚不适合什么。如果你想做一颗能跑 Linux 的高性能处理器那这个路线太初级了它演示的是原理而不是工程极限。如果你想直接拿它做商业芯片设计那也不现实缺的是标准单元库、时序收敛、物理设计等完整流程。更合适的定位是教学原型和实验验证。使用边界同样要强调仿真是最安全的入门方式接错线直接改图就行。实物搭建时电源不能反接芯片不能插错方向LED 要串联限流电阻。如果参考别人的开源 CPU 工程或课程资料要注意署名和开源许可证要求不要直接把别人的文档改成自己的原创内容。3. 环境准备与前置条件开始动手之前先确定走哪条路线。三条路线各有各的门槛匹配难度和真实程度。3.1 软件仿真路线这是最低成本的起点。用 Logisim 或 Logisim-Evolution 可以在图形界面里拖放逻辑门、触发器、乘法器 IP也能直接接虚拟开关和 LED。它不要求装驱动只要电脑能运行 Java。启动 Logisim 的通用命令是这样# 通用示例实际 jar 文件名以你下载的版本为准 java -jar logisim-evolution.jar如果你更想贴近真实芯片设计流程可以用 iverilog GTKWave 做 Verilog 仿真。这个组合能写出模块化代码并直接查看波形比纯图形拖拽更接近工业流程。一个最小环境只要安装好 iverilog 和 GTKWave 就够。3.2 FPGA 开发板路线FPGA 路线需要一块入门级开发板比如常见教学板上会有几十个逻辑单元和多个扩展 IO 口。工具链通常是 Vivado 或 Quartus体积较大磁盘和内存要求较高。好处是你能直接看到综合后的 LUT/FF 占用也能检查最高时钟频率这比仿真更接近真实芯片设计。这条路线的核心不是“手搓 CMOS 晶体管”而是用硬件描述语言描述逻辑门级电路再让综合工具把代码映射成 FPGA 内部的查找表和触发器。3.3 分立逻辑芯片路线如果你想看到物理电路可以用 74LS 系列或 4000 系列 CMOS 芯片。典型组合包含与门芯片、或门芯片、非门芯片、异或门芯片、D 触发器芯片再加上面包板、跳线、按键、LED、电阻和 5V 稳压电源。需要提醒的是实物搭建不要一上来就焊电路。先用仿真把逻辑验证清楚再上板否则排错成本会非常高。接线之前看一遍芯片数据手册确认每个引脚的功能和供电要求。电源、地、信号线要尽量分离避免长线并行走线造成的信号干扰。4. 从逻辑门到 CPU核心模块搭建顺序只靠一堆松散的门电路是跑不了程序的。计算机能被搭出来是因为我们把门电路组合成几个固定功能模块再让这些模块通过总线协同工作。下面按实际搭建顺序拆解。4.1 第一步用逻辑门做一个加法器运算器的起点是加法器。半加器只有两个输入 A、B输出和 Sum 与进位 Carry逻辑表达式非常简洁Sum A XOR BCarry A AND B用 Verilog 描述就是module half_adder( input a, input b, output s, output c ); assign s a ^ b; assign c a b; endmodule半加器虽然简单但它体现了“逻辑门 - 算术单元”的第一层转换。全加器再补一个低位进位输入可以用两个半加器加一个或门搭出。从全加器开始才能继续扩展成 4 位、8 位、16 位加法器。这里最容易踩的坑是把半加器直接串成多位加法器时忘记把前一位的进位传给下一位。结果就是低位进位中断加法结果不稳定。检验方式很简单用两个 4 位二进制数相加手动算一遍进位链再看电路输出。4.2 第二步用 D 触发器搭出寄存器组合逻辑只能根据当前输入得到当前输出不能保存状态。计算机需要保存指令、保存计算结果所以必须有记忆单元。D 触发器是最基础的记忆单元它在时钟上升沿把输入 D 锁存到输出 Q。module dff( input clk, input rst_n, input d, output reg q ); always (posedge clk or negedge rst_n) begin if (!rst_n) q 1b0; else q d; end endmodule多个 D 触发器并排放在一起位宽就是 N 位的寄存器。寄存器在 CPU 中的意义很大ALU 算完的结果要暂时存放在寄存器里指令从内存取出来之后也要放在指令寄存器里。没有寄存器数据就只能在导线上一闪而过无法参与多步运算。4.3 第三步组合出 ALU 运算器ALU 是 CPU 的“执行单元”它把加法器、减法器、与或非门和选择逻辑封装在一起通过操作码选择当前输出哪一种运算结果。一个最简单的 4 位 ALU 可以这样描述module alu4( input [3:0] a, input [3:0] b, input [1:0] op, output reg [3:0] y, output z ); always (*) begin case (op) 2b00: y a b; 2b01: y a b; 2b10: y a | b; 2b11: y a ^ b; endcase end assign z (y 4b0000); endmodule这个 ALU 的核心思路是把多种运算电路并行搭好然后用多路选择器挑出最终结果。在 Logisim 里可以分别画出加法器、与门、或门、异或门再用选择器接一个 op 输入信号。你拨动 op 开关输出就会跟着变化。判断 ALU 是否成功的标准很直接遍历 op 和输入组合对照真值表检查输出。A0101、B0011 时加法结果是 1000与运算是 0001或运算是 0111异或运算是 0110。任何一位不对都说明对应的子模块有问题。4.4 第四步搭出程序计数器 PC程序计数器负责告诉 CPU“下一条指令在哪个地址”。它本质上是一个带自增功能的寄存器每个时钟周期默认加 1当遇到跳转指令时从数据总线加载一个跳转目标地址。module pc #( parameter WIDTH 4 )( input clk, input rst_n, input load, input [WIDTH-1:0] load_value, output reg [WIDTH-1:0] pc ); always (posedge clk or negedge rst_n) begin if (!rst_n) pc {WIDTH{1b0}}; else if (load) pc load_value; else pc pc 1b1; end endmodule从逻辑门视角看PC 就是把多个 D 触发器和加法器组合起来。这个模块很容易犯的错是“计数时机不对”如果时钟边沿选择错误PC 可能在指令还没执行完时就跳到下一行。解决方法是先固定一套时序约定比如“上升沿更新 PC后续状态在下降沿稳定”这样方便统一测试。4.5 第五步控制器把指令变成控制信号控制器是整个 CPU 的“大脑”。它根据指令寄存器里的操作码生成各类使能信号比如寄存器写使能、ALU 操作码、内存读写信号、PC 加载信号。最小实现方式是一个有限状态机状态 T0取指令把内存数据写入指令寄存器状态 T1译码根据操作码确定执行动作状态 T2执行让 ALU 或存储器完成操作更新 PC这一部分特别能解释“计算机组成原理”里的一个核心问题软件指令是如何被硬件理解的。指令本身只是一组二进制位控制单元把它映射成高低电平再通过数据通路让对应模块动作。4.6 第六步用总线把模块连成计算机每一个模块单独看都不算复杂复杂的是如何连起来。一个最小的教学计算机通常有这几条关键链路地址总线PC 输出到内存地址输入数据总线内存数据到指令寄存器或寄存器到 ALU 输入控制信号总线控制器输出到各模块使能端时钟信号统一驱动所有时序逻辑把模块连起来之后整机执行一条指令的过程就是CPU 把 PC 指向的地址送到内存内存返回指令控制器译码后打开对应的数据通路让数据从某处传到某处然后在时钟边沿写入寄存器。所谓“只用基础逻辑门搭出计算机”本质就是把这一整套数据通路用逻辑门逐步具象化。5. 功能测试与效果验证模块没验证就整机连线是最容易翻车的操作。下面给出一套从底向上的测试流程。5.1 基础门与半加器测试测试目的确认半加器输出符合真值表。输入 A0、B0预期 Sum 为 0Carry 为 0输入 A1、B0预期 Sum 为 1Carry 为 0输入 A1、B1预期 Sum 为 0Carry 为 1。如果使用 Logisim直接用开关接输入用 LED 看输出就可以。如果使用 Verilog可以写一个简单 testbenchtimescale 1ns/1ps module tb_half_adder; reg a, b; wire s, c; half_adder uut( .a(a), .b(b), .s(s), .c(c) ); initial begin a 0; b 0; #10; a 0; b 1; #10; a 1; b 0; #10; a 1; b 1; #10; $finish; end endmodule判断标准波形里 Sum 和 Carry 与真值表一致。失败时先查输入输出引脚是否接反再查 XOR 门或 AND 门接法。5.2 寄存器读写测试测试目的验证 D 触发器能否在时钟边沿保存数据。流程是先复位Q 输出应为 0。然后设置 D1等待一个时钟上升沿Q 应变为 1。接着把 D 改为 0在下一个时钟沿之前Q 应保持 1。如果 Q 不保持可能是触发器的时序级联有问题或者时钟信号没有接到正确引脚。寄存器测试通过后再扩展成多位寄存器并测试写使能信号。写使能为 0 时无论数据总线怎么变化寄存器内容都不变写使能为 1 时数据在一个时钟沿后写入。这是 CPU 数据通路里最重要的控制行为之一。5.3 ALU 运算测试测试目的确认不同操作码对应不同运算结果。以 4 位 ALU 为例取 A0101、B0011。op00 时输出 1000op01 时输出 0001op10 时输出 0111op11 时输出 0110。零标志 Z 只在输出为 0000 时为 1。如果发现只有加法正确其他运算错误多半是某个逻辑门输入端接错如果所有运算都错先检查 op 信号是否被选择器正确解码。建议把所有运算结果同时引到 LED再用一个手拨开关选择显示这样能更快定位。5.4 整机跑一段最小程序整机测试的最终目标是让 CPU 执行一段有意义的指令序列。最简单的程序是把 1 放到寄存器 R0把 2 放到寄存器 R1执行加法把结果写到输出端口。预期结果输出端口显示 3。这段程序的伪汇编长这样LDA #1 ADD #2 STA 0xFF HLT不同指令集编码方式不同但执行流程是一致的CPU 从内存地址 0 开始取第一条指令PC 自动加 1再取下一条直到遇到 HLT 停止。判断整机是否成功的标准有三个指令能按地址顺序取出、控制信号能正确打开数据通路、最终输出值符合预期。如果整机跑不出预期最有效的排查方式不是盯着整张电路图看而是沿着一条指令走PC 地址对不对内存返回的指令对不对控制信号有没有拉到正确电平ALU 输入数据从哪来结果有没有被写进目标寄存器。6. 总线接口与批量指令执行从外部看这台 CPU 的“接口”就是总线。它没有 Web API 那种 JSON 请求但有一套更底层的约定地址总线、数据总线、读写控制信号、时钟信号。信号名称方向作用clk输入同步所有时序逻辑rst_n输入复位程序计数器和寄存器addr[7:0]输出选择要访问的内存地址data[7:0]双向传送指令或数据rw输出1 表示读0 表示写valid输出当前总线数据是否有效接口测试的思路是让 CPU 运行一条内存写指令然后观察写周期里 addr、data、rw 信号的时序关系。写入完成后再运行一条读内存指令检查 data 总线是否把相同数据读回来。批量任务在这里就是“程序连续运行”。控制器里的状态机本质上是一个批量调度器每个时钟周期推进一个状态按顺序执行取指、译码、执行、写回。你可以把多段测试指令放进内存连续执行PC 0 while running: IR MEM[PC] PC PC 1 EXECUTE(IR)如果在整机测试中某条指令卡住先看 PC 是否正常递增如果 PC 卡住再查时钟和复位信号如果 PC 正常但结果错再查控制单元在对应操作码下是否输出了正确的使能信号。7. 资源占用与性能观察这个项目不烧显存但资源占用依然值得关注只是观察方式变了。如果是 Logisim 仿真资源占用体现在元件数量和仿真引擎负担。几百个门的小 CPU 一般没问题但如果加入大量闪烁动画、自动旋转元件或超大内存阵列画面刷新会明显变慢。更合理的做法是仿真阶段只保留必要的数据显示模块减少刷屏频率。如果是 FPGA 路线资源占用看综合报告里的 LUT、FF、IO 和时钟频率。基础教学 CPU 通常不会把开发板资源吃满但综合时序可能成为瓶颈。关键路径通常出现在多级组合逻辑链上例如行波进位加法器的进位链从最低位传到最高位需要经过很多级门电路延迟长了之后时钟频率就必须降低。如果是 74LS 分立芯片路线资源占用体现在芯片数量、面包板面积和电源电流。实际电流以芯片数据手册为准不要靠猜。电源电压不稳、地线连接太分散都可能导致逻辑电平抖动。观察信号时最好用逻辑分析仪或示波器看时钟和关键控制信号而不是靠肉眼盯 LED。降低逻辑延迟的通用思路有三个缩短组合逻辑链、插入流水线寄存器、优化关键路径。但对于第一版手搓 CPU不要一上来就优化。先把功能跑通再观察哪里是瓶颈否则会因为引入流水线复杂度而更难排错。8. 常见问题与排查方法问题现象可能原因排查方式解决方案Logisim 启动失败未安装 Java或 jar 文件损坏命令行运行java -version检查 jar 路径安装对应 JDK/JRE重新下载 jar仿真波形不变化没有时钟信号或时钟频率为 0检查时钟源和触发边沿设置添加周期时钟设置合理频率加法结果少一位进位多位加法器漏接进位链逐级检查全加器 Cin/Cout补上前一级进位到后一级输入寄存器数据不保持D 触发器输入或时钟接错用仿真波形观察时钟上升沿位置确认 Q 只在时钟边沿更新ALU 运算结果混合操作码解码错误拨动 op 信号观察选择器输出修正操作码到子模块的映射关系整机执行乱跳PC 递增时机不对观察 PC 跳变波形统一时序约定调整状态机LED 亮度异常电流过大或驱动能力不足检查限流电阻和芯片输出规格串联合适的限流电阻FPGA 综合报错模块端口没连接或命名不一致读综合日志的定位提示按报错位置逐层检查模块连接整机排错时最忌讳一上来就怀疑所有模块。正确顺序是先保证电源和时钟存在再验证复位信号再检查 PC 是否能自增然后检查内存读出指令最后检查执行结果。每测一步就记录一次问题范围会迅速缩小。9. 最佳实践与使用建议第一次做这种项目一定要遵守“先小后大”的原则。先把半加器、全加器、D 触发器这些基础模块测试到无死角再往上搭 ALU 和寄存器堆。整机功能越复杂模块测试的收益越大。工程管理也很重要。仿真工程按版本编号保存不要一个文件改到底。Verilog 代码可以分目录管理把rtl、sim、doc分开。模块命名尽量统一比如alu4、reg_file、pc、controller不要出现module1、module2这种含义不明的名字。信号命名规范能帮你省很多排错时间。时钟统一叫clk低有效复位统一叫rst_n地址总线叫addr_bus数据总线叫data_bus控制信号用enable加模块名前缀。这样即使过两周再回来看电路也能快速定位。合规方面也需要注意如果你参考了开源 CPU 工程、FPGA 教程或课程讲义要在文档里保留来源信息和许可证声明。用真实芯片搭建实验时注意用电安全不要用超过芯片额定电压的电源。涉及对外发布的内容要确保电路设计、代码和文档都是你有权分享的版本。10. 总结与下一步这一集真正值得动手验证的核心是“加法器 - 寄存器 - ALU - PC - 控制器 - 总线”这条完整的链路。最先建议做的是半加器和 D 触发器这两个模块分别代表组合逻辑和时序逻辑它们能跑通后面的 CPU 主链路才有基础。最容易踩的坑有两个一是跳过模块测试直接连整机出了问题根本不知道从哪查二是不统一时钟时序导致 PC、寄存器、控制器的更新时机互相冲突。如果文章里的测试流程都走一遍再把一条指令从取指到执行完整走通你就算真正理解了基础逻辑门如何组成计算机。下一步可以做的事情不少给指令集写一个汇编器把LDA、ADD、STA这样的助记符翻译成二进制机器码给 CPU 增加跳转指令和子程序调用再往后还可以尝试把数据通路改成多周期或者加入简单流水线。到这一步“从逻辑门到计算机”的最后一层窗纸就算捅破了。
RELATED READING

延伸阅读

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