Linux C++ drivers

Koheron SDK V1 documentation. You are reading the documentation for the V1 branch. It uses CFG=.../config.mk, memory.yml, Linux FPGA Manager and device-tree overlays. It is not backward-compatible with 0.x instruments.

Goal

Write the server-side API for an instrument. A C++ driver is compiled into the instrument server. Its public member functions become commands that can be called from Python or TypeScript clients.

A driver usually does three things:

  1. Access generated memory regions from memory.hpp.
  2. Read and write FPGA registers.
  3. Expose a clean command API to clients.

Add a driver to an instrument

List driver files in config.mk:

DRIVERS += $(PROJECT_PATH)/fft.hpp
DRIVERS += $(PROJECT_PATH)/fft.cpp

Board-level drivers can be included from a board make fragment:

include $(SDK_PATH)/boards/alpha250/drivers/drivers.mk

Build only the server while developing C++ code:

make -j CFG=examples/alpha250/fft/config.mk server

Include runtime and memory headers

A driver can include the runtime driver manager and generated memory access headers:

#include "server/runtime/driver_manager.hpp"
#include "server/hardware/memory_manager.hpp"

Board drivers may be included as needed:

#include "boards/alpha250/drivers/clock-generator.hpp"
#include "boards/alpha250/drivers/power-monitor.hpp"

Minimal driver skeleton

#include "server/runtime/driver_manager.hpp"
#include "server/hardware/memory_manager.hpp"

#include <cstdint>

class MyInstrument
{
  public:
    MyInstrument()
    : ctl(hw::get_memory<mem::control>())
    , sts(hw::get_memory<mem::status>())
    {}

    void set_output(uint32_t value) {
        ctl.write<reg::digital_outputs>(value);
    }

    uint32_t get_input() {
        return sts.read<reg::digital_inputs>();
    }

  private:
    hw::Memory<mem::control>& ctl;
    hw::Memory<mem::status>& sts;
};

The exact mem::... and reg::... identifiers come from memory.yml.

Memory access flow

A memory region declared in memory.yml:

memory:
  - name: control
    offset: 0x4000_0000
    range: 4K
    registers:
    - digital_outputs

generates C++ identifiers such as mem::control and reg::digital_outputs.

Use the generated hw::Memory object to read and write registers.

Reuse another driver

Drivers can access other drivers through the runtime driver manager:

auto& power = rt::get_driver<PowerMonitor>();
auto supplies = power.get_supplies_ui();

This lets instrument drivers reuse board drivers for clocks, ADCs, DACs, power monitors and sensors.

Command API design

Public driver methods are exported in generated driver metadata. During the server build, the SDK generates C++ dispatch headers under tmp/<project>/server/, builds a metadata dumper, and writes tmp/<project>/server/drivers.json; the instrument ZIP packages that file as drivers.json.

Clients use this metadata to map class and function names to command IDs.

Examples of exported methods:

void set_input_channel(uint32_t channel);
uint32_t get_fft_size() const;
std::array<int32_t, prm::n_adc> get_adc_raw_data(uint32_t n_avg);
auto get_control_parameters();

Use fixed-width integer types such as uint32_t where possible to avoid ambiguity in the client protocol.

Return types

Clients support common scalar types such as integers, floating-point values and booleans. They also support arrays, vectors, tuples and strings when the C++ return type is exposed in the generated command metadata.

Full archive build

Build the complete instrument archive with:

make -j CFG=examples/alpha250/fft/config.mk

See also

[email protected]