6510 / C64模拟器中的c++,如何实现周期/时钟

6510 / C64 Simulator in C++, how to realize Cycle / Clock

本文关键字:实现 周期 时钟 何实现 模拟器 C64 6510 c++      更新时间:2023-10-16

我正在尝试实现一个简单的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软件通常是手工制作的汇编程序,以获得正确的时间为许多狂野的东西运行。这始于对扩展端口或软件部件上的协议的敲打。还有很多东西,比如屏幕缓冲区后面的影子内存,对那台电脑来说是非常重要的。

但事实上,我相信所有的工作都已经完成了,并且可以在网上找到……所以问题是你想要实现什么?通过制作和使用来学习。