In the previous post I discussed some problems with arithmetic operations. This is the continuation of this blog series.
When you look at a cast operation, you might think: What's the deal? That can't be hard to implement correctly right? At least that was my first thought when I started to implement it.
Lets start with some simple C Code:
uint8_t a=0xFF; uint16_t b=(uint16_t)a; int16_t c=(uint16_t)a; uint16_t d=(int16_t)a; int16_t e=(int16_t)a; uint16_t f=(int8_t)a; int16_t g=(int8_t)a; printf("a=%4x b=%4x c=%4x d=%4x e=%4x f=%4x g=%4x\n",a,b,c,d,e,f,g);
The output of this program is:
a= ff b= ff c= ff d= ff e= ff f=ffff g=ffffffff
The output of
e is rather unsurprising, because
a is unsigned nothing really happens when it is cast to a larger size. With f however the type is changed to a signed type and then resized to a uint16_t. Upon printf'ing the value, it is converted as every usigned. F on the other hand is correctly sign extended from int8 to int16 and then to 32 bit.
The rule I extract from this for PSHDL is the following:
- Upon a cast the type is firstly resized with sign extension if the operand of the cast is a signed type. It doesn't matter what the cast itself is doing, the signedness of the operand determines whether sign extension is used.
- The type is changed after the resize operation. You can acutally see that in VHDL.
int<8> a=-5; uint<16> b=a;
results in the following VHDL code:
b <= intToUint(resizeInt(a, 16));
The type is is first resized as a signed int to 16 bit and then the type is converted from signed
int to usigned
When a signed value is resized to a larger size, the additional bits have to be filled with something. For a proper sign extension, the MSB is used. So a 4 bit number
1011 is sign extended to a 8 bit by using the first bit, the MSB and filling up the first 4 bits with it. The result is then
11111011. When the MSB is zero, as in
0011, then the result would be
00000011. But what about a reduction of size?
When the size is reduced two possible ways can be taken. The first one is to simply clip the value, which is what C does. The reasoning is that, when you do a width reduction, you know what you're doing and it is your task to ensure that the result still makes sense. A simple example to demonstrate what problems might arise:
uint16_t a=0xFFFF; uint8_t b=a; int8_t c=a; int8_t d=-1; uint16_t e= (uint8_t)d; uint16_t f=d; printf("a=%4d b=%4d c=%4d d=%4d e=%4d f=%4d\n",a,b,c,d,e,f);
The output of this is:
a=65535 b= 255 c= -1 d= -1 e= 255 f=65535
As you can see, it can happen that during the width reduction, an usnigned positive value can be become an unsigned negative value. This also works vice versa if the type is changed first, and then it is resized. To avoid that the principle of sign-extension can be used even for reducing the width, which is what the ieee.numeric resize operation is doing in VHDL. For PSHDL however I chose to implement the C way of resizing. Mostly because it doesn't alter the bits in unexpected ways. But it has the down-side that a change in signedness may happen. When you perform a downsize and it can affect your value data bits, you're probably doing something wrong, or you really don't care.
Implementing sign extension
In a programming language where you know the size of your variable, there is a very simple, yet effective way of implementing sign extension. Lets assume we want to cast a
int<8> to an
uint64_t data=0xFF; //Bits from an int<8> uint64_t shift=64-min(8,7); uint64_t seData=(((int64_t)data)<<shift)>>shift;
The minimum of the target and the current size is taken to ensure that the function works in either direction (from int<7> to int<8> and vice versa).The MSB of the current or target is then shifted to the MSB of the variable which in this case has 64 bits. The arithmetic shift is then used to perform a sign correct extension.
This was easy, but what about the implementation in a true arbitrary arithmetic? This will have to wait for the next blog post.