Dzień dobry,
Uczę się programowania i w ramach nauki praktycznie skończyłam pisanie symulatora mikroprocesora 6502. Mam kilka pytań odnośnie kodu.
- Klasa
Cpu
ma prawie 1k linii kodu czy to nie za dużo? Chciałam to jakoś rozbić ale nie wiem jak się za to zabrać praktycznie każdy tryb adresowania potrzebuje dostępu do pamięci jak również rejestrów procesora, więc musiałabym stworzyć nową klasęRegisters
przenieść tam rejestry i następnie przekazywać każdej z 13 metod zarównoRegisters
jak iMemory
, ten sam problem pojawia się w przypadku operacji których jest 56. Z drugiej strony czy w ogóle powinno się rozbijać Cpu? Wydaje mi się, że tryby adresowania jak i operacje są integralną częścią Cpu ale trochę przeraża mnie klasa z 1000 linii kody i czy nie łamię SRP - Czy zwracanie
AddressModeResult
,OperationResult
jest dobrym pomysłem czy może powinnam utworzyć propertypublic ushort AbsoluteAddress { get; private set; }
i na przykład w metodzieAddressMode_ABS
zamiast zwracać wynik ustawić poleAbsoluteAddress = address;
i nic nie zwracać? - Praktycznie w każdym trybie adresowania powtarzają mi się linie pobierające byte z pamięci i przekształcenie little endian na big endian czy to łamie zasadę DRY i czy powinnam wydzielić jakąś/jakieś metody do obsługi tego?
- Czy funkcje AddressMode_XXX i Operation_XXX powinny być prywatne a jako publiczne tylko zrobić np.
Reset()
,IRQ()
,NMI()
,Run()
tylko jak wtedy testować prywatne metody czy powinno je się testować osobno?
Fragmenty programu
public class Cpu
{
private readonly Instruction[] _instructions = new Instruction[256];
private readonly Memory _memory;
// registers
public byte A { get; set; }
public byte X { get; set; }
public byte Y { get; set; }
public byte SP { get; set; }
public ushort PC { get; set; }
// flags
public bool C { get; set; }
public bool Z { get; set; }
public bool I { get; set; }
public bool D { get; set; }
public bool B { get; set; }
public bool R { get; set; }
public bool V { get; set; }
public bool N { get; set; }
// ...
public void Run() { } // start executing
public void IRQ() { } // interruption
public void NMI() { } // interruption
public void Reset() { } // reset cpu
// ...
// addressing modes (13)
public AddressModeResult AddressMode_ABS()
{
var lo = _memory.ReadByte(PC++);
var hi = _memory.ReadByte(PC++);
var address = (ushort)((hi << 8) | lo);
return new AddressModeResult(address);
}
public AddressModeResult AddressMode_ABX()
{
var lo = _memory.ReadByte(PC++);
var hi = _memory.ReadByte(PC++);
var address = (ushort)(((hi << 8) | lo) + X);
return (address & 0xFF00) == hi << 8
? new AddressModeResult(address)
: new AddressModeResult(address, true);
}
public AddressModeResult AddressMode_ABY()
{
var lo = _memory.ReadByte(PC++);
var hi = _memory.ReadByte(PC++);
var address = (ushort)(((hi << 8) | lo) + Y);
return (address & 0xFF00) == hi << 8
? new AddressModeResult(address)
: new AddressModeResult(address, true);
}
// ...
// operations (56)
public OperationResult Operation_LDA(ushort address)
{
A = Fetch(address);
Z = A == 0x00;
N = (A & 0x80) != 0;
return new OperationResult(true, 1);
}
public OperationResult Operation_LDX(ushort address)
{
X = Fetch(address);
Z = X == 0x00;
N = (X & 0x80) != 0;
return new OperationResult(true, 1);
}
public OperationResult Operation_LDY(ushort address)
{
Y = Fetch(address);
Z = Y == 0x00;
N = (Y & 0x80) != 0;
return new OperationResult(true, 1);
}
}
// analogicznie OperationResult
public class AddressModeResult
{
public ushort Address { get; }
public bool AdditionalCycle { get; }
public AddressModeResult(ushort address = 0x0000, bool additionalCycle = false)
{
Address = address;
AdditionalCycle = additionalCycle;
}
}
- Podczas pisania posta nasunął mi się pomysł zrobienia czegoś takiego ale czy to nie będzie specjalne komplikowanie overengineering?
public interface IAddressMode
{
AddressModeResult Calculate();
}
public class AbsoluteAddressMode : IAddressMode
{
public AbsoluteAddressMode(Register register, Memory memory)
{
// ...
}
public AddressModeResult Calculate()
{
// ...
return new AddressModeResult();
}
}
public interface IOperation
{
OperationResult Run();
}
public class Operation_LDA : IOperation
{
public Operation_LDA(Register register, Memory memory)
{
// ...
}
public OperationResult Run()
{
// ...
return OperationResult();
}
}
public class InstructionFactory
{
public InstructionFactory(Registers registers, Memory memory)
{
// ...
}
public Instruction GetInstruction(byte opcode)
{
return opcode switch
{
0xA4 => new Instruction(new AddressModeZeroPage(_registers, _memory), new Operation_LDA(_register, _memory)),
// ...
_ => throw new InvalidOperationException();
}
}
}
public class Cpu
{
private Memory _memory;
private Registers _registers;
private InstructionFactory _factory;
public void Run()
{
var opcode = _memory.ReadByte(_registers.PC++);
var instruction = _factory.getInstruction(opcode);
instruction.Execute();
}
}