Jump to content
Sign in to follow this  
Alexlotl

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

main:
	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.

printline:                     	
	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

printend:			
	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:

 

eureka.png.ef3ab55120f7e1826043315b85832191.png

 

(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

main:
	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
	ret

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
	ret

calcoords:
	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
	ret
	
incy:
	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	 
	ret

nextband:
	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	
	ret

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

nextline:
	inc d			; advance to next line in block - moved this to ensure it's not run on first loop
lineloop:
	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
	ret

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

	savesna "willcoordinated.sna", main

Notes

  • 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.

 

twowillies.png.8e6feb3c0985ce0ec50f497ef7229818.png

 

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.

 

43cdb9d4ddfd7c30989438b4aa4e4b1d.jpg

 

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 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×

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.