From 1cab2dfeb805ead45d15f4c6867e693d7f23fdd2 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Sat, 25 Feb 2017 22:11:46 +1100 Subject: [PATCH] Update to v102r11 release. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- higan/emulator/emulator.hpp | 2 +- higan/md/cartridge/cartridge.cpp | 17 ++++++++++---- higan/md/psg/io.cpp | 1 + higan/md/psg/noise.cpp | 17 +++++++------- higan/md/psg/psg.cpp | 2 +- higan/md/psg/psg.hpp | 4 ++-- higan/md/psg/tone.cpp | 3 --- higan/md/vdp/background.cpp | 8 +++---- higan/md/vdp/dma.cpp | 16 +++++++++----- higan/md/vdp/io.cpp | 20 +++++++---------- higan/md/vdp/memory.cpp | 38 ++++++++++++++++++++++++++++++++ higan/md/vdp/render.cpp | 2 +- higan/md/vdp/sprite.cpp | 2 +- higan/md/vdp/vdp.cpp | 1 + higan/md/vdp/vdp.hpp | 35 ++++++++++++++++++++++++++--- higan/ms/psg/io.cpp | 1 + higan/ms/psg/noise.cpp | 17 +++++++------- higan/ms/psg/psg.cpp | 2 +- higan/ms/psg/psg.hpp | 4 ++-- higan/ms/psg/serialization.cpp | 2 +- higan/ms/psg/tone.cpp | 3 --- icarus/heuristics/mega-drive.cpp | 2 ++ 22 files changed, 137 insertions(+), 62 deletions(-) create mode 100644 higan/md/vdp/memory.cpp diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 28f520ef..34cf8ee2 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -12,7 +12,7 @@ using namespace nall; namespace Emulator { 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 License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/md/cartridge/cartridge.cpp b/higan/md/cartridge/cartridge.cpp index fcf57089..7a1729e0 100644 --- a/higan/md/cartridge/cartridge.cpp +++ b/higan/md/cartridge/cartridge.cpp @@ -60,19 +60,28 @@ auto Cartridge::save() -> void { auto Cartridge::unload() -> void { delete[] rom.data; delete[] ram.data; - rom = Memory(); - ram = Memory(); + rom = {}; + ram = {}; } auto Cartridge::power() -> void { } auto Cartridge::read(uint24 addr) -> uint16 { - uint16 data = rom.data[addr + 0 & rom.mask] << 8; - return data | rom.data[addr + 1 & rom.mask] << 0; + if(addr.bit(21) && ram.size) { + 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 { + if(addr.bit(21) && ram.size) { + ram.data[addr + 0 & ram.mask] = data >> 8; + ram.data[addr + 1 & ram.mask] = data >> 0; + } } } diff --git a/higan/md/psg/io.cpp b/higan/md/psg/io.cpp index d2c711a7..4752b908 100644 --- a/higan/md/psg/io.cpp +++ b/higan/md/psg/io.cpp @@ -29,6 +29,7 @@ auto PSG::write(uint8 data) -> void { case 4: { if(l) tone2.pitch.bits(0,3) = data.bits(0,3); else tone2.pitch.bits(4,9) = data.bits(0,5); + noise.pitch = tone2.pitch; break; } diff --git a/higan/md/psg/noise.cpp b/higan/md/psg/noise.cpp index d416e30d..978ecc89 100644 --- a/higan/md/psg/noise.cpp +++ b/higan/md/psg/noise.cpp @@ -1,23 +1,22 @@ auto PSG::Noise::run() -> void { - auto latch = clock; + if(--counter) return; - counter++; - if(rate == 0) output ^= !counter.bits(0,3); - if(rate == 1) output ^= !counter.bits(0,4); - if(rate == 2) output ^= !counter.bits(0,5); - if(rate == 3) output ^= psg.tone2.clock; + if(rate == 0) counter = 0x10; + if(rate == 1) counter = 0x20; + if(rate == 2) counter = 0x40; + if(rate == 3) counter = pitch; //shared with tone2 - if(!latch && clock) { + if(clock ^= 1) { //0->1 transition + output = lfsr.bit(0); auto eor = enable ? ~lfsr >> 3 : 0; lfsr = (lfsr ^ eor) << 15 | lfsr >> 1; } - - output = lfsr.bit(0); } auto PSG::Noise::power() -> void { volume = ~0; counter = 0; + pitch = 0; enable = 0; rate = 0; lfsr = 0x8000; diff --git a/higan/md/psg/psg.cpp b/higan/md/psg/psg.cpp index b57331ef..8e5a3da7 100644 --- a/higan/md/psg/psg.cpp +++ b/higan/md/psg/psg.cpp @@ -43,7 +43,7 @@ auto PSG::power() -> void { select = 0; lowpass = 0; 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; diff --git a/higan/md/psg/psg.hpp b/higan/md/psg/psg.hpp index c9e802f8..8dcffda9 100644 --- a/higan/md/psg/psg.hpp +++ b/higan/md/psg/psg.hpp @@ -21,7 +21,6 @@ private: uint4 volume; uint10 counter; uint10 pitch; - uint1 clock; uint1 output; } tone0, tone1, tone2; @@ -31,7 +30,8 @@ private: auto power() -> void; uint4 volume; - uint6 counter; + uint10 counter; + uint10 pitch; uint1 enable; uint2 rate; uint16 lfsr; diff --git a/higan/md/psg/tone.cpp b/higan/md/psg/tone.cpp index 052b1831..bae975d4 100644 --- a/higan/md/psg/tone.cpp +++ b/higan/md/psg/tone.cpp @@ -1,8 +1,6 @@ auto PSG::Tone::run() -> void { - clock = 0; if(--counter) return; - clock = 1; counter = pitch; output ^= 1; } @@ -11,6 +9,5 @@ auto PSG::Tone::power() -> void { volume = ~0; counter = 0; pitch = 0; - clock = 0; output = 0; } diff --git a/higan/md/vdp/background.cpp b/higan/md/vdp/background.cpp index 47f12fc7..7d1661d9 100644 --- a/higan/md/vdp/background.cpp +++ b/higan/md/vdp/background.cpp @@ -13,7 +13,7 @@ auto VDP::Background::updateHorizontalScroll(uint y) -> void { address += (y & mask[io.horizontalScrollMode]) << 1; 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 { @@ -22,7 +22,7 @@ auto VDP::Background::updateVerticalScroll(uint x, uint y) -> void { auto address = (x >> 4 & 0 - io.verticalScrollMode) << 1; address += id == ID::PlaneB; - state.verticalScroll = vdp.vsram[address]; + state.verticalScroll = vdp.vsram.read(address); } auto VDP::Background::nametableAddress() -> uint15 { @@ -62,13 +62,13 @@ auto VDP::Background::run(uint x, uint y) -> void { auto address = nametableAddress(); address += (tileY * width + tileX) & 0x0fff; - uint16 tileAttributes = vdp.vram[address]; + uint16 tileAttributes = vdp.vram.read(address); uint15 tileAddress = tileAttributes.bits(0,10) << 4; uint pixelX = (x & 7) ^ (tileAttributes.bit(11) ? 7 : 0); uint pixelY = (y & 7) ^ (tileAttributes.bit(12) ? 7 : 0); tileAddress += pixelY << 1 | pixelX >> 2; - uint16 tileData = vdp.vram[tileAddress]; + uint16 tileData = vdp.vram.read(tileAddress); uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2); if(color) { output.color = tileAttributes.bits(13,14) << 4 | color; diff --git a/higan/md/vdp/dma.cpp b/higan/md/vdp/dma.cpp index 082ebde1..73341728 100644 --- a/higan/md/vdp/dma.cpp +++ b/higan/md/vdp/dma.cpp @@ -1,9 +1,10 @@ auto VDP::DMA::run() -> void { 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 == 2) return fill(); + if(!vdp.io.command.bit(4)) return; 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 data = io.fill; - vdp.writeDataPort(data << 8 | data << 0); + if(vdp.io.command.bits(0,3) == 1) { + vdp.vram.writeByte(vdp.io.address, io.fill); + } io.source.bits(0,15)++; + vdp.io.address += vdp.io.dataIncrement; if(--io.length == 0) { vdp.io.command.bit(5) = 0; } } +//note: this can only copy to VRAM auto VDP::DMA::copy() -> void { - auto data = vdp.vram[io.source.bits(0,14)]; - vdp.writeDataPort(data); + auto data = vdp.vram.readByte(io.source); + vdp.vram.writeByte(vdp.io.address, data); io.source.bits(0,15)++; + vdp.io.address += vdp.io.dataIncrement; if(--io.length == 0) { vdp.io.command.bit(5) = 0; } diff --git a/higan/md/vdp/io.cpp b/higan/md/vdp/io.cpp index 28192d1f..6e5daab7 100644 --- a/higan/md/vdp/io.cpp +++ b/higan/md/vdp/io.cpp @@ -45,7 +45,7 @@ auto VDP::readDataPort() -> uint16 { //VRAM read if(io.command.bits(0,3) == 0) { auto address = io.address.bits(1,15); - auto data = vram[address]; + auto data = vram.read(address); io.address += io.dataIncrement; return data; } @@ -53,8 +53,7 @@ auto VDP::readDataPort() -> uint16 { //VSRAM read if(io.command.bits(0,3) == 4) { auto address = io.address.bits(1,6); - if(address >= 40) return 0x0000; - auto data = vsram[address]; + auto data = vsram.read(address); io.address += io.dataIncrement; return data; } @@ -62,7 +61,7 @@ auto VDP::readDataPort() -> uint16 { //CRAM read if(io.command.bits(0,3) == 8) { auto address = io.address.bits(1,6); - auto data = cram[address]; + auto data = cram.read(address); io.address += io.dataIncrement; 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 if(dma.io.wait.lower()) { dma.io.fill = data >> 8; - return; + //falls through to memory write + //causes extra transfer to occur on VRAM fill operations } //VRAM write if(io.command.bits(0,3) == 1) { auto address = io.address.bits(1,15); if(io.address.bit(0)) data = data >> 8 | data << 8; - vram[address] = data; - if(address >= sprite.io.attributeAddress && address < sprite.io.attributeAddress + 320) { - sprite.write(address, data); - } + vram.write(address, data); io.address += io.dataIncrement; return; } @@ -94,9 +91,8 @@ auto VDP::writeDataPort(uint16 data) -> void { //VSRAM write if(io.command.bits(0,3) == 5) { auto address = io.address.bits(1,6); - if(address >= 40) return; //data format: ---- --yy yyyy yyyy - vsram[address] = data.bits(0,9); + vsram.write(address, data.bits(0,9)); io.address += io.dataIncrement; return; } @@ -105,7 +101,7 @@ auto VDP::writeDataPort(uint16 data) -> void { if(io.command.bits(0,3) == 3) { auto address = io.address.bits(1,6); //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; return; } diff --git a/higan/md/vdp/memory.cpp b/higan/md/vdp/memory.cpp new file mode 100644 index 00000000..aad8c0b2 --- /dev/null +++ b/higan/md/vdp/memory.cpp @@ -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; +} diff --git a/higan/md/vdp/render.cpp b/higan/md/vdp/render.cpp index ea456bb0..eaecb283 100644 --- a/higan/md/vdp/render.cpp +++ b/higan/md/vdp/render.cpp @@ -31,7 +31,7 @@ auto VDP::run() -> void { if(planeA.output.priority) if(auto color = planeA.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++; } diff --git a/higan/md/vdp/sprite.cpp b/higan/md/vdp/sprite.cpp index 7743379e..e7d575b9 100644 --- a/higan/md/vdp/sprite.cpp +++ b/higan/md/vdp/sprite.cpp @@ -72,7 +72,7 @@ auto VDP::Sprite::run(uint x, uint y) -> void { uint pixelY = objectY & 7; tileAddress += pixelY << 1 | pixelX >> 2; - uint16 tileData = vdp.vram[tileAddress]; + uint16 tileData = vdp.vram.read(tileAddress); uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2); if(color) { output.color = o.palette << 4 | color; diff --git a/higan/md/vdp/vdp.cpp b/higan/md/vdp/vdp.cpp index 38f6ec67..52d7e5e1 100644 --- a/higan/md/vdp/vdp.cpp +++ b/higan/md/vdp/vdp.cpp @@ -3,6 +3,7 @@ namespace MegaDrive { VDP vdp; +#include "memory.cpp" #include "io.cpp" #include "dma.cpp" #include "render.cpp" diff --git a/higan/md/vdp/vdp.hpp b/higan/md/vdp/vdp.hpp index 2c1eb505..9ade08be 100644 --- a/higan/md/vdp/vdp.hpp +++ b/higan/md/vdp/vdp.hpp @@ -131,9 +131,38 @@ private: auto screenWidth() const -> uint { return io.tileWidth ? 320 : 256; } auto screenHeight() const -> uint { return io.overscan ? 240 : 224; } - uint16 vram[32768]; - uint9 cram[64]; - uint10 vsram[40]; + //video RAM + struct VRAM { + //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 { //command diff --git a/higan/ms/psg/io.cpp b/higan/ms/psg/io.cpp index 0612cf6c..487aa1d1 100644 --- a/higan/ms/psg/io.cpp +++ b/higan/ms/psg/io.cpp @@ -29,6 +29,7 @@ auto PSG::write(uint8 data) -> void { case 4: { if(l) tone2.pitch.bits(0,3) = data.bits(0,3); else tone2.pitch.bits(4,9) = data.bits(0,5); + noise.pitch = tone2.pitch; break; } diff --git a/higan/ms/psg/noise.cpp b/higan/ms/psg/noise.cpp index 08675afb..8b22453e 100644 --- a/higan/ms/psg/noise.cpp +++ b/higan/ms/psg/noise.cpp @@ -1,23 +1,22 @@ auto PSG::Noise::run() -> void { - auto latch = clock; + if(--counter) return; - counter++; - if(rate == 0) output ^= !counter.bits(0,3); - if(rate == 1) output ^= !counter.bits(0,4); - if(rate == 2) output ^= !counter.bits(0,5); - if(rate == 3) output ^= psg.tone2.clock; + if(rate == 0) counter = 0x10; + if(rate == 1) counter = 0x20; + if(rate == 2) counter = 0x40; + if(rate == 3) counter = pitch; //shared with tone2 - if(!latch && clock) { + if(clock ^= 1) { //0->1 transition + output = lfsr.bit(0); auto eor = enable ? ~lfsr >> 3 : 0; lfsr = (lfsr ^ eor) << 15 | lfsr >> 1; } - - output = lfsr.bit(0); } auto PSG::Noise::power() -> void { volume = ~0; counter = 0; + pitch = 0; enable = 0; rate = 0; lfsr = 0x8000; diff --git a/higan/ms/psg/psg.cpp b/higan/ms/psg/psg.cpp index d800d4ae..5b7adc52 100644 --- a/higan/ms/psg/psg.cpp +++ b/higan/ms/psg/psg.cpp @@ -57,7 +57,7 @@ auto PSG::power() -> void { lowpassLeft = 0; lowpassRight = 0; 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; diff --git a/higan/ms/psg/psg.hpp b/higan/ms/psg/psg.hpp index dfe41db3..8f885ff0 100644 --- a/higan/ms/psg/psg.hpp +++ b/higan/ms/psg/psg.hpp @@ -28,7 +28,6 @@ private: uint4 volume; uint10 counter; uint10 pitch; - uint1 clock; uint1 output; uint1 left; @@ -44,7 +43,8 @@ private: auto serialize(serializer&) -> void; uint4 volume; - uint6 counter; + uint10 counter; + uint10 pitch; uint1 enable; uint2 rate; uint16 lfsr; diff --git a/higan/ms/psg/serialization.cpp b/higan/ms/psg/serialization.cpp index b71592c0..9c2adf28 100644 --- a/higan/ms/psg/serialization.cpp +++ b/higan/ms/psg/serialization.cpp @@ -16,7 +16,6 @@ auto PSG::Tone::serialize(serializer& s) -> void { s.integer(volume); s.integer(counter); s.integer(pitch); - s.integer(clock); s.integer(output); s.integer(left); @@ -26,6 +25,7 @@ auto PSG::Tone::serialize(serializer& s) -> void { auto PSG::Noise::serialize(serializer& s) -> void { s.integer(volume); s.integer(counter); + s.integer(pitch); s.integer(enable); s.integer(rate); s.integer(lfsr); diff --git a/higan/ms/psg/tone.cpp b/higan/ms/psg/tone.cpp index 35877323..552f703b 100644 --- a/higan/ms/psg/tone.cpp +++ b/higan/ms/psg/tone.cpp @@ -1,8 +1,6 @@ auto PSG::Tone::run() -> void { - clock = 0; if(--counter) return; - clock = 1; counter = pitch; output ^= 1; } @@ -11,7 +9,6 @@ auto PSG::Tone::power() -> void { volume = ~0; counter = 0; pitch = 0; - clock = 0; output = 0; left = 1; diff --git a/icarus/heuristics/mega-drive.cpp b/icarus/heuristics/mega-drive.cpp index e3a98dc3..a66fddbc 100644 --- a/icarus/heuristics/mega-drive.cpp +++ b/icarus/heuristics/mega-drive.cpp @@ -11,6 +11,8 @@ struct MegaDriveCartridge { MegaDriveCartridge::MegaDriveCartridge(string location, uint8_t* data, uint size) { manifest.append("board\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("information\n"); manifest.append(" title: ", Location::prefix(location), "\n");