Last active
August 21, 2024 14:59
-
-
Save alsrgv/3cf171c17fffe25806693c26ebb276a8 to your computer and use it in GitHub Desktop.
TMDS encoder/decoder in SystemVerilog
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Wonder why
ones_d_in
andones_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]