// license:BSD-3-Clause // copyright-holders:F. Ulivi /*************************************************************************** cop452.h Frequency generator & counter ***************************************************************************/ #include "emu.h" #include "cop452.h" // Debugging //#define VERBOSE LOG_GENERAL #define VERBOSE 0 #include "logmacro.h" // Device type definition DEFINE_DEVICE_TYPE(COP452, cop452_device, "cop452", "National Semiconductor COP452 frequency generator") cop452_device::cop452_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : device_t(mconfig, COP452, tag, owner, clock) , m_out_handlers(*this) { } void cop452_device::cs_w(int state) { m_cs = state; if (m_cs) { // CS removed m_spi_state = 0; m_sr = 0; } } void cop452_device::sk_w(int state) { if (!m_cs && !m_sk && state) { // Rising edge on SK LOG("bit %d %u\n", m_di, m_spi_state); if (m_spi_state == 0 && m_di) { // Got start bit m_spi_state = 1; } else if (m_spi_state >= 1 && m_spi_state < 6) { // Shifting instruction in m_sr = (m_sr << 1) | m_di; m_spi_state++; if (m_spi_state == 6) { LOG("Inst = %x\n", m_sr); m_spi_state = 22; unsigned idx = !BIT(m_sr, 0); char reg = idx ? 'B' : 'A'; switch (m_sr) { case 0b00000: case 0b00001: // LDRA/B LOG("LDR%c\n", reg); m_spi_state = 6; m_reg[idx] = 0; break; case 0b00010: case 0b00011: // RDRA/B // TODO: not implemented ATM LOG("RDR%c\n", reg); break; case 0b00100: case 0b00101: // TRCA/B LOG("TRC%c\n", reg); m_cnt[idx] = m_reg[idx]; set_timer(idx); break; case 0b00110: case 0b00111: // TCRA/B // TODO: LOG("TCR%c\n", reg); break; case 0b01000: // CK1 LOG("CK1\n"); m_clk_div_4 = false; break; case 0b01001: // CK4 LOG("CK4\n"); m_clk_div_4 = true; break; default: if (m_sr & 0b10000) { // LDM m_mode = m_sr & 0b01111; LOG("LDM %x\n", m_mode); set_timer(0); set_timer(1); if (m_mode == MODE_NUMBER_PULSES) { // Always start with OA = 1 set_output(0, true); } else if (m_mode == MODE_WHITE_NOISE || m_mode == MODE_GATED_WHITE) { // Preset bit 15 & 16 of register A when entering // white noise modes m_reg[0] |= 0x8000; m_regA_b16 = true; } } else { // Unknown instruction LOG("Unknown instruction\n"); } break; } } } else if (m_spi_state >= 6 && m_spi_state < 22) { // Loading A/B register unsigned idx = !BIT(m_sr, 0); m_reg[idx] = (m_reg[idx] << 1) | m_di; m_spi_state++; if (m_spi_state == 22) { LOG("REG%c = %04x\n", idx ? 'B' : 'A', m_reg[idx]); } } } m_sk = state; } void cop452_device::di_w(int state) { m_di = state; } int cop452_device::do_r() { // TODO: return 0; } void cop452_device::device_start() { m_timers[0] = timer_alloc(FUNC(cop452_device::timer_tick), this); m_timers[1] = timer_alloc(FUNC(cop452_device::timer_tick), this); save_item(NAME(m_mode)); save_item(NAME(m_clk_div_4)); save_item(NAME(m_cs)); save_item(NAME(m_sk)); save_item(NAME(m_di)); save_item(NAME(m_out)); save_item(NAME(m_regA_b16)); save_item(NAME(m_reg)); save_item(NAME(m_cnt)); save_item(NAME(m_spi_state)); save_item(NAME(m_sr)); } void cop452_device::device_reset() { // Set reset mode m_mode = MODE_RESET; m_clk_div_4 = true; m_out[0] = m_out[1] = true; set_output(0, false); set_output(1, false); m_spi_state = 0; m_sr = 0; m_timers[0]->reset(); m_timers[1]->reset(); } TIMER_CALLBACK_MEMBER(cop452_device::timer_tick) { switch (m_mode) { case MODE_DUAL_FREQ: toggle_n_reload(param); break; case MODE_TRIG_PULSE: // TODO: NOT IMPLEMENTED break; case MODE_NUMBER_PULSES: if (param == 0) { toggle_n_reload(0); if (!m_out[0]) { // It seems that cnt B decrements each time OA goes low if (m_cnt[1] != 0) { m_cnt[1]--; } else { // End of pulse train toggle_n_reload(1); m_mode = MODE_RESET; } } } break; case MODE_DUTY_CYCLE: // TODO: NOT IMPLEMENTED break; case MODE_FREQ_COUNT: // TODO: NOT IMPLEMENTED break; case MODE_DUAL_COUNT: // TODO: NOT IMPLEMENTED break; case MODE_WAVE_MEAS: // TODO: NOT IMPLEMENTED break; case MODE_TRIG_COUNT: // TODO: NOT IMPLEMENTED break; case MODE_WHITE_NOISE: case MODE_GATED_WHITE: { if (param == 0) { // Reg A & its 17th bit (m_regA_b16) form a 17-bit LFSR // LFSR uses X^17+X^14+1 polynomial to generate a pseudo-random // maximal-length sequence bool feedback = m_regA_b16 ^ BIT(m_reg[0], 13); m_regA_b16 = BIT(m_reg[0], 15); m_reg[0] <<= 1; m_reg[0] |= feedback; } else { toggle_n_reload(1); } bool new_out_0 = m_regA_b16; if (m_mode == MODE_GATED_WHITE) { new_out_0 &= m_out[1]; } set_output(0, new_out_0); } break; default: break; } set_timer(param); } attotime cop452_device::counts_to_attotime(unsigned counts) const { if (m_clk_div_4) { return clocks_to_attotime((counts + 1) * 4); } else { return clocks_to_attotime(counts + 1); } } void cop452_device::set_timer(unsigned idx) { attotime target = attotime::never; switch (m_mode) { case MODE_DUAL_FREQ: // Cnt A & B count independently target = counts_to_attotime(m_cnt[idx]); break; case MODE_TRIG_PULSE: // TODO: NOT IMPLEMENTED break; case MODE_NUMBER_PULSES: // Cnt A generates OA frequency // Cnt B counts the periods to output if (idx == 0) { target = counts_to_attotime(m_cnt[0]); } break; case MODE_DUTY_CYCLE: // TODO: NOT IMPLEMENTED break; case MODE_FREQ_COUNT: // TODO: NOT IMPLEMENTED break; case MODE_DUAL_COUNT: // TODO: NOT IMPLEMENTED break; case MODE_WAVE_MEAS: // TODO: NOT IMPLEMENTED break; case MODE_TRIG_COUNT: // TODO: NOT IMPLEMENTED break; case MODE_WHITE_NOISE: case MODE_GATED_WHITE: // Cnt A is not used. Timer 0 expires once per internal clock period. // Cnt B generates squarewave signal on OB if (idx == 0) { target = counts_to_attotime(0); } else { target = counts_to_attotime(m_cnt[1]); } break; default: break; } m_timers[idx]->adjust(target, idx); } void cop452_device::set_output(unsigned idx, bool state) { if (m_out[idx] != state) { m_out[idx] = state; LOG("OUT %u=%d @%s\n", idx, state, machine().time().as_string()); m_out_handlers[idx](state); } } void cop452_device::toggle_output(unsigned idx) { set_output(idx, !m_out[idx]); } void cop452_device::toggle_n_reload(unsigned idx) { // Toggle output OA/OB and reload its associated counter toggle_output(idx); m_cnt[idx] = m_reg[idx]; }