Данный пример демонстрирует тестовую модель системы связи, разработанную для генерации кода. В модель сознательно внесён ряд упрощений, чтобы наглядно показать процесс перехода от простой концепции к готовому коду для ПЛИС. Полное описание проекта и детальный разбор всех этапов работы представлены в этой статье.
(https://habr.com/ru/companies/etmc_exponenta/articles/969564/)
Первая модель, которую мы рассмотрим, сравнивает блоки, разработанные для когенерации кода, с блоками из стандартной библиотеки Engee. Это позволяет верифицировать созданные нами алгоритмы.

function run_model( name_model)
Path = (@__DIR__) * "/" * name_model * ".engee"
if name_model in [m.name for m in engee.get_all_models()] # Проверка условия загрузки модели в ядро
model = engee.open( name_model ) # Открыть модель
model_output = engee.run( model, verbose=true ); # Запустить модель
else
model = engee.load( Path, force=true ) # Загрузить модель
model_output = engee.run( model, verbose=true ); # Запустить модель
engee.close( name_model, force=true ); # Закрыть модель
end
sleep(0.1)
return model_output
end
run_model("test_sim") # Запуск модели.
out_v_data = collect(simout["test_sim/out_v"]).value
out_ref_data = collect(simout["test_sim/out_ref"]).value
p_ref = plot(out_ref_data,
seriestype = :steppost, # Тип графика - ступеньки
label="Референсный сигнал",
linewidth=2,
color=:orange,
ylabel="Значение",
title="Референсный сигнал (out_ref)",
xlims=(0, 16), # Диапазон от 0 до 16
legend=:topright)
p_v = plot(out_v_data,
seriestype = :steppost, # Тип графика - ступеньки
label="Сигнал модели под генерацию кода",
linewidth=2,
color=:blue,
ylabel="Значение",
title="Сигнал модели под генерацию кода (out_v)",
xlims=(0, 150), # Диапазон от 0 до 150
legend=:topright)
plot(p_ref, p_v,
layout=(@layout [a; b]), # Два графика вертикально
size=(800, 600))
Построив графики выходов QPSK-демодуляторов для обеих моделей, мы видим их идентичную работу. Единственное различие в поведении заключается в том, что благодаря наличию сигнала валидности в модели под генерацию кода, с её выхода исключаются все выборки до момента накопления полного кадра.
На основании проведённого теста можно сделать вывод, что разработанные нами фильтры и модуляторы работают корректно.
Вторая модель которую мы рассмотрим, это итоговая модель под генерацию кода она включает в себя генератор тестовых данных и вывод ошибок между входом и выходом, а так же реализует смещения частоты, без реализации фильтра низких частот, частичная реализация данного алгоритма нацелена в первую очередь на демонстрацию такой возможности, а на не на полноценное отображения функциональности.

run_model("model") # Запуск модели.
err_bit_1_data = collect(simout["model/System/err_bit_1"]).value
err_bit_2_data = collect(simout["model/System/err_bit_2"]).value
err_val_data = collect(simout["model/System/Test.err_val"]).value
sum_err_bit_1 = sum(err_bit_1_data)
sum_err_bit_2 = sum(err_bit_2_data)
sum_err_val = sum(err_val_data)
println("Сумма ошибок по первому биту: ", sum_err_bit_1)
println("Сумма ошибок по второму биту: ", sum_err_bit_2)
println("Сумма ошибок по валидам: ", sum_err_val)
Как мы видим, модель не регистрирует ошибок. Это означает, что по крайней мере без учёта канала связи система работает корректно.
Следующим шагом является генерация кода на Verilog и проверка его работоспособности. Помимо самого кода, нами также была создана модель на языке С с использованием верилятора для функциональной верификации.
run_model("verification") # Запуск модели.
Для проверки работоспособности сгенерированного кода, по аналогии с предыдущей моделью, выполним подсчёт ошибок для битовых потоков и сигналов валидности.
err_bit_1_data = collect(simout["verification/C Function.1"]).value
err_bit_2_data = collect(simout["verification/C Function.2"]).value
err_val_data = collect(simout["verification/C Function.3"]).value
sum_err_bit_1 = sum(err_bit_1_data)
sum_err_bit_2 = sum(err_bit_2_data)
sum_err_val = sum(err_val_data)
println("Сумма ошибок по первому биту: ", sum_err_bit_1)
println("Сумма ошибок по второму биту: ", sum_err_bit_2)
println("Сумма ошибок по валидам: ", sum_err_val)
Как мы видим, сгенерированный код работает идентично исходной модели. Это даёт уверенность в том, что, при отсутствии проблем с ресурсами или временными характеристиками (таймингами), он будет корректно функционировать на ПЛИС, сама файловая структура сгенерированного проекта представлена ниже.
current_dir = @__DIR__
println("Структура файлов в директории: $current_dir")
println()
for (root, dirs, files) in walkdir(current_dir)
for dir in dirs
if startswith(dir, "model_System_code")
println("$dir/")
dir_path = joinpath(root, dir)
for file in readdir(dir_path)
println(" └── $file")
end
println()
end
end
end
Результаты всех этапов тестирования (визуальное сравнение графиков, подсчёт битовых ошибок, верификация C-моделью) однозначно свидетельствуют о работоспособности проекта. Разработанные алгоритмы верифицированы, а процесс автоматической генерации кода из модели отработан и дал корректный результат. Таким образом, представленный пример подтверждает жизнеспособность методологии модельно-ориентированного проектирования (Model-Based Design) для создания цифровых систем связи.
Показан полный путь от функциональной модели в среде Engee до готового для синтеза кода на языке описания аппаратуры, что существенно ускоряет процесс разработки под ПЛИС.