前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在HLS中插入HDL代码

在HLS中插入HDL代码

作者头像
碎碎思
发布2024-07-15 12:36:57
1410
发布2024-07-15 12:36:57
举报
文章被收录于专栏:OpenFPGA

很多人都比较反感用C/C++开发(HLS)FPGA,大家第一拒绝的理由就是耗费资源太多。但是HLS也有自己的优点,除了快速构建算法外,还有一个就是接口的生成,尤其对于AXI类接口,按照标准语法就可以很方便地生成相关接口。

那么有没有能利用HLS的优点,又囊括HDL的优点的方法呢?今天就来介绍一种在HLS中插入HDL代码的方式,结合两者的优势为FPGA开发打造一把“利剑”。

说明

接下来,将介绍如何创建 Vitis-HLS 项目并将其与自定义 Verilog 模块集成一起。

将插入两个黑盒函数 - 第一个在流水线区域(线路接口,ap_none),第二个在数据流区域(FIFO 接口,ap_ctrl_chain)。

步骤

1. 创建C/C++源文件(基于C的HLS模型+Testbench)

创建模块的 C/C++ 模型,其中包括函数源代码(模块预期行为)和测试平台(io 刺激和结果检查)。

根据ug1399-vitis-hls rtl黑盒,rtl黑盒受到几个因素的限制:

  • 应该是Verilog(.v)代码。
  • 必须具有唯一的时钟信号和唯一的高电平有效复位信号。
  • 必须有一个 CE 信号,用于启用或停止 RTL IP。
  • 可以使用 ap_ctrl_chain 或 ap_ctrl_none 块级控制协议。
  • 仅支持 C++。
  • 无法连接到顶层接口 I/O 信号。
  • 不能直接作为被测设计(DUT)。
  • 不支持结构或类类型接口。

main.cpp ——C/C++ 测试台。

代码语言:javascript
复制
#include "add.hpp"
int main (void) {
    static uint32_t a[1024];
    static uint32_t b[1024];
    static uint32_t c[1024];
    static uint32_t c_stream[1024];
    for (uint32_t i = 0; i < 1024; ++i) {
        a[i] = i;
        b[i] = i;
        c[i] = 0;
        c_stream[i] = 0;
    }
    top_module(a, b, c, c_stream);
    for (uint32_t i = 0; i < 1024; ++i) {
        if (c[i] != a[i] + b[i]) {
            printf("Data does not match. %d vs %d\n", c[i], a[i] + b[i]);
            return -1;
        }
        if (c[i] != c_stream[i]) {
            printf("Data does not match. %d vs %d\n", c[i], c_stream[i]);
            printf("Add modules have different results.\n");
            return -2;
        }
    }
    printf("Test succesfull.\n");
    return 0;
}

add.hpp——函数声明。

代码语言:javascript
复制
#ifndef ADD_HPP
#define ADD_HPP
#include <cstdint>
#include <hls_stream.h>
void add(uint32_t a, uint32_t b, uint32_t &c);
void add_stream(
    hls::stream<uint32_t> &a,
    hls::stream<uint32_t> &b,
    hls::stream<uint32_t> &c
);
void scalar_to_stream(uint32_t a, hls::stream<uint32_t> &a_stream);
void stream_to_scalar(hls::stream<uint32_t> &a_stream, uint32_t &a);
void wrap(uint32_t a, uint32_t b, uint32_t &c);
void top_module(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *c_stream);
#endif

add.cpp——函数源代码。

代码语言:javascript
复制
#include "add.hpp"
void add(uint32_t a, uint32_t b, uint32_t &c) {
    c = a + b;
};
void add_stream(
    hls::stream<uint32_t> &a,
    hls::stream<uint32_t> &b,
    hls::stream<uint32_t> &c
) {
    c.write(a.read() + b.read());
};
void scalar_to_stream(uint32_t a, hls::stream<uint32_t> &a_stream) {
    a_stream.write(a);
};
void stream_to_scalar(hls::stream<uint32_t> &a_stream, uint32_t &a) {
    a = a_stream.read();
};
void wrap(uint32_t a, uint32_t b, uint32_t &c) {
#pragma HLS DATAFLOW
    hls::stream<uint32_t> c_s;
    hls::stream<uint32_t> a_s;
    hls::stream<uint32_t> b_s;
    scalar_to_stream(a, a_s);
    scalar_to_stream(b, b_s);
    add_stream(a_s, b_s, c_s);
    stream_to_scalar(c_s, c);
};
void top_module(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *c_stream) {
#pragma HLS INTERFACE mode=m_axi port=a depth=1024 bundle=first
#pragma HLS INTERFACE mode=m_axi port=b depth=1024 bundle=second
#pragma HLS INTERFACE mode=m_axi port=c depth=1024 bundle=first
#pragma HLS INTERFACE mode=m_axi port=c_stream depth=1024 bundle=second
#pragma HLS INTERFACE mode=s_axilite port=return
    main_loop_pipeline: for (uint32_t i = 0; i < 1024; ++i) {
        uint32_t c_o;
        uint32_t const a_t = a[i];
        uint32_t const b_t = b[i];
        add(a_t, b_t, c_o);
        c[i] = c_o;
    }
    main_loop_stream: for (uint32_t i = 0; i < 1024; ++i) {
        wrap(a[i], b[i], c_stream[i]);
    }
};

2. 为 Vitis HLS创建配置文件

Vitis HLS需要配置文件来构建项目。基本配置文件应包含

  • Part——FPGA 部件编号。
  • syn.top——顶级函数名称。
  • tb.file——测试台文件。
  • syn.file — HLS 中使用的文件。

在此示例中,cfg 文件的最小版本如下所示:

代码语言:javascript
复制
part=xc7z007sclg225-1
[hls]
syn.top=top_module
tb.file=main.cpp
syn.file=add.cpp
syn.file=add.hpp
package.output.format=ip_catalog
flow_target=vivado

3. 创建并构建最小项目

启动 Vitis,选择工作区并点击“创建 HLS 组件”。

更改组件位置和名称,单击下一步。

选择从现有配置文件创建,点击下一步。

项目结构如下所示:

无需添加额外的标志,只需仔细检查顶部函数是否是“top_module”,然后单击下一步。

选择芯片(默认部分应该是cfg文件中写的),单击下一步

确认flow_target和package.output.format,点击next。

检查摘要并单击完成。

最后运行所有步骤以确保所有配置均已配置并正常运行。

4.创建blackbox函数json

在此步骤中,我们将用 blackbox verilog 代码替换我们的添加函数。在pipeline区域:

右键单击 hls_component 并单击“创建 RTL blackbox”,将生成 JSON 文件,描述 verilog 模块与其 C 函数之间的连接。

选择包含 C 模块描述的文件。

选择端口方向并填写RTL组配置(verilog模块中的端口名称)。

选择verilog文件,如有必要再填写其他框,单击下一步。

删除 ap_ctrl_chain_protocol 字符串,保留空白。单击完成。

对 add_stream 函数重复所有这些步骤。

输入先进先出:

输出先进先出:

概括:

不要修改 ap_ctrl_chain 信号,因为该模块将使用 ap_ctrl_chain 协议。

此后,hls_component 文件夹中应该会生成两个 json 文件。

add.json

代码语言:javascript
复制
{
  "c_files": [
    {
      "c_file": "add.cpp",
      "cflag": ""
    }
  ],
  "c_function_name": "add",
  "rtl_files": [
    "add.v"
  ],
  "c_parameters": [
    {
      "c_name": "a",
      "c_port_direction": "in",
      "rtl_ports": {
        "data_read_in": "a"
      }
    },
    {
      "c_name": "b",
      "c_port_direction": "in",
      "rtl_ports": {
        "data_read_in": "b"
      }
    },
    {
      "c_name": "c",
      "c_port_direction": "out",
      "rtl_ports": {
        "data_write_out": "c",
        "data_write_valid": "c_vld"
      }
    }
  ],
  "rtl_top_module_name": "add",
  "rtl_performance": {
    "II": "0",
    "latency": "0"
  },
  "rtl_resource_usage": {
    "BRAM": "0",
    "DSP": "0",
    "FF": "0",
    "LUT": "0",
    "URAM": "0"
  },
  "rtl_common_signal": {
    "module_clock": "ap_clk",
    "module_reset": "ap_rst",
    "module_clock_enable": "ap_ce",
    "ap_ctrl_chain_protocol_idle": "",
    "ap_ctrl_chain_protocol_start": "",
    "ap_ctrl_chain_protocol_ready": "",
    "ap_ctrl_chain_protocol_done": "",
    "ap_ctrl_chain_protocol_continue": ""
  }
}

add_stream.json

代码语言:javascript
复制
{
  "c_files": [
    {
      "c_file": "add.cpp",
      "cflag": ""
    }
  ],
  "c_function_name": "add_stream",
  "rtl_files": [
    "add_stream.v"
  ],
  "c_parameters": [
    {
      "c_name": "a",
      "c_port_direction": "in",
      "rtl_ports": {
        "FIFO_empty_flag": "a_empty_flag",
        "FIFO_read_enable": "a_read_enable",
        "FIFO_data_read_in": "a"
      }
    },
    {
      "c_name": "b",
      "c_port_direction": "in",
      "rtl_ports": {
        "FIFO_empty_flag": "b_empty_flag",
        "FIFO_read_enable": "b_read_enable",
        "FIFO_data_read_in": "b"
      }
    },
    {
      "c_name": "c",
      "c_port_direction": "out",
      "rtl_ports": {
        "FIFO_full_flag": "c_full_flag",
        "FIFO_write_enable": "c_write_enable",
        "FIFO_data_write_out": "c"
      }
    }
  ],
  "rtl_top_module_name": "add_stream",
  "rtl_performance": {
    "II": "0",
    "latency": "0"
  },
  "rtl_resource_usage": {
    "BRAM": "0",
    "DSP": "0",
    "FF": "0",
    "LUT": "0",
    "URAM": "0"
  },
  "rtl_common_signal": {
    "module_clock": "ap_clk",
    "module_reset": "ap_rst",
    "module_clock_enable": "ap_ce",
    "ap_ctrl_chain_protocol_idle": "ap_idle",
    "ap_ctrl_chain_protocol_start": "ap_start",
    "ap_ctrl_chain_protocol_ready": "ap_ready",
    "ap_ctrl_chain_protocol_done": "ap_done",
    "ap_ctrl_chain_protocol_continue": "ap_continue"
  }
}

主文件夹应与此类似:

hls_config.cfg 文件应该添加两新行( syn.blackbox.file)

代码语言:javascript
复制
part=xc7z007sclg225-1
[hls]
flow_target=vivado
csim.code_analyzer=0
syn.top=top_module
syn.blackbox.file=add.json
syn.blackbox.file=add_stream.json
tb.file=main.cpp
syn.file=add.cpp
syn.file=add.hpp

5.创建Verilog黑盒函数

函数“add”必须具有ap_none接口,并且 ap_none 作为模块接口。(有关模块接口的更多信息,请查看https://docs.amd.com/r/en-US/ug1399-vitis-hls/JSON-File-for-RTL-Blackbox 。)

根据UG1399,端口a和b是32位宽度的输入端口,输出c端口也是32位宽度,但带有额外的有效信号,我们称之为c_vld。模块还需要ap_clk,ap_ce,ap_rst端口。

Verilog 如下所示:

add.v

代码语言:javascript
复制
`timescale 1ns/1ps
module add (
    input [31:0] a,
    input [31:0] b,
    output [31:0] c,
    output c_vld,
    input ap_ce,
    input ap_rst,
    input ap_clk
);
    reg [31:0] c_d;
    reg c_vld_d;
    assign c = c_d;
    assign c_vld = c_vld_d;
    always @(posedge ap_clk) begin
        if (ap_rst == 1'b1) begin
        c_d <= 32'b0;
        c_vld_d <= 1'b0;
    end else begin
        c_d <= (a + b) & {32{ap_ce}};
        c_vld_d <= ap_ce;
    end
end
endmodule

运行 C 综合和 C/RTL 协同仿真。能够在 HLS 模块中看到打包的 add.v 文件。

单击 hls_config.cfg 文件,在 Vitis GUI 的帮助下将 cosim.trace_level 更改为全部并运行联合仿真。

单击波形查看器。Vivado 会弹出 XSIM。

将 grp_add_fu_134 信号添加到 wcfg

函数行为很奇怪,接下来在 json 中更改黑盒函数 II,看看它如何影响仿真。打开 add.json 并将 II 更改为 10。再次运行 C 综合并重新运行 C/RTL 协同仿真。

add.v 模块是否良好且可以正常工作?其行为是否正确?模块是否正常工作由哪些因素决定?“fixing”模块对资源使用有何影响?

那么 add_stream 呢?函数位于数据流区域,并且必须包含 fifo 端口和 ap_ctrl_chain 协议。

add_stream.v

代码语言:javascript
复制
`timescale 1ns/1ps
module add_stream (
    input [31:0] a,
    input a_empty_flag,
    output a_read_enable,
    input [31:0] b,
    input b_empty_flag,
    output b_read_enable,
    output [31:0] c,
    input c_full_flag,
    output c_write_enable,
    output ap_idle,
    input ap_start,
    output ap_ready,
    output ap_done,
    input ap_continue,
    input ap_ce,
    input ap_rst,
    input ap_clk
);
    reg a_read_enable_d;
    reg b_read_enable_d;
    reg c_write_enable_d;
    reg [31:0] c_d;
    assign a_read_enable = a_read_enable_d;
    assign b_read_enable = b_read_enable_d;
    assign c_write_enable = c_write_enable_d;
    assign c = c_d;
    assign ap_idle = !ap_start;
    assign ap_ready = ap_start;
    assign ap_done = ap_start;
    //Flags are negated...
    assign flags_good = a_empty_flag && b_empty_flag && c_full_flag;
    assign hs_good = ap_start && ap_continue;
    always @(posedge ap_clk) begin
    if (ap_rst == 1'b1) begin
            a_read_enable_d <= 0;
            b_read_enable_d <= 0;
            c_write_enable_d <= 0;
            c_d <= 0;
    end else if (ap_ce == 1'b1) begin
            a_read_enable_d <= flags_good && hs_good;
            b_read_enable_d <= flags_good && hs_good;
            c_write_enable_d <= flags_good && hs_good;
            c_d <= a + b;
        end
    end
endmodule

看起来放置在数据流区域的模块工作正常:

打开 add_stream.json 并将延迟更改为 10。再次运行 C 综合并重新运行 C/RTL 协同仿真。这会影响仿真吗?

相关文件链接:

https://github.com/bartokon/hls/tree/main/rtl-blackbox

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 OpenFPGA 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 说明
  • 步骤
    • 1. 创建C/C++源文件(基于C的HLS模型+Testbench)
      • main.cpp ——C/C++ 测试台。
      • add.hpp——函数声明。
      • add.cpp——函数源代码。
    • 2. 为 Vitis HLS创建配置文件
      • 3. 创建并构建最小项目
        • 4.创建blackbox函数json
          • add.json
          • add_stream.json
        • 5.创建Verilog黑盒函数
          • add.v
          • add_stream.v
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档