计数器¶
在本示例中,我们将开发和测试一个支持 Verilog 代码生成的计数器模型,并使用云编译器测试其性能。
让我们从分析已实现的模型开始。这是一个由基本模块组成的简单模型。它支持代码生成,包括比较运算符块的代码生成模式。生成模板本身如下所示。
该代码是 Chisel 代码生成器,旨在生成逻辑运算符块(如==
,!=
,<
,>
)。它使用内置的辅助函数和标签(如//!
,/*!
)生成 Verilog 代码。
//!BlockType = :RelationalOperator
//!TargetLang = :Chisel
/*!#----------------------------------------------------# */
/*!@Definitions
function show_chisel_type(x::typ) :: 字符串
if 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.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 && !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。
接下来让我们来看看实现的模型本身及其生成设置。
我们可以看到,该模型非常简单,有三个输入端口:
- 计数器增量的步长;
- 允许计数器最大值的限制;
- 计数器复位。
既然已经了解了该模型,我们就来生成其代码,并根据生成的 C 函数和手写的 Verilog 测试平台验证生成的代码,之前已将compare.cgt
文件夹添加到 Engee 路径中。
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)
我们可以看到,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
模块用于测试计数器的运行。下面我们列出了它执行的操作。
- 初始化信号:
-
clock
= 0; -reset
= 1; -io_Step
= 3; -io_Limit_max
= 27; -io_Reset
= 0。 2.2. 产生一个周期为 10 个时钟周期的时钟信号。 - 10 个时钟周期后停用
reset
信号。 4.4. 输出io_Step
、io_Limit_max
和io_Cnt
的值,以观察计数器的行为。 5.5. 200 个时钟周期后结束模拟。
现在,让我们使用 JDoodle Verilog 在线网站运行我们的测试。这是一个方便的在线平台,可直接在浏览器中编写、编译和运行 Verilog 代码。
# display(MIME("text/html"),
# """<iframe
# src="https://www.jdoodle.com/execute-verilog-online"
# width="750" height="500"
# style="border: none;">
# </iframe>""")
结论¶
模拟结果表明,代码工作正常。 并且与仿真结果相似。这意味着生成的计数器可用于 FPGA 操作。