module tmds_encoder_dvi
(
    input  wire         i_clk,      // clock
    input  wire         i_rst,      // reset (active high)
    input  wire [7:0]   i_data,     // colour data
    input  wire [1:0]   i_ctrl,     // control data
    input  wire         i_de,       // display enable (active high)
    output reg  [9:0]   o_tmds      // encoded TMDS data
);

    // select basic encoding based on the ones in the input data
    wire [3:0] d_ones = {3'b0,i_data[0]} + {3'b0,i_data[1]} + {3'b0,i_data[2]}
                      + {3'b0,i_data[3]} + {3'b0,i_data[4]} + {3'b0,i_data[5]}
                      + {3'b0,i_data[6]} + {3'b0,i_data[7]};
    
    wire use_xnor = (d_ones > 4'd4) || ((d_ones == 4'd4) && (i_data[0] == 0));

    // encode colour data with xor/xnor
    /* verilator lint_off UNOPTFLAT */
    wire [8:0] enc_qm;
    assign enc_qm[0] = i_data[0];
    assign enc_qm[1] = (use_xnor) ? (enc_qm[0] ~^ i_data[1]) : (enc_qm[0] ^ i_data[1]);
    assign enc_qm[2] = (use_xnor) ? (enc_qm[1] ~^ i_data[2]) : (enc_qm[1] ^ i_data[2]);
    assign enc_qm[3] = (use_xnor) ? (enc_qm[2] ~^ i_data[3]) : (enc_qm[2] ^ i_data[3]);
    assign enc_qm[4] = (use_xnor) ? (enc_qm[3] ~^ i_data[4]) : (enc_qm[3] ^ i_data[4]);
    assign enc_qm[5] = (use_xnor) ? (enc_qm[4] ~^ i_data[5]) : (enc_qm[4] ^ i_data[5]);
    assign enc_qm[6] = (use_xnor) ? (enc_qm[5] ~^ i_data[6]) : (enc_qm[5] ^ i_data[6]);
    assign enc_qm[7] = (use_xnor) ? (enc_qm[6] ~^ i_data[7]) : (enc_qm[6] ^ i_data[7]);
    assign enc_qm[8] = (use_xnor) ? 0 : 1;
    /* verilator lint_on UNOPTFLAT */

    // disparity in encoded data for DC balancing: needs to cover -8 to +8
    wire signed [4:0] ones = {4'b0,enc_qm[0]} + {4'b0,enc_qm[1]}
                           + {4'b0,enc_qm[2]} + {4'b0,enc_qm[3]} + {4'b0,enc_qm[4]}
                           + {4'b0,enc_qm[5]} + {4'b0,enc_qm[6]} + {4'b0,enc_qm[7]};

    wire signed [4:0] zeros = 5'b01000 - ones;
    wire signed [4:0] balance = ones - zeros;

    // record ongoing DC bias
    reg signed [4:0] bias;

    always @(posedge i_clk) begin
        if (i_rst) begin
            o_tmds <= 10'b1101010100;  // equivalent to ctrl 2'b00
            bias <= 5'sb00000;
        end else begin
            if (i_de == 0) begin   // send control data in blanking interval
                case (i_ctrl)  // ctrl sequences (always have 7 transitions)
                    2'b00:   o_tmds <= 10'b1101010100;
                    2'b01:   o_tmds <= 10'b0010101011;
                    2'b10:   o_tmds <= 10'b0101010100;
                    default: o_tmds <= 10'b1010101011;
                endcase
                
                bias <= 5'sb00000;
            end else begin  // send pixel colour data (at most 5 transitions)
                if (bias == 0 || balance == 0) begin    // no prior bias or disparity
                    if (enc_qm[8] == 0) begin
                        // $display("\t%d %b %d, %d, A1", i_data, enc_qm, ones, bias);
                        o_tmds[9:0] <= {2'b10, ~enc_qm[7:0]};
                        bias <= bias - balance;
                    end else begin
                        // $display("\t%d %b %d, %d, A0", i_data, enc_qm, ones, bias);
                        o_tmds[9:0] <= {2'b01, enc_qm[7:0]};
                        bias <= bias + balance;
                    end
                end else begin
                    if ((bias > 0 && balance > 0) || (bias < 0 && balance < 0)) begin
                        // $display("\t%d %b %d, %d, B1", i_data, enc_qm, ones, bias);
                        o_tmds[9:0] <= {1'b1, enc_qm[8], ~enc_qm[7:0]};
                        bias <= bias + {3'b0, enc_qm[8], 1'b0} - balance;
                    end else begin
                        // $display("\t%d %b %d, %d, B0", i_data, enc_qm, ones, bias);
                        o_tmds[9:0] <= {1'b0, enc_qm[8], enc_qm[7:0]};
                        bias <= bias - {3'b0, ~enc_qm[8], 1'b0} + balance;
                    end
                end
            end
        end
    end
    
endmodule