I am defining some registers (they're all 32-bits wide) to interface with a hardware peripheral using bitfields. At first I defined everything by mixing uint8_t, uint16_t, and uint32_t types. For example:
...
// More definitions above
union {
volatile uint32_t I2C_REVNB_HI;
volatile struct {
uint16_t FUNC : 12;
uint8_t RSVD0 : 2;
uint8_t SCHEME : 2;
uint16_t RSVD1 : 16;
} I2C_REVNB_HI_bits;
}; // 0x04 - 0x08
...
// More definitions below
When a bitfield is bits <= 8 I assign it to a uint8_t, and similarly when 8 < bits <= 16 I assigned it to a uint16_t and 16 < bits <= 32 to a uint32_t . This looked a bit messy to me however and I refactored everything to be uint32_t regardless of the bit width since I assumed it would not matter:
...
// More definitions above
union {
volatile uint32_t I2C_REVNB_HI;
volatile struct {
uint32_t FUNC : 12;
uint32_t RSVD0 : 2;
uint32_t SCHEME : 2;
uint32_t RSVD1 : 16;
} I2C_REVNB_HI_bits;
}; // 0x04 - 0x08
...
// More definitions below
I tried running my code with both implementations of the register definitions, but the one where everything is uint32_t seems to not work; the peripheral is not behaving as expected. So my question is, does anything happen under the hood that is different between the two implementations? I assumed there would be no difference since all bit fields are the same length as before. I could not really find anything online about this.
PS. The code I am writing is to run on one of the Programmable Real-time Units of the AM335X processor, to interface with the I2C peripheral and is compiled using the clpru compiler.
Some notes from the clpru compiler document on bit-fields (p108) that may or may not be useful:
- "Bit fields are handled using the declared type"
- "Bit fields declared volatile are accessed according to the bit field's declared type. A volatile bit field reference generates exactly one reference to its storage; multiple volatile bit field accesses are not merged"
- "The size of a struct containing the bit field depends on the declared type of the bit field. For example, both of these structs have a size of 4 bytes:"
struct st {int a:4};
struct st {char a:4; int :22;};
And on (p69) of the compiler manual:
"In addition to _Bool, signed int, and unsigned int, the compiler allows char, signed char, unsigned char, signed short, unsigned shot, signed long, unsigned long, signed long long, unsigned long long, and enum types as bit-field types."
Update 1
I tried to check if there was any difference assigning to the fields as suggested by @IanAbbot:
i2c_refactored.I2C_REVNB_HI = 0;
i2c_refactored.I2C_REVNB_HI_bits.SCHEME = ~0;
i2c_refactored.I2C_REVNB_HI_bits.FUNC = ~0;
DEBUG_MEMORY_0.status = i2c_refactored.I2C_REVNB_HI;
i2c_original.I2C_REVNB_HI = 0;
i2c_original.I2C_REVNB_HI_bits.SCHEME = ~0;
i2c_original.I2C_REVNB_HI_bits.FUNC = ~0;
DEBUG_MEMORY_1.status = i2c_original.I2C_REVNB;
But the outputs are the same: 0x0000CFFF (or 0b00000000000000001100111111111111)
Update 2
After trying to set all fields to unsigned int instead of uint32_t, the faulty behaviour still occurs
Update 3
I made the following simple testcase:
int main(void){
volatile pru_I2C_tmp i2c_refactored = {0};
volatile pru_I2C i2c_working = {0};
i2c_refactored.I2C_REVNB_HI = 0;
i2c_refactored.I2C_REVNB_HI_bits.SCHEME = ~0;
i2c_refactored.I2C_REVNB_HI_bits.FUNC = ~0;
DEBUG_MEM0.status = i2c_refactored.I2C_REVNB_HI;
i2c_working.I2C_REVNB_HI = 0;
i2c_working.I2C_REVNB_HI_bits.SCHEME = ~0;
i2c_working.I2C_REVNB_HI_bits.FUNC = ~0;
DEBUG_MEM1.status = i2c_working.I2C_REVNB_HI;
__halt();
return 0;
}
With the corresponding ASM (manual link):
[0x0000] 0x240000c0 LDI R0.w2, 0x0000
[0x0001] 0x24080080 LDI R0.w0, 0x0800
[0x0002] 0x0504e0e2 SUB R2, R0, 0x04
[0x0003] 0x2eff818e UNKNOWN-F2
[0x0004] 0x230007c3 JAL R3.w2, 0x0007
[0x0005] 0x240001ee LDI R14, 0x0001
[0x0006] 0x230040c3 JAL R3.w2, 0x0040
[0x0007] 0x05ffe2e2 SUB R2, R2, 0xff
[0x0008] 0x240800ef LDI R15, 0x0800
[0x0009] 0x2400d8f0 LDI R16, 0x00d8
[0x000a] 0xe1fd02c3 SBBO R3.b2, R2, 253, 2
[0x000b] 0x05b3e2e2 SUB R2, R2, 0xb3
[0x000c] 0x0100e2ee ADD R14, R2, 0x00
[0x000d] 0x230033c3 JAL R3.w2, 0x0033
[0x000e] 0x2408d8ef LDI R15, 0x08d8
[0x000f] 0x2400d8f0 LDI R16, 0x00d8
[0x0010] 0x01d8e2ee ADD R14, R2, 0xd8
[0x0011] 0x230033c3 JAL R3.w2, 0x0033
[0x0012] 0x240000e0 LDI R0, 0x0000
[0x0013] 0x24c000e1 LDI R1, 0xc000
[0x0014] 0xe1042280 SBBO R0, R2, 4, 4
[0x0015] 0x0104e2e0 ADD R0, R2, 0x04
[0x0016] 0xf100208e LBBO R14, R0, 0, 4
[0x0017] 0x12e1eee1 OR R1, R14, R1
[0x0018] 0xe1002081 SBBO R1, R0, 0, 4
[0x0019] 0x240fffe1 LDI R1, 0x0fff
[0x001a] 0xf100208e LBBO R14, R0, 0, 4
[0x001b] 0x12e1eee1 OR R1, R14, R1
[0x001c] 0x2eff818e UNKNOWN-F2
[0x001d] 0xe1002081 SBBO R1, R0, 0, 4
[0x001e] 0x240001c1 LDI R1.w2, 0x0001
[0x001f] 0x24000081 LDI R1.w0, 0x0000
[0x0020] 0xf1042280 LBBO R0, R2, 4, 4
[0x0021] 0xe1002180 SBBO R0, R1, 0, 4
[0x0022] 0x240001c1 LDI R1.w2, 0x0001
[0x0023] 0x24024881 LDI R1.w0, 0x0248
[0x0024] 0xe1dc228e SBBO R14, R2, 220, 4
[0x0025] 0xf1dd0200 LBBO R0, R2, 221, 1
[0x0026] 0x13c00000 OR R0.b0, R0.b0, 0xc0
[0x0027] 0xe1dd0200 SBBO R0, R2, 221, 1
[0x0028] 0x240fff80 LDI R0.w0, 0x0fff
[0x0029] 0xf1dc02c0 LBBO R0.b2, R2, 220, 2
[0x002a] 0x1280c080 OR R0.w0, R0.w2, R0.w0
[0x002b] 0xe1dc0280 SBBO R0, R2, 220, 2
[0x002c] 0xf1dc2280 LBBO R0, R2, 220, 4
[0x002d] 0xe1002180 SBBO R0, R1, 0, 4
[0x002e] 0x2a000000 >> HALT
[0x002f] 0x01b3e2e2 ADD R2, R2, 0xb3
[0x0030] 0xf1fd02c3 LBBO R3.b2, R2, 253, 2
[0x0031] 0x01ffe2e2 ADD R2, R2, 0xff
[0x0032] 0x20c30000 JMP R3.w2
[0x0033] 0x5100f00c QBEQ 12, R16, 0
[0x0034] 0x10eeeef1 AND R17, R14, R14
[0x0035] 0x24003000 LDI R0.b0, 0x0030
[0x0036] 0x70f00002 QBGE 2, R0.b0, R16
[0x0037] 0x10f0f000 AND R0.b0, R16, R16
[0x0038] 0x0400f0f0 SUB R16, R16, R0.b0
[0x0039] 0xff00cf12 LBBO R18, R15, 0, b0
[0x003a] 0xef00d112 SBBO R18, R17, 0, b0
[0x003b] 0x5100f004 QBEQ 4, R16, 0
[0x003c] 0x0000efef ADD R15, R15, R0.b0
[0x003d] 0x0000f1f1 ADD R17, R17, R0.b0
[0x003e] 0x21003500 JMP 0x0035
[0x003f] 0x20c30000 JMP R3.w2
[0x0040] 0x230042c3 JAL R3.w2, 0x0042
[0x0041] 0x21004100 JMP 0x0041
[0x0042] 0x10000000 AND R0.b0, R0.b0, R0.b0
[0x0043] 0x20c30000 JMP R3.w2
DEBUG_MEM0 is at address 0x10000 and DEBUG_MEM1 at address 0x10248. The statements seem logical, albeit that there are some statements I don't fully understand. (R2 is the stack pointer and R14 the return register). Does anyone see anything that could hint to why it is not functioning?
The C standard does not specify how an implementation lays out bit-fields in storage units. C 2018 6.7.2.1 11 says (emphasis added):
So a C compiler is free to allocate one, two, or more bytes for a 2-bit bit-field, it is free to allocate two or more bytes for a 12-bit bit-field (assuming eight-bit bytes), and so on. It may base its decision based on the nominal type of the bit-field.
About the only constraint the standard imposes on selecting storage units is, also in 6.7.2.1 11: