Reputation: 171
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
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
Reputation: 9857
The real problems on a C64 are:
SID generated numbers are also pseudorandom and they repeat in a sequence (I can't find the link discussing that)
Raster position is not random.
The only source of true randomness in a c64 is user input.
So what I do is:
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
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
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
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
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
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
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