commit 376400e254bb128395ae6ef1b4cd7132f401c722 parent 676b08806d0c6db74a35a17858dad76fe1071bfe Author: dsp56300 <dsp56300@users.noreply.github.com> Date: Tue, 16 Jul 2024 09:23:47 +0200 initial n2x code, uc boots and initialized DSPs Diffstat:
18 files changed, 628 insertions(+), 2 deletions(-)
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt @@ -4,6 +4,7 @@ option(${CMAKE_PROJECT_NAME}_SYNTH_OSIRUS "Build Osirus" on) option(${CMAKE_PROJECT_NAME}_SYNTH_OSTIRUS "Build OsTIrus" on) option(${CMAKE_PROJECT_NAME}_SYNTH_VAVRA "Build Vavra" on) option(${CMAKE_PROJECT_NAME}_SYNTH_XENIA "Build Xenia" on) +option(${CMAKE_PROJECT_NAME}_SYNTH_N2X "Build N2x" on) # ----------------- DSP56300 emulator @@ -40,9 +41,11 @@ if(${CMAKE_PROJECT_NAME}_BUILD_JUCEPLUGIN) endif() # ----------------- dependencies -if(${CMAKE_PROJECT_NAME}_SYNTH_VAVRA OR ${CMAKE_PROJECT_NAME}_SYNTH_XENIA) +if(${CMAKE_PROJECT_NAME}_SYNTH_VAVRA OR ${CMAKE_PROJECT_NAME}_SYNTH_XENIA OR ${CMAKE_PROJECT_NAME}_SYNTH_N2X) add_subdirectory(mc68k) - add_subdirectory(wLib) + if(${CMAKE_PROJECT_NAME}_SYNTH_VAVRA OR ${CMAKE_PROJECT_NAME}_SYNTH_XENIA) + add_subdirectory(wLib) + endif() endif() # ----------------- Synths Osirus/OsTIrus @@ -100,3 +103,7 @@ if(${CMAKE_PROJECT_NAME}_SYNTH_XENIA) add_subdirectory(xtJucePlugin) endif() endif() + +# ----------------- all nords + +add_subdirectory(nord) diff --git a/source/nord/CMakeLists.txt b/source/nord/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.15) + +if(${CMAKE_PROJECT_NAME}_SYNTH_N2X) + add_subdirectory(n2x) +endif() diff --git a/source/nord/n2x/CMakeLists.txt b/source/nord/n2x/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.15) + +add_subdirectory(n2xLib) +add_subdirectory(n2xTestConsole) diff --git a/source/nord/n2x/n2xLib/CMakeLists.txt b/source/nord/n2x/n2xLib/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.10) + +project(n2xLib) + +add_library(n2xLib STATIC) + +set(SOURCES + n2xdevice.cpp n2xdevice.h + n2xdsp.cpp n2xdsp.h + n2xhardware.cpp n2xhardware.h + n2xmc.cpp n2xmc.h + n2xmiditypes.h + n2xrom.cpp n2xrom.h + n2xtypes.h +) + +target_sources(n2xLib PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_link_libraries(n2xLib PUBLIC synthLib 68kEmu) + +if(DSP56300_DEBUGGER) + target_link_libraries(n2xLib PUBLIC dsp56kDebugger) +endif() + +set_property(TARGET n2xLib PROPERTY FOLDER "N2x") + +target_include_directories(n2xLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/source/nord/n2x/n2xLib/n2xdevice.cpp b/source/nord/n2x/n2xLib/n2xdevice.cpp diff --git a/source/nord/n2x/n2xLib/n2xdevice.h b/source/nord/n2x/n2xLib/n2xdevice.h @@ -0,0 +1,5 @@ +#pragma once + +namespace n2x +{ +} diff --git a/source/nord/n2x/n2xLib/n2xdsp.cpp b/source/nord/n2x/n2xLib/n2xdsp.cpp @@ -0,0 +1,172 @@ +#include "n2xdsp.h" + +#include "n2xhardware.h" +#include "dsp56kEmu/dspthread.h" + +namespace n2x +{ + static constexpr dsp56k::TWord g_xyMemSize = 0x40000; + static constexpr dsp56k::TWord g_externalMemAddr = 0x20000; + static constexpr dsp56k::TWord g_pMemSize = 0x2000; // only $0000 < $1400 for DSP, rest for us + static constexpr dsp56k::TWord g_bootCodeBase = 0x1500; + + // DSP56362 bootloader + static constexpr uint32_t g_dspBootCode[] = + { + 0x350013, 0x0afa23, 0xff0035, 0x0afa22, + 0xff000e, 0x0afa01, 0xff0022, 0x0afa20, + 0xff005e, 0x61f400, 0xff1000, 0x050c8f, + 0x0afa00, 0xff0021, 0x31a900, 0x0afa01, + 0xff0012, 0x0ad161, 0x04d191, 0x019191, + 0xff0013, 0x044894, 0x019191, 0xff0016, + 0x045094, 0x221100, 0x06c800, 0xff001f, + 0x019191, 0xff001c, 0x009814, 0x000000, + 0x050c5a, 0x050c5d, 0x62f400, 0xd00000, + 0x08f4b8, 0xd00409, 0x060680, 0xff0029, + 0x07da8a, 0x0c1c10, 0x219000, 0x219100, + 0x06c800, 0xff0033, 0x060380, 0xff0031, + 0x07da8a, 0x0c1c10, 0x07588c, 0x000000, + 0x050c46, 0x0afa02, 0xff005c, 0x0afa01, + 0xff003e, 0x0afa00, 0xff0046, 0x08f484, + 0x000038, 0x050c0b, 0x0afa20, 0xff0043, + 0x08f484, 0x005018, 0x050c06, 0x08f484, + 0x000218, 0x050c03, 0x08f484, 0x001c1e, + 0x0a8426, 0x0a8380, 0xff0049, 0x084806, + 0x0a8380, 0xff004c, 0x085006, 0x221100, + 0x06c800, 0xff0059, 0x0a83a0, 0xff0058, + 0x0a8383, 0xff0052, 0x00008c, 0x050c03, + 0x085846, 0x000000, 0x0000b9, 0x0ae180, + 0x0afa01, 0xff005f, 0x050c00, 0x66f41b, + 0xff0090, 0x0503a6, 0x04cfdd, 0x013f03, + 0x013e23, 0x045517, 0x060980, 0xff008b, + 0x07de85, 0x07de84, 0x07de86, 0x300013, + 0x70f400, 0x001600, 0x06d820, 0x4258a2, + 0x320013, 0x72f400, 0x000c00, 0x06da20, + 0x075a86, 0x300013, 0x06d800, 0xff007d, + 0x54e000, 0x200063, 0x200018, 0x5cd800, + 0x200043, 0x200018, 0x320013, 0x06da00, + 0xff0083, 0x07da8c, 0x200053, 0x200018, + 0x022d07, 0x08d73c, 0x0d104a, 0x000005, + 0x013d03, 0x00008c, 0x050c02, 0x017d03, + 0x000200, 0x000086 + }; + + namespace + { + dsp56k::DefaultMemoryValidator g_memValidator; + } + DSP::DSP(Hardware& _hw, mc68k::Hdi08& _hdiUc, const uint32_t _index) + : m_hardware(_hw) + , m_hdiUC(_hdiUc) + , m_index(_index) + , m_name(_index ? "DSP B" : "DSP A") + , m_memory(g_memValidator, g_pMemSize, g_xyMemSize, g_externalMemAddr) + , m_dsp(m_memory, &m_periphX, &m_periphNop) + { + if(!_hw.isValid()) + return; + + m_periphX.getEsaiClock().setExternalClockFrequency(44100 * 768); // TODO + m_periphX.getEsaiClock().setSamplerate(44100); // TODO + m_periphX.getEsaiClock().setClockSource(dsp56k::EsaiClock::ClockSource::Instructions); // TODO + + auto config = m_dsp.getJit().getConfig(); + + config.aguSupportBitreverse = true; + config.linkJitBlocks = true; + config.dynamicPeripheralAddressing = false; +#ifdef _DEBUG + config.debugDynamicPeripheralAddressing = true; +#endif + config.maxInstructionsPerBlock = 0; + + m_dsp.getJit().setConfig(config); + + // fill P memory with something that reminds us if we jump to garbage + for(dsp56k::TWord i=0; i<m_memory.sizeP(); ++i) + { + m_memory.set(dsp56k::MemArea_P, i, 0x000200); // debug instruction + m_dsp.getJit().notifyProgramMemWrite(i); + } + + // rewrite bootloader to work at address g_bootCodeBase instead of $ff0000 + for(uint32_t i=0; i<std::size(g_dspBootCode); ++i) + { + uint32_t code = g_dspBootCode[i]; + if((g_dspBootCode[i] & 0xffff00) == 0xff0000) + { + code = g_bootCodeBase | (g_dspBootCode[i] & 0xff); + } + + m_memory.set(dsp56k::MemArea_P, i + g_bootCodeBase, code); + m_dsp.getJit().notifyProgramMemWrite(i + g_bootCodeBase); + } + + // set OMR pins so that bootcode wants program data via HDI08 RX + m_dsp.setPC(g_bootCodeBase); + m_dsp.regs().omr.var |= OMR_MA | OMR_MB | OMR_MC | OMR_MD; + + m_hdiUC.setRxEmptyCallback([&](const bool _needMoreData) + { + onUCRxEmpty(_needMoreData); + }); + m_hdiUC.setWriteTxCallback([&](const uint32_t _word) + { + hdiTransferUCtoDSP(_word); + }); + m_hdiUC.setWriteIrqCallback([&](const uint8_t _irq) + { + hdiSendIrqToDSP(_irq); + }); + m_hdiUC.setReadIsrCallback([&](const uint8_t _isr) + { + return hdiUcReadIsr(_isr); + }); + +#if DSP56300_DEBUGGER + m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str(), std::make_shared<dsp56kDebugger::Debugger>(m_dsp.dsp()))); +#else + m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str())); +#endif + + m_thread->setLogToStdout(false); + } + + void DSP::onUCRxEmpty(bool _needMoreData) + { + } + + void DSP::hdiTransferUCtoDSP(const uint32_t _word) + { + LOG('[' << m_name << "] toDSP writeRX=" << HEX(_word)); + hdi08().writeRX(&_word, 1); + } + + void DSP::hdiSendIrqToDSP(uint8_t _irq) + { + dsp().injectExternalInterrupt(_irq); + + while(dsp().hasPendingInterrupts()) + { + std::this_thread::yield(); + } + } + + uint8_t DSP::hdiUcReadIsr(uint8_t _isr) + { + return _isr; + } + + bool DSP::hdiTransferDSPtoUC() + { + if (m_hdiUC.canReceiveData() && hdi08().hasTX()) + { + const auto v = hdi08().readTX(); + LOG('[' << m_name << "] HDI dsp2uc=" << HEX(v)); + m_hdiUC.writeRx(v); + return true; + } + return false; + } + +} diff --git a/source/nord/n2x/n2xLib/n2xdsp.h b/source/nord/n2x/n2xLib/n2xdsp.h @@ -0,0 +1,60 @@ +#pragma once + +#include <memory> +#include <string> + +#include "dsp56kEmu/dsp.h" +#include "dsp56kEmu/dspthread.h" + +namespace mc68k +{ + class Hdi08; +} + +namespace n2x +{ + class Hardware; + + class DSP + { + public: + DSP(Hardware& _hw, mc68k::Hdi08& _hdiUc, uint32_t _index); + + dsp56k::HDI08& hdi08() + { + return m_periphX.getHDI08(); + } + + dsp56k::DSP& dsp() + { + return m_dsp; + } + + dsp56k::Peripherals56362& getPeriph() + { + return m_periphX; + } + + private: + void onUCRxEmpty(bool _needMoreData); + void hdiTransferUCtoDSP(uint32_t _word); + void hdiSendIrqToDSP(uint8_t _irq); + uint8_t hdiUcReadIsr(uint8_t _isr); + bool hdiTransferDSPtoUC(); + + Hardware& m_hardware; + mc68k::Hdi08& m_hdiUC; + + const uint32_t m_index; + const std::string m_name; + + dsp56k::PeripheralsNop m_periphNop; + dsp56k::Peripherals56362 m_periphX; + dsp56k::Memory m_memory; + dsp56k::DSP m_dsp; + + std::unique_ptr<dsp56k::DSPThread> m_thread; + + bool m_receivedMagicEsaiPacket = false; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xhardware.cpp b/source/nord/n2x/n2xLib/n2xhardware.cpp @@ -0,0 +1,23 @@ +#include "n2xhardware.h" + +namespace n2x +{ + Hardware::Hardware() + : m_uc(m_rom) + , m_dspA(*this, m_uc.getHdi08A(), 0) + , m_dspB(*this, m_uc.getHdi08B(), 1) + { + if(!m_rom.isValid()) + return; + } + + bool Hardware::isValid() const + { + return m_rom.isValid(); + } + + void Hardware::process() + { + m_uc.exec(); + } +} diff --git a/source/nord/n2x/n2xLib/n2xhardware.h b/source/nord/n2x/n2xLib/n2xhardware.h @@ -0,0 +1,23 @@ +#pragma once +#include "n2xdsp.h" +#include "n2xmc.h" +#include "n2xrom.h" + +namespace n2x +{ + class Hardware + { + public: + Hardware(); + + bool isValid() const; + + void process(); + + private: + Rom m_rom; + Microcontroller m_uc; + DSP m_dspA; + DSP m_dspB; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xmc.cpp b/source/nord/n2x/n2xLib/n2xmc.cpp @@ -0,0 +1,143 @@ +#include "n2xmc.h" + +#include <cassert> + +#include "n2xrom.h" + +namespace n2x +{ + Microcontroller::Microcontroller(const Rom& _rom) + { + if(!_rom.isValid()) + return; + + m_rom = _rom.data(); + m_ram.fill(0); + + dumpAssembly("n2x_68k.asm", g_romAddress, g_romSize); + + reset(); + + setPC(g_pcInitial); + } + + uint32_t Microcontroller::read32(uint32_t _addr) + { + return Mc68k::read32(_addr); + } + + uint16_t Microcontroller::readImm16(const uint32_t _addr) + { + if(_addr < g_romSize) return readW(m_rom.data(), _addr); + if(_addr >= g_ramAddress && _addr < g_ramAddress + g_ramSize) return readW(m_ram.data(), _addr - g_ramAddress); + + assert(!m_hdi08A.isInRange(_addr)); + assert(!m_hdi08B.isInRange(_addr)); + + return 0; + } + + uint16_t Microcontroller::read16(const uint32_t _addr) + { + if(_addr < g_romSize) return readW(m_rom.data(), _addr); + if(_addr >= g_ramAddress && _addr < g_ramAddress + g_ramSize) return readW(m_ram.data(), _addr - g_ramAddress); + + const auto pa = static_cast<mc68k::PeriphAddress>(_addr); + + if(m_hdi08A.isInRange(_addr)) return m_hdi08A.read16(pa); + if(m_hdi08B.isInRange(_addr)) return m_hdi08B.read16(pa); + + return Mc68k::read16(_addr); + } + + uint8_t Microcontroller::read8(const uint32_t _addr) + { + if(_addr < g_romSize) return m_rom[_addr]; + if(_addr >= g_ramAddress && _addr < g_ramAddress + g_ramSize) return m_ram[_addr - g_ramAddress]; + + const auto pa = static_cast<mc68k::PeriphAddress>(_addr); + + if(m_hdi08A.isInRange(_addr)) return m_hdi08A.read8(pa); + if(m_hdi08B.isInRange(_addr)) return m_hdi08B.read8(pa); + + return Mc68k::read8(_addr); + } + + void Microcontroller::write16(const uint32_t _addr, const uint16_t _val) + { + if(_addr < g_romSize) + { + assert(false); + writeW(m_rom.data(), _addr, _val); + return; + } + if(_addr >= g_ramAddress && _addr < g_ramAddress + g_ramSize) + { + writeW(m_ram.data(), _addr - g_ramAddress, _val); + return; + } + + const auto pa = static_cast<mc68k::PeriphAddress>(_addr); + + if(m_hdi08A.isInRange(_addr)) + { + m_hdi08A.write16(pa, _val); + return; + } + + if(m_hdi08B.isInRange(_addr)) + { + m_hdi08B.write16(pa, _val); + return; + } + + Mc68k::write16(_addr, _val); + } + + void Microcontroller::write8(const uint32_t _addr, const uint8_t _val) + { + if(_addr < g_romSize) + { + assert(false); + m_rom[_addr] = _val; + return; + } + if(_addr >= g_ramAddress && _addr < g_ramAddress + g_ramSize) + { + m_ram[_addr - g_ramAddress] = _val; + return; + } + + const auto pa = static_cast<mc68k::PeriphAddress>(_addr); + + if(m_hdi08A.isInRange(_addr)) + { + m_hdi08A.write8(pa, _val); + return; + } + + if(m_hdi08B.isInRange(_addr)) + { + m_hdi08B.write8(pa, _val); + return; + } + + Mc68k::write8(_addr, _val); + } + + uint32_t Microcontroller::exec() + { + const auto pc = getPC(); + if(pc >= g_ramAddress) + { + if(pc == 0x1000c8) + dumpAssembly("nl2x_68k_ram.asm", g_ramAddress, g_ramSize); + } + const auto cycles = Mc68k::exec(); + + m_hdi08A.exec(cycles); + m_hdi08B.exec(cycles); + + return cycles; + } +} diff --git a/source/nord/n2x/n2xLib/n2xmc.h b/source/nord/n2x/n2xLib/n2xmc.h @@ -0,0 +1,40 @@ +#pragma once + +#include "n2xtypes.h" +#include "mc68k/hdi08.h" +#include "mc68k/hdi08periph.h" +#include "mc68k/mc68k.h" + +namespace n2x +{ + class Rom; + + using Hdi08DspA = mc68k::Hdi08Periph<g_dspAAddress>; + using Hdi08DspB = mc68k::Hdi08Periph<g_dspBAddress>; + + class Microcontroller : public mc68k::Mc68k + { + public: + explicit Microcontroller(const Rom& _rom); + + auto& getHdi08A() { return m_hdi08A.getHdi08(); } + auto& getHdi08B() { return m_hdi08B.getHdi08(); } + + uint32_t exec() override; + + private: + uint32_t read32(uint32_t _addr) override; + uint16_t readImm16(uint32_t _addr) override; + uint16_t read16(uint32_t _addr) override; + uint8_t read8(uint32_t _addr) override; + + void write16(uint32_t _addr, uint16_t _val) override; + void write8(uint32_t _addr, uint8_t _val) override; + + std::array<uint8_t, g_romSize> m_rom; + std::array<uint8_t, g_ramSize> m_ram; + + Hdi08DspA m_hdi08A; + Hdi08DspB m_hdi08B; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xmiditypes.h b/source/nord/n2x/n2xLib/n2xmiditypes.h diff --git a/source/nord/n2x/n2xLib/n2xrom.cpp b/source/nord/n2x/n2xLib/n2xrom.cpp @@ -0,0 +1,20 @@ +#include "n2xrom.h" + +#include "synthLib/os.h" + +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 @@ -0,0 +1,23 @@ +#pragma once + +#include <array> +#include <cstdint> +#include <string> + +#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; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xtypes.h b/source/nord/n2x/n2xLib/n2xtypes.h @@ -0,0 +1,32 @@ +#pragma once + +#include <cstdint> + +namespace n2x +{ + /* CHIP SELECTS START SIZE + CS0 = DSPs (both) $200000 $800 + CS1 = nc + CS2 = nc + CS3 = nc + CS4 = front panel $202800 $800 + CS5 = keyboard via 74HC374 TSSOP (both) $203000 $800 + CS6 = front panel $202000 $800 + CS7 = nc + CS8 = RAM A $100000 $40000 + CS9 = RAM B $100000 $40000 + CS10 = RAMs (both) $100000 $40000 + CSBOOT = BootROM $?????? $????? + */ + + static constexpr uint32_t g_romSize = 1024 * 512; + static constexpr uint32_t g_ramSize = 1024 * 256; + + static constexpr uint32_t g_pcInitial = 0xc2; + + static constexpr uint32_t g_romAddress = 0; + static constexpr uint32_t g_ramAddress = 0x100000; + + static constexpr uint32_t g_dspAAddress = 0x200008; + static constexpr uint32_t g_dspBAddress = 0x200010; +} diff --git a/source/nord/n2x/n2xTestConsole/CMakeLists.txt b/source/nord/n2x/n2xTestConsole/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.10) + +project(n2xTestConsole) + +add_executable(n2xTestConsole) + +set(SOURCES + n2xTestConsole.cpp +) + +target_sources(n2xTestConsole PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_link_libraries(n2xTestConsole PUBLIC n2xLib) +set_property(TARGET n2xTestConsole PROPERTY FOLDER "N2x") diff --git a/source/nord/n2x/n2xTestConsole/n2xTestConsole.cpp b/source/nord/n2x/n2xTestConsole/n2xTestConsole.cpp @@ -0,0 +1,26 @@ +#include <iostream> + +#include "n2xLib/n2xhardware.h" +#include "n2xLib/n2xrom.h" + +namespace n2x +{ + class Hardware; +} + +int main() +{ + const n2x::Rom rom; + + if(!rom.isValid()) + { + std::cout << "Failed to load rom file\n"; + return -1; + } + + std::unique_ptr<n2x::Hardware> hw; + hw.reset(new n2x::Hardware()); + + while(true) + hw->process(); +}