The Lab
Retro Reference

The SID & C64 Random Numbers

The 6581/8580 register map, and a close look at where randomness comes from on a Commodore 64, the SID noise generator, reading OSC3, and why BASIC's RND is the wrong tool.

The chip at a glance

The SID (MOS 6581, later 8580) is the C64's sound chip, sitting in the I/O block at $d400$d41c. It has three independent voices, each with its own oscillator, an ADSR envelope, and a choice of four waveforms (triangle, sawtooth, pulse, and noise). A single multimode analog filter sits across the voices, and there are a couple of read-only registers that turn out to matter a lot for everything other than sound.

Register map

All addresses are in the I/O block (decimal base 54272 = $d400). Voices 2 and 3 repeat voice 1's seven-register layout.
AddrDecNameR/WFunction

Random numbers on the C64

The C64 has no hardware random-number instruction, and the BASIC RND function is a deterministic generator that is easy to predict and statistically poor. For anything that needs to feel random, most demos and games reach for the SID's noise oscillator.

The SID noise generator

When a voice is set to the noise waveform, its oscillator clocks a 23-bit linear-feedback shift register (LFSR). Each step shifts the register and feeds a new bit in from the exclusive-OR of bits 17 and 22. Eight specific bits of that register (0, 2, 5, 9, 11, 14, 18, 20) are tapped to form the 8-bit noise output. That output is exactly what you read back from voice 3 at $d41b.

Because it is a maximal-length 23-bit LFSR, the sequence runs for 223 − 1 = 8,388,607 steps before repeating. The oscillator's frequency setting controls how fast it advances, so a high frequency means the value you read jumps around quickly between reads.

Reading it: voice 3 and $d41b

Set voice 3 to a high frequency and select the noise waveform, then read $d41b whenever you want a byte from 0 to 255. Voice 3 is the conventional choice because you can also silence it (the $80 bit of $d418) so the noise never reaches the speaker while you keep harvesting numbers.

; --- seed the SID noise source (assembly) ---
lda #$ff
sta $d40e      ; voice 3 freq lo
sta $d40f      ; voice 3 freq hi (run it fast)
lda #$80
sta $d412      ; voice 3 control = noise waveform
lda #$80
sta $d418      ; mute voice 3 in the output mixer
; --- then, any time you need a byte ---
getrnd
lda $d41b      ; A = pseudo-random 0..255
rts
REM --- the same thing in BASIC ---
10 POKE 54286,255 : POKE 54287,255 : REM V3 FREQ
20 POKE 54290,128 : REM V3 = NOISE
30 POKE 54296,128 : REM MUTE V3 ($D418)
40 R = PEEK(54299) : REM RANDOM 0-255 FROM $D41B
50 PRINT R : GOTO 40

For a value in a range, mask or scale the byte (R AND 7 for 0–7, and so on). The result is good enough for game logic and visual effects, but it is still a plain LFSR: predictable if you know the state, with the usual linear correlations, so it is not suitable where real unpredictability matters.

Why BASIC RND is weak

Other sources on the machine

Every on-board option is a pseudo-random or low-entropy source. True randomness has to come from physical noise outside the machine, which is the whole point of feeding a hardware noise board into the C64.