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

mqdsp.cpp (5403B)


      1 #include "mqdsp.h"
      2 
      3 #include "mqhardware.h"
      4 
      5 #if DSP56300_DEBUGGER
      6 #include "dsp56kDebugger/debugger.h"
      7 #endif
      8 
      9 #include "mc68k/hdi08.h"
     10 
     11 #include "dsp56kEmu/aar.h"
     12 
     13 namespace mqLib
     14 {
     15 	static dsp56k::DefaultMemoryValidator g_memoryValidator;
     16 	static constexpr dsp56k::TWord g_magicEsaiPacket = 0x654300;
     17 
     18 	MqDsp::MqDsp(Hardware& _hardware, mc68k::Hdi08& _hdiUC, const uint32_t _index)
     19 	: m_hardware(_hardware), m_hdiUC(_hdiUC)
     20 	, m_index(_index)
     21 	, m_name({static_cast<char>('A' + _index)})
     22 	, m_periphX(nullptr)
     23 	, m_memory(g_memoryValidator, g_pMemSize, g_xyMemSize, g_bridgedAddr, m_memoryBuffer)
     24 	, m_dsp(m_memory, &m_periphX, &m_periphNop)
     25 	, m_boot(m_dsp)
     26 	{
     27 		if(!_hardware.isValid())
     28 			return;
     29 
     30 		m_periphX.getEsaiClock().setExternalClockFrequency(44100 * 768); // measured as being roughly 33,9MHz, this should be exact
     31 		m_periphX.getEsaiClock().setSamplerate(44100); // verified
     32 		m_periphX.getEsaiClock().setClockSource(dsp56k::EsaiClock::ClockSource::Cycles);
     33 
     34 		auto config = m_dsp.getJit().getConfig();
     35 
     36 		config.aguSupportBitreverse = true;
     37 		config.linkJitBlocks = true;
     38 		config.dynamicPeripheralAddressing = false;
     39 #ifdef _DEBUG
     40 		config.debugDynamicPeripheralAddressing = true;
     41 #endif
     42 		config.maxInstructionsPerBlock = 0;	// TODO: needs to be 1 if DSP factory tests are run, to be investigated
     43 
     44 		// allow dynamic peripheral addressing for code following clr b M_AAR3,r2
     45 		enableDynamicPeripheralAddressing(config, m_dsp, 0x62f41b, dsp56k::M_AAR3, 16);
     46 
     47 		m_dsp.getJit().setConfig(config);
     48 
     49 		// fill P memory with something that reminds us if we jump to garbage
     50 		for(dsp56k::TWord i=0; i<m_memory.sizeP(); ++i)
     51 		{
     52 			m_memory.set(dsp56k::MemArea_P, i, 0x000200);	// debug instruction
     53 			m_dsp.getJit().notifyProgramMemWrite(i);
     54 		}
     55 
     56 		getPeriph().disableTimers(true);	// only used to test DSP load, we report 0 all the time for now
     57 
     58 		m_periphX.getEsai().writeEmptyAudioIn(8);
     59 
     60 		hdi08().setRXRateLimit(0);
     61 		hdi08().setTransmitDataAlwaysEmpty(false);
     62 
     63 		m_hdiUC.setRxEmptyCallback([&](const bool needMoreData)
     64 		{
     65 			onUCRxEmpty(needMoreData);
     66 		});
     67 		m_hdiUC.setWriteTxCallback([&](const uint32_t _word)
     68 		{
     69 			if(m_boot.hdiWriteTX(_word))
     70 				onDspBootFinished();
     71 		});
     72 		m_hdiUC.setWriteIrqCallback([&](const uint8_t _irq)
     73 		{
     74 			hdiSendIrqToDSP(_irq);
     75 		});
     76 		m_hdiUC.setReadIsrCallback([&](const uint8_t _isr)
     77 		{
     78 			return hdiUcReadIsr(_isr);
     79 		});
     80 	}
     81 
     82 	void MqDsp::exec()
     83 	{
     84 		m_thread->join();
     85 		m_thread.reset();
     86 
     87 		m_hdiUC.setRxEmptyCallback({});
     88 		m_dsp.exec();
     89 	}
     90 
     91 	void MqDsp::dumpPMem(const std::string& _filename)
     92 	{
     93 		m_memory.saveAssembly((_filename + ".asm").c_str(), 0, g_pMemSize, true, false, &m_periphX);
     94 	}
     95 
     96 	void MqDsp::dumpXYMem(const std::string& _filename) const
     97 	{
     98 		m_memory.save((_filename + "_X.txt").c_str(), dsp56k::MemArea_X);
     99 		m_memory.save((_filename + "_Y.txt").c_str(), dsp56k::MemArea_Y);
    100 	}
    101 
    102 	void MqDsp::transferHostFlagsUc2Dsdp()
    103 	{
    104 		const uint32_t hf01 = m_hdiUC.icr() & 0x18;
    105 
    106 		if (hf01 != m_hdiHF01)
    107 		{
    108 //			LOG('[' << m_name << "] HDI HF01=" << HEXN((hf01>>3),1));
    109 			waitDspRxEmpty();
    110 			m_hdiHF01 = hf01;
    111 			hdi08().setPendingHostFlags01(hf01);
    112 		}
    113 	}
    114 
    115 	void MqDsp::onDspBootFinished()
    116 	{
    117 		m_hdiUC.setWriteTxCallback([&](const uint32_t _word)
    118 		{
    119 			hdiTransferUCtoDSP(_word);
    120 		});
    121 
    122 #if DSP56300_DEBUGGER
    123 		m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str(), std::make_shared<dsp56kDebugger::Debugger>(m_dsp)));
    124 #else
    125 		m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str()));
    126 #endif
    127 
    128 		m_thread->setLogToStdout(false);
    129 	}
    130 
    131 	void MqDsp::onUCRxEmpty(bool _needMoreData)
    132 	{
    133 		hdi08().injectTXInterrupt();
    134 
    135 		if (_needMoreData)
    136 		{
    137 			m_hardware.ucYieldLoop([&]()
    138 			{
    139 				return dsp().hasPendingInterrupts() || (hdi08().txInterruptEnabled() && !hdi08().hasTX());
    140 			});
    141 		}
    142 
    143 		hdiTransferDSPtoUC();
    144 	}
    145 
    146 	bool MqDsp::hdiTransferDSPtoUC()
    147 	{
    148 		if (m_hdiUC.canReceiveData() && hdi08().hasTX())
    149 		{
    150 			const auto v = hdi08().readTX();
    151 //			LOG('[' << m_name << "] HDI dsp2uc=" << HEX(v));
    152 			if (v == g_magicEsaiPacket)
    153 				m_receivedMagicEsaiPacket = true;
    154 			m_hdiUC.writeRx(v);
    155 			return true;
    156 		}
    157 		return false;
    158 	}
    159 
    160 	void MqDsp::hdiTransferUCtoDSP(dsp56k::TWord _word)
    161 	{
    162 		m_haveSentTXtoDSP = true;
    163 //		LOG('[' << m_name << "] toDSP writeRX=" << HEX(_word));
    164 		hdi08().writeRX(&_word, 1);
    165 	}
    166 
    167 	void MqDsp::hdiSendIrqToDSP(uint8_t _irq)
    168 	{
    169 		waitDspRxEmpty();
    170 
    171 		if(_irq == 0x92)
    172 		{
    173 //			LOG('[' << m_name << "] DSP timeout, waiting...");
    174 			// this one is sent by the uc if the DSP taking too long to reset HF2 back to one. Instead of aborting here, we wait a bit longer for the DSP to finish on its own
    175 			m_hardware.ucYieldLoop([&]
    176 			{
    177 				return !bittest(hdi08().readControlRegister(), dsp56k::HDI08::HCR_HF2);
    178 			});
    179 //			LOG('[' << m_name << "] DSP timeout wait done");
    180 		}
    181 //		else
    182 //			LOG('[' << m_name << "] Inject interrupt" << HEXN(_irq,2));
    183 
    184 		dsp().injectExternalInterrupt(_irq);
    185 
    186 		m_hardware.ucYieldLoop([&]()
    187 		{
    188 			return dsp().hasPendingInterrupts();
    189 		});
    190 
    191 		hdiTransferDSPtoUC();
    192 	}
    193 
    194 	uint8_t MqDsp::hdiUcReadIsr(uint8_t _isr)
    195 	{
    196 		// transfer DSP host flags HF2&3 to uc
    197 		const auto hf23 = hdi08().readControlRegister() & 0x18;
    198 		_isr &= ~0x18;
    199 		_isr |= hf23;
    200 		return _isr;
    201 	}
    202 
    203 	void MqDsp::waitDspRxEmpty()
    204 	{
    205 		m_hardware.ucYieldLoop([&]()
    206 		{
    207 			return (hdi08().hasRXData() && hdi08().rxInterruptEnabled()) || dsp().hasPendingInterrupts();
    208 		});
    209 //		LOG("writeRX wait over");
    210 	}
    211 
    212 }