prog8/docs/source/targetsystem.rst
2024-11-08 19:19:11 +01:00

253 lines
12 KiB
ReStructuredText

===========================
Target system specification
===========================
Prog8 targets the following hardware:
- 8 bit MOS 6502/65c02/6510 CPU
- 64 Kb addressable memory (RAM or ROM)
- optional use of memory mapped I/O registers
- optional use of system ROM routines
Currently these machines can be selected as a compilation target (via the ``-target`` compiler argument):
- 'c64': the Commodore 64
- 'cx16': the `Commander X16 <https://www.commanderx16.com/>`_
- 'c128': the Commodore 128 (*limited support*)
- 'pet32': the Commodore PET 4032 (*limited support*)
- 'atari': the Atari 800 XL (*experimental*)
- 'neo': the `Neo6502 <https://github.com/paulscottrobson/neo6502-firmware/wiki>`_ (*experimental*)
- 'virtual': a builtin virtual machine
This chapter explains some relevant system details of the c64 and cx16 machines.
.. hint::
If you only use standard Kernal and prog8 library routines,
it is often possible to compile the *exact same program* for
different machines (just change the compilation target flag)!
Memory Model
============
Generic 6502 Physical address space layout
------------------------------------------
The 6502 CPU can address 64 kilobyte of memory.
Most of the 64 kilobyte address space can be used by Prog8 programs.
This is a hard limit: there is no support for RAM expansions or bank switching built natively into the language.
====================== ================== ========
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
depending on the specific computer system
====================== ================== ========
Memory map for the C64 and the X16
----------------------------------
This is the default memory map of the 64 Kb addressable memory for those two systems.
Both systems have ways to alter the memory map and/or to switch memory banks, but that is not shown here.
See :ref:`banking` for details about that.
.. image:: memorymap.svg
Footnotes for the Commander X16
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*Golden Ram $0400 - $07FF*
*free to use.*
*Zero Page $0000 - $00FF*
$00 and $01 are hardwired as Rom and Ram banking registers.
$02 - $21 are the 16 virtual cx16 registers R0-R15.
$22 - $7F are used by Prog8 to put variables in.
The top half of the ZP ($80-$FF) is reserved for use by the Kernal and Basic in normal operation.
Zero page use by Prog8 can be manipulated with the ``%zeropage`` directive, various options
may free up more locations for use by Prog8 or to reserve them for other things.
Footnotes for the Commodore 64
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*Program RAM $C000-$CFFF*
*free to use:* $C000 - $CFDF
*reserved:* $CFE0 - $CFFF for the 16 virtual cx16 registers R0-R15
*Program RAM / BASIC ROM $A000-$BFFF*
On the C64 the Basic ROM normally occupies this memory area. However Prog8 programs that do not
use floating point variables, actually bank out the Basic ROM to reclaim the 8 Kb of RAM that
is hidden below it. This means that all the memory from $0801 to $D000 (exclusive) is available
as program ram to Prog8 programs.
*Zero Page $0000 - $00FF*
Consider the full zero page to be reserved for use by the Kernal and Basic in normal operation.
Zero page use by Prog8 can be manipulated with the ``%zeropage`` directive, various options
may free up more locations for use by Prog8 or to reserve them for other things.
Zero page usage by the Prog8 compiler
-------------------------------------
Prog8 knows what addresses are safe to use in the various ZP handling configurations.
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.
**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 reasonably restrictive to use the
part of the ZP that is not used by the C64's Kernal routines.
It's possible to claim the whole ZP as well (by disabling the operating system or Kernal).
If you want, it's also possible to be more restrictive and stay clear of the addresses used by BASIC routines too.
This allows the program to exit cleanly back to a BASIC ready prompt - something that is not possible in the other modes.
IRQs and the zeropage
^^^^^^^^^^^^^^^^^^^^^
The normal IRQ routine in the C64'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!
CPU
===
Directly Usable Registers
-------------------------
The hardware CPU registers are not directly accessible from regular Prog8 code.
If you need to mess with them, you'll have to use inline assembly.
The status register (P) carry flag and interrupt disable flag can be written via a couple of special
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
and read via the ``read_flags()`` function.
The 16 'virtual' 16-bit registers that are defined on the Commander X16 machine are not real hardware
registers and are just 16 memory-mapped word values that you *can* access directly.
IRQ Handling
============
Normally, the system's default IRQ handling is not interfered with.
You can however install your own IRQ handler (for clean separation, it is advised to define it inside its own block).
There are a few library routines available to make setting up 60hz/vsync IRQs and raster/line IRQs a lot easier (no assembly code required).
These routines are::
sys.set_irq(uword handler_address)
sys.set_rasterirq(uword handler_address, uword rasterline)
sys.restore_irq() ; set everything back to the systems default irq handler
The IRQ handler routine must return a boolean value (0 or 1) in the A register:
0 means do *not* run the system IRQ handler routine afterwards, 1 means run the system IRQ handler routine afterwards.
**CommanderX16 specific notes**
.. sidebar::
X16 specific routines
For the X16 there are also some specialized IRQ handling routines, see :ref:`x16-specific-irq` below.
Note that for the CommanderX16 the set_rasterirq() will disable VSYNC irqs and never call the system IRQ handler regardless
of the return value of the user handler routine. This also means the default sys.wait() routine won't work anymore,
when using this handler.
These two helper routines are not particularly suited to handle multiple IRQ sources on the Commander X16.
It's possible but it requires correct fiddling with IRQ enable bits, acknowledging the IRQs, and properly calling
or not calling the system IRQ handler routine. See the section below for perhaps a better and easier solution that
is tailored to this system.
The Commander X16 syslib provides some additional routines that should be used *in your IRQ handler routine* if it uses the Vera registers.
They take care of saving and restoring the Vera state of the interrupted main program, otherwise the IRQ handler's manipulation
will corrupt any Vera operations that were going on in the main program. The routines are::
cx16.save_vera_context()
; perhaps also cx16.save_virtual_registers() here... see caution below
; ... do your work that uses vera here!...
; perhaps also cx16.restore_virtual_registers() here... see caution below
cx16.restore_vera_context()
.. caution::
The Commander X16's 16 'virtual registers' R0-R15 *are not preserved* in the IRQ handler! (On any system!)
So you should make sure that the handler routine does NOT use these registers, or do some sort of saving/restoring yourself
of the ones that you do need in the IRQ handler. Note that Prog8 itself may also use these registers, so be very careful.
This is not a X16 specific thing; these registers also exist on the other compiler targets, and the same
issue holds there.
There are two utility routines in cx16 that save and restore *all* 16 registers. It's a bit inefficient if
only a few are clobbered, but it's easy to put calls to them into your IRQ handler routine at the start and end.
These routines are ``cx16.save_virtual_registers()`` and ``cx16.restore_virtual_registers()``.
It is also advised to **not use floating point calculations** inside IRQ handler routines.
Beside them being very slow, there are intricate requirements such as having the
correct ROM bank enabled to be able to successfully call them (and making sure the correct
ROM bank is reset at the end of the handler), and the possibility
of corrupting variables and floating point calculations that are being executed
in the interrupted main program. These memory locations should be backed up
and restored at the end of the handler, further increasing its execution time...
.. _x16-specific-irq:
Commander X16 specific IRQ handling
===================================
Instead of using the routines in ``sys`` as mentioned above (that are more or less portable
across the C64,C128 and cx16), you can also use the special routines made for the Commander X16,
in ``cx16``. The idea is to let Prog8 do the irq dispatching and housekeeping for you, and that
your program only has to register the specific handlers for the specific IRQ sources that you want to handle.
Look at the examples/cx16/multi-irq-new.p8 example to see how these routines can be used.
Here they are, all available in ``cx16``:
``disable_irqs ()``
Disables all Vera IRQ sources. Note that the CPU irq disable flag is not changed by this routine.
you can manipulate that via ``sys.set_irqd()`` and ``sys.clear_irqd()`` as usual.
``enable_irq_handlers (bool disable_all_irq_sources)``
Install the "master IRQ handler" that will dispatch IRQs to the registered handler for each type.
Only Vera IRQs supported for now.
Pass true to initially disable all Vera interrupt sources (they will be enabled individually again
by setting the various handlers), or pass false to not touch this.
The handlers don't need to clear its ISR bit, but have to return 0 or 1 in A,
where 1 means: continue with the system IRQ handler, 0 means: don't call that.
The order in which the handlers are invoked if multiple interrupts occur simultaneously is: LINE, SPRCOL, AFLOW, VSYNC.
``set_vsync_irq_handler (uword address)``
Sets the verical sync interrupt handler routine. Also enables VSYNC interrupts.
``set_line_irq_handler (uword rasterline, uword address)``
Sets the rasterline interrupt handler routine to trigger on the specified raster line.
Also enables LINE interrupts.
You can use ``sys.set_rasterline()`` later to adjust the rasterline on which to trigger.
``set_sprcol_irq_handler (uword address)``
Sets the sprite collision interrupt handler routine. Also enables SPRCOL interrupts.
``set_aflow_irq_handler (uword address)``
Sets the audio buffer underrun interrupt handler routine. Also enables AFLOW interrupts.
Note: the handler must fill the Vera's audio fifo buffer by itself with at least 25% worth of data (1 kb)
otherwise the aflow irq keeps triggering.
``disable_irq_handlers ()``
Hand control back to the system default IRQ handler.