.. index:: bank switching


.. _bankSwitching:

Bank switching
==============

The bank system on the HP-41 allows for up to four pages to reside in
the same 4K address page. The bank switch mechanism is actually
implemented by the memory system. The CPU itself is totally unaware of
it.

In practice, there are two basic ways to approach writing bank
switched code.

The first approach is to use (at least) two address pages. Bank
switching can then be done in one of the pages (which is not
bank switched), and the bank switch affect the other page. This makes
it fairly easy to control and layout memory and is the approach
used in the HP-41CX operating system.

The second approach is to switch bank inside the page you are
executing in, essentially pulling the rug on yourself, or rather
replacing it on the fly. For this to work, you need to place code so
that the other bank is ready to take over immediately where you happen
to be executing when the bank switch takes place.

The advantage of the first approach is that it simpler and allows for
somewhat more flexibility. The disadvantage is that it occupies two
address pages.

This tool set provides support to do either way and makes the latter
single page approach virtually as simple to implement as the two page
approach.


Hardware behavior
-----------------

The original bank switch mechanism as defined by HP, relied on a
specific memory chip that was used to load the ROM images. This chip
had some peculiarities in that it put constraints on what instruction
could appear before the bank switch instruction. One can see this if
one study the code of the HP-41CX or the Advantage ROM, many bank
switch instructions are preceded by a ``goto`` instruction (branching to
the next line). HP also imposed some restrictions on that certain
areas in bank switched modules were required to contain specific
code, otherwise the load software HP used would refuse to accept the
module image.

As HP no longer produce these memory chips or provide any module
building services, we are no longer restricted by these limitations
and rules.

The reason for the imposed specific code was most likely to make
it possible to enable bank pages from diagnostic software, which makes
it possible to verify the checksum of banked pages.

Current hardware available are Clonix, NoV series and NEWT (41CL).
The MLDL2000 is reasonable recent, but sadly not available anymore.
All of them support running banked modules.

The MLDL2000 and NEWT provide bank switching by "pairing pages", that is,
the two pages that are covered by the same physical port are bank
switched together. Some special rules apply for low memory in NEWT as
it needs to handle the HP-41CX mainframe image where bank switching
take place between page 3 and 5. Clonix differs in that bank switching
takes place in all pages in the module, not just the two pages next to
each other.

What it means in practice is that provided we follow the golden rule
that we should never leave a secondary page active when we give
control back to the operating system, then we are safe. Calling
mainframe entry points are OK, provided that they return to the
caller. For routines that will (or may) not return, we should only
call them from the main bank (bank 1). We can also relax the rules
that HP used to impose one banked modules.

If we use the approach to have two pages next to each other, where one
switches the bank of the other and then make calls into it, the first
page will actually also switch bank! This means that we need to mirror
the unbanked page into the secondary banks of that page. Fortunately,
there is no need to duplication the page, the hardware just need to
the configured to show that page in the other bank too. This is either
done automatically by the load tool, or is trivial to do in the
hardware configuration, but can be worth knowing.



.. index:: bank switching, .shadow directive, directive;.shadow

Single page
-----------

To make it possible to arrange code to appear in the right place in
different banks, a mechanism exists for setting up a section fragment
to shadow another one. Essentially, it means that you can specify that
a section fragment (the shadow) is placed relative to another section
fragment, but in a different memory bank.

To make it work you need to insert the appropriate bank switch
instruction at the right place and provide linker rules to get the
different section fragments to reside in the desired bank.


The ``.shadow`` directive creates a shadow relation. The following
macro makes it easy to switch bank on the fly:

.. code-block:: ca65

    switchBank:   .macro  n
                  enrom\n
    10$:
                  .section Code\n
                  .shadow 10$
                  .endm

Provided you have you have defined this macro, you can use it to
switch bank in the following way:

.. code-block:: ca65

                  .name   "ADD"
    ADD:          s9=0
                  gsbp    FindBufferGetXSaveL
                  switchBank 2
                  c=regn  X

This prelude of an ``ADD`` instruction does an initial few instruction
in the main bank, then switch over to bank 2 where the ``c=regn X``
and following instructions are.

In a list file, it looks as follows:

.. code-block:: ca65

    1786  004a 084004               .name   "ADD"
    1786  004c 001
    1787  004d 244    ADD:          s9=0
    1788  004e ...... ADD_2:        gsbp    FindBufferGetXSaveL
    1788  0050 ...
    1789                            switchBank 2
        \ 0051 180                enrom2
        \ 0052        10$:
        \ 0000                    .section Code2
    1790  0000 0f8                  c=regn  X

As can be seen, there is just a single ``enrom2`` instruction inserted
in the code. The code starting with ``c=regn X`` appears in a new
section fragment.


Details of the switchBank macro
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The macro takes a single argument, the bank it should switch to, which
can be any number from 1 to 4. This number is both used as part of the
``enrom`` instruction and the new section fragment. It just happens
that the ``enrom`` instruction wants a number 1 to 4, and we can just
use make up a suitable section name based on it as well.

After executing the ``enrom`` instruction, the CPU will want to
execute the instruction immediately following it. As we just switched
bank, that instruction will be fetched from the bank we switched to.

To make it work, we want to align the code in the other bank so that
it ends up at the location following the ``enrom`` instruction of the
previous bank. To get a reference to that location, the macro defines
a label after the ``enrom`` instruction (the local ``10$`` label).

As the following code has to be in a different bank, the macro starts
with a new section fragment and names it with the bank number as suffix.
There is nothing magical about the section name here, it can
be anything, but doing it this way provides a suitable name that
contains the bank number.

The ``.shadow`` directive takes a location expression as its
argument. In this case we want the address after the ``enrom``
instruction. It adds an annotation to the current section (the newly
created ``Code2`` section fragment) that later will get emitted in the
object file to inform the linker.

The linker will find this ``.shadow`` annotation and arrange so that
the first instruction of this ``Code2`` section fragment will be
placed at the address just after the ``enrom2`` instruction.

For this to work, you need to specify that section ``Code2`` should be
in bank 2 in your linker rules file. Here is how such file can look:

.. code-block:: scheme

    (define memories
      '((memory Page1 (position independent) (bank 1) (address (#x0 . #xFFF))
                (section (FAT #x0) Code1 (Tail #xFD4))
                (checksum #xFFF hp41)
                (fill 0))
        (memory Page2 (position independent) (bank 2) (address (#x0 . #xFFF))
                (section (Bank2Header #x0) Code2 (Tail2 #xFF1))
                (checksum #xFFF hp41)
                (fill 0))))


It is possible to switch banks more than once and it is also possible
to have more than one section fragment as shadows to the same (parent)
section fragment. However, only one ``.shadow`` directive is allowed
in a section fragment, meaning that you can only anchor yourself
relative to one other section fragment.

.. note::
   Switching banks like this will cause small code slices to be
   sprinkled out in the other bank. To get best allocation performance
   out of that bank, it is a good idea to also provide routines in it
   that can be placed more freely, i.e. subroutines.

.. note::
  As a general rule, it is better to have several smaller section
  fragments rather than few large ones, as the smaller section
  fragments give the linker more freedom to place code.



Exiting back
------------

To exit back to the main bank from a secondary bank, you may want to
go through some support routine you have defined that does necessary
cleanup before exiting back to the operating system.

To exit from a secondary bank, you can provide an alternative entry
point for such routine that just consists of a single ``enrom1``
instruction to enble bank 1 and place it so that it appears at the
address location immediately before the actual routine in bank 1.

Here is one way of doing it with the ``.shadow`` directive:

.. code-block:: ca65

                  .section Code2
                  .shadow PutXDrop-1
    PutXDrop_rom2:
                  enrom1

This just outputs a single ``enrom1`` instruction in bank 2, which
gets located at the address before ``PutXDrop`` which is in bank 1.

From the secondary bank, we exit by jumping to ``PutXDrop_rom2``
(i.e. using a ``goto``, ``golong`` or ``golp`` instruction). Inside the
bank the ``PutXDrop_rom2`` routine looks like a single bank switch
instruction located in the middle of nowhere. This instruction opens a
trap door and we drop into bank 1 and end up in the routine that
perform the exit.

.. note::
   The ``PutXDrop`` routine does not need to be the first location of
   the section fragment it belongs to, it can be located anywhere in
   its section fragment.

.. note::
  If you have more banks, you will need one routine like this for each
  bank you plan do drop into ``PutXDrop`` from.


As mentioned, to use it simply jump there using either a ``goto``,
``golong`` or ``golp`` instruction from a bank 2 section:

.. code-block:: ca65

      someEntry:
                  ...
                  golp    PutXDrop_rom2


.. index:: banks, calling between

Calling between banks
---------------------

Calling routines in another bank can be done, but it consumes one
additional subroutine level. To do it, a stub routine can be used that
temporarily switch the bank during the actual subroutine call,
basically surrounding it by ``switchBank`` macros on both sides:

.. code-block:: ca65

              .section Code2
   findBufferUserFlags_rom2:
              c=regn  14
              rcr     12
              st=c
              switchBank 1
              gsbp    findBuffer
              switchBank 2
              cstex
              rtn

If you are in a page relocatable module, the ``gsbp`` instruction
temporarily takes one additional subroutine level. Making calls like this
quickly consumes the preciously small subroutine stack.
In some cases it can still make sense to do it.

There may also be another routine like it in bank 1:

.. code-block:: ca65

              .section Code
   findBufferUserFlags:
              c=regn  14
              rcr     12
              st=c
              gsbp    findBuffer
              cstex
              rtn

In this case we get the same routine available in both bank 1 and 2 at
the cost of some small code duplication. Stack usage will be
the same in this case, and the called routine ``findBuffer`` is
defined only once.


.. note::
   In most cases, some planning on how to arrange code to minimize the
   need of bank switching, is often well spent time.

.. note::
   Small code bduplication can be a better way. In such cases, macros can
   help in defining the actual code sequence only once.
