6510 / C64模拟器中的c++,如何实现周期/时钟
6510 / C64 Simulator in C++, how to realize Cycle / Clock
我正在尝试实现一个简单的c++ C64模拟器(6510 + SID和VIC2)。
到目前为止,我们只介绍了CPU的基础知识,所以我能够实现一个可以从内存中读取和执行指令的CPU,完全忽略了在真正的C64中,一些指令需要超过1个周期的事实。据我所知:—在指令精确仿真中,每条指令在一个CPU周期内执行-在周期精确模拟中,需要3个周期的操作将被拆分为3个周期。
做额外的努力使模拟器周期精确有多重要?没有SID和VIC可以模拟吗?
第二个问题:如果我创建一个主循环,其中我调用985249次CPU, SID, VIC的doccycle方法来模拟0,985249mhz,是否足够?
编辑:不太确定我是否理解正确:
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
}
"做额外的努力使模拟器周期精确有多重要?"没有SID和VIC可以模拟吗?"
主要问题是你为什么要这样做。如果你的目标是能够运行原始的c64游戏,那么你可能需要精确计时,因为许多老游戏都依赖于精确计时。然而,即使你编写了一个周期精确模拟器,在现代非实时操作系统(linux, windows,…)上获得这种精度的机会也很低。
只要写一个模拟器,做正确的事情,忽略计时。这个问题本身就已经够难的了
数据表/文档告诉您在模拟/模拟指令时每个指令有多少个时钟。例如,您需要/使用它来实现周期性中断。我不太了解c64,但如果例如有一些基于时间的中断,你将其转换为时钟,如果累积时钟> N,那么你中断并从计数器中减去N。
通常,一些处理器根据指令的变化有不同的执行时间。内存版本和寄存器版本可能有一个额外的时钟,例如。
我很怀疑这些是流水线的,所以我很怀疑有什么并行的东西需要担心,只是根据每条指令的芯片文档积累时钟。
编辑:不知道你想在那里做什么。我说的不是这个。通常在这些系统上,你会有一些定时器驱动的事件,例如,你会根据显示刷新(h或v)或一些基于时间的中断来更新显示。对于c64,我假设键盘是由单独的逻辑处理的,然后你得到了某种类型的代码,但如果是手持或街机,主cpu可能需要在一定间隔内采样输入(可能还需要处理弹跳,但这是中断可能为你提供的另一个主题)。所以如果你在中断之前到达那里是可以的。在像游戏这样的实时系统中,你需要在每次刷新前准备好下一个屏幕。所以如果你很早到达那里,你只是旋转,等待刷新。所以你不需要让每条cpu指令延迟,就好像你回到了几兆赫兹。从这些用户界面事件开始,看看它们是如何工作的。
你通常不需要一个向下的计数器,除非你处理负数,使它更准确地滚动到下一个时间段的分数。假设每个中断是250个CPU周期,并且您在252上中断,因为最后一条指令是3个时钟。把多余的2美元留到下一个时间段。减去250,则在为该中断累积时钟的变量上还剩2。
去看看m6502或其他指令集模拟器用来做这种事情,有很多。Mame装载了它们,尽管这些代码从386天开始进行了性能调优,但我们不再需要像那样过度优化代码,但它就是这样。
来自我为6502编写的静态二进制转换器
case 0xA1:
printf("case 0x%04X: //%02X %02X %02Xn",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("INTn");
return(INT_NMI);
}
}
我后来弄清楚了特定的rom是如何工作的,以及等待中断发生以允许代码前进到下一帧的代码在哪里。中断也用于采样硬币槽,我不关心模拟,等等…所以我能够完全消除中断。但是如果你正在制作一个通用的系统模拟器,那么你就不能走这些捷径。如果你想平均每条指令,你可以把它叫做3个时钟或其他什么,而不是试图得到太精确。
在本例中,如果发生分支,则添加一个额外的时钟
case 0xF0:
printf("case 0x%04X: //%02X %02X %02Xn",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 zeron");
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足够好,可以扔掉两个额外的时钟。
首先,我相信已经有足够的c64模拟器/模拟器工作良好。https://www.c64-wiki.de/index.php/VICE(德国网站)。也许你会发现链接列表很有帮助:https://www.c64-wiki.de/index.php/Portal:Emulatoren(也是德语,但不懂德语也可以点击链接;))
接下来你可以从头开始,如果你的目的是学习编程。这是一个很好的练习。如果您想要快速的结果,您可以查看其他开源模拟器,例如avr之类的8位cpu模拟器:http://www.nongnu.org/simulavr/。这个模拟器是周期精确的。simulavr的目的是测量中断延迟以及所有其他执行时间测量。它还允许连接其他硬件,如闪光灯、LCD、显示器和许多其他东西。您还可以在那里模拟多个内核,它们都以不同的时钟速度/周期时间运行。并且还模拟了额外的中断周期(获取irq向量)。此外,指令管道的行为是模拟的,因为在prot寄存器上的读/写操作将不会在下一个周期内看到,因为内核的管道化。因此,对于小型cpu内核来说,这是一个简单的起点。
对于你的问题:
做额外的努力使模拟器周期精确有多重要?没有SID和VIC可以模拟吗?
尽可能精确地模拟是绝对重要的。c64软件通常是手工制作的汇编程序,以获得正确的时间为许多狂野的东西运行。这始于对扩展端口或软件部件上的协议的敲打。还有很多东西,比如屏幕缓冲区后面的影子内存,对那台电脑来说是非常重要的。
但事实上,我相信所有的工作都已经完成了,并且可以在网上找到……所以问题是你想要实现什么?通过制作和使用来学习。
- 如果没有malloc,链表实现将失败
- 如何在c++中实现处理器调度模拟器
- 如何在c++中使用引用实现类似python的行为
- 实现无开销push_back的最佳方法是什么
- 使用简单类型列表实现的指数编译时间.为什么
- 如何在BST的这个简单递归实现中消除警告
- 实现一个在集合上迭代的模板函数
- 我应该实现右值推送功能吗?我应该使用std::move吗
- 如何正确实现和访问运算符的各种自定义枚举器
- C++Union/Struct位域的实现和可移植性
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 在c++中实现LinkedList时,应出现未处理的错误
- 为左值和右值的包装器实现C++范围
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- 使用GSoap实现ONVIF
- 在用于格式4的arm模拟器中实现功能时的一个问题
- 用于AVX的ln(x)的实现,m256
- C++中无向图算法中查找周期的实现.
- 6510 / C64模拟器中的c++,如何实现周期/时钟
- 使用boost::interprocess::shared_ptr实现跨进程共享生命周期