在上一节中,我们理解了字符映射与
.data
段的用法。接下来我们将看看另外两个好用的功能:表达式和零页
本节参考了Expressions、Advanced Memory Segments
表达式
Ophis允许书写表达式,这些表达式将在汇编之前完成计算。全部可用运算符如下
运算符 | 含义 |
---|---|
[ ] | 相当于( ),因为圆括号另有涵义 |
< > | 取数的低8位/高8位 |
* / | 乘除 |
+ - | 加减 |
& | ^ | 按位与、或、非 |
在介绍完零页之后,将在附录介绍一个表达式的用例。我们将用表达式实现打印内存中任意位置的字符串的子程序。
零页1
顾名思义,"零页"是指内存的第0
页,即0x0000
-0x00FF
。位于零页的内存不仅访问速度更快(和寄存器速度相同),而且只有该部分内存能够使用间接偏移寻址。这种寻址方式对于编程而言十分重要,我们将在附录的用例中进行详细介绍。
然而,正因为零页十分重要,KERNAL
和BASIC
都使用了零页上的大量空间,留给我们的空间变得十分稀少(只有5个,位于02、fb、fc、fd、fe)。但是在机器代码运行时并不需要BASIC
,因此我们可以另辟蹊径,在程序运行开始时将零页的BASIC
数据区(到8f
为止)搬移到内存的其他区域,在程序结束后进行恢复,从而得到该部分的零页空间的使用权。
不过,因为00
和01
位置被映射到寄存器用于进行内存控制,所以实际可用范围为02
-8f
。
Ophis采用如下语句标识零页位置
.data zp
.org $0002
然后即可进行零页搬移
./platform/c64_0.cph
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Commodore 64 Basic Runtime File
;;
;; Include this at the TOP of your C64 program, and it will handle
;; hiding away the BASIC ROM and data and restoring it at the end.
;;
;; You will have a contiguous block of RAM from $0800 to $CFFF, and
;; Zero Page access from $02 to $8F in the segment "zp".
.include "c64header.oph"
.data zp ; Zero Page memory segment.
.org $0002
.text
.scope
; Cache BASIC zero page underneath the KERNAL, while also
; making RAM copies of the NMI routines
ldx #$00
* lda $00, x
sta $e000, x
inx
bne -
; Swap out the BASIC ROM for RAM
lda $01
and #$fe
ora #$06
sta $01
; Run the real program
jsr _main
; Swap out KERNAL to expose cached BASIC ZP values
; Block IRQs during this period. NMIs cannot be blocked,
; but we copied enough of the processing code into the
; RAM under the KERNAL that we can disable NMI processing
; during this period
sei ; Disable IRQs
lda #$c1 ; Defang NMIs
sta $318
lda $01 ; Swap out KERNAL
and #$fd
sta $01
; Restore BASIC zero page
ldx #$8E
* lda $e001, x
sta $01, x
dex
bne -
; Restore BASIC ROM, KERNAL, and interrupts
lda $01
ora #$07
sta $01
lda #$47 ; Restore NMI vector
sta $318
cli ; Re-enable interrupts
; Back to BASIC. We do this by clearing the keyboard
; buffer and then jumping through the warm start
; vector. This will more cleanly handle case where
; the program has somehow modified BASIC's state,
; such as running through PUCRUNCH or a onefiler.
stx $c6 ; .X is zero from previous loop
jmp ($a002)
_main:
; Program follows...
.scend
./platform/c64header.oph
.word $0801
.org $0801
; BASIC program that just calls our machine language code
.scope
.word _next, 10 ; Next line and current line number
.byte $9e,"2061",0 ; SYS 2061
_next: .word 0 ; End of program
.scend
; Program follows...
在程序的最后,还需要如下语句检查是否越界
.data zp
.checkpc $80
附录
接下来我们将实现打印内存中任意位置的字符串的子程序。
由于内存地址为16
位,因此需要使用两个8
位的连续零页内存空间,按小端序存放该地址。
; PRINTSTR routine. Accumulator stores the low byte of the address,
; X register stores the high byte.
.scope
.data zp
.space _ptr 2
.text
printstr:
sta _ptr
stx _ptr+1
ldy #$00
_lp: lda (_ptr),y
beq _done
jsr chrout
iny
bne _lp
_done: rts
.scend
接下来,因为涉及到超过8位的参数,我们使用宏来调用该子程序。
.macro print
lda #<_1 ; 取参数的低八位
ldx #>_1 ; 取参数的高八位
jsr printstr
.macend