x86128: (Default)
Ещё в начале апреля приехала ко мне китайская версия Raspberry Pi Pico:



Припаял не очень аккуратно :) всё-таки паяльник в руках держал ещё в школе и по роду деятельности, не приходилось с ним иметь дело.

На борту платки имеется 16Мб флеш-памяти и RGB светодиод на базе WS2812B.

ExpandRead more... )
x86128: (Default)
Рассмотрим теперь простой алгоритм преобразования арифметических (или логических) выражений в машинный код БЭСМ-6.

Допустим есть вот эта программа:
MODULE arith;

var
	a : integer;

begin
	a := 5  - 3 * 8 div 6;
	writeint(a);
	writeln
END arith.


После прохода парсера и построения промежуточного вида в кодах виртуальной машины имеем такой код:
 ('LABEL', 'arith_main'),
 ('ALLOC', 1),
Вычисление выражения (без оптимизации) 
('CLOAD', 0),
 ('CLOAD', 1),
 ('CLOAD', 2),
 ('BINOP', '*'),
 ('CLOAD', 3),
 ('BINOP', 'DIV'),
 ('BINOP', '-'),
Сохранение в переменную А
 ('VSTOR', -1, 'a'),

Загрузка на стэк значения А
 ('VLOAD', -1, 'a'),
 ('SYSCALL', 'writeint'),
 ('DEALLOC', 1),

 ('SYSCALL', 'writeln'),
 ('STOP', '12345'),

 ('LABEL', 'const_table'),
 ('WORD', 5),
 ('WORD', 3),
 ('WORD', 8),
 ('WORD', 6),
 ('WORD', 0),
 ('LABEL', 'locals_base'),
 ('LABEL', 'stack_base')]


Суть алгоритма заключается в том, что мы рассматриваем аккумулятор как вершину стека, соотв. линейно просматриваем код первой машины и начинаем заменять команды манипуляции со стеком на команды манипуляции с аккумулятором.
Загрузка значения на аккумулятор взводит флаг того что он занят.
Если два раза подряд попалась команда загрузки на аккумулятор, мы выталкиваем значение в стэк БЭСМ-6. Если же за командой выталкивания следует арифметическая операция, то операция выталкивания заменяется на арифметическую команду БЭСМ с указанием адреса аргумента. Таким образом отбрасывание вершины стека сводится к сбросу флага занятости аккумулятора. Операции вызова процедуры при занятом аккумуляторе автоматически сбрасывают его значение в стэк.

TEXT:
00000:        	VTM  	21    	,M1
00001:        	VTM  	21    	,M15
00002:        	VTM  	16    	,M2
00003:        	UJ   	4    	 

00004: arith_main UTM  	1     	,M15

Вычисление a := 5  - 3 * 8 div 6;
00005:        	XTA  	0     	,M2
00006:        	XTS  	1     	,M2
00007:        	A*X  	2     	,M2
00008:        	A/X  	3     	,M2
00009:        	X-A  	0     	,M15
Запись в А
00010:        	ATX  	-1    	,M1
Вызов процедуры. Аргумент на стэк (т.к. XTA -1, M1 соотв. предыдущей команде, она удаляется)
00011:        	ATX  	0     	,M15
00012:        	*77  	0    	 
Корректировка стэка после вызова
00013:        	UTM  	-1    	,M15
00014:        	*77  	2    	 
00015:        	STOP 	12345	 
Константы
00016:        	WORD 	5    	 
00017:        	WORD 	3    	 
00018:        	WORD 	8    	 
00019:        	WORD 	6    	 
00020:        	WORD 	0    	 

CONSTANT TABLE:
00000:    	5    	5
00001:    	3    	3
00002:    	8    	8
00003:    	6    	6

PROCEDURE TABLE
  arith_main: 4

1
STOP at PC=15 code=12345 ir=0


Итого:
00005:        	XTA  	0     	,M2
00006:        	XTS  	1     	,M2
00007:        	A*X  	2     	,M2
00008:        	A/X  	3     	,M2
00009:        	X-A  	0     	,M15

Против метода курильщика:
00005:       	XTA  	0     	,M2
00006:       	ATX  	0     	,M15
00007:       	XTA  	1     	,M2
00008:       	ATX  	0     	,M15
00009:       	XTA  	2     	,M2
00010:       	ATX  	0     	,M15
00011:       	XTA  	-2    	,M15
00012:       	A*X  	-1    	,M15
00013:       	ATX  	-2    	,M15
00014:       	UTM  	-1    	,M15
00015:       	XTA  	3     	,M2
00016:       	ATX  	0     	,M15
00017:       	XTA  	-2    	,M15
00018:       	A/X  	-1    	,M15
00019:       	ATX  	-2    	,M15
00020:       	UTM  	-1    	,M15
00021:       	XTA  	-2    	,M15
00022:       	A-X  	-1    	,M15
00023:       	ATX  	-2    	,M15
00024:       	UTM  	-1    	,M15
00025:       	XTA  	0     	,M15


Как говорится, почувствуйте разницу 😅️

Дальше необходимо разобраться со ссылочными VAR-аргументами доступ к которым осуществляется по адресу значения и починить вычисление константных выражений.
x86128: (Default)
Продолжаю эксперименты со строительством компилятора.
Реализовал преобразование кода виртуальной машины в код виртуальной целочисленной БЭСМ-6 методом “курильщика” так же известного как Poor Man’s codegen 😅️.

В качестве программы для тестирования возьмем вычисление выражения:

var
    a : integer;

begin
    a := 5  - 3 * 8 div 6;
    writeint(a);
    writeln


В обратной польской записи получается так:

5 3 8 * 6 / - >a


Преобразуется в такой код “первой” виртуальной машины:

 ('CLOAD', 0),
 ('CLOAD', 1),
 ('CLOAD', 2),
 ('BINOP', '*'),
 ('CLOAD', 3),
 ('BINOP', 'DIV'),
 ('BINOP', '-'),
 ('VSTOR', -1, 'a'),
 ('VLOAD', -1, 'a'),
 ('SYSCALL', 'writeint'),
 ('DEALLOC', 1),
 ('SYSCALL', 'writeln'),
 ('STOP', '12345'),

Таблица констант:
 ('LABEL', 'const_table'),
 ('WORD', 5),
 ('WORD', 3),
 ('WORD', 8),
 ('WORD', 6),
 ('WORD', 0),
 ('LABEL', 'locals_base'),
 ('LABEL', 'stack_base')]

Место для локальной переменной a:
 ('WORD', 0),
 ('LABEL', 'locals_base'),
 ('LABEL', 'stack_base')]


Метод курильщика заключается в том, что каждую команду первой машины мы преобразуем буквально в код БЭСМ без каких либо оптимизаций, будто выполняется условный эмулятор первой ВМ на процессоре БЭСМ.

Пусть у нас регистр M1 указывает на последнюю локальную переменную текущего кадра + 1, регистр M2 - на начало блока констант, а М15 - на первый пустой элемент стека, который растет в сторону увеличения адресов.

Работать метод будет тоже со стеком, но пока без учета архитектуры БЭСМ, где аккумулятор является вершиной стека, поэтому код генерируется такой:

00005:           XTA      0         ,M2
00006:           ATX      0         ,M15
00007:           XTA      1         ,M2
00008:           ATX      0         ,M15
00009:           XTA      2         ,M2
00010:           ATX      0         ,M15
00011:           XTA      -2        ,M15
00012:           A*X      -1        ,M15
00013:           ATX      -2        ,M15
00014:           UTM      -1        ,M15
00015:           XTA      3         ,M2
00016:           ATX      0         ,M15
00017:           XTA      -2        ,M15
00018:           A/X      -1        ,M15
00019:           ATX      -2        ,M15
00020:           UTM      -1        ,M15
00021:           XTA      -2        ,M15
00022:           A-X      -1        ,M15
00023:           ATX      -2        ,M15
00024:           UTM      -1        ,M15
00025:           XTA      0         ,M15
Сохранение результата
00026:           ATX      -1        ,M1
Печать числа
00027:           XTA      -1        ,M1
00028:           ATX      0         ,M15
00029:           *77      0         
00030:           UTM      -1        ,M15
Печать перевода строки
00031:           *77      2         
00032:           STOP     12345     


Вот такой вот глупый код, но при этом рабочий.

Рассмотрим как это можно записать с учетом того что аккумулятор является вершиной стека:

5 3 8 * 6 / - >a

XTA =5
XTS =3
A*X =8
A/X =6
X-A ,M15
ATX =a


Как говорится, разница на лицо. Есть над чем поработать.
x86128: (Default)
В построении интерпретатора-компилятора я продвинулся до момента когда необходимо преобразовать код виртуальной машины в код еще более близкий к железу, т.е. к МЭСМ-6.

Процессор БЭСМ-6, в отличии от современных собратьев, имеет интересную особенность: отсутствие привычных команд ветвления анализирующих условия (флажки) по равенству/неравенству, больше или равно, меньше. Вместо этого каждая команда, которая затрагивает аккумулятор устанавливает режим “омега”, но основе которого формируется признак “омега” на основе значения которого выполняются команды условного перехода UZA и U1A (переход по нулю и 1-це значения “омега”).

Признак “омега” формируется следующим образом:
switch (ω mode) {
	case additive: ω = (A[41] != 0); /* A < 0 */ break;
	case multiplicative: ω = (A[48] != 1); /* abs(A) < 0.5 */ break;
	case logical: ω = (A[48:1] != 0); /* A != 0 */ break;
	case 0: ω = 1;
}

Соответственно, чтобы запрограммировать:
if (a < b) {
  …
} else {
 else_block
}


Необходимо, закодировать a < b так:
xta a,M1
a-x b,M1
uza else_block   ; u1a для a >= b
...


Для a == b:
xta a, M1
aex b, M2
u1a else_block   ; uza для a != b


Что как раз очевидно следовало из первого и третьего case.

Но что делать с a <= b ?
Первое что пришло в голову так это выполнить две проверки, сначала на равенство a == b, а потом a < b ведь для этого у нас есть готовые признаки “омега” …

А нужно то было всего лишь применить школьную программу и переставить местами a и b. Маху дал :)

Таким образом:

Для b < a так:
xta b,M1
a-x a,M1
uza else_block   ; u1a для b >= a, что эквивалентно a <= b


Но, как заметил Леонид, для удобства в БЭСМ-6 есть команда x-a (обратное вычитание), получим:

Для a > b:
xta a,M1
x-a b,M1
uza else_block   ; u1a для a <= b


Слишком уж наше поколение стало избалованное процессорами с большим количеством инструкций, когда стоит только подумать, а уже есть какая-нибудь VFMADDSUB213PS, с умными компиляторами к ним.
x86128: (Default)
Сделал простейший шаг оптимизатора по промежуточному представлению, который сворачивает арифметические операции если они совершаются над константами в выражениях.

Например:
CONST 10
CONST 5
BINOP +


Свернется просто в
CONST 15
x86128: (Default)
Внёс мелкие исправления и добавил выдачу листинга помимо трассировки. Дальше буду смотреть в сторону принципов изложенных в книге дракона по компиляторам.

Для примера:
module towers;

var
    n : integer;

procedure write2ln(x, y: integer);
begin
    writeint(x);
    writespace(1);
    writeint(y);
    writeln
end write2ln;

procedure hanoy(n, x, y, z: integer);
begin
    if n > 0 then
        hanoy(n-1, x, z, y);
        write2ln(x, y);
        hanoy(n-1, z, y, x)
    end

end hanoy;

begin
    n := 3;
    hanoy(n, 1, 2, 3)

end towers.


Листинг выглядит так:
TEXT:
00000:    write2ln VLOAD            -2
00001:             SYSCALL    writeint
00002:             DEALLOC           1
00003:             CONST             1
00004:             SYSCALL  writespace
00005:             DEALLOC           1
00006:             VLOAD            -1
00007:             SYSCALL    writeint
00008:             DEALLOC           1
00009:             SYSCALL     writeln
00010:             RETURN             
00011:       hanoy VLOAD            -4
00012:             CONST             0
00013:             RELOP             >
00014:             BR_ZERO          L0
00015:             VLOAD            -4
00016:             CONST             1
00017:             BINOP             -
00018:             VLOAD            -3
00019:             VLOAD            -1
00020:             VLOAD            -2
00021:             CALL             11
00022:             DEALLOC           4
00023:             VLOAD            -3
00024:             VLOAD            -2
00025:             CALL              0
00026:             DEALLOC           2
00027:             VLOAD            -4
00028:             CONST             1
00029:             BINOP             -
00030:             VLOAD            -1
00031:             VLOAD            -2
00032:             VLOAD            -3
00033:             CALL             11
00034:             DEALLOC           4
00035:          L0 
00036:             RETURN             
00037: towers_main ALLOC             1
00038:             CONST             3
00039:             VSTOR            -1
00040:             VLOAD            -1
00041:             CONST             1
00042:             CONST             2
00043:             CONST             3
00044:             CALL             11
00045:             DEALLOC           4
00046:             STOP          12345

CONSTANT TABLE:

PROCEDURE TABLE
    write2ln: 0
       hanoy: 11
 towers_main: 37


Результат работы:
1 2
1 3
2 3
1 2
3 1
3 2
1 2
STOP at 46, CYCLES=316, max stack size: 27
x86128: (Default)
Доброго времени!

Продолжаю эксперименты с компиляторо-строением.

Добавил возможность возвращать результаты работы процедуры через VAR-аргументы.

Поэтому подобный код компилируется и работает:

MODULE Samples;
const
    pass = 12345;
    fail = 54321;

var
    t : integer;

procedure gcd(a,b : integer; var res : integer);
begin
    while b > 0 do
        res := b;
        b := a mod b;
        a := res
    end
end gcd;

begin
    gcd(27+25*3-(10 div 5),15, t);
    if t = 5 then
        halt(pass)
    else
        halt(fail)
    end

END Samples.


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

Схема такая:

| old frame | arg0 | arg1 | v0 | v1 | v2 | old_bp | return address | free RAM cells |
|-----------+------+------+----+----+----+--------+----------------+----------------|
|           |      |      |    |    |    | ^ BP   |                | ^ SP           |


Сделал я это для того, чтобы была возможность работы с рекурсией, но боком вышло то, что глобальные переменные по отношению к телу процедуры “дали прикурить” т.к. их адреса во время компиляции программы неизвестны (так же из-за возможности объявлять вложенные процедуры (своего рода замыкания)). Можно было ввести некоторый GP который бы показывал на локальные переменные вышестоящей области видимости, но тогда пришлось бы организовывать какой-то механизм многократной относительной адресации. Понятно, что эта задача решаема, но в книге по Оберону я пока не добрался до соотв. главы. Если запретить рекурсию, то задача размещения и адресации переменных становится тривиальной, т.к. все адреса можно вычислить прямо на этапе компиляции.

Результатом работы компилятора является такой питонячий объект:
{'c_mem': [12345, 54321],
 'consts': {'fail': {'line': 4, 'offset': 1, 'val': 54321},
            'pass': {'line': 3, 'offset': 0, 'val': 12345}},
 'main': 'Samples_main',
 'name': 'Samples',
 'p_text': [('LABEL', 'L0'),
            ('VLOAD', -2, 'b'),
            ('CONST', 0),
            ('RELOP', '>'),
            ('BR_ZERO', 'L1'),
            ('VLOAD', -2, 'b'),
            ('RSTOR', -1, 'res'),
            ('VLOAD', -3, 'a'),
            ('VLOAD', -2, 'b'),
            ('BINOP', 'MOD'),
            ('VSTOR', -2, 'b'),
            ('RLOAD', -1, 'res'),
            ('VSTOR', -3, 'a'),
            ('BR', 'L0'),
            ('LABEL', 'L1'),
            ('RETURN', ''),
            ('ALLOC', 1),
            ('CONST', 27),
            ('CONST', 25),
            ('CONST', 3),
            ('BINOP', '*'),
            ('BINOP', '+'),
            ('CONST', 10),
            ('CONST', 5),
            ('BINOP', 'DIV'),
            ('BINOP', '-'),
            ('CONST', 15),
            ('ADR_LOAD', -1, 't'),
            ('CALL', 0, 'gcd'),
            ('DEALLOC', 3),
            ('VLOAD', -1, 't'),
            ('CONST', 5),
            ('RELOP', '='),
            ('BR_ZERO', 'L3'),
            ('CLOAD', 0, 'pass'),
            ('SYSCALL', 'halt'),
            ('DEALLOC', 1),
            ('BR', 'L3'),
            ('LABEL', 'L2'),
            ('CLOAD', 1, 'fail'),
            ('SYSCALL', 'halt'),
            ('DEALLOC', 1),
            ('LABEL', 'L3'),
            ('STOP', '12345')],
 'proc_tab': {'Samples_main': {'offset': 16},
              'gcd': {'arg_sz': 3,
                      'args': {'a': {'offset': 2,
                                     'typ': 'integer',
                                     'var': False},
                               'b': {'offset': 1,
                                     'typ': 'integer',
                                     'var': False},
                               'res': {'offset': 0,
                                       'typ': 'integer',
                                       'var': True}},
                      'consts': {},
                      'offset': 0,
                      'v_size': 0,
                      'vars': {}}},
 'v_size': 1,
 'vars': {'t': {'offset': 0, 'size': 1, 'typ': 'integer'}}}


Интерес представляет поле p_text, в котором находится текст программы в виде инструкций для абстрактной виртуальной машины. Я выбрал одноадресное промежуточное представление чтобы быть ближе к инструкциям машины МЭСМ-6, третий аргумент у некоторых операций носит косметический характер и сохраняется для удобочитаемости человеком.

Трассировка вычисления НОД для чисел 100 и 15:
OP=('ALLOC', 1) PC=16 BP=1 SP=1 D_MEM=[0]
OP=('CONST', 27) PC=17 BP=1 SP=2 D_MEM=[0, 27]
OP=('CONST', 25) PC=18 BP=1 SP=3 D_MEM=[0, 27, 25]
OP=('CONST', 3) PC=19 BP=1 SP=4 D_MEM=[0, 27, 25, 3]
OP=('BINOP', '*') PC=20 BP=1 SP=3 D_MEM=[0, 27, 75]
OP=('BINOP', '+') PC=21 BP=1 SP=2 D_MEM=[0, 102]
OP=('CONST', 10) PC=22 BP=1 SP=3 D_MEM=[0, 102, 10]
OP=('CONST', 5) PC=23 BP=1 SP=4 D_MEM=[0, 102, 10, 5]
OP=('BINOP', 'DIV') PC=24 BP=1 SP=3 D_MEM=[0, 102, 2]
OP=('BINOP', '-') PC=25 BP=1 SP=2 D_MEM=[0, 100]
OP=('CONST', 15) PC=26 BP=1 SP=3 D_MEM=[0, 100, 15]
OP=('ADR_LOAD', -1, 't') PC=27 BP=1 SP=4 D_MEM=[0, 100, 15, 0]
OP=('CALL', 0, 'gcd') PC=28 BP=4 SP=6 D_MEM=[0, 100, 15, 0, 1, 29]
OP=('LABEL', 'L0') PC=0 BP=4 SP=6 D_MEM=[0, 100, 15, 0, 1, 29]
OP=('VLOAD', -2, 'b') PC=1 BP=4 SP=7 D_MEM=[0, 100, 15, 0, 1, 29, 15]
OP=('CONST', 0) PC=2 BP=4 SP=8 D_MEM=[0, 100, 15, 0, 1, 29, 15, 0]
OP=('RELOP', '>') PC=3 BP=4 SP=7 D_MEM=[0, 100, 15, 0, 1, 29, True]
OP=('BR_ZERO', 'L1') PC=4 BP=4 SP=6 D_MEM=[0, 100, 15, 0, 1, 29]
OP=('VLOAD', -2, 'b') PC=5 BP=4 SP=7 D_MEM=[0, 100, 15, 0, 1, 29, 15]
OP=('RSTOR', -1, 'res') PC=6 BP=4 SP=6 D_MEM=[15, 100, 15, 0, 1, 29]
OP=('VLOAD', -3, 'a') PC=7 BP=4 SP=7 D_MEM=[15, 100, 15, 0, 1, 29, 100]
OP=('VLOAD', -2, 'b') PC=8 BP=4 SP=8 D_MEM=[15, 100, 15, 0, 1, 29, 100, 15]
OP=('BINOP', 'MOD') PC=9 BP=4 SP=7 D_MEM=[15, 100, 15, 0, 1, 29, 10]
OP=('VSTOR', -2, 'b') PC=10 BP=4 SP=6 D_MEM=[15, 100, 10, 0, 1, 29]
OP=('RLOAD', -1, 'res') PC=11 BP=4 SP=7 D_MEM=[15, 100, 10, 0, 1, 29, 15]
OP=('VSTOR', -3, 'a') PC=12 BP=4 SP=6 D_MEM=[15, 15, 10, 0, 1, 29]
OP=('BR', 'L0') PC=13 BP=4 SP=6 D_MEM=[15, 15, 10, 0, 1, 29]
OP=('LABEL', 'L0') PC=0 BP=4 SP=6 D_MEM=[15, 15, 10, 0, 1, 29]
OP=('VLOAD', -2, 'b') PC=1 BP=4 SP=7 D_MEM=[15, 15, 10, 0, 1, 29, 10]
OP=('CONST', 0) PC=2 BP=4 SP=8 D_MEM=[15, 15, 10, 0, 1, 29, 10, 0]
OP=('RELOP', '>') PC=3 BP=4 SP=7 D_MEM=[15, 15, 10, 0, 1, 29, True]
OP=('BR_ZERO', 'L1') PC=4 BP=4 SP=6 D_MEM=[15, 15, 10, 0, 1, 29]
OP=('VLOAD', -2, 'b') PC=5 BP=4 SP=7 D_MEM=[15, 15, 10, 0, 1, 29, 10]
OP=('RSTOR', -1, 'res') PC=6 BP=4 SP=6 D_MEM=[10, 15, 10, 0, 1, 29]
OP=('VLOAD', -3, 'a') PC=7 BP=4 SP=7 D_MEM=[10, 15, 10, 0, 1, 29, 15]
OP=('VLOAD', -2, 'b') PC=8 BP=4 SP=8 D_MEM=[10, 15, 10, 0, 1, 29, 15, 10]
OP=('BINOP', 'MOD') PC=9 BP=4 SP=7 D_MEM=[10, 15, 10, 0, 1, 29, 5]
OP=('VSTOR', -2, 'b') PC=10 BP=4 SP=6 D_MEM=[10, 15, 5, 0, 1, 29]
OP=('RLOAD', -1, 'res') PC=11 BP=4 SP=7 D_MEM=[10, 15, 5, 0, 1, 29, 10]
OP=('VSTOR', -3, 'a') PC=12 BP=4 SP=6 D_MEM=[10, 10, 5, 0, 1, 29]
OP=('BR', 'L0') PC=13 BP=4 SP=6 D_MEM=[10, 10, 5, 0, 1, 29]
OP=('LABEL', 'L0') PC=0 BP=4 SP=6 D_MEM=[10, 10, 5, 0, 1, 29]
OP=('VLOAD', -2, 'b') PC=1 BP=4 SP=7 D_MEM=[10, 10, 5, 0, 1, 29, 5]
OP=('CONST', 0) PC=2 BP=4 SP=8 D_MEM=[10, 10, 5, 0, 1, 29, 5, 0]
OP=('RELOP', '>') PC=3 BP=4 SP=7 D_MEM=[10, 10, 5, 0, 1, 29, True]
OP=('BR_ZERO', 'L1') PC=4 BP=4 SP=6 D_MEM=[10, 10, 5, 0, 1, 29]
OP=('VLOAD', -2, 'b') PC=5 BP=4 SP=7 D_MEM=[10, 10, 5, 0, 1, 29, 5]
OP=('RSTOR', -1, 'res') PC=6 BP=4 SP=6 D_MEM=[5, 10, 5, 0, 1, 29]
OP=('VLOAD', -3, 'a') PC=7 BP=4 SP=7 D_MEM=[5, 10, 5, 0, 1, 29, 10]
OP=('VLOAD', -2, 'b') PC=8 BP=4 SP=8 D_MEM=[5, 10, 5, 0, 1, 29, 10, 5]
OP=('BINOP', 'MOD') PC=9 BP=4 SP=7 D_MEM=[5, 10, 5, 0, 1, 29, 0]
OP=('VSTOR', -2, 'b') PC=10 BP=4 SP=6 D_MEM=[5, 10, 0, 0, 1, 29]
OP=('RLOAD', -1, 'res') PC=11 BP=4 SP=7 D_MEM=[5, 10, 0, 0, 1, 29, 5]
OP=('VSTOR', -3, 'a') PC=12 BP=4 SP=6 D_MEM=[5, 5, 0, 0, 1, 29]
OP=('BR', 'L0') PC=13 BP=4 SP=6 D_MEM=[5, 5, 0, 0, 1, 29]
OP=('LABEL', 'L0') PC=0 BP=4 SP=6 D_MEM=[5, 5, 0, 0, 1, 29]
OP=('VLOAD', -2, 'b') PC=1 BP=4 SP=7 D_MEM=[5, 5, 0, 0, 1, 29, 0]
OP=('CONST', 0) PC=2 BP=4 SP=8 D_MEM=[5, 5, 0, 0, 1, 29, 0, 0]
OP=('RELOP', '>') PC=3 BP=4 SP=7 D_MEM=[5, 5, 0, 0, 1, 29, False]
OP=('BR_ZERO', 'L1') PC=4 BP=4 SP=6 D_MEM=[5, 5, 0, 0, 1, 29]
OP=('LABEL', 'L1') PC=14 BP=4 SP=6 D_MEM=[5, 5, 0, 0, 1, 29]
OP=('RETURN', '') PC=15 BP=1 SP=4 D_MEM=[5, 5, 0, 0]
OP=('DEALLOC', 3) PC=29 BP=1 SP=1 D_MEM=[5]
OP=('VLOAD', -1, 't') PC=30 BP=1 SP=2 D_MEM=[5, 5]
OP=('CONST', 5) PC=31 BP=1 SP=3 D_MEM=[5, 5, 5]
OP=('RELOP', '=') PC=32 BP=1 SP=2 D_MEM=[5, True]
OP=('BR_ZERO', 'L3') PC=33 BP=1 SP=1 D_MEM=[5]
OP=('CLOAD', 0, 'pass') PC=34 BP=1 SP=2 D_MEM=[5, 12345]

Program halted with code: 12345
SUCCESS
x86128: (Default)
Теперь интерпретатор может переварить более сложный тест:

MODULE Samples;
const
    pass = 12345;
    fail = 54321;

var t1 : integer;

procedure test1;
begin
    if t1 = 10 then
        t1 := 9
    elsif t1 < 0 then
        halt(fail)
    else
        halt(fail)
    end
end test1;

procedure test2;
begin
    if t1 = 10 then
        halt(fail)
    elsif t1 < 0 then
        halt(fail)
    else
        t1 := -1
    end
end test2;

procedure test3;
begin
    if t1 = 10 then
        halt(fail)
    elsif t1 < 0 then
        t1 := pass
    else
        halt(fail)
    end
end test3;

begin
    (* initializing test *)
    t1 := 10;

    (* do modifications of global var t1 *)
    test1;
    test2;
    test3;

    (* t1 must be pass *)
    halt(t1)

END Samples.


Получаем на выходе:
{'decls': {'consts': {'fail': 54321, 'pass': 12345},
           'procs': {'test1': {'args': [],
                               'decls': {'consts': {}, 'procs': {}, 'vars': {}},
                               'text': [('LOAD', 't1'),
                                        ('CONST', 10),
                                        ('RELOP', '='),
                                        ('BR_ZERO', 'L0'),
                                        ('CONST', 9),
                                        ('STOR', 't1'),
                                        ('BR', 'L2'),
                                        ('LABEL', 'L0'),
                                        ('LOAD', 't1'),
                                        ('CONST', 0),
                                        ('RELOP', '<'),
                                        ('BR_ZERO', 'L1'),
                                        ('LOAD', 'fail'),
                                        ('CALL', 'halt'),
                                        ('BR', 'L2'),
                                        ('LABEL', 'L1'),
                                        ('LOAD', 'fail'),
                                        ('CALL', 'halt'),
                                        ('LABEL', 'L2')]},
                     'test2': {'args': [],
                               'decls': {'consts': {}, 'procs': {}, 'vars': {}},
                               'text': [('LOAD', 't1'),
                                        ('CONST', 10),
                                        ('RELOP', '='),
                                        ('BR_ZERO', 'L3'),
                                        ('LOAD', 'fail'),
                                        ('CALL', 'halt'),
                                        ('BR', 'L5'),
                                        ('LABEL', 'L3'),
                                        ('LOAD', 't1'),
                                        ('CONST', 0),
                                        ('RELOP', '<'),
                                        ('BR_ZERO', 'L4'),
                                        ('LOAD', 'fail'),
                                        ('CALL', 'halt'),
                                        ('BR', 'L5'),
                                        ('LABEL', 'L4'),
                                        ('CONST', 1),
                                        ('UNARY', '-'),
                                        ('STOR', 't1'),
                                        ('LABEL', 'L5')]},
                     'test3': {'args': [],
                               'decls': {'consts': {}, 'procs': {}, 'vars': {}},
                               'text': [('LOAD', 't1'),
                                        ('CONST', 10),
                                        ('RELOP', '='),
                                        ('BR_ZERO', 'L6'),
                                        ('LOAD', 'fail'),
                                        ('CALL', 'halt'),
                                        ('BR', 'L8'),
                                        ('LABEL', 'L6'),
                                        ('LOAD', 't1'),
                                        ('CONST', 0),
                                        ('RELOP', '<'),
                                        ('BR_ZERO', 'L7'),
                                        ('LOAD', 'pass'),
                                        ('STOR', 't1'),
                                        ('BR', 'L8'),
                                        ('LABEL', 'L7'),
                                        ('LOAD', 'fail'),
                                        ('CALL', 'halt'),
                                        ('LABEL', 'L8')]}},
           'vars': {'t1': (0, 'integer')}},
 'name': 'Samples',
 'text': [('CONST', 10),
          ('STOR', 't1'),
          ('CALL', 'test1'),
          ('CALL', 'test2'),
          ('CALL', 'test3'),
          ('LOAD', 't1'),
          ('CALL', 'halt'),
          ('STOP', '12345')]}


И результат работы:
Program halted with code: 12345
SUCCESS


Заодно перелопатил на использование объектов вместо туплей чтобы избавиться от конструкций типа e[1][0][1] в пользу st.then_block
x86128: (Default)
Добавил компиляцию оператора IF.

MODULE Samples;
var t1 : integer;

begin

    t1 := 10;
    if t1 = 10 then
        writeint(10)
    elsif t1 < 0 then
        writeint(-5)
    else
        writeint(44)
    end

END Samples.



MODULE Samples
{'decls': {'consts': {}, 'procs': {}, 'vars': {'t1': (0, 'integer')}},
 'name': 'Samples',
 'text': [('CONST', '10'),
          ('STOR', 't1'),
          ('LOAD', 't1'),
          ('CONST', '10'),
          ('RELOP', '='),
          ('BR_ZERO', 'L0'),
          ('CONST', '10'),
          ('CALL', 'writeint'),
          ('BR', 'L3'),
          ('LABEL', 'L0'),
          ('LOAD', 't1'),
          ('CONST', '0'),
          ('RELOP', '<'),
          ('BR_ZERO', 'L1'),
          ('CONST', '5'),
          ('UNARY', '-'),
          ('CALL', 'writeint'),
          ('BR', 'L3'),
          ('LABEL', 'L1'),
          ('CONST', '44'),
          ('CALL', 'writeint'),
          ('LABEL', 'L3'),
          ('STOP', '')]}
10
STOP at 22
x86128: (Default)
В качестве эксперимента затеял сделать минимально возможный компилятор языка похожего на Oberon. Так сказать пощупать руками компиляторостроение.

Почитал по диагонали профильную литературу, но рукам, конечно, не терпится сразу приступить к делу :).

Сегодня заработал самый минимально живущий интерпретатор-компилятор, который я изготовил на Python.

Поскольку код крайне сырой, планирую несколько итераций по причёсыванию кода (замена туплей на объекты и т.д.) чтобы привести его в более идеоматический Python.

Пока нет оператора IF, циклов кроме WHILE, поддержки массивов (поэтому и строк), составных типов, только целые типы - только хардкор. Так же нет тестов.

Парсер LL(1) грамматики, на основе входного потока токенов от лексера, строит подобие синтаксического дерева. Далее "дерево" обходится компилятором который строит код для абстрактной стековой машины. Далее виртуальная машина выполняет код используя стек данных и стек фреймов (куда напресовываются рабочие области процедур (locals)).

MODULE Samples;

procedure gcd(a,b : integer);
    var t : integer;
begin
    while b > 0 do
        t := b;
        b := a mod b;
        a := t
    end;

    writeint(a)
end gcd;

begin
    gcd(27+25*3-(10 div 5),15)

END Samples.


На выходе имеем готовое к употреблению интерпретатором:

MODULE Samples
{'decls': {'consts': {},
           'procs': {'gcd': {'args': [('a', 'integer'), ('b', 'integer')],
                             'decls': {'consts': {},
                                       'procs': {},
                                       'vars': {'a': (0, 'integer'),
                                                'b': (0, 'integer'),
                                                't': (0, 'integer')}},
                             'text': [('STOR', 'b'),
                                      ('STOR', 'a'),
                                      ('LABEL', 'L0'),
                                      ('LOAD', 'b'),
                                      ('CONST', '0'),
                                      ('RELOP', '>'),
                                      ('BR_ZERO', 'L1'),
                                      ('LOAD', 'b'),
                                      ('STOR', 't'),
                                      ('LOAD', 'a'),
                                      ('LOAD', 'b'),
                                      ('BINOP', 'mod'),
                                      ('STOR', 'b'),
                                      ('LOAD', 't'),
                                      ('STOR', 'a'),
                                      ('BR', 'L0'),
                                      ('LABEL', 'L1'),
                                      ('LOAD', 'a'),
                                      ('CALL', 'writeint')]}},
           'vars': {}},
 'name': 'Samples',
 'text': [('CONST', '27'),
          ('CONST', '25'),
          ('CONST', '3'),
          ('BINOP', '*'),
          ('BINOP', '+'),
          ('CONST', '10'),
          ('CONST', '5'),
          ('BINOP', 'div'),
          ('BINOP', '-'),
          ('CONST', '15'),
          ('CALL', 'gcd'),
          ('STOP', '')]}


Что в результате дает:
5
STOP at 11
x86128: (Default)
Обнаружилась полезная утилитка qrencode для генерирования QR-кодов прям из консоли linux:

qrencode -t ansiutf8 'Hello, world!!!'



Таким образом сейчас можно в некоторые программы переносить начальные настройки, например, настройки туннелей в wireguard (или nextcloud), а сам код отправить почтой домашним или родственникам чтобы они щелкнули смартфоном, кому настраивать собственную VPS-ку может быть сложно.

В дополнение к посту https://x86128.dreamwidth.org/1575.html
x86128: (Default)
В качестве небольшого pet-project состряпал на Python клон МЭСМ-6 и ассемблера к ней.

Машина проходит основные тесты от МЭСМ-6. В папке examples лежат не столько примеры кода на ассемблере, сколько недо тесты ассемблера. По сути рабочий там hello.asm который печатает в stdout "hello, world!!!"


# hello world using loop
org 1
ptr prn0 32767

# loop setup
vtm -15,2
lbl loop
xta hello+15,2
atx prn0
vlm loop,2
stop 0o12345,6
dorg 0o2000
arr hello "Hello, world!!!\n"


Скомпилировать его можно так:

python3 asm/asm.py -i examples/hello.asm


и запустить:

python3 pymesm.py -i examples/hello.oct


Hello, world!!!
CPU halted at 00003 with 12345
Success
Simulation finished.
Остальные тесты лежат в папке test/

Код реализации МЭСМ местами умышленно кучерявый :))))))))), например:

popcount(x) = bin(x).count("1") # раз Python то почему бы и нет!

Синтаксис ассемблера отличается от общепринятых.
Делал с ручной токенизацией по принципу "команда аргумент" для того чтобы понимать на практике во что превращается LL(1) грамматика принятая в языках типа Pascal или Go.
Хотел прикинуть сложность возможной реализации языка Оберон для МЭСМ.

https://github.com/x86128/pymesm


x86128: (Default)
Доброго дня!

В целях борьбы с разного рода замедлением и блокировкой от известного министерства, народ состряпал простой гайд: https://www.cyberciti.biz/faq/ubuntu-20-04-set-up-wireguard-vpn-server/

Начиная с Ubuntu 22.04 шаг №11 обязателен

Краткий пересказ

1. Берем обычную VPS-ку с Ubuntu 20.04
2. Обновляем систему и ставим пакеты

apt update
apt upgrade
apt install wireguard

3. делаем ключи для сервера

umask 077; wg genkey | tee privatekey | wg pubkey > publickey


получили два файлика privatekey и publickey назначение которых понятно из названия

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

5. командой ip a смотрим как называется интерфейс с Интернетом и запоминаем адрес (в моем случае это eth0, у вас может быть enps).

6. создаем файл /etc/wireguard/wg0.conf с настройками сервера

[Interface]
## внутренний адрес сервера ##
Address = 192.168.6.1/24
## Правила для iptables чтобы пошел NAT
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
## номер порта сервера на который подключаются клиенты
ListenPort = 41194

## частный ключ сервера (privatekey) ##
PrivateKey = iNsdfgjsdkfjgsdjflgjsdlfkjgnitYWwJHEs=

[Peer]
## публичный ключ клиента (содержимое publickey клиента)
PublicKey = sEhgpzgsdfsdfsdfJIsdfsdfV1PxHi7OC3Dk=
## внутренний адрес клиента
AllowedIPs = 192.168.6.3/32


Секций peer может быть несколько с разными публичными ключами клиентов

7. стартуем wireguard на сервере и добавляем его в автозапуск

sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
sudo systemctl status wg-quick@wg0


8. делаем конфиг для клиента

[Interface]
## Частный ключ клиента (содержимое privatekey) ##
PrivateKey = EsdfgjksdlfkhlsdfgsdfsbvcxbxcvbxcvbxcvbxcvbA=

## Внутренний адрес клиента ##
Address = 192.168.6.3/24
DNS = 8.8.8.8

[Peer]
## публичный ключ сервера ##
PublicKey = vDmsdfgsdfgsdfgsdfgsdfgsdfgsdfgFqeCM=

## Чтобы в VPN уходил весь трафик ##
AllowedIPs = 0.0.0.0/0

## Вместо X.X.X.X ставьте адрес вашего VPS сервера ##
Endpoint = X.X.X.X:41194

## Key connection alive ##
PersistentKeepalive = 15


9. конфиг клиента подкладываем на десктоп или отправляем почтой на телефон
в приложении достаточно выполнить импорт этого файла и всё сразу работает
Например, генерируем qr код прямо из консоли 
qrencode -t ansiutf8 < wg.conf
и сканируем в приложении wireguard

10. приложение на Andriod позволяет выбрать какие приложения отправлять в VPN - это очень удобно, чтобы например youtube не отжирал трафик и процессор телефона.

11. на всякий случай можно проверить что работает маршрутизация
В файлах /etc/sysctl.conf или /etc/ufw/sysctl.conf должна быть не закоменченная строчка строчка net.ipv4.ip_forward=1

x86128: (Default)
Продолжаю осваивать Quartus.

Для работы с внутренней флешкой MAX10 напрямую, решил собрать SoC с процессором Nios2.

Собирая Hello World с Nios2 вылазили, по-мелочи, всякие странные ошибки типа таких:


Хотя я и не просил:


Заглядываем в мастер систем:


Глобальные настройки тоже все в порядке, везде галочки выключены.

Смотрим в отчет:



Сохраняем проект. В дизайнере платформы ставим снимаем галочки на преинициализации памяти, сохраняем и перегенерируем скрипты. Закрываем квартус. Открываем собираем проект - вуаля всё в порядке :)

Но дальше следующие грабли. Мастер генерирования HAL на базе Eclipse падает где угодно при показе File open dialog. Пришлось запускаться снова на Windows.

Генерируем файл описания платформы и проверяем что адреса областей памяти в Linker script верные и совпдают с теми что были в Platform Designer:



Все что нужно попадает в dram, а не flash.

Собираем Hello world и смотрим на него через nios2-terminal.exe (который цепляется сам к JTAG UART)



Запускаем на Nios2 такой код:
#include <stdio.h>
#include <stdint.h>
 
#define _MMIO_DWORD(mem_addr) (*(volatile uint32_t *)(mem_addr))
 
#define BIT0 0x1
#define BIT1 (0x1 << 1)
#define BIT2 (0x1 << 2)
#define BIT3 (0x1 << 3)
#define BIT4 (0x1 << 4)
#define BIT5 (0x1 << 5)
#define BIT6 (0x1 << 6)
#define BIT7 (0x1 << 7)
#define BIT8 (0x1 << 8)
#define BIT9 (0x1 << 9)
 
#define BIT23 (0x1 << 23)
 
 
#define F_CSR _MMIO_DWORD(0x89000)
#define F_CCR _MMIO_DWORD(0x89004)
 
#define F_SECTOR1(addr) _MMIO_DWORD(0x0+addr)
 
void print_status() {
    uint32_t t = F_CSR;
    printf("Flash is ");
    switch(& 0x3) {
        case 0: printf("IDLE "); break;
        case 1: printf("BUSY_ERASE "); break;
        case 2: printf("BUSY_WRITE "); break;
        case 3: printf("BUSY_READ "); break;
    }
 
    printf("\nWrite protect status:\n");
    printf("  Sector ID 1 is ");
    if (& BIT5) printf("protected"); else printf("open");
    printf("\n  Sector ID 2 is ");
    if (& BIT6) printf("protected"); else printf("open");
    printf("\n  Sector ID 3 is ");
    if (& BIT7) printf("protected"); else printf("open");
    printf("\n  Sector ID 4 is ");
    if (& BIT8) printf("protected"); else printf("open");
    printf("\n  Sector ID 5 is ");
    if (& BIT9) printf("protected"); else printf("open");
 
    printf("\nLast operation status (if any, default - failed)\n");
    printf("Read is ");
    if (& BIT2) printf("successful"); else printf("failed");
    printf("\nWrite is ");
    if (& BIT3) printf("successful"); else printf("failed");
    printf("\nErase is ");
    if (& BIT4) printf("successful"); else printf("failed");
    printf("\n");
}
 
void print_control() {
    uint32_t t = F_CCR;
    printf("Sector ID 1 is ");
    if (& BIT23) printf("write protected\n"); else printf("open\n");
}
 
int main()
{
    print_status();
    F_CCR = 0xffffffff;
    print_control();
    F_CCR = 0xffffffff ^ BIT23;
    print_control();
    printf("Data from address 0x0 is %08X\n", F_SECTOR1(0));
    printf("Writing data to address 0x0...\n");
    F_SECTOR1(0) = 0xcafebabe;
    printf("Waiting for IDLE...\n");
    while(F_CSR & 0x3);
    print_status();
    printf("Data from address 0x0 is %08X\n", F_SECTOR1(0));
}
 


Всё работает, ура!!!

x86128: (Default)
Без длительной пересборки проекта с нуля можно быстро обновить содержимое блоков памяти из командной строки:

quartus_cdb --update_mif "project name"
quartus_asm "project name"


А затем выполнить загрузку:

CABLE_NAME   ?= "USB-Blaster"
PROJECT_NAME ?= de10-lite

quartus_pgm -c $(CABLE_NAME) -m JTAG -o "p;$(PROJECT_NAME).sof"



https://www.intel.com/content/www/us/en/programmable/support/support-resources/knowledge-base/solutions/rd12062004_8707.html
x86128: (Default)
Попалась хорошая статья о том как завести на линуксе инструменты Альтеры.

После установки почему то не прописался PATH поэтому добавляем ручками.
Возможно будет необходимо установить некоторые 32-битные библиотеки, поскольку не все инструменты альтеры переведены на 64 бита.

Конкретно в моем случае на Ubuntu 18.04 x86-64 не было прав доступа у пользователя до USB-blaster

Вылечивается так:
Создаем файл: /etc/udev/rules.d/51-altera-usb-blaster.rules

С таким содержимым:
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6001", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6002", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6003", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6010", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6810", MODE="0666"


Перетыкаем плату.

Перегружаем jtag сервер: killall jtagd

Программатор радует:


Очень важно чтобы путь установки Quartus был написан с использованием маленьких букв, иначе будут виснуть мастера IP-ядер. https://www.intel.com/content/www/us/en/programmable/support/support-resources/knowledge-base/solutions/rd12062009_421.html
x86128: (Default)
Спешу поделиться радостью: прямо перед праздничными выходными почта довезла плату с FPGA Altera Max10 в исполнении Terasic de10-lite.



Базовые хеловордлы пройдены: светодиоды, ключи, кнопки, HEX и писк через бузер освоены. Под парами лежат модули RS232 на 3.3v и модуль SD-карточек.

Версия бузера у меня buzzer он немного суров, т.к. имеет усилитель на транзисторе 2TY без ограничивающего резистора, поэтому ток протекающий через него получается великоват и он греется. Надо быть внимательным с полярностью управляющего сигнала.

Для МЭСМ-6 понадобится модуль PLL и блоки памяти M9K.

Вгрузка памяти программ и данных будет организована пока через mif-файл. Скрипт перевода oct файлов в mif готов.

В ближайшее время начну вкорячивать МЭСМ-6 в DE10-LITE.
Page generated Oct. 22nd, 2025 05:57 am
Powered by Dreamwidth Studios