mirror of
https://github.com/michaelcmartin/Ophis.git
synced 2024-11-18 11:06:07 +00:00
158 lines
4.9 KiB
Plaintext
158 lines
4.9 KiB
Plaintext
|
<chapter id="ch5-link">
|
||
|
<title>Local variables and memory segments</title>
|
||
|
|
||
|
<para>
|
||
|
As mentioned in <xref linkend="ch4-link">, there are better ways
|
||
|
to handle waiting than just executing vast numbers of NOPs. The
|
||
|
Commodore 64 KERNAL library includes a <literal>rdtim</literal>
|
||
|
routine that returns the uptime of the machine, in
|
||
|
60<superscript>th</superscript>s 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 <emphasis>least</emphasis> significant
|
||
|
byte, not the most.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Here's a first shot at a better delay routine:
|
||
|
</para>
|
||
|
|
||
|
<programlisting>
|
||
|
.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
|
||
|
</programlisting>
|
||
|
|
||
|
<para>
|
||
|
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 <literal>.alias</literal> 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 <literal>_tmp</literal> and <literal>_target</literal> with:
|
||
|
</para>
|
||
|
|
||
|
<programlisting>
|
||
|
; data used by the delay routine
|
||
|
.alias _tmp $C000
|
||
|
.alias _target $C001
|
||
|
</programlisting>
|
||
|
|
||
|
<para>
|
||
|
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.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Ophis lets us do this with the <literal>.text</literal>
|
||
|
and <literal>.data</literal> commands.
|
||
|
The <literal>.text</literal> command switches to the program-text
|
||
|
counter, and the <literal>.data</literal> command switches to the
|
||
|
variable-data counter. When Ophis first starts assembling a file,
|
||
|
it starts in <literal>.text</literal> mode.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
To reserve space for a variable, use the .space command. This
|
||
|
takes the form:
|
||
|
|
||
|
<programlisting>
|
||
|
.space varname size
|
||
|
</programlisting>
|
||
|
|
||
|
which assigns the name <literal>varname</literal> to the current
|
||
|
program counter, then advances the program counter by the amount
|
||
|
specified in <literal>size</literal>. Nothing is output to the
|
||
|
final binary as a result of the <literal>.space</literal> command.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
You may not put in any commands that produce output into
|
||
|
a <literal>.data</literal> segment. Generally, all you will be
|
||
|
using are <literal>.org</literal> and <literal>.space</literal>
|
||
|
commands. Ophis will not complain if you
|
||
|
use <literal>.space</literal> inside a <literal>.text</literal>
|
||
|
segment, but this is nearly always wrong.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
The final version of <literal>delay</literal> looks like this:
|
||
|
</para>
|
||
|
|
||
|
<programlisting>
|
||
|
; 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
|
||
|
</programlisting>
|
||
|
|
||
|
<para>
|
||
|
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:
|
||
|
</para>
|
||
|
|
||
|
<programlisting>
|
||
|
.data
|
||
|
.org $C000
|
||
|
.text
|
||
|
</programlisting>
|
||
|
|
||
|
<para>
|
||
|
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 <literal>.checkpc</literal> command lets us
|
||
|
assert that the program counter hasn't reached a specific point
|
||
|
yet. We put, at the end of our code:
|
||
|
</para>
|
||
|
|
||
|
<programlisting>
|
||
|
.checkpc $A000
|
||
|
.data
|
||
|
.checkpc $D000
|
||
|
</programlisting>
|
||
|
|
||
|
<para>
|
||
|
The final program is available as <xref linkend="tutor5-src"
|
||
|
endterm="tutor5-fname">. Note that we based this on the
|
||
|
all-uppercase version from the last section, not any of the
|
||
|
charmapped versions.
|
||
|
</para>
|
||
|
</chapter>
|