<!-- PDF page 275 -->

## Chapter 25 – Using Machine Code

### Using Machine Code

Computers do not respond directly to BASIC, or any other higher level programming language. Instead, such languages are either interpreted or compiled into what is known as *machine code*, and it is this which is understood by the CPU. The kind of processor that is built into the computer determines the type of machine code that is used. The ZX Spectrum range contains a Z80 processor, and so one writes Z80 machine code when addressing the processor directly. Specifically for the ZX Spectrum Next, the CPU is an updated one called Z80N which contains a superset of the instructions found in the Z80.\
This section is written mainly for those who understand Z80 machine code. If you do not, but would like to, you might choose to read a book about it. Suitable titles will be something along the lines of *Z80 machine code (or assembly language) for the absolute beginner*. If it also mentions one of the computers in the ZX Spectrum range, so much the better. You might also like to read online resources and find tools such as the Design-Design **Zeus** *cross-assembler*[^p275-1] at: **https://www.desdes.com/products/oldfiles/**, or the **z88dk** suite which includes apart from a C compiler, also an assembler, at **www.z88dk.org**, and last but not least the **specnext.com** forums.

Rather than write the numerical values of a machine code program directly, people usually choose to use mnemonics, known as assembly language, which, although cryptic, is not too difficult to understand with practice. You can see the assembly language instructions understood by the ZX Spectrum Next's CPU in *Appendix A*.

For a computer to execute this code the program must be converted into a sequence of bytes – in this form it is called machine code. This translation is usually done by a computer, using a program called an assembler. There is no assembler built into the ZX Spectrum Next ROM, however, three, loadable ones are included in the **System/Next™** distribution: **Zeus**, **Odin** and **SPED** kindly provided by Neil Mottershead and Simon Brattel for the first, Matt Davies for the second and César Hernández Bañó for the latter respectively. It is also possible do the translation yourself, but this can be a painstaking process.

Let's take as an example the program:

```
ld bc, 99
ret
```

This will load the **BC** register pair with **99** and then return. This translates into the four machine code bytes **1**, **99, 0** (**ld bc**, **99**) and **201** (**ret**). (If you look up codes **1** and **201** in *Appendix A*, you will find that **1** corresponds to **ld bc, NN** – where **NN** stands for any two-byte number; and **201** corresponds to **ret**.)

### Using CLEAR to Make Space

Once you have written your machine code program, the next step is to load it into the computer's memory. You need to decide whereabouts in memory to locate it – the best thing is to make extra space for it between the *NextBASIC* area and the user-defined graphics.

If you enter the command:

```
CLEAR 65267
```

This will give you a space of **100** (for good measure) bytes starting at address **65268**.To create the machine code program, you may run a *NextBASIC* program like this:

```
10 a=65268
20 READ n: POKE a,n
```

[^p275-1]: A cross-assembler is an assembler that runs on a different system than the one it produces code for. For example z88dk is usually executed on a PC running Linux or Windows Operating Systems.

<!-- PDF page 276 -->

```
30 a+=1: GO TO 20
40 DATA 1,99,0,201
```

This will stop with the report **E Out of DATA** when it has filled in the four bytes you specified.

### Using USR to run machine code

To run the machine code, you use the function **USR** or its –preferred– **BANK** command variant. In its simplest form **USR** must be provided with a numeric argument, i.e. the starting address or the bank offset. Its result is the value of the **BC** register on return from the machine code program, so assuming you type:

```
PRINT USR 65268
```

It will return the value **99**.

The return address to *NextBASIC* is stacked in the usual way, so return is by a Z80 **ret** instruction. You should not use the **IY** and **I** registers in a machine code routine that expects to use the *NextBASIC* interrupt mechanism. To perform the exact same function by using the **BANK** variant, make the following changes to our program:

```
10 %a=0
20 BANK NEW %b
30 READ %n : BANK %b POKE %a,%n
40 %a+=1: GO TO 30
50 DATA 1,99,0,201
```

**RUN** it and you'll see the **E Out of Data** error again; Now it's time to execute it and it's done by giving:

```
PRINT % BANK b USR 0
```

There are a few more variants of **USR** that differ in key points and make the life of the machine-code programmer a bit easier. These are:

**USR$** *addr*\
**BANK** *n* **USR$** *offset*

which call the machine code routine at *addr* (or *offset* in bank *n*). Instead however of returning the 16-bit number found in **BC** (as with **USR** *addr*), **USR$** returns a string, defined by the start address returned by the machine-code routine in **DE** and length in **BC**.

Additionally, **USR** as well as **USR$** (and their **BANK** variants) can be provided with optional parameters like:

**USR**(*addr, param1*[, *param2* [, *param3*...]]])

**USR$**(*addr, param1*[, *param2* [, *param3*...]]])

**BANK** *n* **USR**(*addr, param1*[, *param2* [, *param3*...]]])

**BANK** *n* **USR$**(*addr, param1*[, *param2* [, *param3*...]]])

which can be passed to the machine-code routine (instead of just the start address in **BC**).

If a single additional parameter (*param1*) is present, this is passed in **BC** (if it is numeric) or as an address **DE** and length **BC** (if it is a string).

The type of the parameter passed to the routine is indicated by the *zero flag*: if set, the parameter is a string (in **DE**,**BC**); if clear, the parameter is a number in **BC**.

<!-- PDF page 277 -->

Additionally, the type of the expected result is indicated by the carry flag: if set, the expected result is a number (in **BC**); if clear, the expected result is a string (in **DE**,**BC**).

All further parameters are left on the calculator stack for the machine-code routine to use with calculator operations or retrieve using standard ROM routines such as FIND-INT1, FIND-INT2, STK-FETCH. On entry, **A** contains the number of additional parameters on the calculator stack (**0**-**16**) and **HL** contains a bitmask indicating the type of each parameter. Bit **15**, indicates the type of the final parameter (and will be the first to be retrieved from the calculator stack), so types can be read by shifting each bit in turn to the carry flag with **ADD HL,HL**. Type bits are **0** for string, **1** for numeric.

Routines must remove all additional parameters from the calculator stack, otherwise it will be unbalanced and the expression may be calculated incorrectly.

If you are writing a program to run with the 48K or 128K ROM, you should not load **I** with values between **40h** and **7Fh** (even if you never use IM 2). When using one of the 128K ROMs, values between **C0h** and **FFh** for **I** should also be avoided if you plan on enabling contention for your target machine / personality and contended memory (i.e. RAM 4 to 7) is to be paged in between **C000h** and **FFFFh**. This is due to an interaction between the ULA and the Z80 refresh mechanism, which can cause apparently inexplicable crashes, screen corruption or other undesirable effects. Thus, you should only use vector IM 2 interrupts between **8000h** and **BFFFh** unless you are very confident of your memory mapping (or you are only going to run your program on the +2A, +3e or Next personalities where this problem does not exist).

There are a number of standard pitfalls when programming a banked system such as the ZX Spectrum Next from machine code. If you are experiencing problems, check that your stack is not being paged out during interrupts, and that your interrupt routine is always where you expect it to be (it is advisable to disable interrupts during paging operations). It is also recommended that you keep a copy of the current bank register setting in unpaged RAM somewhere as the ports are write-only. *NextBASIC* and the editor use the system variables BANKM and BANK678 for **7FFDh** and **1FFDh** respectively.

If you call *NextZXOS* routines, remember that interrupts should be enabled upon entry to the routines. Remember also that the stack must be below **49120** (**BFE0h**) and above **16384** (**4000h**), and that there must be at least **50** words of stack space available.

You can save your machine code program easily enough with, for example:

```
SAVE "name" CODE 65268,4
```

or, in case you used the **BANK** variant

```
SAVE "name" BANK %b, 0, 4
```

There is no way of saving the program such that when loaded it automatically runs itself; however, you can get round this by using the short *NextBASIC* program:

```
10 LOAD "name" CODE 65268,4
20 PRINT USR 65268
```

Which should also be saved as a separate program, using a command of the following form:

```
SAVE "loader" LINE 10
```

You may run the machine code from *NextBASIC* using the single command:

```
LOAD "loader"
```

This then loads and automatically runs the *NextBASIC* program, which in turn loads and runs the machine code. You can try and make a version with the **BANK** variant as well as that's safer and always preferred.

<!-- PDF page 278 -->

### Calling NextZXOS from NextBASIC

When *NextBASIC*'s **USR** function is used, the code it references is entered with the memory configured with the ROM switched in at the bottom of memory in the address range (000h – 3FFFh) being ROM 3 (the 48 BASIC ROM). The RAM page at the top of memory is Bank 0 and the machine stack resides in this area (unless the **CLEAR** command has been used to reduce it to somewhere below **C000h**). As explained in the accompanying documents explaining the *NextZXOS* API (found in the c:/docs/nextzxos/ folder in your System/Next™ distribution), *NextZXOS* can only be called with RAM page 7 switched in at the top of memory, the stack held somewhere in that range **4000h** to **BFE0h**, and ROM 2 (the *NextZXOS* ROM) switched in at the bottom of memory (**000h** to **3FFFh**).

Consequently, it will be necessary to switch both ROM and RAM, and move the stack before and after calling one of the entries in the DOS jump table.

If the **CLEAR** command has been used so that the *NextBASIC stack* is below **49120** (**BFE0h**), then it is not necessary to move the stack. However, we have done so in the following example to demonstrate the technique when this is not the case.

A simple example to call DOS CATALOG:

```
                org  7000h

mystak          equ  9FFFh                ;arbitrary value picked to be below
                                          ;BFE0h and above 4000h
staksto         equ  9000h                ;somewhere to put BASIC's stack
                                          ;pointer
bankm           equ  5B5Ch                ;system variable that holds the
                                          ;last value output to 7FFDh
port1           equ  7FFDh                ;address of ROM/RAM switching port
                                          ;in I/O map
catbuff         equ  8000h                ;somewhere for DOS to put its cata
                                          ;log
dos_catalog     equ  011Eh                ;the DOS routine to call
demo:           di                        ;unwise to switch RAM/ROM without
                                          ;disabling interrupts
                ld   (staksto),sp         ;save BASIC's stack pointer
                ld   bc,port1             ;the horizontal ROM switch/RAM
                     ;switch I/O address
                ld   a,(bankm)            ;system variable that holds current
                                          ;switch state
                res  4,a                  ;move right to left in horizontal
                                          ;ROM switch (3 to 2)
                or   7                    ;switch in RAM page 7
                ld   (bankm),a            ;must keep system variable up to
                                          ;date (very important)
                out  (c),a                ;make the switch
                ld sp,mystak              ;make sure stack is above 4000h and
                                          ;below BFE0h
                ei                        ;interrupts can now be enabled
                                          ;
                                          ;The above will have switched in
                                          ;the DOS ROM and RAM page 7. The
                                          ;stack has also been located in a
                                          ;"safe" position for calling DOS
                                          ;
                                          ;The following is the code to set
                                          ;up and call DOS CATALOG. This is
                                          ;where yourown code would be
                                          ;placed.
                                          ;
                ld hl,catbuff             ;somewhere for DOS to put the cata
                                          ;log
                ld de,catbuff+1           ;
```

<!-- PDF page 279 -->

```
                ld bc,1024                ;maximum (for +3DOS) is actually
                                          ;64x13+13 = 845
                ld (hl),0
                ldir                      ;make sure at least first entry is
                                          ;zeroed
                ld b,64                   ;the number of entries in the
                                          ;buffer
                ld c,1                    ;include system files in the cata
                                          ;log
                ld de,catbuff             ;the location to be filled with the
                                          ;disk catalog
                ld hl,stardstar           ;the file name ("*.*")
                call dos_catalog          ;call the DOS entry
                push af                   ;save flags and possible error num
                                          ;ber returned by DOS
                pop hl
                ld (dosret),hl            ;put it where it can be seen from
                                          ; NextBASIC
                ld c,b                    ;move number of files in catalog to
                                          ;low byte of BC
                ld b,0                    ;this will be returned in NextBASIC
                                          ;by the USR function
                                          ;
                                          ;If the above worked, then BC holds
                                          ;number of files in catalog, the
                                          ;"catbuff"
                                          ;will be filled with the alpha-
                                          ;numerically sorted catalog and the
                                          ;carry flag but
                                          ;in "dosret" will be set. This will
                                          ;be peeked from NextBASIC to check
                                          ;if all went well.
                                          ;
                                          ;Having made the call to DOS, it is
                                          ;now necessary to undo the ROM and
                                          ;RAM switch and put BASIC's stack
                                          ;back to where it was on entry.
                                          ;The following will achieve this.
                di                        ;about to ROM/RAM switch so be
                                          ;careful
                push bc                   ;save number of files
                ld   bc,port1             ;I/O address of horizontal ROM/RAM
                                          ;switch
                ld   a,(bankm)            ;get current switch state
                set  4,a                  ;move left to right (ROM 2 to ROM
                                          ;3)
                and  F8h                  ;also want RAM page 0
                ld   (bankm),a            ;update the system variable (very
                     ;important)
                out  (c),a                ;make the switch
                pop  bc                   ;get back the saved number of files
                                          ;in catalog
                ld   sp,(staksto)         ;put NextBASIC's stack back
                ret                       ;return to NextBASIC, value in BC
                                          ;is returned to USR
stardstar:
                defb "*.*",FFh            ;the file name, must be terminated
                     ;with FFh
dosret:
                defw 0                    ;a variable to be peeked from BASIC
                                          ;to see if it worked
```

As some of you may not have an assembler available, the following is a *NextBASIC* program that pokes the above code into memory, calls it, and then uses the value returned by the **USR** function and the contents of **dosret** to print a very simple catalog of the disk:

<!-- PDF page 280 -->

```
10 sum=0
20 FOR i=28672 TO 28758
30   READ n
40   POKE i,n : sum+=n
50 NEXT i
60 IF sum <> 9387 THEN PRINT
   "Error in DATA" : STOP
70 x= USR 28672
80 IF INT ( PEEK (28757)/2)=
   PEEK (28757)/2 THEN PRINT
   "Disk Error ";PEEK
   (28758): STOP
90 IF x=1 THEN PRINT "No file
   found": STOP
100 FOR i=0 TO x-2
110 FOR j=0 TO 10
120 PRINT CHR$ ( PEEK
    (32781+i*13+j));
130 NEXT j
140 PRINT
150 NEXT i
160 DATA 243,237,115,0,144,1,
    253,127,58,92,91,203,167,2
    46,7,50,92,91,237,121,49,2
    55,159,251
170 DATA 33,0,128,17,1,128,1,
    0,4,54,0,237,176,6,64,14,1
    ,17,0,128,33,81,112,205,30
    ,1,245,225,34,85,112,72,6,
    0
180 DATA 243,197,1,253,127,58,
    92,91,203,231,230,248,50,9
    2,91,237,121,193,237,123,0
    , 144,201
190 DATA 42,46,42,255,0,0
```

The addresses picked for the above code and its data areas are completely arbitrary. However, it is a good idea to keep things in the central **32K** wherever possible so as not to run into the pitfall of accidentally switching out a vital variable or piece of code.

If interrupts are to be enabled (as is the case in the above example), it is imperative that the system is kept up to date about the latest ROM switch. This means, that the user must make the BANK678 system variable reflect the last value output to the port at **1FFDh**. As shown by the above example, the general technique is to take a copy of the variable in **A**, set/reset the relevant bits, update the system variable then make the switch with an **OUT** instruction. Interrupts must be disabled while the system variable does not reflect the cur-

<!-- PDF page 281 -->

rent state of the port. The port at **1FFDh** doesn't just control the ROM switch, so setting the variable to absolute values would be very unwise. Using AND/OR with a bit mask or SET/RES instructions is the preferred method of updating the variable.

Just as BANK678 reflects the last value output to **1FFDh**, BANKM should also be kept up to date with the last value output to **7FFDh**. Again, it is unwise to use absolute values, as the port is used for other purposes. For example, the bottom 3 bits of the port are used to select the RAM page that is switched into the memory area **C000h** through **FFFFh** (this is also shown in the above example). Naturally, when more than one bit is to be set/reset, a bit mask used with OR/AND is the more efficient method. Note that RAM paging was described in the *Memory Management section* in *Chapter 24*.

The above was a very simple example of calling DOS routines. It works – apart from the ZX Spectrum Next – on the ZX Spectrum +3 and ZX Spectrum +3e as well.

### Opcodes Prefixes

Some Assembler opcodes are preceded by a prefix byte which changes the opcode represented by the following byte.

Assembler opcode prefixes **CBh** (**203**) and **EDh** (**237**) alter the meaning of certain instructions, as indicated in the 5th and 6th columns of *Appendix A*. This includes the provision of some entirely new opcodes for the ZX Spectrum Next.

Assembler opcode prefixes **DDh** (**221**) and **FDh** (**253**) alter the meaning of certain instructions that ordinarily refer to the **H** or **L** registers, so that they refer to either the component registers of **IX** or **IY** register respectively. For example, the instruction **LD H,n** will load the value of **n** into the **H** register. Preceding this two-byte instruction with the **IX** register's opcode prefix **DDh**, would result in the most significant 8 bits of the **IX** register being loaded with that value instead.

This general transformation rule is modified when the original instruction contains (**HL**), with this component replaced by (**IX +N**) and any other reference to **HL** left unaffected. For instance:

**DDh 66h** is interpreted as **ld h,(ix + N)**

A **DDh** opcode will be ignored, interpreted as **nop**, if it precedes **DDh**, **EDh** or **FDh**. Similar rules apply to the **FDh** instruction.

