2. C/RTL协同仿真基础:C仿真与RTL仿真的衔接,仿真环境的搭建
好,咱们直接进入正题。
C/RTL协同仿真,说白了就是让C模型和RTL模型一起跑。你写HLS代码时,C仿真跑得飞快,但RTL仿真慢得像蜗牛。怎么让它们俩配合起来?这就是本章要解决的问题。
2.1 为什么需要协同仿真?
我刚开始做HLS项目时,犯过一个低级错误。C仿真全通过,高高兴兴去综合。结果RTL仿真一跑,数据对不上。查了三天,发现是C模型里一个变量类型用错了。
你想想看,C仿真和RTL仿真各有各的脾气:
- C仿真:速度快,调试方便,但精度有限
- RTL仿真:精度高,能反映硬件行为,但慢得要命
协同仿真就是取长补短。C模型做快速验证,RTL模型做精确验证。两者配合,效率翻倍。
核心思想:C/RTL协同仿真不是替代,而是互补。C模型负责功能正确性,RTL模型负责时序和接口正确性。
2.2 仿真环境的整体架构
先看一张图,了解整体结构。
这张图我画了好几次才满意。你看,核心就是三个模块:C仿真、接口适配、RTL仿真。接口适配层是关键,它负责把C的数据格式转成RTL能吃的格式。
2.3 仿真环境的搭建步骤
2.3.1 准备C测试平台
先写一个C语言的testbench。我个人习惯用C++,因为STL库用起来顺手。
// testbench.cpp
#include <iostream>
#include <vector>
#include "dut.h" // HLS生成的C模型头文件
int main() {
// 准备测试数据
std::vector<int> input_data = {1, 2, 3, 4, 5};
std::vector<int> expected_output = {2, 4, 6, 8, 10};
// 调用DUT
int result;
for (int i = 0; i < input_data.size(); i++) {
dut(input_data[i], &result);
if (result != expected_output[i]) {
std::cout << "Error at index " << i << std::endl;
return 1;
}
}
std::cout << "All tests passed!" << std::endl;
return 0;
}
小技巧:C测试平台最好包含边界条件测试。比如输入最大值、最小值、0值。我在项目中吃过亏,边界条件没测全,RTL仿真时才发现问题。
2.3.2 生成RTL仿真文件
用Vivado HLS或Vitis HLS生成RTL代码。这一步很简单,点一下按钮就行。但要注意:
- 确保选择了正确的目标器件
- 检查接口协议是否匹配
- 确认时钟和复位信号正确
2.3.3 编写RTL testbench
RTL testbench要模拟C testbench的行为。我一般用SystemVerilog写,因为支持面向对象,复用性好。
// testbench.sv
module testbench;
reg clk, rst_n;
reg [31:0] input_data;
wire [31:0] output_data;
reg valid_in;
wire ready_in;
wire valid_out;
reg ready_out;
// 实例化DUT
dut_top u_dut (
.clk(clk),
.rst_n(rst_n),
.input_data(input_data),
.output_data(output_data),
.valid_in(valid_in),
.ready_in(ready_in),
.valid_out(valid_out),
.ready_out(ready_out)
);
// 时钟生成
always #5 clk = ~clk;
// 测试序列
initial begin
// 初始化
clk = 0;
rst_n = 0;
#20 rst_n = 1;
// 发送数据
input_data = 32'd1;
valid_in = 1;
ready_out = 1;
#10;
// 检查结果
if (output_data !== 32'd2) begin
$display("Error: expected 2, got %d", output_data);
$finish;
end
$display("All tests passed!");
$finish;
end
endmodule
注意:RTL testbench的时序必须和C模型一致。我曾经因为握手信号没对齐,浪费了两天时间调试。建议先用波形图对比一下。
2.4 协同仿真的关键点
2.4.1 数据格式对齐
C语言里int是32位,但RTL里可能是任意位宽。怎么对齐?
- C端:用
ap_int<N>或ap_uint<N>类型 - RTL端:用
logic [N-1:0]类型 - 确保N一致,否则数据会截断
2.4.2 接口协议匹配
HLS生成的接口协议有很多种:
| 协议类型 | C端表示 | RTL端表示 | 适用场景 |
|---|---|---|---|
| ap_ctrl_hs | 函数调用 | start/done/ready | 控制密集型 |
| ap_fifo | 流式接口 | FIFO读写 | 数据流处理 |
| ap_memory | 数组参数 | RAM接口 | 大数据块处理 |
| axis | hls::stream | AXI4-Stream | 高速数据传输 |
选错协议,仿真肯定跑不通。我建议一开始就用axis协议,通用性强,后续移植也方便。
2.4.3 仿真脚本自动化
手动跑仿真太累了。写个Makefile或Tcl脚本,一键搞定。
# Makefile
C_SIM:
g++ -o c_sim testbench.cpp dut.cpp
./c_sim
RTL_SIM:
xvlog testbench.sv
xelab testbench
xsim testbench
CO_SIM:
# 先跑C仿真,再跑RTL仿真,最后比较结果
make C_SIM
make RTL_SIM
python compare_results.py
我的习惯:每次修改代码后,先跑C仿真,再跑RTL仿真。如果C仿真没过,绝不跑RTL。这样能节省大量时间。
2.5 常见问题与避坑指南
做协同仿真时,我踩过不少坑。分享几个典型的:
- 数据位宽不匹配:C里用int,RTL里用32位,但HLS综合后可能变成64位。解决办法:在C代码里显式指定位宽。
- 时序没对齐:C仿真里数据一拍就出结果,RTL里可能要等好几个时钟周期。解决办法:在RTL testbench里加等待逻辑。
- 复位状态不一致:C模型没有复位概念,但RTL有。解决办法:在C testbench里模拟复位行为。
避坑指南:我曾经遇到一个项目,C仿真和RTL仿真结果总差一点点。查了三天,发现是C模型里用了float,但RTL里用了fixed-point。精度不一样,结果自然不同。从那以后,我坚持在C模型里就用定点数。
2.6 小结
C/RTL协同仿真,说白了就是让两个世界的人说同一种语言。C模型负责快,RTL模型负责准。接口适配层就是翻译官。
搭建环境时,记住三点:数据格式对齐、接口协议匹配、仿真脚本自动化。做到这三点,协同仿真基本不会出大问题。
嗯,今天就到这里。下一章我们讲约束文件怎么写,那个更有意思。