Introduction | |
PART I | Basic Knowledges And Signal Structure |
PART II | The ZX Spectrum Loading Routine Analysis |
Appendix | Frequencies And T-states |
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
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:
and the tape looks like this:
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.
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.
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.
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)
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.
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.
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.
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.
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.