olo_fix_lin_approx¶
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:
- Describe the approximation in a configuration object (olo_fix_lin_approx_cfg).
- Iterate on the settings using analyze() until the accuracy and the resource usage fit the needs.
- Generate the VHDL entity (generate_entity()) and - optionally - a bit-true testbench (generate_tb()).
- 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.