Комментарии 26
Ого, какая статья здоровая!
Интересно про TMDS. На spinal можно практически строку в строку переложить с этой реализации.
Хотя конечно было бы интересно понять логику. Here is AI to the rescue:
module TMDS_encoder(
input clk, // Clock input
input [7:0] VD, // Video data (8-bit RGB channel)
input [1:0] CD, // Control data (2 control bits)
input VDE, // Video Data Enable (1=video, 0=control)
output reg [9:0] TMDS = 0 // 10-bit TMDS encoded output
);
// Stage 1: Count number of 1s in the input video data
wire [3:0] Nb1s = VD[0] + VD[1] + VD[2] + VD[3] + VD[4] + VD[5] + VD[6] + VD[7];
// Stage 2: Determine if we should XNOR or XOR based on number of 1s
// XNOR if more than 4 1s, or exactly 4 1s with LSB=0
wire XNOR = (Nb1s > 4'd4) || (Nb1s == 4'd4 && VD[0] == 1'b0);
// Stage 3: Generate intermediate 9-bit encoding (q_m)
// MSB indicates XNOR/XOR choice, followed by encoded data
wire [8:0] q_m = {
~XNOR, // Decision bit
q_m[6:0] ^ VD[7:1] ^ {7{XNOR}}, // Encode bits 7:1
VD[0] // Keep LSB unchanged
};
// Stage 4: DC Balance tracking
reg [3:0] balance_acc = 0; // Running disparity
// Calculate current word balance (number of 1s - number of 0s - 4)
wire [3:0] balance = q_m[0] + q_m[1] + q_m[2] + q_m[3] +
q_m[4] + q_m[5] + q_m[6] + q_m[7] - 4'd4;
// Check if current balance has same sign as accumulator
wire balance_sign_eq = (balance[3] == balance_acc[3]);
// Determine if we should invert q_m
wire invert_q_m = (balance == 0 || balance_acc == 0) ? ~q_m[8] : balance_sign_eq;
// Calculate new balance accumulator value
wire [3:0] balance_acc_inc = balance - ({q_m[8] ^ ~balance_sign_eq} &
~(balance == 0 || balance_acc == 0));
wire [3:0] balance_acc_new = invert_q_m ? balance_acc - balance_acc_inc :
balance_acc + balance_acc_inc;
// Stage 5: Generate final TMDS encoded value
wire [9:0] TMDS_data = {invert_q_m, q_m[8], q_m[7:0] ^ {8{invert_q_m}}};
// Control codes for periods when VDE is low
wire [9:0] TMDS_code = CD[1] ? (CD[0] ? 10'b1010101011 : 10'b0101010100)
: (CD[0] ? 10'b0010101011 : 10'b1101010100);
// Register updates on clock edge
always @(posedge clk) begin
TMDS <= VDE ? TMDS_data : TMDS_code; // Select video or control data
balance_acc <= VDE ? balance_acc_new : 4'h0; // Update balance accumulator
end
endmodule
Грандиозно!
C'est magnifique! Пожалуйста, продолжайте. Сейчас нет времени вчитываться, положил в закладки.
Есть ли в ECP5 выходы CML и/или LVDS? Весьма вероятно, они лучше подошли бы для TMDS сигналов.
LVDS выходы в Lattice ECP5 разумеется есть, но мне хотелось показать (попробовать и проверить), что даже это является избыточным. Иными словами, кроме PLL вообще ничего не нужно от ПЛИС чтобы выводить VGA картинку на HDMI. А на достаточно быстром ЦПУ кодирование TMDS возможно сделать врукопашную (программно). :)
Мой принцип: максимум имеющейся аппарапуры.
Отличная работа! Я хотел сделать такую же штуку для своей vga карточки, но руки так не дошли. Только начал рыть в этом направлении.
Много думал над тем чтобы существенно упростить кодер 8б/10б. Если цветов ограниченное количество, то можно подобрать такие величины r, g, b, для которых 10-битный код всегда будет один и тот же. Даже для этого можно пойти на небольшое, невидимое глазу искажение цветов. Тогда кодирование будет осуществляться простой подстановкой заранее подобранного кода из таблицы.
Респект за посхалку
Большое спасибо за такую полную и подробную статью. Редкий пример того как можно подать информацию, не разбавляя её водой ради объёма.
Острожнее с BufferCC для мультибитных даннх: The BufferCC
function is only for signals of type Bit
, or Bits
operating as Gray-coded counters (only 1 bit-flip per clock cycle), and can not used for multi-bit cross-domain processes. For multi-bit cases, it is recommended to use StreamFifoCC
for high bandwidth requirements, or use StreamCCByToggle
to reduce resource usage in cases where bandwidth is not critical.
Замечание абсолютно верное! Действительно для передачи многобитовых сигналов между разными тактовыми доменами необходимо использовать специальные механизмы, например FIFO с кодом Грея. Да, я считерил и хорошо что Вы обратили на это внимание. :-)
Но в свою защиту скажу, что для данного случая такое решение слишком тяжеловесное (FIFO быстро сьедает ресурсы ПЛИС) и является избыточным. В данном дизайне BufferCC "разграничивает" доступ к конфигурационным регистрам и сигналам синхронизации изменения в которых является достаточно редким событием. Правильное значение на выходе BufferCC установится через несколько тактов и этого вполне достаточно для формирования видеоизображения. Да, частые записи в регистры будут приводить к появлению "артефактов" на экране, но от них легко избавиться если писать в регистры в момент обратного хода луча, т.е. когда вывод на экран не осуществляется.
В оригинальном CGA вообще не было никаких методов синхронизации и наличние "снега" на экраны было нормой. :-)
Дело не в "частоте записи" а интервалах прохождения каждого бита до защелки в другом clock domain. Но если гарантируется установка за много тактов до чтения то тогда не важно..
Если fifo дорого - иногда dual-ported ram можно использовать.
Есть еще интересный проект https://github.com/yeokm1/graphics-gremlin-hdmi, но там используется отдельный DVI трансмиттер, поэтому менее круто) Но можно подсмотреть как реализуется CGA/MDA.
Есть гораздо более интересный проект https://github.com/hdl-util/hdmi.git , это полная реализация HDMI для ПЛИС включая звук. Но в нем используются некоторые хард блоки ПЛИС-ов Atera и Xilinx. Я всё собираюсь с силами для попытки отвязять его от хард блоков и портировать на Lattice ECP5. :-)
Это шедевр. Я тоже интересуюсь этой темой сейчас и я тоже был на том фестивале)) Пожалуйста, продолжайте творить!
Разработка цифровой аппаратуры нетрадиционным методом: CGA видеоадаптер на SpinalHDL