LINEAR11 Conversion Using a Sign Extension Bit Twiddling Hack

The LINEAR11 data format is used by PMBus (Power Management Bus) devices. LINEAR11 is a 16-bit number which is composed of a pair of two's complement signed integers. When I was faced with converting from LINEAR11 to floating-point, I ended up running into sign extension problems.

Luckily, I stumbled upon a simple trick that we can use to convert from LINEAR11 to floating-point.

Table of Contents:

About LINEAR11

The PMBus Specification defines the LINEAR11 data format as a 16-bit number that consists of an 11-bit mantissa and 5-bit exponent. Due to these mantissa and exponent sizes, LINEAR11 offers a range 1024 steps and powers-of-2 resolution. The LINEAR11 format is typically used to represent non-output voltages and temperatures in a PMBus system.

The conversion function is defined as follows:

V = X * 2^N

Where:

  • V is the decimal value corresponding to the LINEAR11 data
  • X is a signed 11-bit 2's complement integer
  • N is a signed 5-bit 2's compliment integer

A Simple Conversion Trick

When I first worked at converting a LINEAR11 number to floating-point, I ran into trouble with parsing the bitfields and getting the numbers to properly sign extend.

Whenever I'm troubled by something in the bitfield arena, I check the Standford Graphics Lab Bit Twiddling Hacks page. I was pleased to see Sign extending from a constant bit-width as one of the hacks.

To quote Bit Twiddling Hacks:

Sign extension is automatic for built-in types, such as chars and ints. But suppose you have a signed two's complement number, x, that is stored using only b bits. Moreover, suppose you want to convert x to an int, which has more than b bits. A simple copy will work if x is positive, but if negative, the sign must be extended. For example, if we have only 4 bits to store a number, then -3 is represented as 1101 in binary. If we have 8 bits, then -3 is 11111101. The most-significant bit of the 4-bit representation is replicated sinistrally to fill in the destination when we convert to a representation with more bits; this is sign extending. In C, sign extension from a constant bit-width is trivial, since bit fields may be specified in structs or unions.

Using that for inspiration, I created a linear11_t type:

typedef struct
{
    int16_t base : 11;
    int16_t mantissa : 5;
} linear11_t;

We also define a linear11_val_t union type that can be used while reading/writing in the LINEAR11 format:

typedef union
{
    linear11_t linear;
    uint16_t raw;
} linear11_val_t;

Using the linear11_t structure, we can write straightforward conversion functions:

inline double linear11_to_double(linear11_val_t t)
{
    return t.linear.base * (double)(1 << t.linear.mantissa);
}

Now we can look at this conversion function in context. When we want to read a LINEAR11 value, we declare an linear11_val_t variable. We can then use the raw union member to interface with our device-facing function that provides a uint16_t output value. After we populate our linear11_val_t variable, we can use it as input into the conversion function.

double regulator_temperature_internal(void)
{
    linear11_val_t fixed_temp;
    double temp = 0.0;

    int r = get_internal_temperature(&fixed_temp.raw);
    if(r >= 0)
    {
        temp = linear11_to_double(fixed_temp);
    }

    return temp;
}

double regulator_temperature_external(void)
{
    linear11_val_t fixed_temp;
    double temp = 0.0;

    int r = get_external_temperature(&fixed_temp.raw);
    if(r >= 0)
    {
        temp = linear11_to_double(fixed_temp);
    }

    return temp;
}

Easy, wasn't it?

If you prefer, you can eliminate the need for linear11_val_t by having the structure hidden within the conversion function. Your linear11_to_double function would then just take in uint16_t values.

Further Reading