Author Topic: Hardware reg bitfields and struct/unions V defines of bitnames and bit positions  (Read 3335 times)

0 Members and 1 Guest are viewing this topic.

Offline AlfBazTopic starter

  • Super Contributor
  • ***
  • Posts: 2184
  • Country: au
We are all aware of the benefits decent code editors bring to the table with regards to auto-completion and pop-up lists of members.
These things save a lot of time flipping back and forth from header files where register bits and pieces are defined.

Microchip uses, or at least use to use bitfields, structures and unions to define bits and groups of bits for their hardware registers.
Others such as ST use #defines for the position of the bits and masks for groups of related bits. To make matters worse they define all the regs at the top of the file and the bit defs at the bottom of the file, miles away from the register defs

Code wise, setting hardware register bits where the vendor has supplied a header full of #define's is annoyingly painful compared to writing the name of the register-> and having a complete list of bits to choose from

With defined bit names, bit positions and masks, for a group of bits, it involves ANDing a shifted and NOTted version of the mask to clear the bits of interest followed by ORing a shifted value to the register whereas with a structure of bitfields it’s as simple as Reg->BitGroup = x;

Assembly code generated for bitfields (gcc on arm) take advantage of the Bit Field Clear (BFC) and Bit Field Insert (BFI) instructions.
With the defines method, shifting etc is mostly done by the pre-processor but assembly shows a bunch of register loading presumably with immediate values and is more than twice as many assembly instructions.

Here's some simple C code and select disassembly to demonstrate
Code: [Select]
volatile uint32_t bitreg;

typedef union {
struct {
unsigned bit0:1;
unsigned bit1:1;
unsigned bit2:1;
unsigned bit3:1;
unsigned bgp1:3;
unsigned bgp2:4;
unsigned resv:21;
};
uint32_t Reg;
} BitReg_t;

volatile BitReg_t * BitReg = (BitReg_t *) &bitreg;

#define BGP1_POS 4
#define BGP1_MASK 7 << BGP1_POS

int main(void)
{
BitReg->Reg = 0xFFFF;
BitReg->bgp1 = 5;
/* assembly for last line
ldr r3, [pc, #56] ; (5c <main+0x5c>)
ldr r2, [r3, #0]
ldr r3, [r2, #0]
movs r1, #5
bfi r3, r1, #4, #3
strh r3, [r2, #0]
*/

BitReg->Reg = 0xFFFF;
BitReg->Reg &= ~(BGP1_MASK);
/* assembly for last line
ldr r3, [pc, #32] ; (5c <main+0x5c>)
ldr r3, [r3, #0]
ldr r2, [pc, #28] ; (5c <main+0x5c>)
ldr r2, [r2, #0]
ldr r2, [r2, #0]
bic.w r2, r2, #112 ; 0x70
str r2, [r3, #0]
*/
BitReg->Reg |= 5 << BGP1_POS;
/* assembly for last line
ldr r3, [pc, #16] ; (5c <main+0x5c>)
ldr r3, [r3, #0]
ldr r2, [pc, #12] ; (5c <main+0x5c>)
ldr r2, [r2, #0]
ldr r2, [r2, #0]
orr.w r2, r2, #80 ; 0x50
str r2, [r3, #0]
*/
while(1);
}

Now I realise what goes on under the hood is mostly irrelevant but from a coding perspective, to me, setting register bits is far easier using bitfields and less prone to error than searching for predefined bit names and the mess of shifting, inverting, ORing and ANDing.

So, two questions...
1.   What is the main reason for avoiding bitfields. Is it non <insert standard here> compliant, are there pitfalls I am unaware of, are there portability issues
2.   Given vendor supplied register definitions of the type using bit name defs such as ST’s, is there an easier way of finding these defines other than opening the header and relying on ctrl+F

Thanks in advance


 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11259
  • Country: us
    • Personal site
1.   What is the main reason for avoiding bitfields. Is it non <insert standard here> compliant, are there pitfalls I am unaware of, are there portability issues
C standard says that layout of bit fields is implementation defined. But compilers do tend to behave consistently and as you would expect.

It is not a bad idea to use bit fields like this. It makes for a more optimal code.

2.   Given vendor supplied register definitions of the type using bit name defs such as ST’s, is there an easier way of finding these defines other than opening the header and relying on ctrl+F
Atmel parts have bits named consistently with the datasheet, so you can figure out naming for everything simply by looking at the datasheet.

But I personally like Ctrl-F.
Alex
 

Offline boffin

  • Supporter
  • ****
  • Posts: 1027
  • Country: ca
Actually the best implementation is in Pascal. I remember writing an eprom programmer in Turbo Pascal probably 30 years ago, and if you defined the output ports as sets, it was just so damn easy to understand what was going on

Sets are defined using bits, and you could do stuff like this:

Code: [Select]

VAR x: SET OF [ bit0, bit1, bit3, bit3, bgp1, curtains, lights, action ];

x := inportb(SomeRegister);
x := x - [curtains] + [lights] + [action];
outputb(SomeRegister, x);

or

IF (curtains IN x) THEN BEGIN...
(I apologize for any Pascal syntax errors, it's been a while....)
 

Offline andyturk

  • Frequent Contributor
  • **
  • Posts: 895
  • Country: us
2.   Given vendor supplied register definitions of the type using bit name defs such as ST’s, is there an easier way of finding these defines other than opening the header and relying on ctrl+F

Specifically for STM32, I've been using ST's new LL (low layer?) headers which take care of most of the register punching. That avoids a major problem of rolling your own register definitions and thus owning the responsibility to make sure they're correct. The LL is mostly a "library" of macro definitions or very thin functions. I like it because ST's peripherals are generally consistent between families, but they do occasionally move flags to different registers just to keep things interesting. The LL calls abstract those slight differences away. Note that the LL API is entirely different from the STM32 HAL, which is crap.

ARM/Keil has a program called SVDConv.exe that can be used to generate bitfield headers from .svd files, but I've never used it. What I have tried is writing a pile of Python code to parse those .svd files (they're XML) and generate headers to my own specifications. The problem there is that the SVD standard is way too loose and every ARM vendor seems to release .svd files that are somehow incompatible with everyone else's (when they even release .svd files at all).

On the general question, I'm with you: I'd much rather use bitfields than shifts and masks. However, that desire is outweighed by the need to avoid creating custom register definitions by hand. That way madness lies.
 
The following users thanked this post: donotdespisethesnake

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11259
  • Country: us
    • Personal site
outweighed by the need to avoid creating custom register definitions by hand.
Yes, don't create things by hand. All major vendors supply SVD files.

I also have some tools for parsing SVD files, and don't find them to be too inconsistent. But different vendors use "advanced" features to different extents. But SVDConv does the basic job. Although I'm not a huge fan of its output, it beats making thing be hand.
Alex
 
The following users thanked this post: donotdespisethesnake

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Quote
Assembly code generated for bitfields (gcc on arm) take advantage of the Bit Field Clear (BFC) and Bit Field Insert (BFI) instructions.
There aren't any bitfield instructions on CM0.  :-(  Code generated to deal with bitfields can be horribly inefficient compared to that using masks.  Bitfields also can'd be easily combined.  USART->CFGA = PARITY_BM+STOPB_BM+DATAL_BM;
Bitfields can generate an illusion of bitwise independence.
Code: [Select]
SERCOM5->USART.INTENCLR.bit.ERROR = 1;     is quite wrong and causes "mysterious" behavior, for example.  ( https://community.atmel.com/forum/problem-clearingsetting-bit-interrupt-flag-register )

I do rather like the Atmel style of having both bitfields AND masks available in the .h file.  Though perhaps the algorithm they use to generate them should avoid creating identical fields like:
Code: [Select]
typedef union {
  struct {
    uint32_t OUT:32;           /*!< bit:  0..31  Port Data Output Value             */
  } bit;                       /*!< Structure used for bit  access                  */
  uint32_t reg;                /*!< Type      used for register access              */
} PORT_OUT_Type;
 

Offline hcglitte

  • Regular Contributor
  • *
  • Posts: 137
Is there any way to use this approach for a communication protocol where the payload may be 15 bytes or whatever, and the bit fields don´t align on 8 bit boundaries? 
 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11259
  • Country: us
    • Personal site
Is there any way to use this approach for a communication protocol where the payload may be 15 bytes or whatever, and the bit fields don´t align on 8 bit boundaries? 
If you use uint64_t, then your payloads must align at 64 bit boundaries. If absolutely nothing aligns, then you would have to reassemble fields on the edges. But in 15 byte payload, you will get 1 edge at most.

On the other hand, most protocols I've seen have a very natural size for a bit-field members.  It may not be a byte, but 16 or 32 bits.
Alex
 

Offline ehughes

  • Frequent Contributor
  • **
  • Posts: 409
  • Country: us
The primary reason is that bitfield struct implementations are not defined in the C standard.

Code written with them is not portable.     If you are sticking with the same compiler/architecture then it shouldn't matter.


 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11259
  • Country: us
    • Personal site
Code written with them is not portable.     If you are sticking with the same compiler/architecture then it shouldn't matter.
MCU register sets are inherently not portable. And all major existing compilers implement bit fields the same way. An you would be crazy to make a new compiler that behaves differently.
Alex
 
The following users thanked this post: jnz

Offline ehughes

  • Frequent Contributor
  • **
  • Posts: 409
  • Country: us
Quote
And all major existing compilers implement bit fields the same way. An you would be crazy to make a new compiler that behaves differently.

My experience has been that this is not the case.    Moving from GCC to IAR for example has yielded oddball behaviors with bitfields not being 100% identical.    I understand the appeal but code at the register level often requires the reference manual open anyway.    Bitfields provide a bit of automation but have never saved me enough time to be worth the hassle of not part of an implementation standard.   

I have seen bitfields also used in certain communications protocols, for example VMF,  where the streams are highly packaged to oddball bit boundaries.    In my case of porting code that used bit fields was a nightmare in that it worked for the 6-sigma of cases but failed in some corner cases to be portable.   Had the code be just written with bit masks, it would have just worked with a lot less hassle of hunting down the corner case.   

That and they are not allowed by any formal certification process.


 

Offline AlfBazTopic starter

  • Super Contributor
  • ***
  • Posts: 2184
  • Country: au
@ehughes
Thank you for providing an insight as to where bit-fields may fall down, however if we limit the use case to peripheral register bit definition, don't IAR, Keil, etc have bitfield implementation sufficiently stable to be effective in this space? 
 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11259
  • Country: us
    • Personal site
don't IAR, Keil, etc have bitfield implementation sufficiently stable to be effective in this space? 
That is my experience. I worked on large projects that use bit fields to describe protocols and interfaces. And there were absolutely no problems porting between IAR and GCC.

And peripheral header files as generated by tool vendors are the same for all compilers, and they work fine.
Alex
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Quote
don't IAR, Keil, etc have bitfield implementation sufficiently stable to be effective in this space? 
the biggest incompatibility I've seen is wrt endianness; you might have problems if you're using one of those arm chips with variable endianness.  I used at least one compiler on x86 that couldn't represent the ip "fragment offset" as a direct-mapped structure bitfield (13bits), because it would have needed to byteswap before doing the bitfield extraction to get all the bits in the same bitfield.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf