3niti alpha simu1 - троичный компьютер на двоичном пике

8-битные микроконтроллеры PICmicro (ПИКи) от Microchip и совместимые, а также 16-битные PIC24 и 32-битные PIC32

Moderator: Shaos

Mac Buster
Retired
Posts: 1474
Joined: 03 Aug 2003 22:37
Location: Moscow

Post by Mac Buster »

Где тут очередь? Я первый?
Extreme Entertainment
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Mac Buster wrote:Где тут очередь? Я первый?
Очередь тут:

https://www.tindie.com/products/trc/3niti-alpha-simu1/
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Собрал первую Rev.B:

Image

И оно с ходу заработало:

Image

:kruto:

P.S. Снял трёхчасовое с хвостиком видео сборки - ускорю в 10 раз и выложу на ютюб...
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

код троичного инвертирования (он просто переставляет местами чётные и нечётные биты):

Code: Select all

tri_neg: ; 14 cycles with call
   movwf   tmp1 ; сохраняем байт в tmp1
   andlw   b'01010101' ; берём по маске каждый чётный бит
   movwf   tmp2 ; сохраняем в tmp2
   movf   tmp1,W ; берём tmp1 (то что пришло на вход функции)
   andlw   b'10101010' ; берём по маске каждый нечётный бит
   movwf   tmp1 ; сохраняем в tmp1 
   bcf   STATUS,C ; пишем 0 во флаг C
   rlf   tmp2,F ; сдвигаем влево tmp2
   rrf   tmp1,W ; сдвигаем вправо tmp1 (надо бы перед ним тоже C обнулить, хотя с 3-тритными словами работать должно)
   iorwf   tmp2,W ; складываем по или с tmp2
   return ; возвращаем байт с переставленными чётными и нечётными битами
код снятия состояние переключателей и высвечивания этого состояния на светодиодах:

Code: Select all

loop:
   movlw   b'11111110' ; устанавливаем бегущий ноль в нулевой бит
   movwf   mask ; сохраняем в переменной mask
   movlw   5
   movwf   count ; устанавливаем счётчик в 5
loop0:
   movf   mask,w
   movwf   PORTA ; послать маску в порт A (чтобы выбрать 3 переключателя/светодиода)
   _delay1 10,cnt1 ; ждём
   movf   PORTB,w ; читаем байт из порта B (состояние очередных 3 троичных переключателей)
   call   tri_neg ; троично инвертируем (переключатели вверх-ногами припаяны ;)
   movwf   PORTC ; посылаем получившееся в порт C (на очередные 3 троичных светодиода)
   _delay1   0,cnt1 ; долго ждём
   bsf   STATUS,C ; флаг C = 1
   rlf   mask,f ; сдвигаем наш ноль влево
   decfsz   count,f ; декрементируем счётчик и проверяем, что он ещё не ноль
   goto   loop0 ; переход на следующую итерацию (из пяти)
   goto   loop ; после пяти итераций идём к началу... 
P.S. вышеприведённый код работает не с представлением трита как пары битов с состояниями 01,00,10 а с состояниями 10,11,01 (т.е. двоично инвертированными)

P.P.S. подпрограмма tri_neg может корректно троично инвертировать как двоично-инвертированную триаду, так и "нормальную"
Last edited by Shaos on 19 Nov 2011 09:16, edited 4 times in total.
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Ниже представлен код троичного инкремента триады из второго теста. Триада - это три трита, каждый из которых представлен двумя битами - младшим битом минуса и старшим битом плюса. Триады у нас перевёрнуты из-за особенностей схемы "3niti alpha simu1", т.е. в старших битах находится младший трит, а в младших - старший:

bit 0 - установлен в "1", если старший трит триады отрицателен
bit 1 - установлен в "1", если старший трит триады положителен
bit 2 - установлен в "1", если средней трит триады отрицателен
bit 3 - установлен в "1", если средней трит триады положителен
bit 4 - установлен в "1", если младшей трит триады отрицателен
bit 5 - установлен в "1", если младшей трит триады положителен
bit 6 - должен быть нулевым
bit 7 - должен быть нулевым

Code: Select all

; Ternary increment of W (used tmp1), C if overflow
tri_inc:
	bcf	STATUS,C ; обнуляем флаг переноса C (это у нас признак переполнения при возврате управления из подпрограммы)
	movwf	tmp1 ; сохраняем инкрементируемую триаду в переменной tmp1

; инкремент младшего трита:

tri_i5:	btfss	tmp1,5 ; проверяем взведён ли бит 5 в нашей триаде (это плюс младшего трита)
	goto	tri_i4 ; если взведён, то пропускаем переход на tri_i4 и идём дальше
	bcf	tmp1,5 ; обнуляем  бит 5 (бит плюса младшей триады)
	bsf	tmp1,4 ; взводим бит 4 (бит минуса младшей триады)
	goto	tri_i3 ; в результате получаем -1 вместо +1 в младшей триаде и идём на инкремент следующего трита (перенос)

tri_i4:	btfss	tmp1,4 ; проверяем взведён ли бит 4 в нашей триаде (это минус младшего трита)
	bsf	tmp1,5 ; если не взведён (т.е. младший трит хранил 0), то устанавливаем бит 5 (плюс младшего трита)
	bcf	tmp1,4 ; обнуляем бит 4 (сбрасываем минус младшего трита, если он был)
	movf	tmp1,w ; копируем получившееся представление триады в аккумулятор W
	return ; возвращаем управление из подпрограммы

; инкремент среднего трита:

tri_i3:	btfss	tmp1,3 ; проверяем взведён ли бит 3 в нашей триаде (это плюс среднего трита)
	goto	tri_i2 ; если взведён, то пропускаем переход на tri_i2 и идём дальше
	bcf	tmp1,3 ; обнуляем  бит 3 (бит плюса средней триады)
	bsf	tmp1,2 ; взводим бит 2 (бит минуса средней триады)
	goto	tri_i1 ; в результате получаем -1 вместо +1 в средней триаде и идём на инкремент следующего трита (перенос)
 
tri_i2:	btfss	tmp1,2 ; проверяем взведён ли бит 2 в нашей триаде (это минус среднего трита)
	bsf	tmp1,3 ; если не взведён (т.е. средний трит хранил 0), то устанавливаем бит 3 (плюс среднего трита)
	bcf	tmp1,2 ; обнуляем бит 2 (сбрасываем минус среднего трита, если он был)
	movf	tmp1,w ; копируем получившееся представление триады в аккумулятор W
	return ; возвращаем управление из подпрограммы

; инкремент старшего трита:

tri_i1:	btfss	tmp1,1 ; проверяем взведён ли бит 1 в нашей триаде (это плюс старшего трита)
	goto	tri_i0 ; если взведён, то пропускаем переход на tri_i0 и идём дальше
	bcf	tmp1,1 ; обнуляем  бит 1 (бит плюса старшей триады)
	bsf	tmp1,0 ; взводим бит 0 (бит минуса старшей триады)
	goto	tri_ic ; в результате получаем -1 вместо +1 в старшей триаде и идём на взведение флага переноса (переполнение)

tri_i0:	btfss	tmp1,0 ; проверяем взведён ли бит 0 в нашей триаде (это минус старшего трита)
	bsf	tmp1,1 ; если не взведён (т.е. старший трит хранил 0), то устанавливаем бит 1 (плюс старшего трита)
	bcf	tmp1,0 ; обнуляем бит 0 (сбрасываем минус старшего трита, если он был)
	movf	tmp1,w ; копируем получившееся представление триады в аккумулятор W
	return ; возвращаем управление из подпрограммы

; обработка переполнения:

tri_ic: movf	tmp1,w ; копируем получившееся представление триады в аккумулятор W
	bsf	STATUS,C ; взводим флаг C (признак переполнения)
	return ; возвращаем управление из подпрограммы
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Рассмотрим принципиальную схему и печатную плату - там видно, что при положении переключателя "вверх" (плюс), контакт 4 подключен к 3, что передаёт сканирующий ноль порта A на чётные биты порта B, при положении "вниз" (минус) - контакт 1 подключен к 3, что задействует нечётные биты порта B, а при среднем положении (ноль) никуда не подключенный контакт 2 идёт на 3, что оставляет на обоих битах высокий уровень (т.к. на порту B включены внутренние подтягивающие резисторы), т.е.:

Code: Select all

        - 0 +
-------------
bit 0 = 1 1 0
bit 1 = 0 1 1
Как видно, чтобы превратить это представление в двоично закодированную троичность (binary coded ternary), нужно заменить пару 11 на пару 00:

Code: Select all

; Fix triad W  (used tmp1)
tri_fix: ; 15-24 cycles with call
  movwf tmp1
tri_f2: ; старший трит
  btfss tmp1,0 ; проверить что бит 0 взведён
  goto tri_f1 ; если нет, то идём на следующую пару бит
  btfss tmp1,1 ; проверить что бит 1 взведён
  goto tri_f1 ; если нет, то идём на следующую пару бит
  bcf tmp1,0 ; сбросить бит 0
  bcf tmp1,1 ; сбросить бит 1
tri_f1: ; средний трит
  btfss tmp1,2 ; проверить что бит 2 взведён
  goto tri_f0 ; если нет, то идём на следующую пару бит
  btfss tmp1,3 ; проверить что бит 3 взведён
  goto tri_f0; если нет, то идём на следующую пару бит
  bcf tmp1,2 ; сбросить бит 2
  bcf tmp1,3 ; сбросить бит 3
tri_f0: ; младший трит
  btfss tmp1,4 ; проверить что бит 4 взведён
  goto tri_fe ; если нет, то делаем возврат из подпрограммы
  btfss tmp1,5 ; проверить что бит 5 взведён
  goto tri_fe ; если нет, то делаем возврат из подпрограммы
  bcf tmp1,4 ; сбросить бит 4
  bcf tmp1,5 ; сбросить бит 5
tri_fe: ; возвращаем скорректированую триаду обратно в W
  movf tmp1,w
  return ; возврат из подпрограммы
Как видно бинарное инвертирование (comf var,w) и затем троичное инвертирование (call tri_neg) будет работать быстрее (как собственно и сделано во втором тесте с часами).

В случае светодиодов ноль в чётном бите подсвечивает красный светодиод (минус), а в нечётном - зелёный (плюс). А чтобы двойной светодиод не горел, оба бита должны содержать "1" (в случае обоих нулей светодиоды дадут жёлтый свет):

Code: Select all

        - 0 +
-------------
bit 0 = 0 1 1
bit 1 = 1 1 0
Значит чтобы отобразить триаду на светодиодах её достаточно бинарно инвертировать:

Code: Select all

comf outvar,w
movwf PORTC
При этом PORTA дожен держать низкий уровень в одном из пяти рядов (через RA0,RA1,RA2,RA3,RA4), соответствующих тройкам светодиодов, которую мы собираемся подсветить.
Last edited by Shaos on 19 Nov 2011 14:59, edited 1 time in total.
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Shaos wrote:Триада - это три трита, каждый из которых представлен двумя битами - младшим битом минуса и старшим битом плюса. Триады у нас перевёрнуты из-за особенностей схемы "3niti alpha simu1", т.е. в старших битах находится младший трит, а в младших - старший:

bit 0 - установлен в "1", если старший трит триады отрицателен
bit 1 - установлен в "1", если старший трит триады положителен
bit 2 - установлен в "1", если средней трит триады отрицателен
bit 3 - установлен в "1", если средней трит триады положителен
bit 4 - установлен в "1", если младшей трит триады отрицателен
bit 5 - установлен в "1", если младшей трит триады положителен
bit 6 - должен быть нулевым
bit 7 - должен быть нулевым
Если таким байтом адресоваться к 27-триадному блоку данных, то в двоичной памяти оно расположится так:

Code: Select all

0x00 (00000000) OOO = 0
0x01 (00000001) NOO = -9
0x02 (00000010) POO = +9
0x03 (00000011)
0x04 (00000100) ONO = -3
0x05 (00000101) NNO = -12
0x06 (00000110) PNO = +6
0x07 (00000111)
0x08 (00001000) OPO = +3
0x09 (00001001) NPO = -6
0x0A (00001010) PPO = +12
0x0B (00001011)
0x0C (00001100)
0x0D (00001101)
0x0E (00001110)
0x0F (00001111)
0x10 (00010000) OON = -1
0x11 (00010001) NON = -10
0x12 (00010010) PON = +8
0x13 (00010011)
0x14 (00010100) ONN = -4
0x15 (00010101) NNN = -13
0x16 (00010110) PPN = +5
0x17 (00010111)
0x18 (00011000) OPN = +2
0x19 (00011001) NPN = -7
0x1A (00011010) PPN = +11
0x1B (00011011)
0x1C (00011100)
0x1D (00011101)
0x1E (00011110)
0x1F (00011111)
0x20 (00100000) OOP = +1
0x21 (00100001) NOP = -8
0x22 (00100010) POP = +10
0x23 (00100011)
0x24 (00100100) ONP = -2
0x25 (00100101) NNP = -11
0x26 (00100110) PNP = +7
0x27 (00100111)
0x28 (00101000) OPP = +4
0x29 (00101001) NPP = -5
0x2A (00101010) PPP = +13
0x2B (00101011)
0x2C (00101100)
0x2D (00101101)
0x2E (00101110)
0x2F (00101111)
Как можно видеть, не используются любые ячейки, в адресе которых встречаются две пары единиц у любой из триад (запрещённая комбинация в binary coded ternary).

В первой версии эмулятора для платы "3niti alpha simu1" предлагается держать 27-триад кода в EEPROM (как бы троичный ROM) с вышеуказанными адресами и 27-триад данных в регистровой памяти (как бы троичный RAM) с вышеуказанными адресами, к которым прибавлено число 0x40, т.е. диапазон переменных 0x40...0x6F (нулевой банк файла региcтров в пике).

В терминах длинных адресов "3niti alpha", этот 27-триадный ROM будет отображаться на область OOOOOOxxx, а 27-триадный RAM - на область OPNNNNxxx (начиная с адреса +1093). В текущей прошивке, над которой я сейчас работаю, внешняя память пока использоваться небудет (её можно будет добавить позже - у схемы есть возможность обновления прошивки через бутлоадер).

P.S. Глядя на этот маппинг троичных адресов на двоичные у меня возникла идея - что если прямо так и хранить во внешней памяти? Можно читать по 48 байт (тогда потеряно будет 44% памяти), убрав последние 5 байт получим 43 байта (37% потерь) или даже брать блоками по 11 байт три раза, где будет всего 2 однобайтных дырки (18% потерь). Проблема лишь в том, что придётся адресоваться к памяти через умножение - надо будет умножать на 48 (00110000), 43 (00101011) или 11 (00001011) - хотя точнее надо бы сказать 33 (00100001), т.к. 3 раза по 11. Формула уможения на 48: (x<<5)+(x<<4). Формула умножения на 43: (x<<5)+(x<<3)+(x<<2)+x. Формула умножения на 11: (x<<3)+(x<<1)+x или (x<<3)+(x<<2)-x (т.е. 12*x-x). Формула умножения на 33: (x<<5)+x. Хотя блоками по 27 тоже умножать надо, причём на некрасивое число 00011011...

P.P.S. Прикинул, что при 3x11-байтовом представлении 27-триадной страницы теоретически будет читаться-писаться по 300 страниц в секунду при 100кГц или 1200 страниц при 400кГц. Но т.к. при записи будет до 10 мс задержка на запись очередной порции (до 32 байт длиной) - то это уменьшит скорость записи до 30 страниц в секунду при 100кГц или 120 страниц при 400 кГц. Напомню, что первоначально оговоренный размер блоков RAM и ROM у 3niti alpha составляет (1093*2+1)/27=81 страница, которые можно прочитать меньше чем за треть секунды и записать за 2.7 или за 1.35 секунды в зависимости от тактовой частоты ПЗУ. Байты "потерянные" при такой оптимизации можно использовать для хранения адреса страницы (для проверки корректности места чтения) и её контрольной суммы (для проверки целостности данных).

P.P.P.S. Внимательно прочитал даташит на I2C память - страничная запись подразумевает запись в 32-байтовые области с фиксированными адресами - т.е. придётся таки писать ровно по 32 байта...
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Троичный инкремент 9-тритового регистра PC (program counter):

Code: Select all

; Ternary increment of 9-trit PC
inc_pc3:
        movf    PC_l,w ; берём млашую триаду PC
        call    tri_inc ; троично инкрементируем её
        movwf   PC_l ; сохраняем обратно
        btfss   STATUS,C ; проверяем флаг переноса C
        return ; если он не взведён - выходим из подпрограммы
        movf    PC_m,w ; берём среднюю триаду PC
        call    tri_inc ; троично инкрементируем её
        movwf   PC_m ; сохраняем обратно
        btfss   STATUS,C ; проверяем флаг переноса c
        return ; если он не взведён - выходим из подпрограммы
        movf    PC_h,w ; берём старшую триаду PC
        call    tri_inc ; троично инкрементируем её
        movwf   PC_h ; сохраняем обратно
        return ; выходим из подпровграммы
Печать триады через функцию печати в терминал из бутлоадера (макрос _serial_send_):

Code: Select all

; Serial print triad from W (used tmp0, cnt1)
print_triad:
        movwf   tmp0
        movlw   3
        movwf   cnt1
ptriad: btfsc   tmp0,0
        goto    ptriadn
        btfsc   tmp0,1
        goto    ptriadp
        _serial_send_ 'O'
        goto ptriad1
ptriadp:
        _serial_send_ 'P'
        goto ptriad1
ptriadn:
        _serial_send_ 'N'
ptriad1:
        rrf     tmp0,f
        rrf     tmp0,f
        decfsz  cnt1,f
        goto    ptriad
        return
Вот код циклического инкремента PC с печатью и результат его работы:

Code: Select all

loop:   call    inc_pc3

        movf    PC_h,w
        call    print_triad
        movf    PC_m,w
        call    print_triad
        movf    PC_l,w
        call    print_triad
        _serial_print_nl

        goto    loop
OOOOOOOOP
OOOOOOOPN
OOOOOOOPO
OOOOOOOPP
OOOOOOPNN
OOOOOOPNO
OOOOOOPNP
OOOOOOPON
OOOOOOPOO
OOOOOOPOP
OOOOOOPPN
OOOOOOPPO
OOOOOOPPP
OOOOOPNNN
OOOOOPNNO
OOOOOPNNP
OOOOOPNON
OOOOOPNOO
OOOOOPNOP
OOOOOPNPN
OOOOOPNPO
OOOOOPNPP
OOOOOPONN
OOOOOPONO
OOOOOPONP
OOOOOPOON
OOOOOPOOO
OOOOOPOOP
OOOOOPOPN
OOOOOPOPO
OOOOOPOPP
OOOOOPPNN
OOOOOPPNO
OOOOOPPNP
OOOOOPPON
OOOOOPPOO
OOOOOPPOP
OOOOOPPPN
OOOOOPPPO
OOOOOPPPP
OOOOPNNNN
OOOOPNNNO
и т.д.

P.S. Замерил без печати - PC полностью пробегает свой диапазон (19683) за 0.104 секунды (точнее это было 64 пробега за 6.66 секунд) - что означает примерно 189000 9-тритовых инкрементов в секунду - это и есть предельная скорость работы нашего эмулируемого процессора "3niti alpha" - 189000 операций в секунду...

P.P.S. Хотя если инкремент триады сделать не процедурным, а табличным, то будет побыстрее...
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Как ни странно, но табличный метод оказался даже чуть медленнее (примерно 174000 9-тритовых инкрементов в секунду):

Code: Select all

; Fast ternary increment of the triad W, C is overflow (used tmp1)

	ORG	0100h

tri_inc_table:

	retlw	tOOP
	retlw	tNOP
	retlw	tPOP
	retlw	0xFF
	retlw	tONP
	retlw	tNNP
	retlw	tPNP
	retlw	0xFF
	retlw	tOPP
	retlw	tNPP
	retlw	tPPP
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF

	retlw	tOOO
	retlw	tNOO
	retlw	tPOO
	retlw	0xFF
	retlw	tONO
	retlw	tNNO
	retlw	tPNO
	retlw	0xFF
	retlw	tOPO
	retlw	tNPO
	retlw	tPPO
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF

	retlw	tOPN
	retlw	tNPN
	retlw	tPPN
	retlw	0xFF
	retlw	tOON
	retlw	tNON
	retlw	tPON
	retlw	0xFF
	retlw	tPNN
	retlw	tONN
	retlw	tNNN ; overflow
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF

	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF
	retlw	0xFF

tri_inc_fast_:
	bsf	PCLATH,0
	movwf	PCL

tri_inc_fast: ; 18 with call
	andlw	0x3F
	call	tri_inc_fast_
	movwf	tmp1
	sublw	tNNN
	bcf	STATUS,C
	btfsc	STATUS,Z
	bsf	STATUS,C
	movf	tmp1,w
	return
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Самая сложная подпрограмма, необходимая для работы эмулятора "3niti alpha" - сложение двух триад с учётом флага переноса-заёма - реализация частично табличная (для сложения трёх троичных цифр), частично процедурная (для сложения триад):

Code: Select all

tri_add_3trits: ; 6 cycles with call
	addwf	PCL,f ; jump to proper value
	retlw	b'00000000' ; 000000 ->  0+0+0= 0 S=O C=O
	retlw	b'00000001' ; 000001 ->  0+0-1=-1 S=N C=O
	retlw	b'00000010' ; 000010 ->  0+0+1= 1 S=P C=O
	retlw	b'00010000' ; 000011 -> invalid
	retlw	b'00000001' ; 000100 ->  0-1+0=-1 S=N C=O
	retlw	b'00000110' ; 000101 ->  0-1-1=-2 S=P C=N
	retlw	b'00000000' ; 000110 ->  0-1+1= 0 S=O C=O
	retlw	b'00010000' ; 000111 -> invalid
	retlw	b'00000010' ; 001000 ->  0+1+0= 1 S=P C=O
	retlw	b'00000000' ; 001001 ->  0+1-1= 0 S=O C=O
	retlw	b'00001001' ; 001010 ->  0+1+1= 2 S=N C=P
	retlw	b'00010000' ; 001011 -> invalid
	retlw	b'00010000' ; 001100 -> invalid
	retlw	b'00010000' ; 001101 -> invalid
	retlw	b'00010000' ; 001110 -> invalid
	retlw	b'00010000' ; 001111 -> invalid
	retlw	b'00000001' ; 010000 -> -1+0+0=-1 S=N C=O
	retlw	b'00000110' ; 010001 -> -1+0-1=-2 S=P C=N
	retlw	b'00000000' ; 010010 -> -1+0+1= 0 S=O C=O
	retlw	b'00010000' ; 010011 -> invalid
	retlw	b'00000110' ; 010100 -> -1-1+0=-2 S=P C=N
	retlw	b'00000100' ; 010101 -> -1-1-1=-3 S=O C=N
	retlw	b'00000001' ; 010110 -> -1-1+1=-1 S=N C=O
	retlw	b'00010000' ; 010111 -> invalid
	retlw	b'00000000' ; 011000 -> -1+1+0= 0 S=O C=O
	retlw	b'00000001' ; 011001 -> -1+1-1=-1 S=N C=O
	retlw	b'00000010' ; 011010 -> -1+1+1= 1 S=P C=O
	retlw	b'00010000' ; 011011 -> invalid
	retlw	b'00010000' ; 011100 -> invalid
	retlw	b'00010000' ; 011101 -> invalid
	retlw	b'00010000' ; 011110 -> invalid
	retlw	b'00010000' ; 011111 -> invalid
	retlw	b'00000010' ; 100000 ->  1+0+0= 1 S=P C=O
	retlw	b'00000000' ; 100001 ->  1+0-1= 0 S=O C=O
	retlw	b'00001001' ; 100010 ->  1+0+1= 2 S=N C=P
	retlw	b'00010000' ; 100011 -> invalid
	retlw	b'00000000' ; 100100 ->  1-1+0= 0 S=O C=O
	retlw	b'00000001' ; 100101 ->  1-1-1=-1 S=N C=O
	retlw	b'00000010' ; 100110 ->  1-1+1= 1 S=P C=O
	retlw	b'00010000' ; 100111 -> invalid
	retlw	b'00001001' ; 101000 ->  1+1+0= 2 S=N C=P
	retlw	b'00000010' ; 101001 ->  1+1-1= 1 S=P C=O
	retlw	b'00001000' ; 101010 ->  1+1+1= 3 S=O C=P
	retlw	b'00010000' ; 101011 -> invalid
	retlw	b'00010000' ; 101100 -> invalid
	retlw	b'00010000' ; 101101 -> invalid
	retlw	b'00010000' ; 101110 -> invalid
	retlw	b'00010000' ; 101111 -> invalid
	retlw	b'00010000' ; 110000 -> invalid
	retlw	b'00010000' ; 110001 -> invalid
	retlw	b'00010000' ; 110010 -> invalid
	retlw	b'00010000' ; 110011 -> invalid
	retlw	b'00010000' ; 110100 -> invalid
	retlw	b'00010000' ; 110101 -> invalid
	retlw	b'00010000' ; 110110 -> invalid
	retlw	b'00010000' ; 110111 -> invalid
	retlw	b'00010000' ; 111000 -> invalid
	retlw	b'00010000' ; 111001 -> invalid
	retlw	b'00010000' ; 111010 -> invalid
	retlw	b'00010000' ; 111011 -> invalid
	retlw	b'00010000' ; 111100 -> invalid
	retlw	b'00010000' ; 111101 -> invalid
	retlw	b'00010000' ; 111110 -> invalid
	retlw	b'00010000' ; 111111 -> invalid

tri_add_test MACRO B
	movlw	B
	call tri_add_3trits
	ENDM

; Ternary adder W+A_reg+BCF=A_reg and BCF (used tmp0,tmp1,tmp2,tmp3,tmp4,cnt1)
tri_adder:
	movwf	tmp0
	clrf	tmp4
	movlw	3
	movwf	cnt1
	movf	F_reg,w
	andlw	b'00110000'
	movwf	tmp1
tri_add_loop:
	movf	tmp0,w
	andlw	b'00110000'
	movwf	tmp2
	bcf	STATUS,C
	rrf	tmp2,f
	rrf	tmp2,w
	iorwf	tmp1,f
	movf	A_reg,w
	andlw	b'00110000'
	movwf	tmp2
	swapf	tmp2,w
	iorwf	tmp1,w
	call	tri_add_3trits
	movwf	tmp3
	andlw	b'00000011'
	iorwf	tmp4,f
	movf	tmp3,w
	andlw	b'00001100'
	movwf	tmp1
	rlf	tmp1,f
	rlf	tmp1,f
	decfsz	cnt1,f
	goto	tri_add_next
	movf	F_reg,w
	andlw	b'00001111'
	iorwf	tmp1,w
	movwf	F_reg
	movf	tmp4,w
	movwf	A_reg
	return
tri_add_next:
	rlf	tmp4,f
	rlf	tmp4,f
	rlf	tmp0,f
	rlf	tmp0,f
	rlf	A_reg,f
	rlf	A_reg,f
	goto	tri_add_loop
Сделал замеры - если эмулятор не только бежит по PC, но и постоянно складывает две триады (взятые из PC), то скорость проседает до 33000 операций в секунду! Принимаются предложения по оптимизации этого кода :roll:

P.S. Похоже проблема решается намного проще - надо всего-то навсего перевести слагаемые из троичной системы в двоичную, сложить средствами самого пика и потом вернуть результат обратно в троичную...
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Вот функция процедурного декодера кода операции:

Code: Select all

; Perform single step of the program
tri_step:
	movlw	PC_h
	movwf	FSR
	call	tri_read
	movwf	C_reg
	call	tri_inc_pc3
	btfss	C_reg,0
	goto tri_step0
	; Nxx
	btfss	C_reg,2
	goto	tri_step_0
	; NNx
	btfss	C_reg,4
	goto	tri_step__0
	; NNN (-13) SAN
	movlw	DPn_h
	movwf	FSR
	movf	A_reg,w
	call	tri_write
	return
tri_step__0:
	btfsc	C_reg,5
	goto	tri_step__1
	; NNO (-12) SAO
	movlw	DPo_h
	movwf	FSR
	movf	A_reg,w
	call	tri_write
	return
tri_step__1:
	; NNP (-11) SAP
	movlw	DPp_h
	movwf	FSR
	movf	A_reg,w
	call	tri_write
	return
tri_step_0:
	btfsc	C_reg,3
	goto	tri_step_1
	; NOx
	btfss	C_reg,4
	goto	tri_step_00
	; NON (-10) SAF
	movf	A_reg,w
	movwf	F_reg
	return
tri_step_00:
	btfsc	C_reg,5
	goto	tri_step_01
	; NOO (-9) SPCD
	call	tri_cur_dp
	movf	PC_h,w
	movwf	INDF
	incf	FSR,f
	movf	PC_m,w
	movwf	INDF
	incf	FSR,f
	movf	PC_l,w
	movwf	INDF
	return
tri_step_01:
	; NOP (-8) SAB
	movf	A_reg,w
	movwf	B_reg
	return
tri_step_1:
	; NPx
	btfss	C_reg,4
	goto	tri_step_10
	; NPN (-7) SAL
	call	tri_cur_dp
	goto	tri_step_sal
tri_step_10:
	btfsc	C_reg,5
	goto	tri_step_11
	; NPO (-6) SAM
	call	tri_cur_dp
	goto	tri_step_sam
tri_step_11:
	; NPP (-5) SAH
	call	tri_cur_dp
	goto	tri_step_sah
tri_step_sal:
	incf	FSR,f
tri_step_sam:
	incf	FSR,f
tri_step_sah:
	movf	A_reg,w
	movwf	INDF
	return
tri_step0:
	btfsc	C_reg,1
	goto	tri_step1
	; Oxx
	btfss	C_reg,2
	goto	tri_step00
	; ONx
	btfss	C_reg,4
	goto	tri_step0_0
	; ONN (-4) RLA
	bcf	STATUS,C
	rlf	A_reg,f
	rlf	A_reg,f
	btfsc	F_reg,5
	bsf	A_reg,1
	btfsc	F_reg,4
	bsf	A_reg,0
	bcf	F_reg,5
	btfsc	A_reg,7
	bsf	F_reg,5
	bcf	A_reg,7
	bcf	F_reg,4
	btfsc	A_reg,6
	bsf	F_reg,4
	bcf	A_reg,6
	call	tri_rsf
	return
tri_step0_0:
	btfsc	C_reg,5
	goto	tri_step0_1
	; ONO (-3) ADD
	movf	B_reg,W
	call	tri_adder
	call	tri_rsf
	return
tri_step0_1:
	; ONP (-2) RRA
	btfsc	F_reg,5
	bsf	A_reg,7
	btfsc	F_reg,4
	bsf	A_reg,6
	bcf	STATUS,C
	rrf	A_reg,f
	btfsc	STATUS,C
	bsf	F_reg,4
	bcf	STATUS,C
	rrf	A_reg,f
	btfsc	STATUS,C
	bsf	F_reg,5
	call	tri_rsf
	return
tri_step00:
	btfsc	C_reg,3
	goto	tri_step01
	; OOx
	btfss	C_reg,4
	goto	tri_step000
	; OON (-1) LAI #
	call	tri_read1pc
	movf	tmp0,w
	movwf	A_reg
	return
tri_step000:
	btfsc	C_reg,5
	goto	tri_step001
	; OOO (0) ADI #
	call	tri_read1pc
	movf	tmp0,w
	call	tri_adder
	call	tri_rsf
	return
tri_step001:
	; OOP (1) OPA #
	call	tri_read1pc
	call	tri_opa
	movf	tmp1,w
	movwf	A_reg
	call	tri_rsf
	return
tri_step01:
	; OPx
	btfss	C_reg,4
	goto	tri_step010
	; OPN (2) LDI # # #
	call	tri_read3pc
	call	tri_cur_dp
	movf	tmp1,w
	movwf	INDF
	incf	FSR,f
	movf	tmp2,w
	movwf	INDF
	incf	FSR,f
	movf	tmp3,w
	movwf	INDF
	return
tri_step010:
	btfsc	C_reg,5
	goto	tri_step011
	; OPO (3) JMP # # #
	call	tri_read3pc
	movlw	PC_h
	movwf	FSR
	movf	tmp1,w
	movwf	INDF
	incf	FSR,f
	movf	tmp2,w
	movwf	INDF
	incf	FSR,f
	movf	tmp3,w
	movwf	INDF
	return
tri_step011:
	; OPP (4) OPB # # #
	call	tri_read3pc
	call	tri_opb
	movf	tmp0,w
	movwf	A_reg
	call	tri_rsf
	return
tri_step1:
	; Pxx
	btfss	C_reg,2
	goto	tri_step10
	; PNx
	btfss	C_reg,4
	goto	tri_step1_0
	; PNN (5) LAN
	movlw	DPn_h
	movwf	FSR
	call	tri_read
	movwf	A_reg
	return
tri_step1_0:
	btfsc	C_reg,5
	goto	tri_step1_1
	; PNO (6) LAO
	movlw	DPo_h
	movwf	FSR
	call	tri_read
	movwf	A_reg
	return
tri_step1_1:
	; PNP (7) LAP
	movlw	DPp_h
	movwf	FSR
	call	tri_read
	movwf	A_reg
	return
tri_step10:
	btfsc	C_reg,3
	goto	tri_step11
	; POx
	btfss	C_reg,4
	goto	tri_step100
	; PON (8) LAF
	movf	F_reg,w
	movwf	A_reg
	return
tri_step100:
	btfsc	C_reg,5
	goto	tri_step101
	; POO (9) LPCD
	call	tri_cur_dp
	movf	INDF,w
	movwf	PC_h
	incf	FSR,f
	movf	INDF,w
	movwf	PC_m
	incf	FSR,f
	movf	INDF,w
	movwf	PC_l
	return
tri_step101:
	; POP (10) LAB
	movf	B_reg,w
	movwf	A_reg
	return
tri_step11:
	; NPx
	btfss	C_reg,4
	goto	tri_step110
	; PPN (11) LAL
	call	tri_cur_dp
	goto	tri_step_lal
tri_step110:
	btfsc	C_reg,5
	goto	tri_step111
	; PPO (12) LAM
	call	tri_cur_dp
	goto	tri_step_lam
tri_step111:
	; PPP (13) LAH
	call	tri_cur_dp
	goto	tri_step_lah
tri_step_lal:
	incf	FSR,f
tri_step_lam:
	incf	FSR,f
tri_step_lah:
	movf	INDF,w
	movwf	A_reg
	return
Сделал предварительные замеры - простейшие команды типа SAB выполняются порядка 82000 раз в секунду (примерно 12 мкс), чуть посложнее типа OPA или ADD - порядка 27000 раз в секунду (примерно 37 мкс), совсем сложные типа ADI - порядка 21000 раз в секунду (примерно 48 мкс). Это если запускать программу в пределах одной страницы, находящейся в регистрах - если же страница сидит в EEPROM, то к примеру производительность того же ADI падает до 16500 раз в секунду (60 мкс) - это из-за того что процесс чтения из EEPROM (которых в команде ADI два) это штука нетривиальная и требует некоторых странных телодвижений, отсутствующих при обращении к регистрам...
Last edited by Shaos on 25 Nov 2011 02:41, edited 1 time in total.
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Вот подпрограммы для осуществления универсальных операций OPA и OPB:

Code: Select all

; Perform OPA command on A_reg with code tmp0 and save result in tmp1 (used cnt1)
tri_opa:
	movlw	3
	movwf	cnt1
	clrf	tmp1
tri_opa_:
	btfss	A_reg,0
	goto	tri_opa1
	btfsc	tmp0,0
	bsf	tmp1,6
	btfsc	tmp0,1
	bsf	tmp1,7
	goto	tri_opa_loop
tri_opa1:
	btfss	A_reg,1
	goto	tri_opa0
	btfsc	tmp0,4
	bsf	tmp1,6
	btfsc	tmp0,5
	bsf	tmp1,7
	goto	tri_opa_loop
tri_opa0:
	btfsc	tmp0,2
	bsf	tmp1,6
	btfsc	tmp0,3
	bsf	tmp1,7
tri_opa_loop:
	bcf	STATUS,C
	rrf	tmp1,f
	rrf	tmp1,f
	rrf	A_reg,f
	rrf	A_reg,f
	decfsz	cnt1,f
	goto	tri_opa_
	return

; Perform OPB command on A_reg and B_reg with code tmp1,tmp2,tmp3 and save result in tmp0 (used tmp4,cnt1)
tri_opb:
	movf	tmp1,w
	movwf	tmp4
	movf	tmp3,w
	movwf	tmp0
	call	tri_opa
	movf	tmp1,w
	movwf	tmp3
	movf	tmp2,w
	movwf	tmp0
	call	tri_opa
	movf	tmp1,w
	movwf	tmp2
	movf	tmp4,w
	movwf	tmp0
	call	tri_opa
	clrf	tmp0
	btfss	B_reg,0
	goto	tri_opb_h1
	btfsc	tmp1,0
	bsf	tmp0,0
	btfsc	tmp1,1
	bsf	tmp0,1
	goto	tri_opb_m
tri_opb_h1:
	btfsc	B_reg,1
	goto	tri_opb_h0
	btfsc	tmp3,0
	bsf	tmp0,0
	btfsc	tmp3,1
	bsf	tmp0,1
	goto	tri_opb_m
tri_opb_h0:
	btfsc	tmp2,0
	bsf	tmp0,0
	btfsc	tmp2,1
	bsf	tmp0,1
tri_opb_m:
	btfss	B_reg,2
	goto	tri_opb_m1
	btfsc	tmp1,2
	bsf	tmp0,2
	btfsc	tmp1,3
	bsf	tmp0,3
	goto	tri_opb_l
tri_opb_m1:
	btfsc	B_reg,3
	goto	tri_opb_m0
	btfsc	tmp3,2
	bsf	tmp0,2
	btfsc	tmp3,3
	bsf	tmp0,3
	goto	tri_opb_l
tri_opb_m0:
	btfsc	tmp2,2
	bsf	tmp0,2
	btfsc	tmp2,3
	bsf	tmp0,3
tri_opb_l:
	btfss	B_reg,4
	goto	tri_opb_l1
	btfsc	tmp1,4
	bsf	tmp0,4
	btfsc	tmp1,5
	bsf	tmp0,5
	return
tri_opb_l1:
	btfsc	B_reg,5
	goto	tri_opb_l0
	btfsc	tmp3,4
	bsf	tmp0,4
	btfsc	tmp3,5
	bsf	tmp0,5
	return
tri_opb_l0:
	btfsc	tmp2,4
	bsf	tmp0,4
	btfsc	tmp2,5
	bsf	tmp0,5
	return
в железе они реализуются значительно проще...

замеры по скорости:
OPA - 26800 раз в секунду или 37 мкс
OPB - 11600 раз в секунду или 86 мкс
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Ещё дополнительные подпрограммы для работы декодера команд:

Code: Select all

; Read one triad from the address stored in 3 triads started from FSR and save result to W
tri_read:
; skip middle address for now...
	movf	INDF,w
	subwf	CA_h,w
	btfsc	STATUS,Z
	goto	tri_read_eep
	movf	INDF,w
	subwf	DA_h,w
	btfsc	STATUS,Z
	goto	tri_read_ram
	call	tri_fail
tri_read_eep:
	incf	FSR,f
	incf	FSR,f
	movf	INDF,w
	call	eeprom_read
	_bank0
	return
tri_read_ram:
	incf	FSR,f
	incf	FSR,f
	movf	INDF,w
	addlw	0x40
	movwf	FSR
	movf	INDF,w
	return

; Write one triad W to the address stored in 3 triads started from FSR (used tmp0)
tri_write:
; skip middle address for now...
	movwf	tmp0
	movf	INDF,w
	subwf	CA_h,w
	btfsc	STATUS,Z
	goto	tri_write_eep
	movf	INDF,w
	subwf	DA_h,w
	btfsc	STATUS,Z
	goto	tri_write_ram
	call tri_fail
tri_write_eep:
	incf	FSR,f
	incf	FSR,f
    movf  tmp0,w
    movwf  eeprom_write_value
	movf	INDF,w
	call	eeprom_write
	return
tri_write_ram:
	incf	FSR,f
	incf	FSR,f
	movf	INDF,w
	addlw	0x40
	movwf	FSR
	movf	tmp0,w
	movwf	INDF
	return

; Set current DP in FSR register
tri_cur_dp:
	btfss	F_reg,2
	goto	tri_cur_dp0
	movlw	DPn_h
	goto	tri_cur_dp_
tri_cur_dp0:
	btfsc	F_reg,3
	goto	tri_cur_dp1
	movlw	DPo_h
	goto	tri_cur_dp_
tri_cur_dp1:
	movlw	DPp_h
tri_cur_dp_:
	movwf	FSR
	return

; Set RSF (higher trit of the register F) based of the sign of the value in the register A
tri_rsf:
	btfsc	A_reg,0
	goto	tri_rsf_p
	btfsc	A_reg,1
	goto	tri_rsf_n
	btfsc	A_reg,2
	goto	tri_rsf_p
	btfsc	A_reg,3
	goto	tri_rsf_n
	btfsc	A_reg,4
	goto	tri_rsf_p
	btfsc	A_reg,5
	goto	tri_rsf_n
tri_rsf_o:
	bcf	F_reg,0
	bcf	F_reg,1
	return
tri_rsf_n:
	bsf	F_reg,0
	bcf	F_reg,1
	return
tri_rsf_p:
	bcf	F_reg,0
	bsf	F_reg,1
	return

; Read 1 immediate triad and save it in tmp0
tri_read1pc:
	movlw	PC_h
	movwf	FSR
	call	tri_read
	movwf	tmp0
	call	tri_inc_pc3
	return

; Read 3 immediate triads and save them in tmp1,tmp2,tmp3
tri_read3pc:
	movlw	PC_h
	movwf	FSR
	call	tri_read
	movwf	tmp1
	call	tri_inc_pc3
	movlw	PC_h
	movwf	FSR
	call	tri_read
	movwf	tmp2
	call	tri_inc_pc3
	movlw	PC_h
	movwf	FSR
	call	tri_read
	movwf	tmp3
	call	tri_inc_pc3
	return
P.S. Полный исходник смотреть тут:
https://gitlab.com/nedopc/pix/-/blob/master/16/t1_test3.asm
он почти в тысячу строк получился...

P.P.S. Подпрограмма tri_fail должна будет вводить систему в состояние фатальной ошибки - например путём переключения всех светодиодов в жёлтый цвет и зависания в этом состоянии...
Last edited by Shaos on 25 Nov 2011 09:22, edited 1 time in total.
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Вот полная таблица длительностей выполнения каждой инструкции 3niti alpha в микросекундах из регистровой памяти (тут также можно видеть визуальное отличие длительностей):

Code: Select all

NNN (SAN)     14 =====
NNO (SAO)     15 =====
NNP (SAP)     15 =====
NON (SAF)     11 ====
NOO (SPCD)    16 =====
NOP (SAB)     12 ====
NPN (SAL)     15 =====
NPO (SAM)     15 =====
NPP (SAH)     15 =====
ONN (RLA)     16 =====
ONO (ADD)     38 =============
ONP (RRA)     16 =====
OON (LAI #)   20 =======
OOO (ADI #)   48 ================
OOP (OPA #)   37 ============
OPN (LDI ###) 41 ==============
OPO (JMP ###) 38 =============
OPP (OPB ###) 86 =============================
PNN (LAN)     14 =====
PNO (LAO)     15 =====
PNP (LAP)     15 =====
PON (LAF)     12 ====
POO (LPCD)    17 ======
POP (LAB)     13 ====
PPN (LAL)     16 =====
PPO (LAM)     16 =====
PPP (LAH)     16 =====
Как видно самой быстрой командой является SAF (точное значение - 11.4 мкс или примерно 87400 раз в секунду), а самой медленной - OPB (точное значение - 86.2 мкс или примерно 11600 раз в секунду).

А вот тоже самое, но при чтении из EEPROM:

Code: Select all

NNN (SAN)     21 =======
NNO (SAO)     21 =======
NNP (SAP)     22 =======
NON (SAF)     18 ======
NOO (SPCD)    23 =======
NOP (SAB)     19 ======
NPN (SAL)     22 =======
NPO (SAM)     22 =======
NPP (SAH)     22 =======
ONN (RLA)     25 ========
ONO (ADD)     46 ===============
ONP (RRA)     25 ========
OON (LAI #)   34 ===========
OOO (ADI #)   62 =====================
OOP (OPA #)   51 =================
OPN (LDI ###) 69 =======================
OPO (JMP ###) 66 ======================
OPP (OPB ###)113 ======================================
PNN (LAN)     28 =========
PNO (LAO)     29 =========
PNP (LAP)     29 =========
PON (LAF)     19 ======
POO (LPCD)    24 ========
POP (LAB)     20 =======
PPN (LAL)     22 =======
PPO (LAM)     23 ========
PPP (LAH)     23 ========
Как видно однотриадные команды стали дольше примерно на 7 мкс (одно чтение из EEPROM на команду), двухтриадные - на 14 мкс (два чтения из EEPROM на команду), а четырёхтриадные - на 28 мкс (четыре чтения из EEPROM на команду). Исключение составляют лишь команды LAN,LAO,LAP, которые содержат в себе неявное второе обращение к EEPROM. Также есть странная аномалия у RLA и RRA - они удлиннились не на 7, а на 9 мкс. Ещё интересно, что из-за этого неравномерного удлиннения команда ADI стала быстрее чем LDI и JMP. Теперь самая быстрая команда SAF выполняется 18.3 мкс (примерно 54400 раз в секунду), а самая долгая OPB - 113.3 мкс (примерно 8800 раз в секунду).

P.S. Обработка состояния переключателей и подсветка светодиодов добавляют около 7 мкс к каждой команде

P.P.S. Интересно, что прошивка, выполняющая все инструкции "3niti alpha" в двух страницах памяти и обрабатывающая почти всё управление (кроме возврата в главную программу M) влезла в оставленные от бутлоадера 671 байт практически в притык...
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 23989
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Эпопею с выкатыванием первой версии эмулятора можно считать законченной:

http://nedopc.org/nedopc/16/t1_test3.hex (3K)
http://nedopc.org/nedopc/16/t1_test3.asm (21K)
http://nedopc.org/nedopc/16/PDBLv1.zip (54K)

Прогон команды ADI # (код OOO) по всей памяти в 19,683 триад даёт примерно 52 мкс на команду (это при том, что реально данные читаются не во всех страницах). Также удалось впихнуть в прошивку хак - если переключатели W/R и I/M оба находятся в нижнем положении, то управление передаётся на бутлоадер PDBLv1, через который можно посмотреть состояние переменных и EEPROM.

Image
Я тут за главного - если что шлите мыло на me собака shaos точка net