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-26 01:10 (UTC)
From: [identity profile] spamsink.livejournal.com
Попробуй написать в своей системе

initial begin a = 0; b = 0; end
initial repeat (10) #10 begin a <= a + 2; b <= b + 3; end

assign abuf = a;
always @* begin sum = abuf + b; $display(sum); end

Я пишу
void do_buf () {
    process_sensitive (&a, 0);
    for (;;) {
        process_wait();
        signal_set (&abuf, a.value);
    }
}

void do_sum () {
    process_sensitive (&abuf, 0);
    process_sensitive (&b, 0);

    for (;;) {
        process_wait();

        printf ("%lld\n", abuf.value + b.value);
        signal_set(&sum, abuf.value + b.value);
    }
}

int main (int argc, char **argv)
{
    int i;
    process_init ("buf", do_buf, 4096);
    process_init ("sum", do_sum, 4096);

    for (i = 0; i < 10; ++i) {
    process_delay (10);

    signal_set(&a, a.value + 2);
    signal_set(&b, b.value + 3);
    }
    process_delay(100000);
    return 0;
}


Что я делаю не так? :)

Date: 2013-02-26 01:16 (UTC)
From: [identity profile] spamsink.livejournal.com
Не в том смысле, что у тебя в макросе используется переменная a (это я поправил), а в том, что результаты печатаются не как в верилоге - ты со списками работаешь неправильно.

Date: 2013-02-26 01:51 (UTC)
From: [identity profile] spamsink.livejournal.com
Ну да, но это же очевидным образом неправильно: значения, кратные 5, обязаны присутствовать.
Edited Date: 2013-02-26 01:52 (UTC)

Date: 2013-02-27 06:38 (UTC)
From: [identity profile] spamsink.livejournal.com
Надо понять, сколько и каких углов ты срезаешь. Реализация 4-уровневой логики (а для скаляров - еще и strength) тебя на сколько-то замедлит, но, надеюсь, не в 16 раз.

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
Бэсмовский АУ надо так смоделировать.

Date: 2013-02-27 23:22 (UTC)
From: [identity profile] panchul.livejournal.com
Никакие контексты и лонгджампы не нужны, если вместо

void одна_функция_для_процесса () {
process_sensitive (&a, 0);
process_sensitive (&b, 0);
for (;;) {
process_wait();
signal_set (&с, f (a, b));
}
}

сделать

void функция_инициализации_для_процесса () {
process_sensitive (&a, функция_эвалюации_для_процесса);
process_sensitive (&b, функция_эвалюации_для_процесса);
}

void функция_эвалюации_для_процесса () {
signal_set (&c, f (a, b));
}

Вообще переключение контекстов - это ртосная ментальная модель.
Edited Date: 2013-02-27 23:23 (UTC)

Date: 2013-02-27 23:32 (UTC)
From: [identity profile] spamsink.livejournal.com
Так будет неудобно implicit state machines писать.

Date: 2013-02-27 22:29 (UTC)
From: [identity profile] panchul.livejournal.com
А почему ты с Вакуленко считаете, что генератор случайных netlist-ов вообще является репрезентативным примером RTL кода? Кроме этого:

1. В VCS (насколько я помню по работе в VCS team в 2002 году) есть куча оптимизаций для уплощения и удаления лишних ивентов. Например

assign a = b + c;
assign d = e + f;
assign g = a + d;

при отсутствии других ссылок на a и d уплощается в одно выражение которое не требует ивентов.

2. В VCS все данные для многобитных сигналов хранятся в упакованном виде - например 32 битный вектор типа bit хранится просто как сишный, а 32-битный вектор типа reg - как два числа.

3. VCS оптимизирован для больших дизайнов и имеет большой стартап. Benchmark длительностью полсекунды является бессмысленной.


Вообще в VCS насколько я смутно помню никаких longjump-ов и swapcontext-ов не было, а сгенерированный код разбивался на простые функции, представляющие из себя линейные участки кода.

Date: 2013-02-27 23:31 (UTC)
From: [identity profile] spamsink.livejournal.com
Мы так не считаем, мы просто сравниваем яблоки с яблоками.

1. Поэтому и написано не assign, а always @* c неблокирующим присваиванием, чтобы приходилось очереди событий строить.

2. В смысле, что будь это однобитные переменные, VCS был бы еще быстрее? Пожалуй.

3. Да, конечно. Будь бенчмарк подлиннее, VCS был бы еще быстрее.

Соревноваться с VCS в производительности с надеждой выиграть смысла нет. Есть смысл получить очень короткий код, позволяющий писать на Си как на верилоге с производительностью того же порядка.

Date: 2013-02-27 23:58 (UTC)
From: [identity profile] panchul.livejournal.com
*** Есть смысл получить очень короткий код, позволяющий писать на Си как на верилоге с производительностью того же порядка. ***

Мне нужно показать тебе последнюю инкарнацию CycleC в 2001 году. Ты бы заценил. Как нибудь устроим поход в лес и ты увидишь.

Date: 2013-02-28 03:18 (UTC)
From: [identity profile] spamsink.livejournal.com
Ты ramlamyammambam-у ее покажи.

Date: 2013-02-28 20:20 (UTC)
From: [identity profile] spamsink.livejournal.com
Красиво, но с отдельным стеком в 4 кб для каждого несчастного always блока кроме как для игрушечных дизайнов это неприменимо, потому что в реальных дизайнах этих always блоков миллионы, и это не преувеличение.

Date: 2013-03-01 23:35 (UTC)
From: [identity profile] spamsink.livejournal.com
А на PDP-11 это делалось одной командой JSR PC, @(SP)+

Date: 2013-03-03 01:47 (UTC)
From: [identity profile] spamsink.livejournal.com
А если сделать omit-frame-pointer?