View Single Post
Old 11 February 2019, 11:51   #169
bloodline
Registered User
 
bloodline's Avatar
 
Join Date: Jan 2017
Location: London, UK
Posts: 433
Theory time:

My DMA sequencer is a relatively simple affair. It's a just a function table, with each entry a function pointer to a DMA slot function. One function from the table is called every system cycle. The function called is whatever value is in the low byte of vhposr (i.e. the horizontal beam position).

Every cycle the lower byte of vhposr is incremented, until it reaches 0xE3 (position 227), where it wraps back to 0 and increments the upper byte of vhposr by one (the vertical position), and the CIA B TOD counter is incremented.

Internally I store the upper byte of vhposr with more than 8bits (with only the lower 8bits 0 to 7 visible when reading vhposr, and bit 8 visible as bit 0 of vposr)... This sounds complicated, but it really isn't*.

When the upper byte (plus the extra bit of vposr) of vhposr reaches 0x106 (line 262 since I'm emulating a 200 line NTSC machine for now), The counter wraps back to 0, the CIA A TOD counter is incremented, and 0x8020 is written to the intreq register (to signal to the CPU a VBL as occurred). My internal copperPC is reloaded from Cop1Loc. This means the upper byte of vhposr, counts like this ...250, 251, 252, 253, 254, 255, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, etc... weird I know, but this behaviour should be familiar to Amiga programmers.


So all even entries (0,2,4,6 etc...) in the function table point to the same function. This function checks if the copper needs to use a memory cycle, if it does it calls the copper function and then returns, if the copper is currently waiting then the CPU is given the memory cycle. (I still use immediate blits so the blitter doesn't need the DMA at this time, blits happen as soon as the size register is filled).

The copper is implemented as function which returns TRUE if the copper used the DMA slot memory cycle and FALSE if it didn't.


quick psuedocodeish example:
Code:
bool copperExecute(){
	
	static int copperCycle = 0;
	static uint16_t IR1;
	static uint16_t IR2;

	switch(copperCycle){
	
		case 0:
			IR1 = chipramW[copperPC>>1];  // here the chipram is a word array, thus the CopperPC needs to be divided by 2
			IR1 = swap16(IR1);	// big endian to little endian swap
			copperCycle = 1;
			copperPC +=2;
			return TRUE;	The copper used this DMA slot so return true, and stop this slot being used by blitter or CPU.
			break;

		case 1:
			IR2 = chipramW[copperPC>>1]; 
			IR2 = swap16(IR2);	
			copperCycle = 2;
			copperPC +=2;
			return TRUE;	The copper used this DMA slot so return true, and stop this slot being used by blitter or CPU.
			break;

		case 2:
			copperCycle = 4;  //next copper execution cycle will default to the wait/skip instruction.
		
			//if bit 0 of IR1 = 0 then the next execution cycle will be the move instruction.
			if( (IR1 & 0x1)== 0){
				copperCycle = 3
			}

			return TRUE;	// not sure if the copper should burn up a DMA slot? But I assume it does. 
			break;

		case 3:
			chipramB[0xDFF000+IR1] = IR2	// write IR2 to the required chip register
			cycle = 0; 	// reset the coppercycle
			return TRUE;	// use this DMA slot
			break;

		case 4:
			//Copper doesn't execute when b15 IR2 is clear and blitter is busy.
			if( (IR2 & 0x8000)==0 && (dmaconr & 0x4000) ==1){
				return FALSE;
			}
			
			uint16_t compare = IR1 & 0xFFFE;  //mask out the instruction bit	
			uint16_t pos = vhposr & (IR2 & 0x7FFE); // mask out the evaluation bits.
			
			//is this a skip instruction?
			if( (IR2 & 0x1) ==1){
				
				if( pos >= compare){
					copperPC +=4;  //Skip the next 4 bytes
				}
				
				copperCycle = 0;	//reset the copperCycle and simply execute the next copper instruction.
				return FALSE		//shouldn't need to burn up the DMA slot.
			}
			
			//The copper wll now just wait until the below condition is true before advancing to the next instruction.
			if(pos >= compare){
				copperCycle =0;
			}

		return FALSE;	// a wait instruction shouldn't burn up a DMA slot.
		break;
	}

	//We should never get here;
	Return FALSE:
}
I believe the above code is correct... perhaps someone more familiar with the Copper could confirm my theory? (Edit, I think my evaluation bits code in the wait/skip instruction is wrong)...


All odd entries (1,3,5,7 etc...) in the function table point to a function to handle their specific DMA function (at the moment mostly just updating the bitplane registers and then when plane 1 dat is written, I dump out 16 chunky pixels to the host display buffer).

The bitplane functions don’t start fetching until the vpos vhposr position is greater than the vpos value in the diwstrt register, and the hpos vhposr position is greater than the dffstrt value. I don’t currently use the hpos in the diwstrt register.



*The vpos and hpos counters are actually 32bit, but these are only used internally. They are bitshfted and clipped to the relevant locations in the vposr and vhposr registers at the beginning of each DMA cycle. Only the vposr and vhposr registers are visible to the emulation.

Last edited by bloodline; 11 February 2019 at 14:20.
bloodline is offline  
 
Page generated in 0.04452 seconds with 11 queries