--- tags: [数电] type: note source_type: reconstructed created: 2026-08-18 updated: 2026-08-18 aliases: [控制器, 指令集, 汇编语言, 总线地址映射, 引导程序, iRom, 程序计数器, 立即数] --- # 从零搭建计算机(下):控制与指令 > [!note] 对象层 > 学完这篇,你能**判断**: > > 1. PC 计数器、指令寄存器、控制器各管什么、怎么协作; > 2. 一条指令 = 操作码(opcode) + 操作数(addr/立即数),并读懂完整指令集表; > 3. 冯诺依曼单内存如何在两个时钟周期内"取指→执行"; > 4. 总线有哪些线、CPU 如何用 11 位地址"地址映射"区分内存/磁盘/终端; > 5. 引导程序(iRom → 硬盘 → 内存)为什么需要多级加载。 > > 本篇是 [[10-从零搭建计算机(上)-组成与算力|数电 10(上)]] 的续篇,建议先读上篇。 > **用生活理解**:控制器是"发号施令的调度员"(用查找表把 opcode 翻译成一排开关信号);PC 计数器是"翻页的书签"(每页读完自动 +1,jmp 直接翻到指定页);指令寄存器是"当前这一页的内容";总线是"邮箱系统"(地址=写给谁,数据=内容,ld/str=收信还是寄信)。引导程序则是"开机时快递员":CPU 出厂只认 iRom 这张小纸条,小纸条先去硬盘取一段"小队",小队再把主程序全部搬进内存开工。 --- ## 一、一句话本质 **一台可编程计算机 = PC(决定取哪条指令)+ 指令寄存器(暂存指令)+ 控制器(把指令翻译成控制信号)+ ALU/寄存器(算)+ 一块共享内存(冯诺依曼单内存,指令=opcode+操作数)+ 总线(互连 I/O);启动靠多级引导(iRom→硬盘引导区→内存→主程序)。** --- ## 二、速查表(公式与结论) ### 2.1 指令集表(本机自定义,含立即数版本) | opname | opcode | 操作 | | ------ | ------ | -------------------------------- | | halt | 00000 | 停止时钟 | | ld_a | 00001 | 数据 RAM 的内容 → 寄存器 A | | add | 00010 | 数据 RAM 内容 + A → A | | sub | 00011 | A − 数据 RAM 内容 → A | | or | 00100 | A \| 数据 RAM → A | | and | 00101 | A & 数据 RAM → A | | str | 00110 | A → 数据 RAM 指定地址 | | jmp | 00111 | 无条件跳转指定地址 | | je | 01000 | **相等时**跳转(依赖比较寄存器) | | ld_ia | 01001 | **立即数** → A | | add_ia | 01010 | 立即数 + A → A | | sub_ia | 01011 | A − 立即数 → A | | or_ia | 01100 | A \| 立即数 → A | | and_ia | 01101 | A & 立即数 → A | > **操作数(addr/立即数)两种含义**:`ld_a 7` 里的 7 可能是"数据 RAM 地址",也可能是"立即数 7"——用两套指令区分(末尾带 `_ia` = immediate 立即数)。这就是"操作码 + 操作数"机指格式,也是所有汇编的雏形。 ![指令控制位对照表(各指令在各控制位上的取值)](assets/instruction_control_table.png) ### 2.2 单内存"两阶段"执行(冯诺依曼关键) | 阶段 | state | RAM 地址取什么 | 做什么 | | -------- | ----- | -------------- | ------------------------ | | 取指令 | 0 | 指令地址 | 把指令读入**指令寄存器** | | 执行指令 | 1 | 数据地址 | 读数据、按指令运算/跳转 | > state 只在 0/1 之间切换 → 用**一个 D 触发器**实现;一条指令 = 两个时钟周期。 ### 2.3 总线定义(5 根线) | 线 | 类型 | 作用 | | ---------- | -------- | ---------------- | | A | 地址总线 | 传输寻址地址 | | ld / str | 控制总线 | 表明"读"还是"写" | | Din / Dout | 数据总线 | 传输数据 | ### 2.4 总线地址映射(高 3 位 = 设备号,低 8 位 = 设备内地址) | CPU 寻址范围 | 设备 | | ------------------------------- | ----------------------------------------------- | | 0b000 00000000 ~ 0b000 11111111 | **iRom**(CPU 内置只读区,0~255) | | 0b001 00000000 ~ 0b001 11111111 | **内存**(外部扩展 RAM,256~511) | | 0b010 00000000 ~ 0b010 11111111 | **磁盘**(外部扩展 ROM,可持久化,512~767) | | 0b011 00000000 | **终端**(向此地址写低 8bit ASCII 即显示,768) | ### 2.5 引导流程(多级) ``` CPU 上电 → 执行 iRom(只读、不可改) → iRom 引导:把硬盘前 32 条(引导区)拷贝到内存头 32 条 → 跳转内存执行"引导程序" → 引导程序把硬盘自定义程序全部拷贝到内存 → 跳转主程序执行 ``` --- ## 三、判别规则 1. **区分数据/指令/控制三要素**:内存里取的是"数"还是"控制信号"?→ 数据 RAM vs 指令 RAM;`sel` 控制信号就是指令本身(上篇 2.3)。 2. **操作数语义**:带 `_ia` 后缀 = 立即数参与运算;否则按"数据 RAM 地址"处理。 3. **跳转 vs 条件跳转**:jmp 无条件;je 依赖比较寄存器(`cmp_b x` 先比较,`jne`/`je` 再跳)。 4. **选存储介质**:上电要执行的出厂代码 → iRom;掉电保留 → EEPROM/磁盘;上电后由程序改写 → RAM。 5. **找 Din 冲突**:多个数据源灌进同一条 Din 会短路 → 靠 `ld`/`en_i` 控制信号在电路层面"让路"。 --- ## 四、易混对比 | 易混点 A | 易混点 B | 关键区别 | | ------------------ | -------------- | ------------------------------------------------------------------ | | 数据 RAM | 指令 RAM | 存"要算的数" vs 存"ALU 控制信号";合并后靠"取指/执行两个阶段"区分 | | 操作数=地址 | 操作数=立即数 | `ld_a 7`:7 可能是地址也可能是数;立即数指令多 `_ia` 后缀 | | 冯诺依曼(单内存) | 哈佛(双内存) | 单内存一条数据线=指令+数据二选一(需两阶段);双内存可并行取指取数 | | RAM | EEPROM | RAM 掉电丢、仅程序写;EEPROM 可预编辑/持久化(课件用它当硬盘) | | jmp | je | 无条件跳转;比较寄存器相等才跳(配合 cmp_b) | | 引导区(32 条) | 主程序 | 引导区只负责"搬运+跳转"的短程序,不是最终业务代码 | --- ## 五、典型例题(先想再翻) 1. **读懂程序**:`0. ld_ia 0 / 1. ld_ib 1 / 2. jmp 5 / 3. add 0 / 4. add_ib 1 / 5. cmp_b 101 / 6. jne 3 / 7. save_a 16 / 8. halt` 做什么? > 答:0 到 100 求和——A 从 0 累加 B(=1 递增到 101),B≠101 则跳回第 3 条继续加,相等后把 A 存入内存 16 号地址并停止。 2. **二进制编码**:`add 0` 编码为 0b`00101`?(提示 opcode=00010,操作数 0) > 答:操作码 00010 + 11 位操作数 00000000000 → `0b0001000000000000`(具体位宽以课件烧录格式为准,重点是"opcode+操作数"拼成一条 16 位指令)。 3. **总线地址映射**:`save_a 0b011 00000000` 的效果? > 答:把寄存器 A 的低 8 位写到**终端**(0b011 前缀命中终端模块)→ 终端按 ASCII 显示一个字符。 --- ## 六、完整知识点 ### 6.1 PC 计数器 = 寄存器 + 加法器 给计算单元接一块 EEPROM 当数据存储后,需要一个"逐条取数"的地址产生器:**寄存器存地址 + 加法器恒 +1 + 时钟上升沿写回** → 输出 0,1,2,3…(这就是上篇 6.7 计算单元的延伸)。 **halt 信号**:换成真实震荡时钟后 PC 会 0,1,2,3,0,1,2,3… 反复执行 → 用比较器判断 PC==3,经与门**暂停时钟输出**终止程序。 ### 6.2 指令存储:sel 控制信号 = 指令 想要 `13+45-27-6`:加一块 EEPROM 存 ALU 的 sel 序列(00 加、00 加、01 减、01 减),地址也接 PC → **ALU 的 sel 控制信号 = 指令**,这块 EEPROM 就是"指令存储"。于是计算机有了两块 RAM:数据 RAM + 指令 RAM。 ### 6.3 合并为一块内存(冯诺依曼单内存) 双 RAM 不符合"一块内存编程"的直觉 → 合并成一块。但单内存一个时钟周期无法既传指令又传数据 → **把每条指令拆成两个时钟周期**: - 取指(state=0):RAM 输出指令 → 存入**指令寄存器**; - 执行(state=1):RAM 输出数据 → 按指令运算。 指令格式定型:**操作码(opcode) + 操作数(addr)**,存进 RAM 的一个单元(如 `ld_a` → `0b00001 0000000100`)。PC 扩成 11 位以匹配新寻址。 ### 6.4 控制器 = 查找表(LUT) 把每条指令的 opcode 对应到"每个开关该置 0 还是 1"——用 Digital 的**查找表(LUT)**直接实现:输入 5 位 opcode,输出 halt/ld/str/jmp/je/en_pc/en_a/sel_a/sel_b/sel_alu 等一排控制信号。 ![控制器查找表(opcode → 各控制信号)](assets/lut_controller.png) > 控制器本质上是一张 **真值表/查表器**——这是 FPGA 用 LUT 实现逻辑的底层原理,也和 [[07-半导体存储器|数电 07 用 ROM 实现组合逻辑]] 完全同源。 ### 6.5 立即数与 B 寄存器 - **立即数**:操作数不表示地址、而是直接参与运算的数(上篇 ALU 的 B 输入端由"数据 RAM 输出"或"立即数"二选一,靠 `en_i` 控制)——于是指令集扩出一整套 `_ia` 指令; - **B 寄存器**:只有一个寄存器 A = 只有一个变量;再加一块寄存器 B(含 `ld_ib`/`add_ib` 等指令)就能做多变量运算(x86 汇编里的 `r8~r15` 就是这套思想的放大版)。 ### 6.6 汇编语言与测试 没有编译器也能"翻译-烧录-运行": 1. 用类 C + Godbolt 感受 x86 汇编(`mov/add/cmp/jne/ret`); 2. 照样子写**自己的汇编**(`ld_ia/add_ib/jmp/cmp_b/jne/save_a/halt`…); 3. 手译成 16 位二进制(opcode+操作数拼接)→ 烧进 EEPROM; 4. 时钟调到 100~500Hz,运行后检查内存地址 16 的结果。 ![CPU 控制器与数据通路(寄存器组、ALU、RAM、指令寄存器、PC 联动)](assets/bus_memory_architecture.png) ### 6.7 总线与地址映射 把 CPU 的 A/str/ld/Dout/Din 五根线引出来接到外部设备 → 这五根线就是**总线**。问题:多个外部设备,Din 数据源冲突(会短路)→ 用 `ld` 和 `en_i` 控制信号决定"谁让路"。 **地址映射方案**:11 位地址 = 高 3 位设备编号 + 低 8 位设备内地址(见 2.4 表)。CPU 内部 RAM 电路限制只响应 `0b000` 前缀;内存模块响应 `0b001`(介质用 RAM,上电后由程序改写);磁盘模块响应 `0b010`(介质用 EEPROM 持久化);终端模块响应 `0b011`(en 有效时,时钟上升沿把低 8bit ASCII 显示出来)。**最终 CPU 通过总线连接内存、硬盘、终端三台外部设备**(图见 6.6)。 ### 6.8 引导程序 三块存储各司其职:iRom(CPU 内置只读)、内存(不可持久)、硬盘(持久)。上电先执行 iRom,而 iRom 代码是写死的 → 它的作用是"搬运": - iRom 负责把硬盘**前 32 条**拷贝到内存并跳转; - 硬盘前 32 条(**引导区**)负责加载自定义主程序并跳转; - 最终执行主程序。 > 为什么分两级?程序长度不定,iRom 一次只能搬固定长度 → 先搬一小段"引导程序",再由引导程序自由搬运,兼顾通用与效率。这就是真实 x86 的 MBR/GRUB 与单片机 bootloader 的思想原型(课件用它解释了"开机如何把硬盘里的程序送进内存")。 ![引导程序汇编(搬运+跳转)](assets/boot_program_asm.png) ### 6.9 常用数字芯片(动手采购清单) | 芯片 | 功能 | 用途 | | -------------------------------- | ----------------------------- | --------------------------------- | | 与/或/非门(SN74LVC1G08 等单门) | 基本逻辑 | 组合电路(本课 03) | | **74HC245** | 8 位双向**总线缓冲器/驱动器** | 总线增强驱动、隔离(DIP-20) | | **74HC138** | 3-8 **译码器** | 地址译码产生片选(DIP-16,见 04) | | **DS1302** | **实时时钟 RTC** | 掉电走时(需电池),I2C/单线协议 | ![74HC245 实物(DIP-20 双向总线缓冲器)](assets/ic_74hc245.png) ![74HC138 实物(DIP-16 3-8 译码器)](assets/ic_74hc138.png) > 买芯片三件套:看**原理图**、看**封装**(决定 PCB 焊盘)、查**数据手册**(决定引脚/参数/时序)。课件给每颗芯片附了立创商城购买链接与手册 PDF——动手项目选型的第一课。 --- ## 七、与 PCB 画图的关联 - **总线缓冲**:多路数据共享一根总线时加 74HC245 增强驱动/隔离(对应 [[03-逻辑门电路|数电 03]] 三态总线)。 - **地址译码**:存储/外设片选用 74HC138(对应 [[07-半导体存储器|数电 07]] 字扩展选片、[[04-组合逻辑电路|数电 04]] 译码器)。 - **RTC 电路**:DS1302 + 32.768kHz 晶振 + 后备电池是时钟板标配。 - 理解"地址映射"再看 STM32 的**存储器映射图**(0x40000000 外设区等)就不抽象了——片选、映射、总线一回事。 - 引导程序的"iRom 出厂固件"对应 MCU 的 **Boot ROM / System Memory**(ST 串口 ISP 就是它),上电启动选择器(BOOT0 引脚)即"从哪段执行"。 > 来源:尚硅谷《从零搭建计算机》第 3 章(内存/控制器/立即数/B寄存器/合并RAM)+ 第 4 章(汇编/总线/地址映射)+ 第 5 章(引导程序)+ 第 6 章(常用芯片);关联 [[冯诺依曼体系结构]] 维基页