Леонард: «Беспредельный Шелдон»?!
Шелдон: «Беспредельный Шелдон» бьёт все остальные карты и не нарушает запрет на изготовление карт в домашних условиях, потому что я сделал эту на работе.
Я всё ещё строю релейный компьютер, и поэтому решил сравнить его возможности с похожими хобби-проектами.
Запускал я программы только на своём компьютере (по понятным причинам), но и для остальных нашёл несколько программ, написанных авторами, чтобы можно было сравнить хотя бы их сложность.
Числа Фибоначчи мы уже считали в прошлый раз, поэтому продолжим с программами чуть посложнее.
Не со всеми компьютерами это получится, например DUO 14 Premium совсем маленький и даже программы для умножения 8-битных чисел туда не влезут.
Умножение
Сегодня умножать числа одной инструкцией умеет любая восьмибитная ардуина. В 70-80 годы этой возможности в большинстве процессоров не было. Нет её и в релейных компьютерах,
которые делают энтузиасты.
А значит, числа надо перемножать, используя обычные операции сложения. Конечно, умножать M на N используя цикл на N (или M) итераций не стоит. Лучше сымитировать умножение в столбик. Примерно вот так:
Тут на каждом шаге всё равно делается умножение (хоть и на маленькое число). Если же работать в двоичной системе вместо десятичной, то умножать придётся или на 0, или на 1. Оба случая тривиальны: x * 0 = 0, x * 1 = 1. Остаётся лишь сдвинуть результат частичного умножения на несколько позиций влево, а потом всё сложить.
Можно делать это двумя способами. Пусть мы перемножаем M и N, а результат записываем в R. Тогда один способ — это выполнять в цикле M >>= 1, R += N (если CY), N <<= 1. Другой способ — R <<= 1, M <<= 1, R += N (если CY). Чтобы так умножить восьмибитное число, надо сделать восемь итераций.
Harry Porter's Relay Computer
У компьютера Гарри Портера (HPRC) (а также у компьютеров нескольких его последователей) АЛУ использует в качестве операндов исключительно регистры B и C — всего 2 из 8 доступных. Результат же вычислений всегда помещается в регистр A. Это ещё неудобнее, чем у i8080, где хоть и был аккумулятор, но операнды можно было выбирать.
Поэтому в программах для HPRC появляется много лишних пересылок. Программа для 8-битного умножения состоит из 29 байт (23 инструкции). Я посчитал здесь байты отдельно, потому что длинные инструкции перехода выполняются втрое медленнее операций АЛУ.
Перед началом выполнения программы множители должны храниться в регистрах B и C, а результат она записывает в регистр X.
Address Instruction
00 Y=B
01 X=0
02 A=¬B
03 BNEG Else
06 X=C
Else:
07 A=-7
08 D=A
Loop:
09 B=X
0A A=B<<1
0B X=A
0C B=Y
0D A=B<<1
0E Y=A
0F B=Y
10 A=¬B
11 BNEG Else2
14 B=X
15 A=B+C
16 X=A
Else2:
17 B=D
18 D=B+1
19 BNZ Loop
1C HALT
Relay computer «trainer»
Каждая инструкция этого компьютера может читать операнды и записывать результат в произвольную ячейку памяти. Поэтому программа получается довольно компактной.
; This program multiplies two 8-bit numbers and produces a 8-bit result
org 0x00
argx skip 1
argy skip 1
res_lo skip 1 ; Result low
count skip 1
org 0x10
mul st #15, argx ; Initialize X
st #10, argy ; Initialize Y
st #0, res_lo
st #-8, count
loop lsl res_lo
lsl argy
jcc skip
addto argx, res_lo
skip incjne count, loop
halt
A fistful of relays
В моем компьютере тоже нет никаких аппаратных операций умножения. Но зато АЛУ умеет работать с любыми регистрами (можно даже поксорить PC с чем-нибудь), поэтому программа умножения тоже получается небольшая.
; C, D - operands, A - result
MOVI C, op1
MOVI D, op2
MOVI A, 0
Loop:
SHR C, C
JMP NC, Next
ADD A, A, D
Next:
ADD D, D, D
OR F, C, C
JMP NZ,Loop
HALT
Вот видео её работы для двух небольших чисел:
Алгоритм Евклида
Алгоритм Евклида — эффективный алгоритм для нахождения наибольшего общего делителя (НОД) двух целых чисел. В самом простом случае алгоритм Евклида применяется к паре положительных целых чисел и формирует новую пару, которая состоит из меньшего числа и разницы между большим и меньшим числом. Процесс повторяется, пока числа не станут равными. Найденное число и есть наибольший общий делитель исходной пары.
int gcd (int a, int b) {
return b ? gcd(b, a - b) : a;
}
Узкое место такого алгоритма — большое и маленькое число на входе. Например, для вычисления НОД 1 и 255 (восьмибитный же компьютер) потребуется 255 итераций. Можно применить более совершенную версию — бинарный алгоритм Евклида. Этот алгоритм использует соотношения для НОД (GCD):
Он иллюстрируется следующей программой:
m = a;
n = b;
d = 1;
while (m && n) {
if (m % 2 == 0 && n % 2 == 0) {
d *= 2;
m /= 2;
n /= 2;
} else if (m % 2 == 0 && n % 2 == 1) {
m /= 2;
} else if (m % 2 == 1 && n % 2 == 0) {
n /= 2;
} else if (m >= n) {
m -= n;
} else {
n -= m;
}
}
result = d * (m + n);
Такой алгоритм получше — время его работы пропорционально разрядности чисел. Но программа получится очень длинная, а значит пока что реализовать её у меня не получится. Поэтому посмотрим на простые алгоритмы с вычитанием.
Harry Porter's Relay Computer
К сожалению, Гарри не закодировал алгоритм Евклида, а делать это самому мне не захотелось. Ведь вживую послушать как она будет «тикать» не выйдет.
Relay computer «trainer»
Здесь эталонная реализация есть — и она получается также довольно короткой из-за удобной архитектуры команд.
; Euclid's algorithm using repeated subtraction
org 0x00
a skip 1 ; First number
b skip 1 ; Second number
tmp skip 1 ; Tmp variable
org 0x10
euclid st #144, a ; Initialize A
st #233, b ; Initialize B
euclop jeq b, eucdon ; Done ?
st a, tmp
rsbto b, tmp ; A - B -> TMP
jls tmp, over ; A <= B ?
rsbto b, a ; A - B -> A
jmp euclop
over rsbto a, b ; B - A -> B
jmp euclop
eucdon halt
A fistful of relays
Для своего компьютера я смог не только написать программу, но и запустить её. Ниже на видео результат выполнения с трассировкой инструкций (в виде субтитров).
; A, B - operands, A - result
MOVI A, op1
MOVI B, op2
LOOP:
OR F, A, A
JMP Z, END
OR F, B, B
JMP Z, END
SUB F, A, B
JMP C, L1
SUB A, A, B
JMP LOOP
L1:
SUB B, B, A
JMP LOOP
END:
ADD A, A, B
HALT
Какой компьютер лучше?
В таблице ниже — число инструкций для каждой из программ. В скобках указано число инструкций, необходимых непосредственно для вычислений, а не для загрузки операндов или остановки.
Компьютер | Умножение | Алгоритм Евклида |
---|---|---|
HPRC | 25 (23) | |
Trainer | 10 (7) | 11 (8) |
AFOR | 10 (7) | 14 (11) |
Моя реализация алгоритма Евклида получилась чуть похуже из-за проверки обоих операндов на 0. Также в обоих алгоритмах Trainer'а используется инструкция типа «перейти, если регистр равен 0», которой в моём компьютере нет. Сделать ее было бы несложно, но заранее я об этом не подумал.
Что дальше
Вообще-то я хотел рассмотреть ещё и деление, но программа для него получилась в 18 шагов длиной. А напаял я пока только 16 ячеек ПЗУ. Поэтому вернёмся к нему в следующий раз.
Ссылки
» Страница проекта на github
» Подробное описание системы команд
» Первая часть описания моего релейного компьютера
» Вторая часть описания
» Третья часть описания
» Четвёртая часть описания
Комментарии (6)
AbnormalHead
25.12.2017 21:34Я конечно извиняюсь, что не совсем по теме, но в прошлогоднем видео была ёлка, в виде этого года видим ту же самую ёлку. Вот и вопрос —
Вы ёлку то на лето разбирали?
tronix286
Очень классно. Интересно, а если прибавить тактовую? Начинаются сбои?
Dovgaluk Автор
Судя по спецификациям, время включения-выключения должно быть в районе 15мс. Так что запас по скорости довольно приличный, может потом и перепаяю конденсаторы, чтобы было побыстрее.
tronix286
Ну да, все же, кто пытался ШИМить реле, знают что они работают довольно быстро -)
Dovgaluk Автор
Пока идёт отладка всего — низкая скорость будет преимуществом. Хотя более медленный режим я сейчас не включаю. Использую или пошаговый или полную скорость. Так что может действительно оставить текущий в качестве медленного.