字节顺序定义数据在计算机系统中的存储格式。它描述存储器中地址的最高有效位(MSB)和最低有效位(LSB)的位置。对于数据始终以32位形式保存在系统存储器中的真正32位系统,字节顺序没什么实际意义,但是对于要将字节或16位半字映射到系统存储器中32位字的系统,字节顺序不匹配就会影响数据完整性。
字节顺序结构有两种类型,大端模式(Big Endian, BE)和小端模式(Little Endian, LE)。大端模式将MSB保存在最低存储器地址处,小端模式将LSB保存在最低存储器地址处。多字节数据的最低存储器可以认为是数据的起始地址。
下表是分别按大端模式和小端模式保存在存储器中的32位十六进制数0xAABBCCDD。字节0表示最低存储器地址。
在按原生数据类型(如32位)引用数据时,两种字节顺序所存储的多字节数据域是相同的。但在按字节或半字类型访问数据时,字域次序与系统的字节顺序配置相关。
在以半字方式处理时,存储器地址必须是2的倍数。
大多数嵌入式通信处理器和定制解决方案在数据层采用大端模式(PowerPC,SPARC等)。因为为这些处理器所写的老程序通常遵循网络字节顺序(大端模式)。
某些CPU可以通过设置处理器寄存器而在大端或小端模式(双字节顺序)间切换。
在包含若干IP的SOC中必然会发生字节顺序不匹配的问题,几乎所有第三方公司的IP都支持与处理器同样的字节顺序类型。处理字节顺序不匹配问题的最简单方法是为系统选择一种字节顺序类型(即,小端模式或大端模式),并将全部其他不同字节顺序的模块转化为目标字节顺序类型。
典型的字节顺序类型由系统中CPU体系结构的实现决定,所以建议目标的字节顺序类型与处理器的字节顺序类型相匹配。在对第三方IP选型时要考虑的另一个因素是确认其是否支持双字节顺序结构,以使IP可以方便地编程为大端模式或小端模式以与系统无缝集成。
有两种连接相反字节顺序外设的方法。根据应用的需要,要么选择将地址保持稳定(在地址不变处字节保持在同一地址)或者将位顺序保持稳定(在数据不变处地址改变)。
由于不同的字节顺序模式下多字节域具有不同的字节地址,因此,如果将多字节域作为单个条目进行操作,那么当其在各IP之间移动时,必须保留该条目的位次序。
对于跨字节边界的多位域也同样如此。比如,一个含16位控制寄存器编程模型的IP。如果控制寄存器位域 [8:7] 定义了控制域,那么要求对于所有访问控制寄存器的操作,要保持该16位之间的稳定关系,而不再与字节顺序有关。
为了理解匹配字节顺序以使数据位顺序完整的过程,以由小端模式的外设接收连续帧并将接收到的数据通过DMA/CPU保存到系统存储器举例。
接收到的连续帧以Type、H2、H1和H0的顺序保存在外设存储器中。帧中的域可能跨多个字节并不在字节边界处终止,例如状态域可能是12位。所以对于应用程序来说,数据不会因为字节顺序转换而改变就显得很重要,因为这样软件就可以以该顺序处理数据了。
在上图中,数据按小端寻址保存在外设的存储器中。现在当数据要传送到大端模式的系统RAM中时,必须保证数据位顺序保持不变。为了用硬件达到这个目的,需要对访问外设RAM的存储器的地址进行修改。对地址的改动基于要传送数据的大小,如下所示:
使用上面的逻辑,对地址总线最后两个LSB取反,数据不变,对应HDL代码如下:
assign le_ram_addr[31:0] = (Size = 8-bits) ?
{ram_addr[31:2], ~ram_addr[1], ~ram_addr[0]};
(Size = 16-bits) ?
{ram_addr[31:1], ~ram_addr[0], ~ram_addr};
assign le_ram_data[31:0] = data[31:0];
使用上面的方案可以使字节顺序转换对软件透明,并能确保数据完整性在字节顺序转换过程中不会被破坏。
从小端设备到大端存储器,并保持数据不变的数据流如下所述:
1)DMA发出对外设存储器的读字节操作。
2)若系统产生的地址为0x00,在数据变化的实现中,小端模式设备RAM所看到的地址为0x03。
3)设备存储器对该地址解码,访问位31:24即Type域。
4)外设输出该值为{Type, 0x000000} (32位输出)。
5)DMA对系统的大端存储器发出按字节方式的写操作。
6)再次产生0x00地址(字节访问)。
7)大端存储器将该访问解码为写入位31:24。
8)由于来自于小端存储器的数据处于同样的字节区域,因此可以保持数据的完整性并将数据保存在大端RAM中。
9)对其他需要从外设RAM传输到系统RAM中的数据继续进行该操作。
10)16位和32位访问过程与上面所述相同,只是地址需要加以改变。
与数据不变的字节顺序转换相比,某些应用程序或系统不需要数据保持特定的次序,但是需要在字节顺序转换后数据字节保持在同样的地址区域。这时就需要使用地址不变的字节顺序变换。
参考同一个接收连续帧的例子,对于地址不变的系统,访问字节Type的地址偏移量永远是0x3。而在前一节中,访问该字节需要使用不同的地址偏移量。为了用硬件实现该过程,需要修改或交换从外设RAM中读到的数据值,RTL代码如下所示:
assign le_ram_addr[31:0] = ram_addr;
assign le_ram_data[31:0] = data[0:31];
字节转换顺序如下图所示:
使用地址不变方式,从小端外设到大端存储器的数据流如下所述:
1)DMA发出对外设存储器的读字节操作。
2)若系统产生的地址为0x00,地址不变的实现使地址值始终相同。
3)外设RAM对该地址解码,访问位[7:0]或H0区域。
4)外设输出的数据为{0x000000, H0} (32位输出)。由于字节顺序匹配时地址不变,给系统RAM的数据变为{H0,0x000000} 。
5)DMA发出对系统大端存储器的字节写操作。
6)再次产生地址0x00(字节访问)。
7)大端存储器将该访问解码为写入位[31:24] 。
8)在字节顺序转换完成后,从小端存储器读出的数据已处在同样的地址区域,把地址存入大端RAM。
9)对其他需要从外设RAM传递到系统RAM的字节继续进行以上操作。
10)对16位或32位的访问,上述过程是相同的,只需将输出的数据进行交换即可。
交换字节是实现字节顺序转换的一种方式。在由应用程序本身决定字节顺序的系统中,该模式是有用的。因此,无需用硬件实现这种字节顺序匹配过程。字节顺序中性代码的字节交换方法使用字节交换控制来决定是否必须进行字节交换过程。
在软件中通常使用的各种字节交换方法有:
1)交换汇编指令。
2)用于交换字节的软件库宏。
3)协议特定的交换函数。
4)制定的交换函数。
该方法的限制在于,在软件中实现交换功能会增加额外开支。位交换所引起的软件开支虽然存在,但是在需要处理的包的数量很多时开支引起的问题很容易修复,特别是在高频处理器中。
避免由字节顺序所引发问题的最好方法是在设计中使用字节顺序中性。可以通过两种途径完成这一任务:
字节顺序中性代码可以通过标识外部软件接口实现,遵循下面的指南来访问这种接口:
遵守字节顺序中性指南能使代码有更好的移植性,可以使同一源码运行在不同字节顺序结构的处理器上,从而减少了平台移植时的负担。