Войти
ФлеймФорумЖелезо

8-битный "компьютер мечты" (18 стр)

Страницы: 117 18 19 20 21 Следующая »
#255
(Правка: 9:04) 4:33, 17 окт. 2019

Доделал эмулятор Simpleton 3.x до того что тот может выполнять примитивные программки. Исходник: https://yadi.sk/d/-PGx1pEBf_O6kw
Пока делал пришла еще пара мыслей как изменить систему команд.
Во первых - для относительной лёгкости считывания машкодов переставил местами биты instr и ti таким образом, что все поля инструкции хорошо легли на квадры бит и могут легко считываться невооруженных глазом в hex-записи. При этом декодер инструкции стал такой:

    src     = (word & 0b0000000000000111) >> 0;
    srcInd  = (word & 0b0000000000001000) >> 3;
    dst     = (word & 0b0000000001110000) >> 4;
    dstInd  = (word & 0b0000000010000000) >> 7;
    cond    = (word & 0b0000011100000000) >> 8;
    twoOps  = (word & 0b0000100000000000) >> 11;
    cmd     = (word & 0b1111000000000000) >> 12;
Т.е. код инструкции в hex выглядит как KOYX, где K - субкод инструкции, X и Y - операнды (с битом indirect каждый) и O - это условие вместе с битом двухоперандности инструкции (вместе с субкодом инструкции полный код таким образом это пятибитное число).
Для начала и простоты вбиваю инструкции в память виртуальной машины вызовом методов op и data где просто обозначаются поля:
  Machine m;
  m.reset();

  m.op( REG_R0,    OP_MOV,    IMMED ); // DST, OPCODE, SRC
  m.data( 0xFFFF ); // IMMED - значит в следующем слове лежит данное

  m.op( REG_R1,    OP_MOV,    IMMED );
  m.data( 0xCCCC );

  m.op( REG_R2,    OP_MOV,    REG_R0 );

  m.op( IND_IMMED, OP_MOV,    REG_R1 );
  m.data( 0x002A ); // IND_IMMED - значит в следующем слове лежит адрес данного

  m.op( IND_IMMED, OP_MOV,    IND_IMMED );
  m.data( 0x002A );
  m.data( 0x002B );

  m.show();
  m.step();
  m.step();
  m.step();
  m.step();
  m.step();
  m.show();
Т.е.:
R0 = 0xFFFF
R1 = 0xCCCC
R2 = R0
[ 0x002A ] = R1
[ 0x002B ] = [ 0x002A ]
В результате получаем листинг состояния машины до выполнения пяти инструкций и после. До не привожу - там просто всё нули кроме программы в первом столбце, а вот что получилось после:
R0: FFFF  R1: CCCC  R2: FFFF  R3: 0000  R4: 0000  SP: 0000  PC: 000A  FL: 0000
0000: 000E    0010: 0000    0020: 0000    0030: 0000    0040: 0000    0050: 0000    0060: 0000    0070: 0000
0001: FFFF    0011: 0000    0021: 0000    0031: 0000    0041: 0000    0051: 0000    0061: 0000    0071: 0000
0002: 001E    0012: 0000    0022: 0000    0032: 0000    0042: 0000    0052: 0000    0062: 0000    0072: 0000
0003: CCCC    0013: 0000    0023: 0000    0033: 0000    0043: 0000    0053: 0000    0063: 0000    0073: 0000
0004: 0020    0014: 0000    0024: 0000    0034: 0000    0044: 0000    0054: 0000    0064: 0000    0074: 0000
0005: 00F1    0015: 0000    0025: 0000    0035: 0000    0045: 0000    0055: 0000    0065: 0000    0075: 0000
0006: 002A    0016: 0000    0026: 0000    0036: 0000    0046: 0000    0056: 0000    0066: 0000    0076: 0000
0007: 00FF    0017: 0000    0027: 0000    0037: 0000    0047: 0000    0057: 0000    0067: 0000    0077: 0000
0008: 002A    0018: 0000    0028: 0000    0038: 0000    0048: 0000    0058: 0000    0068: 0000    0078: 0000
0009: 002B    0019: 0000    0029: 0000    0039: 0000    0049: 0000    0059: 0000    0069: 0000    0079: 0000
000A: 0000    001A: 0000    002A: CCCC    003A: 0000    004A: 0000    005A: 0000    006A: 0000    007A: 0000
000B: 0000    001B: 0000    002B: CCCC    003B: 0000    004B: 0000    005B: 0000    006B: 0000    007B: 0000
000C: 0000    001C: 0000    002C: 0000    003C: 0000    004C: 0000    005C: 0000    006C: 0000    007C: 0000
000D: 0000    001D: 0000    002D: 0000    003D: 0000    004D: 0000    005D: 0000    006D: 0000    007D: 0000
000E: 0000    001E: 0000    002E: 0000    003E: 0000    004E: 0000    005E: 0000    006E: 0000    007E: 0000
000F: 0000    001F: 0000    002F: 0000    003F: 0000    004F: 0000    005F: 0000    006F: 0000    007F: 0000
Регистры заполнены как и ожидается, в памяти по адресам 2A и 2B тоже размножилось содержимое R1 - так что операция MOV выполняется.


#256
(Правка: 22:40) 22:39, 23 окт. 2019

Сделал шнурок для подключения ZX-UNO к программатору, и потратил пару вечеров, чтобы вывести картинку на монитор.  Конечно, краешком глаза смотрел в имеющиеся исходники. Чой-то мне кажется, немного испанцы перемудрили, можно было сделать проще, - особенно генерацию композитного сигнала синхронизации (банально перексорить hsync и vsync, вместо кучи IF'ов)... ну и старт растра со значения счётчика hcnt=400... зачем?! Ведь намного проще считать адресацию экрана спектрума, если начинать адресацию hcnt=0

картинка того, что получилось -

Изображение

скриншот сигналов vsync/csync/blank

Изображение

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

+ Показать

больше всего времени ушло понять, почему не синхронизируется картинка на мониторчике для автомобильный камеры заднего вида (а телик спокойно хавает),  - очень капризно относится к сигналам HBLANK/VBLANK, всю голову поломал.

#257
17:19, 24 окт. 2019


https://hackaday.io/project/168025-suite-16

Suite-16 is an experimental 16-bit TTL cpu designed to explore cpu architectures and the interactions between hardware, firmware and software needed to make a functioning computer. The name Suite-16 is a word play on Steve Wozniak's 16-bit virtual cpu "Sweet-16" written in 6502 assembly language to augment the Apple II when performing 16-bit operations. It is well documented here: http://www.6502.org/source/interpreters/sweet16.htm#Instruction_Descriptions_

#258
(Правка: 18:59) 18:51, 24 окт. 2019

Гы, доделал эмулятор Simpleton до того что он может уже успешно парсить ассемблер и исполнять его.
Например сейчас элементарный пример вот такой:

    m.parseStart();
    int line = 0;
    m.parseLine( line++, "start R0 = $FFFF" ); // в R0 грузим константу $FFFF
    m.parseLine( line++, " R1 = $CCCC" ); // в R1 грузим константу $CCCC
    m.parseLine( line++, " R0 -= R1" ); // из R0 вычитаем R1 и заносим результат в R0
    m.parseLine( line++, " [ first ] = R0" ); // в ячейку памяти по адресу first заносим R0
    m.parseLine( line++, " [ second ] =+1 [ first ]" ); // в ячейку памяти по адресу second заносим инкремент ячейки first
    m.parseLine( line++, " R0 = R0" ); // NOP и ноль - эмулятор останавливается на команде NOP
    m.parseLine( line++, "first R0 = R0" ); // метка first ячейки с данными 0
    m.parseLine( line++, "second R0 = R0" ); // метка second ячейки с данными тоже 0 (DW пока не делал)
    m.parseEnd();

    m.show();

    while ( m.mem[ m.reg[ REG_PC ] ] != 0 )  // nop as stop
      m.step();
    m.show();
т.е. код на чистом ассемблере:
start  R0         =   $FFFF      // в R0 грузим константу $FFFF
       R1         =   $CCCC      // в R1 грузим константу $CCCC
       R0         -=  R1         // из R0 вычитаем R1 и заносим результат в R0
       [ first ]  =   R0         // в ячейку памяти по адресу first заносим R0
       [ second ] =+1 [ first ]  // в ячейку памяти по адресу second заносим инкремент ячейки first
       R0         =   R0         // NOP и ноль - эмулятор останавливается на команде NOP
first  R0         =   R0         // метка first ячейки с данными 0
second R0         =   R0         // метка second ячейки с данными тоже 0 (DW пока не делал)
успешно исполняется и даёт после выполнения всех команд такую карту регистров и памяти:
R0:3333  R1:CCCC  R2:0000  R3:0000  R4:0000  SP:0000  PC:000A  FL:0000
0000:000E  0010:0000  0020:0000  0030:0000  0040:0000  0050:0000  0060:0000  0070:0000
0001:FFFF  0011:0000  0021:0000  0031:0000  0041:0000  0051:0000  0061:0000  0071:0000
0002:001E  0012:0000  0022:0000  0032:0000  0042:0000  0052:0000  0062:0000  0072:0000
0003:CCCC  0013:0000  0023:0000  0033:0000  0043:0000  0053:0000  0063:0000  0073:0000
0004:3801  0014:0000  0024:0000  0034:0000  0044:0000  0054:0000  0064:0000  0074:0000
0005:00F0  0015:0000  0025:0000  0035:0000  0045:0000  0055:0000  0065:0000  0075:0000
0006:000B  0016:0000  0026:0000  0036:0000  0046:0000  0056:0000  0066:0000  0076:0000
0007:10FF  0017:0000  0027:0000  0037:0000  0047:0000  0057:0000  0067:0000  0077:0000
0008:000B  0018:0000  0028:0000  0038:0000  0048:0000  0058:0000  0068:0000  0078:0000
0009:000C  0019:0000  0029:0000  0039:0000  0049:0000  0059:0000  0069:0000  0079:0000
000A:0000  001A:0000  002A:0000  003A:0000  004A:0000  005A:0000  006A:0000  007A:0000
000B:3333  001B:0000  002B:0000  003B:0000  004B:0000  005B:0000  006B:0000  007B:0000
000C:3334  001C:0000  002C:0000  003C:0000  004C:0000  005C:0000  006C:0000  007C:0000
000D:0000  001D:0000  002D:0000  003D:0000  004D:0000  005D:0000  006D:0000  007D:0000
000E:0000  001E:0000  002E:0000  003E:0000  004E:0000  005E:0000  006E:0000  007E:0000
000F:0000  001F:0000  002F:0000  003F:0000  004F:0000  005F:0000  006F:0000  007F:0000
Здесь видно что PC дошёл до 000A и остановился - это где первый искуственный NOP (R0 = R0) - check.
В R0 разница между FFFF и CCCC = 3333 - check.
В R1 - CCCC - check.
По адресу 000B хранится 3333 - это метка first - check.
По следующему адресу - метке second хранится увеличенное на 1 значение в first - 3334 - check!
Можно посмотреть в коды инструкций - адреса (а это между прочим forward reference для которых надо было запоминать адреса которые надо поправить после конца парсинга) 000B и 000C явно видно в ячейках с инструкциями по адресам 0006 и 0008-0009.
Забавное ощущение когда свой ассемблер делаешь виртуальной несуществующей машины. :D
Парсер и генератор кода конечно примитивный - лишь бы откровенных ошибок с подстановкой совсем уж неверных типов лексем не на свои места не было. Например косвенная адресация просто как флаг взводится и сбрасывается при встрече символов [ и ] поэтому такой код будет валидным: [ R0 = R1 ] и эквивалентен [ R0 ] = [ R1 ] (строго говоря валидно и [ R0 = R1
Но тем не менее в мнемониках кодировать весьма удобно становится. :)
Когда еще будет время реализую условия и попробую делать циклы.

#259
(Правка: 21:35) 21:18, 24 окт. 2019

P.S.
Проапгрейдил ассемблер до поддержки ключевых слов =, org и dw с нюансами.
Программа теперь может выглядеть так:

       R0         =     $FFFF
       R1         =     $CCCC
       R0         -=    R1
       [ first ]  =     R0
       [ second ] =+1   [ first ]
       [ third ]  +=    16
       dw         0     // DEF WORD 0 помещает в текущую ячейку компиляции константу
       org        $0020 // компилируем теперь начиная с адреса $0020
first  dw         0     // first и second теперь заданы
second dw         0     // как два нулевых слова в памяти
       org        $0030 // смещаем адрес компиляции в $0030
third  dw         forth // пример что в качестве DEF WORD слова можно указать символ, причём forward
forth  =          $1000 // через = значение символа задаётся напрямую без записи данных в память
       dw         1     // эта единица в памяти программы следует сразу за $1000 (third)
Основные моменты - регистрозависимость всех идентификаторов и ключевых слов.
Имена регистров: R0-R4, R5 (он же SP), R6 (он же PC), R7 (он же FLAGS).
Машинные команды имеют вид
DEST OP SRC
Где SRC это один из регистров, константа/символ или адрес задаваемый как регистр или константа/символ в квадратных скобках.
OP это операторы в стиле Си:
= присваивание
=+1 инкремент
=-1 декремент
<?> сравнение
+=
-=
+c= то же что и += с учетом флага переноса
-c= то же что и -= с учетом флага переноса
DEST может быть всем тем же что и SRC кроме константы/символа (не в квадратных скобках)
Числовые константы/литералы или десятичные или начинаются с $ и тогда являются шестнадцатеричными.

Если строка начинается не с пробельного символа, то создаётся символ.
Если он предшествует машинной инструкции или dw, то в него записывается её адрес.
Если он предшествует знаку =, то в него записывается константа или значение символа по правую часть от знака. Формульная математика пока не поддерживается вообще.
Если он предшествует ключевому слову org, то он будет равнятся адресу куда переводит компиляцию этот org.
org переводит запись генерируемых инструкций на указанный адрес (origin)
dw прописывает в текущую ячейку данное - оно может быть или константой или символом.
В стиле ассемблера Zilog 80 (и не в стиле ассемблера Intel) имя символа в чистом виде означает адрес ячейки памяти если это метка, а не значение в этой памяти. Чтобы адресовать ячейку надо использовать квадратные скобки.
Т.е.

some_addr = $1000
R0 = some_addr // в R0 запишется $1000
R0 = [ some_addr ] // в R0 запишется значение в ячейке с адресом $1000
some_addr = R0 // такое вообще запрещено, т.к. в констансту нельзя писать, надо:
[ some_addr ] = R0 // а вот это запишет R0 в ячейку памяти

В общем обновил исходник по ссылке https://yadi.sk/d/-PGx1pEBf_O6kw и можно побаловаться - он берет исходник программы из файла source.asm, ассемблирует и исполняет до первого nop (dw 0), если неожиданно кому то вдруг интересно, но пока без условных переходов не так интересно.

#260
(Правка: 21:35) 21:34, 24 окт. 2019

Кошернo ли ассемблер воротить без мнемоник?
Иначе же C-- получается.
Когда свой эмулятор x80 писал, тоже соблазн был начхать на классику и намнемонить выражения с математическими операторами. Но, как говорится, делайте несерьёзные дела с серьёзным выражением лица.

#261
21:41, 24 окт. 2019

Alikberov
> Кошернo ли ассемблер воротить без мнемоник?

Мечта детства опять таки.
Всегда задавался вопросом почему современные ассемблеры не сделали с человеческим лицом. Если в древности еще как можно более упрощенный синтаксис был довольно таки критичной штукой для минимизации парсеров, но потом то уже зачем было за это цепляться? A = B уже давно намного более понятно и вменяемо выглядит чем MOV A, B - у последнего синтаксиса даже такая ужасная особенность, что на разных ассемблерах меняются местами источник и приёмник просто по прихоти создателей мнемоник. Потому что нет понятных общечеловеческих базисов подразумевающих очевидность.
Хотя некоторые довольно замысловатые инструкции вполне можно писать в "операторном стиле", но у архитектуры Simpleton их просто нет - его архитектура это на 100% Си-стайл.

#262
21:59, 24 окт. 2019

=A=L=X=
> Всегда задавался вопросом почему современные ассемблеры не сделали с
> человеческим лицом.

Здесь так принято... (подход, имею в виду).

=A=L=X=
> Если в древности еще как можно более упрощенный синтаксис был довольно таки
> критичной штукой для минимизации парсеров

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

Alikberov
> Кошернo ли ассемблер воротить без мнемоник?
> Иначе же C-- получается.

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

#263
22:03, 24 окт. 2019

=A=L=X=
A я так в 1998 свой язык и придумал, когда сидел в DOS и дизассемблерил дампы драйверов в тетрадь.
(Не было ни отладчика, ни ассемблера. Один Volcov Commander с Hex-вьювером!)
И как-то мне наскучило в тетради писать mov да add. И я тупо стал всё переводить в выражения. И так появилась идея языка (про Си-- я не знал ещё и не узнаю до 2005 года!), который я потом написал на Турбо Паскале 5 и зарегистрировал авторское право на него в 2004 (вот дурак, а?) в местном бюро.

А вот с x80 я таки думал и зарезервировал там в таблице мнемоник и знак оператора, чтобы иметь возможность добавить режим выбора между мнемониками и значками.
(Вот push/pop тяжело обозначить красиво. Предлагали как «$<-ax» и «ax<-$»… Хотя, можно тупо - запятой.
То есть «push si; push di; call foo; and ax,cx; add [bx],ax» просто записать как «[bx]+=foo(ax, si, di)&cx», где запятые - и есть push'ы, а ax - регистр, который передаётся за foo, чтобы не рвать цепочку.)

#264
(Правка: 22:18) 22:16, 24 окт. 2019

0iStalker
> где парсер, как таковой вообще не нужен

Да уже давно там везде полноценные парсеры, т.к. есть математические сложные выражения со скобками и прочим, которые пусть и в compile-time, но считают всё честно и математически, поэтому это уже какая то экономия на спичках, как имхо.
Я правда думаю, что сложные выражения constExpr если и реализую, то хотя бы изначально только в конструкции вида

symbol = expression
как раз потому что в нём сразу понятно что после = должно быть только constExpr без лишних анализов. А вот потом уже вычисленный символ можно подставлять в команды, места в программе он не занимает.
Единственное что всякие монструозные разросшиеся исторически за десятки лет системы команд типа x86 со всякими PACKUHLOWBYTEMACEX от всяких SSE4 под простое выражение наверное не подгонишь. Бывает тоже нередко. Но такое и в программе на Си выглядело бы как функция/интринсик и в принципе ладно, чего уж.

Alikberov
> И я тупо стал всё переводить в выражения.

Вот вот - абсолютно такое же и со мной произошло когда анализирвал какие то машкоды на спектруме - вместо ld a, b гораздо быстрее было чиркануть в бумажку a = b - (и уж тем более hl = bc) и что главное - заметно понятнее на беглый взгляд. Именно так же - при записи машкодов в тетрадку возникло желание упростить, ускорить и улучшить.

> Вот push/pop тяжело обозначить красиво

Ну вот опять же в Simpleton это просто some = [ SP ] и [ SP ] = some из-за упрощенной системы команд, так что проблем вообще ноль.

#265
22:44, 24 окт. 2019

=A=L=X=
> Единственное что всякие монструозные разросшиеся исторически за десятки лет
> системы команд типа x86 со всякими PACKUHLOWBYTEMACEX от всяких SSE4 под
> простое выражение наверное не подгонишь.

+ :-D
#266
23:13, 24 окт. 2019

=A=L=X=
> A = B уже давно намного более понятно и вменяемо выглядит чем MOV A, B
Если бы я делал современный макроассемблер, то он у меня был бы интерпретируемым языком, в котором команда

A = B;
это команда, которая в переменную времени интерпретации (компиляции) A помещает содержимое переменной B, а
mov(A, B);
добавляет соответствующий машинный код в финальный листинг.
При этом A и B — это произвольные переменные типа регистра, т. е. можно делать что-то типа:
var A = $rax, B = $rcx;
for(var i = 0; i < 8; i++) {
  mov(A, B);
  var C = A;  A = B;  B = C;
}
#267
(Правка: 23:51) 23:49, 24 окт. 2019

}:+()___ [Smile]
Насколько я знаю FASM очень сильно прокачан в этом направлении и в него за счет продвинутых возможностей макросов уровня метапрограммирования даже такие вещи как что является таргетом - дллка для виндовс или исполяемый файл для линукс делается на уровне инклюдов библиотечных файлов где все эти секции, таблицы импорта-экспорта и сцепления с символами всех этих вещей делается на макросах. Очень навороченная система позволяющая и новые опкоды команд со сложным составным форматом добавлять через макросы.

#268
(Правка: 1:03) 0:55, 25 окт. 2019

Вообщe-то я не понимаю, почему элементарная вещь в XXI веке так и не автоматизирована?
А именно -  ассемблер. По-любому, любой ЯВУ может выход давать не бинарями, а ассемблером.
А значит, должен быть, по-идее, инструмент на уровне ИИ, который по описанному ему гипотетическому процессору генерирует сам код ассемблера, который может запускаться на том же гипотетическом процессоре и спокойно работать.

Вот, скажем, мой x80.
Почему я для него должен надрываться и писать ассемблер с нуля (он тут встроен, если кто забыл - 250 строк), когда мог бы, например, в FASM'е просто слегка сменить конфиги.
Фактически, от x86 мой x80 не так и сильно отличается, так как я сторонник синтаксиса ассемблера Intel. Лишь нужно перекодировать все эти mov/add/jmp на другой байт-код и всё.
(Значительная часть x80-BIOS выглядит как x86-код и отлаживалась в той же MS-VisualStudio 6!)

Но, нет же!
Вот здесь тонны кода написали ради этого!

#269
5:37, 25 окт. 2019

Alikberov

Ну, FASM в этом смысле не идеален, потому что базовый формат инструкций в него вшитый всё-же Intel x86.
Но макроассемблер там охрененен по сложности и по сути позволяет в компил-тайме делать в точности то о чём пишет }:+()___ [Smile].
Например вот отсюда: https://flatassembler.net/docs.php?article=ufasm

As it was already noted on example of the DB directive, the output of assembler may not be a program at all. In such case the interpretation of assembler as an compiler is lost and the result of assembly is just plainly the output of interpreted language. Copy the below source code to file interp.asm to see it on an example:

file 'interp.asm'
repeat $
  load A byte from %-1
  if A>='a' & A<='z'
    A = A-'a'+'A'
  end if
  store byte A at %-1
end repeat

This is a program written entirely in flat assembler's interpreted language, using some of its advanced features. It first places the entire contents of interp.asm file (its own source) at current position (it is always 0 when starting assembly) and then for each byte of that file (the $ is the value of current position, so after putting the whole file at position 0, the $ becomes equal to the size of this file) it repeats the process of: taking this byte, converting it to upper-case with help of the simple condition check and writing the modified byte back.


Т.е. да, он прямо в compile-time оперирует интерпретатором который можнт даже файл пропарсить с обработкой его данных и встраиванием их в текущие адреса сборки. Чуть выше примеры с циклами по переменной времени компиляции A есть тоже.

Страницы: 117 18 19 20 21 Следующая »
ФлеймФорумЖелезо