How to convert a 16-bit unsigned to 16-bit signed for multipliers - Page 1
Forum section: electronics / general

author: Dmeads
author_rank: Regular Contributor
timestamp: October 12, 2024, 12:49:53 am
content: I am very new to filter design on FPGA, but I am doing one for a hobby project. I have a 16-bit unsigned value coming from my ADC, which then needs to go into an IIR filter. The FPGA has 16-bit multipliers, and since some of my coefficients are negative, I think I need both operands to be signed in the multiplication. The ADC is unipolar, positive voltage only.What is the proper way to truncate or round the 16-bit unsigned ADC value into a 16-bit signed number (twos compliment)?

author: brucehoult
author_rank: Super Contributor
timestamp: October 12, 2024, 01:11:39 am
content: You can only sensibly convert an unsigned value into a signed one with the same numerical value by making sure the top bit is clear.What are you going to do it it's not clear e.g. your ADC gave a value between 0x8000 and 0xFFFF ? Do you want to set all such values to 0x7FFF? That's not very nice. Maybe you want instead to halve all values -- either digitally, or by limiting the input range to the ADC?But all this is unnecessary anyway, as it's perfectly well possible to multiply an unsigned 16 bit number by a signed 16 bit number to give the CORRECT 32 bit result.CPU instruction sets such as RISC-V provide three different instructions to get the hi bits of a multiply: MULH, MULHU, and MULHSU (SxS, UxU, SxU). The lo bits are the same regardless.You can probably configure your FPGA's DSP blocks to do signed*unsigned multiplication.Or you can write code like...Code:module signed_unsigned_multiplier (input signed [15:0] a, // Signed 16-bit inputinput [15:0] b, // Unsigned 16-bit inputoutput [31:0] result // 32-bit result);assign result = $signed(a) * b;endmodule... which notionally extends a to 32 bits first (which should get optimised, hopefully just by putting the multiplier block into signed*unsigned mode)). A lot of FPGAs actually have 18 bit multipliers, in which case the signed variable only has to be extended to 18 bits (and the unsigned one zero-extended) and all will be well.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 12, 2024, 01:29:20 am
content: Yes, you can keep it unsigned.But if you really want to turn the value into a signed one, I'm assuming you have a 0x0000-0xFFFF range of values from min to max, and would like to turn that into a signed integer, 0x8000 becoming zero, 0x0000 becoming -32768 and 0xFFFF becoming 32767.If so, you'd just have to subtract 0x8000 from the unsigned value.Note that subtracting 0x8000 in that case doesn't even require a complete subtraction. If bit 15 is set, just clear it. If bit 15 is not set, set it. So all in all => just invert bit 15 and you should turn your unsigned value into a signed one, for which 0 corresponds to the middle value.

author: radiolistener
author_rank: Super Contributor
timestamp: October 14, 2024, 09:53:00 am
content: Just subtract 0x8000.But first check ADC documentation, because usually they already send the data in two's compliment signed format. And some have configuration register/pin to select desired format.Note that bit truncation is not a safe operation because it introduces noise. Proper rounding should be used to minimize this noise. Similarly, bit expansion is also not safe, as it can introduce bias if scaling is involved.

author: brucehoult
author_rank: Super Contributor
timestamp: October 14, 2024, 10:42:43 am
content: omg

author: langwadt
author_rank: Super Contributor
timestamp: October 14, 2024, 10:58:45 am
content: Quote from: radiolistener on October 14, 2024, 09:53:00 amwait, what??subtracting 0x10000 from 16 bit number and putting into 16 bit does nothing ...truncation and rounding result in the same noise except for a ~1/2 bit bias

author: radiolistener
author_rank: Super Contributor
timestamp: October 14, 2024, 11:33:02 am
content: Quote from: langwadt on October 14, 2024, 10:58:45 amNo.32768 - 65536 = -3276832769 - 65536 = -32767...65534 - 65536 = -265535 - 65536 = -1Quote from: langwadt on October 14, 2024, 10:58:45 amNo.Simple truncation without rounding introduces more noise compared to truncation with rounding.For example, in DSP code, truncating a clean sine wave without rounding can introduce new frequency spurs that were absent in the original signal.

author: coppice
author_rank: Super Contributor
timestamp: October 14, 2024, 11:37:42 am
content: Quote from: radiolistener on October 14, 2024, 11:33:02 amIts more accurate to say you are removing signal rather than adding noise. If instead of rounding you dither when reducing word lengths you are very specifically adding noise, yet its a good thing as you end up with more signal left after the operation.

author: radiolistener
author_rank: Super Contributor
timestamp: October 14, 2024, 11:50:47 am
content: Quote from: coppice on October 14, 2024, 11:37:42 amI disagree. When you truncate bits without rounding, the truncation error is signal-dependent and introduces a random error, which is effectively added noise. Rounding helps to minimize the amplitude of this noise by providing a more controlled error.When I added rounding to the DSP of my SDR receiver, its spurious performance improved significantly. I noticed that some high spurs disappeared completely, while others were greatly reduced.

author: brucehoult
author_rank: Super Contributor
timestamp: October 14, 2024, 11:51:50 am
content: Quote from: radiolistener on October 14, 2024, 11:33:02 amI have a question for you.What is the 16 bit unsigned binary number for 65534?What is the 16 bit signed binary number for -2?

author: coppice
author_rank: Super Contributor
timestamp: October 14, 2024, 11:56:06 am
content: Quote from: radiolistener on October 14, 2024, 11:50:47 amSure, but if you had dithered instead of rounding you would have had a hard time finding any signs of those spurs at all, and yet you would have added around 1/2 a bit of noise to the signal.

author: radiolistener
author_rank: Super Contributor
timestamp: October 14, 2024, 12:05:10 pm
content: Quote from: brucehoult on October 14, 2024, 11:51:50 amBoth are 16’b1111111111111110 = 16'hfffeThe difference lies in how these numbers are mapped to signal amplitude within the ADC. Therefore, it is important to consider the format in which the ADC transmits data and to perform the necessary conversions if your code operates with a different representation.In a signed representation, the number 0 corresponds to the center of the ADC scale, which roughly aligns with the average signal level. In contrast, for an unsigned representation, the number 32768 corresponds to the center of the ADC scale.As you can see unsigned number 0 is not equals to signed number 0 in context of ADC output format.In reality, the situation is a bit more complex since there is no specific code that represents the center of the scale due to the even number of codes. However, for the sake of simplicity in processing, the AES17 standard considers the signed number 0 as the center of the ADC scale. While this results in an asymmetrical scale, it simplifies the signal processing, and the value -32768 in this standard is considered as exceeding full-scale, because 16-bit full scale of signed number range is assumed as -32767..+32767.To make it more clear, here is table with numbers for 4-bit ADC.1-st column is a signal value in signed decimal format (which is used for processing)2-nd column is a hex ADC output with unsigned format3-rd column is a hex ADC output with signed formatCode:-808-719-62a-53b-44c-35d-26e-17f+080+191+2a2+3b3+4c4+5d5+6e6+7f7Quote from: coppice on October 14, 2024, 11:56:06 amI understand what you're saying, but I'm not sure this approach can yield the same effective results as rounding. Dithering might be preferable if random noise is more acceptable than stable spurs, but it depends on the specific requirements of the application.

author: MarginallyStable
author_rank: Regular Contributor
timestamp: October 14, 2024, 02:51:07 pm
content: If the ADC output is linear, I.E:0x0000 -> -5V0x8000 -> 0V0xFFFF -> ~5V (-1bit value)You just exclusive-or the MSB to get a signed value0x0000 ^ 0x8000 -> 0x8000 (most negative value signed value)0x8000 ^ 0x8000 -> 0x00000xFFFF ^ 0x8000 -> 0x7FFF (most positive signed value)EDIT: Just saw you mention unipolar

author: gael
author_rank: Newbie
timestamp: January 17, 2025, 09:11:15 pm
content: 16-bit unsigned needs to get to 17-bit as signed, with a zero MSB added.