Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

[SOLVED] Parameterized VHDL constants

Status
Not open for further replies.

mtwieg

Advanced Member level 6
Advanced Member level 6
Joined
Jan 20, 2011
Messages
3,904
Helped
1,311
Reputation
2,628
Reaction score
1,435
Trophy points
1,393
Activity points
29,985
When coding in C, I often use preprocessors to calculate thresholds and scaling factors based on various parameters. For example, see below:
C:
#define counter_T_sec 10.0e-6 //time between counter ticks, in seconds
#define counter_thresh_sec 1005.4e-6 //want to detect when this much time elapses
#define counter_thresh_uint(counter_thresh_sec/counter_T_sec) //the threshold in counter ticks

#define ADC_VREF 3.0  //reference voltage of ADC in volts
#define ISENSE_GAIN 1.215   //gain of a current sense amplifier, in volts per amp
#define ADC_MAX 4095.0 //max value of ADC
#define ISENSE_THRESH_amp (-5.41) //threshold in amps
#define ISENSE_THRESH_lsb (ISENSE_THRESH_amp*ISENSE_GAIN/ADC_VREF*ADC_MAX) //threshold in ADC lsbs

uint16 counter;
uint16 counter_thresh=counter_thresh_uint;
int16 ADC_result;
int16 ADC_thresh=ISENSE_THRESH_lsb;
.....
if(counter==counter_thresh)
{
}
....
if(ADC_result<ADC_thresh)
{
}
I put most of my #define parameters as floats, to preserve precision, and the compiler automatically casts it to the right type when assigning it to my variables.

I'm trying to find a similar way to do this in VHDL, but so far haven't found anything close. Generics work fine when I have a finite set of implementations I want to choose from. But in my case some of these parameters could be anything (like the gain of an amplifier).

I see this sort of question brought up often, but usually the requirement is for determining port or variable widths. In my case I just want to control the values of constants (I'm ok with fixing them at a large width to ensure against overflow).

Any tips? I'm working in quartus.
 

I have more often constants defined in a package than passing it through generic ports down the hierarchy. And often as real parameter that is converted to integer respectively fixedpoint when it's instantiated.
 

Im not quite sure what you're getting at. Port widths can be set from generics that could be defined based on specific values anywhere. So I dont really understand what the problem is? why not post some VHDL code that you're struggling with?
 

Fair enough, here's a rough attempt at a vhdl implementation of the current sense threshold example I posted before:

Package file:
Code:
package my_pkg is
    constant ADC_VREF_mv                        : integer := 3000; --Vref is 3.000V
    constant ADC_MAX                            : integer := 4095; --12 bit ADC
    constant ISENSE_GAIN_m                        : integer := 1215; --Gain is 1.215 volts per amp
    constant ISENSE_THRESH_ma                    : integer := 1410; --threshold is 1.41 amps
end my_pkg;

vdl file:
Code:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.my_pkg.all;    

entity example_entity is
    port(
    ADC_IIN        : in unsigned(11 downto 0); --data from ADC
    clk10m                   : in  std_logic;    -- 10MHz clock
    reset                    : in  std_logic
    );
end entity;

architecture rtl of example_entity is

signal Ithresh_flag                        : std_logic;
constant ISENSE_THRESH_lsb                : unsigned(11 downto 0) := to_unsigned(ISENSE_THRESH_ma*ISENSE_GAIN_m/ADC_VREF_mv*ADC_MAX/1000,12);

-- when the current exceeds 1.41, set Ithresh_flag to 1
begin
    Ithresh_proc:process(clk10m,reset)
    begin
        if reset = '1' then
            Ithresh_flag <= '0';
        elsif rising_edge(clk10m) then
            if(ADC_IIN > ISENSE_THRESH_lsb) then
                Ithresh_flag <= '1';
            end if;
        end if;
    end process;
end rtl;

Does that look sensible?
 

Code:
constant ISENSE_THRESH_lsb : unsigned(11 downto 0) := to_unsigned(ISENSE_THRESH_ma*ISENSE_GAIN_m/ADC_VREF_mv*ADC_MAX/1000,12);
The integer expression is evaluated left to right, introducing an unnecessary rounding error. You can improve accuracy by reordering the terms, but it's easier to perform real calculations and convert the final result unsigned.
 

Still not really sure what the question is?
 

Right, so I could probably get better accuracy by dividing after multiplying:
Code:
constant ISENSE_THRESH_lsb : unsigned(11 downto 0) := to_unsigned(ISENSE_THRESH_ma*ISENSE_GAIN_m*ADC_MAX/ADC_VREF_mv/1000,12);
But then I run into the hazard of overflowing the 32 bit size of integer type (the example above does), unless I shave off more precision in the starting parameters. I'd like to avoid the tradeoff, like what the C preprocessor achieves.

it's easier to perform real calculations and convert the final result unsigned.
You mean do the calculations manually and then just write in the final result? That's what I'm currently doing, but some of these have many parameters, so it's an error-prone process.
 

No, I mean to do the real calculation in VHDL.
You mean using hardware resources to calculate them at runtime, or with a method similar to my C example with? The former case isn't feasible for me since I don't have many extra multipliers around. For the latter case, I can't find a method of doing it.
 

Still not really sure what the question is?
Honestly I'm not sure how else to state it. I can do something very effectively in the C preprocessor, and want to do something similar in VHDL, but can't find a method that doesn't have pitfalls with loss of precision and/or variable overflow. Maybe the solution I'm looking for is simple and trivial, but searching around the internet hasn't revealed it yet.
 

With enough bits, you can be as accurate as you want. Usually its a question of how much loss of precision is acceptable. You need to analyse the calcualtion by hand to work out where a rounding error will occur with little/no effect on the outcome, and where it does. You are also allowed to use the real type (floating point) in the derivation of constant values to keep preceision if you need to and/or it makes the calculation simpler.
--- Updated ---

for example, the following is perfectly useable and synthesisable (this function may require normalisation) .


Code VHDL - [expand]
1
2
3
4
5
6
7
8
package my_pkg is
    constant ADC_VREF_mv                        : real := 3.000; --Vref is 3.000V
    constant ADC_MAX                            : real := 4095.0; --12 bit ADC
    constant ISENSE_GAIN_m                      : real := 1.215; --Gain is 1.215 volts per amp
    constant ISENSE_THRESH_ma                   : real := 1.410; --threshold is 1.41 amps
end my_pkg;
 
constant ISENSE_THRESH_lsb                : unsigned(11 downto 0) := to_unsigned( integer(ISENSE_THRESH_ma*(ISENSE_GAIN_m/ADC_VREF_mv)*(ADC_MAX/1000.0) ) ,12);

 
Last edited:

    mtwieg

    Points: 2
    Helpful Answer Positive Rating
You mean using hardware resources to calculate them at runtime, or with a method similar to my C example with? The former case isn't feasible for me since I don't have many extra multipliers around. For the latter case, I can't find a method of doing it.
I'm talking about compile time calculations.

Consider LPFC, FCLK, ISCALE, U2SCALE being real constants and N_TS an integer constant. Then the below unsigned expression can be used anywhere in the code without consuming hardware resources.

Code:
TO_UNSIGNED(INTEGER(0.5/LPFC/FCLK/ISCALE*U2SCALE*2.0**(15+N_TS-1)),15)
 

    mtwieg

    Points: 2
    Helpful Answer Positive Rating
I never used the real type and wasn't aware it could be used like that. I guess that's the "simple and trivial" solution I mentioned above.

So if I use the real type in the way TrickyDicky suggests:
Code:
constant ISENSE_THRESH_lsb                : unsigned(11 downto 0) := to_unsigned( integer(ISENSE_THRESH_ma*(ISENSE_GAIN_m/ADC_VREF_mv)*(ADC_MAX/1000.0) ) ,12);
Do the order of operations no longer matter to the result? The only loss of precision is the truncation when converted to integer?
 

The order of operations wont neccessarily matter in this case, but it is worth learning the operator precidence to avoid problems like this in future. Also, dont give me all the credit. @FvM suggested the same solution as me at the same time.
 

Yep, thanks to you both. Seems to synthesize well so far. I wish I had asked six months ago, would have saved a lot of tedious work.
 

Status
Not open for further replies.

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top