prog8/docs/system.md

138 lines
7.2 KiB
Markdown
Raw Normal View History

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.