Update to v102r08 release.

byuu says:

Changelog:

  - PCE: restructured VCE, VDCs to run one scanline at a time
  - PCE: bound VDCs to 1365x262 timing (in order to decouple the VDCs
    from the VCE)
  - PCE: the two changes above allow save states to function; also
    grants a minor speed boost
  - PCE: added cheat code support (uses 21-bit bus addressing; compare
    byte will be useful here)
  - 68K: fixed `mov *,ccr` to read two bytes instead of one [Cydrak]
  - Z80: emulated /BUSREQ, /BUSACK; allows 68K to suspend the Z80
    [Cydrak]
  - MD: emulated the Z80 executing instructions [Cydrak]
  - MD: emulated Z80 interrupts (triggered during each Vblank period)
    [Cydrak]
  - MD: emulated Z80 memory map [Cydrak]
  - MD: added stubs for PSG, YM2612 accesses [Cydrak]
  - MD: improved bus emulation [Cydrak]

The PCE core is pretty much ready to go. The only major feature missing
is FM modulation.

The Mega Drive improvements let us start to see the splash screens for
Langrisser II, Shining Force, Shining in the Darkness. I was hoping I
could get them in-game, but no such luck. My Z80 implementation is
probably flawed in some way ... now that I think about it, I believe I
missed the BusAPU::reset() check for having been granted access to the
Z80 first. But I doubt that's the problem.

Next step is to implement Cydrak's PSG core into the Master System
emulator. Once that's in, I'm going to add save states and cheat code
support to the Master System core.

Next, I'll add the PSG core into the Mega Drive. Then I'll add the
'easy' PCM part of the YM2612. Then the rest of the beastly YM2612 core.
Then finally, cap things off with save state and cheat code support.

Should be nearing a new release at that point.
This commit is contained in:
Tim Allen 2017-02-20 19:13:10 +11:00
parent 7c9b78b7bb
commit d76c0c7e82
24 changed files with 262 additions and 93 deletions

View File

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

View File

@ -9,7 +9,19 @@ auto APU::Enter() -> void {
}
auto APU::main() -> void {
step(1);
if(!state.enabled) return step(1);
if(state.nmiLine) {
state.nmiLine = 0; //edge-sensitive
irq(0, 0x0066, 0xff);
}
if(state.intLine) {
//level-sensitive
irq(1, 0x0038, 0xff);
}
instruction();
}
auto APU::step(uint clocks) -> void {
@ -17,10 +29,30 @@ auto APU::step(uint clocks) -> void {
synchronize(cpu);
}
auto APU::setNMI(bool value) -> void {
state.nmiLine = value;
}
auto APU::setINT(bool value) -> void {
state.intLine = value;
}
auto APU::enable(bool value) -> void {
if(state.enabled && !value) power();
state.enabled = value;
}
auto APU::power() -> void {
Z80::bus = &busAPU;
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 = {};
}
}

View File

@ -5,7 +5,18 @@ struct APU : Processor::Z80, Thread {
auto main() -> void;
auto step(uint clocks) -> void;
auto enable(bool) -> void;
auto power() -> void;
auto setNMI(bool value) -> void;
auto setINT(bool value) -> void;
private:
struct State {
boolean enabled;
boolean nmiLine;
boolean intLine;
} state;
};
extern APU apu;

View File

@ -8,8 +8,9 @@ BusAPU busAPU;
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 0x0000;
if(addr < 0xa10020) return readIO(addr);
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];
@ -18,8 +19,9 @@ auto BusCPU::readByte(uint24 addr) -> uint16 {
auto BusCPU::readWord(uint24 addr) -> uint16 {
if(addr < 0x400000) return cartridge.read(addr);
if(addr < 0xa00000) return 0x0000;
if(addr < 0xa10000) return 0x0000;
if(addr < 0xa10020) return readIO(addr);
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;
@ -29,20 +31,26 @@ auto BusCPU::readWord(uint24 addr) -> uint16 {
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;
if(addr < 0xa10020) return writeIO(addr, data);
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 < 0xe00000) return vdp.write(addr & ~1, data << 8 | data << 0);
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;
}
auto BusCPU::writeWord(uint24 addr, uint16 data) -> void {
if(addr < 0x400000) return cartridge.write(addr, data);
if(addr < 0xa00000) return;
if(addr < 0xa10000) return;
if(addr < 0xa10020) return writeIO(addr, data);
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 < 0xe00000) return vdp.write(addr, data);
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;
}
@ -65,6 +73,8 @@ auto BusCPU::readIO(uint24 addr) -> uint16 {
case 0xa10008: return peripherals.controllerPort1->readControl();
case 0xa1000a: return peripherals.controllerPort2->readControl();
case 0xa1000c: return peripherals.extensionPort->readControl();
case 0xa11000: return !busAPU.granted();
}
return 0x0000;
@ -79,22 +89,63 @@ auto BusCPU::writeIO(uint24 addr, uint16 data) -> void {
case 0xa10008: return peripherals.controllerPort1->writeControl(data);
case 0xa1000a: return peripherals.controllerPort2->writeControl(data);
case 0xa1000c: return peripherals.extensionPort->writeControl(data);
case 0xa11100: return busAPU.request(data.bit(0));
case 0xa11200: return apu.enable(data.bit(0));
}
}
//
auto BusAPU::read(uint16 addr) -> uint8 {
return 0x00;
if((addr & 0xe000) == 0x0000) {
return ram[addr];
}
if(addr == 0x4000) return ym2612.readStatus();
if(addr == 0x4001) return ym2612.readStatus();
if(addr == 0x4002) return ym2612.readStatus();
if(addr == 0x4003) return ym2612.readStatus();
if((addr & 0x8000) == 0x8000) {
return cartridge.read(bank << 15 | (addr & 0x7ffe)).byte(!addr.bit(0));
}
}
auto BusAPU::write(uint16 addr, uint8 data) -> void {
if((addr & 0xe000) == 0x0000) {
ram[addr] = data;
return;
}
if(addr == 0x4000) return ym2612.writeAddress(0 << 8 | data);
if(addr == 0x4001) return ym2612.writeData(data);
if(addr == 0x4002) return ym2612.writeAddress(1 << 8 | data);
if(addr == 0x4003) return ym2612.writeData(data);
if(addr == 0x6000) {
//1-bit shift register
bank = data.bit(0) << 8 | bank >> 1;
return;
}
if(addr == 0x7f11) return psg.write(data);
if(addr == 0x7f13) return psg.write(data);
if(addr == 0x7f15) return psg.write(data);
if(addr == 0x7f17) return psg.write(data);
if((addr & 0x8000) == 0x8000) {
//todo: do 8-bit writes mirror to 16-bits?
return cartridge.write(bank << 15 | (addr & 0x7ffe), data << 8 | data << 0);
}
}
//unused on Mega Drive
auto BusAPU::in(uint8 addr) -> uint8 {
return 0x00;
}
//unused on Mega Drive
auto BusAPU::out(uint8 addr, uint8 data) -> void {
}

View File

@ -14,8 +14,13 @@ private:
struct BusAPU : Processor::Z80::Bus {
auto read(uint16 addr) -> uint8 override;
auto write(uint16 addr, uint8 data) -> void override;
auto in(uint8 addr) -> uint8 override;
auto out(uint8 addr, uint8 data) -> void override;
private:
uint8 ram[8 * 1024];
uint9 bank;
};
extern BusCPU busCPU;

2
higan/md/psg/io.cpp Normal file
View File

@ -0,0 +1,2 @@
auto PSG::write(uint8 data) -> void {
}

View File

@ -3,13 +3,14 @@
namespace MegaDrive {
PSG psg;
#include "io.cpp"
auto PSG::Enter() -> void {
while(true) scheduler.synchronize(), psg.main();
}
auto PSG::main() -> void {
stream->sample(0.0, 0.0);
stream->sample(0.0);
step(1);
}
@ -19,8 +20,8 @@ auto PSG::step(uint clocks) -> void {
}
auto PSG::power() -> void {
create(PSG::Enter, 52'000); //system.colorburst());
stream = Emulator::audio.createStream(2, 52'000.0);
create(PSG::Enter, system.colorburst() / 16.0);
stream = Emulator::audio.createStream(1, system.colorburst() / 16.0);
}
}

View File

@ -8,6 +8,9 @@ struct PSG : Thread {
auto step(uint clocks) -> void;
auto power() -> void;
//io.cpp
auto write(uint8 data) -> void;
};
extern PSG psg;

View File

@ -33,8 +33,10 @@ auto VDP::main() -> void {
if(io.verticalBlankInterruptEnable) {
cpu.raise(CPU::Interrupt::VerticalBlank);
}
apu.setINT(true);
}
step(1710);
apu.setINT(false);
}
}
@ -43,6 +45,7 @@ auto VDP::step(uint clocks) -> void {
dmaRun();
Thread::step(1);
synchronize(cpu);
synchronize(apu);
}
}

9
higan/md/ym2612/io.cpp Normal file
View File

@ -0,0 +1,9 @@
auto YM2612::readStatus() -> uint8 {
return nall::random();
}
auto YM2612::writeAddress(uint9 data) -> void {
}
auto YM2612::writeData(uint8 data) -> void {
}

View File

@ -3,13 +3,15 @@
namespace MegaDrive {
YM2612 ym2612;
#include "io.cpp"
auto YM2612::Enter() -> void {
while(true) scheduler.synchronize(), ym2612.main();
}
auto YM2612::main() -> void {
step(1);
stream->sample(0.0, 0.0);
step(144);
}
auto YM2612::step(uint clocks) -> void {
@ -19,6 +21,7 @@ auto YM2612::step(uint clocks) -> void {
auto YM2612::power() -> void {
create(YM2612::Enter, system.colorburst() * 15.0 / 7.0);
stream = Emulator::audio.createStream(2, system.colorburst() * 15.0 / 7.0 / 144.0);
}
}

View File

@ -1,11 +1,18 @@
//Yamaha YM2612
struct YM2612 : Thread {
shared_pointer<Emulator::Stream> stream;
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto power() -> void;
//io.cpp
auto readStatus() -> uint8;
auto writeAddress(uint9 data) -> void;
auto writeData(uint8 data) -> void;
};
extern YM2612 ym2612;

View File

@ -13,6 +13,7 @@ struct CPU : Processor::HuC6280, Thread {
//io.cpp
auto read(uint8 bank, uint13 addr) -> uint8 override;
auto read_(uint8 bank, uint13 addr) -> uint8;
auto write(uint8 bank, uint13 addr, uint8 data) -> void override;
auto store(uint2 addr, uint8 data) -> void override;

View File

@ -1,4 +1,10 @@
auto CPU::read(uint8 bank, uint13 addr) -> uint8 {
auto data = read_(bank, addr);
if(auto result = cheat.find(bank << 13 | addr, data)) data = result();
return data;
}
auto CPU::read_(uint8 bank, uint13 addr) -> uint8 {
//$00-7f HuCard
if(!bank.bit(7)) {
return cartridge.read(bank << 13 | addr);

View File

@ -11,7 +11,7 @@ Interface::Interface() {
information.overscan = true;
information.capability.states = true;
information.capability.cheats = false;
information.capability.cheats = true;
Port controllerPort{ID::Port::Controller, "Controller Port"};
@ -116,6 +116,10 @@ auto Interface::unserialize(serializer& s) -> bool {
return system.unserialize(s);
}
auto Interface::cheatSet(const string_vector& list) -> void {
cheat.assign(list);
}
auto Interface::cap(const string& name) -> bool {
return false;
}

View File

@ -43,6 +43,8 @@ struct Interface : Emulator::Interface {
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheatSet(const string_vector&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;

View File

@ -6,6 +6,7 @@
#include <emulator/emulator.hpp>
#include <emulator/thread.hpp>
#include <emulator/scheduler.hpp>
#include <emulator/cheat.hpp>
#include <processor/huc6280/huc6280.hpp>
@ -13,7 +14,9 @@ namespace PCEngine {
#define platform Emulator::platform
namespace File = Emulator::File;
using Scheduler = Emulator::Scheduler;
using Cheat = Emulator::Cheat;
extern Scheduler scheduler;
extern Cheat cheat;
struct Thread : Emulator::Thread {
auto create(auto (*entrypoint)() -> void, double frequency) -> void {

View File

@ -4,6 +4,7 @@ namespace PCEngine {
System system;
Scheduler scheduler;
Cheat cheat;
#include "peripherals.cpp"
#include "serialization.cpp"

View File

@ -12,38 +12,36 @@ auto VCE::Enter() -> void {
}
auto VCE::main() -> void {
vdc0.frame();
vdc1.frame();
timing.vclock = 0;
while(timing.vclock < 262) {
vdc0.scanline();
vdc1.scanline();
timing.hclock = 0;
auto output = buffer + 1365 * timing.vclock;
while(timing.hclock < 1360) {
uint9 color;
if(Model::PCEngine()) color = vdc0.bus();
if(Model::SuperGrafx()) color = vpc.bus(timing.hclock);
color = cram.read(color);
//*output++ = color;
//step(1);
if(clock() >= 2) *output++ = color;
if(clock() >= 2) *output++ = color;
if(clock() >= 3) *output++ = color;
if(clock() >= 4) *output++ = color;
step(clock());
}
step(1365 - timing.hclock);
timing.vclock++;
if(timing.vclock == 0) {
vdc0.frame();
vdc1.frame();
}
scheduler.exit(Scheduler::Event::Frame);
vdc0.scanline();
vdc1.scanline();
timing.hclock = 0;
auto output = buffer + 1365 * timing.vclock;
while(timing.hclock < 1360) {
uint9 color;
if(Model::PCEngine()) color = vdc0.bus();
if(Model::SuperGrafx()) color = vpc.bus(timing.hclock);
color = cram.read(color);
if(clock() >= 2) *output++ = color;
if(clock() >= 2) *output++ = color;
if(clock() >= 3) *output++ = color;
if(clock() >= 4) *output++ = color;
step(clock());
}
step(1365 - timing.hclock);
if(++timing.vclock == 262) {
timing.vclock = 0;
scheduler.exit(Scheduler::Event::Frame);
}
}
auto VCE::step(uint clocks) -> void {

View File

@ -23,61 +23,58 @@ auto VDC::Enter() -> void {
auto VDC::main() -> void {
if(Model::PCEngine() && vdc1.active()) return step(frequency());
timing.vpulse = false;
timing.vclock = 0;
timing.voffset = 0;
timing.vstart = max((uint8)2, timing.verticalDisplayStart) - 2;
timing.vlength = min(242, timing.verticalDisplayLength + 1);
if(timing.vclock == 0) {
timing.voffset = 0;
timing.vstart = max((uint8)2, timing.verticalDisplayStart) - 2;
timing.vlength = min(242, timing.verticalDisplayLength + 1);
}
while(!timing.vpulse) {
timing.hpulse = false;
timing.hclock = 0;
timing.hoffset = 0;
timing.hstart = timing.horizontalDisplayStart;
timing.hlength = (timing.horizontalDisplayLength + 1) << 3;
timing.hclock = 0;
timing.hoffset = 0;
timing.hstart = timing.horizontalDisplayStart;
timing.hlength = (timing.horizontalDisplayLength + 1) << 3;
if(timing.vclock >= timing.vstart && timing.voffset < timing.vlength) {
background.scanline(timing.voffset);
sprite.scanline(timing.voffset);
if(timing.vclock >= timing.vstart && timing.voffset < timing.vlength) {
background.scanline(timing.voffset);
sprite.scanline(timing.voffset);
step(timing.hstart);
step(timing.hstart);
while(timing.hoffset < timing.hlength) {
data = 0;
background.run(timing.hoffset, timing.voffset);
sprite.run(timing.hoffset, timing.voffset);
if(sprite.color && sprite.priority) {
data = 1 << 8 | sprite.palette << 4 | sprite.color << 0;
} else if(background.color) {
data = 0 << 8 | background.palette << 4 | background.color << 0;
} else if(sprite.color) {
data = 1 << 8 | sprite.palette << 4 | sprite.color << 0;
}
step(vce.clock());
timing.hoffset++;
}
while(timing.hclock < 1360 && timing.hoffset < timing.hlength) {
data = 0;
if(timing.voffset == io.lineCoincidence - 64) {
irq.raise(IRQ::Line::LineCoincidence);
background.run(timing.hoffset, timing.voffset);
sprite.run(timing.hoffset, timing.voffset);
if(sprite.color && sprite.priority) {
data = 1 << 8 | sprite.palette << 4 | sprite.color << 0;
} else if(background.color) {
data = 0 << 8 | background.palette << 4 | background.color << 0;
} else if(sprite.color) {
data = 1 << 8 | sprite.palette << 4 | sprite.color << 0;
}
while(!timing.hpulse) step(1);
timing.vclock++;
timing.voffset++;
} else {
data = 0;
while(!timing.hpulse) step(1);
timing.vclock++;
step(vce.clock());
timing.hoffset++;
}
if(timing.vclock == timing.vstart + timing.vlength) {
irq.raise(IRQ::Line::Vblank);
dma.satbStart();
if(timing.voffset == io.lineCoincidence - 64) {
irq.raise(IRQ::Line::LineCoincidence);
}
timing.voffset++;
}
data = 0;
step(1365 - timing.hclock);
if(timing.vclock == timing.vstart + timing.vlength) {
irq.raise(IRQ::Line::Vblank);
dma.satbStart();
}
if(++timing.vclock == 262) {
timing.vclock = 0;
}
}

View File

@ -706,7 +706,7 @@ auto M68K::instructionMOVE_FROM_SR(EffectiveAddress ea) -> void {
}
auto M68K::instructionMOVE_TO_CCR(EffectiveAddress ea) -> void {
auto data = read<Byte>(ea);
auto data = read<Word>(ea);
writeCCR(data);
}

View File

@ -1,13 +1,25 @@
auto Z80::yield() -> void {
//freeze Z80, allow external access until relinquished
if(bus->requested()) {
bus->grant(true);
while(bus->requested()) step(1);
bus->grant(false);
}
}
auto Z80::wait(uint clocks) -> void {
yield();
step(clocks);
}
auto Z80::opcode() -> uint8 {
yield();
step(4);
return bus->read(r.pc++);
}
auto Z80::operand() -> uint8 {
yield();
step(3);
return bus->read(r.pc++);
}
@ -35,21 +47,25 @@ auto Z80::displace(uint16& x) -> uint16 {
}
auto Z80::read(uint16 addr) -> uint8 {
yield();
step(3);
return bus->read(addr);
}
auto Z80::write(uint16 addr, uint8 data) -> void {
yield();
step(3);
return bus->write(addr, data);
}
auto Z80::in(uint8 addr) -> uint8 {
yield();
step(4);
return bus->in(addr);
}
auto Z80::out(uint8 addr, uint8 data) -> void {
yield();
step(4);
return bus->out(addr, data);
}

View File

@ -12,6 +12,8 @@ namespace Processor {
auto Z80::power() -> void {
memory::fill(&r, sizeof(Registers));
r.hlp = &r.hl;
bus->request(false);
bus->grant(false);
}
auto Z80::irq(bool maskable, uint16 pc, uint8 extbus) -> bool {

View File

@ -6,10 +6,21 @@ namespace Processor {
struct Z80 {
struct Bus {
virtual auto requested() -> bool { return _requested; }
virtual auto granted() -> bool { return _granted; }
virtual auto request(bool value) -> void { _requested = value; }
virtual auto grant(bool value) -> void { _granted = value; }
virtual auto read(uint16 addr) -> uint8 = 0;
virtual auto write(uint16 addr, uint8 data) -> void = 0;
virtual auto in(uint8 addr) -> uint8 = 0;
virtual auto out(uint8 addr, uint8 data) -> void = 0;
private:
bool _requested;
bool _granted;
};
virtual auto step(uint clocks) -> void = 0;
@ -21,6 +32,7 @@ struct Z80 {
auto parity(uint8) const -> bool;
//memory.cpp
auto yield() -> void;
auto wait(uint clocks = 1) -> void;
auto opcode() -> uint8;
auto operand() -> uint8;