Abstract由於不少視訊設備採用的是YCbCr信號(如DVD、DV),但VGA使用的RGB信號,若要將input結果經過影像處理後,output在VGA,就必須將YCbCr信號轉成RGB才可處理。
Introduction
小美與
阿帥是Lab中碩一的新生,最近正在研究DE2-70 CD中的
DE2_70_TV範例,由於DE2-70_TV的input是YCbCr信號,需要轉成RGB信號後才能輸出到VGA,範例中提供了
YCbCr2RGB.v提供轉換,小美與阿帥研究了很久,還是不太了解其中的意義,最後決定請教Lab中玩DE2-70很久的
無雙學長,想徹底了解其中的奧秘。
『我知道YCbCr轉RGB的公式如下:』
R = 1.164 (Y- 16 ) + 1.596 (Cr- 128 ) G = 1.164 (Y- 16 ) - 0.391 (Cb- 128 ) - 0.813 (Cr- 128 ) B = 1.164 (Y- 16 ) + 2.018 (Cb- 128 ) 『但整個YCbCr2RGB.v看起來與這個公式差很遠,到底Verilog是怎麼辦到的呢?』小美一臉無助地發問。
YCbCr2RGB.v / Verilog 1 module YCbCr2RGB ( 2 input iCLK, 3 input iRESET, 4 input iDVAL, 5 input [ 7 : 0 ] iY, 6 input [ 7 : 0 ] iCb, 7 input [ 7 : 0 ] iCr, 8 output reg oDVAL, 9 output [ 9 : 0 ] Red, 10 output [ 9 : 0 ] Green, 11 output [ 9 : 0 ] Blue 12 ); 13 14 // Internal Registers/Wires 15 reg [ 9 : 0 ] oRed,oGreen,oBlue; 16 reg [ 3 : 0 ] oDVAL_d; 17 reg [ 19 : 0 ] X_OUT,Y_OUT,Z_OUT; 18 wire [ 26 : 0 ] X,Y,Z; 19 20 assign Red = oRed; 21 assign Green = oGreen; 22 assign Blue = oBlue; 23 24 always @( posedge iCLK) begin 25 if (iRESET) begin 26 oDVAL <= 0 ; 27 oDVAL_d <= 0 ; 28 oRed <= 0 ; 29 oGreen <= 0 ; 30 oBlue <= 0 ; 31 end 32 else begin 33 // Red 34 if (X_OUT[ 19 ]) 35 oRed <= 0 ; 36 else if (X_OUT[ 18 : 0 ] > 1023 ) 37 oRed <= 1023 ; 38 else 39 oRed <= X_OUT[ 9 : 0 ]; 40 41 // Green 42 if (Y_OUT[ 19 ]) 43 oGreen <= 0 ; 44 else if (Y_OUT[ 18 : 0 ] > 1023 ) 45 oGreen <= 1023 ; 46 else 47 oGreen <= Y_OUT[ 9 : 0 ]; 48 49 // Blue 50 if (Z_OUT[ 19 ]) 51 oBlue <= 0 ; 52 else if (Z_OUT[ 18 : 0 ] > 1023 ) 53 oBlue <= 1023 ; 54 else 55 oBlue <= Z_OUT[ 9 : 0 ]; 56 57 // Control 58 {oDVAL, oDVAL_d} <= {oDVAL_d, iDVAL}; 59 end 60 end 61 62 always @( posedge iCLK) begin 63 if (iRESET) begin 64 X_OUT <= 0 ; 65 Y_OUT <= 0 ; 66 Z_OUT <= 0 ; 67 end 68 else begin 69 X_OUT <= ( X - 114131 ) >> 7 ; 70 Y_OUT <= ( Y + 69370 ) >> 7 ; 71 Z_OUT <= ( Z - 141787 ) >> 7 ; 72 end 73 end 74 75 // Y 596, 0, 817 76 MAC_3 u0 ( 77 iY, iCb, iCr, 78 17 ' h00254, 17 ' h00000, 17 ' h00331, 79 X, iRESET, iCLK 80 ); 81 82 // Cb 596, -200, -416 83 MAC_3 u1 ( 84 iY, iCb, iCr, 85 17 ' h00254, 17 ' h3FF38, 17 ' h3FE60, 86 Y, iRESET, iCLK 87 ); 88 89 // Cr 596, 1033, 0 90 MAC_3 u2 ( 91 iY, iCb, iCr, 92 17 ' h00254, 17 ' h00409, 17 ' h00000, 93 Z, iRESET, iCLK 94 ); 95 96 endmodule 『對啊!!尤其69行更詭異,114131、69370、141787是怎麼來的?公式裡完全沒出現這3個數字,而且為什麼最後又要 >> 7呢?』阿帥在旁邊也趕緊附和。
69行
X_OUT <= ( X - 114131 ) >> 7 ; Y_OUT <= ( Y + 69370 ) >> 7 ; Z_OUT <= ( Z - 141787 ) >> 7 ; 無雙學長最近正埋首幫教授編寫DE2-70教材與畢業論文,不過看到小美與阿帥的好學,心裡還是非常感動,就打算暫停手邊的工作,先解決學弟妹的問題。
『這個問題很多人問過我,主要是因為YCbCr轉RGB的公式牽涉到浮點運算,在Verilog並不容易實現,所以使用了一些技巧重新推導了整個公式,我在曾經提過這個技巧,當時也有考慮直接拿YCbCr2RGB.v來解講,不過怕這個公式太複雜而模糊了焦點,所以才另外寫了攝氏溫度轉華氏溫度的範例,但在實務上,YCbCr轉RGB應該是最多人第一次遇到浮點運算的機會,趁這個機會,我就把整個module講解一次。』
無雙學長拿了一張紙,準備將公式重新推導一次。
『這是原本YCbCr轉RGB的公式,我們可以發現這需要浮點運算,Verilog雖然有real型別,但只能用在testbench,不能用在RTL。』
R = 1.164 (Y- 16 ) + 1.596 (Cr- 128 ) G = 1.164 (Y- 16 ) - 0.391 (Cb- 128 ) - 0.813 (Cr- 128 ) B = 1.164 (Y- 16 ) + 2.018 (Cb- 128 ) 『現在將整個算式展開』
R = 1 .164Y + 1 .596Cr - 222.912 G = 1 .164Y - 0 .391Cb - 0 .813Cr + 135.488 B = 1 .164Y + 2 .018Cb - 276.928 『因為Verilog無法處理浮點數,所以我們打算使用提到的技巧:
將整個算式先放大處理,最後再縮小,現在將算式左右兩邊放大512倍,也就是2^9,相當於<<9。』
R >> 9 = 596Y + 817Cr - 114131 G >> 9 = 596Y - 200Cb - 416Cr + 69370 B >> 9 = 596Y + 1033Cb - 141787 『為什麼要*512呢?不可以乘其他數字嗎?』細心的小美馬上發現問題。
『當然可以,你也可以*1024,也就是<<10,只要最後記得>>10還原就好。』
『114131、69370、141787這些整數都出現了耶,為什麼75行要使用megafunction呢?』阿帥又發現了另外一個問題。
75行
// Y 596, 0, 817 MAC_3 u0 ( iY, iCb, iCr, 17 ' h00254, 17 ' h00000, 17 ' h00331, X, iRESET, iCLK ); // Cb 596, -200, -416 MAC_3 u1 ( iY, iCb, iCr, 17 ' h00254, 17 ' h3FF38, 17 ' h3FE60, Y, iRESET, iCLK ); // Cr 596, 1033, 0 MAC_3 u2 ( iY, iCb, iCr, 17 ' h00254, 17 ' h00409, 17 ' h00000, Z, iRESET, iCLK ); 『596 * Y + 817 * Cr這種乘加運算,當然也可以自己用Verilog寫,並自己處理pipeline以提高Fmax,DE2-70 CD中的範例有個特色,就是工程師對Altera的megafunction相當的熟悉,CD範例中到處可看到工程師巧妙使用megafunction之處,這裡使用了ALTMULT_ADD做乘加,透過megawizard設定就可自動幫你處理pipeline了。阿帥啊,你也自己用Verilog而不用megafunction寫寫看當練習。』
『69行為什麼要減114131呢?又為什麼要>>7呢?』這是小美原本百思不解之處。
69行
X_OUT <= ( X - 114131 ) >> 7 ; Y_OUT <= ( Y + 69370 ) >> 7 ; Z_OUT <= ( Z - 141787 ) >> 7 ; 『根據新推導的算式:R >> 9 = 596Y + 817Cr - 114131,X已經MAC_3算出來了,所以還要減去114131才行,至於 >> 7,這是個簡略的寫法,我將完整寫法還原,你們就一目了然了』。
X_OUT <= (( X - 114131 ) >> 9 ) << 2 ; Y_OUT <= (( Y + 69370 ) >> 9 ) << 2 ; Z_OUT <= (( Z - 141787 ) >> 9 ) << 2 ; 『原本算式經過 <<9 放大,最後要用 >>9 還原,這很合理,但別忘了
output [9:0] Red是10 bit,而input [7:0] iY是8 bit,所以最後還得放大 <<2 才行,一來一往就變成 >> 7了』。
『啊!!原來是這樣啊!!』小美恍然大悟。
『這樣看起來就很完美了,但為什麼34行還要對output做些判斷呢?』阿帥果然思考敏捷,立刻又提出了新的問題。
34行
// Red if (X_OUT[ 19 ]) oRed <= 0 ; else if (X_OUT[ 18 : 0 ] > 1023 ) oRed <= 1023 ; else oRed <= X_OUT[ 9 : 0 ]; // Green if (Y_OUT[ 19 ]) oGreen <= 0 ; else if (Y_OUT[ 18 : 0 ] > 1023 ) oGreen <= 1023 ; else oGreen <= Y_OUT[ 9 : 0 ]; // Blue if (Z_OUT[ 19 ]) oBlue <= 0 ; else if (Z_OUT[ 18 : 0 ] > 1023 ) oBlue <= 1023 ; else oBlue <= Z_OUT[ 9 : 0 ]; 『由於做了放大再縮小的運算,難免會造成overflow的狀況,所以最後多加了判斷,若大於1023,就當1023記,若小於0,就當成0,這樣結果才合理。』
『無雙學長,我還有最後一個問題,為什麼在DE2-70的範例常常看到58行這種寫法,這倒底是什麼意思呢?』這也是小美困惑很久的問題。
58行
// Control {oDVAL, oDVAL_d} <= {oDVAL_d, iDVAL}; 『這是友晶工程師一種技巧的寫法,讓code可以一行完成,看起來比較精簡,學弟妹們可以將這種技巧學起來,事實上它就相當於以下寫法。』
oDVAL_d <= iDVAL; oDVAL <= oDVAL_d; 『因為多了一些運算,所以希望data valid的信號多一個clock做delay,若寫成兩行大家就都能看的懂了。』無雙學長做了以上的解釋。
『謝謝學長,我總算看懂整個module在幹什麼了。』
完整程式碼下載
Conclusion
無雙學長最後做了以下結論:『演算法多多少少都會牽涉到浮點運算,不可能剛剛好都是整數,夠過這個技巧,就能將很多演算法從C語言改用Verilog實現,並配合硬體的parallel、concurrent、pipeline等特性做硬體加速。』
See Also