Об этом блоге

Об этом блоге

Добра тебе, читатель.
Это запасной аэродром Великого Позитроника.
А основная писанина — в тележеньке.

среда, 27 марта 2013 г.

Пилим восьмибитный процессор. Часть седьмая, баголовная.

Я две недели не публиковал описаний прогресса разработки процессора.
Тем не менее, это не значит, что я им не занимаюсь - занимаюсь, хотя, пожалуй, уже без той остервенелой увлечённости, что в начале, плюс, отвлекаюсь на другие занятия, да ещё сидел без клавиатуры несколько дней. Но даже с учётом всего этого, две недели - срок вполне достаточный для накопления очередной порции интересных доработок.
Я так думал, да. Всего за пару дней я переделал микросхемы, определяющие тип операндов и их количество, переделал командный блок (переупорядочил команды, добавил новые), добавил новые регистры - и очевидных проблем при этом не проявлялось. Вылезали, конечно, кое-какие баги, не найденные раньше, но ничего серьёзного.
Так что я начал было заниматься командами перехода по памяти, которые сплошь однобайтные - и оказалось, что мой управляемый счётчик неправильно работает на операции, описываемой как for ($=0;$i==0;$i++) - то есть команды без операндов им не воспринимались, что вело к неправильному разбору всей дальнейшей памяти.
Окей, проблема локализована, надо только переделать счётчик...
Я потратил на это неделю.

Несложная, казалось бы, задача - а схема не давалась ни в какую. Подход "разбить на подзадачи и решить по отдельности" не работал - решённые подзадачи просто не склеивались в целое. Расписать логику схемы в виде набора логических функций, сократить вывод и реализовать получившееся схемой вовсе нельзя (вернее, можно, но это задача довольно громоздкая, и я бы только запутался).
Тогда я применил другой подход. Если задача не даётся - займись чем-нибудь другим. Пусть подсознание само решает задачу, тебе останется только проверять выдаваемые им варианты. Что, вы так не умеете? Ха-ха, неудачники!
Прошло время, подсознание действительно выдало вариант решения (уже не помню какого, да и не важно). Я открыл схему, прогнал тестовую программу, чтобы освежить в памяти структуру - а схема-то и не работает.
То есть, допустим, какой-то простой код, вроде mov a,1h не работал совсем. При этом вся логическая цепочка работала как надо, на всех контактах были правильные значения, а нужный регистр не устанавливался и всё. Странно, как и то, что раньше-то это работало - более того, при откате не предыдущую версию схемы работало тоже.
Значит что? Значит накосячил где-то при доработках, и проглядел косяк. Попробовал поискать - нету. А различий с последним рабочим образцом накопилось уже достаточно, и разобраться, какие из них являются причиной бага уже довольно трудно.
В общем, я плюнул, откатился на последнюю рабочую версию, и стал переносить доработки понемногу, усиленно тестируя схему после каждого изменения. Снова переделал микросхемы и командный блок, всё выглядело чисто. В недоумении я хотел уже было браться за блок регистров, но тут мне "повезло" (если так можно выразиться) - одна из тестовых программ давала сбой. Программа примерно такая:

MOV REG1,VALUE ;изменяем любой регистр, не обязательно через MOV
SHL REGx,VALUE ;регистр может быть указан любой, не обязательно тот, что в предыдущей команде

Первая команда выполнялась безупречно, а на момент чтения (даже не выполнения!) первого операнда второй команды REG1 обнулялся. Путём перебора, выяснилось, что такой же баг вызывают некоторые другие операнды во второй команде, например SUB и XOR. А, скажем, SUM проходила нормально. Поэкспериментировав, я выяснил, что и не команда даже влияет на появление бага, а полный байт команда+типы параметров; причём последовательности значений "бажных" байт не выглядели хоть как-то осмысленно. Например, значения с 53h до 57h приводили к ошибке, значения в промежутке 58h-63h ошибки не вызывали, а значения выше 63h - опять приводили к багу.
Логики никакой. Да даже и не в том, дело, какая последовательность вызывает ошибку, дело в том, что процессор попросту не мог такую ошибку допустить. У него есть нога EXECUTE, сигнал на которой вызывает выполенение буферизованной команды. Если на ноге нет сигнала - состояние процессора не меняется. Вот эта нога выделена на развёрнутой схеме:


Клик для увеличения

Как видите, n-транзисторы просто отсекают процессор от буфера, так что совершенно не имеет значения, что там "вовне" - если, конечно, на ноге ноль. А на ноге был ноль (сигнал появлялся только тогда, когда счётчик доходил до нужного значения), но процессор всё время вёл себя по разному.
Ошибки не было - и, в то же время, она была. Я потратил уйму времени и нервов, пытаясь разобраться, и был уже близок к тому, чтобы сдаться, признав, что ошибка где-то в Logisim.
Хорошо, что подсознание и тут предложило мне вариант. "Может быть - подумал я - счётчик и вправду выдаёт на выход EXECUTE сигнал, но тут же убирает его?".

Взглянем снова на схему счётчика:


В начальном состоянии на элемент И подаётся единица с компаратора и ноль с тактового входа. При первом такте с тактового входа придёт единица, а с компаратора - ноль. Если значения меняются одновременно, то после И реузльтат всё равно не изменится (0&1==1&0), как и требуется. Но вдруг одновременности не существует?
Забегая вперёд, скажу, что так оно и есть. В справке Logisim, в разделе "Задержки логических элементов" читаем в описании схемы, похожей на нашу:

Но элементы НЕ не реагируют мгновенно на сигналы на входах в реальности, и в Logisim - тоже. В результате, когда значение на входе этой цепи изменяется с 0 на 1, элемент И будет короткое время видеть 1 на обоих входах, и выдаст на выходе 1 на короткое время. Вы не увидите этого на экране.

Так как же проверить такое поведение? Вариант предложен там же, в справке (к собственному удовольствию я дошёл до него самостоятельно): подключить к выходу триггер, и посмотреть, изменится ли его значение, или нет:


Бах! Триггер переключается - а, значит, ошибка вызвана именно тем, что счётчик подаёт сигнал исполнения не тогда, когда требуется. Что же, процитирую справку ещё раз:

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

Почему же этот баг не проявлял себя раньше? Точно не знаю, но думаю, что просто не успел - слишком проста была схема процессора. Впервые проявлять он себя начал, видимо, уже после усложнения, что и заставило меня начать разбираться.
Устранить баг можно, но не нужно. Счётчик всё равно требуется переделывать. А переделка его мне никак не давалась - о чём я уже жаловался выше.
Помучавшись так и эдак, я применил такой подход: описал требуемую логику счётчика на псевдокоде (хотя, какой, к чёрту, псевдокод, это PHP), и попробовал потом описать этот код логической схемой (это всё равно, что строить блок-схему). Код получился такой:

  1. $COMMANDS=array(2,10,12,0,2,11,13,1,15,1,18,0,2,9,7);
  2. $return=array();
  3. $set=TRUE;
  4. $EXEC_FLAG=FALSE;
  5. $i=0;
  6. while (TRUE) {
  7. if ($EXEC_FLAG) {
  8. echo "-->EXEC ";
  9. print_r ($return);
  10. $EXEC_FLAG=FALSE;
  11. $return=array();
  12. }
  13. if ($set) {
  14. $for=$COMMANDS[$i];
  15. echo "!$for ";
  16. $lim=$for;
  17. $set=FALSE;
  18. $cnt=0;
  19. }
  20. echo ":$cnt ";
  21. $return[$cnt]=$COMMANDS[$i];
  22. if ($cnt==$lim) {
  23. $EXEC_FLAG=TRUE;
  24. $set=TRUE;
  25. $i++;
  26. continue;
  27. }
  28. $cnt++;
  29. $i++;
  30. }

$COMMANDS - массив, содержащий количества операндов при интерпретации ячейки памяти, как команды. Остальное, думаю, очевидно.
А вот схема, которая этот код реализует. Названия элементов соответствуют названиям переменных в коде, у регистра lim срабатывание переключено на высокий уровень (т.е. он обновляется не по переключению такта, а всегда, пока на тактовом входе есть сигнал). EXEC_FLAG и set - T-триггеры (в отличии от D-триггера они не устанавливают поданное на вход значение, а переключают собственное, т.о. их удобно использовать в качестве "флагов").


И, пока что, всё выглядит, будто он работает, как надо, и мой мозг не будет взорван. Потому, если больше не обнаружится столь же серьёзных и трудноуловимых ошибок, результат окончательной доработки процессора появится уже скоро.
Продолжение следует.

Комментариев нет: