vak: (Улыбка)
[personal profile] vak
Традиционно считается, что моделирование цифровой аппаратуры это технически очень сложная задача. Программы-симуляторы для Verilog и VHDL стоят жутких денег. SystemC хоть и бесплатный, но сущий ад для разработчиков. Хуже того: уровень абстракции имеет тенденцию повышаться, и сейчас мало кто вообще понимает, что происходит в симуляторе на самом нижнем уровне. Все нужные алгоритмы были описаны в научных статьях в начале 90-х и похоронены в библиотеках под толстым слоем пыли. В современных книжках в лучшем случае рассказывается про очередь событий и упоминаются дельта-циклы. Живем как питекантропы, поддерживая огонь в костре и не умея его зажечь.

Изначально я задумал это как задачку для продвинутых студентов: сваять в качестве курсовой работы простейший RTL-симулятор для цифровых схем. Но когда стал придумывать 'правильный ответ', оказалось все не так просто. Копать пришлось несколько глубже, чем может осилить студент. Поэтому пришлось превратить это просто в красивую иллюстрацию технологии симуляции. Исходники можно взять здесь: https://github.com/sergev/vak-opensource/tree/master/hardware/rtlsim

Основные установки:
1) Язык Си. Максимальная простота и ясность.
2) Простые структуры данных. Никаких объектов или шаблонов.
3) Событийно-управляемое моделирование: максимальная эффективность.
4) Динамическая память не используется.
5) Сопроцессы реализованы посредством стандартных POSIX-функций семейства getcontext(), входящих в библиотеку glibc.

Вся реализация уложилась примерно в 200 строчек Си-шного кода. Это, наверное, самая красивая программа, которую мне доводилось делать.

Пример симуляции есть в предыдущем посте: https://vak.dreamwidth.org/213855.html
Можно сравнить с тем же примером на Верилоге: https://vak.dreamwidth.org/213634.html


Значения
    typedef unsigned long long value_t;
Тип value_t -- 64-битное беззнаковое целое, используется для счетчика времени и для значений сигналов. Для увеличения эффективности, его можно переопределить в unsigned long, если не нужны сигналы шириной больше 32-х бит.

Время
    value_t time_ticks;
Внешняя переменная time_ticks отслеживает виртуальное модельное время.

Сигналы
Тип signal_t -- это структура данных, реализующая логический сигнал или шину, с шириной от 1 до 64 бит. Примерный эквивалент верилоговского типа reg. Определяется как:
    typedef struct {
...
const char *name;
value_t value;
...
} signal_t;
Программа пользователя может читать значения полей name и value, но не должна их изменять.

Сигналы объявляются в программе как статические переменные, используя макрос signal_init(). В качестве параметров задаются имя и начальное значение сигнала, например:
    signal_t clock = signal_init ("clock",  0);
Изменить значение сигнала можно вызовом функции signal_set(). Это соответствует неблокирующему присваиванию в Верилоге:
    signal_set (&clock, 1);
Процессы
Тип process_t -- структура данных, реализующая сопроцесс (lightweight thread), который моделирует поведение некоторого аппаратного блока. Для переключения сопроцессов используются стандартные POSIX-совместимые функции семейства swapcontext(), присутствующие в библиотеке glibc. Каждый процесс соответствует одному блоку 'always' в Верилоге. Определяется как:
    typedef struct {
...
const char *name;
...
} process_t;
Программа пользователя может читать значения поля name, но не должна его изменять.

Каждый процесс имеет собственный стек, выделяемый из стека родительского процесса. Обычно процессы создаются функцией main() при старте, так что расходуется основной стек программы. При создании процесса задается имя, главная процедура процесса и размер стека. Например:
    process_init ("counter", do_counter, 4096);
Здесь do_counter() - это процедура процесса. Для простых процессов обычно хватает стека в 4 килобайта. Главная процедура процесса работает по типовому алгоритму: устанавливает 'список чувствительности' и входит в бесконечный цикл, ожидая события и затем вычисляя новые значения для выходных сигналов. Например:
    void do_counter()
{
process_sensitive (&clock, POSEDGE);
for (;;) {
process_wait();
...
signal_set (&count, count.value + 1);
...
}
}
Функция process_sensitive(signal, edge) добавляет сигнал и полярность фронта к 'списку чувствительности' текущего процесса. Она выделяет небольшую область памяти из стека (посредством alloca). Список чувствительности может быть произвольного размера. Полярность фронта задается флагом OSEDGE, NEGEDGE или 0 (оба фронта).

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

Функция process_wait(ticks) приостанавливает текущий процесс на указанный интервал модельного времени.

Глобальная переменная:
    process_t *process_current;
указывает на текущий процесс. Поле process_current->name можно использовать для выдачи сообщений трассировки.

Date: 2013-02-27 21:17 (UTC)
From: [identity profile] spamsink.livejournal.com
У нас VCS точно такой же. Но как ты собираешься пользоваться longjmp, если он на сопроцессы не рассчитан?
Кстати, как в твоей модели написать
always begin
    $display("waiting for var1");
    forever begin
        @(var1) $display("var1 changed, now waiting for var2");
        @(var2) $display("var2 changed, now waiting for var1");
    end
end


Date: 2013-02-27 23:39 (UTC)
From: [identity profile] spamsink.livejournal.com
Приведенный по ссылке хак со стеком мне кажется грязноватым. longjmp не предназначен для прыжков вниз по стеку вызовов.

Date: 2013-02-28 03:16 (UTC)
From: [identity profile] spamsink.livejournal.com
Т.е. можно, однажды сделав makecontext с отдельным стеком, потом прыгать с помощью longjmp? Думаешь, будет существенно быстрее, раз без системных вызовов?

Date: 2013-03-03 01:46 (UTC)
From: [identity profile] spamsink.livejournal.com
Бэсмовский АУ надо так смоделировать.