Graznarak
Graznarak

Reputation: 3692

Bitfield write size

I have a volatile struct/bitfield for a memory mapped register on an ARM processor. The particular peripheral must be accessed by words.

struct
{
  unsigned field1 : 1;
  unsigned field2 : 3;
  unsigned field3 : 4;
  unsigned : 24;
} volatile my_variable __attribute__((section(".bss.my_periph")));

ARM Compiler V5 generates 32-bit accesses. ARM Compiler V6 is smart enough to see that only field2 has changed and generates 8-bit accesses. These 8-bit accesses break the world.

Is there a way to ensure that accesses are done by word?

I expect that something like this would work, but I would rather avoid the union:

union
{
  struct
  {
    unsigned field1 : 1;
    unsigned field2 : 3;
    unsigned field3 : 4;
    unsigned : 24;
  } fields;
  unsigned word;
} volatile my_variable __attribute__((section(".bss.my_periph")));

Upvotes: 3

Views: 302

Answers (2)

Lundin
Lundin

Reputation: 213989

Is there a way to ensure that accesses are done by word?

Yes, don't use bit-fields but uint32_t. Which also removes loads of other problems caused by bit-fields, such an undefined bit order, byte order, alignment, padding, "storage unit boundaries", integer types actually supported, signed vs unsigned and so on. The root of all such problems is the pretty much non-existent standardization of bit-fields.

Instead use bit-wise operators and masking. You could just go my_var = THIS | THAT | ~(NOT_THAT); and that's likely just fine for most use cases. Keep it simple.

If we insist on a more advanced, fancy solution, we could cook up more detailed macros - this is purposely a quite verbose solution but I just want to show the options:

// positions of fields within the bit-field
#define MYVAR_FIELD1_POS 31
#define MYVAR_FIELD2_POS 28
#define MYVAR_FIELD3_POS 24

// sizes of fields
#define MYVAR_FIELD1_SIZE 1
#define MYVAR_FIELD2_SIZE 3
#define MYVAR_FIELD3_SIZE 4

// bit masks based on sizes
#define MYVAR_FIELD1_MASK ((1u << MYVAR_FIELD1_SIZE)-1)
#define MYVAR_FIELD2_MASK ((1u << MYVAR_FIELD2_SIZE)-1)
#define MYVAR_FIELD3_MASK ((1u << MYVAR_FIELD3_SIZE)-1)

// bit masks with a value shifted to the correct position
#define MYVAR_FIELD1(val) ( ((val) & MYVAR_FIELD1_MASK) << MYVAR_FIELD1_POS )
#define MYVAR_FIELD2(val) ( ((val) & MYVAR_FIELD2_MASK) << MYVAR_FIELD2_POS )
#define MYVAR_FIELD3(val) ( ((val) & MYVAR_FIELD3_MASK) << MYVAR_FIELD3_POS )

This is how many of the more properly written register maps for microcontrollers work. Now you can just write:

my_var = MYVAR_FIELD1(1) | MYVAR_FIELD2(5) | MYVAR_FIELD3(15);

and you'll get 0xDF000000 (1 | 5 in first nibble then 0xF in next nibble). Benchmarking the above on gcc x86 for example yields a single instruction:

mov     DWORD PTR [rsp+12], -553648128

Where the magic number -553648128 = 0xDF000000.

Regarding single instruction, the above will boil down to an integer constant expression as long as we pass integer constants. If we pass variables, then it can't be calculated at compile time (bit-fields are no different here either). For such cases we should make a habit of using a temporary variable:

uint32_t tmp_var = MYVAR_FIELD1(x) | MYVAR_FIELD2(y) | MYVAR_FIELD3(z);
volatile my_var  = tmp_var; // access the volatile qualified variable on a line of its own

Upvotes: 1

Graznarak
Graznarak

Reputation: 3692

I found that the following works to force word-wise reads and writes.

Structure:

union my_type
{
  struct
  {
    unsigned field1 : 1;
    unsigned field2 : 3;
    unsigned field3 : 4;
    unsigned : 24;
  } fields;
  unsigned word;
} volatile my_variable __attribute__((section(".bss.my_periph")));

Read:

union my_type my_type;
my_type.word = my_variable.word;
return my_type.field2;

Write:

union my_type my_type = {0};
my_type.field3 = 5;
my_variable.word = my_type.word;

Read, Modify, Write:

union my_type my_type;
my_type.word = my_variable.word;
mytype.field2 = 7;
my_variable.word = my_type.word;

Upvotes: 2

Related Questions