Update to v102r17 release.

byuu says:

Changelog:

  - GBA: process audio at 2MHz instead of 32KHz¹
  - MD: do not allow the 68K to stop the Z80, unless it has been granted
    bus access first
  - MD: do not reset bus requested/granted signals when the 68K resets
    the Z80
      - the above two fix The Lost Vikings
  - MD: clean up the bus address decoding to be more readable
  - MD: add support for a13000-a130ff (#TIME) region; pass to cartridge
    I/O²
  - MD: emulate SRAM mapping used by >16mbit games; bank mapping used
    by >32mbit games³
  - MD: add 'reset pending' flag so that loading save states won't
    reload 68K PC, SP registers
      - this fixes save state support ... mostly⁴
  - MD: if DMA is not enabled, do not allow CD5 to be set [Cydrak]
      - this fixes in-game graphics for Ristar. Title screen still
        corrupted on first run
  - MD: detect and break sprite lists that form an infinite loop
    [Cydrak]
      - this fixes the emulator from dead-locking on certain games
  - MD: add DC offset to sign DAC PCM samples [Cydrak]
      - this improves audio in Sonic 3
  - MD: 68K TAS has a hardware bug that prevents writing the result back
    to RAM
      - this fixes Gargoyles
  - MD: 68K TRAP should not change CPU interrupt level
      - this fixes Shining Force II, Shining in the Darkness, etc
  - icarus: better SRAM heuristics for Mega Drive games

Todo:

  - need to serialize the new cartridge ramEnable, ramWritable, bank
    variables

¹: so technically, the GBA has its FIFO queue (raw PCM), plus a GB
chipset. The GB audio runs at 2MHz. However, I was being lazy and
running the sequencer 64 times in a row, thus decimating the audio to
32KHz. But simply discarding 63 out of every 64 samples resorts in
muddier sound with more static in it.

However ... increasing the audio thread processing intensity 64-fold,
and requiring heavy-duty three-chain lowpass and highpass filters is not
cheap. For this bump in sound quality, we're eating a loss of about 30%
of previous performance.

Also note that the GB audio emulation in the GBA core still lacks many
of the improvements made to the GB core. I was hoping to complete the GB
enhancements, but it seems like I'm never going to pass blargg's
psychotic edge case tests. So, first I want to clean up the GB audio to
my current coding standards, and then I'll port that over to the GBA,
which should further increase sound quality. At that point, it sound
exceed mGBA's audio quality (due to the ridiculously high sampling rate
and strong-attenuation audio filtering.)

²: word writes are probably not handled correctly ... but games are
only supposed to do byte writes here.

³: the SRAM mapping is used by games like "Story of Thor" and
"Phantasy Star IV." Unfortunately, the former wasn't released in the US
and is region protected. So you'll need to change the NTSU to NTSCJ in
md/system/system.cpp in order to boot it. But it does work nicely now.
The write protection bit is cleared in the game, and then it fails to
write to SRAM (soooooooo many games with SRAM write protection do this),
so for now I've had to disable checking that bit. Phantasy Star IV has a
US release, but sadly the game doesn't boot yet. Hitting some other bug.

The bank mapping is pretty much just for the 40mbit Super Street Fighter
game. It shows the Sega and Capcom logos now, but is hitting yet another
bug and deadlocking.

For now, I emulate the SRAM/bank mapping registers on all cartridges,
and set sane defaults. So long as games don't write to $a130XX, they
should all continue to work. But obviously, we need to get to a point
where higan/icarus can selectively enable these registers on a per-game
basis.

⁴: so, the Mega Drive has various ways to lock a chip until another
chip releases it. The VDP can lock the 68K, the 68K can lock the Z80,
etc. If this happens when you save a state, it'll dead-lock the
emulator. So that's obviously a problem that needs to be fixed. The fix
will be nasty ... basically, bypassing the dead-lock, creating a
miniature, one-instruction-long race condition. Extremely unlikely to
cause any issues in practice (it's only a little worse than the SNES
CPU/SMP desync), but ... there's nothing I can do about it. So you'll
have to take it or leave it. But yeah, for now, save states may lock up
the emulator. I need to add code to break the loops when in the process
of creating a save state still.
This commit is contained in:
Tim Allen 2017-03-10 21:23:29 +11:00
parent 04072b278b
commit 82c58527c3
14 changed files with 132 additions and 75 deletions

View File

@ -12,13 +12,13 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "102.16";
static const string Version = "102.17";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";
//incremented only when serialization format changes
static const string SerializerVersion = "101";
static const string SerializerVersion = "102.17";
namespace Constants {
namespace Colorburst {

View File

@ -18,9 +18,7 @@ auto APU::Enter() -> void {
}
auto APU::main() -> void {
for(auto n : range(64)) {
runsequencer();
}
runsequencer();
int lsample = regs.bias.level - 0x0200;
int rsample = regs.bias.level - 0x0200;
@ -64,7 +62,7 @@ auto APU::main() -> void {
if(cpu.regs.mode == CPU::Registers::Mode::Stop) lsample = 0, rsample = 0;
stream->sample(sclamp<16>(lsample << 6) / 32768.0, sclamp<16>(rsample << 6) / 32768.0); //should be <<5; use <<6 for added volume
step(512);
step(8);
}
auto APU::step(uint clocks) -> void {
@ -74,8 +72,9 @@ auto APU::step(uint clocks) -> void {
auto APU::power() -> void {
create(APU::Enter, 16'777'216);
stream = Emulator::audio.createStream(2, frequency() / 512.0);
//todo: run sequencer at higher frequency; add low-pass filter
stream = Emulator::audio.createStream(2, frequency() / 8.0);
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
square1.power();
square2.power();

View File

@ -10,7 +10,9 @@ auto APU::Enter() -> void {
}
auto APU::main() -> void {
if(!state.enabled) return step(1);
if(!state.enabled) {
return step(1);
}
if(state.nmiLine) {
state.nmiLine = 0; //edge-sensitive
@ -39,7 +41,9 @@ auto APU::setINT(bool value) -> void {
}
auto APU::enable(bool value) -> void {
if(state.enabled && !value) power();
//68K cannot disable the Z80 without bus access
if(!bus->granted() && !value) return;
if(state.enabled && !value) reset();
state.enabled = value;
}
@ -48,12 +52,12 @@ auto APU::power() -> void {
Z80::power();
create(APU::Enter, system.colorburst());
memory::fill(&state, sizeof(State));
}
r.pc = 0x0000;
r.im = 0;
r.iff1 = 0;
r.iff2 = 0;
r.ir = {};
auto APU::reset() -> void {
create(APU::Enter, system.colorburst());
memory::fill(&r, sizeof(Registers));
memory::fill(&state, sizeof(State));
}
}

View File

@ -7,6 +7,7 @@ struct APU : Processor::Z80, Thread {
auto enable(bool) -> void;
auto power() -> void;
auto reset() -> void;
auto setNMI(bool value) -> void;
auto setINT(bool value) -> void;

View File

@ -7,53 +7,59 @@ BusAPU busAPU;
#include "serialization.cpp"
auto BusCPU::readByte(uint24 addr) -> uint16 {
if(addr < 0x400000) return cartridge.read(addr & ~1).byte(!addr.bit(0));
if(addr < 0xa00000) return 0x0000;
if(addr < 0xa10000) return busAPU.granted() ? busAPU.read(addr) : (uint8)0x0000;
if(addr < 0xa11000) return readIO(addr & ~0xff00);
if(addr < 0xa12000) return readIO(addr & ~0x00ff);
if(addr < 0xc00000) return 0x0000;
if(addr < 0xe00000) return vdp.read(addr & ~1).byte(!addr.bit(0));
return ram[addr & 0xffff];
if(addr >= 0x000000 && addr <= 0x3fffff) return cartridge.read(addr & ~1).byte(!addr.bit(0));
if(addr >= 0xa00000 && addr <= 0xa0ffff) return busAPU.granted() ? busAPU.read(addr) : (uint8)0x0000;
if(addr >= 0xa10000 && addr <= 0xa10fff) return readIO(addr & ~0xff00);
if(addr >= 0xa11000 && addr <= 0xa11fff) return readIO(addr & ~0x00ff);
if(addr >= 0xa13000 && addr <= 0xa130ff) return cartridge.readIO(addr);
if(addr >= 0xc00000 && addr <= 0xdfffff) return vdp.read(addr & ~1).byte(!addr.bit(0));
if(addr >= 0xe00000 && addr <= 0xffffff) {
return ram[addr & 0xffff];
}
return 0x0000;
}
auto BusCPU::readWord(uint24 addr) -> uint16 {
if(addr < 0x400000) return cartridge.read(addr);
if(addr < 0xa00000) return 0x0000;
if(addr < 0xa10000) return busAPU.granted() ? busAPU.read(addr) : (uint8)0x0000;
if(addr < 0xa11000) return readIO(addr & ~0xff00) << 0;
if(addr < 0xa12000) return readIO(addr & ~0x00ff) << 8;
if(addr < 0xc00000) return 0x0000;
if(addr < 0xe00000) return vdp.read(addr);
uint16 data = ram[addr + 0 & 0xffff] << 8;
return data | ram[addr + 1 & 0xffff] << 0;
if(addr >= 0x000000 && addr <= 0x3fffff) return cartridge.read(addr);
if(addr >= 0xa00000 && addr <= 0xa0ffff) return busAPU.granted() ? busAPU.read(addr) : (uint8)0x0000;
if(addr >= 0xa10000 && addr <= 0xa10fff) return readIO(addr & ~0xff00) << 0;
if(addr >= 0xa11000 && addr <= 0xa11fff) return readIO(addr & ~0x00ff) << 8;
if(addr >= 0xa13000 && addr <= 0xa130ff) return cartridge.readIO(addr);
if(addr >= 0xc00000 && addr <= 0xdfffff) return vdp.read(addr);
if(addr >= 0xe00000 && addr <= 0xffffff) {
uint16 data = ram[addr + 0 & 0xffff] << 8;
return data | ram[addr + 1 & 0xffff] << 0;
}
return 0x0000;
}
auto BusCPU::writeByte(uint24 addr, uint16 data) -> void {
if(addr < 0x400000) return cartridge.write(addr & ~1, data << 8 | data << 0);
if(addr < 0xa00000) return;
if(addr < 0xa10000) return busAPU.granted() ? busAPU.write(addr, data) : (void)0;
if(addr < 0xa11000) return writeIO(addr & ~0xff00, data);
if(addr < 0xa12000) return writeIO(addr & ~0x00ff, data);
if(addr < 0xc00000) return;
if(addr < 0xc00010) return vdp.write(addr & ~1, data << 8 | data << 0);
if(addr < 0xc00018) return psg.write(data);
if(addr < 0xe00000) return;
ram[addr & 0xffff] = data;
if(addr >= 0x000000 && addr <= 0x3fffff) return cartridge.write(addr & ~1, data << 8 | data << 0);
if(addr >= 0xa00000 && addr <= 0xa0ffff) return busAPU.granted() ? busAPU.write(addr, data) : (void)0;
if(addr >= 0xa10000 && addr <= 0xa10fff) return writeIO(addr & ~0xff00, data);
if(addr >= 0xa11000 && addr <= 0xa11fff) return writeIO(addr & ~0x00ff, data);
if(addr >= 0xa13000 && addr <= 0xa130ff) return cartridge.writeIO(addr, data);
if(addr >= 0xc00000 && addr <= 0xc0000f) return vdp.write(addr & ~1, data << 8 | data << 0);
if(addr >= 0xc00010 && addr <= 0xc00017) return psg.write(data);
if(addr >= 0xe00000 && addr <= 0xffffff) {
ram[addr & 0xffff] = data;
return;
}
}
auto BusCPU::writeWord(uint24 addr, uint16 data) -> void {
if(addr < 0x400000) return cartridge.write(addr, data);
if(addr < 0xa00000) return;
if(addr < 0xa10000) return busAPU.granted() ? busAPU.write(addr, data) : (void)0;
if(addr < 0xa11000) return writeIO(addr & ~0xff00, data >> 0);
if(addr < 0xa12000) return writeIO(addr & ~0x00ff, data >> 8);
if(addr < 0xc00000) return;
if(addr < 0xc00010) return vdp.write(addr, data);
if(addr < 0xc00018) return psg.write(data);
if(addr < 0xe00000) return;
ram[addr + 0 & 0xffff] = data >> 8;
ram[addr + 1 & 0xffff] = data >> 0;
if(addr >= 0x000000 && addr <= 0x3fffff) return cartridge.write(addr, data);
if(addr >= 0xa00000 && addr <= 0xa0ffff) return busAPU.granted() ? busAPU.write(addr, data) : (void)0;
if(addr >= 0xa10000 && addr <= 0xa10fff) return writeIO(addr & ~0xff00, data >> 0);
if(addr >= 0xa11000 && addr <= 0xa11fff) return writeIO(addr & ~0x00ff, data >> 8);
if(addr >= 0xa13000 && addr <= 0xa130ff) return cartridge.writeIO(addr, data);
if(addr >= 0xc00000 && addr <= 0xc0000f) return vdp.write(addr, data);
if(addr >= 0xc00010 && addr <= 0xc00017) return psg.write(data);
if(addr >= 0xe00000 && addr <= 0xffffff) {
ram[addr + 0 & 0xffff] = data >> 8;
ram[addr + 1 & 0xffff] = data >> 0;
return;
}
}
//

View File

@ -66,23 +66,43 @@ auto Cartridge::unload() -> void {
}
auto Cartridge::power() -> void {
ramEnable = 1;
ramWritable = 1;
for(auto n : range(8)) bank[n] = n;
}
auto Cartridge::read(uint24 addr) -> uint16 {
if(addr.bit(21) && ram.size) {
if(addr.bit(21) && ram.size && ramEnable) {
uint16 data = ram.data[addr + 0 & ram.mask] << 8;
return data | ram.data[addr + 1 & ram.mask] << 0;
} else {
addr = bank[addr >> 19 & 7] << 19 | (addr & 0x7ffff);
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) {
//emulating RAM write protect bit breaks some commercial software
if(addr.bit(21) && ram.size && ramEnable /* && ramWritable */) {
ram.data[addr + 0 & ram.mask] = data >> 8;
ram.data[addr + 1 & ram.mask] = data >> 0;
}
}
auto Cartridge::readIO(uint24 addr) -> uint16 {
return 0x0000;
}
auto Cartridge::writeIO(uint24 addr, uint16 data) -> void {
if(addr == 0xa130f1) ramEnable = data.bit(0), ramWritable = data.bit(1);
if(addr == 0xa130f3) bank[1] = data;
if(addr == 0xa130f5) bank[2] = data;
if(addr == 0xa130f7) bank[3] = data;
if(addr == 0xa130f9) bank[4] = data;
if(addr == 0xa130fb) bank[5] = data;
if(addr == 0xa130fd) bank[6] = data;
if(addr == 0xa130ff) bank[7] = data;
}
}

View File

@ -12,6 +12,9 @@ struct Cartridge {
auto read(uint24 addr) -> uint16;
auto write(uint24 addr, uint16 data) -> void;
auto readIO(uint24 addr) -> uint16;
auto writeIO(uint24 addr, uint16 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
@ -30,6 +33,10 @@ struct Cartridge {
Memory rom;
Memory ram;
uint1 ramEnable;
uint1 ramWritable;
uint6 bank[8];
};
extern Cartridge cartridge;

View File

@ -6,23 +6,17 @@ CPU cpu;
#include "serialization.cpp"
auto CPU::Enter() -> void {
cpu.boot();
while(true) scheduler.synchronize(), cpu.main();
}
auto CPU::boot() -> void {
r.a[7] = bus->readWord(0) << 16 | bus->readWord(2) << 0;
r.pc = bus->readWord(4) << 16 | bus->readWord(6) << 0;
}
auto CPU::main() -> void {
#if 0
static file fp;
if(!fp) fp.open({Path::user(), "Desktop/tracer.log"}, file::mode::write);
fp.print(pad(disassemble(r.pc), -60, ' '), " ", disassembleRegisters().replace("\n", " "), "\n");
#endif
if(state.interruptPending) {
if(state.interruptPending.bit((uint)Interrupt::Reset)) {
state.interruptPending.bit((uint)Interrupt::Reset) = 0;
r.a[7] = bus->readWord(0) << 16 | bus->readWord(2) << 0;
r.pc = bus->readWord(4) << 16 | bus->readWord(6) << 0;
}
if(state.interruptPending.bit((uint)Interrupt::HorizontalBlank)) {
if(4 > r.i) {
state.interruptPending.bit((uint)Interrupt::HorizontalBlank) = 0;
@ -77,6 +71,7 @@ auto CPU::power() -> void {
create(CPU::Enter, system.colorburst() * 15.0 / 7.0);
memory::fill(&state, sizeof(State));
state.interruptPending.bit((uint)Interrupt::Reset) = 1;
}
}

View File

@ -2,6 +2,7 @@
struct CPU : Processor::M68K, Thread {
enum class Interrupt : uint {
Reset,
HorizontalBlank,
VerticalBlank,
};
@ -9,7 +10,6 @@ struct CPU : Processor::M68K, Thread {
using Thread::synchronize;
static auto Enter() -> void;
auto boot() -> void;
auto main() -> void;
auto step(uint clocks) -> void override;
auto synchronize() -> void;

View File

@ -130,6 +130,7 @@ auto VDP::writeControlPort(uint16 data) -> void {
io.command.bits(2,5) = data.bits(4,7);
io.address.bits(14,15) = data.bits(0,1);
if(!dma.io.enable) io.command.bit(5) = 0;
if(dma.io.mode == 3) dma.io.wait = false;
return;
}

View File

@ -38,6 +38,7 @@ auto VDP::Sprite::scanline(uint y) -> void {
uint7 link = 0;
uint tiles = 0;
uint count = 0;
do {
auto& object = oam[link];
link = object.link;
@ -48,7 +49,7 @@ auto VDP::Sprite::scanline(uint y) -> void {
objects.append(object);
tiles += object.width >> 3;
} while(link && link < 80 && objects.size() < 20 && tiles < 40);
} while(link && link < 80 && objects.size() < 20 && tiles < 40 && ++count < 80);
}
auto VDP::Sprite::run(uint x, uint y) -> void {

View File

@ -139,7 +139,7 @@ auto YM2612::sample() -> void {
}
int voiceData = sclamp<14>(accumulator) & outMask;
if(dac.enable && (&channel == &channels[5])) voiceData = dac.sample << 6;
if(dac.enable && (&channel == &channels[5])) voiceData = (int)dac.sample - 0x80 << 6;
if(channel.leftEnable ) left += voiceData;
if(channel.rightEnable) right += voiceData;

View File

@ -1121,8 +1121,12 @@ auto M68K::instructionSWAP(DataRegister with) -> void {
}
auto M68K::instructionTAS(EffectiveAddress with) -> void {
auto data = read<Byte, Hold>(with);
write<Byte>(with, data | 0x80);
//auto data = read<Byte, Hold>(with);
//write<Byte>(with, data | 0x80);
//Mega Drive models 1&2 have a bug that prevents TAS write cycle from completing
//this bugged behavior is required for certain software to function correctly
auto data = read<Byte>(with);
r.c = 0;
r.v = 0;
@ -1131,7 +1135,7 @@ auto M68K::instructionTAS(EffectiveAddress with) -> void {
}
auto M68K::instructionTRAP(uint4 vector) -> void {
exception(Exception::Trap, 32 + vector);
exception(Exception::Trap, 32 + vector, r.i);
}
auto M68K::instructionTRAPV() -> void {

View File

@ -9,10 +9,29 @@ struct MegaDriveCartridge {
};
MegaDriveCartridge::MegaDriveCartridge(string location, uint8_t* data, uint size) {
if(size < 0x200) return;
uint32_t ramFrom = 0;
ramFrom |= data[0x01b4] << 24;
ramFrom |= data[0x01b5] << 16;
ramFrom |= data[0x01b6] << 8;
ramFrom |= data[0x01b7] << 0;
ramFrom &= ~1; //for some reason, most games specify 00200001 as RAM start offset
uint32_t ramTo = 0;
ramTo |= data[0x01b8] << 24;
ramTo |= data[0x01b9] << 16;
ramTo |= data[0x01ba] << 8;
ramTo |= data[0x01bb] << 0;
uint32_t ramSize = ramTo - ramFrom;
if(ramSize > 0x020000) ramSize = 0; //sanity check
ramSize = bit::round(ramSize);
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");
if(ramSize)
manifest.append(" ram name=save.ram size=0x", hex(ramSize), " offset=0x", hex(ramFrom), "\n");
manifest.append("\n");
manifest.append("information\n");
manifest.append(" title: ", Location::prefix(location), "\n");