Zilog Z80 Stack
寄存器 sp
用作堆栈指针,指向最后存储的值到堆栈(堆栈的顶部)。所以 EX (sp),hl
会将 hl
的值与堆栈顶部的值进行交换。
与顶部字相反,堆栈通过减少 sp
在内存中增长,并通过增加 sp
来释放(pops
)值。
对于 sp
= $4844
,其值为 1
,2
,3
存储在堆栈中(3
作为最后一个值被压入堆栈,因此位于其顶部),内存将如下所示:
| address | value bytes | comment (btw, all numbers are in hexadecimal)
| ---------- | ----------- | ---------------------------------
| 4840 | ?? ?? | free stack spaces to be used by next push/call
| 4842 | ?? ?? | or by interrupt call! (don't expect values to stay here)
| sp -> 4844 | 03 00 | 16 bit value "3" on top of stack
| 4846 | 02 00 | 16 bit value "2"
| 4848 | 01 00 | 16 bit value "1"
| 484A | ?? ?? | Other values in stack (up to it's origin)
| 484C | ?? ?? | like for example return address for RET instruction
示例,指令如何使用堆栈:
LD hl,$0506
EX (sp),hl ; $0003 into hl, "06 05" bytes at $4844
POP bc ; like: LD c,(sp); INC sp; LD b,(sp); INC sp
; so bc is now $0506, and sp is $4846
XOR a ; a = 0, sets zero and parity flags
PUSH af ; like: DEC sp; LD (sp),a; DEC sp; LD (sp),f
; so at $4844 is $0044 (44 = z+p flags), sp is $4844
CALL $8000 ; sp is $4842, with address of next ins at top of stack
; pc = $8000 (jumping to sub-routine)
; after RET will return here, the sp will be $4844 again
LD (L1+1),sp ; stores current sp into LD sp,nn instruction (self modification)
DEC sp ; sp is $4843
L1 LD sp,$1234 ; restores sp to $4844 ($1234 was modified)
POP de ; de = $0044, sp = $4846
POP ix ; ix = $0002, sp = $4848
...
...
ORG $8000
RET ; LD pc,(sp); INC sp; INC sp
; jumps to address at top of stack, "returning" to caller
总结 :PUSH
将值存储在堆栈顶部,POP
将从堆栈顶部获取值,它是一个 LIFO ( 后进先出)队列。CALL
与 JP
相同,但它也会在 CALL
之后在堆栈顶部推送下一条指令的地址。RET
也类似于 JP
,从堆栈中弹出地址并跳转到它。
警告 :当中断使能时,sp
必须在中断信号期间有效,并为中断处理程序例程保留足够的可用空间,因为中断信号将在调用处理程序例程之前存储返回地址(实际 pc
),这可能会存储更多数据。堆栈也是。如果发生中断,则可以意外地修改 sp
之前的任何值。
高级技巧 :在 Z80 上,PUSH
需要 11 个时钟周期(11t),POP
需要 10t,展开的 POP
/ PUSH
通过所有寄存器,包括用于阴影变体的 EXX
,是复制内存块的最快方法,甚至比展开的 LDI
更快。但是你必须在中断信号之间计时,以避免内存损坏。同时展开的 PUSH
是在 ZX Spectrum 上填充特定值的内存的最快方式(再次考虑到中断损坏的风险,如果没有正确计时,或者在 DI
下完成)。