Taken from the Ramsoft website - http://www.ramsoft.bbk.org/ - Broken Link!
Converted to HTML by Pete Robinson

Ramsoft presents
-= The ZX Spectrum Loaders Guide =-
VOLUME 1
The ZX Spectrum Normal Speed Tape-Loader

INDEX

Introduction

PART I

Basic Knowledges And Signal Structure

PART II

The ZX Spectrum Loading Routine Analysis

Appendix

Frequencies And T-states


Introduction

Welcome to Ramsoft's ZX Spectrum loader guide.

These documents are intended to provide a useful help for anyone interested on learning loader working. All the most common loader types have been treated to give a complete view of the mainly used protection systems. The very first volume of this guide is dedicated to the normal speed loader in the Spectrum ROM, which is the base for understanding loaders like Speedlock and Alkatraz and their amazing effects.

NOTE: the author takes NO responsibility and won't be liable, under any circumstances, for copyright infringements or any other direct or indirect damage.

Last revised on 9-12-1997


PART I - Basic knowledges and signal structure.

Let's start saying what a BLOCK is. When you save a BASIC program or a piece of CODE with the BASIC command SAVE, you always save 2 BLOCKs. The first one is called the HEADER and it's always 17 bytes long; the second one is the DATA block, and its length changes depending on the amount of bytes saved. In the HEADER you will find the necessary informations the system needs to load the program such as the TYPE (BASIC,CODE,ARRAY), the NAME, the LENGTH of the BLOCK and some other things depending on the type of data you are saving; the DATA block is the actual BASIC program or piece of CODE you want to save.

You might think to find something like this on your tape:

LH LD

(L=LEADER,H=HEADER,D=DATA)

Since the header is only a particular kind of data we can write the following:

LD LD

Actually things are a little more complicated.In fact between LEADER and DATA there's something to mark the end of one and the beginning of the other,as the first one has a fixed frequency but the other doesn't: so here we find what is commonly called a SYNC PULSE (S).

So you may imagine something like that:

LSD LSD

That's not enough. We find another 2 things: the first byte of the DATA is a MARKER that tells the system which kind of block is loading; the last byte is the a CHECKSUM for error checking; so when you save a code block of, say, 6912 bytes you'll find that on tape they are actually 6914 instead (even if the 1st one and the last one won't be stored in memory); so we have that:

DATA=MARKER+BYTES+CHECKSUM

Finally:

  1. LEADER
  2. SYNC PULSE
  3. MARKER (M)
  4. BYTES (B)
  5. CHECKSUM (C)

and the tape looks like this:

LSMBC LSMBC

This representation is valid for every kind of normal speed saved block, since headers are only a particular kind of data, as we said above.

Now let's have a look to each component in detail.

1 - LEADER

The LEADER is an introduction to the DATA: its frequency is nearly 807.2Hz, so the halfwave length is 2168 T. Remember that there's NO DATA stored into the LEADER.

2 - SYNC PULSE

The SYNC PULSE is the endmarker for the LEADER and the introduction to the MARKER byte and the others following. It's composed of 2 halfwaves of 2 different frequencies: the first one is about 2623.7 Hz, the second one is 2381 Hz; the halfwave lengths are respectively 667 and 735 T.

3 - DATA

In normal speed blocks, each bit is saved with a standard frequency:

BIT=0 : about 2046.8 Hz (855 T)
BIT=1 : about 1023.4 Hz (1710 T)

3.1 - MARKER

The MARKER is a byte that tells what sort of BLOCK is ready to be loaded. The only values you may find in BLOCKs saved by the BASIC commands SAVE and LOAD are 0 for HEADERs and 255 (FFh) for DATA blocks.

3.2 - BYTES

This is the byte sequence in memory from the start of BASIC area (for a BASIC program) or from the specified address (for a piece of CODE). Even the BYTES are saved in the above illustrated form.

3.3 - CHECKSUM

This byte is for error checking: when saving, the content of a register (which is initially set to 0) is XORed with the current byte that has to be saved, even the marker; at the end this byte is saved too. The same thing happens when loading in a block, so that the last byte loaded will be XORed with the register above: if the result is 0, then the loading was OK (the SAVE and LOAD checksums are the same, so there have been no errors) and the CARRY flag is set, otherwise the CARRY flag is reset signaling that an error occurred.


PART II - The ZX Spectrum loading routine analysis.

We are now going to analyze each action of the loading routine in the Spectrum ROM: to do this, we will consider a SQUARE wave signal. Please note that we do this in theory, because the signal recorded on tape is far away from being a square wave. Even if the ULA's output is a square wave, the resulting waveform is actually much similar to a SINE; this is due to the physical and mechanical characteristics of the tape (wow & flutter, etc.); also treble and bass tones settings could alter the resulting waveform.

The ZX Spectrum loader routine starts from address 0556h in ROM and here's a completly commented disassembled.

NOTE: the labels used in this disassembled piece of code are the ones given by Dr. Ian Logan & Dr. Franklin O'Hara in their book:

"The complete ZX Spectrum ROM disassembly" , Melbourne House 1983.
ISBN 0 86759 117 X

Input:  	IX=start address (where to place the block)
        	DE=block length
        	A=expected marker (0 or 255 as said above)

        	Cy=SET for loading, RESET for verifying

Output:		IX=address of the last loaded byte plus 1
        	DE=remaining bytes to load (0 if all block has been loaded)

        	Cy=SET if loading was OK, RESET otherwise

053F LD-RET PUSH AF

Keep safe the loading results

LD A,(5C48h)

Pick up the border and the last two lines colour from system variables area

AND 38h

Keep the border colour only (bits 3-5)

RRCA
RRCA
RRCA

Make of it a suitable value for OUTing

OUT (FEh),A

Change border colour and shut MIC output

POP AF

Restore the loading results

RET

Return control to the main program

0556 LD-BYTES INC D

Here begins the actual loading routine.

The routine starts adding 1 to D: this would TEMPORARILY increase the block length by 256 bytes, but also resets the Z flag; this will be useful later for handling the first byte loaded (the marker). Note that if D holds 255 at the start, this would reset the Cy and set the Z flag causing an error later in the routine: this means that we can't load blocks larger than 65279 bytes

EX AF,AF'

Here the expected marker is kept away for later check

DEC D

Restore the original length

DI

Disable the interrupts; leaving interrupts enabled the routine is halted every 50th of a second to execute the interrupt routine and timings would be lost

LD A,0Fh
OUT (FEh),A

Turn the screen border WHITE and turn OFF the MIC signal

LD HL,053Fh (Ld-Ret)
PUSH HL

Now push the return address in the stack

IN A,(FEh)

Sample the EAR port; its status is held in bit 6 of the A register. This is done to find the opposite edge of the wave.

RRA

Rotate the byte, so now the EAR port status is in bit 5 of the A register

AND 20h

Keep only byte 5, for the others are useless

OR 02h

As this is the byte that will be OUTed to the MIC port later, this operation turns the screen border RED.

LD C,A

Store the byte in C. C will hold the byte to be OUTed to MIC port (i.e. the current EAR status in bit 5 and the border colour) for all the routine duration

CP A

Reset the Z flag

056B LD-BREAK RET NZ

Stop loading if Z flag is reset; this may happen if the BREAK key has been pressed (see later)

056C LD-START CALL 05E7h (LD-EDGE-1)

Find the opposite edge of the one held in C

JR NC,056Bh (LD-BREAK)

Jump back if an error has occurred (i.e. BREAK pressed or no edge found)

LD HL,0415h
0574 LD-WAIT DJNZ 0574h (LD-WAIT)
DEC HL
LD A,H
OR L
JR NZ,0574h (LD-WAIT)

This loop causes a pause of about 1 second. At the end H holds 0 (see below)

CALL 05E3h (LD-EDGE-2)

Search for 2 edges for 31475 T states, so for a wave of at least of 222.4 Hz

JR NC,056Bh (LD-BREAK)

Jump back if the edges haven't been found (tape blurr)

0580 LD-LEADER LD B,9Ch

Load B with the timing constant

CALL 05E3h (LD-EDGE-2)

Look for 2 edges

JR NC,056Bh (LD-BREAK)

Jump back if the leader is not continuous

LD A,C6h
CP B
JR NC,056Ch (LD-START)

Now check how long the wave is: the 2 edges must have been found in between 6341 and 13185 T states, so the wave frequency must be between 530 and 1102 Hz The leader is about 807.2 Hz, so it' nearly in the middle of the search range

INC H
JR NZ,0580h (LD-LEADER)

Increase H and check for it to be 0; this will repeat the above process 256 times as H holds 0 since before. To leave the loop, the leader must have a minimum length of about 0.3 seconds

058F LD-SYNC LD B,C9h
CALL 05E7h (LD-EDGE-1)

Now it's time to look for the sync pulse. Load B with the timing constant and look for only 1 edge

JR NC,056Bh (LD-BREAK)

The edge hadn't been found: again tape blurr or faulty tape

LD A,B
CP D4h
JR NC,058Fh (LD-SYNC)

Check how distant is the new edge from the previous one. They must be found in max 7757 T states but only the ones found in less than 2565 T states are good to leave the loop: this means that the frequency should be more than 1364 Hz, otherwise the edges we are considering are still the ones of the tone leader

CALL 05E7h (LD-EDGE-1)

If the first halfwave of the sync pulse has been found, then look for the other one. The available time to look for the second halfwave depends on the time spent for the search of the previous halfwave

RET NC

Stop loading, as the sync pulse search was not successful

LD A,C
XOR 03h
LD C,A

Invert the last 3 bits of the C register. This turns the border from CYAN to YELLOW or from RED to BLUE

LD H,00h

Initialise H with 0. This is the register holding the checksum

LD B,B0h

Load B with the timing constant

JR 05C8h (LD-MARKER)

Jump forward to load the marker byte

05A9 LD-LOOP EX AF,AF'

Restore the register holding the expected marker

JR NZ,05B3h (LD-FLAG)

Jump forward if considering the first byte loaded

JR NC,05BDh (LD-VERIFY)

Jump forward if we are verifying.

Note that if D was holding 255 at the beginning, the Z flag would be set and the Cy flag reset: this causes an error, as the marker would be loaded into memory and not compared with the one stored in the A register here

LD (IX+00h),L

We are loading, so put the byte loaded in memory (held in the L register)

JR 05C2h (LD-NEXT)

Jump forward to load another byte

05B3 LD-FLAG RL C

Here the first byte is handled. The first step is to keep safe the Cy flag

XOR L

Then the 2 values are compared

RET NZ

If they weren't the same, then we're loading the wrong block: stop loading

LD A,C
RRA
LD C,A

Otherwise restore the Cy flag. Now the Z flag is set, so next byte won't be compared to the expected marker, but stored directly into memory

INC DE

Increase DE for its later decreasee

JR 05C4 (LD-DEC)

Jump forward to load the first byte to store in memory

05BD LD-VERIFY LD A,(IX+00h)
XOR L
RET NZ

This is the routine for verifying blocks: fetch the memory content at the location pointed by IX and compare it with the loaded byte: stop loading if they are different

05C2 LD-NEXT INC IX
05C4 LD-DEC DEC DE

Make IX pointing ti the next memory location and decrease length

EX AF,AF'

Keep safe the flags in the alternate register

LD B,B2h

Load B with the timing constant

05C8 LD-MARKER LD L,01h

Now prepare to load 8 bits: L starts with bit 0 set.

05CA LD-8-BITS CALL 05E3h (LD-EDGE-2)

Go searching for 2 edges for about 10707 T states (min freq.=@653.8 Hz)

RET NC

Stop loading if an error occurred (i.e. BREAK pressed or faulty tape)

LD A,CBh CP B

Now decide if the bit is 0 or 1. If the number of T states between the 2 edges is less than 4453 (i.e. the frequency is higher than about 1572 Hz), then the bit is 0; otherwise it's 1.

Remember that bit 0's frequency is about 2046.8 Hz and bit 1's about 1023.4 Hz The result of the comparison is stored in the Cy flag

RL L

Now store the loaded bit in L. If it was the last bit to complete the byte, the carry holds 1, because 8 bits were loaded and the starting bit 0 of the L register (which was SET) is now the carry flag; otherwise the carry is always reset (it would hold the bit 7 of L)

LD B,B0h

Set B with the timing constant for the next byte (if any)

JP NC,05CAh (LD-8-BITS)

Go for another bit if L is not full

LD A,H
XOR L
LD H,A

Update the checksum: the current byte loaded is XORed with H holding the current checksum. Note that the last byte of the sequence is the checksum that has been generated at the end of the saving routine; at the end of loading we must have that: H XOR L = 0 ,otherwise there's been a loading error

LD A,D
OR E
JR NZ,05A9h (LD-LOOP)

Check if we are at the end of loading and jump to store the byte in memory if not

LD A,H
CP 01h

Check for correct loading: as said above, H must hold 0. The comparison with 1 returns the carry flag set only if H is holding 0, otherwise the carry flag is reset

RET

End of loading.

05E3 LD-EDGE-2 CALL 05E7h (LD-EDGE-1)

Look for an edge.

RET NC

Stop loading if BREAK was pressed or the edge hasn't been found, otherwise search for another edge

05E7 LD-EDGE-1 LD A,16h
05E9 LD-DELAY DEC A
JR NZ,05E9 (LD-DELAY)
AND A

The loops loses 354 T states + 4 for the AND A instruction that resets the Cy flag

05ED LD-SAMPLE INC B

B is increased before every sample

RET Z

Stop loading if the time's up (too much distance between 2 edges)

LD A,7Fh
IN A,(FEh)

Sample the EAR port and the keyboard ('BNM[SS][SPACE]' row)

RRA

Shift the byte right: the bit holding the status of the BREAK key is now in the Cy register

RET NC

Return if BREAK was pressed

XOR C

Compare the current status of the EAR port with the previous one (held in the C register)

AND 20h

Consider only bit 5

JR Z,05ED (LD-SAMPLE)

Jump back if they are the same (no signal variation)

LD A,C
CPL
LD C,A

Invert the content of the C register, i.e. change the current status bit and change border colour (BLUE to YELLOW or vice-versa)

AND 07h
OR 08h

Keep only the border colour and signal MIC off

OUT (FEh),A

OUT to port (change border and turn MIC off)

SCF
RET

Signal successful search and return.


Appendix - Frequencies and T-states

Some of the values in this document may appear strange or maybe someone's wondering how to calculate them. There's a well known formula to do this and it's the following:

                 CPU Clock                             CPU Clock
     T-States = -----------     or vice-versa    f =  ------------
                   2 * f                              2 * T-States  

The CPU Clock and the frequency are given in Hz; so for the ZX Spectrum 48K the value for CPU Clock is 3500000 and for 128K is 3540000.

The T-States values are always counted starting from one I/O instruction to another (i.e. from IN to IN); I wrote down a formula to calculate the total amount of T-States between 2 calls to the LD-EDGE-2 routine:

T-States = 206 + 118 * (MaxB - StartB - 1) + 56 * A + STUFF

where MaxB is the maximum value admitted for the B register, StartB is the starting value for the B register and A is the starting value of the A register in the sampling loop. Someone may ask why instead of (MaxB - StartB) I wrote (MaxB - StartB - 1): it's because for a successful loading, you should always find 2 edges, so we must have 2 successful samplings; as 59 is the number of T-States for each unsuccessful pass on the sampling loop, you must subtract 59 * 2 T-States and add the ones for the successful passes (already included in 206). STUFF is the amount of T-States lost from a CALL to the routine LD-EDGE-2.