Update to v106r64 release.

byuu says:

Changelog:

  - sfc: completed BS Memory Cassette emulation (sans bugs, of course --
    testing appreciated)
  - bsnes: don't strip - on MSU1 track names in game ROM mode
    [hex_usr]

I'm going with "metadata.bml" for the flash metadata filename for the
time being, but I'll say that it's subject to change. I'll have to make
a new extension for it to be supported with bsnes.
This commit is contained in:
Tim Allen 2018-09-13 21:13:00 +10:00
parent c58169945c
commit 336d20123f
9 changed files with 765 additions and 113 deletions

View File

@ -28,7 +28,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "106.63";
static const string Version = "106.64";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org/";

View File

@ -22,23 +22,19 @@ auto MCC::power() -> void {
w.exEnableLo = 1;
w.exEnableHi = 0;
w.exMapping = 1;
w.bsQueryable = 0;
w.bsFlashable = 0;
x.enable = 0;
x.value = 0b00111111;
w.internallyWritable = 0;
w.externallyWritable = 0;
commit();
}
auto MCC::commit() -> void {
r = w; //memory::copy(&r, &w, sizeof(Registers));
bsmemory.queryable(r.bsQueryable);
bsmemory.flashable(r.bsFlashable);
r = w;
bsmemory.writable(r.externallyWritable);
}
auto MCC::read(uint24 address, uint8 data) -> uint8 {
if((address & 0xf0f000) == 0x005000) { //$00-0f:5000-5fff
uint4 index = address.bits(16,19);
if(x.enable) return x.value.bit(index & 7);
switch(index) {
case 0: return irq.flag << 7;
case 1: return irq.enable << 7;
@ -52,10 +48,10 @@ auto MCC::read(uint24 address, uint8 data) -> uint8 {
case 9: return r.exEnableLo << 7;
case 10: return r.exEnableHi << 7;
case 11: return r.exMapping << 7;
case 12: return r.bsQueryable << 7;
case 13: return r.bsFlashable << 7;
case 12: return r.internallyWritable << 7;
case 13: return r.externallyWritable << 7;
case 14: return 0; //commit (always zero)
case 15: return 0; //x.enable (always zero)
case 15: return 0; //unknown (always zero)
}
}
@ -65,7 +61,6 @@ auto MCC::read(uint24 address, uint8 data) -> uint8 {
auto MCC::write(uint24 address, uint8 data) -> void {
if((address & 0xf0f000) == 0x005000) { //$00-0f:5000-5fff
uint4 index = address.bits(16,19);
if(x.enable) return x.value.bit(index & 7) = data.bit(7), void();
switch(index) {
case 1: irq.enable = data.bit(7); break;
case 2: w.mapping = data.bit(7); break;
@ -78,10 +73,9 @@ auto MCC::write(uint24 address, uint8 data) -> void {
case 9: w.exEnableLo = data.bit(7); break;
case 10: w.exEnableHi = data.bit(7); break;
case 11: w.exMapping = data.bit(7); break;
case 12: w.bsQueryable = data.bit(7); break;
case 13: w.bsFlashable = data.bit(7); break;
case 12: w.internallyWritable = data.bit(7); break;
case 13: w.externallyWritable = data.bit(7); break;
case 14: if(data.bit(7)) commit(); break;
case 15: x.enable = data.bit(7); break;
}
}
}
@ -258,6 +252,7 @@ auto MCC::exAccess(bool mode, uint24 address, uint8 data) -> uint8 {
auto MCC::bsAccess(bool mode, uint24 address, uint8 data) -> uint8 {
address = bus.mirror(address, bsmemory.size());
if(mode == 0) return bsmemory.read(address, data);
if(!r.internallyWritable) return data;
return bsmemory.write(address, data), data;
}

View File

@ -32,25 +32,21 @@ private:
} irq;
struct Registers {
uint1 mapping; //bit 2 (0 = ignore A15; 1 = use A15)
uint1 psramEnableLo; //bit 3
uint1 psramEnableHi; //bit 4
uint2 psramMapping; //bits 5-6
uint1 romEnableLo; //bit 7
uint1 romEnableHi; //bit 8
uint1 exEnableLo; //bit 9
uint1 exEnableHi; //bit 10
uint1 exMapping; //bit 11
uint1 bsQueryable; //bit 12
uint1 bsFlashable; //bit 13
uint1 mapping; //bit 2 (0 = ignore A15; 1 = use A15)
uint1 psramEnableLo; //bit 3
uint1 psramEnableHi; //bit 4
uint2 psramMapping; //bits 5-6
uint1 romEnableLo; //bit 7
uint1 romEnableHi; //bit 8
uint1 exEnableLo; //bit 9
uint1 exEnableHi; //bit 10
uint1 exMapping; //bit 11
uint1 internallyWritable; //bit 12 (1 = MCC allows writes to BS Memory Cassette)
uint1 externallyWritable; //bit 13 (1 = BS Memory Cassette allows writes to flash memory)
} r, w;
//bit 14 (commit)
struct ExtendedRegisters {
uint1 enable; //bit 15
uint8 value; //bits 24-31
} x;
//bit 14 = commit
//bit 15 = unknown (test register interface?)
};
extern MCC mcc;

View File

@ -1,7 +1,9 @@
auto MCC::serialize(serializer& s) -> void {
s.array(psram.data(), psram.size());
s.integer(irq.flag);
s.integer(irq.enable);
s.integer(r.mapping);
s.integer(r.psramEnableLo);
s.integer(r.psramEnableHi);
@ -11,8 +13,9 @@ auto MCC::serialize(serializer& s) -> void {
s.integer(r.exEnableLo);
s.integer(r.exEnableHi);
s.integer(r.exMapping);
s.integer(r.bsQueryable);
s.integer(r.bsFlashable);
s.integer(r.internallyWritable);
s.integer(r.externallyWritable);
s.integer(w.mapping);
s.integer(w.psramEnableLo);
s.integer(w.psramEnableHi);
@ -22,8 +25,6 @@ auto MCC::serialize(serializer& s) -> void {
s.integer(w.exEnableLo);
s.integer(w.exEnableHi);
s.integer(w.exMapping);
s.integer(w.bsQueryable);
s.integer(w.bsFlashable);
s.integer(x.enable);
s.integer(x.value);
s.integer(w.internallyWritable);
s.integer(w.externallyWritable);
}

View File

@ -2,21 +2,121 @@
namespace SuperFamicom {
#include "serialization.cpp"
BSMemory bsmemory;
#include "serialization.cpp"
auto BSMemory::load() -> void {
queryable(true);
flashable(true);
BSMemory::BSMemory() {
page.self = this;
uint blockID = 0;
for(auto& block : blocks) block.self = this, block.id = blockID++;
block.self = this;
}
auto BSMemory::Enter() -> void {
while(true) scheduler.synchronize(), bsmemory.main();
}
auto BSMemory::main() -> void {
if(ROM) return step(1'000'000); //1 second
for(uint6 id : range(block.count())) {
if(block(id).erasing) return block(id).erase();
block(id).status.ready = 1;
}
compatible.status.ready = 1;
global.status.ready = 1;
step(10'000); //10 milliseconds
}
auto BSMemory::step(uint clocks) -> void {
Thread::step(clocks);
synchronize(cpu);
}
auto BSMemory::load() -> bool {
if(ROM) return true;
if(size() != 0x100000 && size() != 0x200000 && size() != 0x400000) {
memory.reset();
return false;
}
chip.vendor = 0x00'b0; //Sharp
if(size() == 0x100000) chip.device = 0x66'a8; //LH28F800SU
if(size() == 0x200000) chip.device = 0x66'88; //LH28F016SU
if(size() == 0x400000) chip.device = 0x66'88; //LH28F032SU (same device ID as LH28F016SU per datasheet)
chip.serial = 0x00'01'23'45'67'89ull; //serial# should be unique for every cartridge ...
//page buffer values decay to random noise upon losing power to the flash chip
//the randomness is high entropy (at least compared to SNES SRAM/DRAM chips)
for(auto& byte : page.buffer[0]) byte = random();
for(auto& byte : page.buffer[1]) byte = random();
for(auto& block : blocks) {
block.erased = 0;
block.locked = 1;
}
if(auto fp = platform->open(pathID, "metadata.bml", File::Read, File::Optional)) {
auto document = BML::unserialize(fp->reads());
if(auto node = document["flash/vendor"]) {
chip.vendor = node.natural();
}
if(auto node = document["flash/device"]) {
chip.device = node.natural();
}
if(auto node = document["flash/serial"]) {
chip.serial = node.natural();
}
for(uint id : range(block.count())) {
if(auto node = document[{"flash/block(id=", id, ")"}]) {
if(auto erased = node["erased"]) {
block(id).erased = erased.natural();
}
if(auto locked = node["locked"]) {
block(id).locked = locked.boolean();
}
}
}
}
return true;
}
auto BSMemory::unload() -> void {
if(ROM) return memory.reset();
if(auto fp = platform->open(pathID, "metadata.bml", File::Write, File::Optional)) {
string manifest;
manifest.append("flash\n");
manifest.append(" vendor: 0x", hex(chip.vendor, 4L), "\n");
manifest.append(" device: 0x", hex(chip.device, 4L), "\n");
manifest.append(" serial: 0x", hex(chip.serial, 12L), "\n");
for(uint6 id : range(block.count())) {
manifest.append(" block\n");
manifest.append(" id: ", id, "\n");
manifest.append(" erased: ", (uint)block(id).erased, "\n");
manifest.append(" locked: ", (bool)block(id).locked, "\n");
}
fp->writes(manifest);
}
memory.reset();
}
auto BSMemory::power() -> void {
memory.writable(false);
io = {};
create(Enter, 1'000'000); //microseconds
for(auto& block : blocks) {
block.erasing = 0;
block.status = {};
}
compatible.status = {};
global.status = {};
mode = Mode::Flash;
readyBusyMode = ReadyBusyMode::Disable;
queue.flush();
}
auto BSMemory::data() -> uint8* {
@ -29,70 +129,442 @@ auto BSMemory::size() const -> uint {
auto BSMemory::read(uint24 address, uint8 data) -> uint8 {
if(!size()) return data;
address = bus.mirror(address, size());
if(!pin.queryable) return memory.read(address, data);
if(ROM) return memory.read(bus.mirror(address, size()));
if(io.mode == 0x70) {
return 0x80;
if(mode == Mode::Chip) {
if(address == 0) return chip.vendor.byte(0); //only appears once
if(address == 1) return chip.device.byte(0); //only appears once
if((uint3)address == 2) return 0x63; //unknown constant: repeats every eight bytes
return 0x20; //unknown constant: fills in all remaining bytes
}
if(io.mode == 0x71) {
if((uint16)address == 0x0002) return 0x80;
if((uint16)address == 0x0004) return 0x87;
if((uint16)address == 0x0006) return 0x00; //unknown purpose (not always zero)
return 0x00;
if(mode == Mode::Page) {
return page.read(address);
}
if(io.mode == 0x75) {
if((uint8)address == 0x00) return 0x4d; //'M' (memory)
if((uint8)address == 0x02) return 0x50; //'P' (pack)
if((uint8)address == 0x04) return 0x04; //unknown purpose
if((uint8)address == 0x06) return Type << 4 | (uint4)log2(size() >> 10);
return random(); //not actually random, but not ROM data either, yet varies per cartridge
if(mode == Mode::CompatibleStatus) {
return compatible.status();
}
return memory.read(address, data);
if(mode == Mode::ExtendedStatus) {
if((uint16)address == 0x0002) return block(address >> block.bits()).status();
if((uint16)address == 0x0004) return global.status();
return 0x00; //reserved: always zero
}
return block(address >> block.bits()).read(address); //Mode::Flash
}
auto BSMemory::write(uint24 address, uint8 data) -> void {
if(!size() || !pin.queryable) return;
address = bus.mirror(address, size());
if(!size() || ROM) return;
queue.push(address, data);
//write page to flash
if(queue.data(0) == 0x0c) {
if(queue.size() < 3) return;
uint16 count; //1 - 65536
count.byte(0) = queue.data(!queue.address(1).bit(0) ? 1 : 2);
count.byte(1) = queue.data(!queue.address(1).bit(0) ? 2 : 1);
uint24 address = queue.address(2);
do {
block(address >> block.bits()).write(address, page.read(address));
address++;
} while(count--);
page.swap();
mode = Mode::CompatibleStatus;
return queue.flush();
}
//write byte
if(io.mode == 0x10 || io.mode == 0x40) {
if(!pin.flashable) return;
memory.writable(true);
memory.write(address, memory.read(address) & data); //writes can only clear bits
memory.writable(false);
io.mode = 0x70;
if(queue.data(0) == 0x10) {
if(queue.size() < 2) return;
block(queue.address(1) >> block.bits()).write(queue.address(1), queue.data(1));
mode = Mode::CompatibleStatus;
return queue.flush();
}
//erase block
if(queue.data(0) == 0x20) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
block(queue.address(1) >> block.bits()).erase();
mode = Mode::CompatibleStatus;
return queue.flush();
}
//LH28F800SUT-ZI specific? (undocumented / unavailable? for the LH28F800SU)
//write signature, identifier, serial# into current page buffer, then swap page buffers
if(queue.data(0) == 0x38) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
page.write(0x00, 0x4d); //'M' (memory)
page.write(0x02, 0x50); //'P' (pack)
page.write(0x04, 0x04); //unknown constant (maybe block count? eg 1<<4 = 16 blocks)
page.write(0x06, 0x10 | (uint4)log2(size() >> 10)); //d0-d3 = size; d4-d7 = type (1)
page.write(0x08, chip.serial.byte(5)); //serial# (big endian; BCD format)
page.write(0x0a, chip.serial.byte(4)); //smallest observed value:
page.write(0x0c, chip.serial.byte(3)); // 0x00'00'10'62'62'39
page.write(0x0e, chip.serial.byte(2)); //largest observed value:
page.write(0x10, chip.serial.byte(1)); // 0x00'91'90'70'31'03
page.write(0x12, chip.serial.byte(0)); //most values are: 0x00'0x'xx'xx'xx'xx
page.swap();
return queue.flush();
}
//write byte
if(queue.data(0) == 0x40) {
if(queue.size() < 2) return;
block(queue.address(1) >> block.bits()).write(queue.address(1), queue.data(1));
mode = Mode::CompatibleStatus;
return queue.flush();
}
//clear status register
if(queue.data(0) == 0x50) {
for(uint6 id : range(block.count())) {
block(id).status.vppLow = 0;
block(id).status.failed = 0;
}
compatible.status.vppLow = 0;
compatible.status.writeFailed = 0;
compatible.status.eraseFailed = 0;
global.status.failed = 0;
return queue.flush();
}
//read compatible status register
if(queue.data(0) == 0x70) {
mode = Mode::CompatibleStatus;
return queue.flush();
}
//read extended status registers
if(queue.data(0) == 0x71) {
mode = Mode::ExtendedStatus;
return queue.flush();
}
//page buffer swap
if(queue.data(0) == 0x72) {
page.swap();
return queue.flush();
}
//single load to page buffer
if(queue.data(0) == 0x74) {
if(queue.size() < 2) return;
page.write(queue.address(1), queue.data(1));
return queue.flush();
}
//read page buffer
if(queue.data(0) == 0x75) {
mode = Mode::Page;
return queue.flush();
}
//lock block
if(queue.data(0) == 0x77) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
block(queue.address(1) >> block.bits()).lock();
return queue.flush();
}
//abort
//(unsupported)
if(queue.data(0) == 0x80) {
global.status.sleeping = 1; //abort seems to put the chip into sleep mode
return queue.flush();
}
//read chip identifiers
if(queue.data(0) == 0x90) {
mode = Mode::Chip;
return queue.flush();
}
//update ry/by mode
//(unsupported)
if(queue.data(0) == 0x96) {
if(queue.size() < 2) return;
if(queue.data(1) == 0x01) readyBusyMode = ReadyBusyMode::EnableToLevelMode;
if(queue.data(1) == 0x02) readyBusyMode = ReadyBusyMode::PulseOnWrite;
if(queue.data(1) == 0x03) readyBusyMode = ReadyBusyMode::PulseOnErase;
if(queue.data(1) == 0x04) readyBusyMode = ReadyBusyMode::Disable;
return queue.flush();
}
//upload lock status bits
if(queue.data(0) == 0x97) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
for(uint6 id : range(block.count())) block(id).update();
return queue.flush();
}
//upload device information (number of erase cycles per block)
if(queue.data(0) == 0x99) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
page.write(0x06, 0x06); //unknown constant
page.write(0x07, 0x00); //unknown constant
for(uint6 id : range(block.count())) {
uint8 address;
address += id.bits(0,1) * 0x08; //verified for LH28F800SUT-ZI
address += id.bits(2,3) * 0x40; //verified for LH28F800SUT-ZI
address += id.bit ( 4) * 0x20; //guessed for LH28F016SU
address += id.bit ( 5) * 0x04; //guessed for LH28F032SU; will overwrite unknown constants
uint32 erased = 1 << 31 | block(id).erased; //unknown if d31 is set when erased == 0
for(uint2 byte : range(4)) {
page.write(address + byte, erased.byte(byte)); //little endian
}
}
page.swap();
return queue.flush();
}
//erase all blocks
if(queue.data(0) == 0xa7) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
for(uint6 id : range(block.count())) block(id).erase();
mode = Mode::CompatibleStatus;
return queue.flush();
}
//erase suspend/resume
//(unsupported)
if(queue.data(0) == 0xb0) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
mode = Mode::CompatibleStatus;
return queue.flush();
}
//sequential load to page buffer
if(queue.data(0) == 0xe0) {
if(queue.size() < 4) return; //command length = 3 + count
uint16 count; //1 - 65536
count.byte(0) = queue.data(1); //endian order not affected by queue.address(1).bit(0)
count.byte(1) = queue.data(2);
page.write(queue.address(3), queue.data(3));
if(count--) {
queue.data(1) = count.byte(0);
queue.data(2) = count.byte(1);
return queue.pop(); //hack to avoid needing a 65539-entry queue
} else {
return queue.flush();
}
}
//sleep
//(unsupported)
if(queue.data(0) == 0xf0) {
//it is currently unknown how to exit sleep mode; other than via chip reset
global.status.sleeping = 1;
return queue.flush();
}
//write word
if(queue.data(0) == 0xfb) {
if(queue.size() < 3) return;
uint16 value;
value.byte(0) = queue.data(!queue.address(1).bit(0) ? 1 : 2);
value.byte(1) = queue.data(!queue.address(1).bit(0) ? 2 : 1);
//writes are always word-aligned: a0 toggles, rather than increments
block(queue.address(2) >> block.bits()).write(queue.address(2) ^ 0, value.byte(0));
block(queue.address(2) >> block.bits()).write(queue.address(2) ^ 1, value.byte(1));
mode = Mode::CompatibleStatus;
return queue.flush();
}
//read flash memory
if(queue.data(0) == 0xff) {
mode = Mode::Flash;
return queue.flush();
}
//unknown command
return queue.flush();
}
//
auto BSMemory::failed() -> void {
compatible.status.writeFailed = 1; //datasheet specifies these are for write/erase failures
compatible.status.eraseFailed = 1; //yet all errors seem to set both of these bits ...
global.status.failed = 1;
}
//
auto BSMemory::Page::swap() -> void {
self->global.status.page ^= 1;
}
auto BSMemory::Page::read(uint8 address) -> uint8 {
return buffer[self->global.status.page][address];
}
auto BSMemory::Page::write(uint8 address, uint8 data) -> void {
buffer[self->global.status.page][address] = data;
}
//
auto BSMemory::BlockInformation::bits() const -> uint { return 16; }
auto BSMemory::BlockInformation::bytes() const -> uint { return 1 << bits(); }
auto BSMemory::BlockInformation::count() const -> uint { return self->size() >> bits(); }
//
auto BSMemory::Block::read(uint24 address) -> uint8 {
address &= bytes() - 1;
return self->memory.read(id << bits() | address);
}
auto BSMemory::Block::write(uint24 address, uint8 data) -> void {
if(!self->writable() && status.locked) {
status.failed = 1;
return self->failed();
}
//writes to flash can only clear bits
address &= bytes() - 1;
data &= self->memory.read(id << bits() | address);
self->memory.write(id << bits() | address, data);
}
auto BSMemory::Block::erase() -> void {
if(cpu.active()) {
//erase command runs even if the block is not currently writable
erasing = 1;
status.ready = 0;
self->compatible.status.ready = 0;
self->global.status.ready = 0;
return;
}
//erase 64KB page
if(io.mode == 0x20) {
//completes even if !pin.flashable
memory.writable(true);
for(uint offset : range(1 << 16)) memory.write(address & 0xff0000 | offset, 0xff);
memory.writable(false);
io.mode = 0x70;
self->step(300'000); //300 milliseconds are required to erase one block
erasing = 0;
if(!self->writable() && status.locked) {
//does not set any failure bits when unsuccessful ...
return;
}
//erase all pages
if(io.mode == 0xa7) {
//completes even if !pin.flashable
if(Type == 3) return; //Type 3 doesn't support this command
memory.writable(true);
for(uint offset : range(size())) memory.write(offset, 0xff);
memory.writable(false);
io.mode = 0x70;
return;
for(uint24 address : range(bytes())) {
self->memory.write(id << bits() | address, 0xff);
}
if((uint16)address == 0x0000) {
io.mode = data;
erased++;
locked = 0;
status.locked = 0;
}
auto BSMemory::Block::lock() -> void {
if(!self->writable()) {
//produces a failure result even if the page was already locked
status.failed = 1;
return self->failed();
}
locked = 1;
status.locked = 1;
}
//at reset, the locked status bit is set
//this command refreshes the true locked status bit from the device
auto BSMemory::Block::update() -> void {
status.locked = locked;
}
//
auto BSMemory::Blocks::operator()(uint6 id) -> Block& {
return self->blocks[id & count() - 1];
}
//
auto BSMemory::Block::Status::operator()() -> uint8 {
return ( //d0-d1 are reserved; always return zero
vppLow << 2
| queueFull << 3
| aborted << 4
| failed << 5
|!locked << 6 //note: technically the unlocked flag; so locked is inverted here
| ready << 7
);
}
//
auto BSMemory::Compatible::Status::operator()() -> uint8 {
return ( //d0-d2 are reserved; always return zero
vppLow << 3
| writeFailed << 4
| eraseFailed << 5
| eraseSuspended << 6
| ready << 7
);
}
//
auto BSMemory::Global::Status::operator()() -> uint8 {
return (
page << 0
| pageReady << 1
| pageAvailable << 2
| queueFull << 3
| sleeping << 4
| failed << 5
| suspended << 6
| ready << 7
);
}
//
auto BSMemory::Queue::flush() -> void {
history[0] = {};
history[1] = {};
history[2] = {};
history[3] = {};
}
auto BSMemory::Queue::pop() -> void {
if(history[3].valid) { history[3] = {}; return; }
if(history[2].valid) { history[2] = {}; return; }
if(history[1].valid) { history[1] = {}; return; }
if(history[0].valid) { history[0] = {}; return; }
}
auto BSMemory::Queue::push(uint24 address, uint8 data) -> void {
if(!history[0].valid) { history[0] = {true, address, data}; return; }
if(!history[1].valid) { history[1] = {true, address, data}; return; }
if(!history[2].valid) { history[2] = {true, address, data}; return; }
if(!history[3].valid) { history[3] = {true, address, data}; return; }
}
auto BSMemory::Queue::size() -> uint {
if(history[3].valid) return 4;
if(history[2].valid) return 3;
if(history[1].valid) return 2;
if(history[0].valid) return 1;
return 0;
}
auto BSMemory::Queue::address(uint index) -> uint24 {
if(index > 3 || !history[index].valid) return 0;
return history[index].address;
}
auto BSMemory::Queue::data(uint index) -> uint8 {
if(index > 3 || !history[index].valid) return 0;
return history[index].data;
}
}

View File

@ -1,9 +1,34 @@
struct BSMemory : Memory {
auto queryable(bool queryable) { pin.queryable = !ROM && queryable; }
auto flashable(bool flashable) { pin.flashable = !ROM && flashable; }
//MaskROMs supported:
// Sharp LH5S4TNI (MaskROM 512K x 8-bit) [BSMC-CR-01: BSMC-ZS5J-JPN, BSMC-YS5J-JPN]
// Sharp LH534VNF (MaskROM 512K x 8-bit) [BSMC-BR-01: BSMC-ZX3J-JPN]
//Flash chips supported: (16-bit modes unsupported)
// Sharp LH28F800SUT-ZI (Flash 16 x 65536 x 8-bit) [BSMC-AF-01: BSMC-HM-JPN]
// Sharp LH28F016SU ??? (Flash 32 x 65536 x 8-bit) [unreleased: experimental]
// Sharp LH28F032SU ??? (Flash 64 x 65536 x 8-bit) [unreleased: experimental]
//unsupported:
// Sharp LH28F400SU ??? (Flash 32 x 16384 x 8-bit) [unreleased] {vendor ID: 0x00'b0; device ID: 0x66'21}
//notes:
//timing emulation is only present for block erase commands
//other commands generally complete so quickly that it's unnecessary (eg 70-120ns for writes)
//suspend, resume, abort, ready/busy modes are not supported
struct BSMemory : Thread, Memory {
uint pathID = 0;
uint ROM = 1;
auto writable() const { return pin.writable; }
auto writable(bool writable) { pin.writable = !ROM && writable; }
//bsmemory.cpp
auto load() -> void;
BSMemory();
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto load() -> bool;
auto unload() -> void;
auto power() -> void;
@ -15,27 +40,128 @@ struct BSMemory : Memory {
//serialization.cpp
auto serialize(serializer&) -> void;
uint pathID = 0;
//0 = Flash; 1 = MaskROM
uint ROM = 1;
//valid types are 1,2,3,4
//type 2 is currently unsupported
//type 1 is the only type to exist (all flash-based BS Memory Cassettes are type 1)
uint Type = 1;
ProtectableMemory memory;
WritableMemory memory;
private:
struct Pin {
uint1 queryable;
uint1 flashable;
uint1 writable; // => /WP
} pin;
struct IO {
uint8 mode;
} io;
struct Chip {
uint16 vendor;
uint16 device;
uint48 serial;
} chip;
struct Page {
BSMemory* self = nullptr;
auto swap() -> void;
auto read(uint8 address) -> uint8;
auto write(uint8 address, uint8 data) -> void;
uint8 buffer[2][256];
} page;
struct BlockInformation {
BSMemory* self = nullptr;
inline auto bits() const -> uint;
inline auto bytes() const -> uint;
inline auto count() const -> uint;
};
struct Block : BlockInformation {
auto read(uint24 address) -> uint8;
auto write(uint24 address, uint8 data) -> void;
auto erase() -> void;
auto lock() -> void;
auto update() -> void;
uint4 id;
uint32 erased;
uint1 locked;
uint1 erasing;
struct Status {
auto operator()() -> uint8;
uint1 vppLow;
uint1 queueFull;
uint1 aborted;
uint1 failed;
uint1 locked = 1;
uint1 ready = 1;
} status;
} blocks[64]; //8mbit = 16; 16mbit = 32; 32mbit = 64
struct Blocks : BlockInformation {
auto operator()(uint6 id) -> Block&;
} block;
struct Compatible {
struct Status {
auto operator()() -> uint8;
uint1 vppLow;
uint1 writeFailed;
uint1 eraseFailed;
uint1 eraseSuspended;
uint1 ready = 1;
} status;
} compatible;
struct Global {
struct Status {
auto operator()() -> uint8;
uint1 page;
uint1 pageReady = 1;
uint1 pageAvailable = 1;
uint1 queueFull;
uint1 sleeping;
uint1 failed;
uint1 suspended;
uint1 ready = 1;
} status;
} global;
struct Mode { enum : uint {
Flash,
Chip,
Page,
CompatibleStatus,
ExtendedStatus,
};};
uint3 mode;
struct ReadyBusyMode { enum : uint {
EnableToLevelMode,
PulseOnWrite,
PulseOnErase,
Disable,
};};
uint2 readyBusyMode;
struct Queue {
auto flush() -> void;
auto pop() -> void;
auto push(uint24 address, uint8 data) -> void;
auto size() -> uint;
auto address(uint index) -> uint24;
auto data(uint index) -> uint8;
//serialization.cpp
auto serialize(serializer&) -> void;
struct History {
uint1 valid;
uint24 address;
uint8 data;
} history[4];
} queue;
auto failed() -> void;
};
extern BSMemory bsmemory;

View File

@ -1,6 +1,67 @@
auto BSMemory::serialize(serializer& s) -> void {
if(!ROM) s.array(memory.data(), memory.size());
s.integer(pin.queryable);
s.integer(pin.flashable);
s.integer(io.mode);
Thread::serialize(s);
if(ROM) return;
s.array(memory.data(), memory.size());
s.integer(pin.writable);
s.integer(chip.vendor);
s.integer(chip.device);
s.integer(chip.serial);
s.array(page.buffer[0]);
s.array(page.buffer[1]);
for(auto& block : blocks) {
s.integer(block.id);
s.integer(block.erased);
s.integer(block.locked);
s.integer(block.erasing);
s.integer(block.status.vppLow);
s.integer(block.status.queueFull);
s.integer(block.status.aborted);
s.integer(block.status.failed);
s.integer(block.status.locked);
s.integer(block.status.ready);
}
s.integer(compatible.status.vppLow);
s.integer(compatible.status.writeFailed);
s.integer(compatible.status.eraseFailed);
s.integer(compatible.status.eraseSuspended);
s.integer(compatible.status.ready);
s.integer(global.status.page);
s.integer(global.status.pageReady);
s.integer(global.status.pageAvailable);
s.integer(global.status.queueFull);
s.integer(global.status.sleeping);
s.integer(global.status.failed);
s.integer(global.status.suspended);
s.integer(global.status.ready);
s.integer(mode);
s.integer(readyBusyMode);
queue.serialize(s);
}
auto BSMemory::Queue::serialize(serializer& s) -> void {
s.integer(history[0].valid);
s.integer(history[0].address);
s.integer(history[0].data);
s.integer(history[1].valid);
s.integer(history[1].address);
s.integer(history[1].data);
s.integer(history[2].valid);
s.integer(history[2].address);
s.integer(history[2].data);
s.integer(history[3].valid);
s.integer(history[3].address);
s.integer(history[3].data);
}

View File

@ -126,6 +126,7 @@ auto System::power(bool reset) -> void {
if(cartridge.has.SharpRTC) cpu.coprocessors.append(&sharprtc);
if(cartridge.has.SPC7110) cpu.coprocessors.append(&spc7110);
if(cartridge.has.MSU1) cpu.coprocessors.append(&msu1);
if(cartridge.has.BSMemorySlot) cpu.coprocessors.append(&bsmemory);
scheduler.primary(cpu);

View File

@ -124,7 +124,7 @@ auto Program::openRomSuperFamicom(string name, vfs::file::mode mode) -> vfs::sha
}
if(name.match("msu1/track-*.pcm")) {
name.trimLeft("msu1/track-", 1L);
name.trimLeft("msu1/track", 1L);
return vfs::fs::file::open({Location::notsuffix(superFamicom.location), name}, mode);
}