Overhaul Apu, add cycling and port handling
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user