В прошлый раз я показал демо с двумя процессами, работающими параллельно, но без всякого взаимодействия. Давайте теперь добавим взаимодействие. Введём понятие сигнала. Поскольку я веду дело к моделированию цифровой логики, такой термин будет в самый раз.
Сигнал это по сути переменная, значение которой процессы могут использовать или изменять. С точки зрения хардвера сигнал состоит из одного или нескольких битов (в нашем случае до 64 битов). Но самое полезное в концепции сигнала - что процессы могут реагировать на его изменение. Обычно в моделировании каждый процесс определяет значение одного сигнала (выход), и при этом использует значения других сигналов (входы). Когда сигнал изменяет значение, процессы, чувствительные к нему, разблокируются и получают возможность перевычислить свои выходы.
Введём класс signal_t, реализующий сигналы. Создавать сигналы будем в виде глобальных переменных. Все процессы могут ссылаться на нужные им сигналы. Каждому сигналу дадим имя в виде строки - пригодится при отладке. Вторым аргументом конструктора сигналу можно присвоить начальное значение (необязательное). По умолчанию это ноль. Текущее значение сигнала можно опросить методом get(). Вот пример создания четырёх сигналов:
Введём класс sensitivity_t, каждый объект которого будет привязывать один сигнал к одному (текущему) процессу, по заданному фронту. Если нужна чувствительность к нескольким сигналам - процесс вправе создать несколько таких привязок.
Вот пример реализации 4-битного синхронного счётчика, со сбросом и сигналом разрешения. Как традиционно принято в цифровой логике, счетчик изменяет значение по переднему фронту синхросигнала:
Сигнал это по сути переменная, значение которой процессы могут использовать или изменять. С точки зрения хардвера сигнал состоит из одного или нескольких битов (в нашем случае до 64 битов). Но самое полезное в концепции сигнала - что процессы могут реагировать на его изменение. Обычно в моделировании каждый процесс определяет значение одного сигнала (выход), и при этом использует значения других сигналов (входы). Когда сигнал изменяет значение, процессы, чувствительные к нему, разблокируются и получают возможность перевычислить свои выходы.
Введём класс signal_t, реализующий сигналы. Создавать сигналы будем в виде глобальных переменных. Все процессы могут ссылаться на нужные им сигналы. Каждому сигналу дадим имя в виде строки - пригодится при отладке. Вторым аргументом конструктора сигналу можно присвоить начальное значение (необязательное). По умолчанию это ноль. Текущее значение сигнала можно опросить методом get(). Вот пример создания четырёх сигналов:
signal_t clk("clock"); // Синхросигнал
signal_t reset("reset"); // Общий сброс
signal_t enable("enable"); // Сигнал разрешения для счётчика
signal_t count("count"); // 4-битный счётчик
В предыдущем посте упоминался процесс - генератор синхросигнала. Вот он в законченном виде. Метод simulator::set() изменяет значение нужного сигнала на указанное значение.Но установка значений сигналов это еще не взаимодействие процессов. Взаимодействие начинается, когда процессы реагируют на изменение сигналов. Каждый процесс определяет для себя, какие сигналы его интересуют, и по какому фронту: переднему, заднему или обоим.co_void_t do_clock(simulator_t &sim)
{
for (;;) {
sim.set(clk, 1);
co_await sim.delay(1);
sim.set(clk, 0);
co_await sim.delay(1);
}
}
Введём класс sensitivity_t, каждый объект которого будет привязывать один сигнал к одному (текущему) процессу, по заданному фронту. Если нужна чувствительность к нескольким сигналам - процесс вправе создать несколько таких привязок.
Вот пример реализации 4-битного синхронного счётчика, со сбросом и сигналом разрешения. Как традиционно принято в цифровой логике, счетчик изменяет значение по переднему фронту синхросигнала:
Для знакомых с языком Verilog - вышеуказанная процедура в точности соответствует следующему коду на Верилоге:co_void_t do_counter(simulator_t &sim)
{
sensitivity_t hook(sim, clk, POSEDGE);
for (;;) {
co_await co_await_t{};
if (reset.get() != 0) {
sim.set(count, 0);
} else if (enable.get() != 0) {
sim.set(count, (count.get() + 1) & 15);
std::cout << '(' << sim.time() << ") Increment Counter " << count.get() << std::endl;
}
}
}
Также нам понадобится управляющий процесс, чтобы сформировать сигналы сброса и разрешения:always @(posedge clk)
if (reset)
count <= 0;
else if (enable)
count <= (count + 1) & 15;
Главная программа создаёт три процесса и запускает симуляцию:co_void_t master(simulator_t &sim)
{
std::cout << '(' << sim.time() << ") Started" << std::endl;
co_await sim.delay(10);
sim.set(reset, 1);
std::cout << '(' << sim.time() << ") Asserting Reset" << std::endl;
co_await sim.delay(20);
sim.set(reset, 0);
std::cout << '(' << sim.time() << ") De-Asserting Reset" << std::endl;
co_await sim.delay(10);
std::cout << '(' << sim.time() << ") Asserting Enable" << std::endl;
sim.set(enable, 1);
co_await sim.delay(40);
std::cout << '(' << sim.time() << ") De-Asserting Enable" << std::endl;
sim.set(enable, 0);
std::cout << '(' << sim.time() << ") Terminating simulation" << std::endl;
sim.finish();
}
Компилируем и запускаем:int main()
{
simulator_t sim;
sim.make_process("clock", do_clock);
sim.make_process("counter", do_counter);
sim.make_process("master", master);
sim.run();
}
Исходники можно взять здесь: https://github.com/sergev/simulation-with-coroutines$ make demo2
g++-10 -std=c++20 -fcoroutines -c demo2.cpp
g++-10 -std=c++20 -fcoroutines -c simulator.cpp
g++-10 -std=c++20 -fcoroutines demo2.o simulator.o -o demo2
$ ./demo2
(0) Started
(10) Asserting Reset
(30) De-Asserting Reset
(40) Asserting Enable
(40) Increment Counter 0
(42) Increment Counter 1
(44) Increment Counter 2
(46) Increment Counter 3
(48) Increment Counter 4
(50) Increment Counter 5
(52) Increment Counter 6
(54) Increment Counter 7
(56) Increment Counter 8
(58) Increment Counter 9
(60) Increment Counter 10
(62) Increment Counter 11
(64) Increment Counter 12
(66) Increment Counter 13
(68) Increment Counter 14
(70) Increment Counter 15
(72) Increment Counter 0
(74) Increment Counter 1
(76) Increment Counter 2
(78) Increment Counter 3
(80) De-Asserting Enable
(80) Terminating simulation

no subject
Date: 2021-06-26 06:37 (UTC)Compiler version S-2022.06-Alpha_Full64; Runtime version S-2022.06-Alpha_Full64; Jun 25 23:35 2021 aa94b18fe01dc1f8 312f8177e1554de4 51fb4c711ff4f827 $finish called from file "random.v", line 2546. $finish at simulation time 12090001 V C S S i m u l a t i o n R e p o r t Time: 12090001 CPU Time: 22.670 seconds; Data structure size: 0.1Mb Fri Jun 25 23:36:15 2021 CPU time: 1.854 seconds to compile + .709 seconds to elab + 6.398 seconds to link + 22.744 seconds in simulationno subject
Date: 2021-07-04 05:28 (UTC)Первый: нынешний Modelsim несильно уступает VCS. На этом тесте выходит 34 секунды против 22-х. Это я не учитываю лишние 9 секунд, что VCS тратит на компиляцию и линковку. То есть бесплатный Modelsim вполне имеет смысл использовать для всяких хобби и малобюджетных проектов.
Второй вывод: мой код на сопрограммах выполняется на порядок быстрее VCS. На Си++ 1.87 секунды, у тебя на VCS - 22 секунды. Опять же компиляцию не учитываем.