From: rs <> Date: Sun, 1 Feb 2026 23:38:26 +0000 (-0600) Subject: Add 8b10b encoder and decoder X-Git-Url: https://git.the-white-hart.net/?a=commitdiff_plain;ds=sidebyside;p=vhdl Add 8b10b encoder and decoder --- diff --git a/projects/mp3tape/b8b10_decode.vhd b/projects/mp3tape/b8b10_decode.vhd new file mode 100644 index 0000000..638cc35 --- /dev/null +++ b/projects/mp3tape/b8b10_decode.vhd @@ -0,0 +1,117 @@ +library ieee; +use ieee.std_logic_1164.all; + +library unisim; +use unisim.vcomponents.all; + + +entity b8b10_decode is + port ( + rst_i: in std_logic; + clk_i: in std_logic; + + en: in std_logic; + b10: in std_logic_vector(9 downto 0); + + b8: out std_logic_vector(7 downto 0); + k: out std_logic; + invalid: out std_logic + ); +end b8b10_decode; + + +architecture behavioral of b8b10_decode is + + signal tab_entry: std_logic_vector(15 downto 0); + +begin + + invalid <= tab_entry(15); + k <= tab_entry(14); + b8 <= tab_entry(7 downto 0); + + e_8b10b_decode: ramb16_s18 + generic map ( + -- 10-bit table index: jhgfiedcba + -- 16-bit table entry: invalid & is_k & "0000" & HGFEDCBA + INIT_00 => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_01 => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_02 => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_03 => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_04 => x"800000ee00ed800000eb80008000800080008000800080008000800080008000", + INIT_05 => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_06 => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_07 => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_08 => x"8000000e000d8000000b80008000800080008000800080008000800080008000", + INIT_09 => x"8000001e001d001c001b001a0019800000170016001580000013800080008000", + INIT_0A => x"800000010002000c0004000a0009800000080006000580000003800080008000", + INIT_0B => x"80008000800080008000000f0000000780000010001f00140018001200118000", + INIT_0C => x"8000006e006d0078006b007f0070800000670060006f80008000800080008000", + INIT_0D => x"800080008000007c8000007a0079006880000076007500640073006200618000", + INIT_0E => x"800080008000006c8000006a00690077800000660065007b0063007d007e8000", + INIT_0F => x"8000800080008000800080008000800080008000800000748000007200718000", + INIT_10 => x"8000008e008d8000008b80008000800080008000800080008000800080008000", + INIT_11 => x"8000009e009d009c009b009a0099800000970096009580000093800080008000", + INIT_12 => x"800000810082008c0084008a0089800000880086008580000083800080008000", + INIT_13 => x"80008000800080008000008f0080008780000090009f00940098009200918000", + INIT_14 => x"800000ae00ad00b800ab00bf00b0800000a700a000af80008000800080008000", + INIT_15 => x"800000be00bd00bc00bb00ba00b900a800b700b600b500a400b300a200a18000", + INIT_16 => x"800000a100a200ac00a400aa00a900b700a800a600a500bb00a300bd00be8000", + INIT_17 => x"8000800080008000800000af00a000a7800000b000bf00b400b800b200b18000", + INIT_18 => x"800000ce00cd00d800cb00df00d0800000c700c000cf80008000800080008000", + INIT_19 => x"800000de00dd00dc00db00da00d900c800d700d600d500c400d300c200c18000", + INIT_1A => x"800000c100c200cc00c400ca00c900d700c800c600c500db00c300dd00de8000", + INIT_1B => x"8000800080008000800000cf00c000c7800000d000df00d400d800d200d18000", + INIT_1C => x"800000ee00ed00f800eb00ff00f0800000e700e000ef80008000800080008000", + INIT_1D => x"80008000800000fc800000fa00f900e8800000f600f500e400f300e200e18000", + INIT_1E => x"80008000800000ec800000ea00e900f7800000e600e500fb00e300fd00fe8000", + INIT_1F => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_20 => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_21 => x"800000fe00fd00fc00fb00fa00f9800000f700f600f5800000f3800080008000", + INIT_22 => x"800000e100e200ec00e400ea00e9800000e800e600e5800000e3800080008000", + INIT_23 => x"8000800080008000800000ef00e000e7800000f000ff00f400f800f200f18000", + INIT_24 => x"8000002e002d0038002b003f0030800000270020002f80008000800080008000", + INIT_25 => x"8000003e003d003c003b003a0039002800370036003500240033002200218000", + INIT_26 => x"800000210022002c0024002a00290037002800260025003b0023003d003e8000", + INIT_27 => x"80008000800080008000002f0020002780000030003f00340038003200318000", + INIT_28 => x"8000004e004d0058004b005f0050800000470040004f80008000800080008000", + INIT_29 => x"8000005e005d005c005b005a0059004800570056005500440053004200418000", + INIT_2A => x"800000410042004c0044004a00490057004800460045005b0043005d005e8000", + INIT_2B => x"80008000800080008000004f0040004780000050005f00540058005200518000", + INIT_2C => x"8000008e008d0098008b009f0090800000870080008f80008000800080008000", + INIT_2D => x"800080008000009c8000009a0099008880000096009500840093008200818000", + INIT_2E => x"800080008000008c8000008a00890097800000860085009b0083009d009e8000", + INIT_2F => x"8000800080008000800080008000800080008000800000948000009200918000", + INIT_30 => x"8000006e006d8000006b80008000800080008000800080008000800080008000", + INIT_31 => x"8000007e007d007c007b007a0079800000770076007580000073800080008000", + INIT_32 => x"800000610062006c0064006a0069800000680066006580000063800080008000", + INIT_33 => x"80008000800080008000006f0060006780000070007f00740078007200718000", + INIT_34 => x"8000000e000d0018000b001f0010800000070000000f80008000800080008000", + INIT_35 => x"800080008000001c8000001a0019000880000016001500040013000200018000", + INIT_36 => x"800080008000000c8000000a00090017800000060005001b0003001d001e8000", + INIT_37 => x"8000800080008000800080008000800080008000800000148000001200118000", + INIT_38 => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_39 => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_3A => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_3B => x"8000800080008000800080008000800080008000800000f4800000f200f18000", + INIT_3C => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_3D => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_3E => x"8000800080008000800080008000800080008000800080008000800080008000", + INIT_3F => x"8000800080008000800080008000800080008000800080008000800080008000", + + -- Reset output register to zero to ensure no invalid or k bit set at startup + SRVAL => "000000000000000000" + ) + port map ( + WE => '0', + EN => en, + SSR => rst_i, + CLK => clk_i, + ADDR => b10, + DI => (others => '0'), + DIP => (others => '0'), + DOP => open, + DO => tab_entry + ); + +end behavioral; diff --git a/projects/mp3tape/b8b10_encode.vhd b/projects/mp3tape/b8b10_encode.vhd new file mode 100644 index 0000000..2dafb8e --- /dev/null +++ b/projects/mp3tape/b8b10_encode.vhd @@ -0,0 +1,122 @@ +library ieee; +use ieee.std_logic_1164.all; + +library unisim; +use unisim.vcomponents.all; + + +entity b8b10_encode is + port ( + rst_i: in std_logic; + clk_i: in std_logic; + + en: in std_logic; + k: in std_logic; + b8: in std_logic_vector(7 downto 0); + + b10: out std_logic_vector(9 downto 0); + invalid: out std_logic + ); +end b8b10_encode; + + +architecture behavioral of b8b10_encode is + + signal disparity: std_logic := '0'; -- 0 -> -1 + signal tab_idx: std_logic_vector(9 downto 0); + signal tab_entry: std_logic_vector(15 downto 0); + +begin + + tab_idx <= k & disparity & b8; + + invalid <= tab_entry(15); + disparity <= tab_entry(14); + b10 <= tab_entry(9 downto 0); + + e_8b10b_encode: ramb16_s18 + generic map ( + -- 10-bit table index: k & rd & HGFEDCBA + -- 16-bit table entry: invalid_k & new_rd & "0000" & jhgf & iedcba + INIT_00 => x"00ba434e434d436c434b436a436900a743474366436500ab436300ad00ae00b9", + INIT_01 => x"00b5009e009d435c009b435a435900b3009743564355437443534372437100b6", + INIT_02 => x"427a024e024d026c024b026a02694267024702660265426b0263426d426e4279", + INIT_03 => x"4275425e425d025c425b025a0259427342570256025502740253027202714276", + INIT_04 => x"42ba028e028d02ac028b02aa02a942a7028702a602a542ab02a342ad42ae42b9", + INIT_05 => x"42b5429e429d029c429b029a029942b342970296029502b4029302b202b142b6", + INIT_06 => x"433a00ce00cd00ec00cb00ea00e9432700c700e600e5432b00e3432d432e4339", + INIT_07 => x"4335431e431d00dc431b00da00d94333431700d600d500f400d300f200f14336", + INIT_08 => x"013a42ce42cd42ec42cb42ea42e9012742c742e642e5012b42e3012d012e0139", + INIT_09 => x"0135011e011d42dc011b42da42d90133011742d642d542f442d342f242f10136", + INIT_0A => x"417a014e014d016c014b016a01694167014701660165416b0163416d416e4179", + INIT_0B => x"4175415e415d015c415b015a0159417341570156015501740153017201714176", + INIT_0C => x"41ba018e018d01ac018b01aa01a941a7018701a601a541ab01a341ad41ae41b9", + INIT_0D => x"41b5419e419d019c419b019a019941b341970196019501b4019301b201b141b6", + INIT_0E => x"023a41ce41cd41ec41cb41ea41e9022741c741e641e5022b41e3022d022e0239", + INIT_0F => x"0235021e021d41dc021b41da41d90233021741d641d543b441d343b243b10236", + INIT_10 => x"4345008e008d00ac008b00aa00a9435800b800a600a5435400a3435243514346", + INIT_11 => x"434a43614362009c4364009a0099434c43680096009500b4009300b200b14349", + INIT_12 => x"0245424e424d426c424b426a4269025842784266426502544263025202510246", + INIT_13 => x"024a02610262425c0264425a4259024c02684256425542744253427242710249", + INIT_14 => x"0285428e428d42ac428b42aa42a9029842b842a642a5029442a3029202910286", + INIT_15 => x"028a02a102a2429c02a4429a4299028c02a84296429542b4429342b242b10289", + INIT_16 => x"00c5430e430d432c430b432a432900d843384326432500d4432300d200d100c6", + INIT_17 => x"00ca00e100e2431c00e4431a431900cc00e843164315433443134332433100c9", + INIT_18 => x"42c5010e010d012c010b012a012942d801380126012542d4012342d242d142c6", + INIT_19 => x"42ca42e142e2011c42e4011a011942cc42e801160115013401130132013142c9", + INIT_1A => x"0145414e414d416c414b416a4169015841784166416501544163015201510146", + INIT_1B => x"014a01610162415c0164415a4159014c01684156415541744153417241710149", + INIT_1C => x"0185418e418d41ac418b41aa41a9019841b841a641a5019441a3019201910186", + INIT_1D => x"018a01a101a2419c01a4419a4199018c01a84196419541b4419341b241b10189", + INIT_1E => x"41c5004e004d022c004b022a022941d802380226022541d4022341d241d141c6", + INIT_1F => x"41ca41e141e2021c41e4021a021941cc41e802160215023402130232023141c9", + INIT_20 => x"80bac34ec34dc36cc34bc36ac36980a7c347c366c36580abc36380ad80ae80b9", + INIT_21 => x"80b5809e809d00bc809bc35ac35980b38097c356c355c374c353c372c37180b6", + INIT_22 => x"c27a818e818d81ac818b81aa81a9c267818781a681a5c26b81a3c26dc26ec279", + INIT_23 => x"c275c25ec25d427cc25b819a8199c273c2578196819581b4819381b281b1c276", + INIT_24 => x"c2ba814e814d816c814b816a8169c2a7814781668165c2ab8163c2adc2aec2b9", + INIT_25 => x"c2b5c29ec29d42bcc29b815a8159c2b3c297815681558174815381728171c2b6", + INIT_26 => x"c33a80ce80cd80ec80cb80ea80e9c32780c780e680e5c32b80e3c32dc32ec339", + INIT_27 => x"c335c31ec31d433cc31b80da80d9c333c31780d680d580f480d380f280f1c336", + INIT_28 => x"813ac2cec2cdc2ecc2cbc2eac2e98127c2c7c2e6c2e5812bc2e3812d812e8139", + INIT_29 => x"8135811e811d013c811bc2dac2d981338117c2d6c2d5c2f4c2d3c2f2c2f18136", + INIT_2A => x"c17a828e828d82ac828b82aa82a9c167828782a682a5c16b82a3c16dc16ec179", + INIT_2B => x"c175c15ec15d417cc15b829a8299c173c1578296829582b4829382b282b1c176", + INIT_2C => x"c1ba824e824d826c824b826a8269c1a7824782668265c1ab8263c1adc1aec1b9", + INIT_2D => x"c1b5c19ec19d41bcc19b825a8259c1b3c197825682558274825382728271c1b6", + INIT_2E => x"807ac38ec38dc3acc38bc3aac3a98067c387c3a6c3a5806bc3a3806d806e8079", + INIT_2F => x"8075005e005d007c005bc39ac39980730057c396c395c3b4c393c3b2c3b18076", + INIT_30 => x"c345808e808d80ac808b80aa80a9c35880b880a680a5c35480a3c352c351c346", + INIT_31 => x"c34ac361c3624343c364809a8099c34cc3688096809580b4809380b280b1c349", + INIT_32 => x"8185c24ec24dc26cc24bc26ac2698198c278c266c2658194c263819281918186", + INIT_33 => x"818a81a181a2018381a4c25ac259818c81a8c256c255c274c253c272c2718189", + INIT_34 => x"8145c28ec28dc2acc28bc2aac2a98158c2b8c2a6c2a58154c2a3815281518146", + INIT_35 => x"814a8161816201438164c29ac299814c8168c296c295c2b4c293c2b2c2b18149", + INIT_36 => x"80c5c30ec30dc32cc30bc32ac32980d8c338c326c32580d4c32380d280d180c6", + INIT_37 => x"80ca80e180e200c380e4c31ac31980cc80e8c316c315c334c313c332c33180c9", + INIT_38 => x"c2c5810e810d812c810b812a8129c2d8813881268125c2d48123c2d2c2d1c2c6", + INIT_39 => x"c2cac2e1c2e242c3c2e4811a8119c2ccc2e8811681158134811381328131c2c9", + INIT_3A => x"8285c14ec14dc16cc14bc16ac1698298c178c166c1658294c163829282918286", + INIT_3B => x"828a82a182a2028382a4c15ac159828c82a8c156c155c174c153c172c1718289", + INIT_3C => x"8245c18ec18dc1acc18bc1aac1a98258c1b8c1a6c1a58254c1a3825282518246", + INIT_3D => x"824a8261826202438264c19ac199824c8268c196c195c1b4c193c1b2c1b18249", + INIT_3E => x"c385804e804d806c804b806a8069c398807880668065c3948063c392c391c386", + INIT_3F => x"c38a43a143a2438343a4805a8059c38c43a8805680558074805380728071c389", + + -- Output register is used as disparity state bit, + -- so need a reset value of '0' in the disparity bit + SRVAL => "000000000000000000" + ) + port map ( + WE => '0', + EN => en, + SSR => rst_i, + CLK => clk_i, + ADDR => tab_idx, + DI => (others => '0'), + DIP => (others => '0'), + DOP => open, + DO => tab_entry + ); + +end behavioral; diff --git a/projects/mp3tape/tests/test_b8b10_encode.vhd b/projects/mp3tape/tests/test_b8b10_encode.vhd new file mode 100644 index 0000000..81f7e44 --- /dev/null +++ b/projects/mp3tape/tests/test_b8b10_encode.vhd @@ -0,0 +1,92 @@ +library ieee; +use ieee.std_logic_1164.all; + +library work; + + +entity test_b8b10_encode is +end test_b8b10_encode; + + +architecture behavior of test_b8b10_encode is + + constant CLK_I_PERIOD: time := 20 ns; + + signal rst_i: std_logic; + signal clk_i: std_logic; + + signal en: std_logic; + signal k: std_logic; + signal b8: std_logic_vector(7 downto 0); + + signal b10: std_logic_vector(9 downto 0); + signal invalid: std_logic; + + type test_t is ( + T_RESET, + T_3F_0, + T_3F_1, + T_DONE + ); + signal test: test_t; + +begin + + p_test: process + begin + -- Initial values + en <= '0'; + + -- Reset + test <= T_RESET; + rst_i <= '1'; + wait for CLK_I_PERIOD*2; + rst_i <= '0'; + wait until falling_edge(clk_i); + + -- Test + test <= T_3F_0; + en <= '1'; + k <= '0'; + b8 <= x"3f"; + wait until rising_edge(clk_i); + wait for 1 ns; + assert b10 = "1001110101"; + wait until falling_edge(clk_i); + + test <= T_3F_1; + wait until rising_edge(clk_i); + wait for 1 ns; + assert b10 = "1001001010"; + wait until falling_edge(clk_i); + + -- Done + en <= '0'; + test <= T_DONE; + wait; + end process; + + + e_uut: entity work.b8b10_encode + port map ( + rst_i => rst_i, + clk_i => clk_i, + + en => en, + k => k, + b8 => b8, + + b10 => b10, + invalid => invalid + ); + + + p_clk: process + begin + clk_i <= '0'; + wait for CLK_I_PERIOD/2; + clk_i <= '1'; + wait for CLK_I_PERIOD/2; + end process; + +end; diff --git a/projects/mp3tape/tools/bram.py b/projects/mp3tape/tools/bram.py new file mode 100755 index 0000000..cd1e29a --- /dev/null +++ b/projects/mp3tape/tools/bram.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 + +from __future__ import annotations +from typing import * +from encdec8b10b import EncDec8B10B +import math + +# ------------------------------------------------------------------------------ + + +def tabgen_enc8b10b() -> List[int]: + # 10-bit table index: k & rd & value + # 16-bit table entry: invalid_k & new_rd & "0000" & jhgf & iedcba + entries = list() + for ctrl in (0, 1): + for rd in (0, 1): + for value in range(256): + new_rd, enc = EncDec8B10B.enc_8b10b(value, rd, ctrl) + invalid_k = 0 + if ctrl != 0: + if value not in (0x1c, 0x3c, 0x5c, 0x7c, 0x9c, 0xbc, 0xdc, 0xfc, 0xf7, 0xfb, 0xfd, 0xfe): + invalid_k = 1 + entries.append((invalid_k << 15) | (new_rd << 14) | enc) + return entries + + +def tabgen_dec8b10b() -> List[int]: + # 10-bit table index: encoded value + # 16-bit table entry: invalid & is_k & "0000" & hgfedcba + entries = [0x8000] * 1024 + for ctrl in (0, 1): + if ctrl != 0: + if value not in (0x1c, 0x3c, 0x5c, 0x7c, 0x9c, 0xbc, 0xdc, 0xfc, 0xf7, 0xfb, 0xfd, 0xfe): + continue # Skip invalid control codes + for rd in (0, 1): + for value in range(256): + new_rd, enc = EncDec8B10B.enc_8b10b(value, rd, ctrl) + prev = entries[enc] + value = (ctrl << 14) | value + if prev != 0x8000: + if prev != value: + print(hex(prev), hex(value)) + assert prev == value + entries[enc] = value + return entries + + +def initgen_16(entries: List[int]): + assert len(entries) == 1024 + + # Convert entries to hex strings + hex_entries = [f'{(entry & 0xffff):04x}' for entry in entries] + + # For each INIT_xx line + for i in range(0x40): + # Each word in this INIT_xx line, most to least significant + words = reversed([hex_entries[i*16 + j] for j in range(16)]) + line = ''.join(words) + init = f'\t\t\tINIT_{i:02X} => x"{line}",' + print(init) + + +# ------------------------------------------------------------------------------ + + +def main() -> int: + print() + print('\t\t\t-- 10-bit table index: k & rd & HGFEDCBA') + print('\t\t\t-- 16-bit table entry: invalid_k & new_rd & "0000" & jhgf & iedcba') + enc8b10b = tabgen_enc8b10b() + initgen_16(enc8b10b) + + print() + print('\t\t\t-- 10-bit table index: jhgfiedcba') + print('\t\t\t-- 16-bit table entry: invalid & is_k & "0000" & HGFEDCBA') + dec8b10b = tabgen_dec8b10b() + initgen_16(dec8b10b) + + return 0 + + +if __name__ == '__main__': + exit(main()) +