prog8/docs/source/targetsystem.rst

162 lines
7.4 KiB
ReStructuredText

***************************
Target system specification
***************************
IL65 targets the following hardware:
- 8 bit MOS 6502/6510 CPU
- 64 Kb addressable memory (RAM or ROM)
- memory mapped I/O registers
The main target machine is the Commodore-64, which is an example of this.
This chapter explains the relevant system details of such a machine.
Memory Model
============
Physical address space layout
-----------------------------
The 6502 CPU can address 64 kilobyte of memory.
Most of the 64 kilobyte address space can be used by IL65 programs.
This is a hard limit: there is no built-in support for RAM expansions or bank switching.
====================== ================== ========
memory area type note
====================== ================== ========
``$00``--``$ff`` ZeroPage contains many sensitive system variables
``$100``--``$1ff`` Hardware stack used by the CPU, normally not accessed directly
``$0200``--``$ffff`` Free RAM or ROM free to use memory area, often a mix of RAM and ROM
====================== ================== ========
A few of these memory addresses are reserved and cannot be used for arbitrary data.
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 (CPU hw)
``$01`` bank select (CPU hw)
``$02`` IL65 scratch variable
``$03`` IL65 scratch variable
``$fb - $fc`` IL65 scratch variable
``$fd - $fe`` IL65 scratch variable
``$fffa - $fffb`` NMI vector (CPU hw)
``$fffc - $fffd`` RESET vector (CPU hw)
``$fffe - $ffff`` IRQ vector (CPU hw)
================== =======================
The actual machine will often have many other special addresses as well,
For example, the Commodore-64 has:
- ROMs installed in the machine: BASIC, kernal and character roms. Occupying ``$a000``--``$bfff`` and ``$e000``--``$ffff``.
- memory-mapped I/O registers, for the video and sound chips, and the CIA's. Occupying ``$d000``--``$dfff``.
- RAM areas that are used for screen graphics and sprite data: usually at ``$0400``--``$07ff``.
IL65 programs can access all of those special memory locations but it will have a special meaning.
.. _zeropage:
ZeroPage ("ZP")
---------------
The ZeroPage memory block ``$02``--``$ff`` can be regarded as 254 CPU 'registers', because
they take less clock cycles to access and need fewer instruction bytes than accessing other memory locations outside of the ZP.
Theoretically they can all be used in a program, with the follwoing limitations:
- several addresses (``$02``, ``$03``, ``$fb - $fc``, ``$fd - $fe``) are reserved for internal use
- most other addresses will already be in use by the machine's operating system or kernal,
and overwriting them will probably crash the machine. It is possible to use all of these
yourself, but only if the program takes over the entire system (and seizes control from the regular kernal).
This means it can no longer use (most) BASIC and kernal routines from ROM.
- it's more convenient and safe to let IL65 allocate these addresses for you and just
use symbolic names in the program code.
Here is the list of the remaining free-to-use ZP addresses with BASIC and KERNAL active in the Commodore-64:
``$02``, ``$03``, ``$04``, ``$05``, ``$06``, ``$2a``, ``$52``,
``$f7 - $f8``, ``$f9 - $fa``, ``$fb - $fc``, ``$fd - $fe``
*The six reserved addresses mentioned earliser are subtracted from this set,* leaving you with
just *five* 1-byte and *two* 2-byte usable ZP 'registers' for use by the program.
**IL65 knows about all of this.** It will use the free ZP addresses to place its ZP variables in,
until they're all used up. If instructed to output a program that takes over the entire
machine, (almost) all of the ZP addresses are suddenly available and will be used.
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 true for the normal ZP mode but also
for the mode where the whole system and ZP have been taken over.
So the normal IRQ vector can still run and will be when the program is started!
ZeroPage handling is configurable
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There's a global program directive to specify the way the compiler
treats the ZP for the program. The default is to be restrictive to just
the few free locations mentioned above, where most of the ZP is considered a no-go zone by the compiler.
It's possible to claim the whole ZP as well (by disabling the operating system or kernal),
and even ask for a save/restore of the original values to be able to cleanly exit back to a BASIC prompt.
CPU
===
Directly Usable Registers
-------------------------
The following 6502 CPU hardware registers are directly usable in program code (and are reserved symbols):
- ``A``, ``X``, ``Y`` the three main cpu registers (8 bits)
- ``AX``, ``AY``, ``XY`` surrogate 16-bit registers: LSB-order (lo/hi) combined register pairs
- the status register (P) carry flag and interrupt disable flag can be written via the ``P_carry`` and ``P_irqd`` builtin functions.
Subroutine Calling Conventions
------------------------------
Subroutine arguments and results are passed via registers.
Sometimes the status register's Carry flag is used as well (as a boolean flag).
Additional arguments can be passed via memory locations as well ofcourse.
But you'll have to be careful when dealing with chained or even recursive calls then,
because there's a big risk of overwriting those memory locations.
In IL65 the "caller saves" principle applies to calling subroutines.
This means the code that calls a subroutine that clobbers certain
registers (``A``, ``X`` or ``Y``), is responsible for storing and restoring the original values if
those values are needed by the rest of the code.
Normally, 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.
.. important::
Basically, you should assume that the 3 hardware registers ``A``, ``X`` and ``Y``
are volatile. Their values cannot be depended upon, unless you explicitly make sure otherwise.