Очередной эмулятор Nes. Процессор
{
* 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;
Конечный код будет изменён, потому смотрите исходники проекта.