Forums

Replacing specific bits in a number

Started by Crandles 1 month ago5 replieslatest reply 4 weeks ago87 views

Is there a boolean operation (or sequence of operations) to be able to replace specific bits in a particular number? Suppose you have a 32-bit number and want to replace one particular byte with another specified byte. Can this be done purely with boolean instructions operating on the registers? I was able to figure a way around it by storing the value in memory and then writing to the specific bytes that way, but I was wondering if there's a way to achieve this without having to rely on costly memory transfers.

Edit: NVM... just realized I could:

1. use rotations to get the byte I want to replace into position 

2. then clear the byte using an appropriate AND mask, 

3. OR the byte I want to insert into the newly cleared position

4. rotate the number back into it's starting byte arrangement

[ - ]
Reply by KocsonyaMay 28, 2022

Yes, you can do it and there is no need to rotate the register, it is enough to shift the replacement byte. Say, you want to set the most significant byte:

new_word = ( old_word & 0x00ffffff ) | ( new_byte << 24 );

Some cores, like the ARM (not the Cortex-M0, though) have instructions that really help with that; on an ARM it's 2 instructions. The 68020 (and I think the 683xx) had bitfield instructions that could do that in 1.

And of course the above works not only for bytes, but for an arbitrary number of bits within the word, at any position. In embedded code, where you need to set bitfields in hardware registers you will often find code snippets like this:

hw_reg = ( hw_reg & ~( FIELD1_MSK | FILED2_MSK ) ) | ( FIELD1_VAL | FIELD2_VAL );

Kocsonya


[ - ]
Reply by forthnutterMay 29, 2022
I tend to have a common header file using structures and unions.
If then micro has addressable ports. You can make the unions / struct
point to address of port. A simple example.
UL *port1;
port1 = (UL *) MCU_Port;
if(port1->B.D1)  //is bit 1 of port set
{
  port1->B.D2 = true; // Bit 2 set
  port1->B.D1 = false; // Bit 1 cleared
}

typedef struct
{
  unsigned lsn:4;
  unsigned msn:4;
}NIB;

typedef struct
{
  unsigned D0:1;
  unsigned D1:1;
  unsigned D2:1;
  unsigned D3:1;
  unsigned D4:1;
  unsigned D5:1;
  unsigned D6:1;
  unsigned D7:1;
}UCBITS;

typedef union
{
  uchar uc;
  UCBITS b;
  NIB n;
}UC;

typedef struct
{
  uchar lsb;
  uchar msb;
}SUTCHAR;


typedef struct
{
  unsigned D0:1;
  unsigned D1:1;
  unsigned D2:1;
  unsigned D3:1;
  unsigned D4:1;
  unsigned D5:1;
  unsigned D6:1;
  unsigned D7:1;
  unsigned D8:1;
  unsigned D9:1;
  unsigned D10:1;
  unsigned D11:1;
  unsigned D12:1;
  unsigned D13:1;
  unsigned D14:1;
  unsigned D15:1;
}B16ST;

typedef union
{
    ushort us;
    B16ST B;
    SUTCHAR uc;
    unsigned char bytes[2];
}US;

typedef struct
{
    US msw;
    US lsw;
}US32;

typedef struct
{
  unsigned D0:1;
  unsigned D1:1;
  unsigned D2:1;
  unsigned D3:1;
  unsigned D4:1;
  unsigned D5:1;
  unsigned D6:1;
  unsigned D7:1;
  unsigned D8:1;
  unsigned D9:1;
  unsigned D10:1;
  unsigned D11:1;
  unsigned D12:1;
  unsigned D13:1;
  unsigned D14:1;
  unsigned D15:1;
  unsigned D16:1;
  unsigned D17:1;
  unsigned D18:1;
  unsigned D19:1;
  unsigned D20:1;
  unsigned D21:1;
  unsigned D22:1;
  unsigned D23:1;
  unsigned D24:1;
  unsigned D25:1;
  unsigned D26:1;
  unsigned D27:1;
  unsigned D28:1;
  unsigned D29:1;
  unsigned D30:1;
  unsigned D31:1;
}B32ST;

typedef struct
{
  unsigned D0:2;
  unsigned D1:2;
  unsigned D2:2;
  unsigned D3:2;
  unsigned D4:2;
  unsigned D5:2;
  unsigned D6:2;
  unsigned D7:2;
  unsigned D8:2;
  unsigned D9:2;
  unsigned D10:2;
  unsigned D11:2;
  unsigned D12:2;
  unsigned D13:2;
  unsigned D14:2;
  unsigned D15:2;
}B3216ST;

typedef union
{
    ulong ul;
    ushort us[2];
    B32ST B;        //Bits
    B3216ST D;      //double bits
    uchar bytes[4];
}UL;

[ - ]
Reply by rmilneMay 29, 2022

Definitely agree with the union solution which leverages the compiler to figure out the bit manipulations.  A caveat to bitfield usage is endianess of data between machines/protocols.

[ - ]
Reply by KocsonyaMay 30, 2022

There are other caveats with bitfields too. Obviously you declare every HW register as volatile. Volatile bitfield operations, however, require (by the C standard) that every operation reads the register then writes it back separately. That is, if field1 and field2 are bitfields in the same register, then

field1 = value1;

field2 = value2;

will result in two independent sequences of reading the register, masking the bitfield, OR-ing the value and writing the register back. Most of the time that causes no problems (apart from a bit longer and slower code) but some HW registers are sensitive to the fact that you access them (self clearing fields on reads, actions triggered on write, that sort of thing).

The only way to guarantee a certain access pattern is to read the register into a temporary C variable, do the bit manipulations on that and then write the result to the HW register.

Furthermore, the way bitfields are packed into words is compiler dependent (the standard leaves it as implementation specific) so there is no guarantee that code using bitfields on HW registers that works with one compiler would work with another.

Bitfields on HW registers are very elegant and result in very readable code, but they are really dangerous. Unfortunately, that is important only for a very small subset of C users, so the C standard committee has been mainly ignoring the issue.

[ - ]
Reply by jmford94May 28, 2022

You don't say what kind of machine you're using, but if it's a 32 bit machine, you can operate directly on the 32 bits using a set of masks to clear (with AND), and set (with OR) each byte, or field.  They don't have to be aligned with bytes.  Macros can be written to pass in the fields and the masks.  TI's PDK for its Sitara processors has a lot of these kinds of macros defined to access particular fields in a 32 bit word.  I don't have that code handy right now, but essentialy, you create a macro to set a field, then you pass in the field width, the offset from bit 0, and the value to set, and the macro takes care of shifting, and-ing, and or-ing in the new data bits.

If you have an 8 bit machine, your proposal above seems reasonable.  Be sure to use logical shift and not arithmetic shifts to avoid sign-extension when shifting.

John