Getting random number from 6502 assembler

Viewed 3985

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.

7 Answers

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.

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.

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
Related