Update to v102r11 release.

byuu says:

Changelog:

  - MD: connected 32KB cartridge RAM up to every Genesis game under 2MB
    loaded¹
  - MS, GG, MD: improved PSG noise channel emulation, hopefully²
  - MS, GG, MD: lowered PSG volume so that the lowpass doesn't clamp
    samples³
  - MD: added read/write handlers for VRAM, VSRAM, CRAM
  - MD: block VRAM copy when CD4 is clear⁴
  - MD: rewrote VRAM fill, VRAM copy to be byte-based⁵
  - MD: VRAM fill byte set should fall through to regular data port
    write handler⁶

¹: the header parsing for backup RAM is really weird. It's spaces
when not used, and seems to be 0x02000001-0x02003fff for the Shining
games. I don't understand why it starts at 0x02000001 instead of
0x02000000. So I'm just forcing every game to have 32KB of RAM for now.
There's also special handling for ROMs > 2MB that also have RAM
(Phantasy Star IV, etc) where there's a toggle to switch between ROM and
RAM. For now, that's not emulated.

I was hoping the Shining games would run after this, but they're still
dead-locking on me :(

²: Cydrak pointed out some flaws in my attempt to implement what he
had. I was having trouble understanding what he meant, so I went back
and read the docs on the sound chip and tried implementing the counter
the way the docs describe. Hopefully I have this right, but I don't know
of any good test ROMs to make sure my noise emulation is correct. The
docs say the shifted-out value goes to the output instead of the low bit
of the LFSR, so I made that change as well.

I think I hear the noise I'm supposed to in Sonic Marble Zone now, but
it seems like it's not correct in Green Hill Zone, adding a bit of an
annoying buzz to the background music. Maybe it sounds better with the
YM2612, but more likely, I still screwed something up :/

³: it's set to 50% range for both cores right now. For the MD, it
will need to be 25% once YM2612 emulation is in.

⁴: technically, this deadlocks the VDP until a hard reset. I could
emulate this, but for now I just don't do the VRAM copy in this case.

⁵: VSRAM fill and CRAM fill not supported in this new mode. They're
technically undocumented, and I don't have good notes on how they work.
I've been seeing conflicting notes on whether the VRAM fill buffer is
8-bits or 16-bits (I chose 8-bits), and on whether you write the low
byte and then high byte of each words, or the high byte and then low
byte (I chose the latter.)

The VRAM copy improvements fix the opening text in Langrisser II, so
that's great.

⁶: Langrisser II sets the transfer length to one less than needed to
fill the background letter tile on the scenario overview screen. After
moving to byte-sized transfers, a black pixel was getting stuck there.
So effectively, VRAM fill length becomes DMA length + 1, and the first
byte uses the data port so it writes a word value instead of just a byte
value. Hopefully this is all correct, although it probably gets way more
complicated with the VDP FIFO.
This commit is contained in:
Tim Allen 2017-02-25 22:11:46 +11:00
parent 68f04c3bb8
commit 1cab2dfeb8
22 changed files with 137 additions and 62 deletions

View File

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

View File

@ -60,19 +60,28 @@ auto Cartridge::save() -> void {
auto Cartridge::unload() -> void { auto Cartridge::unload() -> void {
delete[] rom.data; delete[] rom.data;
delete[] ram.data; delete[] ram.data;
rom = Memory(); rom = {};
ram = Memory(); ram = {};
} }
auto Cartridge::power() -> void { auto Cartridge::power() -> void {
} }
auto Cartridge::read(uint24 addr) -> uint16 { auto Cartridge::read(uint24 addr) -> uint16 {
uint16 data = rom.data[addr + 0 & rom.mask] << 8; if(addr.bit(21) && ram.size) {
return data | rom.data[addr + 1 & rom.mask] << 0; uint16 data = ram.data[addr + 0 & ram.mask] << 8;
return data | ram.data[addr + 1 & ram.mask] << 0;
} else {
uint16 data = rom.data[addr + 0 & rom.mask] << 8;
return data | rom.data[addr + 1 & rom.mask] << 0;
}
} }
auto Cartridge::write(uint24 addr, uint16 data) -> void { auto Cartridge::write(uint24 addr, uint16 data) -> void {
if(addr.bit(21) && ram.size) {
ram.data[addr + 0 & ram.mask] = data >> 8;
ram.data[addr + 1 & ram.mask] = data >> 0;
}
} }
} }

View File

@ -29,6 +29,7 @@ auto PSG::write(uint8 data) -> void {
case 4: { case 4: {
if(l) tone2.pitch.bits(0,3) = data.bits(0,3); if(l) tone2.pitch.bits(0,3) = data.bits(0,3);
else tone2.pitch.bits(4,9) = data.bits(0,5); else tone2.pitch.bits(4,9) = data.bits(0,5);
noise.pitch = tone2.pitch;
break; break;
} }

View File

@ -1,23 +1,22 @@
auto PSG::Noise::run() -> void { auto PSG::Noise::run() -> void {
auto latch = clock; if(--counter) return;
counter++; if(rate == 0) counter = 0x10;
if(rate == 0) output ^= !counter.bits(0,3); if(rate == 1) counter = 0x20;
if(rate == 1) output ^= !counter.bits(0,4); if(rate == 2) counter = 0x40;
if(rate == 2) output ^= !counter.bits(0,5); if(rate == 3) counter = pitch; //shared with tone2
if(rate == 3) output ^= psg.tone2.clock;
if(!latch && clock) { if(clock ^= 1) { //0->1 transition
output = lfsr.bit(0);
auto eor = enable ? ~lfsr >> 3 : 0; auto eor = enable ? ~lfsr >> 3 : 0;
lfsr = (lfsr ^ eor) << 15 | lfsr >> 1; lfsr = (lfsr ^ eor) << 15 | lfsr >> 1;
} }
output = lfsr.bit(0);
} }
auto PSG::Noise::power() -> void { auto PSG::Noise::power() -> void {
volume = ~0; volume = ~0;
counter = 0; counter = 0;
pitch = 0;
enable = 0; enable = 0;
rate = 0; rate = 0;
lfsr = 0x8000; lfsr = 0x8000;

View File

@ -43,7 +43,7 @@ auto PSG::power() -> void {
select = 0; select = 0;
lowpass = 0; lowpass = 0;
for(auto n : range(15)) { for(auto n : range(15)) {
levels[n] = 0x3fff * pow(2, n * -2.0 / 6.0) + 0.5; levels[n] = 0x2000 * pow(2, n * -2.0 / 6.0) + 0.5;
} }
levels[15] = 0; levels[15] = 0;

View File

@ -21,7 +21,6 @@ private:
uint4 volume; uint4 volume;
uint10 counter; uint10 counter;
uint10 pitch; uint10 pitch;
uint1 clock;
uint1 output; uint1 output;
} tone0, tone1, tone2; } tone0, tone1, tone2;
@ -31,7 +30,8 @@ private:
auto power() -> void; auto power() -> void;
uint4 volume; uint4 volume;
uint6 counter; uint10 counter;
uint10 pitch;
uint1 enable; uint1 enable;
uint2 rate; uint2 rate;
uint16 lfsr; uint16 lfsr;

View File

@ -1,8 +1,6 @@
auto PSG::Tone::run() -> void { auto PSG::Tone::run() -> void {
clock = 0;
if(--counter) return; if(--counter) return;
clock = 1;
counter = pitch; counter = pitch;
output ^= 1; output ^= 1;
} }
@ -11,6 +9,5 @@ auto PSG::Tone::power() -> void {
volume = ~0; volume = ~0;
counter = 0; counter = 0;
pitch = 0; pitch = 0;
clock = 0;
output = 0; output = 0;
} }

View File

@ -13,7 +13,7 @@ auto VDP::Background::updateHorizontalScroll(uint y) -> void {
address += (y & mask[io.horizontalScrollMode]) << 1; address += (y & mask[io.horizontalScrollMode]) << 1;
address += id == ID::PlaneB; address += id == ID::PlaneB;
state.horizontalScroll = vdp.vram[address].bits(0,9); state.horizontalScroll = vdp.vram.read(address).bits(0,9);
} }
auto VDP::Background::updateVerticalScroll(uint x, uint y) -> void { auto VDP::Background::updateVerticalScroll(uint x, uint y) -> void {
@ -22,7 +22,7 @@ auto VDP::Background::updateVerticalScroll(uint x, uint y) -> void {
auto address = (x >> 4 & 0 - io.verticalScrollMode) << 1; auto address = (x >> 4 & 0 - io.verticalScrollMode) << 1;
address += id == ID::PlaneB; address += id == ID::PlaneB;
state.verticalScroll = vdp.vsram[address]; state.verticalScroll = vdp.vsram.read(address);
} }
auto VDP::Background::nametableAddress() -> uint15 { auto VDP::Background::nametableAddress() -> uint15 {
@ -62,13 +62,13 @@ auto VDP::Background::run(uint x, uint y) -> void {
auto address = nametableAddress(); auto address = nametableAddress();
address += (tileY * width + tileX) & 0x0fff; address += (tileY * width + tileX) & 0x0fff;
uint16 tileAttributes = vdp.vram[address]; uint16 tileAttributes = vdp.vram.read(address);
uint15 tileAddress = tileAttributes.bits(0,10) << 4; uint15 tileAddress = tileAttributes.bits(0,10) << 4;
uint pixelX = (x & 7) ^ (tileAttributes.bit(11) ? 7 : 0); uint pixelX = (x & 7) ^ (tileAttributes.bit(11) ? 7 : 0);
uint pixelY = (y & 7) ^ (tileAttributes.bit(12) ? 7 : 0); uint pixelY = (y & 7) ^ (tileAttributes.bit(12) ? 7 : 0);
tileAddress += pixelY << 1 | pixelX >> 2; tileAddress += pixelY << 1 | pixelX >> 2;
uint16 tileData = vdp.vram[tileAddress]; uint16 tileData = vdp.vram.read(tileAddress);
uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2); uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2);
if(color) { if(color) {
output.color = tileAttributes.bits(13,14) << 4 | color; output.color = tileAttributes.bits(13,14) << 4 | color;

View File

@ -1,9 +1,10 @@
auto VDP::DMA::run() -> void { auto VDP::DMA::run() -> void {
if(!io.enable || io.wait) return; if(!io.enable || io.wait) return;
if(!vdp.io.command.bit(5)) return;
if(!vdp.io.command.bit(5)) return;
if(io.mode <= 1) return load(); if(io.mode <= 1) return load();
if(io.mode == 2) return fill(); if(io.mode == 2) return fill();
if(!vdp.io.command.bit(4)) return;
if(io.mode == 3) return copy(); if(io.mode == 3) return copy();
} }
@ -20,21 +21,26 @@ auto VDP::DMA::load() -> void {
} }
} }
//todo: supposedly, this can also write to VSRAM and CRAM (undocumented)
auto VDP::DMA::fill() -> void { auto VDP::DMA::fill() -> void {
auto data = io.fill; if(vdp.io.command.bits(0,3) == 1) {
vdp.writeDataPort(data << 8 | data << 0); vdp.vram.writeByte(vdp.io.address, io.fill);
}
io.source.bits(0,15)++; io.source.bits(0,15)++;
vdp.io.address += vdp.io.dataIncrement;
if(--io.length == 0) { if(--io.length == 0) {
vdp.io.command.bit(5) = 0; vdp.io.command.bit(5) = 0;
} }
} }
//note: this can only copy to VRAM
auto VDP::DMA::copy() -> void { auto VDP::DMA::copy() -> void {
auto data = vdp.vram[io.source.bits(0,14)]; auto data = vdp.vram.readByte(io.source);
vdp.writeDataPort(data); vdp.vram.writeByte(vdp.io.address, data);
io.source.bits(0,15)++; io.source.bits(0,15)++;
vdp.io.address += vdp.io.dataIncrement;
if(--io.length == 0) { if(--io.length == 0) {
vdp.io.command.bit(5) = 0; vdp.io.command.bit(5) = 0;
} }

View File

@ -45,7 +45,7 @@ auto VDP::readDataPort() -> uint16 {
//VRAM read //VRAM read
if(io.command.bits(0,3) == 0) { if(io.command.bits(0,3) == 0) {
auto address = io.address.bits(1,15); auto address = io.address.bits(1,15);
auto data = vram[address]; auto data = vram.read(address);
io.address += io.dataIncrement; io.address += io.dataIncrement;
return data; return data;
} }
@ -53,8 +53,7 @@ auto VDP::readDataPort() -> uint16 {
//VSRAM read //VSRAM read
if(io.command.bits(0,3) == 4) { if(io.command.bits(0,3) == 4) {
auto address = io.address.bits(1,6); auto address = io.address.bits(1,6);
if(address >= 40) return 0x0000; auto data = vsram.read(address);
auto data = vsram[address];
io.address += io.dataIncrement; io.address += io.dataIncrement;
return data; return data;
} }
@ -62,7 +61,7 @@ auto VDP::readDataPort() -> uint16 {
//CRAM read //CRAM read
if(io.command.bits(0,3) == 8) { if(io.command.bits(0,3) == 8) {
auto address = io.address.bits(1,6); auto address = io.address.bits(1,6);
auto data = cram[address]; auto data = cram.read(address);
io.address += io.dataIncrement; io.address += io.dataIncrement;
return data.bits(0,2) << 1 | data.bits(3,5) << 2 | data.bits(6,8) << 3; return data.bits(0,2) << 1 | data.bits(3,5) << 2 | data.bits(6,8) << 3;
} }
@ -76,17 +75,15 @@ auto VDP::writeDataPort(uint16 data) -> void {
//DMA VRAM fill //DMA VRAM fill
if(dma.io.wait.lower()) { if(dma.io.wait.lower()) {
dma.io.fill = data >> 8; dma.io.fill = data >> 8;
return; //falls through to memory write
//causes extra transfer to occur on VRAM fill operations
} }
//VRAM write //VRAM write
if(io.command.bits(0,3) == 1) { if(io.command.bits(0,3) == 1) {
auto address = io.address.bits(1,15); auto address = io.address.bits(1,15);
if(io.address.bit(0)) data = data >> 8 | data << 8; if(io.address.bit(0)) data = data >> 8 | data << 8;
vram[address] = data; vram.write(address, data);
if(address >= sprite.io.attributeAddress && address < sprite.io.attributeAddress + 320) {
sprite.write(address, data);
}
io.address += io.dataIncrement; io.address += io.dataIncrement;
return; return;
} }
@ -94,9 +91,8 @@ auto VDP::writeDataPort(uint16 data) -> void {
//VSRAM write //VSRAM write
if(io.command.bits(0,3) == 5) { if(io.command.bits(0,3) == 5) {
auto address = io.address.bits(1,6); auto address = io.address.bits(1,6);
if(address >= 40) return;
//data format: ---- --yy yyyy yyyy //data format: ---- --yy yyyy yyyy
vsram[address] = data.bits(0,9); vsram.write(address, data.bits(0,9));
io.address += io.dataIncrement; io.address += io.dataIncrement;
return; return;
} }
@ -105,7 +101,7 @@ auto VDP::writeDataPort(uint16 data) -> void {
if(io.command.bits(0,3) == 3) { if(io.command.bits(0,3) == 3) {
auto address = io.address.bits(1,6); auto address = io.address.bits(1,6);
//data format: ---- bbb- ggg- rrr- //data format: ---- bbb- ggg- rrr-
cram[address] = data.bits(1,3) << 0 | data.bits(5,7) << 3 | data.bits(9,11) << 6; cram.write(address, data.bits(1,3) << 0 | data.bits(5,7) << 3 | data.bits(9,11) << 6);
io.address += io.dataIncrement; io.address += io.dataIncrement;
return; return;
} }

38
higan/md/vdp/memory.cpp Normal file
View File

@ -0,0 +1,38 @@
auto VDP::VRAM::read(uint15 address) const -> uint16 {
return memory[address];
}
auto VDP::VRAM::write(uint15 address, uint16 data) -> void {
memory[address] = data;
if(address < vdp.sprite.io.attributeAddress) return;
if(address > vdp.sprite.io.attributeAddress + 319) return;
vdp.sprite.write(address, data);
}
auto VDP::VRAM::readByte(uint16 address) const -> uint8 {
return read(address >> 1).byte(!address.bit(0));
}
auto VDP::VRAM::writeByte(uint16 address, uint8 data) -> void {
auto word = read(address >> 1);
word.byte(!address.bit(0)) = data;
write(address >> 1, word);
}
auto VDP::VSRAM::read(uint6 address) const -> uint10 {
if(address >= 40) return 0x0000;
return memory[address];
}
auto VDP::VSRAM::write(uint6 address, uint10 data) -> void {
if(address >= 40) return;
memory[address] = data;
}
auto VDP::CRAM::read(uint6 address) const -> uint9 {
return memory[address];
}
auto VDP::CRAM::write(uint6 address, uint9 data) -> void {
memory[address] = data;
}

View File

@ -31,7 +31,7 @@ auto VDP::run() -> void {
if(planeA.output.priority) if(auto color = planeA.output.color) output = color; if(planeA.output.priority) if(auto color = planeA.output.color) output = color;
if(sprite.output.priority) if(auto color = sprite.output.color) output = color; if(sprite.output.priority) if(auto color = sprite.output.color) output = color;
outputPixel(cram[output]); outputPixel(cram.read(output));
state.x++; state.x++;
} }

View File

@ -72,7 +72,7 @@ auto VDP::Sprite::run(uint x, uint y) -> void {
uint pixelY = objectY & 7; uint pixelY = objectY & 7;
tileAddress += pixelY << 1 | pixelX >> 2; tileAddress += pixelY << 1 | pixelX >> 2;
uint16 tileData = vdp.vram[tileAddress]; uint16 tileData = vdp.vram.read(tileAddress);
uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2); uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2);
if(color) { if(color) {
output.color = o.palette << 4 | color; output.color = o.palette << 4 | color;

View File

@ -3,6 +3,7 @@
namespace MegaDrive { namespace MegaDrive {
VDP vdp; VDP vdp;
#include "memory.cpp"
#include "io.cpp" #include "io.cpp"
#include "dma.cpp" #include "dma.cpp"
#include "render.cpp" #include "render.cpp"

View File

@ -131,9 +131,38 @@ private:
auto screenWidth() const -> uint { return io.tileWidth ? 320 : 256; } auto screenWidth() const -> uint { return io.tileWidth ? 320 : 256; }
auto screenHeight() const -> uint { return io.overscan ? 240 : 224; } auto screenHeight() const -> uint { return io.overscan ? 240 : 224; }
uint16 vram[32768]; //video RAM
uint9 cram[64]; struct VRAM {
uint10 vsram[40]; //memory.cpp
auto read(uint15 address) const -> uint16;
auto write(uint15 address, uint16 data) -> void;
auto readByte(uint16 address) const -> uint8;
auto writeByte(uint16 address, uint8 data) -> void;
private:
uint16 memory[32768];
} vram;
//vertical scroll RAM
struct VSRAM {
//memory.cpp
auto read(uint6 address) const -> uint10;
auto write(uint6 address, uint10 data) -> void;
private:
uint10 memory[40];
} vsram;
//color RAM
struct CRAM {
//memory.cpp
auto read(uint6 address) const -> uint9;
auto write(uint6 address, uint9 data) -> void;
private:
uint9 memory[64];
} cram;
struct IO { struct IO {
//command //command

View File

@ -29,6 +29,7 @@ auto PSG::write(uint8 data) -> void {
case 4: { case 4: {
if(l) tone2.pitch.bits(0,3) = data.bits(0,3); if(l) tone2.pitch.bits(0,3) = data.bits(0,3);
else tone2.pitch.bits(4,9) = data.bits(0,5); else tone2.pitch.bits(4,9) = data.bits(0,5);
noise.pitch = tone2.pitch;
break; break;
} }

View File

@ -1,23 +1,22 @@
auto PSG::Noise::run() -> void { auto PSG::Noise::run() -> void {
auto latch = clock; if(--counter) return;
counter++; if(rate == 0) counter = 0x10;
if(rate == 0) output ^= !counter.bits(0,3); if(rate == 1) counter = 0x20;
if(rate == 1) output ^= !counter.bits(0,4); if(rate == 2) counter = 0x40;
if(rate == 2) output ^= !counter.bits(0,5); if(rate == 3) counter = pitch; //shared with tone2
if(rate == 3) output ^= psg.tone2.clock;
if(!latch && clock) { if(clock ^= 1) { //0->1 transition
output = lfsr.bit(0);
auto eor = enable ? ~lfsr >> 3 : 0; auto eor = enable ? ~lfsr >> 3 : 0;
lfsr = (lfsr ^ eor) << 15 | lfsr >> 1; lfsr = (lfsr ^ eor) << 15 | lfsr >> 1;
} }
output = lfsr.bit(0);
} }
auto PSG::Noise::power() -> void { auto PSG::Noise::power() -> void {
volume = ~0; volume = ~0;
counter = 0; counter = 0;
pitch = 0;
enable = 0; enable = 0;
rate = 0; rate = 0;
lfsr = 0x8000; lfsr = 0x8000;

View File

@ -57,7 +57,7 @@ auto PSG::power() -> void {
lowpassLeft = 0; lowpassLeft = 0;
lowpassRight = 0; lowpassRight = 0;
for(auto n : range(15)) { for(auto n : range(15)) {
levels[n] = 0x3fff * pow(2, n * -2.0 / 6.0) + 0.5; levels[n] = 0x2000 * pow(2, n * -2.0 / 6.0) + 0.5;
} }
levels[15] = 0; levels[15] = 0;

View File

@ -28,7 +28,6 @@ private:
uint4 volume; uint4 volume;
uint10 counter; uint10 counter;
uint10 pitch; uint10 pitch;
uint1 clock;
uint1 output; uint1 output;
uint1 left; uint1 left;
@ -44,7 +43,8 @@ private:
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
uint4 volume; uint4 volume;
uint6 counter; uint10 counter;
uint10 pitch;
uint1 enable; uint1 enable;
uint2 rate; uint2 rate;
uint16 lfsr; uint16 lfsr;

View File

@ -16,7 +16,6 @@ auto PSG::Tone::serialize(serializer& s) -> void {
s.integer(volume); s.integer(volume);
s.integer(counter); s.integer(counter);
s.integer(pitch); s.integer(pitch);
s.integer(clock);
s.integer(output); s.integer(output);
s.integer(left); s.integer(left);
@ -26,6 +25,7 @@ auto PSG::Tone::serialize(serializer& s) -> void {
auto PSG::Noise::serialize(serializer& s) -> void { auto PSG::Noise::serialize(serializer& s) -> void {
s.integer(volume); s.integer(volume);
s.integer(counter); s.integer(counter);
s.integer(pitch);
s.integer(enable); s.integer(enable);
s.integer(rate); s.integer(rate);
s.integer(lfsr); s.integer(lfsr);

View File

@ -1,8 +1,6 @@
auto PSG::Tone::run() -> void { auto PSG::Tone::run() -> void {
clock = 0;
if(--counter) return; if(--counter) return;
clock = 1;
counter = pitch; counter = pitch;
output ^= 1; output ^= 1;
} }
@ -11,7 +9,6 @@ auto PSG::Tone::power() -> void {
volume = ~0; volume = ~0;
counter = 0; counter = 0;
pitch = 0; pitch = 0;
clock = 0;
output = 0; output = 0;
left = 1; left = 1;

View File

@ -11,6 +11,8 @@ struct MegaDriveCartridge {
MegaDriveCartridge::MegaDriveCartridge(string location, uint8_t* data, uint size) { MegaDriveCartridge::MegaDriveCartridge(string location, uint8_t* data, uint size) {
manifest.append("board\n"); manifest.append("board\n");
manifest.append(" rom name=program.rom size=0x", hex(size), "\n"); manifest.append(" rom name=program.rom size=0x", hex(size), "\n");
if(size <= 0x200000)
manifest.append(" ram name=save.ram size=0x8000\n");
manifest.append("\n"); manifest.append("\n");
manifest.append("information\n"); manifest.append("information\n");
manifest.append(" title: ", Location::prefix(location), "\n"); manifest.append(" title: ", Location::prefix(location), "\n");