Skip to content

Instantly share code, notes, and snippets.

@bd1es
Last active February 16, 2023 07:05
Show Gist options
  • Save bd1es/89de3fa26cc0595a80c1f1644c2e90f9 to your computer and use it in GitHub Desktop.
Save bd1es/89de3fa26cc0595a80c1f1644c2e90f9 to your computer and use it in GitHub Desktop.
Generate [15,11] BCH code for AVR MCUs
/*
* This file is part of the QTR Clock AVR, one of my project displaying time.
*
* Copyright (C) 2010-2022 BD1ES.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
* Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*
* Author: BD1ES
* Date modified: 2022
*
* This program generates constants and code snippets for QTR clock projects.
*/
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <chrono>
#include <stdexcept>
#include <cstdarg>
#include <cstdint>
#include <cmath>
#include <climits>
// ------------------------------------------------------------------------------------------------
static std::string vsformat(const char* fmt, ...)
{
va_list vl;
va_start(vl, fmt);
auto size = vsnprintf(nullptr, 0, fmt, vl) + 1; // The size contains the terminator '\0'.
va_end(vl);
std::vector <char> buf(size + 1);
va_start(vl, fmt);
size = vsnprintf(&buf[0], size_t(size), fmt, vl);
va_end(vl);
return std::string(&buf[0], size);
}
// ------------------------------------------------------------------------------------------------
/* This type is from const_def.c initially written for AVR QTR clocks and put here as a comment.
* The dot_duration_table is not used in the current time2tty because it only involves CW.
*/
struct const_def_t {
void print_comments()
{
std::cout << R"rawliteral(
// These macros are defined for sharing AVR GCC code with PCs.
#if defined(__GNUC__) && defined(__AVR_ARCH__)
#include <avr/pgmspace.h>
#define PGM_ROM_SPACE const PROGMEM
#else
#define PGM_ROM_SPACE const
#define pgm_read_byte *
#define pgm_read_word *
#define pgm_read_dword *
#endif
)rawliteral" << std::endl;
}
// WPM table
void print_wpm_table(double timer_freq, uint8_t wpm_low = 1, uint8_t wpm_high = 64)
{
auto gen_table = [timer_freq, wpm_low, wpm_high] ()
{
static const double PARIS_FACTOR = 50.0;
std::vector<int> table;
for(uint8_t i = wpm_low; i <= wpm_high; i++){
table.push_back(int(0.5 + 60.0*timer_freq / i / PARIS_FACTOR));
}
return table;
};
const std::vector<int> wpm_table = gen_table();
const int maxd = 1 + log10(wpm_table[0]);
const int maxc = (80-4) / (1+maxd);
const std::string d = "%" + vsformat("%d", maxd) + "d";
std::stringstream ss;
ss << vsformat("// Dot durations in %d-%d WPM for %.2f Hz timer interval.\n",
wpm_low, wpm_high, timer_freq);
ss << vsformat("static const uint8_t DOT_DURATION_WPM_LOW = %d;\n", wpm_low);
ss << vsformat("static const uint8_t DOT_DURATION_WPM_HIGH = %d;\n", wpm_high);
ss << vsformat("static PGM_ROM_SPACE int dot_duration_table[%d] = {",
wpm_high-wpm_low+1);
for(size_t i = 0; i < wpm_table.size(); i++){
if((i%maxc) == 0){
ss << "\n ";
}
ss << vsformat(d.c_str(), wpm_table[i]);
if(i < (wpm_table.size()-1)) ss << ",";
}
ss << "\n};\n";
print(ss);
}
// Hamming encoding table
void print_hamming_enc_table()
{
auto gen_table = [] () {
/* s3 = d3^d2^d1;
* s2 = d2^d1^(~d0);
* s1 = d3^d1^(~d0);
* s0 = d3^d2^(~d0);
* d = i3,s3,i2,s2,i1,s1,i0,s0;
*/
std::vector<int> table;
for(int i = 0; i < 16; i++){
int d, d0, d1, d2, d3, s, s0, s1, s2, s3;
d3 = (i>>3) & 1;
d2 = (i>>2) & 1;
d1 = (i>>1) & 1;
d0 = i & 1;
d = (d3<<7) | (d2<<5) | (d1<<3) | (d0<<1);
s3 = d3 ^ d2 ^ d1;
s2 = d2 ^ d1 ^ (~d0&1);
s1 = d3 ^ d1 ^ (~d0&1);
s0 = d3 ^ d2 ^ (~d0&1);
s = (s3<<6) | (s2<<4) | (s1<<2) | s0;
d |= s;
table.push_back(d);
}
return table;
};
std::vector<int> enc_table = gen_table();
std::stringstream ss;
ss << "// [8,4] extended Hamming encoding table:\n";
ss << "static PGM_ROM_SPACE uint8_t hamming_enc_table[16] = {";
for(size_t i = 0; i < enc_table.size(); i++){
if((i%8) == 0){
ss << "\n ";
}
ss << vsformat(" 0x%02x", enc_table[i]);
if(i < (enc_table.size()-1)) ss << ",";
}
ss << "\n};\n";
print(ss);
}
// Hamming decoding table
void print_hamming_dec_table()
{
auto gen_table = [] () {
/* s3 = d7^d5^d1^d0;
* s2 = d7^d3^d2^d1;
* s1 = d5^d4^d3^d1;
* s0 = d7^d6^d5^d4^d3^d2^d1^d0;
*/
std::vector<int>table;
for(int i = 0; i < 256; i++){
int d, d0, d1, d2, d3, d4, d5, d6, d7, s, s0, s1, s2, s3;
d7 = (i>>7) & 1;
d6 = (i>>6) & 1;
d5 = (i>>5) & 1;
d4 = (i>>4) & 1;
d3 = (i>>3) & 1;
d2 = (i>>2) & 1;
d1 = (i>>1) & 1;
d0 = i & 1;
s3 = d7 ^ d5 ^ d1 ^ d0;
s2 = d7 ^ d3 ^ d2 ^ d1;
s1 = d5 ^ d4 ^ d3 ^ d1;
s0 = d7 ^ d6 ^ d5 ^ d4 ^ d3 ^ d2 ^ d1 ^ d0;
s = (s3<<3) | (s2<<2) | (s1<<1) | s0;
switch(s){
case 0x0f:
case 0x0e:
case 0x0c:
case 0x0a:
case 0x06:
d = (d7<<3) | (d5<<2) | (d3<<1) | d1;
break;
case 0x02:
d = ((~d7&0x01)<<3) | (d5<<2) | (d3<<1) | d1;
break;
case 0x04:
d = (d7<<3) | ((~d5&0x01)<<2) | (d3<<1) | d1;
break;
case 0x08:
d = (d7<<3) | (d5<<2) | ((~d3&0x01)<<1) | d1;
break;
case 0x00:
d = (d7<<3) | (d5<<2) | (d3<<1) | (~d1&0x01);
break;
default:
d = 0xff;
break;
}
table.push_back(d);
}
return table;
};
std::vector <int> dec_table = gen_table();
std::stringstream ss;
ss << "// [8,4] extended Hamming decoding table:\n";
ss << "static PGM_ROM_SPACE uint8_t hamming_dec_table[256] = {";
for(size_t i = 0; i < dec_table.size(); i++){
if((i%12) == 0){
ss << "\n ";
}
ss << vsformat(" 0x%02x", dec_table[i]);
if(i < (dec_table.size()-1)) ss << ",";
}
ss << "\n};\n";
print(ss);
}
/* http://users.ece.cmu.edu/~koopman/crc/index.html
* http://users.ece.cmu.edu/~koopman/roses/dsn04/koopman04_crc_poly_embedded.pdf
*/
static const uint16_t CRC16_CCITT = 0x1021; // x^16+x^12+x^5+1
static const uint16_t CRC16_KOOPMAN = 0x5935; // x^16+x^14+x^12+x^11+x^8+x^5+x^4+x^2+1
// CRC table
void print_crc_table(uint16_t poly)
{
auto gen_table = [poly] () {
std::vector<int>table;
for(int i = 0; i < 256; i++){
int d = i << 8;
for(int j = 0; j < 8; j++){
int s = d & 0x8000;
d <<= 1;
if(s) d ^= poly;
}
table.push_back(d & 0xffff);
}
return table;
};
std::vector<int> crc_table = gen_table();
std::stringstream ss;
ss << vsformat("// CRC-16 table for the polynomial 0x%04x:\n", poly);
ss << "static PGM_ROM_SPACE uint16_t crc_table[256] = {";
for(size_t i = 0; i < crc_table.size(); i++){
if((i%9) == 0){
ss << "\n ";
}
ss << vsformat(" 0x%04x", crc_table[i]);
if(i < (crc_table.size()-1)) ss << ",";
}
ss << "\n};\n";
print(ss);
}
private:
void print(std::stringstream &ss)
{
while(!ss.eof()){
std::string s;
std::getline(ss, s);
std::cout << s << std::endl;
}
}
};
// ------------------------------------------------------------------------------------------------
// Simple BCH(15,11,1). This type is used for generating coding tables and test vectors.
struct bch1511_base_t {
bch1511_base_t()
{
uint8_t mreg4, mreg3;
uint8_t tmp;
// Calculate encoder mregs with 3-bit and 4-bit remainders. Put them together in the table.
for(uint8_t mreg = 0; mreg < 16; mreg++){
for(uint8_t d = 0; d < 16; d++){
mreg4 = mreg;
tmp = d;
for(uint8_t i = 0; i < 4; i++){
encoder_shift(mreg4, tmp);
tmp >>= 1;
}
if(d < 8){
mreg3 = mreg;
tmp = d;
for(uint8_t i = 0; i < 3; i++){
encoder_shift(mreg3, tmp);
tmp >>= 1;
}
}else{
// 4-bit data is not required, make them nils.
mreg3 = 0;
}
encoder_mreg[mreg][d] = mreg3*16 + mreg4;
}
}
// Calculate decoder mregs with 3-bit and 4-bit remainders. Put them together in the table.
for(uint8_t mreg = 0; mreg < 16; mreg++){
for(uint8_t d = 0; d < 16; d++){
mreg4 = mreg;
tmp = d;
for(uint8_t i = 0; i < 4; i++){
decoder_shift(mreg4, tmp);
tmp >>= 1;
}
if(d < 8){
mreg3 = mreg;
tmp = d;
for(uint8_t i = 0; i < 3; i++){
decoder_shift(mreg3, tmp);
tmp >>= 1;
}
}else{
mreg3 = 0;
}
decoder_mreg[mreg][d] = mreg3*16 + mreg4;
}
}
// Calculate error positions and the correction patterns based on the codeword '0'.
correction_pattern[0] = error_position[0] = 0;
for(uint8_t i = 0; i < 15; i++){
// Invert the specific bit of the codeword to simulate code with a corruption.
uint16_t x = uint16_t(1 << i);
// Make divisions using the prepared table.
uint8_t mreg = 0;
for(uint8_t j = 0; j < 4; j++){
if(j < 3){
mreg = decoder_mreg[mreg][x&15] & 15;
}else{
mreg = decoder_mreg[mreg][x&15] >> 4;
}
x >>= 4;
}
// Use mreg as a remainder to index a related correction pattern.
correction_pattern[mreg] = uint16_t(1 << i);
error_position[mreg] = i + 1;
}
}
uint16_t encode(uint16_t d)
{
d &= 2047;
uint16_t tmp_d = d;
uint8_t mreg = 0;
for(uint8_t i = 0; i < 3; i++){
if(i < 2){
mreg = encoder_mreg[mreg][d&15] & 15;
}else{
mreg = encoder_mreg[mreg][d&15] >> 4;
}
d >>= 4;
}
return mreg*2048 + tmp_d;
}
uint16_t decode(uint16_t c, uint8_t &e)
{
c &= 32767;
uint16_t tmp_c = c;
uint8_t mreg = 0;
for(uint8_t i = 0; i < 4; i++){
if(i < 3){
mreg = decoder_mreg[mreg][c&15] & 15;
}else{
mreg = decoder_mreg[mreg][c&15] >> 4;
}
c >>= 4;
}
// The error position will be placed in e.
e = error_position[mreg];
return tmp_c ^ correction_pattern[mreg];
}
protected:
uint8_t encoder_mreg[16][16];
uint8_t decoder_mreg[16][16];
uint8_t error_position[16];
uint16_t correction_pattern[16];
// The encoder polynomial division, 1+X+X^4
void encoder_shift(uint8_t &mreg, uint8_t d)
{
// bits of uint8_t mreg
// 7 6 5 4 3 2 1 0 -> x x x x 0 1 2 3
uint8_t bit = (mreg ^ d) & 1;
bit <<= 3;
mreg ^= bit;
mreg = (mreg >> 1) | bit;
}
// The decoder polynomial division, X^4+X+1
void decoder_shift(uint8_t &mreg, uint8_t d)
{
// bits of uint8_t mreg
// 7 6 5 4 3 2 1 0 -> x x x x 3 2 1 0
uint8_t bit = (mreg >> 3) & 1;
mreg ^= bit;
bit ^= d & 1;
mreg = ((mreg << 1) | bit) & 15;
}
};
// ------------------------------------------------------------------------------------------------
// This type is used for generating the BCH code for AVR MCUs.
struct bch1511_avrgen_t : public bch1511_base_t {
void print_code(const char *indent, const char *class_name) {
print_vector(indent, gen_class(class_name));
}
protected:
std::vector <std::string> gen_class(const std::string &class_name)
{
std::stringstream ss;
std::vector <std::string> v;
ss << "// BCH[15,11,1] base type for AVR MCUs.\n";
ss << "struct " << class_name << " {";
v = gen_encoder();
for(auto s : v) if(s.empty()) ss << "\n"; else ss << "\n " << s;
v = gen_decoder();
for(auto s : v) if(s.empty()) ss << "\n"; else ss << "\n " << s;
v = gen_interleave();
for(auto s : v) if(s.empty()) ss << "\n"; else ss << "\n " << s;
v = gen_deinterleave();
for(auto s : v) if(s.empty()) ss << "\n"; else ss << "\n " << s;
v = gen_bitrev();
for(auto s : v) if(s.empty()) ss << "\n"; else ss << "\n " << s;
v = gen_parity();
for(auto s : v) if(s.empty()) ss << "\n"; else ss << "\n " << s;
ss << "};\n";
return get_vector(ss);
}
std::vector <std::string> gen_encoder()
{
std::stringstream ss;
ss << R"rawliteral(
static uint16_t encode(uint16_t d)
{
// Encoder mreg values for 1+X+X^4.
static PGM_ROM_SPACE uint8_t enc_mreg[16][16] = {
)rawliteral";
// Print contents of the encoding table.
for(uint8_t mreg = 0; mreg < 16; mreg++){
ss << " {";
for(uint8_t d = 0; d < 16; d++){
ss << vsformat("%3d", encoder_mreg[mreg][d]);
if(d < 15) ss << ',';
}
ss << "}";
if(mreg < 15){
ss << ",\n";
}
}
ss << "\n };\n";
ss << R"rawliteral(
d &= 2047;
uint16_t tmp_d = d;
uint8_t mreg = 0;
for(uint8_t i = 0; i < 3; i++){
if(i < 2){
mreg = pgm_read_byte(&enc_mreg[mreg][tmp_d&15]) & 15;
}else{
mreg = pgm_read_byte(&enc_mreg[mreg][tmp_d&15]) >> 4;
}
tmp_d >>= 4;
}
return mreg*2048 + d;
}
)rawliteral";
return get_vector(ss);
}
std::vector <std::string> gen_decoder()
{
std::stringstream ss;
ss << R"rawliteral(
static uint16_t decode(uint16_t c, uint8_t &e)
{
// Decoder mreg values for X^4+X+1.
static PGM_ROM_SPACE uint8_t dec_mreg[16][16] = {
)rawliteral";
// Print contents in the decoding table.
for(uint8_t mreg = 0; mreg < 16; mreg++){
ss << " {";
for(uint8_t d = 0; d < 16; d++){
ss << vsformat("%3d", decoder_mreg[mreg][d]);
if(d < 15) ss << ',';
}
ss << "}";
if(mreg < 15){
ss << ",\n";
}
}
ss << "\n };\n";
ss << R"rawliteral(
// Correction patterns.
static PGM_ROM_SPACE uint16_t corr_pattern[16] = {
)rawliteral";
// Print contents in the correction pattern table.
for(uint8_t i = 0; i < 16; i++){
if((i%8) == 0) ss << " ";
ss << vsformat(" 0x%04x", correction_pattern[i]);
if((i%8) < 7){
ss << ",";
}else{
if(i < 15){
ss << ",\n";
}
}
}
ss << "\n };\n";
ss << R"rawliteral(
// Error positions.
static PGM_ROM_SPACE uint8_t err_position[16] = {
)rawliteral";
// Print contents in the error position table.
for(uint8_t i = 0; i < 16; i++){
if((i%8) == 0) ss << " ";
ss << vsformat(" 0x%02x", error_position[i]);
if((i%8) < 7){
ss << ",";
}else{
if(i < 15){
ss << ",\n";
}
}
}
ss << "\n };\n";
ss << R"rawliteral(
c &= 32767;
uint16_t tmp_c = c;
uint8_t mreg = 0;
for(uint8_t i = 0; i < 4; i++){
if(i < 3){
mreg = pgm_read_byte(&dec_mreg[mreg][tmp_c&15]) & 15;
}else{
mreg = pgm_read_byte(&dec_mreg[mreg][tmp_c&15]) >> 4;
}
tmp_c >>= 4;
}
// The error position will be placed in e.
e = pgm_read_byte(&err_position[mreg]);
return c ^ pgm_read_word(&corr_pattern[mreg]);
}
)rawliteral";
return get_vector(ss);
}
std::vector <std::string> gen_interleave()
{
std::stringstream ss;
ss << R"rawliteral(
// Some Bit Twiddlings - graphics.stanford.edu/~seander/bithacks.html
static void interleave(uint16_t x, uint16_t y, uint32_t &z)
{
uint32_t xt = x;
xt = (xt | (xt << 8)) & 0x00FF00FF;
xt = (xt | (xt << 4)) & 0x0F0F0F0F;
xt = (xt | (xt << 2)) & 0x33333333;
xt = (xt | (xt << 1)) & 0x55555555;
uint32_t yt = y;
yt = (yt | (yt << 8)) & 0x00FF00FF;
yt = (yt | (yt << 4)) & 0x0F0F0F0F;
yt = (yt | (yt << 2)) & 0x33333333;
yt = (yt | (yt << 1)) & 0x55555555;
z = xt | (yt << 1);
}
)rawliteral";
return get_vector(ss);
}
std::vector <std::string> gen_deinterleave()
{
std::stringstream ss;
ss << R"rawliteral(
static void deinterleave(uint32_t z, uint16_t &x, uint16_t &y)
{
uint32_t xt = z & 0x55555555;
xt = (xt | (xt >> 1)) & 0x33333333;
xt = (xt | (xt >> 2)) & 0x0F0F0F0F;
xt = (xt | (xt >> 4)) & 0x00FF00FF;
xt = (xt | (xt >> 8)) & 0x0000FFFF;
x = uint16_t(xt);
uint32_t yt = (z>>1) & 0x55555555;
yt = (yt | (yt >> 1)) & 0x33333333;
yt = (yt | (yt >> 2)) & 0x0F0F0F0F;
yt = (yt | (yt >> 4)) & 0x00FF00FF;
yt = (yt | (yt >> 8)) & 0x0000FFFF;
y = uint16_t(yt);
}
)rawliteral";
return get_vector(ss);
}
std::vector <std::string> gen_bitrev()
{
std::stringstream ss;
ss << R"rawliteral(
static uint16_t bitrev(uint16_t v)
{
v = ((v >> 1) & 0x5555) | ((v & 0x5555) << 1);
v = ((v >> 2) & 0x3333) | ((v & 0x3333) << 2);
v = ((v >> 4) & 0x0F0F) | ((v & 0x0F0F) << 4);
v = ((v >> 8) & 0x00FF) | ((v & 0x00FF) << 8);
return v;
}
)rawliteral";
return get_vector(ss);
}
std::vector <std::string> gen_parity()
{
std::stringstream ss;
ss << R"rawliteral(
static uint16_t parity(uint16_t v)
{
v ^= v >> 8;
v ^= v >> 4;
return (0x6996 >> (v&0xf)) & 1;
}
)rawliteral";
return get_vector(ss);
}
private:
std::vector <std::string> get_vector(std::stringstream &ss) {
using namespace std;
vector <string> v;
while(!ss.eof()){
string s;
getline(ss, s);
v.push_back(s);
}
return v;
}
void print_vector(const char *indents, const std::vector <std::string> &v) {
using namespace std;
for(size_t i = 0; i < v.size(); i++){
cout << indents << v[i] << endl;
}
}
};
// ------------------------------------------------------------------------------------------------
// Convert a given integer to a bit string.
template <typename T>
static std::string bitstr(T n, uint8_t num_bits = 0)
{
std::string s = "0b";
if((num_bits==0) || num_bits>CHAR_BIT*sizeof(n)){
num_bits = CHAR_BIT*sizeof(n);
}
for(int8_t i = num_bits-1; i >= 0; i--){
s.push_back((n & (1 << i)) ? '1' : '0');
}
return s;
}
struct bch_codec_test_t {
void run() {
using namespace std;
cerr << "Testing the BCH codec in the space of [0..2047] ";
static const uint32_t NUM_ITTERS = 20000000;
auto start = chrono::high_resolution_clock::now();
for(uint32_t i = 0; i < NUM_ITTERS; i++){
if((i%(NUM_ITTERS/30)) == 0) cerr << '.';
single_test(i);
}
chrono::duration <double> elapsed = chrono::high_resolution_clock::now() - start;
cerr << endl;
cerr << vsformat(" %d codes are tested in %f seconds, and", NUM_ITTERS, elapsed.count())
<< endl;
cerr << vsformat(" each single test took %f nanoseconds.",
elapsed.count() * 1e9 / NUM_ITTERS / 17) << endl;
cerr << "Finished." << endl;
}
private:
bch1511_base_t b1511;
inline void single_test(uint16_t n) {
using namespace std;
uint16_t x, y;
uint8_t res;
// Make 'x' the encoding result.
n &= 2047;
x = b1511.encode(n);
// Test the decoding result with the input code has no error.
y = b1511.decode(x, res);
if(res != 0){
throw runtime_error(
vsformat("%s: n = %d, b1511.decode() returns a non-zero value %d while decoding %s.\n",
__func__, n, res, bitstr(x, 15).c_str())
);
}
if((y&2047) != n){
throw runtime_error(
vsformat("%s: n = %d, b1511.decode() produces an incorrect result %s while decoding %s.\n",
__func__, n, bitstr(y, 15).c_str(), bitstr(x, 15).c_str())
);
}
// Test error corrections by inverting bits of x.
for(uint8_t i = 0; i < 15; i++){
uint16_t tmp = x ^ (1<<i);
y = b1511.decode(tmp, res);
if(res != (i+1)){
throw runtime_error(
vsformat("%s: n = %d, b1511.decode() returns an invalid value %d while decoding %s.\n",
__func__, n, res, bitstr(tmp, 15).c_str())
);
}
if((y&2047) != n){
throw runtime_error(
vsformat("%s: n = %d, b1511.decode() produces an incorrect result %s while decoding %s.\n",
__func__, n, bitstr(y, 15).c_str(), bitstr(tmp, 15).c_str())
);
}
}
}
};
// ------------------------------------------------------------------------------------------------
int main()
{
bch_codec_test_t bch_codec_test;
bch_codec_test.run();
std::cout << std::endl;
const_def_t const_def;
const_def.print_comments();
//const_def.print_wpm_table(4166.67);
const_def.print_wpm_table(9600);
const_def.print_hamming_enc_table();
const_def.print_hamming_dec_table();
const_def.print_crc_table(const_def.CRC16_CCITT);
const_def.print_crc_table(const_def.CRC16_KOOPMAN);
std::cout << std::endl;
bch1511_avrgen_t bch1511_def;
bch1511_def.print_code("", "bch1511_avr_t");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment