Subject: Mockingboard Info.
Date: 1995/11/27
Author: Tom <tom@snsys.com>

Someone was asking for information on the Mockingboard for the Apple ][.
 
Well heres some code from my emulator for the Amiga.
 
The Mockingboard is very simple - it has no ROM on the card.
It consists of a 6522 & AY8910 x2.
You pass simple commands to ORA on the 6522 and the logic decides whether to
  Select ORB as AY register.
  Write ORB to the selected AY register.
  Reset the AY chip.
 
IE. To write 0xFF to AY register 0x02:
 
; Select AY register.
  0x02 -> ORB ; Data
  0x07 -> ORA ; Latch AY register command
  0x04 -> ORA ; Process command
 
 
; Write the data the the AY register.
  0xFF -> ORB ; Data
  0x06 -> ORA ; Write AY register command
  0x04 -> ORA ; Process command
 
 
See the code.
 
Tom.
 
 
 
 
 
; Module for the emulation of the Mockingboard Sound Card
; for the Apple ][.
 
; Thanks to Allan Crout for providing me with the original
documentation
; for the Mockingboard.
 
; This comprises of:
;  2x SY6522 VIA I/O chip
;  2x AY-3-8910 sound generator chip
;  1x Votrax speech synthesizer chip
 
; Nb. Speech chip not emulated.
;    Noise not emulated.
;    Envelopes not emulated.
;    Only Timer1 on 6522A is emulated.
 
 
 
; Chip offsets from card base.
SY6522A_Offset  = $00
SY6522B_Offset  = $80
Votrax_Offset  = $40
 
 
*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*
 
; 6522 registers:
  RSRESET
SY_ORB  rs.b 1 ; $00 - Port B
SY_ORA  rs.b 1 ; $01 - Port A (with handshaking)
SY_DDRB  rs.b 1 ; $02 - Data Direction Register B
SY_DDRA  rs.b 1 ; $03 - Data Direction Register A
SY_TIMER1L  rs.b 1 ; $04 - Read / Write & latch.
SY_TIMER1H  rs.b 1 ; $05 - Read / Write & initiate count.
SY_ACCESS_TIMER1L rs.b 1 ; $06
SY_ACCESS_TIMER1H rs.b 1 ; $07
SY_TIMER2L  rs.b 1 ; $08
SY_ACCESS_TIMER2H rs.b 1 ; $09
SY_SERIAL_SHIFT  rs.b 1 ; $0A
SY_ACR  rs.b 1 ; $0B - Auxiliary Control Register
SY_PCR  rs.b 1 ; $0C - Peripheral Control Register
SY_IFR  rs.b 1 ; $0D - Interrupt Flag Register
SY_IER  rs.b 1 ; $0E - Interrupt Enable Register
SY_ORA_NO_HS  rs.b 1 ; $0F - Port A (without handshaking)
 
SY6522_SIZE = __RS
 
 
 
; AY-3-8910 registers:
  RSRESET
AY_A_Tone_Fine  rs.b 1
AY_A_Tone_Coarse rs.b 1
AY_B_Tone_Fine  rs.b 1
AY_B_Tone_Coarse rs.b 1
AY_C_Tone_Fine  rs.b 1
AY_C_Tone_Coarse rs.b 1
AY_Noise  rs.b 1
AY_Enable  rs.b 1
AY_A_Amplitude  rs.b 1
AY_B_Amplitude  rs.b 1
AY_C_Amplitude  rs.b 1
AY_Envelope_Fine rs.b 1
AY_Envelope_Coarse rs.b 1
AY_Envelope_Shape rs.b 1
AY_Undefined1  rs.b 1
AY_Undefined2  rs.b 1
 
AY8910_SIZE = __RS
 
*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*
 
 
 
 
; IFR & IER:
PERIPHERAL  = 1<<1
TIMER2  = 1<<5
TIMER1  = 1<<6
 
; ACR:
RUNMODE  = 1<<6 ; 0 = 1-Shot Mode, 1 = Free Running Mode
 
 
 
; Votrax registers:
VO_DURPHON  = $00
VO_INFLECT  = $01
VO_RATEINF  = $02
VO_CTTRAMP  = $03
VO_FILFREQ  = $04
 
 
*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*
 
 
; Entry point for WRITES to Mockingboard.
* Pre: D6= Byte to write. (Nb High WORD not zero!)
* D7= 16 bit 6502 address.
 
Mockingboard_Write
 
swap d6  ; Keep data.
move.w d7,d6
and.w #$00F0,d6
and.w #$000F,d7  ; D7 = Register #
lsr.w #2,d6
move.l .Mockingboard_Write_Table(pc,d6.w),a6
clr.w d6
swap d6  ; D6 = BYTE to write.
jmp (a6)
 
.Mockingboard_Write_Table
dc.l SY6522A_Write
REPT 7
dc.l Mockingboard_Write_Undefined
ENDR
dc.l SY6522B_Write
REPT 7
dc.l Mockingboard_Write_Undefined
ENDR
 
*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*
 
SY6522A_Write
lea SY6522_AY8910_A(pc),a5
bra.s SY_W
SY6522B_Write
lea SY6522_AY8910_B(pc),a5
 
SY_W add.w d7,d7
add.w d7,d7
move.l .SY6522_Write_Table(pc,d7.w),a6
jmp (a6)
 
.SY6522_Write_Table
dc.l SY6522_Write_ORB
dc.l SY6522_Write_ORA
dc.l SY6522_Write_DDRB
dc.l SY6522_Write_DDRA
dc.l SY6522_Write_TIMER1L
dc.l SY6522_Write_TIMER1H
REPT 5
dc.l Mockingboard_Write_Out
ENDR
dc.l SY6522_Write_ACR
dc.l SY6522_Write_PCR
dc.l SY6522_Write_IFR
dc.l SY6522_Write_IER
dc.l Mockingboard_Write_Out
 
 
SY6522_Write_ORB
and.b SY6522+SY_DDRB(a5),d6
 
; Ultima pads values written to 6522 with 1's, so mask these
; out to get true value.
and.b #%00000111,d6 ; Only lower 3 data lines used.
 
cmp.b #4,d6
bne.s .store
move.b SY6522+SY_ORB(a5),d6 ; Get last value written.
beq.s .reset  ; 0 = RESET
cmp.b #6,d6  ; 6 = WRITE
beq.s .write
cmp.b #7,d6  ; 7 = LATCH
bne.s .empty
 
.latch ; Select value in Port A as current AY-3-8910 register.
move.b SY6522+SY_ORA(a5),AY_Current_Register(a5)
bra.s .empty
 
.write ; Write data in Port A to current AY-3-8910 register.
move.b AY_Current_Register(a5),d6
move.b SY6522+SY_ORA(a5),AY8910(a5,d6.w)
 
move.w AY_Registers_Written(a5),d7
bset d6,d7
move.w d7,AY_Registers_Written(a5)
 
; Check if written to all 14 AY8910 registers yet.
and.w #$3FFF,d7
cmp.w #$3FFF,d7    ; %0011 1111 1111 1111
bne.s .zero_check
bsr Do_AY8910
bra.s .empty
 
; Check if descended to zero:
; I.E. Written from AYRegX to AYReg0.
; (Ultima only uses [00..0A] - no envelopes)
.zero_check
tst.b d6  ; Leave if not Reg0.
bne.s .empty
tst.w d7  ; Leave if this is the first register.
beq.s .empty
bsr Do_AY8910
bra.s .empty
 
 
.reset ; Reset 6522/AY8910
nop
 
.empty ; Ultima writes (after masking with %00000111):
; 04,00,04 to ORB to RESET.
; 04,07,04 to ORB to LATCH an AY8910 register.
; 06,04,04 to ORB to WRITE to AY8910 register.
moveq #4,d6
 
.store move.b d6,SY6522+SY_ORB(a5)
bra Mockingboard_Write_Out
 
 
SY6522_Write_ORA
and.b SY6522+SY_DDRA(a5),d6
move.b d6,SY6522+SY_ORA(a5)
bra Mockingboard_Write_Out
 
SY6522_Write_DDRB
move.b d6,SY6522+SY_DDRB(a5)
bra Mockingboard_Write_Out
 
SY6522_Write_DDRA
move.b d6,SY6522+SY_DDRA(a5)
bra Mockingboard_Write_Out
 
SY6522_Write_TIMER1L
move.b d6,SY6522+SY_TIMER1L(a5)
bra Mockingboard_Write_Out
 
SY6522_Write_TIMER1H
; Initiate timer & Clears time-out of timer1.
 
; Clear Timer Interrupt Flag.
and.b #~TIMER1,SY6522+SY_IFR(a5)
 
move.b d6,SY6522+SY_TIMER1H(a5)
tst.b SY6522_Offset(a5)
bne.s .out
 
; For 6522A only:
; If (Timer interrupt enabled)
; Get Timer Count
; Enable Amiga Timer
move.b SY6522+SY_IER(a5),d6
and.b #TIMER1,d6
beq.s .out
 
st TimerStatus(a5)
 
move.b SY6522+SY_ACR(a5),d6
and.b #RUNMODE,d6
lsr.b #3,d6  ; RUNMODE-CIACR_RUNMODE
eor.b #CIACRAF_RUNMODE,d6 ; SY bit is opposite to CIA.
move.w d6,.SY_RunMode
 
; Setup Amiga Timer
move.l CIA(pc),a6
move.w CIAcr(pc),d7
*** move.b (a6,d7.w),d6  ; Get CRx
and.b CIAStopMask(pc),d6 ; Clear Timer specific bits.
or.b .SY_RunMode(pc),d6
*** move.b d6,(a6,d7.w)
 
move.w SY6522+SY_TIMER1L(a5),d7
ror.w #8,d7  ; Get Timer Count
; move.w #45457,d6  ; NTSC
move.w #45875,d6  ; PAL
mulu d6,d7  ; x 0.693 x 2^16 (PAL)
clr.w d7
swap d7    ; / 2^16
ror.w #8,d7
move.w CIAlo(pc),d6
*** move.b d7,(a6,d6.w)
ror.w #8,d7
move.w CIAhi(pc),d6
*** move.b d7,(a6,d6.w)  ; And start counter
 
.out bra.s Mockingboard_Write_Out
 
.SY_RunMode dc.w 0
 
 
SY6522_Write_ACR
move.b d6,SY6522+SY_ACR(a5)
bra.s Mockingboard_Write_Out
 
SY6522_Write_PCR  ; Used for Speech chip only.
move.b d6,SY6522+SY_PCR(a5)
bra.s Mockingboard_Write_Out
 
 
SY6522_Write_IFR
; Clear those bits which are set in the lower 7 bits.
; Can't clear bit 7.
and.b #$7f,d6  ; Clear high bit.
eor.b #$7f,d6
and.b d6,SY6522+SY_IFR(a5)
bra.s Mockingboard_Write_Out
 
 
SY6522_Write_IER
tst.b d6
bmi.s .set
 
.clr ; Clear those bits which are set in the lower 7 bits.
eor.b #$7f,d6
and.b d6,SY6522+SY_IER(a5)
 
; Check if timer has been disabled.
and.b #TIMER1,d6
bne.s .out
 
; Is timer on?
tst.b TimerStatus(a5)
beq.s .out
 
; Stop Amiga timer.
sf TimerStatus(a5)
move.l CIA(pc),a6
move.w CIAcr(pc),d7
*** and.b #%11111110,(a6,d7.w) ; Stop Timer
 
.out bra.s Mockingboard_Write_Out
 
 
.set ; Set those bits which are set in the lower 7 bits.
and.b #$7f,d6
or.b d6,SY6522+SY_IER(a5)
 
 
 
Mockingboard_Write_Out
move.l WRT_PageTable(a4),a5
 
Mockingboard_Write_Undefined
moveq #0,d6
moveq #0,d7
rts
 
 
 
*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн* ; Entry point for READS from Mockingboard/Internal ROM for this slot.   * Pre:  D7= 16 bit 6502 address.
* Post: D6= Value read.
* D7= 16 bit 6502 address.
 
Mockingboard_Read
tst.b ss_SLOTCXROM+1  ; Check which ROM is in IO space.
bpl.s .SlotROM
 
.InternalROM
move.b (a3,d7.l),d6
RTS
 
.SlotROM
 
move.w d7,-(sp)  ; Keep 6502 address.
 
move.w d7,d6
and.w #$000F,d6  ; Just register #
and.w #$00F0,d7
lsr.w #2,d7
move.l .Mockingboard_Read_Table(pc,d7.w),a6
move.w d6,d7  ; D7 = Register #
jsr (a6)
 
move.w (sp)+,d7  ; Restore 6502 address.
rts
 
 
.Mockingboard_Read_Table
dc.l SY6522A_Read
REPT 7
dc.l Mockingboard_Read_Undefined
ENDR
dc.l SY6522B_Read
REPT 7
dc.l Mockingboard_Read_Undefined
ENDR
 
 
*нннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннннн*
 
SY6522A_Read
lea SY6522_AY8910_A(pc),a5
bra.s SY_R
SY6522B_Read
lea SY6522_AY8910_B(pc),a5
 
SY_R add.w d7,d7
add.w d7,d7
move.l .SY6522_Read_Table(pc,d7.w),a6
jmp (a6)
 
.SY6522_Read_Table
dc.l SY6522_Read_ORB
dc.l SY6522_Read_ORA
dc.l SY6522_Read_DDRB
dc.l SY6522_Read_DDRA
dc.l SY6522_Read_TIMER1L
REPT 6
dc.l Mockingboard_Read_Out
ENDR
dc.l SY6522_Read_ACR
dc.l SY6522_Read_PCR
dc.l SY6522_Read_IFR
dc.l SY6522_Read_IER
dc.l Mockingboard_Read_Out
 
 
SY6522_Read_ORB
move.b SY6522+SY_ORB(a5),d6
bra.s Mockingboard_Read_Out
SY6522_Read_ORA
move.b SY6522+SY_ORA(a5),d6
bra.s Mockingboard_Read_Out
SY6522_Read_DDRB
move.b SY6522+SY_DDRB(a5),d6
bra.s Mockingboard_Read_Out
SY6522_Read_DDRA
move.b SY6522+SY_DDRA(a5),d6
bra.s Mockingboard_Read_Out
 
SY6522_Read_TIMER1L
move.b .SY6522_TIMER1L_Data(pc),d6
subq.b #8,.SY6522_TIMER1L_Data  ; Keep Skyfox's MB detection happy.
 
; Also clears Timer Interrupt Flag.
and.b #~TIMER1,SY6522+SY_IFR(a5)
 
bra.s Mockingboard_Read_Out
 
.SY6522_TIMER1L_Data dc.b 0,0
 
 
SY6522_Read_ACR
move.b SY6522+SY_ACR(a5),d6
bra.s Mockingboard_Read_Out
SY6522_Read_PCR
move.b SY6522+SY_PCR(a5),d6
bra.s Mockingboard_Read_Out
SY6522_Read_IFR
move.b SY6522+SY_IFR(a5),d6
bra.s Mockingboard_Read_Out
SY6522_Read_IER
move.b SY6522+SY_IER(a5),d6
 
 
 
Mockingboard_Read_Out
move.l WRT_PageTable(a4),a5
 
Mockingboard_Read_Undefined
; Nb. Don't zero D6.
moveq #0,d7
rts

 

-----------------------------------------

tom@snsys.com (Tom) wrote:
 
Embarrassingly, I interchanged the functionality of ORA and ORB. Here is the correct info.
(The example 68000 code is correct (mostly).)
 
Also you will need to know that both 6522 & 8910 have a base chip CLK frequency of 1.023MHz (=Apple 6502 clock)
 
Disassemble Skyfox / Ultima / MCS for example 6502 code.
This can be done easily with AppleWin, by just putting a break point on the range C400-C4FF.
 
Nb. Skyfox has a small Mockingboard detection routine, which reads the low byte of the 6522 timer @ Cx04 and then checks that the routine takes 8 cycles.
 
 
>Someone was asking for information on the Mockingboard for the Apple
>][.
 
>Well heres some code from my emulator for the Amiga.
 
>The Mockingboard is very simple - it has no ROM on the card.
>It consists of a 6522 & AY8910 x2.
You pass simple commands to ORB on the 6522 and the logic decides whether to
  Select ORA as AY register.
  Write ORA to the selected AY register.
>  Reset the AY chip.
 
>IE. To write 0xFF to AY register 0x02:
 
>; Select AY register.
  0x02 -> ORA ; Data
  0x07 -> ORB ; Latch AY register command
  0x04 -> ORB ; Process command
 
 
>; Write the data the the AY register.
  0xFF -> ORA ; Data
  0x06 -> ORB ; Write AY register command
  0x04 -> ORB ; Process command
 
 
>See the code.
 
>Tom.