Currently UniPCemu uses double percision floating point numbers to handle most hardware timings(and sound sample generation). That does take up a lot of clock cycles on a real CPU, because floating point numbers are harder to process than simple integer values.
How much of the IBM PC gaming MS-DOS hardware(Up to Compaq Deskpro 386) actually runs off the 14MHz clock? What about requirements of audio sampling? Are they always multiples of them too?
I would not call it 14MHz clock, it would imply it is a 14.000MHz clock which is isn't.
At least it should be called 14.318MHz clock. Good enough approximation for most use is 14.31818 MHz or 14318180 Hz.
Actual definition for the clock rate comes from NTSC standards and is exactly 315/22 MHz.
On a 4.77MHz PC, these run on x=14.318MHz clock:
-CPU itself (x/3) and thus the ISA bus
-PIT (timer, speaker, memory refresh) (x/12)
-FM synthesis (chip=x/4, samplerate=x/288)
-CMS/GameBlaster (chip=x/2, I recall frequency granularity being to 1 count per chip clock too so samplerate=x/2 too)
-CGA cards use it for pixel clock
-EGA cards use it for pixel clock in CGA modes
These don't:
-SB (SB1/SB2/SBPRO 1MHz granularity)
-Covox (no clock, but in theory as it sits on ISA bus the sample can update at ISA bus cycle granularity)
-Disney Sound System (7kHz +/- 5%)
-EGA cards in EGA mode (16.257MHz)
-VGA cards (25.175MHz, 28.322MHz)
-MDA cards (16.257MHz)
-Hercules cards (16.257MHz or 16.000MHz)
I've just adjusted UniPCemu's hardware clocking(all but video card cores) to tick at 14.31818MHz pace. That will make them run faster(in realtime speed) when running CPU speeds past 8088 4.77MHz equivalent. Although stuff like Sound Blaster is a bit less accurate(1MHz clock not directly used, rounded to 14MHz chunks).
Will that be OK when running applications/rendering(which also runs off that rate)?
There's no need to compromise on any of this stuff. You can have as many different frequency sources as you like, have them all be perfectly accurate, and do all your computations with integers. Suppose you want to have both 14.318MHz and 1MHz clocks. You need to find the least common multiple of those frequencies, which is 315MHz (divide by 22 to get the 14.318MHz clock and by 315 to get the 1MHz clock). So you can just do all your measurements in units of this fast clock - if you want to count N cycles of the 14.318MHz clock, increase your (integer) time variable by N*22 and if you want to count M cycles of the 1MHz clock, increase your time variable by M*315. The only tricky bit is that you have to be careful to avoid your time variable overflowing, which just means that you have to emulate in sufficiently small chunks that no component ever gets too far behind any other. I think in practice you won't even have to resort to 64-bit integers - 32 bits should be fine.
But the problem is that ticking a clock of 315MHz will pretty much hang every usual CPU(at least 4.0GHz i7 CPUs)? There's also all strange stuff used by various sound cards and graphics cards(MDA,CGA,EGA,VGA,ET3000,ET4000), whic vary from 14.31818MHz all the way up to about 60-70MHz(ET4000)?
But the problem is that ticking a clock of 315MHz will pretty much hang every usual CPU(at least 4.0GHz i7 CPUs)?
Why would it? There isn't anything in the emulator that is actually run 315 million times per second - it's just an accounting mechanism. You'd always be increasing a time variable by a multiple of 22 or 315, never by just 1.
There's also all strange stuff used by various sound cards and graphics cards(MDA,CGA,EGA,VGA,ET3000,ET4000), whic vary from 14.31818MHz all the way up to about 60-70MHz(ET4000)?
Try putting the frequencies of all the crystals in question into the LCM algorithm and see what base frequency you get. Pick a value for your "how far apart components can be in time" parameter (say 20ms), and see how many cycles of that base clock you get in that time to see how big the clock numbers need to get.
There's also the problem of infinitely long numbers, like the (S)VGA frequencies? The same with MDA frequencies?
What do you mean by infinitely long numbers? I guarantee that no SVGA or MDA card has a crystal whose nominal value is an irrational number of Hertz. If you mean infinitely long as a decimal expansion (like 14.31818181...MHz) then these are just rational numbers which the LCM method copes with just fine. In the case where you don't know the nominal value or have a range of "in spec" frequencies, just pick the midpoint or a nearby (in spec) "nice looking" number.
Then what about the VGA 25/28MHz clock? And the ET3K/ET4K clocks?
Btw, anyone knows the exact EGA and MDA clocks? As well as the ET3/4K clocks? All I can find are rounded numbers, not exact values(e.g. like the VGA clocks already present in UniPCemu(25.2*1.001 and 28.35*1.001)?)
EGA currently runs on 14.31818 and 16MHz exactly. Is that correct?
The VGA clocks are based on having a frame rate of exactly 60Hz/1.001 (i.e. exactly twice NTSC) when using 480-line modes. These have 800 or 900 total pixels horizontally by 525 total lines vertically. So the nominal values are exactly 25.2MHz/1.001 and 28.35MHz/1.001, or 160/91 and 180/91 of the 14.318MHz PC/CGA clock.
superfury wrote:
And the ET3K/ET4K clocks?
I don't know about those.
superfury wrote:
Btw, anyone knows the exact EGA and MDA clocks?
EGA currently runs on 14.31818 and 16MHz exactly. Is that correct?
Both MDA and EGA's 350-line modes use 16.257MHz I understand. I haven't been able to find a derivation of that frequency, so I'd probably just use 16257MHz/1000 for now. It's probably accurate to +/-50ppm so maybe you'll be able to find a nicer ratio within about 800Hz of that value.
Then what about the VGA 25/28MHz clock? And the ET3K/ET4K clocks?
Btw, anyone knows the exact EGA and MDA clocks? As well as the ET3/4K clocks? All I can find are rounded numbers, not exact values(e.g. like the VGA clocks already present in UniPCemu(25.2*1.001 and 28.35*1.001)?)
EGA currently runs on 14.31818 and 16MHz exactly. Is that correct?
I think I posted the exact clocks already. How they are rounded in your opinion?
VGA: 25.175000 MHz, 28.322000 MHz
EGA: 16.257000 MHz, and 14.318181.... MHz coming from motherboard.
MDA: 16.257000 MHz.
ET4k uses whatever you choose to feed to it. I bet they either use standard crystals or a PLL chip that generates these from a 14.318... MHz crystal, but then they might be approximations that are dependent on the PLL chip, as 11077 to 6300 starts to be a too large ratio maybe.
Note that real crystals have a tolerance that may be about 50ppm, they hardly are tuned in consumer equipment.
Using exact line rates results to error no more than 12ppm when using 25.175000 and 28.322000 MHz when compared to "ideal".
Also, I've never found evidence that VGA clocks would be specifically tuned to NTSC based fractions, they were just standard crystals.
Edit: Forgot to mention, that also modern video standards do specify the pixel clock as to be exactly 25.175 MHz
Also, I've never found evidence that VGA clocks would be specifically tuned to NTSC based fractions, they were just standard crystals.
The alternative (that VGA pixel clocks are unrelated to NTSC frequencies) implies that the VGA 480p signal being exactly 525 lines (exactly double standard NTSC) at a frame rate of 59.94Hz (double standard NTSC) is a coincidence, which seems far fetched to me.
That doesn't mean that the VGA frequencies were ever accurate to NTSC standard (+/-10ppm) - it may be that after the nominal frequency was chosen, IBM designers decided on crystals that were 25.175MHz and 28.322MHz +/- 30ppm or 50ppm as being "close enough". And it seems quite possible to me that (if those were standard crystal values before the advent of VGA) then they were standard values because of their relationship to NTSC frequencies.
Jepael wrote:
Edit: Forgot to mention, that also modern video standards do specify the pixel clock as to be exactly 25.175 MHz
...within a certain tolerance (because when you're building hardware getting a frequency to be exact is impossible). And both 25175MHz/1000 and 25200MHz/1001 lie within that tolerance.
Btw, this isn't completely theoretical - I have used an IBM VGA card to generate composite NTSC signals, with the color carrier period being 4*(90/91) pixels in a 256 colour mode. It worked great - there was no noticeable hue drift between the left and right sides of the screen. The VGA's CRTC (at least the original IBM one) has a bit for setting the DRAM refresh bandwidth in order to support generating video with a 15.75KHz line rate. So compatibility with NTSC monitors was something IBM's engineers had thought about, even if it didn't end up being something that was supported by the BIOS.
Also, I've never found evidence that VGA clocks would be specifically tuned to NTSC based fractions, they were just standard crystals.
The alternative (that VGA pixel clocks are unrelated to NTSC frequencies) implies that the VGA 480p signal being exactly 525 lines (exactly double standard NTSC) at a frame rate of 59.94Hz (double standard NTSC) is a coincidence, which seems far fetched to me.
That doesn't mean that the VGA frequencies were ever accurate to NTSC standard (+/-10ppm) - it may be that after the nominal frequency was chosen, IBM designers decided on crystals that were 25.175MHz and 28.322MHz +/- 30ppm or 50ppm as being "close enough". And it seems quite possible to me that (if those were standard crystal values before the advent of VGA) then they were standard values because of their relationship to NTSC frequencies.
I did not mean to say they are completely unrelated to NTSC frequencies - it is common knowledge that they are designed to be (very close to) double the NTSC scan rate so scanline converters can be used to connect to NTSC television sets and that they are close enough. What I meant is that the clock is just not a perfect ratio to NTSC frequency but only a very close approximation, while the 13.5MHz used for SD video that is an exact multiple that matches both NTSC and PAL signals. Televisions will have no trouble of syncing to this.
reenigne wrote:
Jepael wrote:
Edit: Forgot to mention, that also modern video standards do specify the pixel clock as to be exactly 25.175 MHz
...within a certain tolerance (because when you're building hardware getting a frequency to be exact is impossible). And both 25175MHz/1000 and 25200MHz/1001 lie within that tolerance.
I should know - the tolerance of real world 25.175 MHz crystal falls well within absolute NTSC frequency, however broadcast NTSC standard was pretty strict, below 3ppm, out of range for normal consumer equipment.
Just looked at it(rotating the images 180 degrees to read the crystal values easier). The 25MHz crystals both say 25.175MHz. The 28MHz crystal seems odd indeed: VGA=28.3210MHz, PS/2=28.3220MHz(So it's about 0.0010MHz off? So 1000PPM off? If it's limit is 30 or 50PPM, that's not supposed to work with a NTSC monitor? It's way off, even by their own standards?
Just looked at it(rotating the images 180 degrees to read the crystal values easier). The 25MHz crystals both say 25.175MHz. The 28MHz crystal seems odd indeed: VGA=28.3210MHz, PS/2=28.3220MHz
Note that it reads 25.175000 and 28.322000, which do indicate it's designed for that (within tolerance) and not to 25.2/1.001 for example.
superfury wrote:
(So it's about 0.0010MHz off? So 1000PPM off?
No, how did you come up with that? It's 35ppm.
superfury wrote:
If it's limit is 30 or 50PPM, that's not supposed to work with a NTSC monitor? It's way off, even by their own standards?
What makes you think so? TVs and monitors can lock on to the incoming signal that's way off that. Even digital TVs today must allow 0.5% tolerance, that's 5000ppm.
Edit: Having said that, video cards that have a single 14.31818... MHz crystal with PLL to generate the required clocks, won't be exactly that. A Tseng card with certain PLL can only generate clock that is 25.255681.. MHz, that's 0.32% error and I bet nobody can tell the difference, it will work just fine. It uses 14.31818 MHz * 127 / (18*4) to do that.
Jepael wrote:Note that it reads 25.175000 and 28.322000, which do indicate it's designed for that (within tolerance) and not to 25.2/1.001 fo […] Show full quote
superfury wrote:
Just looked at it(rotating the images 180 degrees to read the crystal values easier). The 25MHz crystals both say 25.175MHz. The 28MHz crystal seems odd indeed: VGA=28.3210MHz, PS/2=28.3220MHz
Note that it reads 25.175000 and 28.322000, which do indicate it's designed for that (within tolerance) and not to 25.2/1.001 for example.
superfury wrote:
(So it's about 0.0010MHz off? So 1000PPM off?
No, how did you come up with that? It's 35ppm.
superfury wrote:
If it's limit is 30 or 50PPM, that's not supposed to work with a NTSC monitor? It's way off, even by their own standards?
What makes you think so? TVs and monitors can lock on to the incoming signal that's way off that. Even digital TVs today must allow 0.5% tolerance, that's 5000ppm.
Edit: Having said that, video cards that have a single 14.31818... MHz crystal with PLL to generate the required clocks, won't be exactly that. A Tseng card with certain PLL can only generate clock that is 25.255681.. MHz, that's 0.32% error and I bet nobody can tell the difference, it will work just fine. It uses 14.31818 MHz * 127 / (18*4) to do that.
I've just adjusted all UniPCemu's timings according to previous posts, while adding special support for the 14MHz clock on the motherboard itself to be used directly. All other modes use a normal double floating point-based clock the old way.
I've read that before, it's the basic principle I've implemented my emulator's core loop is based around(with clipping in case of it being too slow):
1OPTINLINE byte coreHandler() 2{ 3 uint_32 MHZ14passed; //14 MHZ clock passed? 4 byte BIOSMenuAllowed = 1; //Are we allowed to open the BIOS menu? 5 //CPU execution, needs to be before the debugger! 6 lock(LOCK_INPUT); 7 if (unlikely((haswindowactive&0x1C)==0xC)) {getnspassed(&CPU_timing); haswindowactive|=0x10;} //Pending to finish Soundblaster! 8 currenttiming += likely(haswindowactive&2)?getnspassed(&CPU_timing):0; //Check for any time that has passed to emulate! Don't emulate when not allowed to run, keeping emulation paused! 9 unlock(LOCK_INPUT); 10 uint_64 currentCPUtime = (uint_64)currenttiming; //Current CPU time to update to! 11 uint_64 timeoutCPUtime = currentCPUtime+TIMEOUT_TIME; //We're timed out this far in the future (1ms)! 12 13 double instructiontime,timeexecuted=0.0f; //How much time did the instruction last? 14 byte timeout = TIMEOUT_INTERVAL; //Check every 10 instructions for timeout! 15 for (;last_timing<currentCPUtime;) //CPU cycle loop for as many cycles as needed to get up-to-date! 16 { 17 if (debugger_thread) 18 { 19 if (threadRunning(debugger_thread)) //Are we running the debugger? 20 { 21 instructiontime = currentCPUtime - last_timing; //The instruction time is the total time passed! 22 updateAudio(instructiontime); //Discard the time passed! 23 timeexecuted += instructiontime; //Increase CPU executed time executed this block! 24 last_timing += instructiontime; //Increase the last timepoint! 25 goto skipCPUtiming; //OK, but skipped! 26 } 27 } 28 if (BIOSMenuThread) 29 { 30 if (threadRunning(BIOSMenuThread)) //Are we running the BIOS menu and not permanently halted? Block our execution! 31 { 32 if ((CPU[activeCPU].halt&2)==0) //Are we allowed to be halted entirely? 33 { 34 instructiontime = currentCPUtime - last_timing; //The instruction time is the total time passed! 35 updateAudio(instructiontime); //Discard the time passed! 36 timeexecuted += instructiontime; //Increase CPU executed time executed this block! 37 last_timing += instructiontime; //Increase the last timepoint! 38 goto skipCPUtiming; //OK, but skipped! 39 } 40 BIOSMenuAllowed = 0; //We're running the BIOS menu! Don't open it again! 41 } 42 } 43 if ((CPU[activeCPU].halt&2)==0) //Are we running normally(not partly ran without CPU from the BIOS menu)? 44 { 45 BIOSMenuThread = NULL; //We don't run the BIOS menu anymore! 46 } 47 48 if (allcleared) return 0; //Abort: invalid buffer! 49 50 interruptsaved = 0; //Reset PIC interrupt to not used! 51 if (!CPU[activeCPU].registers) //We need registers at this point, but have none to use? 52 { 53 return 0; //Invalid registers: abort, since we're invalid! 54 } 55 56 CPU_resetTimings(); //Reset all required CPU timings required! 57 58 CPU_tickPendingReset(); 59 60 if ((CPU[activeCPU].halt&3) && (BIU_Ready() && CPU[activeCPU].resetPending==0)) //Halted normally with no reset pending? Don't count CGA wait states!
…Show last 212 lines
61 { 62 if (romsize) //Debug HLT? 63 { 64 MMU_dumpmemory("bootrom.dmp"); //Dump the memory to file! 65 return 0; //Stop execution! 66 } 67 68 if (FLAG_IF && PICInterrupt() && ((CPU[activeCPU].halt&2)==0)) //We have an interrupt? Clear Halt State when allowed to! 69 { 70 CPU[activeCPU].halt = 0; //Interrupt->Resume from HLT 71 goto resumeFromHLT; //We're resuming from HLT state! 72 } 73 else 74 { 75 //Execute using actual CPU clocks! 76 CPU[activeCPU].cycles = 1; //HLT takes 1 cycle for now, since it's unknown! 77 } 78 if (CPU[activeCPU].halt==1) //Normal halt? 79 { 80 //Increase the instruction counter every instruction/HLT time! 81 cpudebugger = needdebugger(); //Debugging information required? Refresh in case of external activation! 82 if (cpudebugger) //Debugging? 83 { 84 debugger_beforeCPU(); //Make sure the debugger is prepared when needed! 85 debugger_setcommand("<HLT>"); //We're a HLT state, so give the HLT command! 86 } 87 CPU[activeCPU].executed = 1; //For making the debugger execute correctly! 88 //Increase the instruction counter every cycle/HLT time! 89 debugger_step(); //Step debugger if needed, even during HLT state! 90 } 91 } 92 else //We're not halted? Execute the CPU routines! 93 { 94 resumeFromHLT: 95 if (CPU[activeCPU].instructionfetch.CPU_isFetching && (CPU[activeCPU].instructionfetch.CPU_fetchphase==1)) //We're starting a new instruction? 96 { 97 if (CPU[activeCPU].registers && doEMUsinglestep && allow_debuggerstep) //Single step enabled and allowed? 98 { 99 if (getcpumode() == (doEMUsinglestep - 1)) //Are we the selected CPU mode? 100 { 101 switch (getcpumode()) //What CPU mode are we to debug? 102 { 103 case CPU_MODE_REAL: //Real mode? 104 singlestep |= ((CPU[activeCPU].registers->CS == (((singlestepaddress >> 16)&0xFFFF)) && ((CPU[activeCPU].registers->IP == (singlestepaddress & 0xFFFF))||(singlestepaddress&0x1000000000000ULL)))||(singlestepaddress&0x2000000000000ULL)); //Single step enabled? 105 break; 106 case CPU_MODE_PROTECTED: //Protected mode? 107 case CPU_MODE_8086: //Virtual 8086 mode? 108 singlestep |= ((CPU[activeCPU].registers->CS == (((singlestepaddress >> 32)&0xFFFF)) && ((CPU[activeCPU].registers->EIP == (singlestepaddress & 0xFFFFFFFF))||(singlestepaddress&0x1000000000000ULL)))||(singlestepaddress&0x2000000000000ULL)); //Single step enabled? 109 break; 110 default: //Invalid mode? 111 break; 112 } 113 } 114 } 115 116 cpudebugger = needdebugger(); //Debugging information required? Refresh in case of external activation! 117 MMU_logging = debugger_logging(); //Are we logging? 118 119 HWINT_saved = 0; //No HW interrupt by default! 120 CPU_beforeexec(); //Everything before the execution! 121 if ((!CPU[activeCPU].trapped) && CPU[activeCPU].registers && CPU[activeCPU].allowInterrupts && (CPU[activeCPU].permanentreset==0) && (CPU[activeCPU].internalinterruptstep==0) && BIU_Ready() && (CPU_executionphase_busy()==0)) //Only check for hardware interrupts when not trapped and allowed to execute interrupts(not permanently reset)! 122 { 123 if (FLAG_IF) //Interrupts available? 124 { 125 if (PICInterrupt()) //We have a hardware interrupt ready? 126 { 127 HWINT_nr = nextintr(); //Get the HW interrupt nr! 128 HWINT_saved = 2; //We're executing a HW(PIC) interrupt! 129 if (!((EMULATED_CPU <= CPU_80286) && REPPending)) //Not 80386+, REP pending and segment override? 130 { 131 CPU_8086REPPending(); //Process pending REPs normally as documented! 132 } 133 else //Execute the CPU bug! 134 { 135 CPU_8086REPPending(); //Process pending REPs normally as documented! 136 CPU[activeCPU].registers->EIP = CPU_InterruptReturn; //Use the special interrupt return address to return to the last prefix instead of the start! 137 } 138 CPU_exec_lastCS = CPU_exec_CS; 139 CPU_exec_lastEIP = CPU_exec_EIP; 140 CPU_exec_CS = CPU[activeCPU].registers->CS; //Save for error handling! 141 CPU_exec_EIP = CPU[activeCPU].registers->EIP; //Save for error handling! 142 CPU_saveFaultData(); //Save fault data to go back to when exceptions occur! 143 call_hard_inthandler(HWINT_nr); //get next interrupt from the i8259, if any! 144 } 145 } 146 } 147 148 #ifdef LOG_BOGUS 149 uint_32 addr_start, addr_left, curaddr; //Start of the currently executing instruction in real memory! We're testing 5 instructions! 150 addr_left=2*LOG_BOGUS; 151 curaddr = 0; 152 addr_start = CPU_MMU_start(CPU_SEGMENT_CS,CPU[activeCPU].registers->CS); //Base of the currently executing block! 153 addr_start += REG_EIP; //Add the address for the address we're executing! 154 155 for (;addr_left;++curaddr) //Test all addresses! 156 { 157 if (MMU_directrb_realaddr(addr_start+curaddr)) //Try to read the opcode! Anything found(not 0000h instruction)? 158 { 159 break; //Stop searching! 160 } 161 --addr_left; //Tick one address checked! 162 } 163 if (addr_left==0) //Bogus memory detected? 164 { 165 dolog("bogus","Bogus exection memory detected(%u 0000h opcodes) at %04X:%08X! Previous instruction: %02X(0F:%u)@%04X:%08X",LOG_BOGUS,CPU[activeCPU].registers->CS,CPU[activeCPU].registers->EIP,CPU[activeCPU].previousopcode,CPU[activeCPU].previousopcode0F,CPU_exec_lastCS,CPU_exec_lastEIP); //Log the warning of entering bogus memory! 166 } 167 #endif 168 } 169 170 CPU_exec(); //Run CPU! 171 172 //Increase the instruction counter every cycle/HLT time! 173 debugger_step(); //Step debugger if needed! 174 if (CPU[activeCPU].executed) //Are we executed? 175 { 176 CB_handleCallbacks(); //Handle callbacks after CPU/debugger usage! 177 } 178 } 179 180 //Update current timing with calculated cycles we've executed! 181 if (likely(useIPSclock==0)) //Use cycle-accurate clock? 182 { 183 instructiontime = CPU[activeCPU].cycles*CPU_speed_cycle; //Increase timing with the instruction time! 184 } 185 else 186 { 187 instructiontime = CPU[activeCPU].executed*CPU_speed_cycle; //Increase timing with the instruction time! 188 } 189 last_timing += instructiontime; //Increase CPU time executed! 190 timeexecuted += instructiontime; //Increase CPU executed time executed this block! 191 192 //Tick 14MHz master clock, for basic hardware using it! 193 MHZ14_ticktiming += instructiontime; //Add time to the 14MHz master clock! 194 if (likely(MHZ14_ticktiming<MHZ14tick)) //To not tick some 14MHz clocks? This ix the case with most faster CPUs! 195 { 196 MHZ14passed = 0; //No time has passed on the 14MHz Master clock! 197 } 198 else 199 { 200 MHZ14passed = (uint_32)(MHZ14_ticktiming/MHZ14tick); //Tick as many as possible! 201 MHZ14_ticktiming -= MHZ14tick*(float)MHZ14passed; //Rest the time passed! 202 } 203 204 MMU_logging |= 2; //Are we logging hardware memory accesses(DMA etc)? 205 if (likely((CPU[activeCPU].halt&0x10)==0)) tickPIT(instructiontime,MHZ14passed); //Tick the PIT as much as we need to keep us in sync when running! 206 if (unlikely(MHZ14passed)) //14MHz to be ticked? 207 { 208 updateDMA(MHZ14passed); //Update the DMA timer! 209 if (unlikely(useAdlib)) updateAdlib(MHZ14passed); //Tick the adlib timer if needed! 210 } 211 updateMouse(instructiontime); //Tick the mouse timer if needed! 212 stepDROPlayer(instructiontime); //DRO player playback, if any! 213 updateMIDIPlayer(instructiontime); //MIDI player playback, if any! 214 updatePS2Keyboard(instructiontime); //Tick the PS/2 keyboard timer, if needed! 215 updatePS2Mouse(instructiontime); //Tick the PS/2 mouse timer, if needed! 216 update8042(instructiontime); //Tick the PS/2 mouse timer, if needed! 217 updateCMOS(instructiontime); //Tick the CMOS, if needed! 218 updateFloppy(instructiontime); //Update the floppy! 219 updateMPUTimer(instructiontime); //Update the MPU timing! 220 if (useGameBlaster && ((CPU[activeCPU].halt&0x10)==0)) updateGameBlaster(instructiontime,MHZ14passed); //Tick the Game Blaster timer if needed and running! 221 if (useSoundBlaster && ((CPU[activeCPU].halt&0x10)==0)) updateSoundBlaster(instructiontime,MHZ14passed); //Tick the Sound Blaster timer if needed and running! 222 updateATA(instructiontime); //Update the ATA timer! 223 tickParallel(instructiontime); //Update the Parallel timer! 224 updateUART(instructiontime); //Update the UART timer! 225 if (useLPTDAC && ((CPU[activeCPU].halt&0x10)==0)) tickssourcecovox(instructiontime); //Update the Sound Source / Covox Speech Thing if needed! 226 if (likely((CPU[activeCPU].halt&0x10)==0)) updateVGA(instructiontime); //Update the VGA timer when running! 227 updateModem(instructiontime); //Update the modem! 228 updateJoystick(instructiontime); //Update the Joystick! 229 updateAudio(instructiontime); //Update the general audio processing! 230 BIOSROM_updateTimers(instructiontime); //Update any ROM(Flash ROM) timers! 231 MMU_logging &= ~2; //Are we logging hardware memory accesses again? 232 if (--timeout==0) //Timed out? 233 { 234 timeout = TIMEOUT_INTERVAL; //Reset the timeout to check the next time! 235 currenttiming += getnspassed(&CPU_timing); //Check for passed time! 236 if (currenttiming >= timeoutCPUtime) break; //Timeout? We're not fast enough to run at full speed! 237 } 238 } //CPU cycle loop! 239 240 skipCPUtiming: //Audio emulation only? 241 //Slowdown to requested speed if needed! 242 currenttiming += getnspassed(&CPU_timing); //Add real time! 243 for (;unlikely(currenttiming < last_timing);) //Not enough time spent on instructions? 244 { 245 currenttiming += getnspassed(&CPU_timing); //Add to the time to wait! 246 delay(0); //Update to current time every instruction according to cycles passed! 247 } 248 249 float temp; 250 temp = (float)MAX(last_timing,currenttiming); //Save for substraction(time executed in real time)! 251 last_timing -= temp; //Keep the CPU timing within limits! 252 currenttiming -= temp; //Keep the current timing within limits! 253 254 timeemulated += timeexecuted; //Add timing for the CPU percentage to update! 255 256 updateKeyboard(timeexecuted); //Tick the keyboard timer if needed! 257 258 //Check for BIOS menu! 259 if ((psp_keypressed(BUTTON_SELECT) || (Settings_request==1)) && (BIOSMenuThread==NULL) && (debugger_thread==NULL)) //Run in-emulator BIOS menu requested while running? 260 { 261 if ((!is_gamingmode() && !Direct_Input && BIOSMenuAllowed) || (Settings_request==1)) //Not gaming/direct input mode and allowed to open it(not already started)? 262 { 263 skipstep = 3; //Skip while stepping? 1=repeating, 2=EIP destination, 3=Stop asap. 264 lock(LOCK_INPUT); 265 Settings_request = 0; //We're handling the request! 266 unlock(LOCK_INPUT); 267 BIOSMenuThread = startThread(&BIOSMenuExecution,"UniPCemu_BIOSMenu",NULL); //Start the BIOS menu thread! 268 delay(0); //Wait a bit for the thread to start up! 269 } 270 } 271 return 1; //OK! 272}
Essentially, the accumulator loop is handled for the entire machine based on realtime, as well as for every hardware based on emulated CPU time(clock ticks time tick duration, which is translated to 14MHz or taken directly(as nanoseconds the CPU has ticked instead of 14MHz cycles ticked), depending on the hardware). Stuff like sound rendering itself(rendering to the sound buffers from emulated sound samples) are done based on the 14MHz clock, while the rendering to the actual sound buffers from those buffers(double buffering) as well as input uses the nanosecond timer instead.
Edit: I've made a little improvement, which allows EGA and CGA to use the 14MHz clock directly, for a small speedup:
1OPTINLINE byte coreHandler() 2{ 3 uint_32 MHZ14passed; //14 MHZ clock passed? 4 byte BIOSMenuAllowed = 1; //Are we allowed to open the BIOS menu? 5 //CPU execution, needs to be before the debugger! 6 lock(LOCK_INPUT); 7 if (unlikely((haswindowactive&0x1C)==0xC)) {getnspassed(&CPU_timing); haswindowactive|=0x10;} //Pending to finish Soundblaster! 8 currenttiming += likely(haswindowactive&2)?getnspassed(&CPU_timing):0; //Check for any time that has passed to emulate! Don't emulate when not allowed to run, keeping emulation paused! 9 unlock(LOCK_INPUT); 10 uint_64 currentCPUtime = (uint_64)currenttiming; //Current CPU time to update to! 11 uint_64 timeoutCPUtime = currentCPUtime+TIMEOUT_TIME; //We're timed out this far in the future (1ms)! 12 13 double instructiontime,timeexecuted=0.0f; //How much time did the instruction last? 14 byte timeout = TIMEOUT_INTERVAL; //Check every 10 instructions for timeout! 15 for (;last_timing<currentCPUtime;) //CPU cycle loop for as many cycles as needed to get up-to-date! 16 { 17 if (debugger_thread) 18 { 19 if (threadRunning(debugger_thread)) //Are we running the debugger? 20 { 21 instructiontime = currentCPUtime - last_timing; //The instruction time is the total time passed! 22 updateAudio(instructiontime); //Discard the time passed! 23 timeexecuted += instructiontime; //Increase CPU executed time executed this block! 24 last_timing += instructiontime; //Increase the last timepoint! 25 goto skipCPUtiming; //OK, but skipped! 26 } 27 } 28 if (BIOSMenuThread) 29 { 30 if (threadRunning(BIOSMenuThread)) //Are we running the BIOS menu and not permanently halted? Block our execution! 31 { 32 if ((CPU[activeCPU].halt&2)==0) //Are we allowed to be halted entirely? 33 { 34 instructiontime = currentCPUtime - last_timing; //The instruction time is the total time passed! 35 updateAudio(instructiontime); //Discard the time passed! 36 timeexecuted += instructiontime; //Increase CPU executed time executed this block! 37 last_timing += instructiontime; //Increase the last timepoint! 38 goto skipCPUtiming; //OK, but skipped! 39 } 40 BIOSMenuAllowed = 0; //We're running the BIOS menu! Don't open it again! 41 } 42 } 43 if ((CPU[activeCPU].halt&2)==0) //Are we running normally(not partly ran without CPU from the BIOS menu)? 44 { 45 BIOSMenuThread = NULL; //We don't run the BIOS menu anymore! 46 } 47 48 if (allcleared) return 0; //Abort: invalid buffer! 49 50 interruptsaved = 0; //Reset PIC interrupt to not used! 51 if (!CPU[activeCPU].registers) //We need registers at this point, but have none to use? 52 { 53 return 0; //Invalid registers: abort, since we're invalid! 54 } 55 56 CPU_resetTimings(); //Reset all required CPU timings required! 57 58 CPU_tickPendingReset(); 59 60 if ((CPU[activeCPU].halt&3) && (BIU_Ready() && CPU[activeCPU].resetPending==0)) //Halted normally with no reset pending? Don't count CGA wait states!
…Show last 218 lines
61 { 62 if (romsize) //Debug HLT? 63 { 64 MMU_dumpmemory("bootrom.dmp"); //Dump the memory to file! 65 return 0; //Stop execution! 66 } 67 68 if (FLAG_IF && PICInterrupt() && ((CPU[activeCPU].halt&2)==0)) //We have an interrupt? Clear Halt State when allowed to! 69 { 70 CPU[activeCPU].halt = 0; //Interrupt->Resume from HLT 71 goto resumeFromHLT; //We're resuming from HLT state! 72 } 73 else 74 { 75 //Execute using actual CPU clocks! 76 CPU[activeCPU].cycles = 1; //HLT takes 1 cycle for now, since it's unknown! 77 } 78 if (CPU[activeCPU].halt==1) //Normal halt? 79 { 80 //Increase the instruction counter every instruction/HLT time! 81 cpudebugger = needdebugger(); //Debugging information required? Refresh in case of external activation! 82 if (cpudebugger) //Debugging? 83 { 84 debugger_beforeCPU(); //Make sure the debugger is prepared when needed! 85 debugger_setcommand("<HLT>"); //We're a HLT state, so give the HLT command! 86 } 87 CPU[activeCPU].executed = 1; //For making the debugger execute correctly! 88 //Increase the instruction counter every cycle/HLT time! 89 debugger_step(); //Step debugger if needed, even during HLT state! 90 } 91 } 92 else //We're not halted? Execute the CPU routines! 93 { 94 resumeFromHLT: 95 if (CPU[activeCPU].instructionfetch.CPU_isFetching && (CPU[activeCPU].instructionfetch.CPU_fetchphase==1)) //We're starting a new instruction? 96 { 97 if (CPU[activeCPU].registers && doEMUsinglestep && allow_debuggerstep) //Single step enabled and allowed? 98 { 99 if (getcpumode() == (doEMUsinglestep - 1)) //Are we the selected CPU mode? 100 { 101 switch (getcpumode()) //What CPU mode are we to debug? 102 { 103 case CPU_MODE_REAL: //Real mode? 104 singlestep |= ((CPU[activeCPU].registers->CS == (((singlestepaddress >> 16)&0xFFFF)) && ((CPU[activeCPU].registers->IP == (singlestepaddress & 0xFFFF))||(singlestepaddress&0x1000000000000ULL)))||(singlestepaddress&0x2000000000000ULL)); //Single step enabled? 105 break; 106 case CPU_MODE_PROTECTED: //Protected mode? 107 case CPU_MODE_8086: //Virtual 8086 mode? 108 singlestep |= ((CPU[activeCPU].registers->CS == (((singlestepaddress >> 32)&0xFFFF)) && ((CPU[activeCPU].registers->EIP == (singlestepaddress & 0xFFFFFFFF))||(singlestepaddress&0x1000000000000ULL)))||(singlestepaddress&0x2000000000000ULL)); //Single step enabled? 109 break; 110 default: //Invalid mode? 111 break; 112 } 113 } 114 } 115 116 cpudebugger = needdebugger(); //Debugging information required? Refresh in case of external activation! 117 MMU_logging = debugger_logging(); //Are we logging? 118 119 HWINT_saved = 0; //No HW interrupt by default! 120 CPU_beforeexec(); //Everything before the execution! 121 if ((!CPU[activeCPU].trapped) && CPU[activeCPU].registers && CPU[activeCPU].allowInterrupts && (CPU[activeCPU].permanentreset==0) && (CPU[activeCPU].internalinterruptstep==0) && BIU_Ready() && (CPU_executionphase_busy()==0)) //Only check for hardware interrupts when not trapped and allowed to execute interrupts(not permanently reset)! 122 { 123 if (FLAG_IF) //Interrupts available? 124 { 125 if (PICInterrupt()) //We have a hardware interrupt ready? 126 { 127 HWINT_nr = nextintr(); //Get the HW interrupt nr! 128 HWINT_saved = 2; //We're executing a HW(PIC) interrupt! 129 if (!((EMULATED_CPU <= CPU_80286) && REPPending)) //Not 80386+, REP pending and segment override? 130 { 131 CPU_8086REPPending(); //Process pending REPs normally as documented! 132 } 133 else //Execute the CPU bug! 134 { 135 CPU_8086REPPending(); //Process pending REPs normally as documented! 136 CPU[activeCPU].registers->EIP = CPU_InterruptReturn; //Use the special interrupt return address to return to the last prefix instead of the start! 137 } 138 CPU_exec_lastCS = CPU_exec_CS; 139 CPU_exec_lastEIP = CPU_exec_EIP; 140 CPU_exec_CS = CPU[activeCPU].registers->CS; //Save for error handling! 141 CPU_exec_EIP = CPU[activeCPU].registers->EIP; //Save for error handling! 142 CPU_saveFaultData(); //Save fault data to go back to when exceptions occur! 143 call_hard_inthandler(HWINT_nr); //get next interrupt from the i8259, if any! 144 } 145 } 146 } 147 148 #ifdef LOG_BOGUS 149 uint_32 addr_start, addr_left, curaddr; //Start of the currently executing instruction in real memory! We're testing 5 instructions! 150 addr_left=2*LOG_BOGUS; 151 curaddr = 0; 152 addr_start = CPU_MMU_start(CPU_SEGMENT_CS,CPU[activeCPU].registers->CS); //Base of the currently executing block! 153 addr_start += REG_EIP; //Add the address for the address we're executing! 154 155 for (;addr_left;++curaddr) //Test all addresses! 156 { 157 if (MMU_directrb_realaddr(addr_start+curaddr)) //Try to read the opcode! Anything found(not 0000h instruction)? 158 { 159 break; //Stop searching! 160 } 161 --addr_left; //Tick one address checked! 162 } 163 if (addr_left==0) //Bogus memory detected? 164 { 165 dolog("bogus","Bogus exection memory detected(%u 0000h opcodes) at %04X:%08X! Previous instruction: %02X(0F:%u)@%04X:%08X",LOG_BOGUS,CPU[activeCPU].registers->CS,CPU[activeCPU].registers->EIP,CPU[activeCPU].previousopcode,CPU[activeCPU].previousopcode0F,CPU_exec_lastCS,CPU_exec_lastEIP); //Log the warning of entering bogus memory! 166 } 167 #endif 168 } 169 170 CPU_exec(); //Run CPU! 171 172 //Increase the instruction counter every cycle/HLT time! 173 debugger_step(); //Step debugger if needed! 174 if (CPU[activeCPU].executed) //Are we executed? 175 { 176 CB_handleCallbacks(); //Handle callbacks after CPU/debugger usage! 177 } 178 } 179 180 //Update current timing with calculated cycles we've executed! 181 if (likely(useIPSclock==0)) //Use cycle-accurate clock? 182 { 183 instructiontime = CPU[activeCPU].cycles*CPU_speed_cycle; //Increase timing with the instruction time! 184 } 185 else 186 { 187 instructiontime = CPU[activeCPU].executed*CPU_speed_cycle; //Increase timing with the instruction time! 188 } 189 last_timing += instructiontime; //Increase CPU time executed! 190 timeexecuted += instructiontime; //Increase CPU executed time executed this block! 191 192 //Tick 14MHz master clock, for basic hardware using it! 193 MHZ14_ticktiming += instructiontime; //Add time to the 14MHz master clock! 194 if (likely(MHZ14_ticktiming<MHZ14tick)) //To not tick some 14MHz clocks? This ix the case with most faster CPUs! 195 { 196 MHZ14passed = 0; //No time has passed on the 14MHz Master clock! 197 } 198 else 199 { 200 MHZ14passed = (uint_32)(MHZ14_ticktiming/MHZ14tick); //Tick as many as possible! 201 MHZ14_ticktiming -= MHZ14tick*(float)MHZ14passed; //Rest the time passed! 202 } 203 204 MMU_logging |= 2; //Are we logging hardware memory accesses(DMA etc)? 205 double MHZ14passed_ns; 206 if (unlikely(MHZ14passed)) //14MHz to be ticked? 207 { 208 MHZ14passed_ns = MHZ14passed*MHZ14tick; //Actual ns ticked! 209 updateDMA(MHZ14passed); //Update the DMA timer! 210 if (likely((CPU[activeCPU].halt&0x10)==0)) tickPIT(MHZ14passed_ns,MHZ14passed); //Tick the PIT as much as we need to keep us in sync when running! 211 if (unlikely(useAdlib)) updateAdlib(MHZ14passed); //Tick the adlib timer if needed! 212 updateMouse(MHZ14passed_ns); //Tick the mouse timer if needed! 213 stepDROPlayer(MHZ14passed_ns); //DRO player playback, if any! 214 updateMIDIPlayer(MHZ14passed_ns); //MIDI player playback, if any! 215 updatePS2Keyboard(MHZ14passed_ns); //Tick the PS/2 keyboard timer, if needed! 216 updatePS2Mouse(MHZ14passed_ns); //Tick the PS/2 mouse timer, if needed! 217 update8042(MHZ14passed_ns); //Tick the PS/2 mouse timer, if needed! 218 updateCMOS(MHZ14passed_ns); //Tick the CMOS, if needed! 219 updateFloppy(MHZ14passed_ns); //Update the floppy! 220 updateMPUTimer(MHZ14passed_ns); //Update the MPU timing! 221 if (useGameBlaster && ((CPU[activeCPU].halt&0x10)==0)) updateGameBlaster(MHZ14passed_ns,MHZ14passed); //Tick the Game Blaster timer if needed and running! 222 if (useSoundBlaster && ((CPU[activeCPU].halt&0x10)==0)) updateSoundBlaster(MHZ14passed_ns,MHZ14passed); //Tick the Sound Blaster timer if needed and running! 223 updateATA(MHZ14passed_ns); //Update the ATA timer! 224 tickParallel(MHZ14passed_ns); //Update the Parallel timer! 225 updateUART(MHZ14passed_ns); //Update the UART timer! 226 if (useLPTDAC && ((CPU[activeCPU].halt&0x10)==0)) tickssourcecovox(MHZ14passed_ns); //Update the Sound Source / Covox Speech Thing if needed! 227 if (likely((CPU[activeCPU].halt&0x10)==0)) updateVGA(0.0,MHZ14passed); //Update the video 14MHz timer, when running! 228 } 229 if (likely((CPU[activeCPU].halt&0x10)==0)) updateVGA(instructiontime,0); //Update the normal video timer, when running! 230 if (unlikely(MHZ14passed)) 231 { 232 updateModem(MHZ14passed_ns); //Update the modem! 233 updateJoystick(MHZ14passed_ns); //Update the Joystick! 234 updateAudio(MHZ14passed_ns); //Update the general audio processing! 235 BIOSROM_updateTimers(MHZ14passed_ns); //Update any ROM(Flash ROM) timers! 236 } 237 MMU_logging &= ~2; //Are we logging hardware memory accesses again? 238 if (--timeout==0) //Timed out? 239 { 240 timeout = TIMEOUT_INTERVAL; //Reset the timeout to check the next time! 241 currenttiming += getnspassed(&CPU_timing); //Check for passed time! 242 if (currenttiming >= timeoutCPUtime) break; //Timeout? We're not fast enough to run at full speed! 243 } 244 } //CPU cycle loop! 245 246 skipCPUtiming: //Audio emulation only? 247 //Slowdown to requested speed if needed! 248 currenttiming += getnspassed(&CPU_timing); //Add real time! 249 for (;unlikely(currenttiming < last_timing);) //Not enough time spent on instructions? 250 { 251 currenttiming += getnspassed(&CPU_timing); //Add to the time to wait! 252 delay(0); //Update to current time every instruction according to cycles passed! 253 } 254 255 float temp; 256 temp = (float)MAX(last_timing,currenttiming); //Save for substraction(time executed in real time)! 257 last_timing -= temp; //Keep the CPU timing within limits! 258 currenttiming -= temp; //Keep the current timing within limits! 259 260 timeemulated += timeexecuted; //Add timing for the CPU percentage to update! 261 262 updateKeyboard(timeexecuted); //Tick the keyboard timer if needed! 263 264 //Check for BIOS menu! 265 if ((psp_keypressed(BUTTON_SELECT) || (Settings_request==1)) && (BIOSMenuThread==NULL) && (debugger_thread==NULL)) //Run in-emulator BIOS menu requested while running? 266 { 267 if ((!is_gamingmode() && !Direct_Input && BIOSMenuAllowed) || (Settings_request==1)) //Not gaming/direct input mode and allowed to open it(not already started)? 268 { 269 skipstep = 3; //Skip while stepping? 1=repeating, 2=EIP destination, 3=Stop asap. 270 lock(LOCK_INPUT); 271 Settings_request = 0; //We're handling the request! 272 unlock(LOCK_INPUT); 273 BIOSMenuThread = startThread(&BIOSMenuExecution,"UniPCemu_BIOSMenu",NULL); //Start the BIOS menu thread! 274 delay(0); //Wait a bit for the thread to start up! 275 } 276 } 277 return 1; //OK! 278}
The remaining time is added back into the loop at various points: both using the old accumulated value and adding the new time difference(in nanoseconds) and within gettimepassed_ns(), which takes the delta time, gives the nanosecond time(in whole ns), and stores the remainder back for usage in the next loop, allowing timingss to count in different units(nanoseconds, microseconds, milliseconds) while keeping the rate semi-constant(e.g. one second passed reported is actually one seconds, no exceptions(except the remainder, which is never directly given)).
The main problem that's left is that the core code(the graphics card emulation and CPU emulation in particular) is so heavy, that it's taking way more time to emulate than actual time passes(that's why it's reporting 18-20% at most running on a i7 CPU: it's spending about 5 times as much time emulating the system as actual realtime passes(which is a 16MHz 80386 with VGA hardware as well as all audio hardware that's supported(which basically don't take up any time, according to the profiler))). It's about 30% CPU emulation(CPU/BIU to be exact), 18% VGA and remainder is hardware and rendering to the screen surface(including text surfaces for buttons etc.).
Other than those, it's 5.2% updateAudio, 4% updateSoundBlaster, 27.8% other?