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.
$10, sawtooth $20, pulse $40, noise $80, set in the control register.$d41b (voice 3 oscillator) and $d41c (voice 3 envelope), which a program can read back.$d400). Voices 2 and 3 repeat voice 1's seven-register layout.| Addr | Dec | Name | R/W | Function |
|---|
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.
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.
$d41bSet 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.
RND is weakRND(1) (any positive argument) returns the next value in a fixed sequence from a small linear-congruential generator. From a clean reset it produces the same numbers in the same order every time, so it is repeatable, not random.RND(0) reads the CIA timers and jiffy clock for a seed, which is less predictable but low entropy and biased toward whatever the timers happen to be doing.RND(-n) reseeds the generator from the argument, which is useful for reproducible runs but is the opposite of random.$dc04–$dc07): free-running 16-bit counters. Reading the low byte at an unpredictable moment (after user input) gives some entropy.$d012): the current VIC-II scan line, handy as a fast-changing seed but cyclic and fully predictable on its own.$a0–$a2): the KERNAL's 1/60s counter, fine as a coarse seed but not a source on its own.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.