Bollywood HungamaRamayana producer Namit Malhotra calls Los Angeles premiere his “Oscar moment” ahead of November 5 releaseEl ComercioLa hoja de ruta de Estados Unidos para Venezuela: reformas, vacancia de Delcy Rodríguez y eleccionesPunchBREAKING: NELFUND introduces token system to stop double payment of school feesThe Jerusalem PostQatari mediators present peace deal to Iran in mediation effort, official saysInquirer EntertainmentDonny Pangilinan felt ‘awkward’ over Showtime Baby Doll Juby’s dream about himDaily MaverickWildlife is not livestock — Aucamp’s dangerous plan to turn conservation into agricultureInquirerMimaropa drug bust in Palawan yields ₱51,884 worth of ‘shabu’ZDF heuteEntdecken Sie das ZDF-NachrichtenstudioSky TG24Meteo, freddo e maltempo in Italia nel weekend di Halloween e Ognissanti. Le previsioniVariety‘One Cut of the Dead’ Director Ueda Shinichiro on Turning a Microdrama Into Tokyo Feature ‘Earthlings in Love’NumeramaÀ mi chemin entre le VTC et le longtail, le vélo électrique Nakamura Crosscity+ perd 500 €DeadlineRussell Brand Forcefully Denies Charges As He Gives Evidence During Rape Trial
The Daily Newsstand · Free, Always
Wednesday, October 28, 2026

Генерация Verilog-кода из моделей в среде Engee

Translate

Предположим, мы построили в Engee модель регулятора или фильтра, подали тестовые сигналы и убедились, что алгоритм работает. Теперь нужно перенести эти вычисления в программируемую логическую интегральную схему — ПЛИС (FPGA). Это необходимо, когда требуется параллельно обрабатывать поток данных и выдавать результаты в заданном темпе. Однако работающая модель ещё не определяет всех деталей будущей аппаратной реализации. Нужно выбрать, как представлять числа, какие операции выполнять одновременно и где хранить результаты предыдущих шагов. Пройдём этот путь на примерах Engee: выберем форматы с фиксированной точкой, получим Verilog-код, проверим его поведение и разберём, что необходимо для реализации в ПЛИС.

Сначала разберёмся, что именно мы переносим в ПЛИС. Программа для процессора задаёт последовательность команд. Описание ПЛИС задаёт цифровую схему: вычислительные узлы, связи между ними и регистры для хранения данных. Независимые узлы такой схемы могут работать одновременно. Чтобы описать их работу, используют языки описания аппаратуры, например Verilog и VHDL. По этому описанию можно сначала проверить поведение устройства в симуляции, а затем построить схему средствами проектирования. В статье будем использовать Verilog и разбирать его конструкции по мере появления в примерах.

Чтобы перейти от вычислений к схеме, нужно явно определить, где хранятся данные и когда они изменяются. Такое описание называют RTL — уровнем регистровых передач (Register Transfer Level). Например, сумму входных сигналов может вычислять комбинационная логика: её результат зависит от текущих входов. Накопленную ошибку регулятора, напротив, нужно сохранять в регистре и обновлять по фронту тактового сигнала. Инструмент синтеза преобразует RTL в нетлист — список логических элементов и связей между ними.

Нетлист описывает состав схемы, но ещё не задаёт её размещение в конкретной ПЛИС. На следующем этапе средства проектирования размещают элементы на кристалле и прокладывают соединения между ними. Затем формируют конфигурационный файл — битстрим. Его загружают в ПЛИС, чтобы она реализовала нужную схему. Для заказной интегральной схемы (ASIC) дальнейший путь ведёт к изготовлению кристалла. Здесь будем рассматривать ПЛИС: нам важно понять, как описание вычислений превращается в работающую аппаратуру.

Engee позволяет начать этот путь с графической модели и автоматически получить её RTL-описание. Генератор переносит блоки и соединения в Verilog, сокращая ручную работу. Но прежде нужно принять решения, которые сама математическая формула не задаёт. Первое из них — представление чисел. От него зависят диапазон значений, точность вычислений и затраты аппаратных ресурсов. Поэтому начнём с форматов данных, а затем посмотрим, как они отражаются в коде.

Типы данных с фиксированной точкой

При моделировании удобно использовать плавающую точку: она позволяет работать с числами разного масштаба без заранее заданного положения двоичной точки. В ПЛИС такую арифметику тоже можно реализовать — с помощью IP-блоков, а в некоторых семействах есть специализированная поддержка. Однако обработка мантиссы, порядка и особых значений требует дополнительной логики. Если диапазон сигнала известен заранее, часто можно выбрать более простое представление: отвести постоянное число битов под целую и дробную части. Это формат с фиксированной точкой. Выбирая разрядность и положение точки, мы согласуем нужные точность и диапазон с затратами ресурсов. В Engee такое число создаётся функцией: fi(Value,Sign,Total_bits, Fractional_bits)

Параметры:

● Value — исходное значение;

● Sign — 1 для знакового формата, 0 для беззнакового;

● Total_bits — общая разрядность;

● Fractional_bits — количество битов дробной части.

Посмотрим, что это означает на конкретном числе. Возьмём знаковый формат fixdt(1, 16, 8): всего 16 битов, из них 8 — дробные. В памяти хранится целый код, а значение числа получается делением этого кода на 256. Например, числу 1,5 соответствует код 384 (0x0180). Соседние представимые значения отличаются на 1/256, а весь диапазон составляет от −128 до 127,99609375. Если исходное число не попадает точно на эту сетку, его приходится округлять или усекать по выбранным правилам. Разница между исходным и представимым значениями — погрешность квантования. При той же общей разрядности дополнительные дробные биты уменьшают шаг, но сужают диапазон. Поэтому формат выбирают под значения сигнала и допустимую погрешность.

Выбрать форматы входов недостаточно: нужно проследить, что происходит с числами внутри модели. Полная сумма двух знаковых W-битных чисел с одинаковым масштабом требует W+1 бит; при разных форматах сначала согласуют дробные части. Для полного произведения складывают разрядности операндов, а число дробных битов результата равно сумме дробных разрядностей операндов. При приведении результата к более узкому формату можно потерять точность или получить переполнение. Поэтому настройки типов данных и преобразований в блоках Engee должны учитывать промежуточные значения.

● Вычисление диапазона представимых значений для заданного формата.

● Квантование исходного числа с учётом шага квантования.

● Формирование битового представления.

Выбор разрядностей и контроль точности

Разрядность каждого сигнала выбирают по его диапазону значений и требуемой точности. Удобно действовать последовательно:

1. Соберите статистику по модели. Запустите её на представительном наборе входных сигналов, включая переходные процессы, и зафиксируйте минимальные, максимальные и типичные значения входов, коэффициентов, промежуточных сумм, произведений и аккумуляторов.

2. Определите число дробных битов исходя из требуемой точности. Если абсолютная погрешность не должна превышать ε, ориентировочно требуется F ≥ log₂(1/ε) дробных битов. Например, для шага квантования около 1/256 ≈ 0,0039 достаточно 8 дробных битов.

3. Выберите общую разрядность с учётом диапазона. В знаковом формате шириной W бит с F дробными битами представимы значения от

 до

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

4. Отдельно проверьте промежуточные результаты. Для суммы двух W-битных чисел требуется W+1 бит, а полное произведение двух операндов занимает W₁+W₂ бит. Для аккумулятора, суммирующего N произведений, обычно предусматривают дополнительно ⌈log₂ N⌉ бит сверх разрядности произведения. Если промежуточный результат шире выходного формата, необходимо явно определить правила преобразования, а не просто отбрасывать старшие биты.

5. Предусмотрите запас на переполнение. Для сумм и аккумуляторов полезно оставлять 1–2 дополнительных бита, если диапазон задачи это допускает.

Округление и переполнение

При преобразовании числа к более узкому формату важно заранее определить два правила: как округлять результат и что делать при выходе за допустимый диапазон.

Округление. Усечение отбрасывает младшие биты — как в примере с трапецией, где выбираются биты Productnew_T_1[23:8]. Такой способ прост в аппаратной реализации, но может создавать систематическую погрешность. Для отрицательных чисел результат зависит от принятого способа усечения. Альтернативы — округление к ближайшему значению или к ближайшему чётному. Выбранный режим следует задать в формате Engee и проверить по сгенерированному RTL.

Переполнение. При переносе (wrap-around) старшие биты отбрасываются, поэтому значение может резко изменить знак. При насыщении (saturation) результат ограничивается максимальным или минимальным представимым значением. Насыщение требует дополнительной логики, но обычно обеспечивает более предсказуемое поведение. В примере с трапецией при a = 5, b = 7 и h = 30 площадь 180 не помещается в знаковый 16-битный формат с 8 дробными битами и после переноса интерпретируется как −76. Такие случаи нужно выявлять ещё при выборе форматов.

Как проверить точность относительно исходного алгоритма

1. Подайте на модель Engee и RTL-симуляцию (например, Verilator или Icarus Verilog) одинаковые последовательности входных сигналов, включая граничные значения и переходные процессы.

2. Сначала проверьте, что Engee и RTL дают одинаковые результаты на тех же входах и при согласованных начальных состояниях. Если сравнение должно быть побитовым, даже расхождение на 1–2 LSB требует объяснения. При сравнении с эталонной реализацией в плавающей точке оценивайте ошибку относительно LSB выходного формата: допустимый уровень зависит от требований; систематическое смещение или рост ошибки со временем могут указывать на проблему с форматом.

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

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

5. Сравните результат с эталонной реализацией в плавающей точке, например double precision. Это поможет оценить отклонение реализации с фиксированной точкой от исходного алгоритма. Если ошибка превышает допустимую, вернитесь к выбору форматов.

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

Архитектура генератора Verilog-кода

После выбора диапазонов и форматов сигналов можно перейти к генерации. Генератор анализирует блоки модели, их параметры и соединения, а затем строит аппаратное описание соответствующих вычислений. Например, блок задержки задаёт хранение состояния, а арифметический блок — операцию над сигналами. Для каждого блока должен быть определён способ преобразования в RTL. Поэтому наличие блока в среде моделирования ещё не означает, что генератор поддерживает его трансляцию. Процесс проходит в несколько этапов:

● Анализ графической модели: определение модульной структуры, выделение виртуальных и атомарных подсистем.

● Трансляция вычислительных блоков в аппаратные примитивы на языке Chisel.

● Преобразование Chisel в Verilog с учётом типов данных, разрядностей и тактирования.

Chisel служит промежуточным языком внутри генератора. Для обычной генерации пользователю не нужно писать на нём код: достаточно выбрать подсистему, которую требуется реализовать в ПЛИС, и указать каталог для результата. Например, генерацию можно запустить из скрипта следующим образом:

engee.generate_code("path/to/model.engee","path/to/output_dir",subsystem_name="SubSystem",target="verilog")

Параметры:

1. Путь к файлу модели;

2. Выходная директория;

3. Subsystem_name — имя подсистемы для генерации;

4. Target — целевой язык.

Тот же запуск доступен из графической модели: сначала настраиваем генератор кода, как показано ниже.

После настройки выберите подсистему и запустите генерацию. Её входы и выходы станут портами будущего модуля: через них схема будет получать данные и выдавать результат. Посмотрим, как это связано с блоками Engee, на нескольких примерах сгенерированного кода: регуляторе с памятью, простой арифметической схеме и фильтре.

Примеры сгенерированного кода

ПИ-регулятор

Начнём с регулятора: он не только выполняет вычисления, но и хранит результат предыдущего шага. На входы setpoint и feedback поступают требуемое и измеренное значения, а выход command задаёт управляющее воздействие. Демонстрационный проект называется ПИ-регулятором: в приведённом фрагменте показаны пропорциональная ветвь и накопление ошибки, дифференциальной части здесь нет. Разберём фрагменты одного модуля по порядку и проследим, где выполняется арифметика, а где хранится состояние.

module pid_fixed_SubSystem(
    input clock, reset,
    input [15:0] io_setpoint, io_feedback,
    output [15:0] io_command
);

reg [15:0] UnitDelay_state;
wire [15:0] AddAccumT = io_setpoint - io_feedback;
wire [28:0] Gain2_new_T_1 = {{13{_AddAccum_T[15]}}, AddAccumT} * 29'hA4;
wire [29:0] Gainnew_T_1 = {{14{_AddAccum_T[15]}}, AddAccumT}
* 30'h6000;

Сначала из заданного значения вычитается измеренное — так вычисляется ошибка регулирования. Затем ошибка умножается на коэффициенты двух ветвей. Математически эти операции выглядят просто, но в Verilog важно учитывать разрядность операндов и то, какие биты результата используются. Разберём основные конструкции:

Константы 0xA4 и 0x6000 — целочисленные коды коэффициентов, а не самодостаточная запись вещественных значений: положение двоичной точки задаётся форматами сигналов в модели. Например, при 8 дробных битах эти коды соответствуют примерно 0,64 и 96. В данном RTL после умножения выбираются разряды [28:14] и [29:14], то есть отбрасываются 14 младших битов. Поэтому интерпретировать константы отдельно от форматов входа, промежуточного результата и выхода нельзя. Знаковое расширение увеличивает ширину операнда: 13 повторений старшего бита дают 29-битный операнд, а 14 повторений — 30-битный. В интегральной ветви добавленный младший ноль эквивалентен сдвигу выбранного значения влево на один разряд. В выражении для выхода повторение знакового бита и отбрасывание младшего разряда реализуют арифметический сдвиг вправо на один бит.

● reg — переменные Verilog; в показанном процессе по фронту clock они описывают регистры состояния;

● wire — промежуточные сигналы;

● Знаковое расширение: старший бит числа повторяется, чтобы увеличить разрядность операнда и сохранить знак.

● умножение на константы в шестнадцатеричном виде.

Для пропорциональной ветви достаточно текущей ошибки. Для интегральной ветви нужно учитывать и предыдущие значения: к сохранённому состоянию добавляется очередной вклад ошибки. В модели эту память задаёт блок Unit Delay, а в RTL — регистр UnitDelay_state. Процесс always @(posedge clock) обновляет регистр по положительному фронту clock. Если в этот момент активен reset, состояние обнуляется.

Здесь важно соотнести шаг модели с тактом ПЛИС. В данном фрагменте UnitDelay_state обновляется на каждом положительном фронте clock, то есть один шаг обновления состояния соответствует одному такту. Однако шаг дискретизации модели может составлять, например, 1 мс, тогда как ПЛИС работает на частоте 100 МГц. Если регистр обновлять на каждом фронте такого clock, накопитель будет изменяться каждые 10 нс, а не каждую миллисекунду: регулятор будет интегрировать примерно в 100 000 раз быстрее, что может нарушить устойчивость системы. Чтобы сохранить темп модели, используют сигнал разрешения clock enable, активный один такт из 100 000, либо делитель частоты. При изменении шага дискретизации коэффициент деления нужно пересчитать. В этом фрагменте механизм согласования темпа опущен: показаны арифметика и хранение состояния, но не управление частотой обновления.

wire [15:0] Add1Accum_T =
    {_Gain_2_new_T_1[28:14], 1'h0} + UnitDelay_state;

always @(posedge clock) begin
    if (reset)
        UnitDelay_state <= 16'h0;
    else
        UnitDelay_state <= Add1Accum_T;
end

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

assign io_command =
    Gainnew_T_1[29:14] +
    {_Add_1Accum_T[15], Add1Accum_T[15:1]};

Во втором слагаемом знак сохраняется, а младший разряд отбрасывается: это эквивалентно арифметическому сдвигу вправо на один бит. И накопитель Add1Accum_T, и выход io_command имеют ширину 16 бит: при переполнении старшие разряды отбрасываются, насыщения в показанном фрагменте нет. На примере регулятора видны две составляющие RTL: комбинационная логика, вычисляющая значения, и регистр, который хранит состояние и обновляется по такту. Теперь рассмотрим пример без памяти и проследим, как арифметические формулы преобразуются в Verilog.

Калькулятор параметров трапеции

Возьмём три знакомые формулы: периметр P, среднюю линию m и площадь S трапеции. В Julia их можно записать напрямую, а для двух значений высоты сразу вычислить вектор площадей. Здесь числа служат тестовыми данными для проверки формул. Геометрически они не описывают реальную трапецию: высота 4 или 5 больше боковой стороны c = 3.

a, b, c, d = 5, 7, 3, 4
h = [4, 5]
P = a + b + c + d
m = (a + b) / 2
S = m .* h

Те же формулы соберём из блоков Engee и зададим сигналам формат fixdt(1, 16, 8). В тестовом окружении блок Signal From Workspace последовательно подаёт значения высоты. Для каждого набора входных данных модель вычисляет m, P и S. Таким образом, вместо обработки целого вектора выполняется серия расчётов для отдельных значений. Это ещё не аппаратный конвейер: между операциями нет регистров, а выходы зависят от текущих входов. Такая схема называется комбинационной. Порты clock и reset присутствуют в интерфейсе, но в данном фрагменте кода не используются. Для создания конвейера потребовалось бы добавить регистры между вычислительными ступенями.

module model_trapeze_calculator(
    input clock, reset,
    input [15:0] io_a, io_b, io_h, io_c, io_d,
    output [15:0] io_m, io_P, io_S
);
    wire [15:0] __1Accum_T = io_a + io_b;
    wire [23:0] Productnew_T_1 =
        {{9{__1Accum_T[15]}}, __1Accum_T[15:1]} *
        {{8{io_h[15]}}, io_h};
    assign io_m = {__1Accum_T[15], __1Accum_T[15:1]};
    assign io_P = __1Accum_T + io_c + io_d;
    assign io_S = Productnew_T_1[23:8];
endmodule

Сопоставим строки Verilog с формулами. Этот пример наглядно показывает, как выбор битов заменяет деление и позволяет привести результат к нужному масштабу:

● Деление суммы оснований на 2 реализовано выборкой битов с сохранением знака. Младший бит отбрасывается; для отрицательного нечётного целого кода результат округляется в сторону минус бесконечности;

● Сумма оснований вычисляется один раз и используется повторно;

● Для произведения используется 24-битный промежуточный сигнал, а выборка [23:8] оставляет 16 бит и возвращает масштаб с восемью дробными битами. Это усечение, а не округление к ближайшему значению. Например, при a = 5, b = 7 и h = 30 площадь 180 превышает максимум знакового 16-битного формата с 8 дробными битами; после переноса выход интерпретируется как −76. При a = 100 и b = 40 переполняется уже сумма оснований — до умножения. Поэтому проверять нужно не только конечный результат, но и промежуточные операции.

При a = 100 и b = 40 сумма оснований в знаковом формате с 16 битами и 8 дробными разрядами выходит за диапазон ещё до умножения. Можно расширить промежуточную сумму (например, до 17 бит, согласовав последующие форматы) либо явно реализовать насыщение. Это частный случай рекомендаций из подраздела «Выбор разрядностей и контроль точности».

КИХ-фильтр

Калькулятор обрабатывает текущие входы и не хранит историю. КИХ-фильтру, напротив, нужны текущий отсчёт и несколько предыдущих. В следующей модели QPSK-модулятор соединён с КИХ-фильтром на 11 коэффициентов. Каждый выход фильтра представляет собой сумму отсчётов, умноженных на соответствующие коэффициенты. Для прямой формы нужны текущий отсчёт и ещё 10 задержанных: блоки задержки хранят историю, умножители задают веса, а сумматоры объединяют результаты. Генератор переносит эту структуру в RTL. Количество физических умножителей и DSP-блоков зависит от архитектуры и оптимизаций синтезатора. Для выхода задан тип Fixed{1, 8, 7, Int8}: шаг составляет 1/128, диапазон — от −1 до 127/128. Форматы коэффициентов и аккумулятора, в котором суммируются произведения, выбирают отдельно; промежуточной сумме может потребоваться больший диапазон, чем выходному сигналу.

Разрядность аккумулятора следует выбирать с учётом суммы всех 11 произведений: к разрядности полного произведения обычно добавляют ⌈log₂ 11⌉ = 4 бита запаса. Иначе сумма может переполниться, даже если каждое отдельное произведение помещается в свой формат.

Пользовательские шаблоны генерации

Если генератор не умеет преобразовывать нужный блок в аппаратное описание, можно задать способ преобразования самостоятельно с помощью пользовательского шаблона — файла с расширением .cgt. В шаблоне указывают, как использовать входы, выходы и типы блока в конструкциях промежуточного языка Chisel. Так можно расширить набор поддерживаемых операций. Рассмотрим шаблон для обычного умножения:

Пример шаблона для блока умножения (промежуточный язык — Chisel):

//! BlockType = :Product
//! TargetLang = :Chisel
//! @Definitions
val $(output(1)) = Wire($(show_chisel_type(output(1))))
/*! @Step
function cast(sig)
    ".asTypeOf(FixedPoint($(sig.ty.bits).W,$(sig.ty.fractional_bits).BP))"
end
function maybe_cast_res_begin()
    output(1).ty.fractional_bits == 0 ? "(" : ""
end
function maybe_cast_res_end()
    output(1).ty.fractional_bits == 0 ? ").asSInt" : ""
end
*/
$(output(1)) := \
    $(maybe_cast_res_begin())\
    $(input(1))$(cast(input(1)))*$(input(2))$(cast(input(2)))\
    $(maybe_cast_res_end())

Особенности:

● Макроподстановки $(...) заменяются значениями из модели;

● Условная генерация в зависимости от свойств сигналов;

● Явное управление типами данных;

● Chisel используется как промежуточное представление перед генерацией Verilog.

Графическую модель также можно собрать из скрипта, используя средства управления Engee и поддерживаемые блоки. Создание модели и генерация RTL — два отдельных шага: сначала формируют модель, затем запускают генератор. Такой подход показан в примерах:

1. RRC-filter

2. CIC-filter

Атомарные подсистемы

Шаблоны определяют, как преобразовывать отдельные блоки. По мере роста модели возникает другой вопрос: как организовать сгенерированный код? Небольшую схему удобно рассматривать целиком, а в крупном проекте регулятор, модулятор и фильтр проще разбирать и тестировать по отдельности. В Engee границу модуля можно задать атомарной подсистемой: при генерации она становится отдельным модулем со своими входами и выходами. Обычная, виртуальная подсистема помогает организовать графическую модель, но её содержимое включается в родительский модуль. Атомарная подсистема сохраняет это разделение и в сгенерированном RTL.

Сравнение подходов

Метод

Монолитный

Атомарный

Структура

Один модуль

Иерархия модулей

Читаемость

Зависит от размера модуля

Удобнее при разделении функций

Переиспользование

Возможно при выделении интерфейса

Да

Отладка

Сложнее локализовать ошибку

Проще проверять модули отдельно

Пример: монолитный и атомарный

Рассмотрим модель, в которой данные проходят через генератор, QPSK-модулятор и фильтр. В монолитном варианте регистры и вычислительная логика всех этих частей находятся в одном модуле. Если выделить атомарные подсистемы, Gen_data, QPSK_modulator и fir станут отдельными модулями с собственными интерфейсами. Так границы основных частей модели сохраняются и в коде.

Выбор структуры иерархии

• Монолитный вариант удобен для небольшого примера и проверки функции целиком.

• Атомарные подсистемы удобны, когда нужны отдельные интерфейсы, независимые тесты и повторное использование частей проекта. Сама по себе иерархия не задаёт задержки и не гарантирует определённый результат синтеза: он зависит от оптимизаций, ограничений и настроек сохранения границ модулей.

Верификация сгенерированного кода

После генерации RTL нужно убедиться, что при преобразовании сохранилось поведение исходной модели. Для этого подают одинаковые входные данные на модель Engee и на сгенерированное описание, а затем сравнивают выходы. Если схема содержит регистры, сравнение должно учитывать начальное состояние и число тактов до появления результата. В Engee для такой проверки используется Verilator: он преобразует Verilog в C++-модель, на основе которой собирается библиотека симуляции. Если включить создание верификационного скрипта, формируется проверочная модель с блоком C Function. Этот блок использует библиотеку и позволяет сравнить поведение RTL с исходной моделью.

RTL можно проверить и независимо от Engee — например, с помощью Icarus Verilog. Для этого нужен тестбенч: небольшое окружение, которое подаёт входные данные и наблюдает выходы схемы. Icarus компилирует RTL вместе с тестбенчем, а программа vvp выполняет событийную симуляцию. Так удобно проверять сброс, последовательности отсчётов и граничные значения. В приведённом тестбенче калькулятора сначала задаётся высота 4, затем она меняется на 5, после чего выводятся результаты. Задержки в тестбенче определяют моменты действий в симуляции, но не показывают, за какое время вычисление будет выполнено в реальной ПЛИС.

module tb_trapeze;
    reg clk = 0, rst = 1;

    reg [15:0] a = 5 << 8, b = 7 << 8, h = 4 << 8,

               c = 3 << 8, d = 4 << 8;

    wire [15:0] m, P, S;

    model_trapeze_calculator dut(

        clk, rst, a, b, h, c, d, m, P, S

    );

    always #5 clk = ~clk;

    initial begin

        #15 rst = 0;

        #10;

        $display("h=4: m=%0.2f P=%0.2f S=%0.2f",

            $itor($signed(m)) / 256.0,

            $itor($signed(P)) / 256.0,

            $itor($signed(S)) / 256.0);

        h = 5 << 8;

        #10;

        $display("h=5: m=%0.2f P=%0.2f S=%0.2f",

            $itor($signed(m)) / 256.0,

            $itor($signed(P)) / 256.0,

            $itor($signed(S)) / 256.0);

        #10 $finish;

    end

endmodule

Чтобы прочитать результаты теста, вспомним представление чисел с фиксированной точкой. Входные целые значения переводятся в код умножением на 256 — в тестбенче для этого используется сдвиг << 8. При выводе выходные коды сначала интерпретируются как знаковые, затем делятся на 256.0. В приведённом примере при h = 4 получаются m = 6, P = 19 и S = 24; при h = 5 площадь равна 30. На этих входных данных совпали результаты Julia-скрипта, модели Engee, C++-модели Verilator и тестбенча Icarus. Это подтверждает корректность расчёта для выбранных тестов. Для собственной модели также следует проверить граничные значения, на которых проявляются квантование и переполнение, а для схем с памятью — сброс и задержку результата.

Интеграция с инструментами синтеза

Симуляция позволяет проверить, правильно ли схема вычисляет результат на выбранных тестах. Но для реализации в ПЛИС нужно ответить ещё на два вопроса: достаточно ли ресурсов выбранного устройства и сможет ли схема работать на требуемой частоте. Для этого RTL включают в проект САПР целевой ПЛИС, задают ограничения для тактового сигнала и интерфейсов, выполняют синтез, размещение и трассировку. В отчётах указываются затраты логики, регистров и DSP-блоков. Временной анализ показывает, выполняются ли заданные ограничения.

Производитель

Среда разработки

Серии ПЛИС

AMD/Xilinx

Vivado Design Suite

Spartan, Artix, Kintex, Virtex, Zynq, Versal

Intel

Quartus Prime

MAX, Cyclone, Arria, Stratix, Agilex

Lattice

Diamond, Radiant

iCE40, MachXO, ECP5, CrossLink

Open Source

Yosys + nextpnr

iCE40, ECP5, Xilinx 7-series

Ограничения генератора

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

Тактирование. Для схемы с памятью важно определить, когда обновляются данные. В рассмотренных примерах используется единый clock и синхронный сброс. Сгенерированный RTL не содержит сведений о шаге дискретизации модели: предполагается, что частота clock уже соответствует темпу обновления данных либо между тактовым сигналом и регистрами предусмотрен сигнал разрешения clock enable. Если в модели разные блоки имеют разные шаги дискретизации, это само по себе не создаёт разные тактовые домены в RTL. При необходимости такие домены проектируют и проверяют отдельно, включая передачу данных между ними. Ручные изменения RTL необходимо согласовывать с моделью и учитывать при повторной генерации.

Разрядность. К форматам чисел приходится возвращаться на всех этапах: каждое приведение к более узкому формату может повлиять на результат. Разрядности выбирают с учётом диапазона и требуемой точности, отдельно проверяя промежуточные произведения и аккумуляторы. Режимы округления и переполнения задаются в форматах Engee и переносятся генератором в RTL; ручные изменения RTL необходимо согласовывать с моделью. Практический порядок выбора форматов и проверки точности описан в подразделе «Выбор разрядностей и контроль точности».

Заключение

Путь от модели Engee к ПЛИС начинается с алгоритма и диапазонов его сигналов. Сначала выбирают представление чисел: формат с фиксированной точкой определяет точность и диапазон. Затем задают вычисления и хранение состояния в блоках модели. Генератор переносит эту структуру в Verilog, сравнение модели с RTL помогает проверить поведение, а синтез и временной анализ позволяют оценить реализацию на выбранной ПЛИС. Для первого опыта удобно взять небольшую подсистему, результат работы которой можно рассчитать вручную. Освоив весь путь на таком примере, проще перейти к регуляторам, фильтрам и более крупным моделям. Со всеми примера по генерации кода Verilog можно ознакомиться по ссылке.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.