Files
prog8/docs/source/targetsystem.rst
T

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).