Patricia’s z80 Snippets

Over the next few months I will be adding to this series of code snippets to help people make games on the spectrum next, so make sure you check back every now and then, or you could leave a comment and get notified when I post again!

Snippet 1

This little piece of code will let you check to see which scanline the beam is on as it draws the screen, so rather than using interrupts we can wait for any vertical position on the screen.

In your main loop, after you have done all your game code, you can wait for the beam to be offscreen and then go around the main loop again!

WasteLoop:
						call	ReadRaster		// check the current scan line
						ld	a,192			// check for 192
						cp	l			// are we at 192 yet
						jp	nz,WasteLoop 		// nope so waste cycles	
						jp	MainLoop		// do the whole thing black and blue again and again
So to the snippet

This code will read the scan line and return the value in HL, its all commented so there is not much more to say.

//--------------------------------------------------------------------------
// 
// Read the current Raster into HL
// Out:			hl = raster line on the screen
//
// Dirty 		a,bc
//
//--------------------------------------------------------------------------

ReadRaster:
						ld	a,$1e		// select the Active video line (MSB)
						ld	bc,$243b	// set bc to $243b select the NEXT register to read the video line
						out	(c),a		// tell the hardware we are going to read the video line
						ld	bc,$253b	// set bc to $253b so we can access the read value
						in	a,(c)		// so now read the port $253b	NextREG data/value
						and	1		// mask off the Active line MSB
						ld	h,a		// store the high part in h for returning					
						ld	a,$1f		// now read LSB of the Active raster line LSB (0-255)
						ld	bc,$243b	// as before set bc to $243b select the NEXT register
						out	(c),a		// tell the hardware we now want to read the register $1f
						ld	bc,$253b	// set bc to $253b so we can access the read value for the last time
						in	a,(c)		// read the register	
						ld	l,a		// and store in the low part of hl
						ret

Snippet 2

The next snippet will generate pseudo random numbers, that can be used in many places in your game, such as determining baddie behaviour or just making things look more organic with a little random variance. In my shoot em’ up I am using a random number to rotate and flip my explosion sprites, so they don’t all look the same.

						call	getRandom				// get the random number	
						ld	a,l					// I only need the low part 
						and	%00001110				// mask off the rotations and mirrors
						ld	(ix+object.mirrorRotateBits),a		// bit 3 = X mirror, bit 2 = Y mirror, bit 1 = Rotate
So to the snippet

This code will generate a pseudo random number return the value in HL, its all commented so again there is not much more to say.

//--------------------------------------------------------------------------
// 16bit pseudo random number using the xor shift method 
//
// out	hl	pseudorandom number
//
// dirty 	none
//--------------------------------------------------------------------------

randomSeed:					dw	%0101101001100101		// seed (number) to start with

getRandom:	
						push	af
						ld 	hl,(randomSeed) 		// get the last seed
						ld	a,h				// add high byte register		
						rra					// rotate right accumulator with carry
						ld	a,r				// adding in the Memory Refresh Register
						add	a,l				// now do the same with the low seed byte
						rra					// rotate right accumulator with carry again
						xor	h				// exclusive or high seed byte with the accumulator 
						ld	h,a				// put the accumulator into the high seed
						ld	a,l				// and put the low seed into the accumulator 
						rra					// rotate right accumulator with carry again
						ld	a,h				// do the same with the high using the carry from the low
						rra					// rotate right accumulator with carry again	
						xor	l				// exclusive or low seed byte with the accumulator 
						ld	l,a				// yes store the accumulator in the low seed byte
						xor	h				// exclusive or high seed byte with the accumulator 
						ld	h,a				// now store the accumulator in the high seed byte
						ld	(randomSeed),hl			// and store as the next seed
						pop	af
						ret	

In the past I have used a pseudo random number generator like this to make random levels that always come out exactly the same. Simply by setting the seed before I generate the level, I was able generate an identical sequence of random numbers which saved space as the whole level was just a 16 bit seed. Likewise on other games I have called the random number generator in the main loop which produced a more random set of random numbers.

Snippet 3

Now lets talk about the DMA, which is a fast processor to move large amounts of data pretty quickly, the data destination could be a ZX spectrum next system register, or another address in memory.

The DMA functions are quite self explanatory as I have used binary notation and I have added // 76543210 comments above so you can see which bits are being set, enabling you to learn more about the DMA in the docs.

The first function I use to upload sprites to the FPGA.

//----------------------------------------------------------------------
// Function:	Upload a set of sprites
// In:		a 	= dest sprite shape to start at
//		de 	= length of data
//		hl 	= shape data
//----------------------------------------------------------------------

		define	PORT_DMA_DATA	$6b	// labled zxnDMA  in the docs

dmaSprites:	ld	bc, $303b		// tell the hardware the next value is a sprite slot
		out	(c),a			// write the the slot
		ld	(dmaSpriteSource),hl	// set the source address	
		ld	(dmaSpriteLength),de	// now set the length of the data	
		ld	hl,dmaSpriteCode	// now start the transfer by copying the dma
		ld	b,dmaSpriteCodeLen	// code to the DMA processor which will
		ld	c,PORT_DMA_DATA		// start as soon as all the data has gone 
		otir				// through the port
		ret
//----------------------------------------------------------------------
// actual sprite DMA code 
//----------------------------------------------------------------------

dmaSpriteCode:		db $83			// DMA Disable
			//  76543210
			db %01111101		// WR0-Transfer mode, A -> B, write adress + block length
dmaSpriteSource:	dw 0			// WR0-Port A, Start address (source address)
dmaSpriteLength:	dw 0			// WR0-Block length (length in bytes)
			//  76543210
			db %01010100		// WR1-read A time byte, increment, to memory, bitmask
			db %00000010		// WR1-Cycle length port A
			//  76543210
			db %01101000		// WR2-write v, Port B address is fixed, Port B is IO
			db %00000010		// WR2-Cycle length port B		
			//  76543210
			db %10101101 		// WR4-Continuous mode (use this for block tansfer), write dest adress
			dw PORT_SPRITE		// WR4-Dest address (destination address)
			//  76543210
			db %10000010		// WR5-Restart on end of block, RDY active LOW (STOP)
			//  76543210
			db %11001111		// WR6-Load
			db %10000111		// WR6-Enable DMA
endSpriteDmaCode:		
dmaSpriteCodeLen	equ endSpriteDmaCode-dmaSpriteCode	// calculate the length of the dma code

The next one is a modification of the above function to transfer blocks of memory from one place to another.

//----------------------------------------------------------------------
//
// Function:	DMA block of memory from one location to another
// In:		hl 	= source address
//		de 	= address of destination
//		bc 	= length of data
//
//----------------------------------------------------------------------



dmaBlock:		ld	(dmaBlockSource),hl		// same process as above setting the source
			ld	(dmaBlockLength),bc		// and length of the data,
			ld	(dmaBlockDest),de		// but this time its not a port so we have a dest address
			ld	hl,dmaBlockCode		// now set start the transfer by copying the dma
			ld	b,dmaBlockCodeLen		// code to the DMA processor which will
			ld	c,PORT_DMA_DATA		// start as soon as all the data has gone 
			otir				// through the port
			ret

//----------------------------------------------------------------------
// actual block DMA code 
//----------------------------------------------------------------------

dmaBlockCode:	db $83			// DMA Disable
			//  76543210
			db %01111101		// WR0-Transfer mode, A -> B, write adress + block length
dmaBlockSource:		dw 0			// WR0-Port A, Start address (source address)
dmaBlockLength:		dw 0			// WR0-Block length (length in bytes)
			//  76543210
			db %01010100		// WR1-read A time byte, increment, to memory, bitmask
			db %00000010		// WR1-Cycle length port A
			//  76543210
			db %01010000		// WR2-write v, memory address is increments, Port B is IO
			db %00000010		// WR2-Cycle length port B		
			//  76543210
			db %10101101 		// WR4-Continuous mode (use this for block tansfer), write dest adress
dmaBlockDest:		dw 0			// WR4-Dest address (destination address)
			//  76543210
			db %10000010		// WR5-Restart on end of block, RDY active LOW (STOP)
			//  76543210
			db %11001111		// WR6-Load
			db %10000111		// WR6-Enable DMA
endBlockDmaCode:		
dmaBlockCodeLen		equ endBlockDmaCode-dmaBlockCode	// calculate the length of the dma code


It should be said that I got the original DMA code from a demo in cSpect, and used it to learn, then modified to suit my needs.

One interesting thing you can do with the DMA is clear a block of memory like this.

			// use the DMA to clear a block of memory
			ld	hl,characterScreen	// destination address
			ld	(hl),0			// write zero to the destination
			ld	de,hl			// copy the source address to the destination
			inc	de			// increment the destination
			ld	bc,$a00-1		// how many bytes to clear
			call	dmaBlock		// now use the dma to clear

Snippet 4

Time for jump tables I think, in c++ or most other higher level languages we could use a switch case statement, were each case is handled based on the type of animation, like this.

// animation types
	#define		ANIM_TYPE_NONE		0
	#define		ANIM_TYPE_LOOP		1
	#define		ANIM_TYPE_SINGLE	2
	#define		ANIM_TYPE_PING		3
	#define		ANIM_TYPE_PONG		4
	#define		ANIM_TYPE_STOP		5

void animate()
{
	switch(object.animationType)
	{
		case	ANIM_TYPE_LOOP:
		.... some code
		break;
		case	ANIM_TYPE_SINGLE:
		.... some code
		break;
		case	ANIM_TYPE_PING:
		.... some code
		break;
		.......
	}
}

In z80 we could do many compares and branches like this.

	ld	a,(ix+object.animationType)
	cp	ANIM_TYPE_NONE
	ret	z
	cp	ANIM_TYPE_LOOP
	jp	z,animateLoop
	cp	ANIM_TYPE_SINGLE
	jp	z,animateSingle
	cp	ANIM_TYPE_PING
	jp	z,animatePing
        .... more compares and jumps

but this works out at about 17 t-states per compare and jump, so we could not have many options before it eats most of the t-states.

So is there a faster way to do this in z80?

Yes we can use what we call a jump table which is a method of transferring program control (branching) to another part of a program based on an index.

// animation types
ANIM_TYPE_NONE:		equ	0
ANIM_TYPE_LOOP:		equ	1
ANIM_TYPE_SINGLE:	equ	2
ANIM_TYPE_PING:		equ	3
ANIM_TYPE_PONG:		equ	4
ANIM_TYPE_SHIP:		equ	5
ANIM_TYPE_CASE:		equ	6
ANIM_TYPE_DELETE:	equ	7
ANIM_TYPE_STOP:		equ	8

animJumpTable:	dw	noAnimate				//ANIM_TYPE_NONE
								dw	animateLoop					//ANIM_TYPE_LOOP
								dw	animateSingle			//ANIM_TYPE_SINGLE
								dw	animatePing			//ANIM_TYPE_PING
								dw	animatePong				//ANIM_TYPE_PONG
								dw	animateShip				//ANIM_TYPE_SHIP
								dw	animateCase				//ANIM_TYPE_CASE
								dw	animateDelete			//ANIM_TYPE_DELETE
								dw	notEnded	

Animate:				ld	a,(ix+object.animationType)		// get the type of animation
								ld	hl,animJumpTable			// address of the jump table
								add	hl,a					// add the offset to the base of the table
								add	hl,a					// times 2 for words on the jump table
								ld	de,(hl)					// get the address
								ld	hl,de					// move to hl so we can jump
								jp	(hl)					// jump to the animation function
	
								//-----------------------------------
								// looping animation	
								//-----------------------------------
animateLoop:	.... some animation code

noAnimate:			ret   
								//-----------------------------------
								// ping of the ping pong animation	
								//-----------------------------------
animatePing:	.... some animation code
								ret   

So this works out at about 30 t-states no matter which type of animation function is needed, I am using this method for other things like baddie control (ix+object.BaddieType) and option selections.

Anyhow jump tables are your friend so enjoy!

Snippet 5

If branching on z80 appears a little confusing then how about some macros to make things a little clearer? Especially if you are coming from a 6502 background.

Obviously you can call these what you want but these names make sense to me.

		// jump if less than
		macro	jle	address
		jp		c,address
		endm

		// jump if greater than
		macro	jgt	address
		jp		nc,address
		endm
	
		// jump if equal
		macro	jeq	address
		jp		z,address
		endm

		// jump if not equal 
		macro	jne	address
		jp		nz,address
		endm	

		// jump if signed
		macro	js	address
		jp		m,address
		endm	

		// jump if not signed				
		macro	jns	address
		jp		p,address
		endm
Usage
		ld	a,0
		cp	0
		jeq	someBranchAddress

		ld	a,100
		cp	50
		jle	anotherBranchAddress


Snippet 6

Have you heard of self-modifying code? Self-modifying code is code which achieves its goal by rewriting itself as it goes. It’s a common trick in games programming, particularly in low level languages like assembler.

One use is modifying a call

In z80 we have some indirect jumps using jp (hl), jp (ix) and jp (iy) which are great if you don’t want to return to the next line, but what do we do if we want to just call a function from data and then return to the next line?

This is where self modifying code can help like this, the address from the object gets written into the address of the call, then the call calls the function and returns to the next line.

The hex in memory for a a call to address $FF00 would be CD 00 FF, so by modifying the call+1 writes the address into memory leaving the call (CD) opcode un touched.

initFunction:   ...                                        // more code
                ret

callInit:       ld    hl, (ix + object.initFunction)       // get the address of the init
                                                           // function
                ld    (.callLine + 1), hl                  // self modifying code
.callLine       call  $FF00                                // the $FF00 gets modified with the
                                                           // address of the init function	
                ...                                        // more code 

Workaround using indirect jumps

When the processor does a call it first puts the return address (next line of code) onto the stack and then does the call, so we can use that to our advantage by putting our own return address on the stack then doing a jump, so its our return address that gets pulled on the ret from the jump, making it act like a call.

initFunction:    ...                          // more code
                 ret                          // this will return to nextLineAddress    
       
callInit:        ld    hl,nextLineAddress
                 push  hl
                 ld    ix,initFunction
                 jp    (ix)
nextLineAddress: ...                         // more code

More to come soon so please come back.

11 thoughts on “Patricia’s z80 Snippets”

  1. I’ve been enjoying reading through your code snippets. I’ve backed KS2 so can’t wait to get my Next. In the meantime trying to understand what I can in terms of coding for it. So looking forward to reading more on this great website.

  2. In snippet 4, `ld hl,de` – is this your compiler expanding this out to `ld h, d : ld l, e` or should this just be `ex de, hl`?

    Great stuff too 👍

    1. Thanks for that, Yes I have the compiler set to fake instructions so it takes care of most of that stuff for me, I am new to z80 so i don’t know all the tricks yet and I tend to spend time writing the game and getting speed increases through how I am doing it rather than optimizing every line.

      I am impressed with your work too

  3. I just couldn’t depart your website prior to suggesting that I extremely loved the standard info an individual provide for your visitors? Is gonna be back steadily in order to investigate cross-check new posts.

  4. Hi, I just noticed this and had to say something.
    In snippet 2, the lines
    ld a, h
    rra
    do nothing more than create an 8 t-state delay as the following 2 lines
    ld a, r
    add a, l
    destroy both the accumulator and carry flag rendering the previous 2 lines redundant. Maybe replace the add instructions with adc to make use of the carry generated by the rra instruction.

Leave a Reply

Your email address will not be published. Required fields are marked *