Engee 文档
Notebook

计数器

在本示例中,我们将开发和测试一个支持 Verilog 代码生成的计数器模型,并使用云编译器测试其性能。

让我们从分析已实现的模型开始。这是一个由基本模块组成的简单模型。它支持代码生成,包括比较运算符块的代码生成模式。生成模板本身如下所示。

该代码是 Chisel 代码生成器,旨在生成逻辑运算符块(如==,!=,<,> )。它使用内置的辅助函数和标签(如//!,/*! )生成 Verilog 代码。

//!BlockType = :RelationalOperator
//!TargetLang = :Chisel

/*!#----------------------------------------------------# */
/*!@Definitions
function show_chisel_type(x::typ) :: 字符串
	if x.bits == 1 &amp;&amp; x.fractional_bits == 0
		out = "Bool()"
	elseif x.fractional_bits == 0
		out = (x.is_unsigned ? "U" : "S") * "Int($(x.bits).W)"
	else
		out = "FixedPoint($(x.bits).W,$(x.fractional_bits).BP)"
	end
	out
end
function show_chisel_type(x::signal) :: String
	base_type = show_chisel_type(x.ty)
	len = x.rows * x.cols
	len == 1 ? base_type : "Vec($(len),$(base_type))"
end
*/
val $(output(1)) = Wire($(show_chisel_type(output(1))))
/*! #----------------------------------------------------# */
/*! @Step
function patch_op(op)
   op == "==" ? "===" : op == "~=" ? "=/=" : op
end
function get_idx(len, blen)
   if len == 1
       return ""
   elseif len == blen
       return "(i)"
   else
       return "(i % $(len)))"
   结束
结束
函数 maybe_zext(sig, sig2)
   if sig.ty.is_unsigned &amp;&amp; !sig2.ty.is_unsigned
       返回".zext"
   结束
   ""
结束
函数 impl(sig1, sig2, op, out)
   dim0 = dim(out)
   dim1 = dim(sig1)
   dim2 = dim(sig2)
   blen = lcm(dim1, dim2) # 广播长度
   如果 blen == 1
       loop_decl = ""
   否则
       loop_decl = "for (i <- 0 until$(blen)) "
   end
   "$(loop_decl)$(output(1))$(get_idx(dim0, blen)):= \
      $(sig1)$(get_idx(dim1, blen))$(maybe_zext(sig1, sig2)) \
        $(patch_op(op))\
      $(sig2)$(get_idx(dim2, blen))$(maybe_zext(sig2, sig1))"
end
*/
$(impl(input(1), input(2), param.Operator, output(1))))
/*!#----------------------------------------------------# */

代码是对区块的逐步描述。


1.函数定义(程序块@Definitions

show_chisel_type(x::typ)

定义如何用 Chisel 语法表示x 类型: -Bool() ,如果是 1 位逻辑值。 -UInt(...)SInt(...), 如果是整数类型(无小数部分)。 -FixedPoint(...) ,如果是小数部分(定点)。

show_chisel_type(x::signal) 如果信号是数组 (Vec) ,则返回Vec(n, base_type) ,否则只返回base_type


2.输出信号声明

chisel
val$(output(1)) = Wire($(show_chisel_type(output(1))))

Wire(...) 的形式创建输出信号,其类型由上述函数决定。


3.生成程序块主体(程序块@Step

patch_op(op) 将语句转换为 Chisel 语法: -"==" → → (不等式)"===" -"~=""=/=" (不等式)

get_idx(len, blen) 用于引用向量(数组)逻辑中的索引:

  • 返回"" ,如果是标量。 -(i) ,如果长度匹配。 -(i % len) ,如果需要广播。

maybe_zext(sig, sig2) 如果其中一个信号是unsigned ,而另一个信号是signed ,则可能需要zext (用零扩展为兼容格式)。

impl(sig1, sig2, op, out) 字符串生成的主要功能是

  • 确定信号的大小及其广播长度;
  • 必要时,在for-cycle 中打包。
  • 形成以下形式的赋值 ``凿 output(i) := input1(i) input2(i)
处理`zext` 、索引和运算符转换。

---

该代码为逻辑运算实现了一个通用的 Chisel 块生成器,支持向量信号、广播和类型转换。它被编译成 Chisel 代码,用于硬件块描述,如 FPGA 或 ASIC。

接下来让我们来看看实现的模型本身及其生成设置。 image.png

我们可以看到,该模型非常简单,有三个输入端口:

  1. 计数器增量的步长;
  2. 允许计数器最大值的限制;
  3. 计数器复位。

既然已经了解了该模型,我们就来生成其代码,并根据生成的 C 函数和手写的 Verilog 测试平台验证生成的代码,之前已将compare.cgt 文件夹添加到 Engee 路径中。

image_3.png

In [ ]:
function run_model(name_model)
    Path = string(@__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
    return model_output
end

run_model("Counter_C_test")
Cnt_ref = simout["Counter_C_test/Cnt_ref"].value;
Cnt_C = simout["Counter_C_test/Cnt_C"].value;
plot(Cnt_ref)
plot!(Cnt_C)
Building...
Progress 0%
Progress 100%
Progress 100%
Out[0]:

我们可以看到,C 代码和初始模型的结果完全相同,这表明代码生成器工作正常。 现在,让我们继续编写生成代码的测试流水线。

模块 count_Count(
 输入时钟
               复位、
 输入 [31:0] io_Step
               io_Limit_max
 输入 i_Reset
 输出 [31:0] io_Cnt
);

 reg [31:0] UnitDelay_state
 线 [31:0] 开关 =
  $signed(UnitDelay_state) > $io_Cnt ); reg [31:0] UnitDelay_state; wire [31:0] Switch = signed(io_Limit_max) | io_Reset ?32'h0 : UnitDelay_state
 总是 @(假设时钟)开始
   如果 (reset)
     UnitDelay_state <= 32'h0
   否则
     UnitDelay_state <= io_Step + Switch
 end // always @(posedge)
 分配 io_Cnt = Switch
结束模块

模块 testbench
 reg clock
 reg reset
 reg [31:0] io_Step
 reg [31:0] io_Limit_max
 reg io_Reset
 导线 [31:0] io_Cnt
 
 // 实例化被测模块
 count_Count dut (
   .clock(clock)
   .reset(reset)
   .io_Step(io_Step)
   .io_Limit_max(io_Limit_max)
   .io_Reset(io_Reset)
   .io_Cnt(io_Cnt)
 );
 
 // 生成时钟
 总是 5 clock = ~clock
 
 // 测试序列
 初始化开始
   // 初始化信号
   clock = 0
   reset = 1
   io_Step = 3
   io_Limit_max = 27
   io_Reset = 0
   
   // 显示标题
  $display("tStep\tLimit\tCnt");
   $monitor(io_Step, io_Limit_max, io_Cnt)
   
   // 应用复位
   10 reset = 0
   
   // 运行模拟,直到计数器转一圈
   200;
   $finish
 结束
结束模块

testbench 模块用于测试计数器的运行。下面我们列出了它执行的操作。

  1. 初始化信号: -clock = 0; -reset = 1; -io_Step = 3; -io_Limit_max = 27; -io_Reset = 0。 2.2. 产生一个周期为 10 个时钟周期的时钟信号。
  2. 10 个时钟周期后停用reset 信号。 4.4. 输出io_Stepio_Limit_maxio_Cnt 的值,以观察计数器的行为。 5.5. 200 个时钟周期后结束模拟。

现在,让我们使用 JDoodle Verilog 在线网站运行我们的测试。这是一个方便的在线平台,可直接在浏览器中编写、编译和运行 Verilog 代码。

In [ ]:
# display(MIME("text/html"), 
#                 """<iframe 
#                 src="https://www.jdoodle.com/execute-verilog-online" 
#                 width="750" height="500" 
#                 style="border: none;">
#                 </iframe>""")

image.png

结论

模拟结果表明,代码工作正常。 并且与仿真结果相似。这意味着生成的计数器可用于 FPGA 操作。