Skip to content

Instantly share code, notes, and snippets.

@alsrgv
Last active August 21, 2024 14:59
Show Gist options
  • Save alsrgv/3cf171c17fffe25806693c26ebb276a8 to your computer and use it in GitHub Desktop.
Save alsrgv/3cf171c17fffe25806693c26ebb276a8 to your computer and use it in GitHub Desktop.
TMDS encoder/decoder in SystemVerilog
package tmds_pkg;
typedef struct packed {
logic inv_q_m;
logic use_xor;
logic [7:0] q_m;
} tmds_encoded_t;
typedef enum logic [9:0]{
CTRL_00 = 10'b1101010100,
CTRL_01 = 10'b0010101011,
CTRL_10 = 10'b0101010100,
CTRL_11 = 10'b1010101011
} control_t;
endpackage
module tmds_encoder
import tmds_pkg::*;
(
input logic clk,
input logic reset,
input logic disp_ena,
input logic [1:0] control,
input logic [7:0] d_in,
output tmds_encoded_t d_out
);
// This is a register used to keep track of the data stream disparity.
// A positive value represents the excess number of 1s that have been
// transmitted. A negative value represents the excess number of 0s
// that have been transmitted.
logic signed[5:0] disparity;
logic [3:0] ones_d_in;
logic use_xor;
logic [7:0] q_m;
logic [3:0] ones_q_m;
logic signed[4:0] diff_q_m;
logic inv_q_m;
assign ones_d_in = count_ones(d_in);
assign use_xor = ones_d_in < 4 || ones_d_in == 4 && d_in[0];
assign q_m = use_xor ? rolling_xor(d_in):rolling_xnor(d_in);
assign ones_q_m = count_ones(q_m);
assign diff_q_m = signed'(5'(ones_q_m << 1)) - 5'sd8;
always_comb begin
if (disparity == 0 && ones_q_m == 4) begin
// inv negates xor to preserve the balance between ones and zeroes.
inv_q_m = ~use_xor;
end else begin
inv_q_m = disparity > 0 && ones_q_m > 4 || disparity < 0 && ones_q_m < 4;
end
end
always_ff @(posedge clk or posedge reset) begin
if (reset) begin
d_out <= 0;
disparity <= 0;
end else begin
if (disp_ena) begin
// Output.
d_out <= {inv_q_m, use_xor, inv_q_m ? ~q_m:q_m};
// Adjust disparity.
disparity <= disparity +
(inv_q_m ? -$bits(disparity)'(diff_q_m):$bits(disparity)'(diff_q_m)) +
(inv_q_m ? $bits(disparity)'('sd1):-$bits(disparity)'('sd1));
end else begin
// Output.
case (control)
2'b00: d_out <= CTRL_00;
2'b01: d_out <= CTRL_01;
2'b10: d_out <= CTRL_10;
2'b11: d_out <= CTRL_11;
endcase
// Adjust disparity.
disparity <= 0;
end
end
end
function automatic logic [3:0] count_ones(input logic [7:0] bits);
count_ones = 0;
for (int i = 0; i < 8; i++)
count_ones += $bits(count_ones)'(bits[i]);
endfunction
function automatic logic [7:0] rolling_xor(input logic [7:0] bits);
rolling_xor[0] = bits[0];
for (int i = 1; i < 8; i++)
rolling_xor[i] = rolling_xor[i - 1] ^ bits[i];
endfunction
function automatic logic [7:0] rolling_xnor(input logic [7:0] bits);
rolling_xnor[0] = bits[0];
for (int i = 1; i < 8; i++)
rolling_xnor[i] = rolling_xnor[i - 1] ^~ bits[i];
endfunction
endmodule
module tmds_decoder
import tmds_pkg::*;
(
input clk,
input reset,
input tmds_encoded_t d_in,
output logic disp_ena,
output logic [1:0] control,
output logic [7:0] d_out
);
// Decoded q_m.
logic [7:0] q_m;
assign q_m = d_in.inv_q_m ? ~d_in.q_m:d_in.q_m;
always_ff @(posedge clk or posedge reset) begin
if (reset) begin
disp_ena <= 0;
control <= 0;
d_out <= 0;
end else begin
if (d_in inside {CTRL_00, CTRL_01, CTRL_10, CTRL_11}) begin
disp_ena <= 0;
case (d_in)
CTRL_00: control <= 2'b00;
CTRL_01: control <= 2'b01;
CTRL_10: control <= 2'b10;
CTRL_11: control <= 2'b11;
endcase
d_out <= 0;
end else begin
disp_ena <= 1;
control <= 0;
d_out <= d_in.use_xor ? rolling_xor(q_m):rolling_xnor(q_m);
end
end
end
function automatic logic [7:0] rolling_xor(input logic [7:0] bits);
rolling_xor[0] = bits[0];
for (int i = 1; i < 8; i++)
rolling_xor[i] = bits[i - 1] ^ bits[i];
endfunction
function automatic logic [7:0] rolling_xnor(input logic [7:0] bits);
rolling_xnor[0] = bits[0];
for (int i = 1; i < 8; i++)
rolling_xnor[i] = bits[i - 1] ^~ bits[i];
endfunction
endmodule
module tmds_tb
import tmds_pkg::*;
;
logic clk = 1, reset = 1;
logic disp_ena;
logic [1:0] control;
logic [7:0] d_in;
logic [7:0] d_in_prev;
logic [7:0] d_in_recoded;
tmds_encoded_t d_out;
tmds_encoder enc (.*);
tmds_decoder dec (.clk,
.reset,
.d_in (d_out),
.control (),
.disp_ena (),
.d_out (d_in_recoded));
always begin
#5 clk = ~clk;
end
initial begin
@(negedge clk);
reset = 0; disp_ena = 1; control = 0; d_in = 0; d_in_prev = 0;
// Wait for information to flow through, d_out is initially
// registered to 0 which does not decode to 0.
#10;
repeat (10) begin
#10;
assert (d_in_prev == d_in_recoded);
d_in_prev = d_in;
d_in = $urandom_range(8'h0, 8'hff);
end ;
end
endmodule
@yodalee
Copy link

yodalee commented Sep 12, 2021

Wonder why ones_d_in and ones_q_m are [2:0]? Since they are going to storage number of one in [7:0] data, from 0 to 8. [2:0] will overflow with 8 ones in data. I think it should be [3:0]

@alsrgv
Copy link
Author

alsrgv commented Sep 12, 2021

@yodalee, good catch - I believe you're right. Updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment