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) {}
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() {
// ...
}

View File

@@ -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
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;
}
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();
}
}

View File

@@ -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;

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

View File

@@ -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);

View File

@@ -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() {
// ...
}

View File

@@ -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_;

View File

@@ -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;

View File

@@ -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);