mirror of
https://github.com/michaelcmartin/Ophis.git
synced 2024-12-12 14:30:14 +00:00
Initial import of the Ophis 1.0 distribution and supplemental material
This commit is contained in:
parent
b78f3c614a
commit
2c8dba2450
BIN
doc/a2blink.map
Normal file
BIN
doc/a2blink.map
Normal file
Binary file not shown.
BIN
doc/a2inverse.map
Normal file
BIN
doc/a2inverse.map
Normal file
Binary file not shown.
BIN
doc/a2normal.map
Normal file
BIN
doc/a2normal.map
Normal file
Binary file not shown.
12
doc/c64-1.oph
Normal file
12
doc/c64-1.oph
Normal file
@ -0,0 +1,12 @@
|
||||
.word $0801
|
||||
.org $0801
|
||||
|
||||
.scope
|
||||
.word _next, 10 ; Next line and current line number
|
||||
.byte $9e," 2064",0 ; SYS 2064
|
||||
_next: .word 0 ; End of program
|
||||
.scend
|
||||
|
||||
.advance 2064
|
||||
|
||||
.require "kernal.oph"
|
40
doc/c64-2.oph
Normal file
40
doc/c64-2.oph
Normal file
@ -0,0 +1,40 @@
|
||||
.word $0801
|
||||
.org $0801
|
||||
|
||||
.scope
|
||||
.word _next, 10 ; Next line and current line number
|
||||
.byte $9e," 2064",0 ; SYS 2064
|
||||
_next: .word 0 ; End of program
|
||||
.scend
|
||||
|
||||
.advance $0810
|
||||
|
||||
.require "kernal.oph"
|
||||
|
||||
.data zp
|
||||
.org $0002
|
||||
|
||||
.text
|
||||
|
||||
.scope
|
||||
; Cache BASIC's zero page at top of available RAM.
|
||||
ldx #$7E
|
||||
* lda $01, x
|
||||
sta $CF81, x
|
||||
dex
|
||||
bne -
|
||||
|
||||
jsr _main
|
||||
|
||||
; Restore BASIC's zero page and return control.
|
||||
|
||||
ldx #$7E
|
||||
* lda $CF81, x
|
||||
sta $01, x
|
||||
dex
|
||||
bne -
|
||||
rts
|
||||
|
||||
_main:
|
||||
; Program follows...
|
||||
.scend
|
478
doc/docbook/cmdref.sgm
Normal file
478
doc/docbook/cmdref.sgm
Normal file
@ -0,0 +1,478 @@
|
||||
<appendix id="ref-link">
|
||||
<title>Ophis Command Reference</title>
|
||||
<section>
|
||||
<title>Command Modes</title>
|
||||
<para>
|
||||
These mostly follow the <emphasis>MOS Technology 6500
|
||||
Microprocessor Family Programming Manual</emphasis>, except
|
||||
for the Accumulator mode. Accumulator instructions are written
|
||||
and interpreted identically to Implied mode instructions.
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem><para><emphasis>Implied:</emphasis> <literal>RTS</literal></para></listitem>
|
||||
<listitem><para><emphasis>Accumulator:</emphasis> <literal>LSR</literal></para></listitem>
|
||||
<listitem><para><emphasis>Immediate:</emphasis> <literal>LDA #$06</literal></para></listitem>
|
||||
<listitem><para><emphasis>Zero Page:</emphasis> <literal>LDA $7C</literal></para></listitem>
|
||||
<listitem><para><emphasis>Zero Page, X:</emphasis> <literal>LDA $7C,X</literal></para></listitem>
|
||||
<listitem><para><emphasis>Zero Page, Y:</emphasis> <literal>LDA $7C,Y</literal></para></listitem>
|
||||
<listitem><para><emphasis>Absolute:</emphasis> <literal>LDA $D020</literal></para></listitem>
|
||||
<listitem><para><emphasis>Absolute, X:</emphasis> <literal>LDA $D000,X</literal></para></listitem>
|
||||
<listitem><para><emphasis>Absolute, Y:</emphasis> <literal>LDA $D000,Y</literal></para></listitem>
|
||||
<listitem><para><emphasis>(Zero Page Indirect, X):</emphasis> <literal>LDA ($80, X)</literal></para></listitem>
|
||||
<listitem><para><emphasis>(Zero Page Indirect), Y:</emphasis> <literal>LDA ($80), Y</literal></para></listitem>
|
||||
<listitem><para><emphasis>(Absolute Indirect):</emphasis> <literal>JMP ($A000)</literal></para></listitem>
|
||||
<listitem><para><emphasis>Relative:</emphasis> <literal>BNE loop</literal></para></listitem>
|
||||
<listitem><para><emphasis>(Absolute Indirect, X):</emphasis> <literal>JMP ($A000, X)</literal> — Only available with 65C02 extensions</para></listitem>
|
||||
<listitem><para><emphasis>(Zero Page Indirect):</emphasis> <literal>LDX ($80)</literal> — Only available with 65C02 extensions</para></listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
<section>
|
||||
<title>Basic arguments</title>
|
||||
<para>
|
||||
Most arguments are just a number or label. The formats for
|
||||
these are below.
|
||||
</para>
|
||||
<section>
|
||||
<title>Numeric types</title>
|
||||
<itemizedlist>
|
||||
<listitem><para><emphasis>Hex:</emphasis> <literal>$41</literal> (Prefixed with $)</para></listitem>
|
||||
<listitem><para><emphasis>Decimal:</emphasis> <literal>65</literal> (No markings)</para></listitem>
|
||||
<listitem><para><emphasis>Octal:</emphasis> <literal>0101</literal> (Prefixed with zero)</para></listitem>
|
||||
<listitem><para><emphasis>Binary:</emphasis> <literal>%01000001</literal> (Prefixed with %)</para></listitem>
|
||||
<listitem><para><emphasis>Character:</emphasis> <literal>'A</literal> (Prefixed with single quote)</para></listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
<section>
|
||||
<title>Label types</title>
|
||||
<para>
|
||||
Normal labels are simply referred to by name. Anonymous
|
||||
labels may be referenced with strings of - or + signs (the
|
||||
label <literal>-</literal> refers to the immediate
|
||||
previous anonymous label, <literal>--</literal> the
|
||||
one before that, etc., while <literal>+</literal>
|
||||
refers to the next anonymous label), and the special
|
||||
label <literal>^</literal> refers to the program
|
||||
counter at the start of the current instruction or directive.
|
||||
</para>
|
||||
<para>
|
||||
Normal labels are <emphasis>defined</emphasis> by
|
||||
prefixing a line with the label name and then a colon
|
||||
(e.g., <literal>label:</literal>). Anonymous labels
|
||||
are defined by prefixing a line with an asterisk
|
||||
(e.g., <literal>*</literal>).
|
||||
</para>
|
||||
<para>
|
||||
Temporary labels are only reachable from inside the
|
||||
innermost enclosing <literal>.scope</literal>
|
||||
statement. They are identical to normal labels in every
|
||||
way, except that they start with an underscore.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>String types</title>
|
||||
<para>
|
||||
Strings are enclosed in double quotation marks. Backslashed
|
||||
characters (including backslashes and double quotes) are
|
||||
treated literally, so the string <literal>"The man said,
|
||||
\"The \\ character is the backslash.\""</literal> produces
|
||||
the ASCII sequence for <literal>The man said, "The \
|
||||
character is the backslash."</literal>
|
||||
</para>
|
||||
<para>
|
||||
Strings are generally only used as arguments to assembler
|
||||
directives—usually for filenames
|
||||
(e.g., <literal>.include</literal>) but also for string
|
||||
data (in association with <literal>.byte</literal>).
|
||||
</para>
|
||||
<para>
|
||||
It is legal, though unusual, to attempt to pass a string to
|
||||
the other data statements. This will produces a series of
|
||||
words/dwords where all bytes that aren't least-significant
|
||||
are zero. Endianness and size will match what the directive
|
||||
itself indicated.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>Compound Arguments</title>
|
||||
<para>
|
||||
Compound arguments may be built up from simple ones, using the
|
||||
standard +, -, *, and / operators, which carry the usual
|
||||
precedence. Also, the unary operators > and <, which
|
||||
bind more tightly than anything else, provide the high and low
|
||||
bytes of 16-bit values, respectively.
|
||||
</para>
|
||||
<para>
|
||||
Use brackets [ ] instead of parentheses ( ) when grouping
|
||||
arithmetic operations, as the parentheses are needed for the
|
||||
indirect addressing modes.
|
||||
</para>
|
||||
<para>
|
||||
Examples:
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem><para><literal>$D000</literal> evaluates to $D000</para></listitem>
|
||||
<listitem><para><literal>$D000+32</literal> evaluates to $D020</para></listitem>
|
||||
<listitem><para><literal>$D000+$20</literal> also evaluates to $D020</para></listitem>
|
||||
<listitem><para><literal><$D000+32</literal> evaluates to $20</para></listitem>
|
||||
<listitem><para><literal>>$D000+32</literal> evaluates to $F0</para></listitem>
|
||||
<listitem><para><literal>>[$D000+32]</literal> evaluates to $D0</para></listitem>
|
||||
<listitem><para><literal>>$D000-275</literal> evaluates to $CE</para></listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
<section>
|
||||
<title>Memory Model</title>
|
||||
<para>
|
||||
In order to properly compute the locations of labels and the
|
||||
like, Ophis must keep track of where assembled code will
|
||||
actually be sitting in memory, and it strives to do this in a
|
||||
way that is independent both of the target file and of the
|
||||
target machine.
|
||||
</para>
|
||||
<section>
|
||||
<title>Basic PC tracking</title>
|
||||
<para>
|
||||
The primary technique Ophis uses is <emphasis>program counter
|
||||
tracking</emphasis>. As it assembles the code, it keeps
|
||||
track of a virtual program counter, and uses that to
|
||||
determine where the labels should go.
|
||||
</para>
|
||||
<para>
|
||||
In the absence of an <literal>.org</literal> directive, it
|
||||
assumes a starting PC of zero. <literal>.org</literal>
|
||||
is a simple directive, setting the PC to the value
|
||||
that <literal>.org</literal> specifies. In the simplest
|
||||
case, one <literal>.org</literal> directive appears at the
|
||||
beginning of the code and sets the location for the rest of
|
||||
the code, which is one contiguous block.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Basic Segmentation simulation</title>
|
||||
<para>
|
||||
However, this isn't always practical. Often one wishes to
|
||||
have a region of memory reserved for data without actually
|
||||
mapping that memory to the file. On some systems (typically
|
||||
cartridge-based systems where ROM and RAM are seperate, and
|
||||
the target file only specifies the ROM image) this is
|
||||
mandatory. In order to access these variables symbolically,
|
||||
it's necessary to put the values into the label lookup
|
||||
table.
|
||||
</para>
|
||||
<para>
|
||||
It is possible, but inconvenient, to do this
|
||||
with <literal>.alias</literal>, assigning a specific
|
||||
memory location to each variable. This requires careful
|
||||
coordination through your code, and makes creating reusable
|
||||
libraries all but impossible.
|
||||
</para>
|
||||
<para>
|
||||
A better approach is to reserve a section at the beginning
|
||||
or end of your program, put an <literal>.org</literal>
|
||||
directive in, then use the <literal>.space</literal>
|
||||
directive to divide up the data area. This is still a bit
|
||||
inconvenient, though, because all variables must be
|
||||
assigned all at once. What we'd really like is to keep
|
||||
multiple PC counters, one for data and one for code.
|
||||
</para>
|
||||
<para>
|
||||
The <literal>.text</literal>
|
||||
and <literal>.data</literal> directives do this. Each
|
||||
has its own PC that starts at zero, and you can switch
|
||||
between the two at any point without corrupting the other's
|
||||
counter. In this way each function can have
|
||||
a <literal>.data</literal> section (filled
|
||||
with <literal>.space</literal> commands) and
|
||||
a <literal>.text</literal> section (that contains the
|
||||
actual code). This lets our library routines be almost
|
||||
completely self-contained - we can have one source file
|
||||
that could be <literal>.included</literal> by multiple
|
||||
projects without getting in anything's way.
|
||||
</para>
|
||||
<para>
|
||||
However, any given program may have its own ideas about
|
||||
where data and code go, and it's good to ensure with
|
||||
a <literal>.checkpc</literal> at the end of your code
|
||||
that you haven't accidentally overwritten code with data or
|
||||
vice versa. If your <literal>.data</literal>
|
||||
segment <emphasis>did</emphasis> start at zero, it's
|
||||
probably wise to make sure you aren't smashing the stack,
|
||||
too (which is sitting in the region from $0100 to
|
||||
$01FF).
|
||||
</para>
|
||||
<para>
|
||||
If you write code with no segment-defining statements in
|
||||
it, the default segment
|
||||
is <literal>text</literal>.
|
||||
</para>
|
||||
<para>
|
||||
The <literal>data</literal> segment is designed only
|
||||
for organizing labels. As such, errors will be flagged if
|
||||
you attempt to actually output information into
|
||||
a <literal>data</literal> segment.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>General Segmentation Simulation</title>
|
||||
<para>
|
||||
One text and data segment each is usually sufficient, but
|
||||
for the cases where it is not, Ophis allows for user-defined
|
||||
segments. Putting a label
|
||||
after <literal>.text</literal>
|
||||
or <literal>.data</literal> produces a new segment with
|
||||
the specified name.
|
||||
</para>
|
||||
<para>
|
||||
Say, for example, that we have access to the RAM at the low
|
||||
end of the address space, but want to reserve the zero page
|
||||
for truly critical variables, and use the rest of RAM for
|
||||
everything else. Let's also assume that this is a 6510
|
||||
chip, and locations $00 and $01 are reserved for the I/O
|
||||
port. We could start our program off with:
|
||||
</para>
|
||||
<programlisting>
|
||||
.data
|
||||
.org $200
|
||||
.data zp
|
||||
.org $2
|
||||
.text
|
||||
.org $800
|
||||
</programlisting>
|
||||
<para>
|
||||
And, to be safe, we would probably want to end our code
|
||||
with checks to make sure we aren't overwriting anything:
|
||||
</para>
|
||||
<programlisting>
|
||||
.data
|
||||
.checkpc $800
|
||||
.data zp
|
||||
.checkpc $100
|
||||
</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>Macros</title>
|
||||
<para>
|
||||
Assembly language is a powerful tool—however, there are
|
||||
many tasks that need to be done repeatedly, and with
|
||||
mind-numbing minor modifications. Ophis includes a facility
|
||||
for <emphasis>macros</emphasis> to allow this. Ophis macros
|
||||
are very similar in form to function calls in higher level
|
||||
languages.
|
||||
</para>
|
||||
<section>
|
||||
<title>Defining Macros</title>
|
||||
<para>
|
||||
Macros are defined with the <literal>.macro</literal>
|
||||
and <literal>.macend</literal> commands. Here's a
|
||||
simple one that will clear the screen on a Commodore
|
||||
64:
|
||||
</para>
|
||||
<programlisting>
|
||||
.macro clr'screen
|
||||
lda #147
|
||||
jsr $FFD2
|
||||
.macend
|
||||
</programlisting>
|
||||
</section>
|
||||
<section>
|
||||
<title>Invoking Macros</title>
|
||||
<para>
|
||||
To invoke a macro, either use
|
||||
the <literal>.invoke</literal> command or backquote the
|
||||
name of the routine. The previous macro may be expanded
|
||||
out in either of two ways, at any point in the
|
||||
source:
|
||||
</para>
|
||||
<programlisting>.invoke clr'screen</programlisting>
|
||||
<para>or</para>
|
||||
<programlisting>`clr'screen</programlisting>
|
||||
<para>will work equally well.</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Passing Arguments to Macros</title>
|
||||
<para>
|
||||
Macros may take arguments. The arguments to a macro are
|
||||
all of the <quote>word</quote> type, though byte values may
|
||||
be passed and used as bytes as well. The first argument in
|
||||
an invocation is bound to the label
|
||||
<literal>_1</literal>, the second
|
||||
to <literal>_2</literal>, and so on. Here's a macro
|
||||
for storing a 16-bit value into a word pointer:
|
||||
</para>
|
||||
<programlisting>
|
||||
.macro store16 ; `store16 dest, src
|
||||
lda #<_2
|
||||
sta _1
|
||||
lda #>_2
|
||||
sta _1+1
|
||||
.macend
|
||||
</programlisting>
|
||||
<para>
|
||||
Macro arguments behave, for the most part, as if they were
|
||||
defined by <literal>.alias</literal>
|
||||
commands <emphasis>in the calling context</emphasis>.
|
||||
(They differ in that they will not produce duplicate-label
|
||||
errors if those names already exist in the calling scope,
|
||||
and in that they disappear after the call is
|
||||
completed.)
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Features and Restrictions of the Ophis Macro Model</title>
|
||||
<para>
|
||||
Unlike most macro systems (which do textual replacement),
|
||||
Ophis macros evaluate their arguments and bind them into the
|
||||
symbol table as temporary labels. This produces some
|
||||
benefits, but it also puts some restrictions on what kinds of
|
||||
macros may be defined.
|
||||
</para>
|
||||
<para>
|
||||
The primary benefit of this <quote>expand-via-binding</quote>
|
||||
discipline is that there are no surprises in the semantics.
|
||||
The expression <literal>_1+1</literal> in the macro above
|
||||
will always evaluate to one more than the value that was
|
||||
passed as the first argument, even if that first argument is
|
||||
some immensely complex expression that an
|
||||
expand-via-substitution method may accidentally
|
||||
mangle.
|
||||
</para>
|
||||
<para>
|
||||
The primary disadvantage of the expand-via-binding
|
||||
discipline is that only fixed numbers of words and bytes
|
||||
may be passed. A substitution-based system could define a
|
||||
macro including the line <literal>LDA _1</literal> and
|
||||
accept as arguments both <literal>$C000</literal>
|
||||
(which would put the value of memory location $C000 into
|
||||
the accumulator) and <literal>#$40</literal> (which
|
||||
would put the immediate value $40 into the accumulator).
|
||||
If you <emphasis>really</emphasis> need this kind of
|
||||
behavior, a run a C preprocessor over your Ophis source,
|
||||
and use <literal>#define</literal> to your heart's
|
||||
content.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>Assembler directives</title>
|
||||
<para>
|
||||
Assembler directives are all instructions to the assembler
|
||||
that are not actual instructions. Ophis's set of directives
|
||||
follow.
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem><para><literal>.advance</literal> <emphasis>address</emphasis>:
|
||||
Forces the program counter to
|
||||
be <emphasis>address</emphasis>. Unlike
|
||||
the <literal>.org</literal>
|
||||
directive, <literal>.advance</literal> outputs zeroes until the
|
||||
program counter reaches a specified address. Attempting
|
||||
to <literal>.advance</literal> to a point behind the current
|
||||
program counter is an assemble-time error.</para></listitem>
|
||||
<listitem><para><literal>.alias</literal> <emphasis>label</emphasis> <emphasis>value</emphasis>: The
|
||||
.alias directive assigns an arbitrary value to a label. This
|
||||
value may be an arbitrary argument, but cannot reference any
|
||||
label that has not already been defined (this prevents
|
||||
recursive label dependencies).</para></listitem>
|
||||
<listitem><para><literal>.byte</literal> <emphasis>arg</emphasis> [ , <emphasis>arg</emphasis>, ... ]:
|
||||
Specifies a series of arguments, which are evaluated, and
|
||||
strings, which are included as raw ASCII data. The final
|
||||
results of these arguments must be one byte in size. Seperate
|
||||
constants are seperated by comments.</para></listitem>
|
||||
<listitem><para><literal>.checkpc</literal> <emphasis>address</emphasis>: Ensures that the
|
||||
program counter is less than or equal to the address
|
||||
specified, and emits an assemble-time error if it is not.
|
||||
<emphasis>This produces no code in the final binary - it is there to
|
||||
ensure that linking a large amount of data together does not
|
||||
overstep memory boundaries.</emphasis></para></listitem>
|
||||
<listitem><para><literal>.data</literal> <emphasis>[label]</emphasis>: Sets the segment to
|
||||
the segment name specified and disallows output. If no label
|
||||
is given, switches to the default data segment.</para></listitem>
|
||||
<listitem><para><literal>.incbin</literal> <emphasis>filename</emphasis>: Inserts the
|
||||
contents of the file specified as binary data. Use it to
|
||||
include graphics information, precompiled code, or other
|
||||
non-assembler data.</para></listitem>
|
||||
<listitem><para><literal>.include</literal> <emphasis>filename</emphasis>: Includes the
|
||||
entirety of the file specified at that point in the program.
|
||||
Use this to order your final sources.</para></listitem>
|
||||
<listitem><para><literal>.org</literal> <emphasis>address</emphasis>: Sets the program
|
||||
counter to the address specified. <emphasis>This does not emit any
|
||||
code in and of itself, nor does it overwrite anything that
|
||||
previously existed.</emphasis> If you wish to jump ahead in memory,
|
||||
use <literal>.advance</literal>.</para></listitem>
|
||||
<listitem><para><literal>.require</literal> <emphasis>filename</emphasis>: Includes the entirety
|
||||
of the file specified at that point in the program. Unlike <literal>.include</literal>,
|
||||
however, code included with <literal>.require</literal> will only be inserted once.
|
||||
The <literal>.require</literal> directive is useful for ensuring that certain code libraries
|
||||
are somewhere in the final binary. They are also very useful for guaranteeing that
|
||||
macro libraries are available.</para></listitem>
|
||||
<listitem><para><literal>.space</literal> <emphasis>label</emphasis> <emphasis>size</emphasis>: This
|
||||
directive is used to organize global variables. It defines the
|
||||
label specified to be at the current location of the program
|
||||
counter, and then advances the program counter <emphasis>size</emphasis>
|
||||
steps ahead. No actual code is produced. This is equivalent
|
||||
to <literal>label: .org ^+size</literal>.</para></listitem>
|
||||
<listitem><para><literal>.text</literal> <emphasis>[label]</emphasis>: Sets the segment to
|
||||
the segment name specified and allows output. If no label is
|
||||
given, switches to the default text segment.</para></listitem>
|
||||
<listitem><para><literal>.word</literal> <emphasis>arg</emphasis> [ , <emphasis>arg</emphasis>, ... ]:
|
||||
Like <literal>.byte</literal>, but values are all treated as two-byte
|
||||
values and stored low-end first (as is the 6502's wont). Use
|
||||
this to create jump tables (an unadorned label will evaluate
|
||||
to that label's location) or otherwise store 16-bit
|
||||
data.</para></listitem>
|
||||
<listitem><para><literal>.dword</literal> <emphasis>arg</emphasis> [ , <emphasis>arg</emphasis>, ...]:
|
||||
Like <literal>.word</literal>, but for 32-bit values.</para></listitem>
|
||||
<listitem><para><literal>.wordbe</literal> <emphasis>arg</emphasis> [ , <emphasis>arg</emphasis>, ...]:
|
||||
Like <literal>.word</literal>, but stores the value in a big-endian format (high byte first).</para></listitem>
|
||||
<listitem><para><literal>.dwordbe</literal> <emphasis>arg</emphasis> [ , <emphasis>arg</emphasis>, ...]:
|
||||
Like <literal>.dword</literal>, but stores the value high byte first.</para></listitem>
|
||||
<listitem><para><literal>.scope</literal>: Starts a new scope block. Labels
|
||||
that begin with an underscore are only reachable from within
|
||||
their innermost enclosing <literal>.scope</literal> statement.</para></listitem>
|
||||
<listitem><para><literal>.scend</literal>: Ends a scope block. Makes the
|
||||
temporary labels defined since the last <literal>.scope</literal>
|
||||
statement unreachable, and permits them to be redefined in a
|
||||
new scope.</para></listitem>
|
||||
<listitem><para><literal>.macro</literal> <emphasis>name</emphasis>: Begins a macro
|
||||
definition block. This is a scope block that can be inlined
|
||||
at arbitrary points with <literal>.invoke</literal>. Arguments to the
|
||||
macro will be bound to temporary labels with names like
|
||||
<literal>_1</literal>, <literal>_2</literal>, etc.</para></listitem>
|
||||
<listitem><para><literal>.macend</literal>: Ends a macro definition
|
||||
block.</para></listitem>
|
||||
<listitem><para><literal>.invoke</literal> <emphasis>label</emphasis> [<emphasis>argument</emphasis> [,
|
||||
<emphasis>argument</emphasis> ...]]: invokes (inlines) the specified
|
||||
macro, binding the values of the arguments to the ones the
|
||||
macro definition intends to read. A shorthand for <literal>.invoke</literal>
|
||||
is the name of the macro to invoke, backquoted.</para></listitem>
|
||||
</itemizedlist>
|
||||
<para>
|
||||
The following directives are deprecated, added for
|
||||
compatibility with the old Perl
|
||||
assembler <command>P65</command>. Use
|
||||
the <literal>-d</literal> option to Ophis to enable
|
||||
them.
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem><para><literal>.ascii</literal>: Equivalent to <literal>.byte</literal>,
|
||||
which didn't used to be able to handle strings.</para></listitem>
|
||||
<listitem><para><literal>.code</literal>: Equivalent to <literal>.text</literal>.</para></listitem>
|
||||
<listitem><para><literal>.segment</literal>: Equivalent to <literal>.text</literal>,
|
||||
from when there was no distinction between <literal>.text</literal> and
|
||||
<literal>.data</literal> segments.</para></listitem>
|
||||
<listitem><para><literal>.address</literal>: Equivalent to
|
||||
<literal>.word</literal>.</para></listitem>
|
||||
<listitem><para><literal>.link</literal> <emphasis>filename address</emphasis>: Assembles
|
||||
the file specified as if it began at the address specified.
|
||||
This is generally for use in <quote>top-level</quote> files, where there
|
||||
is not necessarily a one-to-one correspondence between file
|
||||
position and memory position. This is equivalent to an
|
||||
<literal>.org</literal> directive followed by an <literal>.include</literal>.
|
||||
With the introduction of the <literal>.org</literal> directive this one is
|
||||
less useful (and in most cases, any <literal>.org</literal> statement
|
||||
you use will actually be at the top of the <literal>.include</literal>d
|
||||
file).</para></listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
</appendix>
|
29
doc/docbook/ophismanual.sgm
Normal file
29
doc/docbook/ophismanual.sgm
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V3.1//EN"
|
||||
[<!ENTITY part1 SYSTEM "tutor1.sgm">
|
||||
<!ENTITY part2 SYSTEM "tutor2.sgm">
|
||||
<!ENTITY part3 SYSTEM "tutor3.sgm">
|
||||
<!ENTITY part4 SYSTEM "tutor4.sgm">
|
||||
<!ENTITY part5 SYSTEM "tutor5.sgm">
|
||||
<!ENTITY part6 SYSTEM "tutor6.sgm">
|
||||
<!ENTITY part7 SYSTEM "tutor7.sgm">
|
||||
<!ENTITY samplecode SYSTEM "samplecode.sgm">
|
||||
<!ENTITY pre1 SYSTEM "preface.sgm">
|
||||
<!ENTITY cmdref SYSTEM "cmdref.sgm">
|
||||
]>
|
||||
<book>
|
||||
<bookinfo>
|
||||
<title>Programming with Ophis</title>
|
||||
<author><firstname>Michael</firstname><surname>Martin</surname></author>
|
||||
<copyright><year>2006-7</year><holder>Michael Martin</holder></copyright>
|
||||
</bookinfo>
|
||||
&pre1;
|
||||
&part1;
|
||||
&part2;
|
||||
&part3;
|
||||
&part4;
|
||||
&part5;
|
||||
&part6;
|
||||
&part7;
|
||||
&samplecode;
|
||||
&cmdref;
|
||||
</book>
|
74
doc/docbook/preface.sgm
Normal file
74
doc/docbook/preface.sgm
Normal file
@ -0,0 +1,74 @@
|
||||
<preface>
|
||||
<title>Preface</title>
|
||||
|
||||
<para>
|
||||
The Ophis project started on a lark back in 2001. My graduate
|
||||
studies required me to learn Perl and Python, and I'd been playing
|
||||
around with Commodore 64 emulators in my spare time, so I decided
|
||||
to learn both languages by writing a simple cross-assembler for
|
||||
the 6502 chip the C-64 used in both.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The Perl version was quickly abandoned, but the Python one slowly
|
||||
grew in scope and power over the years, and by 2005 was a very
|
||||
powerful, flexible macro assembler that saw more use than I'd
|
||||
expect. In 2007 I finally got around to implementing the last few
|
||||
features I really wanted and polishing it up for general release.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Part of that process has been formatting the various little
|
||||
tutorials and references I'd created into a single, unified
|
||||
document—the one you are now reading.
|
||||
</para>
|
||||
|
||||
<section>
|
||||
<title>Why <quote>Ophis</quote>?</title>
|
||||
<para>
|
||||
It's actually a kind of a horrific pun. See, I was using Python
|
||||
at the time, and one of the things I had been hoping to do with
|
||||
the assembler was to produce working Apple II
|
||||
programs. <quote>Ophis</quote> is Greek
|
||||
for <quote>snake</quote>, and a number of traditions also use it
|
||||
as the actual <emphasis>name</emphasis> of the serpent in the
|
||||
Garden of Eden. So, Pythons, snakes, and stories involving
|
||||
really old Apples all combined to name the assembler.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Getting a copy of Ophis</title>
|
||||
<para>
|
||||
If you're reading this as part of the Ophis install, you clearly
|
||||
already have it. If not, as of this writing the homepage for
|
||||
the Ophis assembler
|
||||
is <ulink url="http://hkn.eecs.berkeley.edu/~mcmartin/ophis/"></ulink>. If
|
||||
this is out-of-date, a Web search on <quote>Ophis 6502
|
||||
assembler</quote> (without the quotation marks) should yield its
|
||||
page.
|
||||
</para>
|
||||
<para>
|
||||
Ophis is written entirely in Python and packaged using the
|
||||
distutils. The default installation script on Unix and Mac OS X
|
||||
systems should put the files where they need to go. If you are
|
||||
running it locally, you will need to install
|
||||
the <literal>Ophis</literal> package somewhere in your Python
|
||||
package path, and then put the <command>ophis</command> script
|
||||
somewhere in your path.
|
||||
</para>
|
||||
<para>
|
||||
Windows users that have Python installed can use the same source
|
||||
distributions that the other operating systems
|
||||
use; <command>ophis.bat</command> will arrange the environment
|
||||
variables accordingly and invoke the main script.
|
||||
</para>
|
||||
<para>
|
||||
If you are on Windows and do not have Python installed, a
|
||||
prepackaged system made with <command>py2exe</command> is also
|
||||
available. The default Windows installer will use this. In
|
||||
this case, all you need to do is
|
||||
have <command>ophis.exe</command> in your path.
|
||||
</para>
|
||||
</section>
|
||||
</preface>
|
749
doc/docbook/samplecode.sgm
Normal file
749
doc/docbook/samplecode.sgm
Normal file
@ -0,0 +1,749 @@
|
||||
<appendix>
|
||||
<title>Example Programs</title>
|
||||
<para>
|
||||
This Appendix collects all the programs referred to in the course
|
||||
of this manual.
|
||||
</para>
|
||||
<section id="tutor1-src">
|
||||
<title id="tutor1-fname"><filename>tutor1.oph</filename></title>
|
||||
<programlisting>
|
||||
.word $0801
|
||||
.org $0801
|
||||
|
||||
.word next, 10 ; Next line and current line number
|
||||
.byte $9e," 2064",0 ; SYS 2064
|
||||
next: .word 0 ; End of program
|
||||
|
||||
.advance 2064
|
||||
|
||||
ldx #0
|
||||
loop: lda hello, x
|
||||
beq done
|
||||
jsr $ffd2
|
||||
inx
|
||||
bne loop
|
||||
done: rts
|
||||
|
||||
hello: .byte "HELLO, WORLD!", 0
|
||||
</programlisting>
|
||||
</section>
|
||||
<section id="tutor2-src">
|
||||
<title id="tutor2-fname"><filename>tutor2.oph</filename></title>
|
||||
<programlisting>
|
||||
.word $0801
|
||||
.org $0801
|
||||
|
||||
.scope
|
||||
.word _next, 10 ; Next line and current line number
|
||||
.byte $9e," 2064",0 ; SYS 2064
|
||||
_next: .word 0 ; End of program
|
||||
.scend
|
||||
|
||||
.advance 2064
|
||||
|
||||
.alias chrout $ffd2
|
||||
|
||||
ldx #0
|
||||
* lda hello, x
|
||||
beq +
|
||||
jsr chrout
|
||||
inx
|
||||
bne -
|
||||
* rts
|
||||
|
||||
hello: .byte "HELLO, WORLD!", 0
|
||||
</programlisting>
|
||||
</section>
|
||||
<section id="c64-1-src">
|
||||
<title id="c64-1-fname"><filename>c64-1.oph</filename></title>
|
||||
<programlisting>
|
||||
.word $0801
|
||||
.org $0801
|
||||
|
||||
.scope
|
||||
.word _next, 10 ; Next line and current line number
|
||||
.byte $9e," 2064",0 ; SYS 2064
|
||||
_next: .word 0 ; End of program
|
||||
.scend
|
||||
|
||||
.advance 2064
|
||||
|
||||
.require "kernal.oph"
|
||||
</programlisting>
|
||||
</section>
|
||||
<section id="kernal-src">
|
||||
<title id="kernal-fname"><filename>kernal.oph</filename></title>
|
||||
<programlisting>
|
||||
; KERNAL routine aliases (C64)
|
||||
|
||||
.alias acptr $ffa5
|
||||
.alias chkin $ffc6
|
||||
.alias chkout $ffc9
|
||||
.alias chrin $ffcf
|
||||
.alias chrout $ffd2
|
||||
.alias ciout $ffa8
|
||||
.alias cint $ff81
|
||||
.alias clall $ffe7
|
||||
.alias close $ffc3
|
||||
.alias clrchn $ffcc
|
||||
.alias getin $ffe4
|
||||
.alias iobase $fff3
|
||||
.alias ioinit $ff84
|
||||
.alias listen $ffb1
|
||||
.alias load $ffd5
|
||||
.alias membot $ff9c
|
||||
.alias memtop $ff99
|
||||
.alias open $ffc0
|
||||
.alias plot $fff0
|
||||
.alias ramtas $ff87
|
||||
.alias rdtim $ffde
|
||||
.alias readst $ffb7
|
||||
.alias restor $ff8a
|
||||
.alias save $ffd8
|
||||
.alias scnkey $ff9f
|
||||
.alias screen $ffed
|
||||
.alias second $ff93
|
||||
.alias setlfs $ffba
|
||||
.alias setmsg $ff90
|
||||
.alias setnam $ffbd
|
||||
.alias settim $ffdb
|
||||
.alias settmo $ffa2
|
||||
.alias stop $ffe1
|
||||
.alias talk $ffb4
|
||||
.alias tksa $ff96
|
||||
.alias udtim $ffea
|
||||
.alias unlsn $ffae
|
||||
.alias untlk $ffab
|
||||
.alias vector $ff8d
|
||||
|
||||
; Character codes for the colors.
|
||||
.alias color'0 144
|
||||
.alias color'1 5
|
||||
.alias color'2 28
|
||||
.alias color'3 159
|
||||
.alias color'4 156
|
||||
.alias color'5 30
|
||||
.alias color'6 31
|
||||
.alias color'7 158
|
||||
.alias color'8 129
|
||||
.alias color'9 149
|
||||
.alias color'10 150
|
||||
.alias color'11 151
|
||||
.alias color'12 152
|
||||
.alias color'13 153
|
||||
.alias color'14 154
|
||||
.alias color'15 155
|
||||
|
||||
; ...and reverse video
|
||||
.alias reverse'on 18
|
||||
.alias reverse'off 146
|
||||
|
||||
; ...and character set
|
||||
.alias upper'case 142
|
||||
.alias lower'case 14
|
||||
</programlisting>
|
||||
</section>
|
||||
<section id="tutor3-src">
|
||||
<title id="tutor3-fname"><filename>tutor3.oph</filename></title>
|
||||
<programlisting>
|
||||
.include "c64-1.oph"
|
||||
|
||||
.macro print
|
||||
ldx #0
|
||||
_loop: lda _1, x
|
||||
beq _done
|
||||
jsr chrout
|
||||
inx
|
||||
bne _loop
|
||||
_done:
|
||||
.macend
|
||||
|
||||
.macro greet
|
||||
`print hello1
|
||||
`print _1
|
||||
`print hello2
|
||||
.macend
|
||||
|
||||
lda #147
|
||||
jsr chrout
|
||||
`greet target1
|
||||
`greet target2
|
||||
`greet target3
|
||||
`greet target4
|
||||
`greet target5
|
||||
`greet target6
|
||||
`greet target7
|
||||
`greet target8
|
||||
`greet target9
|
||||
`greet target10
|
||||
rts
|
||||
|
||||
hello1: .byte "HELLO, ",0
|
||||
hello2: .byte "!", 13, 0
|
||||
|
||||
target1: .byte "PROGRAMMER", 0
|
||||
target2: .byte "ROOM", 0
|
||||
target3: .byte "BUILDING", 0
|
||||
target4: .byte "NEIGHBORHOOD", 0
|
||||
target5: .byte "CITY", 0
|
||||
target6: .byte "NATION", 0
|
||||
target7: .byte "WORLD", 0
|
||||
target8: .byte "SOLAR SYSTEM", 0
|
||||
target9: .byte "GALAXY", 0
|
||||
target10: .byte "UNIVERSE", 0
|
||||
</programlisting>
|
||||
</section>
|
||||
<section id="tutor4a-src">
|
||||
<title id="tutor4a-fname"><filename>tutor4a.oph</filename></title>
|
||||
<programlisting>
|
||||
.include "c64-1.oph"
|
||||
|
||||
.macro print
|
||||
ldx #0
|
||||
_loop: lda _1, x
|
||||
beq _done
|
||||
jsr chrout
|
||||
inx
|
||||
bne _loop
|
||||
_done:
|
||||
.macend
|
||||
|
||||
.macro greet
|
||||
lda #30
|
||||
jsr delay
|
||||
`print hello1
|
||||
`print _1
|
||||
`print hello2
|
||||
.macend
|
||||
|
||||
lda #147
|
||||
jsr chrout
|
||||
`greet target1
|
||||
`greet target2
|
||||
`greet target3
|
||||
`greet target4
|
||||
`greet target5
|
||||
`greet target6
|
||||
`greet target7
|
||||
`greet target8
|
||||
`greet target9
|
||||
`greet target10
|
||||
rts
|
||||
|
||||
hello1: .byte "HELLO, ",0
|
||||
hello2: .byte "!", 13, 0
|
||||
|
||||
target1: .byte "PROGRAMMER", 0
|
||||
target2: .byte "ROOM", 0
|
||||
target3: .byte "BUILDING", 0
|
||||
target4: .byte "NEIGHBORHOOD", 0
|
||||
target5: .byte "CITY", 0
|
||||
target6: .byte "NATION", 0
|
||||
target7: .byte "WORLD", 0
|
||||
target8: .byte "SOLAR SYSTEM", 0
|
||||
target9: .byte "GALAXY", 0
|
||||
target10: .byte "UNIVERSE", 0
|
||||
|
||||
; DELAY routine. Executes 2,560*(A) NOP statements.
|
||||
delay: tax
|
||||
ldy #00
|
||||
* nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
iny
|
||||
bne -
|
||||
dex
|
||||
bne -
|
||||
rts
|
||||
</programlisting>
|
||||
</section>
|
||||
<section id="tutor4b-src">
|
||||
<title id="tutor4b-fname"><filename>tutor4b.oph</filename></title>
|
||||
<programlisting>
|
||||
.include "c64-1.oph"
|
||||
|
||||
.macro print
|
||||
ldx #0
|
||||
_loop: lda _1, x
|
||||
beq _done
|
||||
jsr chrout
|
||||
inx
|
||||
bne _loop
|
||||
_done:
|
||||
.macend
|
||||
|
||||
.macro greet
|
||||
lda #30
|
||||
jsr delay
|
||||
`print hello1
|
||||
`print _1
|
||||
`print hello2
|
||||
.macend
|
||||
|
||||
lda #147
|
||||
jsr chrout
|
||||
lda #lower'case
|
||||
jsr chrout
|
||||
`greet target1
|
||||
`greet target2
|
||||
`greet target3
|
||||
`greet target4
|
||||
`greet target5
|
||||
`greet target6
|
||||
`greet target7
|
||||
`greet target8
|
||||
`greet target9
|
||||
`greet target10
|
||||
rts
|
||||
|
||||
hello1: .byte "Hello, ",0
|
||||
hello2: .byte "!", 13, 0
|
||||
|
||||
target1: .byte "programmer", 0
|
||||
target2: .byte "room", 0
|
||||
target3: .byte "building", 0
|
||||
target4: .byte "neighborhood", 0
|
||||
target5: .byte "city", 0
|
||||
target6: .byte "nation", 0
|
||||
target7: .byte "world", 0
|
||||
target8: .byte "Solar System", 0
|
||||
target9: .byte "Galaxy", 0
|
||||
target10: .byte "Universe", 0
|
||||
|
||||
; DELAY routine. Executes 2,560*(A) NOP statements.
|
||||
delay: tax
|
||||
ldy #00
|
||||
* nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
iny
|
||||
bne -
|
||||
dex
|
||||
bne -
|
||||
rts
|
||||
</programlisting>
|
||||
</section>
|
||||
<section id="tutor4c-src">
|
||||
<title id="tutor4c-fname"><filename>tutor4c.oph</filename></title>
|
||||
<programlisting>
|
||||
.include "c64-1.oph"
|
||||
|
||||
.macro print
|
||||
ldx #0
|
||||
_loop: lda _1, x
|
||||
beq _done
|
||||
jsr chrout
|
||||
inx
|
||||
bne _loop
|
||||
_done:
|
||||
.macend
|
||||
|
||||
.macro greet
|
||||
lda #30
|
||||
jsr delay
|
||||
`print hello1
|
||||
`print _1
|
||||
`print hello2
|
||||
.macend
|
||||
|
||||
lda #147
|
||||
jsr chrout
|
||||
lda #lower'case
|
||||
jsr chrout
|
||||
`greet target1
|
||||
`greet target2
|
||||
`greet target3
|
||||
`greet target4
|
||||
`greet target5
|
||||
`greet target6
|
||||
`greet target7
|
||||
`greet target8
|
||||
`greet target9
|
||||
`greet target10
|
||||
rts
|
||||
|
||||
.charmap 'A, "abcdefghijklmnopqrstuvwxyz"
|
||||
.charmap 'a, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
hello1: .byte "Hello, ",0
|
||||
hello2: .byte "!", 13, 0
|
||||
|
||||
target1: .byte "programmer", 0
|
||||
target2: .byte "room", 0
|
||||
target3: .byte "building", 0
|
||||
target4: .byte "neighborhood", 0
|
||||
target5: .byte "city", 0
|
||||
target6: .byte "nation", 0
|
||||
target7: .byte "world", 0
|
||||
target8: .byte "Solar System", 0
|
||||
target9: .byte "Galaxy", 0
|
||||
target10: .byte "Universe", 0
|
||||
|
||||
; DELAY routine. Executes 2,560*(A) NOP statements.
|
||||
delay: tax
|
||||
ldy #00
|
||||
* nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
iny
|
||||
bne -
|
||||
dex
|
||||
bne -
|
||||
rts
|
||||
</programlisting>
|
||||
</section>
|
||||
<section id="tutor5-src">
|
||||
<title id="tutor5-fname"><filename>tutor5.oph</filename></title>
|
||||
<programlisting>
|
||||
.include "c64-1.oph"
|
||||
|
||||
.data
|
||||
.org $C000
|
||||
.text
|
||||
|
||||
.macro print
|
||||
ldx #0
|
||||
_loop: lda _1, x
|
||||
beq _done
|
||||
jsr chrout
|
||||
inx
|
||||
bne _loop
|
||||
_done:
|
||||
.macend
|
||||
|
||||
.macro greet
|
||||
lda #30
|
||||
jsr delay
|
||||
`print hello1
|
||||
`print _1
|
||||
`print hello2
|
||||
.macend
|
||||
|
||||
lda #147
|
||||
jsr chrout
|
||||
`greet target1
|
||||
`greet target2
|
||||
`greet target3
|
||||
`greet target4
|
||||
`greet target5
|
||||
`greet target6
|
||||
`greet target7
|
||||
`greet target8
|
||||
`greet target9
|
||||
`greet target10
|
||||
rts
|
||||
|
||||
hello1: .byte "HELLO, ",0
|
||||
hello2: .byte "!", 13, 0
|
||||
|
||||
target1: .byte "PROGRAMMER", 0
|
||||
target2: .byte "ROOM", 0
|
||||
target3: .byte "BUILDING", 0
|
||||
target4: .byte "NEIGHBORHOOD", 0
|
||||
target5: .byte "CITY", 0
|
||||
target6: .byte "NATION", 0
|
||||
target7: .byte "WORLD", 0
|
||||
target8: .byte "SOLAR SYSTEM", 0
|
||||
target9: .byte "GALAXY", 0
|
||||
target10: .byte "UNIVERSE", 0
|
||||
|
||||
; 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
|
||||
|
||||
.checkpc $A000
|
||||
.data
|
||||
.checkpc $D000
|
||||
</programlisting>
|
||||
</section>
|
||||
<section id="tutor6-src">
|
||||
<title id="tutor6-fname"><filename>tutor6.oph</filename></title>
|
||||
<programlisting>
|
||||
.include "c64-1.oph"
|
||||
|
||||
.data
|
||||
.org $C000
|
||||
.space cache 2
|
||||
.text
|
||||
|
||||
.macro print
|
||||
lda #<_1
|
||||
ldx #>_1
|
||||
jsr printstr
|
||||
.macend
|
||||
|
||||
.macro greet
|
||||
lda #30
|
||||
jsr delay
|
||||
`print hello1
|
||||
`print _1
|
||||
`print hello2
|
||||
.macend
|
||||
|
||||
; Save the zero page locations that PRINTSTR uses.
|
||||
lda $10
|
||||
sta cache
|
||||
lda $11
|
||||
sta cache+1
|
||||
|
||||
lda #147
|
||||
jsr chrout
|
||||
`greet target1
|
||||
`greet target2
|
||||
`greet target3
|
||||
`greet target4
|
||||
`greet target5
|
||||
`greet target6
|
||||
`greet target7
|
||||
`greet target8
|
||||
`greet target9
|
||||
`greet target10
|
||||
|
||||
; Restore the zero page values printstr uses.
|
||||
lda cache
|
||||
sta $10
|
||||
lda cache+1
|
||||
sta $11
|
||||
|
||||
rts
|
||||
|
||||
hello1: .byte "HELLO, ",0
|
||||
hello2: .byte "!", 13, 0
|
||||
|
||||
target1: .byte "PROGRAMMER", 0
|
||||
target2: .byte "ROOM", 0
|
||||
target3: .byte "BUILDING", 0
|
||||
target4: .byte "NEIGHBORHOOD", 0
|
||||
target5: .byte "CITY", 0
|
||||
target6: .byte "NATION", 0
|
||||
target7: .byte "WORLD", 0
|
||||
target8: .byte "SOLAR SYSTEM", 0
|
||||
target9: .byte "GALAXY", 0
|
||||
target10: .byte "UNIVERSE", 0
|
||||
|
||||
; 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
|
||||
|
||||
; PRINTSTR routine. Accumulator stores the low byte of the address,
|
||||
; X register stores the high byte. Destroys the values of $10 and
|
||||
; $11.
|
||||
|
||||
.scope
|
||||
printstr:
|
||||
sta $10
|
||||
stx $11
|
||||
ldy #$00
|
||||
_lp: lda ($10),y
|
||||
beq _done
|
||||
jsr chrout
|
||||
iny
|
||||
bne _lp
|
||||
_done: rts
|
||||
.scend
|
||||
|
||||
.checkpc $A000
|
||||
.data
|
||||
.checkpc $D000
|
||||
</programlisting>
|
||||
</section>
|
||||
<section id="c64-2-src">
|
||||
<title id="c64-2-fname"><filename>c64-2.oph</filename></title>
|
||||
<programlisting>
|
||||
.word $0801
|
||||
.org $0801
|
||||
|
||||
.scope
|
||||
.word _next, 10 ; Next line and current line number
|
||||
.byte $9e," 2064",0 ; SYS 2064
|
||||
_next: .word 0 ; End of program
|
||||
.scend
|
||||
|
||||
.advance $0810
|
||||
|
||||
.require "kernal.oph"
|
||||
|
||||
.data zp
|
||||
.org $0002
|
||||
|
||||
.text
|
||||
|
||||
.scope
|
||||
; Cache BASIC's zero page at top of available RAM.
|
||||
ldx #$7E
|
||||
* lda $01, x
|
||||
sta $CF81, x
|
||||
dex
|
||||
bne -
|
||||
|
||||
jsr _main
|
||||
|
||||
; Restore BASIC's zero page and return control.
|
||||
|
||||
ldx #$7E
|
||||
* lda $CF81, x
|
||||
sta $01, x
|
||||
dex
|
||||
bne -
|
||||
rts
|
||||
|
||||
_main:
|
||||
; Program follows...
|
||||
.scend
|
||||
</programlisting>
|
||||
</section>
|
||||
<section id="tutor7-src">
|
||||
<title id="tutor7-fname"><filename>tutor7.oph</filename></title>
|
||||
<programlisting>
|
||||
.include "c64-2.oph"
|
||||
|
||||
.data
|
||||
.org $C000
|
||||
.text
|
||||
|
||||
.macro print
|
||||
lda #<_1
|
||||
ldx #>_1
|
||||
jsr printstr
|
||||
.macend
|
||||
|
||||
.macro greet
|
||||
lda #30
|
||||
jsr delay
|
||||
`print hello1
|
||||
`print _1
|
||||
`print hello2
|
||||
.macend
|
||||
|
||||
lda #147
|
||||
jsr chrout
|
||||
`greet target1
|
||||
`greet target2
|
||||
`greet target3
|
||||
`greet target4
|
||||
`greet target5
|
||||
`greet target6
|
||||
`greet target7
|
||||
`greet target8
|
||||
`greet target9
|
||||
`greet target10
|
||||
|
||||
rts
|
||||
|
||||
hello1: .byte "HELLO, ",0
|
||||
hello2: .byte "!", 13, 0
|
||||
|
||||
target1: .byte "PROGRAMMER", 0
|
||||
target2: .byte "ROOM", 0
|
||||
target3: .byte "BUILDING", 0
|
||||
target4: .byte "NEIGHBORHOOD", 0
|
||||
target5: .byte "CITY", 0
|
||||
target6: .byte "NATION", 0
|
||||
target7: .byte "WORLD", 0
|
||||
target8: .byte "SOLAR SYSTEM", 0
|
||||
target9: .byte "GALAXY", 0
|
||||
target10: .byte "UNIVERSE", 0
|
||||
|
||||
; 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
|
||||
|
||||
; PRINTSTR routine. Accumulator stores the low byte of the address,
|
||||
; X register stores the high byte. Destroys the values of $10 and
|
||||
; $11.
|
||||
|
||||
.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
|
||||
|
||||
.checkpc $A000
|
||||
|
||||
.data
|
||||
.checkpc $D000
|
||||
|
||||
.data zp
|
||||
.checkpc $80
|
||||
</programlisting>
|
||||
</section>
|
||||
</appendix>
|
315
doc/docbook/tutor1.sgm
Normal file
315
doc/docbook/tutor1.sgm
Normal file
@ -0,0 +1,315 @@
|
||||
<chapter id="part1">
|
||||
<title>The basics</title>
|
||||
|
||||
<para>
|
||||
In this first part of the tutorial we will create a
|
||||
simple <quote>Hello World</quote> program to run on the Commodore
|
||||
64. This will cover:
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>How to make programs run on a Commodore 64</para></listitem>
|
||||
<listitem><para>Writing simple code with labels</para></listitem>
|
||||
<listitem><para>Numeric and string data</para></listitem>
|
||||
<listitem><para>Invoking the assembler</para></listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
|
||||
<section>
|
||||
<title>A note on numeric notation</title>
|
||||
|
||||
<para>
|
||||
Throughout these tutorials, I will be using a lot of both
|
||||
decimal and hexadecimal notation. Hex numbers will have a
|
||||
dollar sign in front of them. Thus, 100 = $64, and $100 = 256.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Producing Commodore 64 programs</title>
|
||||
|
||||
<para>
|
||||
Commodore 64 programs are stored in
|
||||
the <filename>PRG</filename> format on disk. Some emulators
|
||||
(such as CCS64 or VICE) can run <filename>PRG</filename>
|
||||
programs directly; others need them to be transferred to
|
||||
a <filename>D64</filename> image first.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The <filename>PRG</filename> format is ludicrously simple. It
|
||||
has two bytes of header data: This is a little-endian number
|
||||
indicating the starting address. The rest of the file is a
|
||||
single continuous chunk of data loaded into memory, starting at
|
||||
that address. BASIC memory starts at memory location 2048, and
|
||||
that's probably where we'll want to start.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Well, not quite. We want our program to be callable from BASIC,
|
||||
so we should have a BASIC program at the start. We guess the
|
||||
size of a simple one line BASIC program to be about 16 bytes.
|
||||
Thus, we start our program at memory location 2064 ($0810), and
|
||||
the BASIC program looks like this:
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
10 SYS 2064
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
We <userinput>SAVE</userinput> this program to a file, then
|
||||
study it in a debugger. It's 15 bytes long:
|
||||
</para>
|
||||
|
||||
<screen>
|
||||
1070:0100 01 08 0C 08 0A 00 9E 20-32 30 36 34 00 00 00
|
||||
</screen>
|
||||
|
||||
<para>
|
||||
The first two bytes are the memory location: $0801. The rest of
|
||||
the data breaks down as follows:
|
||||
</para>
|
||||
|
||||
<table frame="all">
|
||||
<title>BASIC program breakdown</title>
|
||||
<tgroup cols='2'>
|
||||
<thead>
|
||||
<row>
|
||||
<entry align="center">Memory Locations</entry>
|
||||
<entry align="center">Value</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row><entry>$0801-$0802</entry><entry>2-byte pointer to the next line of BASIC code ($080C).</entry></row>
|
||||
<row><entry>$0803-$0804</entry><entry>2-byte line number ($000A = 10).</entry></row>
|
||||
<row><entry>$0805</entry><entry>Byte code for the <userinput>SYS</userinput> command.</entry></row>
|
||||
<row><entry>$0806-$080A</entry><entry>The rest of the line, which is just the string <quote> 2064</quote>.</entry></row>
|
||||
<row><entry>$080B</entry><entry>Null byte, terminating the line.</entry></row>
|
||||
<row><entry>$080C-$080D</entry><entry>2-byte pointer to the next line of BASIC code ($0000 = end of program).</entry></row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>
|
||||
That's 13 bytes. We started at 2049, so we need 2 more bytes of
|
||||
filler to make our code actually start at location 2064. These
|
||||
17 bytes will give us the file format and the BASIC code we need
|
||||
to have our machine language program run.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
These are just bytes—indistinguishable from any other sort of
|
||||
data. In Ophis, bytes of data are specified with
|
||||
the <literal>.byte</literal> command. We'll also have to tell
|
||||
Ophis what the program counter should be, so that it knows what
|
||||
values to assign to our labels. The <literal>.org</literal>
|
||||
(origin) command tells Ophis this. Thus, the Ophis code for our
|
||||
header and linking info is:
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
.byte $01, $08, $0C, $08, $0A, $00, $9E, $20
|
||||
.byte $32, $30, $36, $34, $00, $00, $00, $00
|
||||
.byte $00, $00
|
||||
.org $0810
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
This gets the job done, but it's completely incomprehensible,
|
||||
and it only uses two directives—not very good for a
|
||||
tutorial. Here's a more complicated, but much clearer, way of
|
||||
saying the same thing.
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
.word $0801
|
||||
.org $0801
|
||||
|
||||
.word next, 10 ; Next line and current line number
|
||||
.byte $9e," 2064",0 ; SYS 2064
|
||||
next: .word 0 ; End of program
|
||||
|
||||
.advance 2064
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
This code has many advantages over the first.
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para> It describes better what is actually
|
||||
happening. The <literal>.word</literal> directive at the
|
||||
beginning indicates a 16-bit value stored in the typical
|
||||
65xx way (small byte first). This is followed by
|
||||
an <literal>.org</literal> statement, so we let the
|
||||
assembler know right away where everything is supposed to
|
||||
be.
|
||||
</para></listitem>
|
||||
<listitem><para> Instead of hardcoding in the value $080C, we
|
||||
instead use a label to identify the location it's pointing
|
||||
to. Ophis will compute the address
|
||||
of <literal>next</literal> and put that value in as data.
|
||||
We also describe the line number in decimal since BASIC
|
||||
line numbers generally <emphasis>are</emphasis> in decimal.
|
||||
Labels are defined by putting their name, then a colon, as
|
||||
seen in the definition of <literal>next</literal>.
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
Instead of putting in the hex codes for the string part of
|
||||
the BASIC code, we included the string directly. Each
|
||||
character in the string becomes one byte.
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
Instead of adding the buffer ourselves, we
|
||||
used <literal>.advance</literal>, which outputs zeros until
|
||||
the specified address is reached. Attempting
|
||||
to <literal>.advance</literal> backwards produces an
|
||||
assemble-time error.
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
It has comments that explain what the data are for. The
|
||||
semicolon is the comment marker; everything from a semicolon
|
||||
to the end of the line is ignored.
|
||||
</para></listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Related commands and options</title>
|
||||
|
||||
<para>
|
||||
This code includes constants that are both in decimal and in
|
||||
hex. It is also possible to specify constants in octal, binary,
|
||||
or with an ASCII character.
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>To specify decimal constants, simply write the number.</para></listitem>
|
||||
<listitem><para>To specify hexadecimal constants, put a $ in front.</para></listitem>
|
||||
<listitem><para>To specify octal constants, put a 0 (zero) in front.</para></listitem>
|
||||
<listitem><para>To specify binary constants, put a % in front.</para></listitem>
|
||||
<listitem><para>To specify ASCII constants, put an apostrophe in front.</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
Example: 65 = $41 = 0101 = %1000001 = 'A
|
||||
</para>
|
||||
<para>
|
||||
There are other commands besides <literal>.byte</literal>
|
||||
and <literal>.word</literal> to specify data. In particular,
|
||||
the <literal>.dword</literal> command specifies four-byte values
|
||||
which some applications will find useful. Also, some linking
|
||||
formats (such as the <filename>SID</filename> format) have
|
||||
header data in big-endian (high byte first) format.
|
||||
The <literal>.wordbe</literal> and <literal>.dwordbe</literal>
|
||||
directives provide a way to specify multibyte constants in
|
||||
big-endian formats cleanly.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Writing the actual code</title>
|
||||
|
||||
<para>
|
||||
Now that we have our header information, let's actually write
|
||||
the <quote>Hello world</quote> program. It's pretty
|
||||
short—a simple loop that steps through a hardcoded array
|
||||
until it reaches a 0 or outputs 256 characters. It then returns
|
||||
control to BASIC with an <literal>RTS</literal> statement.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Each character in the array is passed as an argument to a
|
||||
subroutine at memory location $FFD2. This is part of the
|
||||
Commodore 64's BIOS software, which its development
|
||||
documentation calls the KERNAL. Location $FFD2 prints out the
|
||||
character corresponding to the character code in the
|
||||
accumulator.
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
ldx #0
|
||||
loop: lda hello, x
|
||||
beq done
|
||||
jsr $ffd2
|
||||
inx
|
||||
bne loop
|
||||
done: rts
|
||||
|
||||
hello: .byte "HELLO, WORLD!", 0
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
The complete, final source is available in
|
||||
the <xref linkend="tutor1-src" endterm="tutor1-fname"> file.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Assembling the code</title>
|
||||
|
||||
<para>
|
||||
The Ophis assembler is a collection of Python modules,
|
||||
controlled by a master script. On Windows, this should all
|
||||
have been combined into an executable
|
||||
file <command>ophis.exe</command>; on other platforms, the
|
||||
Ophis modules should be in the library and
|
||||
the <command>ophis</command> script should be in your path.
|
||||
Typing <command>ophis</command> with no arguments should give a
|
||||
summary of available command line options.
|
||||
</para>
|
||||
|
||||
<table frame="all">
|
||||
<title>Ophis Options</title>
|
||||
<tgroup cols='2'>
|
||||
<thead>
|
||||
<row>
|
||||
<entry align="center">Option</entry>
|
||||
<entry align="center">Effect</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row><entry><option>-6510</option></entry><entry>Allows the 6510 undocumented opcodes as listed in the VICE documentation.</entry></row>
|
||||
<row><entry><option>-65c02</option></entry><entry>Allows opcodes and addressing modes added by the 65C02.</entry></row>
|
||||
<row><entry><option>-v 0</option></entry><entry>Quiet operation. Only reports errors.</entry></row>
|
||||
<row><entry><option>-v 1</option></entry><entry>Default operation. Reports files as they are loaded, and gives statistics on the final output.</entry></row>
|
||||
<row><entry><option>-v 2</option></entry><entry>Verbose operation. Names each assembler pass as it runs.</entry></row>
|
||||
<row><entry><option>-v 3</option></entry><entry>Debug operation: Dumps the entire IR after each pass.</entry></row>
|
||||
<row><entry><option>-v 4</option></entry><entry>Full debug operation: Dumps the entire IR and symbol table after each pass.</entry></row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>
|
||||
The only options Ophis demands are an input file and an output
|
||||
file. Here's a sample session, assembling the tutorial file
|
||||
here:
|
||||
</para>
|
||||
<screen>
|
||||
localhost$ ophis tutor1.oph tutor1.prg -v 2
|
||||
Loading tutor1.oph
|
||||
Running: Macro definition pass
|
||||
Running: Macro expansion pass
|
||||
Running: Label initialization pass
|
||||
Fixpoint failed, looping back
|
||||
Running: Label initialization pass
|
||||
Running: Circularity check pass
|
||||
Running: Expression checking pass
|
||||
Running: Easy addressing modes pass
|
||||
Running: Label Update Pass
|
||||
Fixpoint failed, looping back
|
||||
Running: Label Update Pass
|
||||
Running: Instruction Collapse Pass
|
||||
Running: Mode Normalization pass
|
||||
Running: Label Update Pass
|
||||
Running: Assembler
|
||||
Assembly complete: 45 bytes output (14 code, 29 data, 2 filler)
|
||||
</screen>
|
||||
<para>
|
||||
If your emulator can run <filename>PRG</filename> files
|
||||
directly, this file will now run (and
|
||||
print <computeroutput>HELLO, WORLD!</computeroutput>) as many
|
||||
times as you type <userinput>RUN</userinput>. Otherwise, use
|
||||
a <filename>D64</filename> management utility to put
|
||||
the <filename>PRG</filename> on a <filename>D64</filename>, then
|
||||
load and run the file off that.
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
107
doc/docbook/tutor2.sgm
Normal file
107
doc/docbook/tutor2.sgm
Normal file
@ -0,0 +1,107 @@
|
||||
<chapter>
|
||||
<title>Labels and aliases</title>
|
||||
<para>
|
||||
Labels are an important part of your code. However, since each
|
||||
label must normally be unique, this can lead to <quote>namespace
|
||||
pollution,</quote> and you'll find yourself going through ever
|
||||
more contorted constructions to generate unique label names.
|
||||
Ophis offers two solutions to this: <emphasis>anonymous
|
||||
labels</emphasis> and <emphasis>temporary labels</emphasis>. This
|
||||
tutorial will cover both of these facilities, and also introduce
|
||||
the aliasing mechanism.
|
||||
</para>
|
||||
|
||||
<section>
|
||||
<title>Temporary labels</title>
|
||||
|
||||
<para>
|
||||
Temporary labels are the easiest to use. If a label begins with
|
||||
an underscore, it will only be reachable from inside the
|
||||
innermost enclosing scope. Scopes begin when
|
||||
a <literal>.scope</literal> statement is encountered. This
|
||||
produces a new, inner scope if there is another scope in use.
|
||||
The <literal>.scend</literal> command ends the innermost
|
||||
currently active scope.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
We can thus rewrite our header data using temporary labels, thus
|
||||
allowing the main program to have a label
|
||||
named <literal>next</literal> if it wants.
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
.word $0801
|
||||
.org $0801
|
||||
|
||||
.scope
|
||||
.word _next, 10 ; Next line and current line number
|
||||
.byte $9e," 2064",0 ; SYS 2064
|
||||
_next: .word 0 ; End of program
|
||||
.scend
|
||||
|
||||
.advance 2064
|
||||
</programlisting>
|
||||
|
||||
</section>
|
||||
<section>
|
||||
<title>Anonymous labels</title>
|
||||
<para>
|
||||
Anonymous labels are a way to handle short-ranged branches
|
||||
without having to come up with names for the then and else
|
||||
branches, for brief loops, and other such purposes. To define
|
||||
an anonymous label, use an asterisk. To refer to an anonymous
|
||||
label, use a series of <literal>+</literal>
|
||||
or <literal>-</literal> signs. <literal>+</literal> refers to
|
||||
the next anonymous label, <literal>++</literal> the label
|
||||
after that, etc. Likewise, <literal>-</literal> is the most
|
||||
recently defined label, <literal>--</literal> the one before
|
||||
that, and so on. The main body of the Hello World program
|
||||
with anonymous labels would be:
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
ldx #0
|
||||
* lda hello, x
|
||||
beq +
|
||||
jsr $ffd2
|
||||
inx
|
||||
bne -
|
||||
* rts
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
It is worth noting that anonymous labels are globally available.
|
||||
They are not temporary labels, and they ignore scoping
|
||||
restrictions.
|
||||
</para>
|
||||
</section>
|
||||