SEGA Genesis: Z80

Beside the main Motorola 68000 CPU, the SEGA Genesis has a Z80, often used for audio processing

Z80

https://zeptobars.com/en/read/Zilog-Z80-Z0840004PSC - Resetting Z80 - Write to Z80 memory Silence: move.w #$0100,$00A11100 ; Request access to the Z80 bus, ; by writing $0100 into the BUSREQ port .wait: btst #$0,$00A11101 ; Test bit 0 of A11100 to see if the 68k ; has access to the Z80 bus yet bne .wait ; If we don't yet have control, branch back up ;Now the 68000 has access to the Z80’s bus,and the chip is held in a reset state,so we can write the program data to its memory. This is mapped from $A000000. move.b #$F,z80_memory+$100 move.w #$0000,$00A11100 ; Release control of bus
## Loading Z80 code Z80Code: incbin "..\..\roms\z80.bin" Z80CodeEnd: move.w #$0100,z80_busreq ; Request access to the Z80 bus,by writing $0100 into the BUSREQ port move.w #$0100,z80_reset ; Hold the Z80 in a reset state,by writing $0100 into the RESET port .wait: btst #$0,z80_busreq ; Test bit 0 of A11100 to see if the 68k has access to the Z80 bus yet bne .wait ; If we don't yet have control,branch back up to Wait ;Now the 68000 has access to the Z80’s bus,and the chip is held in a reset state,so we can write the program data to its memory. This is mapped from $A000000. move.l #Z80Code,a0 ; Load address of data into a0 move.l #z80_memory,a1 ; Copy Z80 RAM address to a1 move.l #Z80CodeEnd-Z80Code-1,d0 ; 42 bytes of init data (minus 1 for counter) .CopyZ80: move.b (a0)+,(a1)+ ; Copy data,and increment the source/dest addresses dbra d0,.CopyZ80 move.b #$1,z80_memory+$100 ; this works move.w #$0000,z80_reset ; Release reset state move.w #$0000,z80_busreq ; Release control of bus move.w #$0100,z80_reset ; Reset Z80
## Playing a beep

Z80
psgport = $7f11

; Periods are 10-bit, meaning values in the
; range 0..1023
per = $01af ; like a slightly flat A1 (110 Hz)

    ;play channel 1, frequency F
    ld hl,psgport
    ld a,$90    ; turn on ch1
    ld (hl),a

    ; PSG frequency
    ; 1CCCAAAA00AAAAAA
    ; - 1: always 1
    ; - C: channel ID:
    ;      ch1 000
    ;      ch2 010
    ;      ch3 100
    ; - A: 10-bit period
    ; ....3210..987654

    ld bc,$03ff
    ld hl,psgport

Computing frequency words:

    ; 4 least significant bits
    ;     BC: 00000098 76543210
    ; C&1111:          ....3210
    ld a,c       ; A = FE
    and $0F      ; a &= %00001111
    or a,$80     ; 
    ld (hl),a

    ; 6 most significant bits
    ;           BC: 00000098 76543210
    ; C & 11110000:          7654....
    ; >>4         :          ....7654
    ; B & 00000011: ......98
    ; <<4         : ..98....
    ; OR          : ..987654
    ld  a,c
    and $F0
    srl a
    srl a
    srl a
    srl a
    ld  d,a

    ld  a,b
    and a,$03
    sla a
    sla a
    sla a
    sla a
    or d
    ld (hl),a


Computing Frequencies

The PSG oscillator frequency is expressed as a period value:

The lowest frequency (largest period) the PSG can play is the biggest possible 10-bit value:

1111111111b = 1023

The frequency of this value is around 110 Hz (A1). If we index A1 with 0, we can compute all other note frequencies relative to that:

relative_frequency(note_idx) = 2^(note/12)

Relative period is simply the inverse:

                                 1
relative_period(note_idx) = -----------
                            2^(note/12)

The PSG period of note 0 was 1023, so we can compute periods for note 1 and upwards:

                          1023
psg_period(note_idx) = -----------
                       2^(note/12)

I generated the periods using this Ruby one-liner:

print "dc.w ";(0..24).each { |n| print "$%04x," % (1023/2**(n/12.0)) }
dw $03ff,$03c5,$038f,$035c,$032b,$02fe,$02d3,$02aa,$0284,
   $0260,$023e,$021d,$01ff,$01e2,$01c7,$01ae,$0195,$017f,
   $0169,$0155,$0142,$0130,$011f,$010e,$00ff

References

http://meseec.ce.rit.edu/eecc250-winter99/250-final-review.pdf

http://clrhome.org/table/ http://www.smspower.org/uploads/Development/SN76489-20030421.txt http://md.squee.co/PS https://emu-docs.org/Genesis/gen-hw.txt