Spectranet: Tutorial 7

From Spectrum
Jump to navigation Jump to search

As well as the parts required to support the W5100 ethernet interface, the Spectranet contains extra memomory. Some of this is used to support the socket library and provide required functions to make a useful ethernet interface, but most of the memory is free for the programmer.

This includes most of the flash ROM chip. Only 16K is reserved for the Spectranet (pages 0x00 - 0x03). The remaining flash pages, 0x04 to 0x1F, or 112K of ROM, are available to the user. The great thing about this flash memory is that it is nonvolatile so the code or data within it remains available between power cycles.

You may have already seen that the memory on the Spectranet is organized into 4K pages. Therefore, it's probably no surprise that ROM modules are organized as 4K pages too.

When the Spectrum is reset or powered up, the last thing the Spectranet does before starting the Spectrum's main ROM is to examine each page of flash ROM to see if it contains executable code. This is done by examining the first byte of each page to see if it contains the byte 0xAA. If this is the case, it examines the second byte, which contains the ROM's identity and stores this in a table. It then checks to see whether the ROM has a valid RESET vector, and if it does, it calls the routine that this vector points to. This allows a module to perform some initialization. For example, tbe built-in utility ROM uses this to start the DHCP client. A ROM that extends BASIC can use the reset vector to add its extensions.

To allow access to ROM modules from assembly language or C programs, each ROM has a unique ID number (if you're writing a ROM module, ask me for an ID and I'll allocate one), and there is a call dispatch routine which allows this ID to be used to pass control to the ROM module. This system of using an ID means the ROM doesn't have to be in a certain fixed page for a routine to find it and call it.

Each ROM module, therefore, has a certain format to tell the Spectranet to run the code. The format begins with a table that looks like this:

  • Byte 0: Page signature. 0xAA for a module, 0xFF for empty, anything else for a data page.
  • Byte 1: ROM ID
  • Bytes 2,3: RESET vector (an address)
  • Bytes 4-13: Reserved (will be used as vectors)
  • Bytes 14,15: Address of the ROM's identity string

Code is kept in byte 16 onwards. The code that starts at 16 should handle calls to this module using the MODULECALL entry point (more on that later). After that, what the module contains is up to the programmer.

ROM modules get paged into paging area B, therefore ORG should always be set to 0x2000.

Installing ROM modules into the Spectranet's flash

A standard Spectranet utility is the ROM programmer. It allows you to program the flash over the network. Access this by pressing the NMI button and selecting "Add and remove ROM modules". When you choose Add Module, the Spectrum will listen for data on tcp/2000. Use the ethup utility to send the code to the Spectrum. It will then be programmed into the flash chip.

The "Hello World" of ROM modules

For the first example, I will present the simplest case, a program that just prints "Hello world" on the screen at boot time, and when called via MODULECALL, the string "Hello World Module". This demonstrates clearly a simple case of a reset routine, and a simple case of a function called by MODULECALL.

As I have already said, the first part of a ROM module is a table. We will write this first:

    org 0x2000          ; ORG address for *all* ROM modules
    defb 0xAA           ; This is a code module.
    defb 0xFE           ; This module has the identity 0xFE.
    defw INITROUTINE    ; The RESET vector - call a routine labeled INITROUTINE.
    defw 0xFFFF         ; Reserved
    defw 0xFFFF         ; Reserved
    defw 0xFFFF         ; Reserved
    defw 0xFFFF         ; Reserved
    defw 0xFFFF         ; Reserved
    defw STR_identity   ; Address of the identity string.

This table has brought the address up to 0x2010 - the 16th byte of the module. As I said earlier, this is where the MODULECALL routine begins. Our trivial module call routine prints "Hello World Module". We'll use the Spectranet's 42 column print routine to display the string:

MODULECALL
    ld hl, STR_hellomod
    call PRINT42
    ret
STR_hellomod defb "Hello World Module\n",0

After this, we´ll put our reset vector routine:

INITROUTINE
    ld hl, STR_helloworld
    call PRINT42
    ret
STR_helloworld defb "Hello, world!\n",0

Then, at the end, we'll put the identity string. This is a C string (i.e. terminated with a NULL, 0x00) and is displayed in the ROM utility. Make sure you don't forget the NULL, or you might cause yourself lots of difficulty when you next go to the ROM programming utility!

STR_identity defb "Hwllo world ROM",0

Assemble this code, and upload it to the Spectrum with ethup. Then reset the Spectrum. You'll see the message "Hello, world!" displayed just after the DHCP client runs (and just before the BASIC ROM starts). It stays up just long enough to see!

Using MODULECALL

The MODULECALL entry point allows other programs to call your ROM. MODULECALL takes one 16-bit parameter in the HL register. H contains the ROM ID number, in this case 0xFE, and L contains an arbitrary byte. Most modules will use L to provide a function number. However, it's up to the ROM module to make sense of the contents of L. It can be ignored altogether if the ROM only has one function. Alternately, it could be a table index to a function table, allowing each module to have up to 256 different publically accessable calls. Use the L register however you see fit.

Now your ROM module is installed, we'll write a little program to demonstrate the use of MODULECALL. This can exist anywhere in memory, including another module. For this example, we will assemble a program to address 0x8000 (32768 decimal), and use the paging entry point, which is at 0x3FF8. This is defined in spectranet.asm, it is likely you'll want to include this file in your project. Here is the example program:

    include "spectranet.asm"
    org 0x8000
    ld hl, 0xFE00              ; H is 0xFE, the id of our ROM
    call MODULECALL
    ret

That's all there is to it. When you run this program, you should see the string "Hello World Module" printed on the screen.

An important point to note is that "call MODULECALL" is trapped by the Spectranet hardware, and causes the Spectranet memory to get paged in. On return it is automatically paged out. You must use the CALL instruction and only the CALL instruction; CALL cc, JP or JR won't do - the Spectranet decodes the CALL instruction to make this trap happen. (This applies to the IXCALL, PAGEIN and HLCALL entry points, too). The CALL instruction is trapped rather than the execution address because some Spectrum ROMs have executable code right up to the last byte - and execution address trapping here would cause incompatibility problems.

If you are writing a program that runs within Spectranet memory, or that runs when the Spectranet is purposefully paged in, then you need to make a call to MODULECALL without the paging mechanism. This is done with RST 0x28 (RST MODULECALL_NOPAGE if you have included spectranet.asm). For example:

    include "spectranet.asm"
    org 0x8000
    call PAGEIN                ; Page in Spectranet memory
    ld hl, STR_test
    call PRINT42               ; Don't use IXCALL, call the routine directly because the Spectranet is paged in
    ld hl, 0xFE00
    rst MODULECALL_NOPAGE      ; ...and do the same with modulecall, too.
    call PAGEOUT
    ret

Errors when using MODULECALL

There is only one error defined by the system itself, and it occurs when no module is found. If you make a MODULECALL to a ROM id that doesn't exist, when MODULECALL returns the carry flag will be set and the A register will be set to 0xFF. It's suggested that any code that's written to work with MODULECALL flags errors with the carry flag and the return code in the A register (something other than 0xFF to distinguish it from "module not found", but if a call requires different error handling, there's nothing stop the module author from doing something different.

BASIC extensions in a ROM module

(todo)