pub const POLY1: u32 = 0xf2d0_5351;
pub const POLY2: u32 = 0xe461_3c47;
pub const K_CONSTRAINT: usize = 32;
#[inline]
pub fn encode_step(encstate: u32) -> u32 {
let p1 = {
let mut t = encstate & POLY1;
t ^= t >> 16;
t ^= t >> 8;
(t & 0xff).count_ones() & 1
};
let p2 = {
let mut t = encstate & POLY2;
t ^= t >> 16;
t ^= t >> 8;
(t & 0xff).count_ones() & 1
};
(p1 << 1) | p2
}
pub fn conv_encode(data: &[u8], nbits: usize, out: &mut [u8]) {
assert!(out.len() >= 2 * nbits, "output buffer too small");
let mut encstate: u32 = 0;
for i in 0..nbits {
let bit = (data[i / 8] >> (7 - (i % 8))) & 1;
encstate = (encstate << 1) | bit as u32;
let sym = encode_step(encstate);
out[2 * i] = ((sym >> 1) & 1) as u8;
out[2 * i + 1] = (sym & 1) as u8;
}
}
pub fn build_branch_metrics(llrs: &[f32], bias: f32, scale: f32) -> Vec<[i32; 2]> {
llrs.iter()
.map(|&l| {
let m0 = l * 0.5 - bias;
let m1 = -l * 0.5 - bias;
[(m0 * scale).round() as i32, (m1 * scale).round() as i32]
})
.collect()
}
#[derive(Default)]
struct Node {
encstate: u32,
gamma: i64, metrics: [i32; 4],
tm: [i32; 2],
i: u8, }
pub struct FanoDecodeResult {
pub data: Vec<u8>,
pub metric: i64,
pub cycles: u64,
pub max_np: usize,
pub converged: bool,
}
pub fn fano_decode(
branch_metrics: &[[i32; 2]],
nbits: usize,
delta: i32,
max_cycles_per_bit: u64,
) -> FanoDecodeResult {
assert_eq!(
branch_metrics.len(),
2 * nbits,
"branch_metrics length mismatch"
);
let mut nodes: Vec<Node> = (0..=nbits).map(|_| Node::default()).collect();
for (k, node) in nodes.iter_mut().take(nbits).enumerate() {
let a = branch_metrics[2 * k];
let b = branch_metrics[2 * k + 1];
node.metrics[0] = a[0] + b[0]; node.metrics[1] = a[0] + b[1]; node.metrics[2] = a[1] + b[0]; node.metrics[3] = a[1] + b[1]; }
let last_idx = nbits.saturating_sub(1);
let tail_idx = nbits.saturating_sub(K_CONSTRAINT - 1);
{
let lsym = encode_step(0) as usize;
let m0 = nodes[0].metrics[lsym];
let m1 = nodes[0].metrics[3 ^ lsym];
if m0 > m1 {
nodes[0].tm = [m0, m1];
} else {
nodes[0].tm = [m1, m0];
nodes[0].encstate |= 1;
}
nodes[0].i = 0;
nodes[0].gamma = 0;
}
let max_cycles = max_cycles_per_bit * nbits as u64;
let mut np: usize = 0;
let mut t: i32 = 0;
let mut max_np: usize = 0;
let mut cycles: u64 = 0;
let mut converged = false;
while cycles < max_cycles {
cycles += 1;
if np > max_np {
max_np = np;
}
let ngamma = nodes[np].gamma + nodes[np].tm[nodes[np].i as usize] as i64;
if ngamma >= t as i64 {
if nodes[np].gamma < (t as i64) + delta as i64 {
while ngamma >= (t as i64) + delta as i64 {
t += delta;
}
}
let new_state = nodes[np].encstate << 1;
let new_idx = np + 1;
nodes[new_idx].gamma = ngamma;
nodes[new_idx].encstate = new_state;
np = new_idx;
if np > last_idx {
converged = true;
break;
}
let lsym = encode_step(nodes[np].encstate) as usize;
if np >= tail_idx {
nodes[np].tm[0] = nodes[np].metrics[lsym];
nodes[np].tm[1] = i32::MIN / 2; } else {
let m0 = nodes[np].metrics[lsym];
let m1 = nodes[np].metrics[3 ^ lsym];
if m0 > m1 {
nodes[np].tm = [m0, m1];
} else {
nodes[np].tm = [m1, m0];
nodes[np].encstate |= 1;
}
}
nodes[np].i = 0;
continue;
}
loop {
if np == 0 || nodes[np - 1].gamma < t as i64 {
t -= delta;
if nodes[np].i != 0 {
nodes[np].i = 0;
nodes[np].encstate ^= 1;
}
break;
}
np -= 1;
if np < tail_idx && nodes[np].i == 0 {
nodes[np].i = 1;
nodes[np].encstate ^= 1;
break;
}
}
}
let nbytes = nbits / 8;
let mut data = Vec::with_capacity(nbytes);
for i in 0..nbytes {
data.push(nodes[8 * i + 7].encstate as u8);
}
let final_metric = nodes[np.min(nbits)].gamma;
FanoDecodeResult {
data,
metric: final_metric,
cycles,
max_np,
converged,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_then_decode_noise_free() {
let nbits = 81;
let mut data = [0u8; 11]; for i in 0..50 {
if i % 3 == 0 {
data[i / 8] |= 1 << (7 - (i % 8));
}
}
let mut coded = vec![0u8; 2 * nbits];
conv_encode(&data, nbits, &mut coded);
let llrs: Vec<f32> = coded
.iter()
.map(|&b| if b == 0 { 8.0 } else { -8.0 })
.collect();
let bm = build_branch_metrics(&llrs, 0.0, 16.0);
let res = fano_decode(&bm, nbits, 17, 10_000);
assert!(res.converged, "fano should converge on perfect LLRs");
for i in 0..50 {
let orig = (data[i / 8] >> (7 - (i % 8))) & 1;
let got = (res.data[i / 8] >> (7 - (i % 8))) & 1;
assert_eq!(got, orig, "bit {} mismatch", i);
}
}
#[test]
fn encoder_symmetry_poly1_poly2_odd() {
for state in [0u32, 1, 0xaaaa_aaaa, 0x5555_5555, 0xdead_beef] {
let a = encode_step(state);
let b = encode_step(state ^ 1);
assert_eq!(a ^ b, 0b11, "not complementary for state {:#x}", state);
}
}
}