File mutrig_config.h

File List > libmudaq > mutrig_config.h

Go to the documentation of this file

#ifndef MUTRIG_ODB_CONFIG_GENERATOR_H
#define MUTRIG_ODB_CONFIG_GENERATOR_H

#include <cstdint>
#include <iomanip>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

#include "../constants.h"
#include "odbxx.h"

namespace mutrig {

using midas::odb;

class ODBConfigGenerator {
public:
    struct SectionRange {
        const char* first;
        const char* last;
    };

    static constexpr SectionRange GLOBAL_RANGE{"gen_idle", "en_ch_evt_cnt"};
    static constexpr SectionRange TDC_RANGE{"dmon_sel", "latchbias"};
    static constexpr SectionRange CH_RANGE{"energy_c_en", "recv_all"};
    static constexpr SectionRange FOOTER_RANGE{"coin_xbar_lower_rx_ena", "lvds_tx_bias"};

    class BoundGenerator {
    public:
        explicit BoundGenerator(odb& config_mutrig) : m_config(config_mutrig) {}

        std::size_t total_bits() {
            return section_bits(m_config["Nbits"], GLOBAL_RANGE)
                 + section_bits(m_config["Nbits"], TDC_RANGE)
                 + NMUTRIGCHANNELS * section_bits(m_config["Nbits"], CH_RANGE)
                 + footer_bits();
        }

        std::size_t total_bytes() {
            return (total_bits() + 7u) / 8u;
        }

        bool validate() {
            return total_bits() == N_BITS_MUTRIG && total_bytes() == N_BYTES_MUTRIG;
        }

        std::vector<std::uint8_t> generate(std::uint32_t asic_idx) {
            std::vector<std::uint8_t> bitpattern(total_bytes(), 0);
            std::size_t offset = 0;

            offset += write_global(m_config, bitpattern, offset);
            offset += write_tdc(m_config, bitpattern, offset, asic_idx);
            offset += write_channels(m_config, bitpattern, offset, asic_idx);
            offset += write_footer(m_config, bitpattern, offset, asic_idx);

            if (offset != total_bits()) {
                throw std::runtime_error("MuTRiG config size mismatch while generating ASIC config");
            }

            return bitpattern;
        }

        std::map<std::uint32_t, std::vector<std::uint8_t>> generate_enabled(odb& settings) {
            std::map<std::uint32_t, std::vector<std::uint8_t>> out;
            odb& daq = settings["DAQ"]["Links"];

            const std::size_t nfebs = daq["FEBsActive"].size();
            for (std::size_t feb_idx = 0; feb_idx < nfebs; ++feb_idx) {
                const bool feb_active = daq["FEBsActive"][feb_idx];
                const bool feb_is_mutrig = daq["FEBsMutrig"][feb_idx];
                if (!feb_active || !feb_is_mutrig) {
                    continue;
                }

                const std::uint16_t asic_mask = daq["ASICMask"][feb_idx];
                for (std::uint32_t local_asic = 0; local_asic < N_MUTRIGS_PER_FEB; ++local_asic) {
                    if (((asic_mask >> local_asic) & 0x1u) == 0u) {
                        continue;
                    }

                    const std::uint32_t global_asic =
                        static_cast<std::uint32_t>(feb_idx) * N_MUTRIGS_PER_FEB + local_asic;
                    out[global_asic] = generate(global_asic);
                }
            }

            return out;
        }

        std::string test_json(std::uint32_t asic_idx = 0) {
            const auto bytes = generate(asic_idx);

            std::ostringstream os;
            os << "{\n";
            os << "  \"layout\": {\n";
            os << "    \"n_mutrigs_per_feb\": " << N_MUTRIGS_PER_FEB << ",\n";
            os << "    \"n_mutrig_channels\": " << NMUTRIGCHANNELS << ",\n";
            os << "    \"n_bytes_mutrig_constant\": " << N_BYTES_MUTRIG << ",\n";
            os << "    \"n_bits_mutrig_constant\": " << N_BITS_MUTRIG << ",\n";
            os << "    \"n_bytes_mutrig_from_layout\": " << total_bytes() << ",\n";
            os << "    \"n_bits_mutrig_from_layout\": " << total_bits() << ",\n";
            os << "    \"layout_matches_constants\": " << (validate() ? "true" : "false") << "\n";
            os << "  },\n";

            os << "  \"sections\": {\n";
            os << "    \"header_bits\": " << section_bits(m_config["Nbits"], GLOBAL_RANGE) << ",\n";
            os << "    \"tdc_bits\": " << section_bits(m_config["Nbits"], TDC_RANGE) << ",\n";
            os << "    \"channel_bits\": " << section_bits(m_config["Nbits"], CH_RANGE) << ",\n";
            os << "    \"footer_bits\": " << footer_bits() << "\n";
            os << "  },\n";

            os << "  \"defaults\": {\n";
            write_scalar_section_json(os,
                          "Global",
                          m_config["Global"],
                          m_config["Nbits"],
                          m_config["Inverted"],
                          GLOBAL_RANGE);
            os << ",\n";

            write_scalar_section_json(os,
                                    "TDCs",
                                    m_config["TDCs"],
                                    m_config["Nbits"],
                                    m_config["Inverted"],
                                    TDC_RANGE,
                                    asic_idx);
            os << ",\n";

            write_scalar_section_json(os,
                                    "Channels",
                                    m_config["Channels"],
                                    m_config["Nbits"],
                                    m_config["Inverted"],
                                    CH_RANGE,
                                    asic_idx * NMUTRIGCHANNELS);
            os << ",\n";

            write_scalar_section_json(os,
                                    "Footer",
                                    m_config["TDCs"],
                                    m_config["Nbits"],
                                    m_config["Inverted"],
                                    FOOTER_RANGE,
                                    asic_idx,
                                    &m_config["TDCs"],
                                    asic_idx);

            os << " , \"bitpattern\": {\n";
            os << "    \"asic_index\": " << asic_idx << ",\n";
            os << "    \"bytes\": " << bytes.size() << ",\n";
            os << "    \"hex_le\": \"" << to_hex_le(bytes) << "\",\n";
            os << "    \"hex_be\": \"" << to_hex_be(bytes) << "\"\n";
            os << "  }\n";
            os << "}}\n";

            return os.str();
        }

    private:
        odb& m_config;

        static std::size_t section_bits(odb& nbits, const SectionRange& range) {
            bool enabled = false;
            std::size_t total = 0;

            for (odb& subkey : nbits) {
                const std::string name = subkey.get_name();
                if (name == range.first) {
                    enabled = true;
                }
                if (!enabled) {
                    continue;
                }

                total += static_cast<std::uint32_t>(subkey);
                if (name == range.last) {
                    break;
                }
            }

            return total;
        }

        std::size_t footer_bits() {
            bool enabled = false;
            std::size_t bits = 0;

            for (odb& subkey : m_config["Nbits"]) {
                const std::string name = subkey.get_name();
                if (name == FOOTER_RANGE.first) {
                    enabled = true;
                }
                if (!enabled) {
                    continue;
                }

                if (name == "coin_mat") {
                    bits += static_cast<std::uint32_t>(subkey) * NMUTRIGCHANNELS;
                } else {
                    bits += static_cast<std::uint32_t>(subkey);
                }

                if (name == FOOTER_RANGE.last) {
                    break;
                }
            }

            return bits;
        }

        static std::size_t write_global(odb& config,
                                        std::vector<std::uint8_t>& bitpattern,
                                        std::size_t bit_offset) {
            return write_range(config["Nbits"], config["Global"], config["Inverted"],
                               bitpattern, bit_offset, 0, GLOBAL_RANGE);
        }

        static std::size_t write_tdc(odb& config,
                                     std::vector<std::uint8_t>& bitpattern,
                                     std::size_t bit_offset,
                                     std::uint32_t asic_idx) {
            return write_range(config["Nbits"], config["TDCs"], config["Inverted"],
                               bitpattern, bit_offset, asic_idx, TDC_RANGE);
        }

        static std::size_t write_channels(odb& config,
                                          std::vector<std::uint8_t>& bitpattern,
                                          std::size_t bit_offset,
                                          std::uint32_t asic_idx) {
            std::size_t bits_written = 0;

            for (std::uint32_t ch = 0; ch < NMUTRIGCHANNELS; ++ch) {
                const std::uint32_t global_channel = asic_idx * NMUTRIGCHANNELS + ch;
                bits_written += write_range(config["Nbits"], config["Channels"], config["Inverted"],
                                            bitpattern, bit_offset + bits_written,
                                            global_channel, CH_RANGE);
            }

            return bits_written;
        }

        static std::size_t write_footer(odb& config,
                                        std::vector<std::uint8_t>& bitpattern,
                                        std::size_t bit_offset,
                                        std::uint32_t asic_idx) {
            // coin_mat stays in TDCs in this project
            return write_range(config["Nbits"], config["TDCs"], config["Inverted"],
                               bitpattern, bit_offset, asic_idx, FOOTER_RANGE,
                               &config["TDCs"], asic_idx);
        }

        static std::size_t write_range(odb& nbits,
                                       odb& values,
                                       odb& inverted,
                                       std::vector<std::uint8_t>& bitpattern,
                                       std::size_t bit_offset,
                                       std::uint32_t index,
                                       const SectionRange& range,
                                       odb* coin_mat_values = nullptr,
                                       std::uint32_t asic_idx_for_coinmat = 0) {
            const std::size_t start_offset = bit_offset;
            bool enabled = false;

            for (odb& subkey : nbits) {
                const std::string name = subkey.get_name();
                if (name == range.first) {
                    enabled = true;
                }
                if (!enabled) {
                    continue;
                }

                const std::uint32_t width = static_cast<std::uint32_t>(subkey);
                const bool inv = static_cast<bool>(inverted[name]);

                if (name == "tthresh_offset_0" || name == "tthresh_offset_1" || name == "tthresh_offset_2") {
                    const std::uint8_t tthresh_offset = values["tthresh_offset"][index];
                    std::uint32_t value = 0;
                    if (name == "tthresh_offset_0") value = (tthresh_offset >> 2) & 0x1u;
                    if (name == "tthresh_offset_1") value = (tthresh_offset >> 1) & 0x1u;
                    if (name == "tthresh_offset_2") value = (tthresh_offset >> 0) & 0x1u;
                    pack_value(bitpattern, bit_offset, value, width, inv);
                } else if (name == "coin_mat") {
                    if (coin_mat_values == nullptr) {
                        throw std::runtime_error("coin_mat requires source values");
                    }
                    for (std::uint32_t ch = 0; ch < NMUTRIGCHANNELS; ++ch) {
                        const std::uint32_t global_channel = asic_idx_for_coinmat * NMUTRIGCHANNELS + ch;
                        const std::uint32_t value = (*coin_mat_values)["coin_mat"][global_channel];
                        pack_value(bitpattern, bit_offset, value, width, inv);
                    }
                } else if (is_scalar_global(values, name)) {
                    const std::uint32_t value = static_cast<std::uint32_t>(values[name]);
                    pack_value(bitpattern, bit_offset, value, width, inv);
                } else {
                    const std::uint32_t value = static_cast<std::uint32_t>(values[name][index]);
                    pack_value(bitpattern, bit_offset, value, width, inv);
                }

                if (name == range.last) {
                    break;
                }
            }

            return bit_offset - start_offset;
        }

        static bool is_scalar_global(odb& values, const std::string& name) {
            return values.get_name() == "Global"
                && name != "ms_limits"
                && name != "ms_switch_sel"
                && name != "dmon_sel"
                && name != "dmon_sel_enable"
                && name != "dmon_sw";
        }

        static void pack_value(std::vector<std::uint8_t>& bitpattern,
                               std::size_t& bit_offset,
                               std::uint32_t value,
                               std::uint32_t width,
                               bool inverted_bits) {
            if (width < 32u && (value >> width) != 0u) {
                throw std::out_of_range("ODB value exceeds declared bit width");
            }

            for (std::uint32_t src_bit = 0; src_bit < width; ++src_bit) {
                const bool bit = ((value >> src_bit) & 0x1u) != 0u;
                const std::uint32_t dst_bit = !inverted_bits ? src_bit : (width - 1u - src_bit);

                const std::size_t absolute_bit = bit_offset + dst_bit;
                const std::size_t byte_index = absolute_bit / 8u;
                const std::size_t bit_index = absolute_bit % 8u;

                if (bit) {
                    bitpattern[byte_index] |= static_cast<std::uint8_t>(1u << bit_index);
                } else {
                    bitpattern[byte_index] &= static_cast<std::uint8_t>(~(1u << bit_index));
                }
            }

            bit_offset += width;
        }

        static std::string to_hex_le(const std::vector<std::uint8_t>& bytes) {
            std::ostringstream os;
            os << std::hex << std::setfill('0');
            for (std::uint8_t b : bytes) {
                os << std::setw(2) << static_cast<unsigned int>(b);
            }
            return os.str();
        }

        static std::string to_hex_be(const std::vector<std::uint8_t>& bytes) {
            std::ostringstream os;
            os << std::hex << std::setfill('0');
            for (auto it = bytes.rbegin(); it != bytes.rend(); ++it) {
                os << std::setw(2) << static_cast<unsigned int>(*it);
            }
            return os.str();
        }

        static void write_scalar_section_json(std::ostringstream& os,
                                            const std::string& section_name,
                                            odb& section,
                                            odb& nbits,
                                            odb& inverted,
                                            const SectionRange& range,
                                            std::uint32_t index = 0,
                                            odb* coin_mat_values = nullptr,
                                            std::uint32_t asic_idx_for_coinmat = 0)
        {
            os << "    \"" << section_name << "\": {\n";

            bool first = true;
            bool enabled = false;

            for (odb& subkey : nbits)
            {
                const std::string name = subkey.get_name();

                if (name == range.first)
                    enabled = true;

                if (!enabled)
                    continue;

                if (!first)
                    os << ",\n";
                first = false;

                const std::uint32_t width =
                    static_cast<std::uint32_t>(subkey);

                const bool inv = static_cast<bool>(inverted[name]);

                os << "      \"" << name << "\": ";

                // SPECIAL: tthresh_offset split
                if (name == "tthresh_offset_0" ||
                    name == "tthresh_offset_1" ||
                    name == "tthresh_offset_2")
                {
                    const std::uint8_t tthresh_offset =
                        section["tthresh_offset"][index];

                    std::uint32_t value = 0;

                    if (name == "tthresh_offset_0")
                        value = (tthresh_offset >> 2) & 0x1u;

                    if (name == "tthresh_offset_1")
                        value = (tthresh_offset >> 1) & 0x1u;

                    if (name == "tthresh_offset_2")
                        value = (tthresh_offset >> 0) & 0x1u;

                    os << "{ \"value\": " << value
                    << ", \"bits\": " << width
                    << ", \"inverted\": " << (inv ? "true" : "false")
                    << " }";
                }

                // SPECIAL: coin_mat (32 entries)
                else if (name == "coin_mat")
                {
                    if (coin_mat_values == nullptr)
                        throw std::runtime_error(
                            "coin_mat requires source values");

                    os << "[\n";

                    for (std::uint32_t ch = 0;
                        ch < NMUTRIGCHANNELS;
                        ++ch)
                    {
                        const std::uint32_t global_channel =
                            asic_idx_for_coinmat *
                            NMUTRIGCHANNELS + ch;

                        const std::uint32_t value =
                            static_cast<std::uint32_t>(
                                (*coin_mat_values)["coin_mat"]
                                                [global_channel]);

                        os << "        { \"channel\": "
                        << ch
                        << ", \"value\": "
                        << value
                        << ", \"bits\": "
                        << width
                        << ", \"inverted\": "
                        << (inv ? "true" : "false")
                        << " }";

                        if (ch + 1 != NMUTRIGCHANNELS)
                            os << ",";

                        os << "\n";
                    }

                    os << "      ]";
                }

                // GLOBAL scalar
                else if (is_scalar_global(section, name))
                {
                    const std::uint32_t value =
                        static_cast<std::uint32_t>(section[name]);

                    os << "{ \"value\": "
                    << value
                    << ", \"bits\": "
                    << width
                    << ", \"inverted\": "
                    << (inv ? "true" : "false")
                    << " }";
                }

                // NORMAL indexed field
                else
                {
                    const std::uint32_t value =
                        static_cast<std::uint32_t>(
                            section[name][index]);

                    os << "{ \"value\": "
                    << value
                    << ", \"bits\": "
                    << width
                    << ", \"inverted\": "
                    << (inv ? "true" : "false")
                    << " }";
                }

                if (name == range.last)
                    break;
            }

            os << "\n    }";
        }

        static void write_indexed_section_json(std::ostringstream& os,
                                       const std::string& section_name,
                                       odb& section,
                                       odb& nbits,
                                       std::uint32_t index) {
            os << "    \"" << section_name << "\": {\n";

            bool first = true;
            for (odb& subkey : section) {
                if (!first) {
                    os << ",\n";
                }
                first = false;

                const std::string name = subkey.get_name();

                os << "      \"" << name << "\": { ";

                if (subkey.size() > 0) {
                    os << "\"value\": "
                    << odb_value_to_json_value(subkey[index]);
                } else {
                    os << "\"value\": "
                    << odb_value_to_json_value(subkey);
                }

                os << ", \"bits\": " << static_cast<std::uint32_t>(nbits[name]);

                os << " }";
            }

            os << "\n    }";
        }

        template <typename T>
        static std::string odb_value_to_json_value(T&& value) {
            std::ostringstream os;

            try {
                const bool b = value;
                os << (b ? "true" : "false");
                return os.str();
            } catch (...) {}

            try {
                const std::int64_t i = value;
                os << i;
                return os.str();
            } catch (...) {}

            try {
                const std::uint64_t u = value;
                os << u;
                return os.str();
            } catch (...) {}

            try {
                const double d = value;
                os << d;
                return os.str();
            } catch (...) {}

            try {
                const std::string s = value;
                os << "\"" << s << "\"";
                return os.str();
            } catch (...) {}

            os << "\"<unsupported>\"";
            return os.str();
        }
    };
};

} // namespace mutrig

#endif // MUTRIG_ODB_CONFIG_GENERATOR_H