Skip to content

Commit

Permalink
Initial implementation of Yamaha SKW-01.
Browse files Browse the repository at this point in the history
This is the Kanji Word Processing Unit, which has some unique features:
- non-standard kanji font ROM, accessible via non-standard method
- 32kB main program ROM
- 32kB data ROM
- 2kB battery backed SRAM
- printer port, with non-standard I/O mapping

Thanks to several users on MRC, the device could be reverse-engineered in such
a way that it is clear how the happy flow should work. And that was used to
implement the emulation. See the references in the .cc file for the credits.

Thanks to Wouter for reviewing and his suggestions.
  • Loading branch information
MBilderbeek committed Jan 18, 2023
1 parent 5f8a95d commit 715361e
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 0 deletions.
2 changes: 2 additions & 0 deletions build/msvc/openmsx.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@
<ClCompile Include="$(OpenMSXSrcDir)\SpeedManager.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\ThrottleManager.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\Version.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\YamahaSKW01.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\sound\SVIPSG.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\fdc\SVIFDC.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\SVIPrinterPort.cc" />
Expand Down Expand Up @@ -1594,6 +1595,7 @@ popd</Command>
<None Include="$(OpenMSXSrcDir)\SpeedManager.hh" />
<None Include="$(OpenMSXSrcDir)\ThrottleManager.hh" />
<None Include="$(OpenMSXSrcDir)\Version.hh" />
<None Include="$(OpenMSXSrcDir)\YamahaSKW01.hh" />
<None Include="$(OpenMSXSrcDir)\sound\SVIPSG.hh" />
<None Include="$(OpenMSXSrcDir)\fdc\SVIFDC.hh" />
<None Include="$(OpenMSXSrcDir)\SVIPrinterPort.hh" />
Expand Down
2 changes: 2 additions & 0 deletions build/msvc/openmsx.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,7 @@
<ClCompile Include="$(OpenMSXSrcDir)\serialize_meta.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\ThrottleManager.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\Version.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\YamahaSKW01.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\SVIPrinterPort.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\SVIPPI.cc" />
<ClCompile Include="$(OpenMSXSrcDir)\utils\SerializeBuffer.cc" />
Expand Down Expand Up @@ -2851,6 +2852,7 @@
<None Include="$(OpenMSXSrcDir)\serialize_stl.hh" />
<None Include="$(OpenMSXSrcDir)\ThrottleManager.hh" />
<None Include="$(OpenMSXSrcDir)\Version.hh" />
<None Include="$(OpenMSXSrcDir)\YamahaSKW01.hh" />
<None Include="$(OpenMSXSrcDir)\sound\SVIPSG.hh" />
<None Include="$(OpenMSXSrcDir)\fdc\SVIFDC.hh" />
<None Include="$(OpenMSXSrcDir)\SVIPrinterPort.hh" />
Expand Down
36 changes: 36 additions & 0 deletions share/extensions/Yamaha_SKW-01.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" ?>
<!DOCTYPE msxconfig SYSTEM 'msxconfig2.dtd'>
<msxconfig>
<info>
<!-- Note: the switch to disable SRAM is not emulated! -->
<name>Kanji Word Processor Unit</name>
<manufacturer>Yamaha</manufacturer>
<code>SKW-01</code>
<release_year>1985</release_year>
<description>Japanese Word Processor with Kanji ROM and printer port.</description>
<type>kanji font expansion</type>
</info>
<devices>
<primary slot="any">
<secondary slot="any">
<YamahaSKW01 id="Kanji Word Processor Unit">
<mem base="0x0000" size="0x8000"/>
<rom id="main">
<sha1>6a7f41d89f9d50f4ff648233670660cfb07a41ee</sha1>
<filename>skw-01_main.rom</filename>
</rom>
<rom id="kanjifont">
<sha1>f0901a63de6c95a42ce6ad82dd5e6bfba4bcf180</sha1>
<filename>skw-01_kanjifont.rom</filename>
</rom>
<rom id="data">
<sha1>82f2ecd9f3135a8602d54cc6322216b3b15ae8b3</sha1>
<filename>skw-01_data.rom</filename>
</rom>
<sramname>skw-01.sram</sramname>
</YamahaSKW01>
</secondary>
</primary>

</devices>
</msxconfig>
3 changes: 3 additions & 0 deletions src/DeviceFactory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
#include "MSXMapperIO.hh"
#include "VDPIODelay.hh"
#include "SensorKid.hh"
#include "YamahaSKW01.hh"
#include "CliComm.hh"
#include "MSXException.hh"
#include "components.hh"
Expand Down Expand Up @@ -303,6 +304,8 @@ std::unique_ptr<MSXDevice> DeviceFactory::create(const DeviceConfig& conf)
result = make_unique<MusicalMemoryMapper>(conf);
} else if (type == "Carnivore2") {
result = make_unique<Carnivore2>(conf);
} else if (type == "YamahaSKW01") {
result = make_unique<YamahaSKW01>(conf);
} else if (type == one_of("T7775", "T7937", "T9763", "T9769")) {
// Ignore for now. We might want to create a real device for it later.
} else {
Expand Down
181 changes: 181 additions & 0 deletions src/YamahaSKW01.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
#include "YamahaSKW01.hh"
#include "DummyPrinterPortDevice.hh"
#include "CacheLine.hh"
#include "MSXException.hh"
#include "checked_cast.hh"
#include "serialize.hh"

// Implementation based on reverse-engineering of 'acet'/'uniabis', see:
// https://www.msx.org/forum/msx-talk/hardware/trying-to-dump-yamaha-skw-01
// https://github.com/uniabis/msxrawl/tree/main/dumpskw1
// The printer port is assumed to work the same as a standard MSX printer port,
// but when testing with the built in software, it doesn't seem to work
// correctly, as the output is not what I would expect (when connecting an
// emulated MSX Printer).
// As the emulation is purely based on reverse engineering based on the built
// in software, there may be plenty of details wrong.
// Also note that the delay that is apparently present when writing the font
// address registers (other wise the READY bit wouldn't need to be read out) is
// not emulated. Because of that, the READY bit will always just be enabled.

namespace openmsx {

YamahaSKW01::YamahaSKW01(const DeviceConfig& config)
: MSXDevice(config)
, Connector(MSXDevice::getPluggingController(), MSXDevice::getName() + " printerport",
std::make_unique<DummyPrinterPortDevice>())
, mainRom(MSXDevice::getName() + " main" , "rom", config, "main")
, fontRom(MSXDevice::getName() + " kanjifont", "rom", config, "kanjifont")
, dataRom(MSXDevice::getName() + " data" , "rom", config, "data")
, sram(MSXDevice::getName() + " SRAM", 0x800, config)
{
if (mainRom.size() != 32 * 1024) {
throw MSXException("Main ROM must be exactly 32kB in size.");
}
if (fontRom.size() != 128 * 1024) {
throw MSXException("Font ROM must be exactly 128kB in size.");
}
if (dataRom.size() != 32 * 1024) {
throw MSXException("Data ROM must be exactly 32kB in size.");
}

reset(EmuTime::dummy());
}

void YamahaSKW01::reset(EmuTime::param time)
{
fontAddress = {0, 0, 0, 0};
dataAddress = 0;

writeData(0, time); // TODO check this, see MSXPrinterPort
setStrobe(true, time); // TODO check this, see MSXPrinterPort
}

byte YamahaSKW01::readMem(word address, EmuTime::param time)
{
return peekMem(address, time);
}

void YamahaSKW01::writeMem(word address, byte value, EmuTime::param time)
{
if (0x7FC0 <= address && address <= 0x7FC9) {
unsigned group = (address - 0x7FC0) / 2;
if ((address & 1) == 0) {
// LSB address
fontAddress[group] = (fontAddress[group] & 0xFF00) | (value << 0);
} else {
// MSB address
fontAddress[group] = (fontAddress[group] & 0x00FF) | (value << 8);
}
} else if (address == 0x7FCA || address == 0x7FCB) {
if ((dataAddress & (1 << 15)) != 0) {
sram.write(dataAddress & 0x7FF, value);
}
} else if (address == 0x7FCC) {
setStrobe(value & 1, time);
} else if (address == 0x7FCE) {
writeData(value, time);
}
}

const byte* YamahaSKW01::getReadCacheLine(word start) const
{
if ((start & CacheLine::HIGH) == (0x7FC0 & CacheLine::HIGH)) {
// 0x7FC0-0x7FCF memory mapped registers
return nullptr; // not cacheable
} else if (start < 0x8000) {
return &mainRom[start];
} else {
return unmappedRead.data();
}
}

byte* YamahaSKW01::getWriteCacheLine(word start) const
{
if ((start & CacheLine::HIGH) == (0x7FC0 & CacheLine::HIGH)) {
// 0x7FC0-0x7FCF memory mapped registers
return nullptr; // not cacheable
} else {
return unmappedWrite.data();
}
}

byte YamahaSKW01::peekMem(word address, EmuTime::param time) const
{
if (address == one_of(0x7FC0, 0x7FC2, 0x7FC4, 0x7FC6)) {
return 0x01; // for now, always READY to read
} else if (address == one_of(0x7FC1, 0x7FC3, 0x7FC5, 0x7FC7)) {
unsigned group = (address - 0x7FC1) / 2;
unsigned base = 0x8000 * group;
unsigned offset = fontAddress[group] & 0x7FFF;
return fontRom[base + offset];
} else if (address == 0x7FCA || address == 0x7FCB) {
if ((dataAddress & (1 << 15)) == 0) {
return dataRom[dataAddress & 0x7FFF];
} else {
return sram[dataAddress & 0x7FF];
}
} else if (address == 0x7FCC) {
// bit 1 = status / other bits always 1
return getPluggedPrintDev().getStatus(time)
? 0xFF : 0xFD;
} else if (address < 0x8000) {
return mainRom[address];
} else {
return 0xFF;
}
}

void YamahaSKW01::setStrobe(bool newStrobe, EmuTime::param time)
{
if (newStrobe != strobe) {
strobe = newStrobe;
getPluggedPrintDev().setStrobe(strobe, time);
}
}

void YamahaSKW01::writeData(uint8_t newData, EmuTime::param time)
{
if (newData != data) {
data = newData;
getPluggedPrintDev().writeData(data, time);
}
}

std::string_view YamahaSKW01::getDescription() const
{
return MSXDevice::getName() + " printer port";
}

std::string_view YamahaSKW01::getClass() const
{
return "Printer Port";
}

void YamahaSKW01::plug(Pluggable& dev, EmuTime::param time)
{
Connector::plug(dev, time);
getPluggedPrintDev().writeData(data, time);
getPluggedPrintDev().setStrobe(strobe, time);
}

PrinterPortDevice& YamahaSKW01::getPluggedPrintDev() const
{
return *checked_cast<PrinterPortDevice*>(&getPlugged());
}

template<typename Archive>
void YamahaSKW01::serialize(Archive& ar, unsigned /*version*/)
{
ar.template serializeBase<MSXDevice>(*this);
ar.serialize("fontAddress", fontAddress,
"dataAddress", dataAddress,
"SRAM" , sram,
"strobe" , strobe,
"data" , data);
// TODO force writing data to port?? (See MSXPrinterPort)
}
INSTANTIATE_SERIALIZE_METHODS(YamahaSKW01);
REGISTER_MSXDEVICE(YamahaSKW01, "YamahaSKW01");

} // namespace openmsx
58 changes: 58 additions & 0 deletions src/YamahaSKW01.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#ifndef YAMAHASKW01_HH
#define YAMAHASKW01_HH

#include "MSXDevice.hh"
#include "Connector.hh"
#include "SRAM.hh"
#include "Rom.hh"
#include <array>

namespace openmsx {

class PrinterPortDevice;

class YamahaSKW01 final : public MSXDevice, public Connector
{
public:
explicit YamahaSKW01(const DeviceConfig& config);

// MSXDevice
void reset(EmuTime::param time) override;

[[nodiscard]] byte readMem(word address, EmuTime::param time) override;
void writeMem(word address, byte value, EmuTime::param time) override;
[[nodiscard]] const byte* getReadCacheLine(word start) const override;
[[nodiscard]] byte* getWriteCacheLine(word start) const override;
[[nodiscard]] virtual byte peekMem(word address, EmuTime::param time) const override;

// Connector
[[nodiscard]] std::string_view getDescription() const override;
[[nodiscard]] std::string_view getClass() const override;
void plug(Pluggable& dev, EmuTime::param time) override;

template<typename Archive>
void serialize(Archive& ar, unsigned version);

private:
Rom mainRom;
Rom fontRom;
Rom dataRom;
SRAM sram;
std::array<uint16_t, 4> fontAddress;
uint16_t dataAddress;

// printer port stuff
bool strobe = false; // != true
uint8_t data = 255; // != 0

private:
// printer port stuff
[[nodiscard]] PrinterPortDevice& getPluggedPrintDev() const;

void setStrobe(bool newStrobe, EmuTime::param time);
void writeData(uint8_t newData, EmuTime::param time);
};

} // namespace openmsx

#endif

0 comments on commit 715361e

Please sign in to comment.