Engee documentation
Notebook

The Verilog Averaging Filter

An averaging filter is a type of digital filter widely used in digital signal and image processing to reduce noise levels. The median filter is a nonlinear FIR filter. In this example, we will look at its simplified implementation and see how well it works, as well as generate code from the model and check the correctness of the generated code using icarus verilog. The figure below shows the model of the implemented filter for a filtering window of 10.

image.png

Next, we will define the launch function of the model and test the model and record the results of its operation.

In [ ]:
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
Out[0]:
run_model (generic function with 1 method)

Next, let's run this model.

In [ ]:
run_model("Averaging_filter") # Запуск модели.
Building...
Progress 0%
Progress 100%
Progress 100%
Out[0]:
SimulationResult(
    "filter_out" => WorkspaceArray{Fixed{1, 10, 2, Int16}}("Averaging_filter/filter_out")
,
    "count" => WorkspaceArray{Fixed{1, 10, 2, Int16}}("Averaging_filter/count")

)
In [ ]:
out_model = collect(simout["Averaging_filter/filter_out"]).value
count = collect(simout["Averaging_filter/count"]).value

plot(count, label="Вход фильтра")
plot!(out_model, label="Выход фильтра")
Out[0]:

As we can see, the model is working correctly, and the counter values are averaged.

Code generation

Now let's move on to generating code from the counter block and the filter from the received modules. Later, we will assemble the final project.

In [ ]:
engee.generate_code(
"$(@__DIR__)/Averaging_filter.engee",
"$(@__DIR__)/Averaging_prj",
subsystem_name="cnt"
)
engee.generate_code(
"$(@__DIR__)/Averaging_filter.engee",
"$(@__DIR__)/Averaging_prj",
subsystem_name="Averaging_filter"
)

Icarus Verilog

Next, for testing, we will implement TestBench and run it in Icarus Verilog, a free (GPL) Verilog simulator for Linux and other UNIX-like systems. It is a fully functional Verilog HDL (IEEE-1364) compiler and simulator.

Main purpose:

  • Verification of digital circuits before synthesis
  • Testing and debugging of Verilog code

Key features:

  • Compilation of Verilog into executable code (iverilog)
  • Simulation using a virtual machine (vvp)
  • Support for most of the Verilog-2005 standard
  • Generation of VCD files for analysis in GTKWave

Basic tools:

  1. iverilog — the compiler (converts .v files in bytecode)
  2. vvp — simulator (executes compiled bytecode)
  3. gtkwave — time chart viewer (separate program)

Typical workflow:

# Compilation
of iverilog -o sim design.v testbench.v
# Simulation
vvp sim
# Or with VCD generation for visualization
vvp sim -lxt2
gtkwave waveform.vcd
In [ ]:
filename = "$(@__DIR__)/Averaging_prj/tb.v"
try
    if !isfile(filename)
        println("Файл $filename не найден!")
        return
    end
    println("Содержимое файла $filename:")
    println("="^50)
    content = read(filename, String)
    println(content)
    println("="^50)
    println("Конец файла")
catch e
    println("Ошибка при чтении файла: ", e)
end
Содержимое файла /user/start/examples/codegen/averaging_filter_verilog/Averaging_prj/tb.v:
==================================================
module tb;
  reg clock;
  reg reset;
  wire [9:0] cnt_out;
  wire [9:0] filter_out;
  real real_value;
  Averaging_filter_cnt u_cnt (
    .clock(clock),
    .reset(reset),
    .io_Out1(cnt_out)
  );
  Averaging_filter_Averaging_filter u_filter (
    .clock(clock),
    .reset(reset),
    .io_Count(cnt_out),
    .io_Out1(filter_out)
  );
  always @(*) begin
    real_value = $itor($signed(filter_out)) / 4.0;
  end
  always #5 clock = ~clock;
  initial begin
    clock = 0;
    reset = 1;
    $display("№\tЗначение");
    $display("--\t-------");
    #15 reset = 0;
  end
  integer output_count = 0;
  always @(posedge clock) begin
    if (!reset) begin
      output_count = output_count + 1;
      $display("%d\t%0.2f", output_count, real_value);
      if (output_count >= 8) begin
        $finish;
      end
    end
  end
endmodule
==================================================
Конец файла

This code is a testbench on Verilog for testing two modules.: Averaging_filter_cnt (counter) and Averaging_filter_Averaging_filter (filter).

The test bench generates a clock signal, resets the modules, then starts their operation and outputs the first 8 values of the filter output in a table format with fixed-point conversion (format {1,10,2}) to real numbers divided by 4, stopping the simulation after outputting the 8th value.

In [ ]:
run(`cd $(@__DIR__)/Averaging_prj`)
# Компиляция
run(`iverilog -o sim tb.v Averaging_filter_cnt.v Averaging_filter_Averaging_filter.v`)
# Запуск симуляции
run(`vvp sim`)
№	Значение
--	-------
          1	-3.00
          2	-6.00
          3	-8.75
          4	-11.25
          5	12.25
          6	9.75
          7	7.50
          8	5.25
Out[0]:
Process(`vvp sim`, ProcessExited(0))
In [ ]:
# Вывод заголовка таблицы
println("№\tЗначение")
println("--\t-------")

# Вывод только первых 8 значений
for i in 1:8
    println("$i\t$(out_model[i])")
end
№	Значение
--	-------
1	-3.0
2	-5.75
3	-8.5
4	-11.25
5	12.25
6	9.75
7	7.5
8	5.25

As we can see, the results almost completely matched, but the problem is that the module Averaging_filter_Averaging_filter has internal registers (UnitDelay_state, etc.) that take time to initialize and fill the pipeline. The first few output values may be incorrect or unstable until the filter is filled with data.

Conclusion

In this example, we have analyzed the possibilities of Verilog code verification using the built-in simulator in Engee. This approach allows us not only to obtain the code, but also to verify its operability without leaving the environment.