Overhaul Apu, add cycling and port handling

This commit is contained in:
scawful
2024-04-22 15:55:57 -04:00
parent ca1d0e9131
commit 1c9c1592eb
2 changed files with 180 additions and 50 deletions

View File

@@ -17,6 +17,14 @@ namespace app {
namespace emu {
namespace audio {
static const uint8_t bootRom[0x40] = {
0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0, 0xfc, 0x8f, 0xaa,
0xf4, 0x8f, 0xbb, 0xf5, 0x78, 0xcc, 0xf4, 0xd0, 0xfb, 0x2f, 0x19,
0xeb, 0xf4, 0xd0, 0xfc, 0x7e, 0xf4, 0xd0, 0x0b, 0xe4, 0xf5, 0xcb,
0xf4, 0xd7, 0x00, 0xfc, 0xd0, 0xf3, 0xab, 0x01, 0x10, 0xef, 0x7e,
0xf4, 0x10, 0xeb, 0xba, 0xf6, 0xda, 0x00, 0xba, 0xf4, 0xc4, 0xf4,
0xdd, 0x5d, 0xd0, 0xdb, 0x1f, 0x00, 0x00, 0xc0, 0xff};
void Apu::Init() {
// Set the clock frequency
clock_.SetFrequency(kApuClockSpeed);
@@ -24,8 +32,9 @@ void Apu::Init() {
void Apu::Reset() {
clock_.ResetAccumulatedTime();
spc700_.Reset();
spc700_.Reset(true);
dsp_.Reset();
romReadable = true;
}
void Apu::Update() {
@@ -39,31 +48,147 @@ void Apu::Update() {
}
}
void Apu::Notify(uint32_t address, uint16_t data) {
if (address < 0x2140 || address > 0x2143) {
return;
}
auto offset = address - 0x2140;
spc700_.write(offset, data);
int Apu::RunCycles(uint32_t wanted_cycles) {
int run_cycles = 0;
uint32_t start_cycles = cycles_;
// HACK - This is a temporary solution to get the Apu to play audio
ports_[address - 0x2140] = data;
switch (address) {
case 0x2140:
SignalReady();
break;
case 0x2141:
// TODO: Handle data byte transfer here
break;
case 0x2142:
// TODO: Handle the setup of destination address
break;
case 0x2143:
// TODO: Handle additional communication/commands
break;
while (run_cycles < wanted_cycles) {
spc700_.RunOpcode();
run_cycles += (uint32_t)cycles_ - start_cycles;
start_cycles = cycles_;
}
return run_cycles;
}
void Apu::Cycle() {
if ((cycles_ & 0x1f) == 0) {
// every 32 cycles
dsp_.Cycle();
}
// handle timers
for (int i = 0; i < 3; i++) {
if (timer[i].cycles == 0) {
timer[i].cycles = i == 2 ? 16 : 128;
if (timer[i].enabled) {
timer[i].divider++;
if (timer[i].divider == timer[i].target) {
timer[i].divider = 0;
timer[i].counter++;
timer[i].counter &= 0xf;
}
}
}
timer[i].cycles--;
}
cycles_++;
}
uint8_t Apu::Read(uint16_t adr) {
switch (adr) {
case 0xf0:
case 0xf1:
case 0xfa:
case 0xfb:
case 0xfc: {
return 0;
}
case 0xf2: {
return dspAdr;
}
case 0xf3: {
return dsp_.Read(dspAdr & 0x7f);
}
case 0xf4:
case 0xf5:
case 0xf6:
case 0xf7:
case 0xf8:
case 0xf9: {
return inPorts[adr - 0xf4];
}
case 0xfd:
case 0xfe:
case 0xff: {
uint8_t ret = timer[adr - 0xfd].counter;
timer[adr - 0xfd].counter = 0;
return ret;
}
}
if (romReadable && adr >= 0xffc0) {
return bootRom[adr - 0xffc0];
}
return aram_.read(adr);
}
void Apu::Write(uint16_t adr, uint8_t val) {
switch (adr) {
case 0xf0: {
break; // test register
}
case 0xf1: {
for (int i = 0; i < 3; i++) {
if (!timer[i].enabled && (val & (1 << i))) {
timer[i].divider = 0;
timer[i].counter = 0;
}
timer[i].enabled = val & (1 << i);
}
if (val & 0x10) {
inPorts[0] = 0;
inPorts[1] = 0;
}
if (val & 0x20) {
inPorts[2] = 0;
inPorts[3] = 0;
}
romReadable = val & 0x80;
break;
}
case 0xf2: {
dspAdr = val;
break;
}
case 0xf3: {
if (dspAdr < 0x80) dsp_.Write(dspAdr, val);
break;
}
case 0xf4:
case 0xf5:
case 0xf6:
case 0xf7: {
outPorts[adr - 0xf4] = val;
break;
}
case 0xf8:
case 0xf9: {
inPorts[adr - 0xf4] = val;
break;
}
case 0xfa:
case 0xfb:
case 0xfc: {
timer[adr - 0xfa].target = val;
break;
}
}
aram_.write(adr, val);
}
uint8_t Apu::SpcRead(uint16_t adr) {
Cycle();
return Read(adr);
}
void Apu::SpcWrite(uint16_t adr, uint8_t val) {
Cycle();
Write(adr, val);
}
void Apu::SpcIdle(bool waiting) { Cycle(); }
} // namespace audio
} // namespace emu
} // namespace app

View File

@@ -21,6 +21,14 @@ const int kApuClockSpeed = 1024000; // 1.024 MHz
const int apuSampleRate = 32000; // 32 KHz
const int apuClocksPerSample = 64; // 64 clocks per sample
typedef struct Timer {
uint8_t cycles;
uint8_t divider;
uint8_t target;
uint8_t counter;
bool enabled;
} Timer;
/**
* @class Apu
* @brief The Apu class represents the Audio Processing Unit (APU) of a system.
@@ -46,7 +54,7 @@ const int apuClocksPerSample = 64; // 64 clocks per sample
* register $F1 can be cleared to unmap the IPL ROM and allow read access to
* this RAM.
*/
class Apu : public Observer {
class Apu {
public:
Apu(MemoryImpl &memory, AudioRam &aram, Clock &clock)
: aram_(aram), clock_(clock), memory_(memory) {}
@@ -54,7 +62,16 @@ class Apu : public Observer {
void Init();
void Reset();
void Update();
void Notify(uint32_t address, uint16_t data) override;
int RunCycles(uint32_t wanted_cycles);
uint8_t SpcRead(uint16_t address);
void SpcWrite(uint16_t address, uint8_t data);
void SpcIdle(bool waiting);
void Cycle();
uint8_t Read(uint16_t address);
void Write(uint16_t address, uint8_t data);
// Called upon a reset
void Initialize() {
@@ -62,34 +79,13 @@ class Apu : public Observer {
dsp_.Reset();
}
// Set Port 0 = $AA and Port 1 = $BB
void SignalReady() {
memory_.WriteByte(0x2140, READY_SIGNAL_0);
memory_.WriteByte(0x2141, READY_SIGNAL_1);
}
void WriteToPort(uint8_t portNum, uint8_t value) {
ports_[portNum] = value;
switch (portNum) {
case 0:
memory_.WriteByte(0x2140, value);
break;
case 1:
memory_.WriteByte(0x2141, value);
break;
case 2:
memory_.WriteByte(0x2142, value);
break;
case 3:
memory_.WriteByte(0x2143, value);
break;
}
}
void UpdateClock(int delta_time) { clock_.UpdateClock(delta_time); }
auto dsp() -> Dsp & { return dsp_; }
uint8_t inPorts[6]; // includes 2 bytes of ram
uint8_t outPorts[4];
private:
// Constants for communication
static const uint8_t READY_SIGNAL_0 = 0xAA;
@@ -98,14 +94,23 @@ class Apu : public Observer {
// Port buffers (equivalent to $2140 to $2143 for the main CPU)
uint8_t ports_[4] = {0};
Timer timer[3];
uint32_t cycles_;
uint8_t dspAdr;
bool romReadable = true;
// Member variables to store internal APU state and resources
AudioRam &aram_;
Clock &clock_;
MemoryImpl &memory_;
Dsp dsp_;
Spc700 spc700_{aram_};
ApuCallbacks callbacks_ = {
[this](uint16_t adr, uint8_t val) { SpcWrite(adr, val); },
[this](uint16_t adr) { return SpcRead(adr); },
[this](bool waiting) { SpcIdle(waiting); },
};
Dsp dsp_{aram_};
Spc700 spc700_{aram_, callbacks_};
};
} // namespace audio