Skip to content

olo_fix_lin_approx

Back to Entity List

Status Information

This is a pure Python utility and therefore does not come with VHDL simulation status.

Python Source: olo_fix_lin_approx
Related Entity: olo_fix_lin_approx_calc

Description

olo_fix_lin_approx is a bit-true model and a code generator for function approximations. Any function can be approximated by a table containing the function value (offset) and its derivative (gradient) for regularly spaced points, plus linear interpolation between those points.

This is not just one component but a whole family of components. All approximations share the same HDL implementation of the calculation (olo_fix_lin_approx_calc) and only differ in number formats and table content. The HDL is therefore not written by hand but generated from Python.

The typical workflow is:

  1. Describe the approximation in a configuration object (olo_fix_lin_approx_cfg).
  2. Iterate on the settings using analyze() until the accuracy and the resource usage fit the needs.
  3. Generate the VHDL entity (generate_entity()) and - optionally - a bit-true testbench (generate_tb()).
  4. Use the generated entity in the HDL design and the Python object as bit-true model in the Python design.

For details about the approximation itself, refer to olo_fix_lin_approx_calc.

Usage

Describing an Approximation

from olo_fix import olo_fix_lin_approx, olo_fix_lin_approx_cfg
from en_cl_fix_pkg import *
import numpy as np

cfg = olo_fix_lin_approx_cfg(
    function=lambda x: np.sin(x*2*np.pi)*(1 - 2**-16),  # Prevent +1.0 from occurring
    in_fmt=FixFormat(0, 0, 16),
    out_fmt=FixFormat(1, 0, 16),
    offs_fmt=FixFormat(1, 0, 18),
    grad_fmt=FixFormat(1, 3, 12),
    points=256,
    name="sin16b")
my_approx = olo_fix_lin_approx(cfg)

The function is approximated over the full range of in_fmt. Lambdas can be used to scale the X and Y axes, as shown above where the input range [0, 1) is mapped to one full period of the sine.

Designing an Approximation

analyze() plots the approximation against the ideal function, prints the approximation error and prints the value ranges required for the offset and gradient tables. It is meant to be used interactively while choosing settings and is not part of the bit-true model.

my_approx.analyze()

Typical iteration steps are:

  • Increase points if the approximation error is too large (the error scales roughly with 1/points^2).
  • Adapt offs_fmt and grad_fmt to the ranges printed by analyze() - the higher their resolution the smaller the output error - but also the higher the memory consumption for the table.
  • Restrict valid_range if the function is only used on a part of the input range (e.g. 1/x or sqrt(x) have very steep gradients close to zero). Table entries outside the valid range are still generated but they are not used for the analysis and for the generated testbench.

Generating Code

my_approx.generate_entity("./hdl")     # Generates ./hdl/olo_fix_lin_approx_sin16b.vhd
my_approx.generate_tb("./testbench")   # Generates the testbench plus the co-simulation data files

generate_entity() writes a self-contained VHDL entity that contains the table (as ROM) and instantiates olo_fix_lin_approx_calc. The entity has the following interface:

entity olo_fix_lin_approx_sin16b is
    generic (
        MemStyle_g : string := "auto"    -- Resource control for the table
    );
    port (
        Clk        : in    std_logic;
        Rst        : in    std_logic;
        In_Valid   : in    std_logic := '1';
        In_Data    : in    std_logic_vector(16 - 1 downto 0);
        Out_Valid  : out   std_logic;
        Out_Result : out   std_logic_vector(17 - 1 downto 0)
    );
end entity;

Generating a Table Package

generate_entity() produces one entity per approximation, with the table built in. If instead one entity must select between several tables at elaboration time (e.g. one table per supported output format), use the static generate_package() instead. It takes a dictionary of approximations and writes a package containing one table per entry:

approximations = {
    "sin16b" : olo_fix_lin_approx(cfg16),
    "sin20b" : olo_fix_lin_approx(cfg20),
}
olo_fix_lin_approx.generate_package(approximations, "my_tables_pkg", "./hdl")

The generated package provides the following public interface, where \<name> is the key used in the dictionary:

Function Description
getTable(\<name>) Table content. Each entry contains the gradient (MSBs) and the offset (LSBs).
getTableSize(\<name>) Number of points (segments) of the table
getOffsetFmt(\<name>) Format of the offset entries, as string
getGradientFmt(\<name>) Format of the gradient entries, as string

All entries of all tables are zero padded to the same width so they fit into one array type. The padding is meant to be sliced away by the entity, hence it does not cost any memory.

The internal quarter-sine approximation used by olo_fix_sin (olo_fix_private_lin_approx_qsin.vhd) is an example of this pattern - it selects one of 22 quarter-sine tables based on its output format.

Generating a Testbench

generate_tb() writes a VUnit testbench that checks the generated HDL against the Python model. It uses the same verification components as all other olo_fix testbenches (olo_test_fix_stimuli_vc / olo_test_fix_checker_vc) and it also writes the co-simulation files containing the stimuli and the expected responses.

If Open Logic is compiled into a library other than olo, the library name can be passed to both functions through the olo_library argument.

Using the Bit-True Model

out_data = my_approx.process(in_data)

The model is stateless, hence next() and process() are identical. The result is bit-true to the generated HDL.

Class Descriptions

Generally the methods are documented in python docstring format.

olo_fix_lin_approx_cfg

Data container describing one approximation.

Argument Type Default Description
function Callable - Function to approximate over the full range of in_fmt. Must accept numpy arrays.
in_fmt FixFormat - Format of the input to the approximation
out_fmt FixFormat - Format of the output of the approximation
offs_fmt FixFormat - Format of the offset table (function values at the segment centers)
grad_fmt FixFormat - Format of the gradient table (derivatives at the segment centers)
points int - Number of segments in the table. Must be a power of two and smaller than 2^width(in_fmt)
name str - Name suffix of the approximator generated. The entity generated is named olo_fix_lin_approx\<name>_
valid_range tuple full in_fmt Range in which the approximation is valid. Clipped to the range representable by in_fmt.
round FixRound NonSymPos_s Rounding mode of the output stage
saturate FixSaturate Sat_s Saturation mode of the output stage

Most things are self-explanatory. One thing being worth an explanation is valid_range. This setting does allow defining approximations that are only valid in a certain range. For example it is possible using in_fmt=(0,0,10) (which has a range of 0...~1.0) but define valid_range=(0.25, 1.0) to design an approximation that is not valid in the lowest quarter of the values (e.g in case the value is very large in this area).

olo_fix_lin_approx

The methods are grouped the same way as in the source code: the bit-true model first, then the code generation and finally the helpers used while designing an approximation.

Method Description
reset() Does nothing - the approximation is stateless. Exists for consistency with the other olo_fix models
next(in_data) Bit-true calculation of the approximation for the samples passed
process(in_data) Identical to next() - the approximation is stateless
generate_entity(...) Generate the VHDL entity (including the table) and return the entity name
generate_package(...) Static - generate a VHDL package containing the tables of several approximations and return the package name
generate_tb(...) Generate the bit-true testbench plus co-simulation files and return the TB name
entity_name Property containing the name of the entity generated
analyze(...) Design helper - plot accuracy and print the ranges required for the tables

Verification

The bit-trueness between the Python model and the generated HDL is verified in the Open Logic regression. This co-simulation also covers the code generation (including the templates), hence the code generation is excluded from the python unit-test coverage. The samples used are defined in test/fix/olo_fix_lin_approx/lin_approx_codegen.py and cover all combinations of signed/unsigned input and signed/unsigned output. The entities, testbenches and co-simulation files are generated by sim/codegen.py before VUnit detects the files.