# Reducing absolute value of signed number in VHDL

Related to this: Scaling an input in VHDL, but different enough that I feel it warrants a new question.

I feel this should be simpler that I'm making it.

I have a stream of signed 16 bit samples which I want to attenuate by about 0.5 dB, but let's just use nicer numbers for the sake of illustration. I need:

-32000 (almost -1.0) -> -25600
+32000 (almost +1.0) -> +25600


etc

Simple right? I'm stuck because the multiplication operator must take either signed or unsigned operands, not a mixture of both (that makes sense).

Here is what I initially tried:

variable scale_factor : std_logic_vector(15 downto 0) := x"EFFF";
attenuated_sample <= std_logic_vector(unsigned(sample) * unsigned(scale_factor));


This doesn't work; it makes positive samples smaller but negative samples more negative.

The only way I've managed to achieve what I want is by multiplying the signed sample by a signed scale factor, but I've had to make the scale factor have an extra few zeros at the front so that it is always positive. I.e.,

variable temp : std_logic_vector(39 downto 0);
variable scale_factor : std_logic_vector(19 downto 0) := x"0EFFF";

temp := std_logic_vector(resize(signed(sample), 20) * signed(scale_factor);
attenuated_sample <= temp(31 downto 16);


This appears to work, but I don't like it because it's ugly. What is the correct way to go about this?

-
Does your scaler module know if it is getting signed or unsigned data? – trondd Nov 21 '13 at 13:16

The reason your unsigned() cast doesn't work is because the result takes the exact bits in the input and just 'calls it unsigned'. ie, 0xFFFC signed is -4, but when cast to unsigned, it's 65532.

Instead, since your incoming signal is always 16 bit signed, just check to see if it is negative (as simple as checking the leading bit). Then, instead of casting to unsigned, negate it (which will have the desired effect) and then multiply by your scale factor, negating again afterwards. I can't think at the moment how to do this in a single step, but this should work for you if you can spare two extra multiplies.

-

The simplest (whilst keeping flexibility by calculating the vector ranges) I can make it is this:

constant scale_factor : signed(16 downto 0) := '0' & x"EFFF";
variable temp : signed(sample'high+scale_factor'high-1 downto 0);

temp := resize(sample, scale_factor'length) * scale_factor;
attenuated_sample <= temp(temp'high downto temp'high-attenuate_sample'length);


You may also want to do a more complex calculation for the attenuated sample than the simple truncation done here.

My first answer when people want to scale samples is "don't do that, do it at the end", once the rest of the processing is finished. Just keep track of the scale factor in your head. You may have a good reason why that's not feasible though...

-

Depending how accurate "about" 0.5dB needs to be... if 0.56dB is good enough, multiply by 15/16, or simply subtract 1/16 of itself.

sample : signed(15 downto 0);
scaled : signed(15 downto 0);
output : signed(15 downto 0);

scaled <= SHIFT_RIGHT(sample,4);
output <= sample - scaled;

-