Генерация Verilog для 4-FSK модулятора¶
Здесь мы рассмотрим работу с 4-FSK (Frequency Shift Keying) в Engee.
Частотная модуляция – это вид модуляции, при котором информация кодируется изменением частоты сигнала. 4-FSK, четырехуровневая частотная манипуляция, – это тип модуляции, применяемый в DMR (Digital Mobile Radio), и он оптимален для использования в системах PMR (Professional Mobile Radio).
Также мы выполним генерацию кода Verilog из этой модели и проверим её работоспособность в Vivado.
Verilog — это язык описания аппаратуры, используемый для разработки электронных систем.
Verilog нужен в проектировании, верификации и реализации аналоговых, цифровых и смешанных электронных систем на различных уровнях абстракции.
Объявим вспомогательные функции¶
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.01)
return model_output
end
Анализ модели¶
Мы изучим два варианта модели. Один использует стандартную логику выбора, осуществляемую при помощи переключения. Второй вариант блока реализован при помощи математической формулы. Такими методами часто приходится менять алгоритмы до неузнаваемости при разработке систем с целью оптимизации их работы по скорости или по ресурсам.
Ниже приведена таблица, на основе которой разрабатывалась модель.
symbol = [-3, -1, 1, 3]
bits = [[0, 0], [0, 1], [1, 0], [1, 1]]
println(join(["bits: $bit, symbol: $f" for (f, bit) in zip(symbol, bits)], "\n"))
На скриншотах ниже показана разработанная модель.
Исходный блок 4-FSK.
В блоке, реализованном при помощи формулы, как мы можем заметить, значительно меньше логики, чем в исходном блоке. Кроме того, все блоки умножения в нём используют умножение на 2, а, соответственно, при генерации кода такая логика будет представлять собой сдвиг на один бит.
Также мы можем заметить, что в данном случае используются более короткие типы данных, чем в случае с исходным блоком, где применялся Int8.
Давайте коротко затронем тему типов данных с фиксированной точкой. Этот тип данных задаётся командой fi(X, 1, 16, 5), где слева направо параметры:
- значения числа;
- знак (1-знаковый, 0-беззнаковый);
- полная разрядность слова;
- размер дробной части.
Далее рассмотрим простой пример.
x = fi(7.5, 1, 7, 5)
y = fi(7.5, 1, 7, 3)
println("x: $x")
println("y: $y")
Как мы видим, в первом случае число 7.5 ушло в переполнение.
x+y
Также видно, что при сложении этих двух чисел под них выделяется больше памяти, чем было выделено изначально.
Проверка работоспособности модели¶
Теперь проведём анализ соответствия двух реализаций между собой. Для начала запустим модель.
bit_1 = 1; bit_2 = 1;
println("Inp_bit: $([bit_1, bit_2])")
println()
@time run_model("FSK_V") # Запуск модели.
Теперь сравним результаты. Как мы видим, оба результата соответствуют исходной таблице.
Symbol_math = collect(Symbol_sim).value[end]
println("Symbol_math: $Symbol_math")
Symbol_switch = collect(Symbol_sim_switch).value[end]
println("Symbol_switch: $Symbol_switch")
Для верификации итогового проекта мы можем представить блок, из которого дальше будем генерировать код, в виде формулы. Убедимся, что формула идентична модели.
Symbol_ref = 2 * (2 * bit_1 + bit_2) - 3
println("Symbol_ref: $Symbol_ref")
println("Symbol_sim: $Symbol_math")
Выполним генерацию кода из блока 4-FSK модулятора¶
Начнём с команды для генерации кода. Ниже приведена информация о возможностях использования генератора.
? engee.generate_code
Теперь зададим в модели целевую платформу.
Выполним генерацию кода. В связи с тем, что целевая платформа явно задана в настройках модели, строка выбора таргета нам не понадобится.
engee.generate_code(
"$(@__DIR__)/FSK_V.engee",
"$(@__DIR__)/V_Code",
subsystem_name="4-FSK modulator math",
# target="verilog"
)
Работа с Vivado¶
Теперь выполним тестирование полученного кода в Vivado и скачаем полученные файлы.
Создадим пустой проект.
Добавим сгенерированный файл.
Определим целевую платформу для нашего проекта.
Теперь мы можем посмотреть на итоговую схематику нашего проекта. Она получилась очень простой.
Выполним синтез и имплементацию проекта. Как мы можем заметить, тайминги не определены. Это связано с тем, что входные порты нашего блока пустые, на них ничего не подаётся.
Мы можем убедиться в этом, посмотрев еще и результаты симуляции. Все входы и выходы не определены.
Давайте исправим это и добавим обвязку к нашему блоку, задав входные порты как константы.
Теперь повторим симуляцию.
Результат симуляции может показаться некорректным, но давайте по пунктам разберём сгенерированную логику при условии, что на вход поступает пара бит [0,1]. Начнём с анализа результатов при помощи разработанной нами формулы.
Symbol_ref = 2*(2*0+1)-3 #[0,1]
println("Ожидаемый результат: $Symbol_ref")
Теперь перейдём к нашему коду:
(io_Symbol = {{1'h0, io_Bit_1, 1'h0} + {2'h0, io_Bit_2}, 1'h0} - 4'h3)
- {1'h0, 0, 1'h0}: 000
- {2'h0, 1}: 001
- {0,0,0} + {0,0,1}: 001
- {001, 0}: 0010
- 4'h3: 0011
Теперь перейдём к ответу. Если брать битовое вычитание, то результат равен: [1111].
- 0010 - 0011 = -1
- Берём модуль: 1: 0001
- Инвертируем биты: 0001: 1110
- Добавляем 1: 1110 + 1: 1111
Опираясь на описанные выше тезисы, можно утверждать, что наша упрощённая реализация модулятора 4-FSK работает корректно.
Вывод¶
В данном примере мы разобрали возможности генерации и верификации Verilog кода в Engee, убедились, что такой подход к разработке систем под ПЛИС применим и актуален. Более того, он может значительно ускорить процесс разработки за счёт возможности мгновенного редактирования и тестирования работы модели.