有了第一篇文章的基础,现在我们来分析一个问题:假设某个FPGA的32位寄存器,我们打算用第一篇文章那样的结构体位域的形式来实现对该寄存器的读写。跟第一篇的内容类似,基本上就是结构体的声明不同,当在我们自己的PC机上测试,编译,执行,结果都没有问题。但是在FPGA上放起来时,却发现结果总是不正确。因为要用该结构体来访问寄存器,下面假定FPGA的32位寄存器地址是宏FPGA_REG_ADDR代表的地址:
PC代码如下:
#include <stdio.h> #include <string.h> typedef unsigned long ulong; struct A { ulong hdlc_ts_id : 5; //bit0-bit4 ulong hdlc_e1_id : 6; ulong transp_flag : 8; ulong hdlc_channel_en : 8; ulong reserved3 : 5; //bit27-bit31 }; struct A aa; int main() { memset(&aa, 0x0, sizeof(struct A)); aa.hdlc_e1_id= 13; aa.hdlc_ts_id= 1; return 0; }
FPGA代码:
#include <stdio.h> #include <string.h> typedef unsigned long ulong; struct A { ulong hdlc_ts_id : 5; //bit0-bit4 ulong hdlc_e1_id : 6; ulong transp_flag : 8; ulong hdlc_channel_en : 8; ulong reserved3 : 5; //bit27-bit31 }; struct A *aa= (struct A *)(FPGA_REG_ADDR); int main() { memset(aa, 0x0, sizeof(struct A)); aa->hdlc_e1_id= 13; aa->hdlc_ts_id= 1; return 0; }
FPGA上运行的结果,hdlc_e1_id的值读出来不是13而是5。从结果来看,应该是最高位的1(13d= 1101b)丢掉了。那么为什么PC上是正确的呢?我们先看反汇编后的代码,对于PC上和FPGA上的用的代码,反汇编后基本没有差别,摘出关键部分如下:
117 8048332: c7 05 44 95 04 08 00 movl $0x0,0x8049544 118 8048339: 00 00 00 119 804833c: 0f b7 05 44 95 04 08 movzwl 0x8049544,%eax 120 8048343: 66 25 1f f8 and $0xf81f,%ax 121 8048347: 66 0d a0 01 or $0x1a0,%ax 122 804834b: 66 a3 44 95 04 08 mov %ax,0x8049544 123 8048351: 0f b6 05 44 95 04 08 movzbl 0x8049544,%eax 124 8048358: 83 e0 e0 and $0xffffffe0,%eax 125 804835b: 83 c8 01 or $0x1,%eax 126 804835e: a2 44 95 04 08 mov %al,0x8049544
那么,导致操作结果PC和FPGA不一致的原因是什么呢? 我们知道,对于PC内存,是按照字节编址的。比如0x4000000开始的4个字节存放一个32位的值,那么0x40000000-0x40000003分别对应该32位值的四个字节。所以在第一篇中的操作:从 0x8049535 地址处取一个字节,修改,最后又写回该地址是没问题的。但是该FPGA的编址却不是按照字节来编址的,而是4字节,也就是32位编址的。也就是说,当在PC上执行126行的 mov %al, 0x8049544时,是可以只修改0x8049544的最低一个字节的内容的。但在该FPGA上却是不可以的,同样执行第126句命令,对于该FPGA来说却是把整个32位寄存器的内容都修改成了寄存器al的值。因为FPGA并不支持从四个字节中的某个字节读写,从结果的现象中也反应了这种情况就是有些值是随机出现的,没有规律。这样就解释了为什么我们写入的是13,而结果却是5了。回顾上面的结构体声明,hdlc_e1_id共占6位,但是它的低3位是和hdlc_ts_id共用的一个8位,也就是一个字节,而我们的程序又是先写的hdlc_e1_id,后写的hdlc_ts_id,所以当执行126行的 mov %al, 0x8049544时,自然在FPGA上的该寄存器就只剩了低8位的值了,所以13就变成了5......
这样也许有人会问,是不是对于该FPGA就不能进行位域结构体这样的操作了?当然不是,只是要用点小手段。因为该问题归根结底是该FPGA不是按照字节编址的,而只能按32位写入。不过具体方法这里就不再赘述了,简单的说就是加个共用体,读者可以参考:http://book.51cto.com/art/201012/237939.htm 中使用的方法。另外,要说明的是,我们当然可以直接读写该寄存器,即不用上述的位域结构体的形式,这里只是为了讨论用位域结构体的方式可能带来的问题,读者不要误解。
位域结构体的好处主要体现在 1.为读操作时提供方便。2.也可以在代码中起到注释的作用,方便看该寄存器各个位的含义。
总结:
1. C语言不适合不能寻址到字节粒度的访问。
2. C语言位域比较容易带来问题,尽量不要用。