The CPC Sound Chip
The Amstrad CPC,CPC+ and KC Compact use a
AY-3-8912 Programmable sound generator (referred to as PSG in this document), operating at 1Mhz.
This document will describe it's role in these systems. To understand the full functions please read the datasheet.
In these systems it is accessed through and connected to the
8255 PPI. (You should understand the operation of the
8255 if you wish to access
the PSG)
As well as generating sound, the PSG is also used to scan the keyboard matrix to read the state of the keyboard and joysticks. The selected
keyboard matrix line data is available at PSG I/O Port A. (The selected matrix line is programmed to PPI Port C bits 3-0. See the document about
scanning the keyboard matrix for a full description of the algorithm involved).
The databus of the PSG is connected to PPI Port A. Data is read from/written to the PSG through this port.
The PSG function, defined by the BC1,BC2 and BDIR signals, is controlled by bit 7 and bit 6 of PPI Port C.
PSG signals:
| PPI Port C Bit | PSG Signal |
|---|---|
| 7 | BDIR |
| 6 | BC1 |
PSG function selection:
| PPI Port C Bit | PSG Function | |
|---|---|---|
| 7 | 6 | |
| 0 | 0 | Inactive |
| 0 | 1 | Read from selected PSG register.
When function is set, the PSG will make the register data available to PPI Port A |
| 1 | 0 | Write to selected PSG register
When set, the PSG will take the data at PPI Port A and write it into the selected PSG register |
| 1 | 1 | Select PSG register
When set, the PSG will take the data at PPI Port A and select a register |
Note:
- The "inactive" mode must be used between function changes. If it is not used then there will be problems with the program on the CPC+.
See the document about CPC+ extra information for a description of this.The "inactive" mode can be used to prevent register corruption between changing modes.
Programming the PSG
The PSG has 16 internal registers and these control the operation. Before a register can be read from/written to it must be selected.
Register selection
To write data to the PSG, PPI Port A must be operating as output. (See the document on the 8255 to see how to do this)
To select a register, write the register number into PPI Port A, then set bit 7 and bit 6 of Port C to "1".
The register will now be selected and the user can read from or write to it.
The register will remain selected until another is chosen.
Writing to the selected PSG register
To write data to the PSG, PPI Port A must be operating as output. (See the document on the 8255 to see how to do this)
To write data into the selected PSG register:
- write data to PPI Port A,
- set bit 7 to "1" and bit 6 to "0" of PPI Port C
The data will then be written into the register.
Reading from the selected PSG register
To read data from the PSG, PPI Port A must be operating as input. (See the document on the 8255 to see how to do this)
To read data from the selected register:
- set bit 7 to "0" and bit 6 to "1" of PPI Port C
- read data from PPI Port A,
The register data is available at PPI Port A.
Notes
- If the tone period is in the range 0-4 I can not hear any tone output.
Programming Examples
- Reading a value from a PSG register.
This routine can be used to read data from a PSG register.
;; entry conditions:
;; c = register index
;; exit conditions:
;; a = register data
;; b,c,f corrupt
;; PPI port A is returned to output
;; assumptions:
;; PPI port A is set to output, PPI port C is set to output.
.read_from_psg
ld b,&f4 ;} Setup register index on PPI port A
out (c),c ;}
ld bc,&f6c0 ;} Tell PSG to select register using data on PPI port A
out (c),c ;}
ld bc,&f600 ;} Put PSG into inactive state
out (c),c
;** Set PPI port A to input mode. **
ld b,&f7 ;
8255 PPI Control
ld c,%10010010 ; mode and port configuration
out (c),c ; Port A input, Port B input, Port C output
; All operating in mode 0. (see Programming
;
8255 PPI)
ld bc,&f640 ;} Tell PSG to put the data of the selected register to PPI port A to
out (c),c ;}
ld b,&f4 ;} Read data from PPI port A
in a,(c) ;}
;** Set PPI port A to output mode. *
ld b,&f7 ;
8255 PPI control
ld c,%10000010
out (c),c ;Port A output, Port B input, Port C output
ld bc,&f600 ;} Return PSG to inactive mode.
out (c),c ;}
ret
- Writing to a PSG register.
This routine can be used to write to registers on the PSG. C contains the index of the PSG register to write. A contains the data to write to the PSG register.
;; entry conditions:
;; C = register number
;; A = register data
;; exit conditions:
;; b,C,F corrupt
;; assumptions:
;; PPI port A and PPI port C are setup in output mode.
.write_to_psg
ld b,&f4 ;} setup PSG register number on PPI port A
out (c),c ;}
ld bc,&f6c0 ;} Tell PSG to select register from data on PPI port A
out (c),c ;}
ld bc,&f600 ;} Put PSG into inactive state.
out (c),c ;}
ld b,&f4 ;} setup register data on PPI port A
out (c),a ;}
ld bc,&f680 ;} Tell PSG to write data on PPI port A into selected register
out (c),c ;}
ld bc,&f600 ;} Put PSG into inactive state
out (c),c ;}
ret
AY-3-8912 Additional information
The AY-3-8912 is a programmable sound generator made by General Instruments.
Registers
When reading and writing data to the AY-3-8912 registers, I found that:
- When writing data to register 1,3,5 and 13, and then reading the data back, bits 7..4 will always be "0", regardless of the data written. bits 3..0 will be the same as bits 3..0 of the data.
- When writing data to register 6, 8, 9 and 10, and then reading the data back, bits 7..5 will always be "0", regardless of the data written. bits 4..0 will be the same as bits 4..0 of the data.
- Writing to all the other registers, 0, 2,4,7, 11,12, and then reading the data back will return the data written. e.g. bits 7..0 of the data read is identical to bits 7..0 of the data written.
- The data read from registers 14 and 15 will be different depending on the input/output state of the I/O ports connected to these registers. See below for more details.
Example source code:
;; This source is part of the
AY-3-8912 PSG additional notes document
;; and shows:
;;
;; when a PSG register is written to, and then read back, some bits will always
;; be set to 0.
;;
;; This example is for the Amstrad CPC and also shows how
;; to read/write data from the PSG.
;;
;; This example can be assembled using Maxam or compatible assembler.
;;
;; assemble, then jump to &8000 to begin
;;
;; (c) Kevin Thacker 2001,2002
;;
;; This source is released under the GNU Public License v2.
org &8000
;; firmware function to display
ASCII character on the screen at the current
;; text cursor location
.txt_output equ &bb5a
;; list first 14 registers
ld b,14
;; start at register 0
ld c,0
.loop
push bc
;; disable interrupts
;; (do not let CPC firmware effect this test)
di
push bc
;; write 0x0ff to the register (attempt to set all bits to "1")
ld a,&ff
call write_reg
pop bc
;; read actual register's contents
call read_reg
ei
;; display number in register A as binary
call print_binary_number
;; display a new-line
ld a,10
call txt_output
ld a,13
call txt_output
pop bc
djnz loop
ret
;;-------------------------------------------------------------------------------
;; display a number as a 8-digit binary number
;;
;; Entry Conditions:
;;
;; A = number (0-255)
;;
;; Exit Conditions:
;;
;; A,B corrupt
.print_binary_number
;; 8 digits in binary number
ld b,8
.pbn1
;; transfer bit 7 into carry
rlca
push af
;; convert to
ASCII
;; 0-> "0" and 1-> "1"
adc a,"0"
;; display digit on the screen
call txt_output
pop af
djnz pbn1
ret
;;------------------------------------------------
;; Read from a
AY-3-8912 register
;;
;; Entry conditions:
;;
;; C = register number
;; PPI port A is assumed to be set to output.
;; PSG operation is assumed to be "inactive"
;;
;; Exit conditions:
;;
;; A = register data
;; BC corrupt
;;
;; This function is compatible with the CPC+.
.read_reg
;; step 1: select register
;; write register index to PPI port A
ld b,&f4
out (c),c
;; set PSG operation - "select register"
ld bc,&f6c0
out (c),c
;; set PSG operation - "inactive"
ld bc,&f600
out (c),c
;; PPI port A set to input, PPI port B set to input,
;; PPI port C (lower) set to output, PPI port C (upper) set to output
ld bc,&f700+%10010010
out (c),c
;; set PSG operation - "read register data"
ld bc,&f640
out (c),c
;; step 2 - read data from register
;; read PSG register data from PPI port A
ld b,&f4
in a,(c)
;; PPI port A set to output, PPI port B set to input,
;; PPI port C (lower) set to output, PPI port C (upper) set to output
ld bc,&f700+%10000010
out (c),c
;; set PSG operation - "inactive"
ld bc,&f600
out (c),c
ret
;;------------------------------------------------
;; Write to a
AY-3-8912 register
;;
;; Entry conditions:
;;
;; C = register number
;; A = data
;; PPI port A is assumed to be set to output.
;; PSG operation is assumed to be "inactive"
;;
;; Exit conditions:
;;
;; BC corrupt
;;
;; This function is compatible with the CPC+.
.write_reg
;; step 1 - select register
;; write register index to PPI port A
ld b,&f4
out (c),c
;; set PSG operation - "select register"
ld bc,&f6c0
out (c),c
;; set PSG operation - "inactive"
ld bc,&f600
out (c),c
;; step 2 - write data to register
;; write data to PPI port A
ld b,&f4
out (c),a
;; set PSG operation - "write data to register"
ld bc,&f680
out (c),c
;; set PSG operation - "inactive"
ld bc,&f600
out (c),c
ret
I/O ports
I want to thank Russell Marks for his information which I used in this section.
I read a message on the comp.sys.sinclair newsgroup from Russell Marks. He had discovered a problem between running "Matchday 2" on a emulator and a real Spectrum+2 computer.
There are 3 chips in the AY-3-891x Programmable Sound Generator (PSG) family.
- The AY-3-8910 model has 2 I/O ports.
- The AY-3-8912 model has 1 I/O port.
- The AY-3-8913 model has 0 I/O ports.
Port "A" is accessed through register number 14 and Port "B" is accessed through register number 15.
The Amstrad CPC and Spectrum use the AY-3-8912 model.
From furthur investigation he found the program was using AY-3-8912 register 15 (Port B). But this model of the AY-3-891x does not use Port B.
He discovered the following:
- When a port is defined as output (For port A, set bit 6 of register 7 to "1". For port B set bit 7 of register 7 to "1"), writing to the port register (register 14 for Port A, register 15 for port B) will store the data written. A read of the port register will then return the last value written logically ANDed with the input to that port. All 8-bit values can be written.
- Port B (register 15) can be used. The operation is identical to port A.
- When a port is defined as input (For port A, set bit 6 of register 7 to "0". For port B set bit 7 of register 7 to "0"):
- writing to the port register (register 14 for Port A, or register 15 for port B) will store the data written. If the port is then defined as output, a read of the port register will return the last value written ANDed with the input to the port. All 8-bit values can be used.
- reading from the port register (register 14 for Port A, or register 15 for port B) will ONLY return the inputs!
- each port has a "output register" which is internal to the AY-3-8912,
- when data is written to the port's register (register 14 for port A or register 15 for port B), it is stored in the port's output register, regardless of the input/output state of the port.
- when the port is defined as output it is possible to read the port's "output register". The data read is a logical AND of the "output register" and the inputs to the port.
- when the port is defined as output, the contents of the port's output register will be present on the port's I/O signals on the IC.
- The inputs to each port are not latched, the data that is read through the port's register is "realtime".
Additional notes for the AY-3-8912 in the CPC design
- Port A of the AY-3-8912 is connected to the keyboard. The data for a selected keyboard line can be read through Port A, as long as it is defined as input. The operating system and I believe all programs assume this port has been defined as input. (NWC has found a bug in the Multiface 2 software. The Multiface does not reprogram the input/output state of the AY-3-8912's registers, therefore if port A is programmed as output, the keyboard will be unresponsive and it will not be possible to use the Multiface functions.)
- When port B is defined as input (bit 7 of register 7 is set to "0"), a read of this port will return &FF.








