11-从零搭建计算机(下)-控制与指令.md 14 KB


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 立即数)。这就是"操作码 + 操作数"机指格式,也是所有汇编的雏形。

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 编码为 0b00101?(提示 opcode=00010,操作数 0) > 答:操作码 00010 + 11 位操作数 00000000000b0001000000000000(具体位宽以课件烧录格式为准,重点是"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_a0b00001 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 等一排控制信号。

控制器本质上是一张 真值表/查表器——这是 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 的结果。

6.7 总线与地址映射

把 CPU 的 A/str/ld/Dout/Din 五根线引出来接到外部设备 → 这五根线就是总线。问题:多个外部设备,Din 数据源冲突(会短路)→ 用 lden_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 的思想原型(课件用它解释了"开机如何把硬盘里的程序送进内存")。

6.9 常用数字芯片(动手采购清单)

芯片 功能 用途
与/或/非门(SN74LVC1G08 等单门) 基本逻辑 组合电路(本课 03)
74HC245 8 位双向总线缓冲器/驱动器 总线增强驱动、隔离(DIP-20)
74HC138 3-8 译码器 地址译码产生片选(DIP-16,见 04)
DS1302 实时时钟 RTC 掉电走时(需电池),I2C/单线协议

买芯片三件套:看原理图、看封装(决定 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 章(常用芯片);关联 [[冯诺依曼体系结构]] 维基页