mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
572 lines
29 KiB
ReStructuredText
572 lines
29 KiB
ReStructuredText
***************************
|
|
Target system specification
|
|
***************************
|
|
.. index:: pair: Targets; Overview
|
|
|
|
Prog8 targets the following hardware:
|
|
|
|
- 8 bit MOS 6502/65c02/6510 CPU
|
|
- 32 bit Motorola 68000 CPU
|
|
- 64 Kb addressable memory (RAM or ROM) on 6502 targets, 16 Mb on m68k and virtual targets
|
|
- 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
|
|
- 'pet32': the Commodore PET 4032
|
|
- 'amiga500': Amiga 500 (experimental)
|
|
- 'amiga1200': Amiga 1200 (experimental, 68020 CPU with optional FPU)
|
|
- 'qemu68k': Qemu M68k simulator (experimental)
|
|
- 'virtual': a builtin virtual machine (32-bit pointers, 16 MB memory)
|
|
- custom targets via a separate configuration file (see :ref:`customizable_target`)
|
|
|
|
This chapter explains some relevant system details of the c64 and cx16 machines.
|
|
Details about the m68k targets are in the :ref:`amiga footnotes <amiga500footnotes>` below.
|
|
|
|
.. 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)!
|
|
|
|
.. note::
|
|
When you specify a file name as target, prog8 will try to read the target
|
|
machine's configuration and properties from that configuration file instead.
|
|
See :ref:`customizable_target` for details about this.
|
|
|
|
|
|
.. _customizable_target:
|
|
|
|
Customizable targets
|
|
====================
|
|
.. index:: single: Custom Targets; Overview
|
|
|
|
You can also specify a file name instead of one of the built in target machine names, when using the ``-target`` option.
|
|
In this case the compiler will not use one of the builtin machines configurations, but read it from the configuration file.
|
|
This allows you to define and change your own target machine configuration, and maybe allow Prog8 to generate
|
|
programs for new machines or existing ones it doesn't yet know about.
|
|
|
|
The configuration file should be a "properties" file (Java's ubiquitous configuration file format), which is basically a text file containing "key=value" lines.
|
|
The contents of the file is pretty extensive and it's easier to just look at some examples that are already included:
|
|
:source:`target configuration examples <examples/customtarget/>` .
|
|
The filename base part (the part before any suffix) of the target configuration file will be taken as the name of the compilation target.
|
|
If it matches one of the existing built-in compilation targets, the internal library files for that target will also be searched,
|
|
if the user supplied library location doesn't contain a replacement library file for anything that might get imported.
|
|
The path you need to provide for the ``library`` variable can be relative (to the current working directory where you launch the compiler from)
|
|
and you can use a tilde ``~`` in it like in a shell path, to refer to a user's home directory.
|
|
Library modules not unique to a specific compilation target (for example, `buffers`, `sorting` or `strings`) will
|
|
be picked up from the internal library files just fine as was always the case. You can still provide custom versions of them
|
|
in your own library folder of course, like you already could with using the ``-srcdirs`` compiler flag.
|
|
|
|
Most of the things discussed in the :ref:`portingguide` can and must be configured properly in the target configuration file.
|
|
You also need to create some essential ``syslib`` library module for the configured target if its name does not match
|
|
one of the built in targets. This is because the compiler won't have a built in library that can be used this time.
|
|
The customtarget examples also show how to build the essentials.
|
|
|
|
The target configuration file also allows you to override the entire launcher assembly fragment that the compiler
|
|
usually puts at the start of the program (immediately after the setting of the program counter/load address and
|
|
the program start label). This allows you to have full control over exactly what code or data bytes appear at
|
|
the program's load address. But with great power comes great responsibility: you'll have to make sure you take
|
|
care of some very low level Prog8 internals if you want to support stuff such as the ``exit`` builtin function.
|
|
Maybe the best thing is to look at the code generated by the compiler for an existing target and adapt that.
|
|
|
|
The ``launcher`` property lets you set the default launcher type for all programs compiled for this target,
|
|
without requiring a ``%launcher`` directive in every source file. Valid values are ``basic`` (CBM-BASIC SYS launcher)
|
|
and ``none`` (no launcher). If omitted, the default is ``basic``.
|
|
|
|
The optional ``pointer_size`` property sets the size of pointers in bytes. It defaults to ``2`` for compatibility
|
|
with existing configuration files. The supported values are ``2`` and ``4``.
|
|
|
|
.. _custom_target_m68k:
|
|
|
|
Custom targets for the 68000 CPU
|
|
--------------------------------
|
|
|
|
.. index:: single: Custom Targets; 68000
|
|
|
|
The compiler can also generate m68k code for a custom target machine, not just for the built-in
|
|
``amiga500``, ``amiga1200`` and ``qemu68k`` targets. The code generator is selected from the ``cpu``
|
|
property rather than from the target name, so declaring an m68k CPU is all that is needed to
|
|
route a custom target to the m68k backend.
|
|
|
|
Set the ``cpu`` property to the name of the CpuType enum value, ``M68000`` or ``M68020``::
|
|
|
|
cpu = M68020
|
|
|
|
The exact spelling matters, because the value is the name of the enum value: write ``M68020`` and
|
|
not ``68020``. The 6502 CPUs are spelled ``6502``, ``65C02`` and ``virtual``, and those are tried
|
|
both with and without a ``CPU`` prefix, so ``6502`` and ``CPU6502`` both work.
|
|
|
|
An m68k target needs a much smaller configuration file than a 6502 one, because the m68k has no
|
|
zero page and uses real CPU registers instead of the compiler's virtual registers. This means the
|
|
zeropage related properties are not applicable, and you can simply leave them out of the file:
|
|
|
|
* ``zp_scratch_ptr``, ``zp_scratch_b1``, ``zp_scratch_reg``, ``zp_scratch_w1``, ``zp_scratch_w2``
|
|
* ``zp_fullsafe``, ``zp_kernalsafe``, ``zp_basicsafe``
|
|
* ``virtual_registers``
|
|
|
|
They are only ignored for an m68k CPU; a 6502 target still requires them, and a typo there is still
|
|
reported as a missing property. Set ``pointer_size = 4``, since m68k pointers are 32 bit.
|
|
|
|
Use ``output_type = ELF``. This is the only output type that links, and therefore the only one a
|
|
custom m68k target can use; the ``AMIGAHUNK`` output type assembles straight to an Amiga hunk
|
|
executable and is only available for the built-in Amiga targets. Two other things are worth knowing
|
|
when targeting bare metal: the FPU is enabled for the assembler regardless of the target, and the
|
|
assembler CPU is only upgraded to 68020 for ``amiga500`` with floating point enabled.
|
|
|
|
.. _custom_target_m68k_linker:
|
|
|
|
The linker script
|
|
^^^^^^^^^^^^^^^^^
|
|
|
|
An ``ELF`` output is produced in two steps: the compiler assembles to an object file, and then
|
|
links it into an ELF executable. The layout of that executable comes from a linker script, which
|
|
the compiler looks up in this order:
|
|
|
|
#. ``link.ld`` in the target's library directory (the directory named by the ``library`` property).
|
|
This is how a custom target, which lives outside of the compiler, supplies its own memory layout.
|
|
#. ``/prog8lib/<targetname>/link.ld``, for targets that are built into the compiler.
|
|
#. ``/prog8lib/qemu68k/link.ld`` as a fallback, so a target without a script of its own still links.
|
|
|
|
A minimal script for a custom target looks like this::
|
|
|
|
ENTRY(prog8_program_start)
|
|
SECTIONS
|
|
{
|
|
. = 0x40000;
|
|
.text : { *(.text) }
|
|
.data : {
|
|
. = ALIGN(4);
|
|
*(.data)
|
|
. = ALIGN(4);
|
|
prog8_bss_section_start = .;
|
|
*(.bss)
|
|
*(COMMON)
|
|
. = ALIGN(2);
|
|
prog8_program_end = .;
|
|
}
|
|
}
|
|
|
|
``ENTRY`` should point at ``prog8_program_start``, the label the compiler emits for the start of
|
|
the program. The ``.data`` and ``.bss`` sections are deliberately merged into a single output
|
|
section so that the linker places them in one load segment. If ``.bss`` were its own output section,
|
|
the linker would compute that segment's address as "end of the previous segment + alignment" instead
|
|
of honouring the address in the script. Then ``prog8_program_end`` would no longer point at the true
|
|
end of the loaded image. For the same reason ``prog8_program_end`` is defined by the linker script
|
|
rather than by the generated assembly. Startup code that needs to clear the bss, such as the
|
|
``qemu68k`` bootinfo support, references that symbol, so leave it in.
|
|
|
|
The complete script used by the ``qemu68k`` target, together with a description of the linking
|
|
steps, is in :source:`the m68k linking documentation <codeGenM68k/docs/linking.md>`.
|
|
|
|
Source level restrictions
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
The m68k code generator rejects a number of directives and options that only make sense for a
|
|
6502 target: the ``%launcher``, ``%address``, ``%memtop`` and ``%varsaddress`` directives, split word arrays
|
|
written as ``&>``, and specific block addresses. Register annotations on ordinary subroutine parameters
|
|
are rejected as well; those are only used in ``asmsub``. The ``-varsgolden``, ``-varshigh`` and
|
|
``-varsaddress`` command line options are rejected as well, so where the variables are placed is
|
|
decided by the linker script instead.
|
|
|
|
|
|
Memory Model
|
|
============
|
|
.. index:: single: Targets; Memory model
|
|
|
|
.. _pointer_size:
|
|
|
|
Pointer size
|
|
------------
|
|
The size of a pointer (memory address) depends on the target's CPU architecture:
|
|
|
|
- **6502-family targets** (c64, cx16, c128, pet32): **2 bytes** (16-bit address space, 64 KB)
|
|
- **m68k targets** (amiga500, amiga1200, qemu68k): **4 bytes** (32-bit address space, 16 MB)
|
|
- **virtual target**: **4 bytes** (32-bit address space, 16 MB)
|
|
|
|
The ``pointer`` type keyword automatically takes the correct size for the target,
|
|
making it the natural choice for variables that hold a memory address in portable code.
|
|
On 6502 targets it is equivalent to ``uword``; on m68k and virtual targets it is equivalent to ``long``.
|
|
Alternatively, you can use ``uword`` or ``long`` directly if you know the target in advance.
|
|
|
|
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
|
|
----------------------------------
|
|
.. index:: single: Targets; Memory map
|
|
|
|
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
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.. index:: single: Targets; Commander X16 notes
|
|
|
|
*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.
|
|
Note: these virtual registers are not automatically initialized to zero at program startup.
|
|
|
|
$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
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.. index:: single: Targets; Commodore 64 notes
|
|
|
|
*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.
|
|
|
|
|
|
Footnotes for the Commodore 128
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.. index:: single: Targets; Commodore 128 notes
|
|
|
|
*Golden Ram $1300 - $1BDF*
|
|
Application RAM area, free to use if you don't use BASIC variables or strings.
|
|
Note: $1BE0 - $1BFF are used for the 16 virtual registers cx16.r0 .. cx16.r15.
|
|
There is no "high ram" defined on the C128.
|
|
|
|
*Program RAM $1C00 - $BFFF*
|
|
On the C128 the Basic ROM is banked out by default, reclaiming the RAM area from $1C00 to $BFFF.
|
|
This gives about 41 Kb of contiguous RAM for Prog8 programs.
|
|
|
|
|
|
Footnotes for the Amiga targets
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.. _amiga500footnotes:
|
|
.. index:: single: Targets; Amiga 500 notes
|
|
|
|
The amiga500 and amiga1200 targets are **experimental**. They use the M68k code generator
|
|
backend and produce Amiga Hunk executable files via the vasm assembler.
|
|
The amiga500 targets a plain 68000 CPU; the amiga1200 targets a 68020 CPU (which enables
|
|
some extra instructions) and assembles with the 68881 FPU enabled. Everything else in this
|
|
section applies to both.
|
|
|
|
Unlike the 6502 targets, the 68000/68020 CPU is **big endian**: multi-byte values (words, 32-bit longs and
|
|
pointers) are stored Most Significant Byte first in memory. See :ref:`pointer_size` and
|
|
:ref:`the endianness notes <endianness>` in the variables chapter.
|
|
|
|
The ``-emu`` flag uses **Vamos** from the `Amitools <https://github.com/cnvogelg/amitools>`_ package
|
|
to run the compiled program. Vamos is a very limited Amiga runtime that runs on the host operating system
|
|
directly - it is **not** a full Amiga emulator. Many AmigaOS library calls and features are not supported
|
|
or behave differently. For best results, copy the generated executable to a proper Amiga emulator such as
|
|
`WinUAE <https://www.winuae.net/>`_ or `FS-UAE <https://fs-uae.net/>`_ and run it there instead.
|
|
|
|
Blocks can opt-in to living in Amiga CHIP RAM via the ``%option amiga_chipram`` block-level option;
|
|
see the :ref:`directives` for details on the ``%option`` directive.
|
|
|
|
**Command-line arguments:**
|
|
When a program is launched from the Amiga CLI (shell), command-line arguments are available via
|
|
``sys.arguments``. This is a pointer to a null-terminated string containing the arguments. If the
|
|
program is launched from Workbench (GUI), ``sys.arguments`` is NULL. Example::
|
|
|
|
%import textio
|
|
main {
|
|
sub start() {
|
|
if sys.arguments != 0 {
|
|
txt.print("Arguments: ")
|
|
txt.print(sys.arguments)
|
|
txt.print("\n")
|
|
} else {
|
|
txt.print("Launched from Workbench\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
Note: Do not use ``%option no_sysinit`` on amiga500/amiga1200 programs that need CLI argument handling,
|
|
as the startup code is required to capture and process the arguments.
|
|
|
|
**Icon library and Tooltypes:**
|
|
Amiga programs launched from Workbench don't have an arguments string, but they can read
|
|
configuration from their icon's Tooltypes using the ``icon`` module. Tooltypes are stored in the program's .info file and can be
|
|
accessed via ``icon.GetDiskObject()``, ``icon.FindToolType()``, and ``icon.MatchToolValue()``.
|
|
Note: Check that ``sys.IconBase`` is non-zero before using icon functions.
|
|
|
|
|
|
Zero page usage by the Prog8 compiler
|
|
-------------------------------------
|
|
.. index:: single: Targets; Zero page usage
|
|
|
|
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 Accessible Registers
|
|
-----------------------------
|
|
.. index:: single: Targets; CPU Registers
|
|
|
|
The hardware CPU registers (A, X, Y) are not directly accessible from regular Prog8 code.
|
|
If you need to work with them, you'll have to use some inline assembly with ``%asm``.
|
|
Or, if they are required to have a value as arguments to some external kernal or library assembly routine,
|
|
just use a normal subroutine call to an ``extsub`` that correctly specifies what registers go where.
|
|
The compiler will then take care of loading the arguments into the required registers and returning
|
|
any response value(s) back to the prog8 code.
|
|
|
|
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. With the special status branch statements like ``if_cc``,
|
|
``if_cs`` etc you can branch directly on the status of the flags.
|
|
|
|
.. note::
|
|
On M68k targets, ``set_carry()`` and ``clear_carry()`` also affect the X (extend) flag,
|
|
because the rotate instructions (``rol``, ``ror``) use the X bit as the rotate-carry
|
|
rather than the C bit that is used for comparisons. ``set_carry``/``clear_carry`` manage
|
|
both bits to keep them in sync. On 6502-based targets there is a single carry flag and
|
|
this distinction does not arise.
|
|
|
|
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 from everywhere.
|
|
|
|
|
|
IRQ Handling (general)
|
|
======================
|
|
.. index:: single: Targets; IRQ Handling
|
|
|
|
The IRQ facilities described here apply to the CBM 6502-based targets (c64, c128, pet32, cx16) only.
|
|
The m68k targets do not provide them: the amiga500 and amiga1200 have none of these routines (interrupt handling
|
|
there is done via the AmigaOS, which is out of scope for these convenience routines) and the qemu68k
|
|
simulator only offers the interrupt disable/enable helpers.
|
|
|
|
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).
|
|
|
|
High-level convenience routines
|
|
-------------------------------
|
|
|
|
On the C64, C128, PET32 and CommanderX16 targets there are a few library routines available to make setting up
|
|
60hz/vsync IRQs a lot easier (no assembly code required).
|
|
These routines are::
|
|
|
|
sys.set_irq(uword handler_address)
|
|
sys.restore_irq() ; set everything back to the systems default irq handler
|
|
sys.set_rasterirq(uword handler_address, uword rasterline) (not on PET32)
|
|
sys.update_rasterirq(uword handler_address, uword rasterline) (not on PET32)
|
|
sys.set_rasterline(uword rasterline) (not on PET32)
|
|
|
|
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.
|
|
|
|
.. note::
|
|
The PET32 target only supports ``sys.set_irq()`` and ``sys.restore_irq()``.
|
|
The raster-related routines are not available on PET32.
|
|
|
|
|
|
Low-level bare IRQ handler
|
|
--------------------------
|
|
|
|
Some targets allow you to install a "bare" IRQ handler directly into the system's interrupt vector,
|
|
bypassing the high-level ``sys.set_irq()`` convenience routines. A bare handler is just a subroutine
|
|
that is called directly by the CPU on each interrupt -- there is no register-saving wrapper around it.
|
|
|
|
On Commodore targets (C64, C128, PET32, Commander X16) you install such a handler by writing to the
|
|
``cbm.CINV`` RAM vector::
|
|
|
|
uword saved_irq = cbm.CINV ; save old handler address (may be $0000 if none)
|
|
|
|
sys.set_irqd()
|
|
cbm.CINV = &my_handler ; install our handler
|
|
sys.clear_irqd()
|
|
|
|
Other targets may use a different vector or may not support bare handlers at all.
|
|
|
|
.. caution::
|
|
On Commodore targets, the KERNAL saves A, X, Y before dispatching through the CINV vector,
|
|
so a bare handler does not need to save/restore them. However, the handler **must** save
|
|
and restore the compiler's internal zero-page scratch registers via
|
|
``sys.save_prog8_internals()`` / ``sys.restore_prog8_internals()`` — the KERNAL does not know
|
|
about those, and the handler's own Prog8 code may corrupt them.
|
|
|
|
The handler must also chain to the previous handler to preserve system services
|
|
(jiffy clock, keyboard scan, cursor blink). A correct bare handler on Commodore looks like this::
|
|
|
|
uword saved_irq
|
|
|
|
sub my_handler() {
|
|
sys.save_prog8_internals()
|
|
|
|
; ... your IRQ handler code here ...
|
|
|
|
sys.restore_prog8_internals()
|
|
goto saved_irq ; chain to previous handler
|
|
}
|
|
|
|
Make sure to declare ``saved_irq`` as a ``uword`` variable in the same scope, and save the
|
|
current value of the interrupt vector into it before overwriting it.
|
|
|
|
.. caution::
|
|
Be cautious about calling ROM routines within an interrupt handler. Some kernal routines are fine to call,
|
|
others can be problematic. The safest approach is just to modify a flag variable in the handler and act on that
|
|
flag in the regular main loop of the program (i.e. call the required kernal routines there, and then reset the flag).
|
|
|
|
.. caution::
|
|
It is advised to **not use floating point calculations** inside IRQ handler routines.
|
|
They are very slow, and the correct ROM bank must be enabled to call them. Make sure the correct
|
|
ROM bank is reset at the end of the handler as well.
|
|
There is also 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...
|
|
|
|
.. caution::
|
|
The Commander X16's sixteen '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. 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()``.
|
|
|
|
|
|
.. _x16-specific-irq:
|
|
|
|
Commander X16 specific IRQ handling
|
|
===================================
|
|
.. index:: single: Targets; Commander X16 IRQ Handling
|
|
|
|
Be careful: for the CommanderX16, 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 paragraph below for perhaps a better and easier solution that
|
|
is tailored to this system.
|
|
|
|
.. caution::
|
|
**When Using VERA in the handler:**
|
|
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()
|
|
|
|
|
|
**Multi-IRQ support routines**
|
|
|
|
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.
|
|
*All cautions mentioned above, regarding the VERA and Virtual registers, floating point and kernal routines, still apply!*
|
|
|
|
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. 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, TIMER1(VIA1), VSYNC, SPRCOL, AFLOW.
|
|
|
|
``set_vsync_irq_handler (uword address)``
|
|
Sets the vertical 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.
|
|
|
|
``set_timer1_irq_handler (uword address)``
|
|
Sets the VIA1 TIMER1 irq handler to use with enable_irq_handlers(). Does not enable or disable VIA1 timer irqs setting.
|
|
|
|
``disable_irq_handlers ()``
|
|
Hand control back to the system default IRQ handler.
|
|
|
|
And a utility method to set the VIA1 timer1 to trigger IRQs at regular intervals:
|
|
|
|
``sub set_timer1 (uword delay, bool keeprunning)``
|
|
Set VIA1 timer1 to trigger after the given delay (cycles).
|
|
Enables VIA timer1 irqs if delay>0, otherwise disables it.
|
|
If keeprunning then the timer keeps triggering, otherwise it stops after a single trigger.
|
|
The speed of the timer depends on the clock speed of the X16 (controlled by a jumper on the motherboard, usually 8 MHz).
|