深入实现分支与跳转指令:JMP、JZ/JNZ、JC/JNC、JN/JNN、CALL、RET。这些指令控制程序的执行流——从简单的条件判断到子程序调用,是结构化编程的基础。
条件分支的关键是正确判断标志位。CMP指令设置标志位,然后条件分支根据标志位决定是否跳转:
// CMP R1, R2 的标志位含义
//
// CMP做减法 R1 - R2,但不存结果
//
// R1 == R2 → Z=1 → JZ跳转
// R1 != R2 → Z=0 → JNZ跳转
// R1 > R2 → Z=0, C=0 (无符号) 或 Z=0, N==V (有符号)
// R1 < R2 → C=1 (无符号) 或 N!=V (有符号)
// 示例:if (R1 >= 10) goto label
LDI R2, #10
CMP R1, R2
JNN label // N=0 说明 R1 >= R2 (有符号)
// 示例:if (R1 < 100) goto label (无符号比较)
LDI R2, #100
CMP R1, R2
JC label // C=1 说明 R1 < R2 (无符号,借位)
CALL指令将返回地址(PC+3,因为CALL是3字节指令)压入堆栈,然后跳转到目标地址。RET从堆栈弹出返回地址到PC。这就是C语言函数调用的底层实现。
因为每次CALL都把返回地址压栈,RET都弹出,所以自然支持嵌套调用和递归:
// 嵌套调用示例
// main() → func_a() → func_b()
main:
CALL func_a
HLT
func_a:
CALL func_b
RET
func_b:
// 做些事情
RET
// 堆栈变化:
// CALL func_a: PUSH main_ret, PC=func_a
// CALL func_b: PUSH func_a_ret, PC=func_b
// RET (func_b): POP → PC=func_a_ret
// RET (func_a): POP → PC=main_ret
写出以下高级代码的Retro8汇编:
if (x > 10) {
y = 1;
} else {
y = 0;
}
假设x在R1,y在R2。
用条件分支实现 for(i=0; i<100; i++) { sum += i; }
假设sum在R1,i在R2。计算偏移量。
用跳转表实现switch-case语句:根据R1的值(0-3)跳转到不同的处理代码。设计跳转表的数据结构和访问方式。
达成条件:
奖励:你的CPU现在能做判断和选择了!if/else、for/while、函数调用——所有程序结构的基础都已就绪。
除了基本的if/else和for/while,还有几种常见的控制流模式:
状态机是嵌入式编程的核心模式:
// 简单的状态机:LED闪烁控制器
// 状态0: LED灭,等待1秒
// 状态1: LED亮,等待0.5秒
STATE0: // LED灭
ANDI R1, #0xFE // LED off (bit0=0)
STA R1, $FF07
LDI R2, #100 // 1秒(10ms×100)
WAIT0:
LDI R3, #10 // 10ms延时
CALL delay_ms
DEC R2
JNZ WAIT0
JMP STATE1
STATE1: // LED亮
ORI R1, #0x01 // LED on (bit0=1)
STA R1, $FF07
LDI R2, #50 // 0.5秒
WAIT1:
LDI R3, #10
CALL delay_ms
DEC R2
JNZ WAIT1
JMP STATE0
用状态机模拟多任务——每个"任务"是一个状态,每个循环执行一步:
// 3个协作任务的调度器
MAIN_LOOP:
CALL task_led // 任务1: LED闪烁
CALL task_key // 任务2: 键盘扫描
CALL task_uart // 任务3: 串口通信
JMP MAIN_LOOP // 循环调度
// 每个任务内部用状态变量记录进度
// 每次被调用时执行一步,然后返回
task_led:
LDA R1, led_state
CMP R1, #0
JZ led_off_state
// ... LED on state
RET
led_off_state:
// ... LED off state
RET
条件分支使用8位有符号偏移。偏移的计算公式:
偏移 = 目标地址 - (分支指令地址 + 指令长度)
例子:从$8010跳转到$8005
偏移 = $8005 - ($8010 + 2) = $8005 - $8012 = -13
-13的8位补码 = 0xF3
验证:PC = $8012 + 0xFFF3 = $8012 - 13 = $8005 ✅
例子:从$8020跳转到$8030
偏移 = $8030 - ($8020 + 2) = $8030 - $8022 = +14
14 = 0x0E
注意:偏移范围 = -128 ~ +127
如果跳转距离超过这个范围,需要用JMP替代
同一个if-else可以有多种汇编实现,效率各不同:
// 方案1: 条件跳过then块(适合短then)
// if (R1 == 0) then_block
JNZ skip_then
// then块
skip_then:
// 方案2: 条件跳到else(标准方式)
// if (R1 > 10) { then } else { else }
LDI R2, #10
CMP R1, R2
JNN else_block // R1 <= 10 → else
// then块 (R1 > 10)
JMP end_if
else_block:
// else块
end_if:
// 方案3: 位测试(检查单个位)
// if (R1 的 bit3 为1)
ANDI R1, #0x08
JZ bit3_is_0
// bit3 = 1
JMP done
bit3_is_0:
// bit3 = 0
done:
当case数量多且值连续时,跳转表比一连串CMP+JZ更高效:
// switch(R1) { case 0:... case 1:... case 2:... }
// 跳转表存放在000
// 每个表项是2字节地址
// 边界检查
CMP R1, #3
JNN default_case
// 计算跳转表偏移: R1 * 2
MOV R3, R1 // X = index
SHL R1 // R1 = index * 2
MOV R3, R1 // X = offset
// 从跳转表读取目标地址
LDA R2, 000,X // 地址低8位
INC R3
LDA R1, 000,X // 地址高8位
// JMP {R1,R2} (间接跳转)
减少循环次数,每次迭代多做几次操作:
// 原始循环:每次处理1个元素
// for(i=0; i<100; i++) sum += array[i]
LDI R3, #0
LDI R4, #100
LOOP1:
LDA R2, 000,X
ADD R1, R2
INC R3
CMP R3, R4
JNZ LOOP1
// 每个元素:5条指令 + 分支开销
// 展开4倍:每次处理4个元素
// 100/4 = 25次迭代
LDI R3, #0
LDI R4, #25
LOOP4:
LDA R2, 000,X // 元素0
ADD R1, R2
INC R3
LDA R2, 000,X // 元素1
ADD R1, R2
INC R3
LDA R2, 000,X // 元素2
ADD R1, R2
INC R3
LDA R2, 000,X // 元素3
ADD R1, R2
INC R3
DEC R4
JNZ LOOP4
// 减少了75%的分支判断开销
与0比较不需要CMP指令(可以用DEC+JNZ直接判断零标志),所以倒计数比正计数更高效:
// 正计数:需要CMP
LDI R2, #0
loop:
// ... 循环体 ...
INC R2
LDI R3, #100
CMP R2, R3 // 需要CMP
JNZ loop
// 倒计数:DEC自动设置Z标志
LDI R2, #100
loop:
// ... 循环体 ...
DEC R2 // DEC自动设置Z
JNZ loop // 不需要CMP!省2字节
| 指令 | 字节 | 周期 | 说明 |
|---|---|---|---|
| JMP addr | 3 | 3 | 无条件跳转 |
| JZ offset | 2 | 2-3 | 条件分支:taken=3, not taken=2 |
| CALL addr | 3 | 5 | 压2字节+跳转 |
| RET | 1 | 4 | 弹2字节+跳转 |
| RTI | 1 | 6 | 弹3字节+跳转 |
注意:条件分支taken比not taken多1个周期(需要计算偏移地址)。
在8位系统中,精确延时是常见需求——等待I/O设备、产生音频频率、控制动画速度:
// 精确延时R2×10ms
// 假设CPU频率10MHz,每条指令平均3周期
delay_ms:
PUSH R3
PUSH R4
outer:
LDI R3, #200 // 外层循环计数
inner:
LDI R4, #166 // 内层循环计数(调整得到精确延时)
wait:
DEC R4
JNZ wait // 2周期×166 = 332周期
DEC R3
JNZ inner // 332×200 = 66400周期 ≈ 6.64ms
DEC R2
JNZ outer
POP R4
POP R3
RET