diff options
Diffstat (limited to 'src/mame/audio/sente6vb.cpp')
-rw-r--r-- | src/mame/audio/sente6vb.cpp | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/src/mame/audio/sente6vb.cpp b/src/mame/audio/sente6vb.cpp new file mode 100644 index 00000000000..358e646182d --- /dev/null +++ b/src/mame/audio/sente6vb.cpp @@ -0,0 +1,526 @@ +// license:BSD-3-Clause +// copyright-holders:Aaron Giles +/*************************************************************************** + + Bally/Sente 6VB audio board emulation + + This serial audio board appears to be based on the Sequential Circuits + Six-Trak synthesizer. + + The later revision of this board replaces the 8253-5 PIT and much + associated logic with a ST1001 custom gate array. + +**************************************************************************** + + Memory map + +**************************************************************************** + + ======================================================================== + Z80 CPU + ======================================================================== + 0000-1FFF R xxxxxxxx Program ROM + 2000-3FFF R/W xxxxxxxx Option RAM/ROM (assumed to be RAM for now) + 4000-5FFF R/W xxxxxxxx Program RAM + 6000-6001 W xxxxxxxx 6850 UART output (to main board) + E000-E001 R xxxxxxxx 6850 UART input (from main board) + ======================================================================== + 0000-0003 R/W xxxxxxxx 8253 counter chip I/O + 0008 R ------xx Counter state + R ------x- State of counter #0 OUT signal (active high) + R -------x State of flip-flop feeding counter #0 (active low) + 0008 W --xxxxxx Counter control + W --x----- NMI enable (1=enabled, 0=disabled/clear) + W ---x---- CLEAR on flip-flop feeding counter #0 (active low) + W ----x--- Input of flip-flop feeding counter #0 + W -----x-- PRESET on flip-flop feeding counter #0 (active low) + W ------x- GATE signal for counter #0 (active high) + W -------x Audio enable + 000A W --xxxxxx DAC data latch (upper 6 bits) + 000B W xxxxxx-- DAC data latch (lower 6 bits) + 000C W -----xxx CEM3394 register select + 000E W --xxxxxx CEM3394 chip enable (active high) + W --x----- CEM3394 chip 0 enable + W ---x---- CEM3394 chip 1 enable + W ----x--- CEM3394 chip 2 enable + W -----x-- CEM3394 chip 3 enable + W ------x- CEM3394 chip 4 enable + W -------x CEM3394 chip 5 enable + ======================================================================== + Interrupts: + INT generated by counter #2 OUT signal on 8253 + NMI generated by 6850 UART + ======================================================================== + +***************************************************************************/ + +#include "emu.h" +#include "audio/sente6vb.h" +#include "cpu/z80/z80.h" +#include "machine/clock.h" +#include "speaker.h" + + +#define LOG_CEM_WRITES 0 + +DEFINE_DEVICE_TYPE(SENTE6VB, sente6vb_device, "sente6vb", "Bally Sente 6VB Audio Board") + +/************************************* + * + * Sound CPU memory handlers + * + *************************************/ + +void sente6vb_device::mem_map(address_map &map) +{ + map(0x0000, 0x1fff).rom().region("audiocpu", 0); + map(0x2000, 0x5fff).ram(); + map(0x6000, 0x6001).mirror(0x1ffe).w(m_uart, FUNC(acia6850_device::write)); + map(0xe000, 0xe001).mirror(0x1ffe).r(m_uart, FUNC(acia6850_device::read)); +} + + +void sente6vb_device::io_map(address_map &map) +{ + map.global_mask(0xff); + map(0x00, 0x03).rw(m_pit, FUNC(pit8253_device::read), FUNC(pit8253_device::write)); + map(0x08, 0x0f).r(FUNC(sente6vb_device::counter_state_r)); + map(0x08, 0x09).w(FUNC(sente6vb_device::counter_control_w)); + map(0x0a, 0x0b).w(FUNC(sente6vb_device::dac_data_w)); + map(0x0c, 0x0d).w(FUNC(sente6vb_device::register_addr_w)); + map(0x0e, 0x0f).w(FUNC(sente6vb_device::chip_select_w)); +} + + +/************************************* + * + * Device configuration + * + *************************************/ + +void sente6vb_device::device_add_mconfig(machine_config &config) +{ + Z80(config, m_audiocpu, 8_MHz_XTAL / 2); + m_audiocpu->set_addrmap(AS_PROGRAM, &sente6vb_device::mem_map); + m_audiocpu->set_addrmap(AS_IO, &sente6vb_device::io_map); + + ACIA6850(config, m_uart, 0); + m_uart->txd_handler().set([this] (int state) { m_send_cb(state); }); + m_uart->irq_handler().set([this] (int state) { m_uint = bool(state); }); + + clock_device &uartclock(CLOCK(config, "uartclock", 8_MHz_XTAL / 16)); // 500 kHz + uartclock.signal_handler().set(FUNC(sente6vb_device::uart_clock_w)); + uartclock.signal_handler().append(m_uart, FUNC(acia6850_device::write_txc)); + uartclock.signal_handler().append(m_uart, FUNC(acia6850_device::write_rxc)); + + TIMER(config, m_counter_0_timer, 0).configure_generic(timer_device::expired_delegate(FUNC(sente6vb_device::clock_counter_0_ff), this)); + + PIT8253(config, m_pit, 0); + m_pit->out_handler<0>().set(FUNC(sente6vb_device::counter_0_set_out)); + m_pit->out_handler<2>().set_inputline(m_audiocpu, INPUT_LINE_IRQ0); + m_pit->set_clk<1>(8_MHz_XTAL / 4); + m_pit->set_clk<2>(8_MHz_XTAL / 4); + + SPEAKER(config, "mono").front_center(); + + for (auto &cem_device : m_cem_device) + { + CEM3394(config, cem_device, 0); + cem_device->set_vco_zero_freq(431.894); + cem_device->set_filter_zero_freq(1300.0); + cem_device->add_route(ALL_OUTPUTS, "mono", 0.90); + } + + m_cem_device[0]->set_ext_input_callback(FUNC(sente6vb_device::noise_gen_0), this); + m_cem_device[1]->set_ext_input_callback(FUNC(sente6vb_device::noise_gen_1), this); + m_cem_device[2]->set_ext_input_callback(FUNC(sente6vb_device::noise_gen_2), this); + m_cem_device[3]->set_ext_input_callback(FUNC(sente6vb_device::noise_gen_3), this); + m_cem_device[4]->set_ext_input_callback(FUNC(sente6vb_device::noise_gen_4), this); + m_cem_device[5]->set_ext_input_callback(FUNC(sente6vb_device::noise_gen_5), this); +} + + +/************************************* + * + * ROM definition + * + *************************************/ + +ROM_START( sente6vb ) + ROM_REGION( 0x2000, "audiocpu", 0 ) + ROM_LOAD( "8002-10 9-25-84.5", 0x0000, 0x2000, CRC(4dd0a525) SHA1(f0c447adc5b67917851a9df978df851247e75c43) ) +ROM_END + + +const tiny_rom_entry *sente6vb_device::device_rom_region() const +{ + return ROM_NAME(sente6vb); +} + + +/************************************* + * + * Device initialization + * + *************************************/ + +sente6vb_device::sente6vb_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, SENTE6VB, tag, owner, clock) + , m_pit(*this, "pit") + , m_counter_0_timer(*this, "8253_0_timer") + , m_cem_device(*this, "cem%u", 1U) + , m_audiocpu(*this, "audiocpu") + , m_uart(*this, "uart") + , m_send_cb(*this) + , m_clock_out_cb(*this) +{ +} + + +void sente6vb_device::device_start() +{ + // create the polynomial tables + poly17_init(); + + m_send_cb.resolve_safe(); + m_clock_out_cb.resolve_safe(); + m_uart->write_cts(0); + m_uart->write_dcd(0); + + save_item(NAME(m_counter_control)); + save_item(NAME(m_counter_0_ff)); + save_item(NAME(m_counter_0_out)); + save_item(NAME(m_counter_0_timer_active)); + + save_item(NAME(m_dac_value)); + save_item(NAME(m_dac_register)); + save_item(NAME(m_chip_select)); + + save_item(NAME(m_uint)); + + save_item(NAME(m_noise_position)); +} + + +void sente6vb_device::device_reset() +{ + // reset the manual counter 0 clock + m_counter_control = 0x00; + m_counter_0_ff = false; + m_counter_0_out = false; + m_counter_0_timer_active = false; + m_audiocpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE); + + // reset the CEM3394 I/O states + m_dac_value = 0; + m_dac_register = 0; + m_chip_select = 0x3f; + + // reset the noise generator + memset(m_noise_position, 0, sizeof(m_noise_position)); +} + + + +/************************************* + * + * MM5837 noise generator + * + * NOTE: this is stolen straight from + * POKEY.c + * + *************************************/ + +void sente6vb_device::poly17_init() +{ + uint32_t i, x = 0; + uint8_t *p; + + // allocate memory + p = m_poly17; + + // generate the polynomial + for (i = 0; i < POLY17_SIZE; i++) + { + // store new values + *p++ = x & 1; + + // calculate next bit + x = ((x << POLY17_SHL) + (x >> POLY17_SHR) + POLY17_ADD) & POLY17_SIZE; + } +} + + +inline void sente6vb_device::noise_gen_chip(int chip, int count, short *buffer) +{ + // noise generator runs at 100kHz + uint32_t step = (100000 << 14) / cem3394_device::SAMPLE_RATE; + uint32_t noise_counter = m_noise_position[chip]; + + while (count--) + { + *buffer++ = m_poly17[(noise_counter >> 14) & POLY17_SIZE] << 12; + noise_counter += step; + } + + // remember the noise position + m_noise_position[chip] = noise_counter; +} + +CEM3394_EXT_INPUT(sente6vb_device::noise_gen_0) { noise_gen_chip(0, count, buffer); } +CEM3394_EXT_INPUT(sente6vb_device::noise_gen_1) { noise_gen_chip(1, count, buffer); } +CEM3394_EXT_INPUT(sente6vb_device::noise_gen_2) { noise_gen_chip(2, count, buffer); } +CEM3394_EXT_INPUT(sente6vb_device::noise_gen_3) { noise_gen_chip(3, count, buffer); } +CEM3394_EXT_INPUT(sente6vb_device::noise_gen_4) { noise_gen_chip(4, count, buffer); } +CEM3394_EXT_INPUT(sente6vb_device::noise_gen_5) { noise_gen_chip(5, count, buffer); } + + +/************************************* + * + * 6850 UART communications + * + *************************************/ + +WRITE_LINE_MEMBER(sente6vb_device::rec_w) +{ + m_uart->write_rxd(state); +} + + +WRITE_LINE_MEMBER(sente6vb_device::uart_clock_w) +{ + if (state && BIT(m_counter_control, 5)) + m_audiocpu->set_input_line(INPUT_LINE_NMI, m_uint ? ASSERT_LINE : CLEAR_LINE); + + m_clock_out_cb(!state); +} + + + +/************************************* + * + * Sound CPU counter 0 emulation + * + *************************************/ + +WRITE_LINE_MEMBER(sente6vb_device::counter_0_set_out) +{ + // OUT on counter 0 is hooked to the GATE line on counter 1 through an inverter + m_pit->write_gate1(!state); + + // remember the out state + m_counter_0_out = state; +} + + +WRITE_LINE_MEMBER(sente6vb_device::set_counter_0_ff) +{ + // the flip/flop output is inverted, so if we went high to low, that's a clock + m_pit->write_clk0(!state); + + // remember the new state + m_counter_0_ff = state; +} + + +TIMER_DEVICE_CALLBACK_MEMBER(sente6vb_device::clock_counter_0_ff) +{ + // clock the D value through the flip-flop + set_counter_0_ff(BIT(m_counter_control, 3)); +} + + +void sente6vb_device::update_counter_0_timer() +{ + double maxfreq = 0.0; + int i; + + // if there's already a timer, remove it + if (m_counter_0_timer_active) + m_counter_0_timer->reset(); + m_counter_0_timer_active = false; + + // find the counter with the maximum frequency + // this is used to calibrate the timers at startup + for (i = 0; i < 6; i++) + if (m_cem_device[i]->get_parameter(cem3394_device::FINAL_GAIN) < 10.0) + { + double tempfreq; + + // if the filter resonance is high, then they're calibrating the filter frequency + if (m_cem_device[i]->get_parameter(cem3394_device::FILTER_RESONANCE) > 0.9) + tempfreq = m_cem_device[i]->get_parameter(cem3394_device::FILTER_FREQENCY); + + // otherwise, they're calibrating the VCO frequency + else + tempfreq = m_cem_device[i]->get_parameter(cem3394_device::VCO_FREQUENCY); + + if (tempfreq > maxfreq) maxfreq = tempfreq; + } + + // reprime the timer + if (maxfreq > 0.0) + { + m_counter_0_timer_active = true; + m_counter_0_timer->adjust(attotime::from_hz(maxfreq), 0, attotime::from_hz(maxfreq)); + } +} + + + +/************************************* + * + * Sound CPU counter handlers + * + *************************************/ + +READ8_MEMBER(sente6vb_device::counter_state_r) +{ + // bit D0 is the inverse of the flip-flop state + int result = !m_counter_0_ff; + + // bit D1 is the OUT value from counter 0 + if (m_counter_0_out) result |= 0x02; + + return result; +} + + +WRITE8_MEMBER(sente6vb_device::counter_control_w) +{ + uint8_t diff_counter_control = m_counter_control ^ data; + + // set the new global value + m_counter_control = data; + + // bit D0 enables/disables audio + if (BIT(diff_counter_control, 0)) + { + for (auto & elem : m_cem_device) + elem->set_output_gain(0, BIT(data, 0) ? 1.0 : 0); + } + + // bit D1 is hooked to counter 0's gate + if (BIT(diff_counter_control, 1)) + { + // if we gate on, start a pulsing timer to clock it + if (BIT(data, 1) && !m_counter_0_timer_active) + { + update_counter_0_timer(); + } + + // if we gate off, remove the timer + else if (!BIT(data, 1) && m_counter_0_timer_active) + { + m_counter_0_timer->reset(); + m_counter_0_timer_active = false; + } + } + + // set the actual gate + m_pit->write_gate0(BIT(data, 1)); + + // bits D2 and D4 control the clear/reset flags on the flip-flop that feeds counter 0 + if (!BIT(data, 4)) + set_counter_0_ff(0); + else if (!BIT(data, 2)) + set_counter_0_ff(1); + + // bit 5 clears the NMI interrupt + if (BIT(diff_counter_control, 5) && !BIT(data, 5)) + m_audiocpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE); +} + + + +/************************************* + * + * CEM3394 Interfaces + * + *************************************/ + +WRITE8_MEMBER(sente6vb_device::chip_select_w) +{ + static constexpr uint8_t register_map[8] = + { + cem3394_device::VCO_FREQUENCY, + cem3394_device::FINAL_GAIN, + cem3394_device::FILTER_RESONANCE, + cem3394_device::FILTER_FREQENCY, + cem3394_device::MIXER_BALANCE, + cem3394_device::MODULATION_AMOUNT, + cem3394_device::PULSE_WIDTH, + cem3394_device::WAVE_SELECT + }; + + double voltage = (double)m_dac_value * (8.0 / 4096.0) - 4.0; + int diffchip = data ^ m_chip_select, i; + int reg = register_map[m_dac_register]; + + // remember the new select value + m_chip_select = data; + + // check all six chip enables + for (i = 0; i < 6; i++) + if ((diffchip & (1 << i)) && (data & (1 << i))) + { +#if LOG_CEM_WRITES + double temp = 0; + + // remember the previous value + temp = +#endif + m_cem_device[i]->get_parameter(reg); + + // set the voltage + m_cem_device[i]->set_voltage(reg, voltage); + + // only log changes +#if LOG_CEM_WRITES + if (temp != m_cem_device[i]->get_parameter(reg)) + { + static const char *const names[] = + { + "VCO_FREQUENCY", + "FINAL_GAIN", + "FILTER_RESONANCE", + "FILTER_FREQENCY", + "MIXER_BALANCE", + "MODULATION_AMOUNT", + "PULSE_WIDTH", + "WAVE_SELECT" + }; + logerror("s%04X: CEM#%d:%s=%f\n", m_audiocpu->pcbase(), i, names[m_dac_register], voltage); + } +#endif + } + + // if a timer for counter 0 is running, recompute + if (m_counter_0_timer_active) + update_counter_0_timer(); +} + + + +WRITE8_MEMBER(sente6vb_device::dac_data_w) +{ + // LSB or MSB? + if (offset & 1) + m_dac_value = (m_dac_value & 0xfc0) | ((data >> 2) & 0x03f); + else + m_dac_value = (m_dac_value & 0x03f) | ((data << 6) & 0xfc0); + + // if there are open channels, force the values in + if ((m_chip_select & 0x3f) != 0x3f) + { + uint8_t temp = m_chip_select; + chip_select_w(space, 0, 0x3f); + chip_select_w(space, 0, temp); + } +} + + +WRITE8_MEMBER(sente6vb_device::register_addr_w) +{ + m_dac_register = data & 7; +} |