summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-06-15 08:54:45 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-07-08 10:20:41 -0600
commit6fd11a1bf4d1bc4c4919c3381be8d4c476dd4235 (patch)
treed356d5ed2c944d9d15f06cc36a14c8c779a8c4f7
parent72b9036dc961d9fe7e0e9deb12e87f121a4d0ccf (diff)
wip: flashimg/cpu_hdmi: Initial version of the TMDS decoder/downscalerlukeshu/vid-hdmi
-rw-r--r--.editorconfig5
-rw-r--r--GNUmakefile6
-rwxr-xr-xbuild-aux/lint-src2
-rw-r--r--flashimg/cpu_hdmi/tmds_decode-interp.S95
-rw-r--r--flashimg/cpu_hdmi/tmds_decode.S82
-rw-r--r--flashimg/cpu_hdmi/tmds_decode_table.h1025
-rwxr-xr-xflashimg/cpu_hdmi/tmds_decode_table.h.gen39
7 files changed, 1251 insertions, 3 deletions
diff --git a/.editorconfig b/.editorconfig
index d281cf0..6d6ef60 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -20,6 +20,9 @@ trim_trailing_whitespace = true
[*.{c,h}]
_mode = c
+[*.S]
+_mode = asm
+
[GNUmakefile]
_mode = make
@@ -51,7 +54,7 @@ _mode = sh
[{build-aux/lint-{src,bin},build-aux/gcov-prune,libmisc/error_generated.c.gen,libusb/include/libusb/tusb_helpers.h.gen}]
_mode = bash
-[{build-aux/stack.c.gen,build-aux/tent-graph,libmisc/wrap-cc,lib9p_util/nut.h.gen}]
+[{build-aux/stack.c.gen,build-aux/tent-graph,libmisc/wrap-cc,lib9p_util/nut.h.gen,flashimg/cpu_hdmi/tmds_decode_table.h.gen}]
_mode = python3
indent_style = space
indent_size = 4
diff --git a/GNUmakefile b/GNUmakefile
index d4422d4..a4dfb56 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -69,6 +69,10 @@ generate/files += lib9p_util/nut.h
lib9p_util/nut.h: %: %.gen lib9p_util/nut_gen lib9p_util/nut_gen/*.py
$< $@
+generate/files += flashimg/cpu_hdmi/tmds_decode_table.h
+flashimg/cpu_hdmi/tmds_decode_table.h: %: %.gen
+ $^ >$@
+
generate/files += build-aux/sources.mk
ifeq ($(INNER),)
nonsource/files = $(generate/files)
@@ -183,5 +187,5 @@ format/sh format/bash: format/%:
format/python3: ./build-aux/venv
./build-aux/venv/bin/black $(sources_$(@F))
./build-aux/venv/bin/isort $(sources_$(@F))
-format/make format/cmake format/gitignore format/ini format/9p-idl format/9p-log format/markdown format/pip format/man-cat:
+format/asm format/make format/cmake format/gitignore format/ini format/9p-idl format/9p-log format/markdown format/pip format/man-cat:
@: TODO: Write/adopt formatters for these file types
diff --git a/build-aux/lint-src b/build-aux/lint-src
index d536631..15327f4 100755
--- a/build-aux/lint-src
+++ b/build-aux/lint-src
@@ -148,7 +148,7 @@ get-dscname() {
done
./build-aux/venv/bin/pytest "${testfiles[@]}" || exit $?
;;
- make | cmake | gitignore | ini | 9p-idl | 9p-log | markdown | pip | man-cat)
+ asm | make | cmake | gitignore | ini | 9p-idl | 9p-log | markdown | pip | man-cat)
# TODO: Write/adopt linters for these file types
:
;;
diff --git a/flashimg/cpu_hdmi/tmds_decode-interp.S b/flashimg/cpu_hdmi/tmds_decode-interp.S
new file mode 100644
index 0000000..de1605a
--- /dev/null
+++ b/flashimg/cpu_hdmi/tmds_decode-interp.S
@@ -0,0 +1,95 @@
+// flashimg/cpu_hdmi/tmds_decode-interp.S - Variant of tmds_decode.S that does linear interpolation
+//
+// Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+// .tmds_table is a LUT from 10-bit TMDS-encoded values to the 8-bit
+// decoded values.
+.section .scratch_x.tmds_table, "a"
+tmds_table:
+#define X(dec) .byte dec
+#include "tmds_decode_table.h"
+#undef X
+
+// void tmds_decode_line(uint16_t in_r[width],
+// uint16_t in_g[width],
+// uint16_t in_b[width],
+// uint8_t out[width/2],
+// size_t width);
+//
+// Read in `width` values from 10-bit-encoded TMDS streams of R/G/B
+// channels of 24-bit RGB pixels; and write that out as RGB 2:3:2,
+// scaled down horizontally by half.
+//
+// `width` must be either 640 or 720.
+.section .scratch_x.tmds_decode_line, "ax"
+.global tmds_decode_line
+.type tmds_decode_line,%function
+.thumb_func
+tmds_decode_line:
+ // Of our supported screen formats, the one with the tightest
+ // cycles/pixel requirement is 720x480p@60Hz, which has
+ // H_active=720, H_total=858, meaning that we need to decode
+ // 720 pixels in under 858*10 cycles, giving us a budget of
+ // ~11.9 cycles per pixel.
+#define iR r0
+#define iG r1
+#define iB r2
+#define o r3
+#define w r4
+#define vTab r4
+#define vAcc r5
+#define vTmp1 r6
+#define vTmp2 r7
+#define vOEnd r12
+ push {r4-r7, lr} // ONCE += 1+5 cyc
+ lsrs w, w, #1 // ONCE += 1 cyc
+ add w, o, w // ONCE += 1 cyc
+ mov vOEnd, w // ONCE += 1 cyc
+#undef w
+ ldr vTab, =tmds_table // ONCE += 2 cyc
+loop:
+.rept 4
+ // red
+ ldmia iR!, {vTmp2} // REPT += 1+1 cyc
+ lsrs vTmp1, vTmp2, #16 // REPT += 1 cyc
+ uxth vTmp2, vTmp2 // REPT += 1 cyc
+ ldsb vTmp1, [vTab, vTmp1] // REPT += 2 cyc
+ ldsb vTmp2, [vTab, vTmp2] // REPT += 2 cyc
+ add vTmp1, vTmp1, vTmp2 // REPT += 1 cyc // add 2 8-bit values, producing a 9-bit value...
+ lsrs vTmp1, vTmp1, #7 // REPT += 1 cyc // ...shrink it from 9-bits to 2-bits
+ lsls vAcc, vTmp1, #5 // REPT += 1 cyc
+ // green
+ ldmia iG!, {vTmp2} // REPT += 1+1 cyc
+ lsrs vTmp1, vTmp2, #16 // REPT += 1 cyc
+ uxth vTmp2, vTmp2 // REPT += 1 cyc
+ ldsb vTmp1, [vTab, vTmp1] // REPT += 2 cyc
+ ldsb vTmp2, [vTab, vTmp2] // REPT += 2 cyc
+ add vTmp1, vTmp1, vTmp2 // REPT += 1 cyc // add 2 8-bit values, producing a 9-bit value...
+ lsrs vTmp1, vTmp1, #6 // REPT += 1 cyc // ...shrink it from 9-bits to 3-bits
+ lsls vTmp1, vTmp1, #2 // REPT += 1 cyc
+ orrs vAcc, vAcc, vTmp1 // REPT += 1 cyc
+ // blue
+ ldmia iB!, {vTmp2} // REPT += 1+1 cyc
+ lsrs vTmp1, vTmp2, #16 // REPT += 1 cyc
+ uxth vTmp2, vTmp2 // REPT += 1 cyc
+ ldsb vTmp1, [vTab, vTmp1] // REPT += 2 cyc
+ ldsb vTmp2, [vTab, vTmp2] // REPT += 2 cyc
+ add vTmp1, vTmp1, vTmp2 // REPT += 1 cyc // add 2 8-bit values, producing a 9-bit value...
+ lsrs vTmp1, vTmp1, #7 // REPT += 1 cyc // ...shrink it from 9-bits to 2-bits
+ orrs vAcc, vAcc, vTmp1 // REPT += 1 cyc
+ // store
+ strb vAcc, [o, #0] // REPT += 2 cyc
+ adds o, o, #1 // REPT += 1 cyc
+.endr
+ cmp o, vOEnd // LOOP += 1 cyc
+ bne loop // LOOP += 2 cyc ; ONCE -= 1 cyc
+ pop {r4-r7, pc} // ONCE += 3+5 cyc
+ // TOTAL = ONCE+((720/2)/N)*LOOP+(720/2)*REPT cyc
+ // = 18 +( 360 /4)* 3 + 360 * 37 cyc
+ // = 13608 cyc
+ // BUDGET = 8580 cyc
diff --git a/flashimg/cpu_hdmi/tmds_decode.S b/flashimg/cpu_hdmi/tmds_decode.S
new file mode 100644
index 0000000..888b5b9
--- /dev/null
+++ b/flashimg/cpu_hdmi/tmds_decode.S
@@ -0,0 +1,82 @@
+// flashimg/cpu_hdmi/tmds_decode.S - Decode TMDS-encoded scanlines
+//
+// Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+// .tmds_table is a LUT from 10-bit TMDS-encoded values to the 8-bit
+// decoded values, shifted right 6 bits.
+.section .scratch_x.tmds_table, "a"
+tmds_table:
+#define X(dec) .byte dec>>6
+#include "tmds_decode_table.h"
+#undef X
+
+// void tmds_decode_line(uint16_t in_r[width],
+// uint16_t in_g[width],
+// uint16_t in_b[width],
+// uint8_t out[width/2],
+// size_t width);
+//
+// Read in `width` values from 10-bit-encoded TMDS streams of R/G/B
+// channels of 24-bit RGB pixels; and write that out as RGB 2:3:2,
+// scaled down horizontally by half.
+//
+// `width` must be either 640 or 720.
+//
+// BUG: Scaling down happens by discarding every other pixel, not by
+// interpolation.
+.section .scratch_x.tmds_decode_line, "ax"
+.global tmds_decode_line
+.type tmds_decode_line,%function
+.thumb_func
+tmds_decode_line:
+ // Of our supported screen formats, the one with the tightest
+ // cycles/pixel requirement is 720x480p@60Hz, which has
+ // H_active=720, H_total=858, meaning that we need to decode
+ // 720 pixels in under 858*10 cycles, giving us a budget of
+ // ~11.9 cycles per pixel.
+#define iR r0
+#define iG r1
+#define iB r2
+#define o r3
+#define w r4
+#define vTab r5
+#define vAcc r6
+#define vTmp r7
+ push {r4-r7, lr} // ONCE += 1+5 cyc
+ lsrs w, w, #1 // ONCE += 1 cyc
+ add w, o, w // ONCE += 1 cyc
+ ldr vTab, =tmds_table // ONCE += 2 cyc
+loop:
+.rept 4
+ // red
+ ldmia iR!, {vTmp} // REPT += 1+1 cyc
+ uxth vTmp, vTmp // REPT += 1 cyc
+ ldsb vTmp, [vTab, vTmp] // REPT += 2 cyc
+ lsls vAcc, vTmp, #5 // REPT += 1 cyc
+ // green
+ ldmia iG!, {vTmp} // REPT += 1+1 cyc
+ uxth vTmp, vTmp // REPT += 1 cyc
+ ldsb vTmp, [vTab, vTmp] // REPT += 2 cyc
+ lsls vTmp, vTmp, #3 // REPT += 1 cyc
+ orrs vAcc, vAcc, vTmp // REPT += 1 cyc
+ // blue
+ ldmia iB!, {vTmp} // REPT += 1+1 cyc
+ uxth vTmp, vTmp // REPT += 1 cyc
+ ldsb vTmp, [vTab, vTmp] // REPT += 2 cyc
+ orrs vAcc, vAcc, vTmp // REPT += 1 cyc
+ // store
+ strb vAcc, [o, #0] // REPT += 2 cyc
+ adds o, o, #1 // REPT += 1 cyc
+.endr
+ cmp o, w // LOOP += 1 cyc
+ bne loop // LOOP += 2 cyc ; ONCE -= 1 cyc
+ pop {r4-r7, pc} // ONCE += 3+5 cyc
+ // TOTAL = ONCE+((720/2)/N)*LOOP+(720/2)*REPT cyc
+ // = 17 +( 360 /4)* 3 + 360 * 22 cyc
+ // = 8207 cyc
+ // BUDGET = 8580 cyc
diff --git a/flashimg/cpu_hdmi/tmds_decode_table.h b/flashimg/cpu_hdmi/tmds_decode_table.h
new file mode 100644
index 0000000..4e1d6ea
--- /dev/null
+++ b/flashimg/cpu_hdmi/tmds_decode_table.h
@@ -0,0 +1,1025 @@
+// Generated by `flashimg/cpu_hdmi/tmds_decode_table.h.gen`. DO NOT EDIT!
+X(0b01111111)
+X(0b11111111)
+X(0b00000000)
+X(0b10000000)
+X(0b01111110)
+X(0b11111110)
+X(0b00000001)
+X(0b10000001)
+X(0b01111100)
+X(0b11111100)
+X(0b00000011)
+X(0b10000011)
+X(0b01111101)
+X(0b11111101)
+X(0b00000010)
+X(0b10000010)
+X(0b01111001)
+X(0b11111001)
+X(0b00000110)
+X(0b10000110)
+X(0b01111000)
+X(0b11111000)
+X(0b00000111)
+X(0b10000111)
+X(0b01111010)
+X(0b11111010)
+X(0b00000101)
+X(0b10000101)
+X(0b01111011)
+X(0b11111011)
+X(0b00000100)
+X(0b10000100)
+X(0b01110011)
+X(0b11110011)
+X(0b00001100)
+X(0b10001100)
+X(0b01110010)
+X(0b11110010)
+X(0b00001101)
+X(0b10001101)
+X(0b01110000)
+X(0b11110000)
+X(0b00001111)
+X(0b10001111)
+X(0b01110001)
+X(0b11110001)
+X(0b00001110)
+X(0b10001110)
+X(0b01110101)
+X(0b11110101)
+X(0b00001010)
+X(0b10001010)
+X(0b01110100)
+X(0b11110100)
+X(0b00001011)
+X(0b10001011)
+X(0b01110110)
+X(0b11110110)
+X(0b00001001)
+X(0b10001001)
+X(0b01110111)
+X(0b11110111)
+X(0b00001000)
+X(0b10001000)
+X(0b01100111)
+X(0b11100111)
+X(0b00011000)
+X(0b10011000)
+X(0b01100110)
+X(0b11100110)
+X(0b00011001)
+X(0b10011001)
+X(0b01100100)
+X(0b11100100)
+X(0b00011011)
+X(0b10011011)
+X(0b01100101)
+X(0b11100101)
+X(0b00011010)
+X(0b10011010)
+X(0b01100001)
+X(0b11100001)
+X(0b00011110)
+X(0b10011110)
+X(0b01100000)
+X(0b11100000)
+X(0b00011111)
+X(0b10011111)
+X(0b01100010)
+X(0b11100010)
+X(0b00011101)
+X(0b10011101)
+X(0b01100011)
+X(0b11100011)
+X(0b00011100)
+X(0b10011100)
+X(0b01101011)
+X(0b11101011)
+X(0b00010100)
+X(0b10010100)
+X(0b01101010)
+X(0b11101010)
+X(0b00010101)
+X(0b10010101)
+X(0b01101000)
+X(0b11101000)
+X(0b00010111)
+X(0b10010111)
+X(0b01101001)
+X(0b11101001)
+X(0b00010110)
+X(0b10010110)
+X(0b01101101)
+X(0b11101101)
+X(0b00010010)
+X(0b10010010)
+X(0b01101100)
+X(0b11101100)
+X(0b00010011)
+X(0b10010011)
+X(0b01101110)
+X(0b11101110)
+X(0b00010001)
+X(0b10010001)
+X(0b01101111)
+X(0b11101111)
+X(0b00010000)
+X(0b10010000)
+X(0b01001111)
+X(0b11001111)
+X(0b00110000)
+X(0b10110000)
+X(0b01001110)
+X(0b11001110)
+X(0b00110001)
+X(0b10110001)
+X(0b01001100)
+X(0b11001100)
+X(0b00110011)
+X(0b10110011)
+X(0b01001101)
+X(0b11001101)
+X(0b00110010)
+X(0b10110010)
+X(0b01001001)
+X(0b11001001)
+X(0b00110110)
+X(0b10110110)
+X(0b01001000)
+X(0b11001000)
+X(0b00110111)
+X(0b10110111)
+X(0b01001010)
+X(0b11001010)
+X(0b00110101)
+X(0b10110101)
+X(0b01001011)
+X(0b11001011)
+X(0b00110100)
+X(0b10110100)
+X(0b01000011)
+X(0b11000011)
+X(0b00111100)
+X(0b10111100)
+X(0b01000010)
+X(0b11000010)
+X(0b00111101)
+X(0b10111101)
+X(0b01000000)
+X(0b11000000)
+X(0b00111111)
+X(0b10111111)
+X(0b01000001)
+X(0b11000001)
+X(0b00111110)
+X(0b10111110)
+X(0b01000101)
+X(0b11000101)
+X(0b00111010)
+X(0b10111010)
+X(0b01000100)
+X(0b11000100)
+X(0b00111011)
+X(0b10111011)
+X(0b01000110)
+X(0b11000110)
+X(0b00111001)
+X(0b10111001)
+X(0b01000111)
+X(0b11000111)
+X(0b00111000)
+X(0b10111000)
+X(0b01010111)
+X(0b11010111)
+X(0b00101000)
+X(0b10101000)
+X(0b01010110)
+X(0b11010110)
+X(0b00101001)
+X(0b10101001)
+X(0b01010100)
+X(0b11010100)
+X(0b00101011)
+X(0b10101011)
+X(0b01010101)
+X(0b11010101)
+X(0b00101010)
+X(0b10101010)
+X(0b01010001)
+X(0b11010001)
+X(0b00101110)
+X(0b10101110)
+X(0b01010000)
+X(0b11010000)
+X(0b00101111)
+X(0b10101111)
+X(0b01010010)
+X(0b11010010)
+X(0b00101101)
+X(0b10101101)
+X(0b01010011)
+X(0b11010011)
+X(0b00101100)
+X(0b10101100)
+X(0b01011011)
+X(0b11011011)
+X(0b00100100)
+X(0b10100100)
+X(0b01011010)
+X(0b11011010)
+X(0b00100101)
+X(0b10100101)
+X(0b01011000)
+X(0b11011000)
+X(0b00100111)
+X(0b10100111)
+X(0b01011001)
+X(0b11011001)
+X(0b00100110)
+X(0b10100110)
+X(0b01011101)
+X(0b11011101)
+X(0b00100010)
+X(0b10100010)
+X(0b01011100)
+X(0b11011100)
+X(0b00100011)
+X(0b10100011)
+X(0b01011110)
+X(0b11011110)
+X(0b00100001)
+X(0b10100001)
+X(0b01011111)
+X(0b11011111)
+X(0b00100000)
+X(0b10100000)
+X(0b00011111)
+X(0b10011111)
+X(0b01100000)
+X(0b11100000)
+X(0b00011110)
+X(0b10011110)
+X(0b01100001)
+X(0b11100001)
+X(0b00011100)
+X(0b10011100)
+X(0b01100011)
+X(0b11100011)
+X(0b00011101)
+X(0b10011101)
+X(0b01100010)
+X(0b11100010)
+X(0b00011001)
+X(0b10011001)
+X(0b01100110)
+X(0b11100110)
+X(0b00011000)
+X(0b10011000)
+X(0b01100111)
+X(0b11100111)
+X(0b00011010)
+X(0b10011010)
+X(0b01100101)
+X(0b11100101)
+X(0b00011011)
+X(0b10011011)
+X(0b01100100)
+X(0b11100100)
+X(0b00010011)
+X(0b10010011)
+X(0b01101100)
+X(0b11101100)
+X(0b00010010)
+X(0b10010010)
+X(0b01101101)
+X(0b11101101)
+X(0b00010000)
+X(0b10010000)
+X(0b01101111)
+X(0b11101111)
+X(0b00010001)
+X(0b10010001)
+X(0b01101110)
+X(0b11101110)
+X(0b00010101)
+X(0b10010101)
+X(0b01101010)
+X(0b11101010)
+X(0b00010100)
+X(0b10010100)
+X(0b01101011)
+X(0b11101011)
+X(0b00010110)
+X(0b10010110)
+X(0b01101001)
+X(0b11101001)
+X(0b00010111)
+X(0b10010111)
+X(0b01101000)
+X(0b11101000)
+X(0b00000111)
+X(0b10000111)
+X(0b01111000)
+X(0b11111000)
+X(0b00000110)
+X(0b10000110)
+X(0b01111001)
+X(0b11111001)
+X(0b00000100)
+X(0b10000100)
+X(0b01111011)
+X(0b11111011)
+X(0b00000101)
+X(0b10000101)
+X(0b01111010)
+X(0b11111010)
+X(0b00000001)
+X(0b10000001)
+X(0b01111110)
+X(0b11111110)
+X(0b00000000)
+X(0b10000000)
+X(0b01111111)
+X(0b11111111)
+X(0b00000010)
+X(0b10000010)
+X(0b01111101)
+X(0b11111101)
+X(0b00000011)
+X(0b10000011)
+X(0b01111100)
+X(0b11111100)
+X(0b00001011)
+X(0b10001011)
+X(0b01110100)
+X(0b11110100)
+X(0b00001010)
+X(0b10001010)
+X(0b01110101)
+X(0b11110101)
+X(0b00001000)
+X(0b10001000)
+X(0b01110111)
+X(0b11110111)
+X(0b00001001)
+X(0b10001001)
+X(0b01110110)
+X(0b11110110)
+X(0b00001101)
+X(0b10001101)
+X(0b01110010)
+X(0b11110010)
+X(0b00001100)
+X(0b10001100)
+X(0b01110011)
+X(0b11110011)
+X(0b00001110)
+X(0b10001110)
+X(0b01110001)
+X(0b11110001)
+X(0b00001111)
+X(0b10001111)
+X(0b01110000)
+X(0b11110000)
+X(0b00101111)
+X(0b10101111)
+X(0b01010000)
+X(0b11010000)
+X(0b00101110)
+X(0b10101110)
+X(0b01010001)
+X(0b11010001)
+X(0b00101100)
+X(0b10101100)
+X(0b01010011)
+X(0b11010011)
+X(0b00101101)
+X(0b10101101)
+X(0b01010010)
+X(0b11010010)
+X(0b00101001)
+X(0b10101001)
+X(0b01010110)
+X(0b11010110)
+X(0b00101000)
+X(0b10101000)
+X(0b01010111)
+X(0b11010111)
+X(0b00101010)
+X(0b10101010)
+X(0b01010101)
+X(0b11010101)
+X(0b00101011)
+X(0b10101011)
+X(0b01010100)
+X(0b11010100)
+X(0b00100011)
+X(0b10100011)
+X(0b01011100)
+X(0b11011100)
+X(0b00100010)
+X(0b10100010)
+X(0b01011101)
+X(0b11011101)
+X(0b00100000)
+X(0b10100000)
+X(0b01011111)
+X(0b11011111)
+X(0b00100001)
+X(0b10100001)
+X(0b01011110)
+X(0b11011110)
+X(0b00100101)
+X(0b10100101)
+X(0b01011010)
+X(0b11011010)
+X(0b00100100)
+X(0b10100100)
+X(0b01011011)
+X(0b11011011)
+X(0b00100110)
+X(0b10100110)
+X(0b01011001)
+X(0b11011001)
+X(0b00100111)
+X(0b10100111)
+X(0b01011000)
+X(0b11011000)
+X(0b00110111)
+X(0b10110111)
+X(0b01001000)
+X(0b11001000)
+X(0b00110110)
+X(0b10110110)
+X(0b01001001)
+X(0b11001001)
+X(0b00110100)
+X(0b10110100)
+X(0b01001011)
+X(0b11001011)
+X(0b00110101)
+X(0b10110101)
+X(0b01001010)
+X(0b11001010)
+X(0b00110001)
+X(0b10110001)
+X(0b01001110)
+X(0b11001110)
+X(0b00110000)
+X(0b10110000)
+X(0b01001111)
+X(0b11001111)
+X(0b00110010)
+X(0b10110010)
+X(0b01001101)
+X(0b11001101)
+X(0b00110011)
+X(0b10110011)
+X(0b01001100)
+X(0b11001100)
+X(0b00111011)
+X(0b10111011)
+X(0b01000100)
+X(0b11000100)
+X(0b00111010)
+X(0b10111010)
+X(0b01000101)
+X(0b11000101)
+X(0b00111000)
+X(0b10111000)
+X(0b01000111)
+X(0b11000111)
+X(0b00111001)
+X(0b10111001)
+X(0b01000110)
+X(0b11000110)
+X(0b00111101)
+X(0b10111101)
+X(0b01000010)
+X(0b11000010)
+X(0b00111100)
+X(0b10111100)
+X(0b01000011)
+X(0b11000011)
+X(0b00111110)
+X(0b10111110)
+X(0b01000001)
+X(0b11000001)
+X(0b00111111)
+X(0b10111111)
+X(0b01000000)
+X(0b11000000)
+X(0b10111111)
+X(0b00111111)
+X(0b11000000)
+X(0b01000000)
+X(0b10111110)
+X(0b00111110)
+X(0b11000001)
+X(0b01000001)
+X(0b10111100)
+X(0b00111100)
+X(0b11000011)
+X(0b01000011)
+X(0b10111101)
+X(0b00111101)
+X(0b11000010)
+X(0b01000010)
+X(0b10111001)
+X(0b00111001)
+X(0b11000110)
+X(0b01000110)
+X(0b10111000)
+X(0b00111000)
+X(0b11000111)
+X(0b01000111)
+X(0b10111010)
+X(0b00111010)
+X(0b11000101)
+X(0b01000101)
+X(0b10111011)
+X(0b00111011)
+X(0b11000100)
+X(0b01000100)
+X(0b10110011)
+X(0b00110011)
+X(0b11001100)
+X(0b01001100)
+X(0b10110010)
+X(0b00110010)
+X(0b11001101)
+X(0b01001101)
+X(0b10110000)
+X(0b00110000)
+X(0b11001111)
+X(0b01001111)
+X(0b10110001)
+X(0b00110001)
+X(0b11001110)
+X(0b01001110)
+X(0b10110101)
+X(0b00110101)
+X(0b11001010)
+X(0b01001010)
+X(0b10110100)
+X(0b00110100)
+X(0b11001011)
+X(0b01001011)
+X(0b10110110)
+X(0b00110110)
+X(0b11001001)
+X(0b01001001)
+X(0b10110111)
+X(0b00110111)
+X(0b11001000)
+X(0b01001000)
+X(0b10100111)
+X(0b00100111)
+X(0b11011000)
+X(0b01011000)
+X(0b10100110)
+X(0b00100110)
+X(0b11011001)
+X(0b01011001)
+X(0b10100100)
+X(0b00100100)
+X(0b11011011)
+X(0b01011011)
+X(0b10100101)
+X(0b00100101)
+X(0b11011010)
+X(0b01011010)
+X(0b10100001)
+X(0b00100001)
+X(0b11011110)
+X(0b01011110)
+X(0b10100000)
+X(0b00100000)
+X(0b11011111)
+X(0b01011111)
+X(0b10100010)
+X(0b00100010)
+X(0b11011101)
+X(0b01011101)
+X(0b10100011)
+X(0b00100011)
+X(0b11011100)
+X(0b01011100)
+X(0b10101011)
+X(0b00101011)
+X(0b11010100)
+X(0b01010100)
+X(0b10101010)
+X(0b00101010)
+X(0b11010101)
+X(0b01010101)
+X(0b10101000)
+X(0b00101000)
+X(0b11010111)
+X(0b01010111)
+X(0b10101001)
+X(0b00101001)
+X(0b11010110)
+X(0b01010110)
+X(0b10101101)
+X(0b00101101)
+X(0b11010010)
+X(0b01010010)
+X(0b10101100)
+X(0b00101100)
+X(0b11010011)
+X(0b01010011)
+X(0b10101110)
+X(0b00101110)
+X(0b11010001)
+X(0b01010001)
+X(0b10101111)
+X(0b00101111)
+X(0b11010000)
+X(0b01010000)
+X(0b10001111)
+X(0b00001111)
+X(0b11110000)
+X(0b01110000)
+X(0b10001110)
+X(0b00001110)
+X(0b11110001)
+X(0b01110001)
+X(0b10001100)
+X(0b00001100)
+X(0b11110011)
+X(0b01110011)
+X(0b10001101)
+X(0b00001101)
+X(0b11110010)
+X(0b01110010)
+X(0b10001001)
+X(0b00001001)
+X(0b11110110)
+X(0b01110110)
+X(0b10001000)
+X(0b00001000)
+X(0b11110111)
+X(0b01110111)
+X(0b10001010)
+X(0b00001010)
+X(0b11110101)
+X(0b01110101)
+X(0b10001011)
+X(0b00001011)
+X(0b11110100)
+X(0b01110100)
+X(0b10000011)
+X(0b00000011)
+X(0b11111100)
+X(0b01111100)
+X(0b10000010)
+X(0b00000010)
+X(0b11111101)
+X(0b01111101)
+X(0b10000000)
+X(0b00000000)
+X(0b11111111)
+X(0b01111111)
+X(0b10000001)
+X(0b00000001)
+X(0b11111110)
+X(0b01111110)
+X(0b10000101)
+X(0b00000101)
+X(0b11111010)
+X(0b01111010)
+X(0b10000100)
+X(0b00000100)
+X(0b11111011)
+X(0b01111011)
+X(0b10000110)
+X(0b00000110)
+X(0b11111001)
+X(0b01111001)
+X(0b10000111)
+X(0b00000111)
+X(0b11111000)
+X(0b01111000)
+X(0b10010111)
+X(0b00010111)
+X(0b11101000)
+X(0b01101000)
+X(0b10010110)
+X(0b00010110)
+X(0b11101001)
+X(0b01101001)
+X(0b10010100)
+X(0b00010100)
+X(0b11101011)
+X(0b01101011)
+X(0b10010101)
+X(0b00010101)
+X(0b11101010)
+X(0b01101010)
+X(0b10010001)
+X(0b00010001)
+X(0b11101110)
+X(0b01101110)
+X(0b10010000)
+X(0b00010000)
+X(0b11101111)
+X(0b01101111)
+X(0b10010010)
+X(0b00010010)
+X(0b11101101)
+X(0b01101101)
+X(0b10010011)
+X(0b00010011)
+X(0b11101100)
+X(0b01101100)
+X(0b10011011)
+X(0b00011011)
+X(0b11100100)
+X(0b01100100)
+X(0b10011010)
+X(0b00011010)
+X(0b11100101)
+X(0b01100101)
+X(0b10011000)
+X(0b00011000)
+X(0b11100111)
+X(0b01100111)
+X(0b10011001)
+X(0b00011001)
+X(0b11100110)
+X(0b01100110)
+X(0b10011101)
+X(0b00011101)
+X(0b11100010)
+X(0b01100010)
+X(0b10011100)
+X(0b00011100)
+X(0b11100011)
+X(0b01100011)
+X(0b10011110)
+X(0b00011110)
+X(0b11100001)
+X(0b01100001)
+X(0b10011111)
+X(0b00011111)
+X(0b11100000)
+X(0b01100000)
+X(0b11011111)
+X(0b01011111)
+X(0b10100000)
+X(0b00100000)
+X(0b11011110)
+X(0b01011110)
+X(0b10100001)
+X(0b00100001)
+X(0b11011100)
+X(0b01011100)
+X(0b10100011)
+X(0b00100011)
+X(0b11011101)
+X(0b01011101)
+X(0b10100010)
+X(0b00100010)
+X(0b11011001)
+X(0b01011001)
+X(0b10100110)
+X(0b00100110)
+X(0b11011000)
+X(0b01011000)
+X(0b10100111)
+X(0b00100111)
+X(0b11011010)
+X(0b01011010)
+X(0b10100101)
+X(0b00100101)
+X(0b11011011)
+X(0b01011011)
+X(0b10100100)
+X(0b00100100)
+X(0b11010011)
+X(0b01010011)
+X(0b10101100)
+X(0b00101100)
+X(0b11010010)
+X(0b01010010)
+X(0b10101101)
+X(0b00101101)
+X(0b11010000)
+X(0b01010000)
+X(0b10101111)
+X(0b00101111)
+X(0b11010001)
+X(0b01010001)
+X(0b10101110)
+X(0b00101110)
+X(0b11010101)
+X(0b01010101)
+X(0b10101010)
+X(0b00101010)
+X(0b11010100)
+X(0b01010100)
+X(0b10101011)
+X(0b00101011)
+X(0b11010110)
+X(0b01010110)
+X(0b10101001)
+X(0b00101001)
+X(0b11010111)
+X(0b01010111)
+X(0b10101000)
+X(0b00101000)
+X(0b11000111)
+X(0b01000111)
+X(0b10111000)
+X(0b00111000)
+X(0b11000110)
+X(0b01000110)
+X(0b10111001)
+X(0b00111001)
+X(0b11000100)
+X(0b01000100)
+X(0b10111011)
+X(0b00111011)
+X(0b11000101)
+X(0b01000101)
+X(0b10111010)
+X(0b00111010)
+X(0b11000001)
+X(0b01000001)
+X(0b10111110)
+X(0b00111110)
+X(0b11000000)
+X(0b01000000)
+X(0b10111111)
+X(0b00111111)
+X(0b11000010)
+X(0b01000010)
+X(0b10111101)
+X(0b00111101)
+X(0b11000011)
+X(0b01000011)
+X(0b10111100)
+X(0b00111100)
+X(0b11001011)
+X(0b01001011)
+X(0b10110100)
+X(0b00110100)
+X(0b11001010)
+X(0b01001010)
+X(0b10110101)
+X(0b00110101)
+X(0b11001000)
+X(0b01001000)
+X(0b10110111)
+X(0b00110111)
+X(0b11001001)
+X(0b01001001)
+X(0b10110110)
+X(0b00110110)
+X(0b11001101)
+X(0b01001101)
+X(0b10110010)
+X(0b00110010)
+X(0b11001100)
+X(0b01001100)
+X(0b10110011)
+X(0b00110011)
+X(0b11001110)
+X(0b01001110)
+X(0b10110001)
+X(0b00110001)
+X(0b11001111)
+X(0b01001111)
+X(0b10110000)
+X(0b00110000)
+X(0b11101111)
+X(0b01101111)
+X(0b10010000)
+X(0b00010000)
+X(0b11101110)
+X(0b01101110)
+X(0b10010001)
+X(0b00010001)
+X(0b11101100)
+X(0b01101100)
+X(0b10010011)
+X(0b00010011)
+X(0b11101101)
+X(0b01101101)
+X(0b10010010)
+X(0b00010010)
+X(0b11101001)
+X(0b01101001)
+X(0b10010110)
+X(0b00010110)
+X(0b11101000)
+X(0b01101000)
+X(0b10010111)
+X(0b00010111)
+X(0b11101010)
+X(0b01101010)
+X(0b10010101)
+X(0b00010101)
+X(0b11101011)
+X(0b01101011)
+X(0b10010100)
+X(0b00010100)
+X(0b11100011)
+X(0b01100011)
+X(0b10011100)
+X(0b00011100)
+X(0b11100010)
+X(0b01100010)
+X(0b10011101)
+X(0b00011101)
+X(0b11100000)
+X(0b01100000)
+X(0b10011111)
+X(0b00011111)
+X(0b11100001)
+X(0b01100001)
+X(0b10011110)
+X(0b00011110)
+X(0b11100101)
+X(0b01100101)
+X(0b10011010)
+X(0b00011010)
+X(0b11100100)
+X(0b01100100)
+X(0b10011011)
+X(0b00011011)
+X(0b11100110)
+X(0b01100110)
+X(0b10011001)
+X(0b00011001)
+X(0b11100111)
+X(0b01100111)
+X(0b10011000)
+X(0b00011000)
+X(0b11110111)
+X(0b01110111)
+X(0b10001000)
+X(0b00001000)
+X(0b11110110)
+X(0b01110110)
+X(0b10001001)
+X(0b00001001)
+X(0b11110100)
+X(0b01110100)
+X(0b10001011)
+X(0b00001011)
+X(0b11110101)
+X(0b01110101)
+X(0b10001010)
+X(0b00001010)
+X(0b11110001)
+X(0b01110001)
+X(0b10001110)
+X(0b00001110)
+X(0b11110000)
+X(0b01110000)
+X(0b10001111)
+X(0b00001111)
+X(0b11110010)
+X(0b01110010)
+X(0b10001101)
+X(0b00001101)
+X(0b11110011)
+X(0b01110011)
+X(0b10001100)
+X(0b00001100)
+X(0b11111011)
+X(0b01111011)
+X(0b10000100)
+X(0b00000100)
+X(0b11111010)
+X(0b01111010)
+X(0b10000101)
+X(0b00000101)
+X(0b11111000)
+X(0b01111000)
+X(0b10000111)
+X(0b00000111)
+X(0b11111001)
+X(0b01111001)
+X(0b10000110)
+X(0b00000110)
+X(0b11111101)
+X(0b01111101)
+X(0b10000010)
+X(0b00000010)
+X(0b11111100)
+X(0b01111100)
+X(0b10000011)
+X(0b00000011)
+X(0b11111110)
+X(0b01111110)
+X(0b10000001)
+X(0b00000001)
+X(0b11111111)
+X(0b01111111)
+X(0b10000000)
+X(0b00000000)
diff --git a/flashimg/cpu_hdmi/tmds_decode_table.h.gen b/flashimg/cpu_hdmi/tmds_decode_table.h.gen
new file mode 100755
index 0000000..f3b330f
--- /dev/null
+++ b/flashimg/cpu_hdmi/tmds_decode_table.h.gen
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+# flashimg/cpu_hdmi/tmds_decode_table.h.gen - Generate a table for TMDS decoding
+#
+# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+import sys
+
+print(f"// Generated by `{' '.join(sys.argv)}`. DO NOT EDIT!")
+for enc in range(1 << 10):
+ D = [bool((enc >> (9 - i)) & 1) for i in range(10)]
+ # Algorithm from DVI 1.0 Figure 3-6
+ if D[9]:
+ for i in range(8):
+ D[i] = not D[i]
+ if D[8]:
+ Q = [
+ D[0],
+ D[1] != D[0],
+ D[2] != D[1],
+ D[3] != D[2],
+ D[4] != D[3],
+ D[5] != D[4],
+ D[6] != D[5],
+ D[7] != D[6],
+ ]
+ else:
+ Q = [
+ D[0],
+ D[1] == D[0],
+ D[2] == D[1],
+ D[3] == D[2],
+ D[4] == D[3],
+ D[5] == D[4],
+ D[6] == D[5],
+ D[7] == D[6],
+ ]
+ s = "".join("1" if b else "0" for b in Q)
+ print(f"X(0b{s})")