1 Отредактировано Voldemar0 (18-02-2018 15:13)

Тема: Пакет cc65

Привет!

Предлагаю тут вести обсуждение пакета cc65.

Чем полезен:

Готовый мультиплатформенный кросс-транслятор для mos6502.
Компиляет с C, макро-ассемблера, ведёт объектные файлы, библиотеки и даже имеет дизассемблер.

Читать много полезного тут:
http://www.cc65.org/doc/

Предполагаю, что добавлением простого препроцессора/обёртки можно заставить его компилять проги, предназначенные для ДОК и получать на выходе FIL-файлы либо сразу закидывать результат в образ.

Пример компиляции простой проги:

        .setcpu "6502"

pCOUT   := $36

        ldx #0
loop:   lda msg, x
        beq fin
        jsr cout
        inx
        bne loop

fin:    ;rts
        jmp ($fffc)     ; вектор сброса

msg:    .asciiz "Hello world"

cout:   jmp (pCOUT)

Этот текст пишем в PC-текстовом редакторе.
Затем компилируем (пакет cc65 где-то в путях либо в текущем каталоге либо указываем к нему полный путь):

ca65 -l -v -v -v hw.asm
ld65 -o hw.bin -t none -S '$1800' hw.o

На выходе получаем hw.bin
Я согнал эти команды в командный файл, чтобы не набирать каждый раз.
Закидываем сюда же загрузочный образ Агата с бейсиком-60 (для ИКП надо будет больше действий), запускаем dos33c2 и заходим в образ.
Нажимаем F9, выбираем "импорт файла"/"Двоичный B-файл, без перекодировки"
и набираем имя файла "hw.bin".
Файл импортируется, для удобства его можно переименовать (нажмите букву "r") в "HW".
Затем загружаемся с этого образа в эмуляторе (ну или на реальном Агате) и набираем "BRUN HW":

]BRUN HWhелло ворлд
]_       

Кодировка неправильная, это надо решать. Есть разные варианты, cc65 поддерживает внутренние виды перекодировок, но агата там, понятно, нет.
Но в остальном код вполне исправный.

2

Re: Пакет cc65

Правильная тема! Вот только на днях на этот проект наткнулся.
Меня, правда, больше интересует именно C-шная составляющая. Потому что 30-40 Кб исходников на асме еще как-то можно пережить, но дальше - закопаешься с головой. А на C код намного чище и понятнее.

3 Отредактировано Voldemar0 (19-02-2018 06:04)

Re: Пакет cc65

Alex начал этой темой заниматься, но, говорит- код очень тяжелый генеряет. Я попробовал - подтверждаю.
Там создаётся собственный стек, сложение, даже на 8 бит, через кучу операций проходит.

Есть Aztec-C, портированный на агат, если есть интерес - покопай его, опишешь.

4 Отредактировано Voldemar0 (24-02-2018 20:49)

Re: Пакет cc65

Прочитал полностью доку на ca65, потестил для режима сборки библиотека+программа - рулез.
Обёртку вокруг этого ассемблера делать ни к чему - много благостного будет потеряно. Надо только допилить импортёр готового бинарника в образ (уже есть работающие наброски).
С перекодировкой строк разобрался: у него это хотя и немного странно (нужно на каждый символ написать отдельную команду), но поддерживается внутри.

Мелкие недостатки:
- Очень странно, что символы > < (взятие младшего/старшего байта от 16-битного значения) используются наоборот, чем в ДОК.
- Также точки в именах переменных не допускаются (можно разрешить только точку в начале имени).
- И не разрешены комментарии, начинающиеся со "*".

Есть несколько нейтральных отличий. В основном, это замена ключевых слов DFB, ASC, DW, EQU и прочих на свои аналоги. EQU разделён на два варианта: "=" (для обычных значений) и ":=" (для адресов/указателей). Как я понял, отличие только в дампе информации для какого нибудь внешнего отладчика.

Прочие отличия положительны:
1) Есть поддержка сегментов, есть готовый сегмент нулевой страницы. Т.е. можно тупо забивать переменные в Segment "zeropage" (в разных частях проги и библиотек) и линкер затем разложит их по порядку. Пока не разобрался, как сделать сегмент совсем сквозным, но это надо вчитаться теперь в описание линкера.

2) При экспорте/импорте переменных чётко отслеживается размер - один или два байта. Линкер всё проверяет. Т.е. если я пишу, что ожидаю здесь переменную ZP, то и связанные с ней команды будут собираться с короткой адресацией, даже если фактическое значение ещё неизвестно.

3) Есть локальные метки, начинаются с "@":

IOS_PRINT_PTR:
 STX IOS_PTR+0
 STA IOS_PTR+1
 LDY #0

@loop:
 LDA (IOS_PTR),Y
 BEQ @fin
 JSR IOS_COUT
 INY
 BNE @loop

@fin:
 JSR IOS_CR
 SEC
 RTS

Их можно использовать многократно, главное, чтобы между ними стояли нормальные метки (это граница локальности).

4) Поддержка комментариев /* в стиле C */

5) Нет команды DCI , зато есть ASCIIZ. Для нынешней ситуации с повальным увлечением полными знакогенераторами так даже лучше.

6) Есть встроенная переменная .time - можно вкомпиливать в прогу дату сборки (и выводить потом где нибудь).

 year = .time / 31556926 + 1970
 year_m = .time .mod 31556926
 month = year_m / 2629743 + 1
 month_m = year_m .mod 2629743
 day = month_m / 86400 + 1
 
 jsr IOS_PRINT
 .asciiz .sprintf("Сборка программы: %02d.%02d.%04d", day, month, year)

7) Мощная арифметика констант. Не инженерный калькулятор, но хотя бы умножение/деление есть. Все переменные 32-битные. Старшие 16 разрядов могут использоваться, например, как номер банка памяти.

Там ещё полно плюшек, но, IMHO, для агатовских задач они излишни.

Ну и просто быстро это всё. Мои примеры собираются от исходника до bin меньше чем за секунду.

5 Отредактировано Voldemar0 (26-02-2018 06:03)

Re: Пакет cc65

Сегодня мне удалось добиться от cc65 совершенно идеальной работы.
Я получил возможность собирать из исходных текстов сразу FIL-контейнер с файлом,
причем для этого использовались только штатные утилиты cc65: ca65 и ld65.

Но чтобы объяснить механизм такого синтеза, следует рассказать об очень мощной вещи, реализованной в современных ассемблерах, в т.ч. в ca65. Речь пойдет о Сегментах.

Сегмент (иногда ещё называют "Секция") - это некий регион адресного пространства, имеющий собственные аттрибуты: счётчик адреса, особенности размещения в ОЗУ или ПЗУ, размеры (текущие и максимальные) и разное другое.

Сегменты существовали даже в ДОК: если помните команды DSECT / DEND. Они создавали некую обособленную область, которая никуда не записывалась и нужна была только для расчёта смещений меток. Использовалась, как правило, так:

 DSECT
 ORG $E8
X1 DS 1
X2 DS 4
 DEND

При этом метка X1 получала значение $E8, а X2 - $E9. Смысл этого состоял в том, что появлялась возможность автоматически распределять память для переменных в нулевой странице. При объявлении очередного региона DSECT / DEND счётчик начинал считать вновь от нуля (или от ORG, если она присутствовала).

Т.е. работа велась как бы в двух сегментах: в DSECT / DEND и в основном (который записывался в файл и/или копировался в ОЗУ).

Механизм DSECT / DEND использовался, наверное, 2/3 программистов Агата. Остальные даже его игнорировали и писали всё в фикс-стиле:

X1 EQU $E8
X2 EQU $E9 ..$EC

Но реальная большая прога запросто имела 3-4, а то и больше неявных сегментов (т.е. описанных только в комментариях или мозгах автора). Считаем:

1) Переменные в нулевой странице;
2) Переменные в основной памяти (если в нулевой странице места не хватает), массивы, значения которых не важны до запуска программы (т.е. программа заполняет их по мере работы);
3) То же переменные и массивы, но значения которых заданы до начала работы программы;
4) Те же переменные и массивы, но значения которых вообще недопустимо изменять (выводимый на экран текст, например);
5) Код в основной памяти (хотя бы тот, который части программы - то есть те же сегменты - раскидывает по разным регионам ОЗУ);
6) А может быть и код в дополнительной памяти (ЭмПЗУ);

Теперь представим себе, что у нас есть исходники двух библиотек: L1 и L2. Каждая из них использует ячейки нулевой страницы: например, переменные VAR1 и VAR2. Как распределить между ними память? Есть пара путей:

1) Пишем в начале основной программы VAR1 EQU $E0, VAR2 EQU $E1 - т.е. каждой переменной назначем фиксированные адреса переменных.
Недостаток способа в том, что нам нужно вычленить все переменные из библиотеки L1 и L2 и разместить их где-то в начале основной программы. Я сам часто пользовался этим способом. Это не было самой сложной или долгой операцией, но это всё равно требует времени. К тому же нужно хорошо знать библиотеки, знать, какие у них есть требования к размещению переменных. Зато в заголовке программы наглядно видны все используемые рессурсы.

2) Пишем в начале каждой библиотеки всё те же статические EQU и надеемся, что они не пересекутся.
Этот путь применялся практически всеми разработчиками широко используемых библиотек: RWTS, IOSub, ... да тех же Бейсика и ДОС. EQU можно заменить на DSECT / DEND, суть не изменится. Библиотеку можно будет подключать к основной программе на уровне исходника или бинаря. Ну, правда, есть проблема: если подключить как исходник, то получается нечто вроде:

 LDA VAR1
 CHN L1

VAR1 EQU $28
 LDA (VAR1),Y

Синтаксически почти всё верно, но ассемблер, увидев LDA VAR1 предположит, что переменная "длиная". Встретив VAR1 EQU $28 присвоит ей значение $0028, а на LDA (VAR1),Y выдаст ошибку "требуется короткая переменная" (текст сообщения будет менее понятный, но суть не меняется).

Чтобы избежать этой ошибки, можно написать LDA (>VAR1),Y, но я не видел, чтобы этот приём кем-то использовался на Агате. Чаще подключение выполнялось на уровне бинарного файла. Но это тоже плохо:
1) Нужно примерно знать свободный регион ОЗУ для библиотеки и компилировать её, указав этот адрес.
2) В основной программе нужно повторить все описания меток. Замечу, что ДОК не имел инструкции .include.

Этих проблем вроде бы не много, но, фактически, они приводили к тому, что любая - крупная ли, мелкая - библиотеки всякий раз дорабатывались напильником под конкретную прогу.

Под библиотекой не обязательно понимать нечто логически обособленное.
Представьте себе обычный агатовский ДОС 3.3. Сколько в нём сегментов?

1) Таки нулевая страница, размазанная между ячейками IOSub и сисмона.
2) Автолоадер первого этапа: код сектора 0/0, загружающийся ПЗУшным лоадером при старте машины.
3) RWTS (драйвер дисковода), загружающийся автолоадером первого этапа.
4) Автолоадер второго этапа: код в нескольких секторах от 0/1, загружающийся автолоадером первого этапа.
5) Остальная кодовая тушка, которую с секторов 2/4, вниз по номерам, грузит автолоадер второго этапа.
6) Регион массивов десктрипторов файлов (где-то от адреса $9600). Не загружается, инициализируется при старте системы.
7) Регион ключевых векторов, размещается где-то на адресах $3C0..$3FF, инициализируется при старте системы.

Как скомпилировать такую программенцию ? Только по частям. Примерно прикидывая размещение всех частей. Как собрать в кучу? Как правило, на агате использовали командный файл такого вида:

[LOAD MAIN,1000
[LOAD SPRITE,2800
[LOAD FONT,3000
[SAVE PROGA,1000,2800

Это в совсем простом случае.

Развитый механизм сегментов как раз таки призван решать этот вопрос.

Возвращаемся к библиотекам L1 и L2. Используя ca65 можно сделать так:

Текст L1:

.zeropage
var1 .res 1
.code
lda var1

Текст L2:

.zeropage
var2 .res 1
.code
lda var2

В чём смысл: сегмент нулевой страницы zeropage заполняется самостоятельно, отдельно от сегмента кода code. В результате мы получим размещение var2 сразу за var1, а lda var2 сразу за lda var1. Если мы объявим, что zeropage начинается с адреса $A7, а code с адреса $2800, то var1 = $A7, var2 = $A8, а lda var1 размещена по адресу $2800, lda var2 по адресу $2802.

Тексты библиотек, таким образом, никак не связаны, но память выделяется аккуратно, байт за байтом, причем без нашего участия. Более того, это будет происходить как в случае объединения библиотек на уровне текстов (например, используя .include или просто сделав copy-paste), так и при сборке линкером уже скомпилированных объектных файлов.

Идём дальше. Вы можете объявить в программе несколько кодовых сегментов:

.loader_code, адрес загрузки = $1000
 <тут размещён конфигуратор программы>
.main_code, адрес запуска = $d000
 <тут основная программа>

Причём линкер создаёт описание фактически получившихся сегментов, которые вы можете использовать внутри программы. Например, вы можете в конфигураторе программы разместить процедуру копирования main_code в ЭмПЗУ. Линкер в этом случае настроит loader_code для работы по адресу $1000, а main_code для работы по адресу $d000 и затем разместит их подряд в один файл. Причем в loader_code будет передана информация как об адресе загрузки, так и об адресе запуска main_code и, конечно, фактическом размере main_code.

Это я немного упрощённо показал структуру файла HELLO, в котором живёт Basic-60.
Только вот в Basic-60 всё это собиралось и подгонялось вручную.

Стандартные названия сегментов (они стары как весь компьютерный мир и до сих пор применяются) такие:

.code (или чаще .text) - основная часть кода программы;
.data - данные, которые должны быть определены до запуска программы (что-то вроде static int a=10);
.rodata - то же, что .data, но запрещена модификация данных;
.bss - данные, которые могут быть неопределены (или обнулены) до запуска программы;
.eeprom - данные, хранимые в энергонезависимой памяти (часто встречается у микроконтроллеров).

Логично предположить, что раз данные в сегменте bss - просто нули, в файл его записывать не нужно. С другой стороны - память всё таки нужно выделить. А если по каким-то причинам нам нужно несколько сегментов кода или данных? Мы плавно подошли к вопросу о возникновении формата EXE-файлов. Если помните, в MS-DOS были популярны файлы COM (практически аналог агатовских B- и К- типов). Отличие EXE- файлов от COM состоит как раз таки именно в том, что EXE как бы не до конца слинкован: т.е. в нём есть отдельные секции (сегменты) кода, данных и чего угодно ещё.
И операционка, загружая такой файл, выделяет память для каждого сегмента в соответствии с его описанием в EXE. И это одинакого относится как к MS-DOS, так и к Windows (там структура EXE была усложнена, в частности добавлены ссылки на требуемые библиотеки, тип операционной системы, для которой предназначена прога и т.д.).

У Агата в операционках не предусмотрена линковка при загрузке, поэтому собирать сегменты в кучу - задача (в случае использования cc65) линкера ld65. И так как параметров-правил сборки немало, он использует отдельный конфиг-файл для их хранения. Например, я создал файл конфига для сборки программ, запускающихся в среде ДОК.

А как насчёт заголовка FIL-контейнера ?
В конфиге линкера описываем сегмент размером $28 байт с именем HEADER и правило интеграции в программу: "перед другими сегментами". А исходник сегмента описываем в include-файле, который подключаем к основной программе. Так как описание идёт в отдельном сегменте, оно никак не влияет на код программы и на её данные. Но так, как заголовок собирается наравне с другими сегментами, при его генерации линкер может подставить фактические значения точки загрузки и длины кода (а также данных, если объявлен сегменты .data и .rodata).

На сегодня всё. Дальше я планирую сделать несколько типовых конфигураций линкера (по крайней мере для Бейсик-60 и ИКП-Бейсик) и HEADER-файлы для синтаза B- FIL. Наверное, можно надрессировать ld65 и на генерацию S- файлов BTK или плагинов Basic Master.


Ставьте лайки, подписывайтесь на канал :)

6 Отредактировано Voldemar0 (04-03-2018 14:13)

Re: Пакет cc65

Начал готовить демку для cc65 под винду: никогда такого не было и вот опять нашелся баг :(

Суть в том, что когда-то кому-то пришло в голову сделать в языке C символьный тип знаковым. Через это символ с кодом $FF удачно преобразуется в -1, строго равную специальному коду EOF - конец файла.
Эта проблема есть как в source-base unix-версии, так и в официальном бинаре под винду.

Т.е. если ca65 встречает в тексте программы (хотя бы даже в комментарии) символ "я" - обработка текста прерывается на этом месте или не идёт дальше данной строки.

Под юнихами, с их православной КОИ8-r это ерунда, потому что $FF - очень сильно нужный заглавный твёрдый знак ("Ъ"). А вот под виндой это маленькая "я". Заметно полезная.

Лечение очень простое: в исходнике ca65 (cc65 этому не подвержен) нужно поправить строку.

cc65-2.13.2/src/ca65/scanner.c

Тут описание структуры struct InputFile. Меняем в ней поле

unsigned char   Line[256];          /* The current input line */

и всё начинает работать нормально.


Если вы владеете искусством сборки C-шных прог под винду, попробуйте пересобрать ca65 с этой правкой или попробуйте разобраться, кто сейчас является владельцем официального репозитория cc65 и опишите ему этот баг.

7 Отредактировано Voldemar0 (08-03-2018 17:48)

Re: Пакет cc65

Сделал пример проги из темы про линкер, но теперь перенесённой на ca65.

Распаковываем аттач куда нибудь, например, c:\exm-c65\.
Создаём там же каталог c:\exm-c65\cc65.
Пакет самого cc65 качаем с их сайта: http://www.cc65.org/
Распаковываем его в c:\exm-c65\cc65.

hw.bat - вызов компилятора и редактора связей для сборки примера. Запускаем его.
Смотрим на сообщения об ошибках. Думаем, исправляем.

Затем, используя dos33c2, закидываем получившийся hw.fil в ikp_7.dsk

Запускаем образ в эмуляторе.

Выбираем в меню ИКП "Ассемблер"

После загрузки и появления цифры "1" нажимаем ESC, выбираем "выйти в отладчик", там пишем:

[RUN HELLOW

Наслаждаемся.

Файлы в аттаче:

hw.asm - пример простой проги, классом чуть выше чем "Hello World!"
IOSUB.asm - пример простой библиотеки, которая используется программой hw.asm и компилируется отдельно.

agatfeat.inc - Рекомендуется для включения во все исходники. Немного подстраивает ca65 под ДОК-style.

agatfilK.inc
agatfilB.inc - Синтезирует K или B- заголовки для FIL-файлов.
    Без одного из этих файлов получится обычный бинарь (.bin).
    Даже если сделаете расширение .FIL, dos33c2 будет показывать в таком файле мусор.

chars_koi.inc
chars_win.inc - Перекодирует все строковые константы в агатовскую кодировку из вашей (cp1251 или КОИ8-R).
    Этот файл пристёгивается к вашей программе через agatfeat.inc.

agat-B-IKP.cfg
agat-B-basic60.cfg
agat-K-DOK.cfg - Любой из этих файлов указывается линкеру (ld65) в параметре -C
    и описывает рекомендуемую карту памяти.


!! Важно: Пример IOSUB.asm / HW.asm для некоторого упрощения пилился только под ДОК,
так что собирать его под другие ОС нет смысла без доработки !!


*.o - это объектные файлы транслятора, бинарники, смотреть ни к чему.
*.map - карта памяти, генерируется редактором связей, текст, смотреть интересно.
*.lst - листинг, представляет собой текст программы и комментарии транслятора. Смотреть крайне полезно. Позволяет понять, как транслятор видит ваши исходники.


Неочевидные из примера, но важные особенности:

   - знаки > и < используются наоборот: > - старший байт, < - младший байт
   - точки в именах переменных не допускаются (можно разрешить только точку в начале имени)
   - не разрешены комментарии, начинающиеся со "*"
   - допускаются HEX-числа как с префиксом $ так и с суффиксом h
   - в исходнике, даже в комментариях, недопустим символ с кодом $FF - транслятор считает его концом файла

Post's attachments

Attachment icon cc65-complex-example.zip 112.45 kb, 111 downloads since 2018-03-08 

8

Re: Пакет cc65

Продолжу тему...
Некоторое время назад, я решил написать программу для агата для работы с платой расширения, мучения с бейсиком и inline ассемблером ни к чему не привели. Программа получалась слишком тяжелой, медленной и трудноотлаживаемой.
Поэтому я решил попробовать переписать ее на C используя cc65. И буквально за один день получил работающий прототип, а за второй привел все к задуманному результату.
Но у такого решения есть ряд недостатков:

  • Программа запускается только в режиме эмуляции Apple2 на девятке, с 140к дискеты (эмулятора). Нужны два контроллера дисковода и два дисковода.

  • Недоступны более продвинутые экранные режимы Агат

  • cc65 при работе под DOS 3.3 не поддерживает работу с файлами и прерывания. Под Prodos поддерживает, но prodos не работает в режиме эмуляции apple2 на Агат-9, хотя по тех. требованиям вродебы должен. Во всяком случае версии до 2.хх и неофициальная 2.4.2

В режим эпл захожу через меню ИКП-9 и выбор "Apple Soft". Что вообще происходит в этот момент? Управление передается контроллеру 140к, а он работает также как в Apple2 или загружается какойто адаптированный Apple DOS и он ищет файл HELLO на дискете? Причем поведение немного отличается в зависимости от того была ли вставлена в дисковод 140к дискета в момент выбора "Apple Soft" или нет. При этом если дискеты нет, то команда PR#6 не работает на физическом агате, но работает в эмуляторе.
Может быть есть какой то другой способ работы с AppleDOS?

Еще вопрос, можно ли запустить Apple программу с дисковода 840к, предварительно переведя Агат в режим А2? Хотя если программа использует вызовы DOS 3.3 то без него она работать не будет?

P.S. Пожалуйста напишите пример простой программы на ассемблере, переводящей Агат9 в режим эпл и выводящей строку на экран через подпрограмму COUT монитора, а то я уже совсем запутался.
P.P.S. На семерке через ячейку программы, скомпилированные cc65, тоже запускаются и работают.

9 Отредактировано Voldemar0 (06-08-2019 19:29)

Re: Пакет cc65

> cc65 при работе под DOS 3.3 не поддерживает работу с файлами и прерывания. Под Prodos поддерживает, но prodos не работает в режиме эмуляции apple2 на Агат-9, хотя по тех. требованиям вродебы должен. Во всяком случае версии до 2.хх и неофициальная 2.4.2

А что делает ?
Мне помнится, у продос была какая-то заморочка, что-то с безумной оптимизацией связанное.
Может ошибаюсь.
Суть в том, что после загрузки несколько первых секторов, она что-то копирует из ПЗУшного драйвера
загрузки (который на контроллере флопика)  в ОЗУ и это получается как бы её собственный кусок.
Зачем так - не знаю, наверное чтобы не грузить лишние байт 100, но такой трюк мне попадался где-то.

А не работает дальше, потому что кусок в агатовской 140ке вроде бы отличается от эпловской
где-то в последих 4 байтах.

И ещё вот нашел тут отличие ( http://ac/Reading/fl140k_.shtml ):
"Помимо описанных здесь и далее, Disk ][ имеет ещё одно отличие в ПЗУ: для определения номера своего слота ПЗУ-загрузчик обращается к пустой (RTS) процедуре в системном мониторе и затем изучает стек, чтобы выяснить собственный адрес. С этой целью агатовские загрузчики использовали процедуру по адресу ¤FFCB, в то время как Disk ][ - ¤FF58."

Не помню - влияет это или нет.

Соответственно, можно попробовать подкинуть эмулятору ПЗУ Disk][ оригинального эпла, а потом уже грузить эплософт.

-=-=

Что происходит при загрузке 9-ки - это в доках на машину описывается. Грузится всё, что должно лежать
в ПЗУ эпла с дискеты-840ки, и дальше переключается триггер "признак архитектуры" или "признак машины".
Какой-то из них отвечает за архитектуру контроллера памяти, другой - за архитектуру дисплейного контроллера. Переключение контроллера памяти необратимо (до выключения питания), происходит, кажись, при записи любого значения по адресу C0Fx.

-=-=

Есть загрузчики ,которые могут изобразить из девятки эпл с поддержкой 840-ки вместо 140ки.
Вроде от Романа Бадера такие были, но может быть и от Ильи Волкова. Опять же - если Игорь выкладывал их на сайт. На их основе строились игротеки для девятки, забитые эпловскими играми.

-=-=

Можешь попробовать ещё Aztec-C - где-то на сайте есть или должен быть (если Игорь выложил) в версии для агата. Там и IDE сразу есть.

-=-=

> cc65 при работе под DOS 3.3 не поддерживает работу с файлами и прерывания.

Но поддерживает под ProDOS ? Или где ?

>  напишите пример простой программы на ассемблере, переводящей Агат9 в режим эпл и выводящей строку на экран через подпрограмму COUT монитора,

Простой программы нет: нужно сперва заполнить память хотя бы сисмоном (предварительно откуда нибудь его прочитав), потом переключить режим контроллеров памяти  и ДК.

Сложная программа (несколько десятков блоков) была в комплектах The BEST , кажется, так и называлась - Apple2. Опять  же - если Игорь выкладывал это. Но, по сути, это та же переводилка, которая работает из ИКП, я так думаю.

10

Re: Пакет cc65

Voldemar0 пишет:

Есть загрузчики ,которые могут изобразить из девятки эпл с поддержкой 840-ки вместо 140ки.
Вроде от Романа Бадера такие были, но может быть и от Ильи Волкова. Опять же - если Игорь выкладывал их на сайт. На их основе строились игротеки для девятки, забитые эпловскими играми.

Вот это интересно. Даже как-то и не подумал об этом.

Voldemar0 пишет:

Соответственно, можно попробовать подкинуть эмулятору ПЗУ Disk][ оригинального эпла, а потом уже грузить эплософт.

Работает ;), успешно удалось загрузить Продос  2.4.2.
Но как я понимаю на реальной машине такой трюк проделать будет сложнее. Можно обойтись патчем Эпл монитора загружаемого с дискеты?

Voldemar0 пишет:

Что происходит при загрузке 9-ки - это в доках на машину описывается. Грузится всё, что должно лежать
в ПЗУ эпла с дискеты-840ки, и дальше переключается триггер "признак архитектуры" или "признак машины".

Да, я понял что надо сначала загрузить в память Агата содержимое ПЗУ Эпл 2, и после переключения режима  монитор Эпл2 будет на своем месте.

Voldemar0 пишет:

Можешь попробовать ещё Aztec-C - где-то на сайте есть или должен быть (если Игорь выложил) в версии для агата. Там и IDE сразу есть.

Кросс компилятор был бы предпочтительнее, потому, что не надо писать программу на самом агате. Плюс cc65 развивается и документирован а этот Aztec уже нет.

Voldemar0 пишет:

> cc65 при работе под DOS 3.3 не поддерживает работу с файлами и прерывания.

Но поддерживает под ProDOS ? Или где ?

Функции типа fopen/fread не работают если запускать программу под DOS 3.3. Под prodos работает.

Спасибо за ответы.

11

Re: Пакет cc65

> Кросс компилятор был бы предпочтительнее, потому, что не надо писать программу на самом агате. Плюс cc65 развивается и документирован а этот Aztec уже нет.
http://www.6502.org/tools/lang/
вот, кстати, тут ещё повод для обсуждения
много поводов.

> Но как я понимаю на реальной машине такой трюк проделать будет сложнее. Можно обойтись патчем Эпл монитора загружаемого с дискеты?

проще саму продос пропатчить.


> Функции типа fopen/fread не работают если запускать программу под DOS 3.3. Под prodos работает.
Да, я читал о таком. Почему-то недопилили, хотя в этом досе есть такие функции.
Может просто считали, что проще под prodos работать - она же того же железа требует, а функций всяких больше.

12

Re: Пакет cc65

http://agatcomp.ru/Gamez/Loader.shtml

13 Отредактировано Voldemar0 (07-08-2019 06:27)

Re: Пакет cc65

У меня че-то какая-то с утра философская мысль возникла:
чем характерен C ? он, наверное больше чем любой другой язык, любит указатели.
Надо, например, две строки сравнить: strncmp("образец", char * string, sizeof(string)).
Это компилируется в что-то вроде strncmp(ptr1, ptr2, int);
Куда пошли указатели ptr1, ptr2 ? Если компилятор оптимизирует и условия позволяют - через регистры, если нет - через стек.
Ок, не вопрос: i386: eax = ptr1, ebx = ptr2. Или -4(esp), -8(esp).
Всё просто и в рамках одной-двух команд.

Если мы хотим изобразить сравнение строки с образцом на ассемблере 6502, то тоже можно сделать довольно
симпатично:

; На входе A/X указывает на входную строку, а внутри процедуры образец
test:
 stx ptr+0
 sta ptr+1
 ldy #0
 ldx #0
loop:
 lda sample, X
 beq match
 cmp (ptr),Y
 bne not_match
 inx
 iny
 jmp loop
sample: .byte "образец", 0

Но как написать честный прототип strncmp на ассемблере ?
Т.е. два аргумента и размер строки ?
Как их хотя бы передать в функцию ?

Регистры ? Их просто не хватит для передачи двух указателей.
Zeropage ? Ну если не используется реентерабельность...
Стек ? Убиться проще: у нас нет индексной выборки по SP, да ещё и со смещением.

Дальше.
Получили два указателя.
lda (ptr1), Y
cmp (ptr2), Y
Хорошо если нас устраивает совпадение Y. А если нам нужно, например, сделать реверс строки - новую строку от последнего символа к первому ?
ldy #0
lda (ptr1),y
ptr1++ - --- ну где-то три команды минимум
sta (ptr2),y
ptr2-- - --- ещё три
.....

Т.е. команда вида lda XXXX,y на C просто никак не ложится.
Команды вида lda (ptr),Y тоже, по сути, будут использоваться как несуществующая lda (ptr).
За что бились в архитектуре amd64 ? Не за арифметику же в 64 бита - её и сопроцессор мог легко сделать.
Даже в рамках 8087. А именно за длинные указатели.

Мне как-то даже интересно : был ли какой-то компилятор C заточенный под эту специфику 6502 ?

14

Re: Пакет cc65

Мне сейчас подумалось... Может быть, фраза о командах контроллеров, ориентированных на язык Си (встречал в документации на AVR) имеет смысл?..

15

Re: Пакет cc65

Да, фраза, возможно, имеет смысл. В AVR, хотя бы, есть целых три указателя по 16 бит каждый.
И есть команды вычитания/сложения указателя и смещения.

Это не сравнить со сложными методами адресации, которые есть у 80386+, хотя в гибкости и - даже больше - изяществе - им, наверное, далеко до ассемблера VAX.11/PDP.11. Там вообще всё было очень интересно и мощно:
всего 8 методов адресации и всего 8 регистров, из которых один - счётчик команд, а второй - указатель стека, остальные - ниверсальные.
И, если не ошибаюсь, там не очень заморачивались на работу с 8 битами - всё было сразу в 16.

Так вот в PDP методы адресации были такие:
непосредственный (значение регистра), индексный (регистр в качестве указаателя),
индексно-инкрементный, индексно-декрементный (регистр в качестве указателя, но после или перед операцией его значение уменьшается или увеличивается) и ещё какие-то (не помню уже).

Замечу: в методах не было непосредственной адресации, например, к памяти.
Как быть ? Да просто ! Грузим константу 2 в регистр R0:

mov 2, R0

компилируется, фактически, в такой код:

mov (+R7), R0
.word 2

R7 - счётчик команд.
Т.е. команда вызывает инкремент счётчика команд, затем использует его как указатель (на строку .word 2)
и берёт из неё значение, копирует его в R0.

Надо со стеком работать ?

mov (R6+), R0

R6 - указатель стека. pop R0.

Если не ошибаюсь, та же песня с переходами.
(А я могу ошибаться в деталях. Возможно, один из аргументов всегда должен был быть регистр... а может оба могли быть и регистром и МАДр.... давно это было, да и писал я на нём совсем немного)

Код операции состоял из кода действия, регистра-источника, метода адресации к аргументу-источнику,  регистра-приёмника и метода адресации к аргументу-приёмнику. Так что ассемблер и дизассемблер были довольно простыми.

С другой стороны, вспомните историю команд leave и enter (подготовка стека в начале вызова процедуры и перед возвратом из неё).
Их ввели, кажись, в 80286 ради языков высокого уровня, но потом так и не стали использовать.
Они выполняли много операций, но были довольно медленными в результате.

А вообще, 86е - нормальное семейство.
Вроде, когда эмуль писал юниховый, в общем-то никаких особых неудобств не испытывал.
Да Clang тоже нормальный сейчас. Наверное и GCC тоже.
Написал прогу :

int t(int * a) {                                                                                                    
  return 2 + *a++;                                                                                                  
}                                                                                                                   
                                                                                                                    
int main() {                                                                                                        
  static int r=4;                                                                                                   
  t (&r);                                                                                                           
}                                                                                                                  

Скомпилил - ничего лишнего, всё что у меня одна операция, в асме тоже одна операция.
Скомпилил с O2 (Moderate level of optimization) - компилер вообще выпилил всё из main, кроме return 0.
Оптимизировал :)) Процедуру t только зачем-то оставил в коде. Хотя и там только сложение с 2 есть, а ++ - нет.

Здорово, что у нас есть возможность и знания всем этим пользоваться :))

16

Re: Пакет cc65

Оказывается я уже пробовал с этими загрузчиками и ничего не работало.

Попробовал еще раз, простейшая программа:

#include <stdio.h>

void main(void)
{
    printf("Hello, world!\n");
}

Не запускается в "BaderDos.dsk" с ошибкой I/O Error и выпадает в монитор в "Loader79.dsk".
Прикладываю бинарник без заголовков, стартовый адрес, стандартный для apple2 в сс65: $803

Post's attachments

Attachment icon hellow 2.87 kb, 2 downloads since 2019-08-07 

17 Отредактировано Voldemar0 (08-08-2019 18:59)

Re: Пакет cc65

У меня оба образа валятся с такой ошибкой:

D39C-    A=00 X=13 Y=D4 P=F5 S=F7

На самом деле причина простая:
В этих микродосах есть что-то в использовании карты памяти, то ли банки другие используются,
то ли ещё что.

А в проге у тебя такие инструкции:

1344-  2C 81 C0    "..@"    BIT   C081
1347-  2C 81 C0    "..@"    BIT   C081

Они переключают на эпле память в состояние доступности ПЗУшного бейсика/LC RAM.
В ИКП-Эплсофт они, фактически, ничего не переключают, но это и требуется.
А в микродосах это переключение приводит к тому, что какие-то проги в районе
LC исчезают из пространства.

Таких переключений в твоей проге несколько.

0816-  2C 82 C0   "??@"   BIT  $C082
0837-  2C 82 C0   "??@"   BIT  $C082
083D-  2C 80 C0   "??@"   BIT  $C080
1344-  2C 81 C0   "??@"   BIT  $C081
1347-  2C 81 C0   "??@"   BIT  $C081
1365-  2C 80 C0   "??@"   BIT  $C080

Мне вообще не ясно, для чего такие изыски: насколько помнится, 80, 81, 82, 83 - это всего два переключателя:
0 и 3 - это доступ к LC RAM на чтение, а 1 и 2 - это чтение ПЗУ. Ну, впрочем, я плохо знаю эпл, может там что-то сложнее...

/REM: когда девятка эмулирует эпл, ей, вообще-то, без разницы что забить как в LC RAM так и в ПЗУ.
Памяти навалом, там ещё много неиспользуемой остаётся.
Возможно, мало каким прогам был нужен доступ именно к ПЗУшному бейсику и авторы микродоса
как бы или не столкнулись с тем, что подобные проги всё же существуют или не поняли в чём проблема./

Я порылся по нашим запасам, но более правильной версии чем ИКПшная и та, что шла с The BEST не нашел.
Но с 840ками они не работают.
/ У меня сейчас загруженность будет большая на этой неделе, так что я сильно копаться глубоко не могу.
Если не найдешь решения - где нибудь через пару недель я буду свободнее, можно ещё покопаться /

18

Re: Пакет cc65

Да, в коде инициализации Apple2 разработчики cc65 довольно часто оперируют памятью LC
https://github.com/cc65/cc65/blob/3d63a … rt0.s#L129

        ; Switch in LC bank 2 for W/O.
        bit     $C081
        bit     $C081

https://github.com/cc65/cc65/blob/3d63a … rt0.s#L158

        ; Switch in LC bank 2 for R/O and return.
        bit     $C080
        rts

Или https://github.com/cc65/cc65/blob/maste … le2/home.s

; HOME routine
;
        .export         HOME
        .include        "apple2.inc"
        .segment        "LOWCODE"

HOME:
        ; Switch in ROM and call HOME
        bit     $C082
        jsr     $FC58           ; Clear current text screen

        ; Switch in LC bank 2 for R/O and return
        bit     $C080
        rts

Как ты и написал они подключают банк ROM, чтобы выполнить функцию монитора, а потом возвращают LC RAM.
Попробую разобраться что можно с ними сделать, потому что по идее памяти должно хватить и без LC.

19 Отредактировано Voldemar0 (09-08-2019 06:03)

Re: Пакет cc65

они там вызывают функцию копирования региона памяти, что ли

т.е. им не память дополнительная нужна, а доступ к загруженному в ПЗУ коду