Получение случайного числа от 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:
Сгенерированные номера SID также являются псевдослучайными и повторяются в последовательности (я не могу найти ссылку, где это обсуждается)
Положение растра не случайное.
Единственный источник истинной случайности в c64 - пользовательский ввод.
Итак, что я делаю:
- инициализировать сигнал шума SID
- получить таймер cia 1 LSB при запуске (что нормально на обычном c64, но не случайно на эмуляторе)
- запустить таймер cia 2
- подождите, пока пользователь нажмет любую клавишу (или кнопку направления / джойстика)
- получить таймер cia 2 LSB
- получить значение амплитуды SID
- опционально получить позицию растра, но в зависимости от того, вызываете ли вы эту процедуру из базового или ассемблера, вы можете не получить полностью случайное значение.
Тогда у вас есть случайное начальное число для вашей любимой псевдослучайной процедуры. Или просто одноразовое 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