Получение случайного числа от 6502 ассемблера

Попытка сгенерировать серию случайных чисел на моем C64 с использованием JSR $E09A и получение числа от $63 и $64. (который согласно всей документации, которую я видел, является той же самой процедурой, когда вы используете RND(0) из BASIC. Но не можете заставить его повторяться. Следующее сработает и поместит другое число в $63 и $ 64 при выполнении сам.

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

Теперь, когда я пытаюсь повторить 10 раз с помощью следующего кода, он никогда не возвращается.

. 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

Я что-то упускаю настолько очевидное, что не вижу этого? Я не беспокоюсь о том, насколько это "случайно". На данный момент я просто хочу серию случайных чисел.

8 ответов

Решение

Спасибо Россу Риджу за предложение, что вызываемая функция изменяет значение в регистре Y. Я знал, что это должно быть что-то очевидное!

Храня Y перед JSR и восстанавливая после, он теперь будет повторяться правильно. Вот быстрое решение:

Изменить: Обновлено 7/10/17 - чтобы показать полный код и включить предложение JeremyP. По сути, это итератор с переворотом монет (50000 повторений) для экспериментов со случайными

.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

Я могу получить случайное число по LDA $63 или же LDA $64 внутри цикла и использовать его для моих целей.

Это оказалось намного медленнее, чем ожидалось, и заняло только половину времени, которое потребовалось бы в Бейсике. Функция RND занимает много циклов, однако я нашел этот Compute! статья, которая использует чип SID в качестве генератора случайных чисел.

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  

После включения он генерирует числа независимо и не должен быть выполнен снова. Цикл, который постоянно вызывает LDA $D41B Вы получите новое случайное число на каждой итерации. В моем тесте 50000 итераций заняли 1,25 секунды, а миллион - чуть более 24 секунд. Довольно впечатляет для 1 МГц компьютера!

Чип SID может фактически генерировать числа, которые являются более случайными, чем псевдослучайные числа BASIC. Запустите генератор с:

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

Тогда вы можете получить случайные числа, когда захотите:

LDA $D41B ; get random value from 0-255

Вы по сути звоните RND(0) который использует таймер для генерации семян. Однако это не может быть использовано непосредственно при сборке. Сначала попробуйте переключиться на положительное число (любое число) и посмотрите, начнет ли оно генерировать значения.

Если у вас нет программы с временным растровым IRQ или чем-то подобным, вы можете просто получить "случайное" число с помощью lda $d012,

      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

Настоящие проблемы на C64:

  1. Сгенерированные номера SID также являются псевдослучайными и повторяются в последовательности (я не могу найти ссылку, где это обсуждается)

  2. Положение растра не случайное.

Единственный источник истинной случайности в c64 - пользовательский ввод.

Итак, что я делаю:

  1. инициализировать сигнал шума SID
  2. получить таймер cia 1 LSB при запуске (что нормально на обычном c64, но не случайно на эмуляторе)
  3. запустить таймер cia 2
  4. подождите, пока пользователь нажмет любую клавишу (или кнопку направления / джойстика)
  5. получить таймер cia 2 LSB
  6. получить значение амплитуды SID
  7. опционально получить позицию растра, но в зависимости от того, вызываете ли вы эту процедуру из базового или ассемблера, вы можете не получить полностью случайное значение.

Тогда у вас есть случайное начальное число для вашей любимой псевдослучайной процедуры. Или просто одноразовое 16/24/32 битное случайное число.

В игре, например, вы можете получить таймеры cia, когда пользователь перемещает джойстик и получает случайный байт.

Примечание: удаление prg или d64 в эмуляторе сильно отличается от записи "load...", потому что каждый пользователь записывает каждый раз по-разному, а LSB таймеров в этом случае является "случайным".

В некоторых эмуляторах по этой причине к запуску компьютера добавляется случайная задержка.

Я нашел этот поток в поисках более общей процедуры RND(начало, конец) в сборке C64. Что-то реализовано как этот пример BASIC:

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

Хотя здесь есть много полезных ответов, мне не хватало такого решения, поэтому мне пришлось найти свое собственное; и это может быть полезно для другого человека, заходящего в эту ветку, так что вот оно:

            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

Подпрограмма работает с 16-битными числами в диапазоне от 0 до 32767. Аргументы начинаются с 251252; заканчиваются на 253, 254. 16-битный результат найден в $14.

Сейчас очень поздно, но в зависимости от требований, вы также можете запустить свой собственный PRNG. Некоторые алгоритмы достаточно просты для реализации, в качестве примера я покажу здесь 32- битную реализацию xorshift, используя параметры [3,25,24] (потому что это заставляет две смены использовать очень мало кода). Возвращенное случайное число имеет 16 бит:

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

Пример использования:

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
Другие вопросы по тегам