Timing of emulator base on win32
Timing of emulator base on win32
I'd want to build an Emulator base on win32,I refer some code and I can achieve instruction of 6502,but how to make the emulator run speed is the same of actual speed(about 4Mhz)?
Skyler,
In my simulator, I poll the system timer, which increments every 1ms.
What I did is to let the simulator execute 1ms worth of instructions (4000 cycles @ 4MHz) then wait for that ms to end. Then, I do another 1ms worth of instructions and wait. For most programs, this is acceptable. Sound generation based on timing loops would not be accurate since the loops would not be equally timed.
In my next release, I am attempting to time each instruction more accurately by using timing loops, cycle counters, and feedback to equally space each instruction within the 1ms window.
You can download my simulator and the C source code from my web site.
http://65c02.tripod.com/
Good luck!
Daryl
In my simulator, I poll the system timer, which increments every 1ms.
What I did is to let the simulator execute 1ms worth of instructions (4000 cycles @ 4MHz) then wait for that ms to end. Then, I do another 1ms worth of instructions and wait. For most programs, this is acceptable. Sound generation based on timing loops would not be accurate since the loops would not be equally timed.
In my next release, I am attempting to time each instruction more accurately by using timing loops, cycle counters, and feedback to equally space each instruction within the 1ms window.
You can download my simulator and the C source code from my web site.
http://65c02.tripod.com/
Good luck!
Daryl
-
schidester
- Posts: 57
- Joined: 04 Sep 2002
- Location: Iowa
For sound, you'd probably want to buffer up 4000 cycles worth of sound and pass that off to the Windows sound API as a 1ms buffer to play in the background, then work on the next 4000 cycles in another buffer. But you probably don't want to bit-bang sound from a simulator. As a matter of fact, (I haven't looked at this in a long time, and not to deeply at that), you could use the notification that Windows gives you that it's done with the buffer and ready for the next as your 1ms timer, instead of polling.
Scott
Scott
-
schidester
- Posts: 57
- Joined: 04 Sep 2002
- Location: Iowa
Scott,
First, I'm no expert on Windows programming. I read up on the sleep function and found that it only has millisecond resolution. I'm counting cycles within a 1ms window. If the CPU takes .38ms to process the instructions, I cannot tell it to sleep for .62ms.
I can only sleep for whole ms periods. Do you have any suggestions?
Daryl
First, I'm no expert on Windows programming. I read up on the sleep function and found that it only has millisecond resolution. I'm counting cycles within a 1ms window. If the CPU takes .38ms to process the instructions, I cannot tell it to sleep for .62ms.
I can only sleep for whole ms periods. Do you have any suggestions?
Daryl
-
schidester
- Posts: 57
- Joined: 04 Sep 2002
- Location: Iowa
Daryl,
Hmm.... I'm somewhat novice myself on the Win32 stuff. And for "no expert," your simulator sure seems cool. OK, enough flattery. Some suggestions:
1. If it was Linux (or UNIX), I'd say use pthread_cond_timedwait() which uses an absolute time; then, no matter how long your instructions took to execute, you'd be woken up on the next millisecond. For example, if your run your simulation at Aug 29th, 2003, at 1:20:15.0100, then execute 4000 cycles and sleep until Aug 29th, 2003, at 1:20:15.0101. There should be an equivalent "wait until absolute time" function in Win32, but I couldn't find it.
2. You can set up a separate thread using CreateThread, which waited on 1ms intervals and incremented a semaphore. Your 6502 thread would simply execute 4000 cycles and then wait on the semaphore. This may still be off because of the time taken between 1ms waits to post the semaphore.
3. You can increase the period of your simulation loop, for example, execute for an equivalent of 100 ms. Then, if it takes 38.5 ms to execute 400,000 cycles, you'll wait for the remaining 62 ms with an insignificant amount of real-time error. Of course, you may get some flicker in your simulation at 100ms, but if it's just a simulation for testing, it should be fine. Or trade off timing accuracy for smoothness.
There are a few more ways to do it, but the absolute time method (#1) is best (if Win32 provides it). If you can wake up on 1ms boundaries and execute your 4000 cycles, and then sleep until the next 1ms boundary, you'll be dead-on, and your program won't hog the CPU.
Scott
Hmm.... I'm somewhat novice myself on the Win32 stuff. And for "no expert," your simulator sure seems cool. OK, enough flattery. Some suggestions:
1. If it was Linux (or UNIX), I'd say use pthread_cond_timedwait() which uses an absolute time; then, no matter how long your instructions took to execute, you'd be woken up on the next millisecond. For example, if your run your simulation at Aug 29th, 2003, at 1:20:15.0100, then execute 4000 cycles and sleep until Aug 29th, 2003, at 1:20:15.0101. There should be an equivalent "wait until absolute time" function in Win32, but I couldn't find it.
2. You can set up a separate thread using CreateThread, which waited on 1ms intervals and incremented a semaphore. Your 6502 thread would simply execute 4000 cycles and then wait on the semaphore. This may still be off because of the time taken between 1ms waits to post the semaphore.
3. You can increase the period of your simulation loop, for example, execute for an equivalent of 100 ms. Then, if it takes 38.5 ms to execute 400,000 cycles, you'll wait for the remaining 62 ms with an insignificant amount of real-time error. Of course, you may get some flicker in your simulation at 100ms, but if it's just a simulation for testing, it should be fine. Or trade off timing accuracy for smoothness.
There are a few more ways to do it, but the absolute time method (#1) is best (if Win32 provides it). If you can wake up on 1ms boundaries and execute your 4000 cycles, and then sleep until the next 1ms boundary, you'll be dead-on, and your program won't hog the CPU.
Scott
-
schidester
- Posts: 57
- Joined: 04 Sep 2002
- Location: Iowa
If you are waiting for an absolute time, such as "wait until 9:22 am, 50.0001 seconds" and, after running 4000 cycles you find that it's already 50.0004 seconds, you simply run your next 4000 cycles--your wait function should return immediately. The simulation will run slow, but it will also run no faster than it would in real time.
If your PC is too slow to run a 6502 simulation at 4MHz, then it's probably too slow to run Windows. If your simulation is honestly too slow for a Windows box, then you should probably take some time to optimize it. If you're using DOS, then just poll anyway--nothing else but your simulation will be running.
I couldn't find much help on MM_TIMER; I'm using an older VisualStudio. Are you using MFC? Are you programming for NT/2000 or 95/98? Are you familiar with multithreaded programming?
Scott
If your PC is too slow to run a 6502 simulation at 4MHz, then it's probably too slow to run Windows. If your simulation is honestly too slow for a Windows box, then you should probably take some time to optimize it. If you're using DOS, then just poll anyway--nothing else but your simulation will be running.
I couldn't find much help on MM_TIMER; I'm using an older VisualStudio. Are you using MFC? Are you programming for NT/2000 or 95/98? Are you familiar with multithreaded programming?
Scott
Skyler,
You could set a variable called target_cycles.
At the end of a ms period, if there are any unexecuted cycles, then set target_cycles = unexecuted cycles. If all the cycles were executed, set it to zero.
Now, before entering the next ms period, ADD 4000 to target_cycles and use that value as the number of cycles to execute.
Daryl
You could set a variable called target_cycles.
At the end of a ms period, if there are any unexecuted cycles, then set target_cycles = unexecuted cycles. If all the cycles were executed, set it to zero.
Now, before entering the next ms period, ADD 4000 to target_cycles and use that value as the number of cycles to execute.
Daryl
Daryl,
I know how to do now.thank you.
skyler
+++++++++++++++++++++++++++++++++++++++++++++++
Scott,
I think Daryl's way is a good way to resolve my problem.
Infact,the below code just occupy CPU usage just 2-4%.I think it will work fine in the future.
I'll set a target_cycles,once the computer is too busy to finish 4000 cycle in ms,this will make it correct.
I'm using SDK and I want my simulation can run in NT,98,2K and XP.I'm an beginer of C++,I don't know about MFC.
Below is the code I use in my project now.
Thank's for your help.
skyler
***********************************************************
***********************************************************
I know how to do now.thank you.
skyler
+++++++++++++++++++++++++++++++++++++++++++++++
Scott,
I think Daryl's way is a good way to resolve my problem.
Infact,the below code just occupy CPU usage just 2-4%.I think it will work fine in the future.
I'll set a target_cycles,once the computer is too busy to finish 4000 cycle in ms,this will make it correct.
I'm using SDK and I want my simulation can run in NT,98,2K and XP.I'm an beginer of C++,I don't know about MFC.
Below is the code I use in my project now.
Thank's for your help.
skyler
***********************************************************
Code: Select all
CMMTimers::CMMTimers()
{
TIMECAPS tc;
if (!Intilizatize)
{
if (TIMERR_NOERROR == ::timeGetDevCaps(&tc,sizeof(TIMECAPS)))
{
timerRes = min(max(tc.wPeriodMin,1),tc.wPeriodMax);
timeBeginPeriod(timerRes);
}
}
}
CMMTimers::~CMMTimers()
{
stopTimer();
if (0 != timerRes)
{
timeEndPeriod(timerRes);
timerRes = 0;
}
}
void CALLBACK internalTimerProc(UINT id,UINT msg,DWORD dwUser,DWORD dw1,DWORD dw2)
{
static char str[40];
sprintf(str,"Cycle:%08d",MMTimer.Counter);
TextOut(Debugger.hdcMSG,0,0,str,strlen(str));
MMTimer.Counter++;
if (CPU.RUN) CPU.EXE(4000);
}
bool CMMTimers::startTimer(void)
{
bool res = false;
MMRESULT RETURN;
if (!Runing)
{
Counter=0;
result = timeSetEvent(
1,
timerRes,
internalTimerProc,
(DWORD)this,
TIME_PERIODIC
);
if (NULL != RETURN)
{
timerId = (UINT)RETURN;
Runing=true;
res = true;
}
return res;
}
return false;
}
bool CMMTimers::stopTimer()
{
MMRESULT RETURN;
result = timeKillEvent(timerId);
if (TIMERR_NOERROR == RETURN)
timerId = 0;
Runing = RETURN;
return TIMERR_NOERROR == RETURN;
}
Last edited by skyler on Fri Sep 19, 2003 5:09 pm, edited 1 time in total.
-
schidester
- Posts: 57
- Joined: 04 Sep 2002
- Location: Iowa
Skyler,
Yeah, that looks like it'll work. The TIMER_PERIODIC is what really saves you. At 2% to 4%, I wouldn't worry about not getting the 4000 cycles done in time at all, but if you don't, you should get a callback for each millisecond missed.
After you start your timer you probably return back to Windows's message loop, which blocks and gives up the CPU until the timeouts occur.
Scott.
Yeah, that looks like it'll work. The TIMER_PERIODIC is what really saves you. At 2% to 4%, I wouldn't worry about not getting the 4000 cycles done in time at all, but if you don't, you should get a callback for each millisecond missed.
After you start your timer you probably return back to Windows's message loop, which blocks and gives up the CPU until the timeouts occur.
Scott.
Scott,
Because I have not simulate the LCD controler,after do this,I think it will become slowly.
If it can finish each ms task,it will exit callback and return to windows's message loop.
If it can not do this,Can i insert the code below to translate windows's message before start next ms task?If I not to do this,windows will have no chance to translate message.I can't prove the code will work fine.
thank you.
skyler
******************************************************
******************************************************
Because I have not simulate the LCD controler,after do this,I think it will become slowly.
If it can finish each ms task,it will exit callback and return to windows's message loop.
If it can not do this,Can i insert the code below to translate windows's message before start next ms task?If I not to do this,windows will have no chance to translate message.I can't prove the code will work fine.
thank you.
skyler
******************************************************
Code: Select all
void CALLBACK internalTimerProc(UINT id,UINT msg,DWORD dwUser,DWORD dw1,DWORD dw2)
{
static char str[40];
static bool Intime;
do
{
T1=GetCurrentTime();
sprintf(str,"Cycle:%08d",MMTimer.Counter);
TextOut(Debugger.hdcMSG,0,0,str,strlen(str));
MMTimer.Counter++;
if (!CPU.RUN) return;
CPU.EXE(4000);
T2=GetCurrentTime();
if ((T2-T1)>=1ms)
{
Intime=true;
DoEvent();
}
else
Intime=false;
}
while (Intime);
}
void DoEvent()
{
MSG msg;
BOOL bRet;
bRet = GetMessage( &msg, NULL, 0, 0 );
if (bRet != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Last edited by skyler on Fri Sep 19, 2003 5:10 pm, edited 1 time in total.
-
schidester
- Posts: 57
- Joined: 04 Sep 2002
- Location: Iowa
Skyler,
Again, I'm only a novice at Windows programming. It seems to me that whether or not you finish your 4000 cycles in 1ms, you want to return to your message loop. Then if you couldn't complete your simulation in 1ms, a timeout message would be waiting for you immediately, wouldn't it?
Are you sure? I'm not sure how it works since I've only used a little MFC, but I'd think that any messages posted to your application would simply be waiting in a queue when you get back to the message loop.
Scott
Again, I'm only a novice at Windows programming. It seems to me that whether or not you finish your 4000 cycles in 1ms, you want to return to your message loop. Then if you couldn't complete your simulation in 1ms, a timeout message would be waiting for you immediately, wouldn't it?
Quote:
If I not to do this,windows will have no chance to translate message.
Scott
Quote:
CMMTimers::CMMTimers()
{
TIMECAPS tc;
if (!Intilizatize)
{
if (TIMERR_NOERROR == ::timeGetDevCaps(&tc,sizeof(TIMECAPS)))
{
timerRes = min(max(tc.wPeriodMin,1),tc.wPeriodMax);
timeBeginPeriod(timerRes);
}
}
}
{
TIMECAPS tc;
if (!Intilizatize)
{
if (TIMERR_NOERROR == ::timeGetDevCaps(&tc,sizeof(TIMECAPS)))
{
timerRes = min(max(tc.wPeriodMin,1),tc.wPeriodMax);
timeBeginPeriod(timerRes);
}
}
}
This is what the above would look like if posted, verbatim, inside a code block:
Code: Select all
CMMTimers::CMMTimers()
{
TIMECAPS tc;
if (!Intilizatize)
{
if (TIMERR_NOERROR == ::timeGetDevCaps(&tc,sizeof(TIMECAPS)))
{
timerRes = min(max(tc.wPeriodMin,1),tc.wPeriodMax);
timeBeginPeriod(timerRes);
}
}
}