Vitis HLS 学习笔记--global_array_RAM初始化及复位

目录

1. 简介

2. 示例代分析

2.1 源代码

2.2 URAM 不可用 

2.3 代码功能解释

2.4 综合报告

2.4.1 顶层控制接口

2.4.2  软件 IO 信息

2.4.3 存储绑定

3. 对比两种 solution

3.1 solution_A

3.2 solution_B

4. 总结


1. 简介

在C++程序中,数组是一种基本的数据结构,用来存储一系列的数据。程序员可以动态地分配和释放数组的内存空间。

但是在将程序综合到硬件上时,就不能再动态地分配内存了,因为硬件需要提前知道需要多少内存来存储数组的数据。在FPGA上,有一个本地存储器,相比于全局存储器(比如DDR或HBM存储器),本地存储器的访问速度更快,通常只需要一个或多个周期。

本文例子展示了如何将全局数组映射到具有不同实现的RAM,并展示了它们如何初始化以及如何重置。

2. 示例代分析

2.1 源代码

#include <ap_int.h>

ap_int<10> A[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
ap_int<10> B[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
ap_int<10> C[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};

int example(int i) {
#pragma HLS BIND_STORAGE variable = A type = RAM_2P impl = BRAM
#pragma HLS BIND_STORAGE variable = B type = RAM_2P impl = LUTRAM

    A[i] += B[i] + C[i];
    B[i] += 5;
    C[i] += 10;

    int result = (A[i] + B[i] + C[i]).to_int();
    return result;
}

2.2 URAM 不可用 

// #pragma HLS BIND_STORAGE variable=C type=RAM_2P impl=URAM 

该指令不可用!

有用户提出了类似的问题,发现即使使用了 #pragma HLS BIND_STORAGE 指令指定将数组存储到 URAM 中,综合报告显示每个 URAM 单元只存储了一个数组元素,而没有充分利用 URAM 的容量。解决方法是在代码中手动对数据进行打包,将多个较小位宽的数打包成一个更大位宽的数后存入 URAM。

这表明,虽然 URAM 可以通过手动优化来使用,但 Vitis HLS 可能没有直接支持全局数组使用 URAM 的内置机制。

2.3 代码功能解释

首先定义三个数组:A、B 和 C,每个数组都包含了 10 个 ap_int<10> 类型的元素。这些数组在 HLS 中被绑定到不同的存储实现类型上。

#pragma HLS BIND_STORAGE 是用于指定存储实现类型的指令。在这里,我们有两个绑定:

  • A 使用 BRAM(Block RAM)作为存储实现类型。
  • B 使用 LUTRAM(Look-Up Table RAM)作为存储实现类型。

然后,对这些数组进行了一系列操作:

  • A[i] += B[i] + C[i]:将 B[i] 和 C[i] 的值相加,并将结果加到 A[i] 上。
  • B[i] += 5:将 B[i] 的值增加 5。
  • C[i] += 10:将 C[i] 的值增加 10。
  • 最后,计算了 A[i] + B[i] + C[i] 的和,并将其转换为整数类型,然后返回该结果。 

2.4 综合报告

2.4.1 顶层控制接口

包含:块级控制协议、时钟、复位、中断;

* TOP LEVEL CONTROL
+-----------+---------------+-----------+
| Interface | Type          | Ports     |
+-----------+---------------+-----------+
| ap_clk    | clock         | ap_clk    |
| ap_rst_n  | reset         | ap_rst_n  |
| interrupt | interrupt     | interrupt |
| ap_ctrl   | ap_ctrl_chain |           |
+-----------+---------------+-----------+

2.4.2  软件 IO 信息

================================================================
== SW I/O Information
================================================================
* Top Function Arguments
+----------+-----------+----------+
| Argument | Direction | Datatype |
+----------+-----------+----------+
| i        | in        | int      |
| return   | out       | int      |
+----------+-----------+----------+

* SW-to-HW Mapping
+----------+---------------+----------+-------------------------------------+
| Argument | HW Interface  | HW Type  | HW Info                             |
+----------+---------------+----------+-------------------------------------+
| i        | s_axi_control | register | name=i offset=0x18 range=32         |
| return   | s_axi_control | register | name=ap_return offset=0x10 range=32 |
+----------+---------------+----------+-------------------------------------+

2.4.3 存储绑定

变量A、B、C和编译指令期望的输出是一致的:

================================================================
== Bind Storage Report
================================================================
+-----------+------+------+--------+----------+---------+--------+---------+
| Name      | BRAM | URAM | Pragma | Variable | Storage | Impl   | Latency |
+-----------+------+------+--------+----------+---------+--------+---------+
| + example | 1    | 0    |        |          |         |        |         |
|   B_V_U   | -    | -    | pragma | B_V      | ram_2p  | lutram | 1       |
|   C_V_U   | -    | -    |        | C_V      | ram_1p  | auto   | 1       |
|   A_V_U   | 1    | -    | pragma | A_V      | ram_2p  | bram   | 1       |
+-----------+------+------+--------+----------+---------+--------+---------+

3. 对比两种 solution

3.1 solution_A

对 kernel 代码不做复位相关编译指令,进行综合后,查看图示文件:

在 HLS 设计中,为了初始化全局 RAM 数组(不包括URAM),会生成一种特定的结构,这种结构专门用于配置 BRAM/LUTRAM:

---
变量A
---
(* ram_style = "block" *)reg [DataWidth-1:0] ram[0:AddressRange-1];

initial begin
    $readmemh("./example_A_V_RAM_2P_BRAM_1R1W.dat", ram);
end

---
变量B
---
(* ram_style = "distributed" *)reg [DataWidth-1:0] ram[0:AddressRange-1];

initial begin
    $readmemh("./example_B_V_RAM_2P_LUTRAM_1R1W.dat", ram);
end

*.dat文件包含相应数组的初始值。

3.2 solution_B

对 kernel 代码添加优化指令:

#pragma HLS reset variable=A
#pragma HLS reset variable=B
#pragma HLS reset variable=C

进行综合后,可以看到当对 BRAM/LUTRAM 使用复位指令生成的代码结构:

解释:

当在静态数组/全局数组(这里的A、B、C)上使用复位指令后,生成的RTL代码中,每个数组都会通过ROM和RAM来实现。数组的初始值只会被加载到ROM里面,这一点和solution_A是一样的。

但是,每当复位信号被激活,从数组读取的数据就默认会来自ROM。当然,如果有新数据写入了某个地址,该地址将被记录,滞后读取的数据就会转到RAM中。这样做的结果是,每次重置之后,数组都会回到它的初始状态,就像重新启动一样。

三个数组 A/B/C 的相同结构如下所示:

module example_A_V_RAM_2P_BRAM_1R1W
#(parameter
    DataWidth    = 10,
    AddressWidth = 4,
    AddressRange = 10
)(
    input  wire                    clk,
    input  wire                    reset,
    input  wire [AddressWidth-1:0] address0,
    input  wire                    ce0,
    output wire [DataWidth-1:0]    q0,
    input  wire [AddressWidth-1:0] address1,
    input  wire                    ce1,
    input  wire                    we1,
    input  wire [DataWidth-1:0]    d1
);
//------------------------Local signal-------------------
reg  [AddressRange-1:0] written = {AddressRange{1'b0}};
wire [DataWidth-1:0]    q0_ram;
wire [DataWidth-1:0]    q0_rom;
wire                    q0_sel;
reg  [0:0]              sel0_sr;
//------------------------Instantiation------------------
example_A_V_RAM_2P_BRAM_1R1W_ram #(
    .DataWidth(DataWidth),
    .AddressWidth(AddressWidth),
    .AddressRange(AddressRange))
example_A_V_RAM_2P_BRAM_1R1W_ram_u(
    .clk      ( clk ),
    .reset    ( reset ),
    .ce0      ( ce0 ),
    .address0 ( address0 ),
    .q0       ( q0_ram ),
    .ce1      ( ce1 ),
    .address1 ( address1 ),
    .we1      ( we1 ),
    .d1       ( d1 )
);

example_A_V_RAM_2P_BRAM_1R1W_rom #(
    .DataWidth(DataWidth),
    .AddressWidth(AddressWidth),
    .AddressRange(AddressRange))
example_A_V_RAM_2P_BRAM_1R1W_rom_u(
    .clk      ( clk ),
    .ce0      ( ce0 ),
    .address0 ( address0 ),
    .q0       ( q0_rom )
);
//------------------------Body---------------------------
assign q0     = q0_sel? q0_ram : q0_rom;
assign q0_sel = sel0_sr[0];

always @(posedge clk) begin
    if (reset)
        written <= 1'b0;
    else begin
        if (ce1 & we1) begin
            written[address1] <= 1'b1;
        end
    end
end

always @(posedge clk) begin
    if (ce0) begin
        sel0_sr[0] <= written[address0];
    end
end

endmodule

语句说明:

assign q0     = q0_sel? q0_ram : q0_rom;

用于选择输出数据来源于 RAM 或者 ROM。而 q0 来自于 written[address0]。

reg  [AddressRange-1:0] written = {AddressRange{1'b0}};

用于记录某个地址是否被写入过,写入过的地址会被标记,下次读取时,就会从 RAM 中读取。

4. 总结

在本文中,我们探讨了如何在Vitis HLS中处理FPGA的全局数组映射和初始化问题。通过示例代码,我们了解了如何将C++数组映射到不同类型的RAM,并使用#pragma HLS BIND_STORAGE指令来指定存储实现。我们还讨论了URAM的使用限制和手动数据打包的解决方案。最后,我们比较了两种解决方案:一种是不使用复位指令的solution_A,另一种是使用复位指令的solution_B。solution_B通过ROM和RAM的结合,提供了一种在复位信号激活时能够将数组恢复到初始状态的方法。这些知识对于理解和优化FPGA设计中的内存管理至关重要。

  • 29
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值