Update documentation and examples for version 2.2

This commit is contained in:
Michael Martin 2024-07-24 23:17:42 -07:00
parent 94d0b9d0c4
commit f957df94f2
17 changed files with 241 additions and 83 deletions

Binary file not shown.

View File

@ -20,7 +20,7 @@
<bookinfo>
<title>Programming with Ophis</title>
<author><firstname>Michael</firstname><surname>Martin</surname></author>
<copyright><year>2006-2014</year><holder>Michael Martin</holder></copyright>
<copyright><year>2006-2024</year><holder>Michael Martin</holder></copyright>
</bookinfo>
&pre1;
<part label="I">

View File

@ -6,7 +6,7 @@
consoles. Its primary design goals are code readability and output
flexibility - Ophis has successfully been used to create programs
for the Nintendo Entertainment System, the Atari 2600, and various
8-bit Commodore machines.
8-bit Commodore and Apple machines.
</para>
<para>
Ophis's syntax is noticably different from the formats
@ -39,20 +39,20 @@
cross-assembler for the 6502 chip the C64 used in both.
</para>
<para>
The Perl one&mdash;uncreatively
dubbed <quote>Perl65</quote>&mdash;was quickly abandoned, but
the Python one saw more work. When it came time to name it, one
of the things I had been hoping to do with the assembler was to
The Perl one&mdash;uncreatively dubbed
<quote>Perl65</quote>&mdash;was quickly abandoned, but the
Python one saw more work. When it came time to name it, 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.<footnote><para>Ironically, cross-platform development
for the Apple II is extremely difficult, and while Ophis has
been very successfully used to develop code for the Commodore
64, Nintendo Entertainment System, and Atari 2600, it has yet to
actually be deployed on any of the Apples which inspired its
for the Apple II is much less straightforward than for the
Commodore 8-bits or ROM-based consoles, and it took many years
after its release before it was actually used to write code
deployed on any of the Apples which inspired its
name.</para></footnote>
</para>
<para>
@ -68,18 +68,38 @@
</para>
<para>
After its release Ophis 2 was picked up by a number of
developers work with actual hardware from the period, including
prototype machines that never saw production. Some of their
contributions have refined the code generators for version 2.1.
developers working with actual hardware from the period,
including prototype machines that never saw production. Some
of their contributions have refined the code generators for
version 2.1.
</para>
<para>
This is an updated edition of <emphasis>Programming With
Ophis</emphasis>, including documentation for all new features
introduced and expanding the examples to include simple
demonstration programs for platforms besides the Commodore
64. It also includes updated versions of the <emphasis>To HLL
and Back</emphasis> essays I wrote using Ophis and Perl65 as
example languages.
At that point, the program was basically done, and very little
changes for about five years. The world, however, moved on, and
Python 2, my implementation language, was deprecated and
rendered obsolete. That didn't change much about the 2.1
release&mdash;Python 2 was still installed on non-Windows
machines by default, and the Windows distribution was as a
bundled .EXE file&mdash;but it threatened the viability of the
program overall. In 2019, then, I converted the source base to
the backwards-incompatible Python 3, in the hopes of
future-proofing the system. Five years after
<emphasis>that</emphasis>, enough bug reports and bug fixes had
trickled in to justify a fresh release, and 2.2 was published in
2024&mdash;a lightly polished update that now fit more neatly
into the Python toolchains of the 2020s.
</para>
<para>
In the twenty years since I first started this project, I've
gained quite a bit more experience with programming the computer
systems of the 1970s and 1980s. I have left this manual largely
as it was in its 2014 edition, including its versions of the
<emphasis>To HLL and Back</emphasis> essays I wrote using Ophis
and Perl65 as example languages. I don't think I stand behind my
design decisions back then as firmly as I did when I wrote those
essays, but there's noting <emphasis>wrong</emphasis> with them
either so I'm happy to leave them as a testament to my younger,
brasher self.
</para>
</section>

View File

@ -708,7 +708,7 @@ _done: rts
.checkpc $D000
.data zp
.checkpc $80
.checkpc $90
</programlisting>
</section>
<section id="structure-src">

View File

@ -58,11 +58,11 @@
<para>
We <userinput>SAVE</userinput> this program to a file, then
study it in a debugger. It's 15 bytes long:
study it with a hex dumper. It's 15 bytes long:
</para>
<screen>
1070:0100 01 08 0C 08 0A 00 9E 20-32 30 36 34 00 00 00
00000000 01 08 0c 08 0a 00 9e 20 32 30 36 34 00 00 00 |....... 2064...|
</screen>
<para>
@ -72,29 +72,32 @@
<table frame="all">
<title>BASIC program breakdown</title>
<tgroup cols='2'>
<tgroup cols='3'>
<thead>
<row>
<entry align="center">File Offsets</entry>
<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>
<row><entry>0-1</entry><entry>Nowhere</entry><entry>2-byte pointer to where in memory to load the rest of the file ($0801).</entry></row>
<row><entry>2-3</entry><entry>$0801-$0802</entry><entry>2-byte pointer to the next line of BASIC code ($080C).</entry></row>
<row><entry>4-5</entry><entry>$0803-$0804</entry><entry>2-byte line number ($000A = 10).</entry></row>
<row><entry>6</entry><entry>$0805</entry><entry>Byte code for the <userinput>SYS</userinput> command.</entry></row>
<row><entry>7-11</entry><entry>$0806-$080A</entry><entry>The rest of the line, which is just the string <quote> 2064</quote>.</entry></row>
<row><entry>12</entry><entry>$080B</entry><entry>Null byte, terminating the line.</entry></row>
<row><entry>13-14</entry><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.
That's 15 bytes, of which 13 are actually loaded into memory.
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>
@ -176,6 +179,16 @@ next: .word 0 ; End of program
</para></listitem>
</itemizedlist>
</para>
<para>
We can do better still, though. That initial starting address
of 2064 was only ever a guess; now that we know that we overshot
by two bytes, we can simply change the starting address to 2062
and omit the <literal>.advance</literal> directive entirely. In
fact, we can even remove the space before the number and make it
2061 instead&mdash;BASIC doesn't need that space in its
instruction and it's arguably a wasted byte.
</para>
</section>
<section>

View File

@ -25,9 +25,9 @@
</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.
We can rewrite our header data using temporary labels, allowing
the main program to have a label named <literal>next</literal>
if it wants.
</para>
<programlisting>

View File

@ -24,7 +24,8 @@
<para>
A related directive, <literal>.require</literal>, will include
the file as long as it hasn't been included yet elsewhere. It
is useful for ensuring a library is linked in.
is useful for ensuring a library is present somewhere in the
final code.
</para>
<para>
@ -132,11 +133,6 @@ _done:
<section>
<title>Macro invocations</title>
<para>
Macros may be invoked in two ways: one that looks like a
directive, and one that looks like an instruction.
</para>
<para>
The most common way to invoke a macro is to backquote the name
of the macro. It is also possible to use

View File

@ -104,4 +104,12 @@ target10: .byte "Universe", 0
provided with the sample programs
as <filename>petscii.map</filename>.
</para>
<para>
Versions of Ophis prior to 2.2 have a bug where only the first
argument to <literal>.byte</literal> would be translated. That's
fine for our example code here, with only one string per line, but
a more text-heavy title that relied on this should confirm their
version before getting too far in.
</para>
</chapter>

View File

@ -91,7 +91,11 @@ delay: sta _tmp ; save argument (rdtim destroys it)
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.
segment, but this is nearly always wrong. Remember,
both <literal>.org</literal> and <literal>.space</literal> only
ever alter the way that Ophis computes labels. They do not output
any bytes, nor do they change where in the output file the bytes
are actually written.
</para>
<para>

View File

@ -110,8 +110,8 @@ _done: rts
</table>
<para>
Note that brackets, not parentheses, are used to group arithmetic
operations. This is because parentheses are used for the indirect
addressing modes, and it makes parsing much easier.
operations. Parentheses are reserved for the indirect addressing
modes.
</para>
<para>

View File

@ -130,7 +130,7 @@ _done: rts
<programlisting>
.data zp
.checkpc $80
.checkpc $90
</programlisting>
<para>

View File

@ -1,5 +1,5 @@
<chapter>
<title>Platform-Specific Techniques</title>
<title>Included Platform Support</title>
<para>
Ophis is intended to produce cross-assembled binaries that will
@ -18,9 +18,7 @@
In a real sense, the Commodore 64 is the &quot;native&quot;
target platform for Ophis. It was the first platform targeted
and it's the one that has received the most additional
support. It's also one where the developer needs to take the
most care about exactly what kind of program they are
writing.
support.
</para>
<itemizedlist>
@ -166,27 +164,33 @@
<title>The Nintendo Entertainment System</title>
<para>
The NES development community is somewhat more fragmented than
the others. A skeletal <literal>nes.oph</literal> file is
provided, but memory locations are not as consistently
named. Much sample code doesn't provide aliases for control
registers at all.
The NES development community in 2024 has standardized on the
sophisticated <literal>ca65</literal> assembler for major
homebrew projects, but Ophis's simpler output model has
advantages of its own. A skeletal <literal>nes.oph</literal>
file is provided in the platform support directory, but most
NES code you'll find in the wild doesn't use aliases for control
registers at all&mdash;it just sticks with the register numbers.
</para>
<para>
Conveniently creating runnable NES programs is somewhat
involved. Any given product was generally burned onto several
chips that were affixed to one of a large number of circuit
boards. These are often referred to as &quot;mappers&quot; by
developers because their effect is to implement various
bankswitching schemes. The result is a program built out of
parts, each with its own origin. A &quot;Hello World&quot;
sample program ships with Ophis. It does not use a bankswitcher,
but it does split its contents into a program chip and a
graphics chip, with one of two wrapper files to knit them
together into a file that other software will recognize. Samples
are given for the common iNES format and the defunct UNIF
format.
Creating output files that emulators or other tools will
recognize as complete NES programs is somewhat involved.
Any given product was generally one of a large selection of
circuit boards with several ROM or support-logic chips
affixed to it. These circuit board configurations are generally
referred to as &quot;mappers&quot; by developers because their
effect is to implement various bankswitching schemes. The result
is a program built out of parts, each with its own origin.
A simple <quote>Hello World</quote> sample program ships with
Ophis. It is configured to use "Mapper Zero", or a simulation of
the <quote>NROM</quote> circuit board, which had no special
bankswitching logic and simply wired the program chip and the
graphics chip directly into the address bus. The sample code
includes one source file for each chip, and then two wrapper
files to knit them together into a file that other software will
recognize. As of 2024, the UNIF format is entirely abandoned in
favor of the backwards-compatible iNES 2.0 format.
</para>
</section>
@ -194,18 +198,66 @@
<title>The Atari 2600 VCS</title>
<para>
Of all the 8-bit development communities, the Atari developers
seem to be the most cohesive. The development documents
available are universal, and analysts and developers alike all
use the register names in the <emphasis>Stella Developer's
Guide</emphasis>. Ophis follows their lead, providing these
names in the header <literal>stella.oph</literal>.
Ophis provides a <literal>stella.oph</literal> header that names
the system's registers to match the documentation in
the <emphasis>Stella Programmer's Guide</emphasis>. It also
replicates two macros that were widely shared on mailing lists
and other tutorial documents at the time Ophis was first
released. See the file itself for details.
</para>
<para>
The <literal>stella.oph</literal> header also replicates two
macros that appear in the header files distributed to budding
VCS developers. They are documented in the file.
Atari 2600 ROM images are simple ROM dumps and do not require
any more sophisticated organization in the Ophis source files
than an <literal>.advance</literal> directive to pad the output
to the appropriate size.
</para>
<para>
Two sample programs ship with Ophis 2.2; a tiny hello-world
program, and a more sophisticated interactive program that
explores the system's color palette.
</para>
</section>
<section>
<title>Other Atari 8-bits</title>
<para>
The Atari 2600's successor, the Atari 5200, shares much of its
architecture with the Atari 400/800/1200/XL/XE line. Atari DOS
had an executable format that divided itself up into chunks that
were independently loaded, with some chunks being special and
identifying program entry points or intervening processing to be
done mid-load.
</para>
<para>
A simple Hello World program compatible with Atari DOS is
included in the examples directory. The output file may be
loaded and run directly in many emulators, or may be copied
into a disk image with a tool like <literal>atr</literal> or
Altirra and executed from the DOS prompt.
</para>
</section>
<section>
<title>The Apple II series</title>
<para>
For most of its lifespan, Apple II systems ran either a
primitive system named "DOS 3.3" or more sophisticated one
named ProDOS. ProDOS 8 is as of 2024 still under active
development, and its superior support for machine-language
interfacing with the disk drive makes it the preferable
choice for Ophis-based development.
</para>
<para>
A simple Hello World program is included in the examples
directory. To actually run the resulting binary, it must be
added to a ProDOS-formatted disk using a tool such as CADIUS
or CiderPress.
</para>
</section>
</chapter>

View File

@ -94,4 +94,4 @@ _done: rts
.checkpc $D000
.data zp
.checkpc $80
.checkpc $90

34
examples/hello_a800.oph Normal file
View File

@ -0,0 +1,34 @@
.outfile "hello.obj"
.word $ffff ; Binary file
.word start ; start of code
.word end-1 ; end of code
.org $0600 ; Load into page 6
;; `iostob OFFSET, VALUE
;; `iostow OFFSET, VALUE
;; Store value in OFFSET in I/O control block X>>4.
.macro iostob
lda #_2
sta $340+_1,x
.macend
.macro iostow
`iostob _1,<_2
`iostob _1+1,>_2
.macend
start: ldx #$00 ; Channel 0 (E:)
;; Write message with one I/O call, and exit
`iostob 2,11 ; WRITE command
`iostow 4,msg ; buffer pointer
`iostow 8,msgend-msg ; buffer length
jmp $e456 ; Do I/O call and quit
msg: .byte "Hello, world!",$9b
msgend: ; End of message
end: ; End of code block
;; Autostart at start
.word $02e0,$02e1,start

31
examples/hello_apple2.oph Normal file
View File

@ -0,0 +1,31 @@
;;; ----------------------------------------------------------------------
;;; HELLO WORLD for the Apple II
;;; This is a ProDOS 8 program. Its output should be importable by
;;; CiderPress without incident.
;;; ----------------------------------------------------------------------
.outfile "HI.SYSTEM#ff2000"
.org $2000
;; Write message
ldx #$00
* lda msg,x
beq wait
ora #$80 ; Disable inverse
jsr $fded ; CHROUT
inx
bne -
;; Wait for keypress
wait: bit $c000 ; Check keypress bit
bpl wait
bit $c010 ; Acknowledge keypress
;; Return to ProDOS
jsr $bf00
.byte $65
.word +
brk ; Unreachable
* .byte 4, 0, 0, 0, 0, 0, 0
msg: .byte "HELLO, WORLD!",13,"PRESS ANY KEY TO EXIT",0

View File

@ -1,9 +1,9 @@
"Hi Stella" is a simple "Hello World" program for the "Stella" chip,
more famously known as the Atari 2600. Simply running
"Hi Stella" is a simple "Hello World" program for the "Stella"
system, more famously known as the Atari 2600. Simply running
ophis hi_stella.oph
should produce hi_stella.bin, a 256-byte file that prints "HI" on
should produce hi_stella.bin, a simple demo that prints "HI" on
the screen with some rolling color bars.
A more sophisticated program is colortest, which lets the user

View File

@ -36,7 +36,7 @@ def parse_args(raw_args):
parser = optparse.OptionParser(
usage="Usage: %prog [options] srcfile [srcfile ...]",
version="Ophis 6502 cross-assembler, version 2.1")
version="Ophis 6502 cross-assembler, version 2.2")
parser.add_option("-o", default=None, dest="outfile",
help="Output filename (default 'ophis.bin')")