summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/sound/pokey.cpp
blob: ebeaf344660d31a0da2840f555691b88698153d3 (plain) (tree)
1
2
3
4
5
6
7
8
9
                       
                                                                 

                                                                              
                           



                                                                           
                                                                        
  
        
                                                                                            


                                                                 
                                                                  

                      










                                                                         
                                                                






















                                                                        


                            

                                                                               
                

                  

                     

                                                             
 





                                                             
 
                                       
 




                         
 
                                                        
 
                                                                    
 
                                                                    
 
                                                                  
 
                                                                  
 



                 
 


                 
 
                                                           

                             
                             
 
           




                                                               
 
            







                                                                    
 
                  







                                                                        
 
                     






                                                                                                        
 
                    






                                                                 
 

                                                                               
 



                                         
 


                 
 
                                                         
 
                                               



                         
                                                                       





                                                                            
                              

                                                   
                                                                                                           
                                                      


                                                         

                                             
                                  
                                                                                                     


                                     


 
                                                   


                                                   
                                 



                                    









                                               



                                                
                                  





                                                                             
 
                                          

                                        
 


                                      
                   
 
                                                                            

                              
 

                                                              
                     

                                                                                           
                    




                     
                        



                          

                        
 
                                       
                           
         
                                   

         

                           
                              
         
 

                                          


                                     


                                               
                                                                  


                                    
 





                                                                 


                                                        
 
                                     



                               
 



                                  




                                  

                                 




                                       



















                                                         
                                      
                                
 
 



                                                   
                                 








                                                   
                                     








                                                   
                                         
 
                                               
 






                                                               






                                                   
                                                                                           
 
                   
         
               
                                     
                                        

                                             

                                                   
                 
                      
               
                                     
                                        

                                             

                                                   
                 
                      
               
                                 
                                        
                 
                                             

                                                   
                 
                      


                                                            
                                                    



                                                     
                                                           

                      
                                                        





                                           
                                                                                 
         

 

                                

          
                                                    

                                        
                                                                                  


                                           
 

                               
 
 

 
                                                   

                                            

                                                   
                                  


                              

                                   
                                                      
 





                                                                       
                                                        
                                 
                                                             

                                                                   




                                                                              
                                               
























                                                                               
                                                                

                                                                                       
                                                                       

                                                                             

                                                                           































                                                              
                             

                
                        











                                                                          







                                                                                                                         
                                           
 
                    
                         
                                         

                                                                   




































                                                                                                 

                              


                                                                       
 









                                                                                 

                                                               
         
 








                                                                                 

                                                               
         
 












                                                                                 

                                                               
         
 













                                                                                 
 

                                  
                                                                                                                                                                                        


                   
 



                                                   
 
 
                                                                                                                              

                                             
 
                                           
         
                                





                                                            

                                        




                                                 
 

                                                                                                                                                
 

                                    



                                                                         


                 
                                                    

                                                 



                                                                                                        
 


                                                                                 

                                                                                        


                                    


                                                                         


                 


                                                 


                                                                                





                                                                                                                         



                                                                                    

                 

                                                 
                                                 

                                    

                                        
                 
         

 



                                                         
                                  
 
                      
 

                                                  




                                                           
                                           
                 

                                                      
                                                                                                

                    

                                             
                                                                                                
                 

                      
                      
                                                                                 


                                                                                  


                                              
                                                                                         
                 
                                                  
                 
                                                     
                                                                                                                   


                    
                                               
                                                                                 







                                

                                      
                                                    
                                                                                          


                    
                                                             
                                                                                            
                 


                      

                                                       
                               
                                                                





                                                                               
                                                                




                                                             
                                                                


                      
                                                                    
                            


                      






                                                   
                                    
 
                                                    

 
                                                              
 
                                                 
                            

                     
                                                                      
                                               
                      
 
                     
                                                                                           
                                               
                      
 
                     
                                                                      
                                               
                      
 
                     
                                                                                           
                                               
                      
 
                     
                                                                      
                                               
                      
 
                     
                                                                                           
                                               
                      
 
                     
                                                                      
                                               
                      
 
                     
                                                                                           
                                               
                      
 
                      
                                      
                               
                                                                                             
                                

                      
 
                      
                                                                      
 
                                                



                                                                   






                                                                     
                      
 

                                  
                                                                
                                                            
                      
 
                     
                                                                
                              
                      
 
                      
                                                                
                                            
                                      










                                                                                              
                                                                
 
                                                          
                                     
                 

                                                                                                                    
                 

                                      





                                                                                        
                      
 
                     
                                     
                               
                                                                
                               





















                                                                                          

                                                                     





                                                                      
 

 











                                        
                                         



                                                



                                                   
                                                 
 
                                                                     
         





                                                                     
                    
                                                                       
         


 
                                    
 
                
 
                                                        
 

                          
 
                                      
         
                                  
                                               
                 
                                                     
 











                                                                                      
 

                                         



                 




                                                                                  

                                                            















                                                           




                                                                 
                                                                                         
                 











                                                                
 
                                                                                  

                                   
              
                          

                                      
                                   
         



                                                                     
                                      

                                                     


         
                                                           
 

                                   
                            

                                      

                       
         
                                           
                 





                                                                         
                                     

                                                             
                 
         

            
                                           
                 



                                                                       
                                     

                                                             


                 
 
 
                                     




















                                                      
         


                    
                                       



















                                          
                    

 
                                            
                      
                          
                                

                             
                                  

 
// license:BSD-3-Clause
// copyright-holders:Brad Oliver, Eric Smith, Juergen Buchmueller
/*****************************************************************************
 *
 *  POKEY chip emulator 4.6
 *
 *  Based on original info found in Ron Fries' Pokey emulator,
 *  with additions by Brad Oliver, Eric Smith and Juergen Buchmueller,
 *  paddle (a/d conversion) details from the Atari 400/800 Hardware Manual.
 *  Polynomial algorithms according to info supplied by Perry McFarlane.
 *
 *  4.6:
 *    [1] http://ploguechipsounds.blogspot.de/2009/10/how-i-recorded-and-decoded-pokeys.html
 *  - changed audio emulation to emulate borrow 3 clock delay and
 *    proper channel reset. New frequency only becomes effective
 *    after the counter hits 0. Emulation also treats counters
 *    as 8 bit counters which are linked now instead of monolithic
 *    16 bit counters.
 *
 *  4.51:
 *  - changed to use the attotime datatype
 *  4.5:
 *  - changed the 9/17 bit polynomial formulas such that the values
 *    required for the Tempest Pokey protection will be found.
 *    Tempest expects the upper 4 bits of the RNG to appear in the
 *    lower 4 bits after four cycles, so there has to be a shift
 *    of 1 per cycle (which was not the case before). Bits #6-#13 of the
 *    new RNG give this expected result now, bits #0-7 of the 9 bit poly.
 *  - reading the RNG returns the shift register contents ^ 0xff.
 *    That way resetting the Pokey with SKCTL (which resets the
 *    polynomial shifters to 0) returns the expected 0xff value.
 *  4.4:
 *  - reversed sample values to make OFF channels produce a zero signal.
 *    actually de-reversed them; don't remember that I reversed them ;-/
 *  4.3:
 *  - for POT inputs returning zero, immediately assert the ALLPOT
 *    bit after POTGO is written, otherwise start trigger timer
 *    depending on SK_PADDLE mode, either 1-228 scanlines or 1-2
 *    scanlines, depending on the SK_PADDLE bit of SKCTL.
 *  4.2:
 *  - half volume for channels which are inaudible (this should be
 *    close to the real thing).
 *  4.1:
 *  - default gain increased to closely match the old code.
 *  - random numbers repeat rate depends on POLY9 flag too!
 *  - verified sound output with many, many Atari 800 games,
 *    including the SUPPRESS_INAUDIBLE optimizations.
 *  4.0:
 *  - rewritten from scratch.
 *  - 16bit stream interface.
 *  - serout ready/complete delayed interrupts.
 *  - reworked pot analog/digital conversion timing.
 *  - optional non-indexing pokey update functions.
 *
 *  TODO:  liberatr clipping
 *
 *
 *****************************************************************************/

#include "emu.h"
#include "pokey.h"

#include "debugger.h"

/* Four channels with a range of 0..32767 and volume 0..15 */
//#define POKEY_DEFAULT_GAIN (32767/15/4)

/*
 * But we raise the gain and risk clipping, the old Pokey did
 * this too. It defined POKEY_DEFAULT_GAIN 6 and this was
 * 6 * 15 * 4 = 360, 360/256 = 1.40625
 * I use 15/11 = 1.3636, so this is a little lower.
 */

#define POKEY_DEFAULT_GAIN (32767/11/4)

#define VERBOSE         0
#define VERBOSE_SOUND   0
#define VERBOSE_TIMER   0
#define VERBOSE_POLY    0
#define VERBOSE_RAND    0

#define LOG(x) do { if (VERBOSE) logerror x; } while (0)

#define LOG_SOUND(x) do { if (VERBOSE_SOUND) logerror x; } while (0)

#define LOG_TIMER(x) do { if (VERBOSE_TIMER) logerror x; } while (0)

#define LOG_POLY(x) do { if (VERBOSE_POLY) logerror x; } while (0)

#define LOG_RAND(x) do { if (VERBOSE_RAND) logerror x; } while (0)

#define CHAN1   0
#define CHAN2   1
#define CHAN3   2
#define CHAN4   3

#define TIMER1  0
#define TIMER2  1
#define TIMER4  2

/* values to add to the divisors for the different modes */
#define DIVADD_LOCLK        1
#define DIVADD_HICLK        4
#define DIVADD_HICLK_JOINED 7

/* AUDCx */
#define NOTPOLY5    0x80    /* selects POLY5 or direct CLOCK */
#define POLY4       0x40    /* selects POLY4 or POLY17 */
#define PURE        0x20    /* selects POLY4/17 or PURE tone */
#define VOLUME_ONLY 0x10    /* selects VOLUME OUTPUT ONLY */
#define VOLUME_MASK 0x0f    /* volume mask */

/* AUDCTL */
#define POLY9       0x80    /* selects POLY9 or POLY17 */
#define CH1_HICLK   0x40    /* selects 1.78979 MHz for Ch 1 */
#define CH3_HICLK   0x20    /* selects 1.78979 MHz for Ch 3 */
#define CH12_JOINED 0x10    /* clocks channel 1 w/channel 2 */
#define CH34_JOINED 0x08    /* clocks channel 3 w/channel 4 */
#define CH1_FILTER  0x04    /* selects channel 1 high pass filter */
#define CH2_FILTER  0x02    /* selects channel 2 high pass filter */
#define CLK_15KHZ   0x01    /* selects 15.6999 kHz or 63.9211 kHz */

/* IRQEN (D20E) */
#define IRQ_BREAK   0x80    /* BREAK key pressed interrupt */
#define IRQ_KEYBD   0x40    /* keyboard data ready interrupt */
#define IRQ_SERIN   0x20    /* serial input data ready interrupt */
#define IRQ_SEROR   0x10    /* serial output register ready interrupt */
#define IRQ_SEROC   0x08    /* serial output complete interrupt */
#define IRQ_TIMR4   0x04    /* timer channel #4 interrupt */
#define IRQ_TIMR2   0x02    /* timer channel #2 interrupt */
#define IRQ_TIMR1   0x01    /* timer channel #1 interrupt */

/* SKSTAT (R/D20F) */
#define SK_FRAME    0x80    /* serial framing error */
#define SK_KBERR    0x40    /* keyboard overrun error - pokey documentation states *some bit as IRQST */
#define SK_OVERRUN  0x20    /* serial overrun error - pokey documentation states *some bit as IRQST */
#define SK_SERIN    0x10    /* serial input high */
#define SK_SHIFT    0x08    /* shift key pressed */
#define SK_KEYBD    0x04    /* keyboard key pressed */
#define SK_SEROUT   0x02    /* serial output active */

/* SKCTL (W/D20F) */
#define SK_BREAK    0x80    /* serial out break signal */
#define SK_BPS      0x70    /* bits per second */
#define SK_FM       0x08    /* FM mode */
#define SK_PADDLE   0x04    /* fast paddle a/d conversion */
#define SK_RESET    0x03    /* reset serial/keyboard interface */
#define SK_KEYSCAN  0x02    /* key scanning enabled ? */
#define SK_DEBOUNCE 0x01    /* Debouncing ?*/

#define DIV_64      28       /* divisor for 1.78979 MHz clock to 63.9211 kHz */
#define DIV_15      114      /* divisor for 1.78979 MHz clock to 15.6999 kHz */

#define P4(chip)  chip->poly4[chip->p4]
#define P5(chip)  chip->poly5[chip->p5]
#define P9(chip)  chip->poly9[chip->p9]
#define P17(chip) chip->poly17[chip->p17]

#define CLK_1 0
#define CLK_28 1
#define CLK_114 2

static const int clock_divisors[3] = {1, DIV_64, DIV_15};

constexpr unsigned pokey_device::FREQ_17_EXACT;



// device type definition
DEFINE_DEVICE_TYPE(POKEY, pokey_device, "pokey", "Atari C012294 POKEY")

//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

//-------------------------------------------------
//  pokey_device - constructor
//-------------------------------------------------

pokey_device::pokey_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, POKEY, tag, owner, clock),
		device_sound_interface(mconfig, *this),
		device_execute_interface(mconfig, *this),
		device_state_interface(mconfig, *this),
		m_output_type(LEGACY_LINEAR),
		m_icount(0),
		m_stream(nullptr),
		m_pot_r_cb{ {*this}, {*this}, {*this}, {*this}, {*this}, {*this}, {*this}, {*this} },
		m_allpot_r_cb(*this),
		m_serin_r_cb(*this),
		m_serout_w_cb(*this)
{
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void pokey_device::device_start()
{
	//int sample_rate = clock();
	int i;

	/* Setup channels */
	for (i=0; i<POKEY_CHANNELS; i++)
	{
		m_channel[i].m_parent = this;
		m_channel[i].m_INTMask = 0;
	}
	m_channel[CHAN1].m_INTMask = IRQ_TIMR1;
	m_channel[CHAN2].m_INTMask = IRQ_TIMR2;
	m_channel[CHAN4].m_INTMask = IRQ_TIMR4;

	// bind callbacks
	m_keyboard_r.bind_relative_to(*owner());
	m_irq_f.bind_relative_to(*owner());

	/* calculate the A/D times
	 * In normal, slow mode (SKCTL bit SK_PADDLE is clear) the conversion
	 * takes N scanlines, where N is the paddle value. A single scanline
	 * takes approximately 64us to finish (1.78979MHz clock).
	 * In quick mode (SK_PADDLE set) the conversion is done very fast
	 * (takes two scanlines) but the result is not as accurate.
	 */

	/* initialize the poly counters */
	poly_init_4_5(m_poly4, 4, 1, 0);
	poly_init_4_5(m_poly5, 5, 2, 1);

	/* initialize 9 / 17 arrays */
	poly_init_9_17(m_poly9,   9);
	poly_init_9_17(m_poly17, 17);
	vol_init();

	/* The pokey does not have a reset line. These should be initialized
	 * with random values.
	 */

	m_KBCODE = 0x09;         /* Atari 800 'no key' */
	m_SKCTL = SK_RESET;  /* let the RNG run after reset */
	m_SKSTAT = 0;
	/* This bit should probably get set later. Acid5200 pokey_setoc test tests this. */
	m_IRQST = IRQ_SEROC;
	m_IRQEN = 0;
	m_AUDCTL = 0;
	m_p4 = 0;
	m_p5 = 0;
	m_p9 = 0;
	m_p17 = 0;
	m_ALLPOT = 0x00;

	m_pot_counter = 0;
	m_kbd_cnt = 0;
	m_out_filter = 0;
	m_output = 0;
	m_kbd_state = 0;

	/* reset more internal state */
	for (i=0; i<3; i++)
	{
		m_clock_cnt[i] = 0;
	}

	for (i=0; i<8; i++)
	{
		m_POTx[i] = 0;
	}

	for (devcb_read8 &cb : m_pot_r_cb)
		cb.resolve();
	m_allpot_r_cb.resolve();
	m_serin_r_cb.resolve();
	m_serout_w_cb.resolve_safe();

	m_stream = stream_alloc(0, 1, clock());

	timer_alloc(SYNC_WRITE);    /* timer for sync operation */
	timer_alloc(SYNC_NOOP);
	timer_alloc(SYNC_POT);
	timer_alloc(SYNC_SET_IRQST);

	for (i=0; i<POKEY_CHANNELS; i++)
	{
		save_item(NAME(m_channel[i].m_borrow_cnt), i);
		save_item(NAME(m_channel[i].m_counter), i);
		save_item(NAME(m_channel[i].m_filter_sample), i);
		save_item(NAME(m_channel[i].m_output), i);
		save_item(NAME(m_channel[i].m_AUDF), i);
		save_item(NAME(m_channel[i].m_AUDC), i);
	}

	save_item(NAME(m_clock_cnt));
	save_item(NAME(m_p4));
	save_item(NAME(m_p5));
	save_item(NAME(m_p9));
	save_item(NAME(m_p17));

	save_item(NAME(m_POTx));
	save_item(NAME(m_AUDCTL));
	save_item(NAME(m_ALLPOT));
	save_item(NAME(m_KBCODE));
	save_item(NAME(m_SERIN));
	save_item(NAME(m_SEROUT));
	save_item(NAME(m_IRQST));
	save_item(NAME(m_IRQEN));
	save_item(NAME(m_SKSTAT));
	save_item(NAME(m_SKCTL));

	save_item(NAME(m_pot_counter));
	save_item(NAME(m_kbd_cnt));
	save_item(NAME(m_kbd_latch));
	save_item(NAME(m_kbd_state));

	// State support

	state_add(AUDF1_C, "AUDF1", m_channel[0].m_AUDF);
	state_add(AUDC1_C, "AUDC1", m_channel[0].m_AUDC);
	state_add(AUDF2_C, "AUDF2", m_channel[1].m_AUDF);
	state_add(AUDC2_C, "AUDC2", m_channel[1].m_AUDC);
	state_add(AUDF3_C, "AUDF3", m_channel[2].m_AUDF);
	state_add(AUDC3_C, "AUDC3", m_channel[2].m_AUDC);
	state_add(AUDF4_C, "AUDF4", m_channel[3].m_AUDF);
	state_add(AUDC4_C, "AUDC4", m_channel[3].m_AUDC);
	state_add(AUDCTL_C, "AUDCTL", m_AUDCTL);
#if 0
	state_add(STIMER_C, "STIMER", m_STIMER);
	state_add(SKREST_C, "SKREST_C", m_SKREST);
	state_add(POTGO_C, "POTGO", m_POTGO_C);
#endif
	state_add(SEROUT_C, "SEROUT", m_SEROUT);
	state_add(IRQEN_C, "IRQEN", m_IRQEN);
	state_add(SKCTL_C, "SKCTL", m_SKCTL);

	// set our instruction counter
	set_icountptr(m_icount);
}

//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void pokey_device::device_reset()
{
	m_stream->update();
}


//-------------------------------------------------
//  device_post_load - device-specific post-load
//-------------------------------------------------

void pokey_device::device_post_load()
{
}


//-------------------------------------------------
//  device_clock_changed - called if the clock
//  changes
//-------------------------------------------------

void pokey_device::device_clock_changed()
{
	m_clock_period = clocks_to_attotime(1);

	if (clock() != 0)
	{
		if (m_stream != nullptr)
			m_stream->set_sample_rate(clock());
		else
			m_stream = stream_alloc(0, 1, clock());
	}
}

//-------------------------------------------------
//  stream_generate - handle update requests for
//  our sound stream
//-------------------------------------------------

void pokey_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	switch (id)
	{
	case 3:
		/* serout_ready_cb */
		if (m_IRQEN & IRQ_SEROR)
		{
			m_IRQST |= IRQ_SEROR;
			if (!m_irq_f.isnull())
				m_irq_f(IRQ_SEROR);
		}
		break;
	case 4:
		/* serout_complete */
		if (m_IRQEN & IRQ_SEROC)
		{
			m_IRQST |= IRQ_SEROC;
			if (!m_irq_f.isnull())
				m_irq_f(IRQ_SEROC);
		}
		break;
	case 5:
		/* serin_ready */
		if (m_IRQEN & IRQ_SERIN)
		{
			m_IRQST |= IRQ_SERIN;
			if (!m_irq_f.isnull())
				m_irq_f(IRQ_SERIN);
		}
		break;
	case SYNC_WRITE:
		{
			offs_t offset = (param >> 8) & 0xff;
			uint8_t data = param & 0xff;
			write_internal(offset, data);
		}
		break;
	case SYNC_NOOP:
		/* do nothing, caused by a forced resync */
		break;
	case SYNC_POT:
		//logerror("x %02x \n", (param & 0x20));
		m_ALLPOT |= (param & 0xff);
		break;
	case SYNC_SET_IRQST:
		m_IRQST |=  (param & 0xff);
		break;
	default:
		assert_always(false, "Unknown id in pokey_device::device_timer");
	}
}

void pokey_device::execute_run()
{
	do
	{
		uint32_t new_out = step_one_clock();
		if (m_output != new_out)
		{
			//printf("forced update %08d %08x\n", m_icount, m_output);
			m_stream->update();
			m_output = new_out;
		}

		m_icount--;
	} while (m_icount > 0);

}


//-------------------------------------------------
//  step_one_clock - step the whole chip one
//  clock cycle.
//-------------------------------------------------

void pokey_device::step_keyboard()
{
	if (++m_kbd_cnt > 63)
		m_kbd_cnt = 0;
	if (!m_keyboard_r.isnull())
	{
		uint8_t ret = m_keyboard_r(m_kbd_cnt);

		switch (m_kbd_cnt)
		{
		case POK_KEY_BREAK:
			if (ret & 2)
			{
				/* check if the break IRQ is enabled */
				if (m_IRQEN & IRQ_BREAK)
				{
					m_IRQST |= IRQ_BREAK;
					if (!m_irq_f.isnull())
						m_irq_f(IRQ_BREAK);
				}
			}
			break;
		case POK_KEY_SHIFT:
			m_kbd_latch = (m_kbd_latch & 0xbf) | ((ret & 2) << 5);
			if (m_kbd_latch & 0x40)
				m_SKSTAT |= SK_SHIFT;
			else
				m_SKSTAT &= ~SK_SHIFT;
			/* FIXME: sync ? */
			break;
		case POK_KEY_CTRL:
			m_kbd_latch = (m_kbd_latch & 0x7f) | ((ret & 2) << 6);
			break;
		}
		switch (m_kbd_state)
		{
		case 0: /* waiting for key */
			if (ret & 1)
			{
				m_kbd_latch = (m_kbd_latch & 0xc0) | m_kbd_cnt;
				m_kbd_state++;
			}
			break;
		case 1: /* waiting for key confirmation */
			if ((m_kbd_latch & 0x3f) == m_kbd_cnt)
			{
				if (ret & 1)
				{
					m_KBCODE = m_kbd_latch;
					m_SKSTAT |= SK_KEYBD;
					if (m_IRQEN & IRQ_KEYBD)
					{
						/* last interrupt not acknowledged ? */
						if(m_IRQST & IRQ_KEYBD)
							m_SKSTAT |= SK_KBERR;
						m_IRQST |= IRQ_KEYBD;
						if (!m_irq_f.isnull())
							m_irq_f(IRQ_KEYBD);
					}
					m_kbd_state++;
				}
				else
					m_kbd_state = 0;
			}
			break;
		case 2: /* waiting for release */
			if ((m_kbd_latch & 0x3f) == m_kbd_cnt)
			{
				if ((ret & 1)==0)
					m_kbd_state++;
				else
					m_SKSTAT |= SK_KEYBD;
			}
			break;
		case 3:
			if ((m_kbd_latch & 0x3f) == m_kbd_cnt)
			{
				if (ret & 1)
					m_kbd_state = 2;
				else
				{
					m_SKSTAT &= ~SK_KEYBD;
					m_kbd_state = 0;
				}
			}
			break;
		}
	}
}

void pokey_device::step_pot()
{
	int pot;
	uint8_t upd = 0;
	m_pot_counter++;
	for (pot = 0; pot < 8; pot++)
	{
		if ((m_POTx[pot]<m_pot_counter) || (m_pot_counter == 228))
		{
			upd |= (1<<pot);
			/* latching is emulated in read */
		}
	}
	synchronize(SYNC_POT, upd);
}

/*
 * http://www.atariage.com/forums/topic/3328-sio-protocol/page__st__100#entry1680190:
 * I noticed that the Pokey counters have clocked carry (actually, "borrow") positions that delay the
 * counter by 3 cycles, plus the 1 reset clock. So 16 bit mode has 6 carry delays and a reset clock.
 * I'm sure this was done because the propagation delays limited the number of cells the subtraction could ripple though.
 *
 */

uint32_t pokey_device::step_one_clock(void)
{
	int ch, clk;
	uint32_t sum = 0;
	int clock_triggered[3] = {0,0,0};
	int base_clock = (m_AUDCTL & CLK_15KHZ) ? CLK_114 : CLK_28;

	if( (m_SKCTL & SK_RESET) )
	{
		/* Clocks only count if we are not in a reset */
		for (clk = 0; clk < 3; clk++)
		{
			m_clock_cnt[clk]++;
			if (m_clock_cnt[clk] >= clock_divisors[clk])
			{
				m_clock_cnt[clk] = 0;
				clock_triggered[clk] = 1;
			}
		}

		m_p4 = (m_p4 + 1) % 0x0000f;
		m_p5 = (m_p5 + 1) % 0x0001f;
		m_p9 = (m_p9 + 1) % 0x001ff;
		m_p17 = (m_p17 + 1 ) % 0x1ffff;

		clk = (m_AUDCTL & CH1_HICLK) ? CLK_1 : base_clock;
		if (clock_triggered[clk])
			m_channel[CHAN1].inc_chan();

		clk = (m_AUDCTL & CH3_HICLK) ? CLK_1 : base_clock;
		if (clock_triggered[clk])
			m_channel[CHAN3].inc_chan();

		if (clock_triggered[base_clock])
		{
			if (!(m_AUDCTL & CH12_JOINED))
				m_channel[CHAN2].inc_chan();
			if (!(m_AUDCTL & CH34_JOINED))
				m_channel[CHAN4].inc_chan();
		}

		/* Potentiometer handling */
		if ((clock_triggered[CLK_114] || (m_SKCTL & SK_PADDLE)) && (m_pot_counter < 228))
			step_pot();

		/* Keyboard */
		if (clock_triggered[CLK_114] && (m_SKCTL & SK_KEYSCAN))
			step_keyboard();
	}

	/* do CHAN2 before CHAN1 because CHAN1 may set borrow! */
	if (m_channel[CHAN2].check_borrow())
	{
		int isJoined = (m_AUDCTL & CH12_JOINED);
		if (isJoined)
			m_channel[CHAN1].reset_channel();
		m_channel[CHAN2].reset_channel();
		process_channel(CHAN2);

		/* check if some of the requested timer interrupts are enabled */
		if ((m_IRQST & IRQ_TIMR2) && !m_irq_f.isnull())
				m_irq_f(IRQ_TIMR2);
	}

	if (m_channel[CHAN1].check_borrow())
	{
		int isJoined = (m_AUDCTL & CH12_JOINED);
		if (isJoined)
			m_channel[CHAN2].inc_chan();
		else
			m_channel[CHAN1].reset_channel();
		process_channel(CHAN1);
		/* check if some of the requested timer interrupts are enabled */
		if ((m_IRQST & IRQ_TIMR1) && !m_irq_f.isnull())
			m_irq_f(IRQ_TIMR1);
	}

	/* do CHAN4 before CHAN3 because CHAN3 may set borrow! */
	if (m_channel[CHAN4].check_borrow())
	{
		int isJoined = (m_AUDCTL & CH34_JOINED);
		if (isJoined)
			m_channel[CHAN3].reset_channel();
		m_channel[CHAN4].reset_channel();
		process_channel(CHAN4);
		/* is this a filtering channel (3/4) and is the filter active? */
		if (m_AUDCTL & CH2_FILTER)
			m_channel[CHAN2].sample();
		else
			m_channel[CHAN2].m_filter_sample = 1;
		if ((m_IRQST & IRQ_TIMR4) && !m_irq_f.isnull())
			m_irq_f(IRQ_TIMR4);
	}

	if (m_channel[CHAN3].check_borrow())
	{
		int isJoined = (m_AUDCTL & CH34_JOINED);
		if (isJoined)
			m_channel[CHAN4].inc_chan();
		else
			m_channel[CHAN3].reset_channel();
		process_channel(CHAN3);
		/* is this a filtering channel (3/4) and is the filter active? */
		if (m_AUDCTL & CH1_FILTER)
			m_channel[CHAN1].sample();
		else
			m_channel[CHAN1].m_filter_sample = 1;
	}

	for (ch = 0; ch < 4; ch++)
	{
		sum |= (((((m_channel[ch].m_output ^ m_channel[ch].m_filter_sample) || (m_channel[ch].m_AUDC & VOLUME_ONLY)) ? (m_channel[ch].m_AUDC & VOLUME_MASK) : 0 )) << (ch * 4));
	}
	return sum;
}

//-------------------------------------------------
//  stream_generate - handle update requests for
//  our sound stream
//-------------------------------------------------


void pokey_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	stream_sample_t *buffer = outputs[0];

	if (m_output_type == LEGACY_LINEAR)
	{
		int32_t out = 0;
		for (int i = 0; i < 4; i++)
			out += ((m_output >> (4*i)) & 0x0f);
		out *= POKEY_DEFAULT_GAIN;
		out = (out > 0x7fff) ? 0x7fff : out;
		while( samples > 0 )
		{
			*buffer++ = out;
			samples--;
		}
	}
	else if (m_output_type == RC_LOWPASS)
	{
		double rTot = m_voltab[m_output];

		double V0 = rTot / (rTot+m_r_pullup) * m_v_ref / 5.0 * 32767.0;
		double mult = (m_cap == 0.0) ? 1.0 : 1.0 - exp(-(rTot + m_r_pullup) / (m_cap * m_r_pullup * rTot) * m_clock_period.as_double());

		while( samples > 0 )
		{
			/* store sum of output signals into the buffer */
			m_out_filter += (V0 - m_out_filter) * mult;
			*buffer++ = m_out_filter;
			samples--;

		}
	}
	else if (m_output_type == OPAMP_C_TO_GROUND)
	{
		double rTot = m_voltab[m_output];
		/* In this configuration there is a capacitor in parallel to the pokey output to ground.
		 * With a LM324 in LTSpice this causes the opamp circuit to oscillate at around 100 kHz.
		 * We are ignoring the capacitor here, since this oscillation would not be audible.
		 */

		/* This post-pokey stage usually has a high-pass filter behind it
		 * It is approximated by eliminating m_v_ref ( -1.0 term)
		 */

		double V0 = ((rTot+m_r_pullup) / rTot - 1.0) * m_v_ref  / 5.0 * 32767.0;

		while( samples > 0 )
		{
			/* store sum of output signals into the buffer */
			*buffer++ = V0;
			samples--;

		}
	}
	else if (m_output_type == OPAMP_LOW_PASS)
	{
		double rTot = m_voltab[m_output];
		/* This post-pokey stage usually has a low-pass filter behind it
		 * It is approximated by not adding in VRef below.
		 */

		double V0 = (m_r_pullup / rTot) * m_v_ref  / 5.0 * 32767.0;
		double mult = (m_cap == 0.0) ? 1.0 : 1.0 - exp(-1.0 / (m_cap * m_r_pullup) * m_clock_period.as_double());

		while( samples > 0 )
		{
			/* store sum of output signals into the buffer */
			m_out_filter += (V0 - m_out_filter) * mult;
			*buffer++ = m_out_filter /* + m_v_ref */;       // see above
			samples--;
		}
	}
	else if (m_output_type == DISCRETE_VAR_R)
	{
		int32_t out = m_voltab[m_output];
		while( samples > 0 )
		{
			*buffer++ = out;
			samples--;
		}
	}
}

//-------------------------------------------------
//  read - memory interface for reading the active status
//-------------------------------------------------

READ8_MEMBER( pokey_device::read )
{
	int data, pot;

	synchronize(SYNC_NOOP); /* force resync */

	switch (offset & 15)
	{
	case POT0_C: case POT1_C: case POT2_C: case POT3_C:
	case POT4_C: case POT5_C: case POT6_C: case POT7_C:
		pot = offset & 7;
		if( m_ALLPOT & (1 << pot) )
		{
			/* we have a value measured */
			data = m_POTx[pot];
			LOG(("POKEY '%s' read POT%d (final value)  $%02x\n", tag(), pot, data));
		}
		else
		{
			data = m_pot_counter;
			LOG(("POKEY '%s' read POT%d (interpolated) $%02x\n", tag(), pot, data));
		}
		break;

	case ALLPOT_C:
		/****************************************************************
		 * If the 2 least significant bits of SKCTL are 0, the ALLPOTs
		 * are disabled (SKRESET). Thanks to MikeJ for pointing this out.
		 ****************************************************************/
		if( (m_SKCTL & SK_RESET) == 0)
		{
			data = 0;
			LOG(("POKEY '%s' ALLPOT internal $%02x (reset)\n", tag(), data));
		}
		else if( !m_allpot_r_cb.isnull() )
		{
			data = m_allpot_r_cb(offset);
			LOG(("%s: POKEY '%s' ALLPOT callback $%02x\n", machine().describe_context(), tag(), data));
		}
		else
		{
			data = m_ALLPOT ^ 0xff;
			LOG(("POKEY '%s' ALLPOT internal $%02x\n", tag(), data));
		}
		break;

	case KBCODE_C:
		data = m_KBCODE;
		break;

	case RANDOM_C:
		if( m_AUDCTL & POLY9 )
		{
			data = m_poly9[m_p9] & 0xff;
			LOG_RAND(("POKEY '%s' rand9[$%05x]: $%02x\n", tag(), m_p9, data));
		}
		else
		{
			data = (m_poly17[m_p17] >> 8) & 0xff;
			LOG_RAND(("POKEY '%s' rand17[$%05x]: $%02x\n", tag(), m_p17, data));
		}
		break;

	case SERIN_C:
		if( !m_serin_r_cb.isnull() )
			m_SERIN = m_serin_r_cb(offset);
		data = m_SERIN;
		LOG(("POKEY '%s' SERIN  $%02x\n", tag(), data));
		break;

	case IRQST_C:
		/* IRQST is an active low input port; we keep it active high */
		/* internally to ease the (un-)masking of bits */
		data = m_IRQST ^ 0xff;
		LOG(("POKEY '%s' IRQST  $%02x\n", tag(), data));
		break;

	case SKSTAT_C:
		/* SKSTAT is also an active low input port */
		data = m_SKSTAT ^ 0xff;
		LOG(("POKEY '%s' SKSTAT $%02x\n", tag(), data));
		break;

	default:
		LOG(("POKEY '%s' register $%02x\n", tag(), offset));
		data = 0xff;
		break;
	}
	return data;
}


//-------------------------------------------------
//  write - memory interface for write
//-------------------------------------------------

WRITE8_MEMBER( pokey_device::write )
{
	synchronize(SYNC_WRITE, (offset<<8) | data);
}

void pokey_device::write_internal(offs_t offset, uint8_t data)
{
	/* determine which address was changed */
	switch (offset & 15)
	{
	case AUDF1_C:
		LOG_SOUND(("POKEY '%s' AUDF1  $%02x\n", tag(), data));
		m_channel[CHAN1].m_AUDF = data;
		break;

	case AUDC1_C:
		LOG_SOUND(("POKEY '%s' AUDC1  $%02x (%s)\n", tag(), data, audc2str(data)));
		m_channel[CHAN1].m_AUDC = data;
		break;

	case AUDF2_C:
		LOG_SOUND(("POKEY '%s' AUDF2  $%02x\n", tag(), data));
		m_channel[CHAN2].m_AUDF = data;
		break;

	case AUDC2_C:
		LOG_SOUND(("POKEY '%s' AUDC2  $%02x (%s)\n", tag(), data, audc2str(data)));
		m_channel[CHAN2].m_AUDC = data;
		break;

	case AUDF3_C:
		LOG_SOUND(("POKEY '%s' AUDF3  $%02x\n", tag(), data));
		m_channel[CHAN3].m_AUDF = data;
		break;

	case AUDC3_C:
		LOG_SOUND(("POKEY '%s' AUDC3  $%02x (%s)\n", tag(), data, audc2str(data)));
		m_channel[CHAN3].m_AUDC = data;
		break;

	case AUDF4_C:
		LOG_SOUND(("POKEY '%s' AUDF4  $%02x\n", tag(), data));
		m_channel[CHAN4].m_AUDF = data;
		break;

	case AUDC4_C:
		LOG_SOUND(("POKEY '%s' AUDC4  $%02x (%s)\n", tag(), data, audc2str(data)));
		m_channel[CHAN4].m_AUDC = data;
		break;

	case AUDCTL_C:
		if( data == m_AUDCTL )
			return;
		LOG_SOUND(("POKEY '%s' AUDCTL $%02x (%s)\n", tag(), data, audctl2str(data)));
		m_AUDCTL = data;

		break;

	case STIMER_C:
		LOG_TIMER(("POKEY '%s' STIMER $%02x\n", tag(), data));

		/* From the pokey documentation:
		 * reset all counters to zero (side effect)
		 * Actually this takes 4 cycles to actually happen.
		 * FIXME: Use timer for delayed reset !
		 */
		for (int i = 0; i < POKEY_CHANNELS; i++)
		{
			m_channel[i].reset_channel();
			m_channel[i].m_output = 0;
			m_channel[i].m_filter_sample = (i<2 ? 1 : 0);
		}

		break;

	case SKREST_C:
		/* reset SKSTAT */
		LOG(("POKEY '%s' SKREST $%02x\n", tag(), data));
		m_SKSTAT &= ~(SK_FRAME|SK_OVERRUN|SK_KBERR);
		break;

	case POTGO_C:
		LOG(("POKEY '%s' POTGO  $%02x\n", tag(), data));
		pokey_potgo();
		break;

	case SEROUT_C:
		LOG(("POKEY '%s' SEROUT $%02x\n", tag(), data));
		m_serout_w_cb(offset, data);
		m_SKSTAT |= SK_SEROUT;
		/*
		 * These are arbitrary values, tested with some custom boot
		 * loaders from Ballblazer and Escape from Fractalus
		 * The real times are unknown
		 */
		timer_set(attotime::from_usec(200), 3);
		/* 10 bits (assumption 1 start, 8 data and 1 stop bit) take how long? */
		timer_set(attotime::from_usec(2000), 4);// FUNC(pokey_serout_complete), 0, p);
		break;

	case IRQEN_C:
		LOG(("POKEY '%s' IRQEN  $%02x\n", tag(), data));

		/* acknowledge one or more IRQST bits ? */
		if( m_IRQST & ~data )
		{
			/* reset IRQST bits that are masked now, except the SEROC bit (acid5200 pokey_seroc test) */
			m_IRQST &= (IRQ_SEROC | data);
		}
		/* store irq enable */
		m_IRQEN = data;
		/* if SEROC irq is enabled trigger an irq (acid5200 pokey_seroc test) */
		if (m_IRQEN & m_IRQST & IRQ_SEROC)
		{
			if (!m_irq_f.isnull())
				m_irq_f(IRQ_SEROC);
		}
		break;

	case SKCTL_C:
		if( data == m_SKCTL )
			return;
		LOG(("POKEY '%s' SKCTL  $%02x\n", tag(), data));
		m_SKCTL = data;
		if( !(data & SK_RESET) )
		{
			write_internal(IRQEN_C,  0);
			write_internal(SKREST_C, 0);
			/****************************************************************
			 * If the 2 least significant bits of SKCTL are 0, the random
			 * number generator is disabled (SKRESET). Thanks to Eric Smith
			 * for pointing out this critical bit of info!
			 * Couriersud: Actually, the 17bit poly is reset and kept in a
			 * reset state.
			 ****************************************************************/
			m_p9 = 0;
			m_p17 = 0;
			m_p4 = 0;
			m_p5 = 0;
			m_clock_cnt[0] = 0;
			m_clock_cnt[1] = 0;
			m_clock_cnt[2] = 0;
			/* FIXME: Serial port reset ! */
		}
		break;
	}

	/************************************************************
	 * As defined in the manual, the exact counter values are
	 * different depending on the frequency and resolution:
	 *    64 kHz or 15 kHz - AUDF + 1
	 *    1.79 MHz, 8-bit  - AUDF + 4
	 *    1.79 MHz, 16-bit - AUDF[CHAN1]+256*AUDF[CHAN2] + 7
	 ************************************************************/

}

WRITE_LINE_MEMBER( pokey_device::sid_w )
{
	if (state)
	{
		m_SKSTAT |= SK_SERIN;
	}
	else
	{
		m_SKSTAT &= ~SK_SERIN;
	}
}

void pokey_device::serin_ready(int after)
{
	timer_set(m_clock_period * after, 5, 0);
}

//-------------------------------------------------
//  private stuff
//-------------------------------------------------

inline void pokey_device::process_channel(int ch)
{
	if ((m_channel[ch].m_AUDC & NOTPOLY5) || (m_poly5[m_p5] & 1))
	{
		if (m_channel[ch].m_AUDC & PURE)
			m_channel[ch].m_output ^= 1;
		else if (m_channel[ch].m_AUDC & POLY4)
			m_channel[ch].m_output = (m_poly4[m_p4] & 1);
		else if (m_AUDCTL & POLY9)
			m_channel[ch].m_output = (m_poly9[m_p9] & 1);
		else
			m_channel[ch].m_output = (m_poly17[m_p17] & 1);
	}
}


void pokey_device::pokey_potgo(void)
{
	int pot;

	LOG(("POKEY #%p pokey_potgo\n", (void *) this));

	m_ALLPOT = 0x00;
	m_pot_counter = 0;

	for( pot = 0; pot < 8; pot++ )
	{
		m_POTx[pot] = 228;
		if( !m_pot_r_cb[pot].isnull() )
		{
			int r = m_pot_r_cb[pot](pot);

			LOG(("POKEY %s pot_r(%d) returned $%02x\n", tag(), pot, r));
			if (r >= 228)
			{
				r = 228;
			}
			if (r == 0)
			{
				/* immediately set the ready - bit of m_ALLPOT
				 * In this case, most likely no capacitor is connected
				 */
				m_ALLPOT |= (1<<pot);
			}

			/* final value */
			m_POTx[pot] = r;
		}
	}
}

void pokey_device::vol_init()
{
	double resistors[4] = {90000, 26500, 8050, 3400};
	double pull_up = 10000;
	/* just a guess, there has to be a resistance since the doc specifies that
	 * Vout is at least 4.2V if all channels turned off.
	 */
	double r_off = 8e6;
	double r_chan[16];
	double rTot;

	for (int j=0; j<16; j++)
	{
		rTot = 1.0 / 1e12; /* avoid div by 0 */;
		for (int i=0; i<4; i++)
		{
			if (j & (1 << i))
				rTot += 1.0 / resistors[i];
			else
				rTot += 1.0 / r_off;
		}
		r_chan[j] = 1.0 / rTot;
	}
	if (VERBOSE)
		for (int j=0; j<16; j++)
		{
			rTot = 1.0 / r_chan[j] + 3.0 / r_chan[0];
			rTot = 1.0 / rTot;
			LOG(("%s: %3d - %4.3f\n", tag(), j, rTot / (rTot+pull_up)*4.75));
		}
	for (int j=0; j<0x10000; j++)
	{
		rTot = 0;
		for (int i=0; i<4; i++)
		{
			rTot += 1.0 / r_chan[(j>>(i*4)) & 0x0f];
		}
		rTot = 1.0 / rTot;
		m_voltab[j] = rTot;
	}

}

void pokey_device::poly_init_4_5(uint32_t *poly, int size, int xorbit, int invert)
{
	int mask = (1 << size) - 1;
	int i;
	uint32_t lfsr = 0;

	LOG_POLY(("poly %d\n", size));
	for( i = 0; i < mask; i++ )
	{
		/* calculate next bit */
		int in = !((lfsr >> 0) & 1) ^ ((lfsr >> xorbit) & 1);
		lfsr = lfsr >> 1;
		lfsr = (in << (size-1)) | lfsr;
		*poly = lfsr ^ invert;
		LOG_POLY(("%05x: %02x\n", i, *poly));
		poly++;
	}
}

void pokey_device::poly_init_9_17(uint32_t *poly, int size)
{
	int mask = (1 << size) - 1;
	int i;
	uint32_t lfsr =mask;

	LOG_RAND(("rand %d\n", size));

	if (size == 17)
	{
		for( i = 0; i < mask; i++ )
		{
			/* calculate next bit @ 7 */
			int in8 = ((lfsr >> 8) & 1) ^ ((lfsr >> 13) & 1);
			int in = (lfsr & 1);
			lfsr = lfsr >> 1;
			lfsr = (lfsr & 0xff7f) | (in8 << 7);
			lfsr = (in << 16) | lfsr;
			*poly = lfsr;
			LOG_RAND(("%05x: %02x\n", i, *poly));
			poly++;
		}
	}
	else
	{
		for( i = 0; i < mask; i++ )
		{
			/* calculate next bit */
			int in = ((lfsr >> 0) & 1) ^ ((lfsr >> 5) & 1);
			lfsr = lfsr >> 1;
			lfsr = (in << 8) | lfsr;
			*poly = lfsr;
			LOG_RAND(("%05x: %02x\n", i, *poly));
			poly++;
		}
	}

}

char *pokey_device::audc2str(int val)
{
	static char buff[80];
	if( val & NOTPOLY5 )
	{
		if( val & PURE )
			strcpy(buff,"pure");
		else
		if( val & POLY4 )
			strcpy(buff,"poly4");
		else
			strcpy(buff,"poly9/17");
	}
	else
	{
		if( val & PURE )
			strcpy(buff,"poly5");
		else
		if( val & POLY4 )
			strcpy(buff,"poly4+poly5");
		else
			strcpy(buff,"poly9/17+poly5");
	}
	return buff;
}

char *pokey_device::audctl2str(int val)
{
	static char buff[80];
	if( val & POLY9 )
		strcpy(buff,"poly9");
	else
		strcpy(buff,"poly17");
	if( val & CH1_HICLK )
		strcat(buff,"+ch1hi");
	if( val & CH3_HICLK )
		strcat(buff,"+ch3hi");
	if( val & CH12_JOINED )
		strcat(buff,"+ch1/2");
	if( val & CH34_JOINED )
		strcat(buff,"+ch3/4");
	if( val & CH1_FILTER )
		strcat(buff,"+ch1filter");
	if( val & CH2_FILTER )
		strcat(buff,"+ch2filter");
	if( val & CLK_15KHZ )
		strcat(buff,"+clk15");
	return buff;
}

pokey_device::pokey_channel::pokey_channel()
	:   m_AUDF(0),
		m_AUDC(0),
		m_borrow_cnt(0),
		m_counter(0),
		m_output(0),
		m_filter_sample(0)
{
}