VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

I'm currently using the following code with my cycle-accurate CPU/hardware emulation:

extern uint_32 CPU_InterruptReturn, CPU_exec_EIP; //Interrupt return address!

extern byte allcleared;

double currenttiming = 0.0; //Current timing spent to emulate!

extern byte Settings_request; //Settings requested to be executed?

OPTINLINE byte coreHandler()
{
uint_32 MHZ14passed; //14 MHZ clock passed?
byte BIOSMenuAllowed = 1; //Are we allowed to open the BIOS menu?
//CPU execution, needs to be before the debugger!
currenttiming += getnspassed(&CPU_timing); //Check for any time that has passed to emulate!
uint_64 currentCPUtime = (uint_64)currenttiming; //Current CPU time to update to!
uint_64 timeoutCPUtime = currentCPUtime+TIMEOUT_TIME; //We're timed out this far in the future (1ms)!

double instructiontime,timeexecuted=0.0f; //How much time did the instruction last?
byte timeout = TIMEOUT_INTERVAL; //Check every 10 instructions for timeout!
for (;last_timing<currentCPUtime;) //CPU cycle loop for as many cycles as needed to get up-to-date!
{
if (debugger_thread)
{
if (threadRunning(debugger_thread)) //Are we running the debugger?
{
updateAudio(getnspassed(&CPU_timing)); //Discard the time passed!
return 1; //OK, but skipped!
}
}
if (BIOSMenuThread)
{
if (threadRunning(BIOSMenuThread)) //Are we running the BIOS menu and not permanently halted? Block our execution!
{
if ((CPU[activeCPU].halt&2)==0) //Are we allowed to be halted entirely?
{
updateAudio(getnspassed(&CPU_timing)); //Discard the time passed!
return 1; //OK, but skipped!
}
BIOSMenuAllowed = 0; //We're running the BIOS menu! Don't open it again!
}
}
if ((CPU[activeCPU].halt&2)==0) //Are we running normally(not partly ran without CPU from the BIOS menu)?
{
BIOSMenuThread = NULL; //We don't run the BIOS menu anymore!
}

if (allcleared) return 0; //Abort: invalid buffer!

interruptsaved = 0; //Reset PIC interrupt to not used!
if (!CPU[activeCPU].registers) //We need registers at this point, but have none to use?
{
return 0; //Invalid registers: abort, since we're invalid!
}
if (CPU[activeCPU].halt) //Halted?
{
if (romsize) //Debug HLT?
{
MMU_dumpmemory("bootrom.dmp"); //Dump the memory to file!
return 0; //Stop execution!
}
Show last 163 lines

if (CPU[activeCPU].halt & 0xC) //CGA wait state is active?
{
if ((CPU[activeCPU].halt&0xC) == 8) //Are we to resume execution now?
{
CPU[activeCPU].halt &= ~0xC; //We're resuming execution!
goto resumeFromHLT; //We're resuming from HLT state!
}
goto skipHaltRestart; //Count cycles normally!
}
else if (CPU[activeCPU].registers->SFLAGS.IF && PICInterrupt() && ((CPU[activeCPU].halt&2)==0)) //We have an interrupt? Clear Halt State when allowed to!
{
CPU[activeCPU].halt = 0; //Interrupt->Resume from HLT
goto resumeFromHLT; //We're resuming from HLT state!
}
else
{
skipHaltRestart:
if (DosboxClock) //Execute using Dosbox clocks?
{
CPU[activeCPU].cycles = 1; //HLT takes 1 cycle for now!
}
else //Execute using actual CPU clocks!
{
CPU[activeCPU].cycles = 1; //HLT takes 1 cycle for now, since it's unknown!
}
}
if (CPU[activeCPU].halt==1) //Normal halt?
{
//Increase the instruction counter every instruction/HLT time!
cpudebugger = needdebugger(); //Debugging information required? Refresh in case of external activation!
if (cpudebugger) //Debugging?
{
debugger_beforeCPU(); //Make sure the debugger is prepared when needed!
debugger_setcommand("<HLT>"); //We're a HLT state, so give the HLT command!
}
debugger_step(); //Step debugger if needed, even during HLT state!
}
}
else //We're not halted? Execute the CPU routines!
{
resumeFromHLT:
if (CPU[activeCPU].registers && doEMUsinglestep) //Single step enabled?
{
if (getcpumode() == (doEMUsinglestep - 1)) //Are we the selected CPU mode?
{
switch (getcpumode()) //What CPU mode are we to debug?
{
case CPU_MODE_REAL: //Real mode?
singlestep |= ((CPU[activeCPU].registers->CS == (singlestepaddress >> 16)) && (CPU[activeCPU].registers->IP == (singlestepaddress & 0xFFFF))); //Single step enabled?
break;
case CPU_MODE_PROTECTED: //Protected mode?
case CPU_MODE_8086: //Virtual 8086 mode?
singlestep |= ((CPU[activeCPU].registers->CS == singlestepaddress >> 32) && (CPU[activeCPU].registers->EIP == (singlestepaddress & 0xFFFFFFFF))); //Single step enabled?
break;
default: //Invalid mode?
break;
}
}
}

HWINT_saved = 0; //No HW interrupt by default!
CPU_beforeexec(); //Everything before the execution!
if (!CPU[activeCPU].trapped && CPU[activeCPU].registers) //Only check for hardware interrupts when not trapped!
{
if (CPU[activeCPU].registers->SFLAGS.IF) //Interrupts available?
{
if (PICInterrupt()) //We have a hardware interrupt ready?
{
HWINT_nr = nextintr(); //Get the HW interrupt nr!
HWINT_saved = 2; //We're executing a HW(PIC) interrupt!
if (!((EMULATED_CPU <= CPU_80286) && REPPending)) //Not 80386+, REP pending and segment override?
{
CPU_8086REPPending(); //Process pending REPs normally as documented!
}
else //Execute the CPU bug!
{
CPU_8086REPPending(); //Process pending REPs normally as documented!
CPU[activeCPU].registers->EIP = CPU_InterruptReturn; //Use the special interrupt return address to return to the last prefix instead of the start!
}
call_hard_inthandler(HWINT_nr); //get next interrupt from the i8259, if any!
}
}
}
cpudebugger = needdebugger(); //Debugging information required? Refresh in case of external activation!
MMU_logging = debugger_logging(); //Are we logging?
CPU_exec(); //Run CPU!

//Increase the instruction counter every instruction/HLT time!
debugger_step(); //Step debugger if needed!

CB_handleCallbacks(); //Handle callbacks after CPU/debugger usage!
}

//Update current timing with calculated cycles we've executed!
instructiontime = CPU[activeCPU].cycles*CPU_speed_cycle; //Increase timing with the instruction time!
last_timing += instructiontime; //Increase CPU time executed!
timeexecuted += instructiontime; //Increase CPU executed time executed this block!

//Tick 14MHz master clock, for basic hardware using it!
MHZ14_ticktiming += instructiontime; //Add time to the 14MHz master clock!
if (MHZ14_ticktiming>=MHZ14tick) //To tick some 14MHz clocks?
{
MHZ14passed = (uint_32)(MHZ14_ticktiming/MHZ14tick); //Tick as many as possible!
MHZ14_ticktiming -= MHZ14tick*(float)MHZ14passed; //Rest the time passed!
}
else
{
MHZ14passed = 0; //No time has passed on the 14MHz Master clock!
}

tickPIT(instructiontime,MHZ14passed); //Tick the PIT as much as we need to keep us in sync!
updateDMA(MHZ14passed); //Update the DMA timer!
updateMouse(instructiontime); //Tick the mouse timer if needed!
stepDROPlayer(instructiontime); //DRO player playback, if any!
if (useAdlib) updateAdlib(MHZ14passed); //Tick the adlib timer if needed!
if (useGameBlaster) updateGameBlaster(MHZ14passed); //Tick the Game Blaster timer if needed!
if (useSoundBlaster) updateSoundBlaster(instructiontime,MHZ14passed); //Tick the Sound Blaster timer if needed!
//updateATA(instructiontime); //Update the ATA timer! This is currently not used, so ignore it!
tickParallel(instructiontime); //Update the Parallel timer!
if (useLPTDAC) tickssourcecovox(instructiontime); //Update the Sound Source / Covox Speech Thing if needed!
updateVGA(instructiontime); //Update the VGA timer!
updateJoystick(instructiontime); //Update the Joystick!
updateAudio(instructiontime); //Update the general audio processing!
if (--timeout==0) //Timed out?
{
timeout = TIMEOUT_INTERVAL; //Reset the timeout to check the next time!
currenttiming += getnspassed(&CPU_timing); //Check for passed time!
if (currenttiming >= timeoutCPUtime) break; //Timeout? We're not fast enough to run at full speed!
}
} //CPU cycle loop!

//Slowdown to requested speed if needed!
currenttiming += getnspassed(&CPU_timing); //Add real time!
for (;currenttiming < last_timing;) //Not enough time spent on instructions?
{
currenttiming += getnspassed(&CPU_timing); //Add to the time to wait!
delay(0); //Update to current time every instruction according to cycles passed!
}

float temp;
temp = (float)MAX(last_timing,currenttiming); //Save for substraction(time executed in real time)!
last_timing -= temp; //Keep the CPU timing within limits!
currenttiming -= temp; //Keep the current timing within limits!

timeemulated += timeexecuted; //Add timing for the CPU percentage to update!

updateKeyboard(timeexecuted); //Tick the keyboard timer if needed!

//Check for BIOS menu!
if (psp_keypressed(BUTTON_SELECT) || Settings_request) //Run in-emulator BIOS menu requested?
{
if ((!is_gamingmode() && !Direct_Input && BIOSMenuAllowed) || Settings_request) //Not gaming/direct input mode and allowed to open it(not already started)?
{
lock(LOCK_INPUT);
Settings_request = 0; //We're handling the request!
unlock(LOCK_INPUT);
BIOSMenuThread = startThread(&BIOSMenuExecution,"BIOSMenu",NULL); //Start the BIOS menu thread!
delay(0); //Wait a bit for the thread to start up!
}
}
return 1; //OK!
}

It runs without problems when the CPU and hardware are running with the CPU speed above 44 Dosbox-style cycles(in the case of 44.1kHz signals being generated, not to mention hardware running at higher frequencies). But when the user sets the cycle amount too low(so that the CPU runs more slowly than the hardware), the hardware won't be executed until the CPU actually does something, idling in the meantime, and generating that time in one big block when the CPU stops idling. In the case of audio output, this causes the audio to be generated too late for the audio output to give audio, while generating a huge amount of samples(the entire time the CPU spends idling is rendered all at once) causing dropouts in audio output.

Is there a good way to keep this synchronized at both slow and fast(independant of CPU speed) CPU speed settings, while keeping accuracy with the CPU/hardware cycle counting? I've currently fixed this by changing audio sampling to be able to store one full second of audio data(1 Dosbox-cycle set in the Setting menu, so 1000 instructions/second, which is the minimum speed) to always have enough buffered for playback, even at those slow speeds(1kIPS, tested with 9kIPs w/ Sound Blaster 2.0 emulation).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io