Define your complex number as an array of two signed numbers
type complex is array(0 to 1) of signed(your_range);
The problem with that approach, and also if you try
to use a record...
type complex_r is record
Re: signed(your_range);
Im: signed(your_range);
end record;
.... is that "your_range" must be fixed at the outset.
There is an alternative approach.
type complex_bit is record
Re: std_logic;
Im: std_logic;
end record;
type complex is array (natural range <>) of complex_bit;
Now I can choose the width of my complex numbers easily:
...
variable complex_8: complex(7 downto 0);
variable complex_16: complex(15 downto 0);
...
Doing arithmetic then becomes just a little messy, but
it's easy enough to write a package that does the job.
The key is a group of functions to extract real and
imaginary parts from the complex object, and to compose
complex values from existing real and imaginary parts:
function Re(c: complex) return signed is
variable result: signed(c'range);
begin
for i in c'range loop
result(i) := c(i).Re;
end loop;
return result;
end;
(and, of course, similarly for Im)
function to_complex(Re: signed; Im: signed := "0")
return complex is
constant width: positive := max(Re'length, Im'length);
variable norm_Re, norm_Im: signed(width-1 downto 0);
variable c: complex(width-1 downto 0);
begin
norm_Re := resize(Re, width);
norm_Im := resize(Im, width);
for i in c'range loop
c(i) := (norm_Re(i), norm_Im(i));
end loop;
return c;
end;
function to_complex(Re: integer; Im: integer; bits: positive)
return complex is
begin
return to_complex(to_signed(Re, bits), to_signed(Im, bits));
end;
These functions are pretty clumsy, but will synthesize to
nothing more than a bunch of wires, so that's OK. And then
you can easily overload the arithmetic operators...
function "+"(L, R: complex) return complex is
begin
return to_complex( Re(L) + Re(R), Im(L) + Im(R) );
end;
function "*"(L, R: complex) return complex is
begin
return to_complex( Re(L) * Re(R) - Im(L) * Im(R),
Im(L) * Re(R) + Re(L) * Im(R) );
end;
Once again, fully synthesisable. "*" will return an
appropriately widened result, and you can then pick
whatever bits you need from that wide product.
You'll also need resize(), ability to do arithmetic
between complex and scalar values (both integer and
signed), and a few other things. Writing a good
arithmetic package is non-trivial.
All this stuff can go into a package. If your synth
tool can cope with numeric_std and records, it can cope
with this. Yes, I know simulation will be a little slow
because of all the packing and unpacking of bits into
the complex data type, but I reckon that's a small price
to pay for the convenience and regularity of this sort
of thing:
...
use work.complex_pkg.all;
...
variable A, B, result : complex(7 downto 0);
variable product16 : complex(15 downto 0);
...
product16 := A*B;
result := product16(15 downto 8);
I have most of this done already; would people find it
useful if I were to work it up in detail? (Oh, and
would I get some of the credit for the assignments?)
--
Jonathan Bromley, Consultant
DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * e * Perl * Tcl/Tk * Project Services
Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
(e-mail address removed)
http://www.MYCOMPANY.com
The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.