Skip to content

Commit

Permalink
m32x+md fixes (#1807)
Browse files Browse the repository at this point in the history
1. Revised pwm emulation, mostly to align with documentation. Not likely
to affect much except for rare edge cases. Also, added a dc filter to
prevent audio popping in certain situations (like window losing focus),
but pops on init are correct behavior. Fixes #937 by implementing
byte-wide fifo writes from the SH2 side.
2. Various fixups to 32x io access by the 68k. Fixes #1196 and missing
music in Brutal.
3. Follow-up to a previous PR: cram dots were not limited properly, and
line buffer adjustment was crashing 32x in certain cases (as reported in
the discord).
  • Loading branch information
TascoDLX authored Feb 3, 2025
1 parent e99c9c5 commit aef110f
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 65 deletions.
36 changes: 23 additions & 13 deletions ares/md/m32x/io-external.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ auto M32X::readExternalIO(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
//bank set
if(address == 0xa15104) {
data.bit(0,1) = io.romBank;
data.bit(2, 15) = 0;
}

//data request control
Expand Down Expand Up @@ -87,20 +88,23 @@ auto M32X::readExternalIO(n1 upper, n1 lower, n24 address, n16 data) -> n16 {

//PWM left channel pulse width
if(address == 0xa15134) {
data = pwm.lfifoLatch;
data.bit(15) = pwm.lfifo.full();
data.bit(0,12) = pwm.lfifoLatch;
data.bit(14) = pwm.lfifo.empty();
data.bit(15) = pwm.lfifo.full();
}

//PWM right channel pulse width
if(address == 0xa15136) {
data = pwm.rfifoLatch;
data.bit(15) = pwm.rfifo.full();
data.bit(0,12) = pwm.rfifoLatch;
data.bit(14) = pwm.rfifo.empty();
data.bit(15) = pwm.rfifo.full();
}

//PWM mono pulse width
if(address == 0xa15138) {
data = pwm.mfifoLatch;
data.bit(15) = pwm.lfifo.full() || pwm.rfifo.full();
data.bit(0,12) = pwm.mfifoLatch;
data.bit(14) = pwm.lfifo.empty() && pwm.rfifo.empty();
data.bit(15) = pwm.lfifo.full() || pwm.rfifo.full();
}

//bitmap mode
Expand Down Expand Up @@ -190,12 +194,17 @@ auto M32X::writeExternalIO(n1 upper, n1 lower, n24 address, n16 data) -> void {
if(lower) {
dreq.vram = data.bit(0);
dreq.dma = data.bit(1);
dreq.active = data.bit(2);
if(!dreq.active) {
if(dreq.active > data.bit(2)) {
// Night Trap 32X: delay between the last fifo write and flush on disable
if(cpu.active()) cpu.wait(1);
// Note: The current fifo implementation is incorrect (hw uses dual-block fifo)
// but dreq dma works fine as is for normal cases.

dreq.fifo.flush();
shm.dmac.dreq[0] = 0;
shs.dmac.dreq[0] = 0;
}
dreq.active = data.bit(2);
}
}

Expand Down Expand Up @@ -225,11 +234,14 @@ auto M32X::writeExternalIO(n1 upper, n1 lower, n24 address, n16 data) -> void {

//FIFO
if(address == 0xa15112) {
if(!lower || !upper) debug(unusual, "[DREQ] fifo byte write ", lower ? "(odd)" : "(even)");
if(dreq.active && !dreq.fifo.full()) {
dreq.fifo.write(data);
if(!--dreq.length) dreq.active = 0;
shm.dmac.dreq[0] = !dreq.fifo.empty();
shs.dmac.dreq[0] = !dreq.fifo.empty();
} else {
debug(unusual, "[DREQ] missed fifo write");
}
}

Expand All @@ -251,8 +263,6 @@ auto M32X::writeExternalIO(n1 upper, n1 lower, n24 address, n16 data) -> void {
if(lower) {
pwm.lmode = data.bit(0,1);
pwm.rmode = data.bit(2,3);
if(!pwm.lmode) pwm.lsample = 0;
if(!pwm.rmode) pwm.rsample = 0;
pwm.mono = data.bit(4);
//pwm.dreqIRQ = data.bit(7) = readonly;
}
Expand All @@ -270,7 +280,7 @@ auto M32X::writeExternalIO(n1 upper, n1 lower, n24 address, n16 data) -> void {

//PWM left channel pulse width
if(address == 0xa15134) {
if(upper) pwm.lfifoLatch.byte(1) = data.byte(1);
if(upper) pwm.lfifoLatch.bit(8,11) = data.bit(8,11);
if(lower) {
pwm.lfifoLatch.byte(0) = data.byte(0);
pwm.lfifo.write(pwm.lfifoLatch);
Expand All @@ -279,7 +289,7 @@ auto M32X::writeExternalIO(n1 upper, n1 lower, n24 address, n16 data) -> void {

//PWM right channel pulse width
if(address == 0xa15136) {
if(upper) pwm.rfifoLatch.byte(1) = data.byte(1);
if(upper) pwm.rfifoLatch.bit(8,11) = data.bit(8,11);
if(lower) {
pwm.rfifoLatch.byte(0) = data.byte(0);
pwm.rfifo.write(pwm.rfifoLatch);
Expand All @@ -288,7 +298,7 @@ auto M32X::writeExternalIO(n1 upper, n1 lower, n24 address, n16 data) -> void {

//PWM mono pulse width
if(address == 0xa15138) {
if(upper) pwm.mfifoLatch.byte(1) = data.byte(1);
if(upper) pwm.mfifoLatch.bit(8,11) = data.bit(8,11);
if(lower) {
pwm.mfifoLatch.byte(0) = data.byte(0);
pwm.lfifo.write(pwm.mfifoLatch);
Expand Down
40 changes: 25 additions & 15 deletions ares/md/m32x/io-internal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,23 @@ auto M32X::readInternalIO(n1 upper, n1 lower, n29 address, n16 data) -> n16 {

//PWM left channel pulse width
if(address == 0x4034) {
data = pwm.lfifoLatch;
data.bit(15) = pwm.lfifo.full();
data.bit(0,12) = pwm.lfifoLatch;
data.bit(14) = pwm.lfifo.empty();
data.bit(15) = pwm.lfifo.full();
}

//PWM right channel pulse width
if(address == 0x4036) {
data = pwm.rfifoLatch;
data.bit(15) = pwm.rfifo.full();
data.bit(0,12) = pwm.rfifoLatch;
data.bit(14) = pwm.rfifo.empty();
data.bit(15) = pwm.rfifo.full();
}

//PWM mono pulse width
if(address == 0x4038) {
data = pwm.mfifoLatch;
data.bit(15) = pwm.lfifo.full() || pwm.rfifo.full();
data.bit(0,12) = pwm.mfifoLatch;
data.bit(14) = pwm.lfifo.empty() && pwm.rfifo.empty();
data.bit(15) = pwm.lfifo.full() || pwm.rfifo.full();
}

//bitmap mode
Expand Down Expand Up @@ -236,8 +239,6 @@ auto M32X::writeInternalIO(n1 upper, n1 lower, n29 address, n16 data) -> void {
if(lower) {
pwm.lmode = data.bit(0,1);
pwm.rmode = data.bit(2,3);
if(!pwm.lmode) pwm.lsample = 0;
if(!pwm.rmode) pwm.rsample = 0;
pwm.mono = data.bit(4);
pwm.dreqIRQ = data.bit(7);
if(!pwm.dreqIRQ) {
Expand All @@ -259,21 +260,30 @@ auto M32X::writeInternalIO(n1 upper, n1 lower, n29 address, n16 data) -> void {

//PWM left channel pulse width
if(address == 0x4034) {
pwm.lfifoLatch = data;
pwm.lfifo.write(pwm.lfifoLatch);
if(upper) pwm.lfifoLatch.bit(8,11) = data.bit(8,11);
if(lower) {
pwm.lfifoLatch.byte(0) = data.byte(0);
pwm.lfifo.write(pwm.lfifoLatch);
}
}

//PWM right channel pulse width
if(address == 0x4036) {
pwm.rfifoLatch = data;
pwm.rfifo.write(pwm.rfifoLatch);
if(upper) pwm.rfifoLatch.bit(8,11) = data.bit(8,11);
if(lower) {
pwm.rfifoLatch.byte(0) = data.byte(0);
pwm.rfifo.write(pwm.rfifoLatch);
}
}

//PWM mono pulse width
if(address == 0x4038) {
pwm.mfifoLatch = data;
pwm.lfifo.write(pwm.mfifoLatch);
pwm.rfifo.write(pwm.mfifoLatch);
if(upper) pwm.mfifoLatch.bit(8,11) = data.bit(8,11);
if(lower) {
pwm.mfifoLatch.byte(0) = data.byte(0);
pwm.lfifo.write(pwm.mfifoLatch);
pwm.rfifo.write(pwm.mfifoLatch);
}
}

//bitmap mode
Expand Down
2 changes: 2 additions & 0 deletions ares/md/m32x/m32x.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//Mega 32X
#include "nall/dsp/iir/dc-removal.hpp"

struct M32X {
Node::Object node;
Expand Down Expand Up @@ -123,6 +124,7 @@ struct M32X {

struct PWM : Thread {
Node::Audio::Stream stream;
nall::DSP::IIR::DCRemoval dcfilter_l, dcfilter_r;

//pwm.cpp
auto load(Node::Object) -> void;
Expand Down
63 changes: 40 additions & 23 deletions ares/md/m32x/pwm.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
auto M32X::PWM::load(Node::Object parent) -> void {
stream = parent->append<Node::Audio::Stream>("PWM");
stream->setChannels(2);
n12 clocks = cycle - 1;
updateFrequency();
}

Expand All @@ -12,6 +11,7 @@ auto M32X::PWM::unload(Node::Object parent) -> void {

auto M32X::PWM::main() -> void {
n12 clocks = cycle - 1;
if(clocks == 0) clocks++;

//check if cycle rate has changed and update sample rate accordingly
static n12 previousCycle = cycle;
Expand All @@ -20,28 +20,42 @@ auto M32X::PWM::main() -> void {
updateFrequency();
}

if (clocks && (lmode.bit(0) ^ lmode.bit(1) || rmode.bit(0) ^ rmode.bit(1))) {
if (lmode == 1) lsample = lfifo.read();
if (lmode == 2) lsample = rfifo.read();
// skip action if cycle == 1 (illegal setting: "PWM will no longer operate")
if (cycle != 1 && (lmode.bit(0) ^ lmode.bit(1) || rmode.bit(0) ^ rmode.bit(1))) {
lsample = lfifo.read();
rsample = rfifo.read();

if (rmode == 1) rsample = rfifo.read();
if (rmode == 2) rsample = lfifo.read();
if (periods++ == n4(timer - 1)) {
periods = 0;
m32x.shm.irq.pwm.active = 1;
m32x.shs.irq.pwm.active = 1;
m32x.shm.dmac.dreq[1] = dreqIRQ;
m32x.shs.dmac.dreq[1] = dreqIRQ;
}
}

lfifoLatch.bit(14) = lfifo.empty();
rfifoLatch.bit(14) = rfifo.empty();
mfifoLatch.bit(14) = lfifoLatch.bit(14) & rfifoLatch.bit(14);
i12 outL, outR;

if (periods++ == n4(timer - 1)) {
periods = 0;
m32x.shm.irq.pwm.active = 1;
m32x.shs.irq.pwm.active = 1;
m32x.shm.dmac.dreq[1] = dreqIRQ;
m32x.shs.dmac.dreq[1] = dreqIRQ;
}
}
if (lmode == 0) outL = 0;
if (lmode == 1) outL = lsample;
if (lmode == 2) outL = rsample;
if (lmode == 3) outL = 0; // illegal mode

if (rmode == 0) outR = 0;
if (rmode == 1) outR = rsample;
if (rmode == 2) outR = lsample;
if (rmode == 3) outR = 0; // illegal mode

auto left = outL / (f32)clocks;
auto right = outR / (f32)clocks;

// filter DC offset to normalize output and limit popping
left = dcfilter_l.process(left);
right = dcfilter_r.process(right);

auto left = cycle > 0 ? lsample / (f32)cycle : 0;
auto right = cycle > 0 ? rsample / (f32)cycle : 0;
// handle clipping due to improper settings
if(abs(left) > 1.0) left /= abs(left);
if(abs(right) > 1.0) right /= abs(right);

stream->frame(left, right); // Output the frame without the loop, since the sample rate is adjusted dynamically

Expand All @@ -67,13 +81,16 @@ auto M32X::PWM::power(bool reset) -> void {
rsample = 0;
lfifo.flush();
rfifo.flush();
lfifoLatch = 0x4000; // empty
rfifoLatch = 0x4000; // empty
mfifoLatch = 0x4000; // empty
lfifoLatch = 0;
rfifoLatch = 0;
mfifoLatch = 0;
updateFrequency();
dcfilter_l.reset();
dcfilter_r.reset();
}

auto M32X::PWM::updateFrequency() -> void {
n12 clocks = cycle - 1;
stream->setFrequency((system.frequency() * 6) / (7.0 * (2 * (clocks) - 1)));
if(clocks == 0) clocks++;
stream->setFrequency((system.frequency() / 7.0) * 3.0 / clocks);
}
30 changes: 20 additions & 10 deletions ares/md/vdp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ template<bool _h40, bool _refresh> auto VDP::tick() -> void {

if(cram.bus.active) {
vdp.dac.dot(hcounter()*2+1, cram.bus.data);
vdp.dac.dot(hcounter()*2+2, cram.bus.data);

// DAC dot artifacts may be drawn continuously in the case of consectutive writes.
// We can detect for this by checking for an impending CRAM write thru the fifo.
// If refresh or fifo delay occurs, the data will not be updated, resulting in an extended dot.
if(displayEnable() || fifo.slots[0].empty() || fifo.slots[0].target != 3) cram.bus.active = 0;
// Note: we're not currently checking for back-to-back slots when display is enabled.
if(displayEnable() || fifo.slots[0].empty() || fifo.slots[0].target != 3)
cram.bus.active = 0;
else {
if(fifo.slots[0].latency > 1 || vram.refreshing) cram.bus.data = vdp.cram.color(vdp.io.backgroundColor);
vdp.dac.dot(hcounter()*2+2, cram.bus.data);
}
}

// There is reportedly a latch effect when enabling the display, but it might be a fixed delay
Expand Down Expand Up @@ -174,10 +180,12 @@ auto VDP::mainH32() -> void {
state.hcounter = 0;

sprite.begin();
if(dac.pixels) blocks<false, true>();
else blocks<false, false>();

if(Mega32X()) m32x.vdp.scanline(pixels, vcounter());
if(dac.pixels) {
blocks<false, true>();
if(Mega32X()) m32x.vdp.scanline(pixels, vcounter());
} else {
blocks<false, false>();
}

tick<false>(); slot();
tick<false>(); slot();
Expand Down Expand Up @@ -235,10 +243,12 @@ auto VDP::mainH40() -> void {
state.hcounter = 0;

sprite.begin();
if(dac.pixels) blocks<true, true>();
else blocks<true, false>();

if(Mega32X()) m32x.vdp.scanline(pixels, vcounter());
if(dac.pixels) {
blocks<true, true>();
if(Mega32X()) m32x.vdp.scanline(pixels, vcounter());
} else {
blocks<true, false>();
}

tick<true>(); slot();
tick<true>(); slot();
Expand Down
7 changes: 3 additions & 4 deletions ares/md/vdp/vdp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,10 @@ auto VDP::pixels() -> u32* {

if(h40()) {
// H40 mode has slightly shorter lines, so sides are blanked.
// Left side would be 13 wide, but we'll realign to whole pixel (3*4) for sanity.
for(auto n: range(12)) output[ n] = 0b101 << 9;
for(auto n: range(15)) output[1415-15+n] = 0b101 << 9;
for(auto n: range(13)) output[ n] = 0b101 << 9;
for(auto n: range(14)) output[1415-14+n] = 0b101 << 9;

return output+12;
return output+13;
}

return output;
Expand Down

0 comments on commit aef110f

Please sign in to comment.