Kenny
Kenny

Reputation: 171

Getting random number from 6502 assembler

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.

Upvotes: 8

Views: 5474

Answers (8)

Zibri
Zibri

Reputation: 9857

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

Upvotes: 0

Zibri
Zibri

Reputation: 9857

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.

Upvotes: 0

Lovro
Lovro

Reputation: 185

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.

Upvotes: 0

user2371524
user2371524

Reputation:

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

Upvotes: -1

Mike
Mike

Reputation: 2761

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

Upvotes: 11

A.vH
A.vH

Reputation: 1026

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

Upvotes: 0

Kenny
Kenny

Reputation: 171

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!

Upvotes: 9

Chet
Chet

Reputation: 3729

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.

Upvotes: 1

Related Questions