rethinking some aspects and rewriting text a bit

This commit is contained in:
Irmen de Jong 2018-07-01 23:24:32 +02:00
parent 1ea7c015c8
commit bc8d56cbd7
11 changed files with 340 additions and 295 deletions

3
.gitignore vendored
View File

@ -7,13 +7,12 @@
/build/
/dist/
/output/
.cache/
.*cache/
.eggs/
*.directory
*.prg
*.asm
*.labels.txt
.mypy_cache/
__pycache__/
parser.out
parsetab.py

View File

@ -127,7 +127,7 @@ Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(kernal, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

View File

@ -21,13 +21,12 @@ which aims to provide many conveniences over raw assembly code (even when using
- automatic type conversions
- floating point operations
- optional automatic preserving and restoring CPU registers state, when calling routines that otherwise would clobber these
- abstracting away low level aspects such as zero page handling, program startup, explicit memory addresses
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
- conditional gotos
- various code optimizations (code structure, logical and numerical expressions, ...)
- @todo: loops
- @todo: memory block operations
It still allows for low level programming however and inline assembly blocks
to write performance critical pieces of code, but otherwise compiles fairly straightforwardly

View File

@ -1,133 +1,171 @@
IL65 / 'Sick' - Experimental Programming Language for 8-bit 6502/6510 microprocessors
=====================================================================================
What is a Program?
------------------
*Written by Irmen de Jong (irmen@razorvine.net)*
A "complete program" is a compiled, assembled, and linked together single unit.
It contains all of the program's code and data and has a certain file format that
allows it to be loaded directly on the target system.
*Software license: GNU GPL 3.0, see file LICENSE*
Most programs will need a tiny BASIC launcher that does a SYS into the generated machine code,
but it is also possible to output just binary programs that can be loaded into memory elsewhere.
Compiling a program
-------------------
Compilation of a program is done by compiling just the main source code module file.
Other modules that the code needs can be imported from within the file.
The compiler will eventually link them together into one output program.
Program code structure
----------------------
A program is created by compiling and linking *one or more module source code files*.
### Module file
This is a file with the ``.ill`` suffix, without spaces in its name, containing:
- source code comments
- global program options
- imports of other modules
- one or more *code blocks*
The filename doesn't really matter as long as it doesn't contain spaces.
The full name of the symbols defined in the file is not impacted by the filename.
#### Source code comments
A=5 ; set the initial value to 5
; next is the code that...
In any file, everything after a semicolon '``;``' is considered a comment and is ignored by the compiler.
If all of the line is just a comment, it will be copied into the resulting assembly source code.
This makes it easier to understand and relate the generated code.
This is an experimental programming language for the 8-bit 6502/6510 microprocessor from the late 1970's and 1980's
as used in many home computers from that era. IL65 is a medium to low level programming language,
which aims to provide many conveniences over raw assembly code (even when using a macro assembler):
#### Things global to the program
- reduction of source code length
- easier program understanding (because it's higher level, and more terse)
- option to automatically run the compiled program in the Vice emulator
- modularity, symbol scoping, subroutines
- subroutines have enforced input- and output parameter definitions
- various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs)
- automatic variable allocations, automatic string variables and string sharing
- constant folding in expressions (compile-time evaluation)
- automatic type conversions
- floating point operations
- optional automatic preserving and restoring CPU registers state, when calling routines that otherwise would clobber these
- abstracting away low level aspects such as zero page handling, program startup, explicit memory addresses
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
- conditional gotos
- various code optimizations (code structure, logical and numerical expressions, ...)
- @todo: loops
- @todo: memory block operations
The global program options that can be put at the top of a module file,
determine the settings for the entire output program.
They're all optional (defaults will be chosen as mentioned below).
If specified though, they can only occur once in the entire program:
%output prg
%address $0801
%launcher none
%zp compatible
It still allows for low level programming however and inline assembly blocks
to write performance critical pieces of code, but otherwise compiles fairly straightforwardly
into 6502 assembly code. This resulting code is assembled into a binary program by using
an external macro assembler, [64tass](https://sourceforge.net/projects/tass64/).
It can be compiled pretty easily for various platforms (Linux, Mac OS, Windows) or just ask me
to provide a small precompiled executable if you need that.
You need [Python 3.5](https://www.python.org/downloads/) or newer to run IL65 itself.
##### ``%output`` : select output format of the program
- ``raw`` : no header at all, just the raw machine code data
- ``prg`` : C64 program (with load address header)
IL65 is mainly targeted at the Commodore-64 machine, but should be mostly system independent.
The default is ``prg``.
Memory Model
------------
##### ``%address`` : specify start address of the code
Most of the 64 kilobyte address space can be accessed by your program.
| type | memory area | note |
|-----------------|-------------------------|-----------------------------------------------------------------|
| Zero page | ``$00`` - ``$ff`` | contains many sensitive system variables |
| Hardware stack | ``$100`` - ``$1ff`` | is used by the CPU and should normally not be accessed directly |
| Free RAM or ROM | ``$0200`` - ``$ffff`` | free to use memory area, often a mix of RAM and ROM |
- default for ``raw`` output is $c000
- default for ``prg`` output is $0801
- cannot be changed if you select ``prg`` with a ``basic`` launcher;
then it is always $081d (immediately after the BASIC program), and the BASIC program itself is always at $0801.
This is because the C64 expects BASIC programs to start at this address.
A few memory addresses are reserved and cannot be used freely by your own code,
because they have a special hardware function, or are reserved for internal use by the compiler:
##### ``%launcher`` : specify launcher type
| reserved | address |
|----------------|------------|
| data direction | ``$00`` |
| bank select | ``$01`` |
| scratch var #1 | ``$02`` |
| scratch var #2 | ``$03`` |
| NMI vector | ``$fffa`` |
| RESET vector | ``$fffc`` |
| IRQ vector | ``$fffe`` |
A particular 6502/6510 machine such as the Commodore-64 will have many other special addresses due to:
- ROMs installed in the machine (BASIC, kernel and character generator roms)
- memory-mapped I/O registers (for the video and sound chip for example)
- RAM areas used for screen graphics and sprite data.
Only relevant when using the ``prg`` output type. Defaults to ``basic``.
- ``basic`` : add a tiny C64 BASIC program, whith a SYS statement calling into the machine code
- ``none`` : no launcher logic is added at all
### Usable Hardware Registers
##### ``%zp`` : select ZeroPage behavior
The following 6502 hardware registers are directly accessible in your code (and are reserved symbols):
- ``A``, ``X``, ``Y``
- ``AX``, ``AY``, ``XY`` (surrogate registers: 16-bit combined register pairs in LSB byte order lo/hi)
- ``SC`` (status register's Carry flag)
- ``SI`` (status register's Interrupt Disable flag)
- ``compatible`` : only use a few free locations in the ZP
- ``full`` : use the whole ZP for variables, makes the program faster but can't use BASIC or KERNAL routines anymore, and cannot exit cleanly
- ``full-restore`` : like ``full``, but makes a backup copy of the original values at program start.
These are restored when exiting the program back to the BASIC prompt
Defaults to ``compatible``.
The exact meaning of these options can be found in the paragraph
about the ZeroPage in the system documentation.
### Zero Page ("ZP")
##### Program Start and Entry Point
The zero page locations ``$02`` - ``$ff`` can be regarded as 254 other registers because
they take less clock cycles to access and need fewer instruction bytes than access to other memory locations.
Theoretically you can use all of them in your program but there are a few limitations:
- several locations (``$02``, ``$03``, ``$fb - $fc``, ``$fd - $fe``) are reserved for internal use as scratch registers by IL65
- most other addresses often are in use by the machine's operating system or kernal,
and overwriting them can crash the machine. Your program must take over the entire
system to be able to safely use all zero page locations.
- it's often more convenient to let IL65 allocate the particular locations for you and just
use symbolic names in your code.
Your program must have a single entry point where code execution begins.
The compiler expects a ``start`` subroutine in the ``main`` block for this,
taking no parameters and having no return value.
As any subroutine, it has to end with a ``return`` statement (or a ``goto`` call).
For the Commodore-64 here is a list of free-to-use zero page locations even when its BASIC and KERNAL are active:
~ main {
sub start () -> () {
; program entrypoint code here
return
}
}
``$02``; ``$03``; ``$04``; ``$05``; ``$06``; ``$2a``; ``$52``;
``$f7`` - ``$f8``; ``$f9`` - ``$fa``; ``$fb`` - ``$fc``; ``$fd`` - ``$fe``
The ``main`` module is always relocated to the start of your programs
address space, and the ``start`` subroutine (the entrypoint) will be on the
first address. This will also be the address that the BASIC loader program (if generated)
calls with the SYS statement.
The four reserved locations mentioned above are subtracted from this set, leaving you with
five 1-byte and two 2-byte usable zero page registers.
IL65 knows about all this: it will use the above zero page locations to place its ZP variables in,
until they're all used up. You can instruct it to treat your program as taking over the entire
machine, in which case (almost) all of the zero page locations are suddenly available for variables.
IL65 can generate a special routine that saves and restores the zero page to let your program run
and return safely back to the system afterwards - you don't have to take care of that yourself.
**IRQ and the Zero page:**
The normal IRQ routine in the C-64's kernal will read and write several locations in the zero page:
``$a0 - $a2``; ``$91``; ``$c0``; ``$c5``; ``$cb``; ``$f5 - $f6``
These locations will not be used by the compiler for zero page variables, so your variables will
not interfere with the IRQ routine and vice versa. This is true for the normal zp mode but also
for the mode where the whole zp has been taken over. So the normal IRQ vector is still
running when your program is entered, even when you use ``%zp clobber``.
Blocks and subroutines are explained below.
@todo: some global way (in ZP block) to promote certian other blocks/variables from that block or even
subroutine to the zeropage. Don't do this in the block itself because it's a global optimization
and if blocks require it themselves you can't combine various modules anymore once ZP runs out.
#### Using other modules via import
Immediately following the global program options at the top of the module file,
the imports of other modules are placed:
``%import filename``
This reads and compiles the named module source file as part of your current program.
Symbols from the imported module become available in your code,
without a module or filename prefix.
You can import modules one at a time, and importing a module more than once has no effect.
Data Types
----------
#### Blocks, Scopes, and accessing Symbols
Blocks are the separate pieces of code and data of your program. They are combined
into a single output program. No code or data can occur outside a block.
~ blockname [address] {
[directives...]
[variables...]
[subroutines...]
}
Block names must be unique in your entire program.
It's possible to omit the blockname, but then you can only refer to the contents of the block via its absolute address,
which is required in this case. If you omit *both* name and address, the block is *ignored* by the compiler (and a warning is displayed).
This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you
want to work on later, because the contents of the ignored block are not fully parsed either.
The address can be used to place a block at a specific location in memory.
Otherwise the compiler will automatically choose the location (usually immediately after
the previous block in memory).
The address must be >= $0200 (because $00-$fff is the ZP and $100-$200 is the cpu stack).
A block is also a *scope* in your program so the symbols in the block don't clash with
symbols of the same name defined elsewhere in the same file or in another file.
You can refer to the symbols in a particular block by using a *dotted name*: ``blockname.symbolname``.
Labels inside a subroutine are appended again to that; ``blockname.subroutinename.label``.
Every symbol is 'public' and can be accessed from elsewhere given its dotted name.
**The special "ZP" ZeroPage block**
Blocks named "ZP" are treated a bit differently: they refer to the ZeroPage.
The contents of every block with that name (this one may occur multiple times) are merged into one.
Its start address is always set to $04, because $00/$01 are used by the hardware
and $02/$03 are reserved as general purpose scratch registers.
Code elements
-------------
### Data types for Variables and Values
IL65 supports the following data types:
@ -162,9 +200,17 @@ routines in the C-64 BASIC and KERNAL ROMs are used.
So floating point operations will only work if the C-64 BASIC ROM (and KERNAL ROM) are banked in, and your code imports the ``c654lib.ill``.
The largest 5-byte MFLPT float that can be stored is: 1.7014118345e+38 (negative: -1.7014118345e+38)
The initial values of your variables will be restored automatically when the program is (re)started,
*except for string variables*. It is assumed these are left unchanged by the program.
If you do modify them in-place, you should take care yourself that they work as
expected when the program is restarted.
Indirect addressing and address-of
----------------------------------
@todo pointers/addresses? (as opposed to normal WORDs)
@todo signed integers (byte and word)?
### Indirect addressing and address-of
**Address-of:**
The ``#`` prefix is used to take the address of something. This is sometimes useful,
@ -183,68 +229,33 @@ For an indirect goto call, the 6502 CPU has a special instruction
using a couple of instructions.
Program Structure
-----------------
### Conditional Execution
In IL65 every line in the source file can only contain *one* statement or definitons.
Compilation is done on *one* main source code file, but other files can be imported.
A source file can start with global *directives* (starting with ``%``) and continues
with imports and block definitions.
Conditional execution means that the flow of execution changes based on certiain conditions,
rather than having fixed gotos or subroutine calls. IL65 has a *conditional goto* statement for this,
that is translated into a comparison (if needed) and then a conditional branch instruction:
if[_XX] [<expression>] goto <label>
The if-status XX is one of: [cc, cs, vc, vs, eq, ne, true, not, zero, pos, neg, lt, gt, le, ge]
It defaults to 'true' (=='ne', not-zero) if omitted. ('pos' will translate into 'pl', 'neg' into 'mi')
@todo signed: lts==neg?, gts==eq+pos?, les==neg+eq?, ges==pos?
The <expression> is optional. If it is provided, it will be evaluated first. Only the [true] and [not] and [zero]
if-statuses can be used when such a *comparison expression* is used. An example is:
if_not A > 55 goto more_iterations
### Comments
Everything after a semicolon '``;``' is a comment and is ignored.
If the comment is the only thing on the line, it is copied into the resulting assembly source code.
This makes it easier to understand and relate the generated code.
Conditional jumps are compiled into 6502's branching instructions (such as ``bne`` and ``bcc``) so
the rather strict limit on how *far* it can jump applies. The compiler itself can't figure this
out unfortunately, so it is entirely possible to create code that cannot be assembled successfully.
You'll have to restructure your gotos in the code (place target labels closer to the branch)
if you run into this type of assembler error.
### Output Mode
### Including other files or raw assembly code literally
The default format of the generated program is a "raw" binary where code starts at ``$c000``.
You can generate other types of programs as well, by telling IL65 via an output mode directive
at the beginning of your program:
| mode directive | meaning |
|-----------------------|------------------------------------------------------------------------------------|
| ``%output raw`` | no load address bytes |
| ``%output prg`` | include the first two load address bytes, (default is ``$0801``), no BASIC program |
| ``%output prg,basic`` | as 'prg', but include a BASIC start program with SYS call, default code start is immediately after the BASIC program at ``$081d``, or after that. |
| | |
| ``%address $0801`` | override program start address (default is set to ``$c000`` for raw mode and ``$0801`` for C-64 prg mode). Cannot be used if output mode is ``prg,basic`` because BASIC programs always have to start at ``$0801``. |
### ZeroPage Options
You can tell the compiler how to treat the *zero page*. Normally it is considered a 'no-go' area
except for the frew free locations mentioned under "Memory Model".
However you can specify some options globally in your program by using the zp directive
to change this behavior:
- ``%zp clobber``
Use the whole zeropage for variables. It is not possible to exit your program
correctly back to BASIC, other than resetting the machine.
It does make your program smaller and faster because many more variables can
be stored in the ZP, which is more efficient.
- ``%zp clobber, restore``
Use the whole zeropage, but make a backup copy of the original values at program start.
When your program exits, the original ZP is restored (except for the software jiffy clock
in ``$a0 - $a2``) and you drop back to the BASIC prompt.
Not that the default IRQ routine is *still enabled* when your program is entered!
See the paragraph on the zero page for more info about this.
If you use ``%zp clobber``, you can no longer use most BASIC or KERNAL routines,
because these depend on most of the locations in the ZP. This includes the floating-point
logic and several utility routines that do I/O, such as ``print_string``.
### Importing, Including and Binary-Including other files or raw assembly code
- ``%import "filename[.ill]"``
Must be used *after* any global option directives, and outside of a block,
but otherwise can be placed anywhere in the program.
Reads and compiles the named IL65 file and stores the resulting definitions
as if they were a part of your current program.
- ``%asminclude "filename.txt", scopelabel``
This directive can only be used inside a block.
The assembler will include the file as raw assembly source text at this point, il65 will not process this at all.
@ -259,47 +270,6 @@ logic and several utility routines that do I/O, such as ``print_string``.
The ``%asm {`` and ``}`` start and end markers each have to be on their own unique line.
### Program Start and Entry Point
Every program has to have one entry point where code execution begins.
The compiler looks for the ``start`` label in the ``main`` block for this.
For proper program termination, this block has to end with a ``return`` statement (or a ``goto`` call).
Blocks and other details are described below.
The initial values of your variables will be restored automatically when the program is (re)started,
*except for string variables*. It is assumed they are unchanged by your program.
If you do modify them in-place, you should take care yourself that they work as
expected when the program is restarted.
### Blocks and Scopes
~ blockname [address] {
[directives...]
[statements...]
}
Blocks form the separate pieces of code and data of your program. They are combined and
arranged to a single output program. No code or data can occur outside a block.
A block is also a *scope* in your program so the symbols in the block don't clash with
symbols of the same name defined elsewhere. You can refer to the symbols in a particular block
by using a *dotted name*: ``blockname.symbolname``.
Note that the *file* (or module) that blocks are defined in, is of no consequence to the
full scope name; all blocks defined in it are simply added to the set of blocks that make up the
entire program.
Hence block names must be unique in your entire program.
Except "ZP": the contents of every block with that name are merged into one.
This "ZP" block name is special and refers to the zero page. Its start address is always set to $04,
because $00/$01 are used by the hardware and $02/$03 are reserved as general purpose scratch registers.
Block address must be >= $0200 (because $00-$fff is the ZP and $100-$200 is the cpu stack)
When declaring a block in your code, it's possible to omit the blockname.
But then you can only refer to the contents of the block via its absolute address,
which is required in this case. If you omit *both* name and address, the block is *ignored* by the compiler (and a warning is displayed).
This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you
want to work on later, because the contents of the ignored block are not fully parsed either.
### Assignments
@ -402,59 +372,6 @@ as well clobber all three registers. Preserving the original values does result
stack manipulation code to be inserted for every call like this, which can be quite slow.
#### Calling Convention
Subroutine arguments and results are passed via registers (and sometimes implicitly
via certain memory locations).
@todo support call non-register args (variable parameter passing)
In IL65 the "caller saves" principle applies to registers used in a subroutine.
This means the code that calls a subroutine or performs some function that clobber certain
registers (A, X or Y), is responsible for storing and restoring the original values if
that is required.
*You should assume that the 3 hardware registers A, X and Y are volatile and their contents
cannot be depended upon, unless you make sure otherwise*.
Normally, the registers are NOT preserved when calling a subroutine or when a certian
operations are performed. Most calls will be simply a few instructions to load the
values in the registers and then a JSR or JMP.
By using the ``%saveregisters`` directive (globally or in a block) you can tell the
compiler to preserve all registers. This does generate a lot of extra code that puts
original values on the stack and gets them off the stack again once the subroutine is done.
In this case however you don't have to worry about A, X and Y losing their original values
and you can essentially treat them as three local variables instead of scratch data.
You can also use a ``!`` on a single subroutine call to preserve register values, instead of
setting this behavior for the entire block or even program. See the paragraph on Subroutine Calling
for more info.
### Conditional Execution Flow
Conditional execution flow means that the flow of execution changes based on certiain conditions,
rather than having fixed gotos or subroutine calls. IL65 has a *conditional goto* statement for this,
that is translated into a comparison (if needed) and then a conditional branch instruction:
if[_XX] [<expression>] goto <label>
The if-status XX is one of: [cc, cs, vc, vs, eq, ne, true, not, zero, pos, neg, lt, gt, le, ge]
It defaults to 'true' (=='ne', not-zero) if omitted. ('pos' will translate into 'pl', 'neg' into 'mi')
@todo signed: lts==neg?, gts==eq+pos?, les==neg+eq?, ges==pos?
The <expression> is optional. If it is provided, it will be evaluated first. Only the [true] and [not] and [zero]
if-statuses can be used when such a *comparison expression* is used. An example is:
if_not A > 55 goto more_iterations
Conditional jumps are compiled into 6502's branching instructions (such as ``bne`` and ``bcc``) so
the rather strict limit on how *far* it can jump applies. The compiler itself can't figure this
out unfortunately, so it is entirely possible to create code that cannot be assembled successfully.
You'll have to restructure your gotos in the code (place target labels closer to the branch)
if you run into this type of assembler error.
Debugging (with Vice)
---------------------
@ -462,17 +379,25 @@ Debugging (with Vice)
The ``%breakpoint`` directive instructs the compiler to put
a *breakpoint* at that position in the code. It's a logical breakpoint instead of a physical
BRK instruction because that will usually halt the machine altogether instead of breaking execution.
Instead, a NOP instruction is generated and in a special output file the list of breakpoints is written.
Instead of this, a NOP instruction is generated and in a special output file the list of breakpoints is written.
This file is called "yourprogramname.vice-mon-list" and is meant to be used by the Vice C-64 emulator.
This file is called "programname.vice-mon-list" and is meant to be used by the Vice C-64 emulator.
It contains a series of commands for Vice's monitor, this includes source labels and the breakpoint settings.
If you use the vice autostart feature of the compiler, it will be automatically processed by Vice.
If you launch Vice manually, you can use a command line option to load the file: ``x64 -moncommands yourprogramname.vice-mon-list``
If you launch Vice manually, you can use a command line option to load this file: ``x64 -moncommands programname.vice-mon-list``
Vice will use the label names in memory disassembly, and will activate the breakpoints as well
so if your program runs and it hits a breakpoint, Vice will halt execution and drop into the monitor.
Troubleshooting
---------------
Getting assembler error about undefined symbols such as ``not defined 'c64flt'``?
This happens when your program uses floating point values, and you forgot to import the ``c64lib``.
If you use floating points, the program will need routines from that library.
Fix it by adding an ``%import c64lib``.
@Todo
@ -625,26 +550,3 @@ to define CHARACTERS (8x8 monochrome or 4x8 multicolor = 8 bytes)
and SPRITES (24x21 monochrome or 12x21 multicolor = 63 bytes)
--> PLACE in memory on correct address (base+sprite pointer, 64-byte aligned)
### More Datatypes
@todo pointers/addresses? (as opposed to normal WORDs)
@todo signed integers (byte and word)?
### Some support for simple arithmetic
A *= Y
A = X * Y
A /= Y
A = Y / Y
Troubleshooting
---------------
Getting assembler error about undefined symbols such as ``not defined 'c64flt'``?
This happens when your program uses floating point values, and you forgot to import the ``c64lib``.
If you use floating points, the program will need routines from that library.
Fix it by adding an ``%import c64lib``.

137
docs/system.md Normal file
View File

@ -0,0 +1,137 @@
System documentation: memory model and CPU
------------------------------------------
### Memory address space layout
The 6502 CPU can directly address 64 kilobyte of memory.
Most of the 64 kilobyte address space can be used by IL65 programs.
| type | memory area | note |
|-----------------|-------------------------|-----------------------------------------------------------------|
| ZeroPage | ``$00`` - ``$ff`` | contains many sensitive system variables |
| Hardware stack | ``$100`` - ``$1ff`` | is used by the CPU and should normally not be accessed directly |
| Free RAM or ROM | ``$0200`` - ``$ffff`` | free to use memory area, often a mix of RAM and ROM |
A few memory addresses are reserved and cannot be used,
because they have a special hardware function, or are reserved for internal use in the
code generated by the compiler:
| reserved address | in use for |
|------------------|----------------------|
| ``$00`` | data direction (hw) |
| ``$01`` | bank select (hw) |
| ``$02`` | IL65 scratch var |
| ``$03`` | IL65 scratch var |
| ``$fb-$fc`` | IL65 scratch var |
| ``$fd-$fe`` | IL65 scratch var |
| ``$fffa-$fffb`` | NMI vector (hw) |
| ``$fffc-$fffd`` | RESET vector (hw) |
| ``$fffe-$ffff`` | IRQ vector (hw) |
A particular 6502/6510 based machine such as the Commodore-64 will have many other special addresses as well:
- ROMs installed in the machine (BASIC, kernal and character roms)
- memory-mapped I/O registers (for the video and sound chip for example)
- RAM areas used for screen graphics and sprite data
### Directly Usable CPU Registers
The following 6502 CPU hardware registers are directly usable in program code (and are reserved symbols):
- ``A``, ``X``, ``Y`` the A, X and Y cpu registers (8 bits)
- ``AX``, ``AY``, ``XY`` surrogate 16-bit registers: LSB-order (lo/hi) combined register pairs
- ``SC`` status register's Carry flag
- ``SI`` status register's Interrupt Disable flag
The other status bits of the status register are not directly accessible,
but can be acted upon via conditional statements.
The stack pointer and program counter registers are not accessible.
### ZeroPage ("ZP")
The ZP addresses ``$02`` - ``$ff`` can be regarded as 254 other 'registers', because
they take less clock cycles to access and need fewer instruction bytes than access to other memory locations.
Theoretically you can use all of them in a program but there are a few limitations:
- several addresses (``$02``, ``$03``, ``$fb - $fc``, ``$fd - $fe``) are reserved for internal use as scratch registers by IL65
- most other addresses often are in use by the machine's operating system or kernal,
and overwriting them can crash the machine. The program must take over the entire
system to be able to safely use all ZP addresses. This means it can no longer use
most BASIC and kernal routines.
- it's often more convenient to let IL65 allocate the particular addresses for you and just
use symbolic names in the code.
For the Commodore-64 here is a list of free-to-use ZP addresses even when its BASIC and KERNAL are active:
``$02``; ``$03``; ``$04``; ``$05``; ``$06``; ``$2a``; ``$52``;
``$f7`` - ``$f8``; ``$f9`` - ``$fa``; ``$fb`` - ``$fc``; ``$fd`` - ``$fe``
The six reserved addresses mentioned above are subtracted from this set, leaving you with
just five 1-byte and two 2-byte usable ZP 'registers'.
IL65 knows about all this: it will use the above ZP addresses to place its ZP variables in,
until they're all used up. You can instruct it to output a program that takes over the entire
machine, in which case (almost) all of the ZP addresses are suddenly available for variables.
IL65 can also generate a special routine that saves and restores the ZP to let the program run
and return safely back to the system afterwards - you don't have to take care of that yourself.
**IRQs and the ZeroPage:**
The normal IRQ routine in the C-64's kernal will read and write several addresses in the ZP
(such as the system's software jiffy clock which sits in ``$a0 - $a2``):
``$a0 - $a2``; ``$91``; ``$c0``; ``$c5``; ``$cb``; ``$f5 - $f6``
These addresses will never be used by the compiler for ZP variables, so variables will
not interfere with the IRQ routine and vice versa. This is tru for the normal zp mode but also
for the mode where the whole ZP has been taken over. So the normal IRQ vector is still
running when the program is entered!
### ZeroPage handling in programs
The global ``%zp`` directive can be used to specify the way the compiler
will treat the ZP for the program. The default is ``compatible``, where most
of the ZP is considered a no-go zone by the compiler.
- ``compatible`` : only use the few 'free' addresses in the ZP, and don't change anything else.
This allows full use of BASIC and KERNAL ROM routines including default IRQs during normal system operation.
- ``full`` : claim the whole ZP for variables for the program, overwriting everything,
except the few addresses mentioned above that are used by the system's IRQ routine.
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
It is also not possible to cleanly exit the program, other than resetting the machine.
This option makes programs smaller and faster because many more variables can
be stored in the ZP, which is more efficient.
- ``full-restore`` : like ``full``, but makes a backup copy of the original values at program start.
These are restored (except for the software jiffy clock in ``$a0 - $a2``)
when the program exits, and allows it to exit back to the BASIC prompt.
### Subroutine Calling Conventions
Subroutine arguments and results are passed via registers (and sometimes implicitly
via certain memory locations).
@todo support call non-register args (variable parameter passing)
In IL65 the "caller saves" principle applies to registers used in a subroutine.
This means the code that calls a subroutine or performs some function that clobber certain
registers (A, X or Y), is responsible for storing and restoring the original values if
that is required.
*You should assume that the 3 hardware registers A, X and Y are volatile and their contents
cannot be depended upon, unless you make sure otherwise*.
Normally, the registers are NOT preserved when calling a subroutine or when a certian
operations are performed. Most calls will be simply a few instructions to load the
values in the registers and then a JSR or JMP.
By using the ``%saveregisters`` directive in a block, you can tell the
compiler to preserve all registers. This does generate a lot of extra code that puts
original values on the stack and gets them off the stack again once the subroutine is done.
In this case however you don't have to worry about A, X and Y losing their original values
and you can essentially treat them as three local variables instead of scratch data.
You can also use a ``!`` on a single subroutine call to preserve register values, instead of
setting this behavior for the entire block.

View File

@ -103,12 +103,12 @@ class AssemblyGenerator:
if zpblock:
# if there's a Zeropage block, it always goes first
self.cur_block = zpblock # type: ignore
out("\n; ---- zero page block: '{:s}' ----".format(zpblock.name))
out("\n; ---- ZeroPage block: '{:s}' ----".format(zpblock.name))
out("; file: '{:s}' src l. {:d}\n".format(zpblock.sourceref.file, zpblock.sourceref.line))
out("{:s}\t.proc\n".format(zpblock.label))
generate_block_init(out, zpblock)
generate_block_vars(out, zpblock, True)
# there's no code in the zero page block.
# there's no code in the ZeroPage block.
out("\v.pend\n")
for block in sorted(self.module.all_nodes(Block), key=lambda b: b.address or 0):
ctx = Context(out=out, stmt=None, scope=block.scope, floats_enabled=self.floats_enabled)

View File

@ -1,5 +1,5 @@
; IL65 definitions for the Commodore-64
; Including memory registers, I/O registers, Basic and Kernel subroutines, utility subroutines.
; Including memory registers, I/O registers, Basic and Kernal subroutines, utility subroutines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
; ;
@ -454,7 +454,7 @@ sub float_sub_SW1_from_XY (mflt: XY) -> (?) {
; ---- this block contains (character) Screen and text I/O related functions ----
sub clear_screen (char:A, color: Y) -> () {
sub clear_screen (char: A, color: Y) -> () {
; ---- clear the character screen with the given fill character and character color.
; (assumes screen is at $0400, could be altered in the future with self-modifying code)
; @todo X = SCREEN ADDR HI BYTE

View File

@ -1,4 +1,4 @@
; backup/restore the zero page
; backup/restore the ZeroPage
; this is in a separate file so it can be omitted completely if it's not needed.
_il65_save_zeropage
@ -7,10 +7,12 @@ _il65_save_zeropage
lda #%00100111
sta _il65_zp_backup+1 ; default value for $01
ldx #2
sei
- lda $00,x
sta _il65_zp_backup,x
inx
bne -
cli
rts
_il65_restore_zeropage

View File

@ -775,7 +775,7 @@ class VarDef(AstNode):
vartype = attr.ib()
datatype = attr.ib()
size = attr.ib(type=list, default=None)
zp_address = attr.ib(type=int, default=None, init=False) # the address in the zero page if this var is there, will be set later
zp_address = attr.ib(type=int, default=None, init=False) # the address in the ZeroPage if this var is there, will be set later
@property
def value(self) -> Expression:

View File

@ -5,7 +5,12 @@
%output basic ; create a c-64 program with basic SYS call to launch it
%zp restore , clobber ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
%zp restore , clobber ; clobber over the zp memory normally used by basic/kernal rom, frees up more zp
options
%blocks
~ main
@ -24,6 +29,7 @@
var .word initword9876 = $9876
var .array(10) bytes2 = $44
var .wordarray(10) words2 = $bbcc
]
start: ;foo

View File

@ -25,7 +25,7 @@ if len(sys.argv) == 3:
if len(sys.argv) not in (2, 3):
raise SystemExit("provide 1 or 2 program file names as arguments")
# zero page and hardware stack of a 6502 cpu are off limits for now
# ZeroPage and hardware stack of a 6502 cpu are off limits for now
VM.readonly_mem_ranges = [(0x00, 0xff), (0x100, 0x1ff)]
vm = VM(mainprogram, timerprogram)
vm.enable_charscreen(0x0400, 40, 25)