mirror of
https://github.com/slsdetectorgroup/slsDetectorPackage.git
synced 2026-05-09 04:52:03 +02:00
CTB frequency rounding, CTB frequency measurement, CTB frequency units (#1423)
* round CTB clocks to next closest possible value, added freq measurement * added time for firmware to measrue actual value after frequency change * add check for backwards compatibility * change CTB and XCTB clock values to MHz, TODO: units and validation errors * changed runclk command to use units and float, TODO: dbit, adcclk, why is everything called StringTo ? * do the same for dbit and adcclk * added tolerance to exptime, fixed test * update default values in server defs * added virtual check in Altera_PLL, update testcases * change python and pyctbgui to accept and return floating point MHz * update help and comments * Dev/ctb clocks fix (#1434) * introduced new type Hz, typetraits, String conversions, command generation (not yet generated) * incorrect unit typo * cmd generation and compiles * default to MHz, removed space between units for consistency with timers, min and max checks for clks * in python, but need to change the default to Hz again for clean code and intuition * allow ints, doubles, implicit conversions * dont allow raw ints, doubles and implicit conversions * fixed tests * added operators for Hz in python * fix test for min clk for xilinx ctb * fix test * fix python tests * fixed xilinx period and default clks * test fix * removed the 3 clock cycle check for ctb and implemented properly the max adc clk frq for altera ctb * removing 3 clock cycle code from xilinx as well * formatting * loadpattern before 3 clk cycles code * actualtime and measurement time to be implemented in 100ns already in fw * fix tests * pyzmq dependency forthe tests * fixed pyctbgui for freq * insert tolerance check again * also added tolerance check for patwaittime * formatting * minor: rounding test * removed Rep redundant in ToString for freq * intro frequency unit enums, removed unnecessary template behavior for ToString with freq unit, switching from parsing string unit argument to the enum argument for ToString, adding parsing string to unit at CLI boundary * minor, and binaries * minor, default clk vals are 0 but set up at detector setup * get frequency only for that unit * tolerance process * missed in previous commit * some more changes to exptime and validations * ctb is probably done * periodleft and delayleft * fixed xilinx freq conv as well * fixed m3 bug, binaries * xilinx: setup also done in stop server so that the clk is not 0 * missed a test marker * binaries in * review fixes, simpler validation of timers in ctb and xilinx ctb * typo fix * format * fix tests --------- Co-authored-by: Martin Mueller <martin.mueller@psi.ch> Co-authored-by: Dhanya Thattil <dhanya.thattil@psi.ch>
This commit is contained in:
@@ -13,6 +13,7 @@ pybind11_add_module(_slsdet
|
||||
src/DurationWrapper.cpp
|
||||
src/pedestal.cpp
|
||||
src/bit.cpp
|
||||
src/frequency.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(_slsdet PUBLIC
|
||||
|
||||
@@ -34,6 +34,9 @@ scanParameters = _slsdet.scanParameters
|
||||
currentSrcParameters = _slsdet.currentSrcParameters
|
||||
DurationWrapper = _slsdet.DurationWrapper
|
||||
pedestalParameters = _slsdet.pedestalParameters
|
||||
Hz = _slsdet.Hz
|
||||
kHz = _slsdet.kHz
|
||||
MHz = _slsdet.MHz
|
||||
|
||||
import os
|
||||
def read_version():
|
||||
|
||||
+32
-13
@@ -14,11 +14,12 @@ powerIndex = slsDetectorDefs.powerIndex
|
||||
detectorType = slsDetectorDefs.detectorType
|
||||
streamingInterface = slsDetectorDefs.streamingInterface
|
||||
|
||||
|
||||
defs = slsDetectorDefs
|
||||
|
||||
from .utils import element_if_equal, all_equal, get_set_bits, list_to_bitmask
|
||||
from .utils import Geometry, to_geo, element, reduce_time, is_iterable, hostname_list
|
||||
from ._slsdet import xy, freeSharedMemory, getUserDetails
|
||||
from ._slsdet import xy, Hz, freeSharedMemory, getUserDetails
|
||||
from .gaincaps import Mythen3GainCapsWrapper
|
||||
from . import utils as ut
|
||||
from .proxy import JsonProxy, ClkDivProxy, MaxPhaseProxy, ClkFreqProxy, PatLoopProxy, PatNLoopProxy, PatWaitProxy, PatWaitTimeProxy
|
||||
@@ -3441,15 +3442,21 @@ class Detector(CppDetectorApi):
|
||||
@element
|
||||
def runclk(self):
|
||||
"""
|
||||
[Ctb] Sets Run clock frequency in MHz. \n
|
||||
[Xilinx Ctb] Sets Run clock frequency in kHz.
|
||||
"""
|
||||
[Ctb][Xilinx Ctb] Sets Run clock frequency.
|
||||
|
||||
Example
|
||||
--------
|
||||
>>> d.runclk
|
||||
>>> 10MHz
|
||||
>>> d.runclk = MHz(5)
|
||||
>>> d.runclk = Hz(5 * 1000 * 1000)
|
||||
>>> d.runclk = kHz(2000)
|
||||
"""
|
||||
return self.getRUNClock()
|
||||
|
||||
@runclk.setter
|
||||
def runclk(self, freq):
|
||||
ut.set_using_dict(self.setRUNClock, freq)
|
||||
ut.set_using_dict(self.setRUNClock, freq)
|
||||
|
||||
@property
|
||||
@element
|
||||
@@ -3526,10 +3533,16 @@ class Detector(CppDetectorApi):
|
||||
@element
|
||||
def dbitclk(self):
|
||||
"""
|
||||
[Ctb] Sets clock for latching the digital bits in MHz. \n
|
||||
[Xilinx Ctb] clock for latching the digital bits in kHz.
|
||||
"""
|
||||
[Ctb][Xilinx Ctb] Sets clock for latching the digital bits.
|
||||
|
||||
Example
|
||||
--------
|
||||
>>> d.dbitclk
|
||||
>>> 10MHz
|
||||
>>> d.dbitclk = MHz(5)
|
||||
>>> d.dbitclk = Hz(5 * 1000 * 1000)
|
||||
>>> d.dbitclk = kHz(2000)
|
||||
"""
|
||||
return self.getDBITClock()
|
||||
|
||||
@dbitclk.setter
|
||||
@@ -3657,10 +3670,16 @@ class Detector(CppDetectorApi):
|
||||
@element
|
||||
def adcclk(self):
|
||||
"""
|
||||
[Ctb] Sets ADC clock frequency in MHz. \n
|
||||
[Xilinx Ctb] Sets ADC clock frequency in kHz.
|
||||
"""
|
||||
[Ctb][Xilinx Ctb] Sets ADC clock frequency.
|
||||
|
||||
Example
|
||||
--------
|
||||
>>> d.adcclk
|
||||
>>> 10MHz
|
||||
>>> d.adcclk = MHz(5)
|
||||
>>> d.adcclk = Hz(5 * 1000 * 1000)
|
||||
>>> d.adcclk = kHz(2000)
|
||||
"""
|
||||
return self.getADCClock()
|
||||
|
||||
@adcclk.setter
|
||||
@@ -3671,7 +3690,7 @@ class Detector(CppDetectorApi):
|
||||
@element
|
||||
def syncclk(self):
|
||||
"""
|
||||
[Ctb] Sync clock in MHz.
|
||||
[Ctb] Sync clock.
|
||||
|
||||
:setter: Not implemented
|
||||
"""
|
||||
@@ -3704,7 +3723,7 @@ class Detector(CppDetectorApi):
|
||||
[Ctb][Mythen3][Xilinx Ctb] Gets the pattern file name including path of the last pattern uploaded. Returns an empty if nothing was uploaded or via a server default
|
||||
file
|
||||
"""
|
||||
return self.getPatterFileName()
|
||||
return self.getPatternFileName()
|
||||
|
||||
def patternstart(self):
|
||||
"""[Ctb][Mythen3][Xilinx Ctb] Starts pattern. """
|
||||
|
||||
+15
-15
@@ -1529,23 +1529,31 @@ void init_det(py::module &m) {
|
||||
Detector::setNumberOfAnalogSamples,
|
||||
py::arg(), py::arg() = Positions{});
|
||||
CppDetectorApi.def("getADCClock",
|
||||
(Result<int>(Detector::*)(sls::Positions) const) &
|
||||
(Result<defs::Hz>(Detector::*)(sls::Positions) const) &
|
||||
Detector::getADCClock,
|
||||
py::arg() = Positions{});
|
||||
CppDetectorApi.def("setADCClock",
|
||||
(void (Detector::*)(int, sls::Positions)) &
|
||||
(void (Detector::*)(defs::Hz, sls::Positions)) &
|
||||
Detector::setADCClock,
|
||||
py::arg(), py::arg() = Positions{});
|
||||
CppDetectorApi.def("getRUNClock",
|
||||
(Result<int>(Detector::*)(sls::Positions) const) &
|
||||
(Result<defs::Hz>(Detector::*)(sls::Positions) const) &
|
||||
Detector::getRUNClock,
|
||||
py::arg() = Positions{});
|
||||
CppDetectorApi.def("setRUNClock",
|
||||
(void (Detector::*)(int, sls::Positions)) &
|
||||
(void (Detector::*)(defs::Hz, sls::Positions)) &
|
||||
Detector::setRUNClock,
|
||||
py::arg(), py::arg() = Positions{});
|
||||
CppDetectorApi.def("getDBITClock",
|
||||
(Result<defs::Hz>(Detector::*)(sls::Positions) const) &
|
||||
Detector::getDBITClock,
|
||||
py::arg() = Positions{});
|
||||
CppDetectorApi.def("setDBITClock",
|
||||
(void (Detector::*)(defs::Hz, sls::Positions)) &
|
||||
Detector::setDBITClock,
|
||||
py::arg(), py::arg() = Positions{});
|
||||
CppDetectorApi.def("getSYNCClock",
|
||||
(Result<int>(Detector::*)(sls::Positions) const) &
|
||||
(Result<defs::Hz>(Detector::*)(sls::Positions) const) &
|
||||
Detector::getSYNCClock,
|
||||
py::arg() = Positions{});
|
||||
CppDetectorApi.def("getPowerList",
|
||||
@@ -1646,14 +1654,6 @@ void init_det(py::module &m) {
|
||||
(void (Detector::*)(defs::readoutMode, sls::Positions)) &
|
||||
Detector::setReadoutMode,
|
||||
py::arg(), py::arg() = Positions{});
|
||||
CppDetectorApi.def("getDBITClock",
|
||||
(Result<int>(Detector::*)(sls::Positions) const) &
|
||||
Detector::getDBITClock,
|
||||
py::arg() = Positions{});
|
||||
CppDetectorApi.def("setDBITClock",
|
||||
(void (Detector::*)(int, sls::Positions)) &
|
||||
Detector::setDBITClock,
|
||||
py::arg(), py::arg() = Positions{});
|
||||
CppDetectorApi.def("getExternalSamplingSource",
|
||||
(Result<int>(Detector::*)(sls::Positions) const) &
|
||||
Detector::getExternalSamplingSource,
|
||||
@@ -1891,9 +1891,9 @@ void init_det(py::module &m) {
|
||||
Detector::configureTransceiver,
|
||||
py::arg() = Positions{});
|
||||
CppDetectorApi.def(
|
||||
"getPatterFileName",
|
||||
"getPatternFileName",
|
||||
(Result<std::string>(Detector::*)(sls::Positions) const) &
|
||||
Detector::getPatterFileName,
|
||||
Detector::getPatternFileName,
|
||||
py::arg() = Positions{});
|
||||
CppDetectorApi.def(
|
||||
"setPattern",
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-other
|
||||
// Copyright (C) 2021 Contributors to the SLS Detector Package
|
||||
/*
|
||||
This file contains Python bindings for the Hz and for conversion to other units from and to string.
|
||||
*/
|
||||
#include "py_headers.h"
|
||||
#include <cmath>
|
||||
|
||||
#include "sls/ToString.h"
|
||||
#include "sls/sls_detector_defs.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
constexpr double kHz = 1e3;
|
||||
constexpr double MHz = 1e6;
|
||||
|
||||
void init_freq(py::module &m) {
|
||||
|
||||
py::class_<slsDetectorDefs::Hz> Hz(m, "Hz");
|
||||
Hz.def(py::init<int>());
|
||||
Hz.def_readwrite("value", &slsDetectorDefs::Hz::value);
|
||||
Hz.def("__repr__", [](const slsDetectorDefs::Hz &f) {
|
||||
return sls::ToString(f);
|
||||
});
|
||||
Hz.def("__str__", [](const slsDetectorDefs::Hz &f) {
|
||||
return sls::ToString(f);
|
||||
});
|
||||
|
||||
Hz.def(py::self == py::self);
|
||||
Hz.def("__mul__", [](const slsDetectorDefs::Hz &h, int x) {
|
||||
return slsDetectorDefs::Hz(h.value * x);
|
||||
}, py::is_operator());
|
||||
Hz.def("__rmul__", [](const slsDetectorDefs::Hz &h, int x) {
|
||||
return slsDetectorDefs::Hz(h.value * x);
|
||||
}, py::is_operator());
|
||||
Hz.def("__truediv__", [](const slsDetectorDefs::Hz &h, int x) {
|
||||
return slsDetectorDefs::Hz(h.value / x);
|
||||
}, py::is_operator());
|
||||
Hz.def("__add__", [](const slsDetectorDefs::Hz &a,
|
||||
const slsDetectorDefs::Hz &b) {
|
||||
return slsDetectorDefs::Hz(a.value + b.value);
|
||||
}, py::is_operator());
|
||||
Hz.def("__sub__", [](const slsDetectorDefs::Hz &a,
|
||||
const slsDetectorDefs::Hz &b) {
|
||||
return slsDetectorDefs::Hz(a.value - b.value);
|
||||
}, py::is_operator());
|
||||
|
||||
m.def("kHz", [](double v) {
|
||||
return slsDetectorDefs::Hz(static_cast<int>(std::round(v * kHz)));
|
||||
});
|
||||
|
||||
m.def("MHz", [](double v) {
|
||||
return slsDetectorDefs::Hz(static_cast<int>(std::round(v * MHz)));
|
||||
});
|
||||
}
|
||||
@@ -21,6 +21,7 @@ void init_source(py::module &);
|
||||
void init_duration(py::module &);
|
||||
void init_pedestal(py::module &);
|
||||
void init_bit(py::module &);
|
||||
void init_freq(py::module &);
|
||||
|
||||
PYBIND11_MODULE(_slsdet, m) {
|
||||
m.doc() = R"pbdoc(
|
||||
@@ -42,6 +43,7 @@ PYBIND11_MODULE(_slsdet, m) {
|
||||
init_duration(m);
|
||||
init_pedestal(m);
|
||||
init_bit(m);
|
||||
init_freq(m);
|
||||
// init_experimental(m);
|
||||
|
||||
py::module io = m.def_submodule("io", "Submodule for io");
|
||||
|
||||
@@ -396,6 +396,193 @@ def test_patternstart(session_simulator, request):
|
||||
Log(LogLevel.INFOGREEN, f"✅ {request.node.name} passed")
|
||||
|
||||
|
||||
|
||||
@pytest.mark.detectorintegration
|
||||
def test_runclk(session_simulator, request):
|
||||
""" Test using runclk for ctb and xilinx_ctb."""
|
||||
det_type, num_interfaces, num_mods, d = session_simulator
|
||||
assert d is not None
|
||||
|
||||
from slsdet import Hz, MHz, kHz
|
||||
|
||||
if det_type in ['ctb', 'xilinx_ctb']:
|
||||
prev_runclk = d.getRUNClock()
|
||||
|
||||
d.runclk
|
||||
|
||||
# invalid value type
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.runclk = 5e6
|
||||
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.runclk = 5 * 1000 * 1000
|
||||
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.runclk = Hz(5e6)
|
||||
|
||||
d.runclk = MHz(15)
|
||||
assert d.runclk.value == 15_000_000
|
||||
|
||||
d.runclk = MHz(14.5)
|
||||
assert d.runclk.value == 14_500_000
|
||||
|
||||
d.runclk = kHz(15000.5)
|
||||
assert d.runclk.value == 15_000_500
|
||||
|
||||
# invalid values from server
|
||||
# max is 300MHz
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.runclk = MHz(301)
|
||||
|
||||
# min is 2MHz for ctb and 10MHz for xilinx_ctb
|
||||
if det_type == 'ctb':
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.runclk = MHz(1)
|
||||
else:
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.runclk = MHz(9)
|
||||
|
||||
c = MHz(2)
|
||||
for rc in [5, 10, 15, 20]:
|
||||
d.runclk = rc * c
|
||||
assert d.runclk.value == 40_000_000
|
||||
|
||||
for i in range(len(d)):
|
||||
d.setRUNClock(prev_runclk[i], [i])
|
||||
|
||||
Log(LogLevel.INFOGREEN, f"✅ {request.node.name} passed")
|
||||
|
||||
|
||||
@pytest.mark.detectorintegration
|
||||
def test_adcclk(session_simulator, request):
|
||||
""" Test using adcclk for ctb and xilinx_ctb."""
|
||||
det_type, num_interfaces, num_mods, d = session_simulator
|
||||
assert d is not None
|
||||
|
||||
from slsdet import Hz, MHz, kHz
|
||||
|
||||
if det_type in ['ctb', 'xilinx_ctb']:
|
||||
prev_adcclk = d.getADCClock()
|
||||
|
||||
d.adcclk
|
||||
|
||||
# invalid value type
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.adcclk = 5e6
|
||||
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.adcclk = 5 * 1000 * 1000
|
||||
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.adcclk = Hz(5e6)
|
||||
|
||||
d.adcclk = MHz(15)
|
||||
assert d.adcclk.value == 15_000_000
|
||||
|
||||
d.adcclk = MHz(14.5)
|
||||
assert d.adcclk.value == 14_500_000
|
||||
|
||||
d.adcclk = kHz(15000.5)
|
||||
assert d.adcclk.value == 15_000_500
|
||||
|
||||
# invalid values from server
|
||||
# max is 300MHz for xilinx and 54 MHz for ctb
|
||||
if det_type == 'ctb':
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.adcclk = MHz(66)
|
||||
else:
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.adcclk = MHz(301)
|
||||
|
||||
# min is 2MHz for ctb and 10MHz for xilinx_ctb
|
||||
if det_type == 'ctb':
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.adcclk = MHz(1)
|
||||
else:
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.adcclk = MHz(9)
|
||||
|
||||
c = MHz(2)
|
||||
for rc in [5, 10, 15, 20]:
|
||||
d.adcclk = rc * c
|
||||
assert d.adcclk.value == 40_000_000
|
||||
|
||||
for i in range(len(d)):
|
||||
d.setADCClock(prev_adcclk[i], [i])
|
||||
|
||||
Log(LogLevel.INFOGREEN, f"✅ {request.node.name} passed")
|
||||
|
||||
|
||||
@pytest.mark.detectorintegration
|
||||
def test_dbitclk(session_simulator, request):
|
||||
""" Test using dbitclk for ctb and xilinx_ctb."""
|
||||
det_type, num_interfaces, num_mods, d = session_simulator
|
||||
assert d is not None
|
||||
|
||||
from slsdet import Hz, MHz, kHz
|
||||
|
||||
if det_type in ['ctb', 'xilinx_ctb']:
|
||||
prev_dbitclk = d.getDBITClock()
|
||||
|
||||
d.dbitclk
|
||||
|
||||
# invalid value type
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.dbitclk = 5e6
|
||||
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.dbitclk = 5 * 1000 * 1000
|
||||
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.dbitclk = Hz(5e6)
|
||||
|
||||
d.dbitclk = MHz(15)
|
||||
assert d.dbitclk.value == 15_000_000
|
||||
|
||||
d.dbitclk = MHz(14.5)
|
||||
assert d.dbitclk.value == 14_500_000
|
||||
|
||||
d.dbitclk = kHz(15000.5)
|
||||
assert d.dbitclk.value == 15_000_500
|
||||
|
||||
# invalid values from server
|
||||
# max is 300MHz
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.dbitclk = MHz(301)
|
||||
|
||||
# min is 2MHz for ctb and 10MHz for xilinx_ctb
|
||||
if det_type == 'ctb':
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.dbitclk = MHz(1)
|
||||
else:
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
d.dbitclk = MHz(9)
|
||||
|
||||
c = MHz(2)
|
||||
for rc in [5, 10, 15, 20]:
|
||||
d.dbitclk = rc * c
|
||||
assert d.dbitclk.value == 40_000_000
|
||||
|
||||
for i in range(len(d)):
|
||||
d.setDBITClock(prev_dbitclk[i], [i])
|
||||
|
||||
Log(LogLevel.INFOGREEN, f"✅ {request.node.name} passed")
|
||||
|
||||
|
||||
|
||||
@pytest.mark.detectorintegration
|
||||
def test_syncclk(session_simulator, request):
|
||||
""" Test using syncclk for ctb."""
|
||||
det_type, num_interfaces, num_mods, d = session_simulator
|
||||
assert d is not None
|
||||
|
||||
if det_type in ['ctb']:
|
||||
d.syncclk
|
||||
|
||||
Log(LogLevel.INFOGREEN, f"✅ {request.node.name} passed")
|
||||
|
||||
|
||||
|
||||
@pytest.mark.detectorintegration
|
||||
def test_v_limit(session_simulator, request):
|
||||
"""Test v_limit."""
|
||||
@@ -715,3 +902,4 @@ def test_dac(session_simulator, request):
|
||||
|
||||
|
||||
Log(LogLevel.INFOGREEN, f"✅ {request.node.name} passed")
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
from slsdet import Hz, MHz, kHz
|
||||
|
||||
def test_Hz():
|
||||
f = Hz(1)
|
||||
assert f.value == 1
|
||||
f = Hz(1 * 1000)
|
||||
assert f.value == 1000
|
||||
f = MHz(5)
|
||||
assert f.value == 5_000_000
|
||||
f = MHz(0.5)
|
||||
assert f.value == 500_000
|
||||
f = kHz(2.5)
|
||||
assert f.value == 2500
|
||||
f = kHz(5000)
|
||||
assert f.value == 5_000_000
|
||||
|
||||
def test_rounding_exact():
|
||||
f = MHz(1.234)
|
||||
assert f.value == round(1_234_000)
|
||||
|
||||
|
||||
def test_mul():
|
||||
c = MHz(1)
|
||||
assert (c * 2).value == 2_000_000
|
||||
assert (c * 4).value == 4_000_000
|
||||
|
||||
|
||||
def test_rmul():
|
||||
c = MHz(1)
|
||||
assert (2 * c).value == 2_000_000
|
||||
assert (4 * c).value == 4_000_000
|
||||
|
||||
c = c * 2
|
||||
assert c.value == 2_000_000
|
||||
|
||||
for rc in [1, 2, 4, 8]:
|
||||
c = rc * c
|
||||
assert c.value == 128_000_000
|
||||
|
||||
|
||||
def test_div():
|
||||
c = MHz(1)
|
||||
assert (c / 2).value == 500_000
|
||||
|
||||
def test_eq():
|
||||
assert MHz(1) == MHz(1)
|
||||
assert MHz(1) != MHz(2)
|
||||
assert MHz(1) == kHz(1000)
|
||||
Reference in New Issue
Block a user