-- Instruction pre-decoder for microwatt
-- One cycle latency.  Does 'WIDTH' instructions in parallel.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library work;
use work.common.all;
use work.decode_types.all;
use work.insn_helpers.all;

entity predecoder is
    generic (
        HAS_FPU   : boolean := true;
        WIDTH     : natural := 2;
        ICODE_LEN : natural := 10;
        IMAGE_LEN : natural := 26
        );
    port (
        clk        : in  std_ulogic;
        valid_in   : in  std_ulogic;
        insns_in   : in  std_ulogic_vector(WIDTH * 32 - 1 downto 0);
        icodes_out : out std_ulogic_vector(WIDTH * (ICODE_LEN + IMAGE_LEN) - 1 downto 0)
        );
end entity predecoder;

architecture behaviour of predecoder is

    type predecoder_rom_t is array(0 to 2047) of insn_code;

    constant major_predecode_rom : predecoder_rom_t := (
        2#001100_00000# to 2#001100_11111# =>  INSN_addic,
        2#001101_00000# to 2#001101_11111# =>  INSN_addic_dot,
        2#001110_00000# to 2#001110_11111# =>  INSN_addi,
        2#001111_00000# to 2#001111_11111# =>  INSN_addis,
        2#010011_00100# to 2#010011_00101# =>  INSN_addpcis,
        2#011100_00000# to 2#011100_11111# =>  INSN_andi_dot,
        2#011101_00000# to 2#011101_11111# =>  INSN_andis_dot,
        2#000000_00000#                    =>  INSN_attn,
        2#010010_00000# to 2#010010_00001# =>  INSN_brel,
        2#010010_00010# to 2#010010_00011# =>  INSN_babs,
        2#010010_00100# to 2#010010_00101# =>  INSN_brel,
        2#010010_00110# to 2#010010_00111# =>  INSN_babs,
        2#010010_01000# to 2#010010_01001# =>  INSN_brel,
        2#010010_01010# to 2#010010_01011# =>  INSN_babs,
        2#010010_01100# to 2#010010_01101# =>  INSN_brel,
        2#010010_01110# to 2#010010_01111# =>  INSN_babs,
        2#010010_10000# to 2#010010_10001# =>  INSN_brel,
        2#010010_10010# to 2#010010_10011# =>  INSN_babs,
        2#010010_10100# to 2#010010_10101# =>  INSN_brel,
        2#010010_10110# to 2#010010_10111# =>  INSN_babs,
        2#010010_11000# to 2#010010_11001# =>  INSN_brel,
        2#010010_11010# to 2#010010_11011# =>  INSN_babs,
        2#010010_11100# to 2#010010_11101# =>  INSN_brel,
        2#010010_11110# to 2#010010_11111# =>  INSN_babs,
        2#010000_00000# to 2#010000_00001# =>  INSN_bcrel,
        2#010000_00010# to 2#010000_00011# =>  INSN_bcabs,
        2#010000_00100# to 2#010000_00101# =>  INSN_bcrel,
        2#010000_00110# to 2#010000_00111# =>  INSN_bcabs,
        2#010000_01000# to 2#010000_01001# =>  INSN_bcrel,
        2#010000_01010# to 2#010000_01011# =>  INSN_bcabs,
        2#010000_01100# to 2#010000_01101# =>  INSN_bcrel,
        2#010000_01110# to 2#010000_01111# =>  INSN_bcabs,
        2#010000_10000# to 2#010000_10001# =>  INSN_bcrel,
        2#010000_10010# to 2#010000_10011# =>  INSN_bcabs,
        2#010000_10100# to 2#010000_10101# =>  INSN_bcrel,
        2#010000_10110# to 2#010000_10111# =>  INSN_bcabs,
        2#010000_11000# to 2#010000_11001# =>  INSN_bcrel,
        2#010000_11010# to 2#010000_11011# =>  INSN_bcabs,
        2#010000_11100# to 2#010000_11101# =>  INSN_bcrel,
        2#010000_11110# to 2#010000_11111# =>  INSN_bcabs,
        2#001011_00000# to 2#001011_11111# =>  INSN_cmpi,
        2#001010_00000# to 2#001010_11111# =>  INSN_cmpli,
        2#100010_00000# to 2#100010_11111# =>  INSN_lbz,
        2#100011_00000# to 2#100011_11111# =>  INSN_lbzu,
        2#110010_00000# to 2#110010_11111# =>  INSN_lfd,
        2#110011_00000# to 2#110011_11111# =>  INSN_lfdu,
        2#110000_00000# to 2#110000_11111# =>  INSN_lfs,
        2#110001_00000# to 2#110001_11111# =>  INSN_lfsu,
        2#101010_00000# to 2#101010_11111# =>  INSN_lha,
        2#101011_00000# to 2#101011_11111# =>  INSN_lhau,
        2#101000_00000# to 2#101000_11111# =>  INSN_lhz,
        2#101001_00000# to 2#101001_11111# =>  INSN_lhzu,
        2#100000_00000# to 2#100000_11111# =>  INSN_lwz,
        2#100001_00000# to 2#100001_11111# =>  INSN_lwzu,
        2#000111_00000# to 2#000111_11111# =>  INSN_mulli,
        2#011000_00000# to 2#011000_11111# =>  INSN_ori,
        2#011001_00000# to 2#011001_11111# =>  INSN_oris,
        2#010100_00000# to 2#010100_11111# =>  INSN_rlwimi,
        2#010101_00000# to 2#010101_11111# =>  INSN_rlwinm,
        2#010111_00000# to 2#010111_11111# =>  INSN_rlwnm,
        2#010001_00000# to 2#010001_11111# =>  INSN_sc,
        2#100110_00000# to 2#100110_11111# =>  INSN_stb,
        2#100111_00000# to 2#100111_11111# =>  INSN_stbu,
        2#110110_00000# to 2#110110_11111# =>  INSN_stfd,
        2#110111_00000# to 2#110111_11111# =>  INSN_stfdu,
        2#110100_00000# to 2#110100_11111# =>  INSN_stfs,
        2#110101_00000# to 2#110101_11111# =>  INSN_stfsu,
        2#101100_00000# to 2#101100_11111# =>  INSN_sth,
        2#101101_00000# to 2#101101_11111# =>  INSN_sthu,
        2#100100_00000# to 2#100100_11111# =>  INSN_stw,
        2#100101_00000# to 2#100101_11111# =>  INSN_stwu,
        2#001000_00000# to 2#001000_11111# =>  INSN_subfic,
        2#000010_00000# to 2#000010_11111# =>  INSN_tdi,
        2#000011_00000# to 2#000011_11111# =>  INSN_twi,
        2#011010_00000# to 2#011010_11111# =>  INSN_xori,
        2#011011_00000# to 2#011011_11111# =>  INSN_xoris,
        -- major opcode 4
        2#000100_10000#                    =>  INSN_maddhd,
        2#000100_10001#                    =>  INSN_maddhdu,
        2#000100_10011#                    =>  INSN_maddld,
        -- major opcode 30
        2#011110_01000# to 2#011110_01001# =>  INSN_rldic,
        2#011110_01010# to 2#011110_01011# =>  INSN_rldic,
        2#011110_00000# to 2#011110_00001# =>  INSN_rldicl,
        2#011110_00010# to 2#011110_00011# =>  INSN_rldicl,
        2#011110_00100# to 2#011110_00101# =>  INSN_rldicr,
        2#011110_00110# to 2#011110_00111# =>  INSN_rldicr,
        2#011110_01100# to 2#011110_01101# =>  INSN_rldimi,
        2#011110_01110# to 2#011110_01111# =>  INSN_rldimi,
        2#011110_10000# to 2#011110_10001# =>  INSN_rldcl,
        2#011110_10010# to 2#011110_10011# =>  INSN_rldcr,
        -- major opcode 58
        2#111010_00000#                    =>  INSN_ld,
        2#111010_00001#                    =>  INSN_ldu,
        2#111010_00010#                    =>  INSN_lwa,
        2#111010_00100#                    =>  INSN_ld,
        2#111010_00101#                    =>  INSN_ldu,
        2#111010_00110#                    =>  INSN_lwa,
        2#111010_01000#                    =>  INSN_ld,
        2#111010_01001#                    =>  INSN_ldu,
        2#111010_01010#                    =>  INSN_lwa,
        2#111010_01100#                    =>  INSN_ld,
        2#111010_01101#                    =>  INSN_ldu,
        2#111010_01110#                    =>  INSN_lwa,
        2#111010_10000#                    =>  INSN_ld,
        2#111010_10001#                    =>  INSN_ldu,
        2#111010_10010#                    =>  INSN_lwa,
        2#111010_10100#                    =>  INSN_ld,
        2#111010_10101#                    =>  INSN_ldu,
        2#111010_10110#                    =>  INSN_lwa,
        2#111010_11000#                    =>  INSN_ld,
        2#111010_11001#                    =>  INSN_ldu,
        2#111010_11010#                    =>  INSN_lwa,
        2#111010_11100#                    =>  INSN_ld,
        2#111010_11101#                    =>  INSN_ldu,
        2#111010_11110#                    =>  INSN_lwa,
        -- major opcode 59
        2#111011_00100# to 2#111011_00101# =>  INSN_fdivs,
        2#111011_01000# to 2#111011_01001# =>  INSN_fsubs,
        2#111011_01010# to 2#111011_01011# =>  INSN_fadds,
        2#111011_01100# to 2#111011_01101# =>  INSN_fsqrts,
        2#111011_10000# to 2#111011_10001# =>  INSN_fres,
        2#111011_10010# to 2#111011_10011# =>  INSN_fmuls,
        2#111011_10100# to 2#111011_10101# =>  INSN_frsqrtes,
        2#111011_11000# to 2#111011_11001# =>  INSN_fmsubs,
        2#111011_11010# to 2#111011_11011# =>  INSN_fmadds,
        2#111011_11100# to 2#111011_11101# =>  INSN_fnmsubs,
        2#111011_11110# to 2#111011_11111# =>  INSN_fnmadds,
        -- major opcode 62
        2#111110_00000#                    =>  INSN_std,
        2#111110_00001#                    =>  INSN_stdu,
        2#111110_00100#                    =>  INSN_std,
        2#111110_00101#                    =>  INSN_stdu,
        2#111110_01000#                    =>  INSN_std,
        2#111110_01001#                    =>  INSN_stdu,
        2#111110_01100#                    =>  INSN_std,
        2#111110_01101#                    =>  INSN_stdu,
        2#111110_10000#                    =>  INSN_std,
        2#111110_10001#                    =>  INSN_stdu,
        2#111110_10100#                    =>  INSN_std,
        2#111110_10101#                    =>  INSN_stdu,
        2#111110_11000#                    =>  INSN_std,
        2#111110_11001#                    =>  INSN_stdu,
        2#111110_11100#                    =>  INSN_std,
        2#111110_11101#                    =>  INSN_stdu,
        -- major opcode 63
        2#111111_00100# to 2#111111_00101# =>  INSN_fdiv,
        2#111111_01000# to 2#111111_01001# =>  INSN_fsub,
        2#111111_01010# to 2#111111_01011# =>  INSN_fadd,
        2#111111_01100# to 2#111111_01101# =>  INSN_fsqrt,
        2#111111_01110# to 2#111111_01111# =>  INSN_fsel,
        2#111111_10000# to 2#111111_10001# =>  INSN_fre,
        2#111111_10010# to 2#111111_10011# =>  INSN_fmul,
        2#111111_10100# to 2#111111_10101# =>  INSN_frsqrte,
        2#111111_11000# to 2#111111_11001# =>  INSN_fmsub,
        2#111111_11010# to 2#111111_11011# =>  INSN_fmadd,
        2#111111_11100# to 2#111111_11101# =>  INSN_fnmsub,
        2#111111_11110# to 2#111111_11111# =>  INSN_fnmadd,
        -- prefix word, PO1
        2#000001_00000# to 2#000001_11111# =>  INSN_prefix,
        -- Major opcodes 57 and 61 are SFFS load/store instructions when prefixed
        2#111001_00000# to 2#111001_11111# =>  INSN_op57,
        2#111101_00000# to 2#111101_11111# =>  INSN_op61,
        others                             =>  INSN_illegal
        );

    constant row_predecode_rom : predecoder_rom_t := (
        -- Major opcode 31
        -- Address bits are 0, insn(10:1)
        2#0_01000_01010#  =>  INSN_add,
        2#0_11000_01010#  =>  INSN_add, -- addo
        2#0_00000_01010#  =>  INSN_addc,
        2#0_10000_01010#  =>  INSN_addc, -- addco
        2#0_00100_01010#  =>  INSN_adde,
        2#0_10100_01010#  =>  INSN_adde, -- addeo
        2#0_00101_01010#  =>  INSN_addex,
        2#0_00010_01010#  =>  INSN_addg6s,
        2#0_00111_01010#  =>  INSN_addme,
        2#0_10111_01010#  =>  INSN_addme, -- addmeo
        2#0_00110_01010#  =>  INSN_addze,
        2#0_10110_01010#  =>  INSN_addze, -- addzeo
        2#0_00000_11100#  =>  INSN_and,
        2#0_00001_11100#  =>  INSN_andc,
        2#0_00111_11100#  =>  INSN_bperm,
        2#0_00110_11011#  =>  INSN_brh,
        2#0_00100_11011#  =>  INSN_brw,
        2#0_00101_11011#  =>  INSN_brd,
        2#0_01001_11010#  =>  INSN_cbcdtd,
        2#0_01000_11010#  =>  INSN_cdtbcd,
        2#0_00000_00000#  =>  INSN_cmp,
        2#0_01111_11100#  =>  INSN_cmpb,
        2#0_00111_00000#  =>  INSN_cmpeqb,
        2#0_00001_00000#  =>  INSN_cmpl,
        2#0_00110_00000#  =>  INSN_cmprb,
        2#0_00001_11010#  =>  INSN_cntlzd,
        2#0_00000_11010#  =>  INSN_cntlzw,
        2#0_10001_11010#  =>  INSN_cnttzd,
        2#0_10000_11010#  =>  INSN_cnttzw,
        2#0_10111_10011#  =>  INSN_darn,
        2#0_00010_10110#  =>  INSN_dcbf,
        2#0_00001_10110#  =>  INSN_dcbst,
        2#0_01000_10110#  =>  INSN_dcbt,
        2#0_00111_10110#  =>  INSN_dcbtst,
        2#0_11111_10110#  =>  INSN_dcbz,
        2#0_01100_01001#  =>  INSN_divdeu,
        2#0_11100_01001#  =>  INSN_divdeu, -- divdeuo
        2#0_01100_01011#  =>  INSN_divweu,
        2#0_11100_01011#  =>  INSN_divweu, -- divweuo
        2#0_01101_01001#  =>  INSN_divde,
        2#0_11101_01001#  =>  INSN_divde, -- divdeo
        2#0_01101_01011#  =>  INSN_divwe,
        2#0_11101_01011#  =>  INSN_divwe, -- divweo
        2#0_01110_01001#  =>  INSN_divdu,
        2#0_11110_01001#  =>  INSN_divdu, -- divduo
        2#0_01110_01011#  =>  INSN_divwu,
        2#0_11110_01011#  =>  INSN_divwu, -- divwuo
        2#0_01111_01001#  =>  INSN_divd,
        2#0_11111_01001#  =>  INSN_divd, -- divdo
        2#0_01111_01011#  =>  INSN_divw,
        2#0_11111_01011#  =>  INSN_divw, -- divwo
        2#0_11001_10110#  =>  INSN_rnop, -- dss
        2#0_01010_10110#  =>  INSN_rnop, -- dst
        2#0_01011_10110#  =>  INSN_rnop, -- dstst
        2#0_11010_10110#  =>  INSN_eieio,
        2#0_01000_11100#  =>  INSN_eqv,
        2#0_11101_11010#  =>  INSN_extsb,
        2#0_11100_11010#  =>  INSN_extsh,
        2#0_11110_11010#  =>  INSN_extsw,
        2#0_11011_11010#  =>  INSN_extswsli,
        2#0_11011_11011#  =>  INSN_extswsli,
        2#0_11110_10110#  =>  INSN_icbi,
        2#0_00000_10110#  =>  INSN_icbt,
        2#0_00000_01111#  =>  INSN_isel,
        2#0_00001_01111#  =>  INSN_isel,
        2#0_00010_01111#  =>  INSN_isel,
        2#0_00011_01111#  =>  INSN_isel,
        2#0_00100_01111#  =>  INSN_isel,
        2#0_00101_01111#  =>  INSN_isel,
        2#0_00110_01111#  =>  INSN_isel,
        2#0_00111_01111#  =>  INSN_isel,
        2#0_01000_01111#  =>  INSN_isel,
        2#0_01001_01111#  =>  INSN_isel,
        2#0_01010_01111#  =>  INSN_isel,
        2#0_01011_01111#  =>  INSN_isel,
        2#0_01100_01111#  =>  INSN_isel,
        2#0_01101_01111#  =>  INSN_isel,
        2#0_01110_01111#  =>  INSN_isel,
        2#0_01111_01111#  =>  INSN_isel,
        2#0_10000_01111#  =>  INSN_isel,
        2#0_10001_01111#  =>  INSN_isel,
        2#0_10010_01111#  =>  INSN_isel,
        2#0_10011_01111#  =>  INSN_isel,
        2#0_10100_01111#  =>  INSN_isel,
        2#0_10101_01111#  =>  INSN_isel,
        2#0_10110_01111#  =>  INSN_isel,
        2#0_10111_01111#  =>  INSN_isel,
        2#0_11000_01111#  =>  INSN_isel,
        2#0_11001_01111#  =>  INSN_isel,
        2#0_11010_01111#  =>  INSN_isel,
        2#0_11011_01111#  =>  INSN_isel,
        2#0_11100_01111#  =>  INSN_isel,
        2#0_11101_01111#  =>  INSN_isel,
        2#0_11110_01111#  =>  INSN_isel,
        2#0_11111_01111#  =>  INSN_isel,
        2#0_00001_10100#  =>  INSN_lbarx,
        2#0_11010_10101#  =>  INSN_lbzcix,
        2#0_00011_10111#  =>  INSN_lbzux,
        2#0_00010_10111#  =>  INSN_lbzx,
        2#0_00010_10100#  =>  INSN_ldarx,
        2#0_10000_10100#  =>  INSN_ldbrx,
        2#0_11011_10101#  =>  INSN_ldcix,
        2#0_00001_10101#  =>  INSN_ldux,
        2#0_00000_10101#  =>  INSN_ldx,
        2#0_10010_10111#  =>  INSN_lfdx,
        2#0_10011_10111#  =>  INSN_lfdux,
        2#0_11010_10111#  =>  INSN_lfiwax,
        2#0_11011_10111#  =>  INSN_lfiwzx,
        2#0_10000_10111#  =>  INSN_lfsx,
        2#0_10001_10111#  =>  INSN_lfsux,
        2#0_00011_10100#  =>  INSN_lharx,
        2#0_01011_10111#  =>  INSN_lhaux,
        2#0_01010_10111#  =>  INSN_lhax,
        2#0_11000_10110#  =>  INSN_lhbrx,
        2#0_11001_10101#  =>  INSN_lhzcix,
        2#0_01001_10111#  =>  INSN_lhzux,
        2#0_01000_10111#  =>  INSN_lhzx,
        2#0_00000_10100#  =>  INSN_lwarx,
        2#0_01011_10101#  =>  INSN_lwaux,
        2#0_01010_10101#  =>  INSN_lwax,
        2#0_10000_10110#  =>  INSN_lwbrx,
        2#0_11000_10101#  =>  INSN_lwzcix,
        2#0_00001_10111#  =>  INSN_lwzux,
        2#0_00000_10111#  =>  INSN_lwzx,
        2#0_10010_00000#  =>  INSN_mcrxrx,
        2#0_00000_10011#  =>  INSN_mfcr,
        2#0_00010_10011#  =>  INSN_mfmsr,
        2#0_01010_10011#  =>  INSN_mfspr,
        2#0_01000_01001#  =>  INSN_modud,
        2#0_01000_01011#  =>  INSN_moduw,
        2#0_11000_01001#  =>  INSN_modsd,
        2#0_11000_01011#  =>  INSN_modsw,
        2#0_00100_10000#  =>  INSN_mtcrf,
        2#0_00100_10010#  =>  INSN_mtmsr,
        2#0_00101_10010#  =>  INSN_mtmsrd,
        2#0_01110_10011#  =>  INSN_mtspr,
        2#0_00010_01001#  =>  INSN_mulhd,
        2#0_00000_01001#  =>  INSN_mulhdu,
        2#0_00010_01011#  =>  INSN_mulhw,
        2#0_00000_01011#  =>  INSN_mulhwu,
        -- next 4 have reserved bit set
        2#0_10010_01001#  =>  INSN_mulhd,
        2#0_10000_01001#  =>  INSN_mulhdu,
        2#0_10010_01011#  =>  INSN_mulhw,
        2#0_10000_01011#  =>  INSN_mulhwu,
        2#0_00111_01001#  =>  INSN_mulld,
        2#0_10111_01001#  =>  INSN_mulld, -- mulldo
        2#0_00111_01011#  =>  INSN_mullw,
        2#0_10111_01011#  =>  INSN_mullw, -- mullwo
        2#0_01110_11100#  =>  INSN_nand,
        2#0_00011_01000#  =>  INSN_neg,
        2#0_10011_01000#  =>  INSN_neg, -- nego
        -- next 8 are reserved no-op instructions
        2#0_10000_10010#  =>  INSN_rnop,
        2#0_10001_10010#  =>  INSN_rnop,
        2#0_10010_10010#  =>  INSN_rnop,
        2#0_10011_10010#  =>  INSN_rnop,
        2#0_10100_10010#  =>  INSN_rnop,
        2#0_10101_10010#  =>  INSN_rnop,
        2#0_10110_10010#  =>  INSN_rnop,
        2#0_10111_10010#  =>  INSN_rnop,
        2#0_00011_11100#  =>  INSN_nor,
        2#0_01101_11100#  =>  INSN_or,
        2#0_01100_11100#  =>  INSN_orc,
        2#0_00011_11010#  =>  INSN_popcntb,
        2#0_01111_11010#  =>  INSN_popcntd,
        2#0_01011_11010#  =>  INSN_popcntw,
        2#0_00101_11010#  =>  INSN_prtyd,
        2#0_00100_11010#  =>  INSN_prtyw,
        2#0_00100_00000#  =>  INSN_setb,
        2#0_01100_00000#  =>  INSN_setb, -- setbc
        2#0_01101_00000#  =>  INSN_setb, -- setbcr
        2#0_01110_00000#  =>  INSN_setb, -- setnbc
        2#0_01111_00000#  =>  INSN_setb, -- setnbcr
        2#0_01111_10010#  =>  INSN_slbia,
        2#0_00000_11011#  =>  INSN_sld,
        2#0_00000_11000#  =>  INSN_slw,
        2#0_11000_11010#  =>  INSN_srad,
        2#0_11001_11010#  =>  INSN_sradi,
        2#0_11001_11011#  =>  INSN_sradi,
        2#0_11000_11000#  =>  INSN_sraw,
        2#0_11001_11000#  =>  INSN_srawi,
        2#0_10000_11011#  =>  INSN_srd,
        2#0_10000_11000#  =>  INSN_srw,
        2#0_11110_10101#  =>  INSN_stbcix,
        2#0_10101_10110#  =>  INSN_stbcx,
        2#0_00111_10111#  =>  INSN_stbux,
        2#0_00110_10111#  =>  INSN_stbx,
        2#0_10100_10100#  =>  INSN_stdbrx,
        2#0_11111_10101#  =>  INSN_stdcix,
        2#0_00110_10110#  =>  INSN_stdcx,
        2#0_00101_10101#  =>  INSN_stdux,
        2#0_00100_10101#  =>  INSN_stdx,
        2#0_10110_10111#  =>  INSN_stfdx,
        2#0_10111_10111#  =>  INSN_stfdux,
        2#0_11110_10111#  =>  INSN_stfiwx,
        2#0_10100_10111#  =>  INSN_stfsx,
        2#0_10101_10111#  =>  INSN_stfsux,
        2#0_11100_10110#  =>  INSN_sthbrx,
        2#0_11101_10101#  =>  INSN_sthcix,
        2#0_10110_10110#  =>  INSN_sthcx,
        2#0_01101_10111#  =>  INSN_sthux,
        2#0_01100_10111#  =>  INSN_sthx,
        2#0_10100_10110#  =>  INSN_stwbrx,
        2#0_11100_10101#  =>  INSN_stwcix,
        2#0_00100_10110#  =>  INSN_stwcx,
        2#0_00101_10111#  =>  INSN_stwux,
        2#0_00100_10111#  =>  INSN_stwx,
        2#0_00001_01000#  =>  INSN_subf,
        2#0_10001_01000#  =>  INSN_subf, -- subfo
        2#0_00000_01000#  =>  INSN_subfc,
        2#0_10000_01000#  =>  INSN_subfc, -- subfco
        2#0_00100_01000#  =>  INSN_subfe,
        2#0_10100_01000#  =>  INSN_subfe, -- subfeo
        2#0_00111_01000#  =>  INSN_subfme,
        2#0_10111_01000#  =>  INSN_subfme, -- subfmeo
        2#0_00110_01000#  =>  INSN_subfze,
        2#0_10110_01000#  =>  INSN_subfze, -- subfzeo
        2#0_10010_10110#  =>  INSN_sync,
        2#0_00010_00100#  =>  INSN_td,
        2#0_00000_00100#  =>  INSN_tw,
        2#0_01001_10010#  =>  INSN_tlbie,
        2#0_01000_10010#  =>  INSN_tlbiel,
        2#0_10001_10110#  =>  INSN_tlbsync,
        2#0_00000_11110#  =>  INSN_wait,
        2#0_01001_11100#  =>  INSN_xor,

        -- Major opcode 19
        -- Columns with insn(4) = '1' are all illegal and not mapped here; to
        -- fit into 2048 entries, the columns are remapped so that 16-24 are
        -- stored here as 8-15; in other words the address bits are
        -- 1, insn(10..6), 1, insn(5), insn(3..1)
        -- Columns 16-17 here are opcode 19 columns 0-1
        -- Columns 24-31 here are opcode 19 columns 16-23
        2#1_10000_11000#  =>  INSN_bcctr,
        2#1_00000_11000#  =>  INSN_bclr,
        2#1_10001_11000#  =>  INSN_bctar,
        2#1_01000_10001#  =>  INSN_crand,
        2#1_00100_10001#  =>  INSN_crandc,
        2#1_01001_10001#  =>  INSN_creqv,
        2#1_00111_10001#  =>  INSN_crnand,
        2#1_00001_10001#  =>  INSN_crnor,
        2#1_01110_10001#  =>  INSN_cror,
        2#1_01101_10001#  =>  INSN_crorc,
        2#1_00110_10001#  =>  INSN_crxor,
        2#1_00100_11110#  =>  INSN_isync,
        2#1_00000_10000#  =>  INSN_mcrf,
        2#1_00000_11010#  =>  INSN_rfid,
        2#1_01000_11010#  =>  INSN_rfid, -- hrfid

        -- Major opcode 59
        -- Address bits are 1, insn(10..6), 1, 0, insn(3..1)
        -- Only column 14 is valid here; columns 16-31 are handled in the major table
        -- Column 14 is mapped to column 22.
        -- Columns 20-23 here are opcode 59 columns 12-15
        2#1_11010_10110#  =>  INSN_fcfids,
        2#1_11110_10110#  =>  INSN_fcfidus,

        -- Major opcode 63
        -- Columns 0-15 are mapped here; columns 16-31 are in the major table.
        -- Address bits are 1, insn(10:6), 0, insn(4:1)
        -- Columns 0-15 here are opcode 63 columns 0-15
        2#1_00000_00000#  =>  INSN_fcmpu,
        2#1_00001_00000#  =>  INSN_fcmpo,
        2#1_00010_00000#  =>  INSN_mcrfs,
        2#1_00100_00000#  =>  INSN_ftdiv,
        2#1_00101_00000#  =>  INSN_ftsqrt,
        2#1_00001_00110#  =>  INSN_mtfsb,
        2#1_00010_00110#  =>  INSN_mtfsb,
        2#1_00100_00110#  =>  INSN_mtfsfi,
        2#1_11010_00110#  =>  INSN_fmrgow,
        2#1_11110_00110#  =>  INSN_fmrgew,
        2#1_10010_00111#  =>  INSN_mffs,
        2#1_10110_00111#  =>  INSN_mtfsf,
        2#1_00000_01000#  =>  INSN_fcpsgn,
        2#1_00001_01000#  =>  INSN_fneg,
        2#1_00010_01000#  =>  INSN_fmr,
        2#1_00100_01000#  =>  INSN_fnabs,
        2#1_01000_01000#  =>  INSN_fabs,
        2#1_01100_01000#  =>  INSN_frin,
        2#1_01101_01000#  =>  INSN_friz,
        2#1_01110_01000#  =>  INSN_frip,
        2#1_01111_01000#  =>  INSN_frim,
        2#1_00000_01100#  =>  INSN_frsp,
        2#1_00000_01110#  =>  INSN_fctiw,
        2#1_00100_01110#  =>  INSN_fctiwu,
        2#1_11001_01110#  =>  INSN_fctid,
        2#1_11010_01110#  =>  INSN_fcfid,
        2#1_11101_01110#  =>  INSN_fctidu,
        2#1_11110_01110#  =>  INSN_fcfidu,
        2#1_00000_01111#  =>  INSN_fctiwz,
        2#1_00100_01111#  =>  INSN_fctiwuz,
        2#1_11001_01111#  =>  INSN_fctidz,
        2#1_11101_01111#  =>  INSN_fctiduz,

        others            =>  INSN_illegal
        );

    constant IOUT_LEN : natural := ICODE_LEN + IMAGE_LEN;

    type predec_t is record
        image         : std_ulogic_vector(31 downto 0);
        maj_predecode : unsigned(ICODE_LEN - 1 downto 0);
        row_predecode : unsigned(ICODE_LEN - 1 downto 0);
    end record;

    subtype index_t is integer range 0 to WIDTH-1;
    type predec_array is array(index_t) of predec_t;

    signal pred : predec_array;
    signal valid : std_ulogic;

begin
    predecode_0: process(clk)
        variable majaddr  : std_ulogic_vector(10 downto 0);
        variable rowaddr  : std_ulogic_vector(10 downto 0);
        variable iword    : std_ulogic_vector(31 downto 0);
        variable majcode  : insn_code;
        variable rowcode  : insn_code;
    begin
        if rising_edge(clk) then
            valid <= valid_in;
            for i in index_t loop
                iword := insns_in(i * 32 + 31 downto i * 32);
                pred(i).image <= iword;

                if is_X(iword) then
                    pred(i).maj_predecode <= (others => 'X');
                    pred(i).row_predecode <= (others => 'X');
                else
                    majaddr := iword(31 downto 26) & iword(4 downto 0);

                    -- row_predecode_rom is used for op 19, 31, 59, 63
                    -- addr bit 10 is 0 for op 31, 1 for 19, 59, 63
                    rowaddr(10) := iword(31) or not iword(29);
                    rowaddr(9 downto 5) := iword(10 downto 6);
                    if iword(28) = '0' then
                        -- op 19 and op 59
                        rowaddr(4 downto 3) := '1' & iword(5);
                    else
                        -- op 31 and 63; for 63 we only use this when iword(5) = '0'
                        rowaddr(4 downto 3) := iword(5 downto 4);
                    end if;
                    rowaddr(2 downto 0) := iword(3 downto 1);

                    majcode := major_predecode_rom(to_integer(unsigned(majaddr)));
                    pred(i).maj_predecode <= to_unsigned(insn_code'pos(majcode), ICODE_LEN);
                    rowcode := row_predecode_rom(to_integer(unsigned(rowaddr)));
                    pred(i).row_predecode <= to_unsigned(insn_code'pos(rowcode), ICODE_LEN);
                end if;
            end loop;
        end if;
    end process;

    predecode_1: process(all)
        variable iword    : std_ulogic_vector(31 downto 0);
        variable use_row  : std_ulogic;
        variable illegal  : std_ulogic;
        variable ici      : std_ulogic_vector(IOUT_LEN - 1 downto 0);
        variable icode    : unsigned(ICODE_LEN - 1 downto 0);
    begin
        for i in index_t loop
            iword := pred(i).image;
            icode := pred(i).maj_predecode;
            use_row := '0';
            illegal := '0';

            case iword(31 downto 26) is
                when "000100" => -- 4
                    -- major opcode 4, mostly VMX/VSX stuff but also some integer ops (madd*)
                    illegal := not iword(5);

                when "010011" => -- 19
                    -- Columns 8-15 and 24-31 don't have any valid instructions
                    -- (where insn(5..1) is the column number).
                    -- addpcis (column 2) is in the major table
                    -- Other valid columns are mapped to columns in the second
                    -- half of the row table: columns 0-1 are mapped to 16-17
                    -- and 16-23 are mapped to 24-31.
                    illegal := iword(4);
                    use_row := iword(5) or (not iword(3) and not iword(2));

                when "011000" => -- 24
                    -- ori, special-case the standard NOP
                    if std_match(iword, "01100000000000000000000000000000") then
                        icode := to_unsigned(insn_code'pos(INSN_nop), ICODE_LEN);
                    end if;

                when "011111" => -- 31
                    -- major opcode 31, lots of things
                    -- Use the first half of the row table for all columns
                    use_row := '1';

                when "111011" => -- 59
                    -- floating point operations, mostly single-precision
                    -- Columns 0-11 are illegal; columns 12-15 are mapped
                    -- to columns 20-23 in the second half of the row table,
                    -- and columns 16-31 are in the major table.
                    illegal := not iword(5) and (not iword(4) or not iword(3));
                    use_row := not iword(5);

                when "111111" => -- 63
                    -- floating point operations, general and double-precision
                    -- Use columns 0-15 of the second half of the row table
                    -- for columns 0-15, and the major table for columns 16-31.
                    use_row := not iword(5);

                when others =>
            end case;
            if use_row = '1' then
                icode := pred(i).row_predecode;
            end if;

            -- Mark FP instructions as illegal if we don't have an FPU
            if not HAS_FPU and not is_X(icode) and
                to_integer(icode) >= insn_code'pos(INSN_first_frs) then
                illegal := '1';
            end if;

            ici(31 downto 0) := iword;
            ici(IOUT_LEN - 1 downto 32) := (others => '0');
            if valid = '0' or illegal = '1' or is_X(icode) or
                icode = to_unsigned(insn_code'pos(INSN_illegal), ICODE_LEN) then
                -- Since an insn_code currently fits in 9 bits, use just
                -- the most significant bit of ici to indicate illegal insns.
                ici(IOUT_LEN - 1) := '1';
            else
                ici(IOUT_LEN - 1 downto IMAGE_LEN) := std_ulogic_vector(icode);
            end if;
            icodes_out(i * IOUT_LEN + IOUT_LEN - 1 downto i * IOUT_LEN) <= ici;
        end loop;
    end process;

end architecture behaviour;