Bootstrap

When the Altair booted from disk, a 1702A EPROM chip loaded the first two sectors of track 0 disk 0 into memory at address 0000, where execution then began. This code is known as the 'bootstrap' and it's job is simply to load the rest of BASIC off the disk. It's commonly understood that, having forgotten to do it earlier, the first version of this bootstrap code was written by Paul Allen whilst on the flight to MITS for the original demo of BASIC.

1.1. Bootstrap Relocation

As noted above, the bootstrap is loaded by ROM into memory address 0000. Unfortunately, this is where BASIC itself is going to live. So the very first job for the bootstrap is to copy itself higher up into memory from where it can do it's work in safety. The higher memory address is 5C00 which, as will become clear in later sections, is the first available address that BASIC itself doesn't use.

boot_reloc:0000LXI H,0013HL=0013, mem copy source.
0003LXI D,5C00DE=5C00, mem copy destination.
0006MVI C,FCC=FC, mem copy size.
0008MOV A,MRead from source.
0009STAX DWrite to destination.
000AINX H
000BINX D
000CDCR C
000DJNZ 0008Loop back until all FCh bytes are done.
0010JMP 5C00Jump to 5C00, where the bootstrap now lives.


1.2. Serial IO Initialisation

Until I find a copy of the original manuals, I cannot say with real confidence what this code does.

5C00DIDisable Interrupts.
5C01XRA AWrite 00h to port 22h (poss. Centronics Printer IO).
5C02OUT 22
5C04CMAWrite FFh to port 23h (poss. Centronics Printer IO).
5C05OUT 23
5C07MVI A,2CWrite 2Ch to port 22h (poss. Centronics Printer IO).
5C09OUT 22
5C0BMVI A,03Reset the serial IO card.
5C0DOUT 10
5C0FIN FF?What is port FF is for?
5C11ANI 10
5C13RRC
5C14RRC
5C15ADI 10
5C17OUT 10Another serial IO status write. Effect unknown.


1.3. Preparation for reading BASIC from disk.

Selects disk 0 and moves the drive head to track 0, in readiness to read from it.

5C19LXI SP,5D8ASP=5D8A. Presumably a safe place.
5C1CXRA ASelect disk 0 for IO.
5C1DOUT 08
5C1FIN 08Wait for bit 3 of port 8 to be 0. I don't yet know what we're waiting for here!
5C21ANI 08
5C23JNZ 5C1C
5C26MVI A,04Load head to drive surface.
5C28OUT 09
5C2AJMP 5C38Jump ahead to Track0Test.
MoveHeadOut:5C2DIN 08Wait for permission to move head.
5C2FANI 02
5C31JNZ 5C2D
5C34MVI A,02Move head out one track (ie towards track 0 on the rim).
5C36OUT 09
Track0Test:5C38IN 08If we're not on track 0 then jump back to MoveHeadOut.
5C3AANI 40
5C3CJNZ 5C2D


1.4. Read BASIC from disk.

Reads BASIC from disk into 0000-5BFF. On disk, BASIC lives on sectors 8 through 31 of track 0; and then the whole of tracks 1 to 5. That makes 184 sectors, the payloads of which (128 bytes each) total 5C00. Now we know why the bootstrap is placed where it is. Note however, that the code does not iterate straight through to the end of track 5, rather it keeps going until it finds a sector with a signature less than 5C00. This greatly simplifies the job of modifying the bootstrap for different sizes of BASIC. It is odd that this BASIC is 23K in size... it must have grown a lot from the original 4K version, which I hope to disassemble at a future date.

Sectors are read in the order of storage, ie even sectors first (0,2,4,...28,30), followed by the odd sectors (1,3,5,...29,31). The code reads the signature, payload, and checksum of each sector (134 bytes) from disk into 5CFC-5D81; and then copies the payload (128 bytes) into position within 0000-5BFF.

HACKERS! There are two magnificently slimy hacks in here. The same technique is used in both - they've wangled a way to fit more executing code into the memory that contains it. The first hack is at address 5C44, which interpreted as the instruction JNZ 0006. This jump never happens because the zero flag is reset a line or two previously. However, the last two bytes of this instruction are interpreted as something different : MVI B,00. This latter instruction is what is jumped to later in the program. The second hack is at 5CD6, where a succession of three MVI B,??3E instructions conceal three MVI A,?? instructions. The concealed instructions each assign a different error code to the accumulator, and are jumped to (in the case of a pretty disastrous error situation) from various places in this code section. It's a great hack!

[Note that the mnemonics for this section are Z80 style. I have not got around to making them the less-familiar-to-me-and-frankly-inferior 8080 style.]

5C3FLD DE,0000DE=0000, ie BASIC destination.
5C42LD B,08B=08h, ie # of first sector to read from track 0.
5C44JP NZ,0006What a slimy hack! This jump never occurs; it's a cheap way to avoid executing the next instruction (at 5C45) on the first pass, ie when reading track 0.
ReadTrack:5C45LD B,00B=00h, # of first sector (pass 2).
5C47LD A,10A=10h, max attempts to read a sector.
5C49PUSH AF
5C4APUSH DE
5C4BPUSH BC
5C4CPUSH DE
5C4DLD DE,8086E=86h, ie bytes to read. D is ignored.
5C50LD HL,5CFC
WaitForSector:5C53IN A,(09)Wait for the required sector (in B register) to spin past.
5C55RRA
5C56JP C,5C53
5C59AND 1F
5C5BCP B
5C5CJP NZ,5C53
ReadSector:5C5FIN A,(08)Wait for a new byte to be readable.
5C61OR A
5C62JM M,5C5F
5C65IN A,(0A)Read the byte from the disk and write it to 5CFC+.
5C67LD (HL),A
5C68INC HL
5C69DEC EIf we've read the whole sector, then jump ahead to CopyPayload.
5C6AJP Z,5C75
5C6DDEC E
5C6EIN A,(0A)Read another byte from the disk and write it to 5CFC+.
5C70LD (HL),A
5C71INC HL
5C72JP NZ,5C5FJump back until sector complete.
CopyPayload:5C75POP HLHL=BASIC destination.
5C76LD DE,5CFFDE=5CFF, sector payload source.
5C79LD BC,0080B=00 (checksum). C=80 (payload size).
CopyLoop:5C7CLD A,(DE)Copy one byte of sector payload to BASIC destination.
5C7DLD (HL),A
5C7ECP (HL)Check the mem copy worked. If it failed then jump to ErrBadMemory.
5C7FJP NZ,5CDC
5C82ADD A,BAdd the byte value to the checksum in B.
5C83LD B,A
5C84INC DE
5C85INC HL
5C86DEC CLoop until all 80h bytes of the payload have been copied.
5C87JP NZ,5C7C
CheckEOP:5C8ALD A,(DE)Check for the End of Payload (EOP) marker byte.
5C8BCP FF
5C8DJP NZ,5C93
5C90INC DE
5C91LD A,(DE)Compare the payload checksum from disk with what it should be (stored in B).
5C92CP B
5C93POP BCB= # of sector read.
5C94EX DE,HLHL=payload source, DE=BASIC destination.
5C95JP NZ,5CD0If checksum bad, jump to SectorIsBad.
5C98POP AFClear up stack. AF is ignored.
5C99POP AF
5C9ALD HL,(5CFD)HL=sector signature.
5C9DPUSH DEPreserve DE (BASIC destination).
5C9ELD DE,5C00Compare sector signature to 5C00.
5CA1CALL 5CF6
5CA4POP DEDE=BASIC destination.
5CA5JP C,5CD9If sector sig. > 5C00 then jump to ErrBadSig.
5CA8CALL 5CF6Compare sector signature to DE.
5CABJP NC,5CC9If sig. <= DE then jump ahead to RunBASIC
5CAEINC B
5CAFINC BAdd 2 to sector #.
5CB0LD A,B
5CB1CP 20Loop back to foobar until we've read the last sector for the current pass.
5CB3JP C,5C47
5CB6LD B,01B=1 (first sector # for second half of read).
5CB8JP Z,5C47Jump back to foobar for second read
5CBBIN A,(08)Wait for permission to move head.
5CBDAND 02
5CBFJP NZ,5CBB
5CC2LD A,01Move head inwards onto the next track.
5CC4OUT (09),A
5CC6JP 5C45Jump back to ReadTrack
RunBASIC:5CC9LD A,80Deselect disk 0.
5CCBOUT (08),A
5CCDJP 0000Jump to BASIC!!!
SectorIsBad:5CD0POP DE
5CD1POP AFA=bad sectors tolerated
5CD2DEC ADecrement allowed sector read attempts.
5CD3JP NZ,5C49If allowed, jump back to try to read the sector again.
ErrBadSector:5CD6LD A,43Error code 43h.
5CD8LD BC,4F3E
5CDBLD BC,4D3E
(Execution continues at DoError (5CDE) below.)
ErrBadSig:5CD9LD A,4FError code 4Fh.
5CDBLD BC,4D3E
(Execution continues at DoError (5CDE) below.)
ErrBadMemory:5CDCLD A,4DError code 4Dh.
DoError:5CDEEI
5CDFLD (0000),AWrite error code to mem addr 0000.
5CE2LD (0001),HLHL is presumably extra error info.
5CE5LD B,AB=Error code.
5CE6LD A,80Deselect disk 0.
5CE8OUT (08),A
5CEALD A,BA=Error code.
ErrorOut:5CEBOUT (01),A?What is port 01h?
5CEDOUT (11),A?What is port 11h?
5CEFOUT (05),A?What is port 05h?
5CF1OUT (23),A?What is port 23h?
5CF3JP 5CEBInfinite loop back to ErrorOut.
CompareDEandHL:5CF6LD A,DSimple helper function that compares DE and HL for equality.
5CF7CP H
5CF8RET NZ
5CF9LD A,E
5CFACP L
5CFBRET