Очередной эмулятор Nes. Процессор

ca1f445e2aa3293002f6119353724e8d.png
{
 *  Copyright (c) 2024 SSW
 *
 *  This software is provided 'as-is', without any express or
 *  implied warranty. In no event will the authors be held
 *  liable for any damages arising from the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute
 *  it freely, subject to the following restrictions:
 *
 *  1. The origin of this software must not be misrepresented;
 *     you must not claim that you wrote the original software.
 *     If you use this software in a product, an acknowledgment
 *     in the product documentation would be appreciated but
 *     is not required.
 *
 *  2. Altered source versions must be plainly marked as such,
 *     and must not be misrepresented as being the original software.
 *
 *  3. This notice may not be removed or altered from any
 *     source distribution.
}

const
  // флаги для регистра "P"
  f_N   = $80;                    // нечётность
  f_V   = $40;                    // переполнение
  f_nop = $20;                    // резерв
  f_B   = $10;                    // прерывание (BRK)
  f_D   = $08;                    // десятичный режим (не работает на Dendy)
  f_I   = $04;                    // прерывание
  f_Z   = $02;                    // нуль
  f_C   = $01;                    // перенос
  clear_C    = $FF - f_C;
  clear_Z    = $FF - f_Z;
  clear_I    = $FF - f_I;
  clear_V    = $FF - f_V;
  clear_D    = $FF - f_D;
  clear_N    = $FF - f_N;
  clear_B    = $FF - f_B;
  clear_nop  = $FF - f_nop;
  clear_VNZC = $FF - f_V - f_N - f_Z - f_C;
  clear_NZ   = $FF - f_N - f_Z;
  clear_NZC  = $FF - f_N - f_Z - f_C;
  clear_VNZ  = $FF - f_V - f_N - f_Z; 
  // мнемоники, здесь у меня длинный список всех операций, в
  // развёрнутом виде. Изначально приходится делать так, по той
  // причине, что мы не можем видеть какие операции будут выполняться
  // одинаково. И только в процессе реализации инструкций сможем
  // точно определиться какие инструкции можно объеденить.
  m_ADC_IMM = $69;
  m_ADC_ZP  = $65;
  m_ADC_ZPX = $75;
  m_ADC_ABS = $6D;
  m_ADC_ABX = $7D;
  m_ADC_ABY = $79;
  m_ADC_NDX = $61;
  m_ADC_NDY = $71;
    m_AND_IMM = $29;
    m_AND_ZP  = $25;
    m_AND_ZPX = $35;
  ...
  // инстукции
  instructionLen: array[0..255] of byte  = (2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 2, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, 3, 2, 3, 3, 3,
                                            3, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, 3, 2, 3, 3, 3,
                                            1, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, 3, 2, 3, 3, 3,
                                            1, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, 3, 2, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, 3, 2, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3,
                                            2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, 3, 2, 3, 3, 3);
  instructionTime: array[0..255] of byte = (7, 6, 0, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6,
                                            2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
                                            6, 6, 0, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6,
                                            2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
                                            6, 6, 0, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6,
                                            2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
                                            6, 6, 0, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6,
                                            2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
                                            2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
                                            2, 6, 0, 6, 4, 3, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5,
                                            2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
                                            2, 5, 0, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4,
                                            2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
                                            2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
                                            2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
                                            2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7);
  // Z и N - флаги
  ZNTables: array [0..255] of Byte       = (f_Z, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,       // это решение я увидел в "nes9x"
                                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                            f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N,
                                            f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N,
                                            f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N,
                                            f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N,
                                            f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N,
                                            f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N,
                                            f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N,
                                            f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N, f_N); 

type
  // структура процессора. Она будет состоять не только из регистров,
  // но и так же из дополнительных данных.
  TNesCPU = record
    regPC: LongWord;             // на самом деле 16-ти бытный должен быть, но с 32-х битными данными в новых процессорах работать проще.
                                 // Потому обязательно делать проверку на выход за пределы 16-ти байтного значения.
    regA, regX, regY: LongWord;
    regP, regS: LongWord;        // это флаги, так будет проще эмулировать.
                                 // для regS будем использовать смещённое значение? Если брать байт от этого значения, то получим нужное число.
    start: Boolean;              // этот флаг только програмно можно выключить! Реализация отключения питания.
    cicles: Integer;             // это "остаточные" такты. Если cicles <> 0 то надо пропускать обработку.

    reg01, reg02, reg03: LongWord;   // дополнительные внутренние регистры и флаги (созданы для того, чтоб не создавать лишних переменных).
    reg04, reg05: LongWord;
    _flag, addCicle: Boolean;
    // регистр P = (N, V, nop, B, D, I, Z, C)
    // где N - флаг знака, V - флаг переполнения, B - программное
    //   прерывание (BRK), D - десятичный режим (в Dendy не работает),
    //   I - прерывания, Z - нуль, C - перенос.
  end;

var
  // вся память, $0000-$07FF - RAM, $0800-$FFFF - ROM
  NesMemory: array[0..$FFFF] of Byte;
  // и сам процессор
  NesCPU: TNesCPU;

// очень часто используемый код, для установки флагов "Z" и "N".
// но впоследствии заменено на код
// (regP := regP or ZNTables[byte(regA)];)
procedure SetFlagsNegZero(reg: byte);
begin
  with NesCPU do
  begin
    if reg = 0 then
      regP := (regP or f_Z) and clear_N
    else
      if reg >= $80 then
        regP := (regP or f_N) and clear_Z
      else
        regP := regP and clear_Z and clear_N;
  end;
end;

// это всё относится к ADC, похоже к любой версии.
procedure inst_ADC;
begin
  with NesCPU do
  begin
    reg03 := regA + reg02 + regP and f_C;
    regP := regP and clear_VNZC;
    _flag := (not (regA xor reg02) and (regA xor reg03) and $80) <> 0;
    if _flag then
      regP := regP or f_V;
    if reg04 > $FF then
    begin
      regP := regP or f_C;
      regA := reg03 and $FF;
    end;
    regP := regP or ZNTables[byte(regA)];
  end;
end;

// то всё относится к ASL, кроме аккумулятора
procedure inst_ASL(mem: LongWord);
begin
  with NesCPU do
  begin
    reg03 := NesMemory[mem];
    regP := regP and clear_NZC;
    if reg03 >= $80 then
      regP := regP or f_C;
    reg03 := (reg03 shl 1) and $FF;
    SetFlagsNegZero(byte(reg03));
    NesMemory[mem] := reg03;
  end;
end;

// восстановление "PC" из стека.
procedure PopPC;
begin
  with NesCPU do
  begin
    inc(regS);
    if regS > $1FF then
      regS := $1FF;
    inc(regS);
    regPC := NesMemory[regS];
    if regs > $1FF then
      regS := $1FF;
    regPC := regPC or (NesMemory[regS] shl 8);
  end;
end;

// на данный момент, у меня это не процедура, а точка входа в
// программу. Но для правильной эмуляции надо делать именно
// процедуру обработки циклов процессора.
procedure TimeCPU;
begin
  with NesCPU do
  begin
    // это типо основной цикл. Но на самом деле не так.
    // Основной цикл - это тактовый генератор, который посылает
    // сигналы синхроимпульсов.
    if start then
    begin
      if cicles = 0 then
      begin
        // надо прочитать данные и работать с ними.
        reg01 := NesMemory[regPC];
        cicles := Instruction[reg01].time;   // ииии.... дополнительное время...
        // надо отметить, находятся ли данные значения на одной
        // странице. Многие инструкции требуют дополнительный байт,
        // если команда переходит с одной страницы на другую.
        // Не реализовано! (но возможно будет реализовано, когда
        // закончу статью)
        inc(regPC);       // это не правильно, но пока не будем заморачиваться.
        case reg01 of
          0..7, 9, 11, 12, 16..23, 26, 28, 33..39, 41, 43, 48..55, 58, 60, 65..71, 73, 75, 80..87, 90, 92, 97..103, 105, 107, 112..119, 122, 124, 128..135, 137, 139,
          144..151, 160..167, 169, 171, 176..183, 192..199, 201, 203, 208..215, 218, 220, 224..231, 233, 235, 240..247, 250, 252:
            begin
              // все двухбайтовые значения
              case reg01 of
                $02, $12, $22, $32, $42, $52, $62, $72, $92, $B2, $D2, $F2: begin
                  // это "убийство" программы, больше работать программа не должна, пока не придёт полный сброс?
                end;
                $04, $0C, $14, $1A, $1C, $34, $3A, $3C, $44, $54, $5A, $5C, $64, $74, $7A, $7C, $80, $82, $89, $C2, $D4, $DA, $DC, $E2, $F4, $FA, $FC: begin
                  // это чёртова туча nop-ов...
                end;
                else begin
                  if (regPC mod 256) = 0 then
                    addCicle := true
                  else
                    addCicle := False;
                  // считываем второе значение из памяти
                  reg02 := NesMemory[regPC];
                  inc(regPC);
                  case reg01 of
                    m_ADC_IMM: begin
                      // сразу складываем значения регистра и данных идущих следом.
                      inst_ADC;
                    end;
                    m_ADC_ZP: begin
                      // второй раз читаем из памяти, из нулевой страницы
                      reg02 := NesMemory[reg02];
                      inst_ADC;
                    end;
                    m_ADC_ZPX: begin
                      // второй раз читаем со смещением из нулевой страницы
                      reg02 := NesMemory[reg02 + regX];
                      inst_ADC;
                    end;
                    m_ADC_NDX: begin
                      reg02 := (reg02 + regX) and $FF;
                      // читаем два значения для вычисления адреса
                      reg03 := NesMemory[reg02 + 1];
                      reg02 := NesMemory[reg02];
                      // и читаем с адреса
                      reg02 := NesMemory[(reg03 shl 8) or reg02];
                      inst_ADC;
                    end;
                    m_ADC_NDY: begin
                      reg03 := NesMemory[reg02 + 1];
                      reg02 := NesMemory[reg02];
                      reg02 := NesMemory[(reg03 shl 8) or reg02 + regY];
                      inst_ADC;
                      // команда на один цикл больше, если был переход между страницами
                      if addCicle then
                        inc(cicles);
                    end;
                    m_AND_IMM: begin
                      regA := regA and reg02;
                      regP := regP and clear_NZ;
                      SetFlagsNegZero(byte(regA));
                    end;
                    m_AND_ZP: begin
                      reg02 := NesMemory[reg02];
                      regA := regA and reg02;
                      regP := regP and clear_NZ;
                      SetFlagsNegZero(byte(regA));
                    end;
                    m_AND_ZPX: begin
                      reg02 := NesMemory[reg02 + regX];
                      regA := regA and reg02;
                      regP := regP and clear_NZ;
                      SetFlagsNegZero(byte(regA));
                    end;
                    m_AND_NDX: begin
                      reg02 := (reg02 + regX) and $FF;
                      reg03 := NesMemory[reg02 + 1];
                      reg02 := NesMemory[reg02];
                      reg02 := NesMemory[(reg03 shl 8) or reg02];
                      regA := regA and reg02;
                      regP := regP and clear_NZ;
                      SetFlagsNegZero(byte(regA));
                    end;
                    m_AND_NDY: begin
                      reg03 := NesMemory[reg02 + 1];
                      reg02 := NesMemory[reg02];
                      reg02 := NesMemory[(reg03 shl 8) or reg02 + regY];
                      regA := regA and reg02;
                      regP := regP and clear_NZ;
                      SetFlagsNegZero(byte(regA));
                    end;
                    m_ASL_ZP: begin
                      inst_ASL(reg02);
                    end;
                    m_ASL_ZPX: begin
                      inst_ASL(reg02 + regX);
                    end;

                    ...
                    ...
                    ...

                    m_SBC_IMM_EB: begin

                    end;
                    m_AHX_NDY: begin

                    end;
                  end;
                end;
              end;
            end;
          13..15, 25, 27, 29..32, 44..47, 57, 59, 61..63, 76..79, 89, 91, 93..95, 108..111, 121, 123, 125..127, 140..143, 153, 155..159, 172..175, 185, 187..191, 204..207,      // 74
          217, 219, 221..223, 236..239, 249, 251, 253..255:
            begin
              // все трёхбайтовые значения
              if ((regPC mod 256) = 0) or (((regPC + 1) mod 256) = 0) then
                addCicle := true
              else
                addCicle := False;
              // считываем второе значение из памяти
              reg02 := NesMemory[regPC];
              inc(regPC);
              // считываем третье значение из памяти
              reg03 := NesMemory[regPC];
              inc(regPC);
              case reg01 of
                m_ADC_ABS: begin
                  reg05 := NesMemory[reg03 shl 8 or reg02];
                  reg04 := regA + reg05 + Byte(regP and f_C);
                  _flag := (not (regA xor reg05) and (regA xor reg04) and $80) <> 0;
                  if _flag then
                    regP := regP or f_V
                  else
                    regP := regP and clear_V;
                  regA := Byte(reg04);
                  if reg04 > 255 then
                    regP := regP or f_C
                  else
                    regP := regP and clear_C;
                end;
                m_ADC_ABS: begin
                  // читаем из заданной памяти
                  reg02 := NesMemory[(reg03 shl 8) or reg02];
                  // и после этого складываем
                  inst_ADC;
                end;
                m_ADC_ABX: begin
                  // тут учитываем смещение за счёт регистра
                  reg02 := NesMemory[(reg03 shl 8) or reg02 + regX];
                  inst_ADC;
                  if addCicle then
                    inc(cicles);
                end;
                m_ADC_ABY: begin
                  // тут учитываем смещение за счёт регистра
                  reg02 := NesMemory[(reg03 shl 8) or reg02 + regY];
                  inst_ADC;
                  if addCicle then
                    inc(cicles);
                end;
                m_AND_ABS: begin
                  reg02 := NesMemory[(reg03 shl 8) or reg02];
                  regA := regA and reg02;
                  regP := regP and clear_NZ;
                  SetFlagsNegZero(byte(regA));
                end;
                m_AND_ABX: begin
                  reg02 := NesMemory[(reg03 shl 8) or reg02 + regX];
                  regA := regA and reg02;
                  regP := regP and clear_NZ;
                  SetFlagsNegZero(byte(regA));
                  // команда на один цикл больше, если был переход между страницами
                  if addCicle then
                    inc(cicles);
                end;
                m_AND_ABY: begin
                  reg02 := NesMemory[(reg03 shl 8) or reg02 + regY];
                  regA := regA and reg02;
                  regP := regP and clear_NZ;
                  SetFlagsNegZero(byte(regA));
                  if addCicle then
                    inc(cicles);
                end;
                m_ASL_ABS: begin
                  inst_ASL((reg03 shl 8) or reg02);
                end;
                m_ASL_ABX: begin
                  inst_ASL((reg03 shl 8) or reg02 + regX);
                end;

                ...
                ...
                ...

                m_TAS: begin

                end;
                m_LAS: begin

                end;
              end;
            end;
          else begin                                   // 28?
            // все однобайтовые значения
            case reg01 of
              m_ASL_ACC: begin
                if (regA and $80) > 0 then
                  regP := regP or f_C;
                regA := regA shl 1;
                SetFlagsNegZero(regA);
              end;
              m_CLC: begin
                regP := regP and clear_C;
              end;
              m_CLD: begin
                regP := regP and clear_D;
              end;

              ...
              ...
              ...

              m_TXS: begin
                regS := regX;
              end;
              m_TYA: begin
                regA := regY;
                regP := regP and clear_NZ;
                SetFlagsNegZero(regA);
              end;
            end;
          end;
        end;
      end;
      cicles := cicles - 1;
    end;
  end;  
end;

Конечный код будет изменён, потому смотрите исходники проекта.

© Habrahabr.ru