Генерация Verilog в Engee
Генерация Verilog из моделей Engee с фиксированной точкой
Рассмотрим генерацию Verilog из четырёх моделей Engee с фиксированной точкой. На вычислении площади и периметра покажем влияние разрядности, переполнение и добавление регистра. Затем проверим сгенерированные модули с помощью тестовых стендов.
Арифметика с фиксированной точкой
Тип задаёт знак, разрядность и число дробных битов. Значение хранится как . Для знакового типа диапазон равен
Сравним форматы fixdt(1,16,8) и fixdt(1,6,2). В моделях используется округление Floor, насыщение отключено: переполнение приводит к циклическому переходу значения.
Описание моделей
Первые три модели вычисляют среднюю линию, периметр и площадь трапеции:
Заданы , , , , ; при проверке регистра также используем .
- model_old — вычисления в формате fixdt(1,16,8).
- model_new — тот же расчёт в формате fixdt(1,6,2).
- model_delay — формат fixdt(1,16,8) и Unit Delay на выходе площади.
- model_triangle — отдельный расчёт при , .
Модели используют библиотечные Add, Gain и Product. Для генерации выбран TargetHardware = Verilog.
Подготовка
Модели и тестовые стенды находятся в папке примера. Сгенерированный код сохраняется во временную папку внутри /user. Подготовим функции запуска и проверки.
using Main: engee, plot, plot!, histogram, hline!, savefig, Animation, frame, gif
root = @__DIR__
generated = mktempdir("/user"; prefix=".verilog_demo_")
println("Сгенерированные файлы: ", generated)
function run_demo_model(root::String, name::String)
path = joinpath(root, name * ".engee")
@assert isfile(path) "Нет модели: $path"
model = engee.load(path; force=true)
try
return engee.run(model; verbose=false)
finally
engee.close(model; force=true)
end
end
function logged_values(result, signal)
return Float64.((collect(result[signal])).value)
end
for file in ("model_old.engee", "model_new.engee", "model_delay.engee",
"model_triangle.engee", "tb_trapezoid_delay.v", "tb_triangle.v")
@assert isfile(joinpath(root, file)) "Нет исходного файла: $file"
end
println("Исходные модели и тестбенчи найдены")
Проверим наличие файлов четырёх моделей и двух тестовых стендов.
Шаг 1. Разрядность и переполнение
Запустим две модели трапеции. При достаточной разрядности ожидаем периметр 19 и площадь 24.
old_sim = run_demo_model(root, "model_old")
new_sim = run_demo_model(root, "model_new")
old_P = logged_values(old_sim, "P")
new_P = logged_values(new_sim, "P")
old_S = logged_values(old_sim, "S")
new_S = logged_values(new_sim, "S")
@assert !isempty(old_P) && !isempty(new_P)
@assert isapprox(old_P[end], 19.0; atol=1/256)
println("P: 16 бит = $(old_P[end]), 6 бит = $(new_P[end])")
println("S: 16 бит = $(old_S[end]), 6 бит = $(new_S[end])")
В формате fixdt(1,16,8) получаем P = 19 и S = 24. В формате fixdt(1,6,2) сумма оснований 12 выходит за диапазон и превращается в −4. Поэтому m = −2, P = 3 и S = −8.
Сгенерируем Verilog и проверим разрядность портов: 16 и 6 бит соответственно.
old_dir = joinpath(generated, "01_trapezoid_16bit")
new_dir = joinpath(generated, "02_trapezoid_6bit")
mkpath(old_dir)
mkpath(new_dir)
engee.generate_code(joinpath(root, "model_old.engee"), old_dir,
subsystem_name="trapeze_calculator")
engee.generate_code(joinpath(root, "model_new.engee"), new_dir,
subsystem_name="trapeze_calculator")
old_v = joinpath(old_dir, "model_old_trapeze_calculator.v")
new_v = joinpath(new_dir, "model_new_trapeze_calculator.v")
@assert isfile(old_v) && isfile(new_v)
@assert occursin(r"input\s+\[15:0\]", read(old_v, String))
@assert occursin(r"input\s+\[5:0\]", read(new_v, String))
println("RTL: 16-битные и 6-битные входы подтверждены")
В сгенерированном коде Gain реализуется сдвигом, а Product — умножением с выбором нужных разрядов результата. Размеры сигналов отражают форматы фиксированной точки в модели.
Шаг 2. Регистр и тактовая проверка
Запустим модель с Unit Delay. На первом шаге площадь должна быть нулевой, далее — равной результату предыдущего шага.
delay_sim = run_demo_model(root, "model_delay")
delay_P = logged_values(delay_sim, "P")
@assert length(delay_P) >= 5 && length(old_P) >= 5
@assert isapprox(delay_P[1], 0.0; atol=1/256)
@assert all(isapprox.(delay_P[2:5], old_P[1:4]; atol=1/256))
println("P без задержки: ", old_P[1:5])
println("P с задержкой: ", delay_P[1:5])
Результат моделирования подтверждает задержку площади на один шаг.
Сгенерируем модуль с регистром и выполним тактовую проверку в Icarus Verilog. Тестовый стенд проверяет сброс и результаты для двух значений высоты. Для запуска нужны iverilog и vvp.
delay_dir = joinpath(generated, "03_trapezoid_delay")
mkpath(delay_dir)
engee.generate_code(joinpath(root, "model_delay.engee"), delay_dir,
subsystem_name="trapeze_calculator")
delay_v = joinpath(delay_dir, "model_delay_trapeze_calculator.v")
@assert isfile(delay_v)
delay_rtl = read(delay_v, String)
@assert occursin(r"\breg\b", delay_rtl)
@assert occursin("posedge clock", delay_rtl)
delay_tb = joinpath(root, "tb_trapezoid_delay.v")
delay_exe = joinpath(delay_dir, "sim_delay")
run(`iverilog -o $delay_exe $delay_tb $delay_v`)
delay_output = read(`vvp $delay_exe`, String)
@assert occursin("h=4: m=6.00 P=19.00 S=24.00", delay_output)
@assert occursin("h=5: m=6.00 P=19.00 S=30.00", delay_output)
println(delay_output)
Тестовый стенд подтверждает m = 6, P = 19 и площади 24 и 30. Unit Delay преобразуется в регистр, обновляемый по фронту тактового сигнала.
Шаг 3. Отдельный вычислительный модуль
В отдельной модели вычислим площадь треугольника при a = 8 и h = 5. Ожидаемый результат — 20.
triangle_sim = run_demo_model(root, "model_triangle")
triangle_S = logged_values(triangle_sim, "S")
@assert !isempty(triangle_S)
println("Значения S модели Engee: ", triangle_S)
Модель выдаёт S = 20.
Сгенерируем модуль S_triangle и проверим три набора входных данных. Ожидаемые площади — 10, 10.5 и 30.
triangle_dir = joinpath(generated, "04_triangle")
mkpath(triangle_dir)
engee.generate_code(joinpath(root, "model_triangle.engee"), triangle_dir,
subsystem_name="S_triangle")
triangle_v = joinpath(triangle_dir, "model_triangle_S_triangle.v")
@assert isfile(triangle_v)
triangle_rtl = read(triangle_v, String)
@assert occursin("module model_triangle_S_triangle", triangle_rtl)
triangle_tb = joinpath(root, "tb_triangle.v")
triangle_exe = joinpath(triangle_dir, "sim_triangle")
run(`iverilog -o $triangle_exe $triangle_tb $triangle_v`)
triangle_output = read(`vvp $triangle_exe`, String)
for expected in ("a=5, h=4: S=10.00", "a=7, h=3: S=10.50",
"a=10, h=6: S=30.00")
@assert occursin(expected, triangle_output) "Не совпал RTL-результат: $expected"
end
println(triangle_output)
Все три проверки совпадают с ожидаемыми значениями. Выбор разрядов произведения учитывает дробную часть формата и деление на два.
Закроем модели, если они ещё открыты.
for name in ("model_old", "model_new", "model_delay", "model_triangle")
if name in [m.name for m in engee.get_all_models()]
engee.close(name; force = true)
end
end
"Verilog demo passed; four models and two RTL testbenches verified"
Вывод
Сгенерированный Verilog сохраняет особенности вычислений модели: разрядность, переполнение и задержку на один такт. Проверки в Icarus Verilog подтвердили ожидаемые результаты для рассмотренных вычислительных модулей.