1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles
3 /***************************************************************************
4 
5     Bally/Sente 6VB audio board emulation
6 
7     This serial audio board appears to be based on the Sequential Circuits
8     Six-Trak synthesizer.
9 
10     The later revision of this board replaces the 8253-5 PIT and much
11     associated logic with a ST1001 custom gate array.
12 
13 ****************************************************************************
14 
15     Memory map
16 
17 ****************************************************************************
18 
19     ========================================================================
20     Z80 CPU
21     ========================================================================
22     0000-1FFF   R     xxxxxxxx    Program ROM
23     2000-3FFF   R/W   xxxxxxxx    Option RAM/ROM (assumed to be RAM for now)
24     4000-5FFF   R/W   xxxxxxxx    Program RAM
25     6000-6001     W   xxxxxxxx    6850 UART output (to main board)
26     E000-E001   R     xxxxxxxx    6850 UART input (from main board)
27     ========================================================================
28     0000-0003   R/W   xxxxxxxx    8253 counter chip I/O
29     0008        R     ------xx    Counter state
30                 R     ------x-    State of counter #0 OUT signal (active high)
31                 R     -------x    State of flip-flop feeding counter #0 (active low)
32     0008          W   --xxxxxx    Counter control
33                   W   --x-----    NMI enable (1=enabled, 0=disabled/clear)
34                   W   ---x----    CLEAR on flip-flop feeding counter #0 (active low)
35                   W   ----x---    Input of flip-flop feeding counter #0
36                   W   -----x--    PRESET on flip-flop feeding counter #0 (active low)
37                   W   ------x-    GATE signal for counter #0 (active high)
38                   W   -------x    Audio enable
39     000A          W   --xxxxxx    DAC data latch (upper 6 bits)
40     000B          W   xxxxxx--    DAC data latch (lower 6 bits)
41     000C          W   -----xxx    CEM3394 register select
42     000E          W   --xxxxxx    CEM3394 chip enable (active high)
43                   W   --x-----    CEM3394 chip 0 enable
44                   W   ---x----    CEM3394 chip 1 enable
45                   W   ----x---    CEM3394 chip 2 enable
46                   W   -----x--    CEM3394 chip 3 enable
47                   W   ------x-    CEM3394 chip 4 enable
48                   W   -------x    CEM3394 chip 5 enable
49     ========================================================================
50     Interrupts:
51         INT generated by counter #2 OUT signal on 8253
52         NMI generated by 6850 UART
53     ========================================================================
54 
55 ***************************************************************************/
56 
57 #include "emu.h"
58 #include "sound/mm5837.h"
59 #include "audio/sente6vb.h"
60 #include "cpu/z80/z80.h"
61 #include "machine/clock.h"
62 #include "speaker.h"
63 
64 
65 #define LOG_CEM_WRITES      0
66 
67 DEFINE_DEVICE_TYPE(SENTE6VB, sente6vb_device, "sente6vb", "Bally Sente 6VB Audio Board")
68 
69 
70 
71 /*************************************
72  *
73  *  Sound CPU memory handlers
74  *
75  *************************************/
76 
mem_map(address_map & map)77 void sente6vb_device::mem_map(address_map &map)
78 {
79 	map(0x0000, 0x1fff).rom().region("audiocpu", 0);
80 	map(0x2000, 0x5fff).ram();
81 	map(0x6000, 0x6001).mirror(0x1ffe).w(m_uart, FUNC(acia6850_device::write));
82 	map(0xe000, 0xe001).mirror(0x1ffe).r(m_uart, FUNC(acia6850_device::read));
83 }
84 
85 
io_map(address_map & map)86 void sente6vb_device::io_map(address_map &map)
87 {
88 	map.global_mask(0xff);
89 	map(0x00, 0x03).rw(m_pit, FUNC(pit8253_device::read), FUNC(pit8253_device::write));
90 	map(0x08, 0x0f).r(FUNC(sente6vb_device::counter_state_r));
91 	map(0x08, 0x09).w(FUNC(sente6vb_device::counter_control_w));
92 	map(0x0a, 0x0b).w(FUNC(sente6vb_device::dac_data_w));
93 	map(0x0c, 0x0d).w(FUNC(sente6vb_device::register_addr_w));
94 	map(0x0e, 0x0f).w(FUNC(sente6vb_device::chip_select_w));
95 }
96 
97 
98 /*************************************
99  *
100  *  Device configuration
101  *
102  *************************************/
103 
device_add_mconfig(machine_config & config)104 void sente6vb_device::device_add_mconfig(machine_config &config)
105 {
106 	Z80(config, m_audiocpu, 8_MHz_XTAL / 2);
107 	m_audiocpu->set_addrmap(AS_PROGRAM, &sente6vb_device::mem_map);
108 	m_audiocpu->set_addrmap(AS_IO, &sente6vb_device::io_map);
109 
110 	ACIA6850(config, m_uart, 0);
111 	m_uart->txd_handler().set([this] (int state) { m_send_cb(state); });
112 	m_uart->irq_handler().set([this] (int state) { m_uint = bool(state); });
113 
114 	clock_device &uartclock(CLOCK(config, "uartclock", 8_MHz_XTAL / 16)); // 500 kHz
115 	uartclock.signal_handler().set(FUNC(sente6vb_device::uart_clock_w));
116 	uartclock.signal_handler().append(m_uart, FUNC(acia6850_device::write_txc));
117 	uartclock.signal_handler().append(m_uart, FUNC(acia6850_device::write_rxc));
118 
119 	TIMER(config, m_counter_0_timer, 0).configure_generic(FUNC(sente6vb_device::clock_counter_0_ff));
120 
121 	PIT8253(config, m_pit, 0);
122 	m_pit->out_handler<0>().set(FUNC(sente6vb_device::counter_0_set_out));
123 	m_pit->out_handler<2>().set_inputline(m_audiocpu, INPUT_LINE_IRQ0);
124 	m_pit->set_clk<1>(8_MHz_XTAL / 4);
125 	m_pit->set_clk<2>(8_MHz_XTAL / 4);
126 
127 	SPEAKER(config, "mono").front_center();
128 
129 	mm5837_stream_device &noise(MM5837_STREAM(config, "noise", 0));
130 //  noise.set_vdd(-6.5);   // seems too low -- possible the mapping in mm5837 is wrong
131 	noise.set_vdd(-8.0);
132 
133 	for (auto &cem_device : m_cem_device)
134 	{
135 		CEM3394(config, cem_device, 0);
136 		cem_device->set_vco_zero_freq(431.894);
137 		cem_device->set_filter_zero_freq(1300.0);
138 		cem_device->add_route(ALL_OUTPUTS, "mono", 0.90);
139 		noise.add_route(0, *cem_device, 0.5);
140 	}
141 }
142 
143 
144 /*************************************
145  *
146  *  ROM definition
147  *
148  *************************************/
149 
150 ROM_START( sente6vb )
151 	ROM_REGION( 0x2000, "audiocpu", 0 )
152 	ROM_LOAD( "8002-10 9-25-84.5", 0x0000, 0x2000, CRC(4dd0a525) SHA1(f0c447adc5b67917851a9df978df851247e75c43) )
153 ROM_END
154 
155 
device_rom_region() const156 const tiny_rom_entry *sente6vb_device::device_rom_region() const
157 {
158 	return ROM_NAME(sente6vb);
159 }
160 
161 
162 /*************************************
163  *
164  *  Device initialization
165  *
166  *************************************/
167 
sente6vb_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)168 sente6vb_device::sente6vb_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
169 	device_t(mconfig, SENTE6VB, tag, owner, clock),
170 	m_pit(*this, "pit"),
171 	m_counter_0_timer(*this, "8253_0_timer"),
172 	m_cem_device(*this, "cem%u", 1U),
173 	m_audiocpu(*this, "audiocpu"),
174 	m_uart(*this, "uart"),
175 	m_send_cb(*this),
176 	m_clock_out_cb(*this)
177 {
178 }
179 
180 
device_start()181 void sente6vb_device::device_start()
182 {
183 	m_send_cb.resolve_safe();
184 	m_clock_out_cb.resolve_safe();
185 	m_uart->write_cts(0);
186 	m_uart->write_dcd(0);
187 
188 	save_item(NAME(m_counter_control));
189 	save_item(NAME(m_counter_0_ff));
190 	save_item(NAME(m_counter_0_out));
191 	save_item(NAME(m_counter_0_timer_active));
192 
193 	save_item(NAME(m_dac_value));
194 	save_item(NAME(m_dac_register));
195 	save_item(NAME(m_chip_select));
196 
197 	save_item(NAME(m_uint));
198 }
199 
200 
device_reset()201 void sente6vb_device::device_reset()
202 {
203 	// reset the manual counter 0 clock
204 	m_counter_control = 0x00;
205 	m_counter_0_ff = false;
206 	m_counter_0_out = false;
207 	m_counter_0_timer_active = false;
208 	m_audiocpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE);
209 
210 	// reset the CEM3394 I/O states
211 	m_dac_value = 0;
212 	m_dac_register = 0;
213 	m_chip_select = 0x3f;
214 }
215 
216 
217 
218 /*************************************
219  *
220  *  6850 UART communications
221  *
222  *************************************/
223 
WRITE_LINE_MEMBER(sente6vb_device::rec_w)224 WRITE_LINE_MEMBER(sente6vb_device::rec_w)
225 {
226 	m_uart->write_rxd(state);
227 }
228 
229 
WRITE_LINE_MEMBER(sente6vb_device::uart_clock_w)230 WRITE_LINE_MEMBER(sente6vb_device::uart_clock_w)
231 {
232 	if (state && BIT(m_counter_control, 5))
233 		m_audiocpu->set_input_line(INPUT_LINE_NMI, m_uint ? ASSERT_LINE : CLEAR_LINE);
234 
235 	m_clock_out_cb(!state);
236 }
237 
238 
239 
240 /*************************************
241  *
242  *  Sound CPU counter 0 emulation
243  *
244  *************************************/
245 
WRITE_LINE_MEMBER(sente6vb_device::counter_0_set_out)246 WRITE_LINE_MEMBER(sente6vb_device::counter_0_set_out)
247 {
248 	// OUT on counter 0 is hooked to the GATE line on counter 1 through an inverter
249 	m_pit->write_gate1(!state);
250 
251 	// remember the out state
252 	m_counter_0_out = state;
253 }
254 
255 
WRITE_LINE_MEMBER(sente6vb_device::set_counter_0_ff)256 WRITE_LINE_MEMBER(sente6vb_device::set_counter_0_ff)
257 {
258 	// the flip/flop output is inverted, so if we went high to low, that's a clock
259 	m_pit->write_clk0(!state);
260 
261 	// remember the new state
262 	m_counter_0_ff = state;
263 }
264 
265 
TIMER_DEVICE_CALLBACK_MEMBER(sente6vb_device::clock_counter_0_ff)266 TIMER_DEVICE_CALLBACK_MEMBER(sente6vb_device::clock_counter_0_ff)
267 {
268 	// clock the D value through the flip-flop
269 	set_counter_0_ff(BIT(m_counter_control, 3));
270 }
271 
272 
update_counter_0_timer()273 void sente6vb_device::update_counter_0_timer()
274 {
275 	double maxfreq = 0.0;
276 	int i;
277 
278 	// if there's already a timer, remove it
279 	if (m_counter_0_timer_active)
280 		m_counter_0_timer->reset();
281 	m_counter_0_timer_active = false;
282 
283 	// find the counter with the maximum frequency
284 	// this is used to calibrate the timers at startup
285 	for (i = 0; i < 6; i++)
286 		if (m_cem_device[i]->get_parameter(cem3394_device::FINAL_GAIN) < 10.0)
287 		{
288 			double tempfreq;
289 
290 			// if the filter resonance is high, then they're calibrating the filter frequency
291 			if (m_cem_device[i]->get_parameter(cem3394_device::FILTER_RESONANCE) > 0.9)
292 				tempfreq = m_cem_device[i]->get_parameter(cem3394_device::FILTER_FREQENCY);
293 
294 			// otherwise, they're calibrating the VCO frequency
295 			else
296 				tempfreq = m_cem_device[i]->get_parameter(cem3394_device::VCO_FREQUENCY);
297 
298 			if (tempfreq > maxfreq) maxfreq = tempfreq;
299 		}
300 
301 	// reprime the timer
302 	if (maxfreq > 0.0)
303 	{
304 		m_counter_0_timer_active = true;
305 		m_counter_0_timer->adjust(attotime::from_hz(maxfreq), 0, attotime::from_hz(maxfreq));
306 	}
307 }
308 
309 
310 
311 /*************************************
312  *
313  *  Sound CPU counter handlers
314  *
315  *************************************/
316 
counter_state_r()317 uint8_t sente6vb_device::counter_state_r()
318 {
319 	// bit D0 is the inverse of the flip-flop state
320 	int result = !m_counter_0_ff;
321 
322 	// bit D1 is the OUT value from counter 0
323 	if (m_counter_0_out) result |= 0x02;
324 
325 	return result;
326 }
327 
328 
counter_control_w(uint8_t data)329 void sente6vb_device::counter_control_w(uint8_t data)
330 {
331 	uint8_t diff_counter_control = m_counter_control ^ data;
332 
333 	// set the new global value
334 	m_counter_control = data;
335 
336 	// bit D0 enables/disables audio
337 	if (BIT(diff_counter_control, 0))
338 	{
339 		for (auto & elem : m_cem_device)
340 			elem->set_output_gain(0, BIT(data, 0) ? 1.0 : 0);
341 	}
342 
343 	// bit D1 is hooked to counter 0's gate
344 	if (BIT(diff_counter_control, 1))
345 	{
346 		// if we gate on, start a pulsing timer to clock it
347 		if (BIT(data, 1) && !m_counter_0_timer_active)
348 		{
349 			update_counter_0_timer();
350 		}
351 
352 		// if we gate off, remove the timer
353 		else if (!BIT(data, 1) && m_counter_0_timer_active)
354 		{
355 			m_counter_0_timer->reset();
356 			m_counter_0_timer_active = false;
357 		}
358 	}
359 
360 	// set the actual gate
361 	m_pit->write_gate0(BIT(data, 1));
362 
363 	// bits D2 and D4 control the clear/reset flags on the flip-flop that feeds counter 0
364 	if (!BIT(data, 4))
365 		set_counter_0_ff(0);
366 	else if (!BIT(data, 2))
367 		set_counter_0_ff(1);
368 
369 	// bit 5 clears the NMI interrupt
370 	if (BIT(diff_counter_control, 5) && !BIT(data, 5))
371 		m_audiocpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE);
372 }
373 
374 
375 
376 /*************************************
377  *
378  *  CEM3394 Interfaces
379  *
380  *************************************/
381 
chip_select_w(uint8_t data)382 void sente6vb_device::chip_select_w(uint8_t data)
383 {
384 	static constexpr uint8_t register_map[8] =
385 	{
386 		cem3394_device::VCO_FREQUENCY,
387 		cem3394_device::FINAL_GAIN,
388 		cem3394_device::FILTER_RESONANCE,
389 		cem3394_device::FILTER_FREQENCY,
390 		cem3394_device::MIXER_BALANCE,
391 		cem3394_device::MODULATION_AMOUNT,
392 		cem3394_device::PULSE_WIDTH,
393 		cem3394_device::WAVE_SELECT
394 	};
395 
396 	double voltage = (double)m_dac_value * (8.0 / 4096.0) - 4.0;
397 	int diffchip = data ^ m_chip_select, i;
398 	int reg = register_map[m_dac_register];
399 
400 	// remember the new select value
401 	m_chip_select = data;
402 
403 	// check all six chip enables
404 	for (i = 0; i < 6; i++)
405 		if ((diffchip & (1 << i)) && (data & (1 << i)))
406 		{
407 #if LOG_CEM_WRITES
408 			double temp = 0;
409 
410 			// remember the previous value
411 			temp =
412 #endif
413 				m_cem_device[i]->get_parameter(reg);
414 
415 			// set the voltage
416 			m_cem_device[i]->set_voltage(reg, voltage);
417 
418 			// only log changes
419 #if LOG_CEM_WRITES
420 			if (temp != m_cem_device[i]->get_parameter(reg))
421 			{
422 				static const char *const names[] =
423 				{
424 					"VCO_FREQUENCY",
425 					"FINAL_GAIN",
426 					"FILTER_RESONANCE",
427 					"FILTER_FREQENCY",
428 					"MIXER_BALANCE",
429 					"MODULATION_AMOUNT",
430 					"PULSE_WIDTH",
431 					"WAVE_SELECT"
432 				};
433 				logerror("s%04X:   CEM#%d:%s=%f\n", m_audiocpu->pcbase(), i, names[m_dac_register], voltage);
434 			}
435 #endif
436 		}
437 
438 	// if a timer for counter 0 is running, recompute
439 	if (m_counter_0_timer_active)
440 		update_counter_0_timer();
441 }
442 
443 
444 
dac_data_w(offs_t offset,uint8_t data)445 void sente6vb_device::dac_data_w(offs_t offset, uint8_t data)
446 {
447 	// LSB or MSB?
448 	if (offset & 1)
449 		m_dac_value = (m_dac_value & 0xfc0) | ((data >> 2) & 0x03f);
450 	else
451 		m_dac_value = (m_dac_value & 0x03f) | ((data << 6) & 0xfc0);
452 
453 	// if there are open channels, force the values in
454 	if ((m_chip_select & 0x3f) != 0x3f)
455 	{
456 		uint8_t temp = m_chip_select;
457 		chip_select_w(0x3f);
458 		chip_select_w(temp);
459 	}
460 }
461 
462 
register_addr_w(uint8_t data)463 void sente6vb_device::register_addr_w(uint8_t data)
464 {
465 	m_dac_register = data & 7;
466 }
467