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

Попытка сгенерировать серию случайных чисел на моем Commodore 64 (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

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


person Kenny    schedule 05.07.2017    source источник
comment
У меня нет ответа, но мне интересно, могут ли на новом форуме Retro Computing здесь, на Stack Exchange, присутствовать люди, которые также могут вам помочь.   -  person TomServo    schedule 06.07.2017
comment
Вызываемая функция может изменять значение в регистре Y.   -  person Ross Ridge    schedule 06.07.2017
comment
Вот и все, я знал, что упускаю что-то очевидное   -  person Kenny    schedule 06.07.2017
comment
JLH имеет в виду Retrocomputing, но вопросы программирования полностью относятся к теме здесь, в Stack Overflow, независимо от того, сколько лет вашей целевой машине. :-)   -  person Cody Gray    schedule 06.07.2017
comment
Спасибо @CodyGray, я проверю раздел Retrocomputing. Недавно я наткнулся на некоторые из моих старых дискет Commodore и смог перенести их на USB. Я нашел эмуляторы коммодоров VICE и теперь с радостью снова переживаю середину 80-х, подправив некоторые из моих BASIC-вещей с помощью некоторых процедур сборки,   -  person Kenny    schedule 06.07.2017
comment
@Kenny JSR $ E09E даже лучше.   -  person Zibri    schedule 01.09.2020


Ответы (7)


Чип 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
person Mike    schedule 24.07.2017
comment
Можете ли вы по-прежнему использовать эту технику, если ваша программа активно использует SID (все три голоса одновременно) для воспроизведения музыки? - person Psychonaut; 09.01.2019
comment
Нет ... а может быть. Вы можете извлечь случайные числа, сгенерированные заранее, из кеша, и, когда голос 3 не используется, выключить вывод и повторно заполнить кеш перед сбросом голоса 3, готовым к следующему выводу. Это просто связано с изменением режима воспроизведения музыки, чтобы использовать ее во время перерывов. - person Mike; 10.01.2019
comment
Однако последовательность всегда начинается одинаково: 254, 131, 229, 233, 173, ... - person Lovro; 13.02.2020
comment
На чем вы его запускаете? Я не могу говорить об эмуляторах, но аппаратный подход использовался для генерации случайных чисел для премиальных облигаций. Я думаю, что чип SID действительно генерирует шум. - person Mike; 14.02.2020
comment
по частоте лучше 1 или 2 .. потому что форма сигнала шума повторяется ... и нет причин заставлять его менять чаще, чем один раз за цикл процессора. - person Zibri; 21.06.2020

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

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

Изменить: Обновлено 10.07.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 внутри цикла и использовать его для своих целей.

Это оказалось намного медленнее, чем ожидалось, и заняло лишь половину времени, которое потребовалось бы в BASIC. Функция 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, будет давать вам новое случайное число на каждой итерации. В моем тесте 50 000 итераций заняли 1,25 секунды, а миллион - немногим более 24 секунд. Довольно впечатляюще для компьютера с тактовой частотой 1 МГц!

person Kenny    schedule 06.07.2017

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

person Chet    schedule 06.07.2017
comment
И, как сказал Росс, функция изменяет регистр Y. См. Также Полностью комментируемую разборку ПЗУ Commodore 64 - person Jester; 06.07.2017
comment
О верно. Так что мой ответ совершенно неверен. Я не уверен насчет ТАКОГО этикета, мне его удалить? - person Chet; 06.07.2017
comment
Это не совсем неправильно, он звонит rnd(0) и использует таймер. Просто не знаю, почему вы сказали, что его нельзя использовать. - person Jester; 06.07.2017
comment
Я что-то неправильно прочитал, поэтому подумал, что вам нужно скопировать таймер в определенную область памяти, прежде чем вы вызываете RND (0). Но из вашей ссылки ясно, что функция RND выполняет эту копию за вас. - person Chet; 06.07.2017
comment
Согласно документации, JSR $ E09A будет использовать значение в аккумуляторе для определения начального числа, и в этом случае 0 будет захватывать текущие значения в регистрах таймера. Таким образом, он ведет себя так же, как BASIC RND (0) - person Kenny; 06.07.2017
comment
@Kenny: вы хотите заполнить RNG только один раз, поэтому RND (0) предназначен только для первого вызова, после этого вы должны поддерживать текущее семя. Повторная инициализация начального числа все время пагубно сказывается на результатах, на самом деле, если вы вызываете его достаточно быстро, а регистры таймера все еще имеют то же значение, вы можете получить те же случайные значения обратно. (это общий ответ ГСЧ, объясняющий принцип, я не изучал случай C64, поэтому я понятия не имею, как вы должны правильно обрабатывать начальное значение после инициализации, является ли последнее случайное число также семенем для следующего или что-то еще) . - person Ped7g; 06.07.2017
comment
Хорошо, так что либо LDA $01, либо JSR $E0BE, чтобы вообще пропустить проверку знака. - person Chet; 06.07.2017
comment
Вы всегда можете отредактировать ответ, чтобы сделать его более полным, используя информацию в комментариях. - person Cody Gray; 06.07.2017
comment
@ Ped7g: Ты прав, я могу засеять перед входом в цикл, а затем JSR $ E0BE вырезать несколько циклов - person Kenny; 06.07.2017

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

person A.vH    schedule 13.07.2017
comment
даже без синхронизированных IRQ, поскольку циклы ЦП жестко привязаны к VIC, вы рискуете получить постоянно повторяющуюся последовательность таким образом ... Метод SID, показанный в самоответе, лучше;) - person ; 13.07.2017
comment
Это верно, если ваша программа работает без какого-либо взаимодействия с пользователем. метод Сида крадет один Голос для музыки. - person A.vH; 14.07.2017
comment
Да, в интерактивной программе это вряд ли случится, вы правы :) Просто говоря, что метод SID вообще не подвергает такой опасности. Конечно, для игры качество случайности в любом случае не имеет большого значения. - person ; 14.07.2017

Я нашел этот поток в поисках более общей процедуры 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. Аргументы начинаются с 251 252; заканчиваются на 253, 254. 16-битный результат найден в 14 $.

person Lovro    schedule 20.02.2020

Настоящие проблемы на 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 таймеров в этом случае случайны.

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

person Zibri    schedule 01.09.2020
comment
Я не уверен, что здесь проблема только в псевдослучайности. Маловероятно, что числа генерируются для криптографических целей. - person Ross Ridge; 02.09.2020
comment
к вашему сведению, ГПСЧ на основе Фибоначчи проходит все проверки на случайность ... проверьте это: github.com/Zibri/rand2 - person Zibri; 09.03.2021

Сейчас это очень поздно, но в зависимости от требований вы также можете использовать собственный ГПСЧ. Некоторые алгоритмы достаточно просты для реализации, в качестве примера я покажу 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
person Community    schedule 06.11.2017
comment
Я пробовал этот код, но похоже, что он не создает очень однородного распределения в младших битах (например, некоторые битовые шаблоны вообще не генерируются, некоторые генерируются часто). См. gist.github.com/nurpax/d0fee60a74633ae3027d41a7d1d02c19 - person Nurpax; 27.04.2018
comment
@Nurpax Я тоже, я обнаружил, что только один байт имеет хорошее распределение некоторое время назад ... Я совершенно забыл, что разместил его здесь. Не уверен, в чем причина, реализация верна в соответствии с приведенной статьей, но, возможно, я неправильно понял этот список возможных параметров - это может быть не идеальный выбор. Использование других параметров значительно увеличило бы время выполнения (сделав немного больше смещения), поэтому в конце я заменил его чрезвычайно простой 32-битной реализацией LFSR, использующей фиксированный XOR 0x04c11db7 при смещении немного влево. Дает гораздо лучшие значения. - person ; 27.04.2018
comment
@Nurpax, и, естественно, он быстрее ... Я мог бы переписать этот ответ, если вам интересно. - person ; 27.04.2018
comment
Мне было бы интересно ваше более простое решение. Я ищу простой в реализации ГПСЧ. Попытка не катить самостоятельно, так как это легко приведет к появлению трудно заметных ошибок. Заранее благодарим за код. :) - person Nurpax; 27.04.2018
comment
@Nurpax ну, я бы никогда не стал отказываться от своей идеи, но даже прокат моей собственной реализации прошел не так хорошо в этом случае, так что да: D Я обновлю этот ответ кодом, который я сейчас использую, это только небольшое вариант кода, который вы также найдете на codebase64. Простой LFSR, поэтому, вероятно, некоторые свойства хорошей случайности не идеальны (не используйте это для шифрования), но распределение кажется довольно приятным, я использую его в демонстрации для случайного движения воды и звезд. Я оставлю комментарий, как только отредактирую этот ответ! - person ; 27.04.2018
comment
Спасибо! Это входит в игру, поэтому я определенно не ищу криптовалюту (если бы я занимался таким бизнесом, я бы, вероятно, не запускал c64 :)). Но играбельность пострадает из-за плохой раздачи. Я уже реализовал решение на основе таблиц для другой потребности в игре, так как я хотел использовать перемешивание Фишера Йейтса. - person Nurpax; 27.04.2018
comment
У меня был смешанный опыт копирования и вставки кода из codebase64. Например, я извлек отсюда код soundfx для своей игры codebase64.org/doku.php ? id = base: sound_fx_player только для того, чтобы узнать, что в нем есть множество ошибок. :) Но я его раздвоил, исправил ошибки и даже сделал для него звуковой редактор. Кстати, не стесняйтесь заглядывать в / r / c64coding, если вас интересует небольшое сообщество разработчиков c64. - person Nurpax; 27.04.2018
comment
Я нашел этот LFSR maximintegrated.com/en/app-notes/ index.mvp / id / 4400 (код здесь, gist.github.com/nurpax/ d32529b017f71fd0e77c89a9b5a5e328). Похоже, что это простой ГСЧ для переноса на 6510. Дадим ему шанс. - person Nurpax; 29.04.2018
comment
@Nurpax, который я использовал в качестве базы, был этим LFSR из codebase64. Думаю, это нормально, просто придал ему какую-то форму. Извините, я все еще не обновил этот (теперь глупый) ответ. Я хочу сделать это идеально со второй попытки :) - person ; 29.04.2018
comment
Это включает формирование макроса для LFSR, а также некоторый образец кода заполнения из обычно хороших источников (например, растровая линия VIC и таймер CIA). - person ; 29.04.2018
comment
Я пошел дальше и повторно реализовал C LFSR в 6510. Как ни странно, я, по крайней мере, получил версию 6510, точно совпадающую с моими выводами C rng. gist.github.com/nurpax/. Все еще интересно увидеть ваше обновление, хотя :) - person Nurpax; 29.04.2018
comment
@Nurpax, это круто, спасибо за ссылку. Может быть, тогда ты хочешь добавить свой ответ? :) Но, AFAIK, классический PRNG в C - это не LFSR, а что-то вот так (моя реализация для MS-DOS) - person ; 29.04.2018
comment
Ага, я думаю, это LCG. Они полагаются на умножение, и поэтому LFSR кажутся гораздо лучшей идеей. :) И, наверное, хуже по случайности по сравнению с хорошим LFSR. Я, возможно, добавлю ответ, мне нужно сначала убедиться, что я правильно засеваю свою реализацию. - person Nurpax; 29.04.2018
comment
Я никогда не добавлял ответ на свой запрос. В итоге я запустил на нем пакет rng eval, и он не очень хорошо с ним справился. Вероятно потому, что мой был настроен на вывод на 8–16-битные выходные биты. - person Nurpax; 10.05.2018