采样包结构与实时数据传输封包逻辑设计

本设计基于http://120.55.75.228:23080/zhdyz/hutp/-/tree/master/Design中文档 《HUTP.pdf》基础上进行设计。

相关参数

采样率:子卡进行采集的采样率。可调,通常设定为一定范围内的整数值,如:80 000 000(80M),2 000 000(2M),500 000(500k),50 000(50k)等。目前约定,不同子卡的采样率,必须互为整数倍。

采样包:针对不同子卡采样率不一致的情况下,基于旧版的采集数据组织结构采样点,新设计的相关采集数据组织结构。

Blob容量:一个Blob包含的采样包数量。

子卡采样点:一张子卡单次采集下,所有通道数据本次采集的数据集合。

总采样数:一次采集的采样包数,当采够总采样数时,采样会停止。

通道掩码:表示各个子卡内相关AI/AO通道中,哪些通道采集数据会被实时传输。

降采样系数:表示各个子卡的子卡采样点中,需要从多少次采集的子卡采样点中抽取一个用于实时传输。

事务编号:用于表示采集工作的编号。

采集模式:载板采集数据工作形式,分为单次采集模式(SHOT MODE)、连续采集模式(STREAM MODE)。SHOT MODE,本次采集工作,采集数据达到总裁样数个采样点后会自行停止;STREAM MODE,本次采集工作,载板持续进行采集,直到收到停止采集的的外部触发信号或指令才会主动停止。

采样包

设计背景

在之前版本的设计中,采集数据组织结构为采样点。采样点包含了单次采样里面,所有子卡的所有开启通道在本次采样中的全部采集数据。该设计里面,预设所有子卡按照统一采样率同步进行工作,因此,所有子卡同步进行采样。且在配置参数不变的情况下,采样点大小恒定。

例如,配置5张2M32ch AD子卡,通道全开,所有子卡按照2M采样率进行采集。此时,单个采样点包含5张子卡,每张子卡32通道的总计160通道采集数据,所有数据都是各个子卡同一时刻进行采样得到的。

当前版本里面,不同的AI采集卡,采样率可能存在不同,因此,某张子卡进行采样的同一时刻,其他子卡可能不会进行采样。此外,相同时间里面,不同的采集卡进行的采样次数也不相同。因此,对采样点概念进行相关拓展,设计采样包结构。

定义

采样包:所有参与本次采集的采集卡数据源,在同一个时间窗口内产生的数据,按约定顺序组织形成的逻辑数据单元。每个数据源贡献该窗口内的一个或多个完整子卡采样点;各采集卡数据源提供的子卡采样点数量,由自身采样率和窗口长度决定。

时间窗口

当前约定,采样包表示的时间窗口,为最低采样率的对应采集卡的采样周期。由于我们已约定,不同子卡的采样率,必须互为整数倍,因此,该时间窗口长度必然为其他子卡的采样周期整数倍,在该时间窗口内,均可以完成整数次完整采样,次数为该子卡采样率 / 最低采样率。

例如,配置子卡0,1的采样率为2M,子卡2的采样率为40M,子卡3,4的采样率为80M,则采样窗口长度约定为500ns,在该窗口内,子卡0,1可以完整完成1次采样,子卡2可以完整完成20次采样,子卡3,4可以完成40次采样。

采集数据组织格式

一个采样包时间窗口长度,对应最低采样率子卡的一个采样周期。在该时间窗口内,以最高采样率的采样周期划分若干个基本时隙,按照时间先后依次组织数据。

每个时隙内,仅写入在该时隙对应时刻进行采样的子卡数据;多张子卡在同一时刻采样时,按照子卡编号从小到大排列。每张子卡的一次采样数据,由该次采样的全部有效通道值按约定通道顺序组成。

未在当前时隙采样的子卡不写入数据,也不插入占位或补零字节。各时隙的数据连续紧密拼接,完成整个时间窗口后,即形成一个采样包。

使用该示例说明:

子卡0: 2M,32通道
子卡1: 2M,32通道
子卡2:40M, 4通道
子卡3:80M, 3通道
子卡4:80M, 3通道

此时,采样包的采样周期长度500ns,基本时隙为12.5ns,一个采样包包含40个基本时隙的采集数据。组包格式如下图所示:

在一个采样包里面:

时隙编号 相对时间 本时隙的数据排列
0 0ns 子卡0第0点 → 子卡1第0点 → 子卡2第0点 → 子卡3第0点 → 子卡4第0点
1 12.5ns 子卡3第1点 → 子卡4第1点
2 25ns 子卡2第1点 → 子卡3第2点 → 子卡4第2点
3 37.5ns 子卡3第3点 → 子卡4第3点
4 50ns 子卡2第2点 → 子卡3第4点 → 子卡4第4点
… … …
38 475ns 子卡2第19点 → 子卡3第38点 → 子卡4第38点
39 487.5ns 子卡3第39点 → 子卡4第39点

其中,每个“子卡第几点”都展开成该子卡对应的一整组通道值。例如时隙0为:

sub0_point0_ch0 … sub0_point0_ch31 ->
sub1_point0_ch0 … sub1_point0_ch31 ->
sub2_point0_ch0 … sub2_point0_ch3 ->
sub3_point0_ch0 … sub3_point0_ch2 ->
sub4_point0_ch0 … sub4_point0_ch2

这里的时隙描述的是采样时间关系,不要求FPGA恰好每12.5ns完成一轮读FIFO;实际传输可以经过缓存后连续执行,只要保持上述排列顺序。64位传输拍也可以跨越通道组和时隙边界,不需要为每个时隙单独对齐补零。

采样包与采样点关系

当各个子卡采样率一致的时候,一个采样包时间窗口长度,等同于采样包内的基本时隙长度,此时,一个采样包只会包含一个采样时隙,每张子卡贡献的子卡采样点有且仅有一个。当各个子卡采样时刻对齐的时候,采样包组织结构和旧版的采样点结构一致。

因此,可以认为,旧版的采样点结构,属于现版的采样包结构在特殊的参数配置下的一种情况。

应用范围

采样包不只是应用于实时数据传输,还会在数据流盘的文件组织、预处理模块中应用到相关结构。

另外,在实时数据传输过程中,还会有降采样操作,不同子卡降采样倍率可能不相同。此时,最终某通道的数据在实时传输里面,表现的实际采样率,既会受到子卡本身采集工作的采样率影响,也会受到载板端的对应子卡采集数据降采样倍率影响。最终载板实时采集数据传输过程中,进行采样包组织的时候,需要以经过降采样的实际采样率为各个子卡采样率的参考进行组包。

Blob/Slice

采样块(Blob):在采集过程中,传输的采集数据基本单元,由若干个采样包组成。

采样片(Slice):当Blob容量过大,单个UDP包无法完整传输一整个Blob,则需要将一个Blob,结合UDP包最大容量和各个采样包大小,将Blob分片为能够通过单个UDP包传输的多个采样片。

采样包容量小于UDP包最大容量

当一个采样包小于UDP包最大容量时,一个Slice对应着一个实际的UDP包,内部包含着多个采样包,一个采样包包含所有开启的子卡一个采样周期内采集到的数据。

采样包容量小于UDP包最大容量的情况下,HUTP传输单元格式示意图如下所示:

分片逻辑如下:

  1. 根据单个UDP包最大容量,结合单个采样包大小(p * 2),以及包头字段长度k,计算出单个Slice最多能容纳n个采样包,其中,p表示单个采样包内包含的采集通道数。此时,(p * 2) * n + k ≤ 单个UDP包最大容量。
  2. 根据单个Slice最多容纳n个采样包,计算得出单个Blob至少需要分片为m个Slice,其中,前(m-1)个Slice包含n个采样包,最后一个Slice包含i个采样包,则(m - 1) * n + i = Blob容量。

通常可设置UDP包payload的最大长度为2016字节(需开启巨型帧)(普通帧的最大MTU为1500字节,最大UDP payload为1500-20-8=1472字节)。

目前设定payload部分的头16个字节为UDP包的包头,后面的所有字节由多个采样包组成。

采样包容量大于UDP包最大容量

但是,在当前更新采样点结构为采样包结构后,一个采样包大小可能会大于一个UDP包最大容量。

例如如下极端情况:

子卡0,1的采样率为200k,每张子卡开启32通道;子卡2,3,4采样率为80M,每张子卡开启3通道。此时,按照前面所说的采样包组织格式,单个采样包内,会包含1个子卡0,1的子卡采样点数据,以及(80M/200k)=400个子卡2,3,4的子卡采样点数据。按照每通道2字节位宽计算,可得:此时一个采样包大小为2 * 1 * 32 * 2 + 3 * 400 * 3 * 2 = 7328字节,远大于前面所述的2016字节。

当不同子卡之间,采样率差距越大,则采样包大小越大,且没有上限。因此,建议对传输过程中的不同子卡采样率差距进行相关限制,避免采样包大小严重膨胀。目前建议不同子卡之间采样率差距不应大于100倍。另外,该情况下,约定分片内部,不跨采样包存储采集数据。

在这种情况下,需要对每个采样包进行分片处理。分片逻辑设计如下:

  1. 根据单个UDP包最大容量u,结合单个采样包大小(p * 2),以及包头字段长度k,计算出,单个采样包至少需要n个Slice来传输,其中,p 应表示单个采样包内的 16 位采样值总数,包含高采样率子卡的多次采样,不能仅理解为开启的物理通道数。此时,n * (u - k) ≥ (p * 2)。
  2. 实际在传输一个采样包的过程中,最后一个Slice的大小为i字节,因此,(n - 1)* (u - k) + (i - k) = (p * 2)。
  3. 此时一个Blob包含m个采样点,因此一个Blob包含m * n个Slice。

此时HUTP传输单元格式示意图如下所示:

丢包处理逻辑

另外,需要注意。在旧有采样点组织格式里面,单个采样点大小永远小于UDP包的最大容量,因此,不论是否存在丢包,上位机端永远可以接受到完整的采样点的采集数据。但是,现在的采样包组织格式里面,如果采样包大小大于单个UDP包最大容量,则丢包后,单个采样包内数据会出现缺失,直接严重影响上位机端的采集数据解析逻辑。因此,需要提供配套的丢包处理逻辑,保证采样包完整后再发布。

初步设计,在包头添加以下信息:

  1. 采样包编号:找到对应的重组缓存;
  2. 采样包总有效长度:确定完整数据范围;
  3. 当前分片的数据偏移:把数据放到正确位置;
  4. 当前分片有效长度:确定本次写入范围;
  5. 其他所需信息。

上位机端接收程序应当持续收包,同时维护重组缓存。

接收UDP数据
    ↓
检查包头、长度、偏移及配置
    ↓
放入对应采样包的重组缓存,更新已接收范围
    ├── 已完整:按顺序发布至共享内存
    └── 未完整:保留缓存,继续接收其他包

周期性检查重组缓存
    ↓
超时仍不完整:丢弃该采样包,记录缺失序号,释放缓存

超时应从首次收到该采样包数据开始计时,且不因重复分片无限延后。超时时间应覆盖正常分片发送间隔和接收调度波动;完整包一旦收齐就立即处理,不需要等待超时到期。同时设置缓存数量和总容量上限,避免持续丢包时缓存无限增长。完整性检查必须确认全部字节范围都已覆盖且没有缺口。不能只把收到的长度相加,否则重复收到同一分片,可能被误判为已经收齐。


本文章使用limfx的vscode插件快速发布