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;
parent
51552e2c43
commit
d1fdf590ee
@ -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…
Reference in new issue