我正在尝试实现一个简单的C++ C64模拟器(6510 + SID,也许还有VIC2)。
到目前为止,我们只介绍了CPU的基础知识,所以我能够实现一个可以从内存中读取和执行指令的CPU,完全忽略了在真正的C64中,一些指令需要超过1个周期的事实。
据我所知:-在指令精确模拟中,每条指令都在一个CPU周期内执行-在周期中-精确模拟,一个需要3个周期的操作将被分成3个周期。
做额外的努力使模拟器周期精确有多重要?SID和VIC是否可以在没有SID和VIC的情况下模拟?
第二个问题:如果我创建一个主循环,在其中我调用985249次中央处理器的doCycle方法,SID,VIC来模拟0,985249 the,是否足够?
编辑:不太确定我是否理解正确:
void CPU::emulateCPUCycle(){
cyclesLeft--;
if (cyclesLeft<= 0){
// fetch op-code
uint8_t op = mem->read_byte(reg_pc);
executeInstruction(op);
cyclesLeft= numOfCyclesTable[op]; // contains the required cycle number per instruction
// increase PC
reg_pc++;
}
else
// waste cycle
}发布于 2015-09-18 23:46:18
“做额外的努力使模拟器周期精确有多重要? SID和VIC甚至可以在没有的情况下模拟吗?”
主要问题是你为什么要这样做。如果你的目标是能够以某种方式运行原始的c64游戏,那么你可能需要精确的计时,因为许多老游戏依赖于精确的计时。然而,即使你写了一个周期精确的仿真器,在现代的非实时操作系统(linux,windows,..)上也有可能达到这种程度的精度。实际工作的成本很低。
只需编写一个模拟器,它可以做正确的事情并忽略计时。这本身就是一个很难解决的问题
发布于 2015-09-20 08:33:39
数据手册/文档告诉您在模拟/模拟您计算时钟的指令时,每条指令的时钟数。例如,您需要/使用它来实现周期性中断。我不太了解c64,但是如果有一些基于时间的中断,你可以将其转换为时钟,如果累积的时钟>N,那么你就中断并从计数器中减去N。
通常,根据指令的不同,一些处理器具有不同的执行时间。例如,存储器版本vs寄存器版本可以具有额外的时钟。
我严重怀疑它们是流水线的,所以我严重怀疑是否有任何并行需要担心,只需根据每条指令的芯片文档累积时钟即可。
编辑:
不知道你想在那里做什么。这不是我要说的。通常在这些系统上,你会有一些计时器驱动的事件,例如,你会根据显示刷新(h或v)或出现的一些基于时间的中断来更新显示。对于c64,我假设键盘是由单独的逻辑处理的,然后你会得到某种类型的代码,但比方说手持或街机,主cpu可能也需要以一定的间隔对输入进行采样(可能还需要处理弹跳,但这是中断可能会为你涵盖的另一个主题)。因此,如果您在中断之前到达那里,这是可以的。在像游戏这样的实时系统中,你需要在下一个屏幕刷新之前准备好下一个屏幕,每次刷新。所以如果你很早就到了那里,你只需旋转并等待刷新。因此,你不需要让每条cpu指令都延迟,就好像你又重新运行了几兆赫。从这些用户界面事件开始,看看它是如何工作的。
你通常不想要一个递减计数器,除非你处理负数,这样可以更准确地将分数滚动到下一个时间段。假设每个中断有250个cpu周期,你在252中断,因为最后一条指令是3个时钟。将额外的2保存到下一时间段。减法250,2留在为该中断累加时钟的变量上。
去看看m6502或其他指令集模拟器,用来做这类事情的,有很多。mame是用它们加载的,尽管代码经过了386天的性能调整,我们不再需要像那样过度优化代码,但它就是这样。
来自我为6502编写的静态二进制翻译器
case 0xA1:
printf("case 0x%04X: //%02X %02X %02X\n",opadd,opcode,rom[opadd+1],rom[opadd+2]);
printf(" clockticks+=6;\n");
temp=rom[opadd+1];
printf(" value=0x%02X; value+=X;\n",temp); //this should clip
printf(" temp=ReadMemory(value+1);\n");
printf(" temp<<=8;\n");
printf(" temp|=ReadMemory(value);\n");
printf(" A=ReadMemory(temp);\n");
printf(" ZN=A;\n");
break;然后在别处
if(ticks>250)
{
ticks-=250;
rom[0x2001] ^= 0xff;
if (++nmi_count >= 24)
{
nmi_count = 0;
printf("INT\n");
return(INT_NMI);
}
}这是小行星,我后来弄清楚了特定的rom是如何工作的,以及代码在哪里,它等待中断发生,以允许代码前进到下一帧。中断也被用来采样硬币槽,我并不想模仿,etc...so我能够完全删除中断。
但是如果你在做一个通用的系统仿真器,那么你就不能走捷径。如果你想求出每条指令的平均值,你可以把它称为3个时钟或其他任何东西,而不是试图得到太准确的结果。
在这种情况下,如果分支发生,那么我们添加一个额外的时钟
case 0xF0:
printf("case 0x%04X: //%02X %02X %02X\n",opadd,opcode,rom[opadd+1],rom[opadd+2]);
printf(" clockticks+=2;\n");
temp=rom[opadd+1];
if(temp&0x80) temp-=0x100;
temp2=(opadd+temp+2)&addrmask;
printf(" if(ZN==0)//if zero\n");
printf(" { \n");
printf(" clockticks++; \n");
printf(" //showme(0x%04X,0x%02X);\n",opadd,opcode);
printf(" PCSTART=0x%04X;\n",temp2);
printf(" /**/return;\n");
printf(" //goto L_%04X;\n",temp2);
printf(" } \n");
break;如果你对准确性不是很感兴趣,并且使用平均时钟时间来节省编码或代码空间,那么你可能不会关心中断周期的额外时钟252与250是否足够好来抛出两个额外的时钟。我仍然认为递增计数器更容易,但这只是我…
发布于 2015-09-20 23:28:15
首先,我相信已经有足够多的c64仿真器/仿真器运行良好。https://www.c64-wiki.de/index.php/VICE (德国网站)。也许你会发现这个链接列表很有帮助:https://www.c64-wiki.de/index.php/Portal:Emulatoren (也是德语的,但是在不熟悉德语的情况下也可以使用这些链接;)
接下来,如果你打算学习编程,你可以从头开始。这是一个很好的实践。如果你想要更快的结果,你可以看看其他的开源模拟器,比如一个8位处理器的模拟器,比如avr这里:http://www.nongnu.org/simulavr/。这个模拟器是周期精确的。simulavr的目的是测量中断延迟以及所有其他执行时间测量。它还允许连接到其他硬件,如flashes,LCD,uarts和许多其他东西。您还可以在那里模拟多个内核,它们都以不同的时钟速度/周期时间运行。并且还模拟了附加的中断周期(获取irq向量)。此外,模拟指令流水线的行为,因为在内核流水线的下一周期内将不会看到对prot寄存器的读/写动作。因此,对于小型cpu内核来说,这是一个简单的起点。
关于你的问题:
做额外的努力使模拟器周期精确有多重要?SID和VIC是否可以在没有SID和VIC的情况下模拟?
尽可能精确的模拟是绝对重要的。c64软件通常是手工汇编的东西,以获得正确的计时,以便运行许多狂野的东西。这从扩展端口或软件uarts上的协议的比特碰撞开始。还有很多东西,比如屏幕缓冲区后面的阴影ram,对计算机来说是绝对重要的。
但事实上,我相信所有的工作都已经完成了,可以在网上找到……所以这是一个问题,你想要实现什么?通过制作或使用它来学习。
https://stackoverflow.com/questions/32654678
复制相似问题