Skip to content →

Sprites, more CPU timings and IRQ quirks

Implementing sprites is of course absolutely necessary for our C64. Sprites make the very detailled graphics, that can easily be placed (almost) anywhere on our screen, even in the border areas – if the borders are turned off.

The C64 offers 8 slots for sprites. So we can draw 8 sprites in a single line (it is possible to draw even more with a lot of trickery, but for now we will keep it with the basics).

Sprites timing

Yes, sprites also affect our timing, and yes, this is an additional nuisance.

For each sprite, there is a pointer, that points to the actual data of the sprite. These sprite pointers are loaded each time we have a raster line where there are sprites to be drawn.

Cycle timings betweens VIC II and CPU

The loading of said sprite pointers takes place in H-Blank. But since H-Blank goes over a linebreak of the rasterbeam, Sprites 0, 1 and 2 are being loaded in line N-1 while Sprites 3, 4, 5, 6 and 7 are loaded in line N.

Loading a single sprite pointer takes up a single VIC cycle. So for example, the pointer for sprite 3 is loaded in cycle 1. Now, when we want to load the actual sprite data, the VIC does not have enough time on its hands, therefore it steals more cycles from the CPU!
Right after each sprite pointer, the VIC uses the CPU cycle, its next VIC cycle, and the next CPU cycle to fetch the 3 bytes of data. So, in total it takes 2 full cycles (both phases for the VIC) to load a sprite pointer and 3 bytes of data. Why 3 bytes? Because we use 3 bytes to display a single line of the sprite (see structure for more details).

Now, you could think that you just block the CPU for 2 cycles, for every sprite that is being displayed, right? Well, no.

For the VIC to tell the CPU to basically stop working, there is something that is called a bus-takeover. This takeover is achieved by the BA-line (bus available) being pulled down. This signals to the CPU that the VIC will be in control in 3 cycles. So, it takes 3 additional cycles for the bus to take over.

To simplify it: if you load a sprite, the 3 cycles before and 1 cycle after will always be used by the VIC (sometimes the 3 cycles are an actualy bus-takeover, sometimes they are another sprite, sometimes they are just skipped because the bus can’t be freed in that time [less than 3 cycles])
So, depending on which sprites you choose to use, there will or won’t be time for the CPU inbetween.

More CPU timings

Sadly, we are not done yet. The time of the bus-takeover, the 3 cycles, the CPU is not completely blocked – it can still perform WRITE-accesses. On the first READ-access though, the CPU is stopped. For us this means, our CPU really needs to be cycle accurate, and it even needs to represent the type of R/W access it performs.

example of subinstructions and its type of accesses in the 6502/6510

For me personally this means, I had to come up with changes to my CPU yet once again. This all sounds like an unrelevant thing but shouldn’t be overlooked.
In the sprite timing tests I used (using code samples from retro-programming.de , thanks Jörn!) this would actually become relevant, where using just sprite 4 made a testline longer than expected, because the CPU was completely blocked in the last write-cycle of an STA instruction.
It can be dumbed down to “let the CPU execute for max 3 cycles or until a READ instruction was encountered”.

Since we need to block the first READ instruction though, I decided to make a lookup-table, to be able to tell what my instruction will be beforehand, so it wouldn’t execute and by any chance mess something up.

const RW RW_LOOKUP_TABLE[0x100][7] {
	{ RW::READ },																								//	0x00
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0x01
	{ RW::READ },																								//	0x02
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0x03
	{ RW::READ,  RW::READ, RW::READ },																			//	0x04
	{ RW::READ,  RW::READ, RW::READ },																			//	0x05
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0x06
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0x07
	{ RW::READ,  RW::READ, RW::WRITE },																			//	0x08
	{ RW::READ,  RW::READ },																					//	0x09
	{ RW::READ,  RW::READ },																					//	0x0a
	{ RW::READ },																								//	0x0b
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x0c
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x0d
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x0e
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x0f
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x10
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0x11
	{ RW::READ },																								//	0x12
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0x13
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x14
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x15
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x16
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x17
	{ RW::READ,  RW::READ },																					//	0x18
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x19
	{ RW::READ,  RW::READ },																					//	0x1a
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x1b
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x1c
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x1d
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x1e
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x1f
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE, RW::READ },											//	0x20
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0x21
	{ RW::READ },																								//	0x22
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0x23
	{ RW::READ,  RW::READ, RW::READ },																			//	0x24
	{ RW::READ,  RW::READ, RW::READ },																			//	0x25
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0x26
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0x27
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x28
	{ RW::READ,  RW::READ },																					//	0x29
	{ RW::READ,  RW::READ },																					//	0x2a
	{ RW::READ },																								//	0x2b
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x2c
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x2d
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x2e
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x2f
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x30
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0x31
	{ RW::READ },																								//	0x32
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0x33
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x34
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x35
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x36
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x37
	{ RW::READ,  RW::READ },																					//	0x38
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x39
	{ RW::READ,  RW::READ },																					//	0x3a
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x3b
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x3c
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x3d
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x3e
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x3f
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0x40
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0x41
	{ RW::READ },																								//	0x42
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0x43
	{ RW::READ,  RW::READ, RW::READ },																			//	0x44
	{ RW::READ,  RW::READ, RW::READ },																			//	0x45
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0x46
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0x47
	{ RW::READ,  RW::READ, RW::WRITE },																			//	0x48
	{ RW::READ,  RW::READ },																					//	0x49
	{ RW::READ,  RW::READ },																					//	0x4a
	{ RW::READ },																								//	0x4b
	{ RW::READ,  RW::READ, RW::READ },																			//	0x4c
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x4d
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x4e
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x4f
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x50
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0x51
	{ RW::READ },																								//	0x52
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0x53
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x54
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x55
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x56
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x57
	{ RW::READ,  RW::READ },																					//	0x58
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x59
	{ RW::READ,  RW::READ },																					//	0x5a
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x5b
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x5c
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x5d
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x5e
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x5f
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0x60
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0x61
	{ RW::READ },																								//	0x62
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0x63
	{ RW::READ,  RW::READ, RW::READ },																			//	0x64
	{ RW::READ,  RW::READ, RW::READ },																			//	0x65
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0x66
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0x67
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x68
	{ RW::READ,  RW::READ },																					//	0x69
	{ RW::READ,  RW::READ },																					//	0x6a
	{ RW::READ },																								//	0x6b
	{ RW::READ,  RW::READ },																					//	0x6c
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x6d
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x6e
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x6f
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x70
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0x71
	{ RW::READ },																								//	0x72
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0x73
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x74
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x75
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x76
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0x77
	{ RW::READ,  RW::READ },																					//	0x78
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x79
	{ RW::READ,  RW::READ },																					//	0x7a
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x7b
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x7c
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0x7d
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x7e
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0x7f
	{ RW::READ,  RW::READ },																					//	0x80
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE },											//	0x81
	{ RW::READ,  RW::READ },																					//	0x82
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE },											//	0x83
	{ RW::READ,  RW::READ, RW::WRITE },																			//	0x84
	{ RW::READ,  RW::READ, RW::WRITE },																			//	0x85
	{ RW::READ,  RW::READ, RW::WRITE },																			//	0x86
	{ RW::READ,  RW::READ, RW::WRITE },																			//	0x87
	{ RW::READ,  RW::READ },																					//	0x88
	{ RW::READ,  RW::READ },																					//	0x89
	{ RW::READ,  RW::READ },																					//	0x8a
	{ RW::READ },																								//	0x8b
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE },																//	0x8c	
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE },																//	0x8d
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE },																//	0x8e
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE },																//	0x8f
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0x90
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE },											//	0x91
	{ RW::READ },																								//	0x92
	{ RW::READ },																								//	0x93
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE },																//	0x94
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE },																//	0x95
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE },																//	0x96
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE },																//	0x97
	{ RW::READ,  RW::READ },																					//	0x98
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE },														//	0x99
	{ RW::READ,  RW::READ },																					//	0x9a
	{ RW::READ },																								//	0x9b
	{ RW::READ },																								//	0x9c
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE },														//	0x9d
	{ RW::READ },																								//	0x9e
	{ RW::READ },																								//	0x9f
	{ RW::READ,  RW::READ },																					//	0xa0
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0xa1
	{ RW::READ,  RW::READ },																					//	0xa2
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0xa3
	{ RW::READ,  RW::READ, RW::READ },																			//	0xa4
	{ RW::READ,  RW::READ, RW::READ },																			//	0xa5
	{ RW::READ,  RW::READ, RW::READ },																			//	0xa6
	{ RW::READ,  RW::READ, RW::READ },																			//	0xa7
	{ RW::READ,  RW::READ },																					//	0xa8
	{ RW::READ,  RW::READ },																					//	0xa9
	{ RW::READ,  RW::READ },																					//	0xaa
	{ RW::READ,  RW::READ },																					//	0xab
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xac
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xad
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xae
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xaf
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xb0
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0xb1
	{ RW::READ },																								//	0xb2
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0xb3
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xb4
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xb5
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xb6
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xb7
	{ RW::READ,  RW::READ },																					//	0xb8
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0xb9
	{ RW::READ,  RW::READ },																					//	0xba
	{ RW::READ },																								//	0xbb
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0xbc
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0xbd
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0xbe
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0xbf
	{ RW::READ,  RW::READ },																					//	0xc0
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0xc1
	{ RW::READ,  RW::READ },																					//	0xc2
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0xc3
	{ RW::READ,  RW::READ, RW::READ },																			//	0xc4
	{ RW::READ,  RW::READ, RW::READ },																			//	0xc5
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0xc6
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0xc7
	{ RW::READ,  RW::READ },																					//	0xc8
	{ RW::READ,  RW::READ },																					//	0xc9
	{ RW::READ,  RW::READ },																					//	0xca
	{ RW::READ },																								//	0xcb
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xcc
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xcd
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0xce
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0xcf
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xd0
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0xd1
	{ RW::READ },																								//	0xd2
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0xd3
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xd4
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xd5
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0xd6
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0xd7
	{ RW::READ,  RW::READ },																					//	0xd8
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0xd9
	{ RW::READ,  RW::READ },																					//	0xda
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0xdb
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0xdc
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0xdd
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0xde
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0xdf
	{ RW::READ,  RW::READ },																					//	0xe0
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0xe1
	{ RW::READ,  RW::READ },																					//	0xe2
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0xe3
	{ RW::READ,  RW::READ, RW::READ },																			//	0xe4
	{ RW::READ,  RW::READ, RW::READ },																			//	0xe5
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0xe6
	{ RW::READ,  RW::READ, RW::READ, RW::WRITE, RW::WRITE },													//	0xe7
	{ RW::READ,  RW::READ },																					//	0xe8
	{ RW::READ,  RW::READ },																					//	0xe9
	{ RW::READ,  RW::READ },																					//	0xea
	{ RW::READ,  RW::READ },																					//	0xeb
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xec
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xed
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0xee
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0xef
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xf0
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ },											//	0xf1
	{ RW::READ },																								//	0xf2
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },						//	0xf3
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xf4
	{ RW::READ,  RW::READ, RW::READ, RW::READ },																//	0xf5
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0xf6
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },											//	0xf7
	{ RW::READ,  RW::READ },																					//	0xf8
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0xf9
	{ RW::READ,  RW::READ },																					//	0xfa
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0xfb
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0xfc
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ },														//	0xfd
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE },								//	0xfe
	{ RW::READ,  RW::READ, RW::READ, RW::READ, RW::READ, RW::WRITE, RW::WRITE }									//	0xff
};

So in the main loop I can check for the READ-operations:

//	READ occured in bus-takeover, stall CPU
if (BUS_takeoverActive() && RW_LOOKUP_TABLE[CURRENT_OPCODE][SUB_CYC-1] == RW::READ) {
	BUS_haltCPU();
	return 1;
}

switch(getByte(PC)) {
...

Still not precise enough – IRQ quirks

So, I was testing with International Karate all the time, which had a minor bug, that still annoyed the hell out of me.

It started out with this:

The few abandoned pixels you see, were happening because I wasn’t actually executing the subinstructions in time, but I took a shortcut, and just returned the whole length of them, and executed everything in the last cycle – big mistake! Mostly because timings of scanline detections would be messed up, that are necessary for scanline IRQs.
So, in the line with the pixels, IK is very timing sensitive in the code where it changes from Bitmap mode to Text mode. Since we are too late with the change, we still can see pixels from the Bitmap data, which are supposed to be Text data already:

Full screen in Bitmap data of International Karate

So, after successfully making it into a state machine that represented the actual cycles behavior, I was able to get rid of those pixels. Well, most of them!

It turned out, whenever sprite #1 and #2 are on the line where the mode change happens, pixels flicker again for a few single frames!

So, figuring this out cost me quite a while, and a lot of brain cells. The corresponding code is this:

0x099E is the instruction that will ultimately set the display mode to Text mode. But, after I compared my emulator to C64Debugger, I saw that apparently I’m a cycle too early at my breakpoint at 0x098F. But why, and how?


After I backstepped a bit, I found out that 0x0985 is called from the IRQ handler! And even there I was 1 cycle early (5 cycles leeway) compared to C64Debugger.
Why is this throwing is off though? It turns out, being 1 cycle early at times, will result in the lines 0x098F and 0x0992 (CMP and BCS), the lines that check for the scanline 0xA1, running too often! Because when we are said cycle too early, the last cycle of CMP will be on the last available cycle of the CPU for the scanline 0xA0, resulting in BCS not branching, and CMP running again. This costs us 6 cycles too much (in combination with skipped cycles for the badline and sprites #1 and #2).

In the end it will result in the STA 0xD011 instruction NOT being executed before the badline cycles of 0xA3, but after. Therefore, the mode is changed too late, and we can see those darn pixels from the Bitmap data again.

So, what caused this? Well, thankfully at this point in time I had a rough idea of what might cause this, and I was able to narrow it down. At that point in time, I didn’t (correctly) account for the way IRQs are being handled.
A rasterline IRQ happens (of course) as soon as the scanline changes, so on Cycle 0 of the new line. But, it is not handled immediately. The current instruction will be finished first.
Now comes the tricky part. If the IRQ got flagged later than the second-to-last cycle of the current instruction, it will execute the next instruction as well, before it handles the IRQ!

IRQ handling – wait condition

So, since I was missing out on this, I was 1 cycle early at times, causing these pixels to show up.

void setIRQ() {
	irq = true;
	irq_cycle_count = 0;
}

...

if (irq == true && irq_cycle_count >= 1 && status.interruptDisable == 0) {
	IRQ(SUB_CYC);
	SUB_CYC++;
	return 1;
}

...

irq_cycle_count++;
switch(getByte(PC)) {
...

With this implementation, IK was finally not blinking with unwanted pixels anymore. (Ignore the missing lines in the sprites for now, I will fix and address this later)

IK – timing fixed

Comments

Leave a Reply