Saturday, March 5, 2011

IQ Correction VHDL update

Greetings everyone,

I have a VHDL implementation of the IQ phase and gain correction algorithm
working. This implementation isn't synthesizeable in logic (yet), but it will be
as soon as I figure out why signed multiplication gives the wrong result.

Since I have a version that is working (with some of the internal math done with
variables instead of registers) I'm taking the opportunity to work on gain and
phase lock and the creation of "corrected" I and Q signals.

I is passed along with a filter delay, and corrected Q is passed along.

This part needs to be done regardless of how the internals of the correction
block are implemented.

If you haven't visited the opencores.com site, then you might want to drop by
and check it out. We're in there!


IQ Phase and Gain Correction is the name of the project and our page is
http://opencores.com/project,iqcorrection

I'll have today's cut of code up there after a celebratory lunch. 
 
More soon (phase and gain lock signals in progress)
-Michelle W5NYV


Potestatem obscuri lateris nescis.

Tuesday, February 22, 2011

Package that makes normally distributed random numbers from uniform numbers

In order to be able to have normal distribution noise, I made a package that
takes the uniform distribution random numbers and uses the central limit theorem
to give an (approximately) normal distribution. Here's the package:

library ieee;
use ieee.std_logic_1164.all;
use ieee.math_real.all;
use ieee.numeric_std.all;
use work.random_int.all;

--by MEP 22 February 2011
--usage:
--this is a function, which means it can be on the right-hand side
--of an assignment. It returns a mean-zero random number from a
--normal distribution. The argument is a real number that indicates
--the standard deviation desired.
--
--random_noise(sigma);
--

package normal_distribution_random_noise is

function random_noise (
sigma : real)
return real;

end package normal_distribution_random_noise;


package body normal_distribution_random_noise is

function random_noise (
sigma : real
)
return real is

--variables
variable u_noise: real; --uniform distribution noise
variable n_noise: real := 0.0; --normal distribution noise
variable seed1 : positive;
variable seed2 : positive;

begin

--obtain a uniformly distributed random number
uniform(seed1, seed2, u_noise);
--report "Random uniform noise is " & real'image(u_noise) & ".";

for normal_count in 0 to 12 loop
--Turn the uniform distributed number
--into a normally distributed number
--by using the central limit theorem.
--Make it mean zero and make it have
--the range of the uniform numbers
--that it is composed from.
n_noise := n_noise + u_noise;
end loop;

n_noise := n_noise - (0.5)*(real(12)); --normal distribution with a mean
of zero
--report "Random normal noise is " & real'image(n_noise) & ".";
n_noise := n_noise/(real(12));
--report "Random normal noise using range of uniform is " &
real'image(n_noise) & ".";
n_noise := sigma*n_noise;

return n_noise;
end function random_noise;
end package body normal_distribution_random_noise;

Package that creates I and Q samples to test the IQ Correction block

library ieee;
use ieee.std_logic_1164.all;
use ieee.math_real.all;
use ieee.numeric_std.all;
use work.normal_distribution_random_noise.all;

--by MEP 22 February 2011
--usage:
--these are functions, which means they can be on the right-hand side
--of an assignment. These functions create an I and Q sample.
--The arguments are
--a natural number standing for the index of the sample,
--a real number that provides a way to have many samples per period,
--a real number standing for the standard deviation of the normally distributed
noise added to the sample,
--a real number standing for the amplitude of the signal,
--a natural number indicating the width of the vector holding the returned
value,
--a real number indicating the gain error of Q with respect to I,
--and a real number indicating the phase error of Q with respect to I
--
--create_I_sample(n_dat, freq, sgma, amplitude, return_width);
--create_Q_sample(n_dat, freq, sgma, amplitude, return_width, e1, a1);
--

package create_sample is

function create_I_sample(
n_dat : integer;
freq : real;
sgma : real;
amplitude : real;
return_width : natural)
return signed;

function create_Q_sample(
n_dat : integer;
freq : real;
sgma : real;
amplitude : real;
return_width : natural; --x1_tb'LENGTH
e1 : real; --gain error
a1 : real) --phase error
return signed;

end package create_sample;


package body create_sample is

function create_I_sample(
n_dat : integer;
freq : real;
sgma : real;
amplitude : real;
return_width : natural) --x1_tb'LENGTH
return signed is

variable local_x1 : real;
variable int_x1: integer;
variable returned_x1 : signed(return_width downto 0);

begin

local_x1 := amplitude*sin(2.0*math_pi*(real(n_dat))*freq) +
random_noise(sgma);
--report "local_x1 inside CREATE_I_SAMPLE function is " &
real'image(local_x1) & ".";

--AGC scaling. Scaling factor is maximum value the signal can take.
local_x1 := local_x1/(1.11);
--report "local_x1 after AGC inside CREATE_I_SAMPLE function is " &
real'image(local_x1) & ".";

int_x1 := integer(trunc(local_x1*((2.0**31.0)-1.0))); --scaled
--report "integer version of x1 inside CREATE_I_SAMPLE function is " &
integer'image(int_x1) & ".";

returned_x1 := (to_signed(int_x1, return_width+1));

return returned_x1;
end function;

function create_Q_sample(
n_dat : integer;
freq : real;
sgma : real;
amplitude : real;
return_width : natural; --x1_tb'LENGTH
e1 : real; --gain error
a1 : real)
return signed is

variable local_y1 : real;
variable int_y1: integer;
variable returned_y1 : signed(return_width downto 0);

begin
local_y1 := amplitude*(1.0 + e1)*cos(2.0*math_pi*(real(n_dat))*freq +
a1) + random_noise(sgma);
--report "local_y1 first created CREATE_Q_SAMPLE function is " &
real'image(local_y1) & ".";

--AGC scaling. Scaling factor is maximum value the signal can take.
local_y1 := local_y1/(1.11);
--report "local_y1 after AGC inside CREATE_Q_SAMPLE function is " &
real'image(local_y1) & ".";

int_y1 := integer(trunc(local_y1*((2.0**31.0)-1.0))); --scaled
--report "integer version of y1 inside CREATE_Q_SAMPLE function is " &
integer'image(int_y1) & ".";

returned_y1 := (to_signed(int_y1, return_width+1));

return returned_y1;
end function;
end package body create_sample;

IQ Phase Gain Correction testbench

library ieee;
use ieee.std_logic_1164.all;
use ieee.math_real.all;
use ieee.numeric_std.all;
use work.normal_distribution_random_noise.all;
use work.create_sample.all;

entity IQGainPhaseCorrection_testbench is
end entity;


architecture IQGainPhaseCorrection_testbench_arch of
IQGainPhaseCorrection_testbench is

--declare the DUT as a component.
component IQGainPhaseCorrection is
generic(width :natural);
port(
clk :in std_logic;
x1 :in signed(width downto 0);
y1 :in signed(width downto 0);
gain_error :out signed(width downto 0);
gain_lock :out bit;
phase_error :out signed(width downto 0);
phase_lock :out bit;
corrected_x1 :out signed(width downto 0);
corrected_y1 :out signed(width downto 0)
);
end component;

--provide signals to run the DUT.
signal clk_tb : std_logic := '0';
signal clk_tb_delayed : std_logic := '0';
signal x1_tb : signed(31 downto 0);
signal y1_tb : signed(31 downto 0);
signal gain_error_tb : signed(31 downto 0);
signal gain_lock_tb : bit;
signal phase_error_tb : signed(31 downto 0);
signal phase_lock_tb : bit;
signal corrected_x1_tb : signed(31 downto 0);
signal corrected_y1_tb : signed(31 downto 0);

begin

--connect the testbench signal to the component
DUT:IQGainPhaseCorrection
generic map(
width => 31
)
port map(
clk => clk_tb_delayed,
x1 => x1_tb,
y1 => y1_tb,
gain_error => gain_error_tb,
gain_lock => gain_lock_tb,
phase_error => phase_error_tb,
phase_lock => phase_lock_tb,
corrected_x1 => corrected_x1_tb,
corrected_y1 => corrected_y1_tb
);


--create I and Q. MTreseler says, "sin in vhdl I use use ieee.math_real.all and
cast to integer."

CREATE_I_Q_SAMPLES: process (clk_tb) is
--for both I and Q
variable n_dat : integer := 0;
variable freq : real := 0.03; --relative frequency
variable sgma : real :=0.01; --sigma of noise
variable amplitude : real := 1.0; --amplitude
--for Q
variable e1 : real := 0.1; --gain error
variable a1 : real := (10.0*math_pi)/180.0; --phase error of 10 degrees

begin
if (clk_tb'event and clk_tb = '1') then
x1_tb <= create_I_sample(n_dat, freq, sgma, amplitude, 31);
y1_tb <= create_Q_sample(n_dat, freq, sgma, amplitude, 31, e1, a1);
n_dat := n_dat + 1;
end if;
end process CREATE_I_Q_SAMPLES;


DRIVE_CLOCK:process
begin
wait for 50 ns;
clk_tb <= not clk_tb;
clk_tb_delayed <= not clk_tb_delayed after 1 ns;
end process;

end IQGainPhaseCorrection_testbench_arch;

IQ Phase Gain Correction architecture

architecture IQGainPhaseCorrection_beh of IQGainPhaseCorrection is

--signal declarations

--phase error estimate accumulator
signal reg_1:signed(width downto 0) := (others => '0');

--gain error estimate accumulator
signal reg_2:signed(width downto 0) := (0 => '1', others => '0');

--Phase Offset Adjustment Applied to y1
signal y2:signed(width downto 0) := (others => '0');

--Gain and Phase Adjustment Applied to y1
signal y3:signed(2*width+1 downto 0) := (others => '0');

signal x1y2:signed(2*width+1 downto 0):= (others => '0');
signal mu_1:signed(width downto 0):= (others => '0');
signal x1x1y3y3:signed(width downto 0):= (others => '0');
signal mu_2:signed(width downto 0):= (others => '0');

signal reg_1x1:signed(2*width+1 downto 0):= (others => '0');
signal y3y3: signed(4*width+3 downto 0):= (others => '0');
signal x1x1: signed(2*width+1 downto 0):= (others => '0');

begin

correction : process (clk) is
begin

if clk'event and clk = '1' then

--phase error estimate, step size set to 0.000244
--which is achieved with a shift right by 12.
reg_1x1 <= reg_1 * x1; --clock 0
y2 <= y1 - reg_1x1(2*width+1 downto width+1); --clock 1
x1y2 <= x1 * y2; --clock 2
mu_1 <= shift_right(x1y2(2*width+1 downto width+1),12); --step size
applied. --clock 3
reg_1 <= reg_1 + mu_1; --clock 4
phase_error <= reg_1; --update phase error estimate. --clock 5

--gain error estimate, step size set to 0.000122
--which is achieved with a shift right by 13.
y3 <= y2 * reg_2; --clock 0 --63 downto 0 n*32 - 1, n = 2
x1x1 <= x1 * x1; --clock 0 --63 downto 0
y3y3 <= y3 * y3; --clock 1 --127 downto 0 n*32 -1, n = 4 to
n = 3
x1x1y3y3 <= (abs(x1x1(2*width+1 downto width+1))) - (abs(y3y3(4*width+3
downto 3*width+3))); --clock 2
mu_2 <= shift_right(x1x1y3y3, 13); --clock 3
reg_2 <= reg_2 + mu_2; --clock 4
gain_error <= reg_2; --update gain error estimate. --clock 5

end if;

end process;

end IQGainPhaseCorrection_beh;

IQ Phase Gain Correction entity

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith;
use ieee.numeric_std.all;


entity IQGainPhaseCorrection is

generic(width:natural);

port(
clk :in std_logic;
x1 :in signed(width downto 0);
y1 :in signed(width downto 0);
gain_error :out signed(width downto 0);
gain_lock :out bit;
phase_error :out signed(width downto 0);
phase_lock :out bit;
corrected_x1 :out signed(width downto 0);
corrected_y1 :out signed(width downto 0)
);

end IQGainPhaseCorrection;

IQ Phase and Gain Correction - Testbench

Below is the testbench for the IQ Phase and Gain Correction.

As you can see, there's a lot more going on in the testbench than in the block
for the algorithm. This is because the testbench has to generate the test
signals and provide the clock and establish the various settings for the test,
and that all adds up in this case to more lines of code.


On the list of improvements is to move the I and Q signal creation into a
procedure. This encapsulates code and will make the testbench a lot more
readable.


Another obvious improvement is to figure out exactly why the gain correction
isn't working yet.


More soon,
-Michelle W5NYV


library ieee;
use ieee.std_logic_1164.all;
use ieee.math_real.all;
use ieee.numeric_std.all;

entity IQGainPhaseCorrection_testbench is
end entity;

architecture IQGainPhaseCorrection_testbench_arch of
IQGainPhaseCorrection_testbench is
--declare the DUT as a component.
component IQGainPhaseCorrection is
generic(width :natural);
port(
clk :in std_logic;
x1 :in signed(width downto 0);
y1 :in signed(width downto 0);
gain_error :out signed(width downto 0);
gain_lock :out bit;
phase_error :out signed(width downto 0);
phase_lock :out bit;
corrected_x1 :out signed(width downto 0);
corrected_y1 :out signed(width downto 0)
);
end component;

--provide signals to run the DUT.
signal clk_tb : std_logic := '0';
signal x1_tb : signed(31 downto 0);
signal y1_tb : signed(31 downto 0);
signal gain_error_tb : signed(31 downto 0);
signal gain_lock_tb : bit;
signal phase_error_tb : signed(31 downto 0);
signal phase_lock_tb : bit;
signal corrected_x1_tb : signed(31 downto 0);
signal corrected_y1_tb : signed(31 downto 0);

begin

--connect the testbench signal to the component
DUT:IQGainPhaseCorrection
generic map(
width => 31
)
port map(
clk => clk_tb,
x1 => x1_tb,
y1 => y1_tb,
gain_error => gain_error_tb,
gain_lock => gain_lock_tb,
phase_error => phase_error_tb,
phase_lock => phase_lock_tb,
corrected_x1 => corrected_x1_tb,
corrected_y1 => corrected_y1_tb
);


--create x1 and y1. MTreseler says, "sin in vhdl I use use ieee.math_real.all
and cast to integer."


CREATE_X1_I: process
variable angle : real;
variable local_x1 : real;
variable sgma : real :=0.01; --sigma of noise
variable amplitude : real := 1.0; --amplitude
variable freq : real := 0.03; --relative frequency
variable u_noise: real; --uniform distribution noise
variable n_noise: real := 0.0; --normal distribution noise

variable seed1 : positive := 10;
variable seed2 : positive := 200;

--loop controls
variable make_normal_count : integer := 12;
variable n_dat : integer := 4;

variable int_x1: integer;

begin
for n_dat_count in 0 to n_dat loop
--make a random number
uniform(seed1, seed2, u_noise);
report "Random uniform noise in I creation is " & real'image(u_noise) &
".";

for normal_count in 0 to make_normal_count loop
--turn the uniform distributed number
--into a normally distributed number
--by using the central limit theorem.
n_noise := n_noise + u_noise;
end loop;
n_noise := n_noise - (0.5)*(real(make_normal_count)); --normal
distribution with a mean of zero
report "Random normal noise in I creation is " & real'image(n_noise) &
".";
n_noise := n_noise/(real(make_normal_count)); --max values reduced?
report "Random normal noise (normalized?) in I creation is " &
real'image(n_noise) & ".";

local_x1 := amplitude*cos(2.0*math_pi*(real(n_dat_count))*freq) +
sgma*(n_noise);
--local_x1 := cos(2.0*math_pi*(real(n_dat_count))*freq); --simpler
version

--AGC scaling
local_x1 := local_x1/(1.01);

report "local_x1 is " & real'image(local_x1) & ".";
--x1_tb <= local_x1; --somehow get real turned into signed.
-- 1. rescale to 0..(nearly)4096, find integer part
--int_x1 := INTEGER(TRUNC(local_x1*4294967296.0)); -- from random_vector


int_x1 := integer(trunc(local_x1*(2.0**31.0))); --scaled

report "integer version of x1 is " & integer'image(int_x1) & ".";
-- 2. convert to signed
x1_tb <= (to_signed(int_x1, x1_tb'LENGTH));


end loop;
wait;

end process CREATE_X1_I;


CREATE_Y1_Q: process
variable angle : real;
variable local_y1 : real;
variable e1 : real := 0.1; --gain error
variable a1 : real := (10.0*math_pi)/180.0; --phase error of 10 degrees
variable sgma : real := 0.01; --sigma of noise
variable amplitude : real := 1.0; --amplitude
variable freq : real := 0.03; --relative frequency
variable u_noise: real; --uniformly distributed noise
variable n_noise: real := 0.0; --normally distributed noise
variable seed1 : positive := 1;
variable seed2 : positive := 2;

--loop controls
variable make_normal_count : integer := 12;
variable n_dat : integer := 4;

variable int_y1: integer;
begin

for n_dat_count in 0 to n_dat loop
--make a random number
uniform(seed1, seed2, u_noise);
report "Random uniform noise in I creation is " & real'image(u_noise) &
".";

for normal_count in 0 to make_normal_count loop
--turn the uniform distributed number
--into a normally distributed number
--by using the central limit theorem.
n_noise := n_noise + u_noise;
end loop;
n_noise := n_noise - (0.5)*(real(make_normal_count));
n_noise := n_noise/(real(make_normal_count)); --reduce size of noise
report "Random normal noise in I creation is " & real'image(n_noise) &
".";
n_noise := n_noise/(real(make_normal_count)); --max values reduced?
report "Random normal noise (normalized?) in I creation is " &
real'image(n_noise) & ".";

local_y1 := amplitude*(1.0 + e1)*cos(2.0*math_pi*(real(n_dat_count))*freq +
a1) + sgma*(n_noise);

--AGC scaling
local_y1 := local_y1/(1.01);

--int_y1 := INTEGER(TRUNC(local_y1*4294967296.0)); -- from random_vector
int_y1 := integer(trunc(local_y1*(2.0**31.0))); --scaled
report "integer version of y1 is " & integer'image(int_y1) & ".";

-- 2. convert to signed
y1_tb <= (to_signed(int_y1, y1_tb'LENGTH));

end loop;
wait;

end process CREATE_Y1_Q;


DRIVE_CLOCK:process
begin
clk_tb <= not clk_tb;
wait for 50 ns;
end process;

end IQGainPhaseCorrection_testbench_arch;