Skip to content

Commit

Permalink
Hamming ECC (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkorbel1 authored Jan 18, 2024
1 parent 6b8326d commit 084e01e
Show file tree
Hide file tree
Showing 14 changed files with 631 additions and 94 deletions.
3 changes: 2 additions & 1 deletion doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ Some in-development items will have opened issues, as well. Feel free to create
- Pseudorandom
- LFSR
- Error checking & correction
- ECC
- [ECC](./components/ecc.md)
- CRC
- [Parity](./components/parity.md)
- Interleaving
- Data flow
- Ready/Valid
- Connect/Disconnect
Expand Down
14 changes: 14 additions & 0 deletions doc/components/ecc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Error Correcting Codes (ECC)

ROHD-HCL implements [Hamming codes](https://en.wikipedia.org/wiki/Hamming_code) for error correction and detection. The Wikipedia article does a good job explaining the background and functionality of Hamming codes, for those unfamiliar.

An error correcting code is a code that can be included in a transmission with some data that enables the receiver to check whether there was an error (up to some number of bit flips), and possibly correct the error (up to a limit). There is a trade-off where increasing the number of additional bits included in the code improves error checking/correction, but costs more bits (area/power/storage).

ROHD-HCL only has Hamming codes currently, but there are many types of error correcting codes. The `HammingEccTransmitter` and `HammingEccReceiver` can support any data width, and support the following configurations:

| Type | Errors detectable | Errors correctable | Extra parity bit |
|------|-------------------|--------------------|------------------|
| Single Error Correction (SEC) | 1 | 1 | No |
| Double Error Detection (SEDDED) | 2 | 0 | No |
| Single Error Correction, Double Error Detection (SECDED) | 2 | 1 | Yes |
| Triple Error Detection (SEDDEDTED) | 3 | 0 | Yes |
2 changes: 1 addition & 1 deletion lib/rohd_hcl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ export 'src/carry_save_mutiplier.dart';
export 'src/component_config/component_config.dart';
export 'src/count.dart';
export 'src/encodings/encodings.dart';
export 'src/error_checking/error_checking.dart';
export 'src/exceptions.dart';
export 'src/fifo.dart';
export 'src/find.dart';
export 'src/interfaces/interfaces.dart';
export 'src/memory/memories.dart';
export 'src/models/models.dart';
export 'src/multiplier.dart';
export 'src/parity.dart';
export 'src/ripple_carry_adder.dart';
export 'src/rotate.dart';
export 'src/shift_register.dart';
Expand Down
3 changes: 2 additions & 1 deletion lib/src/component_config/components/component_registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import 'package:rohd_hcl/rohd_hcl.dart';
List<Configurator> get componentRegistry => [
RotateConfigurator(),
FifoConfigurator(),
PriorityArbiterConfigurator(),
EccConfigurator(),
RoundRobinArbiterConfigurator(),
PriorityArbiterConfigurator(),
RippleCarryAdderConfigurator(),
CarrySaveMultiplierConfigurator(),
BitonicSortConfigurator(),
Expand Down
1 change: 1 addition & 0 deletions lib/src/component_config/components/components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: BSD-3-Clause

export 'config_carry_save_multiplier.dart';
export 'config_ecc.dart';
export 'config_fifo.dart';
export 'config_one_hot.dart';
export 'config_priority_arbiter.dart';
Expand Down
39 changes: 39 additions & 0 deletions lib/src/component_config/components/config_ecc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// config_ecc.dart
// Configurator for ECC.
//
// 2024 January 18
// Author: Max Korbel <[email protected]>

import 'package:rohd/rohd.dart';
import 'package:rohd_hcl/rohd_hcl.dart';

/// A [Configurator] for [HammingEccReceiver] and [HammingEccTransmitter].
class EccConfigurator extends Configurator {
/// A knob controlling the [HammingType].
final ChoiceConfigKnob<HammingType> typeKnob =
ChoiceConfigKnob(HammingType.values, value: HammingType.sec);

/// A knob controlling the data width.
final IntConfigKnob dataWidthKnob = IntConfigKnob(value: 4);

@override
Module createModule() => HammingEccReceiver(
HammingEccTransmitter(
Logic(width: dataWidthKnob.value),
hammingType: typeKnob.value,
).transmission,
hammingType: typeKnob.value,
);

@override
late final Map<String, ConfigKnob<dynamic>> knobs = {
'Data Width': dataWidthKnob,
'Hamming Type': typeKnob,
};

@override
final String name = 'ECC';
}
239 changes: 239 additions & 0 deletions lib/src/error_checking/ecc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// Copyright (C) 2023-2024 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// ecc.dart
// Error correcting code hardware generators.
//
// 2024 January 18
// Author: Max Korbel <[email protected]>

import 'dart:math';

import 'package:meta/meta.dart';
import 'package:rohd/rohd.dart';
import 'package:rohd_hcl/rohd_hcl.dart';

/// Type of Hamming code, with different characteristics for error correction,
/// error detection, and number of check bits required.
enum HammingType {
/// Single error correction (SEC), but cannot detect double bit errors.
sec._(hasExtraParityBit: false, hasCorrection: true),

/// Double error detection (DED): can detect up to double-bit errors, but
/// performs no correction.
sedded._(hasExtraParityBit: false, hasCorrection: false),

/// Single error correction, double error detection (SECDED).
secded._(hasExtraParityBit: true, hasCorrection: true),

/// Triple error detection (TED), can detect up to triple-bit errors, but
/// performs no correction.
seddedted._(hasExtraParityBit: true, hasCorrection: false);

/// Indicates whether this type requires an additional parity bit.
final bool hasExtraParityBit;

/// Indicates whether this type supports correction of errors.
final bool hasCorrection;

/// Constrcut a [HammingType] with given characteristics.
const HammingType._(
{required this.hasExtraParityBit, required this.hasCorrection});

/// The number of extra parity bits required for this type.
int get _extraParityBits => hasExtraParityBit ? 1 : 0;
}

/// Returns whether [n] is a power of two.
bool _isPowerOfTwo(int n) => n != 0 && (n & (n - 1) == 0);

/// A transmitter for data which generates a Hamming code for error detection
/// and possibly correction.
class HammingEccTransmitter extends ErrorCheckingTransmitter {
/// The type of Hamming code to use.
final HammingType hammingType;

/// Creates a [transmission] which includes a [code] that protects [data] with
/// the specified [hammingType].
HammingEccTransmitter(super.data,
{super.name = 'hamming_ecc_tx', this.hammingType = HammingType.sec})
: super(
definitionName: 'hamming_ecc_transmitter_${hammingType.name}',
codeWidth:
_parityBitsRequired(data.width) + hammingType._extraParityBits);

/// Calculates the number of parity bits required for a Hamming code to
/// protect [dataWidth] bits.
static int _parityBitsRequired(int dataWidth) {
var m = 0;
double k;
do {
m++;
k = pow(2, m) - m - 1;
} while (k < dataWidth);
return m;
}

@override
@protected
Logic calculateCode() {
final parityBits = List<Logic?>.generate(code.width, (index) => null);
final dataBits = List<Logic>.generate(
data.width, (index) => Logic(name: 'd${index + 1}')..gets(data[index]));

final hammingCodeWidth = code.width - hammingType._extraParityBits;

var dataIdx = 0;
for (var i = 1;
i <= transmission.width - hammingType._extraParityBits;
i++) {
if (!_isPowerOfTwo(i)) {
final ilv = LogicValue.ofInt(i, hammingCodeWidth);

for (var p = 0; p < hammingCodeWidth; p++) {
if (ilv[p].toBool()) {
if (parityBits[p] == null) {
parityBits[p] = dataBits[dataIdx];
} else {
parityBits[p] = parityBits[p]! ^ dataBits[dataIdx];
}
}
}
dataIdx++;
}
}

var calculatedCode = [
for (var i = 0; i < hammingCodeWidth; i++)
Logic(name: 'p${1 << i}')..gets(parityBits[i]!),
].rswizzle();

if (hammingType.hasExtraParityBit) {
// extra parity bit is calculated by calculating parity across entire rest
// of the transmission
final pExtra = Logic(name: 'pExtra')
..gets(ParityTransmitter([calculatedCode, data].swizzle()).code);
calculatedCode = [pExtra, calculatedCode].swizzle();
}

return calculatedCode;
}
}

/// A receiver for transmissions sent with a Hamming code for error detection
/// and possibly correction.
class HammingEccReceiver extends ErrorCheckingReceiver {
/// The type of Hamming code to use to understand the original [transmission].
final HammingType hammingType;

/// Consumes a [transmission] which includes a [code] that can check whether
/// the [originalData] contains errors and possibly correct it to
/// [correctedData], depending on the specified [hammingType].
HammingEccReceiver(super.transmission,
{super.name = 'hamming_ecc_rx', this.hammingType = HammingType.sec})
: super(
codeWidth: _codeWidthFromTransmissionWidth(
transmission.width - hammingType._extraParityBits) +
hammingType._extraParityBits,
definitionName: 'hamming_ecc_receiver_${hammingType.name}',
supportsErrorCorrection: hammingType.hasCorrection,
) {
final tx = HammingEccTransmitter(originalData, hammingType: hammingType);
final hammingCode =
hammingType.hasExtraParityBit ? code.getRange(0, -1) : code;
final expectedHammingCode =
tx.hammingType.hasExtraParityBit ? tx.code.getRange(0, -1) : tx.code;

_syndrome <= hammingCode ^ expectedHammingCode;
final hammingError = _syndrome.or();

final hammingTransmissionWidth =
transmission.width - hammingType._extraParityBits;

if (hammingType.hasCorrection) {
final correction =
Logic(name: 'correction', width: hammingTransmissionWidth)
..gets(
(Const(1, width: hammingTransmissionWidth + 1) << _syndrome)
.getRange(1),
);

final encodingToDataMap = _encodingToData();

_correctedData <=
[
for (var i = 1; i <= hammingTransmissionWidth; i++)
if (encodingToDataMap.containsKey(i))
Logic(name: 'd${encodingToDataMap[i]! + 1}')
..gets(originalData[encodingToDataMap[i]] ^ correction[i - 1])
].rswizzle();
}

Logic? extraErr;
if (hammingType.hasExtraParityBit) {
extraErr = ParityReceiver(transmission).uncorrectableError;
}

switch (hammingType) {
case HammingType.sec:
_correctableError <= hammingError;
_uncorrectableError <= Const(0);
case HammingType.sedded:
_correctableError <= Const(0);
_uncorrectableError <= hammingError;
case HammingType.secded:
// error location(s) -> meaning
// ---------------- | ----------------
// extra & !hamming -> bit flip on extra parity, hamming can ignore
// correctable single-bit
// extra & hamming -> error on hamming region occurred,
// correctable single-bit
// !extra & !hamming -> no error
// !extra & hamming -> extra parity has no error, but hamming does
// double-bit error, uncorrectable
_correctableError <= extraErr!;
_uncorrectableError <= ~extraErr & hammingError;
case HammingType.seddedted:
_correctableError <= Const(0);
_uncorrectableError <= extraErr! | hammingError;
}
}

/// The "syndrome" used to decode an error pattern in Hamming parity bits
/// into a correction pattern.
late final Logic _syndrome =
Logic(name: 'syndrome', width: code.width - hammingType._extraParityBits);

/// The number of Hamming code bits that must have been included in a
/// transmission of [transmissionWidth], not including any extra parity bit.
static int _codeWidthFromTransmissionWidth(int transmissionWidth) =>
log2Ceil(transmissionWidth + 1);

/// Builds a mapping from Hamming bits encoding to data position (1-indexed).
Map<int, int> _encodingToData() {
final mapping = <int, int>{};
var dataIdx = 0;
for (var encodedIdx = 1; encodedIdx <= transmission.width; encodedIdx++) {
if (!_isPowerOfTwo(encodedIdx)) {
mapping[encodedIdx] = dataIdx++;
}
}
return mapping;
}

@override
@protected
Logic calculateCorrectableError() => _correctableError;
late final Logic _correctableError = Logic();

@override
@protected
Logic? calculateCorrectedData() =>
hammingType.hasCorrection ? _correctedData : null;
late final Logic _correctedData = Logic(width: originalData.width);

@override
@protected
Logic calculateUncorrectableError() => _uncorrectableError;
late final Logic _uncorrectableError = Logic();
}
Loading

0 comments on commit 084e01e

Please sign in to comment.