Any neat sign extension tricks for 10-bit to 16-bit?

Go To Last Post
11 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

I'd like to know neat tricks for converting 10-bit offset binary value with 3-bit scaling value to 16-bit signed integer.

Imagine you receive 13 actual bits from a device, of which 10 is the mantissa or value for a 10-bit DAC, and other 3 bits are the controls for a 7-step analog attenuator after the DAC. It means you can have a 16-bit range with 10-bit resolution. 10-bit offset binary means value 0 is most negative -512, 0x1FF is -1, 0x200 is midpoint 0, 0x201 is +1 and 0x3FF is most positive 511, and so to convert that to signed integer you can just XOR the MSB 0x200, after which 0x200=-512, 0x3FF=-1, 0x00=0, 0x01=+1, and 0x1FF=+511.

To convert that into linear 16-bit integer, I now use this code:

int16_t convert(uint16_t indata)
{
    // indata format
    // 3 MSBs is the shift value 1..7, 0 never encountered
    // 10 bits DAC value
    // 3 LSBs unused bits, random even

    uint16_t shiftval;
    int16_t value;

    // first set 10-bit offset binary value to MSB 
    value=(indata<<3)&0xFFC0;

    // convert offset binary to signed integer, toggle MSB
    value=value^0x8000;

    // since shiftval bits  originally means 1=minimum range and 7=maximum range, we need to invert it for right shift
    shiftval=7-(indata>>13);

    // do the attennuation by 0 to 6 bits
    value=value>>shiftval;
}

Now I'd like to be more efficient, so is there a way to do the sign extension without first copying to MSB and then doing right shifts with negated shift value? Can I just XOR it with some other value based on BIT9 and then do left shifts with (indata>>13)-1 ?

Would this work?

int16_t convert(uint16_t indata)
{
    // indata format
    // 3 MSBs is the shift value 1..7, 0 never encountered
    // 10 bits DAC value
    // 3 LSBs unused bits, random even

    uint16_t shiftval;
    int16_t value;

    // first set 10-bit offset binary value to LSB 
    value=(indata>>3)&0x3FF;

    // convert offset binary to signed integer

    if (value&0x200) // positive
    {
        value=value^0x200;
    }
    else // negative
    {
        value=value^0xFE00;
    }

    // shiftval bits  originally means 1=minimum range and 7=maximum range
    shiftval=(indata>>13)-1;

    // do the gain by 0 to 6 bits
    value=value<<shiftval;
}

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

To sign extend 13 to 16 I do this: if(n & 0x1000) n |= 0x7000;

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Or even better: if (n & 0x1000) n |= 0xE000;

That actually sets the 3 topmost bits.

Einstein was right: "Two things are unlimited: the universe and the human stupidity. But i'm not quite sure about the former..."

Last Edited: Wed. Feb 16, 2011 - 12:15 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

!?

Quebracho seems to be the hardest wood.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

(I noticed this was more than four years ago. Got here searching for sign extension.) (At the risc of stating the obvious:)
To go from an offset representation to plain, subtract the offset:
value = ((indata>>3) & 0x3FF) - 0x200;
If bit 9 was on before, it is off after; if off before, bits 15-9 will be set after.
Would work with any offset, e.g., 300 - obviousness lying in the eye of the beholder.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

...obviousness lying in the eye of the beholder

Z8 -- if really interested in this, there was a very spirited and extensive discussion on this in the main forum a while back.  Purists point their noses to the sky when simple and effective solutions such as yours are recommended.  [Sheesh, portability be damned--we are doing sign extension for an AVR8 after all.]

https://www.avrfreaks.net/forum/s...

Let me see if I can find the thread.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I guess we ended up in something like...

 

inline int16_t foo ( uint16_t arg )
{
    return ( arg ^ 0x200 ) - 0x200;
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

oloftangrot wrote:

I guess we ended up in something like...

 

inline int16_t foo ( uint16_t arg )
{
    return ( arg ^ 0x200 ) - 0x200;
}

 

Here we go again... LOL--I reviewed the thread again when I dug it up.  I still fail to see why that solution is as good as the (IMO) straightforward and shorter and faster

    int main(void)
    {
    int result;

    	result = ADCW;
    	if (result & 0x0200) result |= 0xfc00;
    	PORTD = result;
    	PORTD = result>>8;
    }

    	result = ADCW;
      46:	80 91 78 00 	lds	r24, 0x0078
      4a:	90 91 79 00 	lds	r25, 0x0079
    	if (result & 0x0200) result |= 0xfc00;
      4e:	91 fd       	sbrc	r25, 1
      50:	9c 6f       	ori	r25, 0xFC	; 252
    	PORTD = result;
      52:	8b b9       	out	0x0b, r24	; 11
    	PORTD = result>>8;
      54:	89 2f       	mov	r24, r25
    ...

Two cycles; two words.

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am beaten by the pig by one lousy clock cycle. Victory and honor is all yours theush.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Now y'all have got me going again...

 

-- http://en.wikipedia.org/wiki/Sig... Short Wikipedia article just says "pad with one bits".

 

-- https://graphics.stanford.edu/~s... A Stanford student included a method that is interesting, letting the C compiler extend a bitfield to full width.  I don't think that was explicitly brought up in the long thread.  Interesting enough to post here...

In C, sign extension from a constant bit-width is trivial, since bit fields may be specified in structs or unions. For example, to convert from 5 bits to an full integer:

int x; // convert this from using 5 bits to a full int
int r; // resulting sign extended number goes here
struct {signed int x:5;} s;
r = s.x = x;

CodeVision seems to "do the job", but many cycles/code words.  GCC/Studio6 code word count isn't bad.  (still much more than two words, two cycles)

int main(void) 
{
struct {signed int adc:10;} s;
int result;
s.adc = ADCW;
result = s.adc;

PORTD = result;
PORTB = result>>8;
}
int main(void) 
{
struct {signed int adc:10;} s;
int result;
s.adc = ADCW;
  46:	80 91 78 00 	lds	r24, 0x0078
  4a:	90 91 79 00 	lds	r25, 0x0079
result = s.adc;

PORTD = result;
  4e:	8b b9       	out	0x0b, r24	; 11
#include <avr/io.h>                    // Include AVR device-specific IO definitions
int main(void) 
{
struct {signed int adc:10;} s;
int result;
s.adc = ADCW;
  50:	26 e0       	ldi	r18, 0x06	; 6
  52:	88 0f       	add	r24, r24
  54:	99 1f       	adc	r25, r25
  56:	2a 95       	dec	r18
  58:	e1 f7       	brne	.-8      	; 0x52 <main+0xc>
  5a:	36 e0       	ldi	r19, 0x06	; 6
  5c:	95 95       	asr	r25
  5e:	87 95       	ror	r24
  60:	3a 95       	dec	r19
  62:	e1 f7       	brne	.-8      	; 0x5c <main+0x16>
result = s.adc;

PORTD = result;
PORTB = result>>8;
  64:	95 b9       	out	0x05, r25	; 5
}

 

-- http://stackoverflow.com/questio... This stackoverflow thread is pertinent/interesting.  One answer is very like my preferred method:

int signExtension(int instr) {
    int value = (0x0000FFFF & instr);
    int mask = 0x00008000;
    if (mask & instr) {
        value += 0xFFFF0000;
    }
    return value;
}

with a difference:  the adjustment constant is added to the base value.  (that thread is on a 32-bit "int" system)

 

Another post there lets the compiler do the work, but the shift method(s) are painful in size/speed:

Anyway sign extending is much easier than the proposals. Just make sure you are using signed variables and then use 2 shifts.

long value;   // 32 bit storage
value=0xffff; // 16 bit 2's complement -1, value is now 0x0000ffff
value = ((value << 16) >> 16); // value is now 0xffffffff

If the variable is signed then the C compiler translates >> to Arithmetic Shift Right which preserves sign. This behaviour is platform independent.

-- https://www.owasp.org/index.php/... I don't know what OWASP is, but the discussion is interesting (IMO) wrt this topic:

If one extends a signed number incorrectly, if negative numbers are used, an incorrect extension may result.

Consequences

  • Integrity: If one attempts to sign extend a negative variable with an unsigned extension algorithm, it will produce an incorrect result.

Hmmm-- +1 for the pedants, eh?

 

-- http://aggregate.org/MAGIC/#Sign... University of Kentucky poster mentions already listed approaches:

Sign Extension

Although many instruction sets provide single machine instructions that implement sign extension of 2's-complement integers, I've been sent a number of tricks for sign extension. I've included them here because sign extension instructions generally work only on the data sizes directly understood by the processor, whereas these methods work on any bit precisions.

The most obvious method assumes that you have a signed right shift: to extend an a-bit number x to b bits, shift left by b-a, then signed shift that value right by b-a bits. I believe this has been widely known and used for many years -- I know I didn't invent it, but used it decades ago.

Jean-Charles Meyrignac suggested a shiftless variation that basically does a 1-bit add to flip high bits: if n is 2 to the a, simply compute (((x | -n) + (n/2)) ^ (n/2)). This version has been posted here for some time....

However, in August 2010, Joe Zbiciak sent me a little email with a much cleaner shiftless sign extension: ((x ^ n) - n) where n is the value of the top bit position in the number to be extended. Thus, to sign-extend an 8-bit value x, compute ((x ^ 128) - 128). It really couldn't be much simpler or more obvious... at least once you've been told how to do it. ;-)

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My general solution looks like this:

    arg ^= 0x200;
    arg += ( 0x0 ^ 0x8000 ) - ( 0x0 ^ 0x200 );
    arg ^= 0x8000;

 

I guess the beauty of it lies in the symmetry and it's very easy to adopt to different bit positions for the different sign bit, but still it costs a darn cycle on the AVR.