This document reflects the v6.2.3 version of the source code.
The Developer Guide explains how to build and modify the source code of the DAQ40 software, refer to the User Guide to learn how to use this software. Both guides concern only the software, for the firmware please refer to the Firmware Guides. For the Experiment Control System components and software (implemented on top of the DAQ40 software) please refer to https://lhcb-ecs.web.cern.ch/.

Getting the source

The project source code is versioned using the CERN gitlab infrastructure, read access is given by default to all members of the LHCb gitlab group.

git clone -b nameofbranch https://gitlab.cern.ch/lhcb-daq40/lhcb-daq40-software.git

Two main branches are available to choose from:

devel

This branch is intended for most development. Effort is taken to ensure that the head is always buildable but that is not guaranteed. If the current head revision fails to build from source, just use the most recent development tag (which is guaranteed to build). Development tags have a -wip suffix. The most recent development tag will be the last listed by:

git tag | sort | grep "\-wip"
master

This branch contains only tested releases that are meant for end users. This means that this branch contains only merge commits from devel and occasionally urgent bugfixes. The git head will already correspond to the most recent stable tag, but it can be also obtained at the end of this output:

git tag | sort | grep -v "\-wip"

Building from source

The build system for this project is based on GNU Make.

A top-level Makefile is provided to build all subprojects at once. A top-level build cannot be an in-tree build, a dedicated build folder must be created first. For example:

mkdir build/ && cd "$_"
make -f ../Makefile

To avoid repeating the -f flag in each make invocation, a ./make wrapper script is automatically created in the build folder as a shortcut, it can be invoked in the same way make would.

In a top-level build, the following targets are available:

default, install, uninstall, man, maninstall

Only available out-of-tree, each just makes the given target in all subprojects (subproject targets are documented below).

clean

If in-tree, cleans each subproject, if out-of-tree, empties the build folder.

doc

Only available in-tree, builds the documentation.

Each subfolder containing a Makefile can be build individually by simply issuing make from there. Dependencies on targets built in other subfolders will be built automatically. This can be used during development to quickly iterate on a single subproject. By default, a sub-project Makefile implements at least the following targets:

default

Builds all library and executable targets.

install

Copies installable files to their destination.

uninstall

Removes files copied by both install and maninstall.

man

Builds man pages.

maninstall

Installs man pages under $(PREFIX)/share/man.

clean

Cleans build directory and deletes final targets.

depclean

Makes the clean target in every dependency of this subproject.

Build dependencies

On CC7, the following packages are required to build the software:

rpm-build glibc-headers glibc-devel git make gcc-c++ gcc flex boost-devel libpcap-devel dim dim-devel hwloc-devel libcurl-devel

On CC7, the following packages are required to build the kernel drivers:

rpm-build make gcc kernel kernel-devel dkms git

On CC7, when using devtoolset-7, the following packages are required to build the software:

rpm-build centos-release-scl git flex boost-devel libpcap-devel dim dim-devel hwloc-devel libcurl-devel

On EL9, the following packages are required to build the software:

rpm-build glibc-headers glibc-devel git make gcc-c++ gcc flex tcl boost-devel libpcap-devel dim dim-devel hwloc-devel libcurl-devel

DKMS is not included in the base distribution:

RUN yum install -y epel-release

On EL9, the following packages are required to build the kernel drivers:

rpm-build make gcc kernel kernel-devel dkms git

Configuring the build

Several variables can be set in order to control what to build and what dependencies to enable, these can be set either in the environment, for example:

ENABLE_XXX=false make   # Set flag for single command, or
export ENABLE_XXX=false # export flag in environment and
make                    # reuse exported flags.

Or by directly editing the file common/flags.mk.

The following dependency flags are available:

ENABLE_DIM

Whether to include support for DIM. Default: true.

ENABLE_INFLUX

Whether to produce monitoring data (uses the InfluxDB Line Protocol format). Default: true

ENABLE_MON

Whether to include the interface for DQMP (Data Quality Monitor and Presenter). Default: false.

ENABLE_HWLOC

Whether to include support for the hwloc library (will try to schedule threads processing data from a PCIe40 board on a core closest to the corresponding PCIe root complex). Default: true.

ENABLE_IB

Whether to include support for the InfiniBand verbs API. Default: false.

ENABLE_DOCRA

Whether to generate the documentation (HTML and man pages). Requires docra and asciidoctor installed. Default: true.

The following flags can be used to build only a subset of the targets:

ENABLE_DAQ40

Build tools and libraries that are common to both AMC40 and PCIe40 setups. Default: true.

ENABLE_AMC40

Build tools and libraries specific to the AMC40 board. Default: true.

ENABLE_CCPC40

Build tools and libraries specific to the AMC40 CCPC module. Default: false.

ENABLE_PCIE40

Build tools and libraries specific the the PCIe40 board. Default: true.

Building in a docker container

In order to have a build environment compatible with the production environment where the AMC40 and PCIe40 boards are deployed, several Dockerfiles are provided that already include all the build dependencies. These are:

rpm/os/cc7/Dockerfile

CERN CentOS 7 with all dependencies. This is the most used environment.

rpm/os/cc7-dkms/Dockerfile

CERN CentOS 7 with kernel dependencies for building DKMS-enabled drivers. Only used to build the device and emulation drivers and to ensure the RPMs install cleanly after they’re build.

rpm/os/cc7-dt7/Dockerfile

CERN CentOS 7 with all dependencies and the devtoolset-7 software collection. This environment is used to ensure compatibility with more recent versions of GCC.

rpm/os/el9/Dockerfile

AlmaLinux 9 (RHEL9 compatible) with all dependencies. This is the most used environment.

rpm/os/el9-dkms/Dockerfile

Alma Linux 9 with kernel dependencies for building DKMS-enabled drivers. Only used to build the device and emulation drivers and to ensure the RPMs install cleanly after they’re build.

rpm/os/slc6/Dockerfile

Scientific Linux 6 with all dependencies plus the AMC40 LLI and 32-bit runtime libraries (required to build executables for the AMC40 CCPC).

rpm/os/slc6-dkms/Dockerfile

Scientific Linux 6 with dependencies for building DKMS-enabled drivers. Only provided for backwards compatibility. PCIe40 users are strongly reccommended to run CC7.

The script at rpm/makecontainers.sh can be used to automatically build all these containers on your development machine.

The script at rpm/makecontainers-gitlab.sh can be used to automatically push these containers to the CERN gitlab container registry. This will update the containers used to run the Continuous Integration jobs.

Running the container

cc7

The container is tagged as lhcb/daq40-sw-rpm-cc7 and can be run using ./rpm/os/slc6/run.sh from the top-level folder. This is equivalent to ( TOP is the top level of the git repository)

${TOP}/common/docker/run.sh lhcb/daq40-sw-rpm-cc7 \
 -v=${TOP}:${HOME}/rpmbuild/SOURCES/lhcb-daq40-software \
 -v=${TOP}/rpmbuild/el7:${HOME}/rpmbuild/RPMS
cc7-dkms

The container is tagged as lhcb/daq40-sw-dkms-cc7 and can be run using ./rpm/os/cc7-dkms/run.sh from the top-level folder. This is equivalent to ( TOP is the top level of the git repository)

${TOP}/common/docker/run.sh lhcb/daq40-sw-dkms-cc7 \
 -v=${TOP}:/usr/src/lhcb-daq40-software \
 -v=${TOP}/rpmbuild/el7:/rpm \
 -u=root
cc7-dt7

The container is tagged as lhcb/daq40-sw-rpm-cc7-dt7 and can be run using ./rpm/os/slc6/run.sh from the top-level folder. This is equivalent to ( TOP is the top level of the git repository)

${TOP}/common/docker/run.sh lhcb/daq40-sw-rpm-cc7-dt7 \
 -v=${TOP}:${HOME}/rpmbuild/SOURCES/lhcb-daq40-software \
 -v=${TOP}/rpmbuild/el7:${HOME}/rpmbuild/RPMS
el9

The container is tagged as lhcb/daq40-sw-rpm-el9 and can be run using ./rpm/os/slc6/run.sh from the top-level folder. This is equivalent to ( TOP is the top level of the git repository)

${TOP}/common/docker/run.sh lhcb/daq40-sw-rpm-el9 \
 -v=${TOP}:${HOME}/rpmbuild/SOURCES/lhcb-daq40-software \
 -v=${TOP}/rpmbuild/el9:${HOME}/rpmbuild/RPMS
el9-dkms

The container is tagged as lhcb/daq40-sw-dkms-el9 and can be run using ./rpm/os/el9-dkms/run.sh from the top-level folder. This is equivalent to ( TOP is the top level of the git repository)

${TOP}/common/docker/run.sh lhcb/daq40-sw-dkms-el9 \
 -v=${TOP}:/root/rpmbuild/SOURCES/lhcb-daq40-software \
 -v=${TOP}/rpmbuild/el9:/root/rpmbuild/RPMS \
 -u=root
slc6

The container is tagged as lhcb/daq40-sw-rpm-slc6 and can be run using ./rpm/os/slc6/run.sh from the top-level folder. This is equivalent to ( TOP is the top level of the git repository)

${TOP}/common/docker/run.sh lhcb/daq40-sw-rpm-slc6 \
 -v=${TOP}:${HOME}/rpmbuild/SOURCES/lhcb-daq40-software \
 -v=${TOP}/rpmbuild/el6:${HOME}/rpmbuild/RPMS
slc6-dkms

The container is tagged as lhcb/daq40-sw-dkms-slc6 and can be run using ./rpm/os/slc6-dkms/run.sh from the top-level folder. This is equivalent to ( TOP is the top level of the git repository)

${TOP}/common/docker/run.sh lhcb/daq40-sw-dkms-slc6 \
 -v=${TOP}:/usr/src/lhcb-daq40-software \
 -v=${TOP}/rpmbuild/el6:/rpm \
 -u=root

Inside any of these containers you can run rpmbuild or dkms.sh as required for each package. When using rpmbuild , the resulting RPM will be written under the rpmbuild/ subdirectory of the top-level source folder, which must be created if it does not exist.

Packaging

Using the provided spec files, RPM packages can be built by simply executing rpmbuild -ba file.spec. Regular packages assume that the source code top-level directory is ~/rpmbuild/SOURCES/lhcb-daq40-software while DKMS packages use /usr/src/lhcb-daq40-software (using Running the container these pre-requirements are automatically satisfied).

rpm/lhcb-daq40-common.spec

This package provides tools and libraries common to both AMC40 and PCIe40 setups. These include:

libdaq40
libdaq40_jtag
daq40_frgchecker
daq40_frgreader
daq40_frgreader_v0
daq40_frg2mdf
daq40_mdfchecker
daq40_mdfreader
daq40_mdfvalidator
daq40_jtag
daq40_ramdisk
daq40_txtparser
daq40_sysinfo
daq40_xmlgen
daq40_writespeed
rpm/lhcb-daq40-jtag.spec

This package provides the proprietary tools used to configure PCIe40 and AMC40 FPGA devices via JTAG.

rpm/lhcb-amc40-tools.spec

This package provides tools and libraries specific to the AMC40 board. These include:

libamc40
amc40_ccsetup
amc40_net_setup_pc
amc40_capchecker
amc40_frgchecker
amc40_frgreader
amc40_frgwriter
amc40_frg2mdf
amc40_mdfchecker
amc40_mdfreader
amc40_mdfvalidator
amc40_pgm
amc40_udpgen
amc40_sysinfo
rpm/lhcb-pcie40-driver.spec

This package provides the PCIe40 driver (after DKMS removed RPM integration). These include:

jtagd
jtagconfig
quartus_pgm
quartus_pgmw
rpm/lhcb-pcie40-libs.spec

This package provides C libraries specific to the PCIe40 board and some debug tools that use them. These include:

libpcie40_id
libpcie40_ecs
libpcie40_i2c
libpcie40_daq
libpcie40_hwloc
libpcie40_localeb
pcie40_id
pcie40_ecs
pcie40_i2c
pcie40_daq
pcie40_hwloc
pcie40_localeb
rpm/lhcb-pcie40-tools.spec

This package provides readout tools specific to the PCIe40 board. These include:

libpcie40
pcie40_daqserver
pcie40_frgchecker
pcie40_frgreader
pcie40_frg2mdf
pcie40_mdfchecker
pcie40_mdfreader
pcie40_mdfvalidator
pcie40_localebserver
pcie40_mfpreader
pcie40_pgm
pcie40_pgmserver
pcie40_reload
pcie40_rfg
pcie40_simchecker
pcie40_simreader
pcie40_sim2frg
pcie40_sofusercode
pcie40_sysinfo
pcie40_systemd
rpm/lhcb-daq40-doc.spec

This package provides the HTML documentation for offline viewing.

The RPMs produced from this repository have dependencies between each other. In order to always ensure compatibility between them the spec files enforces each package and its dependents to always be at the same version.

DKMS

Building DKMS packages is not as straightforward as simply launching rpmbuild, therefore helper scripts are provided, they can be found at rpm/os/{slc6|cc7}-dkms/dkms.sh and are used, for example, in the CI pipeline.

The scripts take one parameter, which is the subfolder containing the kernel module source and the DKMS configuration. dkms.conf is effectively just a shell script and contains the following:

DAQ40_VERSION=`/usr/src/lhcb-daq40-software/rpm/getversion.sh`  (1)
DAQ40_RELEASE=`/usr/src/lhcb-daq40-software/rpm/getrelease.sh`  (2)
export DAQ40_RELEASE  (3)
DAQ40_VER_REL=${DAQ40_VERSION}-${DAQ40_RELEASE}
export DAQ40_VER_REL  (4)
DKMS_DIRECTIVE=DAQ40_VER_REL=${DAQ40_VER_REL}  (5)

PACKAGE_NAME=lhcb-pcie40-driver  (6)
PACKAGE_VERSION=${DAQ40_VERSION}-${DAQ40_RELEASE}  (7)
AUTOINSTALL=yes  (8)
BUILT_MODULE_NAME[0]=lhcb_pcie40  (9)
DEST_MODULE_LOCATION[0]=/extra
BUILT_MODULE_NAME[1]=lhcb_pcie40_emu  (10)
DEST_MODULE_LOCATION[1]=/extra
 (11)
1 Detect version from git tag.
2 Detect release from git tag.
3 DAQ40_RELEASE is NOT used by DKMS directly but it is read by the .spec template
4 Representation of version and release used in packages and executable code
5 Explicitly inject variable in DKMS build environment
6 Required by DKMS.
7 Required by DKMS. We include the release part so that each package will be stored under unique paths. Without it two RPMs differing only in release will store their data under the same directories and when upgrading the DKMS tree of the new RPM will be deleted by the uninstall step of the previous RPM.
8 Ensure the driver is recompiled when a new kernel is installed
9 Name of the device driver.
10 Name of the emulation driver.
11 DEST_MODULE_LOCATION is ignored by the rpm but dkms add fails without it.

The helper script proceeds as follows:

source /usr/src/lhcb-daq40-software/$1/dkms.conf  (1)
dkms add --verbose --rpm_safe_upgrade /usr/src/lhcb-daq40-software/$1  (2)
sed -i "s/DAQ40_VERSION=.*/DAQ40_VERSION=${DAQ40_VERSION}/" /usr/src/${PACKAGE_NAME}-${PACKAGE_VERSION}/dkms.conf  (3)
sed -i "s/DAQ40_RELEASE=.*/DAQ40_RELEASE=${DAQ40_RELEASE}/" /usr/src/${PACKAGE_NAME}-${PACKAGE_VERSION}/dkms.conf  (4)
find /usr/src/${PACKAGE_NAME}-${PACKAGE_VERSION} -mindepth 1 -type d | xargs rm -rf  (5)
dkms build --verbose -k `ls /lib/modules` -m ${PACKAGE_NAME} -v ${PACKAGE_VERSION}  (6)
ln -sf /var/lib/dkms/${PACKAGE_NAME}/${PACKAGE_VERSION}/ /var/lib/dkms/${PACKAGE_NAME}/${DAQ40_VERSION}  (7)
dkms mkrpm --verbose -k `ls /lib/modules` -m ${PACKAGE_NAME} -v ${DAQ40_VERSION}  (8)
rm /var/lib/dkms/${PACKAGE_NAME}/${DAQ40_VERSION}
mkdir -p /rpm/noarch
cp /var/lib/dkms/${PACKAGE_NAME}/${PACKAGE_VERSION}/rpm/*.noarch.rpm /rpm/noarch  (9)
1 This is just a shell script and can be sourced as such.
2 This will copy the source code under /usr/src/<module-moduleversion> where DKMS expects it.
3 Remove reference to version detection command (which is not present on the host system when rebuilding the module).
4 Likewise, remove reference to release detection command and replace it with its value
5 Remove subdirectories (because in our case they contain things like historical driver performance logs which make no sense to distribute in the target machine)
6 Compile the module
7 Create symbolic link since mkrpm does not accept - characters in ${PACKAGE_VERSION}
8 Create rpm under /var/lib/dkms/…​
9 Copy to usual place (note that DKMS packages are noarch since they still contain the source and will automatically recompile the pre-built binary for the target architecture and kernel)

Versioning

This repository follows semantic versioning, meaning that versions are identified by a monotonically increasing tuple of three elements (major, minor, patch).

From the current git head, the scripts under rpm/getversion.sh and rpm/getrelease.sh are used to dynamically obtain the version and release identifiers. These identifiers are used, for example, to automatically generate RPM packages.

version

If the git head is tagged, and the tag starts with v, then the version identifier will be everything in the tag name after the initial v. This identifier must follow semantic versioning order (for example, the first minor release after v1.2.3 must be v1.3.0 ).

release

The release identifier is used, together with the version identifier, to define the package name used to publish a particular commit. Releases are identified by an integer, starting at 1 for a tag release (as specified at https://fedoraproject.org/wiki/Packaging:Versioning) and automatically increased by one for any untagged commit on the master branch.

  • Tagged commits are published to the stable RPM repository.

  • Untagged commits to the master branch are published to the unstable RPM repository.

  • Untagged commits in other branches can be published manually through GitLab CI by triggering the publish-unstable job. In this case the branch name and git hash are appended to the automatic release identifier.

Releasing

To create an unstable release it is sufficient to tag the desired commit on the devel branch according to the format described above. In git, tags need to be pushed with a dedicated command:

git push --tags

On the other hand, commits for stable releases (on the master branch) are either one of two types:

  1. Merge commits from the devel branch

  2. Urgent bugfixes on top of such a merge commit that could not be readily merged from the devel branch

In the first case, squashing and fast-forwarding commits should be avoided in order to keep track of the development leading to a particular release. The following command can be used to always create a merge commit:

git merge --no-ff devel

Continuous Integration

A GitLab Continuous Integration pipeline is implemented in order to automatically and reproducibly build and publish each software release to a dedicated RPM repository.

The pipeline implements the following stages:

diag 6723b525b4fa111d1633f61da0aa02ab
  • build (on very commit)

  • package (only commits in devel and master branches)

  • install (as above)

  • publish (only semver tags by default, manual trigger available)

  • deploy (manual trigger only)

Each stage includes several parallel tasks. CI tasks can, to some extent, be tested locally, by installing gitlab-runner and docker and doing:

gitlab-runner exec docker <taskname>

The following describes each stage in detail.

Build

The tasks in the build stage compile the entire source code for different operating systems and with different flag combinations.

make-cc7-dim

Build for CC7 with the following flags:

ENABLE_DIM: 'true'
ENABLE_MON: 'false'
ENABLE_HWLOC: 'false'
make-cc7-disableall

Build for CC7 with the following flags:

ENABLE_DIM: 'false'
ENABLE_MON: 'false'
ENABLE_HWLOC: 'false'
make-cc7-driver

Build kernel drivers under CC7.

make-cc7-dt7

Build for CC7 using the devtoolset-7 compiler with the following flags:

ENABLE_DIM: 'true'
ENABLE_MON: 'true'
ENABLE_HWLOC: 'true'
make-cc7-hwloc

Build for CC7 with the following flags:

ENABLE_DIM: 'false'
ENABLE_MON: 'false'
ENABLE_HWLOC: 'true'
make-cc7-mon

Build for CC7 with the following flags:

ENABLE_DIM: 'false'
ENABLE_MON: 'true'
ENABLE_HWLOC: 'false'
make-doc

Build the documentation.

make-el9-dim

Build for EL9 with the following flags:

ENABLE_DIM: 'true'
ENABLE_MON: 'false'
ENABLE_HWLOC: 'false'
make-el9-disableall

Build for EL9 with the following flags:

ENABLE_DIM: 'false'
ENABLE_MON: 'false'
ENABLE_HWLOC: 'false'
make-el9-driver

Build kernel drivers under EL9.

make-el9-hwloc

Build for EL9 with the following flags:

ENABLE_DIM: 'false'
ENABLE_MON: 'false'
ENABLE_HWLOC: 'true'
make-el9-mon

Build for EL9 with the following flags:

ENABLE_DIM: 'false'
ENABLE_MON: 'true'
ENABLE_HWLOC: 'false'
make-slc6-dim

Build for SLC6 with the following flags:

ENABLE_DIM: 'true'
ENABLE_MON: 'false'
ENABLE_HWLOC: 'false'
make-slc6-disableall

Build for SLC6 with the following flags:

ENABLE_DIM: 'false'
ENABLE_MON: 'false'
ENABLE_HWLOC: 'false'
make-slc6-driver

Build kernel drivers under SLC6.

make-slc6-hwloc

Build for SLC6 with the following flags:

ENABLE_DIM: 'false'
ENABLE_MON: 'false'
ENABLE_HWLOC: 'true'
make-slc6-mon

Build for SLC6 with the following flags:

ENABLE_DIM: 'false'
ENABLE_MON: 'true'
ENABLE_HWLOC: 'false'

Package

The tasks in the package stage create the RPMs for all the supported operating systems.

dkms-cc7

Builds the lhcb-pcie40-driver package for CC7.

dkms-el9

Builds the lhcb-pcie40-driver package for el9.

dkms-slc6

Builds the lhcb-pcie40-driver package for SLC6.

rpm-cc7-amc40

Builds the lhcb-amc40-tools package for CC7.

rpm-cc7-daq40

Builds the lhcb-daq40-common and lhcb-daq40-wincc packages for CC7.

rpm-cc7-pcie40

Builds the lhcb-pcie40-libs, lhcb-pcie40-tools and lhcb-pcie40-wincc packages for CC7.

rpm-daq40-doc

Builds the lhcb-daq40-doc package.

rpm-el9-amc40

Builds the lhcb-amc40-tools package for el9.

rpm-el9-daq40

Builds the lhcb-daq40-common and lhcb-daq40-wincc packages for EL9.

rpm-el9-pcie40

Builds the lhcb-pcie40-libs, lhcb-pcie40-tools and lhcb-pcie40-wincc packages for EL9.

rpm-slc6-amc40

Builds the lhcb-amc40-tools package for SLC6.

rpm-slc6-ccpc40

Builds the lhcb-ccpc40-tools package for SLC6.

rpm-slc6-daq40

Builds the lhcb-daq40-common package for SLC6.

rpm-slc6-pcie40

Builds the lhcb-pcie40-libs and lhcb-pcie40-tools package for SLC6.

Install

The tasks in the install stage ensure that the packages created in the previous stage install cleanly.

install-cc7

Installs all the CC7 packages built during the previous stage.

install-el9

Installs all the EL9 packages built during the previous stage.

install-slc6

Installs all the SLC6 packages built during the previous stage.

Publish

The tasks in the publish stage take all the artifacts produced by the previous stages and make them available to other users and developers.

publish-branch

This task can be triggered by hand to publish an unstable release from any (non-master) branch. The branch name will automatically become part of the RPM release field.

publish-docs

This task copies the HTML documentation from the last commit on the devel branch onto a dedicated folder. This ensures that the documentation at https://cern.ch/lhcb-online-soft/doc/daq/devel/ always reflects the current state of the development branch and that it can quickly be updated by a simple push, without having to tag a new release.

publish-unstable

This task only applies to unstable releases (See Versioning). First it updates the unstable repositories described in the User Guide. Then it copies the corresponding documentation under https://cern.ch/lhcb-online-soft/doc/daq/<tag> and updates https://cern.ch/lhcb-online-soft/doc/daq/latest-unstable/ to always point to the most recent development release.

High-level code walkthrough

This section describes how different parts of the high-level (meaning C++) codebase interact together in order to implement a given piece of functionality.

Common code

In order to reuse as much code as possible between AMC40 and PCIe40 implementation, a common set of base classes is provided which are then specialized for the different readout boards.

Parsing configuration files

Configuration files follow an INI-like syntax. The parser is implemented in the [dcrIni_] class. This class is extended in [dcrdaq40_cfg_] to add all the configuration parameters supported by DAQ40 software tools and documented in the User Guide. A system typically uses only one global configuration file, daq40.cfg. Starting from the firmware parameters read from a configuration file, daq40_cfg::detect_data_format is responsible for detecting the actual data format and chose the most appropriate function to use when parsing frontend data (either in binary or textual format).

Parsing simulated frontend data

When running a firmware simulation, it is possible to generate text files containing a cycle-by-cycle representation of the data that was generated by the frontend generators on each optical link. The class daq40_txt_flex can be used to parse these text files. For each supported format, a scanner was implemented using GNU Flex. The following are available:

parse_ff_lsb

Scanner for FF data in LSB order (LSB is deprecated in the firmware and should not be used).

parse_ff_msb

Scanner for FF data in MSB order.

parse_fv_lsb

Scanner for FV data in LSB order (LSB is deprecated in the firmware and should not be used).

parse_fv_msb

Scanner for FV data in MSB order.

parse_muon_msb

Scanner for MUON data in MSB order.

parse_scifi_msb

Scanner for SciFi data in FF format.

parse_scifi_msb

Scanner for SciFi channels in MSB order (SciFi data is aligned on 28-bit boundaries).

parse_velo_msb

Scanner for VELO front-end data, each line is 124 bits and contains 4 SuperPixelPackets (SPP).

Parsing fragment data

A readout board receives data from several Front-End optical links. Front-End data corresponding to the same collision (meaning, data having the same BXID), is assembled by the readout board firmware into a fragment (fragments are in turn assembled by the event builder into complete events). So, a fragment contains collision data seen by an individual readout board, an event contains collision data from all LHCb subdetectors (and subdetector-like data sources). The DAQ40 software includes decoders able to locate within a single fragment the data originating from a given Front-End optical link, these are mostly used for validating the firmware. Two functions are available depending on the data ordering used in the firmware: [dcrdaq40_msb_parse_fragment_] for MSB data (preferred) and [dcrdaq40_lsb_parse_fragment_] for LSB data. Internally, the binary data generated by the firmware is densely packed and impossible to easily parse in software. Two utility classes are implemented in order to access individual bits and bit ranges within a fragment: [dcrreversed_bitbuffer_] for LSB data and [dcrbitbuffer_] for MSB data.

These classes are only used to validate the default data processing done by the FPGA! In reality each subdetector should, inside the FPGA, rearchitect its data layout to emit fragments in a format that is practical to decode by software.

Data buffering

Both in the case of an AMC40 board and a PCIe40 board (and in data acquisition in general), the process of receiving data from the board is decoupled from the process of consuming this data. In the DAQ40 software, this means that those two functions execute in independent threads. In order to synchronize data movement in a producer/consumer pair there exists a generic daq40_blockbuffer class.

Consuming data streams

Data streams can be produced using either an AMC40 or a PCIe40 board. The DAQ protocols are radically different depending on the hardware used and no common classes exist to encapsulate data producers. On the opposite end, all data consumer threads do have to conform to a set of common interfaces, the first one being [dcrdaq40_consumer_] . Consumers that use the received data to create files on the local disk implement a specialized version of this interface, called [dcrdaq40_writer_] . When DIM is enabled, each consumer is associated with an instance of [dcrdaq40_consumer_dim_] (or [dcrdaq40_writer_dim_] depending on the case) in order to interface with the control system.

Monitoring data streams

As a data stream is being consumed (either to be sent to the event builder network or to be stored locally), it can also, optionally, be sampled for the purpose of collecting real-time information about data quality. The generic interface between a data stream and the monitoring system is represented by [dcrdaq40_monitor_] . At this time, a single concrete implementation of this interface exists, [dcrdaq40_monitor_dqmp_] .

Writing data files

Currently, the primary usage of the DAQ40 software is to store the data captured by a given readout board onto a local file for later analysis. The files currently generated have an .frg extension and contain the data fragments as they were captured from the readout board (see Frg data format). The common base class for .frg file writers is daq40_frgwriter .

Frg data format

An .frg file starts with a header, defined by the struct [dcrdaq40_file_hdr_top_] , and continues with a list of data blocks. Each data block (often misleadingly referred to as a "MEP block") also starts with a header, defined by the struct [dcrdaq40_file_hdr_block_] and followed by a list of data fragments. In turn, each fragment also has a header, defined by the struct daq40_file_hdr_frg . Each fragment is associated with a temporally unique event id assigned by the TFC subsystem. The fragment header is followed by all the data, corresponding to that event id, received from all the optical input links associated with a given data stream (in the case of the AMC40, this means all the active fibers on the board, in the case of the PCIe40, this means the fibers going to PCIe link 0 or link 1).

Reading data files

The DAQ40 software includes a command-line tool and a C++ library that can be used to read back the .frg data files produced using either AMC40 or PCIe40 setups. The main class implementing this functionality is daq40_frgreader . A typical example using this class to iterate a file would begin like this:

daq40_frgreader rdr(argv[1]);  (1)
if (!rdr.open()) {  (2)
  perror("Unable to open file");
1 Instantiate the reader
2 Open the file

Then iterate:

for (daq40_frgreader::block_iterator m = rdr.begin(); m != rdr.end(); ++m) {  (1)
  const daq40_file_hdr_block &block_hdr = m.hdr();  (2)
  if (block_hdr.frags) {
    for (daq40_frgreader::frg_iterator f = m.begin(); f != m.end(); ++f) {  (3)
      daq40_file_hdr_frg const *frg_hdr = f.hdr();  (4)

PCIe40-specific code

Configuration

The [dcrpcie40_cfg_] class specializes [dcrdaq40_cfg_] to implement parameters that are specific to the PCIe40.

DMA streams

Data produced by a given DMA stream is in turn consumed by an instance of [dcrpcie40_frgwriter_] . When DIM support is enabled, each writer is associated with a pcie40_frgwriter_dim instance to be accessible through the control system. Data movement from producers to consumers is always synchronized through a dedicated subclass of [dcrpcie40_blockbuffer_] .

DMA interface

A single PCIe40 board contains two PCIe interfaces. Each interface is represented by a [dcrpcie40_interface_dim_] instance. All PCIe40 interfaces within a single readout unit are enumerated inside one common [dcrpcie40_daqserver_dim_] instance.

AMC40-specific code

Configuration

The [dcramc40_cfg_] class specializes [dcrdaq40_cfg_] to implement parameters that are specific to the AMC40. All parameters are described in the User Guide.

Low-level code walkthrough

This section describes the low-level C API that high-level code uses to interface with the operating system (in particular with the readout board kernel drivers).

PCIe40-specific code

Low-level ECS driver API

In order to interact with the ECS interface of the PCIe40 driver, C libraries are provided (libpcie40_ecs.so and libpcie40_ecs.a). To use these libraries, add #include <lhcb/pcie40/ecs.h> to your source code and -lpcie40_ecs to your linker flags. This API is very simple, consisting only of a handful of functions to create I/O memory mappings and to access individual I/O registers, see PCIe40 driver ECS API.

Low-level DAQ driver API

In order to interact with the DAQ interface of the PCIe40 driver, C libraries are provided (libpcie40_daq.so or libpcie40_daq.a). To use these libraries, add #include <lhcb/pcie40/daq.h> to your source code and -lpcie40_daq to your linker flags. The full API is listed at PCIe40 driver DAQ API. Functions starting in p40_ctrl_ access the stream controller on the FPGA (there is one such controller per PCIe link), while functions starting in p40_stream_ access an individual stream (there can be more streams instantiated on the FPGA inside a given controller). The most important use of this API is to consume a data stream produced by the FPGA, the main steps in this case are:

pcie40 daq api

The above diagram reflects how the library is used internally by [dcrpcie40_stream_dma_] , [dcrpcie40_blockbuffer_] and pcie40_frgwriter .

Kernel-level code walkthrough

This section describes how kernel driver code is organized.

PCIe40-specific code

Device driver

Device initialization

When the kernel module is loaded, pcie40_init is immediately called. Its dual at unload time is pcie40_exit . The actual PCI device management happens in pcie40_probe and pcie40_remove .

ECS interface

The ECS submodule implements the system calls behind the BAR0 and BAR2 device files. The submodule is initialized and uninitialized at the same time as the main module, through the pcie40_ecs_init and pcie40_ecs_exit functions. Likewise the ECS-specific probing logic is encapsulated in pcie40_ecs_probe and pcie40_ecs_remove .

AMC40-specific code

There exists a kernel driver that is used to interface the AMC40 FPGA with the control system components running on the Credit-Card PC (CCPC), this is however a separate project maintained by CPPM (Centre de physique des particules de Marseille) and is not documented here, the source code is available in this repository.

High-level code cross-reference

daq40_blockbuffer

Generic implementation of thread-safe producer/consumer pair backed by an array of fixed-size blocks.

This class is specialized depending on the board, and for a given board, also depending on the type of data it handles.

daq40 blockbuffer

The AMC40 board has a single specialization for holding UDP packets, [dcramc40_blockbuffer_udp_] . The PCIe40 needs two: one ( [dcrpcie40_blockbuffer_nometa_] ) is used when metadata acceleration is disabled, in this case some "pseudo-metadata" is generated internally by the software, a second one ( pcie40_blockbuffer_meta ) is instead used when metadata acceleration is enabled, in this case the instance does not allocate any memory blocks itself but it merely manages the DMA circular buffer used by the FPGA (and allocated by the kernel driver).


daq40_cfg

Generic configuration file parameters shared between AMC40 and PCIe40 tools.


daq40_cfg::detect_data_format

Detects the LHCb firmware data format from the parameters under the key section of the configuration file.

daq40_format daq40_cfg::detect_data_format(const char *key);

Returnsdaq40_format

If the given configuration key does not specify a size for the BXID or the info bits then no decoding is possible and the data will be interpreted as just a RAW stream of bits. Otherwise, if the configuration specifies a subdetector-specific format (as is the case for the UT and the SciFi), then that is returned. If not, one of the generic formats is returned. If fe_datalen_bits is 0, there is no length field, therefore all frames are of fixed size and FORMAT_FF is returned. Lastly, if fe_datalen_always is true, the length is present even when there is no data, so the header is fixed in size even if the data length varies. In this case FORMAT_FV is returned. There exists a definition for a FORMAT_VV option (variable header size, variable data size) but this is not used by any subdetector.


daq40_consumer

Generic interface for a DAQ40 fragment stream consumer. This interface exposes methods to monitor the amount of data having been consumed during the current run, it is implemented by AMC40 and PCIe40 streams according to the different DAQ protocols.


daq40_consumer_dim

Optional DIM publisher associated with a daq40_consumer .


daq40_file_hdr_block

Header describing a block of fragments in an .frg file.


daq40_file_hdr_frg

On-disk representation of a data fragment header received from either an AMC40 or PCIe40 board (see daq40_frg_hdr ).

typedef struct daq40_frg_hdr daq40_file_hdr_frg;

daq40_file_hdr_mrg

Top-level header identifying a .mrg file produced by daq40_merge.


daq40_file_hdr_top

Top-level header identifying a .frg file produced by amc40_frgwriter or pcie40_daqserver.

Header for a file produced by amc40_frgwriter or pcie40_daqserver


daq40_format

The following formats are possible:

enum daq40_format
FORMAT_RAW_GBT,
FORMAT_RAW_WIDE,
FORMAT_RAW_GWT,
FORMAT_FF,
FORMAT_FV,
FORMAT_VV,
FORMAT_UT_4x3,
FORMAT_UT_2x3,
FORMAT_UT_2x4,
FORMAT_UT_2x5,
FORMAT_SCIFI_FF,
FORMAT_SCIFI_FV,
FORMAT_CALO,
FORMAT_RICH,
FORMAT_MUON,
FORMAT_VELO,
FORMAT_UNKNOWN

daq40_frg_hdr

Generic header for a data fragment produced by the TELL40 firmware data processing block.


daq40_frg_hdr::BIT_STEP_RUN

BIT_STEP_RUN  = 18

As of april 2018, bit 18 determines whether we are in "step run" mode, in that case the gdl is restricted to bits [11..0] and bits [17..12] are used for the step number (only applies to old data format).


daq40_frg_hdr::BIT_TRUNCATED

BIT_TRUNCATED = 19,

As of october 2017, msb of the size determines whether the fragment is truncated (only applies to old data format).


daq40_frgreader

File reader interface for .frg files produced by either AMC40 or PCIe40 setups.

This class can be used to open an .frg file and iterate over its contents.

daq40 frgreader

Two iterator classes are provided for this purpose. Since temporally consecutive fragments are aggregated into blocks, [dcrdaq40_frgreader_block_iterator_] iterates over the blocks themselves. Within a given block [dcrdaq40_frgreader_frg_iterator_] can be used to iterate from one fragment to the next.


daq40_frgreader::block_iterator

Sequential iterator to access fragment blocks within an .frg file.


daq40_frgreader::frg_iterator

Sequential iterator to access individual fragments within a fragment block.


daq40_frgwriter

Base class for both AMC40 and PCIe40 .frg file writers, implements the daq40_writer interface.


daq40_frgwriter::fsm_start

Opens a new output and spawns internal thread that writes to it.


daq40_frgwriter::fsm_stop

Closes the file and waits for the thread to exit.


daq40_frgwriter::set_run_number

Set the current run number before opening a new file


daq40_frgwriter_dim

Optional DIM publisher associated with a given daq40_frgwriter .


daq40_fsm

Base class for any FSM, compliant with the LHCb control system structure.


daq40_fsm::fsm_state

Encoding of FSM states.

enum fsm_state {
  FSM_ERROR = -100,
  FSM_NOT_READY = 0,
  FSM_READY = 100,
  FSM_RUNNING = 200
};

daq40_fsm_dim

Extends a daq40_fsm to make it controllable over DIM.


daq40_fsm_handler

Interface to handle FSM state transitions. An instance can be passed to the daq40_fsm constructor to be registered as its FSM handler for all state transitions.


daq40_lsb_parse_fragment

Parse binary data containing an assembled fragment (in LSB order) and optionally compare it against text files representing Front-End data.

void daq40_lsb_parse_fragment(daq40_cfg &cfg, uint16_t frg_bxid, size_t frg_gdl, reversed_bitbuffer &buffer, daq40_txt_flex::map &txt, int &fail_counter);
cfg (I)

global configuration

frg_bxid (I)

BXID of fragment (compared against the BXID in each fiber, if not stripped)

buffer (IO)

reference to reversed_bitbuffer containing the LSB data payload (after the global fragment header, which is not included here)

txt (IO)

optional pointer to array of daq40_txt_flex objects (one for each optical Front-End link)

fail_counter (IO)

Error counter used by the caller to track validation state (if txt is given)


daq40_monitor

Interface to monitor a given stream.

This class is derived from daq40_fsm so that any implementation must also provide logic for the different possible FSM transitions. This allows the monitoring process to be synchronized (through the control system) with the lifecycle of the stream that is being monitored.


daq40_monitor_dqmp

Implementation of daq40_monitor interface for DQMP.

This class exposes a shared-memory interface between a given data stream (coming from either an AMC40 or a PCIe40 readout board) and the DQMP (Data Quality Monitor and Presenter) software.


daq40_msb_parse_fragment

Parse binary data containing an assembled fragment (in MSB order) and optionally compare it against text files representing Front-End data.

void daq40_msb_parse_fragment(daq40_cfg &cfg, uint16_t frg_bxid, uint8_t frg_type, size_t frg_gdl, bitbuffer &buffer, daq40_txt_flex::map &txt, int &fail_counter);

daq40_msb_parse_fragment_scifi

void daq40_msb_parse_fragment_scifi(daq40_cfg &cfg, uint16_t frg_bxid, uint8_t frg_type, size_t frg_gdl, bitbuffer &buf, daq40_txt_flex::map &txt, int &fail_counter);

daq40_msb_parse_fragment_velo

Parse binary fragments containing VELO SPPs and compare against velo SPPs from front-end input files. SPPs in front-end data are grouped by link and sorted in time.

void daq40_msb_parse_fragment_velo(daq40_cfg &cfg, uint16_t frg_bxid, uint8_t frg_type, size_t frg_gdl, bitbuffer &buf, daq40_txt_velo::chipmap &txt, int &fail_counter);

daq40_txt_flex

Configurable parser for ModelSim-generated frontend data text files.


daq40_txt_flex::TOK_SYNC

A SYNC frame (including its BXID).


daq40_txt_flex::scan

Parse the input text according to the front-end format in the configuration.

daq40_txt_flex::token_type daq40_txt_flex::scan();

Returns[dcrdaq40_txt_flex_token_type_] in particular [dcrdaq40_txt_flex_TOK_INVALID_] in case of error and daq40_txt_flex::TOK_EOT at the end of the text file.


daq40_txt_flex::seek

Seek inside a text stream looking for a particular BXID.

daq40_txt_flex::token_type daq40_txt_flex::seek(uint16_t bxid);

Returns → The first [dcrdaq40_txt_flex_token_type_] for the collision with the given BXID, or [dcrdaq40_txt_flex_TOK_EOT_] if the file ended without finding it.


daq40_txt_flex::skip

Skip a given number of BXIDs.

daq40_txt_flex::token_type daq40_txt_flex::skip(size_t bxids);
bxids (I)

Number of collisions to skip in the data stream.

Returns → The first daq40_txt_flex::token_type for the first collision after the skipped range.


daq40_txt_flex::token_type

The type of tokens produced by the lexer.


daq40_writer

Base class for all DAQ file writers. NOTE: Only the PCIe40 uses this atm, the AMC40 code must still be ported.

This class extends [dcrdaq40_consumer_] adding methods to set where data is to be written and how much data should be written at most during a given run (so as to prevent a runaway process from completely filling the local storage). This class also inherits [dcrdaq40_fsm_] , this forces all implementers to implement FSM transitions in order to be driven by the control system.


daq40_writer::get_mode

daq40_cfg::out_mode get_mode()

daq40_writer::get_open_path

const std::string &get_open_path()

daq40_writer::get_path

const std::string &get_path()

daq40_writer::set_max_mbytes

Set maximum size of output file

bool set_max_mbytes(int max_mbytes);

daq40_writer::set_mode

Set the writer mode and parameters

bool set_mode(daq40_cfg::out_mode mode, const std::string &tcp_host, uint16_t tcp_port);
tcp_host (I)

Host to connect to (only used in TCP mode)

tcp_port (I)

Port number to connect to (only used in TCP mode)


daq40_writer::set_path

Set the full path of the output file

bool set_path(const std::string &path);

daq40_writer_dim

Optional DIM publisher associated with a daq40_writer .


Ini

Customized parser for INI-like configuration files.


daq_mmap_common

static int daq_mmap_common(struct pcie40_dma_stream *stream, struct vm_area_struct *vma);

g_block_frags

number of fragments in last block


pcie40_blockbuffer

Specialization of daq40_blockbuffer associated with a PCIe40 stream.

pcie40 blockbuffer


pcie40_daqserver_dim

DIM publisher for the top-level server FSM.

This class enumerates all pcie40_interface_dim within a readout unit and publishes DIM commands and services that can be used to monitor and control them.

pcie40 daqserver dim


pcie40_daqserver_ifx

Influx Line Protocol publisher for the top-level server state.


pcie40_frg_block

Pseudo-metadata block created internally by server when FPGA metadata is not available.


pcie40_frg_hdr

Representation of a fragment header emitted by the PCIe40 board when metadata acceleration is disabled.

struct __attribute__((__packed__)) pcie40_frg_hdr

pcie40_interface_dim

DIM publisher for a given PCIe40 interface. Each PCIe40 board implements up to two PCIe interfaces.

This class contains up to three data producers (one for the main data stream, one for the metadata and one for odin banks if implemented in the FPGA) and up to two data consumers (one for writing out .frg files for with the DAQ data and one, optional, to do the same for SODIN data).

pcie40 interface dim


pcie40_stream_dma

Encapsulates a single DMA stream from a PCIe40.


Low-level C API cross-reference

p40_id_close

Destroy file descriptor returned by p40_id_open .

void p40_id_close(int fd);

p40_id_exists

Check whether there exists a device id endpoint with the given device number.

int p40_id_exists(int dev);

Returns → 0 if the endpoint exists and < 0 in case of error.


p40_id_find

The name string is compared against the name and serial number of each interface. In addition, the following rules are applied:

  • A name string in the form XXX:0 or XXX_0 matches the first interface with name XXX and link id 0

  • A name string in the form XXX:1 or XXX_1 matches the first interface with name XXX and link id 1

  • A name string in the form XX-XX-XX-XX-XX-XX-XX-XX:0 matches the interface with the given FPGA serial and link id 0

  • A name string in the form XX-XX-XX-XX-XX-XX-XX-XX:1 matches the interface with the given FPGA serial and link id 1

  • A name string in the form bus:device.function matches the PCIe interface with the given address

  • A name string in the form 0xABCD matches the interface with the given source id

Find lowest interface number with given name.

int p40_id_find(const char *name);

Returns → an interface number (>= 0) or < 0 if no such interface is present.


p40_id_find_all

If a name is given, all matching variations including those with :0, _0, :1 and _1 suffixes will be returned.

Find all interface numbers matching a given name.

uint32_t p40_id_find_all(const char *name);
name (I)

optional board name, if empty or null all existing interfaces are returned

Returns → A 32-bit mask with one bit set for each matched interface number.


p40_id_get_fpga

Get serial number of FPGA.

int64_t p40_id_get_fpga(int fd);

p40_id_get_leds

Get status of front panel LEDs for interface identification.


Identify the PCIe link connecting to this controller.

int64_t p40_id_get_link(int fd);

p40_id_get_name

Get user-defined name for this data source.

int p40_id_get_name(int fd, char *buffer, size_t size);
size (I)

Should be higher than 8, as the string consists of up to 8 ASCII bytes and a \0 terminator is always appended.


p40_id_get_name_unique

Normalize the name returned by the interface to always end with the local link id.

int p40_id_get_name_unique(int fd, char *buffer, size_t size);
size (I)

Buffer must hold at least 11 characters (8 for the name, 2 for the link suffix and a \0 terminator).


p40_id_get_regmap

Get version of PCIe register map instantiated on the FPGA.

int64_t p40_id_get_regmap(int fd);

p40_id_get_rwtest

Read test register.

int64_t p40_id_get_rwtest(int fd);

p40_id_get_source

Get globally unique source number and format version for this data source.

int p40_id_get_source(int fd);

See P40_ID_GET_SOURCE for how to interpret the return value.


p40_id_get_version

Get version of PCIe core instantiated on the FPGA.

int64_t p40_id_get_version(int fd);

p40_id_open

Create a file descriptor to access the device id endpoint with the given device number.

int p40_id_open(int dev);

p40_id_set_leds

Set front panel LEDs for interface identification.

int p40_id_set_leds(int fd, int leds);

p40_id_set_name

The following names are not allowed:

  • Longer than 8 characters

  • Starting with a digit

  • Containing colons or periods

  • Ending with either "_0" or "_1"

Assign user-defined name to this data source.

int p40_id_set_name(int fd, const char *buffer);
size (I)

Only the first 8 bytes of the string are stored.


p40_id_set_rwtest

Write test register.

int p40_id_set_rwtest(int fd, uint32_t val);

p40_id_set_source

Set globally unique source number for this data source.

int p40_id_set_source(int fd, uint16_t source_id);

p40_ecs_bar_size

Query driver for size of a given PCI BAR.

int p40_ecs_bar_size(int fd);
fd (I)

File descriptor returned by p40_ecs_open .


p40_ecs_close

Destroy file descriptor to an open ECS BAR.

void p40_ecs_close(int fd, uint32_t *regs);
fd (I)

File descriptor returned by p40_ecs_open .

regs (I)

Pointer populated by p40_ecs_open .


p40_ecs_open

Create file descriptor to access an ECS BAR.

int p40_ecs_open(int dev, int bar, uint32_t **regs);
dev (I)

Device identifier corresponding to the given board.

bar (I)

PCI BAR to access, valid values are 0 (for the user registers), 2 (for the low-level registers) or the range 8-72 (for reverse BARs, if enabled in the FPGA).

regs (O)

Address of a valid uint32_t * pointer. It is set by the function upon return.


p40_ecs_r32

Read register at given address.

uint32_t p40_ecs_r32(uint32_t volatile *regs, uint32_t addr);
regs (I)

Pointer populated by p40_ecs_open .

addr (I)

Address of register.


p40_ecs_rab_init

Initialize reverse BAR.

dev (I)

Device identifier corresponding to the given board.

bar (I)

Reverse BAR to setup, valid values are in the range 8-72 (depending on FPGA configuration).

size (I)

Number of bytes to allocate (up to 4MiB).


p40_ecs_w32

Write register at given address.

void p40_ecs_w32(uint32_t volatile *regs, uint32_t addr, uint32_t val);
regs (I)

Pointer populated by p40_ecs_open .

addr (I)

Address of register.

val (I)

32-bit value to write.


p40_ctrl_close

Destroy file descriptor returned by p40_ctrl_open .

void p40_ctrl_close(int fd);

p40_ctrl_exists

Check whether there exists a device controller with the given device number.

int p40_ctrl_exists(int dev);

Returns → 0 if the controller exists and < 0 in case of error.


p40_ctrl_get_main_gen_ctl

int32_t p40_ctrl_get_main_gen_ctl(int fd);

p40_ctrl_get_main_gen_fixed

Get fixed-pattern used by main data generator.

int64_t p40_ctrl_get_main_gen_fixed(int fd);

p40_ctrl_get_main_raw_mode

Get whether the main stream input is receiving raw data.

int p40_ctrl_get_main_raw_mode(int fd);

p40_ctrl_get_odin_gen_ctl

int32_t p40_ctrl_get_odin_gen_ctl(int fd);

p40_ctrl_get_pcie_proto

Get PCIe protocol currently negotiated on the given link.

int32_t p40_ctrl_get_pcie_proto(int fd);

p40_ctrl_get_reset_counters

Read state of counters reset bit.

int p40_ctrl_get_reset_counters(int fd);

p40_ctrl_get_reset_default

Read state of default reset bit.

int p40_ctrl_get_reset_default(int fd);

p40_ctrl_get_reset_flush

Read state of flush reset bit.

int p40_ctrl_get_reset_flush(int fd);

p40_ctrl_get_reset_logic

Read state of logic reset bit.

int p40_ctrl_get_reset_logic(int fd);

p40_ctrl_get_rx_err_total_events

int64_t p40_ctrl_get_rx_err_total_events(int fd, P40_DAQ_STREAM stream_id, RawBankType type);

p40_ctrl_get_rx_err_total_since_evid

int64_t p40_ctrl_get_rx_err_total_since_evid(int fd, P40_DAQ_STREAM stream_id, RawBankType type);

p40_ctrl_get_rx_last_evid

int64_t p40_ctrl_get_rx_last_evid(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_spill_buf_size

int p40_ctrl_get_spill_buf_size(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_spill_buf_used

int p40_ctrl_get_spill_buf_used(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_status

Read error register.

int p40_ctrl_get_status(int fd);
 v2276+
typedef enum {
  P40_DAQ_CTRL_STATUS_BIT_MAIN_THR_READY,  0
  P40_DAQ_CTRL_STATUS_BIT_MAIN_CDC_READY,  1
  P40_DAQ_CTRL_STATUS_BIT_MAIN_ERR_TXREADY,  2
  P40_DAQ_CTRL_STATUS_BIT_MAIN_ERR_SOPMISS,  3
  P40_DAQ_CTRL_STATUS_BIT_MAIN_ERR_SOPEXTR,  4
  P40_DAQ_CTRL_STATUS_BIT_MAIN_ERR_EOPEARL,  5
  P40_DAQ_CTRL_STATUS_BIT_MAIN_ERR_EOPLATE,  6
  P40_DAQ_CTRL_STATUS_BIT_MAIN_ERR_EVDJUMP,  7
  P40_DAQ_CTRL_STATUS_BIT_MAIN_ERR_EVIDDUP,  8
  P40_DAQ_CTRL_STATUS_BIT_MAIN_MUX_READY,  9
  P40_DAQ_CTRL_STATUS_BIT_MAIN_ORD_READY,  10
  P40_DAQ_CTRL_STATUS_BIT_MAIN_FLU_READY,  11
  P40_DAQ_CTRL_STATUS_BIT_MAIN_ERR_CDCJUMP  12
} P40_DAQ_CTRL_STATUS_BIT;

p40_ctrl_get_stream_sel

P40_DAQ_STREAM p40_ctrl_get_stream_sel(int fd);

p40_ctrl_get_throt_last_cycles

int64_t p40_ctrl_get_throt_last_cycles(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_throt_last_from_evid

int64_t p40_ctrl_get_throt_last_from_evid(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_throt_last_to_evid

int64_t p40_ctrl_get_throt_last_to_evid(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_throt_total_events

int64_t p40_ctrl_get_throt_total_events(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_throt_total_since_evid

int64_t p40_ctrl_get_throt_total_since_evid(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_trunc_last_cycles

int64_t p40_ctrl_get_trunc_last_cycles(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_trunc_last_from_evid

int64_t p40_ctrl_get_trunc_last_from_evid(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_trunc_last_to_evid

int64_t p40_ctrl_get_trunc_last_to_evid(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_trunc_thres

int p40_ctrl_get_trunc_thres(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_trunc_total_events

int64_t p40_ctrl_get_trunc_total_events(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_trunc_total_since_evid

int64_t p40_ctrl_get_trunc_total_since_evid(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_get_tx_last_evid

int64_t p40_ctrl_get_tx_last_evid(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_main_gen_disable

Disable the main data generator (resets the event id counter).

int p40_ctrl_main_gen_disable(int fd);

p40_ctrl_main_gen_enable

Enable the main data generator.

int p40_ctrl_main_gen_enable(int fd);

p40_ctrl_main_gen_enabled

Read whether the main stream data generator is enabled.

int p40_ctrl_main_gen_enabled(int fd);

p40_ctrl_main_gen_get_limit

Get rate limit of main generator.

int p40_ctrl_main_gen_get_limit(int fd);

p40_ctrl_main_gen_get_mode

Get main data generator setting. See P40_MAIN_GEN_MODE .

P40_MAIN_GEN_MODE p40_ctrl_main_gen_get_mode(int fd);

p40_ctrl_main_gen_running

Read whether the main stream data generator is running.

int p40_ctrl_main_gen_running(int fd);

p40_ctrl_main_gen_set_limit

Set rate limit of main generator.

int p40_ctrl_main_gen_set_limit(int fd, int percent);
percent (I)

Value between 0 (no limit) and 100 (no data).


p40_ctrl_main_gen_set_mode

Change main data generator setting. See P40_MAIN_GEN_MODE .

int p40_ctrl_main_gen_set_mode(int fd, P40_MAIN_GEN_MODE);

p40_ctrl_main_gen_start

Start the main data generator.

int p40_ctrl_main_gen_start(int fd);

p40_ctrl_main_gen_stop

Stop the main data generator.

int p40_ctrl_main_gen_stop(int fd);

p40_ctrl_odin_gen_disable

Disable the odin data generator (resets the event id counter).

int p40_ctrl_odin_gen_disable(int fd);

p40_ctrl_odin_gen_enable

Enable the odin data generator.

int p40_ctrl_odin_gen_enable(int fd);

p40_ctrl_odin_gen_enabled

Read whether the odin stream data generator is enabled.

int p40_ctrl_odin_gen_enabled(int fd);

p40_ctrl_odin_gen_get_limit

Get rate limit of odin generator.

int p40_ctrl_odin_gen_get_limit(int fd);

p40_ctrl_odin_gen_running

Read whether the odin stream data generator is running.

int p40_ctrl_odin_gen_running(int fd);

p40_ctrl_odin_gen_set_limit

Set rate limit of odin generator.

int p40_ctrl_odin_gen_set_limit(int fd, int percent);
percent (I)

Value between 0 (no limit) and 100 (no data).


p40_ctrl_odin_gen_start

Start the odin data generator.

int p40_ctrl_odin_gen_start(int fd);

p40_ctrl_odin_gen_stop

Stop the odin data generator.

int p40_ctrl_odin_gen_stop(int fd);

p40_ctrl_open

Create a file descriptor to access the device controller with the given device number.

int p40_ctrl_open(int dev);

See ctrl_open .


p40_ctrl_reset_counters

Reset monitoring counters on the controller.

int p40_ctrl_reset_counters(int fd);

p40_ctrl_reset_default

Reset controller to its default power-on state.

int p40_ctrl_reset_default(int fd);

p40_ctrl_reset_flush

Request the controller to flush the DMA buffers.

int p40_ctrl_reset_flush(int fd);

p40_ctrl_reset_logic

Reset the logic on the controller.

int p40_ctrl_reset_logic(int fd);

p40_ctrl_set_main_gen_fixed

Set fixed-pattern used by main data generator.

int p40_ctrl_set_main_gen_fixed(int fd, uint32_t pattern);

p40_ctrl_set_msi_bytes

int p40_ctrl_set_msi_bytes(int fd, uint32_t bytes);

p40_ctrl_set_stream_sel

int p40_ctrl_set_stream_sel(int fd, P40_DAQ_STREAM stream_id);

p40_ctrl_set_trunc_thres

int p40_ctrl_set_trunc_thres(int fd, P40_DAQ_STREAM stream_id, int thres);

p40_stream_close

Destroy file descriptor created by p40_stream_open .

void p40_stream_close(int fd, void *buffer);

See p40driver`dma_release` .


p40_stream_consume_host_bytes

TODO

int p40_stream_consume_host_bytes(int fd, uint32_t bytes);

This would only work if the ctrl is in MSI_MAIN or MSI_META mode!


p40_stream_disable

Disable the stream.

int p40_stream_disable(int fd);

p40_stream_disable_bit

Disable individual sub-streams.

int p40_stream_disable_mask(int fd, int mask);

p40_stream_enable

Enable the stream.

int p40_stream_enable(int fd);

p40_stream_enable_bit

Enable individual sub-streams.

int p40_stream_enable_mask(int fd, int mask);

p40_stream_enabled

Get whether the stream is enabled.

int p40_stream_enabled(int fd);

p40_stream_exists

Check whether the given controller implements a DMA stream.

int p40_stream_exists(int dev, P40_DAQ_STREAM stream_id);

Returns → 0 if the stream exists and < 0 in case of error.


p40_stream_free_host_buf_bytes

Free bytes from circular buffer in host memory.

ssize_t p40_stream_free_host_buf_bytes(int fd, size_t bytes);

p40_stream_get_dmabuf

Allocate a dmabuf-compatible file descriptor.

int p40_stream_get_dmabuf(int fd);

p40_stream_get_fpga_buf_bytes

Get size of DMA buffer on FPGA.

int p40_stream_get_fpga_buf_bytes(int fd);

p40_stream_get_fpga_buf_bytes_used

Get number of bytes currently used from the DMA buffer on the FPGA.

int p40_stream_get_fpga_buf_bytes_used(int fd);

p40_stream_get_fpga_buf_desc_bytes

Get size of each DMA descriptor region on FPGA.

int p40_stream_get_fpga_buf_desc_bytes(int fd);

p40_stream_get_fpga_buf_desc_fill_bytes

Get number of bytes used in DMA descriptor currently being filled.

int p40_stream_get_fpga_buf_desc_fill_bytes(int fd);

p40_stream_get_fpga_buf_descs

Get number of DMA descriptor regions on FPGA.

int p40_stream_get_fpga_buf_descs(int fd);

p40_stream_get_fpga_buf_descs_busy

Get bimask of DMA descriptors in BUSY state.

int64_t p40_stream_get_fpga_buf_descs_busy(int fd);

p40_stream_get_fpga_buf_descs_fill

Get bitmask of DMA descriptors in FILL state.

int64_t p40_stream_get_fpga_buf_descs_fill(int fd);

p40_stream_get_host_buf_bytes

Get size of DMA buffer in host memory.

int64_t p40_stream_get_host_buf_bytes(int fd);

p40_stream_get_host_buf_bytes_used

Get number of bytes currently used from DMA buffer in host memory.

int64_t p40_stream_get_host_buf_bytes_used(int fd);

p40_stream_get_host_buf_read_off

Get read offset within DMA buffer in host memory.

int64_t p40_stream_get_host_buf_read_off(int fd);

p40_stream_get_host_buf_write_off

Get write offset within DMA buffer in host memory.

int64_t p40_stream_get_host_buf_write_off(int fd);

p40_stream_get_locker

Return PID of the process currently locking the stream, if any, or 0 if none.

pid_t p40_stream_get_locker(int fd);

p40_stream_get_reset_default

Read state of default reset bit.

int p40_stream_get_reset_default(int fd);

p40_stream_get_reset_flush

Read state of flush reset bit.

int p40_stream_get_reset_flush(int fd);

p40_stream_get_reset_logic

Read state of logic reset bit.

int p40_stream_get_reset_logic(int fd);

p40_stream_get_status

Get stream status flags.

int64_t p40_stream_get_status(int fd, int sel);
 v2276+
typedef enum {
  P40_DAQ_STREAM_STATUS_BIT_SNK_READY,
  P40_DAQ_STREAM_STATUS_BIT_HOST_MAP_BOOTSTRAP,
  P40_DAQ_STREAM_STATUS_BIT_DMA_WR_RX_READY,
  P40_DAQ_STREAM_STATUS_BIT_SOP_SEEN,
  P40_DAQ_STREAM_STATUS_BIT_EOP_SEEN,
  P40_DAQ_STREAM_STATUS_BIT_EOP_SEND,
  P40_DAQ_STREAM_STATUS_BIT_EOP_SENT,
  P40_DAQ_STREAM_STATUS_BIT_EOP_WAIT,
  P40_DAQ_STREAM_STATUS_BIT_EOP_ACK,
  P40_DAQ_STREAM_STATUS_BIT_EOP_ACKED,
  P40_DAQ_STREAM_STATUS_BIT_EOH_SEEN,
  P40_DAQ_STREAM_STATUS_BIT_SNK_PAD_ALIGNING,
  P40_DAQ_STREAM_STATUS_BIT_SNK_PAD_ALIGNED,
  P40_DAQ_STREAM_STATUS_BIT_HOST_BUF_READY
} P40_DAQ_STREAM_STATUS_BIT;

p40_stream_id_to_meta_mask

Derive metadata bit position corresponding to the given stream.

int p40_stream_id_to_meta_mask(int dev, P40_DAQ_STREAM stream_id);

p40_stream_id_to_name

Convert numerical stream identifier to string. See P40_DAQ_STREAM .

const char *p40_stream_id_to_name(P40_DAQ_STREAM stream_id);

p40_stream_lock

Lock a stream to the current process until the process terminates or the lock is explicitly released by p40_stream_unlock . Only the process holding the lock, if any, is allowed to move the read pointer for this stream.

int p40_stream_lock(int fd);

See also P40_STREAM_LOCK .


p40_stream_map

Map DMA circular buffer in memory.

void *p40_stream_map(int fd);

This function automatically does double-buffering of the circular buffer in order to avoid complicated wrap-around logic in the user application. See also p40driver`dma_mmap` .


p40_stream_name_to_id

Convert stream name to numerical id. See P40_DAQ_STREAM .

P40_DAQ_STREAM p40_stream_name_to_id(const char *name);

p40_stream_open

Create file descriptor to control a DMA stream on given interface. See P40_DAQ_STREAM .

int p40_stream_open(int dev, P40_DAQ_STREAM stream_id);

See p40driver`dma_open` .


p40_stream_poll

Query the stream for data.

ssize_t p40_stream_poll(int fd, loff_t *pread_off, int msecs);

This is a low-level function, it just returns how many bytes are available to read. If pread_off is not null, it must point to the base of the buffer as initialized by p40_stream_map , then, on return, it will be adjusted to point to the new data. Returns 0 on timeout, < 0 on error. See also p40driver`dma_poll` .


p40_stream_reset_default

Reset stream to its default power-on state.

int p40_stream_reset_default(int fd);

p40_stream_reset_flush

Request the stream to flush its DMA buffers.

int p40_stream_reset_flush(int fd);

p40_stream_reset_logic

Reset the logic on the stream.

int p40_stream_reset_logic(int fd);

p40_stream_scrub

Scrub stream DMA buffer by filling it with zeros.

int p40_stream_scrub(int fd);

p40_stream_set_meta_packing

int p40_stream_set_meta_packing(int fd, int32_t mpf);

p40_stream_unlock

Unlock a stream previously locked by the current process using p40_stream_lock .

int p40_stream_unlock(int fd);

See also P40_STREAM_UNLOCK .


p40_stream_unmap

Remove memory mapping created by p40_stream_map .

void p40_stream_unmap(int fd, void *buffer);

P40_DAQ_STREAM

typedef enum {
  P40_DAQ_STREAM_NULL = -1,
  P40_DAQ_STREAM_MAIN = 0,
  P40_DAQ_STREAM_META,
  P40_DAQ_STREAM_ODIN0,
  P40_DAQ_STREAM_ODIN1,
  P40_DAQ_STREAM_ODIN2,
  P40_DAQ_STREAM_ODIN3,
  P40_DAQ_STREAM_ODIN4
} P40_DAQ_STREAM;

P40_MAIN_GEN_MODE

Main stream data generator mode.

typedef enum {
  P40_MAIN_GEN_MODE_ERROR = -1,
  P40_MAIN_GEN_MODE_FRAGS,
  P40_MAIN_GEN_MODE_FIXED
} P40_MAIN_GEN_MODE;

Kernel-level code cross-reference

ctrl_open

static int ctrl_open(struct inode *inode, struct file *filp);

ctrl_release

static int ctrl_release(struct inode *inode, struct file *filp);

daq_mmap

static int daq_mmap(struct file *filp, struct vm_area_struct *vma);

daq_open

static int daq_open(struct inode *inode, struct file *filp);

daq_poll

static unsigned int daq_poll(struct file *filp, poll_table *wait);

daq_release

static int daq_release(struct inode *inode, struct file *filp);

Since this function is called when the last handle to a given stream is closed, the stream lock is also released here, if held by the current process.


dma_stream_configure

static int dma_stream_configure(int ifc_num, struct pcie40_dma_stream *stream, size_t map_base, size_t map_max_entries, size_t buf_requested_bytes, int numa_node);

dma_stream_destroy

static void dma_stream_destroy(int ifc_num, struct pcie40_dma_stream *stream);

ecs_open

static int ecs_open(struct inode *inode, struct file *filp);

ecs_release

static int ecs_release(struct inode *inode, struct file *filp);

pcie40_daq_exit

void pcie40_daq_exit(void);

pcie40_daq_init

int pcie40_daq_init(void);

pcie40_dma_stream

Kernelspace representation of a DMA stream between the PCIe40 FPGA and upstream memory.


pcie40_ecs_exit

Destroy ECS device class.

void pcie40_ecs_exit(void);

pcie40_ecs_init

Register ECS device class.

int pcie40_ecs_init(void);

This functions registers a dedicated device class used to create ECS device files.


pcie_bus_check_mps

static void pcie_bus_check_mps(struct pci_dev *pci);

pcie_bus_check_sta

static int pcie_bus_check_sta(struct pci_dev *pci);

static int pcie_link_retrain(struct pci_dev *pci, int gen);

pcie_show_secondary

static void pcie_show_secondary(struct pci_dev *pci);

dma_map_alloc

static int dma_map_alloc(struct pci_dev *pci_dev, struct pcie40_dma_map *map, int numa_node);

dma_map_free

static int dma_map_free(struct pci_dev *pci_dev, struct pcie40_dma_map *map, int reuse);

dma_map_read_entry_base

static inline dma_addr_t dma_map_read_entry_base(struct pcie40_dma_map *map, int i);

dma_map_read_entry_size

static inline size_t dma_map_read_entry_size(struct pcie40_dma_map *map, int i);

dma_map_write_entry

static void dma_map_write_entry(struct pcie40_dma_map *map, int i);

ecs_mmap

static int ecs_mmap(struct file* filp, struct vm_area_struct* vma);

pcie40_daq_probe

int pcie40_daq_probe(struct pci_dev *dev, const struct pci_device_id *id);

pcie40_daq_remove

void pcie40_daq_remove(struct pci_dev *dev);

pcie40_device_ids

static const struct pci_device_id pcie40_device_ids[] = {
  { PCI_DEVICE(0x10DC, 0xCE40), },  PCIe40 (Arria10)
  { PCI_DEVICE(0x10DC, 0xCE80), },  PCIe400 (Agilex-F)
  { PCI_DEVICE(0x10DC, 0xCECD), },  PCIe400 (Agilex-I)
  { 0, }
};
MODULE_DEVICE_TABLE(pci, pcie40_device_ids);

pcie40_ecs_probe

Initialize ECS BARs and create device files.

int pcie40_ecs_probe(struct pci_dev *dev, const struct pci_device_id *id);

This function allocates a pcie40_ecs_state instance to keep ECS-specific state. It then requests exclusive access to the ECS BARs and allocates a range of minor numbers for its character devices. One such device is created for BAR0 and a second one for BAR2.


pcie40_ecs_remove

Destroy ECS device files.

void pcie40_ecs_remove(struct pci_dev *dev);

pcie40_exit

Unregister PCIe driver and uninitialize subdrivers.

static void __exit pcie40_exit(void)

pcie40_init

Initialize subdrivers and register PCIe driver with kernel.

static int __init pcie40_init(void);

The first module to be initialized is the ECS, using pcie40_ecs_init . Followed by the DAQ, using pcie40_daq_init . The driver is registered with the kernel using pci_register_driver, its argument also contains the PCI device ids that correspond to the PCIe40 firmware (see pcie40_device_ids ).


pcie40_isr

static irqreturn_t pcie40_isr(int irq, void *arg);

pcie40_probe

Scan PCI bus to detect PCIe40 board.

static int pcie40_probe(struct pci_dev *dev, const struct pci_device_id *id);

This function allocates a pcie40_state instance used to track the state of this device within the kernel. The state is initialized with the position and size of all PCI BARs. BAR1 is always mapped inside the kernel as it’s used directly by DAQ interface. Using this mapping, the driver ensures that PCIe registers on the FPGA can be accessed. Then it reads the pcie.dma_ctrl.link_id register to identify which PCIe link from the FPGA is being probed. Using this information, a unique interface identifier is allocated to the PCIe link. Interfaces with PCIe link 0 get an even interface id. Interfaces with PCIe link 1 get an odd interface id. The driver always allocates the lowest available interface id. Finally it calls the probing logic of the subdrivers via pcie40_ecs_probe and pcie40_daq_probe . After initializing the subdrivers, this function always returns success, this is to ensure that pcie40_remove is always called also in case only some subdrivers are loaded.


pcie40_remove

Decrease internal reference count.

static void pcie40_remove(struct pci_dev *dev);

pcie40_state_release

Remove PCIe40 interface from kernel (once its reference count is zero).

First the submodules are uninitialized using pcie40_daq_remove and pcie40_ecs_remove . BAR1 is unmapped using iounmap. Finally the PCI device is disabled and the pcie40_state memory is freed.


daq_emu_thread

Thread loop generating emulated board data.

static int daq_emu_thread(void *data)
data (I)

Opaque pointer to interface state

One instance of this thread is spawned for each emulated interface. The thread loops until the module is unloaded.

while (!kthread_should_stop()) { ... }

Every loop iteration reads the emulated control registers

mmr_reset = pcie40_read32_ctrl(state->common, P40_DMA_CTRL_OFF_RESET);
mmr_main_gen_ctl = pcie40_read32_ctrl(state->common, P40_DMA_CTRL_OFF_MAIN_GEN_CTL);
mmr_main_gen_fixed = pcie40_read32_ctrl(state->common, P40_DMA_CTRL_OFF_MAIN_GEN_FIXED);
mmr_main_raw_mode = pcie40_read32_ctrl(state->common, P40_DMA_CTRL_OFF_MAIN_RAW_MODE);
mmr_main_enable = pcie40_read32_stream(&state->main_stream, P40_DMA_DAQ_STREAM_OFF_ENABLE);
mmr_meta_enable = pcie40_read32_stream(&state->meta_stream, P40_DMA_DAQ_STREAM_OFF_ENABLE);
mpf = pcie40_read32_stream(&state->main_stream, P40_DMA_DAQ_STREAM_OFF_META_PACKING);

Then uses these values to drive its behaviour for the rest of the current emulation cycle.


dma_stream_emu_write

Emulate a dma write into a given stream.

static ssize_t dma_stream_emu_write(
  struct pcie40_dma_stream *stream, int *map_idx, uint32_t *buf_off, const void *from, size_t bytes);
stream (I)

Stream to write to.

map_idx (IO)

Index of current DMA buffer, must be initialized to 0.

buf_off (IO)

Offset within current DMA buffer, must be initialized to 0.

from (I)

Pointer to data to write, if NULL, zeroes will be written instead.

bytes (I)

Number of bytes to write.

The write is started only if sufficient space is available. If not, a negative value is immediately returned. Its magnitude is the number of bytes missing. A write is split across multiple DMA buffers if the current one does not have sufficient space available.


ecs_emu_mmap

static int ecs_emu_mmap(struct file* filp, struct vm_area_struct* vma);

Appendix A: PCIe40 backpressure behaviour

Data fragments produced in the FPGA by an onboard TELL40 instance reach the corresponding PCIe output stage via a clock-crossing FIFO. The write clock domain is defined by the specific TELL40 data processing implementation. The read clock domain is the 220MHz used internally by the DMA controller. The pcie.dma_ctrl.trunc_thres register that controls the truncation threshold operates on the fill level of this FIFO. Its value is 32 upon power-on reset (or default reset) but it can be configured via the trunc_threshold setting in the configuration file (up to 64).

Depending on the fill level, three scenarios are possible:

FIFO is full

The Avalon snk_ready signal from the controller is deasserted to report backpressure. Since no further data can be transmitted, the current fragment is throttled (its type is changed to DaqErrorFragmentThrottled and its size is cut at the size transmitted so far). Data words from the TELL40 are discarded so long as backpressure persists, but the last transmitted and the last seen event IDs are recorded in the DMA controller. When space becomes available, the controller generates zero-size DaqErrorFragmentThrottled fragments for all missing event IDs. The ready signal is reasserted when the transmitted event ID sequence has caught up and normal data transmission can resume.

FIFO is above threshold

The Avalon snk_ready signal from the controller remains asserted, but an additional snk_trunc signal is also asserted. The latter instructs the TELL40 that, although space is still available, backpressure is imminent and the next fragment should be truncated to reduce the likelyhood of throttling. Truncated fragments have a size of 0 and a type of DaqErrorFragmentTruncated . Data words from the TELL40 pass through the FIFO unmodified, throttle engages automatically if space is exhausted.

FIFO is below threshold

Data words from the TELL40 pass through the FIFO unmodified, truncation engages automatically if the threshold is crossed and throttle engages automatically if space is exhausted.

Appendix B: PCIe40 DMA data format

Depending on the FPGA configuration, the board can produce data according to three different data formats.

Main data only (no metadata)

When raw mode is off and the metadata stream is not enabled, the FPGA will emit readout data as a continuous stream of fragments aligned to 32-byte (256-bits) boundaries. Each fragment is laid out in memory as follows, starting with the event ID:

diag 8c32066054fe517039d21de3645cc0b2

The class pcie40_frg_hdr is provided for convenience in order to access the different fields preceding the payload.

Optimized (with metadata acceleration)

The previous data format favours simplicity over performance, when the optional metadata stream is enabled in the firmware, several adjustments are made to the output data format to increase its efficiency. These are:

  1. The event ID is omitted from the fragment header

  2. Fragments are aligned on 8 byte boundaries, instead of 32.

This results in the following fragment format:

diag 4b1a81ac6f2c0fb9270b4f64ca767d3a

A global fragment header can be inspected using an instance of the daq40_frg_hdr class.

Appendix C: DIM interface

{consumer}/blocks

SVC(I:2) A 64-bit integer (represented as two 32-bit values).

Contains the number of blocks consumed during the last run.


{consumer}/bytes

SVC(I:2) A 64-bit integer (represented as two 32-bit values).

Contains the number of bytes consumed during the last run.


{consumer}/evid

SVC(I:2) A 64-bit integer (represented as two 32-bit values).

Contains the unique EventID of the last fragment that was consumed.


{consumer}/frags

SVC(I:2) A 64-bit integer (represented as two 32-bit values).

Contains the number of fragments consumed during the last run.


{frgwriter}/set_subdet_info

CMD(I) Sets an arbitrary subdetector-defined 16bit value, this quantity will be stored in a dedicated field in the .frg file header.


{frgwriter}/subdet_info

SVC(I) A subdetector-defined 16bit value.


{writer}/max_mbytes

SVC(I) The maximum size (in MiB) before the output file is truncated.


{writer}/mode

SVC(C) The output mode configured for this data consumer.


{writer}/path

SVC(C) The path to the file being written.


{fsm}/action

CMD(C) Character string indicating an FSM transition to execute.

The following commands are accepted:

  • "configure"

  • "start"

  • "stop"

  • "reset"


{fsm}/state

SVC(I) An integer encoding the current FSM state. See daq40_fsm::fsm_state .


{daqserver}/build

SVC(C) Software build version and release identifiers, separated by a dash sign.


{daqserver}/set_run_number

CMD(I) Set the run number to be used next time a new file is opened.


{interface}/leds

SVC(I) Link identifier for this PCIe interface within one FPGA board.


SVC(I) Link identifier for this PCIe interface within one FPGA board.


{interface}/pcie

SVC(I) PCIe state currently negotiated by FPGA device.


{interface}/version

SVC(I) Version of LHCb PCIe component on FPGA.


Appendix D: Data Quality Monitor interface

Appendix E: PCIe40 driver IOCTL interface

P40_CTRL_GET_HOST_MSI_NSECS

#define P40_CTRL_GET_HOST_MSI_NSECS _IOR('h', 2, uint64_t)

P40_CTRL_GET_MAIN_GEN_CTL

#define P40_CTRL_GET_MAIN_GEN_CTL    _IOR('c',  4, uint64_t)
#define P40_CTRL_GET_ODIN_GEN_CTL    _IOR('c', 11, uint64_t)

P40_CTRL_GET_MAIN_MSI_BYTES

#define P40_CTRL_GET_MSI_BYTES  _IOR('c', 16, uint64_t)

P40_CTRL_GET_MAIN_MSI_CYCLES

uint64_t)//+P40_CTRL_GET_META_MSI_BYTES

#define P40_CTRL_GET_MSI_CYCLES _IOR('c', 18, uint64_t)
#define P40_CTRL_GET_META_MSI_BYTES  _IOR('c', 19,

uint64_t)//+P40_CTRL_SET_META_MSI_BYTES

#define P40_CTRL_SET_META_MSI_BYTES  _IOW('c', 20,

uint64_t)//+P40_CTRL_GET_META_MSI_CYCLES

#define P40_CTRL_GET_META_MSI_CYCLES _IOR('c', 21,

uint64_t)//+P40_CTRL_GET_META_MSI_BLOCKS

#define P40_CTRL_GET_META_MSI_BLOCKS _IOR('c', 22,

uint64_t)//+P40_CTRL_SET_META_MSI_BLOCKS

#define P40_CTRL_SET_META_MSI_BLOCKS _IOW('c', 23,


P40_CTRL_GET_MAIN_RAW_MODE

uint64_t)//+P40_CTRL_GET_META_PACKING

#define P40_CTRL_GET_MAIN_RAW_MODE   _IOR('c',  8, uint64_t)
#define P40_CTRL_GET_META_PACKING    _IOR('c',  9,

uint64_t)//+P40_CTRL_SET_META_PACKING

#define P40_CTRL_SET_META_PACKING    _IOW('c', 10,


P40_CTRL_GET_PCIE_PROTO

Reports the result of the PCIe link negotiation. The returned value is 32 bits.

  • bits[25..20] PCIe link width reported by the operating system.

  • bits[19..16] PCIe link gen reported by the operating system.

  • bits[9..4] PCIe link width reported by the FPGA (see pcie.dma_ctrl.pcie_lanes ).

  • bits[3..0] PCIe link gen reported by the FPGA (see pcie.dma_ctrl.pcie_gen ).

#define P40_CTRL_GET_PCIE_PROTO        _IOR('c', 15, uint64_t)

P40_CTRL_GET_RESET

#define P40_CTRL_GET_RESET           _IOR('c',  2, uint64_t)

P40_CTRL_GET_RX_LAST_EVID

#define P40_CTRL_GET_RX_LAST_EVID           _IOR('c', 41, uint64_t)
#define P40_CTRL_GET_TX_LAST_EVID           _IOR('c', 42, uint64_t)

P40_CTRL_GET_SPILL_SIZE

See regmap`pcie.dma_ctrl.spill_buf_size`

#define P40_CTRL_GET_SPILL_SIZE      _IOR('c', 24, uint64_t)

P40_CTRL_GET_SPILL_USED

See regmap`pcie.dma_ctrl.spill_buf_used`

#define P40_CTRL_GET_SPILL_USED      _IOR('c', 25, uint64_t)

P40_CTRL_GET_STATUS

#define P40_CTRL_GET_STATUS          _IOR('c',  1, uint64_t)

P40_CTRL_GET_STREAM_SEL

See regmap`pcie.dma_ctrl.spill_buf_sel`

#define P40_CTRL_GET_STREAM_SEL      _IOR('c', 39, uint64_t)

P40_CTRL_SET_MAIN_MSI_BYTES

#define P40_CTRL_SET_MSI_BYTES  _IOW('c', 17, uint64_t)

P40_CTRL_SET_ODIN_GEN_CTL

#define P40_CTRL_SET_ODIN_GEN_CTL    _IOW('c', 12, uint64_t)
#define   P40_ODIN_GEN_BIT_ENABLE  (0)
#define   P40_ODIN_GEN_BIT_RUNNING (1)
#define   P40_ODIN_GEN_BIT_LIMIT   (2)
#define P40_CTRL_GET_MSI_MODE        _IOR('c', 13,

uint64_t)//+P40_CTRL_SET_MSI_MODE

#define P40_CTRL_SET_MSI_MODE        _IOW('c', 14,


P40_CTRL_SET_RESET

#define P40_CTRL_SET_RESET           _IOW('c',  3, uint64_t)

P40_CTRL_SET_STREAM_SEL

See regmap`pcie.dma_ctrl.spill_buf_sel`

#define P40_CTRL_SET_STREAM_SEL      _IOW('c', 40, uint64_t)

P40_ECS_SET_BAR_SIZE

#define P40_ECS_SET_BAR_SIZE _IOW('e', 2, uint64_t)

P40_ID_GET_FPGA

#define P40_ID_GET_FPGA     _IOR('i',  5, uint64_t)

The value returned by this IOCTL encodes both the PCI topological address and the FPGA link number, as follows:

  • bits[31..24]: PCI bus number

  • bits[23..16]: PCI slot number

  • bits[15..8]: PCI function number

  • bits[7..0]: Local PCI link number (see pcie.dma_ctrl.link_id )

#define P40_ID_GET_LINK     _IOR('i',  6, uint64_t)

P40_ID_GET_REGMAP

The returned value is 64 bits.

  • bits[63..32]: register map version expected by the driver

  • bits[31..0]: value of the FPGA register pcie.dma_ctrl.regmap

#define P40_ID_GET_REGMAP   _IOR('i',  3, uint64_t)

P40_ID_GET_RWTEST

#define P40_ID_GET_RWTEST   _IOR('i',  1, uint64_t)

P40_ID_GET_SOURCE

See pcie.dma_ctrl.link_id The value returned by this IOCTL encodes both the source identifier and its data format version, as follows:

  • bits[23..16]: source version

  • bits[15..11]: source subsystem number

  • bits[10..0]: source number within the subsystem

#define P40_ID_GET_SOURCE   _IOR('i', 11, uint64_t)

P40_ID_GET_VERSION

The returned value is 64 bits.

  • bits[48..32]: DMA core version expected by the driver

  • bits[31..0]: value of the FPGA register pcie.dma_ctrl.version

#define P40_ID_GET_VERSION  _IOR('i',  4, uint64_t)

P40_ID_SET_RWTEST

#define P40_ID_SET_RWTEST   _IOW('i',  2, uint64_t)

P40_ID_SET_SOURCE

Sets the source identifier for a given data source using the lowest 16 bits of the input argument, other bits are ignored. The value is stored in the SourceID portion of the pcie.dma_ctrl.link_id FPGA register.

#define P40_ID_SET_SOURCE   _IOW('i', 12, uint64_t)

P40_STREAM_FREE_HOST_BUF_BYTES

#define P40_STREAM_FREE_HOST_BUF_BYTES     _IOWR('m', 4, uint64_t)

If the stream is locked, only the owner process is allowed to change the read pointer, EPERM is returned otherwise. Note that value is internally rounded up to its nearest multiple of 4.


P40_STREAM_GET_DMABUF

#define P40_STREAM_GET_DMABUF              _IOR('m', 9, uint64_t)

P40_STREAM_GET_HOST_BUF_BYTES

for huge buffer allocation on demand, size is zero before allocation

#define P40_STREAM_GET_HOST_BUF_BYTES      _IOR('m', 1, uint64_t)

P40_STREAM_GET_HOST_BUF_BYTES_USED

uint64_t)//+P40_STREAM_GET_HOST_MSI_COUNT

#define P40_STREAM_GET_HOST_BUF_BYTES_USED _IOR('m', 2, uint64_t)
#define P40_STREAM_GET_HOST_MSI_COUNT      _IOR('m', 3,

P40_STREAM_GET_HOST_BUF_READ_OFF

#define P40_STREAM_GET_HOST_BUF_READ_OFF        _IOR('s', 13, uint64_t)

P40_STREAM_GET_HOST_BUF_WRITE_OFF

#define P40_STREAM_GET_HOST_BUF_WRITE_OFF       _IOR('s', 12, uint64_t)

P40_STREAM_GET_LOCKER

#define P40_STREAM_GET_LOCKER              _IOR('m', 5, uint64_t)

P40_STREAM_GET_META_PACKING

See regmap`pcie.dma_stream.meta_packing` .

#define P40_STREAM_GET_META_PACKING             _IOR('s', 14, uint64_t)

P40_STREAM_GET_STATUS

See regmap`pcie.dma_daq_stream.ready`

#define P40_STREAM_GET_STATUS                   _IOR('s',  3, uint64_t)

P40_STREAM_LOCK

#define P40_STREAM_LOCK                    _IO('m', 6)

The stream can be locked only if no other process currently holds the lock, otherwise EBUSY is returned. The same process can lock the same stream multiple times. In this case all subsequent calls will also succeed, but since locks are not reference counted the first unlock operation will immediately unlock the stream regardless of how many times it was locked.


P40_STREAM_SET_META_PACKING

#define P40_STREAM_SET_META_PACKING             _IOW('s', 15, uint64_t)

P40_STREAM_SET_STATUS_SEL

See regmap`pcie.dma_daq_stream.ready`

#define P40_STREAM_SET_STATUS_SEL               _IOW('s', 16, uint64_t)

P40_STREAM_UNLOCK

#define P40_STREAM_UNLOCK                  _IO('m', 7)

The stream can only be unlocked by the process currently holiding the lock, otherwise EPERM is returned.


Appendix F: PCIe40 PCIe register map

pcie_regmap_version

version = 0x20181106

Current version of the register map. This value must be the same between the driver and the firmware.


pcie_core_version

version = 0x0602

Expected version of the FPGA PCIe core. This value must be the same between the driver and the firmware.


pcie_dma_ctrl_qsys_base

base = 0x1000

QSys base address of DMA controller.


pcie_dma_daq_main_buf_qsys_base

base = 0x100000

QSys base address of main FPGA memory buffer.


pcie_dma_daq_main_map_qsys_base

base = 0x10000

QSys base address of main host memory map.


pcie_dma_daq_main_stream_qsys_base

base = 0x1100

QSys base address of main stream registers.


pcie_dma_daq_meta_buf_qsys_base

base = 0x200000

QSys base address of meta FPGA memory buffer.


pcie_dma_daq_meta_map_qsys_base

base = 0x20000

QSys base address of meta host memory map.


pcie_dma_daq_meta_stream_qsys_base

base = 0x2000

QSys base address of meta stream controller.


pcie_dma_daq_odin0_buf_qsys_base

base = 0x300000

QSys base address of first odin FPGA memory buffer.


pcie_dma_daq_odin0_map_qsys_base

base = 0x30000

QSys base address of first odin host memory map.


pcie_dma_daq_odin0_stream_qsys_base

base = 0x3000

QSys base address of first odin stream controller.


pcie_dma_daq_odin1_buf_qsys_base

base = 0x400000

QSys base address of second odin FPGA memory buffer.


pcie_dma_daq_odin1_map_qsys_base

base = 0x40000

QSys base address of second odin host memory map.


pcie_dma_daq_odin1_stream_qsys_base

base = 0x4000

QSys base address of second odin stream controller.


pcie_dma_daq_odin2_buf_qsys_base

base = 0x500000

QSys base address of third odin FPGA memory buffer.


pcie_dma_daq_odin2_map_qsys_base

base = 0x50000

QSys base address of third odin host memory map.


pcie_dma_daq_odin2_stream_qsys_base

base = 0x5000

QSys base address of third odin stream controller.


pcie_dma_daq_odin3_buf_qsys_base

base = 0x600000

QSys base address of fourth odin FPGA memory buffer.


pcie_dma_daq_odin3_map_qsys_base

base = 0x60000

QSys base address of fourth odin host memory map.


pcie_dma_daq_odin3_stream_qsys_base

base = 0x6000

QSys base address of fourth odin stream controller.


pcie_dma_daq_odin4_buf_qsys_base

base = 0x700000

QSys base address of fifth odin FPGA memory buffer.


pcie_dma_daq_odin4_map_qsys_base

base = 0x70000

QSys base address of fifth odin host memory map.


pcie_dma_daq_odin4_stream_qsys_base

base = 0x7000

QSys base address of fifth odin stream controller.


pcie.dma_ctrl

DMA controller registers.


pcie.dma_ctrl.err_total_events

off_rx_err_total_events = 0x68

(RO) Events lost because of malformed input data.


pcie.dma_ctrl.err_total_since_evid

off_rx_err_total_since_evid_lo = 0x6C
off_rx_err_total_since_evid_hi = 0x70

(RO) EVID when an input format error was first detected since the last counter reset.


pcie.dma_ctrl.fpga

off_fpga_lo = 0x0C
off_fpga_hi = 0x10

(RO) FPGA serial number


pcie.dma_ctrl.leds_id

off_leds_id = 0x18

(RW) Control frontplate identification LEDs according to the following format:

  • bit 8: override (if this bit is zero the led colors remain those set by the LLI)

  • bits[6..4]: binary RGB value for second LED

  • bits[2..0]: binary RGB value for first LED


off_link_id = 0x14

(RW) Link identifier according to the following format:

  • bits[31..24]: source version (read-only)

  • bits[23..8]: global link identifier (SourceID)

    • bits[23..19]: subsystem number

    • bits[18..8]: source number within the subsystem

  • bits[7..0]: local link identifier (read-only)

The value of the local link identifier is hardcoded in the FPGA and currently can only be either 0 (for the primary PCIe link in a given board) or 1 (for the secondary).


pcie.dma_ctrl.main_gen_ctl

off_main_gen_ctl = 0x2C

(RW) Main data generator control, the following bits are defined:

  • bits[30..24]: limiter% (0 = no limit, 100 = no data)

  • bit 3: set rate limiter

  • bit 2: generate fixed pattern/pseudo fragments

  • bit 1: start/stop generator

  • bit 0: enable/disable generator


pcie.dma_ctrl.main_gen_fixed

off_main_gen_fixed = 0x30

(RW) 32-bit pattern used when generating data in fixed-pattern mode.


pcie.dma_ctrl.main_raw_mode

off_main_raw_mode = 0x34

(RO) Indicates whether the input to the main stream is in raw (1) or normal (0) format.


pcie.dma_ctrl.meta_alignment

off_meta_alignment = 0x44

(RO) Alignment between metadata blocks.


pcie.dma_ctrl.name

off_name_lo = 0x1C
off_name_hi = 0x20

(RW) Globally unique board name within the system, it is a 8-byte ASCII-encoded string.

Both interfaces on one board can share the same value for this field. To uniquely reference a given interface when calling p40_id_find , append either _0 or :0 to the name for the first interface and either _1 or :1 for the second.

pcie.dma_ctrl.odin_gen_ctl

off_odin_gen_ctl = 0x4C

(RW) Odin data generator control, the following bits are defined:

  • bits[30..24]: limiter% (0 = no limit, 100 = no data)

  • bit 2: set rate limiter

  • bit 1: start/stop generator

  • bit 0: enable/disable generator


pcie.dma_ctrl.pcie_gen

off_pcie_gen = 0x80

(RO) PCI-express protocol version, depending on this value, the serial link speed will be as follows:

  1. 2.5 Gb/s

  2. 5.0 Gb/s

  3. 8.0 Gb/s


pcie.dma_ctrl.pcie_lanes

off_pcie_lanes = 0x78

(RO) PCI-express number of active lanes.


pcie.dma_ctrl.pcie_ltssm

off_pcie_ltssm = 0x7C

(RW) PCI-express link training state machine log. Each read returns a word in the form:

  • bits[31..24]: entry position in log

  • bits[23..8]: state transition length (in 10ns cycles)

  • bits[7..0]: LTSSM state

Write 0 to move the read position to the beginning of the log (subsequent reads will return subsequent positions). Write 0xFFFFFFFF to clear the log.


pcie.dma_ctrl.regmap

off_regmap = 0x04

(RO) Version of the register map implemented by the FPGA, it must match pcie_regmap_version .


pcie.dma_ctrl.reset

off_reset = 0x24

(RW) Reset register for the controller. The reset that is performed depends on which bit is written to it:

  • bit 0: reset everything to its power-on default state

  • bit 1: reset only logic processes and buffers, do not revert user-controlled registers

  • bit 2: only flush the buffers

  • bit 3: only reset counters


pcie.dma_ctrl.rwtest

off_rwtest = 0x00

(RW) Test register, can be used to check that reads and writes are correctly handled by the Avalon bus on the FPGA.


pcie.dma_ctrl.rx_last_evid

off_rx_last_evid_lo = 0x94
off_rx_last_evid_hi = 0x98

(RO) EVID of last valid event received.


pcie.dma_ctrl.spill_size

off_spill_size = 0xA0

(RO) Size of spill buffer, in words.


pcie.dma_ctrl.spill_used

off_spill_used = 0xA4

(RO) Current fill level of spill buffer, in words.


pcie.dma_ctrl.status

off_status = 0x28

(RO) Status register.


pcie.dma_ctrl.stream_sel

off_stream_sel = 0x9C

(RW) Stream counters selector.


pcie.dma_ctrl.throt_last_cycles

off_throt_last_cycles = 0xD8

(RO) Duration, in consecutive cycles, of last time the input ready signal was deasserted.


pcie.dma_ctrl.throt_last_from_evid

off_throt_last_from_evid_lo = 0xDC
off_throt_last_from_evid_hi = 0xE0

(RO) EVID when the input ready signal was last deasserted.


pcie.dma_ctrl.throt_last_to_evid

off_throt_last_to_evid_lo = 0xE4
off_throt_last_to_evid_hi = 0xE8

(RO) EVID when the input ready signal was last asserted.


pcie.dma_ctrl.throt_total_events

off_throt_total_events = 0xCC

(RO) Events lost to throttle since the last counter reset.


pcie.dma_ctrl.throt_total_since_evid

off_throt_total_since_evid_lo = 0xD0
off_throt_total_since_evid_hi = 0xD4

(RO) EVID when the input ready signal was first deasserted since the last counter reset.


pcie.dma_ctrl.trunc_last_cycles

off_trunc_last_cycles = 0xB8

(RO) Duration, in consecutive cycles, of last time the truncation signal was asserted.


pcie.dma_ctrl.trunc_last_from_evid

off_trunc_last_from_evid_lo = 0xBC
off_trunc_last_from_evid_hi = 0xC0

(RO) EVID when the truncation signal was last asserted.


pcie.dma_ctrl.trunc_last_to_evid

off_trunc_last_to_evid_lo = 0xC4
off_trunc_last_to_evid_hi = 0xC8

(RO) EVID when the truncation signal was last deasserted.


pcie.dma_ctrl.trunc_thres

off_trunc_thres = 0xA8

(RW) Spill buffer threshold level, in words, above which the truncation signal is triggered.


pcie.dma_ctrl.trunc_total_events

off_trunc_total_events = 0xAC

(RO) Events lost to truncation since the last counter reset.


pcie.dma_ctrl.trunc_total_since_evid

off_trunc_total_since_evid_lo = 0xB0
off_trunc_total_since_evid_hi = 0xB4

(RO) EVID when the truncation signal was first asserted since the last counter reset.


pcie.dma_ctrl.tx_last_evid

off_tx_last_evid_lo = 0xEC
off_tx_last_evid_hi = 0xF0

(RO) EVID of last valid event transmitted.


pcie.dma_ctrl.version

off_version = 0x08

(RO) Version of the DMA module on the FPGA. It must match pcie_core_version .


pcie.dma_daq_stream.enable

off_enable = 0x00

(RW) Controls which DMA channels are enabled (1) or not (0).


pcie.dma_daq_stream.fpga_buf_bytes

off_fpga_buf_bytes = 0x0C

(RO) Size of the DMA buffer on the FPGA (in bytes)


pcie.dma_daq_stream.fpga_buf_desc_bytes

off_fpga_buf_desc_bytes = 0x14

(RO) Size of each descriptor region inside the DMA buffer on the FPGA (in bytes)


pcie.dma_daq_stream.fpga_buf_desc_fill_bytes

off_fpga_buf_desc_fill_bytes = 0x20

(RO) Bytes used in the description region currently being filled.


pcie.dma_daq_stream.fpga_buf_descs

off_fpga_buf_descs = 0x10

(RO) Number of descriptor regions inside the DMA buffer on the FPGA


pcie.dma_daq_stream.fpga_buf_descs_busy

off_fpga_buf_descs_busy_lo = 0x24
off_fpga_buf_descs_busy_hi = 0x28

(RO) Descriptor state bitmask, if a bit is 1 the corresponding descriptor is in the BUSY state.


pcie.dma_daq_stream.fpga_buf_descs_fill

off_fpga_buf_descs_fill_lo = 0x18
off_fpga_buf_descs_fill_hi = 0x1C

(RO) Descriptor state bitmask, if a bit is 1 the corresponding descriptor is in the FILL state (at most one descriptor can be in this state at any moment).


pcie.dma_daq_stream.host_buf_read_off

off_host_buf_read_off_lo = 0x30
off_host_buf_read_off_hi = 0x40

(RW) Current DMA read offset inside the DMA buffer on the host.


pcie.dma_daq_stream.host_buf_write_off

off_host_buf_write_off_lo = 0x2C
off_host_buf_write_off_hi = 0x3C

(RO) Current DMA write offset inside the DMA buffer on the host.


pcie.dma_daq_stream.host_map_entries

off_host_map_entries = 0x34

(RW) Number of entries inside the memory map representing the DMA host buffer.


pcie.dma_daq_stream.host_map_pages

off_host_map_pages = 0x38

(RO) Number of pages inside the memory map representing the DMA host buffer.


pcie.dma_daq_stream.meta_packing

off_meta_packing = 0x44

(RW) Metadata packing factor.


pcie.dma_daq_stream.reset

off_reset = 0x08

(RW) Reset register.


pcie.dma_daq_stream.status

off_status = 0x04

(RO) Status flags for this stream.


Appendix G: BAR0 PCIe register map

This list is partial, refer to the control system datapoints for the full register map.

bar0.top_0.base

base = 0x5ff000

First (and only) instance of top-level registers.


bar0.top.array_rx_errorseen

array_rx_errorseen = 0x0F4-0xF8

bar0.top.array_rx_ready

array_rx_ready = 0x0FC-0x100

bar0.top.board_id

board_id = 0xFFC

Unique board identifier, on a PCIe40 this is derived from the lowest 32 bits of the FPGA serial number.


bar0.top.build_id

build_id = 0x400-0x9FF

Contains an autogenerated string formatted according to Build ID ROM format.


bar0.top.compilation_time

compilation_time = 0x318-0x31C

bar0.top.counter_lsb_error_fec

counter_lsb_error_fec = 0xB04-0xBC0

bar0.top.counter_msb_error_fec

counter_msb_error_fec = 0xA04-0xAC0

bar0.top.counter_rx_errorseen

counter_rx_errorseen = 0x1C4-0x280

bar0.top.counter_rx_ready

counter_rx_ready = 0x104-0x1C0

bar0.top.ext_clock_40mhz

ext_clock_40mhz = 0x310

Clock configuration register, the following values are possible:

  1. internal

  2. external

  3. tfc

  4. custom


bar0.top.fiber_mapping_tfc_or_acq

fiber_mapping_tfc_or_acq = 0xFE0-0xFE4

force_fe_data_in_tfc_links = 0x0C8-0xCC

bar0.top.fw_config_enb

fw_config_enb = 0x0BC

Includes several fields:

  • bits[0]: FW_with_FE

  • bits[4]: FW_with_PRBS

  • bits[8]: sol40_with_hardcoded_sbtype

  • bits[9]: tfc_sfp_rx_ready_0[3]

  • bits[10]: tfc_sfp_rx_ready_1[3]

  • bits[15..12]: SCA_VERSION

  • bits[21..16]: SCA_MAX_PER_LINK

  • bits[22]: tfc_sfp_rx_ready_0[1]

  • bits[23]: tfc_sfp_rx_ready_1[1]

  • bits[24]: SOL40_FW_with_SODIN

  • bits[25]: tfc_sfp_rx_ready_0[0]

  • bits[26]: tfc_sfp_tx_ready_0

  • bits[27]: tfc_sfp_rx_ready_1[0]

  • bits[28]: tfc_sfp_tx_ready_1

  • bits[29]: tfc_sfp_rx_ready_0[2]

  • bits[30]: tfc_sfp_rx_ready_1[2]

  • bits[31]: link_ordered


bar0.top.sol40_with_sca_core

sol40_with_sca_core = 0x0C0-0x0C4

bar0.tell40_0.base

base = 0x700000

First TELL40 instance.


bar0.tell40_1.base

base = 0x1700000

Second TELL40 instance.


Appendix H: BAR2 PCIe register map

This list is partial, refer to the low-level interface for the full register map.

bar2.lli_0.base

base = 0x30000

First (and only) instance of low-level interface.


bar2.lli.status

status = 0x120

After the FPGA is programmed, this register is 0, after the PLLs and clocks have been configured correctly, this register reads 1.


bar2.lli_0.ext_clock_select

ext_clock_select = 0x10140
 Clock tree selector, set bit 2 to enable the LHC clock input.
https://gitlab.cern.ch/lhcb-daq40/lhcb-daq40-software/blob/v6.2.3/common/bar2_regmap.cfg#L19[&#182;]

Appendix I: AMC40 XGbE register map

Appendix J: Build ID ROM format

The ROM contents are generated at compile-time and consist of a list of strings, each terminated by a semicolon.

Example contents (rearranged and shortened for readability)
2019-01-27T08:15:46; (1)
admin@lbminidaq2-17.dyndns.cern.ch; (2)
16.1.0 196 10/24/2016; (3)
:master:7be53db:v20190110-1; (4)
lli-amc40:master:2fc7f22:v6.0.0; (5)
lli-simulation:master:c3b617a:v6.0.0;
lli-gbt:master:2681257:v6.0.1;
tell40:master:536a2d0:v6.0.0;
...
sodin:master:32d4b1e:v6.0.1;
sol40:master:b156a3f:v6.0.1-1-gb156a3f; (5)
sol40-sc:master:db57d78:v6.0.0-5-gdb57d78;
1 Timestamp of compilation
2 Username and host used for compilation
3 Quartus version used for compilation
4 Git upstream repository information
5 Git submodule repository information (for each submodule)

Each line containing git repository information is itself a colon-separated list of fields, in order:

  1. Submodule name (empty for upstream repository)

  2. Branch name used for compilation

  3. SHA of commit used for compilation

  4. Tag name (or the output of git-describe if no tag matches the commit exactly)

Appendix K: Testing JCOP components

The fwTap JCOP component can be used to produce test coverage reports from CTRL scripts, for consumption by a test harness. The output format follows TAP specification the that is supported by tools and libraries in many languages (TAP stands for "Test Anything Protocol"). This implementation is mostly a straight C-to-CTRL conversion of the libtap project hosted at http://www.shlomifish.org/open-source/projects/libtap/.

This component provides a library that other JCOP components can use to run unit tests and report their success, or failure. The next section describes the library API, while the last section of this appendix shows several use-case examples.

fwTap library cross reference

diag

Print a diagnostic message (use instead of printf/fprintf). diag ensures that the output will not be considered to be a test result by the TAP test harness. It will append '\n' for you.

unsigned diag(string fmt, ...);
fmt (I)

the format of the printf-style message


end_tests

This function must be called at the end of each test script.

void end_tests();

except

Note that a test raised an exception

In addition to pass and fail , a test can fail by throwing an exception, this can be reported by catching the exception passing it as a first argument to this function.

void except(dyn_errClass exc, string fmt, ...);
exc (I)

the exception to report

fmt (I)

the printf-style name of the test


exit_status

The value that main should return.

For maximum compatability your test program should return a particular exit code (ie. 0 if all tests were run, and every test which was expected to succeed succeeded).

int exit_status();

fail

Note that a test failed

For complicated code paths, it can be easiest to simply call pass in one branch and fail in another.

void fail(string fmt, ...);
fmt (I)

the printf-style name of the test


ok

Conditional test with a name.

If the expression is true, the test passes. The name of the test will be the filename, line number, and the printf-style string. This can be clearer than simply the expression itself.

bool ok(bool e, string fmt, ...);
e (I)

the expression which we expect to be true

fmt (I)

the printf-style name of the test


pass

Note that a test passed.

For complicated code paths, it can be easiest to simply call pass in one branch and fail in another.

void pass(string fmt, ...);
fmt (I)

the printf-style name of the test


plan_no_plan

I have no idea how many tests I’m going to run.

In some situations you may not know how many tests you will be running, or you are developing your test program, and do not want to update the plan_tests call every time you make a change. For those situations use plan_no_plan instead of plan_tests . It indicates to the test harness that an indeterminate number of tests will be run.

Remember, if you fail to plan, you plan to fail.

int plan_no_plan(string name);
name (I)

name for this test plan


plan_skip_all

Indicate that you will skip all tests.

If your test program detects at run time that some required functionality is missing (for example, it relies on a database connection which is not present, or a particular configuration option that has not been included in the running kernel) use plan_skip_all instead of plan_tests .

int plan_skip_all(string name, string reason);
name (I)

name for this test plan

reason (I)

the string indicating why you can’t run any tests


plan_tests

Announce the number of tests you plan to run.

This should be the first call in your test program: it allows tracing

int plan_tests(string name, unsigned tests);
name (I)

name for this test plan

tests (I)

the number of tests


skip

Print a diagnostic message (use instead of printf/fprintf).

Sometimes tests cannot be run because the test system lacks some feature: you should explicitly document that you’re skipping tests using skip().

From the Test::More documentation:

If it’s something the user might not be able to do, use SKIP. This includes optional modules that aren’t installed, running under an OS that doesn’t have some feature (like fork() or symlinks), or maybe you need an Internet connection and one isn’t available.

int skip(unsigned n, string fmt, ...);
n (I)

number of tests you’re skipping

fmt (I)

the format of the reason you’re skipping the tests


todo_start

Mark tests that you expect to fail.

It’s extremely useful to write tests before you implement the matching fix or features: surround these tests by todo_start / todo_end . These tests will still be run, but with additional output that indicates that they are expected to fail.

This way, should a test start to succeed unexpectedly, tools like prove(1) will indicate this and you can move the test out of the todo block. This is much more useful than simply commenting out (or '#if 0') the tests.

From the Test::More documentation:

If it’s something the programmer hasn’t done yet, use TODO. This is for any code you haven’t written yet, or bugs you have yet to fix, but want to put tests in your testing script (always a good idea).

void todo_start(string fmt, ...);
fmt (I)

the reason they currently fail


fwTap library examples

Example 1. Using diag
diag("Now running complex tests");

.Using except

try {
  x = do_something();
  if (!checkable(x) || check_value(x))
    pass("do_something() returned a valid value");
  else
    fail("do_something() returned an invalid value");
} catch {
  except(getLastException(), "do_something() raised an exception");
}

.Using ok

ok(init_subsystem() == 1);
ok(init_subsystem() == 0, "Second initialization should fail");

.Using pass and fail

x = do_something();
if (!checkable(x) || check_value(x))
  pass("do_something() returned a valid value");
else
  fail("do_something() returned an invalid value");

.Using plan_no_plan

plan_no_plan("myComponent");
while (random() % 2)
  ok(some_test());
exit(exit_status());

.Using plan_skip_all

if (!have_some_feature) {
  plan_skip_all("myComponent", "Need some_feature support");
  exit(exit_status());
}
plan_tests("myComponent", 13);

.Using plan_tests

plan_tests("mycomponent", 13);

.Using skip

if(HAVE_SOME_FEATURE)
  ok(test_some_feature());
else
  skip(1, "Don't have SOME_FEATURE");

.Using todo_start

todo_start("dwim() not returning true yet");
ok(dwim(), "Did what the user wanted");
todo_end();