Example #1
0
static enum TimerType getIrqTimerMode(uint8_t ciaIdx) {	
	if (envIsPSID() == 1) {		
		return vicIsIrqActive() ? RASTER_TIMER : CIA1_TIMER;
	} else {	
		// todo: the below impl neglects that both raster and cia1 interrupts may be active at the same
		// time. the strategy to give priority to raster (and then ignore the cia1) works for some of
		// the affected songs (see Face_It.sid) but it is not correct

		if (vicIsIrqActive()) {
			return RASTER_TIMER;
		} else if (isCiaActive(ciaIdx)) {
			return CIA1_TIMER;
		} else {
			return NO_IRQ;
		}
	}
}
Example #2
0
enum timertype getIrqTimerMode(struct timer *t) {	
	if (sIsPSID == 1) {
		return isRasterIrqActive() ? RASTER_TIMER : CIA1_TIMER;
	} else {	
		// todo: the below impl neglects that both raster and cia1 interrupts may be active at the same
		// time. the strategy to give priority to raster (and then ignore the cia1) works for some of
		// the affected songs (see Face_It.sid) but it is not correct

		if (isRasterIrqActive()) {
			return RASTER_TIMER;
		} else if (isCiaActive(t)) {
			return CIA1_TIMER;
		} else {
			return NO_IRQ;
		}
	}
}
Example #3
0
static void runScreenSimulation(int16_t *synthBuffer, uint32_t cyclesPerScreen, uint16_t samplesPerCall, int16_t **synthTraceBufs) {
	setupVolumeHack();

	// cpu cycles used during processing
	int32_t irqCycles= 0, nmiCycles= 0, mainProgCycles= 0;	// only mainProgCycles can become negative..

	// reminder: timings of IRQ and NMI are calculated as if these where independent (which is
	// of course flawed). A proper premptive scheduling is not simulated.

	int32_t availableIrqCycles= cyclesPerScreen;	// cpu cycles still available for processing (limitation: only the waiting time is currently considered..)
	int32_t availableNmiCycles= cyclesPerScreen;

	uint32_t synthPos= 0;	// start time for the samples synthesized from SID settings (measured in cycles)						

	/*
	* process NMI and IRQ interrupts in their order of appearance - and as long as they fit into this screen
	*/	
	uint32_t currentIrqTimer= forwardToNextInterrupt(getIrqTimerMode(CIA1), CIA1, availableIrqCycles);
	
	// FIXME: the assumption that only RSID uses NMI meanwhile proved to be wrong (see MicroProse_Soccer_V1.sid 
	// tracks >4) .. also an IRQ run may also update the NMI schedule! flawed: ciaForwardToNextInterrupt 
	// calculation does NOT consider the time when the timer is actually started but only the time that some 
	// interrupt returns (which will lead to distortions)
	
	uint32_t currentNmiTimer= envIsRSID() ? ciaForwardToNextInterrupt(CIA2, availableNmiCycles) : NO_INT;	
	
	// KNOWN LIMITATION: ideally all 4 timers should be kept in sync - not just A/B within the same unit! 
	// however progs that actually rely on multiple timers (like Vicious_SID_2-Carmina_Burana.sid) probably 
	// need timer info that is continuously updated - so that effort would only make sense in a full fledged 
	// cycle-by-cycle emulator.
	
	uint8_t hack= vicIsIrqActive() && isCiaActive(CIA1);
	
	uint32_t tNmi= currentNmiTimer;		// NMI simulation progress in cycles
	uint32_t tIrq= currentIrqTimer;		// IRQ simulation progress in cycles
	uint32_t tDone= 0;
	
	uint16_t mainProgStep= 100;	// periodically give the main prog a chance to run.. 
	uint16_t mainProgStart= mainProgStep;

	if (0 || (envIsRSID() && (currentIrqTimer == NO_INT) && (currentNmiTimer == NO_INT))) {
		_mainLoopOnlyMode= 1;
		mainProgStart= 0; 	// this might be the better choice in any case.. but to avoid regression testing lets use it here for now..
	} else {
		_mainLoopOnlyMode= 0;

		// Wonderland_XII-Digi_part_4 has high interrupt rate..
		#define ABORT_FUSE 400		// better safe than sorry.. no need to crash the player with an endless loop

		uint16_t fuse;
		for (fuse= 0; (fuse<ABORT_FUSE) && ((currentIrqTimer != NO_INT) || (currentNmiTimer != NO_INT)) ; fuse++) {
			// periodically give unused cycles to main program (needed, e.g. by THCM's stuff)	
			
			if (tDone > mainProgStart) {
				// todo: the handing of main prog cycles is rather flawed (timing for main code that would normally run before 
				// 'mainProgStep'). In an alternative "improved" impl I ran "main" directly before "processInterrupt" calls below - letting 
				// "main" use all cycles unused up to that moment. also I had added interruption for "LDA#00; BEQ .." based endless main-loop 
				// logic (which would require IRQ logic to break out) to avoid unnecessary cycle usage.. while those changes work fine for 
				// THCM's recent stuff they broke stuff like "Arkanoid", etc (so I rolled them back for now..) 
				// -> now that the D012 handling has been improved I might give it another try..
				
				int32_t availableMainCycles= tDone - (nmiCycles + irqCycles + mainProgCycles);
				
				if (availableMainCycles > 0) {
					processMain(cyclesPerScreen, mainProgStart, availableMainCycles);

					mainProgCycles+= availableMainCycles;				
				}			
				mainProgStart= tDone + mainProgStep;
			}
			
			// FIXME: better allow NMI to interrupt IRQ, i.e. set respective cycle-limit
			// accordingly - and then resume the IRQ later. see Ferrari_Formula_One.sid
			
			if (!isIrqNext(currentIrqTimer, currentNmiTimer, tIrq, tNmi)) {		// handle next NMI
				tDone= tNmi;
				// problem: songs like Arcade_Classics re-set the d418 volume nowhere else but in their
				// NMI digi-player: when only used for the short intervals between NMIs the respective changing
				// settings do not seem to pose any problems for the rendering of the regular SID output 
				// (or are even intentionally creating some kind of tremolo) but when SID output rendering 
				// is performed for longer intervals (e.g. like is done here) then "wrong" settings are also 
				// used for these longer intervals which may cause audible clicking/pauses in the output.

				// if the handled NMIs were preemtively scheduled/properly timed, then renderSynth() could 
				// be performed also for these shorter intervals.. but unfortunately they are not and respective 
				// rendering cannot be done here, i.e. the IRQ based rendering then has no clue regarding the 
				// validity/duration of respectice NMI-volume infos..
				
				nmiCycles+= processInterrupt(NMI_OFFSET_MASK, getNmiVector(), tNmi, cyclesPerScreen);
					
				availableNmiCycles-= currentNmiTimer;		

				if (availableNmiCycles <= 0) {
					availableNmiCycles= 0;		// see unsigned use below
				}
				currentNmiTimer= ciaForwardToNextInterrupt(CIA2, availableNmiCycles);
				
				tNmi+= currentNmiTimer;		// will be garbage on last run..		
			} else {															// handle next IRQ
				tDone= tIrq;
				renderSynth(synthBuffer, cyclesPerScreen, samplesPerCall, synthPos, tIrq, synthTraceBufs);
				synthPos= tIrq;
				
				if (hack) {
					// in case CIA1 has also been configured to trigger IRQs, our current "raster IRQ or timer IRQ" impl 
					// is obviously flawed.. this hack will fix simple scenarios like Cycles.sid
					ciaSignalUnderflow(CIA1, TIMER_A); 
				}
				
				// FIXME: multi-frame IRQ handling would be necessary to deal with songs like: Musik_Run_Stop.sid	
				uint32_t usedCycles= processInterrupt(IRQ_OFFSET_MASK, getIrqVector(), tIrq, _irqTimeout);
				// flaw: IRQ might have switched off NMI, e.g. Vicious_SID_2-Blood_Money.sid
				if (usedCycles >=_irqTimeout) { // IRQ gets aborted due to a timeout
					if (cpuIrqFlag()) {
						// this IRQ handler does not want to be interrupted.. (either the code got stuck due to some impossible
						// condition - e.g. waiting for a timer which we don't update - or it is an endless IRQ routine that needs to 
						// continue..)						
					} else {
						// this IRQ handler was intentionally waiting to get interrupted - and it would normally not have burned
						// the same amount of cycles (we have no chance to get the timing right here..)
						usedCycles= 1;
					}					
				}
				
				irqCycles+= usedCycles;
				availableIrqCycles-= currentIrqTimer;
			
				if (availableIrqCycles <= 0) {
					availableIrqCycles= 0;		// see unsigned use below
				}
				
				currentIrqTimer= forwardToNextInterrupt(getIrqTimerMode(CIA1), CIA1, availableIrqCycles);
				tIrq+= currentIrqTimer;		// will be garbage on last run..
				
				if (envIsTimerDrivenPSID() && (currentIrqTimer != NO_INT))  {
					if(usedCycles > currentIrqTimer) {
						currentIrqTimer= NO_INT;	// hack for Eye_of_the_Tiger
					}
				}
				// some dumbshit timer-PSIDs do not ackn IRQ (MasterComposer rips & Automatas.sid)
				if(envIsTimerDrivenPSID())  { ciaReadMem(0xdc0d); }
			}
		}

#ifdef DEBUG		
		if (fuse >= ABORT_FUSE) {
			EM_ASM_({ console.log('fuse blown: ' + $0); }, fuse);
			EM_ASM_({ console.log('last IRQ timer: ' + $0); }, currentIrqTimer);