Update to v097r25 release.

byuu says:

Changelog:
- WS: added HblankTimer and VblankTimer IRQs; although they don't appear
  to have any effect on any games that use them :/
- WS: added sound emulation; works perfectly in some games (eg Riviera);
  is completely silent in most games (eg GunPey)

The sound emulation only partially supports the hypervoice (headphone
only) channel. I need to implement the SDMA before it'll actually do
anything useful. I'm a bit confused about how exactly things work. It
looks like the speaker volume shift and clamp only applies to speaker
mode and not headphone mode, which is very weird. Then there's the
software possibility of muting the headphones and/or the speaker.
Preferably, I want to leave the emulator always in headphone mode for
the extra audio channel. If there are games that force-mute the
headphones, but not speakers, then I may need to force headphones back
on but with the hypervoice channel disabled. I guess we'll see how
things go.

Rough guess is probably that the channels default to enabled after the
IPLROM, and games aren't bothering to manually enable them or something.
This commit is contained in:
Tim Allen 2016-03-14 22:03:32 +11:00
parent c33065fbd1
commit b586471562
15 changed files with 514 additions and 143 deletions

View File

@ -6,7 +6,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "097.24";
static const string Version = "097.25";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -4,14 +4,61 @@ namespace WonderSwan {
APU apu;
#include "io.cpp"
#include "channel.cpp"
#include "channel0.cpp"
#include "channel1.cpp"
#include "channel2.cpp"
#include "channel3.cpp"
#include "channel4.cpp"
auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main();
}
auto APU::main() -> void {
step(128);
interface->audioSample(0, 0);
channel0.run();
channel1.run();
channel2.run();
channel3.run();
if(s.clock.bits(0,12) == 0) {
channel2.sweep();
}
if(s.clock.bits(0,6) == 0) {
channel4.run();
dacRun();
}
s.clock++;
step(1);
}
auto APU::dacRun() -> void {
int left = 0;
if(channel0.r.enable) left += channel0.o.left;
if(channel1.r.enable) left += channel1.o.left;
if(channel2.r.enable) left += channel2.o.left;
if(channel3.r.enable) left += channel3.o.left;
left = (left >> r.speakerShift) << 5;
if(channel4.r.enable) left += channel4.o.left;
left = sclamp<16>(left << 3);
int right = 0;
if(channel0.r.enable) right += channel0.o.right;
if(channel1.r.enable) right += channel1.o.right;
if(channel2.r.enable) right += channel2.o.right;
if(channel3.r.enable) right += channel3.o.right;
right = (right >> r.speakerShift) << 5;
if(channel4.r.enable) right += channel4.o.right;
right = sclamp<16>(right << 3);
if(!r.speakerEnable) {
left = 0;
right = 0;
}
interface->audioSample(left, right);
}
auto APU::step(uint clocks) -> void {
@ -22,40 +69,74 @@ auto APU::step(uint clocks) -> void {
auto APU::power() -> void {
create(APU::Enter, 3'072'000);
for(uint n = 0x006a; n <= 0x006b; n++) iomap[n] = this;
for(uint n = 0x0080; n <= 0x0094; n++) iomap[n] = this;
channel1.r.pitch = 0;
channel1.r.volumeLeft = 0;
channel1.r.volumeRight = 0;
channel1.r.enable = 0;
channel2.r.pitch = 0;
channel2.r.volumeLeft = 0;
channel2.r.volumeRight = 0;
channel2.r.enable = 0;
channel2.r.voice = 0;
channel3.r.pitch = 0;
channel3.r.volumeLeft = 0;
channel3.r.volumeRight = 0;
channel3.r.sweepValue = 0;
channel3.r.sweepTime = 0;
channel3.r.enable = 0;
channel3.r.sweep = 0;
channel4.r.pitch = 0;
channel4.r.volumeLeft = 0;
channel4.r.volumeRight = 0;
channel4.r.noiseMode = 0;
channel4.r.enable = 0;
channel4.r.noise = 0;
s.clock = 0;
r.waveBase = 0;
r.speakerEnable = 0;
r.speakerShift = 0;
r.headphoneEnable = 0;
r.voiceEnableLeft = 0;
r.voiceEnableRight = 0;
channel0.o.left = 0;
channel0.o.right = 0;
channel0.s.period = 0;
channel0.s.sampleOffset = 0;
channel0.r.pitch = 0;
channel0.r.volumeLeft = 0;
channel0.r.volumeRight = 0;
channel0.r.enable = 0;
channel1.o.left = 0;
channel1.o.right = 0;
channel1.s.period = 0;
channel1.s.sampleOffset = 0;
channel1.r.pitch = 0;
channel1.r.volumeLeft = 0;
channel1.r.volumeRight = 0;
channel1.r.enable = 0;
channel1.r.voice = 0;
channel1.r.voiceEnableLeft = 0;
channel1.r.voiceEnableRight = 0;
channel2.o.left = 0;
channel2.o.right = 0;
channel2.s.period = 0;
channel2.s.sampleOffset = 0;
channel2.s.sweepCounter = 0;
channel2.r.pitch = 0;
channel2.r.volumeLeft = 0;
channel2.r.volumeRight = 0;
channel2.r.sweepValue = 0;
channel2.r.sweepTime = 0;
channel2.r.enable = 0;
channel2.r.sweep = 0;
channel3.o.left = 0;
channel3.o.right = 0;
channel3.s.period = 0;
channel3.s.sampleOffset = 0;
channel3.s.noiseOutput = 0;
channel3.s.noiseLFSR = 0;
channel3.r.pitch = 0;
channel3.r.volumeLeft = 0;
channel3.r.volumeRight = 0;
channel3.r.noiseMode = 0;
channel3.r.noiseReset = 0;
channel3.r.noiseUpdate = 0;
channel3.r.enable = 0;
channel3.r.noise = 0;
channel4.o.left = 0;
channel4.o.right = 0;
channel4.s.data = 0;
channel4.r.volume = 0;
channel4.r.scale = 0;
channel4.r.speed = 0;
channel4.r.enable = 0;
channel4.r.unknown = 0;
channel4.r.leftEnable = 0;
channel4.r.rightEnable = 0;
}
}

View File

@ -1,6 +1,7 @@
struct APU : Thread, IO {
static auto Enter() -> void;
auto main() -> void;
auto dacRun() -> void;
auto step(uint clocks) -> void;
auto power() -> void;
@ -8,41 +9,98 @@ struct APU : Thread, IO {
auto portRead(uint16 addr) -> uint8;
auto portWrite(uint16 addr, uint8 data) -> void;
struct Channel1 {
struct State {
uint13 clock;
} s;
struct Registers {
//$0080-0081 SND_CH1_PITCH
//$008f SND_WAVE_BASE
uint8 waveBase;
//$0091 SND_OUTPUT
uint1 speakerEnable;
uint2 speakerShift;
uint1 headphoneEnable;
} r;
struct Channel {
Channel(uint id);
auto sample(uint5 index) -> uint4;
const uint id;
struct Output {
int16 left;
int16 right;
} o;
};
struct Channel0 : Channel {
Channel0();
auto run() -> void;
struct State {
uint11 period;
uint5 sampleOffset;
} s;
struct Registers {
//$0080-0081 SND_CH0_PITCH
uint11 pitch;
//$0088 SND_CH1_VOL
//$0088 SND_CH0_VOL
uint4 volumeLeft;
uint4 volumeRight;
//$0090 SND_CTRL
uint1 enable;
} r;
} channel1;
} channel0;
struct Channel1 : Channel {
Channel1();
auto run() -> void;
struct State {
uint11 period;
uint5 sampleOffset;
} s;
struct Channel2 {
struct Registers {
//$0082-0083 SND_CH2_PITCH
//$0082-0083 SND_CH1_PITCH
uint11 pitch;
//$0089 SND_CH2_VOL
//$0089 SND_CH1_VOL
uint4 volumeLeft;
uint4 volumeRight;
//$0090 SND_CTRL
uint1 enable;
uint1 voice;
} r;
} channel2;
struct Channel3 {
//$0092 SND_VOICE_CTRL
uint2 voiceEnableLeft;
uint2 voiceEnableRight;
} r;
} channel1;
struct Channel2 : Channel {
Channel2();
auto sweep() -> void;
auto run() -> void;
struct State {
uint11 period;
uint5 sampleOffset;
int sweepCounter;
} s;
struct Registers {
//$0084-0085 SND_CH3_PITCH
//$0084-0085 SND_CH2_PITCH
uint11 pitch;
//$008a SND_CH3_VOL
//$008a SND_CH2_VOL
uint4 volumeLeft;
uint4 volumeRight;
@ -56,39 +114,61 @@ struct APU : Thread, IO {
uint1 enable;
uint1 sweep;
} r;
} channel3;
} channel2;
struct Channel3 : Channel {
Channel3();
auto noiseSample() -> uint4;
auto run() -> void;
struct State {
uint11 period;
uint5 sampleOffset;
uint1 noiseOutput;
uint15 noiseLFSR;
} s;
struct Channel4 {
struct Registers {
//$0086-0087 SND_CH4_PITCH
//$0086-0087 SND_CH3_PITCH
uint11 pitch;
//$008b SND_CH4_VOL
//$008b SND_CH3_VOL
uint4 volumeLeft;
uint4 volumeRight;
//$008e SND_NOISE
uint5 noiseMode;
uint3 noiseMode;
uint1 noiseReset;
uint1 noiseUpdate;
//$0090 SND_CTRL
uint1 enable;
uint1 noise;
} r;
} channel4;
} channel3;
struct Channel4 : Channel {
Channel4();
auto run() -> void;
struct State {
int8 data;
} s;
struct Registers {
//$008f SND_WAVE_BASE
uint8 waveBase;
//$006a HYPER_CTRL
uint2 volume;
uint2 scale;
uint3 speed;
uint1 enable;
//$0091 SND_OUTPUT
uint1 speakerEnable;
uint2 speakerShift;
uint1 headphoneEnable;
//$0092 SND_VOICE_CTRL
uint2 voiceEnableLeft;
uint2 voiceEnableRight;
//$006b HYPER_CHAN_CTRL
uint4 unknown;
uint1 leftEnable;
uint1 rightEnable;
} r;
} channel4;
};
extern APU apu;

8
higan/ws/apu/channel.cpp Normal file
View File

@ -0,0 +1,8 @@
APU::Channel::Channel(uint id) : id(id) {
}
auto APU::Channel::sample(uint5 index) -> uint4 {
auto data = iram.read((apu.r.waveBase << 6) + (id << 4) + (index >> 1));
if(index.bit(0) == 0) return data.bits(0,3);
if(index.bit(0) == 1) return data.bits(4,7);
}

11
higan/ws/apu/channel0.cpp Normal file
View File

@ -0,0 +1,11 @@
APU::Channel0::Channel0() : Channel(0) {
}
auto APU::Channel0::run() -> void {
if(--s.period == r.pitch) {
s.period = 0;
auto output = sample(s.sampleOffset++);
o.left = output * r.volumeLeft;
o.right = output * r.volumeRight;
}
}

15
higan/ws/apu/channel1.cpp Normal file
View File

@ -0,0 +1,15 @@
APU::Channel1::Channel1() : Channel(1) {
}
auto APU::Channel1::run() -> void {
if(r.voice) {
uint8 volume = r.volumeLeft << 4 | r.volumeRight << 0;
o.left = r.voiceEnableLeft ? volume : (uint8)0x80;
o.right = r.voiceEnableRight ? volume : (uint8)0x80;
} else if(--s.period == r.pitch) {
s.period = 0;
auto output = sample(s.sampleOffset++);
o.left = output * r.volumeLeft;
o.right = output * r.volumeRight;
}
}

18
higan/ws/apu/channel2.cpp Normal file
View File

@ -0,0 +1,18 @@
APU::Channel2::Channel2() : Channel(2) {
}
auto APU::Channel2::sweep() -> void {
if(r.sweep && --s.sweepCounter < 0) {
s.sweepCounter = r.sweepTime;
r.pitch += r.sweepTime;
}
}
auto APU::Channel2::run() -> void {
if(--s.period == r.pitch) {
s.period = 0;
auto output = sample(s.sampleOffset++);
o.left = output * r.volumeLeft;
o.right = output * r.volumeRight;
}
}

30
higan/ws/apu/channel3.cpp Normal file
View File

@ -0,0 +1,30 @@
APU::Channel3::Channel3() : Channel(3) {
}
auto APU::Channel3::noiseSample() -> uint4 {
return s.noiseOutput ? 0xf : 0x0;
}
auto APU::Channel3::run() -> void {
if(--s.period == r.pitch) {
s.period = 0;
auto output = r.noise ? noiseSample() : sample(s.sampleOffset++);
o.left = output * r.volumeLeft;
o.right = output * r.volumeRight;
if(r.noiseReset) {
r.noiseReset = 0;
s.noiseLFSR = 0;
s.noiseOutput = 0;
}
if(r.noiseUpdate) {
static const int taps[8] = {14, 10, 13, 4, 8, 6, 9, 11};
auto tap = taps[r.noiseMode];
s.noiseOutput = (1 ^ (s.noiseLFSR >> 7) ^ (s.noiseLFSR >> tap)) & 1;
s.noiseLFSR = s.noiseLFSR << 1 | s.noiseOutput;
}
}
}

11
higan/ws/apu/channel4.cpp Normal file
View File

@ -0,0 +1,11 @@
APU::Channel4::Channel4() : Channel(4) {
}
auto APU::Channel4::run() -> void {
int16 sample = s.data << 8;
if(r.scale != 3) sample >>= r.volume;
if(r.scale == 1) sample |= 0x70000 >> r.volume;
o.left = r.leftEnable ? sample : (int16)0;
o.right = r.rightEnable ? sample : (int16)0;
}

View File

@ -1,65 +1,84 @@
auto APU::portRead(uint16 addr) -> uint8 {
//SND_HYPER_CTRL
if(addr == 0x006a) return (
channel4.r.volume << 0
| channel4.r.scale << 2
| channel4.r.speed << 4
| channel4.r.enable << 7
);
//SND_HYPER_CHAN_CTRL
if(addr == 0x006b) return (
channel4.r.unknown << 0
| channel4.r.leftEnable << 5
| channel4.r.rightEnable << 6
);
//SND_CH0_PITCH
if(addr == 0x0080) return channel0.r.pitch.bits(0, 7);
if(addr == 0x0081) return channel0.r.pitch.bits(8,11);
//SND_CH1_PITCH
if(addr == 0x0080) return channel1.r.pitch.bits(0, 7);
if(addr == 0x0081) return channel1.r.pitch.bits(8,11);
if(addr == 0x0082) return channel1.r.pitch.bits(0, 7);
if(addr == 0x0083) return channel1.r.pitch.bits(8,11);
//SND_CH2_PITCH
if(addr == 0x0082) return channel2.r.pitch.bits(0, 7);
if(addr == 0x0083) return channel2.r.pitch.bits(8,11);
if(addr == 0x0084) return channel2.r.pitch.bits(0, 7);
if(addr == 0x0085) return channel2.r.pitch.bits(8,11);
//SND_CH3_PITCH
if(addr == 0x0084) return channel3.r.pitch.bits(0, 7);
if(addr == 0x0085) return channel3.r.pitch.bits(8,11);
if(addr == 0x0086) return channel3.r.pitch.bits(0, 7);
if(addr == 0x0087) return channel3.r.pitch.bits(8,11);
//SND_CH4_PITCH
if(addr == 0x0086) return channel4.r.pitch.bits(0, 7);
if(addr == 0x0087) return channel4.r.pitch.bits(8,11);
//SND_CH0_VOL
if(addr == 0x0088) return (
channel0.r.volumeRight << 0
| channel0.r.volumeLeft << 4
);
//SND_CH1_VOL
if(addr == 0x0088) return (
if(addr == 0x0089) return (
channel1.r.volumeRight << 0
| channel1.r.volumeLeft << 4
);
//SND_CH2_VOL
if(addr == 0x0089) return (
if(addr == 0x008a) return (
channel2.r.volumeRight << 0
| channel2.r.volumeLeft << 4
);
//SND_CH3_VOL
if(addr == 0x008a) return (
if(addr == 0x008b) return (
channel3.r.volumeRight << 0
| channel3.r.volumeLeft << 4
);
//SND_CH4_VOL
if(addr == 0x008b) return (
channel4.r.volumeRight << 0
| channel4.r.volumeLeft << 4
);
//SND_SWEEP_VALUE
if(addr == 0x008c) return channel3.r.sweepValue;
if(addr == 0x008c) return channel2.r.sweepValue;
//SND_SWEEP_TIME
if(addr == 0x008d) return channel3.r.sweepTime;
if(addr == 0x008d) return channel2.r.sweepTime;
//SND_NOISE
if(addr == 0x008e) return channel4.r.noiseMode;
//(noiseReset [bit 3] always reads as zero)
if(addr == 0x008e) return (
channel3.r.noiseMode << 0
| channel3.r.noiseUpdate << 4
);
//SND_WAVE_BASE
if(addr == 0x008f) return r.waveBase;
//SND_CTRL
if(addr == 0x0090) return (
channel1.r.enable << 0
| channel2.r.enable << 1
| channel3.r.enable << 2
| channel4.r.enable << 3
| channel2.r.voice << 5
| channel3.r.sweep << 6
| channel4.r.noise << 7
channel0.r.enable << 0
| channel1.r.enable << 1
| channel2.r.enable << 2
| channel3.r.enable << 3
| channel1.r.voice << 5
| channel2.r.sweep << 6
| channel3.r.noise << 7
);
//SND_OUTPUT
@ -71,78 +90,95 @@ auto APU::portRead(uint16 addr) -> uint8 {
);
//SND_RANDOM
if(addr == 0x0092) return rand() & 0xff;
if(addr == 0x0093) return rand() & 0x7f;
if(addr == 0x0092) return channel3.s.noiseLFSR.bits(0, 7);
if(addr == 0x0093) return channel3.s.noiseLFSR.bits(8,14);
//SND_VOICE_CTRL
if(addr == 0x0094) return (
r.voiceEnableRight << 0
| r.voiceEnableLeft << 2
channel1.r.voiceEnableRight << 0
| channel1.r.voiceEnableLeft << 2
);
return 0x00;
}
auto APU::portWrite(uint16 addr, uint8 data) -> void {
//SND_HYPER_CTRL
if(addr == 0x006a) {
channel4.r.volume = data.bits(0,1);
channel4.r.scale = data.bits(2,3);
channel4.r.speed = data.bits(4,6);
channel4.r.enable = data.bit (7);
}
//SND_HYPER_CHAN_CTRL
if(addr == 0x006b) {
channel4.r.unknown = data.bits(0,3);
channel4.r.leftEnable = data.bit (5);
channel4.r.rightEnable = data.bit (6);
}
//SND_CH0_PITCH
if(addr == 0x0080) { channel0.r.pitch.bits(0, 7) = data.bits(0,7); return; }
if(addr == 0x0081) { channel0.r.pitch.bits(8,11) = data.bits(0,3); return; }
//SND_CH1_PITCH
if(addr == 0x0080) { channel1.r.pitch.bits(0, 7) = data.bits(0,7); return; }
if(addr == 0x0081) { channel1.r.pitch.bits(8,11) = data.bits(0,3); return; }
if(addr == 0x0082) { channel1.r.pitch.bits(0, 7) = data.bits(0,7); return; }
if(addr == 0x0083) { channel1.r.pitch.bits(8,11) = data.bits(0,3); return; }
//SND_CH2_PITCH
if(addr == 0x0082) { channel2.r.pitch.bits(0, 7) = data.bits(0,7); return; }
if(addr == 0x0083) { channel2.r.pitch.bits(8,11) = data.bits(0,3); return; }
if(addr == 0x0084) { channel2.r.pitch.bits(0, 7) = data.bits(0,7); return; }
if(addr == 0x0085) { channel2.r.pitch.bits(8,11) = data.bits(0,3); return; }
//SND_CH3_PITCH
if(addr == 0x0084) { channel3.r.pitch.bits(0, 7) = data.bits(0,7); return; }
if(addr == 0x0085) { channel3.r.pitch.bits(8,11) = data.bits(0,3); return; }
if(addr == 0x0086) { channel3.r.pitch.bits(0, 7) = data.bits(0,7); return; }
if(addr == 0x0087) { channel3.r.pitch.bits(8,11) = data.bits(0,3); return; }
//SND_CH4_PITCH
if(addr == 0x0086) { channel4.r.pitch.bits(0, 7) = data.bits(0,7); return; }
if(addr == 0x0087) { channel4.r.pitch.bits(8,11) = data.bits(0,3); return; }
//SND_CH0_VOL
if(addr == 0x0088) {
channel0.r.volumeRight = data.bits(0,3);
channel0.r.volumeLeft = data.bits(4,7);
return;
}
//SND_CH1_VOL
if(addr == 0x0088) {
if(addr == 0x0089) {
channel1.r.volumeRight = data.bits(0,3);
channel1.r.volumeLeft = data.bits(4,7);
return;
}
//SND_CH2_VOL
if(addr == 0x0089) {
if(addr == 0x008a) {
channel2.r.volumeRight = data.bits(0,3);
channel2.r.volumeLeft = data.bits(4,7);
return;
}
//SND_CH3_VOL
if(addr == 0x008a) {
if(addr == 0x008b) {
channel3.r.volumeRight = data.bits(0,3);
channel3.r.volumeLeft = data.bits(4,7);
return;
}
//SND_CH4_VOL
if(addr == 0x008b) {
channel4.r.volumeRight = data.bits(0,3);
channel4.r.volumeLeft = data.bits(4,7);
return;
}
//SND_SWEEP_VALUE
if(addr == 0x008c) {
channel3.r.sweepValue = data;
channel2.r.sweepValue = data;
return;
}
//SND_SWEEP_TIME
if(addr == 0x008d) {
channel3.r.sweepTime = data.bits(0,4);
channel2.r.sweepTime = data.bits(0,4);
return;
}
//SND_NOISE
if(addr == 0x008e) {
channel4.r.noiseMode = data.bits(0,4);
channel3.r.noiseMode = data.bits(0,2);
channel3.r.noiseReset = data.bit (3);
channel3.r.noiseUpdate = data.bit (4);
return;
}
@ -154,13 +190,13 @@ auto APU::portWrite(uint16 addr, uint8 data) -> void {
//SND_CTRL
if(addr == 0x0090) {
channel1.r.enable = data.bit(0);
channel2.r.enable = data.bit(1);
channel3.r.enable = data.bit(2);
channel4.r.enable = data.bit(3);
channel2.r.voice = data.bit(5);
channel3.r.sweep = data.bit(6);
channel4.r.noise = data.bit(7);
channel0.r.enable = data.bit(0);
channel1.r.enable = data.bit(1);
channel2.r.enable = data.bit(2);
channel3.r.enable = data.bit(3);
channel1.r.voice = data.bit(5);
channel2.r.sweep = data.bit(6);
channel3.r.noise = data.bit(7);
return;
}
@ -174,8 +210,8 @@ auto APU::portWrite(uint16 addr, uint8 data) -> void {
//SND_VOICE_CTRL
if(addr == 0x0094) {
r.voiceEnableRight = data.bits(0,1);
r.voiceEnableLeft = data.bits(2,3);
channel1.r.voiceEnableRight = data.bits(0,1);
channel1.r.voiceEnableLeft = data.bits(2,3);
return;
}
}

View File

@ -1,22 +1,17 @@
auto CPU::poll() -> void {
if(!state.poll || !(r.interruptStatus & r.interruptEnable)) return;
state.halt = false;
if(!V30MZ::r.f.i) return;
if(!V30MZ::r.f.i || !state.poll) return;
//find and execute first pending interrupt in order of priority (7-0)
for(int n = 7; n >= 0; n--) {
if(r.interruptStatus & r.interruptEnable & (1 << n)) {
if(!r.interruptEnable.bit(n)) continue;
if(!r.interruptStatus.bit(n)) continue;
return interrupt(r.interruptBase + n);
}
}
}
auto CPU::raise(Interrupt i) -> void {
auto mask = 1 << (uint)i;
r.interruptStatus |= mask;
auto CPU::raise(Interrupt irq) -> void {
r.interruptStatus.bit((uint)irq) = 1;
}
auto CPU::lower(Interrupt i) -> void {
auto mask = 1 << (uint)i;
r.interruptStatus &= ~mask;
auto CPU::lower(Interrupt irq) -> void {
r.interruptStatus.bit((uint)irq) = 0;
}

View File

@ -123,6 +123,30 @@ auto PPU::portRead(uint16 addr) -> uint8 {
);
}
//TMR_CTRL
if(addr == 0x00a2) return (
r.htimerEnable << 0
| r.htimerRepeat << 1
| r.vtimerEnable << 2
| r.vtimerRepeat << 3
);
//HTMR_FREQ
if(addr == 0x00a4) return r.htimerFrequency.byte(0);
if(addr == 0x00a5) return r.htimerFrequency.byte(1);
//VTMR_FREQ
if(addr == 0x00a6) return r.vtimerFrequency.byte(0);
if(addr == 0x00a7) return r.vtimerFrequency.byte(1);
//HTMR_CTR
if(addr == 0x00a8) return r.htimerCounter.byte(0);
if(addr == 0x00a9) return r.htimerCounter.byte(1);
//VTMR_CTR
if(addr == 0x00aa) return r.vtimerCounter.byte(0);
if(addr == 0x00ab) return r.vtimerCounter.byte(1);
return 0x00;
}
@ -289,4 +313,20 @@ auto PPU::portWrite(uint16 addr, uint8 data) -> void {
r.palette[addr.bits(4,1)].color[addr.bit(0) * 2 + 0] = data.bits(2,0);
return;
}
//TMR_CTRL
if(addr == 0x00a2) {
r.htimerEnable = data.bit(0);
r.htimerRepeat = data.bit(1);
r.vtimerEnable = data.bit(2);
r.vtimerRepeat = data.bit(3);
}
//HTMR_FREQ
if(addr == 0x00a4) r.htimerFrequency.byte(0) = data;
if(addr == 0x00a5) r.htimerFrequency.byte(1) = data;
//VTMR_FREQ
if(addr == 0x00a6) r.vtimerFrequency.byte(0) = data;
if(addr == 0x00a7) r.vtimerFrequency.byte(1) = data;
}

View File

@ -31,13 +31,21 @@ auto PPU::main() -> void {
output[status.vclk * 224 + status.hclk] = pixel.color;
step(1);
}
for(uint x = 224; x < 256; x++) {
step(1);
}
step(32);
} else {
step(256);
}
scanline();
if(r.htimerEnable && r.htimerCounter < r.htimerFrequency) {
if(++r.htimerCounter == r.htimerFrequency) {
if(r.htimerRepeat) {
r.htimerCounter = 0;
} else {
r.htimerEnable = false;
}
cpu.raise(CPU::Interrupt::HblankTimer);
}
}
}
auto PPU::scanline() -> void {
@ -48,6 +56,16 @@ auto PPU::scanline() -> void {
}
if(status.vclk == 144) {
cpu.raise(CPU::Interrupt::Vblank);
if(r.vtimerEnable && r.vtimerCounter < r.vtimerFrequency) {
if(++r.vtimerCounter == r.vtimerFrequency) {
if(r.vtimerRepeat) {
r.vtimerCounter = 0;
} else {
r.vtimerEnable = false;
}
cpu.raise(CPU::Interrupt::VblankTimer);
}
}
}
if(status.vclk == 159) frame();
}
@ -70,6 +88,8 @@ auto PPU::power() -> void {
for(uint n = 0x0000; n <= 0x0017; n++) iomap[n] = this;
for(uint n = 0x001c; n <= 0x003f; n++) iomap[n] = this;
iomap[0x00a2] = this;
for(uint n = 0x00a4; n <= 0x00ab; n++) iomap[n] = this;
for(auto& n : output) n = 0;
@ -110,6 +130,14 @@ auto PPU::power() -> void {
r.iconSleep = 0;
r.vtotal = 158;
r.vblank = 155;
r.htimerEnable = 0;
r.htimerRepeat = 0;
r.vtimerEnable = 0;
r.vtimerRepeat = 0;
r.htimerFrequency = 0;
r.vtimerFrequency = 0;
r.htimerCounter = 0;
r.vtimerCounter = 0;
for(auto& color : r.pool) color = 0;
for(auto& p : r.palette) for(auto& color : p.color) color = 0;

View File

@ -143,6 +143,24 @@ struct PPU : Thread, IO {
struct Palette {
uint3 color[4];
} palette[16];
//$00a2 TMR_CTRL
uint1 htimerEnable;
uint1 htimerRepeat;
uint1 vtimerEnable;
uint1 vtimerRepeat;
//$00a4,$00a5 HTMR_FREQ
uint16 htimerFrequency;
//$00a6,$00a7 VTMR_FREQ
uint16 vtimerFrequency;
//$00a8,$00a9 HTMR_CTR
uint16 htimerCounter;
//$00aa,$00ab VTMR_CTR
uint16 vtimerCounter;
} r;
};