μHAL
μHAL Design

A primer on the hardware with which this project interfaces should be useful in understanding the design choices made.

Low level interface

The low level interface consists simply of a struct pcie_bars object, which allows its user to read and write from BARs 2 and 4 (BAR0 is considered an implementation detail) using the functions defined in util/pcie.h.

BAR4 can also be accessed when struct pcie_bars points to a serial port, which is used when debugging boards outside of a μTCA crate. The serial port doesn't allow access to BAR2, though.

All other functionality of this project depends on this low level interface.

Basic abstractions

SDB access

SDB access is provided by the functions in util/util_sdb.h. These functions are higher level abstractions on top of the libsdbfs project.

Unlike HALCS, which iterated through the SBD and launched a handler for each core it found, the only function in this library which iterates over the SDB is the one which prints its contents. For any other use, users of the library will search for specific cores (with their index in depth-first traversal) with read_sdb(). In order to know which cores should be available in a given board, users should consult the build information provided by get_synthesis_info().

While this implementation might seem less flexible at first, it's impossible to escape from the need to know what cores are available when using this library on an IOC: records for them must be instantiated and have the proper names. Therefore, this isn't adding any new limitations to an IOC.

FPGA core access

For the most part, acess to each FPGA core is split into two classes under the core's namespace (e.g. afc_timing): one is the "decoder", afc_timing::Core, the other is the "controller", afc_timing::Controller. Usually, the decoder implements read-only access, while the controller has to be read-write.

The base classes involved in this are class RegisterDecoderBase, class RegisterDecoder, class RegisterController and class RegisterDecoderController. The implementation for each core is under the modules/ directory.

Register maps

The register map for each module is encoded as a C struct, which is provided by the header generated by cheby. Headers generated by wbgen2 don't include a C struct, so it is necessary to define this struct ourselves.

These register fields are decoded using bitmasks — and only bitmasks, we don't use shift macros and instead obtain the shift directly from the mask, in order to simplify boilerplate code and avoid mismatches — provided by the generated headers, using the functions from util/util-bits.h.

RegisterController and RegisterDecoderController

Before class RegisterDecoderController was created, controllers were mostly developed manually, duplicating the correspondence between the register fields we are interested in and their location in the register map, the mask used to obtain them, and how to interpret their data. This also made it necessary for library users to address fields differently when reading or writing them.

class RegisterDecoderController removes this need, requiring only a small amount of boilerplate to expose writing into register fields under a common interface.

Unconventional controllers

Some FPGA cores couldn't be implemented simply by decoding and encoding register fields. Some of them are listed here: