Journal Alioth's Journal: I'm so geeky (and one for StB) 5
OK - I put the 'StB' in the title to get the attention of Sam the Butcher, since he's learning how to code. I mentioned ages ago how learning a little bit about asm (any asm, it doesn't matter - a simple 8-bit one is fine) will help the understanding of what has to go on underneath - which gains a better understanding of how to program in higher level languages, and helps avoid common performance sapping pitfalls.
Anyway...this is all inspired by "Hey Hey 16K", which I posted a couple of JEs ago.
Sigh. It seems like my Spectrum+ has died. Since it was a rainy day so no outside stuff to be done, I thought I'd wallow in nostalgia on the old Speccy. There's a program for my Powerbook which will turn tape images (for emulators) into sound, so you can use your laptop as a fantastically expensive tape recorder for a Sinclair Spectrum. I've not got to try it though - the Speccy powers up, but never initializes. I suspect one of the RAM chips went bad (IIRC, the first thing the Speccy does is clear RAM, or perhaps it writes 0xFF to RAM - I can't remember).
So instead, I've just been playing with Fuse (a Mac and Unix Spectrum emulator), and I'm getting a replacement Speccy off eBay (they are cheap - so many of them were made they are still pretty common, and being simple, few of them fail - well, except mine).
I played a couple of games that I used to like (Jet Set Willy, yeah!), but the real object here is try to remember how the HiSoft Gens assembler worked, and see if I could remember any Z80 ASM after not having written any since I was about 17 or so. There's a very geeky project that's likely to tail onto this.
So far I've done this. Comments so those (that's probably all of you) who have never done asm on a Speccy have an idea what's going on. Unfortunately, Slashdot doesn't seem to pay attention to the pre tag, so the spacing's not perfect.
Here's a simple example that puts a pattern on the Spectrum's screen:
10 ORG 25000 ; Code should go at address 25000 (decimal)
20 LD HL,16384 ; Load HL register pair with 16384
30 LD A,#AA ; Load A register with hex AA (bin 10101010)
40 LD (HL),A ; Load the address pointed to by HL with the contents of A
50 LD DE,16385 ; Load DE register pair with 16385
60 LD BC,6143 ; Load BC with 6143
70 LDIR ; Block copy memory (like memcpy())
80 RET ; Return
For those who aren't knowing of Z80 asm on the Speccy, 16384 (decimal) is the address of the first byte of the frame buffer, and the frame buffer (before the colour attributes, which start at 16384+6144) is 6144 bytes long. This snippet of asm is effectively equivalent to the following C code:
char *fb=ADDRESS_OF_FRAME_BUFFER();
*fb=0xAA;
memcpy(fb+1, fb, 6143);
For those who don't do C either, what it does is puts 0xAA in the first byte of the frame buffer and then copies that value over the rest of the frame buffer...and thus fills the screen with a kind of stripy pattern.
For an encore, I went on to do "Hello World".
The Spectrum has no equivalent of printf(). To write a message on the screen, you have to pass it a character at a time to the ROM routine at address 0x10 (you can use the Z80 instruction RST 16 to do this in fewer bytes than CALL #10). To print a single character, you do this:
10 ORG 25000 ; Our code will be at addr 25000 (decimal)
20 LD A,2
30 CALL #1601 ; I never remember having to do this 'back in the day'...
40 LD A,#55 ; That's the letter 'V' in ASCII
50 RST 16 ; Print the contents of the A register (the letter V)
60 RET
The funny thing is I never remember having to call #1601 (that's a function in the Spectrum ROM - the ROM sits in the address space #0000 to #3FFF (16383 decimal) i.e. from zero to the last byte in the memory map before the frame buffer starts). Setting A to 2 and calling #1601 must do some initialization that I've long forgotten about. I really don't remember to ever have to do that! I don't even know what #1601 does at this point - I've long lost my copy of 'Advanced Spectrum Machine Code' which had stuff like this in it.
Fundamentally, the next code snippet, the actual "Hello world!" code is doing what under the covers what things like printf() do to print a character string. Somewhere, someone has to write stuff out a character at a time. It's easy to forget that if all you use is a high level language, that something underneath has to do lots of very small things to get stuff done. Like C, it uses a null terminated string. This might not be the best or most efficient way to get it done - as I said, it's been a while so my Z80-fu is not strong (and TMTOWTDI, as Perl monks like to say):
The screen shot is formatted better (but uncommented) - so the screenie from the actual Spectrum is here if you wanna check it out: http://www.alioth.net/tmp/helloworld.png
10 ORG 25000 ; This code will start at address 25000 dec.
20 LD A,2 ; Load register A with 2, presumably an arg to #1601
30 CALL #1601 ; Initialize the Spectrum print routine.
40 LD HL,MSG ; Load the HL register pair with MSG - which is just a label which is the address of the message string.
50 LOOP LD A,(HL) ; LOOP is a label for the assembler. Load A with whatever HL points at.
60 CP #00 ; Compare A with zero - i.e. is the contents of the memory pointed at by HL the null terminator?
70 JR Z,EXIT ; If A == 0, then jump to the label 'EXIT' [0]
80 RST 16 ; Print the character stored in A
90 INC HL ; Add 1 to the HL register pair
100 JR LOOP ; And jump back to the LOOP label
110 EXIT RET ; Return from this code
120 MSG DEFM "Hello world!" ; The string Hello World (DEFM means nothing to the CPU - it's merely for the assembler)
130 DEFB #00 ; The null terminator
([0] Actually, this isn't strictly 'if (a==0)
So there you have it. Hello World for the Z80 on in a Sinclair Spectrum.
When learning even simple asm like this (which I did initially when I was about 15 or so) means you are immediately involved in things like pointers, buffers, pointer arithmetic, the consequences of buffer overflows and all sorts of things - even just writing 'Hello World' - and Hello World isn't too complex to write, either, despite this.
I also feel having done asm many years ago benefits me today - even when writing Perl or some other higher level language, simply because you understand better what the machine is fundamentally doing when you, say, call print().
Well, I think I shall play some more 8-bit games as a reward
UPDATE: I've found out what the routine does at #1601. It sets the output stream, the stream passed in register A. Stream 2 is the main 'window' (stream 1 is the editor 'window' and stream 3 is the ZX printer).
Re: (Score:2)
Output stream (Score:1)
Re:Output stream (Score:2)
What I think was happening before I put the call to #1601 was that the output was going to the bottom "input window" (stream 1 I think), and then getting overwritten by the "0 OK, 0:1" message before it could be seen. To add to the confusion, some example code does a PUSH HL/PUSH BC before RST 16, then POP BC/POP HL - b
LDIR is not the fastest way (Score:2)
Re:LDIR is not the fastest way (Score:2)
Back 'in the day' though, I always used ldir (and that snippet was one of my first adventures into Z80 asm on the Spectrum). Unfortunately, I was never good at getting projects finished - I did start out with a grand project for writing a terminal program for the online game "Shades". The only bit I finished was my very own 64-column character printing routine (which I was quite proud of at the time, but when I tested it, it was no faster than t