16K / 48K ZX Spectrum Reference

This section is broken into several parts: Channels & Streams, Hardware, ZX Interface I and Tape Data Structure. Each of these may have several sub-sections. Within each section, you may find links to additional information elsewhere in this FAQ, or to other reference documents. Commented ROM listings are available from Geoff Wearmouth, author of the SEA Change ROM.

Channels & Streams:
There are 5 subsections: Introduction, Opening and Closing, Device Independence, More Stream Commands and Memory Formats.

  • Introduction
    The Spectrum has a surprisingly modern system of input and output when the age of the Spectrum is considered. However, what is more surprising is the fact that the Spectrum manual barely scratches the surface of what is possible.

    I/O on the Spectrum is based on channels and streams. Since the standard Spectrum has only a limited range of I/O devices is makes sense that different commands are available for each I/O device. For example, PRINT is used to send output to the screen, whereas LPRINT is used to send output to the printer.

    The extra devices catered for by the ZX Interface I (microdrives, RS-232, and networking) reduced the practicality of continuing to invent new commands, although this was provided for in the case of the microdrives.

    Streams and channels intuitively correspond to the software and hardware parts of I/O respectively. That is, a stream should be thought of merely as a collection of data going to or coming from a piece of hardware, and a channel should be associated with a particular piece of hardware such as a printer. On the Spectrum streams are numbered from 0 through 15, and their basic operations are reading and writing data.

    The BASIC statement INPUT #s; [input-list] will read data from stream number s, 0 <= s <= 15, into the variables specified in the input-list. Conversely, the BASIC statement PRINT #s; [print-list] will write data to stream s, 0 <= s <= 15. In general both INPUT # and PRINT # can be used in the same way as their ordinary counterparts INPUT and PRINT. In particular all the normal complexity of a PRINT statement can be used equally well in a PRINT # statement. In each case the data sent to the stream is exactly the same as the data which would be sent to the screen by the PRINT statement. The INPUT # statement is slightly more complicated in that is can both read and write data, as in INPUT "What is your name? "; A$. In fact each stream really has two components, an input stream and an output stream. Data written to the stream by either PRINT # or INPUT # goes to the output stream while input comes from the input stream.

    It is even possible to change streams part way through a PRINT statement, as in PRINT #3; "hello"; #6; "there". This is obviously fairly confusing though, so should probably be avoided unless their is a good reason for using this construct.

  • Opening and Closing
    How do you know which stream numbers are associated with which channel? Before a stream is used it must be OPENed. Opening a stream serves two purposes. It associates the given stream with a particular piece of hardware (the channel), and actually signals the relevant device that it is going to be used. Stream are opened in BASIC using the syntax OPEN #s, c where s is the stream number being opened and c is a string specifying the channel to associate the stream with. Following this command, any data sent to stream s will go to the specified channel. It is possible to open several streams to the same device, but each stream can only be associated with a single channel.

    The statement CLOSE #s is used to end the association of stream s with a channel. If you attempt to close a channel which is already closed on an unexpanded Spectrum then the machine might crash due to two bugs in the ROM.

    The unexpanded Spectrum supports four channels: "K" the keyboard channel, "S" the screen channel, "P" the printer channel, and "R" an internal channel used by the Spectrum to send data to the edit buffer. In practice the only channel that has both input and output of these is the keyboard channel. When the Spectrum is first powered up the following streams are opened automatically: 0: "K", 1: "K", 2: "S", 3: "P". Thus, the command LPRINT is really an alternative to writing PRINT #3. The "R" channel cannot be opened from BASIC. It is possible to redefine the standard channels, thus OPEN #2, "P" will cause output normally sent to the screen to be redirected to the printer.

    For example, OPEN #5, "K" associates stream 5 with the keyboard, and thereafter INPUT #5; A$ would behave in an identical manner to INPUT A$.

  • Device Independence
    The most important advantage of using streams is in the writing of device independent programs. Say that you wish to give the user the option of having all output go to either the screen or to the printer. Without using streams it is necessary then to have separate output statements for each device, as in;

    IF (output = printer) THEN LPRINT "Hello" ELSE PRINT "Hello"

    By using streams we can just open a particular stream (say 4) to the desired output device and thereafter use only one output statement;

    PRINT #4; "Hello"

    Obviously this will result is a much shorter program, particularly, if there are many output statements in the program. Further, it is an easy matter to add even further output devices if they become an option later in the programs development.

    You could also hack an existing program by adding somewhere near the start;

    IF [printer required] THEN OPEN #2,"P"

    which will make all ordinary PRINT instructions go to the printer.

  • More Stream Commands
    BASIC also allows LIST and INKEY$ to be used to streams. LIST #s will send a copy of the program to stream s; e.g. normally LIST #3 is the same thing as LLIST. However, on the standard Spectrum INKEY$ can only be used with the keyboard channel.

    Note that INKEY$ is not the same as INKEY$ #1 because the former does a stand-alone key scan whereas the latter attempts to read a key from the "K" device (which might involve changing the cursor mode and listing the editing area if you happen to press both shifts, for example).

  • Memory Formats
    Knowing about the actual layout of the stream records in memory is useful if you want to add your own hardware devices to the Spectrum, or if you which to make you own specialized streams. The information that defines each channel is stored in the channel information area starting at CHANS and ending at PROG - 2. Each channel record has the following format:

    • two-byte address of the output routine,
    • two-byte address of the input routine,
    • one-byte channel code letter.

    where the input and output routines are address of machine code sub- routines. The output routine must accept Spectrum character codes passed to it in the A register. The input routine must return data in the form of Spectrum character codes, and signal that data is available by setting the carry flag. If no data is available then this is indicated be resetting both the carry and zero flags. Stubs should be provided if a channel does not support either input or output (e.g. the stub may simply call RST 8 with an error code)

    With the ZX Interface I attached, an extended format is used;
    offset len description
         0  2  0x0008 (address of Spectrum error routine)
         2  2  0x0008 (ditto)
         4  1  A character describing the channel
         5  2  Address of output routine in the shadow ROM
         7  2  Address of input routine in the shadow ROM
         9  2  Total length of channel information
        11  *  Any other data needed by the channel
    The first two words being 8 denotes that the input and output routines are to be found in the shadow ROM. Either of them could be an ordinary number instead, in which case the shadow ROM will not be paged in and the word at offset 5 or 7 (as appropriate) could contain any data.

    Data about which streams are associated with which channels is in a 38-byte area of memory starting at STRMS. The table is a series of 16-bit offsets to the channel record vectored from CHANS. A value of one indicates the channel record starting at CHANS, and so on. This accounts for 32 bytes of the 38 - the remaining 6 bytes are for three hidden streams (253, 254, 255) used internally by BASIC. A zero entry in the table indicates a stream not open.

    It is possible to redirect existing channels to your own I/O routines. This can be used among other things to cause LPRINT to use your own printer driver rather than the one provided in the ROM. It allows you to perform I/O for your own hardware devices, or for you to write your own handlers from PRINT and INPUT.

    It is easiest to modify the existing "P" channel record. The "K" channel is not a good option for modification because its values are constantly being restored by BASIC.

    It is not possible to create a new channel from BASIC. Another difficulty is that without Interface I, OPEN will only work with K, S, and P and so it is necessary to provide some other way of opening your own channels. This short assembler routine will create a new channel and associate a stream with it:
         LD HL,(PROG)  ; A new channel starts below PROG
         DEC HL        ;
         LD BC,0x0005  ; Make space
         CALL 0x1655   ; 
         INC HL        ; HL points to 1st byte of new channel data
         LD A,0xfd     ; LSB of output routine
         LD (HL),A     ;
         INC HL        ;
         PUSH HL       ; Save address of 2nd byte of new channel data
         LD A,0xfd     ; MSB of output routine
         LD (HL),A     ;
         INC HL        ;
         LD A,0xc4     ; LSB of input routine
         LD (HL),A     ;
         INC HL        ;
         LD A,0x15     ; MSB of input routine
         LD (HL),A     ;
         INC HL        ;
         LD A,0x55     ; Channel name 'U' 
         LD (HL),A     ;
         POP HL        ; Get address of 2nd byte of output routine
         LD DE,(CHANS) ; Calculate the offset to the channel data
         AND A         ; and store it in DE
         SBC HL,DE     ;
         EX DE,HL      ;
         LD HL,'STRMS' ;
         LD A,0x04     ; Stream to open, in this case #4.
         ADD A,0x03    ; Calculate the offset and store it in HL
         ADD A,A       ;
         LD B,0x00     ;
         LD C,A        ;
         ADD HL,BC     ;
         LD (HL),E     ; LSB of 2nd byte of new channel data
         INC HL        ;
         LD (HL),D     ; MSB of 2nd byte of new channel data
         RET
    This routine will create a channel "U" (any ASCII character from 0 to 255 will be accepted by the standard ROM since it ignores this information). This channel has an output routine at 65021 and an input routine at 5572 (generates error report 'J'). A new entry is created in the STRMS table which points to the address of the second byte of new channel data. The stream number can be from -3 to 15, but it is best to stick to the range 4 to 15 and not modify the system streams (-3 to -1) or the standard streams (0 to 3).

Hardware:
At the hardware level, the Spectrum is a very simple machine. There's the 16K ROM which occupies the lowest part of the address space, and 48K of RAM which fills up the rest. An ULA which reads the lowest 6912 bytes of RAM to display the screen, and contains the logic for just one I/O port completes the machine, from a software point of view at least.

There are 4 subsections available: Port 0xfe, 48K Spectrum, Contended Memory and Contended Input/Output. Within each section, you may find links to additional information elsewhere in this FAQ, or to reference documents located elsewhere.

  • Port 0xfe
    Every even I/O address will address the ULA, but to avoid problems with other I/O devices only Port 0xfe should be used. If this port is written to, bits have the following meaning:

    •         Bit   7   6   5   4   3   2   1   0
                  +-------------------------------+
                  |   |   |   | E | M |   Border  |
                  +-------------------------------+
    The lowest three bits specify the border colour; a zero in bit 3 activates the MIC output, whilst a one in bit 4 activates the EAR output and the internal speaker. However, the EAR and MIC sockets are connected only by resistors, so activating one activates the other; the EAR is generally used for output as it produces a louder sound. The upper two bits are unused.

    If Port 0xfe is read from, the highest eight address lines are important too. A zero on one of these lines selects a particular half-row of five keys:

    •       IN:    Reads keys (bit 0 to bit 4 inclusive)
      
            0xfefe  SHIFT, Z, X, C, V            0xeffe  0, 9, 8, 7, 6
            0xfdfe  A, S, D, F, G                0xdffe  P, O, I, U, Y
            0xfbfe  Q, W, E, R, T                0xbffe  ENTER, L, K, J, H
            0xf7fe  1, 2, 3, 4, 5                0x7ffe  SPACE, SYM SHFT, M, N, B
    A zero in one of the five lowest bits means that the corresponding key is pressed. If more than one address line is made low, the result is the logical AND of all single inputs, so a zero in a bit means that at least one of the appropriate keys is pressed. For example, only if each of the five lowest bits of the result from reading from Port 00FE (for instance by XOR A/IN A,(FE)) is one, no key is pressed. A final remark about the keyboard. It is connected in a matrix-like fashion, with 8 rows of 5 columns, as is obvious from the above remarks. Any two keys pressed simultaneously can be uniquely decoded by reading from the IN ports. However, if more than two keys are pressed decoding may not be uniquely possible. For instance, if you press CAPS, B and V, the Spectrum will think also the Space key is pressed, and react by giving the "Break into Program" report. Without this matrix behaviour Zynaps, for instance, won't pause when you press 5,6,7,8 and 0 simultaneously.

    Bit 6 of IN-Port 0xfe is the EAR input bit. The value read from this port is not trivial, as can be seen from the following program:

          10 OUT 254,BIN 11101111
          20 PRINT IN 254
          30 OUT 254,BIN 11111111
          40 PRINT IN 254
          50 GOTO 10
    For a correct test do not press any key while running, and have no EAR input.

    • If the output is 191,255,191,255 etc, you are on real Spectrum Issue 3.
    • If output is always 191 or always 255, change the value in line 10 to BIN 11100111.
    • If output is then 191,255,191,255 etc, then you are on Spectrum Issue 2.
    • If output is still always 191 or always 255 you are on Spectrum emulator.

    The ULA chip uses the same pin (28) for all of the MIC socket, EAR socket and the internal speaker, so bits 3 and 4 of an OUT to Port 0xfe will affect bit 6 as read by an IN from Port 0xfe. The difference between Issue 2 and 3 machines is:

    Value output to bit: 4  3  |  Iss 2  Iss 3   Iss 2 V    Iss 3 V
                         1  1  |    1      1       3.79       3.70
                         1  0  |    1      1       3.66       3.56
                         0  1  |    1      0       0.73       0.66
                         0  0  |    0      0       0.39       0.34
    Iss 2 is value of bit 6 read by IN 254 after the appropriate OUT from an Issue 2, and Iss 3 is same for an Issue 3. Iss 2 V and Iss 3 V are voltage levels on pin 28 of the ULA chip after the OUT, with no input signal on the EAR socket.

    From the above, it is clear that the difference between Issue 2 and 3 is:

    • On an Issue 3, an OUT 254 with bit 4 reset will give a reset bit 6 from IN 254.
    • On an Issue 2, both bits 3 and 4 must be reset for the same effect to occur.

    Pera Putnik tested the level at pin 28 at which input bit 6 changes from 0 to 1 or reverse. This is exactly 0.70 Volts on both Issue 2 and Issue 3, with no inverting or hysteresis; this means that bit 6 is 1 if the voltage on pin 28 is over 0.70 V, and otherwise it is 0, on both Issues. At the hardware level, the only apparent difference between Issue 2 and 3 is that there are slightly higher voltages from Issue 2 machines. As can be seen from the table, the input combination '0 1' gives output voltages that are very close to the crucial 0.7 V.

    The BASIC program used above is relatively slow, and for faster programs the situation isn't so simple, as there is some delay when output bit 4 changes from 1 to 0. To illustrate this, here are 2 short assembler routines:

           ORG 45000
           LD A,0x18
           OR 0xf8
           OUT (254),A
           LD A,0x08
           OR 0xe8
           OUT (254),A
    TIMING LD B,7      ;crucial value
    DL     LD IX,0
           DJNZ DL
           IN A,(254)  ;query state
    In this case IN A,(254), or output of this value sometimes gives 255 and sometimes 191. If you make the constant in the TIMING line smaller then result will be always 255, if delay is longer then result will be always 191. Of course, the effect occurs only for Issue 3 machines.

    The situation is again slightly different for a longer duration of high output level on port 254:

           ORG 50000
           HALT        ;synchronize with interrupts
           LD A,0x18
           OUT (254),A
           HALT        ;wait 20ms
           LD A,0x08
           OUT (254),A
           LD B,107    ;crucial value
    DL     LD IX,0
           DJNZ DL
           IN A,(254)
    As you can see, after a longer high level duration, the delay is also much longer. The delay varies from approximately 180 T states (about 50 microsec) to 2800 T states (about 800 microsec), depending from duration of high level on port 254. The explanation for this delay is that there are capacitors connected between pin 28 of the ULA and the EAR and MIC connectors, but note that there is no delay when bit 4 changes from 0 to 1.

    The 'traditional' explanation of the difference between Issue 2 and 3 Spectrum (from techinfo.doc) is that PRINT IN 254 gives bit 6 reset on an Issue 3 and set on an Issue 2 machine occurs because, as PRINT IN 254 is typed at a BASIC prompt, the speaker is called for every keystroke, and the ROM beep routine contains a OR 8 before OUT (0xfe),A, so bit 3 is always set, and therefore an Issue 2 machine will always return a set bit 6.

    Bits 5 and 7 as read by INning from Port 0xfe are always one. The ULA with the lower 16K of RAM, and the processor with the upper 32K RAM and 16K ROM are working independently of each other. The data and address buses of the Z80 and the ULA are connected by small resistors; normally, these do effectively decouple the buses. However, if the Z80 wants to read or write the lower 16K, the ULA halts the processor if it is busy reading, and after it's finished lets the processor access lower memory through the resistors. A very fast, cheap and neat design indeed!

    If you read from a port that activates both the keyboard and a joystick port (e.g. Kempston), the joystick takes priority.

  • 48K ZX Spectrum
    If you run a program in the lower 16K of RAM, or read or write in that memory, the processor is halted sometimes, as the ULA needs to access the video memory to keep the TV updated; the electron beam can't be interrupted, so the ULA is given a higher priority to access the contended memory. This part of memory is therefore somewhat slower than the upper 32K block. This is also the reason that you cannot write a sound- or save-routine in lower memory; the timing won't be exact, and the music will sound harsh. Also, INning from Port 0xfe (or any other even port) will halt the processor, because the ULA has to supply the result. Therefore, INning from Port 0xfe is slower on average than INning from other ports; whilst normally an IN A,(nn) instruction would take 11 T states, it takes slightly longer if nn=0xfe. There is also an effect if the high byte of the port address is between 0x40 and 0x7f, and this looks similar to a contended memory access to the ULA. See the Contended Input/Output section for more information.

    If the processor reads from a non-existing IN port, for instance FF, the ULA won't stop (unless the high byte of the port address is between 0x40 and 0x7f), but nothing will put anything on the data bus. Therefore, you'll read a mixture of 0xff (idle bus), and screen and ATTR data bytes. This will only happen when the ULA is reading the screen memory, about 60% of the 1/50th second time slice in which a frame is generated. The other 40% the ULA is building the border or generating a vertical retrace. This behaviour is actually used in some programs, for instance, in Arkanoid.

    Finally, there is an interesting bug in the ULA which also has to do with this split bus. After each instruction fetch cycle of the processor, the processor puts the I-R register "pair" (not the 8 bit internal Instruction Register, but the Interrupt and R registers) on the address bus. The lowest 7 bits, the R register, are used for memory refresh. However, the ULA gets confused if I is in the range 64-127, because it thinks the processor wants to read from lower 16K RAM very, very often. The ULA can't cope with this read-frequency, and regularly misses a screen byte. Instead of the actual byte, the byte previously read is used to build up the video signal. The screen seems to be filled with 'snow'; however, the Spectrum won't crash, and program will continue to run normally. One program which uses this to generate a nice effect is Vectron.

    The 50 Hz interrupt is synchronized with the video signal generation by the ULA; both the interrupt and the video signal are generated by it. Many programs use the interrupt to synchronize with the frame cycle. Some use it to generate fantastic effects, such as full-screen characters, full-screen horizon (Aquaplane) or pixel colour (Uridium, for instance) Very many modern programs use the fact that the screen is "written" (or "fired") to the CRT in a finite time to do as much time-consuming screen calculations as possible without causing character flickering: although the ULA has started displaying the screen for this frame already, the electron beam will for a moment not "pass" this or that part of the screen so it's safe to change something there. So the exact time in the 1/50 second time-slice at which the screen is updated is very important. Each line takes exactly 224 T states.

    After an interrupt occurs, 64 line times (14336 T states; see below for exact timings) pass before the first byte of the screen (16384) is displayed. At least the last 48 of these are actual border-lines; the others may be either border or vertical retrace.

    Then the 192 screen+border lines are displayed, followed by 56 border lines again. Note that this means that a frame is (64+192+56)*224=69888 T states long, which means that the '50 Hz' interrupt is actually a 3.5MHz/69888=50.08 Hz interrupt. This fact can be seen by taking a clock program, and running it for an hour, after which it will be the expected 6 seconds fast. However, on a real Spectrum, the frequency of the interrupt varies slightly as the Spectrum gets hot; the reason for this is unknown, but placing a cooler onto the ULA has been observed to remove this effect.

    Now for the timings of each line itself: define a screen line to start with 256 screen pixels, then border, then horizontal retrace, and then border again. All this takes 224 T states. Every half T state a pixel is written to the CRT, so if the ULA is reading bytes it does so each 4 T states (and then it reads two: a screen and an ATTR byte). The border is 48 pixels wide at each side. A video screen line is therefore timed as follows: 128 T states of screen, 24 T states of right border, 48 T states of horizontal retrace and 24 T states of left border.

    Now when to OUT to the border to change it at the place you want? First of all, you cannot change the border within a "byte", an 8-pixel chunk. If we forget about the screen for a moment, if you execute an OUT to Port 0xfe such that the OUT ends when 14339, 14340, 14341 or 14342 T states have passed since the ULA pulled /INT low, the border will change at exactly the position of byte 16384 of the screen. The other positions can be computed by remembering that 8 pixels take 4 T states, and a line takes 224 T states. However, there are complications due to the fact that Port 0xfe is contended (as the ULA must supply the result); see the Contended Input/Output section for details.

    NB: Investigations in May 2004 have revealed that the machine timings vary, even between machines with the same model of ULA. Some machines use the timings given in this FAQ, while others are one T state later for all timings. The reason for this is not currently understood.

    The Spectrum's 'FLASH' effect is also produced by the ULA: Every 16 frames, the ink and paper of all flashing bytes is swapped; ie a normal to inverted to normal cycle takes 32 frames, which is (good as) 0.64 seconds.

  • Contended Memory
    When the ULA is drawing the screen, it needs to access video memory; the RAM cannot be read by two devices (the ULA and the processor) at once, and the ULA is given higher priority (as the electron beam cannot be interrupted), so programs which run in the contended memory (from 0x4000 to 0x7fff) or try to read from Port 0xfe (when the ULA must supply the result) will be slowed if the ULA is reading the screen. Note this effect occurs only when the actual screen is being drawn; when the border is being drawn, the ULA supplies the result and no delays occur. The precise details are as follows:

    • At cycle 14335 (just one cycle before the top left corner is reached) the delay is 6 cycles.
    • At cycle 14336 the delay is 5 cycles, and so on according to the following table:
          Cycle #    Delay
          -------    -----
           14335       6 (until 14341)
           14336       5 (  "     "  )
           14337       4 (  "     "  )
           14338       3 (  "     "  )
           14339       2 (  "     "  )
           14340       1 (  "     "  )
           14341   No delay
           14342   No delay
           14343       6 (until 14349)
           14344       5 (  "     "  )
           14345       4 (  "     "  )
           14346       3 (  "     "  )
           14347       2 (  "     "  )
           14348       1 (  "     "  )
           14349   No delay
           14350   No delay
    etc., until the cycle 14463 (always relative to the start of the interrupt), in which the electron beam reaches the border again for 96 more cycles. At cycle 14559 the same situation repeats. This is valid for all 192 lines of screen data. While the ULA is updating the border the delay does not happen at any time.

    When counting cycles several things must be taken into account. One is the interrupt setup time; another one is the precise moment within an instruction in which the R/W or I/O operation is performed (see the table below). And one more thing: the fact that an interrupt can't happen in the middle of an instruction (and a HALT counts as many NOPs), so some cycles may be lost while waiting for the current instruction to end. That's an additional difficulty e.g. for byte-precision colour changes.

    Now all that remains is to know exactly in which point(s) within an instruction is the R/W or I/O operation acting, to know where to apply the delay. That depends on each instruction. For those one-byte ops which do not perform memory or I/O access, the only affected point is the opcode fetch which happens at the first cycle of the instruction, and the address to test for contention is the current value of the program counter PC.

    For example, for a NOP (4 cycles), only the first cycle will be affected and only if PC lies within the contended memory range. So if it's executed in contended memory at cycle 14334, no delay will happen and the next instruction will (try to) be executed at cycle 14338, but if the NOP is executed at cycle 14335, it will be delayed for 6 cycles thus taking 6+4=10 cycles so the next instruction will (try to) be executed at cycle 14345. This case will be annotated in the table below as pc:4, meaning that if PC lies within contended memory then the first cycle will be subject to delay and the remaining three will be free of delays.

    The "try to" in the above paragraph is because, unless the NOP is at PC=32767, the next instruction will be subject to another delay when its opcode is fetched (the first cycle in an opcode fetch is always subject to delays) since the cycle number relative to the start of the frame is also delayed.

    So an entry like 'hl+1:3' means that if HL+1 is in range 16384-32767 and the current cycle number is subject to delays, then the delay corresponding to the current cycle must be inserted before the number of T-states that figure after the colon.

    Things get a bit more difficult with more-than-one-byte-long instructions. Here's the sample pseudocode to apply delays to an instruction with an entry in the table which reads 'pc:4,hl:3' (e.g. LD (HL),A):

      If 16384<=PC<=32767 then
         (Insert the delay corresponding to the current cycle, relative to the start of the frame)
         (according to the above table)
      (end if)
      Delay for 4 cycles (time after 'pc:').
      If 16384<=HL<=32767 then
         (Insert the delay corresponding to the current cycle...)
      (end if)
      Store A into (HL)
      Delay for 3 cycles (time taken to store A)
    Example 1: if PC = 25000 and HL = 26000 and the instruction at address 25000 is LD (HL),A and we're in cycle 14335:

    • Insert 6 cycles (count for cycle 14335) going to 14341.
    • Read the opcode.
    • Insert 4 cycles (opcode fetch). We're at cycle 14345.
    • Insert 4 cycles (count for cycle 14345). We're at 14349.
    • Store the byte.
    • Insert 3 cycles (write to (HL)).

    Next opcode will be read at cycle 14352 (and 5 cycles will be inserted then for sure because PC=25001).

    Example 2: same but PC=40000 (not contended):

    • Read the opcode.
    • Insert 4 cycles (opcode fetch). We're at cycle 14339.
    • Insert 2 cycles (count for cycle 14339). We're at 14341.
    • Store the byte.
    • Insert 3 cycles (write to (HL)).

    Access to I/O ports is treated slightly differently to access to memory; full details are given in the Contended Input/Output section. The delays specified there should be applied when an I/O port is accessed; this is designated by "IO" in the table below.

    The values for the registers listed in the table below are relative to the starting value of the register when the instruction is about to be executed.

    In the table below:

    • dd is any of the registers BC,DE,HL,SP
    • qq is any of the registers BC,DE,HL,AF
    • ss is any of the registers BC,DE,HL
    • ii is either of the index registers IX or IY.
    • cc is any (applicable) condition NZ,Z,NC,C,PO,PE,P,M
    • nn is a 16-bit number
    • n is an 8-bit number
    • b is a number from 0 to 7 (BIT/SET/RES instructions)
    • r and r' are any of the registers A,B,C,D,E,H,L
    • alo is an arithmetic or logical operation: ADD/ADC/SUB/SBC/AND/XOR/OR and CP
    • sro is a shift/rotate operation: RLC/RRC/RL/RR/SLA/SRA/SRL and SLL (undocumented)

    Further notes:

    • For conditional instructions, entries in [square brackets] are applied if the condition is met. If the instruction is not conditional (e.g. CALL nn) the entries in [] always apply.
    • The replacement of HL by either IX or IY does not affect the timings, except for the addition of an initial pc:4 for the DD or FD prefix; similarly, a DD or FD prefix on an instruction which does not involve HL just adds an initial pc:4.
    • The undocumented variants of the doubly shifted DDCB and FDCB opcodes have the same timings as the documented versions.
    • In some read-modify-write operations (like INC (HL)), the write operation is always the last one. That may be important to know the exact point in which video is updated, for example. In such instructions that point is annotated for clarity as "(write)" after the address.

        Instruction    Breakdown
        -----------    ---------
        NOP            pc:4
        LD r,r' 
        alo A,r 
        INC/DEC r 
        EXX 
        EX AF,AF' 
        EX DE,HL 
        DAA 
        CPL 
        CCF 
        SCF 
        DI 
        EI 
        RLA 
        RRA 
        RLCA 
        RRCA 
        JP (HL)
    
        NOPD           pc:4,pc+1:4
        sro r
        BIT b,r 
        SET b,r 
        RES b,r 
        NEG 
        IM 0/1/2 
    
        LD A,I         pc:4,pc+1:5
        LD A,R 
        LD I,A 
        LD R,A
    
        INC/DEC dd     pc:6
        LD SP,HL
    
        ADD HL,dd      pc:11
    
        ADC HL,dd      pc:4,pc+1:11
        SBC HL,dd
    
        LD r,n         pc:4,pc+1:3
        alo A,n
    
        LD r,(ss)      pc:4,ss:3
        LD (ss),r
    
        alo A,(HL)     pc:4,hl:3
    
        LD r,(ii+n)    pc:4,pc+1:4,pc+2:3,pc+2:1 x 5,ii+n:3
        LD (ii+n),r    
        alo A,(ii+n)
        
        BIT b,(HL)     pc:4,pc+1:4,hl:3,hl:1
    
        BIT b,(ii+n)   pc+1:4,pc+2:3,pc+3:3,pc+3:1 x 2,ii+n:3,ii+n:1
    
        LD dd,nn       pc:4,pc+1:3,pc+2:3
        JP nn 
        JP cc,nn
    
        LD (HL),n      pc:4,pc+1:3,hl:3
    
        LD (ii+n),n    pc:4,pc+1:4,pc+2:3,pc+3:3,pc+3:1 x 2,ii+n:3
    
        LD A,(nn)      pc:4,pc+1:3,pc+2:3,nn:3
        LD (nn),A
    
        The following entry applies to the unprefixed version of these
        opcodes (22 and 2A)
    
        LD HL,(nn)     pc:4,pc+1:3,pc+2:3,nn:3,nn+1:3
        LD (nn),HL
    
        The following entry applies to the prefixed version of these
        opcodes (ED43, ED4B, ED53, ED5B, ED63, ED6B, ED73 and ED7B)
    
        LD dd,(nn)     pc:4,pc+1:4,pc+2:3,pc+3:3,nn:3,nn+1:3
        LD (nn),dd
    
        INC/DEC (HL)   pc:4,hl:3,hl:1,hl(write):3
    
        SET b,(HL)     pc:4,pc+1:4,hl:3,hl:1,hl(write):3
        RES b,(HL) 
        sro (HL)
    
        INC/DEC (ii+n) pc:4,pc+1:4,pc+2:3,pc+2:1 x 5,ii+n:3,ii+n:1,ii+n(write):3
    
        SET b,(ii+n)   pc:4,pc+1:4,pc+2:3,pc+3:3,pc+3:1 x 2,ii+n:3,ii+n:1,ii+n(write):3
        RES b,(ii+n)   
        sro (ii+n)
    
        POP dd         pc:4,sp:3,sp+1:3
        RET 
    
        RETI           pc:4,pc+1:4,sp:3,sp+1:3
        RETN
    
        RET cc         pc:5,[sp:3,sp+1:3]
    
        PUSH dd        pc:5,sp-1:3,sp-2:3
        RST n
    
        CALL nn        pc:4,pc+1:3,pc+2:3,[pc+2:1,sp-1:3,sp-2:3]
        CALL cc,nn     
    
        JR n           pc:4,pc+1:3,[pc+1:1 x 5]
        JR cc,n        
    
        DJNZ n         pc:5,pc+1:3,[pc+1:1 x 5]
    
        RLD            pc:4,pc+1:4,hl:3,hl:1 x 4,hl(write):3
        RRD
    
        IN A,(n)       pc:4,pc+1:3,IO
        OUT (n),A
    
        IN r,(C)       pc:4,pc+1:4,IO
        OUT (C),r
    
        EX (SP),HL     pc:4,sp:3,sp+1:4,sp(write):3,sp+1(write):3,sp+1(write):1 x 2
    
        LDI/LDIR       pc:4,pc+1:4,hl:3,de:3,de:1 x 2,[de:1 x 5]
        LDD/LDDR       
    
        CPI/CPIR       pc:4,pc+1:4,hl:3,hl:1 x 5,[hl:1 x 5]
        CPD/CPDR       
    
        INI/INIR       pc:4,pc+1:5,IO,hl:3,[hl:1 x 5]
        IND/INDR
    
        OUTI/OTIR      pc:4,pc+1:5,hl:3,IO,[hl:1 x 5]
        OUTD/OTDR
    The above information is correct for the 48K ZX Spectrum - for the 128k/+2 models the contention sequence starts at cycle 14361.

  • Contended Input/Output
    It takes four T states for the Z80 to read a value from an I/O port, or write a value to a port. As is the case with memory access, this can be lengthened by the ULA. There are two effects which occur here:

    1. If the port address being accessed has its low bit reset, the ULA is required to supply the result, which leads to a delay if it is currently busy handling the screen.
    2. The address of the port being accessed is placed on the data bus. If this is in the range 0x4000 to 0x7fff, the ULA treats this as an attempted access to contended memory and therefore introduces a delay. If the port being accessed is between 0xc000 and 0xffff, this effect does not apply, even on a 128K machine if a contended memory bank is paged into the range 0xc000 to 0xffff.

    These two effects combine to lead to the following contention patterns:
        High byte   |         | 
        in 40 - 7F? | Low bit | Contention pattern  
        ------------+---------+-------------------
             No     |  Reset  | N:1, C:3
             No     |   Set   | N:4
            Yes     |  Reset  | C:1, C:3
            Yes     |   Set   | C:1, C:1, C:1, C:1
    The 'Contention pattern' column should be interpreted from left to right. An "N:n" entry means that no delay is applied at this cycle, and the Z80 continues uninterrupted for 'n' T states. A "C:n" entry means that the ULA halts the Z80; the delay is exactly the same as would occur for a contended memory access at this cycle (eg 6 T states at cycle 14335, 5 at 14336, etc on the 48K machine). After this delay, the Z80 then continues for 'n' cycles.

    For reference, the following changes have been made to the previous two sections since version 1.1.4 of the FAQ (10 April 2004):

    • Version 1.1.5
      • Correct the values for the first contended cycle to be 14335 and 14361 for the 48K and 128K machines respectively.
      • The table above now includes any prefixes on the instruction.
      • The pc+2:3 entry in CALL cc,nn applies when the condition is false.
      • The hl:4 entry in BIT b,(HL), INC/DEC (HL), SET b,(HL), RES b,(HL) and sro (HL) corrected to hl:3,hl:1.
      • The hl:7 entry in RLD and RRD corrected to hl:3,hl:1 x 4.
      • The sp+1(write):5 entry in EX (SP),HL corrected to sp+1(write):3,sp+1(write):1 x 2.
      • Added information about the (IX+n) and (IY+n) instructions.
      • Corrected information on INI, IND, INIR, INDR, OUTI, OUTD, OTIR and OTDR.
      • Added information on the delays that port address being between 0x4000 and 0x7fff introduces.

ZX Interface I:
The ZX Interface I uses three different I/O ports, and contains logic to page and unpage an 8K ROM if new commands are used. The ROM is paged if the processor executes the instruction at ROM address 0x0008 or 0x1708, the error and close# routines. It is inactivated when the Z80 executes the RET at address 0x0700.

Geoff Wearmouth provides complete assembly listings of version 1 and 2 (used after serial number 87315) of the ZX Interface I ROM at his web site. You may find these helpful while reading this section.

There are 3 subsections available: Port 0xe7, Port 0xef and Port 0xf7.

  • Port 0xe7
    I/O Port 0xe7 is used to send or receive data to and from the microdrive. Accessing this port will halt the Z80 until the Interface I has collected 8 bits from the microdrive head; therefore, it the microdrive motor isn't running, or there is no formatted cartridge in the microdrive, the Spectrum hangs. This is the famous 'IN 0 crash'.

  • Port 0xef
    Bits DTR and CTS are used by the RS232 interface. The WAIT bit is used by the Network to synchronise, GAP, SYNC, WR_PROT, ERASE, R/W, COMMS CLK and COMMS DATA are used by the microdrive system.

    •        Bit    7   6    5    4    3    2    1     0
                  +---------------------------------------+
              READ|   |   |    |busy| dtr |gap| sync|write|
                  |   |   |    |    |     |   |     |prot.|
                  |---+---+----+----+-----+---+-----+-----|
             WRITE|   |   |wait| cts|erase|r/w|comms|comms|
                  |   |   |    |    |     |   | clk | data|
                  +---------------------------------------+
  • Port 0xf7
    If the microdrive is not being used, the COMMS DATA output selects the function of bit 0 of OUT-Port 0xf7:

    •        Bit      7    6   5   4   3   2   1       0
                  +------------------------------------------+
              READ|txdata|   |   |   |   |   |   |    net    |
                  |      |   |   |   |   |   |   |   input   |
                  |------+---+---+---+---+---+---+-----------|
             WRITE|      |   |   |   |   |   |   |net output/|
                  |      |   |   |   |   |   |   |   rxdata  |
                  +------------------------------------------+
    TXDATA and RXDATA are the input and output of the RS232 port. COMMS DATA determines whether bit 0 of 0xf7 is output for the RS232 or the network.

Tape Data Structure:
The SAVE command produces two blocks on tape: a 19 byte header block and a variable length data block.

Each block has the following structure:

  • A leader consisting of 8063 (for header blocks) or 3223 (data blocks) pulses, each of which has a duration of 2168 tstates.
  • A first sync pulse of 667 tstates.
  • A second sync pulse of 735 tstates.
  • The block data: a reset bit is encoded as two pulses of 855 tstates each, a set bit as two pulses of 1710 tstates each. The lowest byte in memory is first on tape, with the most significant bit first within each byte.

Notes:

  1. The TZX format specification incorrectly states that header pulses contain 8064 leader pulses and data blocks 3220.
  2. All timings above are without taking output contention into account.

Within each block, the data has the following structure:

  • A flag byte: this is 0x00 for header blocks and 0xff for data blocks.
  • The actual data.
  • A checksum byte, calculated such that XORing all the data bytes together (including the flag byte) produces 0x00.

The structure of the 17 byte tape header is as follows.

Byte    Length  Description
---------------------------
0       1       Type (0,1,2 or 3)
1       10      Filename (padded with blanks)
11      2       Length of data block
13      2       Parameter 1
15      2       Parameter 2

These 17 bytes are prefixed by the flag byte (0x00) and suffixed by the checksum byte to produce the 19 byte block seen on tape. The type is 0,1,2 or 3 for a PROGRAM, Number array, Character array or CODE file. A SCREEN$ file is regarded as a CODE file with start address 16384 and length 6912 decimal. If the file is a PROGRAM file, parameter 1 holds the autostart line number (or a number >=32768 if no LINE parameter was given) and parameter 2 holds the start of the variable area relative to the start of the program. If it's a CODE file, parameter 1 holds the start of the code block when saved, and parameter 2 holds 32768. For data files finally, the byte at position 14 decimal holds the variable name.

For example, SAVE "ROM" CODE 0,2 will produce the following data on tape (the vertical bar denotes the break between blocks):

00 03 52 4f 4d 7x20 02 00 00 00 00 80 f1 | ff f3 af a3

^^ ................ flag byte
   ^^ ............. first byte of header, indicating a code block
      ^^^^^^^^^^^^^ file name
header info ........^^^^^^^^^^^^^^^^^
checksum of header ...................^^

flag byte .................................^^
first two bytes of rom .......................^^^^^
checksum ...........................................^^