Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ tests/logs/
# Benchmark result files
benchmark_*.mat
simple_comparison_*.mat
.codex-review/
1 change: 1 addition & 0 deletions configs/arena_registry/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ G6:
1: G6_2x10
2: G6_2x8of10
3: G6_3x12of18
4: G6_3x16_full
61 changes: 61 additions & 0 deletions g6/crc16_ccitt_false.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
function crc = crc16_ccitt_false(bytes)
% CRC16_CCITT_FALSE Compute CRC-16/CCITT-FALSE over a byte sequence.
%
% Used for the G6 .pat per-frame trailer over {FR_magic, frame_index,
% panel_blocks} of that frame only. Trailer is 2 bytes appended little-endian
% after the panel blocks of each frame.
%
% Parameters (per Modular-LED-Display/docs/development/g6_04-pattern-file-format.md):
% polynomial : 0x1021
% init : 0xFFFF
% refin/refout : false / false
% xorout : 0x0000
% universal check : 0x29B1 over "123456789"
%
% The 256-entry LUT is built from the polynomial constant on first call and
% cached in a persistent variable. The universal check runs alongside the
% LUT build and ERRORS on mismatch.
%
% Input:
% bytes - uint8 vector (any length, including empty)
%
% Output:
% crc - uint16

persistent LUT;
if isempty(LUT)
LUT = zeros(256, 1, 'uint16');
poly = uint16(hex2dec('1021'));
for b = 0:255
c = uint16(bitshift(uint16(b), 8));
for i = 1:8
if bitand(c, uint16(32768)) ~= 0
c = bitxor(uint16(bitshift(c, 1)), poly);
else
c = uint16(bitshift(c, 1));
end
c = bitand(c, uint16(65535));
end
LUT(b + 1) = c;
end

% Universal-check gate: "123456789" -> 0x29B1
ucheck = uint8('123456789');
c = uint16(65535);
for i = 1:length(ucheck)
idx = bitand(bitxor(bitshift(c, -8), uint16(ucheck(i))), uint16(255));
c = bitand(bitxor(uint16(bitshift(c, 8)), LUT(idx + 1)), uint16(65535));
end
if c ~= uint16(hex2dec('29B1'))
error('crc16_ccitt_false:LUTBuildFailed', ...
'CRC-16/CCITT-FALSE universal check failed: got 0x%04X, expected 0x29B1', c);
end
end

c = uint16(65535);
for i = 1:length(bytes)
idx = bitand(bitxor(bitshift(c, -8), uint16(bytes(i))), uint16(255));
c = bitand(bitxor(uint16(bitshift(c, 8)), LUT(idx + 1)), uint16(65535));
end
crc = c;
end
64 changes: 64 additions & 0 deletions g6/crc8_autosar.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
function crc = crc8_autosar(bytes)
% CRC8_AUTOSAR Compute CRC-8/AUTOSAR over a byte sequence.
%
% Used for the G6 .pat file header byte 17 (CRC over header bytes 0-16) and
% for the panel-protocol wire-level CIPO confirmation byte (handled by the
% panel firmware, not encoders).
%
% Parameters (per Modular-LED-Display/docs/development/g6_01-panel-protocol.md):
% polynomial : 0x2F (Koopman 0x97)
% init : 0xFF
% refin/refout : false / false
% xorout : 0xFF
% universal check : 0xDF over "123456789"
%
% The 256-entry LUT is built from the polynomial constant on first call and
% cached in a persistent variable. The universal check runs alongside the
% LUT build and ERRORS on mismatch — that's the gate that catches table-
% construction bugs before any encoder/parser uses the function.
%
% Input:
% bytes - uint8 vector (any length, including empty)
%
% Output:
% crc - uint8

persistent LUT;
if isempty(LUT)
LUT = zeros(256, 1, 'uint8');
poly = uint8(hex2dec('2F'));
for b = 0:255
c = uint8(b);
for i = 1:8
if bitand(c, 128) ~= 0
c = bitxor(uint8(bitshift(c, 1)), poly);
else
c = uint8(bitshift(c, 1));
end
end
LUT(b + 1) = c;
end

% Universal-check gate: "123456789" -> 0xDF
% NOTE on indexing: bitxor on uint8 returns uint8, and adding 1 to
% uint8(255) SATURATES at 255 rather than rolling over to 256. Cast
% the XOR result to double before `+ 1` so the LUT lookup hits the
% correct entry for the byte-value 0xFF.
ucheck = uint8('123456789');
c = uint8(255);
for i = 1:length(ucheck)
c = LUT(double(bitxor(c, ucheck(i))) + 1);
end
c = bitxor(c, uint8(255));
if c ~= uint8(hex2dec('DF'))
error('crc8_autosar:LUTBuildFailed', ...
'CRC-8/AUTOSAR universal check failed: got 0x%02X, expected 0xDF', c);
end
end

c = uint8(255);
for i = 1:length(bytes)
c = LUT(double(bitxor(c, uint8(bytes(i)))) + 1);
end
crc = bitxor(c, uint8(255));
end
9 changes: 6 additions & 3 deletions g6/g6_encode_panel.m
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,18 @@

% Count '1' bits in version's bits 0..6 (exclude bit 7 = parity slot).
% MATLAB bitget is 1-indexed: bits 1..7 == bits 0..6.
total_ones = 0;
% NOTE: use double for the running sum — bitget returns the input class
% (uint8), and a uint8 accumulator saturates at 255. For GS16 panels the
% popcount can reach ~800 and the saturation breaks the parity bit.
total_ones = double(0);
for bit_idx = 1:7
total_ones = total_ones + bitget(version, bit_idx);
total_ones = total_ones + double(bitget(version, bit_idx));
end

% Add '1' bits from cmd + payload (bytes 2..block_len in MATLAB 1-indexing).
for byte_idx = 2:block_len
for bit_idx = 1:8
total_ones = total_ones + bitget(panel_block(byte_idx), bit_idx);
total_ones = total_ones + double(bitget(panel_block(byte_idx), bit_idx));
end
end

Expand Down
Loading