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

n2xdsp.cpp (5781B)


      1 #include "n2xdsp.h"
      2 
      3 #include "n2xhardware.h"
      4 
      5 #include "dsp56kDebugger/debugger.h"
      6 
      7 #include "dsp56kEmu/dspthread.h"
      8 
      9 #include "mc68k/hdi08.h"
     10 
     11 namespace n2x
     12 {
     13 	static constexpr dsp56k::TWord g_xyMemSize			= 0x010000;
     14 	static constexpr dsp56k::TWord g_externalMemAddr	= 0x008000;
     15 	static constexpr dsp56k::TWord g_pMemSize			= 0x004000;
     16 
     17 	namespace
     18 	{
     19 		dsp56k::DefaultMemoryValidator g_memValidator;
     20 	}
     21 	DSP::DSP(Hardware& _hw, mc68k::Hdi08& _hdiUc, const uint32_t _index)
     22 	: m_hardware(_hw)
     23 	, m_hdiUC(_hdiUc)
     24 	, m_index(_index)
     25 	, m_name(_index ? "DSP B" : "DSP A")
     26 	, m_memory(g_memValidator, g_pMemSize, g_xyMemSize, g_externalMemAddr)
     27 	, m_dsp(m_memory, &m_periphX, &m_periphNop)
     28 	, m_haltDSP(m_dsp)
     29 	, m_boot(m_dsp)
     30 	{
     31 		if(!_hw.isValid())
     32 			return;
     33 
     34 		{
     35 			auto& clock = m_periphX.getEsaiClock();
     36 			auto& esai = m_periphX.getEsai();
     37 
     38 			clock.setExternalClockFrequency(3'333'333); // schematic claims 1 MHz but we measured 10/3 Mhz
     39 
     40 			constexpr auto samplerate = g_samplerate;
     41 			constexpr auto clockMultiplier = 2;
     42 
     43 			clock.setSamplerate(samplerate * clockMultiplier);
     44 
     45 			clock.setClockSource(dsp56k::EsaiClock::ClockSource::Cycles);
     46 
     47 			if(m_index == 0)
     48 			{
     49 				// DSP A = chip U2 = left on the schematic
     50 				// Sends its audio to DSP B at twice the sample rate, it sends four words per frame
     51 				clock.setEsaiDivider(&esai, 0);
     52 			}
     53 			else
     54 			{
     55 				// DSP B = chip U3 = right on the schematic
     56 				// receives audio from DSP A at twice the sample rate
     57 				// sends its audio to the DACs at regular sample rate
     58 				clock.setEsaiDivider(&esai, 1, 0);
     59 //				clock.setEsaiCounter(&esai, -1, 0);
     60 			}
     61 		}
     62 
     63 		auto config = m_dsp.getJit().getConfig();
     64 
     65 		config.aguSupportBitreverse = true;
     66 		config.linkJitBlocks = true;
     67 		config.dynamicPeripheralAddressing = false;
     68 #ifdef _DEBUG
     69 		config.debugDynamicPeripheralAddressing = true;
     70 #endif
     71 		config.maxInstructionsPerBlock = 0;
     72 		config.support16BitSCMode = true;
     73 		config.dynamicFastInterrupts = true;
     74 
     75 		m_dsp.getJit().setConfig(config);
     76 
     77 		// fill P memory with something that reminds us if we jump to garbage
     78 		for(dsp56k::TWord i=0; i<m_memory.sizeP(); ++i)
     79 		{
     80 			m_memory.set(dsp56k::MemArea_P, i, 0x000200);	// debug instruction
     81 			m_dsp.getJit().notifyProgramMemWrite(i);
     82 		}
     83 
     84 		hdi08().setRXRateLimit(0);
     85 
     86 		m_periphX.getEsai().writeEmptyAudioIn(2);
     87 
     88 		m_hdiUC.setRxEmptyCallback([&](const bool _needMoreData)
     89 		{
     90 			onUCRxEmpty(_needMoreData);
     91 		});
     92 
     93 		m_hdiUC.setWriteTxCallback([this](const uint32_t _word)
     94 		{
     95 			if(m_boot.hdiWriteTX(_word))
     96 				onDspBootFinished();
     97 		});
     98 		m_hdiUC.setWriteIrqCallback([&](const uint8_t _irq)
     99 		{
    100 			hdiSendIrqToDSP(_irq);
    101 		});
    102 		m_hdiUC.setReadIsrCallback([&](const uint8_t _isr)
    103 		{
    104 			return hdiUcReadIsr(_isr);
    105 		});
    106 		m_hdiUC.setInitHdi08Callback([&]
    107 		{
    108 			// clear init flag again immediately, code is waiting for it to happen
    109 			m_hdiUC.icr(m_hdiUC.icr() & 0x7f);
    110 			m_hdiUC.isr(m_hdiUC.isr() | mc68k::Hdi08::IsrBits::Txde | mc68k::Hdi08::IsrBits::Trdy);
    111 		});
    112 
    113 		m_irqInterruptDone = dsp().registerInterruptFunc([this]
    114 		{
    115 			m_triggerInterruptDone.notify();
    116 		});
    117 	}
    118 
    119 	void DSP::terminate()
    120 	{
    121 		for(uint32_t i=0; i<32768; ++i)
    122 			m_triggerInterruptDone.notify();
    123 
    124 		m_thread->terminate();
    125 	}
    126 
    127 	void DSP::join() const
    128 	{
    129 		m_thread->join();
    130 	}
    131 
    132 	void DSP::onDspBootFinished()
    133 	{
    134 		m_hdiUC.setWriteTxCallback([&](const uint32_t _word)
    135 		{
    136 			hdiTransferUCtoDSP(_word);
    137 		});
    138 
    139 #if DSP56300_DEBUGGER
    140 		if(!m_index)
    141 			m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str(), std::make_shared<dsp56kDebugger::Debugger>(m_dsp)));
    142 		else
    143 #endif
    144 		m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str()));
    145 
    146 		m_thread->setLogToStdout(false);
    147 	}
    148 
    149 	void DSP::onUCRxEmpty(const bool _needMoreData)
    150 	{
    151 		if(_needMoreData)
    152 		{
    153 			hwLib::ScopedResumeDSP rA(m_hardware.getDSPA().getHaltDSP());
    154 			hwLib::ScopedResumeDSP rB(m_hardware.getDSPB().getHaltDSP());
    155 
    156 			while(dsp().hasPendingInterrupts())
    157 				std::this_thread::yield();
    158 		}
    159 		hdiTransferDSPtoUC();
    160 	}
    161 
    162 	void DSP::hdiTransferUCtoDSP(const uint32_t _word)
    163 	{
    164 //		LOG('[' << m_name << "] toDSP writeRX=" << HEX(_word) << ", ucPC=" << HEX(m_hardware.getUC().getPrevPC()));
    165 		hdi08().writeRX(&_word, 1);
    166 	}
    167 
    168 	void DSP::hdiSendIrqToDSP(const uint8_t _irq)
    169 	{
    170 		if(m_hardware.requestingHaltDSPs() && getHaltDSP().isHalting())
    171 		{
    172 			// this is a very hacky way to execute a DSP interrupt even though the DSP is halted. This case happens if the DSPs run too fast
    173 			// and are halted by the sync code, but the UC wants to inject an interrupt, which needs to be executed immediately.
    174 			// In this case, we execute the interrupt without altering the DSP state
    175 
    176 			const auto numOps = dsp().getInstructionCounter();
    177 			const auto numCycles = dsp().getCycles();
    178 
    179 			const auto pc = dsp().getPC();
    180 			dsp().getJit().exec(_irq);
    181 			dsp().setPC(pc);
    182 
    183 			const_cast<uint64_t&>(dsp().getInstructionCounter()) = numOps;
    184 			const_cast<uint64_t&>(dsp().getCycles()) = numCycles;
    185 		}
    186 		else
    187 		{
    188 			dsp().injectExternalInterrupt(_irq);
    189 			dsp().injectExternalInterrupt(m_irqInterruptDone);
    190 
    191 			hwLib::ScopedResumeDSP rA(m_hardware.getDSPA().getHaltDSP());
    192 			hwLib::ScopedResumeDSP rB(m_hardware.getDSPB().getHaltDSP());
    193 			m_triggerInterruptDone.wait();
    194 		}
    195 
    196 		hdiTransferDSPtoUC();
    197 	}
    198 
    199 	uint8_t DSP::hdiUcReadIsr(uint8_t _isr)
    200 	{
    201 		hdiTransferDSPtoUC();
    202 
    203 		// transfer DSP host flags HF2&3 to uc
    204 		const auto hf23 = hdi08().readControlRegister() & 0x18;
    205 		_isr &= ~0x18;
    206 		_isr |= hf23;
    207 		// always ready to receive more data
    208 		_isr |= mc68k::Hdi08::IsrBits::Trdy;
    209 		return _isr;
    210 	}
    211 
    212 	bool DSP::hdiTransferDSPtoUC()
    213 	{
    214 		if (m_hdiUC.canReceiveData() && hdi08().hasTX())
    215 		{
    216 			const auto v = hdi08().readTX();
    217 //			LOG('[' << m_name << "] HDI dsp2UC=" << HEX(v));
    218 			m_hdiUC.writeRx(v);
    219 			return true;
    220 		}
    221 		return false;
    222 	}
    223 }