Войти
ФлеймФорумЖелезо

А если ли у нас ностальгирующие по MOS 6502? (13 стр)

Страницы: 18 9 10 11 12 13
#180
8:20, 19 фев. 2020

В процессоре MOS 6502 как полагается были и аппаратные прерывания - маскируемое (IRQ) и немаскируемое (NMI), а так же была и инструкция программного прерывания BRK, которая срабатывала в целом как аппаратное маскируемое прерывание. По сравнению с i8086 или даже i8080/Z80, однако, векторов прерываний было только два и они были неизменные - располагались в конце адресного пространства.
Интересно, что код инструкции BRK был 0x00. Это несколько контрастирует с i8080/Z80 где 0x00 - это NOP (отсутствие операции). В MOS 6502 код операции NOP был 0xEA.
В принципе BRK можно использовать и для входа в некие функции ОС (более того инструкция работала так что как бы содержала IMM-байт сразу за своим кодом, т.е. по сути является двухбайтовой) и для отладки, но было еще одно забавное применение: внесение горячих правок в... ПЗУ.

Как это делалось - дело в том, что первые способные к прожигу в домашних условиях ПЗУ (PROM) были не перезаписываемые, а просто изначально содержали все замкнутые контакты в матрице контактов и это часто воспринималось как байт 0xFF. А после подачи большого напряжение на конкретный контакт он перегорает и там образуется нулевой бит.
Допустим мы хитрые создатели ПО в таких плашках памяти и делаем вот что: прожигаем программу сбрасывая некоторые биты в 0 в начале плашки, а в конце где то нередко остаётся свободное место занятое 0xFF. И вот допустим мы уже прожгли 10000 чипов ПЗУ и вдруг обнаружили ошибку в логике программы. Фиаско?
Еще нет. Мы можем пережечь все контакты в первой ошибочной инструкции и там образуется 0x00 - код BRK, и когда программа до него дойдёт - вызовется прерывание. Этим можно воспользоваться если нам не нужны IRQ (или мы хитро их обрабатываем) - можно прописать такой его обработчик который проанализирует адрес возврата и сделает прыжок в "фикс" действительно горячий как дымящаяся в булочке сосиска - фиксы так же допрожигаются в конец свободного места и если их несколько растут как стек сверху-вниз.
Довольно забавно. Техника померкла и забылась когда стали доступны и популярны EEPROM которые уже можно было перезаписывать как угодно.

Если задуматься, то на i8080/Z80 такое тоже можно было проворачивать ведь там была инструкция RST 38h с кодом 0xFF. Но тут надо было принять инвертированную по сравнению со схемой у 6502 схему считывания бит - да просто инвертором.


#181
(Правка: 11:59) 11:59, 18 июня 2020

Забавно, тут вчера на форумах на nesdev.com появился интересный топик - некто с ником JesperGravgaard заявил, что его разработка - компилятор KickC может теперь делать код для Famicom/NES/Денди (ранее оно было ориентировано на восьмибитную звезду северной Америки - Commodore 64).

+ Показать

На гитлабе тут: https://gitlab.com/camelot/kickc
Если возникнет желание скачать и нет регистрации на гитлабе и по ссылке там начнёт требовать регистрацию, то вот более прямая ссылка на скачку: https://gitlab.com/camelot/kickc/-/releases

KickC - это реально оптимизирующий компилятор для процессора MOS 6502 базированный на языке Си, но Си он овосьмибичивает максимально вплоть до несовместимости.
Как и GCC KickC компилирует не сразу в машинный код, а в текст программы на ассемблере причём в диалекте ассемблера для MOS 6502 который создан опять таки самим же автором проекта и называется KickAssembler. Тоже забавная штука, покажу её ниже.
Первое и самое главное - KickC не поддерживает рекурсию функций.
На MOS 6502 со стеком вообще всё плохо - регистр указателя стека восьмибитен и данные стека могут лежать только во второй 256-байтной странице памяти. Более того - система команд процессора устроена так, что по человечески этот стек полезен только для хранения адресов возврата процедур, ну и каких то текущих 8-битных данных push/pop аккумулятора. Вообще явно пушить/пОпить можно только аккумулятор и регистр флагов (восьмибитные). Поэтому ну прям такое...
В связи с этим в совместимом со стандартом Си компиляторе для этого же процессора - CC65 указатель "Си-стека" для параметров и локальных переменных это отдельный глобальный указатель и работа с этими вещами превращается в достаточно серьёзный геморрой с точки зрения производительности.
Так вот - отказавшись от реентерабельности KickC вылил все эти проблемы из тазика древних архитектур.
Почему так: во первых - параметры в функции стали просто глобальными переменными. Причём в синтаксисе KickAssembler они получают имена имя_процедуры.имя_параметра. То же самое с локальными переменными - никакие они уже не локальные, а как примерно как static в Си, но компилятор оптимизирующий, поэтому отслеживает потенциальные деревья вызовов и размещает параметры и переменные разных функций в одних и тех же ячейках памяти это не вызовет проблем.
И это уже настолько мощная потенция к оптимизации, что как выражаются англоязычные - это "game changer"!

Сразу раскрою все карты:

KickC не поддерживает по сравнению с Си:
- типы с плавающей точкой (float, double)
- умножение, деление и взятие остатка где оба параметра не константы (причём с константами работает только для степеней двойки)
- union
- массивы массивов
- указатели на функции возвращающие значение или имеющие параметры (т.е. чтобы на функция можно было взять указатель она должна быть void())
- рекурсию
- оператор alignof
- вариадические параметры ... (кроме встроенного printf)

KickC имеет некоторые атипичные ключевые слова иногда встречающиеся в других компиляторах Си (нестандарт):
- директива выравнивания, например: align($100)
- директива принудительного размещения переменной в регистре, например: register(X)
- встроенный ассемблер, например: asm { SEI CLD };
- функция main всегда имеет прототип void main()

Что своего из расширений появилось в KickC:
- циклы for вида for( char i: 0.. 10) { }
- компоновка слова из двух байт фигурными скобками, например: unsigned int w = { hi, lo };
- операторы ссылки на нижний и верхний байты слова - '>' и '<', например: char lo = <w; <w = 12;

Давно уже я привык тестировать 8-битные компиляторы "на вшивость" путём рассмотрения того что они порождают в машинном коде из следующей фукнции на Си:

void str_cpy( char *dst, char *src )
{
   while ( *dst++ = *src++ ) {};
};
Оно многое показывает. Например то, что все компиляторы под 8-битные платформы что я видел до сих пор (около 4-х штук на самом деле) - говно в плане оптимизации и проигрывает в скорости ручному написанию кода на ассемблере примерно на порядок, т.е. примерно "в 10 раз".

Для того чтобы понять какие этот компилятор предоставляет следует рассмотреть какой код тут будет идеальным если его написать на ассемблере вручную.
Насчёт идеальности тут конечно можно еще спорить, но от компилятора хотелось бы минимум следующего:
Во первых в 6502 не было 16-битных регистров кроме счётчика инструкций и косвенная адресация любого фрагмента памяти производилась через адреса хранимые в первых 256 байтах памяти (так называемый zero page). Поэтому адреса в процедуру следует передавать в известных "слотах" в zero page, тогда код по копированию будет выглядеть так:

; на входе:
; dst - адрес в zero page где лежит указатель на блок памяти куда копируем
; src - адрес в zero page где лежит указатель на блок памяти откуда копируем
.proc str_cpy
  ldy # 0 ; загрузим в индексный регистр Y ноль
loop:
  lda (src),y ; загрузим в аккумулятор байт из [src+Y]
  sta (dst),y ; сохраним его в [dst+y]
  bne cont    ; если он не был равен 0, то продолжаем
  rts         ; иначе - выходим из процедуры
cont:
  inc src     ; увеличиваем нижний байт src прямо в памяти
bne skip      ; если он не стал 0 - пропускаем 
  inc src+1   ; инкремент старшего байта src
skip:
  inc dst     ; увеличиваем нижний байт dst прямо в памяти
bne loop      ; если он не стал 0 - идём в начало цикла
  inc dst+1   ; иначе увеличиваем страший байт dst
  jmp loop    ; и возвращаемся на следующую итерацию
.endproc

Первое полевое испытание проведём на компиляторе для MOS 6502 - CC65:

char* dst1 = (char*)0x0400;
char* dst2 = (char*)0x0428;

void str_cpy( char *dst, char const *src ) {
   while ( *dst++ = *src++ ) {}
}

void main() {
    str_cpy(dst1, "hello");
    str_cpy(dst2, "world");
}
на нём компилируется в:
.proc   _str_cpy: near

.segment        "CODE"

        jsr     pushax
L0006:  ldy     #$03
        lda     (sp),y
        tax
        dey
        lda     (sp),y
        sta     regsave
        stx     regsave+1
        clc
        adc     #$01
        bcc     L0008
        inx
L0008:  jsr     staxysp
        lda     regsave
        ldx     regsave+1
        jsr     pushax
        ldy     #$03
        lda     (sp),y
        tax
        dey
        lda     (sp),y
        sta     regsave
        stx     regsave+1
        clc
        adc     #$01
        bcc     L000A
        inx
L000A:  jsr     staxysp
        ldy     #$00
        lda     (regsave),y
        jsr     staspidx
        tax
        bne     L0006
        jmp     incsp4
.endproc
Мягко говоря это тихий ужас.
CC65 весьма неплохо делает код для типовых инструкций, но обобщённые указатели выносят ему мозг и всю производительность под 0.
Но тут ему приходится часто вызывать свои внутренние процедуры рантайма (JSR) для обращения к стеку Си.
Плюс он перебрасывает указатели из стека в zero page чтобы адресация через указатель в zero page вообще стала возможна.
Плюс всё прочее создаёт просто замедление x10 если не x20 в реале по сравнению с оптимизированным руками выше варианте на чистом асме.

Что же родит компилятор KickC?
А вот что:

// str_cpy(byte* zp(4) dst, byte* zp(2) src)
str_cpy: {
    .label dst = 4
    .label src = 2
  __b1:
    // *dst++ = *src++
    ldy #0
    lda (src),y
    sta (dst),y
    // while ( *dst++ = *src++ )
    lda (dst),y
    inc.z dst
    bne !+
    inc.z dst+1
  !:
    inc.z src
    bne !+
    inc.z src+1
  !:
    cmp #0
    bne __b1
    // }
    rts
}
И это очень круто! Сразу надо заметить, что тут используется (естественно) синтаксис ассемблера KickAssembler и, например, когда адресация памяти ведется одним байтом адреса в zero page инструкции имеют префикс .z.
Кроме того когда идут переходы вида !+ - это переходы на одноимённую метку вперед или назад (если был бы минус).
Всё что можно об этом коде сказать - что итерации цикла захватывают лишнее для общей логики ldy # 0, т.к. регистр Y в цикле не меняется и что можно было бы не делать cmp (сравнение) с нулём если правильно аранжировать инструкции.
Но в любом случае - оптимизация очень качественная и даже то что я от этого кода видел на компиляторах для Z80 или его потомке - Z280 - выглядит по сравнению с этим просто чудовищно.

#182
(Правка: 13:20) 13:20, 18 июня 2020

=A=L=X=
> Оно многое показывает. Например то, что все компиляторы под 8-битные платформы
> что я видел до сих пор (около 4-х штук на самом деле) - говно в плане
> оптимизации и проигрывает в скорости ручному написанию кода на ассемблере
> примерно на порядок, т.е. примерно "в 10 раз".

Я бы не полагался на сырые, любительские компиляторы. Моя практика показывает, что при написании серьёзных проектов словить глюк из-за компилятора неизбежно!  Тут профессиональные компиляторы часто дефекты имеют, что говорить о любительских тогда.

Расово верный путь - использовать GCC (не знаю как с 6502, но для М68К есть). Нет ограничений в синтаксисе и надёжен.  Стартап есть у Стефана.

Копирование память-память лучше писать ассемблерными вставками.  Си использовать только для вызова функций верхнего уровня  и для общей логики работы программы.

#183
13:22, 18 июня 2020

=A=L=X=
> Что же родит компилятор KickC?
> А вот что:

И это удивительно!  Потому что на максимальной оптимизации этот код должен быть просто уничтожен!  Вы просто гоняете байты туда-обратно, даже не выводите их на дисплей. Данные не волатильны.  Этот код хороший компилятор должен был просто вырезать!

#184
(Правка: 14:03) 14:00, 18 июня 2020

Gradius
> Я бы не полагался на сырые, любительские компиляторы.

Угу. Я бы тоже, НООООО.... (огромное и большое но)
https://gamedev.ru/flame/forum/?id=240745&page=5&m=5172157#m67
Можно скачать прям сейчас экстремально оптимизирующий компилятор Си под платформу микроконтроллерную e280 от фирмы Zilog той самой что создала процессор Z80 в 80-е годы и сохраняет с ней совместимость до сих пор...
И насладится по полной.
Прям по полной - полным гавнищем оптимизации, их "высоко оптимизированный компилятор" рожает стыд и стыдобищу.
По ссылке всё показано.
И это всё происходит не в 80е, а прямо сейчас. Сегодня. В микроконтроллерах. Рядом с нами. Кродётся. МЫШЪ.

Сегодня и рядом с нами такое - такое говнище.
Поэтому компилятор KickC реально - глоток свежего воздуха.
Он реально не заявляет что может оптимизировать код под 8 бит, а оптимизирует его.

#185
14:28, 18 июня 2020

=A=L=X=
> Поэтому компилятор KickC реально - глоток свежего воздуха.
> Он реально не заявляет что может оптимизировать код под 8 бит, а оптимизирует
> его.

Я не умаляю его  достоинств.  Но он должен пройти закалку.  Попробуйте написать хотя бы 20 000 строк на Си и скомпилировать этим компилятором.  Бинарник будет 100% рабочим?

#186
9:09, 22 июня 2020

Gradius
> Бинарник будет 100% рабочим?

Он сам пишет что версия 0.8 и поэтому могут быть нестабильности. Естественно если они встретятся, то желательно писать багрепорты.
Я сам не буду на нём писать потому что хочу ощутить весь вкус ассемблера. Но выглядит реально недурно - все компиляторы что я видел до сих пор на восьми битах оптимизируют хреновенько. Во многом это вызвано тем что на 8 битах реально переменные в стеке - это геморрой и код быстро превращается в перекладывания туда-сюда байт. Тот же CC65 несмотря на совместимость со стандартом Си содержит в документации трюки как оптимизировать код и это отказ от параметров в стеке и тому подобное. Но Си не очень хорош даже когда используем для переменных ключевое слово static, т.к. его смысл мешает повторно использовать одну и ту же память.
KickC же отказавшись от локальных переменных очень хорошо заруливает эти проблемы. Это примерно то какие были компиляторы в 60-е годы - тот же фортран ключевое слово REENTRANT (или RECURSIVE) получил много лет позже того как был создан.

#187
(Правка: 11:53) 11:12, 22 июня 2020

cc64 is a small-C compiler, written in Forth, targeting the 6502 CPU and hosted on the C64. I wrote it during my university years; the majority of the code was written 1989-1991.

https://github.com/pzembrod/cc64

P.S. Тоже, много чего не поддерживает. :)

Здесь ещё некоторое количество С,  http://www.6502.org/tools/lang/

А, некоторые, ждут коллапса и пишут ОС под 8-ми битные Z80
https://collapseos.org/why.html

#188
10:23, 30 июня 2020
Robot Game is a project to compare the performance of several programming languages for the 65C02 processor. The four languages being compared are:

    Traditional assembly
    Assembly optimized with my 65C02 Assembly Optimizer
    C compiled with the CC65 C compiler
    Forth compiled with a modified version of Tali Forth 2

Each version of the game runs on a JavaScript-based 65C02 simulator in the browser:


Robot Game
#189
18:35, 3 июля 2020

KPG
> А, некоторые, ждут коллапса и пишут ОС под 8-ми битные Z80

Надо срочно ламповый аналог Z80 делать. :) Так будет гарантия, что процессор будет маловосприимчив к электро-магнитным атакам.

Страницы: 18 9 10 11 12 13
ФлеймФорумЖелезо