🏠

data_mover_runtime 模块技术深度解析

开篇:这个模块在解决什么问题

想象你正在开发一个 FPGA 加速系统。你的算法内核期望以特定的二进制格式接收数据——通常是固定宽度的 AXI 流,数据以十六进制形式打包。但你的上游数据源往往是文本文件:CSV 日志、浮点数值、传感器读数。如何将这些人类可读的文本数据转换成 FPGA 能够直接消费的硬件格式?

这就是 data_mover_runtime 模块存在的意义。它不是一个运行时库,而是一组数据形态转换工具——将软件世界中的抽象数据类型(floatdoublehalf、各种位宽的整数)转换成硬件世界中的位级表示(固定宽度的十六进制比特流)。

这个模块的核心洞察是:数据在跨域流动时需要被重新诠释。同样的 32 位模式,在 CPU 的浮点单元中是 1.5f,在 FPGA 的 AXI 总线上就是 0x3fc00000data_mover_runtime 就是这个跨域诠释的翻译器。


架构全景:模块的角色与边界

在更大的 Vitis Libraries 生态中,data_mover_runtime 位于主机端软件栈的底层。它向上为各种算法演示和基准测试提供数据准备能力,向下直接操作文件系统和内存。

┌─────────────────────────────────────────────────────────────┐
│                    算法内核 (FPGA Kernel)                     │
│              期望: 固定宽度 AXI 流, 十六进制格式               │
└─────────────────────────────────────────────────────────────┘
                              ▲
                              │ 数据通过 OpenCL/XRT 加载
┌─────────────────────────────────────────────────────────────┐
│              data_mover_runtime (本模块)                      │
│         职责: 文本 → 十六进制比特流的格式转换                  │
│         核心: data_converter.cpp                             │
└─────────────────────────────────────────────────────────────┘
                              ▲
                              │ 读取文本文件
┌─────────────────────────────────────────────────────────────┐
│                     上游数据源                               │
│         CSV 文件, 数值日志, 随机生成的测试数据               │
└─────────────────────────────────────────────────────────────┘

关键边界约定

  1. 输入契约:上游必须提供可被 operator>> 解析的文本流。对于 half 类型,模块会先将输入读作 float 再转换,因为标准流操作不支持 half

  2. 输出契约:下游内核必须能够消费固定宽度的十六进制字符串数组。输出格式是逗号分隔的 C 风格字符串字面量,如 "0x3fc00000",可直接嵌入 C/C++ 源码或硬件测试平台。

  3. 位宽约束:输出流宽度必须是输入数据类型位宽的整数倍。例如,若输入是 32 位 float,输出宽度可以是 64、96、128... 但不能是 48。


核心组件深度解析

1. read_type<T> —— 类型层面的适配器

这是一个典型的 C++ 策略模式应用,用模板特化来处理类型系统的差异。

// 默认实现:identity mapping
template <class T>
struct read_type {
    typedef T type;
};

// 特化:half 类型需要先读作 float
template <>
struct read_type<half> {
    typedef float type;
};

设计意图half(16 位浮点)是 FPGA 加速器中常用的低精度数值类型,但标准 C++ I/O 库并不原生支持它。read_type 在编译期建立了一个类型映射表:当模板参数是 half 时,实际用于流读取的类型是 float,后续再通过显式转换得到 half

使用方式

typename read_type<T>::type tmp;
fin >> tmp;  // 编译期确定实际类型
vec[i++] = tmp;  // 若 T 是 half,这里会发生 float→half 转换

2. get_bits() 函数族 —— 位级reinterpretation

这是一组**类型双关(type punning)**函数,用于提取浮点数的底层位模式。

uint16_t get_bits(half v) {
    return v.get_bits();
}

uint32_t get_bits(float v) {
    union {
        float f;
        uint32_t u;
    } u;
    u.f = v;
    uint32_t ret = u.u;
    return ret;
}

uint64_t get_bits(double v) { /* 类似 float 的实现 */ }

设计意图:在硬件描述语言(如 Verilog/VHDL)中,数值通常以无符号整数的形式流动,语义由消费端根据固定位宽重新诠释。get_bits 就是软件端的对应物:它回答"这些比特在内存中究竟长什么样"的问题。

实现技巧

  • half 类型来自 Xilinx HLS 库,直接提供 get_bits() 成员函数
  • float/double 使用 union type punning,这是一种在 C/C++ 标准边缘跳舞的技术。它依赖实现定义行为(implementation-defined),但在主流平台(IEEE-754 浮点)上可靠

为什么不用 memcpymemcpy 是实现定义行为的标准化方式,但会引入函数调用开销。在 I/O 密集的转换场景中,union 方式通常被内联,且对于 4/8 字节的复制,现代编译器会生成最优代码。

3. generate_hex<T>() —— 主转换引擎

这是模块的核心编排逻辑,处理文件 I/O、类型转换、格式化和流控制。

template <typename T>
void generate_hex(std::string& ifile, std::string& ofile, 
                  const int width, const int total_num)

执行流程

  1. 参数校验

    • assert(width < MAXVEC) —— 输出宽度不能超过 512 位(硬件限制)
    • assert(n > 0) —— 输出宽度必须是输入类型位宽的整数倍
  2. 流准备

    • 打开输入文件文本流
    • 打开输出文件文本流
  3. 批处理循环

    while (fin >> tmp) {
        cur_num++;
        vec[i++] = tmp;  // 累料到本地数组
        
        if (i == n) {  // 凑够一个输出宽度
            // 输出十六进制字符串
            // 格式: "0x{高位在前的大端十六进制}"
            i = 0;  // 重置批处理指针
        }
        
        if (cur_num == total_num) break;
    }
    
  4. 尾部处理

    • 如果输入结束但数组未填满(i > 0),剩余位置用 0 填充
    • 这保证了硬件总能读到固定宽度的数据
  5. 资源清理

    • 关闭文件流

关键设计决策

决策 选择 理由
批处理大小 编译期计算 n = width / (8 * sizeof(T)) 硬件 AXI 流宽度固定,必须在编译期确定打包粒度
输出字节序 大端(高位在前) 匹配硬件预期,符合 RTL 设计惯例
填充策略 尾部零填充 保证事务完整性,避免硬件读到不确定状态
类型转换 编译期多态(模板) 零运行时开销,每种类型生成最优代码

内存与资源管理模型

内存所有权

资源 分配者 所有者 生命周期
vec[MAXVEC / W] 栈帧自动分配 generate_hex 函数 函数返回时销毁
fin/fout 流对象 栈帧自动分配 generate_hex 函数 close() 后或析构时
文件描述符 ifstream/ofstream 内部 流对象(RAII) 流析构时自动关闭

关键模式:完全使用栈分配 + RAII,没有堆内存管理负担。vec 是固定大小的 C 风格数组,其大小在编译期由 MAXVEC 和类型位宽确定。这种设计确保在资源受限的嵌入式主机环境中也能可靠运行。

对象生命周期与值语义

  • read_type<T>:纯编译期元函数,无实例,无生命周期
  • get_bits 返回值:按值返回 POD(Plain Old Data)整数,无复杂生命周期
  • 流操作:标准 iostream 的异常安全保证(basic guarantee)

错误处理策略

错误场景 处理方式 行为
参数违反(width 过大) assert() 调试构建中断言失败,发布构建未定义行为
文件打开失败 隐式检查 is_open() 无显式处理,操作失败标志需调用者检查
流提取失败 while (fin >> tmp) 条件 循环自然终止,剩余数据按尾部填充处理
类型不匹配 编译期模板实例化 不支持类型在编译期报错

设计权衡:该工具采用**快速失败(fail-fast)**哲学。参数检查使用 assert 而非异常,因为这些都是编程错误(调用者违反了契约),而非运行时异常条件。文件 I/O 错误没有被显式传播,因为该工具的典型使用模式是脚本化的批处理——输入文件的存在和质量由调用环境保证。


数据流全链路追踪

让我们追踪一次典型的转换调用:generate_hex<float>("input.txt", "output.hex", 128, 1024)

阶段 1:输入解析(主机文本域)

input.txt 内容:
1.0
2.5
3.14159
...
std::ifstream fin("input.txt");
typename read_type<float>::type tmp;  // 实际类型: float
fin >> tmp;  // 使用 operator>>(istream&, float)

关键转换:ASCII 字符序列 → IEEE-754 二进制浮点(主机字节序)

阶段 2:类型转换(若有)

vec[i++] = tmp;  // float → float (本例无转换)

T = half

vec[i++] = half(tmp);  // float → half (精度截断,范围检查)

阶段 3:批处理累积

const int W = 8 * sizeof(float);  // 32
float vec[MAXVEC / W];            // 缓冲区
const int n = 128 / 32;           // 每批 4 个 float

// 循环直到凑够 n 个元素
while (i < n) {
    fin >> tmp;
    vec[i++] = tmp;
}

阶段 4:比特提取与格式化(关键转换)

fout << "\"0x";
for (int j = n - 1; j >= 0; --j) {
    // j=3: vec[3] 是最高位 (MSB)
    // j=0: vec[0] 是最低位 (LSB)
    fout << std::hex << std::setfill('0') << std::setw(8) << get_bits(vec[j]);
}
fout << "\"";

// 输出示例: "0x40490fd040080000400200003f800000"
//           |  vec[3] ||  vec[2] ||  vec[1] ||  vec[0]  |

关键洞察:输出是大端(big-endian)格式——数组索引较小的元素被放在十六进制字符串的右侧(低位),这与硬件 AXI 流的预期一致。

阶段 5:尾部处理与资源释放

// 处理未填满的批次
if (i > 0) {
    // 剩余位置用 0 填充
    for (int j = n - 1; j >= 0; --j) {
        if (j >= i) fout << "00000000";
        else fout << get_bits(vec[j]);
    }
}

fin.close();
fout.close();

设计决策与权衡分析

决策 1:模板编译期多态 vs. 运行时类型分支

选择:使用 C++ 模板,为每种类型生成独立实例

template <typename T>
void generate_hex(...) { ... }

// 实例化 9 种类型
if (type == "half") generate_hex<half>(...);
else if (type == "float") generate_hex<float>(...);
// ...

替代方案:使用 void* 和类型标记,在运行时统一处理

权衡分析

维度 模板方案(实际选择) 运行时方案
性能 零开销,类型特化代码最优 分支预测开销,难以向量化
代码体积 9 份独立函数实例 单一份通用代码
类型安全 编译期检查,类型不匹配报错 运行时错误,类型标记需人工维护
调试体验 模板实例化错误信息复杂 单一调用栈,易追踪
扩展性 新增类型需添加模板实例化点 新增类型只需扩展 switch 分支

为何选择模板:这是一个数据密集型批处理工具,性能是首要考虑。模板确保每种类型生成最优的比特提取代码(特别是 half 直接调用 get_bits() 而非通过运行时分发)。同时,9 种类型 × 单一函数的规模在代码膨胀上是可接受的。

决策 2:Union Type Punning vs. memcpy/std::bit_cast

选择:使用 Union 进行类型双关

uint32_t get_bits(float v) {
    union { float f; uint32_t u; } u;
    u.f = v;
    return u.u;
}

替代方案

  • C++20 std::bit_cast<uint32_t>(v) —— 标准保证的合法类型双关
  • memcpy(&u, &v, sizeof(v)) —— 标准保证,但可能引入函数调用开销

权衡分析

方案 标准符合性 性能 代码复杂度 依赖
Union punning 实现定义(GCC/Clang 支持) 通常内联为寄存器操作 极简
std::bit_cast 标准 C++20 编译期为常量表达式,运行时最优 简单 C++20
memcpy 标准 C99/C++11 小尺寸可能被内联,大尺寸调用库 中等 标准库

为何选择 Union:该代码库的目标平台是 Xilinx/AMD FPGA 工具链,使用的编译器是 GCC/Clang,对 Union type punning 有良好支持。同时,代码库需要兼容 C++11/14(从头文件风格判断),无法依赖 C++20 的 std::bit_cast。Union 方案以零依赖、零开销、极简代码实现了目标。

潜在风险:在严格遵循 C++ 标准的场景下,Union 类型双关的返回值是"实现定义"的。若未来代码需要移植到对这方面严格的编译器,需要重构为 std::bit_cast(如果升级 C++20)或 memcpy

决策 3:栈分配缓冲区 vs. 堆分配/内存映射

选择:使用固定大小的栈分配数组

#define MAXVEC 512
T vec[MAXVEC / W];  // W = 8 * sizeof(T)

替代方案

  • std::vector<T> 动态堆分配
  • new T[n] 手动堆分配
  • mmap 文件内存映射

权衡分析

方案 内存位置 生命周期管理 缓存局部性 尺寸灵活性 失败模式
栈数组(当前) 自动,无泄漏风险 极佳(连续,热缓存) 固定上限(512 位宽度) 栈溢出(递归/大帧)
std::vector RAII,自动释放 好(连续,但需堆遍历) 运行时动态 bad_alloc 异常
new[] 手动,易泄漏 运行时动态 nullptr/异常
mmap 内核页缓存 手动/自动 依赖文件系统缓存 文件尺寸 系统调用失败

为何选择栈数组

  1. 确定性性能:数据处理是 I/O 密集型的,而非内存受限。栈分配避免了堆遍历和缓存未命中。

  2. 资源受限环境:FPGA 加速卡的主机端通常是嵌入式 ARM 或有限内存的服务器节点。栈分配不增加堆内存压力。

  3. 固定上限合理MAXVEC 512 位(64 字节)是 AXI4-Stream 的典型最大宽度。更大的宽度需要自定义协议,超出本工具范围。

  4. 无异常设计:该代码库似乎避免异常(无 try/catch 可见)。栈分配不抛出 bad_alloc,符合错误处理哲学。

潜在限制:若未来硬件接口宽度超过 512 位,需要修改 MAXVEC 并重新编译。


C/C++ 特定分析

内存所有权与 RAII

std::ifstream fin(ifile, std::ios_base::in);
std::ofstream fout;
fout.open(ofile);
// ... 处理 ...
fin.close();
fout.close();

所有权模型

  • fin/fout:栈分配,拥有底层 FILE*(通过 stdio 实现)。RAII 保证即使异常抛出,析构函数也会关闭文件描述符。
  • vec:函数栈帧拥有,无堆参与。
  • ifile/ofilestd::string 按引用传入,函数借用字符串视图,不拥有内存。

关键风险:代码手动调用 fin.close()/fout.close(),这是冗余但无害的。标准保证析构时会再次关闭,第二次 close() 是空操作。若中途需要错误检查,应在 close() 后检查 fail()

对象生命周期与模板实例化

template <typename T>
void generate_hex(...) { ... }

// main.cpp 中的显式实例化点
if (type == "half") generate_hex<half>(...);
else if (type == "float") generate_hex<float>(...);
// ... 8 more types

生命周期

  • 模板本身无生命周期,是编译期代码生成模板。
  • 每个 generate_hex<T> 实例是独立的函数,拥有独立的代码段和栈帧布局。
  • if-else 链在运行时选择调用哪个实例,一旦进入函数,类型 T 固定,无运行时多态开销。

值语义T 以值传递/返回(get_bits(T v)),对于小尺寸类型(最大 8 字节 double),这是最优的(寄存器传参)。无指针别名问题,编译器可自由优化。

错误处理策略

代码采用分层错误处理

层级 机制 适用场景
编译期 模板约束 类型不支持(如传入自定义类)导致编译失败
调试期 assert() 参数违反(width 过大,对齐错误)触发断言
运行时 流状态位 文件打开/读写失败通过 std::ios::fail() 隐式标记

显著特点:无异常传播。即使文件打开失败,代码继续执行(fout.open 后无检查),失败会延迟到第一个输出操作,此时 fout << ... 无实际写入。这是有意为之的简化:在脚本化使用场景中,调用者(shell/Makefile)负责前置条件检查。

危险区域

// 若 ofile 路径不可写,此处无报错
fout.open(ofile);
// ... 数百行处理 ...
fout.close();  // 静默失败,输出文件可能为空或不存在

生产环境使用应添加:

if (!fout.is_open()) {
    std::cerr << "Failed to open output file: " << ofile << std::endl;
    return 1;
}

const 正确性分析

代码在 const 使用上较为宽松:

// 当前实现
uint32_t get_bits(float v) { ... }  // v 不会被修改,但非 const
template <typename T>
void generate_hex(std::string& ifile, ...)  // ifile 不会被修改,但非 const&

改进建议

// 更严格的 const 正确性
uint32_t get_bits(const float v) { ... }
template <typename T>
void generate_hex(const std::string& ifile, const std::string& ofile,
                  const int width, const int total_num)

当前实现功能正确,但缺少 const 意味着编译器无法某些别名分析优化,且读者无法一眼确认参数是否被修改。

并发与线程安全

设计立场:单线程顺序执行。

  • 无全局/静态可变状态(MAXVEC 是宏定义的编译期常量)
  • 所有状态(流对象、缓冲区、计数器)都是函数栈局部
  • 流操作(fin >> tmp)是阻塞 I/O,无并发优化

多线程使用后果:若强行在多线程中并发调用 generate_hex,每个线程有独立栈帧,理论上数据竞争安全。但 ofstream 的并发写入未定义,且磁盘 I/O 会成为瓶颈,无实际加速收益。

性能架构

热点分析

代码段 复杂度 优化策略
fin >> tmp \(O(N)\),N=输入数量 缓冲 I/O,标准库优化
get_bits(vec[j]) \(O(1)\),每个元素 内联,寄存器操作
fout << std::hex << ... \(O(N \times W)\),W=输出宽度 格式化 I/O,不可避免

瓶颈:此程序是I/O 密集型。磁盘/文件系统 I/O 主导执行时间,CPU 计算(类型转换、格式化)占比低。优化方向应是减少 I/O 往返(如内存映射文件),而非微优化 get_bits

内存布局

  • vec 是连续数组,顺序访问,缓存友好
  • 输出格式化为十六进制字符串,每个字符 1 字节,输出文件大小约为输入数字个数的 \((2 \times W/8 + 2)\) 倍(0x 前缀 + 两个十六进制字符/字节)

使用模式与示例

基础用法:转换浮点测试数据

# 准备输入:一行一个浮点数
$ cat input.txt
1.0
2.0
3.14159
1.618
2.718

# 转换:4 个 float 打包成 128 位 AXI 流
$ ./data_converter -t float -i input.txt -o output.hex -w 128 -n 6

# 查看输出:每行是一个 C 字符串字面量
$ cat output.hex
"0x400000003f800000",
"0x402df85440090e56",
"0x00000000400a3c54"

处理半精度(half)数据

# 输入文本使用 float 格式(half 无法直接从文本解析)
$ cat half_input.txt
0.5
1.0
1.5

# 转换:8 个 half 打包成 128 位(half=16 位)
$ ./data_converter -t half -i half_input.txt -o half_output.hex -w 128 -n 3

# 输出:每个 half 占 4 个十六进制字符
$ cat half_output.hex
"0x00003c0038003000"
#      ^     ^    ^
#      |     |    0.5 (0x3000)
#      |     1.0 (0x3800)
#      1.5 (0x3c00,因 padding 到 8 个 half)

批处理脚本集成

#!/bin/bash
# prepare_test_data.sh:为多个测试用例生成输入数据

TYPES=("float" "int32_t" "half")
WIDTHS=(64 128 256)

for type in "${TYPES[@]}"; do
    for width in "${WIDTHS[@]}"; do
        input="test_${type}.txt"
        output="test_\({type}_w\){width}.hex"
        
        ./data_converter -t "\(type" -i "\)input" -o "$output" \
                         -w "$width" -n 1000
        
        if [ $? -ne 0 ]; then
            echo "Error processing \(type with width \)width" >&2
            exit 1
        fi
    done
done

边缘情况与潜在陷阱

1. 输出宽度对齐约束

// 危险:float 是 32 位,width 必须是 32 的整数倍
// 以下将导致 assert 失败
./data_converter -t float -w 48 ...  // 48 不是 32 的整数倍

应对:始终在脚本或调用代码中验证 width % (sizeof(T) * 8) == 0

2. 输入数量不匹配

// 输入文件只有 5 行,但 -n 10
./data_converter -t float -n 10 -i small_input.txt ...

// 实际行为:读取到文件结束,处理 5 个有效输入
// 注意:不会报错,调用者需自行确保输入数量充足

应对:生产环境使用前应验证输入文件行数与 -n 参数匹配。

3. Half 类型精度损失

// 输入文本:1.0001
// Half 只有 10 位尾数,约 3 位十进制精度
./data_converter -t half -i precise_input.txt ...

// 输出 hex 对应的 half 值可能是 1.0,精度被截断

应对:使用 half 类型前,确保输入数据范围在 ±65504 内,且精度需求不超过 3-4 位有效数字。

4. 大端 vs 小端误解

// 输出示例:"0x400000003f800000"
// 对应两个 float: 2.0 (0x40000000) 和 1.0 (0x3f800000)

// 数组索引: vec[0]=1.0, vec[1]=2.0
// 输出 hex: 高位在前 -> 2.0 在左,1.0 在右

陷阱:硬件工程师若期望"第一个输入在低位",会误解输出。实际上vec[0](最先读取的输入)被放在十六进制字符串的最右侧(LSB)。

应对:文档中明确标注输出格式为"大端,输入顺序对应 LSB 到 MSB"。


与其他模块的关系

data_mover_runtime
│
├─ 调用(uses):
│   ├─ xf::common::utils_sw::ArgParser (参数解析)
│   │   来自:外部库(xf_utils_sw)
│   │   用途:命令行选项解析,--help 生成
│   │
│   ├─ hls_half.h (Half 浮点类型)
│   │   来自:Vitis HLS 运行时
│   │   用途:16 位浮点数的软件仿真
│   │
│   └─ 标准库:<iostream>, <fstream>, <iomanip>
│      用途:文件 I/O、格式化输出
│
└─ 被调用(used by):
    ├─ 各类 kernel 测试平台(testbench)
    │   场景:为 FPGA kernel 准备输入数据
    │   示例:图像编解码器测试、FFT 输入生成
    │
    └─ CI/CD 自动化脚本
       场景:批量生成回归测试向量

重要依赖说明

  1. xf_utils_sw/ArgParser:该依赖表明 data_mover_runtime 不是孤立工具,而是 Vitis 生态系统的一部分。参数解析器的风格(-t, -i, -o 短选项)与 Vitis 其他工具一致。

  2. hls_half.h:使用 HLS_NO_XIL_FPO_LIB 宏禁用完整 FPO 库,仅引入基础 half 类型。这是链接时优化——避免引入未使用的浮点运算库。


总结:给新贡献者的行动指南

当你需要修改此模块时:

  1. 添加新数据类型支持

    • main()if-else 链中添加新分支
    • 若类型需要特殊读取逻辑(如 halffloat),添加 read_type<> 特化
    • 提供 get_bits() 重载(若类型非 POD)
  2. 修改输出格式

    • 调整 fout << ... 块的格式化逻辑
    • 注意保持逗号分隔符和引号格式(下游工具可能依赖)
  3. 处理更大宽度

    • 修改 MAXVEC 宏(当前 512)
    • 验证硬件平台是否支持更宽 AXI 流

常见调试场景:

现象 诊断 解决
assert(width < MAXVEC) 失败 请求的 AXI 宽度超过硬件支持 检查 -w 参数,或增大 MAXVEC 重新编译
输出 hex 长度不对 输入类型位宽与输出宽度不对齐 确保 width % (sizeof(T)*8) == 0
Half 结果与预期不符 Half 精度和范围有限 验证输入在 ±65504 内,精度需求 ≤3 位十进制
输出文件为空 输入文件不存在或 -n 过大 检查输入文件路径,验证文件行数与 -n 匹配

代码审查检查清单:

  • [ ] 新增类型是否提供了 get_bits() 支持?
  • [ ] read_type<> 特化是否必要?(仅当类型无法直接用 operator>> 读取时)
  • [ ] 文件 I/O 是否检查了 is_open()
  • [ ] 参数是否使用了 const& 避免不必要的拷贝?
  • [ ] 格式化输出是否保持与下游工具的兼容性?

附录:核心代码快速参考

支持的类型映射表

命令行参数 C++ 类型 位宽 读取方式 特殊处理
half hls::half 16 先读 float read_type<half> 特化
float float 32 直接读取
double double 64 直接读取
int8_t int8_t 8 直接读取 get_bits 模板默认实现
int16_t int16_t 16 直接读取 同上
int32_t int32_t 32 直接读取 同上
int64_t int64_t 64 直接读取 同上

输出格式规范

格式: 逗号分隔的 C 字符串字面量列表,每个字面量包含:
      - 前缀: "0x
      - 内容: 大端十六进制(高位字节在前)
      - 后缀: "

示例(2 个 float,宽度 64 位):
输入: 1.0, 2.0
输出: "0x400000003f800000"
       |______||______|
        2.0    1.0  (vec[1]=2.0 在高位,vec[0]=1.0 在低位)

多个批次用逗号换行分隔:
"0x400000003f800000",
"0x4040000040400000"

编译与运行

# 编译(假设已设置 Vitis 环境)
g++ -std=c++11 -I${XILINX_XRT}/include \
    -I${XILINX_HLS}/include \
    data_converter.cpp -o data_converter

# 运行帮助
./data_converter --help

# 典型调用:将 1000 个 float 转换为 128 位宽 hex 格式
./data_converter -t float -i input.txt -o output.hex -w 128 -n 1000

文档版本: 1.0

关联模块: codec_acceleration_and_demos, jpeg_and_resize_demos

On this page