From 9538ab12a28efc0542689deb6f69732dab685309 Mon Sep 17 00:00:00 2001 From: tomeichlersmith Date: Mon, 27 Jul 2026 14:36:04 +0000 Subject: [PATCH 1/4] cache sipm/led settings in software - update tool to support fiberless in Bias menu again - make Bias no-copy so cache never needs to be copied --- app/tool/bias.cxx | 29 ++++---- include/pflib/Bias.h | 95 ++++++++++++-------------- include/pflib/HcalBackplane.h | 20 ++++-- include/pflib/HcalTarget.h | 21 ++++++ src/pflib/Bias.cxx | 90 +++++++++++++++++------- src/pflib/HcalBackplane.cxx | 18 +++-- src/pflib/bittware/HcalBackplane.cxx | 2 +- src/pflib/zcu/HGCROCBoardFiberless.cxx | 10 +-- src/pflib/zcu/HcalBackplane.cxx | 2 +- test/on_zcu/read_bias.cxx | 7 +- 10 files changed, 188 insertions(+), 106 deletions(-) create mode 100644 include/pflib/HcalTarget.h diff --git a/app/tool/bias.cxx b/app/tool/bias.cxx index 8cc0acbe1..7833a944e 100644 --- a/app/tool/bias.cxx +++ b/app/tool/bias.cxx @@ -5,16 +5,17 @@ * * Only usable for HcalBackplane type targets. */ -#include "pflib/HcalBackplane.h" +#include "pflib/HcalTarget.h" +#include "pflib/Bias.h" #include "pftool.h" ENABLE_LOGGING(); -static void bias(const std::string& cmd, pflib::HcalBackplane* pft) { +static void bias(const std::string& cmd, pflib::HcalTarget* pft) { static int iboard = 0; if (cmd == "STATUS") { iboard = pftool::readline_int("Which board? ", iboard); - pflib::Bias bias = pft->bias(iboard); + pflib::Bias& bias = pft->bias(iboard); double temp = bias.readTemp(); std::cout << "Board temperature: " << temp << " C" << std::endl; for (int ch = 0; ch < 16; ch++) { @@ -24,7 +25,7 @@ static void bias(const std::string& cmd, pflib::HcalBackplane* pft) { } if (cmd == "READ_SIPM") { iboard = pftool::readline_int("Which board? ", iboard); - pflib::Bias bias = pft->bias(iboard); + pflib::Bias& bias = pft->bias(iboard); int ich = pftool::readline_int( "Which (zero-indexed) channel? (-1 for all) ", iboard); if (ich == -1) { @@ -37,7 +38,7 @@ static void bias(const std::string& cmd, pflib::HcalBackplane* pft) { } if (cmd == "READ_LED") { iboard = pftool::readline_int("Which board? ", iboard); - pflib::Bias bias = pft->bias(iboard); + pflib::Bias& bias = pft->bias(iboard); int ich = pftool::readline_int( "Which (zero-indexed) channel? (-1 for all) ", iboard); if (ich == -1) { @@ -50,7 +51,7 @@ static void bias(const std::string& cmd, pflib::HcalBackplane* pft) { } if (cmd == "SET_SIPM") { iboard = pftool::readline_int("Which board? ", iboard); - pflib::Bias bias = pft->bias(iboard); + pflib::Bias& bias = pft->bias(iboard); int ich = pftool::readline_int( "Which (zero-indexed) channel? (-1 for all) ", iboard); uint16_t dac = pftool::readline_int("Which DAC value? ", 0); @@ -64,7 +65,7 @@ static void bias(const std::string& cmd, pflib::HcalBackplane* pft) { } if (cmd == "SET_LED") { iboard = pftool::readline_int("Which board? ", iboard); - pflib::Bias bias = pft->bias(iboard); + pflib::Bias& bias = pft->bias(iboard); int ich = pftool::readline_int( "Which (zero-indexed) channel? (-1 for all) ", iboard); uint16_t dac = pftool::readline_int("Which DAC value? ", 0); @@ -78,33 +79,31 @@ static void bias(const std::string& cmd, pflib::HcalBackplane* pft) { } if (cmd == "INIT") { iboard = pftool::readline_int("Which board? ", iboard); - pflib::Bias bias = pft->bias(iboard); - bias.initialize(); + pft->bias(iboard).initialize(); } if (cmd == "READ_TEMP") { iboard = pftool::readline_int("Which board? ", iboard); - pflib::Bias bias = pft->bias(iboard); - double temp = bias.readTemp(); + double temp = pft->bias(iboard).readTemp(); std::cout << "Board temperature: " << temp << " C" << std::endl; } } static void render(Target* tgt) { - auto hcal = dynamic_cast(tgt); + auto hcal = dynamic_cast(tgt); if (not hcal) { pflib_log(error) - << "BIAS menu of commands only availabe for HcalBackplane targets."; + << "BIAS menu of commands only availabe for Hcal targets."; } } static void bias_wrapper(const std::string& cmd, Target* tgt) { - auto hcal = dynamic_cast(tgt); + auto hcal = dynamic_cast(tgt); if (hcal) { bias(cmd, hcal); } else { PFEXCEPTION_RAISE("NotImpl", "The BIAS menu of commands is only available for " - "HcalBackplane targets."); + "Hcal targets."); } } diff --git a/include/pflib/Bias.h b/include/pflib/Bias.h index 1662d10e8..9e614c951 100644 --- a/include/pflib/Bias.h +++ b/include/pflib/Bias.h @@ -49,6 +49,14 @@ class MAX5825 { */ MAX5825(std::shared_ptr i2c, uint8_t max_addr); + /** + * send the SW_RESET command to the MAX5825 + * + * "All CODE, DAC, and Control Register values are returned to + * their power-on reset values" + */ + void reset(); + /** * Get the settings for the DACs on this MAX * @@ -59,8 +67,6 @@ class MAX5825 { */ std::vector get(uint8_t channel); - void set(uint8_t channel, uint16_t data_bytes); - /** * Write a setting for the DACs on this MAX * @@ -73,54 +79,20 @@ class MAX5825 { * Since the chip expects the 12-bit settings to be the 12 MSBs * from these two concatenated data words. */ - // void set(uint8_t cmd, uint16_t data_bytes); - - /** - * get by DAC index - * - * The per-DAC commands return the 12-bit settings as the 12 MSBs - * of two adjacent concatenated bits. We do this concatenation - * in this file as well as determine how many bytes to request - * depending on if we are requesting a single DAC (i_dac <= 7) - * or if we are requesting all DACs (i_dac > 7). - * - * The per-DAC commands use the 4 MSBs as a command the 4 LSBs - * to make the DAC selection. Using the commands defined as - * static constants in this class, the combination of i_dac and - * command is simply adding them together, which is done here. - */ - // std::vector getByDAC(uint8_t i_dac, uint8_t cmd); + void set(uint8_t channel, uint16_t data_bytes); /** - * set by DAC index - * - * The per-DAC commands expect the 12-bit settings as the 12 MSBs - * of the two adjacent concatenated data bytes. We do the shifting - * inside of this function, so the twelve_bit_setting should be the - * value you want the DAC to be set at. + * Set reference voltage * - * The per-DAC commands use the 4 MSBs as a command the 4 LSBs - * to make the DAC selection. Using the commands defined as - * static constants in this class, the combination of i_dac and - * command is simply adding them together, which is done here. + * @param[in] level (0 - external, 1 - 2.5V, 2 - 2.048V, 3 - 4.096V) */ - // void setByDAC(uint8_t i_dac, uint8_t cmd, uint16_t twelve_bit_setting); - - /** Set reference voltage - * 0 - external - * 1 - 2.5V - * 2 - 2.048V - * 3 - 4.096V - */ - // void setRefVoltage(int level); + void setRefVoltage(int level); private: /// our connection std::shared_ptr i2c_; /// our addr on the chip uint8_t our_addr_; - /// our bus - // int bus_; }; // MAX5825 /** @@ -129,15 +101,18 @@ class MAX5825 { * two handle the 16 SiPM bias voltages. * * Both the LED and SiPM bias voltages are indexed from 0 to 15. - * To convert from that index to a chip and DAC index on that chip, - * just use integer division and modulus. + * The conversion from that index to a chip and DAC index on that chip + * is done in this class. * - * chip_index = int(index > 7) - * dac_index = index - 8*chip_index; + * The readback of the DAC settings in the MAX5825 seem to require + * a "repeated start" I2C transaction where the register to be read + * and the read command are sent before the next I2C stop. + * Originally pointed out by Lennart at Lund, this "repeated start" + * transaction *cannot* be done by the lpGBT, so *actual* readback + * of the DAC settings is not supported in fiberfull setups. */ class Bias { public: - /// DAC chip addresses static const uint8_t ADDR_LED_0; static const uint8_t ADDR_LED_1; static const uint8_t ADDR_SIPM_0; @@ -145,33 +120,53 @@ class Bias { public: /** - * Wrap an I2C class for communicating with all the DAC chips. + * Wrap I2C objects for communicating with all the DAC chips. * * The bus is 4 + , so we set the default to 4 for * the case where we only have one board with bus number 0. + * + * @param[in] bias_i2c I2C object for interacting with MAX5825 bias DACs + * @param[in] board_i2c I2C object for talking with HGCROC board periferials + * @param[in] use_cache whether to use the software cache for readback (true) + * or not (false). This is necessary for readback to function on fiberless + * setups. */ - Bias(std::shared_ptr bias_i2c, std::shared_ptr board_i2c); + Bias(std::shared_ptr bias_i2c, std::shared_ptr board_i2c, bool use_cache); + + /// dont copy construct this class + Bias(const Bias&) = delete; + /// dont copy assign this class + Bias& operator=(const Bias&) = delete; /** * Initialize to standard settings - * Reference voltage - 4.096V */ void initialize(); + + /** + * Read the temperature from the temp sensor on the HGCROC board + */ double readTemp(); int readSiPM(uint8_t i_sipm); int readLED(uint8_t i_led); void setSiPM(uint8_t i_sipm, uint16_t code); void setLED(uint8_t i_led, uint16_t code); - private: + /// I2C comms with bias DACs MAX5825 std::shared_ptr i2c_bias_; + /// I2C comms with HGCROC board periferials std::shared_ptr i2c_board_; - /// LED bias chips std::vector led_; /// SiPM bias chips std::vector sipm_; + /// whether to use the software cache of the settings + bool use_cache_; + /// cache of sipm settings + std::array sipm_cache_; + /// cache of led settings + std::array led_cache_; }; // Bias } // namespace pflib diff --git a/include/pflib/HcalBackplane.h b/include/pflib/HcalBackplane.h index 57b8de0e9..d255a8ca1 100644 --- a/include/pflib/HcalBackplane.h +++ b/include/pflib/HcalBackplane.h @@ -6,14 +6,14 @@ #include "pflib/Bias.h" #include "pflib/GPIO.h" #include "pflib/TRIG.h" -#include "pflib/Target.h" +#include "pflib/HcalTarget.h" namespace pflib { /** * representing an HcalBackplane */ -class HcalBackplane : public Target { +class HcalBackplane : public HcalTarget { public: /// virtual destructor since we'll be holding this as a Target virtual ~HcalBackplane() = default; @@ -28,8 +28,11 @@ class HcalBackplane : public Target { * @param[in] trig_lpgbt accessor to TRIG lpGBT * @param[in] hgcroc_boards bit-mask saying if a board is active/enabled (1) * or inactive/disabled (0) - the bit in position i represents board i + * @param[in] use_bias_cache use software cache of bias voltage settings + * instead of readback direct from chip, see Bias class for why this + * is necessary in fiberfull setups */ - void init(lpGBT& daq_lpgbt, lpGBT& trig_lpgbt, int hgcroc_boards); + void init(lpGBT& daq_lpgbt, lpGBT& trig_lpgbt, int hgcroc_boards, bool use_bias_cache); /** number of boards */ virtual int nrocs() override { return nhgcroc_; } @@ -56,7 +59,7 @@ class HcalBackplane : public Target { virtual ECON& econ(int which) override; /** Get an I2C interface for the given HGCROC board's bias bus */ - Bias bias(int which); + Bias& bias(int which) override; /** Get the GPIO object for debugging purposes */ virtual GPIO& gpio() { return *gpio_; } @@ -101,6 +104,15 @@ class HcalBackplane : public Target { struct HGCROCBoard { ROC roc; Bias bias; + /// constructor to forward constructor arguments to members + HGCROCBoard( + std::shared_ptr roc_i2c, + uint8_t roc_addr, + const std::string& roc_typename, + std::shared_ptr bias_i2c, + std::shared_ptr board_i2c, + bool bias_use_cache + ); }; /// the backplane can hold up to 4 HGCROC boards diff --git a/include/pflib/HcalTarget.h b/include/pflib/HcalTarget.h new file mode 100644 index 000000000..a108458f8 --- /dev/null +++ b/include/pflib/HcalTarget.h @@ -0,0 +1,21 @@ +#pragma once +#ifndef PFLIB_HCALTARGET_H +#define PFLIB_HCALTARGET_H + +#include "pflib/Target.h" + +namespace pflib { + +class Bias; + +/** + * An HcalTarget is a Target with access to the biasing chips + */ +class HcalTarget : public Target { + public: + virtual Bias& bias(int which) = 0; +}; + +} + +#endif diff --git a/src/pflib/Bias.cxx b/src/pflib/Bias.cxx index 69d0346ee..3cdb61c9f 100644 --- a/src/pflib/Bias.cxx +++ b/src/pflib/Bias.cxx @@ -31,6 +31,11 @@ std::vector MAX5825::get(uint8_t channel) { return retval; } +void MAX5825::reset() { + i2c_->set_bus_speed(100); + i2c_->general_write_read(our_addr_, {0x35, 0x96, 0x30}, 0); +} + void MAX5825::set(uint8_t channel, uint16_t code) { uint8_t cmd = (uint8_t)(0xB0 | (channel & 0x07)); @@ -42,16 +47,28 @@ void MAX5825::set(uint8_t channel, uint16_t code) { 0); } +void MAX5825::setRefVoltage(int level) { + if (level < 0 or level > 3) { + PFEXCEPTION_RAISE("BadLevel", + "The MAX5825 reference voltage setting needs to be 0, 1, 2, or 3. " + + std::to_string(level) + " is out of this range."); + } + uint8_t cmd = 0; + cmd |= 0x20; // tell MAX5825 we are configuring the REF + cmd |= 0x04; // turn on DAC + cmd |= static_cast(level & 0x3); // ref voltage + i2c_->set_bus_speed(100); + i2c_->general_write_read(our_addr_, {cmd, 0x00, 0x00}, 0); +} + /// DAC chip addresses taken from HGCROC specs const uint8_t Bias::ADDR_LED_0 = 0x18; const uint8_t Bias::ADDR_LED_1 = 0x1A; const uint8_t Bias::ADDR_SIPM_0 = 0x10; const uint8_t Bias::ADDR_SIPM_1 = 0x12; -Bias::Bias(std::shared_ptr i2c_bias, std::shared_ptr i2c_board) { - i2c_bias_ = i2c_bias; - i2c_board_ = i2c_board; - +Bias::Bias(std::shared_ptr i2c_bias, std::shared_ptr i2c_board, bool use_cache) + : i2c_bias_{i2c_bias}, i2c_board_{i2c_board}, use_cache_{use_cache} { led_.emplace_back(i2c_bias, Bias::ADDR_LED_0); led_.emplace_back(i2c_bias, Bias::ADDR_LED_1); sipm_.emplace_back(i2c_bias, Bias::ADDR_SIPM_0); @@ -59,31 +76,31 @@ Bias::Bias(std::shared_ptr i2c_bias, std::shared_ptr i2c_board) { } void Bias::initialize() { - // Reset all DAC:s - i2c_bias_->set_bus_speed(100); - i2c_bias_->general_write_read(0x10, {0x35, 0x96, 0x30}, 0); - i2c_bias_->general_write_read(0x12, {0x35, 0x96, 0x30}, 0); - i2c_bias_->general_write_read(0x14, {0x35, 0x96, 0x30}, 0); - i2c_bias_->general_write_read(0x18, {0x35, 0x96, 0x30}, 0); - - // Set internal ref on DAC to 4.096 V - i2c_bias_->set_bus_speed(100); - i2c_bias_->general_write_read(0x10, {0x27, 0x00, 0x00}, 0); - i2c_bias_->general_write_read(0x12, {0x27, 0x00, 0x00}, 0); - i2c_bias_->general_write_read(0x14, {0x27, 0x00, 0x00}, 0); - i2c_bias_->general_write_read(0x18, {0x27, 0x00, 0x00}, 0); - - // Set up the GPIO device MCP23008 + /// reset all CODEs, DACs, and CRs to zero + for (int i_chip{0}; i_chip < 2; i_chip++) { + led_[i_chip].reset(); + sipm_[i_chip].reset(); + } + sipm_cache_.fill(0); + led_cache_.fill(0); + + /// Set internal ref on DAC to 4.096 V + for (int i_chip{0}; i_chip < 2; i_chip++) { + led_[i_chip].setRefVoltage(3); + sipm_[i_chip].setRefVoltage(3); + } + + /// Set up the GPIO device MCP23008 i2c_board_->set_bus_speed(100); i2c_board_->general_write_read(0x20, {0x00, 0x70}, 0); - // Turn on the status LED + /// Turn on the status LED i2c_board_->general_write_read(0x20, {0x09, 0x80}, 0); - // Set up the board temperature sensor TMP101 + /// Set up the board temperature sensor TMP101 i2c_board_->general_write_read(0x4A, {0x01, 0x60}, 0); - // Set up the two onewire-to-I2C devices DS2482 + /// Set up the two onewire-to-I2C devices DS2482 // Reset i2c_board_->general_write_read(0x1C, {0xF0}, 0); // Standard speed, weak pull-up, no active pull-up @@ -105,27 +122,54 @@ double Bias::readTemp() { snprintf(cs, 10, "%d.%d", integer, decimal); std::string str = cs; return std::stod(str); - ; } int Bias::readSiPM(uint8_t channel) { + if (channel > 15) { + PFEXCEPTION_RAISE("BadChannel", + "Channel number " + std::to_string(channel) + + " is out of range for the bias chip"); + } + if (use_cache_) { + return sipm_cache_[static_cast(channel)]; + } int i_chip = (channel > 7); std::vector data = sipm_.at(i_chip).get((channel & 0x07)); return ((data.at(0) * 256 + data.at(1)) >> 4); } int Bias::readLED(uint8_t channel) { + if (channel > 15) { + PFEXCEPTION_RAISE("BadChannel", + "Channel number " + std::to_string(channel) + + " is out of range for the bias chip"); + } + if (use_cache_) { + return led_cache_[static_cast(channel)]; + } int i_chip = (channel > 7); std::vector data = led_.at(i_chip).get((channel & 0x07)); return ((data.at(0) * 256 + data.at(1)) >> 4); } void Bias::setSiPM(uint8_t channel, uint16_t code) { + if (channel > 15) { + PFEXCEPTION_RAISE("BadChannel", + "Channel number " + std::to_string(channel) + + " is out of range for the bias chip"); + } + sipm_cache_[channel] = code; int i_chip = (channel > 7); sipm_.at(i_chip).set((channel & 0x07), code); } void Bias::setLED(uint8_t channel, uint16_t code) { + if (channel > 15) { + PFEXCEPTION_RAISE("BadChannel", + "Channel number " + std::to_string(channel) + + " is out of range for the bias chip"); + } + led_cache_[channel] = code; int i_chip = (channel > 7); led_.at(i_chip).set((channel & 0x07), code); } diff --git a/src/pflib/HcalBackplane.cxx b/src/pflib/HcalBackplane.cxx index 68567fc70..afd95b608 100644 --- a/src/pflib/HcalBackplane.cxx +++ b/src/pflib/HcalBackplane.cxx @@ -18,8 +18,18 @@ HcalBackplane::HcalBackplane() { necon_ = 0; } +HcalBackplane::HGCROCBoard::HGCROCBoard( + std::shared_ptr roc_i2c, + uint8_t roc_addr, + const std::string& roc_typename, + std::shared_ptr bias_i2c, + std::shared_ptr board_i2c, + bool bias_use_cache + ) : roc{roc_i2c, roc_addr, roc_typename}, + bias{bias_i2c, board_i2c, bias_use_cache} {} + void HcalBackplane::init(lpGBT& daq_lpgbt, lpGBT& trig_lpgbt, - int hgcroc_boardmask) { + int hgcroc_boardmask, bool use_bias_cache) { // Load GPIO configuration for lpGBTs pflib::lpgbt::standard_config::setup_hcal_daq_gpio(daq_lpgbt); pflib::lpgbt::standard_config::setup_hcal_trig_gpio(trig_lpgbt); @@ -95,8 +105,8 @@ void HcalBackplane::init(lpGBT& daq_lpgbt, lpGBT& trig_lpgbt, nhgcroc_++; rocs_[ibd] = std::make_unique( - ROC(roc_i2c, (0x20 | (ibd * 8)), "sipm_rocv3b"), - Bias(bias_i2c, board_i2c)); + roc_i2c, (0x20 | (ibd * 8)), "sipm_rocv3b", + bias_i2c, board_i2c, use_bias_cache); i2c_[pflib::utility::string_format("HGCROC_%d", ibd)] = roc_i2c; i2c_[pflib::utility::string_format("BOARD_%d", ibd)] = board_i2c; i2c_[pflib::utility::string_format("BIAS_%d", ibd)] = bias_i2c; @@ -184,7 +194,7 @@ ECON& HcalBackplane::econ(int which) { return *(econs_[which]); } -Bias HcalBackplane::bias(int which) { +Bias& HcalBackplane::bias(int which) { if (which < 0 or which >= rocs_.size()) { PFEXCEPTION_RAISE( "InvalidROCid", diff --git a/src/pflib/bittware/HcalBackplane.cxx b/src/pflib/bittware/HcalBackplane.cxx index f861a49bf..1159a7691 100644 --- a/src/pflib/bittware/HcalBackplane.cxx +++ b/src/pflib/bittware/HcalBackplane.cxx @@ -30,7 +30,7 @@ class HcalBackplaneBW : public HcalBackplane { trig_lpgbt_ = std::make_unique(opto_["TRG"]->lpgbt_transport()); - this->init(*daq_lpgbt_, *trig_lpgbt_, board_mask); + this->init(*daq_lpgbt_, *trig_lpgbt_, board_mask, true /* use bias cache */); elinks_ = std::make_unique(itarget, dev); diff --git a/src/pflib/zcu/HGCROCBoardFiberless.cxx b/src/pflib/zcu/HGCROCBoardFiberless.cxx index 758278516..16156c3ba 100644 --- a/src/pflib/zcu/HGCROCBoardFiberless.cxx +++ b/src/pflib/zcu/HGCROCBoardFiberless.cxx @@ -7,7 +7,7 @@ #include "pflib/Exception.h" #include "pflib/GPIO.h" #include "pflib/I2C_Linux.h" -#include "pflib/Target.h" +#include "pflib/HcalTarget.h" #include "pflib/packing/DAQSampleHeader.h" #include "pflib/packing/ECONDFormatter.h" #include "pflib/zcu/UIO.h" @@ -169,7 +169,7 @@ void FiberlessCapture::advanceLinkReadPtr() { uio_.rmw(ADDR_TOP_CTL, MASK_ADVANCE_FIFO, MASK_ADVANCE_FIFO); } -class HcalFiberless : public Target { +class HcalFiberless : public HcalTarget { public: static constexpr const char* GPO_HGCROC_RESET_HARD = "HGCROC_HARD_RSTB"; static constexpr const char* GPO_HGCROC_RESET_SOFT = "HGCROC_SOFT_RSTB"; @@ -189,7 +189,7 @@ class HcalFiberless : public Target { PFEXCEPTION_RAISE("Invalid", "No ECONs connected for Fiberless targets."); } - virtual Bias bias(int which) { + Bias& bias(int which) override { if (which == 0) return *bias_; PFEXCEPTION_RAISE("NoMore", "Only one bias board (index=0) for fiberless setup."); @@ -216,7 +216,7 @@ class HcalFiberless : public Target { virtual bool have_econ(int iecon) const override { return false; } virtual std::vector econ_ids() const override { return {}; } - HcalFiberless() : Target() { + HcalFiberless() { auto i2croc = std::shared_ptr(new I2C_Linux("/dev/i2c-24")); if (not i2croc) { PFEXCEPTION_RAISE("I2CError", "Could not open ROC I2C bus"); @@ -227,7 +227,7 @@ class HcalFiberless : public Target { } roc_ = std::make_unique(i2croc, 0x20, "sipm_rocv3b"); - bias_ = std::make_unique(i2cboard, i2cboard); + bias_ = std::make_unique(i2cboard, i2cboard, false /*use bias cache*/); gpio_.reset(make_GPIO_HcalHGCROCZCU()); diff --git a/src/pflib/zcu/HcalBackplane.cxx b/src/pflib/zcu/HcalBackplane.cxx index 39f3eb8e9..8186d70b6 100644 --- a/src/pflib/zcu/HcalBackplane.cxx +++ b/src/pflib/zcu/HcalBackplane.cxx @@ -29,7 +29,7 @@ class HcalBackplaneZCU : public HcalBackplane { trig_lpgbt_ = std::make_unique(opto_["TRG"]->lpgbt_transport()); - this->init(*daq_lpgbt_, *trig_lpgbt_, board_mask); + this->init(*daq_lpgbt_, *trig_lpgbt_, board_mask, true /* use bias cache */); elinks_ = std::make_unique(&(*daq_lpgbt_), &(*trig_lpgbt_), itarget); diff --git a/test/on_zcu/read_bias.cxx b/test/on_zcu/read_bias.cxx index b06a3f926..6eed0bfb7 100644 --- a/test/on_zcu/read_bias.cxx +++ b/test/on_zcu/read_bias.cxx @@ -2,14 +2,15 @@ #include #include "hgcroc_connection.h" -#include "pflib/HcalBackplane.h" +#include "pflib/HcalTarget.h" +#include "pflib/Bias.h" BOOST_AUTO_TEST_SUITE(read_bias) void read_biases() { - auto hcal = dynamic_cast(hgcroc_connection::tgt.get()); + auto hcal = dynamic_cast(hgcroc_connection::tgt.get()); if (not hcal) return; - pflib::Bias bias = hcal->bias(0); + pflib::Bias& bias = hcal->bias(0); for (int i = 0; i < 16; i++) { bias.readLED(i); bias.readSiPM(i); From fd44b90a3e8f9112707eff2f4a63d361fce94960 Mon Sep 17 00:00:00 2001 From: tomeichlersmith Date: Mon, 27 Jul 2026 15:44:06 +0000 Subject: [PATCH 2/4] use optional in cache to be transparent about when we don't know something print ???? when we are unsure on what the value is since we don't have readback abilities --- app/tool/bias.cxx | 9 +++++++++ include/pflib/Bias.h | 10 ++++++---- src/pflib/Bias.cxx | 16 ++++++++-------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/app/tool/bias.cxx b/app/tool/bias.cxx index 7833a944e..463ea0aff 100644 --- a/app/tool/bias.cxx +++ b/app/tool/bias.cxx @@ -11,6 +11,15 @@ ENABLE_LOGGING(); +std::ostream& operator<<(std::ostream& o, std::optional val) { + if (val) { + o << val.value(); + } else { + o << "????"; + } + return o; +} + static void bias(const std::string& cmd, pflib::HcalTarget* pft) { static int iboard = 0; if (cmd == "STATUS") { diff --git a/include/pflib/Bias.h b/include/pflib/Bias.h index 9e614c951..b26705b26 100644 --- a/include/pflib/Bias.h +++ b/include/pflib/Bias.h @@ -4,6 +4,7 @@ #include #include +#include #include "pflib/I2C.h" @@ -113,6 +114,7 @@ class MAX5825 { */ class Bias { public: + static const int N_CHANNELS = 16; static const uint8_t ADDR_LED_0; static const uint8_t ADDR_LED_1; static const uint8_t ADDR_SIPM_0; @@ -148,8 +150,8 @@ class Bias { */ double readTemp(); - int readSiPM(uint8_t i_sipm); - int readLED(uint8_t i_led); + std::optional readSiPM(uint8_t i_sipm); + std::optional readLED(uint8_t i_led); void setSiPM(uint8_t i_sipm, uint16_t code); void setLED(uint8_t i_led, uint16_t code); private: @@ -164,9 +166,9 @@ class Bias { /// whether to use the software cache of the settings bool use_cache_; /// cache of sipm settings - std::array sipm_cache_; + std::array, N_CHANNELS> sipm_cache_; /// cache of led settings - std::array led_cache_; + std::array, N_CHANNELS> led_cache_; }; // Bias } // namespace pflib diff --git a/src/pflib/Bias.cxx b/src/pflib/Bias.cxx index 3cdb61c9f..d30b42c62 100644 --- a/src/pflib/Bias.cxx +++ b/src/pflib/Bias.cxx @@ -124,28 +124,28 @@ double Bias::readTemp() { return std::stod(str); } -int Bias::readSiPM(uint8_t channel) { - if (channel > 15) { +std::optional Bias::readSiPM(uint8_t channel) { + if (channel >= N_CHANNELS) { PFEXCEPTION_RAISE("BadChannel", "Channel number " + std::to_string(channel) + " is out of range for the bias chip"); } if (use_cache_) { - return sipm_cache_[static_cast(channel)]; + return sipm_cache_[channel]; } int i_chip = (channel > 7); std::vector data = sipm_.at(i_chip).get((channel & 0x07)); return ((data.at(0) * 256 + data.at(1)) >> 4); } -int Bias::readLED(uint8_t channel) { - if (channel > 15) { +std::optional Bias::readLED(uint8_t channel) { + if (channel >= N_CHANNELS) { PFEXCEPTION_RAISE("BadChannel", "Channel number " + std::to_string(channel) + " is out of range for the bias chip"); } if (use_cache_) { - return led_cache_[static_cast(channel)]; + return led_cache_[channel]; } int i_chip = (channel > 7); std::vector data = led_.at(i_chip).get((channel & 0x07)); @@ -153,7 +153,7 @@ int Bias::readLED(uint8_t channel) { } void Bias::setSiPM(uint8_t channel, uint16_t code) { - if (channel > 15) { + if (channel >= N_CHANNELS) { PFEXCEPTION_RAISE("BadChannel", "Channel number " + std::to_string(channel) + " is out of range for the bias chip"); @@ -164,7 +164,7 @@ void Bias::setSiPM(uint8_t channel, uint16_t code) { } void Bias::setLED(uint8_t channel, uint16_t code) { - if (channel > 15) { + if (channel >= N_CHANNELS) { PFEXCEPTION_RAISE("BadChannel", "Channel number " + std::to_string(channel) + " is out of range for the bias chip"); From 91370fdc6773b4a67598a57d56c443b73c81cb63 Mon Sep 17 00:00:00 2001 From: tomeichlersmith Date: Mon, 27 Jul 2026 15:56:02 +0000 Subject: [PATCH 3/4] fixup printing and add warning message --- app/tool/bias.cxx | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/app/tool/bias.cxx b/app/tool/bias.cxx index 463ea0aff..4fab2032b 100644 --- a/app/tool/bias.cxx +++ b/app/tool/bias.cxx @@ -13,7 +13,7 @@ ENABLE_LOGGING(); std::ostream& operator<<(std::ostream& o, std::optional val) { if (val) { - o << val.value(); + o << std::setw(4) << val.value(); } else { o << "????"; } @@ -39,10 +39,10 @@ static void bias(const std::string& cmd, pflib::HcalTarget* pft) { "Which (zero-indexed) channel? (-1 for all) ", iboard); if (ich == -1) { for (int i = 0; i < 16; i++) { - std::cout << "Channel " << i << ": " << bias.readSiPM(i) << std::endl; + std::cout << "Channel " << std::setw(2) << i << ": " << bias.readSiPM(i) << std::endl; } } else { - std::cout << "Channel " << ich << ": " << bias.readSiPM(ich) << std::endl; + std::cout << "Channel " << std::setw(2) << ich << ": " << bias.readSiPM(ich) << std::endl; } } if (cmd == "READ_LED") { @@ -52,10 +52,10 @@ static void bias(const std::string& cmd, pflib::HcalTarget* pft) { "Which (zero-indexed) channel? (-1 for all) ", iboard); if (ich == -1) { for (int i = 0; i < 16; i++) { - std::cout << "Channel " << i << ": " << bias.readLED(i) << std::endl; + std::cout << "Channel " << std::setw(2) << i << ": " << bias.readLED(i) << std::endl; } } else { - std::cout << "Channel " << ich << ": " << bias.readLED(ich) << std::endl; + std::cout << "Channel " << std::setw(2) << ich << ": " << bias.readLED(ich) << std::endl; } } if (cmd == "SET_SIPM") { @@ -103,6 +103,17 @@ static void render(Target* tgt) { pflib_log(error) << "BIAS menu of commands only availabe for Hcal targets."; } + if (pftool::state.readout_config() != pftool::State::CFG_HCALFMC) { + std::cout << R"WARN( + We currently do not have the ability to readback the voltage settings + from the MAX5825 via the lpGBT. This means we cache the last value we + wrote to the board in the software. + A value of '????' means we haven't written to that channel of the board, + so we do not know what value that channel is set to. + In this case, either set the channel to the desired setting or check the + board manually with a multimeter. +)WARN" << std::endl; + } } static void bias_wrapper(const std::string& cmd, Target* tgt) { From 2ebe52e3200292f0f6a4c61803d798ef2f2770d5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Jul 2026 16:12:12 +0000 Subject: [PATCH 4/4] Apply clang-format --style=Google --- app/tool/bias.cxx | 18 ++++++----- include/pflib/Bias.h | 12 +++++--- include/pflib/HcalBackplane.h | 16 ++++------ include/pflib/HcalTarget.h | 2 +- src/pflib/Bias.cxx | 42 ++++++++++++++------------ src/pflib/HcalBackplane.cxx | 23 +++++++------- src/pflib/bittware/HcalBackplane.cxx | 3 +- src/pflib/zcu/HGCROCBoardFiberless.cxx | 5 +-- src/pflib/zcu/HcalBackplane.cxx | 3 +- test/on_zcu/read_bias.cxx | 2 +- 10 files changed, 66 insertions(+), 60 deletions(-) diff --git a/app/tool/bias.cxx b/app/tool/bias.cxx index 4fab2032b..2b4d05404 100644 --- a/app/tool/bias.cxx +++ b/app/tool/bias.cxx @@ -5,8 +5,9 @@ * * Only usable for HcalBackplane type targets. */ -#include "pflib/HcalTarget.h" #include "pflib/Bias.h" + +#include "pflib/HcalTarget.h" #include "pftool.h" ENABLE_LOGGING(); @@ -39,10 +40,12 @@ static void bias(const std::string& cmd, pflib::HcalTarget* pft) { "Which (zero-indexed) channel? (-1 for all) ", iboard); if (ich == -1) { for (int i = 0; i < 16; i++) { - std::cout << "Channel " << std::setw(2) << i << ": " << bias.readSiPM(i) << std::endl; + std::cout << "Channel " << std::setw(2) << i << ": " << bias.readSiPM(i) + << std::endl; } } else { - std::cout << "Channel " << std::setw(2) << ich << ": " << bias.readSiPM(ich) << std::endl; + std::cout << "Channel " << std::setw(2) << ich << ": " + << bias.readSiPM(ich) << std::endl; } } if (cmd == "READ_LED") { @@ -52,10 +55,12 @@ static void bias(const std::string& cmd, pflib::HcalTarget* pft) { "Which (zero-indexed) channel? (-1 for all) ", iboard); if (ich == -1) { for (int i = 0; i < 16; i++) { - std::cout << "Channel " << std::setw(2) << i << ": " << bias.readLED(i) << std::endl; + std::cout << "Channel " << std::setw(2) << i << ": " << bias.readLED(i) + << std::endl; } } else { - std::cout << "Channel " << std::setw(2) << ich << ": " << bias.readLED(ich) << std::endl; + std::cout << "Channel " << std::setw(2) << ich << ": " + << bias.readLED(ich) << std::endl; } } if (cmd == "SET_SIPM") { @@ -100,8 +105,7 @@ static void bias(const std::string& cmd, pflib::HcalTarget* pft) { static void render(Target* tgt) { auto hcal = dynamic_cast(tgt); if (not hcal) { - pflib_log(error) - << "BIAS menu of commands only availabe for Hcal targets."; + pflib_log(error) << "BIAS menu of commands only availabe for Hcal targets."; } if (pftool::state.readout_config() != pftool::State::CFG_HCALFMC) { std::cout << R"WARN( diff --git a/include/pflib/Bias.h b/include/pflib/Bias.h index b26705b26..e5fe8446f 100644 --- a/include/pflib/Bias.h +++ b/include/pflib/Bias.h @@ -3,8 +3,8 @@ #include -#include #include +#include #include "pflib/I2C.h" @@ -52,8 +52,8 @@ class MAX5825 { /** * send the SW_RESET command to the MAX5825 - * - * "All CODE, DAC, and Control Register values are returned to + * + * "All CODE, DAC, and Control Register values are returned to * their power-on reset values" */ void reset(); @@ -133,7 +133,8 @@ class Bias { * or not (false). This is necessary for readback to function on fiberless * setups. */ - Bias(std::shared_ptr bias_i2c, std::shared_ptr board_i2c, bool use_cache); + Bias(std::shared_ptr bias_i2c, std::shared_ptr board_i2c, + bool use_cache); /// dont copy construct this class Bias(const Bias&) = delete; @@ -144,7 +145,7 @@ class Bias { * Initialize to standard settings */ void initialize(); - + /** * Read the temperature from the temp sensor on the HGCROC board */ @@ -154,6 +155,7 @@ class Bias { std::optional readLED(uint8_t i_led); void setSiPM(uint8_t i_sipm, uint16_t code); void setLED(uint8_t i_led, uint16_t code); + private: /// I2C comms with bias DACs MAX5825 std::shared_ptr i2c_bias_; diff --git a/include/pflib/HcalBackplane.h b/include/pflib/HcalBackplane.h index d255a8ca1..12616ee67 100644 --- a/include/pflib/HcalBackplane.h +++ b/include/pflib/HcalBackplane.h @@ -5,8 +5,8 @@ #include "pflib/Bias.h" #include "pflib/GPIO.h" -#include "pflib/TRIG.h" #include "pflib/HcalTarget.h" +#include "pflib/TRIG.h" namespace pflib { @@ -32,7 +32,8 @@ class HcalBackplane : public HcalTarget { * instead of readback direct from chip, see Bias class for why this * is necessary in fiberfull setups */ - void init(lpGBT& daq_lpgbt, lpGBT& trig_lpgbt, int hgcroc_boards, bool use_bias_cache); + void init(lpGBT& daq_lpgbt, lpGBT& trig_lpgbt, int hgcroc_boards, + bool use_bias_cache); /** number of boards */ virtual int nrocs() override { return nhgcroc_; } @@ -105,14 +106,9 @@ class HcalBackplane : public HcalTarget { ROC roc; Bias bias; /// constructor to forward constructor arguments to members - HGCROCBoard( - std::shared_ptr roc_i2c, - uint8_t roc_addr, - const std::string& roc_typename, - std::shared_ptr bias_i2c, - std::shared_ptr board_i2c, - bool bias_use_cache - ); + HGCROCBoard(std::shared_ptr roc_i2c, uint8_t roc_addr, + const std::string& roc_typename, std::shared_ptr bias_i2c, + std::shared_ptr board_i2c, bool bias_use_cache); }; /// the backplane can hold up to 4 HGCROC boards diff --git a/include/pflib/HcalTarget.h b/include/pflib/HcalTarget.h index a108458f8..4a5dfb90c 100644 --- a/include/pflib/HcalTarget.h +++ b/include/pflib/HcalTarget.h @@ -16,6 +16,6 @@ class HcalTarget : public Target { virtual Bias& bias(int which) = 0; }; -} +} // namespace pflib #endif diff --git a/src/pflib/Bias.cxx b/src/pflib/Bias.cxx index d30b42c62..b5144ad97 100644 --- a/src/pflib/Bias.cxx +++ b/src/pflib/Bias.cxx @@ -49,14 +49,15 @@ void MAX5825::set(uint8_t channel, uint16_t code) { void MAX5825::setRefVoltage(int level) { if (level < 0 or level > 3) { - PFEXCEPTION_RAISE("BadLevel", - "The MAX5825 reference voltage setting needs to be 0, 1, 2, or 3. " - + std::to_string(level) + " is out of this range."); + PFEXCEPTION_RAISE( + "BadLevel", + "The MAX5825 reference voltage setting needs to be 0, 1, 2, or 3. " + + std::to_string(level) + " is out of this range."); } uint8_t cmd = 0; - cmd |= 0x20; // tell MAX5825 we are configuring the REF - cmd |= 0x04; // turn on DAC - cmd |= static_cast(level & 0x3); // ref voltage + cmd |= 0x20; // tell MAX5825 we are configuring the REF + cmd |= 0x04; // turn on DAC + cmd |= static_cast(level & 0x3); // ref voltage i2c_->set_bus_speed(100); i2c_->general_write_read(our_addr_, {cmd, 0x00, 0x00}, 0); } @@ -67,8 +68,9 @@ const uint8_t Bias::ADDR_LED_1 = 0x1A; const uint8_t Bias::ADDR_SIPM_0 = 0x10; const uint8_t Bias::ADDR_SIPM_1 = 0x12; -Bias::Bias(std::shared_ptr i2c_bias, std::shared_ptr i2c_board, bool use_cache) - : i2c_bias_{i2c_bias}, i2c_board_{i2c_board}, use_cache_{use_cache} { +Bias::Bias(std::shared_ptr i2c_bias, std::shared_ptr i2c_board, + bool use_cache) + : i2c_bias_{i2c_bias}, i2c_board_{i2c_board}, use_cache_{use_cache} { led_.emplace_back(i2c_bias, Bias::ADDR_LED_0); led_.emplace_back(i2c_bias, Bias::ADDR_LED_1); sipm_.emplace_back(i2c_bias, Bias::ADDR_SIPM_0); @@ -126,9 +128,9 @@ double Bias::readTemp() { std::optional Bias::readSiPM(uint8_t channel) { if (channel >= N_CHANNELS) { - PFEXCEPTION_RAISE("BadChannel", - "Channel number " + std::to_string(channel) + - " is out of range for the bias chip"); + PFEXCEPTION_RAISE("BadChannel", "Channel number " + + std::to_string(channel) + + " is out of range for the bias chip"); } if (use_cache_) { return sipm_cache_[channel]; @@ -140,9 +142,9 @@ std::optional Bias::readSiPM(uint8_t channel) { std::optional Bias::readLED(uint8_t channel) { if (channel >= N_CHANNELS) { - PFEXCEPTION_RAISE("BadChannel", - "Channel number " + std::to_string(channel) + - " is out of range for the bias chip"); + PFEXCEPTION_RAISE("BadChannel", "Channel number " + + std::to_string(channel) + + " is out of range for the bias chip"); } if (use_cache_) { return led_cache_[channel]; @@ -154,9 +156,9 @@ std::optional Bias::readLED(uint8_t channel) { void Bias::setSiPM(uint8_t channel, uint16_t code) { if (channel >= N_CHANNELS) { - PFEXCEPTION_RAISE("BadChannel", - "Channel number " + std::to_string(channel) + - " is out of range for the bias chip"); + PFEXCEPTION_RAISE("BadChannel", "Channel number " + + std::to_string(channel) + + " is out of range for the bias chip"); } sipm_cache_[channel] = code; int i_chip = (channel > 7); @@ -165,9 +167,9 @@ void Bias::setSiPM(uint8_t channel, uint16_t code) { void Bias::setLED(uint8_t channel, uint16_t code) { if (channel >= N_CHANNELS) { - PFEXCEPTION_RAISE("BadChannel", - "Channel number " + std::to_string(channel) + - " is out of range for the bias chip"); + PFEXCEPTION_RAISE("BadChannel", "Channel number " + + std::to_string(channel) + + " is out of range for the bias chip"); } led_cache_[channel] = code; int i_chip = (channel > 7); diff --git a/src/pflib/HcalBackplane.cxx b/src/pflib/HcalBackplane.cxx index afd95b608..6505a554c 100644 --- a/src/pflib/HcalBackplane.cxx +++ b/src/pflib/HcalBackplane.cxx @@ -18,15 +18,14 @@ HcalBackplane::HcalBackplane() { necon_ = 0; } -HcalBackplane::HGCROCBoard::HGCROCBoard( - std::shared_ptr roc_i2c, - uint8_t roc_addr, - const std::string& roc_typename, - std::shared_ptr bias_i2c, - std::shared_ptr board_i2c, - bool bias_use_cache - ) : roc{roc_i2c, roc_addr, roc_typename}, - bias{bias_i2c, board_i2c, bias_use_cache} {} +HcalBackplane::HGCROCBoard::HGCROCBoard(std::shared_ptr roc_i2c, + uint8_t roc_addr, + const std::string& roc_typename, + std::shared_ptr bias_i2c, + std::shared_ptr board_i2c, + bool bias_use_cache) + : roc{roc_i2c, roc_addr, roc_typename}, + bias{bias_i2c, board_i2c, bias_use_cache} {} void HcalBackplane::init(lpGBT& daq_lpgbt, lpGBT& trig_lpgbt, int hgcroc_boardmask, bool use_bias_cache) { @@ -104,9 +103,9 @@ void HcalBackplane::init(lpGBT& daq_lpgbt, lpGBT& trig_lpgbt, ADDR_MUX_BOARD, (1 << ibd)); nhgcroc_++; - rocs_[ibd] = std::make_unique( - roc_i2c, (0x20 | (ibd * 8)), "sipm_rocv3b", - bias_i2c, board_i2c, use_bias_cache); + rocs_[ibd] = std::make_unique(roc_i2c, (0x20 | (ibd * 8)), + "sipm_rocv3b", bias_i2c, + board_i2c, use_bias_cache); i2c_[pflib::utility::string_format("HGCROC_%d", ibd)] = roc_i2c; i2c_[pflib::utility::string_format("BOARD_%d", ibd)] = board_i2c; i2c_[pflib::utility::string_format("BIAS_%d", ibd)] = bias_i2c; diff --git a/src/pflib/bittware/HcalBackplane.cxx b/src/pflib/bittware/HcalBackplane.cxx index 1159a7691..b5dbfd50e 100644 --- a/src/pflib/bittware/HcalBackplane.cxx +++ b/src/pflib/bittware/HcalBackplane.cxx @@ -30,7 +30,8 @@ class HcalBackplaneBW : public HcalBackplane { trig_lpgbt_ = std::make_unique(opto_["TRG"]->lpgbt_transport()); - this->init(*daq_lpgbt_, *trig_lpgbt_, board_mask, true /* use bias cache */); + this->init(*daq_lpgbt_, *trig_lpgbt_, board_mask, + true /* use bias cache */); elinks_ = std::make_unique(itarget, dev); diff --git a/src/pflib/zcu/HGCROCBoardFiberless.cxx b/src/pflib/zcu/HGCROCBoardFiberless.cxx index 16156c3ba..ca12139ec 100644 --- a/src/pflib/zcu/HGCROCBoardFiberless.cxx +++ b/src/pflib/zcu/HGCROCBoardFiberless.cxx @@ -6,8 +6,8 @@ #include "pflib/Bias.h" #include "pflib/Exception.h" #include "pflib/GPIO.h" -#include "pflib/I2C_Linux.h" #include "pflib/HcalTarget.h" +#include "pflib/I2C_Linux.h" #include "pflib/packing/DAQSampleHeader.h" #include "pflib/packing/ECONDFormatter.h" #include "pflib/zcu/UIO.h" @@ -227,7 +227,8 @@ class HcalFiberless : public HcalTarget { } roc_ = std::make_unique(i2croc, 0x20, "sipm_rocv3b"); - bias_ = std::make_unique(i2cboard, i2cboard, false /*use bias cache*/); + bias_ = + std::make_unique(i2cboard, i2cboard, false /*use bias cache*/); gpio_.reset(make_GPIO_HcalHGCROCZCU()); diff --git a/src/pflib/zcu/HcalBackplane.cxx b/src/pflib/zcu/HcalBackplane.cxx index 8186d70b6..ae8cca820 100644 --- a/src/pflib/zcu/HcalBackplane.cxx +++ b/src/pflib/zcu/HcalBackplane.cxx @@ -29,7 +29,8 @@ class HcalBackplaneZCU : public HcalBackplane { trig_lpgbt_ = std::make_unique(opto_["TRG"]->lpgbt_transport()); - this->init(*daq_lpgbt_, *trig_lpgbt_, board_mask, true /* use bias cache */); + this->init(*daq_lpgbt_, *trig_lpgbt_, board_mask, + true /* use bias cache */); elinks_ = std::make_unique(&(*daq_lpgbt_), &(*trig_lpgbt_), itarget); diff --git a/test/on_zcu/read_bias.cxx b/test/on_zcu/read_bias.cxx index 6eed0bfb7..016d96d5d 100644 --- a/test/on_zcu/read_bias.cxx +++ b/test/on_zcu/read_bias.cxx @@ -2,8 +2,8 @@ #include #include "hgcroc_connection.h" -#include "pflib/HcalTarget.h" #include "pflib/Bias.h" +#include "pflib/HcalTarget.h" BOOST_AUTO_TEST_SUITE(read_bias)