Skip to content

XLS backend - #1475

Open
vasdommes wants to merge 129 commits into
fastmachinelearning:mainfrom
vasdommes:xls_backend
Open

XLS backend#1475
vasdommes wants to merge 129 commits into
fastmachinelearning:mainfrom
vasdommes:xls_backend

Conversation

@vasdommes

@vasdommesvasdommes commented May 14, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds XLS backend. It is based on PR #1343, with most of the code rewritten and new features added.

Google XLS is an open-source (Apache 2) High Level Syntesis toolchain that produces an RTL (Verilog or SystemVerilog) design from a high-level description (DSLX or C++).

Adding XLS as a new hls4ml backend allows to generate RTL without vendor-specific dependencies and benefit from the developments that XLS brings to HLS field.

XLS workflow

XLS backend performs the following transformations:

DSLX -> IR -> (System)Verilog conversion is done by XLS.
IP can generated from Verilog file manually e.g. via Vivado or another vendor.

XLS features

XLS backend supports the following layers: Input, ApplyAlpha, BatchNormalization, Dense, Conv1D, DepthwiseConv1D, Conv2D, DepthwiseConv2D, Pooling1D, Pooling2D, GlobalPooling1D, GlobalPooling2D, Merge, Concatenate, Dot, Activation, HardActivation, ParametrizedActivation, PReLU, Reshape, Softmax, Transpose, TernaryTanh.

You can override default codegen options as follows:

config=hls4ml.utils.config_from_keras_model(model)
# This sets hls_model.config['XLSCodegenFlags']hls_model=hls4ml.converters.convert_from_keras_model(
model, hls_config=config, backend='XLS',
xls_codegen_flags={'delay_model': 'asap7', 'generator': 'pipeline', 'use_system_verilog': False}
)

DSLX standard library has only signed FixedPoint type (similar to ap_fixed). Thus, unsigned types are not supported.

Currently, XLS backend implements only IOType: io_parallel. Strategy is ignored.
All operations are fully unrolled.

io_stream could be implemented via DSLX procs. @calad0i and I are going to work on that after finishing this PR.

Other changes

I made some minor changes in non-XLS code:

Dependencies

XLS backend uses xls-python to access XLS API. It is enabled by dependency group xls:

pip install hls4ml[xls]

xls-python comes with batteries (libxls.so and DSLX standard library) included, no separate XLS installation is required.
The code has been tested for the version xls-python=0.1.9875.

Known issues

XLS doesn't work with Dense layer imported from PyTorch Linear layer because of shape mismatch: PyTorch stores Linear weights as (out_features, in_features), while hls4ml Dense layers use the Keras-style layout (in_features, out_features).

Repro: add XLS backend to test_pytorch_api.py/test_squeeze and run the test.

Note that the weights in this test are constant, and other backends flatten them without checking shape.
So, it is unclear whether they handle this situation correctly or not.

Type of change

  • Documentation update
  • New feature (non-breaking change which adds functionality)

Tests

📝 Please describe the tests that you ran to verify your changes.

  • Provide instructions so we can reproduce.
  • Please also list any relevant details for your test configuration.

XLS has been added to the following tests:
test_activations.py, test_auto_precision.py, test_binary_cnn.py, test_causalpadding.py, test_depthconv1d.py, test_depthconv2d.py, test_keras_api.py, test_keras_v3_api.py, test_merge.py, test_multi_dense.py, test_pointwiseconv.py, test_pooling.py, test_pytorch_api.py, test_reshape.py, test_sepconv1d.py, test_sepconv2d.py, test_softmax.py.

Test Configuration

Add xls dependency, e.g.

pip install .[da,testing,testing-keras2,sr,optimization,xls]"
# or: pip install .[da,testing,testing-keras3,sr,xls]"

and run tests, e.g.:

pytest test/pytest --randomly-dont-reset-seed -k XLS

Notes on performance

Some test cases are very slow for XLS (e.g. ~30 minutes vs ~10 seconds on other backends).
This happens because XLS generates (in model.compile()) and uses (in model.predict()) an optimized XLS IR code, where all loops are fully unrolled. The resulting file can be huge and thus slow for the likes of Conv2D.

During development, I made test faster by reducing dimensions in some tests.
For example, in test_keras_api.py/test_conv2d I replaced

input_shape = (28, 28, 3)
filters=32

with

input_shape = (14, 14, 3)
filters=8

I haven't pushed such changes, but that could be one of the ways of speeding things up.

UPD: I reduced XLS dimensions in many tests, see 8578a62 and 75a04fb.

Checklist

  • I have read the guidelines for contributing.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation.
  • My changes generate no new warnings.
  • I have installed and run pre-commit on the files I edited or added.
  • I have added tests that prove my fix is effective or that my feature works.

Girjoabaand others added 30 commits July 16, 2025 18:04
… pass, merge of dense_relu written as an opt pass
This fixes two test cases in test_softmax.py
(one of them still fails due to another error)
TODO: check layer.class_name == 'Input' instead of taking layers[0]?
This fixes DSLX compilation error in test_softmax.py
# Conflicts:
#	docs/requirements.txt
#	hls4ml/backends/__init__.py
#	hls4ml/model/graph.py
#	hls4ml/report/__init__.py
#	test/pytest/test_activations.py
#	test/pytest/test_keras_api.py
#	test/pytest/test_softmax.py
See fastmachinelearning#1443
Setting 'strategy' for Softmax layer did not affect anything, and the code always chose the default implementation=stable.
TODO: all backends fail when implementation=latency (low accuracy, probably due to overflow).
Comment threadhls4ml/backends/xls/xls_types.py Outdated
Comment threadhls4ml/backends/xls/xls_types.py Outdated
Comment threadhls4ml/backends/xls/xls_types.py
Comment threadhls4ml/backends/xls/xls_types.py Outdated
Comment threadhls4ml/backends/xls/xls_backend.py Outdated
Comment threadhls4ml/backends/xls/xls_types.py Outdated
Comment threadhls4ml/backends/xls/xls_types.py Outdated
Comment threadhls4ml/report/xls_report.py Outdated
Comment threadhls4ml/backends/xls/xls_types.py Outdated
Comment threadhls4ml/backends/xls/xls_types.py Outdated
…vado after that.
XLS backend should not have anything vendor-specific. It is up to user to choose the tools for the next stages (RTL -> IP conversion).
Removed all Vivado references from XLS, leaving only build_prj.tcl (Vivado script) as an example.
…loats
use quantizers.get_fixed_quantizer_np()
# Conflicts:
#	pyproject.toml
#	test/pytest/ci-template.yml
Used e.g. in test_qkeras.py
TODO: currently it fails because XLS does not support unsigned types.
…om BuildAttr, refactor xls_writer.
Custom attributes (input and output variables, weights etc.) are not needed anymore. Now we wrap existing attributes.
Some former attributes are now computed on the fly by functions xls_layer_util.py.
In DSLX code, now we use existing TensorVariable names when possible (e.g. in function arguments).
We also do not specify input types separately - we import them from other layers.
…ke the tests pass.
These test cases started to fail after fixing rounding and saturation in 8e28449.
See also commit 987961e.
Use stable implementations for softplus and sigmoid, clip exp(x) for softmax_latency.
Also use math.logp1(x) and math.expm1(x) when possible to increase accuracy.
@vasdommes
vasdommes requested a review from calad0iJuly 7, 2026 20:33
- Move XLS weights definition out of XLSWeightVarConverter
- Remove xls_ prefix when not needed (i.e. everywhere except for wrapper classes)
- class XLSDefinitionBase
Comment threadhls4ml/backends/xls/xls_backend.py Outdated


def xls_func_name(node: Layer) -> XLSQualifiedName:
match node.class_name:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we do something like f'{node.class_name}{node.index}' to save the whole case-match?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, here we need qualified names for library functions, e.g. activations::softmax_stable or pooling::global_pooling_2d.

We could put all layer functions into one giant DSLX module (note that DSLX does not allow re-exports), but that's ugly. Also simply returning node.class_name and removing ValueError would lead to more obscure DSLX errors if layer is not supported.

Comment on lines +463 to +496
match class_name:
case 'BatchNormalization':
# NB: we need flattening because sometimes the weights can be e.g.
# (1,1,1,n_filt) instead of (n_filt,)
# We'll throw an error if there are several dimensions larger than 1.
data = data.flatten()
n_filt = node.get_attr('n_filt')
if n_filt == -1:
n_filt = input_var.shape[-1]
expected_shape = (n_filt,)
case 'Conv1D':
expected_shape = tuple(node.get_attr(x) for x in ['filt_width', 'n_chan', 'n_filt'])
case 'DepthwiseConv1D':
expected_shape = tuple(node.get_attr(x) for x in ['filt_width', 'n_chan', 'depth_multiplier'])
case 'Conv2D':
expected_shape = tuple(node.get_attr(x) for x in ['filt_height', 'filt_width', 'n_chan', 'n_filt'])
case 'DepthwiseConv2D':
expected_shape = tuple(
node.get_attr(x) for x in ['filt_height', 'filt_width', 'n_chan', 'depth_multiplier']
)
case 'Dense':
# Transpose the weights so that we can call dot_prod(x, w[i]) in nnet_utils/dense.x
data = data.T
expected_shape = (output_var.shape[0], input_var.shape[0])
case 'PReLU':
expected_shape = (input_var.shape[0],)
case _:
raise ValueError(f'Unsupported weights for layer {node.class_name}')

if expected_shape is not None:
assert shape_tuple(data.shape) == expected_shape, (
f'Weights shape mismatch: expected {expected_shape}, got {data.shape}'
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asserting in variable definition is probably a bit misplaced, and we likely don't really need to assert these outside the corresponding layer specific handlers. Maybe remove?

@vasdommesvasdommesAug 7, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally yes, everything should be handled earlier. But if something goes wrong either with XLS or with layer handling, as it does in PyTorch Linear layer case:

# TODO: this test fails for XLS due to PyTorch weights shape mismatch.
@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'oneAPI'])
@pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream'])
deftest_squeeze(test_case_id, backend, io_type):

then assertion error

AssertionError: Weights shape mismatch: expected (1, 5, 3), got (1, 3, 5)

is more readable than XLS error

layer_6_linear.x:50:5-50:27 TypeInferenceError: type mismatch: FixedPoint<16, -10>[5] vs. FixedPoint<16, -10>[LAYER6_OUT_DIM_1]. The body of function transform_2d does not actually return the function's declared return type, which is FixedPoint<16, -10>[LAYER6_OUT_DIM_1][LAYER6_OUT_DIM_0]

Comment threadhls4ml/model/graph.py
self._top_function_lib = ctypes.cdll.LoadLibrary(lib_name)

def _get_top_function(self, x):
def _get_top_function(self, x, *args, **kwargs):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If warp predict() directly, this part could be removed.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that _get_top_function is called inside _predict.

def_predict(self, x):
top_function, ctype=self._get_top_function(x)

I see two options here:

  1. Keep get_top_function hook.
  2. Remove get_top_function hook, lift self._get_top_function(x) call from _predict() to predict(). Then every predict() implementation would need to make an extra self._get_top_function(x) call:
defpredict(self, x)
# <...>top_function, ctype=self._get_top_function(x)
returnself._predict(x, top_function, ctype)

Option 1 seems lees intrusive to me, although it adds three extra lines of code.

ctype is not used in XLS, but it is required by ModelGraph._predict() API
# Conflicts:
#	test/pytest/test_softmax.py
@vasdommes
vasdommes requested a review from calad0iAugust 12, 2026 17:42
@JanFSchulteJanFSchulte added please test Trigger testing by creating local PR branch feature New hls4ml feature and removed please test Trigger testing by creating local PR branch labels Aug 12, 2026
@vasdommes

Copy link
Copy Markdown
ContributorAuthor

test_softmax failed for Quartus with implementation=latency:

FAILED test_softmax.py::test_softmax[16,6-input_shape4-9,6-io_parallel-False-latency-Quartus-standalone] - assert 0.5308 >= 0.98
FAILED test_softmax.py::test_softmax[16,6-input_shape5-9,6-io_stream-False-latency-Quartus-standalone] - assert 0.5216 >= 0.98
FAILED test_softmax.py::test_softmax[9,6-input_shape6-18,8-io_parallel-False-latency-Quartus-standalone] - assert 0.5636 >= 0.98
FAILED test_softmax.py::test_softmax[9,6-input_shape7-18,8-io_stream-False-latency-Quartus-standalone] - assert 0.5598 >= 0.98

This became visible because of my fixes for #1443 (enable latency and argmax in test_softmax), which are not directly related to XLS.
I can revert these changes to let CI tests pass for now. @jmitrevs made a similar fix, included in PRs #1476 and #1494.

P.S. Other CI failures seem to be due to timeout (probably unrelated to XLS, same tests work on my machine).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

featureNew hls4ml featureplease testTrigger testing by creating local PR branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test_softmax.py does not test argmax and latency implementations; latency fails

7 participants

@vasdommes@calad0i@vloncar@JanFSchulte@jmitrevs@Girjoaba