From 3c2f149c222d7d6f39b2314e88f6f51c59a7a122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=B6jdh?= Date: Mon, 17 Mar 2025 08:46:26 +0100 Subject: [PATCH] Adding patterntools to slsdet (#1142) * added patterntools from mythen3tools * refactored do use implementation from slsSupportLib --- docs/CMakeLists.txt | 1 + docs/src/index.rst | 1 + docs/src/pyPatternGenerator.rst | 34 ++++ python/CMakeLists.txt | 2 + python/slsdet/PatternGenerator.py | 226 ++++++++++++++++++++++ python/slsdet/__init__.py | 1 + python/slsdet/bits.py | 31 +++ python/src/pattern.cpp | 8 +- python/tests/test_PatternGenerator.py | 152 +++++++++++++++ python/tests/test_bits.py | 50 +++++ slsDetectorSoftware/include/sls/Pattern.h | 3 +- slsDetectorSoftware/src/Pattern.cpp | 96 ++++----- 12 files changed, 545 insertions(+), 60 deletions(-) create mode 100644 docs/src/pyPatternGenerator.rst create mode 100644 python/slsdet/PatternGenerator.py create mode 100644 python/slsdet/bits.py create mode 100644 python/tests/test_PatternGenerator.py create mode 100644 python/tests/test_bits.py diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 3a50020d6..861debc58 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -40,6 +40,7 @@ set(SPHINX_SOURCE_FILES src/pydetector.rst src/pyenums.rst src/pyexamples.rst + src/pyPatternGenerator.rst src/servers.rst src/receiver_api.rst src/result.rst diff --git a/docs/src/index.rst b/docs/src/index.rst index af291cd6e..aca6e711c 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -36,6 +36,7 @@ Welcome to slsDetectorPackage's documentation! pydetector pyenums pyexamples + pyPatternGenerator .. toctree:: :caption: Command line diff --git a/docs/src/pyPatternGenerator.rst b/docs/src/pyPatternGenerator.rst new file mode 100644 index 000000000..af078c127 --- /dev/null +++ b/docs/src/pyPatternGenerator.rst @@ -0,0 +1,34 @@ +PatternGenerator +===================================================== + +Python class to generate patterns for the Chip Test Board. + +.. code-block:: python + + from slsdet import PatternGenerator + + p = PatternGenerator() + p.SB(5) + p.PW() + p.SB(8,9) + p.PW() + p.CB(5) + +Created a pattern like this: + +.. code-block:: bash + + patword 0x0000 0x0000000000000020 + patword 0x0001 0x0000000000000320 + patword 0x0002 0x0000000000000300 + patioctrl 0x0000000000000000 + patlimits 0x0000 0x0002 + ... + +.. py:currentmodule:: slsdet + +.. autoclass:: PatternGenerator + :members: + :undoc-members: + :show-inheritance: + :inherited-members: \ No newline at end of file diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 794af10b5..0ae86941f 100755 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -27,6 +27,7 @@ set_target_properties(_slsdet PROPERTIES set( PYTHON_FILES slsdet/__init__.py slsdet/adcs.py + slsdet/bits.py slsdet/dacs.py slsdet/powers.py slsdet/decorators.py @@ -38,6 +39,7 @@ set( PYTHON_FILES slsdet/errors.py slsdet/gaincaps.py slsdet/pattern.py + slsdet/PatternGenerator.py slsdet/gotthard2.py slsdet/moench.py slsdet/proxy.py diff --git a/python/slsdet/PatternGenerator.py b/python/slsdet/PatternGenerator.py new file mode 100644 index 000000000..74c61fc54 --- /dev/null +++ b/python/slsdet/PatternGenerator.py @@ -0,0 +1,226 @@ +from . import Detector, Pattern +from .bits import setbit, clearbit +import textwrap +from pathlib import Path + + +class PatternGenerator: + """ + Class to generate a pattern for the SLS detector. Intents to as closely as possible + mimic the old pattern generation in the C code. + """ + def __init__(self): + self.pattern = Pattern() + self.iaddr = 0 + + def SB(self, *bits): + """ + Set one or several bits. Change will take affect with the next PW. + """ + for bit in bits: + self.pattern.word[self.iaddr] = setbit(bit, self.pattern.word[self.iaddr]) + return self.pattern.word[self.iaddr] + + def CB(self, *bits): + """ + Clear one or several bits. Change will take affect with the next PW. + """ + for bit in bits: + self.pattern.word[self.iaddr] = clearbit(bit, self.pattern.word[self.iaddr]) + return self.pattern.word[self.iaddr] + + + def _pw(self, verbose = False): + if verbose: + print(f'{self.iaddr:#06x} {self.pattern.word[self.iaddr]:#018x}') + + #Limits are inclusive so we need to increment the address before writing the next word + self.pattern.limits[1] = self.iaddr + self.iaddr += 1 + self.pattern.word[self.iaddr] = self.pattern.word[self.iaddr-1] + + def PW(self, x = 1, verbose = False): + for i in range(x): + self._pw(verbose) + + # def REPEAT(self, x, verbose = False): + # for i in range(x): + # self._pw(verbose) + + # def PW2(self, verbose = 0): + # self.REPEAT(2, verbose) + + + def CLOCKS(self, bit, times = 1, length = 1, verbose = False): + """ + clocks "bit" n "times", every half clock is long "length" + length is optional, default value is 1 + """ + for i in range(0, times): + self.SB(bit); self.PW(length, verbose) + self.CB(bit); self.PW(length, verbose) + + def CLOCK(self, bit, length = 1, verbose = 0): + self.CLOCKS(bit, 1, length ,verbose) + + def serializer(self, value, serInBit, clkBit, nbits, msbfirst = True, length = 1): + """serializer(value,serInBit,clkBit,nbits,msbfirst=1,length=1) + Produces the .pat file needed to serialize a word into a shift register. + value: value to be serialized + serInBit: control bit corresponding to serial in + clkBit: control bit corresponding to the clock + nbits: number of bits of the target register to load + msbfirst: if 1 pushes in the MSB first (default), + if 0 pushes in the LSB first + length: length of all the PWs in the pattern + It produces no output because it modifies directly the members of the class pat via SB and CB""" + + c = value + self.CB(serInBit, clkBit) + self.PW(length) #generate initial line with clk and serIn to 0 + + start = 0 + stop = nbits + step = 1 + + if msbfirst: + start = nbits - 1 + stop = -1 + step =- 1 #reverts loop if msb has to be pushed in first + + for i in range(start, stop, step): + if c & (1< 0: + self.pattern.word[n] = self.pattern.word[n-1] + + + def send_to_detector(self, det): + """ + Load the pattern into the detector. + """ + det.setPattern(self.pattern) \ No newline at end of file diff --git a/python/slsdet/__init__.py b/python/slsdet/__init__.py index 7f9ba6b8f..d46354351 100755 --- a/python/slsdet/__init__.py +++ b/python/slsdet/__init__.py @@ -12,6 +12,7 @@ from .gotthard2 import Gotthard2 from .moench import Moench from .pattern import Pattern, patternParameters from .gaincaps import Mythen3GainCapsWrapper +from .PatternGenerator import PatternGenerator import _slsdet xy = _slsdet.xy diff --git a/python/slsdet/bits.py b/python/slsdet/bits.py new file mode 100644 index 000000000..5ccff764f --- /dev/null +++ b/python/slsdet/bits.py @@ -0,0 +1,31 @@ +import numpy as np + + +def setbit(bit, word): + if isinstance(word, np.generic): + mask = word.dtype.type(1) + mask = mask << bit + else: + mask = 1 << bit + return word | mask + + +def setbit_arr(bit, arr): + arr |= arr.dtype.type(1 << bit) + + +def clearbit(bit, word): + """ + Clear the bit at position bit in word. + Two paths to avoid converting the types. + """ + if isinstance(word, np.generic): + mask = word.dtype.type(1) + mask = ~(mask << bit) + else: + mask = ~(1 << bit) + return word & mask + + +def clearbit_arr(bit, arr): + arr &= arr.dtype.type(~(1 << bit)) \ No newline at end of file diff --git a/python/src/pattern.cpp b/python/src/pattern.cpp index ca671c66c..67447fff3 100644 --- a/python/src/pattern.cpp +++ b/python/src/pattern.cpp @@ -20,8 +20,10 @@ void init_pattern(py::module &m) { }); py::class_ Pattern(m, "Pattern"); - Pattern.def(py::init()); - Pattern.def("load", &sls::Pattern::load); - Pattern.def("data", (pat * (sls::Pattern::*)()) & sls::Pattern::data, + Pattern.def(py::init()) + .def("load", &sls::Pattern::load) + .def("save", &sls::Pattern::save) + .def("str", &sls::Pattern::str) + .def("data", (pat * (sls::Pattern::*)()) & sls::Pattern::data, py::return_value_policy::reference); } diff --git a/python/tests/test_PatternGenerator.py b/python/tests/test_PatternGenerator.py new file mode 100644 index 000000000..b8a36e64a --- /dev/null +++ b/python/tests/test_PatternGenerator.py @@ -0,0 +1,152 @@ +import pytest +from slsdet import PatternGenerator + + +def apply_detconf(p): + """ + Hacky workaround to apply detConf_mh02 to a pattern + """ + DACMON = 0 + cnt_en_3 = 1 + pulse_counter_en = 2 + cnt_en_1 = 3 + par_load = 4 + pulse_mux_ctr = 5 + reset_cnt = 6 + reset_periphery = 7 + config_load = 8 + cnt_en_0 = 9 + tbl = 10 + clk_ext = 11 + trimbit_load_reg = 12 + store = 13 + data_in = 14 + en_pll_clk = 15 + cnt_en_2 = 16 + DACINT = 17 + data_out_slow = 18 #IN + COMP2_MON = 19 #IN + start_read = 20 + dac_store = 21 + CNT3_MON = 22 #IN + EN_PIX_DIG_MON = 23 + clk_sel = 24 + BUSY = 25 #IN + COMP3_MON = 26 #IN + CNT2_MON = 27 #IN + + dbit_ena=62 #FIFO LATCH + adc_ena=63 #ADC ENABLE + + #FPGA input/ouutputs + p.setoutput(DACMON) + p.setoutput(cnt_en_3) + p.setoutput(pulse_counter_en) + p.setoutput(cnt_en_1) + p.setoutput(par_load) + p.setoutput(pulse_mux_ctr) + p.setoutput(reset_cnt) + p.setoutput(reset_periphery) + p.setoutput(cnt_en_0) + p.setoutput(tbl) + p.setoutput(clk_ext) + p.setoutput(config_load) + p.setoutput(trimbit_load_reg) + p.setoutput(store) + p.setoutput(data_in) + p.setoutput(en_pll_clk) + p.setoutput(cnt_en_2) + p.setoutput(DACINT) + p.setinput(data_out_slow) + p.setinput(COMP2_MON) + p.setoutput(start_read) + p.setoutput(dac_store) + p.setinput(CNT3_MON) + p.setoutput(EN_PIX_DIG_MON) + p.setoutput(clk_sel) + p.setinput(BUSY) + p.setinput(COMP3_MON) + p.setinput(CNT2_MON) + + #system signals + p.setoutput(adc_ena) + # FIFO LATCH + p.setoutput(dbit_ena) + return p + + + + + +def test_first_two_PW(): + p = PatternGenerator() + + #The pattern is created with a single empty word + assert p.pattern.limits[0] == 0 + assert p.pattern.limits[1] == 0 + + p.SB(8) + p.PW() + + #When doing the first PW the empty word is overwritten + assert p.pattern.limits[0] == 0 + assert p.pattern.limits[1] == 0 + assert p.pattern.word[0] == 256 + + p.SB(9) + p.PW() + + #When doing the second PW we add a new word + assert p.pattern.limits[0] == 0 + assert p.pattern.limits[1] == 1 + assert p.pattern.word[0] == 256 + assert p.pattern.word[1] == 768 + +def test_simple_pattern(): + """ + Using enable pll pattern for MH02 + """ + en_pll_clk = 15 + p = PatternGenerator() + p = apply_detconf(p) + p.SB(en_pll_clk) + p.PW() + p.PW() + + lines = str(p).split("\n") + + enable_pll_pattern = [ + "patword 0x0000 0x0000000000008000", + "patword 0x0001 0x0000000000008000", + "patioctrl 0xc000000001b3ffff", + "patlimits 0x0000 0x0001", + "patloop 0 0x1fff 0x1fff", + "patnloop 0 0", + "patloop 1 0x1fff 0x1fff", + "patnloop 1 0", + "patloop 2 0x1fff 0x1fff", + "patnloop 2 0", + "patloop 3 0x1fff 0x1fff", + "patnloop 3 0", + "patloop 4 0x1fff 0x1fff", + "patnloop 4 0", + "patloop 5 0x1fff 0x1fff", + "patnloop 5 0", + "patwait 0 0x1fff", + "patwaittime 0 0", + "patwait 1 0x1fff", + "patwaittime 1 0", + "patwait 2 0x1fff", + "patwaittime 2 0", + "patwait 3 0x1fff", + "patwaittime 3 0", + "patwait 4 0x1fff", + "patwaittime 4 0", + "patwait 5 0x1fff", + "patwaittime 5 0", + ] + + assert len(lines) == len(enable_pll_pattern) + + for i, line in enumerate(lines): + assert line == enable_pll_pattern[i] \ No newline at end of file diff --git a/python/tests/test_bits.py b/python/tests/test_bits.py new file mode 100644 index 000000000..e679ea656 --- /dev/null +++ b/python/tests/test_bits.py @@ -0,0 +1,50 @@ +from slsdet.bits import clearbit, clearbit_arr, setbit, setbit_arr +import numpy as np + + +def test_clearbit_on_python_int(): + val = 5 # 0b101 + r = clearbit(0, val) + assert r == 4 + assert val == 5 + +def test_setbit_on_python_int(): + val = 5 # 0b101 + r = setbit(1, val) + assert r == 7 + assert val == 5 + + +def test_setbit_doesnt_change_type(): + word = np.int32(5) + ret = setbit(0, word) + assert isinstance(ret, np.int32) + + +def test_clearbit_doesnt_change_type(): + word = np.uint8(5) + ret = clearbit(0, word) + assert isinstance(ret, np.uint8) + + + +def test_setbit_on_array_element(): + arr = np.zeros(10, dtype=np.uint64) + arr[5] = setbit(0, arr[5]) + arr[5] = setbit(1, arr[5]) + arr[5] = setbit(4, arr[5]) + assert arr[4] == 0 + assert arr[5] == 19 # 0b10011 + assert arr[6] == 0 + + +def test_setbit_arr(): + arr = np.zeros(10, dtype=np.int32) + setbit_arr(3, arr[3:9]) + assert all(arr == np.array((0, 0, 0, 8, 8, 8, 8, 8, 8, 0), dtype=np.int32)) + + +def test_clearbit_arr(): + arr = np.array((5, 5, 5), dtype=np.int8) + clearbit_arr(0, arr) + assert all(arr == (4, 4, 4)) \ No newline at end of file diff --git a/slsDetectorSoftware/include/sls/Pattern.h b/slsDetectorSoftware/include/sls/Pattern.h index 6fa8665dd..4a8cb6f47 100644 --- a/slsDetectorSoftware/include/sls/Pattern.h +++ b/slsDetectorSoftware/include/sls/Pattern.h @@ -23,6 +23,7 @@ typedef struct __attribute__((packed)) { #ifdef __cplusplus class Pattern { patternParameters *pat = new patternParameters{}; + std::ostream& stream(std::ostream &os) const; public: Pattern(); @@ -34,7 +35,7 @@ class Pattern { patternParameters *data() const; size_t size() const noexcept { return sizeof(patternParameters); } void validate() const; - void load(const std::string &fname); + size_t load(const std::string &fname); void save(const std::string &fname); std::string str() const; }; diff --git a/slsDetectorSoftware/src/Pattern.cpp b/slsDetectorSoftware/src/Pattern.cpp index 3b7049b60..4083df7fa 100644 --- a/slsDetectorSoftware/src/Pattern.cpp +++ b/slsDetectorSoftware/src/Pattern.cpp @@ -93,7 +93,8 @@ void Pattern::validate() const { } } -void Pattern::load(const std::string &fname) { +size_t Pattern::load(const std::string &fname) { + size_t numPatWords = 0; std::ifstream input_file(fname); if (!input_file) { throw RuntimeError("Could not open pattern file " + fname + @@ -125,6 +126,7 @@ void Pattern::load(const std::string &fname) { throw RuntimeError("Invalid address for " + ToString(args)); } pat->word[addr] = StringTo(args[2]); + ++numPatWords; } else if (cmd == "patioctrl") { if (nargs != 1) { throw RuntimeError("Invalid arguments for " + @@ -238,6 +240,41 @@ void Pattern::load(const std::string &fname) { } } } + return numPatWords; +} + +std::ostream& Pattern::stream(std::ostream &os) const{ + for (uint32_t i = pat->limits[0]; i <= pat->limits[1]; ++i) { + os << "patword " << ToStringHex(i, 4) << " " + << ToStringHex(pat->word[i], 16) << std::endl; + } + + // patioctrl + os << "patioctrl " << ToStringHex(pat->ioctrl, 16) << std::endl; + + // patlimits + os << "patlimits " << ToStringHex(pat->limits[0], 4) << " " + << ToStringHex(pat->limits[1], 4) << std::endl; + + for (size_t i = 0; i < MAX_PATTERN_LEVELS; ++i) { + // patloop + os << "patloop " << i << " " + << ToStringHex(pat->startloop[i], 4) << " " + << ToStringHex(pat->stoploop[i], 4) << std::endl; + // patnloop + os << "patnloop " << i << " " << pat->nloop[i] << std::endl; + } + + for (size_t i = 0; i < MAX_PATTERN_LEVELS; ++i) { + // patwait + os << "patwait " << i << " " << ToStringHex(pat->wait[i], 4) + << std::endl; + // patwaittime + os << "patwaittime " << i << " " << pat->waittime[i]; + if (ilimits[0]; i <= pat->limits[1]; ++i) { - output_file << "patword " << ToStringHex(i, 4) << " " - << ToStringHex(pat->word[i], 16) << std::endl; - } - - // patioctrl - output_file << "patioctrl " << ToStringHex(pat->ioctrl, 16) << std::endl; - - // patlimits - output_file << "patlimits " << ToStringHex(pat->limits[0], 4) << " " - << ToStringHex(pat->limits[1], 4) << std::endl; - - for (size_t i = 0; i < MAX_PATTERN_LEVELS; ++i) { - // patloop - output_file << "patloop " << i << " " - << ToStringHex(pat->startloop[i], 4) << " " - << ToStringHex(pat->stoploop[i], 4) << std::endl; - // patnloop - output_file << "patnloop " << i << " " << pat->nloop[i] << std::endl; - } - - for (size_t i = 0; i < MAX_PATTERN_LEVELS; ++i) { - // patwait - output_file << "patwait " << i << " " << ToStringHex(pat->wait[i], 4) - << std::endl; - // patwaittime - output_file << "patwaittime " << i << " " << pat->waittime[i] - << std::endl; - } + stream(output_file); } std::string Pattern::str() const { std::ostringstream oss; - oss << '[' << std::setfill('0') << std::endl; - int addr_width = 4; - int word_width = 16; - for (int i = 0; i < MAX_PATTERN_LENGTH; ++i) { - if (pat->word[i] != 0) { - oss << "patword " << ToStringHex(i, addr_width) << " " - << ToStringHex(pat->word[i], word_width) << std::endl; - } - } - oss << "patioctrl " << ToStringHex(pat->ioctrl, word_width) << std::endl - << "patlimits " << ToStringHex(pat->limits[0], addr_width) << " " - << ToStringHex(pat->limits[1], addr_width) << std::endl; - - for (int i = 0; i != MAX_PATTERN_LEVELS; ++i) { - oss << "patloop " << i << ' ' - << ToStringHex(pat->startloop[i], addr_width) << " " - << ToStringHex(pat->stoploop[i], addr_width) << std::endl - << "patnloop " << pat->nloop[i] << std::endl - << "patwait " << i << ' ' << ToStringHex(pat->wait[i], addr_width) - << std::endl - << "patwaittime " << i << ' ' << pat->waittime[i] << std::endl; - } - - oss << ']'; + stream(oss); return oss.str(); }