mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-05-23 16:22:03 +00:00
feat(gui): PR-R — host control surface fill-in (audit M-2/M-3/M-4/M-6/M-7)
The RTL has been ahead of the host opcode/widget surface since PR-G:
several runtime knobs (MEDIUM PRI, soft-CFAR alpha, ADC power-down) are
fully wired in radar_system_top.v but had no enum / spinbox path, so
the operator could only reach them via raw _send_custom_command. This
PR closes the gap for everything except M-5 (status-packet medium PRI
readback, which needs an RTL change to add a status word).
M-2 — Opcode enum gains MEDIUM_CHIRP=0x17, MEDIUM_LISTEN=0x18,
CFAR_ALPHA_SOFT=0x2D. Truth-table docstring refreshed.
Two new spinboxes in Waveform Timing ("Medium Chirp Cycles",
"Medium Listen Cycles") with the V2 defaults 500 / 15600 (5 us
chirp, 161 us PRI). One new spinbox in Detection (CFAR)
("CFAR Alpha Soft (Q4.4)") with the RP_DEF_CFAR_ALPHA_SOFT=0x18
default.
M-3 — ADC_PWDN=0x32 added to the enum (was previously commented as
"reserved for S-25"; the fix landed at radar_system_top.v:1152
routing to the physical adc_pwdn pin). New "ADC (AD9484)"
group on the right column with two buttons: ADC Normal (0x32=0)
and ADC Power Down (0x32=1). Buttons rather than a spinbox
prevent accidental non-{0,1} values.
M-4 — ADC_FORMAT widget added to the same ADC group: a 2-choice combo
("Offset-binary (SJ1 1-2)" vs "Two's-complement (SJ1 2-3)") with
a Set button, since AD9484 SPI is tied off (CSB high) and the
only way to flip sign convention is via this opcode.
M-6 — Replay opcode dispatch in _dispatch_to_software_fpga() expanded:
SoftwareFPGA gains cfar_alpha_soft mirror + setter; 0x2D wired
through. RTL-only opcodes (chirp timing, range mode, ADC strap,
self-test, status_request) are no longer silently dropped — they
log at info-level "acknowledged (no effect on replay — RTL-only
state)" so the operator gets visible feedback.
M-7 — Chirps Per Elevation widget default 32 -> 48; hint changed from
"1-32, clamped" to "must be 48 (RTL clamps)". RTL latches
chirps_mismatch_error in status word 4 bit 10 for any value != 48
since PR-F. Bonus: SHORT defaults bumped 50/17450 -> 100/17400 to
match RP_DEF_SHORT_*_CYCLES_V2 (PR-E 1-us SHORT chirp width).
Tests: +10 (TestOpcodeEnumFillIn 5, TestSoftwareFpgaCfarAlphaSoft 2,
TestReplayOpcodeDispatch 3). 247/247 PASS. Ruff clean.
M-5 (status packet medium_chirp/medium_listen readback) deferred —
needs an RTL change to extend status_words from 7 to 8 (current word 3
has only 10 reserved bits, not enough for two 16-bit fields).
This commit is contained in:
@@ -111,17 +111,19 @@ class Opcode(IntEnum):
|
||||
"""Host register opcodes — must match radar_system_top.v case(usb_cmd_opcode).
|
||||
|
||||
FPGA truth table (from radar_system_top.v opcode dispatch case-block):
|
||||
0x01 host_radar_mode 0x16 host_gain_shift
|
||||
0x02 host_trigger_pulse 0x17 host_medium_chirp_cycles (M-2 — no enum yet)
|
||||
0x03 host_detect_threshold 0x18 host_medium_listen_cycles (M-2 — no enum yet)
|
||||
0x04 host_stream_control 0x20 host_range_mode
|
||||
0x10 host_long_chirp_cycles 0x21-0x27 CFAR / MTI / DC-notch
|
||||
0x11 host_long_listen_cycles 0x28-0x2C AGC control
|
||||
0x12 host_guard_cycles 0x2D host_cfar_alpha_soft (M-2 — no enum yet)
|
||||
0x13 host_short_chirp_cycles 0x30 host_self_test_trigger
|
||||
0x14 host_short_listen_cycles 0x31/0xFF host_status_request
|
||||
0x15 host_chirps_per_elev 0x32 host_adc_pwdn (M-3 — no enum yet)
|
||||
0x33 host_adc_format (AD9484 SCLK/DFS strap; AUDIT-C3)
|
||||
0x01 host_radar_mode 0x20 host_range_mode
|
||||
0x02 host_trigger_pulse 0x21-0x27 CFAR / MTI / DC-notch
|
||||
0x03 host_detect_threshold 0x28-0x2C AGC control
|
||||
0x04 host_stream_control 0x2D host_cfar_alpha_soft
|
||||
0x10 host_long_chirp_cycles 0x30 host_self_test_trigger
|
||||
0x11 host_long_listen_cycles 0x31/0xFF host_status_request
|
||||
0x12 host_guard_cycles 0x32 host_adc_pwdn
|
||||
0x13 host_short_chirp_cycles 0x33 host_adc_format
|
||||
0x14 host_short_listen_cycles
|
||||
0x15 host_chirps_per_elev
|
||||
0x16 host_gain_shift
|
||||
0x17 host_medium_chirp_cycles (PR-G G2)
|
||||
0x18 host_medium_listen_cycles (PR-G G2)
|
||||
"""
|
||||
# --- Basic control (0x01-0x04) ---
|
||||
RADAR_MODE = 0x01 # 2-bit mode select
|
||||
@@ -132,13 +134,17 @@ class Opcode(IntEnum):
|
||||
# --- Digital gain (0x16) ---
|
||||
GAIN_SHIFT = 0x16 # 4-bit digital gain shift
|
||||
|
||||
# --- Chirp timing (0x10-0x15) ---
|
||||
# --- Chirp timing (0x10-0x18) ---
|
||||
LONG_CHIRP = 0x10
|
||||
LONG_LISTEN = 0x11
|
||||
GUARD = 0x12
|
||||
SHORT_CHIRP = 0x13
|
||||
SHORT_LISTEN = 0x14
|
||||
CHIRPS_PER_ELEV = 0x15
|
||||
# PR-G G2 / PR-Q.1: MEDIUM ladder. Defaults RP_DEF_MEDIUM_*_CYCLES_V2 give
|
||||
# PRI = 161 us so the 3-PRI CRT unfolder has 3 distinct PRIs (175/161/167).
|
||||
MEDIUM_CHIRP = 0x17
|
||||
MEDIUM_LISTEN = 0x18
|
||||
|
||||
# --- Signal processing (0x20-0x27) ---
|
||||
RANGE_MODE = 0x20
|
||||
@@ -157,18 +163,25 @@ class Opcode(IntEnum):
|
||||
AGC_DECAY = 0x2B
|
||||
AGC_HOLDOFF = 0x2C
|
||||
|
||||
# --- 2-tier CFAR soft threshold (0x2D, PR-G G1) ---
|
||||
# 8-bit Q4.4 alpha for the soft (CAND) tier of the 2-class CFAR. Default
|
||||
# RP_DEF_CFAR_ALPHA_SOFT = 0x18 (1.5 in Q4.4) corresponds to ~Pfa 1e-5.
|
||||
CFAR_ALPHA_SOFT = 0x2D
|
||||
|
||||
# --- Board self-test / status (0x30-0x31, 0xFF) ---
|
||||
SELF_TEST_TRIGGER = 0x30
|
||||
SELF_TEST_STATUS = 0x31
|
||||
STATUS_REQUEST = 0xFF
|
||||
|
||||
# --- AD9484 ADC sign-convention (0x33, AUDIT-C3) ---
|
||||
# 2'b00 = offset-binary (default; SJ1 jumper pins 1-2 bridged)
|
||||
# 2'b01 = two's-complement (SJ1 jumper pins 2-3 bridged)
|
||||
# --- AD9484 ADC power + sign convention (0x32, 0x33; AUDIT-C3 / S-25) ---
|
||||
# 0x32 ADC_PWDN: 1-bit power-down driving the AD9484 PWDN pin
|
||||
# (radar_system_top.v -> physical adc_pwdn). 0=normal, 1=PD.
|
||||
# 0x33 ADC_FORMAT: 2'b00 = offset-binary (SJ1 pins 1-2 bridged, default),
|
||||
# 2'b01 = two's-complement (SJ1 pins 2-3 bridged).
|
||||
# AD9484 CSB is hard-tied HIGH on the Main Board (SPI unavailable);
|
||||
# this opcode lets the host adapt the DDC to the physical strap
|
||||
# 0x33 lets the host adapt the DDC sign convention to the physical strap
|
||||
# without rebuilding the bitstream.
|
||||
# (Opcode 0x32 is reserved for the future AUDIT-S25 adc_pwdn fix.)
|
||||
ADC_PWDN = 0x32
|
||||
ADC_FORMAT = 0x33
|
||||
|
||||
|
||||
|
||||
@@ -1627,6 +1627,85 @@ class TestDashboardConfidenceDisplay(unittest.TestCase):
|
||||
self.assertEqual(color.name().upper(), DARK_TEXT.upper())
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Test: PR-R / audit M-2..M-7 — host control surface fill-in
|
||||
# =============================================================================
|
||||
|
||||
class TestOpcodeEnumFillIn(unittest.TestCase):
|
||||
"""M-2 + M-3: enum gains MEDIUM_CHIRP, MEDIUM_LISTEN, CFAR_ALPHA_SOFT, ADC_PWDN."""
|
||||
|
||||
def test_medium_chirp_listen_opcodes(self):
|
||||
from radar_protocol import Opcode
|
||||
self.assertEqual(Opcode.MEDIUM_CHIRP.value, 0x17)
|
||||
self.assertEqual(Opcode.MEDIUM_LISTEN.value, 0x18)
|
||||
|
||||
def test_cfar_alpha_soft_opcode(self):
|
||||
from radar_protocol import Opcode
|
||||
self.assertEqual(Opcode.CFAR_ALPHA_SOFT.value, 0x2D)
|
||||
|
||||
def test_adc_pwdn_opcode(self):
|
||||
from radar_protocol import Opcode
|
||||
self.assertEqual(Opcode.ADC_PWDN.value, 0x32)
|
||||
|
||||
def test_adc_format_opcode_unchanged(self):
|
||||
from radar_protocol import Opcode
|
||||
self.assertEqual(Opcode.ADC_FORMAT.value, 0x33)
|
||||
|
||||
def test_no_duplicate_opcodes(self):
|
||||
"""All Opcode values are unique (catches accidental collisions)."""
|
||||
from radar_protocol import Opcode
|
||||
values = [op.value for op in Opcode]
|
||||
self.assertEqual(len(values), len(set(values)),
|
||||
"duplicate opcode values would silently shadow earlier entries")
|
||||
|
||||
|
||||
class TestSoftwareFpgaCfarAlphaSoft(unittest.TestCase):
|
||||
"""M-6: SoftwareFPGA mirrors the soft-tier alpha and clamps to 8 bits."""
|
||||
|
||||
def test_default(self):
|
||||
from v7.software_fpga import SoftwareFPGA
|
||||
fpga = SoftwareFPGA()
|
||||
self.assertEqual(fpga.cfar_alpha_soft, 0x18) # RP_DEF_CFAR_ALPHA_SOFT
|
||||
|
||||
def test_setter_masks_to_8_bits(self):
|
||||
from v7.software_fpga import SoftwareFPGA
|
||||
fpga = SoftwareFPGA()
|
||||
fpga.set_cfar_alpha_soft(0x1234)
|
||||
self.assertEqual(fpga.cfar_alpha_soft, 0x34)
|
||||
|
||||
|
||||
@unittest.skipUnless(_pyqt6_available(), "PyQt6 not installed")
|
||||
class TestReplayOpcodeDispatch(unittest.TestCase):
|
||||
"""M-6: replay dispatch routes 0x2D to SoftwareFPGA + acknowledges inert opcodes."""
|
||||
|
||||
def _dashboard_with_replay(self):
|
||||
"""Build a minimal dashboard-like object: just what _dispatch_to_software_fpga needs."""
|
||||
from v7.software_fpga import SoftwareFPGA
|
||||
from v7.dashboard import RadarDashboard
|
||||
# Bypass full QMainWindow init — call the unbound method against a
|
||||
# fake `self` that only carries the two attributes the dispatch reads.
|
||||
class _Fake:
|
||||
pass
|
||||
fake = _Fake()
|
||||
fake._software_fpga = SoftwareFPGA()
|
||||
return RadarDashboard._dispatch_to_software_fpga, fake
|
||||
|
||||
def test_0x2d_routed_to_set_cfar_alpha_soft(self):
|
||||
dispatch, fake = self._dashboard_with_replay()
|
||||
dispatch(fake, 0x2D, 42)
|
||||
self.assertEqual(fake._software_fpga.cfar_alpha_soft, 42)
|
||||
|
||||
def test_inert_opcode_does_not_raise(self):
|
||||
"""Inert opcodes (e.g. 0x32 ADC_PWDN) accepted without exception."""
|
||||
dispatch, fake = self._dashboard_with_replay()
|
||||
for inert in (0x10, 0x15, 0x17, 0x18, 0x20, 0x32, 0x33, 0xFF):
|
||||
dispatch(fake, inert, 1) # should not raise
|
||||
|
||||
def test_unknown_opcode_does_not_raise(self):
|
||||
dispatch, fake = self._dashboard_with_replay()
|
||||
dispatch(fake, 0xEE, 0) # unmapped — debug-log only, no exception
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Helper: lazy import of v7.models
|
||||
# =============================================================================
|
||||
|
||||
@@ -759,13 +759,21 @@ class RadarDashboard(QMainWindow):
|
||||
grp_wf = QGroupBox("Waveform Timing")
|
||||
wf_layout = QVBoxLayout(grp_wf)
|
||||
|
||||
# PR-R / M-2: MEDIUM chirp+listen exposed (RTL has had 0x17/0x18 since
|
||||
# PR-G G2; defaults RP_DEF_MEDIUM_*_CYCLES_V2 = 500 / 15600 give
|
||||
# PRI = 161 us for the 3-PRI ladder).
|
||||
# PR-R / M-7: CHIRPS_PER_ELEV default 32 -> 48 to match PR-F's
|
||||
# RP_CHIRPS_PER_FRAME = 48; FPGA latches `chirps_mismatch_error` for
|
||||
# any value other than 48, so the spinbox cannot offer 32 anymore.
|
||||
wf_params = [
|
||||
("Long Chirp Cycles", 0x10, 3000, 16, "0-65535, rst=3000"),
|
||||
("Long Listen Cycles", 0x11, 13700, 16, "0-65535, rst=13700"),
|
||||
("Guard Cycles", 0x12, 17540, 16, "0-65535, rst=17540"),
|
||||
("Short Chirp Cycles", 0x13, 50, 16, "0-65535, rst=50"),
|
||||
("Short Listen Cycles", 0x14, 17450, 16, "0-65535, rst=17450"),
|
||||
("Chirps Per Elevation", 0x15, 32, 6, "1-32, clamped"),
|
||||
("Long Chirp Cycles", 0x10, 3000, 16, "0-65535, rst=3000"),
|
||||
("Long Listen Cycles", 0x11, 13700, 16, "0-65535, rst=13700"),
|
||||
("Guard Cycles", 0x12, 17540, 16, "0-65535, rst=17540"),
|
||||
("Short Chirp Cycles", 0x13, 100, 16, "0-65535, rst=100 (1us @100MHz)"),
|
||||
("Short Listen Cycles", 0x14, 17400, 16, "0-65535, rst=17400 (175us PRI)"),
|
||||
("Medium Chirp Cycles", 0x17, 500, 16, "0-65535, rst=500 (5us @100MHz)"),
|
||||
("Medium Listen Cycles", 0x18, 15600, 16, "0-65535, rst=15600 (161us PRI)"),
|
||||
("Chirps Per Elevation", 0x15, 48, 6, "must be 48 (RTL clamps)"),
|
||||
]
|
||||
for label, opcode, default, bits, hint in wf_params:
|
||||
self._add_fpga_param_row(wf_layout, label, opcode, default, bits, hint)
|
||||
@@ -782,12 +790,15 @@ class RadarDashboard(QMainWindow):
|
||||
grp_cfar = QGroupBox("Detection (CFAR)")
|
||||
cfar_layout = QVBoxLayout(grp_cfar)
|
||||
|
||||
# PR-R / M-2: CFAR_ALPHA_SOFT (0x2D) is the soft-tier (CAND) threshold
|
||||
# of the 2-class CFAR; default RP_DEF_CFAR_ALPHA_SOFT = 0x18 (1.5 Q4.4).
|
||||
cfar_params = [
|
||||
("CFAR Enable", 0x25, 0, 1, "0=off, 1=on"),
|
||||
("CFAR Guard Cells", 0x21, 2, 4, "0-15, rst=2"),
|
||||
("CFAR Train Cells", 0x22, 8, 5, "1-31, rst=8"),
|
||||
("CFAR Alpha (Q4.4)", 0x23, 48, 8, "0-255, rst=0x30=3.0"),
|
||||
("CFAR Mode", 0x24, 0, 2, "0=CA 1=GO 2=SO"),
|
||||
("CFAR Enable", 0x25, 0, 1, "0=off, 1=on"),
|
||||
("CFAR Guard Cells", 0x21, 2, 4, "0-15, rst=2"),
|
||||
("CFAR Train Cells", 0x22, 8, 5, "1-31, rst=8"),
|
||||
("CFAR Alpha (Q4.4)", 0x23, 48, 8, "0-255, rst=0x30=3.0"),
|
||||
("CFAR Alpha Soft (Q4.4)", 0x2D, 24, 8, "0-255, rst=0x18=1.5"),
|
||||
("CFAR Mode", 0x24, 0, 2, "0=CA 1=GO 2=SO"),
|
||||
]
|
||||
for label, opcode, default, bits, hint in cfar_params:
|
||||
self._add_fpga_param_row(cfar_layout, label, opcode, default, bits, hint)
|
||||
@@ -846,6 +857,41 @@ class RadarDashboard(QMainWindow):
|
||||
|
||||
right_layout.addWidget(grp_agc)
|
||||
|
||||
# ── ADC (AD9484) ──────────────────────────────────────────────
|
||||
# PR-R / M-3 + M-4: AD9484 has SPI tied off (CSB high) so all runtime
|
||||
# control is via these two opcodes. PWDN is the physical AD9484
|
||||
# power-down pin; FORMAT switches the DDC sign convention to match
|
||||
# the SJ1 strap (offset-binary vs two's-complement).
|
||||
grp_adc = QGroupBox("ADC (AD9484)")
|
||||
adc_layout = QVBoxLayout(grp_adc)
|
||||
|
||||
# Power-down toggle (0x32). Two buttons rather than a spinbox so
|
||||
# the operator cannot accidentally type a non-{0,1} value.
|
||||
adc_pd_row = QHBoxLayout()
|
||||
btn_adc_normal = QPushButton("ADC Normal")
|
||||
btn_adc_normal.clicked.connect(lambda: self._send_fpga_cmd(0x32, 0))
|
||||
adc_pd_row.addWidget(btn_adc_normal)
|
||||
btn_adc_pd = QPushButton("ADC Power Down")
|
||||
btn_adc_pd.clicked.connect(lambda: self._send_fpga_cmd(0x32, 1))
|
||||
adc_pd_row.addWidget(btn_adc_pd)
|
||||
adc_layout.addLayout(adc_pd_row)
|
||||
|
||||
# Sign-convention combo (0x33). 0 = offset-binary (default), 1 = two's-
|
||||
# complement. _add_fpga_param_row would force a spinbox, so do it inline.
|
||||
adc_fmt_row = QHBoxLayout()
|
||||
adc_fmt_row.addWidget(QLabel("ADC Format:"))
|
||||
self._adc_format_combo = QComboBox()
|
||||
self._adc_format_combo.addItem("Offset-binary (SJ1 1-2)", 0)
|
||||
self._adc_format_combo.addItem("Two's-complement (SJ1 2-3)", 1)
|
||||
adc_fmt_row.addWidget(self._adc_format_combo, stretch=1)
|
||||
btn_adc_fmt = QPushButton("Set")
|
||||
btn_adc_fmt.clicked.connect(
|
||||
lambda: self._send_fpga_cmd(0x33, self._adc_format_combo.currentData()))
|
||||
adc_fmt_row.addWidget(btn_adc_fmt)
|
||||
adc_layout.addLayout(adc_fmt_row)
|
||||
|
||||
right_layout.addWidget(grp_adc)
|
||||
|
||||
# Custom Command
|
||||
grp_custom = QGroupBox("Custom Command")
|
||||
cust_layout = QGridLayout(grp_custom)
|
||||
@@ -1600,11 +1646,21 @@ class RadarDashboard(QMainWindow):
|
||||
self._replay_frame_label.setText(f"{current} / {total}")
|
||||
|
||||
def _dispatch_to_software_fpga(self, opcode: int, value: int):
|
||||
"""Route an FPGA opcode+value to the SoftwareFPGA setter."""
|
||||
"""Route an FPGA opcode+value to the SoftwareFPGA setter.
|
||||
|
||||
PR-R / M-6: opcodes split into three classes:
|
||||
- APPLIED — affect the host signal-processing chain in replay
|
||||
(CFAR, MTI, DC-notch, AGC mirror, gain, threshold).
|
||||
- INERT — RTL-only state with no effect on already-recorded
|
||||
playback (chirp timing, range mode, ADC controls,
|
||||
self-test, status). Logged at info so the operator
|
||||
sees the change was acknowledged but not applied.
|
||||
- UNKNOWN — unmapped opcode, debug-only.
|
||||
"""
|
||||
fpga = self._software_fpga
|
||||
if fpga is None:
|
||||
return
|
||||
_opcode_dispatch = {
|
||||
applied = {
|
||||
0x03: lambda v: fpga.set_detect_threshold(v),
|
||||
0x16: lambda v: fpga.set_gain_shift(v),
|
||||
0x21: lambda v: fpga.set_cfar_guard(v),
|
||||
@@ -1619,11 +1675,25 @@ class RadarDashboard(QMainWindow):
|
||||
0x2A: lambda v: fpga.set_agc_params(attack=v),
|
||||
0x2B: lambda v: fpga.set_agc_params(decay=v),
|
||||
0x2C: lambda v: fpga.set_agc_params(holdoff=v),
|
||||
0x2D: lambda v: fpga.set_cfar_alpha_soft(v),
|
||||
}
|
||||
handler = _opcode_dispatch.get(opcode)
|
||||
# Inert in replay: RTL-only chirp timing / range mode / self-test /
|
||||
# status / ADC strap. The recorded I/Q already reflects whatever
|
||||
# values were active at capture time; changing them now would
|
||||
# require re-running the chirp generator.
|
||||
inert = {
|
||||
0x01, 0x02, 0x04,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x17, 0x18,
|
||||
0x20, 0x30, 0x31, 0x32, 0x33, 0xFF,
|
||||
}
|
||||
handler = applied.get(opcode)
|
||||
if handler is not None:
|
||||
handler(value)
|
||||
logger.info(f"SoftwareFPGA: 0x{opcode:02X} = {value}")
|
||||
elif opcode in inert:
|
||||
logger.info(
|
||||
f"SoftwareFPGA: 0x{opcode:02X} = {value} acknowledged "
|
||||
f"(no effect on replay — RTL-only state)")
|
||||
else:
|
||||
logger.debug(f"SoftwareFPGA: opcode 0x{opcode:02X} not handled (no-op)")
|
||||
|
||||
|
||||
@@ -91,7 +91,8 @@ class SoftwareFPGA:
|
||||
self.cfar_enable: bool = False # 0x25
|
||||
self.cfar_guard: int = 2 # 0x21
|
||||
self.cfar_train: int = 8 # 0x22
|
||||
self.cfar_alpha: int = 0x30 # 0x23 Q4.4
|
||||
self.cfar_alpha: int = 0x30 # 0x23 Q4.4 (CONFIRM tier)
|
||||
self.cfar_alpha_soft: int = 0x18 # 0x2D Q4.4 (CAND tier, PR-G)
|
||||
self.cfar_mode: int = 0 # 0x24 0=CA,1=GO,2=SO
|
||||
|
||||
# MTI
|
||||
@@ -129,6 +130,9 @@ class SoftwareFPGA:
|
||||
def set_cfar_alpha(self, val: int) -> None:
|
||||
self.cfar_alpha = int(val) & 0xFF
|
||||
|
||||
def set_cfar_alpha_soft(self, val: int) -> None:
|
||||
self.cfar_alpha_soft = int(val) & 0xFF
|
||||
|
||||
def set_cfar_mode(self, val: int) -> None:
|
||||
self.cfar_mode = int(val) & 0x03
|
||||
|
||||
|
||||
Reference in New Issue
Block a user