Verilike

From PSXDEV
Revision as of 21:56, 12 November 2018 by Org (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Verilike - это специальный скриптовый язык описания цифровой логики. Inspired by Verilog.

Contents

Обзор

Verilike - это транслируемый язык, который предназначен для симуляции цифровых схем. Базовая конструкция - это регистровая передача :

{out1, out2, ..., outN} = cell (expr1, expr2, ..., exprN);

То есть одна регистровая передача имитирует стандартную ячейку или некий кусок схемы.

Как видно, у ячейки может быть несколько выходов. Если выход один - не обязательно заключать его в скобки { }

В качестве входов могут выступать любые другие выражения, в том числе составные регистровые передачи.

Результатом работы компилятора является дерево, узлами которого являются cells, а входными и выходными ветками - соответственно входы и выходы cell. Траверс дерева может начаться с любого узла, все остальные узлы будут выполняться рекурсивно, до тех пор, пока новые значения входов НЕ будут равняться старым. Для защиты от race condition мы добавляем таймаут, на максимальное количество повторов выполнения блоков. При возникновении таймаута дерево признается негодным, а программисту выписывается калабаха.

В лиспе - всё списки (c)

Типы данных

Всего один тип данных : wire.

wire соединяется с другим wire или соединяется с входом или выходом cell.

Из проводов можно делать массивы (шины), например : wire data_bus[8];

При описании модуля к слову wire добавляется ключевое слово input или output, которое позволяет разделить провода на входные и выходные.

Syntax

Скрипт как положено разбирается на токены лексическим анализатором. Токены могут быть следующих типов:

  • Ключевое слово
  • Оператор
  • Число
  • Идентификатор

Ключевые слова

module, endmodule, block, begin, end, wire, input, output

module: Задает новый модуль, в котором содержатся синтезируемые блоки:

module name (input wire a, input wire b, output wire x, output reg y);
...
endmodule

После ключевого слова module указывается имя модуля, которое потом можно использовать в качестве составных ячеек, затем в скобках перечисляются входы и выходы в любом порядке. input может быть с таким же именем, что и output (для двунаправленных проводов).

begin и end просто задают начало и конец группы регистровых передач.

block: определяет чувствительный блок (аналог always в Verilog)

block (clk <= '/', res)
    begin
       ...
    end

В скобках перечисляются входы и значения, при смене НА которые должен активироваться блок. Если значение не указано, то блок активируется при любом изменении входного сигнала.

Всё остальное (циклы, условия) нам не нужно, обойдемся и без них.

Операторы

Операций у нас как таковых нет.

Клиент, который использует Verilike перед симуляцией должен определить "библиотеку" операций.

То есть он должен передать ядру симуляции список ячеек с callback-ами, что делют ячейки. Например NOT:

void cell_not ( var_t * input, var_t * output )
{
    if ( input[0].val[0] == '0' ) output[0].val[0] = '1';
    else output[0].val[0] = '0';
}
 
add_cell ( "not", cell_not, 1 /*количество входов*/, 1 /*количество выходов*/ );

После этого Verilike-скрипт может использовать эту ячейку в своей работе.

Единственное что регистровая передача может быть мгновенной, реализуемой операцией =, а может быть внеблочной <= (непосредственно присваивание производится после выполнения всего блока). Реализуется внеблочное присваивание путём использования теневых значений (shadows): при обычном присваивании значения переменных такие же как и теневые, поэтому после выхода из блока они просто обновляются, внеблочное присваивание помещает значения только в теневые, которые после выполнения блока заменяют старые значения.

Приоритет операций задается скобками.

К операциям также относятся следующие токены : { } ( ) ; , [ ]

Числа

Числа могут быть только целые без знака. Знаковую интерпретацию уже производит сам девайс.

В качестве префикса можно использовать 0b или 0B для двоичной записи и 0x или 0X для шестнадцатеричной. Для восьмеричной записи используется префикс 0... или 0... (0, а затем цифры 0-7). Вообщем почти всё как в си. Разрешается использовать подчеркивание _ для разделение групп цифр (как в Verilog).

Если число не помещается в провод, то производится truncate. Хранение чисел производится поразрядно, lsb first (индексу [0] соответствует нулевой разряд числа)

Значения разрядов (только для систем счисления по основанию 2, 8, 16):

  • Логические уровни : '1' - '9', означающие weak и strong (то есть логическая единица может быть "слабой" и "сильной"). При симуляции "мощность" не учитывается. Это нам нужно для задания стандартных ячеек PSX CPU, которые могут выдавать сигнал разной степени мощности.
  • Земля : '0'
  • Отсоединено (high-Z): 'Z', 'z'
  • Неизвестное значение (undefined): 'X', 'x'
  • Фронт сигнала : '/' - raising (нарастающий, posedge), '\' - falling (спадающий, negedge)

Десятичное задание числа всегда приводит его к использованию двоичного представления разрядами '0' и '1'.

Идентификаторы

Задают имена проводов. Правила для имён обычные.

Видимость : провода определенные внутри блока видны только в нём. Глобальной видимостью обладают только входы и выходы модуля.

При наличии нескольких файлов-скриптов в проекте - глобальные входы и выходы имеют общую видимость.

Хранятся все провода в контексте (аналог таблицы символов). При встрече нового идентификатора ему автоматически назначается тип (провод) и значение по умолчанию (для проводов - это 'z').

У каждого идентификтора есть три "уровня" значений:

  • Старое значение: значение идентификатора на момент начала прогона симуляции блока.
  • Новое значение: значение идентификатора на момент окончания прогона симуляции блока.
  • Теневое значение: используется для внеблочного присваивания.

Реализацию лексического анализатора можно глянуть на SVN : https://code.google.com/p/psxdev/source/browse/trunk/verilike/lexer.c (по моему мнению всё готово :0))

Runtime

Стадии выполнения скрипта разделяются на 2 стадии : компиляцию и трансляцию.

Компиляция переводит программу написанную человеком во внутреннее представление (контекст и дерево регистровых передач).

Трансляция занимается траверсом дерева регистровых передач.

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

Задержка распространения

Propagation delay никак не симулируется, все регистровые передачи выполняются одна за одной. Но используя операцию внеблочного присваивания <= можно организовать параллельную передачу значений схемы.