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


More to come soon so please come back.

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

Leave a Reply

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