gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit a3f110b873171fddae152685798fc534b9ab67fe
parent 20b1a6e302b3c258972cf1d3a48a85c557add6d5
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sat, 20 Jul 2024 01:56:21 +0200

implement i2c and n2x i2c flash support

Diffstat:
Msource/nord/n2x/n2xLib/CMakeLists.txt | 3+++
Asource/nord/n2x/n2xLib/i2c.cpp | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/i2c.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xi2cflash.cpp | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xi2cflash.h | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msource/nord/n2x/n2xLib/n2xmc.cpp | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msource/nord/n2x/n2xLib/n2xmc.h | 2++
Msource/nord/n2x/n2xLib/n2xrom.cpp | 13-------------
Msource/nord/n2x/n2xLib/n2xrom.h | 18++----------------
Asource/nord/n2x/n2xLib/n2xromdata.cpp | 30++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xromdata.h | 25+++++++++++++++++++++++++
Msource/nord/n2x/n2xLib/n2xtypes.h | 5+++--
12 files changed, 489 insertions(+), 32 deletions(-)

diff --git a/source/nord/n2x/n2xLib/CMakeLists.txt b/source/nord/n2x/n2xLib/CMakeLists.txt @@ -5,12 +5,15 @@ project(n2xLib) add_library(n2xLib STATIC) set(SOURCES + i2c.cpp i2c.h n2xdevice.cpp n2xdevice.h n2xdsp.cpp n2xdsp.h n2xhardware.cpp n2xhardware.h + n2xi2cflash.cpp n2xi2cflash.h n2xmc.cpp n2xmc.h n2xmiditypes.h n2xrom.cpp n2xrom.h + n2xromdata.cpp n2xromdata.h n2xtypes.h ) diff --git a/source/nord/n2x/n2xLib/i2c.cpp b/source/nord/n2x/n2xLib/i2c.cpp @@ -0,0 +1,162 @@ +#include "i2c.h" + +#include <cassert> + +#include "dsp56kEmu/logging.h" + +namespace n2x +{ + void I2c::masterWrite(const bool _sda, const bool _scl) + { + if(_sda != m_sda && _scl != m_scl) + { + assert(false && "only one pin should flip"); + return; + } + + if(_sda != m_sda) + { + m_sda = _sda; + sdaFlip(_sda); + } + else if(_scl != m_scl) + { + m_scl = _scl; + sclFlip(_scl); + } + } + + std::optional<bool> I2c::masterRead(const bool _scl) + { + if(_scl == m_scl) + return {}; + + if(m_state != State::Start) + return {}; + + m_scl = _scl; + + if(_scl) + { + if(m_nextBit == BitAck) + { + m_nextBit = Bit7; + return m_ackBit; // this was returned already in onAck() + } + + if(m_nextBit >= Bit0 && m_nextBit <= Bit7) + { + if(m_nextBit == Bit7) + m_byte = onReadByte(); + + auto res = m_byte & (1<<m_nextBit); + --m_nextBit; + if(m_nextBit < 0) + m_nextBit = BitInvalid; + return res; + } + } + + return {}; + } + + std::optional<bool> I2c::setSdaWrite(const bool _write) + { + if(m_sdaWrite == _write) + return {}; + + m_sdaWrite = _write; + + if(m_state != State::Start) + return {}; + + if(!m_sdaWrite) + { + if(m_nextBit == BitAck) + { + const auto ackBit = onAck(); + if(ackBit) + { + m_ackBit = *ackBit; + } + return ackBit; + } + } + return {}; + } + + void I2c::onStateChanged(const State _state) + { + LOG("state: " << (_state == State::Start ? "start" : "stop")); + + switch (_state) + { + case State::Stop: + m_nextBit = BitInvalid; + break; + case State::Start: + m_nextBit = Bit7; + m_byte = 0; + break; + } + } + + void I2c::onStartCondition() + { + setState(State::Start); + } + + void I2c::onStopCondition() + { + setState(State::Stop); + } + + void I2c::onByteWritten() + { + LOG("Got byte " << HEXN(byte(), 2)); + } + + void I2c::sdaFlip(const bool _sda) + { + if(m_scl) + { + if(!_sda) + onStartCondition(); + else + onStopCondition(); + } + } + + void I2c::sclFlip(const bool _scl) + { + if(_scl && m_state == State::Start) + { + if(m_nextBit >= Bit0 && m_nextBit <= Bit7) + { + LOG("next bit " << static_cast<int>(m_nextBit) << " = " << m_sda); + + if(m_nextBit == Bit7) + m_byte = 0; + + // data input + if(m_sda) + m_byte |= (1<<m_nextBit); + + --m_nextBit; + + if(m_nextBit < 0) + { + onByteWritten(); + } + } + } + } + + void I2c::setState(const State _state) + { +/* if(m_state == _state) + return; +*/ m_state = _state; + onStateChanged(_state); + } +} diff --git a/source/nord/n2x/n2xLib/i2c.h b/source/nord/n2x/n2xLib/i2c.h @@ -0,0 +1,63 @@ +#pragma once + +#include <cstdint> +#include <optional> + +namespace n2x +{ + class I2c + { + public: + enum class State + { + Stop, + Start, + }; + enum BitPos : int8_t + { + Bit7 = 7, + Bit6 = 6, + Bit5 = 5, + Bit4 = 4, + Bit3 = 3, + Bit2 = 2, + Bit1 = 1, + Bit0 = 0, + BitAck = -1, + BitInvalid = -2, + }; + + I2c() = default; + + virtual ~I2c() = default; + + void masterWrite(bool _sda, bool _scl); + virtual std::optional<bool> masterRead(bool _scl); + std::optional<bool> setSdaWrite(bool _write); + + bool sda() const { return m_sda; } + bool scl() const { return m_scl; } + uint8_t byte() const { return m_byte; } + + protected: + virtual void onStateChanged(State _state); + virtual void onStartCondition(); + virtual void onStopCondition(); + virtual void onByteWritten(); + virtual std::optional<bool> onAck() { return {}; } + virtual uint8_t onReadByte() { return 0; } + + private: + void sdaFlip(bool _sda); + void sclFlip(bool _scl); + void setState(State _state); + + bool m_sdaWrite = true; // true = write + bool m_sda = false; + bool m_scl = false; + State m_state = State::Stop; + int8_t m_nextBit = BitInvalid; + uint8_t m_byte; + bool m_ackBit = true; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xi2cflash.cpp b/source/nord/n2x/n2xLib/n2xi2cflash.cpp @@ -0,0 +1,90 @@ +#include "n2xi2cflash.h" + +#include <cassert> + +#include "dsp56kEmu/logging.h" + +namespace n2x +{ + void I2cFlash::onStartCondition() + { + m_state = State::ReadDeviceSelect; + I2c::onStartCondition(); + } + + void I2cFlash::onStopCondition() + { + I2c::onStopCondition(); + } + + void I2cFlash::onByteWritten() + { + I2c::onByteWritten(); + + switch (m_state) + { + case State::ReadDeviceSelect: + m_deviceSelect = byte(); + m_state = State::AckDeviceSelect; + break; + case State::ReadAddressMSB: + m_address = byte() << 8; + m_state = State::AckAddressMSB; + break; + case State::ReadAddressLSB: + m_address |= byte(); + m_state = State::AckAddressLSB; + break; + case State::ReadWriteData: + writeByte(byte()); + break; + default: + assert(false && "invalid state"); + break; + } + } + + std::optional<bool> I2cFlash::onAck() + { + switch (m_state) + { + case State::AckDeviceSelect: + m_state = State::ReadAddressMSB; + return false; + case State::AckAddressMSB: + m_state = State::ReadAddressLSB; + return false; + case State::AckAddressLSB: + m_state = State::ReadWriteData; + return false; + case State::ReadWriteData: + return false; + default: + assert(false && "invalid state"); + return true; + } + } + + uint8_t I2cFlash::onReadByte() + { + assert((m_deviceSelect & DeviceSelectMask::Area) == DeviceSelectValues::AreaMemory); + assert((m_deviceSelect & DeviceSelectMask::Rw) == DeviceSelectValues::Read); + const auto res = data()[m_address]; + advanceAddress(); + return res; + } + + void I2cFlash::writeByte(uint8_t _byte) + { + assert((m_deviceSelect & DeviceSelectMask::Area) == DeviceSelectValues::AreaMemory); + assert((m_deviceSelect & DeviceSelectMask::Rw) == DeviceSelectValues::Write); + + data()[m_address] = _byte; + advanceAddress(); + } + + void I2cFlash::advanceAddress() + { + m_address = (m_address & 0xFF80) | ((m_address + 1) & 0x7F); + } +} diff --git a/source/nord/n2x/n2xLib/n2xi2cflash.h b/source/nord/n2x/n2xLib/n2xi2cflash.h @@ -0,0 +1,49 @@ +#pragma once + +#include <optional> + +#include "i2c.h" +#include "n2xromdata.h" +#include "n2xtypes.h" + +namespace n2x +{ + class I2cFlash : public I2c, public RomData<g_flashSize> + { + enum class State + { + ReadDeviceSelect, AckDeviceSelect, + ReadAddressMSB, AckAddressMSB, + ReadAddressLSB, AckAddressLSB, + ReadWriteData, + }; + + enum DeviceSelectMask + { + Area = 0b1111'000'0, + ChipEnable = 0b0000'111'0, + Rw = 0b0000'000'1, + }; + + enum DeviceSelectValues + { + AreaMemory = 0b1010'000'0, + AreaId = 0b1011'000'0, + Read = 0b0000'000'1, + Write = 0b0000'000'0, + }; + + protected: + void onStartCondition() override; + void onStopCondition() override; + void onByteWritten() override; + std::optional<bool> onAck() override; + uint8_t onReadByte() override; + void writeByte(uint8_t _byte); + void advanceAddress(); + + State m_state = State::ReadDeviceSelect; + uint8_t m_deviceSelect = 0; + uint64_t m_address = 0; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xmc.cpp b/source/nord/n2x/n2xLib/n2xmc.cpp @@ -7,7 +7,13 @@ namespace n2x { - Microcontroller::Microcontroller(const Rom& _rom) + // OC2 = PGP4 = SDA + // OC3 = PGP5 = SCL + + static constexpr uint32_t g_bitSDA = 4; + static constexpr uint32_t g_bitSCL = 5; + + Microcontroller::Microcontroller(const Rom& _rom) : m_midi(getQSM()) { if(!_rom.isValid()) return; @@ -20,6 +26,59 @@ namespace n2x reset(); setPC(g_pcInitial); + + getPortF().writeRX(0xff); + + getPortGP().setWriteTXCallback([this](const mc68k::Port& _port) + { + const auto v = _port.read(); + const auto d = _port.getDirection(); + + const auto sdaV = (v >> g_bitSDA) & 1; + const auto sclV = (v >> g_bitSCL) & 1; + + const auto sdaD = (d >> g_bitSDA) & 1; + const auto sclD = (d >> g_bitSCL) & 1; + + LOG("PortGP write SDA=" << sdaV << " SCL=" << sclV); + + if(sdaD && sclD) + m_flash.masterWrite(sdaV, sclV); + else if(!sdaD && sclD) + { + if(const auto res = m_flash.masterRead(sclV)) + { + auto r = v; + r &= ~(1 << g_bitSDA); + r |= *res ? g_bitSDA : 0; + + getPortGP().writeRX(r); + LOG("PortGP return SDA=" << *res); + } + else + { +// LOG("PortGP return SDA={}, wrong SCL"); + } + } + }); + getPortGP().setDirectionChangeCallback([this](const mc68k::Port& _port) + { + // OC2 = PGP4 = SDA + // OC3 = PGP5 = SCL + const auto p = _port.getDirection(); + const auto sda = (p >> g_bitSDA) & 1; + const auto scl = (p >> g_bitSCL) & 1; + LOG("PortGP dir SDA=" << (sda?"w":"r") << " SCL=" << (scl?"w":"r")); + if(scl) + { + const auto ack = m_flash.setSdaWrite(sda); + if(ack) + { + LOG("Write ACK " << (*ack)); + getPortGP().writeRX(*ack ? (1<<g_bitSDA) : 0); + } + } + }); } uint32_t Microcontroller::read32(uint32_t _addr) diff --git a/source/nord/n2x/n2xLib/n2xmc.h b/source/nord/n2x/n2xLib/n2xmc.h @@ -1,5 +1,6 @@ #pragma once +#include "n2xi2cflash.h" #include "n2xtypes.h" #include "mc68k/hdi08.h" #include "mc68k/hdi08periph.h" @@ -33,6 +34,7 @@ namespace n2x std::array<uint8_t, g_romSize> m_rom; std::array<uint8_t, g_ramSize> m_ram; + I2cFlash m_flash; Hdi08DspA m_hdi08A; Hdi08DspB m_hdi08B; diff --git a/source/nord/n2x/n2xLib/n2xrom.cpp b/source/nord/n2x/n2xLib/n2xrom.cpp @@ -4,17 +4,4 @@ namespace n2x { - Rom::Rom() - { - const auto filename = synthLib::findROM(m_data.size(), m_data.size()); - if(filename.empty()) - return; - std::vector<uint8_t> data; - if(!synthLib::readFile(data, filename)) - return; - if(data.size() != m_data.size()) - return; - std::copy(data.begin(), data.end(), m_data.begin()); - m_filename = filename; - } } diff --git a/source/nord/n2x/n2xLib/n2xrom.h b/source/nord/n2x/n2xLib/n2xrom.h @@ -1,23 +1,9 @@ #pragma once -#include <array> -#include <cstdint> -#include <string> - +#include "n2xromdata.h" #include "n2xtypes.h" namespace n2x { - class Rom - { - public: - Rom(); - - bool isValid() const { return !m_filename.empty(); } - const auto& data() const { return m_data; } - - private: - std::array<uint8_t, g_romSize> m_data; - std::string m_filename; - }; + class Rom : public RomData<g_romSize> {}; } diff --git a/source/nord/n2x/n2xLib/n2xromdata.cpp b/source/nord/n2x/n2xLib/n2xromdata.cpp @@ -0,0 +1,30 @@ +#include "n2xromdata.h" + +#include "n2xtypes.h" + +#include "synthLib/os.h" + +namespace n2x +{ + template <uint32_t Size> RomData<Size>::RomData() + { + const auto filename = synthLib::findROM(m_data.size(), m_data.size()); + if(filename.empty()) + return; + std::vector<uint8_t> data; + if(!synthLib::readFile(data, filename)) + return; + if(data.size() != m_data.size()) + return; + std::copy(data.begin(), data.end(), m_data.begin()); + m_filename = filename; + } + + template <uint32_t Size> void RomData<Size>::saveAs(const std::string& _filename) + { + synthLib::writeFile(_filename, m_data); + } + + template class RomData<g_flashSize>; + template class RomData<g_romSize>; +} diff --git a/source/nord/n2x/n2xLib/n2xromdata.h b/source/nord/n2x/n2xLib/n2xromdata.h @@ -0,0 +1,25 @@ +#pragma once + +#include <array> +#include <cstdint> +#include <string> + +namespace n2x +{ + template<uint32_t Size> + class RomData + { + public: + RomData(); + + bool isValid() const { return !m_filename.empty(); } + const auto& data() const { return m_data; } + auto& data() { return m_data; } + + void saveAs(const std::string& _filename); + + private: + std::array<uint8_t, Size> m_data; + std::string m_filename; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xtypes.h b/source/nord/n2x/n2xLib/n2xtypes.h @@ -19,8 +19,9 @@ namespace n2x CSBOOT = BootROM $?????? $????? */ - static constexpr uint32_t g_romSize = 1024 * 512; - static constexpr uint32_t g_ramSize = 1024 * 256; + static constexpr uint32_t g_romSize = 1024 * 512; + static constexpr uint32_t g_ramSize = 1024 * 256; + static constexpr uint32_t g_flashSize = 1024 * 64; static constexpr uint32_t g_pcInitial = 0xc2;