add DMR EMB and QR 16,7,6 tests; correct issue where QR 16,7,6 decode() was correcting and returning the wrong number of bits; correct issue where DMR EMB would not actually use the corrected QR 16,7,6 codeword;

r05a04_dev
Bryan Biedenkapp 3 weeks ago
parent 51552e2c43
commit d1fdf590ee

@ -47,8 +47,9 @@ void EMB::decode(const uint8_t* data)
DMREMB[1U] = (data[18U] << 4) & 0xF0U; DMREMB[1U] = (data[18U] << 4) & 0xF0U;
DMREMB[1U] |= (data[19U] >> 4) & 0x0FU; DMREMB[1U] |= (data[19U] >> 4) & 0x0FU;
// decode QR (16,7,6) FEC // decode QR (16,7,6) FEC and get corrected data
edac::QR1676::decode(DMREMB); uint8_t corrected = edac::QR1676::decode(DMREMB);
DMREMB[0U] = (corrected << 1) & 0xFEU;
m_colorCode = (DMREMB[0U] >> 4) & 0x0FU; m_colorCode = (DMREMB[0U] >> 4) & 0x0FU;
m_PI = (DMREMB[0U] & 0x08U) == 0x08U; m_PI = (DMREMB[0U] & 0x08U) == 0x08U;

@ -76,7 +76,7 @@ uint8_t QR1676::decode(const uint8_t* data)
code ^= error_pattern; code ^= error_pattern;
return code >> 7; return (code >> 8) & 0x7FU;
} }
/* Encode QR (16,7,6) FEC. */ /* Encode QR (16,7,6) FEC. */

@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Test Suite
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2026 Bryan Biedenkapp, N2PLL
*
*/
#include "common/Log.h"
#include "common/Utils.h"
#include <catch2/catch_test_macros.hpp>
#include <cstring>
#include "common/dmr/data/EMB.h"
using namespace dmr::data;
TEST_CASE("EMB encodes and decodes without errors", "[dmr][emb]") {
EMB emb;
emb.setColorCode(7);
emb.setPI(true);
emb.setLCSS(2);
uint8_t data[24U];
::memset(data, 0x00U, sizeof(data));
emb.encode(data);
EMB decoded;
decoded.decode(data);
REQUIRE(decoded.getColorCode() == 7);
REQUIRE(decoded.getPI() == true);
REQUIRE(decoded.getLCSS() == 2);
}
TEST_CASE("EMB corrects single-bit errors in embedded signaling", "[dmr][emb]") {
EMB emb;
emb.setColorCode(5);
emb.setPI(false);
emb.setLCSS(1);
uint8_t data[24U];
::memset(data, 0x00U, sizeof(data));
emb.encode(data);
// Save the encoded data
uint8_t original[24U];
::memcpy(original, data, sizeof(data));
// EMB data is stored in nibbles at positions 13, 14, 18, 19
// This gives us 16 bits total to test
const uint32_t embPositions[] = {13, 14, 18, 19};
// Test single-bit errors in each nibble
for (auto pos : embPositions) {
for (uint32_t bit = 0; bit < 8; bit++) {
::memcpy(data, original, sizeof(data));
// Introduce single-bit error
data[pos] ^= (1U << bit);
EMB decoded;
decoded.decode(data);
// QR(16,7,6) should correct single-bit errors
REQUIRE(decoded.getColorCode() == 5);
REQUIRE(decoded.getPI() == false);
REQUIRE(decoded.getLCSS() == 1);
}
}
}
TEST_CASE("EMB corrects two-bit errors in embedded signaling", "[dmr][emb]") {
EMB emb;
emb.setColorCode(12);
emb.setPI(true);
emb.setLCSS(3);
uint8_t data[24U];
::memset(data, 0x00U, sizeof(data));
emb.encode(data);
// Save the encoded data
uint8_t original[24U];
::memcpy(original, data, sizeof(data));
// Test two-bit errors in different positions
const struct {
uint32_t pos1, bit1, pos2, bit2;
} errorPairs[] = {
{13, 0, 13, 7}, // Same byte
{13, 4, 14, 3}, // Adjacent bytes
{13, 5, 18, 2}, // Distant bytes
{14, 1, 19, 6}, // Different nibble pairs
{18, 0, 19, 7} // Same nibble pair
};
for (auto& pair : errorPairs) {
::memcpy(data, original, sizeof(data));
// Introduce two-bit errors
data[pair.pos1] ^= (1U << pair.bit1);
data[pair.pos2] ^= (1U << pair.bit2);
EMB decoded;
decoded.decode(data);
// QR(16,7,6) should correct two-bit errors
REQUIRE(decoded.getColorCode() == 12);
REQUIRE(decoded.getPI() == true);
REQUIRE(decoded.getLCSS() == 3);
}
}
TEST_CASE("EMB tests all color code values", "[dmr][emb]") {
// Color code is 4 bits (0-15)
for (uint32_t cc = 0; cc < 16; cc++) {
EMB emb;
emb.setColorCode(cc);
emb.setPI(cc & 1); // Alternate PI
emb.setLCSS(cc & 3); // Cycle through LCSS values
uint8_t data[24U];
::memset(data, 0x00U, sizeof(data));
emb.encode(data);
EMB decoded;
decoded.decode(data);
REQUIRE(decoded.getColorCode() == cc);
REQUIRE(decoded.getPI() == (bool)(cc & 1));
REQUIRE(decoded.getLCSS() == (cc & 3));
}
}
TEST_CASE("EMB verifies error correction restores correct values after corruption", "[dmr][emb]") {
// This test specifically verifies that the bug fix works:
// Before the fix, decode() would return the corrected value but EMB
// would read from the uncorrected buffer, causing wrong results.
EMB emb;
emb.setColorCode(9);
emb.setPI(false);
emb.setLCSS(2);
uint8_t data[24U];
::memset(data, 0xAAU, sizeof(data)); // Non-zero background
emb.encode(data);
// Corrupt the EMB data with a single-bit error in position 13, bit 4
// This should be correctable by QR(16,7,6)
data[13] ^= 0x10U;
EMB decoded;
decoded.decode(data);
// Verify the corrected values are read (not the corrupted buffer)
REQUIRE(decoded.getColorCode() == 9);
REQUIRE(decoded.getPI() == false);
REQUIRE(decoded.getLCSS() == 2);
// Now encode again and verify we get the same result as original
uint8_t reencoded[24U];
::memset(reencoded, 0xAAU, sizeof(reencoded)); // Same background as original
decoded.encode(reencoded);
// The EMB portions should match the original uncorrupted encoding
uint8_t original[24U];
::memset(original, 0xAAU, sizeof(original));
emb.encode(original);
// EMB data is in nibbles, so we need to mask and compare
REQUIRE((reencoded[13] & 0x0F) == (original[13] & 0x0F));
REQUIRE((reencoded[14] & 0xF0) == (original[14] & 0xF0));
REQUIRE((reencoded[18] & 0x0F) == (original[18] & 0x0F));
REQUIRE((reencoded[19] & 0xF0) == (original[19] & 0xF0));
}

@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Digital Voice Modem - Test Suite
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2026 Bryan Biedenkapp, N2PLL
*
*/
#include "common/Log.h"
#include "common/Utils.h"
#include <catch2/catch_test_macros.hpp>
#include <cstring>
#include "common/edac/QR1676.h"
using namespace edac;
TEST_CASE("QR1676 preserves all-zero data", "[edac][qr1676]") {
uint8_t data[2U];
::memset(data, 0x00U, sizeof(data));
QR1676::encode(data);
uint8_t errors = QR1676::decode(data);
REQUIRE(errors == 0x00U);
REQUIRE(data[0U] == 0x00U);
REQUIRE(data[1U] == 0x00U);
}
TEST_CASE("QR1676 preserves all-ones data", "[edac][qr1676]") {
// QR(16,7,6): 7 data bits + 9 parity bits = 16 bits
// Data is stored in upper 7 bits of first byte, shifted left by 1
uint8_t data[2U];
data[0U] = 0xFEU; // 0b11111110 - all 7 data bits set, LSB is parity start
data[1U] = 0x00U;
QR1676::encode(data);
Utils::dump(2U, "QR1676::encode() all ones", data, 2U);
uint8_t errors = QR1676::decode(data);
REQUIRE(errors == 0x7FU); // 7 data bits all set
}
TEST_CASE("QR1676 encodes and decodes specific patterns", "[edac][qr1676]") {
const uint8_t testValues[] = {0x00U, 0x2AU, 0x54U, 0x0FU, 0x70U, 0x33U, 0x66U, 0x5AU, 0x4BU};
for (auto value : testValues) {
uint8_t data[2U];
::memset(data, 0x00U, sizeof(data));
// Store 7-bit value in upper bits, shifted left by 1
data[0U] = (value & 0x7FU) << 1;
QR1676::encode(data);
uint8_t decoded = QR1676::decode(data);
REQUIRE(decoded == (value & 0x7FU));
}
}
TEST_CASE("QR1676 encodes all 128 possible 7-bit values", "[edac][qr1676]") {
for (uint32_t value = 0U; value < 128U; value++) {
uint8_t data[2U];
::memset(data, 0x00U, sizeof(data));
data[0U] = (value & 0x7FU) << 1;
QR1676::encode(data);
uint8_t decoded = QR1676::decode(data);
REQUIRE(decoded == value);
}
}
TEST_CASE("QR1676 corrects single-bit errors", "[edac][qr1676]") {
uint8_t original = 0x5AU; // Test pattern
uint8_t data[2U];
::memset(data, 0x00U, sizeof(data));
data[0U] = (original & 0x7FU) << 1;
QR1676::encode(data);
// Save encoded data
uint8_t encoded[2U];
::memcpy(encoded, data, 2U);
// Test single-bit errors in all 16 bit positions
for (uint32_t bit = 0U; bit < 16U; bit++) {
::memcpy(data, encoded, 2U);
// Introduce single-bit error
uint32_t bytePos = bit / 8;
uint32_t bitPos = bit % 8;
data[bytePos] ^= (1U << bitPos);
uint8_t decoded = QR1676::decode(data);
// QR(16,7,6) should correct all single-bit errors
REQUIRE(decoded == (original & 0x7FU));
}
}
TEST_CASE("QR1676 corrects two-bit errors", "[edac][qr1676]") {
uint8_t original = 0x3CU; // Test pattern
uint8_t data[2U];
::memset(data, 0x00U, sizeof(data));
data[0U] = (original & 0x7FU) << 1;
QR1676::encode(data);
// Save encoded data
uint8_t encoded[2U];
::memcpy(encoded, data, 2U);
// Test two-bit error patterns
const uint32_t errorPairs[][2] = {
{0, 7}, {1, 8}, {2, 11}, {4, 13}, {6, 15}
};
for (auto& pair : errorPairs) {
::memcpy(data, encoded, 2U);
// Introduce two-bit errors
for (auto bitPos : pair) {
uint32_t bytePos = bitPos / 8;
uint32_t bit = bitPos % 8;
data[bytePos] ^= (1U << bit);
}
uint8_t decoded = QR1676::decode(data);
// QR(16,7,6) should correct two-bit errors
REQUIRE(decoded == (original & 0x7FU));
}
}
TEST_CASE("QR1676 handles random 7-bit patterns", "[edac][qr1676]") {
// Test with various pseudo-random patterns
for (uint32_t test = 0; test < 10; test++) {
uint8_t value = (uint8_t)((test * 37 + 53) % 128);
uint8_t data[2U];
::memset(data, 0x00U, sizeof(data));
data[0U] = (value & 0x7FU) << 1;
QR1676::encode(data);
uint8_t decoded = QR1676::decode(data);
REQUIRE(decoded == value);
}
}
Loading…
Cancel
Save

Powered by TurnKey Linux.