8

Trying to generate a series of random numbers on my Commodore 64 (C64) using JSR $E09A and retrieving the number from $63 and $64. (which according to all the documentation I've seen is the same routine when you use RND(0) from BASIC. But can't get it to iterate. The following will work and place a different number in $63 and $64 when executed by itself.

. C000  A5 00    LDA $00
. C002  20 9A E0 JSR $E09A
. C005  00       BRK

Now when I try to iterate say 10 times with the following code, it never returns.

. C000  A0 0A    LDY #$0A
. C002  A9 00    LDA #$00
. C004  20 9A E0 JSR $E09A
. C007  88       DEY
. C008  D0 F8    BNE $C002
. C00A  00       BRK

Am I missing something so obvious I can't see it. I'm not worried about how "random" it is. At this point I just want a series of random numbers.

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Kenny
  • 171
  • 1
  • 6
  • I don't have an answer, but I wonder if the new Retro Computing forum here on Stack Exchange might have some folks on it who could help you also. – TomServo Jul 06 '17 at 00:01
  • 8
    The function you're calling may be changing the value in the Y register. – Ross Ridge Jul 06 '17 at 00:05
  • 1
    That was it, I knew I was missing something obvious – Kenny Jul 06 '17 at 00:21
  • 6
    JLH is referring to [Retrocomputing.SE], but programming questions are completely on-topic here on Stack Overflow, no matter how old your target machine may be. :-) – Cody Gray - on strike Jul 06 '17 at 11:27
  • Thanks @CodyGray I will check out the Retrocomputing section. I recently came across some of my old Commodore floppies and was able to transfer them to USB. I found the VICE commodore emulators and am now happily reliving the mid '80s, by tweaking some of my all BASIC stuff with some assembly routines, – Kenny Jul 06 '17 at 12:13
  • @Kenny JSR $E09E is even better. – Zibri Aug 31 '20 at 21:37

8 Answers8

11

The SID chip can actually generate numbers that are more random than BASIC's pseudo-random numbers. Start the generator with:

LDA #$FF  ; maximum frequency value
STA $D40E ; voice 3 frequency low byte
STA $D40F ; voice 3 frequency high byte
LDA #$80  ; noise waveform, gate bit off
STA $D412 ; voice 3 control register
RTS

Then you can get random numbers whenever you want with:

LDA $D41B ; get random value from 0-255
Mike
  • 2,721
  • 1
  • 15
  • 20
  • Can you still use this technique if your program is actively using the SID (all three voices simultaneously) to play music? – Psychonaut Jan 09 '19 at 08:53
  • 5
    No...and maybe. You can pull random numbers generated in advance from a cache and, when voice 3 is not being used, turn off the output and re-fill the cache before resetting voice 3 ready for the next output. It just involves a change to the music playing routine to use it during the gaps. – Mike Jan 10 '19 at 18:38
  • However, the sequence always starts the same: 254, 131, 229, 233, 173, ... – Lovro Feb 13 '20 at 07:01
  • 1
    What are you running it on? I can't speak for emulators but the hardware approach was used to generate random numbers for premium bonds. The SID chip actually does generate noise, I think. – Mike Feb 14 '20 at 11:46
  • in frequency is better 1 or 2 .. because the noise waveform repeats... and there is no reason to make it change more thance once per cpu cycle. – Zibri Jun 21 '20 at 09:34
  • I'm guessing you used an emulator when you got that same sequence. Emulators don't truly simulate the hardware. A real SID chip is capable of seemingly true random numbers. I used to use this technique for all of my c64 games/apps and the random numbers were better than any modern pseudo random number generator I've seen. – iPaul Apr 11 '22 at 03:28
  • You have to be careful when reading random numbers in quick succession. If you write super fast hand-coded assembly that pulls values from $D41B too quickly, you will get pairs of duplicates. So, for the caching technique someone suggested, you'd have to throw out every other value or have a NOP instruction in between your LDAs and STAs. – iPaul Apr 11 '22 at 12:55
9

Thanks to Ross Ridge for suggesting that the called function was changing the value in the Y register. I knew it had to be something obvious!

By storing Y before the JSR, and restoring after, it now will iterate properly. Here is the quick fix:

Edit: Updated 7/10/17 - to show full code and incorporate JeremyP suggestion. This is essentially a coin flip iterator (50000 repetitions) for purposes of experimenting with random

.C 033c  A9 00       LDA #$00
.C 033e  85 FB       STA $FB    ; set up register for counter
.C 0340  85 FC       STA $FC
.C 0342  A2 C8       LDX #$C8   ; outer loop= 200
.C 0344  86 FD       STX $FD
.C 0346  A0 FA       LDY #$FA   ; inner loop=250
.C 0348  84 FE       STY $FE
.C 034a  20 94 E0    JSR $E094  ; Get random# Vic20 Address (E09B for C64)
.C 034d  A5 63       LDA $64
.C 034f  C9 80       CMP #$80   ; >128 = HEADS
.C 0351  90 0D       BCC $0360  ; else continue loop
.C 0353  18          CLC        ; increment 2 byte number
.C 0354  A5 FB       LDA $FB
.C 0356  69 01       ADC #$01   ; LSB
.C 0358  85 FB       STA $FB
.C 035a  A5 FC       LDA $FC
.C 035c  69 00       ADC #$00   ; MSB
.C 035e  85 FC       STA $FC
.C 0360  C6 FE       DEC $FE
.C 0362  D0 E6       BNE $034A  ; end inner loop
.C 0364  C6 FD       DEC $FD
.C 0366  D0 DE       BNE $0346  ; end outer loop
.C 0368  60          RTS        ; return to basic

I can get the random number by LDA $63 or LDA $64 inside the loop and use it for my purposes.

This turned out to be a lot slower than expected, taking only half the time it would've taken in BASIC. The RND function takes a lot of cycles, however, I found this Compute! article which uses the SID chip as a random number generator.

LDA #$FF  ; maximum frequency value
STA $D40E ; voice 3 frequency low byte
STA $D40F ; voice 3 frequency high byte
LDA #$80  ; noise waveform, gate bit off
STA $D412 ; voice 3 control register  

Once turned on it generates numbers independently and doesn't have to be executed again. A loop that repeatadly calls LDA $D41B will get you a new random number on each iteration. In my test 50,000 iterations took 1.25 seconds and million took a little over 24 seconds. Pretty impressive for a 1MHz computer!

Kenny
  • 171
  • 1
  • 6
1

You are essentially calling RND(0) which uses the timer to generate a seed. However, that is not directly usuable in assembly. First try switching to a positive number (any number) and see if it starts producing values.

Chet
  • 3,461
  • 1
  • 19
  • 24
  • 2
    And, as Ross said, the function is changing `Y` register. See also the [Fully Commented Commodore 64 ROM Disassembly](http://www.pagetable.com/c64rom/c64rom_en.html#E097) – Jester Jul 06 '17 at 00:16
  • Oh right. So my answer is completely wrong. I'm not sure about SO etiquette, should I delete it? – Chet Jul 06 '17 at 00:18
  • It's not completely wrong, he is calling `rnd(0)` and that is using the timer. Just not sure why you said it wasn't usable. – Jester Jul 06 '17 at 00:22
  • I misread something so I thought you had to copy the timer into a specific memory location before you called RND(0). But it is clear from your link that the RND function is performing this copy for you. – Chet Jul 06 '17 at 00:29
  • According to the documentation JSR $E09A will use the value in the accumulator to determine the seed in which case 0 will grab the current values in the timer registers. So it behaves just like the BASIC RND(0) – Kenny Jul 06 '17 at 00:33
  • 2
    @Kenny: you want to "seed" the RNG only once, so RND(0) is only for first call, afterwards you should maintain the current seed. Reinitializing the seed all the time is detrimental to the results, actually if you call it fast enough and the timer regs have still the same value, you may receive same random values back. (this is general RNG answer explaining the principle, I didn't study the C64 case, so I have no idea how you should properly treat the seed value after initialization, whether the last random number is also seed for next one, or what). – Ped7g Jul 06 '17 at 01:49
  • Right, so either `LDA $01` or `JSR $E0BE` to skip the sign check altogether. – Chet Jul 06 '17 at 02:11
  • You can always [edit] the answer to make it more complete, using the information in the comments. – Cody Gray - on strike Jul 06 '17 at 11:26
  • @Ped7g: You're right I can seed before entering the loop and then JSR $E0BE cutting a few cycles out – Kenny Jul 06 '17 at 12:20
0

If you don't have a programm with timed raster-IRQ or something similar, you can just get a "random" number with lda $d012.

A.vH
  • 881
  • 6
  • 10
  • even without timed IRQs, as the CPU cycles are hard-wired to the VIC, you risk to get an ever repeating sequence this way... The SID method shown in the self-answer is better ;) –  Jul 13 '17 at 14:26
  • That's right, if your program is running without any user interaction. the Sid method steals one Voice for music. – A.vH Jul 14 '17 at 07:33
  • Yes, it's unlikely to happen in an interactive program, you're correct :) Just saying the "SID method" doesn't expose that risk at all. For a game, the quality of the randomness won't matter too much anyways of course. –  Jul 14 '17 at 07:34
0

I found this thread searching for more general RND(start, end) routine in C64 assembly. Something implemented as this BASIC example:

INT(RND(1) * (end- start + 1)) + start

While there are many helpful answers here, I was missing this kind of solution, so I had to find my own; and it might be helpful to another person coming to this thread, so here it goes:

            lda #<end   
            sta $FD
            lda #>end
            sta $FE
            lda #<start
            sta $FB
            lda #>start
            sta $FC
rnd:
            //reseed, to avoid repeated sequence; RND(0)
            lda #00
            jsr $E09A
            //++end 
            inc $FD
            bne skip1
            inc $FE
skip1:
            //- start
            lda $FD
            sec
            sbc $FB
            sta $FD
            lda $FE
            sbc $FC
            sta $FE         

            //++end-start to FAC
            ldy $FD
            lda $FE
            jsr $B391 //A(h),Y(L) - FAC 
            ldx #<flt
            ldy #>flt
            jsr $BBD4   //store FAC to flt
            //get actual RND(1)
            lda #$7f
            jsr $E09A
            //multiply by ++end - start
            lda #<flt
            ldy #>flt
            jsr $BA28
            //to integer
            jsr $BCCC
            //FAC to int;
            jsr $B1BF
            lda $65         
            clc
            adc $FB
            sta $14
            lda $64
            adc $FC
            sta $15
            rts     
flt:        .byte 0,0,0,0,0

The routine works with 16 bit numbers in range 0 - 32767. Arguments start in 251,252; end in 253, 254. 16 bit result found in $14.

Lovro
  • 185
  • 1
  • 3
  • 12
0

The real problems on a C64 are:

  1. SID generated numbers are also pseudorandom and they repeat in a sequence (I can't find the link discussing that)

  2. Raster position is not random.

The only source of true randomness in a c64 is user input.

So what I do is:

  1. initialize SID noise waveform
  2. get cia timer 1 LSB at startup (which is fine on a normal c64, but is not random on an emulator)
  3. start cia timer 2
  4. wait for the user to press any key (or a joystick direction/button)
  5. get cia timer 2 LSB
  6. get SID amplitude value
  7. optionally get raster position but depending if you are calling this routine from basic or assembler you might not get a totally random value.

Then you have your random seed for your favourite pseudorandom routine. Or just a one shot 16/24/32 bit random number.

In a game, for example you can get the cia timers when the user moves the joystick and get a random byte.

Note: dropping a prg or d64 in an emulator is very different than writing "load..." because every user writes differently every time and timers LSB are "random" in that case.

In some emulator a random delay is added to the computer start for this reason.

Zibri
  • 9,096
  • 3
  • 52
  • 44
  • 1
    I'm not sure being only pseudo-random is a problem here. It's unlikely the numbers are being generated for cryptographic purposes. – Ross Ridge Sep 02 '20 at 05:29
  • just FYI, a fibonacci based PRNG passes all checks for randomness... check this out: https://github.com/Zibri/rand2 – Zibri Mar 09 '21 at 14:47
  • @RossRidge here you go: https://stackoverflow.com/a/76500260/236062 – Zibri Jun 24 '23 at 15:18
0
LDA #$FF
STA $D40E ; set maximum frequency
STA $D40F ; set maximum frequency
LDA #$81
STA $D412 ; set NOISE waveform

JSR RND  ; A will contain a random byte
RTS

RND:
   LDA $DC04 ; read Timer A of CIA#1
   STA YY+1
XX:
   EOR #$FF
   STA ($fd),y
   EOR $D41B ; read Waveform Amplitude
YY:
   EOR #$00
   STA XX+1
   RTS
Zibri
  • 9,096
  • 3
  • 52
  • 44
  • 1
    What algorithm does this use? It looks like it's only XORing with some values from memory, and updating the immediate in the `YY: EOR`. So I don't see any mixing of bits horizontally within a seed value. Is it reading memory locations that get modified by interrupts, or I/O ports? – Peter Cordes Jun 18 '23 at 17:46
  • No. It's mixing the contents of the white noise random generator of the SID with the Timer A of CIA1. – Zibri Jun 23 '23 at 08:48
  • It would be a good idea for your answer to say that, in comments on the code and/or in text. I see you have an earlier answer on this question; you could have edited a new section into it, or if this is separate enough, could just have its own text explanation. – Peter Cordes Jun 23 '23 at 13:01
  • @PeterCordes ok.. I commented the code. – Zibri Jun 24 '23 at 15:17
-1

This is very late now, but depending on the requirements, you can also roll your own PRNG. Some algorithms are simple enough to implement, as an example, I'll show a 32bit xorshift implementation here using the parameters [3,25,24] (because this makes two of the shifts use very little code). The returned random number has 16 bits:

rnd_seed:
                sta     $22             ; store pointer to PRNG state
                stx     $23
                lda     #$00            ; initialize with 0
                ldy     #$03
rs_clrloop:     sta     ($22),y
                dey
                bne     rs_clrloop
                lda     $d012           ; except for LSB, use current raster
                bne     seed_ok
                lda     #$7f            ; or a fixed value if 0
seed_ok:        sta     ($22),y
                rts

rnd:
                sta     $22             ; store pointer to PRNG state
                stx     $23
                ldy     #$03
r_cpyloop:      lda     ($22),y         ; copy to ZP $fb - $fe
                sta     $fb,y
                dey
                bpl     r_cpyloop
                ldy     #$03            ; and shift left 3 bits
r_shiftloop:    asl     $fb
                rol     $fc
                rol     $fd
                rol     $fe
                dey
                bpl     r_shiftloop
                ldy     #$03
r_xorloop:      lda     ($22),y         ; xor with original state
                eor     $fb,y
                sta     ($22),y
                dey
                bpl     r_xorloop
                ldy     #$03
                lda     ($22),y
                lsr     a               ; MSB >> 1 gives ">> 25"
                ldy     #$00
                eor     ($22),y         ; xor with original state
                sta     ($22),y
                ldy     #$03            ; this is also value for "<< 24"
                eor     ($22),y         ; so xor with MSB
                sta     ($22),y
                tax                     ; use the two "higher" bytes as result ...
                dey
                lda     ($22),y         ; ... in A/X
                rts

Usage example:

main:
                lda     init
                bne     noinit
                lda     #<prng
                ldx     #>prng
                inc     init
                jsr     rnd_seed
noinit:         lda     #<prng
                ldx     #>prng
                jsr     rnd
                jmp     $bdcd        ; C64 BASIC routine output 16bit int in A/X

init:           .byte   $00
prng:           .res    4            ; 32bit PRNG state
  • I tried this code but it doesn't seem to generate a very uniform distribution in lower bits (e.g., some bit patterns don't get generated at all, some get generated often). See: https://gist.github.com/nurpax/d0fee60a74633ae3027d41a7d1d02c19 – Nurpax Apr 27 '18 at 12:48
  • @Nurpax Me too, I found only **one** byte has a good distribution with it a while ago ... I completely forgot I posted it here. Not sure what's the reason, the implementation is correct according to the paper linked, but maybe I misunderstood this listing of possible parameters -- it might not be an ideal choice. Using other parameters would greatly increase runtime (doing a lo0t more shifting), so in the end, I replaced it with an extremely simple 32bit LFSR implementation using a fixed XOR 0x04c11db7 on shifting out a bit to the left. Gives much better values. –  Apr 27 '18 at 12:54
  • @Nurpax and it's naturally faster as well ... I could rewrite this answer if you're interested. –  Apr 27 '18 at 12:56
  • I’d be interested in your simpler solution. I’m looking for for an easy to implement PRNG. Trying to not roll my own as it will easily lead to hard to notice bugs. Thanks in advance for your code. :) – Nurpax Apr 27 '18 at 18:12
  • @Nurpax well I would never roll my own idea, but even rolling my own implementation went not-so well in this case, so, yes :D I'll update this answer with the code I'm currently using, it's only a slight variation of code you also find on codebase64. A simple LFSR, so probably some properties for "good randomness" aren't ideal (don't use this for encryption), but distribution seems quite nice, I use it in a demo for "random" water movement and stars. I'll leave a comment once I edited this answer! –  Apr 27 '18 at 18:39
  • Thanks! This is going into a game so definitely not looking for crypto grade (if I was into such business I probably wouldn’t be running c64:)). But the game playability will suffer with a bad distribution. I already implemented a table based solution for another need in the game as I wanted to use Fisher Yates shuffle. – Nurpax Apr 27 '18 at 18:49
  • I've had some mixed experiences copy&pasting code from codebase64. For example, I pulled the soundfx code for my game from here http://codebase64.org/doku.php?id=base:sound_fx_player only to find out it had quite a bunch of bugs in it. :) But I forked it, fixed the bugs, and even made a sound editor for it. Btw, feel free to drop by at /r/c64coding if you're interested in a small c64 dev community. – Nurpax Apr 27 '18 at 18:55
  • I found this LFSR https://www.maximintegrated.com/en/app-notes/index.mvp/id/4400 (code in gist here https://gist.github.com/nurpax/d32529b017f71fd0e77c89a9b5a5e328). This looks an easy RNG to port to 6510. Will give it a shot. – Nurpax Apr 29 '18 at 18:19
  • @Nurpax what I used as a base was [this LFSR from codebase64](http://codebase64.org/doku.php?id=base:32bit_galois_lfsr). I think it's fine, just gave it some shape. Sorry I still didn't update this (now stupid-looking) answer. I want to do it "perfect" in the second attempt :) –  Apr 29 '18 at 18:21
  • This includes shaping a macro for the LFSR as well as some sample seeding code from normally-good sources (like VIC rasterline and a CIA timer) –  Apr 29 '18 at 18:22
  • I went ahead and re-implemented the C LFSR in 6510. Quite surprisingly, I at least got the 6510 version match exactly what my C rng outputs. https://gist.github.com/nurpax/d32529b017f71fd0e77c89a9b5a5e328#file-lfsr_6510-asm. Still interested to see your update tho :) – Nurpax Apr 29 '18 at 19:18
  • @Nurpax that's pretty cool, thanks for the link. Maybe you want to add your own answer then? :) But AFAIK, the "classic" PRNG in C isn't a LFSR but something [like this](https://github.com/Zirias/clang-libdos/blob/master/src/stdlib/stdlib_rand.c) (my implementation for MS-DOS) –  Apr 29 '18 at 19:20
  • Yup, I guess that's an LCG. These rely on multiplication and so the LFSRs seem like a much better idea. :) And probably worse in terms of randomness compared to a good LFSR. I'll maybe add an answer, I'll need to ensure first that I'm seeding my implementation right. – Nurpax Apr 29 '18 at 19:30
  • I never added an answer for my prng. I did end up running an rng eval suite on it and it didn’t do super well on it. Probably because mine was tuned for outputting on 8–16 bit output bits. – Nurpax May 10 '18 at 14:29