Как стать автором
Обновить

Разработка цифровой аппаратуры нетрадиционным методом: CGA видеоадаптер на SpinalHDL

Уровень сложностиСложный
Время на прочтение127 мин
Количество просмотров4.4K
Всего голосов 43: ↑42 и ↓1+61
Комментарии26

Комментарии 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

Спасибо, попробую. Да, хакер который писал TMDS энкодер на Verilog-е хорошо постарался обфускировать минимизировать свой код - ничего лишнего. :)

C'est magnifique! Пожалуйста, продолжайте. Сейчас нет времени вчитываться, положил в закладки.

Есть ли в ECP5 выходы CML и/или LVDS? Весьма вероятно, они лучше подошли бы для TMDS сигналов.

LVDS выходы в Lattice ECP5 разумеется есть, но мне хотелось показать (попробовать и проверить), что даже это является избыточным. Иными словами, кроме PLL вообще ничего не нужно от ПЛИС чтобы выводить VGA картинку на HDMI. А на достаточно быстром ЦПУ кодирование TMDS возможно сделать врукопашную (программно). :)

Мой принцип: максимум имеющейся аппарапуры.

Это всеобщий принцип навязываемый нам вендорами. В последние годы меня всё больше интересуют решения построенные по принципу достаточного минимализма. :)

Я имел в виду использование имеющейся аппаратуры по максимуму.

Ну, выжать максимум из имеющегося и потом еще немножко - это святое. :)

Отличная работа! Я хотел сделать такую же штуку для своей vga карточки, но руки так не дошли. Только начал рыть в этом направлении.

Много думал над тем чтобы существенно упростить кодер 8б/10б. Если цветов ограниченное количество, то можно подобрать такие величины r, g, b, для которых 10-битный код всегда будет один и тот же. Даже для этого можно пойти на небольшое, невидимое глазу искажение цветов. Тогда кодирование будет осуществляться простой подстановкой заранее подобранного кода из таблицы.

Там есть нюанс - нужно сохранять балланс нулей и единиц, то есть просто табличным методом не поработать. Но наверное можно выбрать какое-то ограниченное подмножество "символов" и работать только ими, добавляя коррекцию балланса.

Респект за посхалку

Делаю заключени, что Вы один из тех немногих кто смог прочитать статью. :)

Прочёл целиком - пасхалки не заметил)

Придется прочитать заново xD

Большое спасибо за такую полную и подробную статью. Редкий пример того как можно подать информацию, не разбавляя её водой ради объёма.

Острожнее с 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. :-)

Полный уход от проприетарщины - достойнейшее дело!

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации