Update Clock class for cycle accurate components
This commit is contained in:
@@ -13,6 +13,9 @@ namespace emu {
|
||||
APU::APU(Memory& memory) : memory_(memory) {}
|
||||
|
||||
void APU::Init() {
|
||||
// Set the clock frequency
|
||||
SetFrequency(kApuClockSpeed);
|
||||
|
||||
// Initialize registers
|
||||
// ...
|
||||
}
|
||||
@@ -25,7 +28,7 @@ void APU::Reset() {
|
||||
// ...
|
||||
}
|
||||
|
||||
void APU::Run(int cycles) {
|
||||
void APU::Update() {
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "app/emu/clock.h"
|
||||
#include "app/emu/mem.h"
|
||||
#include "app/emu/spc700.h"
|
||||
|
||||
@@ -10,7 +11,11 @@ namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class APU : public SPC700 {
|
||||
const int kApuClockSpeed = 1024000; // 1.024 MHz
|
||||
const int apuSampleRate = 32000; // 32 KHz
|
||||
const int apuClocksPerSample = 64; // 64 clocks per sample
|
||||
|
||||
class APU : public SPC700, public Clock {
|
||||
public:
|
||||
// Initializes the APU with the necessary resources and dependencies
|
||||
APU(Memory &memory);
|
||||
@@ -20,8 +25,8 @@ class APU : public SPC700 {
|
||||
// Resets the APU to its initial state
|
||||
void Reset();
|
||||
|
||||
// Runs the APU for a specified number of clock cycles
|
||||
void Run(int cycles);
|
||||
// Runs the APU for one frame
|
||||
void Update();
|
||||
|
||||
// Reads a byte from the specified APU register
|
||||
uint8_t ReadRegister(uint16_t address);
|
||||
|
||||
51
src/app/emu/clock.h
Normal file
51
src/app/emu/clock.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef YAZE_APP_EMU_CLOCK_H_
|
||||
#define YAZE_APP_EMU_CLOCK_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class Clock {
|
||||
public:
|
||||
Clock() = default;
|
||||
virtual ~Clock() = default;
|
||||
|
||||
void UpdateCycleCount(double deltaTime) {
|
||||
accumulatedTime += deltaTime;
|
||||
double cycleTime = 1.0 / frequency;
|
||||
|
||||
while (accumulatedTime >= cycleTime) {
|
||||
Cycle();
|
||||
accumulatedTime -= cycleTime;
|
||||
}
|
||||
}
|
||||
|
||||
void Cycle() {
|
||||
cycle++;
|
||||
cycleCount++;
|
||||
}
|
||||
|
||||
void UpdateClock(double delta) {
|
||||
UpdateCycleCount(delta);
|
||||
ResetAccumulatedTime();
|
||||
}
|
||||
|
||||
unsigned long long GetCycleCount() const { return cycleCount; }
|
||||
float GetFrequency() const { return frequency; }
|
||||
void SetFrequency(float new_frequency) { this->frequency = new_frequency; }
|
||||
void ResetAccumulatedTime() { accumulatedTime = 0.0; }
|
||||
|
||||
private:
|
||||
uint64_t cycle = 0; // Current cycle
|
||||
float frequency = 0.0; // Frequency of the clock in Hz
|
||||
unsigned long long cycleCount = 0; // Total number of cycles executed
|
||||
double accumulatedTime = 0.0; // Accumulated time since the last cycle update
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_CLOCK_H_
|
||||
@@ -69,18 +69,15 @@ uint8_t CPU::FetchByteDirectPage(uint8_t operand) {
|
||||
return fetchedByte;
|
||||
}
|
||||
|
||||
void CPU::Run() {
|
||||
while (true) {
|
||||
// Fetch the next opcode from memory at the current program counter
|
||||
uint8_t opcode = memory.ReadByte(PC);
|
||||
void CPU::Update() {
|
||||
auto cycles_to_run = GetCycleCount();
|
||||
|
||||
// Increment the program counter to point to the next instruction
|
||||
PC++;
|
||||
// Execute the calculated number of cycles
|
||||
for (int i = 0; i < cycles_to_run; i++) {
|
||||
// Fetch and execute an instruction
|
||||
ExecuteInstruction(FetchByte());
|
||||
|
||||
// Execute the instruction corresponding to the fetched opcode
|
||||
ExecuteInstruction(opcode);
|
||||
|
||||
// Optionally, handle interrupts or other external events
|
||||
// Handle any interrupts, if necessary
|
||||
HandleInterrupts();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/clock.h"
|
||||
#include "app/emu/log.h"
|
||||
#include "app/emu/mem.h"
|
||||
|
||||
@@ -69,37 +70,15 @@ const std::unordered_map<uint8_t, std::string> opcode_to_mnemonic = {
|
||||
|
||||
};
|
||||
|
||||
class Clock {
|
||||
public:
|
||||
Clock() = default;
|
||||
virtual ~Clock() = default;
|
||||
|
||||
void Cycle() { cycle++; }
|
||||
void SetFrequency(float frequency) { this->frequency = frequency; }
|
||||
float GetFrequency() const { return frequency; }
|
||||
unsigned long long GetCycleCount() const { return cycleCount; }
|
||||
|
||||
private:
|
||||
uint64_t cycle; // Current cycle
|
||||
float frequency; // Frequency of the clock in Hz
|
||||
unsigned long long cycleCount; // Total number of cycles executed
|
||||
};
|
||||
const int kCpuClockSpeed = 21477272; // 21.477272 MHz
|
||||
|
||||
class CPU : public Memory, public Clock, public Loggable {
|
||||
public:
|
||||
explicit CPU(Memory& mem) : memory(mem) {}
|
||||
void Init() { memory.ClearMemory(); }
|
||||
|
||||
uint8_t ReadByte(uint16_t address) const override;
|
||||
uint16_t ReadWord(uint16_t address) const override;
|
||||
uint32_t ReadWordLong(uint16_t address) const override;
|
||||
void WriteByte(uint32_t address, uint8_t value) override;
|
||||
void WriteWord(uint32_t address, uint16_t value) override;
|
||||
void SetMemory(const std::vector<uint8_t>& data) override {
|
||||
memory.SetMemory(data);
|
||||
void Init() {
|
||||
SetFrequency(kCpuClockSpeed);
|
||||
memory.ClearMemory();
|
||||
}
|
||||
int16_t SP() const override { return memory.SP(); }
|
||||
void SetSP(int16_t value) override { memory.SetSP(value); }
|
||||
|
||||
uint8_t FetchByte();
|
||||
uint16_t FetchWord();
|
||||
@@ -109,7 +88,7 @@ class CPU : public Memory, public Clock, public Loggable {
|
||||
|
||||
uint8_t FetchByteDirectPage(uint8_t operand);
|
||||
|
||||
void Run();
|
||||
void Update();
|
||||
void ExecuteInstruction(uint8_t opcode);
|
||||
void HandleInterrupts();
|
||||
|
||||
@@ -1048,6 +1027,17 @@ class CPU : public Memory, public Clock, public Loggable {
|
||||
E = carry;
|
||||
}
|
||||
|
||||
uint8_t ReadByte(uint16_t address) const override;
|
||||
uint16_t ReadWord(uint16_t address) const override;
|
||||
uint32_t ReadWordLong(uint16_t address) const override;
|
||||
void WriteByte(uint32_t address, uint8_t value) override;
|
||||
void WriteWord(uint32_t address, uint16_t value) override;
|
||||
void SetMemory(const std::vector<uint8_t>& data) override {
|
||||
memory.SetMemory(data);
|
||||
}
|
||||
int16_t SP() const override { return memory.SP(); }
|
||||
void SetSP(int16_t value) override { memory.SetSP(value); }
|
||||
|
||||
private:
|
||||
void compare(uint16_t register_value, uint16_t memory_value) {
|
||||
uint16_t result;
|
||||
|
||||
@@ -20,7 +20,7 @@ void PPU::RenderScanline() {
|
||||
// ...
|
||||
}
|
||||
|
||||
void PPU::Run(int cycles) {
|
||||
void PPU::Update() {
|
||||
// Fetch the tile data from VRAM, tile map data from memory, and palette data
|
||||
// from CGRAM
|
||||
UpdateTileData(); // Fetches the tile data from VRAM and stores it in an
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/clock.h"
|
||||
#include "app/emu/mem.h"
|
||||
|
||||
namespace yaze {
|
||||
@@ -585,14 +586,14 @@ struct JoypadRegisters {
|
||||
|
||||
// DMA Registers
|
||||
struct DmaRegisters {
|
||||
uint8_t startDmaTransfer; // Register $420B
|
||||
uint8_t enableHDmaTransfer; // Register $420C
|
||||
uint8_t dmaControlRegister[8]; // Register $43?0
|
||||
uint8_t dmaDestinationAddress[8]; // Register $43?1
|
||||
uint32_t dmaSourceAddress[8]; // Register $43?2/$43?3/$43?4
|
||||
uint16_t bytesToTransfer[8]; // Register $43?5/$43?6/$43?7
|
||||
uint16_t hdmaCountPointer[8]; // Register $43?8/$43?9
|
||||
uint8_t scanlinesLeft[8]; // Register $43?A
|
||||
uint8_t startDmaTransfer; // Register $420B
|
||||
uint8_t enableHDmaTransfer; // Register $420C
|
||||
uint8_t dmacontrol_register_ister[8]; // Register $43?0
|
||||
uint8_t dmaDestinationAddress[8]; // Register $43?1
|
||||
uint32_t dmaSourceAddress[8]; // Register $43?2/$43?3/$43?4
|
||||
uint16_t bytesToTransfer[8]; // Register $43?5/$43?6/$43?7
|
||||
uint16_t hdmaCountPointer[8]; // Register $43?8/$43?9
|
||||
uint8_t scanlinesLeft[8]; // Register $43?A
|
||||
};
|
||||
|
||||
// WRAM access Registers
|
||||
@@ -625,7 +626,9 @@ struct BackgroundLayer {
|
||||
bool enabled; // Whether the background layer is enabled
|
||||
};
|
||||
|
||||
class PPU {
|
||||
const int kPpuClockSpeed = 5369318; // 5.369318 MHz
|
||||
|
||||
class PPU : public Clock {
|
||||
public:
|
||||
// Initializes the PPU with the necessary resources and dependencies
|
||||
PPU(Memory& memory);
|
||||
@@ -633,14 +636,15 @@ class PPU {
|
||||
void Init() {
|
||||
// Initialize the frame buffer with a size that corresponds to the
|
||||
// screen resolution
|
||||
SetFrequency(kPpuClockSpeed);
|
||||
frame_buffer_.resize(256 * 240, 0);
|
||||
}
|
||||
|
||||
// Resets the PPU to its initial state
|
||||
void Reset() { std::fill(frame_buffer_.begin(), frame_buffer_.end(), 0); }
|
||||
|
||||
// Runs the PPU for a specified number of clock cycles
|
||||
void Run(int cycles);
|
||||
// Runs the PPU for one frame.
|
||||
void Update();
|
||||
|
||||
// Reads a byte from the specified PPU register
|
||||
uint8_t ReadRegister(uint16_t address);
|
||||
|
||||
@@ -265,52 +265,37 @@ void SNES::Init(ROM& rom) {
|
||||
void SNES::Run() {
|
||||
running_ = true;
|
||||
|
||||
const int cpuClockSpeed = 21477272; // 21.477272 MHz
|
||||
const int ppuClockSpeed = 5369318; // 5.369318 MHz
|
||||
const int apuClockSpeed = 32000; // 32 KHz
|
||||
const double targetFPS = 60.0; // 60 frames per second
|
||||
const double targetFPS = 60.0; // 60 frames per second
|
||||
|
||||
const double cpuCycleTime = 1.0 / cpuClockSpeed;
|
||||
const double ppuCycleTime = 1.0 / ppuClockSpeed;
|
||||
const double apuCycleTime = 1.0 / apuClockSpeed;
|
||||
const double frameTime = 1.0 / targetFPS;
|
||||
|
||||
double cpuAccumulatedTime = 0.0;
|
||||
double ppuAccumulatedTime = 0.0;
|
||||
double apuAccumulatedTime = 0.0;
|
||||
double frameAccumulatedTime = 0.0;
|
||||
double frame_accumulated_time = 0.0;
|
||||
|
||||
auto lastTime = std::chrono::high_resolution_clock::now();
|
||||
auto last_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
if (running_) {
|
||||
auto currentTime = std::chrono::high_resolution_clock::now();
|
||||
double deltaTime =
|
||||
std::chrono::duration<double>(currentTime - lastTime).count();
|
||||
lastTime = currentTime;
|
||||
auto current_time = std::chrono::high_resolution_clock::now();
|
||||
double delta_time =
|
||||
std::chrono::duration<double>(current_time - last_time).count();
|
||||
last_time = current_time;
|
||||
|
||||
cpuAccumulatedTime += deltaTime;
|
||||
ppuAccumulatedTime += deltaTime;
|
||||
apuAccumulatedTime += deltaTime;
|
||||
frameAccumulatedTime += deltaTime;
|
||||
frame_accumulated_time += delta_time;
|
||||
|
||||
while (cpuAccumulatedTime >= cpuCycleTime) {
|
||||
cpu.ExecuteInstruction(cpu.FetchByte());
|
||||
cpuAccumulatedTime -= cpuCycleTime;
|
||||
}
|
||||
// Update the CPU
|
||||
cpu.UpdateClock(delta_time);
|
||||
cpu.Update();
|
||||
|
||||
while (ppuAccumulatedTime >= ppuCycleTime) {
|
||||
RenderScanline();
|
||||
ppuAccumulatedTime -= ppuCycleTime;
|
||||
}
|
||||
// Update the PPU
|
||||
ppu.UpdateClock(delta_time);
|
||||
ppu.Update();
|
||||
|
||||
while (apuAccumulatedTime >= apuCycleTime) {
|
||||
// apu.Update();
|
||||
apuAccumulatedTime -= apuCycleTime;
|
||||
}
|
||||
// Update the APU
|
||||
apu.UpdateClock(delta_time);
|
||||
apu.Update();
|
||||
|
||||
if (frameAccumulatedTime >= frameTime) {
|
||||
if (frame_accumulated_time >= frameTime) {
|
||||
// renderer.Render();
|
||||
frameAccumulatedTime -= frameTime;
|
||||
frame_accumulated_time -= frameTime;
|
||||
}
|
||||
|
||||
HandleInput();
|
||||
@@ -319,7 +304,7 @@ void SNES::Run() {
|
||||
|
||||
// Enable NMI Interrupts
|
||||
void SNES::EnableVBlankInterrupts() {
|
||||
vBlankFlag = false;
|
||||
v_blank_flag_ = false;
|
||||
|
||||
// Clear the RDNMI VBlank flag
|
||||
memory_.ReadByte(0x4210); // RDNMI
|
||||
@@ -330,10 +315,10 @@ void SNES::EnableVBlankInterrupts() {
|
||||
|
||||
// Wait until the VBlank routine has been processed
|
||||
void SNES::WaitForVBlank() {
|
||||
vBlankFlag = true;
|
||||
v_blank_flag_ = true;
|
||||
|
||||
// Loop until `vBlankFlag` is clear
|
||||
while (vBlankFlag) {
|
||||
// Loop until `v_blank_flag_` is clear
|
||||
while (v_blank_flag_) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
@@ -350,15 +335,15 @@ void SNES::NmiIsr() {
|
||||
cpu.DB = 0x80; // Assuming bank 0x80, can be changed to 0x00
|
||||
cpu.D = 0;
|
||||
|
||||
if (vBlankFlag) {
|
||||
if (v_blank_flag_) {
|
||||
VBlankRoutine();
|
||||
|
||||
// Clear `vBlankFlag`
|
||||
vBlankFlag = false;
|
||||
// Clear `v_blank_flag_`
|
||||
v_blank_flag_ = false;
|
||||
}
|
||||
|
||||
// Increment 32-bit frameCounter
|
||||
frameCounter++;
|
||||
// Increment 32-bit frame_counter_
|
||||
frame_counter_++;
|
||||
|
||||
// Restore CPU registers
|
||||
cpu.PHB();
|
||||
@@ -371,24 +356,6 @@ void SNES::VBlankRoutine() {
|
||||
// ...
|
||||
}
|
||||
|
||||
void SNES::RenderScanline() {
|
||||
// Render background layers
|
||||
for (int layer = 0; layer < 4; layer++) {
|
||||
DrawBackgroundLayer(layer);
|
||||
}
|
||||
|
||||
// Render sprites
|
||||
DrawSprites();
|
||||
}
|
||||
|
||||
void SNES::DrawBackgroundLayer(int layer) {
|
||||
// ...
|
||||
}
|
||||
|
||||
void SNES::DrawSprites() {
|
||||
// ...
|
||||
}
|
||||
|
||||
void SNES::HandleInput() {
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -82,11 +82,6 @@ class SNES : public DMA {
|
||||
// VBlank routine
|
||||
void VBlankRoutine();
|
||||
|
||||
// Functions for PPU-related operations
|
||||
void RenderScanline();
|
||||
void DrawBackgroundLayer(int layer);
|
||||
void DrawSprites();
|
||||
|
||||
// Controller input handling
|
||||
void HandleInput();
|
||||
|
||||
@@ -118,11 +113,11 @@ class SNES : public DMA {
|
||||
std::vector<uint8_t> rom_data;
|
||||
|
||||
// Byte flag to indicate if the VBlank routine should be executed or not
|
||||
std::atomic<bool> vBlankFlag;
|
||||
std::atomic<bool> v_blank_flag_;
|
||||
|
||||
// 32-bit counter to track the number of NMI interrupts (useful for clocks and
|
||||
// timers)
|
||||
std::atomic<uint32_t> frameCounter;
|
||||
std::atomic<uint32_t> frame_counter_;
|
||||
|
||||
// Other private member variables
|
||||
bool running_;
|
||||
|
||||
@@ -11,6 +11,7 @@ void SPC700::ExecuteInstructions(uint8_t opcode) {
|
||||
switch (opcode) {
|
||||
// 8-bit Move Memory to Register
|
||||
case 0xE8: // MOV A, #imm
|
||||
MOV(A, true);
|
||||
break;
|
||||
case 0xE6: // MOV A, (X)
|
||||
break;
|
||||
|
||||
@@ -65,9 +65,9 @@ class SDSP {
|
||||
class SPC700 {
|
||||
AudioRAM aram;
|
||||
SDSP sdsp;
|
||||
uint8_t testReg;
|
||||
uint8_t controlReg;
|
||||
uint8_t dspAddrReg;
|
||||
uint8_t test_register_;
|
||||
uint8_t control_register_;
|
||||
uint8_t dsp_address_register_;
|
||||
|
||||
// Registers
|
||||
uint8_t A; // 8-bit accumulator
|
||||
@@ -95,13 +95,13 @@ class SPC700 {
|
||||
uint8_t read(uint16_t address) {
|
||||
switch (address) {
|
||||
case 0xF0:
|
||||
return testReg;
|
||||
return test_register_;
|
||||
case 0xF1:
|
||||
return controlReg;
|
||||
return control_register_;
|
||||
case 0xF2:
|
||||
return dspAddrReg;
|
||||
return dsp_address_register_;
|
||||
case 0xF3:
|
||||
return sdsp.readGlobalReg(dspAddrReg);
|
||||
return sdsp.readGlobalReg(dsp_address_register_);
|
||||
default:
|
||||
if (address < 0xFFC0) {
|
||||
return aram.read(address);
|
||||
@@ -116,16 +116,16 @@ class SPC700 {
|
||||
void write(uint16_t address, uint8_t value) {
|
||||
switch (address) {
|
||||
case 0xF0:
|
||||
testReg = value;
|
||||
test_register_ = value;
|
||||
break;
|
||||
case 0xF1:
|
||||
controlReg = value;
|
||||
control_register_ = value;
|
||||
break;
|
||||
case 0xF2:
|
||||
dspAddrReg = value;
|
||||
dsp_address_register_ = value;
|
||||
break;
|
||||
case 0xF3:
|
||||
sdsp.writeGlobalReg(dspAddrReg, value);
|
||||
sdsp.writeGlobalReg(dsp_address_register_, value);
|
||||
break;
|
||||
default:
|
||||
if (address < 0xFFC0) {
|
||||
@@ -182,7 +182,7 @@ class SPC700 {
|
||||
|
||||
// Instructions
|
||||
// MOV
|
||||
void MOV(uint8_t operand, bool isImmediate) {
|
||||
void MOV(uint8_t operand, bool isImmediate = false) {
|
||||
uint8_t value = isImmediate ? imm() : operand;
|
||||
operand = value;
|
||||
PSW.Z = (operand == 0);
|
||||
|
||||
Reference in New Issue
Block a user