Разработка и эмуляция на УКНЦ

 


С УКНЦ я познакомился в 1998 году, когда устроился преподавателем в одну школу. Был класс и минимум документации и софта - игрушки, BASIC и системные диски. Через два года я поменял работу на другую - зряплата побольше и компы поинтереснее. Сейчас нашлось свободное время, и я занялся изучением неведанного компьютера. Скачал эмулятор UKNCBTL, образы дисков, и занялся программированием. Облом первый: при запуске эмулятор требует DLL.Взял прежнюю версию UKNCBTL-609-exe.zip, запустил. Для разминки попробовал классическую задачу печати "Hello,world!". Запустил редактор EDIK, набрал найденный исходник и получил второй облом: в эмуляторе не удалось переключиться на русскую раскладку. Перепробовал массу вариантов, плюнул и ввел текст транслитом. Возникает вопрос - как сохранить текст и выйти? Взял утилиту rt11dsk, извлек из образа EDIK.TXT и надолго залип, разбираясь с кодировкой. На помощь пришла Алиса AI, которая накидала исходник конверсии текста koi7conv. Перекодировал текст, нашел описание и не сумел сохранить текст и выйти из программы. Алиса снова подсказала:

В текстовом редакторе EDIK для УКНЦ выход из программы зависит от того, сохранили ли вы изменения. 

Выход с сохранением — нажмите К1 + Е (клавиша К1 в сочетании с буквой Е).

Выход без сохранения — нажмите К1 + Q (клавиша К1 плюс буква Q).

(Эти клавиши работают не во всех версиях программы).

Сохранил текст, ввел команды HELLO HELLO  LINK HELLO и получил результат:

Исходный тест:
.MCALL .TTYOUT, .EXIT

HELLO:
 MOV #MESSAGE, R1
L1:
 MOVB (R1)+,R0
 BEQ DONE
 .TTYOUT
 JMP L1
DONE:
 .EXIT
MESSAGE:
 .ASCIIZ /Priwet,mir!/
.END HELLO

Последняя строка должна быть пустая, чтобы компиляция прошла.

Теперь я стал искать информацию о графическом режиме, получил массу сведений и не сумел изобразить на экране хоть что-нибудь. Задал на вопрос на гф и, как ожидалось, получил ответ:
ссылка на документации, которые я уже видел. Позже получил пример работы программы:
START:
 MOV #10000,R3
 CLR R0
 MOV #100000,@#176640
CYCLE:
 MOV R0,@#176642
 INC R0
 INC @#176640
 SOB R3,CYCLE
 HALT
.END START


Стал разбираться с кодом. Выглядит это так:
В УКНЦ дисплейный контроллер имеет регистры, отображённые в адресное пространство ввода-вывода:
176640 (восьмеричное) — регистр адреса видеопамяти. Записав сюда значение, вы указываете контроллеру, с какого слова видеопамяти начинать работу.
176642 (восьмеричное) — регистр данных видеопамяти. Запись сюда помещает слово в видеопамять по текущему адресу.
А пишешь адрес видеопамяти (если сильно не вдаваться в кучу подробностей работы ПП таблицы видеострок и прочего) - короче пишешь туда адрес видеопамяти вида 100000 (восьмеричное)
mov #100000, @#176640
После этого в регистре 176642 появляется слово из двух байт планов зеленого и красного цвета которое представляет собой 8 пикселей экрана (там ещё есть и синий план, ну да фиг с ним пока-что).
Если ты запишешь после такого в регистр 176642 какое-нибудь слово (после того как установил в 176640 адрес экрана, начало с 100000 считай) - у тебя обновится 8 пикселей на экране.
Понятно что в УКНЦ есть ещё и скролл экрана и много всяких вещей, но для начала "и так пойдет" для понимания.

Если более нормально - заносишь в 176640 адрес/2 памяти которая "у центрального процессора". Почему и 100000 ибо реальный адрес будет 200000 (хотя такое адресовать процессор 16 бит не может). После чего ты манипулируешь (читаешь/пишешь) регистром 176642 в котором будет по считыванию находиться значение этой памяти, ну а по записи ты это значение обновишь. Сама видеопамять УКНЦ может в зави-ти от прихотей программиста располагаться да где угодно, но там надо много курить мануалы и лезть в периферийный процессор.

Например тебе надо обновить 8 бит экрана в восьмой строке с самого начала (опять-же я опускаю скролл экрана). Каждая строка это 80 слов видеопамяти в ЦП (опять-же не говорю про таблицу строк, пусть будет дефолт). Значит надо занести 120*10+100000 (я в восьмеричном пишу) в регистр 176640 (помножь на 2 ибо туда заносится деленное на 2 - узнаешь что адрес будет выше 200000) и затем записать (можешь и считать кст) что-нибудь (слово) в 176642

Я написал программу по-другому
START:
 MOV #100000,R0

 MOV R0,@#176640
 MOV #80.,R1
LL:
 MOV #125125,@#176642
 INC @#176640
 SOB R1,LL
WW:
 BR WW

.END START

На гф последовало объяснение - нужно сбросить рулон, бла-бла-бла. Я решил попробовать очистку экрана. Очистилось, но результат стал иным:
А если  сбросить этот самый рулон?
.MCALL  .TTOUT, .EXIT
START:
;CLS
 MOV #033, R0
 .TTOUT
 MOV #12., R0
 .TTOUT
; https://emuverse.ru/wiki/УКНЦ_RAM.LST_app
 MOV #033, R0
 .TTOUT
 MOV #131, R0 ; same 231
 .TTOUT
 MOV #32., R0
 .TTOUT
 MOV #32., R0
 .TTOUT
; https://textarchive.ru/c-1107613-p6.html
; MOV #ST,R1
; EMT 46

 MOV #100000,R0
 MOV R0,@#176640
 MOV #80.,R1
LL:
 MOV #125125,@#176642
 INC @#176640
 SOB R1,LL
WW:
 BR WW
;ST:
; .BYTE 033,12.,033,131,32.,32.,0,0

.END START

В начальном коде много лишнего с выводом кодов и я решил разобраться с печатью позже. Но мне надоел этот нудный процесс - загрузка, редактирование, компиляция и запуск, поэтому я вернулся к кросс-средствам - rt1dsk и эмулятор RT-11. Теперь компиляция делается пакетным файлом:
del pr.sav
del pr.obj
rt11 macro pr /list:pr.lst
rt11 link pr
rt11dsk d sys1002.dsk pr.sav
rt11dsk a sys1002.dsk pr.sav
pause

Дело пошло быстрее, я вернулся к решению задуманной задачи. Но сначала я проверю неясное утверждение: порядок точек задается обратно:
.MCALL  .PRINT,.TTOUT, .EXIT
START:
;CLS
.PRINT  #MSG

 MOV #100000,@#176640
 MOV #1,@#176642

LL:
 BR LL
.END START

На экране появилась точка на левом углу - я записал значение 1.

Написал код, пострадал с отладчиком. Нужно выбрать пункт меню Debug->Debug views. Минимум команд доступен в окне Debug console. И все же я получил результат:
.MCALL  .PRINT,.TTOUT, .EXIT, .TTYIN
START:
;CLS
.PRINT  #MSG

 MOV #100000,@#176640
; MOV #1,@#176642

; .RADIX 8
; --- Отправить ESC (033) напрямую в порт, минуя фильтр ---
ESC1: TSTB @#177564 ; бит 7 = готовность ПП
 BPL ESC1 ; ждём
 MOVB #033, @#177566 ; записываем ESC в регистр данных
.PRINT #MSG2

 CLR R0 ; Y
YLP:
 CLR R1 ; X
XLP:
 CLR R3 ; bit value
 MOV #1.,R4
BLP:
 MOV R0,R2
 XOR R1,R2 ; X!Y
; N%9
MOD9:
 CMP R2,#9.
 BLT DONE
 SUB #9., R2
 BR MOD9
DONE:
 TST R2
 BNE ZERO
 ADD R4,R3
ZERO:
 INC R1
 ADD R4,R4
 CMP #256.,R4
 BNE BLP
 MOV R3,R4
 SWAB R3
 ADD R4,R3
 MOV R3,@#176642
 INC @#176640
 CMP #640.,R1
 BNE XLP
 INC R0
 CMP #280.,R0
 BNE YLP

WW:
 BR WW
MSG2: .ASCIZ /%!0/ ; Если дальше понадобится, например, вернуть курсор обратно (ESC %!3)
MSG:
  .BYTE 033,014,033,131,040,040
  .BYTE 0
.END START


Курсор убран командой печати управляющих символов ESC%!0. Программа рисует картинку довольно медленно.
Остался еще один неясный момент. У компьютера УКНЦ джва процессора на борту: ЦП(центральный процессор) и ПП(периферийный процессор). ПП занимается работой с экраном и периферией. Я попытался разобраться с принципом работы, но получилось не сразу. Только долгий чат с одной гражданкой подсказал пример:
        .TITLE  LOADPP
        .MCALL  .PRINT, .EXIT

; === Регистры канала К2 ===
K2STS   = 176674            ; Регистр состояния канала К2
K2DAT   = 176676            ; Регистр данных канала К2

START:
        ; Длину берём из PPLEN, а не из #()
        MOV   PPLEN, R0    ; Загрузить длину из памяти
        ASR   R0           ; Байты -> слова
        MOV   R0, LENGTH
        ; ... дальше отправка МП ...
        ; --- Этап 1: Запрос памяти в ПП ---
        MOVB  #1, COMAND            ; Команда "ВЫДЕЛИТЬ ПАМЯТЬ"
        MOV   PPLEN, LENGTH          ; Длина программы в словах
        JSR PC,SENDMP                 ; Послать МП в ПП
        TSTB  MASSIV                 ; Проверить результат
        BNE   ERR                    ; Не 0 — ошибка

        ; --- Этап 2: Запись программы в ПП ---
        MOVB  #20, COMAND            ; Команда "ЗАПИСЬ"
        MOV   PPADR, ADR$CP          ; Откуда в ОЗУ ЦП
        MOV   PPLEN, LENGTH          ; Сколько слов
        JSR PC,SENDMP                 ; Послать МП в ПП
        TSTB  MASSIV
        BNE   ERR

        ; --- Этап 3: Запуск программы в ПП ---
        MOVB  #30, COMAND            ; Команда "ПУСК"
        ; ADR$PP уже содержит адрес, который вернул ПП
        ; на этапе 1 (выделение памяти)
        JSR PC,SENDMP
        TSTB  MASSIV
        BNE   ERR

        ; ... здесь можно ждать завершения ...

        ; --- Этап 4: Освобождение памяти ---
        MOVB  #2, COMAND             ; Команда "ОСВОБОДИТЬ ПАМЯТЬ"
        JSR PC,SENDMP
        TSTB  MASSIV
        BNE   ERR

        .EXIT

ERR:    .PRINT #ERRMSG
        .EXIT

ERRMSG: .ASCIZ /Ошибка загрузки в ПП/

; === Подпрограмма передачи МП в ПП через К2 ===
.EVEN                      ; Выровнять на чётную границу
SENDMP:
        ; Сначала инициализация канала: 4 байта (0,0,0,377)
        MOV   #4, R1                 ; 4 байта инициализации
        MOV   #INITB, R0             ; Адрес массива инициализации
1$:     TSTB  @#K2STS                ; Ждать готовности канала
        BPL   1$
        MOVB  (R0)+, @#K2DAT         ; Послать байт инициализации
        SOB   R1, 1$

        ; Теперь послать адрес МП и стоповый элемент
        MOV   #ADRMAS, R0            ; R0 -> на поле ADRMAS в МП
        MOV   #5, R1                  ; 4 байта адреса + 1 байт (стоп)
2$:     TSTB  @#K2STS
        BPL   2$
        MOVB  (R0)+, @#K2DAT         ; Послать байт
        SOB   R1, 2$

        ; Ждать, пока ПП примет и обработает МП
3$:     TSTB  @#K2STS                ; Ждать подтверждения
        BPL   3$

        RTS   PC

INITB:  .BYTE  0, 0, 0, 377         ; Байты инициализации канала

; === Массив параметров ===
MASSIV: .BYTE  0                    ; Байт ошибки (заполняет ПП)
COMAND: .BYTE  0                    ; Код команды
TYPDEV: .WORD  32                   ; Устройство = ПП
ADR$PP: .WORD  0                    ; Адрес в ОЗУ ПП (заполняет ПП)
ADR$CP: .WORD  0                    ; Адрес в ОЗУ ЦП
LENGTH: .WORD  0                    ; Длина в словах
ADRMAS: .WORD  MASSIV              ; Адрес начала МП
        .WORD  401                  ; Стоповый элемент

; === Программа, которая будет загружена в ПП ===
SPP:
        ; Здесь — код, который будет работать в ПП.
        ; Программа должна быть перемещаемой (position-independent),
        ; т.к. адрес её размещения заранее неизвестен.
        ;
        ; Например — запись в видеопамять:
        MOV   #100000, @#177010     ; Адрес начала ВОЗУ в РА
        MOV   #0177777, @#177012     ; все биты в планах 0 и 1
        MOV   #0177777, @#177014      ; все биты в плане 2
        RTS   PC                    ; Возврат (завершение процесса)

EPP:
; === Параметры загружаемой программы ===
PPADR:  .WORD  SPP                  ; Адрес программы в ОЗУ ЦП
PPLEN: .WORD EPP-SPP ; Длина в словах
        .END   START
Ниже строчки "Empty bytes:" нарисована линия 8 точек.И пока на этом можно поставить паузу.
Файлы тут. В интернетах найдется немало документации, чтобы забить голову.

Комментарии