Implementing New Boards

I am eager to expand the number of boards supported by the xbeaver emulator. Hence anyone with knowledge of an 80bus board that is not included in this emulation is encouraged to implement a new board.

Coding style

New boards should be implemented in new files. This makes source control & merging easy. Either a single board or multiple similar boards can be implemented in a single c source file and associated header file. All functions implemented should be made static, use a common short prefix, and prototyped near the top of the file.

Create a new instance of IOCARD_CARD_DEFN_T for each board

Each board requires a global instance of the type IOCARD_CARD_DEFN_T this should be given a suitable name reflecting the board name/number. It contains the following items:-

The name of the board
This should be in lower case and be alphanumeric without spaces.
A short description of the board.
This is displayed when xbeaver is started with the -h option.
A pointer to the initialisation function
This sets up the io ports and other items that need need initialisation.
The size of a private data area.
A seperate data area is supplied for each instance of the board. This data area is initialised to zeros before the call to the initialisation routine.

The name of the IOCARD_CARD_DEFN_T defined should be added to the list of boards defined in commands.c with the macro BOARD_LIST. New boards should be placed in a suitable position in the list to reflect their function as the order of this lists defines the order the boards are listed with the -h option.

Implement an initialisation function for each board

This function is called once for each new board in the system. It has the following arguments:-

base-port
The io port number that the board it to be mapped to.
private_data
A pointer to the private data area (already zerod).
cmd_tail
A pointer to the command tail. This can be used to parse any additional arguments used by the board

It should perform the following functions:-

  1. Parse the command tail.
  2. Initialise the private data area as required.
  3. Define any input ports the board supports using the function iobus_define_inport.
  4. Define any output ports the board supports using the function iobus_define_outport.
  5. Perform any other functions required.
  6. A periodic callback can be created using the function poll_install_routine.
  7. Any resources claimed can be released on exit by defining a callback using the function exit_install_routine.
  8. Return the value 0 for success, or a negative value for an error.

Implement any board specific input ports

Input ports are implemented as callbacks.

For an input port the callback takes the following arguments:-

offset
The offset from the base of the iogroup - this can often be ignored.
private_data
Pointer to the private data area for the board.
porthi
The top 8 bits on the Z80 io bus - this can often be ignored.
The routine returns the value read by the input port.

Implement any board specific output ports

Output ports are implemented as callbacks.

For an output port the callback takes the following arguments:-

offset
The offset from the base of the iogroup - this can often be ignored.
private_data
Pointer to the private data area for the board.
port data
The port data to be written to the port.
porthi
The top 8 bits on the Z80 io bus - this can often be ignored.

Performing DMA

The beaver system uses memory management to increase memory space accessable from 64kBytes to 1MByte. This 1MByte space is divided into 256 pages of physical memory each of 4k. The Z80 can reference any 16 of these pages as logical memory. Which 16 pages can be references is controlled by the memory management unit.

When implementing DMA it is important to decide if the DMA is to be performed with physical or logical memory.

If the DMA address is programmable then it is likely to be restricted to a 64k address range. In which case logical memory should be used. However it should be remembered that it is possible that the mapping of physical to logical pages may change while the DMA is in progress. Often the best way of avoiding this problem is to make sure that the DMA completes as soon as it is started.

If the DMA address is only set by the command tail parameters then there is no restriction on the address range. In this case the use of physical addressing is to be prefered. This is particularly true for video cards which need to retain the same display memory even when the memory management unit settings are changed.

The following routines can be used to access memory:-
gm813_mmu_physical_read
Reads physical memory at a given address
GetBYTE
Reads logical memory at a given address
PutBYTE
Writes logical memory at a given address
In addition the routine gm813_mmu_ramdis_override can be used to map board memory into the Z80 logical address space in a similar manner to the hardware ramdis signal. This routine requires Z80 addresses and sizes that are multiples of 2K.

Writing a device driver

If the new board already exists as physical hardware it is likely that software exists to check the emulation works correctly. This is often the best way of ensuring the correctness of the emulation.

If the device is a character device, or can be treated as such, then a device driver can be written for VPMM. A guide to doing so is available here. It is also worthy of note that vpmm device drivers can be used under cp/m using the devload program.