Update to v082r22 release.

byuu says:

Mappers are now optionally threaded.
Fixed up MMC3 emulation, SMB3 and MM3-6 are all fully playable. However,
many unusual variants of this chip are not supported still.
Added UNROM+UOROM for Contra and MM1, allowing all six MM games to play
now.
Added VRC6 with sound emulation, because I wanted to get audio mixing in
place.

Chose VRC6 because it has Esper Dream 2, which is an absolutely amazing
game that everyone should play :D
The game didn't use sawtooth, and I didn't test any other VRC6 games, so
hopefully that is emulated passably well enough.
This commit is contained in:
Tim Allen 2011-09-26 21:27:06 +10:00
parent 82a17ac0f5
commit 046e478d86
19 changed files with 643 additions and 76 deletions

View File

@ -47,7 +47,7 @@ void APU::main() {
clock_frame_counter_divider();
signed output = rectangle_dac[rectangle_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output];
interface->audioSample(output);
interface->audioSample(output + cartridge_sample);
tick();
}
@ -62,6 +62,10 @@ void APU::set_irq_line() {
cpu.set_irq_apu_line(frame.irq_pending || dmc.irq_pending);
}
void APU::set_sample(int16 sample) {
cartridge_sample = sample;
}
void APU::power() {
for(unsigned n = 0; n < 2; n++) {
rectangle[n].sweep.shift = 0;
@ -144,6 +148,8 @@ void APU::reset() {
frame.divider = 1;
enabled_channels = 0;
cartridge_sample = 0;
set_irq_line();
}

View File

@ -3,6 +3,7 @@ struct APU : Processor {
void main();
void tick();
void set_irq_line();
void set_sample(int16 sample);
void power();
void reset();
@ -139,6 +140,7 @@ struct APU : Processor {
void clock_frame_counter_divider();
uint8 enabled_channels;
int16 cartridge_sample;
int16 rectangle_dac[32];
int16 dmc_triangle_noise_dac[128][16][16];

View File

@ -8,6 +8,7 @@ void APU::serialize(serializer &s) {
frame.serialize(s);
s.integer(enabled_channels);
s.integer(cartridge_sample);
}
void APU::Envelope::serialize(serializer &s) {

View File

@ -4,6 +4,14 @@ namespace NES {
Cartridge cartridge;
void Cartridge::Main() {
cartridge.main();
}
void Cartridge::main() {
mapper->main();
}
void Cartridge::load(const string &xml, const uint8_t *data, unsigned size) {
rom_size = size - 16;
rom_data = new uint8[rom_size];
@ -31,10 +39,13 @@ void Cartridge::load(const string &xml, const uint8_t *data, unsigned size) {
switch(mapperNumber) {
default : mapper = &Mapper::none; break;
case 1: mapper = &Mapper::mmc1; break;
case 2: mapper = &Mapper::uorom; break;
case 3: mapper = &Mapper::cnrom; break;
case 4: mapper = &Mapper::mmc3; break;
case 7: mapper = &Mapper::aorom; break;
case 16: mapper = &Mapper::bandaiFCG; break;
case 24: mapper = &Mapper::vrc6; Mapper::vrc6.abus_swap = 0; break;
case 26: mapper = &Mapper::vrc6; Mapper::vrc6.abus_swap = 1; break;
}
system.load();
@ -61,10 +72,12 @@ uint8* Cartridge::ram_data() {
}
void Cartridge::power() {
create(Cartridge::Main, 21477272);
mapper->power();
}
void Cartridge::reset() {
create(Cartridge::Main, 21477272);
mapper->reset();
}

View File

@ -1,4 +1,7 @@
struct Cartridge : property<Cartridge> {
struct Cartridge : Processor, property<Cartridge> {
static void Main();
void main();
void load(const string &xml, const uint8_t *data, unsigned size);
void unload();

View File

@ -40,6 +40,9 @@ void CPU::add_clocks(unsigned clocks) {
ppu.clock -= clocks;
if(ppu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(ppu.thread);
cartridge.clock -= clocks;
if(cartridge.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cartridge.thread);
}
void CPU::power() {

View File

@ -1,8 +1,23 @@
BandaiFCG bandaiFCG;
uint8 BandaiFCG::prg_read(uint16 addr) {
clock();
void BandaiFCG::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_counter_enable) {
if(--irq_counter == 0xffff) {
cpu.set_irq_line(1);
irq_counter_enable = false;
}
}
tick();
}
}
uint8 BandaiFCG::prg_read(uint16 addr) {
if(addr >= 0x8000 && addr <= 0xbfff) {
unsigned rom_addr = (prg_bank << 14) | (addr & 0x3fff);
return cartridge.prg_data[mirror(rom_addr, cartridge.prg_size)];
@ -17,8 +32,6 @@ uint8 BandaiFCG::prg_read(uint16 addr) {
}
void BandaiFCG::prg_write(uint16 addr, uint8 data) {
clock();
if(addr >= 0x6000) {
addr &= 0x0f;
switch(addr) {
@ -95,15 +108,6 @@ unsigned BandaiFCG::ciram_addr(unsigned addr) const {
throw;
}
void BandaiFCG::clock() {
if(irq_counter_enable) {
if(--irq_counter == 0xffff) {
cpu.set_irq_line(1);
irq_counter_enable = false;
}
}
}
//
void BandaiFCG::serialize(serializer &s) {

View File

@ -1,4 +1,6 @@
struct BandaiFCG : Mapper {
void main();
uint8 prg_read(uint16 addr);
void prg_write(uint16 addr, uint8 data);
@ -15,7 +17,6 @@ struct BandaiFCG : Mapper {
private:
unsigned ciram_addr(unsigned addr) const;
void clock();
uint8 chr_bank[8];
uint8 prg_bank;

View File

@ -1,8 +1,24 @@
#include <nes/nes.hpp>
namespace NES {
namespace Mapper {
void Mapper::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
cartridge.clock += 12 * 4095;
tick();
}
}
void Mapper::tick() {
cartridge.clock += 12;
if(cartridge.clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
}
unsigned Mapper::mirror(unsigned addr, unsigned size) const {
unsigned base = 0;
if(size) {
@ -43,6 +59,8 @@ namespace Mapper {
#include "cnrom/cnrom.cpp"
#include "mmc1/mmc1.cpp"
#include "mmc3/mmc3.cpp"
}
#include "uorom/uorom.cpp"
#include "vrc6/vrc6.cpp"
}
}

View File

@ -1,5 +1,8 @@
namespace Mapper {
struct Mapper {
virtual void main();
virtual void tick();
unsigned mirror(unsigned addr, unsigned size) const;
uint8& prg_data(unsigned addr);
uint8& chr_data(unsigned addr);
@ -28,4 +31,6 @@ namespace Mapper {
#include "cnrom/cnrom.hpp"
#include "mmc1/mmc1.hpp"
#include "mmc3/mmc3.hpp"
#include "uorom/uorom.hpp"
#include "vrc6/vrc6.hpp"
}

View File

@ -1,24 +1,34 @@
MMC3 mmc3;
void MMC3::clock_irq_test(uint16 addr) {
if(!(last_chr_addr & 0x1000) && (addr & 0x1000)) {
void MMC3::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_delay) irq_delay--;
cpu.set_irq_line(irq_line);
tick();
}
}
void MMC3::irq_test(uint16 addr) {
if(!(chr_abus & 0x1000) && (addr & 0x1000)) {
if(irq_delay == 0) {
if(irq_counter == 0) {
irq_counter = irq_latch;
irq_line = irq_enable;
cpu.set_irq_line(irq_line);
} else if(--irq_counter == 0) {
if(irq_enable) irq_line = 1;
}
irq_counter--;
}
irq_delay = 5;
irq_delay = 6;
}
last_chr_addr = addr;
chr_abus = addr;
}
unsigned MMC3::prg_addr(uint16 addr) {
if((addr & 0xe000) == 0x8000) {
if((bank_select & 0x40) == 1) return (0x3e << 13) | (addr & 0x1fff);
if((bank_select & 0x40) != 0) return (0x3e << 13) | (addr & 0x1fff);
return (prg_bank[0] << 13) | (addr & 0x1fff);
}
@ -39,8 +49,6 @@ unsigned MMC3::prg_addr(uint16 addr) {
}
uint8 MMC3::prg_read(uint16 addr) {
if(irq_delay) irq_delay--;
if((addr & 0xe000) == 0x6000) { //$6000-7fff
if(prg_ram_enable) {
return prg_ram[addr & 0x1fff];
@ -55,8 +63,6 @@ uint8 MMC3::prg_read(uint16 addr) {
}
void MMC3::prg_write(uint16 addr, uint8 data) {
if(irq_delay) irq_delay--;
if((addr & 0xe000) == 0x6000) { //$6000-7fff
if(prg_ram_enable && prg_ram_write_protect == false) {
prg_ram[addr & 0x1fff] = data;
@ -133,26 +139,25 @@ unsigned MMC3::chr_addr(uint16 addr) {
}
uint8 MMC3::chr_read(uint16 addr) {
clock_irq_test(addr);
irq_test(addr);
return chr_data(chr_addr(addr));
}
void MMC3::chr_write(uint16 addr, uint8 data) {
clock_irq_test(addr);
last_chr_addr = addr;
irq_test(addr);
if(cartridge.chr_ram == false) return;
chr_data(chr_addr(addr)) = data;
}
unsigned MMC3::ciram_addr(uint13 addr) {
clock_irq_test(0x2000 | addr);
irq_test(0x2000 | addr);
if(mirror_select == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
if(mirror_select == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
throw;
}
uint8 MMC3::ciram_read(uint13 addr) {
clock_irq_test(0x2000 | addr);
irq_test(0x2000 | addr);
return ppu.ciram_read(ciram_addr(addr));
}
@ -194,7 +199,24 @@ void MMC3::reset() {
irq_enable = false;
irq_delay = 0;
irq_line = 0;
chr_abus = 0;
}
void MMC3::serialize(serializer &s) {
s.array(prg_ram);
s.integer(bank_select);
s.array(prg_bank);
s.array(chr_bank);
s.integer(mirror_select);
s.integer(prg_ram_enable);
s.integer(prg_ram_write_protect);
s.integer(irq_latch);
s.integer(irq_counter);
s.integer(irq_enable);
s.integer(irq_delay);
s.integer(irq_line);
s.integer(chr_abus);
}

View File

@ -1,4 +1,6 @@
struct MMC3 : Mapper {
void main();
uint8 prg_read(uint16 addr);
void prg_write(uint16 addr, uint8 data);
@ -31,12 +33,12 @@ private:
unsigned irq_delay;
bool irq_line;
uint16 last_chr_addr;
uint16 chr_abus;
void irq_test(uint16 addr);
unsigned prg_addr(uint16 addr);
unsigned chr_addr(uint16 addr);
unsigned ciram_addr(uint13 addr);
void clock_irq_test(uint16 addr);
};
extern MMC3 mmc3;

View File

@ -0,0 +1,46 @@
UOROM uorom;
uint8 UOROM::prg_read(uint16 addr) {
if((addr & 0xc000) == 0x8000) {
return prg_data((prg_bank << 14) | (addr & 0x3fff));
}
if((addr & 0xc000) == 0xc000) {
return prg_data((0x0f << 14) | (addr & 0x3fff));
}
}
void UOROM::prg_write(uint16 addr, uint8 data) {
if(addr & 0x8000) prg_bank = data & 0x0f;
}
uint8 UOROM::chr_read(uint16 addr) {
return chr_data(addr);
}
void UOROM::chr_write(uint16 addr, uint8 data) {
if(cartridge.chr_ram == false) return;
chr_data(addr) = data;
}
uint8 UOROM::ciram_read(uint13 addr) {
if(cartridge.mirroring == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr);
}
void UOROM::ciram_write(uint13 addr, uint8 data) {
if(cartridge.mirroring == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr, data);
}
void UOROM::power() {
reset();
}
void UOROM::reset() {
prg_bank = 0;
}
void UOROM::serialize(serializer &s) {
s.integer(prg_bank);
}

View File

@ -0,0 +1,20 @@
struct UOROM : Mapper {
uint8 prg_read(uint16 addr);
void prg_write(uint16 addr, uint8 data);
uint8 chr_read(uint16 addr);
void chr_write(uint16 addr, uint8 data);
uint8 ciram_read(uint13 addr);
void ciram_write(uint13 addr, uint8 data);
void power();
void reset();
void serialize(serializer&);
private:
uint4 prg_bank;
};
extern UOROM uorom;

342
bsnes/nes/mapper/vrc6/vrc6.cpp Executable file
View File

@ -0,0 +1,342 @@
VRC6 vrc6;
//
void VRC6::Pulse::clock() {
if(--divider == 0) {
divider = frequency;
cycle++;
output = (mode == 1 || cycle < duty) ? volume : (uint4)0;
}
if(enable == false) output = 0;
}
void VRC6::Pulse::serialize(serializer &s) {
s.integer(mode);
s.integer(duty);
s.integer(volume);
s.integer(enable);
s.integer(frequency);
s.integer(divider);
s.integer(cycle);
s.integer(output);
}
VRC6::Pulse::Pulse() {
for(unsigned n = 0; n < 16; n++) {
dac[n] = -15360 + (n * 2048);
}
}
//
void VRC6::Sawtooth::clock() {
if(--divider == 0) {
divider = frequency;
if(phase == 0) {
phase = 0;
accumulator = 0;
} else if((phase & 1) == 0) {
accumulator += rate;
}
}
output = accumulator >> 3;
if(enable == false) output = 0;
}
void VRC6::Sawtooth::serialize(serializer &s) {
s.integer(rate);
s.integer(enable);
s.integer(frequency);
s.integer(divider);
s.integer(phase);
s.integer(accumulator);
s.integer(output);
}
VRC6::Sawtooth::Sawtooth() {
for(unsigned n = 0; n < 32; n++) {
dac[n] = -15872 + (n * 1024);
}
}
//
void VRC6::main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(irq_enable) {
if(irq_mode == 0) {
irq_scalar -= 3;
if(irq_scalar <= 0) {
irq_scalar += 341;
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
if(irq_mode == 1) {
if(irq_counter == 0xff) {
irq_counter = irq_latch;
irq_line = 1;
} else {
irq_counter++;
}
}
}
pulse1.clock();
pulse2.clock();
sawtooth.clock();
signed output = 0;
output += pulse1.dac[ pulse1.output];
output += pulse2.dac[ pulse2.output];
output += sawtooth.dac[sawtooth.output];
output /= 6; //div by 3 for channel count; div that by 2 to match NES volume intensity
output = sclamp<16>(output);
apu.set_sample(output);
cpu.set_irq_line(irq_line);
tick();
}
}
uint8 VRC6::prg_read(uint16 addr) {
if((addr & 0xe000) == 0x6000) {
return prg_ram[addr & 0x1fff];
}
if((addr & 0xc000) == 0x8000) {
return prg_data((prg_bank[0] << 14) | (addr & 0x3fff));
}
if((addr & 0xe000) == 0xc000) {
return prg_data((prg_bank[1] << 13) | (addr & 0x1fff));
}
if((addr & 0xe000) == 0xe000) {
return prg_data((0xff << 13) | (addr & 0x1fff));
}
return cpu.mdr();
}
void VRC6::prg_write(uint16 addr, uint8 data) {
if((addr & 0xe000) == 0x6000) {
prg_ram[addr & 0x1fff] = data;
return;
}
addr = (addr & 0xf003);
if(abus_swap) addr = (addr & ~3) | ((addr & 2) >> 1) | ((addr & 1) << 1);
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prg_bank[0] = data;
break;
case 0x9000:
pulse1.mode = data & 0x80;
pulse1.duty = (data & 0x70) >> 4;
pulse1.volume = data & 0x0f;
break;
case 0x9001:
pulse1.frequency = (pulse1.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0x9002:
pulse1.frequency = (pulse1.frequency & 0x00ff) | ((data & 0x0f) << 8);
pulse1.enable = data & 0x80;
break;
case 0xa000:
pulse2.mode = data & 0x80;
pulse2.duty = (data & 0x70) >> 4;
pulse2.volume = data & 0x0f;
break;
case 0xa001:
pulse2.frequency = (pulse2.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0xa002:
pulse2.frequency = (pulse2.frequency & 0x00ff) | ((data & 0x0f) << 8);
pulse2.enable = data & 0x80;
break;
case 0xb000:
sawtooth.rate = data & 0x3f;
break;
case 0xb001:
sawtooth.frequency = (sawtooth.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0xb002:
sawtooth.frequency = (sawtooth.frequency & 0x00ff) | ((data & 0x0f) << 8);
sawtooth.enable = data & 0x80;
break;
case 0xb003:
mirror_select = (data >> 2) & 3;
break;
case 0xc000: case 0xc001: case 0xc002: case 0xc003:
prg_bank[1] = data;
break;
case 0xd000: case 0xd001: case 0xd002: case 0xd003:
chr_bank[0 + (addr & 3)] = data;
break;
case 0xe000: case 0xe001: case 0xe002: case 0xe003:
chr_bank[4 + (addr & 3)] = data;
break;
case 0xf000:
irq_latch = data;
break;
case 0xf001:
irq_mode = data & 0x04;
irq_enable = data & 0x02;
irq_acknowledge = data & 0x01;
if(irq_enable) {
irq_counter = irq_latch;
irq_scalar = 341;
}
irq_line = 0;
break;
case 0xf002:
irq_enable = irq_acknowledge;
irq_line = 0;
break;
}
}
uint8 VRC6::chr_read(uint16 addr) {
unsigned bank = chr_bank[(addr >> 10) & 7];
return chr_data((bank << 10) | (addr & 0x03ff));
}
void VRC6::chr_write(uint16 addr, uint8 data) {
if(cartridge.chr_ram == false) return;
unsigned bank = chr_bank[(addr >> 10) & 7];
chr_data((bank << 10) | (addr & 0x03ff)) = data;
}
unsigned VRC6::ciram_addr(unsigned addr) const {
switch(mirror_select) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
}
uint8 VRC6::ciram_read(uint13 addr) {
return ppu.ciram_read(ciram_addr(addr));
}
void VRC6::ciram_write(uint13 addr, uint8 data) {
return ppu.ciram_write(ciram_addr(addr), data);
}
unsigned VRC6::ram_size() {
return 8192u;
}
uint8* VRC6::ram_data() {
return prg_ram;
}
void VRC6::power() {
reset();
}
void VRC6::reset() {
foreach(n, prg_ram) n = 0xff;
prg_bank[0] = 0;
prg_bank[1] = 0;
chr_bank[0] = 0;
chr_bank[1] = 0;
chr_bank[2] = 0;
chr_bank[3] = 0;
chr_bank[4] = 0;
chr_bank[5] = 0;
chr_bank[6] = 0;
chr_bank[7] = 0;
mirror_select = 0;
irq_latch = 0;
irq_mode = 0;
irq_enable = 0;
irq_acknowledge = 0;
irq_counter = 0;
irq_scalar = 0;
irq_line = 0;
pulse1.mode = 0;
pulse1.duty = 0;
pulse1.volume = 0;
pulse1.enable = 0;
pulse1.frequency = 0;
pulse1.divider = 0;
pulse1.cycle = 0;
pulse1.output = 0;
pulse2.mode = 0;
pulse2.duty = 0;
pulse2.volume = 0;
pulse2.enable = 0;
pulse2.frequency = 0;
pulse2.divider = 0;
pulse2.cycle = 0;
pulse2.output = 0;
sawtooth.rate = 0;
sawtooth.enable = 0;
sawtooth.frequency = 0;
sawtooth.divider = 0;
sawtooth.phase = 0;
sawtooth.accumulator = 0;
sawtooth.output = 0;
}
void VRC6::serialize(serializer &s) {
s.array(prg_ram);
s.array(prg_bank);
s.array(chr_bank);
s.integer(mirror_select);
s.integer(irq_latch);
s.integer(irq_mode);
s.integer(irq_enable);
s.integer(irq_acknowledge);
s.integer(irq_counter);
s.integer(irq_scalar);
s.integer(irq_line);
pulse1.serialize(s);
pulse2.serialize(s);
sawtooth.serialize(s);
}

75
bsnes/nes/mapper/vrc6/vrc6.hpp Executable file
View File

@ -0,0 +1,75 @@
struct VRC6 : Mapper {
void main();
uint8 prg_read(uint16 addr);
void prg_write(uint16 addr, uint8 data);
uint8 chr_read(uint16 addr);
void chr_write(uint16 addr, uint8 data);
uint8 ciram_read(uint13 addr);
void ciram_write(uint13 addr, uint8 data);
unsigned ram_size();
uint8* ram_data();
void power();
void reset();
void serialize(serializer&);
//privileged:
bool abus_swap;
private:
uint8 prg_ram[8192];
uint8 prg_bank[2];
uint8 chr_bank[8];
uint2 mirror_select;
uint8 irq_latch;
bool irq_mode;
bool irq_enable;
bool irq_acknowledge;
uint8 irq_counter;
signed irq_scalar;
bool irq_line;
struct Pulse {
bool mode;
uint3 duty;
uint4 volume;
bool enable;
uint12 frequency;
uint12 divider;
uint4 cycle;
uint4 output;
int16 dac[16];
void clock();
void serialize(serializer&);
Pulse();
} pulse1, pulse2;
struct Sawtooth {
uint6 rate;
bool enable;
uint12 frequency;
uint12 divider;
uint4 phase;
uint8 accumulator;
uint5 output;
int16 dac[32];
void clock();
void serialize(serializer&);
Sawtooth();
} sawtooth;
unsigned ciram_addr(unsigned addr) const;
};
extern VRC6 vrc6;

View File

@ -21,6 +21,10 @@ void System::runtosave() {
scheduler.thread = apu.thread;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::All;
scheduler.thread = cartridge.thread;
runthreadtosave();
scheduler.sync = Scheduler::SynchronizeMode::None;
}

View File

@ -41,7 +41,7 @@ Application::Application(int argc, char **argv) {
inputManager = new InputManager;
utility = new Utility;
title = "bsnes v082.21";
title = "bsnes v082.22";
#if defined(PLATFORM_WIN)
normalFont = "Tahoma, 8";