The New ROM Antics – Building the ZX Spectrum 128(theregister.com)
theregister.com
The New ROM Antics – Building the ZX Spectrum 128
https://www.theregister.com/2024/01/15/opinion_column_zxspectrum_128/
8 comments
> This was an actual book, readily available at your local bookstore
Or on my bookshelf, still :)
Or on my bookshelf, still :)
Same here! For those not similarly blessed, the Internet archive has a copy.
https://archive.org/details/CompleteSpectrumROMDisassemblyTh...
Of particular note, the use of a stack based VM for floating point.
https://archive.org/details/CompleteSpectrumROMDisassemblyTh...
Of particular note, the use of a stack based VM for floating point.
I will presume that you know of the more recent tome on the ULA?
"The ZX Spectrum Ula: How to Design a Microcomputer" by Chris Smith
Know of but don’t (yet) own. Strong recommendation?
I like it. It’s bottom-up, and I’d go as far as saying it’s the definitive book on the Speccy ULA.
http://www.zxdesign.info/book/
http://www.zxdesign.info/book/
I’ll add it to my wish list!
Ditto. If you’re a Sinclair fan, all his books are worth a read.
http://www.ianlogan.co.uk/sinclair/books.htm
http://www.ianlogan.co.uk/sinclair/books.htm
> The rush job was the UK launch of the ZX Spectrum 128, now known as the Toast Rack because of the lake heatsink bolted on the side.
https://en.wikipedia.org/wiki/File:ZX_Spectrum128K.jpg
The heat sink fins really add to the aesthetic, and that looks so cool for a small home computer in 1985, who could complain.
(The black NeXT cube wasn't for another few years, and it also cost a prohibitive sum for most homes.)
https://en.wikipedia.org/wiki/File:ZX_Spectrum128K.jpg
The heat sink fins really add to the aesthetic, and that looks so cool for a small home computer in 1985, who could complain.
(The black NeXT cube wasn't for another few years, and it also cost a prohibitive sum for most homes.)
Would it even be legal now?
As a child I burned myself continuously on that mofo.
I was running that Z80 hard though :)
As a child I burned myself continuously on that mofo.
I was running that Z80 hard though :)
"Summer of '69" https://www.youtube.com/watch?v=eFjjO_lhf9c
o/~ played it till my fingers bled o/~
"Summer of '85" o/~ hacked it till my hand flesh grilled o/~Oh, the days a computer could get away with just a heatsink instead of 20 fans...
The heatsink cools the LM7805 power regulator, not the CPU. In the original 48k Spectrum, the heatsink was internal.
And if you use a modern switch-mode replacement, you don’t need a heat sink at all.
I know some folks like to put small heat sinks on the ULA though.
I know some folks like to put small heat sinks on the ULA though.
Soviet clones had ULAs built from individual 74xx ics. No need for heatsinks!
Excellent! I need to mess around with them at some point.
looks at the Macbook Air she's typing this on now
Well, that's an outlier. Even the M2 Mac Mini I typed the previous post on has a fan.
For those curious about what the original Spectrum ROM looked like: https://skoolkit.ca/disassemblies/rom/maps/all.html
(The tool used to generate this, https://github.com/skoolkid/skoolkit, is absolutely amazing and still sees regular updates)
(The tool used to generate this, https://github.com/skoolkid/skoolkit, is absolutely amazing and still sees regular updates)
The listing appears to be a copy of Logan's book, down to the labels and comments.
EDIT: it does say so in the footer.
EDIT: it does say so in the footer.
There is a link at the bottom to switch addresses to hexadecimal.
Thank 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
[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
It says:
---
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.
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
[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
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.
System programming: the only viable option for hackers who prefer machines over humans.
Thanks for sharing.
Thanks for sharing.
This was an actual book, readily available at your local bookstore, that had a full, independently produced, Z80 disassembly listing (with great comments) of the original Spectrum's 16K ROM.
That the actual Sinclair team were using it as their main reference when they updated the ROM is just fantastic.