Jump to content
Sign in to follow this  

Adventures in ZX Spectrum Dev

Recommended Posts

First program - a simple Hello World, as featured in the screenshot above. This is pretty closely based on this example.

	DEVICE ZXSPECTRUM48	; compiler directive - target platform

	ORG $8000		; go to $8000 - this is the start of the uncontended (fast!) RAM

	ld a, $2		; load target channel into accumulator. Channel 2 is the main screen
	call $1601             	; ROM call - takes a channel number from the accumulator and opens it for writing
	ld hl,line             	; load the address of the line label into 16-bit register pair hl
	call printline		; call printline routine
	ret                     ; return - ends program.

	ld a,(hl)               ; Load the current character into the accumulator
	cp '$'			; compare it with '$' - if true, Z is set. ASCII characters treated as numbers, so '$' == $24
	jp z,printend           ; if Z flag is set (match made), jump to end routine
	rst $10                 ; Spectrum ROM call: Print the character in the accumulator to the next available character space on screen
	inc hl                  ; Increment memory pointer in hl to point to next character in string
	jp printline            ; Run this subroutine again

	ret			; returns to main loop.

; Data
; defb stores the argument(s) provided in memory at the provided label.
; $d = new line.
; using $ as an end-of-string delimiter here (see printline routine)
line:	defb 'Hello, world!', $d,'$'

	savesna "helloworld.sna", main	; Save this as a SNA file, start execution at label main

This is fairly straightforward, although I'm not really that comfortable with ROM calls, which feel a bit too much like magic boxes to me. Rather confusingly, there are two ways to call them - most are done via CALL, but there are a handful of simple functions called via RST, which you can see being used here to print the character to the screen. I'm not sure if it's worth learning all these, or if they become irrelevant once you start writing directly to the VRAM.


I like the way defb can take strings or individual bytes, and you can just chain them all together with commas to put them in contigious memory. Note that characters (e.g. $) can be used interchangeably with their numeric value in the speccy character table, which is pretty much ASCII, with a few tweaks.


Style wise, I need to work out when to use hex ($ prefix) and when to use decimal. No-one seems particularly consistent in this. I think I definitely want to use hex for memory addresses and ROM calls, but it might make more sense to use decimal for sprite bytes.


Oh, and it turns out the Windows 10 calculator in Programmer Mode is actually pretty damn handy for all this DEC/HEX/BIN stuff.

  • Upvote 9

Share this post

Link to post
Share on other sites

On holiday this week in a badly sound-insulated holiday house, so I haven't had a chance to watch that vid - I'll give it a look next week. The comments section yielded this very useful link, though - http://www.speccyvirgins.com/


I was going to do a quick-and-dirty post about colour attribute data (which seems to be a lot more straightforward than the actual pixel memory layout), but I thought I'd come up with a nice way of drawing my willy (hnuk) at any block coordinate. So the other night, I wrote a nice little routine to do just that. Then, doing some Googling afterwards, I struck upon this:




(From this site)


This is crazy - you can turn your X and Y values into memory addresses just through (fast!) bit-twiddling. This has opened my eyes a bit - clearly anything that splits on any power of two is open to this kind of solution using masking, shifting, etc.


Anyway, inspired by the above, I re-wrote my coordinate routine. It's not the same as the one at the link, as it's not down to pixel level, only to block level. I also added a little routine to increment the y value by 1, moving to the next band if  necessary; this was partially to ensure I understood all the bit shifting, and partially as I'm not sure of the best approach. I could store the original X and Y values on the stack, recover them after drawing the first block then call calcoords again, but I'd need to sit down and work out the T-state difference between the two approaches. Also, can use of the stack be measured solely in T-states, or does memory latency become a factor once you start using RAM? But isn't each call to CALL/RET using the stack anyway? More research needed.


	DEVICE ZXSPECTRUM48	; compiler directive - target platform

	ORG $8000		; go to $8000 - this is the start of the uncontended (fast!) RAM

	ld a, $00		; load the colour black into the accumulator
	out ($FE), a		; rom call - set border colour
	ld b,2			; x block co-ordinate (0-31)
	ld c,7			; y block co-ordinate (0-23)
	call drawwilly
	ld b,24
	ld c,15
	call drawwilly

	ld hl, minerwilly	; load the willy sprite data
	call calcoords		; using coords in b and c, put target memory address in de
	call drawblock		; draw first block
	call incy		; shift destination address in de to next line vertically
	call drawblock		; draw second block

	ld a,c 			; copy y-coord to accumulator
	and %00011000		; Wipes out all but the bits relating to band
	or %01000000		; adds the 010 prefix
	ld d,a			; copy to MSB of destination
	ld a,c			; re-copy y-coord to accumulator
	and %00000111		; Wipes out all but the bits relating to row within band
	rra			; rotate right 4 times: 0000 00YY (Y)
	rra			; Y000 000Y (Y)
	rra			; YY00 0000 (Y)
	rra			; YYY0 0000 (0)
	add a,b			; simply add x-coord
	ld e,a			; copy accumulator to LSB of destination
	ld a,e			; copy LSB into accumulator
	cp %11100000		; compare with value of last-line-of-band y coords
	jp nc, nextband		; if equal to or larger, we need a new band calculating
	add a,$20		; otherwise, just add 32 for the next row
	ld e,a			; copy the updated LSB back into the destination
	ld a,d			; get the MSB of the destination
	and %01011000		; Resets Y pixel offset.
	ld d,a			; copy back into destination MSB	 

	and %00011111		; Blanks out y coords of LSB
	ld e,a			; copy back to destination LSB
	ld a,d			; get destination MSB
	add a,$08		; increment band bit - this will push us into attribute space if we're already in the last band
	and %01011000		; 0101 1000 - ensures 010 prefix is maintained, wiping out any carry. Also wipes Y pixel offset.
	ld d,a			; copy back into destination MSB	

	ld b, 8			; line counter - 8 lines in block
	jp lineloop

	inc d			; advance to next line in block - moved this to ensure it's not run on first loop
	ld a, (hl)		; load the line of sprite data into accumulator
	ld (de),a		; copy this into the current target memory location
	inc hl			; increment sprite pointer - we want to do this even if we've finished drawing this block
	djnz nextline		; decrements b and jumps to nextline if not zero

; Data
	DEFB	$06,$3E,$7C,$34,$3E,$3C,$18,$3C
	DEFB	$7E,$7E,$F7,$FB,$3C,$76,$6E,$77

	savesna "willcoordinated.sna", main


  • Discovered I can use % prefixed binary numbers, which is useful for bit-twiddling
  • djnz is a neat instruction that decrements B, then jumps to a label if it's not zero. Under the bonnet this is a relative jump, which is limited to +/-128 bytes, but that seems like acres here, and it's a whisper faster than separate dec and jp nz commands.
  • Nicked a bit from one of Jonathan Cauldwell's samples to set the border to black - this made is easier to confirm my willies were drawing correctly.
  • Annoyingly, there are only three bands, not four (not a power of two!), so there's no easy bitmask solution to stop the nextband routine potentially shifting sprite data into attribute space (I haven't crunched the figures yet, but I'm guessing the entirety of attribute space sits the memory that would be used by that fourth band, if it existed). I could add some extra logic here to defend against that, but I think a caveat emptor approach will suffice.

And here's the not-very-thrilling output. Both of these willies are straddling screen bands, the one on the left straddling the first and second band, and the one on the right straddling the second and third.




Next time - colour, finally.

  • Upvote 6

Share this post

Link to post
Share on other sites

In other news, I've been reading ZX Spectrum Machine Language for the Absolute Beginner, and I think it's a pretty damn good book. The registers-as-hands-and-feet metaphor seemed a bit contrived at first, but makes for nice explanations about some of the Z80's behaviour (I liked the concept of A/HL as "preferred hands", like my right hand). It also does a good job of communicating a lot of Z80 conventions and neat tricks (like using XOR A as a quick way of setting the accumulator to 0). There's some skippable stuff about interfacing with BASIC and hand-coding the machine code, but other than that it's pretty tightly written.


Recommended, thus far - check out the link in my first post.


  • Upvote 3

Share this post

Link to post
Share on other sites

Busy week, but will hopefully get some more time on this soon.


Got this in the post today - it's pristine, spine never cracked! Smells weird though, what I'm guessing was once New Textbook Smell, but is now 35 years old.




As mentioned briefly in my first post, The Oliver Twins confirmed to me via Twitter that they named Wizard Zaks after the author of this book, such was its bible status back in the day.


Also spent some of the evening watching this while doing the dishes:



Uncle Clive doesn't seem to be the loveable character I'd imagined as a child. Compelling stuff though, with a great soundtrack.


  • Upvote 2

Share this post

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. Use of this website is subject to our Privacy Policy, Terms of Use, and Guidelines.