3 ms·
For those curious about what the original Spectrum ROM looked like: https://skoolkit.ca/disassemblies/rom/maps/all.html https://skoolkit.ca/disassemblies/rom/ma
by PreInternet01 3y ago
For those curious about what the original Spectrum ROM looked like: https://skoolkit.ca/disassemblies/rom/maps/all.html https://skoolkit.ca/disassemblies/rom/maps/all.html
(The tool used to generate this, https://github.com/skoolkid/skoolkit https://github.com/skoolkid/skoolkit, is absolutely amazing and still sees regular updates)
- peterfirefly 3y agoThere is a link at the bottom to switch addresses to hexadecimal.
- varjag 3y agoThe listing appears to be a copy of Logan's book, down to the labels and comments. EDIT: it does say so in the footer.
- peterfirefly 3y agoThank you so much for this link -- I have finally satisfied my curiosity after 40 years! I've spent some time today looking into the floating-point routines. Some parts are quite nice, others are a bit... clumsy. Something that surprised me was that they use 5 constants (0, 1, 0.5, pi/2, 10 -- in that order) stored in some sort of ad hoc packed format. That saves all of 7 bytes but the code to handle that takes more. Why?! There is a routine that takes a constant index (0-4) in the A register and stores the (unpacked) floating-point constant on the calculator stack. It does that by skipping the constants before the desired one -- and that is done by performing a dummy unpack on them (by unpacking them to a ROM address). See: https://skoolkit.ca/disassemblies/rom/hex/asm/341B.html https://skoolkit.ca/disassemblies/rom/hex/asm/341B.html [ah, the mystery is solved later -- see below] There are low-level routines for addition, multiplication, division, abs/neg (they share most of their code), and some low-level stack manipulation. There are special-cased comparison operations for 0 and true. OR and AND build on those special-cased comparison operations. Subtraction is built on neg + addition. sin/cos/tan/arcsin/arccos/arctan/exp/ln perform argument validity checking, argument reduction, and then evaluate a Chebyshev polynomial (and clean up the result). There is a special "series evaluator" function for that + some of the routines share their argument reduction code. The power operator is built on ln and exp. The square root operator is built on the power operator (x^0.5). Most of the internal floating-point code use a compact VM to represent calculations involving other, lower-level floating-point code. We enter the VM with the RST $28 instruction (a single-byte call to address 28h) and then a stream of single-byte opcodes follow, with the last one being a terminator. Each opcode encodes a call to a single floating-point operation. Some have immediate bytes: the opcode for pushing (packed) immediates to the calculator stack, the opcode for invoking the Chebyshev series evaluator (it is followed by a number of packed constants), and the opcodes for a loops and branches (followed by a single displacement byte for two of them). The loop instruction is basically a DJNZ, the unconditional branch is a "JR", and the conditional branch is a "JR true" where "true" is taken to be a non-zero value at the top of the calculator stack (it doesn't get popped). So, it turns out that all floating-point constants in the ROM are in the packed format. That solves the mystery above. The Spectrum uses a 5-byte floating-point format with a full byte for the exponent -- this is much easier to work with on a Z80 than the 32-bit IEEE format (because the Z80 is really not good at bit fiddling across bytes). It also encodes a 16-bit integer format into the same 5 bytes, which speeds up most parts of a typical BASIC program (+ avoids weird rounding errors). The floating-point package handles both formats -- the outermost routines "unpack" the integers to real floating-point if necessary, the innermost routines only work on real floating-point numbers. --- There seems to be a bug in the comments for the LN function: https://skoolkit.ca/disassemblies/rom/hex/asm/3713.html https://skoolkit.ca/disassemblies/rom/hex/asm/3713.html It says: 3717 DEFB $00 jump_true to VALID: X 3718 DEFB $04 multiply: X It should have said: 3717 DEFB $00 jump_true to VALID: X 3718 DEFB $04 because the 4 is not a multiply opcode, it is just the displacement immediate for the jump_true opcode. --- Most VM opcodes are treated simple enough: just use the opcode as in index into a jump table. If bit 7 is set, we have a special case: The real opcode is in bits 6-5 and there is a parameter encoded in bits 4-0. This is used for the opcodes that push a known constant (0, 1, 0.5, pi/2, 10) to the stack, it is used for the series evaluator, and for pushing and popping any of 6 internal floating-point "registers" (used for FOR-NEXT loops and various other routines). The series evaluator uses the parameter in the low nibble to indicate how many constants to use: 6, 8, or 12.