Local variables and memory segments
As mentioned in , there are better ways
to handle waiting than just executing vast numbers of NOPs. The
Commodore 64 KERNAL library includes a rdtim
routine that returns the uptime of the machine, in
60ths of a second, as a 24-bit integer.
The Commodore 64 programmer's guide available online actually has
a bug in it, reversing the significance of the A and Y registers.
The accumulator holds the least significant
byte, not the most.
Here's a first shot at a better delay routine:
.scope
; data used by the delay routine
_tmp: .byte 0
_target: .byte 0
delay: sta _tmp ; save argument (rdtim destroys it)
jsr rdtim
clc
adc _tmp ; add current time to get target
sta _target
* jsr rdtim
cmp _target
bmi - ; Buzz until target reached
rts
.scend
This works, but it eats up two bytes of file space that don't
really need to be specified. Also, it's modifying data inside a
program text area, which isn't good if you're assembling to a ROM
chip. (Since the Commodore 64 stores its programs in RAM, it's
not an issue for us here.) A slightly better solution is to
use .alias to assign the names to chunks of RAM
somewhere. There's a 4K chunk of RAM from $C000 through $CFFF
between the BASIC ROM and the I/O ROM that should serve our
purposes nicely. We can replace the definitions
of _tmp and _target with:
; data used by the delay routine
.alias _tmp $C000
.alias _target $C001
This works better, but now we've just added a major bookkeeping
burden upon ourselves—we must ensure that no routines step on
each other. What we'd really like are two separate program
counters—one for the program text, and one for our variable
space.
Ophis lets us do this with the .text
and .data commands.
The .text command switches to the program-text
counter, and the .data command switches to the
variable-data counter. When Ophis first starts assembling a file,
it starts in .text mode.
To reserve space for a variable, use the .space command. This
takes the form:
.space varname size
which assigns the name varname to the current
program counter, then advances the program counter by the amount
specified in size. Nothing is output to the
final binary as a result of the .space command.
You may not put in any commands that produce output into
a .data segment. Generally, all you will be
using are .org and .space
commands. Ophis will not complain if you
use .space inside a .text
segment, but this is nearly always wrong.
The final version of delay looks like this:
; DELAY routine. Takes values from the Accumulator and pauses
; for that many jiffies (1/60th of a second).
.scope
.data
.space _tmp 1
.space _target 1
.text
delay: sta _tmp ; save argument (rdtim destroys it)
jsr rdtim
clc
adc _tmp ; add current time to get target
sta _target
* jsr rdtim
cmp _target
bmi - ; Buzz until target reached
rts
.scend
We're not quite done yet, however, because we have to tell the
data segment where to begin. (If we don't, it starts at 0, which
is usually wrong.) We add a very brief data segment to the top of
our code:
.data
.org $C000
.text
This will run. However, we also ought to make sure that we aren't
overstepping any boundaries. Our program text shouldn't run into
the BASIC chip at $A000, and our data shouldn't run into the I/O
region at $D000. The .checkpc command lets us
assert that the program counter hasn't reached a specific point
yet. We put, at the end of our code:
.checkpc $A000
.data
.checkpc $D000
The final program is available as . Note that we based this on the
all-uppercase version from the last section, not any of the
charmapped versions.