Synthesizing code with intermediate real values

T

Topi

Hi,

I am a little surprised that the following code refuses to synthesize
(at least with Quartus and Synopsys (Lattice's)):

**********************

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

entity real_synth is
port(
a_in: in unsigned(15 downto 0);
b_out: out unsigned(15 downto 0)
);
end;

architecture synth of real_synth is
begin
process(a_in)
variable r: real;
begin
r := real(to_integer(a_in));
r := r*1.2;
if r<0.0 then
r := 0.0;
elsif r>65535.0 then
r := 65535.0;
end if;
b_out <= to_unsigned(integer(r),16);
end process;
end;

****************************

Ok, I do understand that real values are a problem when they need to
be stored, or transported (with signals/ports).

But in this case the mapping of a_in => b_out can be evaluated by
brute force (by going through all input states, and running the code
inside the process for every possible input combination and noting the
output values).

Upto today I had thought that all synthesizers would fallback to the
brute force method if intelligent algorithm generator fails. It seems
that I had mistrusted them, though.

Any ideas why the synthesizers DO NOT have this brute-force fallback
method?

- Topi
 
T

Tricky

Hi,

I am a little surprised that the following code refuses to synthesize
(at least with Quartus and Synopsys (Lattice's)):

**********************

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

entity real_synth is
        port(
                a_in: in unsigned(15 downto 0);
                b_out: out unsigned(15 downto 0)
        );
end;

architecture synth of real_synth is
begin
        process(a_in)
                variable r: real;
        begin
                r := real(to_integer(a_in));
                r := r*1.2;
                if r<0.0 then
                        r := 0.0;
                elsif r>65535.0 then
                        r := 65535.0;
                end if;
                b_out <= to_unsigned(integer(r),16);
        end process;
end;

****************************

Ok, I do understand that real values are a problem when they need to
be stored, or transported (with signals/ports).

But in this case the mapping of a_in => b_out can be evaluated by
brute force (by going through all input states, and running the code
inside the process for every possible input combination and noting the
output values).

Upto today I had thought that all synthesizers would fallback to the
brute force method if intelligent algorithm generator fails. It seems
that I had mistrusted them, though.

Any ideas why the synthesizers DO NOT have this brute-force fallback
method?

- Topi

Real types are not appropriate for synthesis in any shape or form.
There is no definition of how they exist in binary (because it is not
an array type) and so cannot be synthesised into gates and register.

You will have to convert your real values to fixed point. Try looking
at the new IEEE fixed packages. 93 compatible versions of the library
can be found here:
http://www.vhdl.org/fphdl/
 
B

Brian Drummond

Hi,

I am a little surprised that the following code refuses to synthesize
(at least with Quartus and Synopsys (Lattice's)):


process(a_in)
variable r: real;
begin
r := real(to_integer(a_in));
r := r*1.2;
if r<0.0 then
r := 0.0;
elsif r>65535.0 then
r := 65535.0;
end if;
b_out <= to_unsigned(integer(r),16);
end process;

Rearrange the computation such that all the real arithmetic operates on
constants, to return a (constant) integer or unsigned result, and
synthesis tools should behave correctly.

For this simple example you can afford to test every interesting input
value in simulation against the "real" version to prove that no rounding
errors have occurred; in general that may not be feasible.

Alternatively, explore the fixed point libraries as Tricky suggested.

- Brian
 
A

Allan Herriman

Real types are not appropriate for synthesis in any shape or form.


It's quite ok to use real types in synthesisable VHDL for code that only
gets executed at compile or elaboration time. For example, you could
write a function that uses real types internally, which is used to
produce a (non real, e.g. unsigned) value which is assigned to a constant.

generic myreal : real = 0.0;

....

constant foo : natural := myfuncthatusesreals(myreal);


This has had major tool support for a long time.

Regards,
Allan
 
T

Topi

Thanks for the suggestions.

My point of interest is towards understanding synthesizing process,
not circumventing the problem.

As Brian pointed out, in simple cases it _could be feasible_ to crawl
all possible combinations to get a truth table a_in =to> b_out.
But in more complex cases the result might be too complex to fit in to
the target (or to optimize the truth table).

Tricky: The synthesizer does not need to implement any real-valued
signals in the synthesized netlist. Actually there aren't any in the
source code either (the variable is not persistent, so it does not map
to a register). The synthesized result could be, e.g. a 64kx16 bit rom
memory.

Anyway I still don't know/understand why the synthesizer companies
have opted out from supporting "discrete input =to> discrete output
mapping, even if there are non-trivial intermediate phased".

I could easily make a vhdl to vhdl preprosessor to crawl through all
possible input combinations and to produce truth table from input to
output.

Wonder what the synthesizers would think, if e.g. I would replace 8 x
8 multiplier by 65536 entry table. Would it utilize DSP-block (in
FPGA) to implement the function?

- Topi
 
K

KJ

Thanks for the suggestions.

As Brian pointed out, in simple cases it _could be feasible_ to crawl
all possible combinations to get a truth table a_in =to> b_out.

That's not what Brian said at all. He said you can use reals to
compute constants that, in the end, vanish in the resulting function
output.
But in more complex cases the result might be too complex to fit in to
the target (or to optimize the truth table).

Such as what? Your example is not such an example if that's what you
had in mind. For starters, the comparison with 0 is not needed, the
input is unsigned, therefore could never be less than 0. Second,
rather than taking the input and multiplying by 1.2 and comparing that
to 65535.0, one could instead compare r with the computed constant
65535.0 / 1.2 converted to an unsigned. That is what Brian general
case suggestion would be for your specific example by the way.

There may indeed be more complex examples as you stated, but give an
example of such that cannot be trivially changed to be equivalent as
is the case with your original posting.
Anyway I still don't know/understand why the synthesizer companies
have opted out from supporting "discrete input =to> discrete output
mapping, even if there are non-trivial intermediate phased".

Most likely because there is little market demand from users. Even
real constants didn't use to be supported. Now (and for the past
several years) they are. Given a compelling reason the synthesis
vendors do support user requests although usually not nearly as fast
as some might like.

If you can come up with a compelling use case then you should submit
it to all the vendors as a feature suggestion. However, you would
have to do better than your example where the only motivation you
could provide is that you don't like the looks of "65535.0 / 1.2" as
compared to "r * 1.2".
I could easily make a vhdl to vhdl preprosessor to crawl through all
possible input combinations and to produce truth table from input to
output.

OK. And then compare what you get with that approach versus computing
the constant as suggested here. The metrics of importance to most
people would be amount of logic resources used and performance. If
your approach is an improvement you're on to something. If not, maybe
the synthesis vendors aren't doing such a bad job after all.
Wonder what the synthesizers would think, if e.g. I would replace 8 x
8 multiplier by 65536 entry table. Would it utilize DSP-block (in
FPGA) to implement the function?

I would find it highly unlikely that any synthesis tool would take a
truth table specification in the source code and infer a multiply
operation from it and then use a DSP block to implement the
multiply...not to say that it couldn't, I would just be very surprised
if it did.

Kevin Jennings
 
B

Brian Drummond

That's not what Brian said at all. He said you can use reals to compute
constants that, in the end, vanish in the resulting function output.

Thanks, that was it.
Such as what? Your example is not such an example if that's what you
had in mind. For starters, the comparison with 0 is not needed, the
input is unsigned, therefore could never be less than 0. Second, rather
than taking the input and multiplying by 1.2 and comparing that to
65535.0, one could instead compare r with the computed constant 65535.0
/ 1.2 converted to an unsigned. That is what Brian general case
suggestion would be for your specific example by the way.

Indeed. But to expand on this, the original code would output r * 1.2 in
cases where saturation did not occur, so at least an integer constant
multiplication is necessary.

However it should be a multiplication of the form :
r := a_in * to_unsigned(1.2 * 65536) / 65536;
where the division is trivial.

Now if careful attention is paid to issues of rounding vs. truncation,
this will deliver identical results to the original, while eliminating
run-time floating point arithmetic; otherwise it may deliver results
differing by +/-1 LSB.

To be explicit about this, you may need to round the coefficient
r := a_in * to_unsigned(1.2 * 65536 + 0.5) / 65536;
or the
r := (a_in * to_unsigned(1.2 * 65536) + 0.5)/ 65536;
or both
to match the integer(r) function in the original version.

THIS is where I recommend exhaustive testing; comparing the modified
process vs. the original, IN SIMULATION, for every input value.
Assert the outputs are equal; regard any difference as a failure.

This is the brute force approach, but for only 2**16 input values it is
the easiest. I have used it up to 2**24 inputs without too much pain.
Alternatively, you may find a mathematical analysis to reduce the number
of test cases required (otherwise, for 2 independent 32-bit inputs, 2**64
testcases would be required!)

Or you may need to justify (and document!) that a 1 LSB error is
permissible in your use case; e.g. because the input data has a noise
component much grater than this level.


- Brian
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top