Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hamming ECC #74

Merged
merged 12 commits into from
Jan 18, 2024
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