Engee 文档
Notebook

柜台

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

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

该代码是一个凿子代码生成器,旨在生成逻辑运算符的块(例如, ==, !=, <, >). 它使用内置的帮助器函数和标签(例如, //!, /*!)来生成Verilog代码。

//! BlockType=:RelationalOperator
//! TargetLang=:凿子

/*! #----------------------------------------------------# */
/*! @定义
函数show_chisel_type(x::typ)::字符串
  如果x.bits==1&&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.\text{位})\text{。}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($(\text{伦}), $(base_type))"
end
*/
val $(\text{输出}(1))=\text{电线}($(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)
  如果sig.ty.is_unsigned&&!sig2.ty.is_unsigned
      返回"。zext"
  结束
  ""
结束
函数impl(sig1,sig2,op,out)
  dim0=dim(出)
  dim1=dim(sig1)
  dim2=dim(sig2)
  blen=lcm(dim1,dim2)#广播长度
  如果blen==1
      loop_decl=""
  其他
      loop_decl="for(i<-0直到 $(blen)) "
  end
  "$\text{(}loop_decl\text{)}$(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(输入(1),输入(2),param。运算符,输出(1)))
/*! #----------------------------------------------------# */

代码是对块的逐步描述。


1. 函数的定义(块 @Definitions)

show_chisel_type(x::typ)

定义如何表示类型 x 在凿子语法:

  • Bool() 如果是1位布尔值。
  • UInt(...)SInt(...) 如果它是整数类型(没有小数部分)。
  • FixedPoint(...) 如果有一个小数部分(定点)。

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


2. 输出信号的公告

``'凿子
瓦尔 (show_chisel_type(输出(1))))


创建输出信号为 `Wire(...)`,其类型由上述功能决定。

---

  **3. 生成块体(块 `@Step`)**

 `patch_op(op)`
将运算符转换为凿子语法:
- `"=="``"==="`
- `"~="``"=/="` (不平等)

 `get_idx(len, blen)`
它用于访问向量(海量)逻辑中的索引。:
-回报 `""` if是一个标量。
- `(i)` 如果长度匹配。
- `(i % len)` 如果需要广播。

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

 `impl(sig1, sig2, op, out)`
字符串生成的主要功能:
-确定信号的大小及其广播长度;
-如果有必要,它包装在 `for`-一个循环。
-生成表单的赋值
``'凿子
输出(i):=input1(i)<op>input2(i)

与处理 zext、运算符的索引和翻译。


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

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

正如我们所看到的,该模型非常简单,有三个输入端口。:

  1. 执行计数器递增的步骤;
  2. 计数器的最大允许值的限制;
  3. 重置计数器。

现在我们已经弄清楚了模型,我们将生成它的代码,并根据生成的C函数并基于手写的Verilog测试台验证生成的代码,在添加一个带有 compare.cgt 在途中。

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]:

正如我们所看到的,使用代码和源模型的结果是相同的,这表明代码生成器正常工作。
现在让我们继续编写生成代码的测试绑定。
``'verilog
模块count_Count(
输入时钟,
重置,
输入[31:0]io_Step,
io_Limit_max,
输入io_Reset,
输出[31:0]io_Cnt
);

reg[31:0]UnitDelay_state;
电线[31:0]开关=
$signed(UnitDelay_state) > $签名(io_Limit_max)/io_Reset? 32'0:UnitDelay_state;
总是@(posedge时钟)开始
如果(重置)
UnitDelay_state<=32'0;
其他
UnitDelay_state<=io_Step+开关;
结束//总是@(posedge)
分配io_Cnt=开关;
端模,端模

模块测试台;
reg时钟;
reg重置;
reg[31:0]io_Step;
reg[31:0]io_Limit_max;
注册io_Reset;
线[31:0]io_Cnt;

//实例化被测模块
count_Count dut(
.时钟(clock),
.重置(reset),
.io_Step(io_Step),
.io_Limit_max(io_Limit_max),
.io_Reset(io_Reset),
.io_Cnt(io_Cnt)
);

//时钟生成
总是5时钟=~时钟;

//测试顺序
初始开始
//初始化信号
时钟=0;
重置=1;
io_Step=3;
io_Limit_max=27;
io_Reset=0;

//显示标题
$display("tStep\tLimit\tCnt");
$监视器(io_Step,io_Limit_max,io_Cnt);

//应用重置
10复位=0;

//运行模拟直到计数器结束
200;
$完成;

结束
端模,端模


---

模块 `testbench` 它用于检查计数器的操作. 下面我们列出他执行的操作。

1. 初始化信号:
 - `clock` = 0;
 - `reset` = 1;
 - `io_Step` = 3;
 - `io_Limit_max` = 27;
 - `io_Reset` = 0.
2. 产生周期为10个时钟周期的时钟信号。
3. 在10个时钟周期后停用信号 `reset`.
4. 输出值 `io_Step`, `io_Limit_max``io_Cnt` 来监控计数器的行为。
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操作。