学期末尤其繁忙,不是考试就是小组作业,终于有时间写出这篇文章。话不多说,让我们开始吧
在介绍 LC-3 处理器前,我们需要先回顾指令集体系结构(Instruction Set Architecture, ISA),其作为计算机系统中软硬件交互的核心界面,定义了处理器可执行指令的集合、寄存器组织结构、内存访问方式以及中断处理机制等基础硬件特性。
LC3 简介
LC-3 有 16 条指令,每条指令有 16 个字符,也就意味着它有个地址空间,它还有 8 个寄存器(Register)。每个寄存器以 3 位二进制字符进行编号。
以下是这 16 条指令,以 15 到 12 位不同的操作符进行区分,此外操作符 1101 的指令并没有使用,所以其实只有 15 条指令
操作指令
LC-3 有三种操作指令:ADD, AND, NOT
ADD 和 AND 需要两个操作数,NOT 只需要一个源操作数。下面这条操作指令将 R5 寄存器中的值进行 NOT 运算,然后存入 R3 寄存器。第 5 到第 0 位是未使用的位,以 1 进行填充。
DR 代表目标寄存器(Destination Register),是将操作结果存储到该寄存器中,SR 代表源寄存器(Source Register)
ADD 指令
ADD 指令的操作码为 0001,它将两个操作数相加,结果存入目标寄存器 DR。ADD 支持两种寻址模式,由第 5 位决定:
- 寄存器模式(bit[5] = 0):两个源操作数均来自寄存器。bit[2] 指定 SR2,bit[8] 指定 SR1,bit[4] 固定填充
00。 - 立即数模式(bit[5] = 1):第二个操作数为 5 位有符号立即数(imm5),需要先进行符号扩展至 16 位后再与 SR1 相加。
寄存器模式: 0001 DR SR1 0 00 SR2
立即数模式: 0001 DR SR1 1 imm5
举个例子:
0001 011 010 1 00101表示 R2 = R2 + 5。将 R2(SR1,也是 DR)与符号扩展后的立即数 5 相加,结果存回 R2。
ADD 指令会根据计算结果自动更新条件码(Condition Code, CC):结果为正则 N=0 Z=0 P=1,结果为负则 N=1 Z=0 P=0,结果为零则 N=0 Z=1 P=0。
AND 指令
AND 指令的操作码为 0101,与 ADD 结构完全一致,同样支持两种寻址模式,区别仅在于执行按位与(bitwise AND)而非加法:
寄存器模式: 0101 DR SR1 0 00 SR2
立即数模式: 0101 DR SR1 1 imm5
同样会根据结果更新条件码。立即数模式常用于位掩码操作,例如 0101 011 010 1 00011 表示 R2 = R2 AND 3,可用来提取 R2 的低 2 位。
NOT 指令
NOT 指令的操作码为 1001,它是唯一一条只有一个源操作数的操作指令。对 SR 的值按位取反后存入 DR,低 6 位以 1 填充:
NOT: 1001 DR SR 111111
举个例子:
1001 011 101 111111表示 R3 = NOT(R5),对 R5 按位取反后存入 R3。
NOT 同样会根据结果更新条件码。利用 NOT 和 ADD 组合,可以实现减法操作:先对减数取反,再加 1(二进制补码),然后与被减数相加。
数据搬移指令
数据搬移指令负责在寄存器和内存之间传输数据,是 LC-3 中指令数量最多的一类,共 7 条。根据寻址方式的不同,可分为三类:PC 相对寻址(LD、ST、LEA、LDI、STI)、基址偏移寻址(LDR、STR)和立即数加载(LEA 严格来说加载的是地址而非内存数据)。
LD 指令(Load)
操作码 0010,将内存中某个地址的值加载到目标寄存器 DR。地址计算方式:将 9 位有符号偏移量(PCoffset9)符号扩展至 16 位,然后与递增后的 PC 相加得到目标地址。
LD: 0010 DR PCoffset9
举个例子:
0010 011 000000101表示 R3 = M[PC + 5],将 PC+5 地址处的值加载到 R3。
ST 指令(Store)
操作码 0011,将源寄存器 SR 的值存储到内存中。地址计算方式与 LD 完全相同——将 PCoffset9 符号扩展后与递增后的 PC 相加。
ST: 0011 SR PCoffset9
注意:LD 和 ST 的寻址范围是 PC ± 256,这是因为 9 位有符号偏移量的范围是 -256 到 +255。
LDR 指令(Load Register)
操作码 0110,使用基址加偏移的方式计算地址:将 6 位有符号偏移量(offset6)符号扩展至 16 位,与基址寄存器 BaseR 的值相加。
LDR: 0110 DR BaseR offset6
举个例子:
0110 011 010 000110表示 R3 = M[R2 + 6],以 R2 为基址、偏移 6 来访问内存。
对比 LD:LD 以 PC 为基址(PC 相对寻址),适合访问全局变量和静态数据;LDR 以任意寄存器为基址,灵活度更高,适合访问数组元素和结构体成员。
STR 指令(Store Register)
操作码 0111,与 LDR 结构一致,区别在于将 SR 的值写入内存而非读取:
STR: 0111 SR BaseR offset6
LDI 指令(Load Indirect)
操作码 1010,间接寻址:先以 PC 相对寻址方式找到一个内存地址,再以该地址存储的值为最终地址,去读取数据加载到 DR。用 C 语言的指针概念来理解,就是双重解引用。
LDI: 1010 DR PCoffset9
举个例子:
1010 011 000001010,先计算 PC+10 处地址 A,再读取 M[A] 的值为地址 B,最终 R3 = M[B]。
STI 指令(Store Indirect)
操作码 1011,与 LDI 结构一致,区别在于将 SR 的值存储到通过间接寻址得到的目标地址中:
STI: 1011 SR PCoffset9
LEA 指令(Load Effective Address)
操作码 1110,这条指令不访问内存,而是直接将计算出的地址(而非地址中的内容)加载到 DR。地址计算方式与 LD 相同:PCoffset9 符号扩展后与递增后的 PC 相加。
LEA: 1110 DR PCoffset9
举个例子:
1110 011 000000101表示 R3 = PC + 5。注意:R3 得到的是地址值 PC+5,而非 M[PC+5] 的内容。
LEA 常用于获取某个数据区域或字符串的起始地址,然后配合 LDR/STR 遍历访问。
| 指令 | 操作码 | 格式 | 寻址方式 |
|---|---|---|---|
| LD | 0010 | 0010 DR PCoffset9 | PC 相对 |
| ST | 0011 | 0011 SR PCoffset9 | PC 相对 |
| LDR | 0110 | 0110 DR BaseR offset6 | 基址偏移 |
| STR | 0111 | 0111 SR BaseR offset6 | 基址偏移 |
| LDI | 1010 | 1010 DR PCoffset9 | 间接 |
| STI | 1011 | 1011 SR PCoffset9 | 间接 |
| LEA | 1110 | 1110 DR PCoffset9 | 立即数(地址) |
控制指令
控制指令负责改变程序的执行流程,包括条件分支、无条件跳转、子程序调用和系统陷阱等。
BR 指令(Branch)
操作码 0000,根据条件码 N、Z、P 的状态决定是否跳转。bit[11
BR: 0000 n z p PCoffset9
| n z p | 助记符 | 跳转条件 |
|---|---|---|
| 0 0 0 | — | 永不跳转(无操作) |
| 1 1 1 | BR | 无条件跳转 |
| 1 0 0 | BRn | N=1 时跳转(结果为负) |
| 0 1 0 | BRz | Z=1 时跳转(结果为零) |
| 0 0 1 | BRp | P=1 时跳转(结果为正) |
| 1 1 0 | BRnz | N=1 或 Z=1 时跳转 |
| 0 1 1 | BRzp | Z=1 或 P=1 时跳转 |
| 1 0 1 | BRnp | N=1 或 P=1 时跳转 |
若跳转发生,目标地址 = PC + sext(PCoffset9);若跳转不发生,程序顺序执行下一条指令。汇编器会将条件组合自动翻译为对应的助记符。
JMP 指令(Jump)
操作码 1100,无条件跳转到基址寄存器 BaseR 中存储的地址。bit[8
0 填充。
JMP: 1100 000 BaseR 000000
举个例子:
1100 000 010 000000表示 PC = R2,程序跳转到 R2 中存储的地址继续执行。
对比 BR:BR 的跳转目标是相对于 PC 的偏移且受条件码约束,而 JMP 的跳转目标来自寄存器,可以跳转到任意地址,更加灵活。
JSR 与 JSRR 指令(Jump to Subroutine)
操作码 0100,用于子程序调用。在执行跳转前,JSR/JSRR 会自动将返回地址(即当前 PC 的值,指向下一条指令)保存到 R7 寄存器中,以便子程序执行完毕后通过 RET 返回。
JSR 和 JSRR 由同一个操作码 0100 实现,通过 bit[11] 区分:
- JSR(bit[11] = 1):使用 11 位 PC 相对偏移量,跳转范围为 PC ± 1024。
- JSRR(bit[11] = 0):使用基址寄存器 BaseR 指定目标地址,bit[8] 指定寄存器,其余位填充
0。
JSR: 0100 1 PCoffset11
JSRR: 0100 0 00 BaseR 000000
举个例子:
0100 1 00000001001先将当前 PC 保存到 R7,再跳转到 PC + 9。
注意:JSRR 的基址寄存器同样可以是 R7,但由于返回地址已经被写入 R7,若再以 R7 作为 BaseR 去跳转,会导致程序跳转回返回地址处(即自己),这种用法并不常见。
RET 指令(Return)
操作码 1100。实际上,RET 是 JMP R7 的别名——将 R7(保存了返回地址)的值加载到 PC,从而从子程序返回。
RET: 1100 000 111 000000
为什么是 R7?因为 JSR/JSRR 在跳转前已经把返回地址写入了 R7,所以 RET 只需一句 JMP R7 就能回到调用点。在 LC-3 汇编器中,RET 和 JMP R7 是完全等价的。
RTI 指令(Return from Interrupt)
操作码 1000,用于从中断服务程序返回。RTI 的行为较为特殊——它不会修改 PC 的值;当中断被触发时,处理器会将当前 PC 值压入系统栈,RTI 只是向处理器发出"中断处理完毕"的信号,由硬件恢复现场。
RTI: 1000 000000000000
在 LC-3 教学模型中,RTI 的具体行为取决于模拟器的实现,很多情况下它被简化处理。如果你用的是 LC-3 模拟器,建议查阅其文档确认 RTI 的精确行为。
TRAP 指令
操作码 1111,高 8 位为 11110000,低 8 位为 trapvect8,用于调用操作系统的服务例程。TRAP 与 JSR 类似:先将 PC 保存到 R7,然后跳转到由 trapvect8 指定的服务例程地址。
TRAP: 1111 0000 trapvect8
常见的 TRAP 服务例程(LC-3 操作系统内置):
| trapvect8 | 助记符 | 功能 |
|---|---|---|
x20 | GETC | 从键盘读取一个字符(不回显),存入 R0[7] |
x21 | OUT | 输出 R0[7] 中的字符到屏幕 |
x22 | PUTS | 输出以 R0 为起始地址的字符串(以 NUL 结尾) |
x23 | IN | 提示输入一个字符,回显后存入 R0[7] |
x24 | PUTSP | 输出以 R0 为起始地址的打包字符串 |
x25 | HALT | 停止程序执行,打印停机信息 |
举个例子:
] 中的字符输出到显示器上。1111 0000 00100001(即 TRAP x21),将 R0[7
TRAP 是用户程序与 LC-3 操作系统之间的桥梁。你的程序不需要知道 OUT 服务例程具体放在内存的哪个位置,只需要通过 TRAP 指令加上对应的 trapvect8 即可调用。
| 指令 | 操作码 | 格式 | 说明 |
|---|---|---|---|
| BR | 0000 | 0000 n z p PCoffset9 | 条件分支 |
| JMP | 1100 | 1100 000 BaseR 000000 | 无条件跳转 |
| JSR | 0100 | 0100 1 PCoffset11 | 子程序调用(PC 相对) |
| JSRR | 0100 | 0100 0 00 BaseR 000000 | 子程序调用(寄存器) |
| RET | 1100 | 1100 000 111 000000 | 从子程序返回(JMP R7) |
| RTI | 1000 | 1000 000000000000 | 从中断返回 |
| TRAP | 1111 | 1111 0000 trapvect8 | 系统服务调用 |
总结
LC-3 总共定义了 16 条指令(操作码 1101 保留未使用,实际使用 15 条),按照功能可以分成三大类:
- 操作指令(ADD、AND、NOT):执行算术和逻辑运算,结果存入寄存器,同时更新条件码。
- 数据搬移指令(LD、ST、LDR、STR、LDI、STI、LEA):在寄存器和内存之间搬运数据,不同指令对应不同的寻址方式——PC 相对、基址偏移或间接寻址。
- 控制指令(BR、JMP、JSR/JSRR、RET、RTI、TRAP):控制程序执行流程,实现条件分支、跳转、子程序调用和系统交互。
理解这些指令的二进制格式,是读懂 LC-3 机器码、手写汇编程序以及后续学习微架构设计和LC-3 数据通路的基础。建议对照指令格式表,多在 LC-3 模拟器上动手编写和调试几段程序,熟悉后你会发现这 16 条指令虽然简单,但已经能完成相当多有趣的事情了。
喜欢的话,留下你的评论吧~