Commit a22e2d17 authored by Morag Jean Williams's avatar Morag Jean Williams
Browse files

Merge branch 'clicpix2_Decoder' into 'master'

Move CLICpix2 Decoder into Corryvreckan sources

See merge request !59
parents 0f969096 725a727a
Pipeline #586095 failed with stages
in 19 minutes and 48 seconds
# - Try to find the Peary framework
# Once done this will define
# Peary_FOUND - System has Peary
# Peary_INCLUDE_DIRS - The Peary main include directory
# Peary_LIBRARIES - The libraries for Peary and the required components
MESSAGE(STATUS "Looking for Peary...")
FIND_PATH(Peary_INCLUDE_DIR NAMES "peary/device/device.hpp" PATHS "$ENV{PEARYPATH}")
FIND_LIBRARY(Peary_LIBRARIES NAMES "peary" HINTS "$ENV{PEARYPATH}/lib")
LIST(APPEND Peary_INCLUDE_DIRS "${Peary_INCLUDE_DIR}/peary/utils")
LIST(APPEND Peary_INCLUDE_DIRS "${Peary_INCLUDE_DIR}/devices")
IF(Peary_FIND_COMPONENTS)
FOREACH(component ${Peary_FIND_COMPONENTS})
FIND_PATH(Peary_COMP_INCLUDE NAMES "devices/${component}/${component}Device.hpp" PATHS "$ENV{PEARYPATH}")
FIND_LIBRARY(Peary_COMP_LIB NAMES "PearyDevice${component}" HINTS "$ENV{PEARYPATH}/lib")
IF(Peary_COMP_INCLUDE AND Peary_COMP_LIB)
LIST(APPEND Peary_LIBRARIES "${Peary_COMP_LIB}")
LIST(APPEND Peary_INCLUDE_DIRS "${Peary_COMP_INCLUDE}/devices/${component}")
SET(Peary_${component}_FOUND TRUE)
MESSAGE(STATUS "Looking for Peary component: ${component} -- Found")
ELSE()
MESSAGE(STATUS "Looking for Peary component: ${component} -- NOTFOUND")
ENDIF()
ENDFOREACH()
ENDIF()
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Peary REQUIRED_VARS Peary_INCLUDE_DIRS Peary_LIBRARIES
HANDLE_COMPONENTS
FAIL_MESSAGE "Could not find Peary or a required component, make sure all necessary components are compiled and that the variable PEARYPATH points to the installation location:\n$ export PEARYPATH=/your/path/to/Peary\n")
// Implementation of the CLICpix2 frame decoder
#include "clicpix2_frameDecoder.hpp"
#include <cmath>
#include <iterator>
using namespace caribou;
const clicpix2_frameDecoder::WORD_TYPE clicpix2_frameDecoder::DELIMITER(1, 0xf7);
clicpix2_frameDecoder::clicpix2_frameDecoder(const bool pixelCompression,
const bool DCandSuperPixelCompression,
const std::map<std::pair<uint8_t, uint8_t>, pixelConfig>& pixel_conf)
: pixelCompressionEnabled(pixelCompression), DCandSuperPixelCompressionEnabled(DCandSuperPixelCompression) {
// Resolve and store long-counter states:
for(const auto& pixel : pixel_conf) {
counter_config[pixel.first.first][pixel.first.second] = pixel.second.GetLongCounter();
}
}
void clicpix2_frameDecoder::decode(const std::vector<uint32_t>& frame, bool decodeCnt) {
std::vector<WORD_TYPE> dataVector = repackageFrame(frame);
if(dataVector.empty()) {
throw caribou::DataException("Frame is empty");
}
auto data = dataVector.cbegin();
auto dataEnd = dataVector.cend();
do {
decodeHeader(*data++); // header
extractColumns(data, dataEnd);
} while(std::distance(data, dataEnd) && !(std::distance(data, dataEnd) == 1 && *data == DELIMITER));
if(decodeCnt)
decodeCounter();
}
pearydata clicpix2_frameDecoder::getZerosuppressedFrame() {
pearydata decframe;
for(size_t r = 0; r < static_cast<size_t>(clicpix2_frameDecoder::CLICPIX2_ROW); ++r) {
for(size_t c = 0; c < static_cast<size_t>(clicpix2_frameDecoder::CLICPIX2_COL); ++c) {
// Only return pixels with flag set:
if(matrix[r][c].GetFlag()) {
decframe[std::make_pair(c, r)] = std::make_unique<pixelReadout>(matrix[r][c]);
continue;
}
}
}
return decframe;
}
std::vector<clicpix2_frameDecoder::WORD_TYPE> clicpix2_frameDecoder::repackageFrame(const std::vector<uint32_t>& frame) {
std::vector<WORD_TYPE> data;
for(auto const& it : frame) {
data.emplace_back((it >> 17) & 0x1, (it >> 8) & 0xFF); // MSByte
data.emplace_back((it >> 16) & 0x1, it & 0xFF); // LSByte
}
return data;
}
void clicpix2_frameDecoder::decodeHeader(const clicpix2_frameDecoder::WORD_TYPE word) {
if(word.is_control != 0) {
throw DataException("Packet header should be a regular data word: " + to_hex_string(word.word));
}
rcr = (word.word >> 6) & 0x3;
if(rcr == 0) {
throw DataException("Unsupported RCR in packet header");
}
firstColumn = word.word & 0x1F;
}
void clicpix2_frameDecoder::extractColumns(std::vector<clicpix2_frameDecoder::WORD_TYPE>::const_iterator& data,
std::vector<clicpix2_frameDecoder::WORD_TYPE>::const_iterator dataEnd) {
std::array<std::array<pixelReadout, 8>, CLICPIX2_ROW * 2>
pixels_dc; // stores results of the processed doube columns (up to 8 if compression is enabled)
std::array<size_t, 8> row_index = {
{0, 0, 0, 0, 0, 0, 0, 0}}; // 8 independent (in case of compression) counters navigating through the <pixels_dc>
std::array<int, 8> row_slice = {{CLICPIX2_PIXEL_SIZE - 1,
CLICPIX2_PIXEL_SIZE - 1,
CLICPIX2_PIXEL_SIZE - 1,
CLICPIX2_PIXEL_SIZE - 1,
CLICPIX2_PIXEL_SIZE - 1,
CLICPIX2_PIXEL_SIZE - 1,
CLICPIX2_PIXEL_SIZE - 1,
CLICPIX2_PIXEL_SIZE - 1}}; // 8 independent
//(in case of compression) counters navigating through the <pixels_dc>
#define DC_COUNTER_INIT (2 * (CLICPIX2_ROW * CLICPIX2_PIXEL_SIZE + CLICPIX2_ROW / CLICPIX2_SUPERPIXEL_SIZE) + 1)
std::array<unsigned int, 8> dc_counter = {{DC_COUNTER_INIT,
DC_COUNTER_INIT,
DC_COUNTER_INIT,
DC_COUNTER_INIT,
DC_COUNTER_INIT,
DC_COUNTER_INIT,
DC_COUNTER_INIT,
DC_COUNTER_INIT}}; // 8 independent (in case of compression)
// counters indicating number of bits processed for the given double-column
// value 3601 indicates beginning of the double-column
std::array<unsigned int, 8> sp_counter = {
{0, 0, 0, 0, 0, 0, 0, 0}}; // number of pixels in the processed super-pixel for the given column
do {
WORD_TYPE word = *data++;
if(word == DELIMITER) // end of double column
break;
if(word.is_control)
throw DataException("Found control word different than delimiter");
unraveDC(pixels_dc, dc_counter, sp_counter, row_index, row_slice, word.word);
} while(std::distance(data, dataEnd));
for(unsigned int i = 0; i < static_cast<unsigned int>(1 << rcr); i++)
if(dc_counter[i] != 3600)
throw DataException("Partial double column");
// remove snake pattern
for(unsigned int r = 0; r < 256; ++r)
for(unsigned int c = 0; c < static_cast<unsigned int>(1 << rcr); c++) {
switch(r % 4) {
case 0:
matrix[r / 2][c * 2 * 64 / static_cast<unsigned int>(1 << rcr) +
static_cast<unsigned int>(firstColumn * 2)] = pixels_dc[r][c];
break; // left column
case 1:
matrix[r / 2][c * 2 * 64 / static_cast<unsigned int>(1 << rcr) + static_cast<unsigned int>(firstColumn * 2) +
1] = pixels_dc[r][c];
break; // right column
case 2:
matrix[r / 2][c * 2 * 64 / static_cast<unsigned int>(1 << rcr) + static_cast<unsigned int>(firstColumn * 2) +
1] = pixels_dc[r][c];
break; // right column
case 3:
matrix[r / 2][c * 2 * 64 / static_cast<unsigned int>(1 << rcr) +
static_cast<unsigned int>(firstColumn * 2)] = pixels_dc[r][c];
break; // left column
default:
throw DataException("Invalid row ID");
break;
}
}
}
void clicpix2_frameDecoder::unraveDC(std::array<std::array<pixelReadout, 8>, CLICPIX2_ROW * 2>& pixels_dc,
std::array<unsigned int, 8>& dc_counter,
std::array<unsigned int, 8>& sp_counter,
std::array<size_t, 8>& row_index,
std::array<int, 8>& row_slice,
const uint8_t data) {
// unravel the double-columns
switch(rcr) {
case 1:
for(int i = 0; i < 8; i += 2) {
processDCbit(pixels_dc, dc_counter[0], sp_counter[0], row_index[0], 0, row_slice[0], (data >> (i)) & 0x1);
processDCbit(pixels_dc, dc_counter[1], sp_counter[1], row_index[1], 1, row_slice[1], (data >> (i + 1)) & 0x1);
}
break;
case 2:
for(int i = 0; i < 8; i += 4) {
processDCbit(pixels_dc, dc_counter[0], sp_counter[0], row_index[0], 0, row_slice[0], (data >> (i)) & 0x1);
processDCbit(pixels_dc, dc_counter[1], sp_counter[1], row_index[1], 1, row_slice[1], (data >> (i + 1)) & 0x1);
processDCbit(pixels_dc, dc_counter[2], sp_counter[2], row_index[2], 2, row_slice[2], (data >> (i + 2)) & 0x1);
processDCbit(pixels_dc, dc_counter[3], sp_counter[3], row_index[3], 3, row_slice[3], (data >> (i + 3)) & 0x1);
}
break;
case 3:
for(int i = 0; i < 8; i += 8) {
processDCbit(pixels_dc, dc_counter[0], sp_counter[0], row_index[0], 0, row_slice[0], (data >> (i)) & 0x1);
processDCbit(pixels_dc, dc_counter[1], sp_counter[1], row_index[1], 1, row_slice[1], (data >> (i + 1)) & 0x1);
processDCbit(pixels_dc, dc_counter[2], sp_counter[2], row_index[2], 2, row_slice[2], (data >> (i + 2)) & 0x1);
processDCbit(pixels_dc, dc_counter[3], sp_counter[3], row_index[3], 3, row_slice[3], (data >> (i + 3)) & 0x1);
processDCbit(pixels_dc, dc_counter[4], sp_counter[4], row_index[4], 4, row_slice[4], (data >> (i + 4)) & 0x1);
processDCbit(pixels_dc, dc_counter[5], sp_counter[5], row_index[5], 5, row_slice[5], (data >> (i + 5)) & 0x1);
processDCbit(pixels_dc, dc_counter[6], sp_counter[6], row_index[6], 6, row_slice[6], (data >> (i + 6)) & 0x1);
processDCbit(pixels_dc, dc_counter[7], sp_counter[7], row_index[7], 7, row_slice[7], (data >> (i + 7)) & 0x1);
}
break;
default:
throw DataException("Invalid RCR");
break;
}
}
void clicpix2_frameDecoder::processDCbit(std::array<std::array<pixelReadout, 8>, CLICPIX2_ROW * 2>& pixels_dc,
unsigned int& dc_counter,
unsigned int& sp_counter,
size_t& row_index,
const size_t col_index,
int& row_slice,
const bool data) {
// middle of the double-column
if(dc_counter < static_cast<int>(2 * (CLICPIX2_ROW * CLICPIX2_PIXEL_SIZE + CLICPIX2_ROW / CLICPIX2_SUPERPIXEL_SIZE))) {
// super-pixel bit
if(sp_counter == 0) {
if(data || // not empty super-pixel
!DCandSuperPixelCompressionEnabled) { // or sp compression disabled
dc_counter++;
sp_counter++;
} else { // empty super-pixel
for(auto i = 0; i < static_cast<int>(CLICPIX2_SUPERPIXEL_SIZE); ++i)
pixels_dc[row_index++][col_index].setLatches(0x00);
dc_counter += CLICPIX2_SUPERPIXEL_SIZE * CLICPIX2_PIXEL_SIZE + 1;
}
}
// pixel bit
else if(row_slice == static_cast<int>(CLICPIX2_PIXEL_SIZE - 1)) {
if(data || // not empty pixel
!pixelCompressionEnabled) { // or pixel compression disabled
pixels_dc[row_index][col_index].setLatches(data, static_cast<uint8_t>(row_slice--));
dc_counter++;
sp_counter++;
} else { // empty pixel
pixels_dc[row_index++][col_index].setLatches(0x00);
dc_counter += CLICPIX2_PIXEL_SIZE;
sp_counter += CLICPIX2_PIXEL_SIZE;
}
}
// pixel payload
else {
pixels_dc[row_index][col_index].setLatches(data, static_cast<uint8_t>(row_slice--));
dc_counter++;
sp_counter++;
if(row_slice < 0) {
row_index++;
row_slice = CLICPIX2_PIXEL_SIZE - 1;
}
}
if(sp_counter > static_cast<int>(CLICPIX2_SUPERPIXEL_SIZE * CLICPIX2_PIXEL_SIZE)) // reset sp_counter
sp_counter = 0;
}
// beginning of the double-column (dc_counter == 3601)
else {
if(dc_counter ==
static_cast<int>(2 * (CLICPIX2_ROW * CLICPIX2_PIXEL_SIZE + CLICPIX2_ROW / CLICPIX2_SUPERPIXEL_SIZE) + 1)) {
if(data || // not empty double-column
!DCandSuperPixelCompressionEnabled) // or collumn compression disabled
dc_counter = 0;
else { // empty double-column
for(auto i = 0; i < static_cast<int>(2 * CLICPIX2_ROW); ++i)
pixels_dc[row_index++][col_index].setLatches(0x00);
dc_counter = 2 * (CLICPIX2_ROW * CLICPIX2_PIXEL_SIZE + CLICPIX2_ROW / CLICPIX2_SUPERPIXEL_SIZE); // 3600
}
}
}
}
void clicpix2_frameDecoder::decodeCounter() {
for(size_t r = 0; r < static_cast<size_t>(clicpix2_frameDecoder::CLICPIX2_ROW); ++r) {
for(size_t c = 0; c < static_cast<size_t>(clicpix2_frameDecoder::CLICPIX2_COL); ++c) {
// Only decode pixels with a flag set
if(!matrix[r][c].GetFlag()) {
continue;
}
if(counter_config[r][c]) {
matrix[r][c].SetCounter(lfsr13_lut[matrix[r][c].GetLatches() & 0x1fff]);
} else {
matrix[r][c].SetTOT(lfsr5_lut[(matrix[r][c].GetLatches() >> 8) & 0x1f]);
matrix[r][c].SetTOA(lfsr8_lut[matrix[r][c].GetLatches() & 0xff]);
}
}
}
}
namespace caribou {
std::ostream& operator<<(std::ostream& out, const clicpix2_frameDecoder& decoder) {
for(size_t r = 0; r < static_cast<size_t>(clicpix2_frameDecoder::CLICPIX2_ROW); ++r)
for(size_t c = 0; c < static_cast<size_t>(clicpix2_frameDecoder::CLICPIX2_COL); ++c) {
out << "[" << r << "][" << c << "] [" << decoder.matrix[r][c] << "], ";
if(c % 64 == 63)
out << "\n";
}
return out;
}
} // namespace caribou
// CLICpix2 frame decoder
// Base on System Verilog CLICpix2_Readout_scoreboard
#ifndef CLICPIX2_FRAMEDECODER_HPP
#define CLICPIX2_FRAMEDECODER_HPP
#include <array>
#include <cstdint>
#include <ostream>
#include <vector>
#include "CLICpix2/clicpix2_pixels.hpp"
namespace caribou {
class clicpix2_frameDecoder {
// Internal class representing a SERDES word
class WORD_TYPE {
public:
WORD_TYPE(){};
WORD_TYPE(bool control, uint8_t w) : is_control(control), word(w){};
bool operator==(const WORD_TYPE& a) const { return a.is_control == is_control && a.word == word; };
bool operator!=(const WORD_TYPE& a) const { return a.is_control != is_control || a.word != word; };
bool is_control;
uint8_t word;
};
// Parameters of the Frame decoder
static const unsigned int CLICPIX2_ROW = 128;
static const unsigned int CLICPIX2_COL = 128;
// Number of pixels in the super-pixel
static const unsigned int CLICPIX2_SUPERPIXEL_SIZE = 16;
static const unsigned int CLICPIX2_PIXEL_SIZE = 14;
static const WORD_TYPE DELIMITER; // K23.7 Carrier extender
std::array<std::array<pixelReadout, CLICPIX2_COL>, CLICPIX2_ROW> matrix; //[row][column]
// repackage uint32_t words into WORD_TYPE
std::vector<WORD_TYPE> repackageFrame(const std::vector<uint32_t>& frame);
void decodeHeader(const WORD_TYPE word);
void extractColumns(std::vector<WORD_TYPE>::const_iterator& data, std::vector<WORD_TYPE>::const_iterator dataEnd);
void unraveDC(std::array<std::array<pixelReadout, 8>, CLICPIX2_ROW * 2>& pixels_dc,
std::array<unsigned int, 8>& dc_counter,
std::array<unsigned int, 8>& sp_counter,
std::array<size_t, 8>& row_index,
std::array<int, 8>& row_slice,
const uint8_t data);
void processDCbit(std::array<std::array<pixelReadout, 8>, CLICPIX2_ROW * 2>& pixels_dc,
unsigned int& dc_counter,
unsigned int& sp_counter,
size_t& row_index,
const size_t col_index,
int& row_slice,
const bool data);
void decodeCounter();
// current RCR register value
uint8_t rcr;
// firt column of the currently analyzed part of the package
uint16_t firstColumn;
// Configutation
bool pixelCompressionEnabled;
bool DCandSuperPixelCompressionEnabled;
std::map<size_t, std::map<size_t, bool>> counter_config; // [row][column]
public:
clicpix2_frameDecoder(const bool pixelCompressionEnabled,
const bool DCandSuperPixelCompressionEnabled,
const std::map<std::pair<uint8_t, uint8_t>, pixelConfig>& pixel_config);
void decode(const std::vector<uint32_t>& frame, bool decodeCnt = true);
pearydata getZerosuppressedFrame();
pixelReadout get(const unsigned int row, const unsigned int column) { return matrix[row][column]; };
static const uint16_t lfsr13_lut[8191];
static const uint8_t lfsr8_lut[255];
static const uint8_t lfsr5_lut[31];
/** Overloaded ostream operator for simple printing of pixel data
*/
friend std::ostream& operator<<(std::ostream& out, const clicpix2_frameDecoder& decoder);
};
} // namespace caribou
#endif
// It contains LUT for lfsr5 counter
#include "clicpix2_frameDecoder.hpp"
using namespace caribou;
const uint8_t clicpix2_frameDecoder::lfsr5_lut[31] = {0, 1, 11, 2, 8, 12, 27, 3, 9, 25, 13, 15, 28, 22, 4, 17,
30, 10, 7, 26, 24, 14, 21, 16, 29, 6, 23, 20, 5, 19, 18};
// It contains LUT for lfsr8 counter
#include "clicpix2_frameDecoder.hpp"
using namespace caribou;
const uint8_t clicpix2_frameDecoder::lfsr8_lut[255] = {
0, 1, 198, 2, 104, 199, 238, 3, 193, 105, 113, 200, 141, 239, 14, 4, 194, 181, 106, 185, 114, 228, 201, 120,
142, 147, 240, 69, 15, 36, 5, 47, 195, 110, 182, 66, 107, 84, 186, 133, 115, 87, 229, 212, 202, 159, 121, 21,
189, 143, 208, 148, 92, 241, 56, 70, 136, 16, 152, 37, 98, 6, 221, 48, 196, 236, 111, 12, 183, 118, 67, 45,
108, 82, 85, 157, 187, 90, 134, 96, 234, 116, 80, 88, 232, 230, 174, 213, 176, 203, 169, 160, 215, 122, 245, 22,
252, 190, 178, 144, 63, 209, 205, 149, 42, 93, 171, 242, 60, 57, 162, 71, 137, 32, 17, 217, 153, 165, 38, 124,
99, 128, 7, 247, 222, 74, 49, 24, 254, 197, 103, 237, 192, 112, 140, 13, 180, 184, 227, 119, 146, 68, 35, 46,
109, 65, 83, 132, 86, 211, 158, 20, 188, 207, 91, 55, 135, 151, 97, 220, 235, 11, 117, 44, 81, 156, 89, 95,
233, 79, 231, 173, 175, 168, 214, 244, 251, 177, 62, 204, 41, 170, 59, 161, 31, 216, 164, 123, 127, 246, 73, 23,
253, 102, 191, 139, 179, 226, 145, 34, 64, 131, 210, 19, 206, 54, 150, 219, 10, 43, 155, 94, 78, 172, 167, 243,
250, 61, 40, 58, 30, 163, 126, 72, 101, 138, 225, 33, 130, 18, 53, 218, 9, 154, 77, 166, 249, 39, 29, 125,
100, 224, 129, 52, 8, 76, 248, 28, 223, 51, 75, 27, 50, 26, 25};
// Defines CLICpix2 pixels types
#ifndef CLICPIX2_PIXELS_HPP
#define CLICPIX2_PIXELS_HPP
#include <ostream>
#include "datatypes.hpp"
namespace caribou {
// Basic pixel class
// The information is internally stored in the same way, the chip stores it, as
// a 14bit register.
//
// The individual values are set via the member functions of a specialized classes
class clicpix2_pixel : public virtual pixel {
public:
// direct latch access
void setLatches(uint16_t latches) { m_latches = latches; }
// set a dedicated bit of the latch
void setLatches(bool bit, uint8_t idx) {
if(bit) {
m_latches = static_cast<uint16_t>(m_latches | (1 << idx));
} else {
m_latches &= static_cast<uint16_t>(~(1 << idx));
}
}
/* Member function to return single bit of the latches.
*/
bool GetBit(uint8_t bit) { return ((m_latches >> bit) & 0x1); }
uint16_t GetLatches() const { return m_latches; }
/** Overloaded comparison operators
*/
bool operator==(const clicpix2_pixel& rhs) const {
if(this->GetLatches() == rhs.GetLatches()) {
return true;
}
return false;
}
bool operator!=(const clicpix2_pixel& rhs) const { return !(*this == rhs); }
protected:
clicpix2_pixel(){};
clicpix2_pixel(uint16_t latches) : m_latches(latches){};
uint16_t m_latches;
};
/* CLICpix2 pixel configuration class
*
* Class to hold all information required to fully configure one pixel.
* The information is internally stored in the same way, the chip stores it, as
* a 14bit register. The individual values are set via the member functions
* and can be retrieved bitwise for convenience.
*/
class pixelConfig : public virtual clicpix2_pixel {
public:
/* Default constructor
*
* Initializes the pixel in a masked state
*/
pixelConfig() : clicpix2_pixel(0x2000){};
pixelConfig(bool mask, uint8_t threshold, bool cntmode, bool tpenable, bool longcnt) : pixelConfig() {
SetMask(mask);
SetThreshold(threshold);
SetCountingMode(cntmode);
EnableTestpulse(tpenable);
LongCounter(longcnt);
};
/* Mask setting of the pixel
*/
void SetMask(bool mask) {
if(mask) {
m_latches |= (1 << 13);
} else {
m_latches &= static_cast<uint16_t>(~(1 << 13));
}
}
bool GetMask() const { return (m_latches >> 13) & 0x1; }
/* Individual threshold adjustment (4bit)
*/
void SetThreshold(uint8_t thr_adjust) {
m_latches = static_cast<uint16_t>((m_latches & 0xf0ff) | static_cast<uint16_t>((thr_adjust & 0x0f) << 8));
}
uint8_t GetThreshold() const { return (m_latches >> 8) & 0x0f; }
/* Enable/disable counting mode
*/
void SetCountingMode(bool cntmo) {
if(cntmo) {
m_latches |= (1 << 3);
} else {
m_latches &= static_cast<uint16_t>(~(1 << 3));
}
}
bool GetCountingMode() const { return (m_latches >> 3) & 0x1; }
/* Enable/disable testpulse circuit for this pixel
*/
void EnableTestpulse(bool tpen) {
if(tpen) {
m_latches |= (1 << 2);
} else {
m_latches &= static_cast<uint16_t>(~(1 << 2));
}
}