mirror of
https://github.com/irmen/prog8.git
synced 2025-01-11 13:29:45 +00:00
138 lines
7.2 KiB
Markdown
138 lines
7.2 KiB
Markdown
|
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.
|