Update Clock class for cycle accurate components

This commit is contained in:
scawful
2023-08-20 22:30:06 -04:00
parent 2f6c6d414c
commit 2ec43cfb3f
11 changed files with 146 additions and 133 deletions

View File

@@ -13,6 +13,9 @@ namespace emu {
APU::APU(Memory& memory) : memory_(memory) {} APU::APU(Memory& memory) : memory_(memory) {}
void APU::Init() { void APU::Init() {
// Set the clock frequency
SetFrequency(kApuClockSpeed);
// Initialize registers // Initialize registers
// ... // ...
} }
@@ -25,7 +28,7 @@ void APU::Reset() {
// ... // ...
} }
void APU::Run(int cycles) { void APU::Update() {
// ... // ...
} }

View File

@@ -3,6 +3,7 @@
#include <cstdint> #include <cstdint>
#include "app/emu/clock.h"
#include "app/emu/mem.h" #include "app/emu/mem.h"
#include "app/emu/spc700.h" #include "app/emu/spc700.h"
@@ -10,7 +11,11 @@ namespace yaze {
namespace app { namespace app {
namespace emu { 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: public:
// Initializes the APU with the necessary resources and dependencies // Initializes the APU with the necessary resources and dependencies
APU(Memory &memory); APU(Memory &memory);
@@ -20,8 +25,8 @@ class APU : public SPC700 {
// Resets the APU to its initial state // Resets the APU to its initial state
void Reset(); void Reset();
// Runs the APU for a specified number of clock cycles // Runs the APU for one frame
void Run(int cycles); void Update();
// Reads a byte from the specified APU register // Reads a byte from the specified APU register
uint8_t ReadRegister(uint16_t address); uint8_t ReadRegister(uint16_t address);

51
src/app/emu/clock.h Normal file
View 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_

View File

@@ -69,18 +69,15 @@ uint8_t CPU::FetchByteDirectPage(uint8_t operand) {
return fetchedByte; return fetchedByte;
} }
void CPU::Run() { void CPU::Update() {
while (true) { auto cycles_to_run = GetCycleCount();
// Fetch the next opcode from memory at the current program counter
uint8_t opcode = memory.ReadByte(PC);
// Increment the program counter to point to the next instruction // Execute the calculated number of cycles
PC++; for (int i = 0; i < cycles_to_run; i++) {
// Fetch and execute an instruction
ExecuteInstruction(FetchByte());
// Execute the instruction corresponding to the fetched opcode // Handle any interrupts, if necessary
ExecuteInstruction(opcode);
// Optionally, handle interrupts or other external events
HandleInterrupts(); HandleInterrupts();
} }
} }

View File

@@ -6,6 +6,7 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "app/emu/clock.h"
#include "app/emu/log.h" #include "app/emu/log.h"
#include "app/emu/mem.h" #include "app/emu/mem.h"
@@ -69,37 +70,15 @@ const std::unordered_map<uint8_t, std::string> opcode_to_mnemonic = {
}; };
class Clock { const int kCpuClockSpeed = 21477272; // 21.477272 MHz
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
};
class CPU : public Memory, public Clock, public Loggable { class CPU : public Memory, public Clock, public Loggable {
public: public:
explicit CPU(Memory& mem) : memory(mem) {} explicit CPU(Memory& mem) : memory(mem) {}
void Init() { memory.ClearMemory(); } void Init() {
SetFrequency(kCpuClockSpeed);
uint8_t ReadByte(uint16_t address) const override; memory.ClearMemory();
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); }
uint8_t FetchByte(); uint8_t FetchByte();
uint16_t FetchWord(); uint16_t FetchWord();
@@ -109,7 +88,7 @@ class CPU : public Memory, public Clock, public Loggable {
uint8_t FetchByteDirectPage(uint8_t operand); uint8_t FetchByteDirectPage(uint8_t operand);
void Run(); void Update();
void ExecuteInstruction(uint8_t opcode); void ExecuteInstruction(uint8_t opcode);
void HandleInterrupts(); void HandleInterrupts();
@@ -1048,6 +1027,17 @@ class CPU : public Memory, public Clock, public Loggable {
E = carry; 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: private:
void compare(uint16_t register_value, uint16_t memory_value) { void compare(uint16_t register_value, uint16_t memory_value) {
uint16_t result; uint16_t result;

View File

@@ -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 // Fetch the tile data from VRAM, tile map data from memory, and palette data
// from CGRAM // from CGRAM
UpdateTileData(); // Fetches the tile data from VRAM and stores it in an UpdateTileData(); // Fetches the tile data from VRAM and stores it in an

View File

@@ -5,6 +5,7 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include "app/emu/clock.h"
#include "app/emu/mem.h" #include "app/emu/mem.h"
namespace yaze { namespace yaze {
@@ -585,14 +586,14 @@ struct JoypadRegisters {
// DMA Registers // DMA Registers
struct DmaRegisters { struct DmaRegisters {
uint8_t startDmaTransfer; // Register $420B uint8_t startDmaTransfer; // Register $420B
uint8_t enableHDmaTransfer; // Register $420C uint8_t enableHDmaTransfer; // Register $420C
uint8_t dmaControlRegister[8]; // Register $43?0 uint8_t dmacontrol_register_ister[8]; // Register $43?0
uint8_t dmaDestinationAddress[8]; // Register $43?1 uint8_t dmaDestinationAddress[8]; // Register $43?1
uint32_t dmaSourceAddress[8]; // Register $43?2/$43?3/$43?4 uint32_t dmaSourceAddress[8]; // Register $43?2/$43?3/$43?4
uint16_t bytesToTransfer[8]; // Register $43?5/$43?6/$43?7 uint16_t bytesToTransfer[8]; // Register $43?5/$43?6/$43?7
uint16_t hdmaCountPointer[8]; // Register $43?8/$43?9 uint16_t hdmaCountPointer[8]; // Register $43?8/$43?9
uint8_t scanlinesLeft[8]; // Register $43?A uint8_t scanlinesLeft[8]; // Register $43?A
}; };
// WRAM access Registers // WRAM access Registers
@@ -625,7 +626,9 @@ struct BackgroundLayer {
bool enabled; // Whether the background layer is enabled bool enabled; // Whether the background layer is enabled
}; };
class PPU { const int kPpuClockSpeed = 5369318; // 5.369318 MHz
class PPU : public Clock {
public: public:
// Initializes the PPU with the necessary resources and dependencies // Initializes the PPU with the necessary resources and dependencies
PPU(Memory& memory); PPU(Memory& memory);
@@ -633,14 +636,15 @@ class PPU {
void Init() { void Init() {
// Initialize the frame buffer with a size that corresponds to the // Initialize the frame buffer with a size that corresponds to the
// screen resolution // screen resolution
SetFrequency(kPpuClockSpeed);
frame_buffer_.resize(256 * 240, 0); frame_buffer_.resize(256 * 240, 0);
} }
// Resets the PPU to its initial state // Resets the PPU to its initial state
void Reset() { std::fill(frame_buffer_.begin(), frame_buffer_.end(), 0); } void Reset() { std::fill(frame_buffer_.begin(), frame_buffer_.end(), 0); }
// Runs the PPU for a specified number of clock cycles // Runs the PPU for one frame.
void Run(int cycles); void Update();
// Reads a byte from the specified PPU register // Reads a byte from the specified PPU register
uint8_t ReadRegister(uint16_t address); uint8_t ReadRegister(uint16_t address);

View File

@@ -265,52 +265,37 @@ void SNES::Init(ROM& rom) {
void SNES::Run() { void SNES::Run() {
running_ = true; running_ = true;
const int cpuClockSpeed = 21477272; // 21.477272 MHz const double targetFPS = 60.0; // 60 frames per second
const int ppuClockSpeed = 5369318; // 5.369318 MHz
const int apuClockSpeed = 32000; // 32 KHz
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; const double frameTime = 1.0 / targetFPS;
double cpuAccumulatedTime = 0.0; double frame_accumulated_time = 0.0;
double ppuAccumulatedTime = 0.0;
double apuAccumulatedTime = 0.0;
double frameAccumulatedTime = 0.0;
auto lastTime = std::chrono::high_resolution_clock::now(); auto last_time = std::chrono::high_resolution_clock::now();
if (running_) { if (running_) {
auto currentTime = std::chrono::high_resolution_clock::now(); auto current_time = std::chrono::high_resolution_clock::now();
double deltaTime = double delta_time =
std::chrono::duration<double>(currentTime - lastTime).count(); std::chrono::duration<double>(current_time - last_time).count();
lastTime = currentTime; last_time = current_time;
cpuAccumulatedTime += deltaTime; frame_accumulated_time += delta_time;
ppuAccumulatedTime += deltaTime;
apuAccumulatedTime += deltaTime;
frameAccumulatedTime += deltaTime;
while (cpuAccumulatedTime >= cpuCycleTime) { // Update the CPU
cpu.ExecuteInstruction(cpu.FetchByte()); cpu.UpdateClock(delta_time);
cpuAccumulatedTime -= cpuCycleTime; cpu.Update();
}
while (ppuAccumulatedTime >= ppuCycleTime) { // Update the PPU
RenderScanline(); ppu.UpdateClock(delta_time);
ppuAccumulatedTime -= ppuCycleTime; ppu.Update();
}
while (apuAccumulatedTime >= apuCycleTime) { // Update the APU
// apu.Update(); apu.UpdateClock(delta_time);
apuAccumulatedTime -= apuCycleTime; apu.Update();
}
if (frameAccumulatedTime >= frameTime) { if (frame_accumulated_time >= frameTime) {
// renderer.Render(); // renderer.Render();
frameAccumulatedTime -= frameTime; frame_accumulated_time -= frameTime;
} }
HandleInput(); HandleInput();
@@ -319,7 +304,7 @@ void SNES::Run() {
// Enable NMI Interrupts // Enable NMI Interrupts
void SNES::EnableVBlankInterrupts() { void SNES::EnableVBlankInterrupts() {
vBlankFlag = false; v_blank_flag_ = false;
// Clear the RDNMI VBlank flag // Clear the RDNMI VBlank flag
memory_.ReadByte(0x4210); // RDNMI memory_.ReadByte(0x4210); // RDNMI
@@ -330,10 +315,10 @@ void SNES::EnableVBlankInterrupts() {
// Wait until the VBlank routine has been processed // Wait until the VBlank routine has been processed
void SNES::WaitForVBlank() { void SNES::WaitForVBlank() {
vBlankFlag = true; v_blank_flag_ = true;
// Loop until `vBlankFlag` is clear // Loop until `v_blank_flag_` is clear
while (vBlankFlag) { while (v_blank_flag_) {
std::this_thread::yield(); std::this_thread::yield();
} }
} }
@@ -350,15 +335,15 @@ void SNES::NmiIsr() {
cpu.DB = 0x80; // Assuming bank 0x80, can be changed to 0x00 cpu.DB = 0x80; // Assuming bank 0x80, can be changed to 0x00
cpu.D = 0; cpu.D = 0;
if (vBlankFlag) { if (v_blank_flag_) {
VBlankRoutine(); VBlankRoutine();
// Clear `vBlankFlag` // Clear `v_blank_flag_`
vBlankFlag = false; v_blank_flag_ = false;
} }
// Increment 32-bit frameCounter // Increment 32-bit frame_counter_
frameCounter++; frame_counter_++;
// Restore CPU registers // Restore CPU registers
cpu.PHB(); 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() { void SNES::HandleInput() {
// ... // ...
} }

View File

@@ -82,11 +82,6 @@ class SNES : public DMA {
// VBlank routine // VBlank routine
void VBlankRoutine(); void VBlankRoutine();
// Functions for PPU-related operations
void RenderScanline();
void DrawBackgroundLayer(int layer);
void DrawSprites();
// Controller input handling // Controller input handling
void HandleInput(); void HandleInput();
@@ -118,11 +113,11 @@ class SNES : public DMA {
std::vector<uint8_t> rom_data; std::vector<uint8_t> rom_data;
// Byte flag to indicate if the VBlank routine should be executed or not // 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 // 32-bit counter to track the number of NMI interrupts (useful for clocks and
// timers) // timers)
std::atomic<uint32_t> frameCounter; std::atomic<uint32_t> frame_counter_;
// Other private member variables // Other private member variables
bool running_; bool running_;

View File

@@ -11,6 +11,7 @@ void SPC700::ExecuteInstructions(uint8_t opcode) {
switch (opcode) { switch (opcode) {
// 8-bit Move Memory to Register // 8-bit Move Memory to Register
case 0xE8: // MOV A, #imm case 0xE8: // MOV A, #imm
MOV(A, true);
break; break;
case 0xE6: // MOV A, (X) case 0xE6: // MOV A, (X)
break; break;

View File

@@ -65,9 +65,9 @@ class SDSP {
class SPC700 { class SPC700 {
AudioRAM aram; AudioRAM aram;
SDSP sdsp; SDSP sdsp;
uint8_t testReg; uint8_t test_register_;
uint8_t controlReg; uint8_t control_register_;
uint8_t dspAddrReg; uint8_t dsp_address_register_;
// Registers // Registers
uint8_t A; // 8-bit accumulator uint8_t A; // 8-bit accumulator
@@ -95,13 +95,13 @@ class SPC700 {
uint8_t read(uint16_t address) { uint8_t read(uint16_t address) {
switch (address) { switch (address) {
case 0xF0: case 0xF0:
return testReg; return test_register_;
case 0xF1: case 0xF1:
return controlReg; return control_register_;
case 0xF2: case 0xF2:
return dspAddrReg; return dsp_address_register_;
case 0xF3: case 0xF3:
return sdsp.readGlobalReg(dspAddrReg); return sdsp.readGlobalReg(dsp_address_register_);
default: default:
if (address < 0xFFC0) { if (address < 0xFFC0) {
return aram.read(address); return aram.read(address);
@@ -116,16 +116,16 @@ class SPC700 {
void write(uint16_t address, uint8_t value) { void write(uint16_t address, uint8_t value) {
switch (address) { switch (address) {
case 0xF0: case 0xF0:
testReg = value; test_register_ = value;
break; break;
case 0xF1: case 0xF1:
controlReg = value; control_register_ = value;
break; break;
case 0xF2: case 0xF2:
dspAddrReg = value; dsp_address_register_ = value;
break; break;
case 0xF3: case 0xF3:
sdsp.writeGlobalReg(dspAddrReg, value); sdsp.writeGlobalReg(dsp_address_register_, value);
break; break;
default: default:
if (address < 0xFFC0) { if (address < 0xFFC0) {
@@ -182,7 +182,7 @@ class SPC700 {
// Instructions // Instructions
// MOV // MOV
void MOV(uint8_t operand, bool isImmediate) { void MOV(uint8_t operand, bool isImmediate = false) {
uint8_t value = isImmediate ? imm() : operand; uint8_t value = isImmediate ? imm() : operand;
operand = value; operand = value;
PSW.Z = (operand == 0); PSW.Z = (operand == 0);