1. 程式人生 > 其它 >FPGA影象處理技巧 搬運自fpga開源工作室 也就那樣

FPGA影象處理技巧 搬運自fpga開源工作室 也就那樣

1Verilog是一種思維方式

先來談一下怎樣才能學好Verilog這個問題。有人說學Verilog很難,好像比C語言還要難學。有一定難度是真的,但並沒有比別的語言更難學。我們剛開始學C語言的時候也覺得C語言很難,直到我們把思維方式轉變過來了,把微機原理學好了,能模擬CPU的執行方式來思考問題了,就會發現C語言也沒那麼難了。所以這裡面存在一個思維方式的轉換的過程。這對於學Verilog來說也是一樣的,只不過Verilog比C語言還要更加底層,我們只掌握了CPU的思維模式還不行,還需要再往下學一層“硬體電路的思維模式”,才能更好的掌握硬體程式語言。

我對學習的一個經驗總結就是,如果你想要很好的掌握某一個層面的知識技能,那就必須要往下再學一個更基礎的層面。比如C語言是軟體層面的,理論上你不知道CPU的工作原理也能程式設計。但要成為高手也還是必須對更下層的微機原理、編譯原理等有深入的瞭解。再往下一層,也就是數位電路層,對於軟體程式設計來說已經不太重要了。但要學好Verilog,則又必須再往下學好數位電路這一層。也就是要了解什麼是時序電路,組合電路,RTL,什麼是觸發器的建立時間和保持時間等這些重要概念。至於觸發器是由什麼樣的閘電路構成的,邏輯閘的版圖又是怎麼畫的,這樣更底層的知識其實對學Verilog來說也不太重要,但要是學晶片設計,這些又很重要。

總的來說現在會硬體程式設計的人才少是因為之前微電子專業培養的人太少了。而計算機專業的想來用FPGA那自然會覺得Verilog難學,因為他們可能沒學過數位電路這些基礎知識,或者學過也早忘了。所以如果現在想學硬體程式設計,而以前也沒有學過數位電路的基礎,那就先得補充點基礎知識,再通過實踐訓練,也能很快掌握。Verilog沒有比C更難,能學好C的肯定也能學好Verilog。但這需要你再進行一次思維方式的轉換和訓練。如果你直接把寫C語言的方式套用來寫Verilog上,那就是大錯特錯了,它們不是同一個層面的。

2多實踐,表掉進概念的坑

剛開始學Verilog的時候可能會發現有些概念很難理解。比如Verilog和VHDL有什麼區別?阻塞賦值和非阻塞賦值有什麼區別?什麼是可綜合和不可綜合?

初學時可能看了很多書和文章卻還是搞不清楚這些概念。其實要弄明白這些概念的關鍵不是去調研看別人的解釋,而是要自己去實踐。網上寫文章解釋這些概念的人未必自己就搞的很清楚。比如Verilog和VHDL我就認為它們之間只是形式上有些區別,一個簡潔一點一個囉嗦一些,本質上沒啥區別,換湯不換藥。能看懂Verilog去看VHDL也沒問題,我還幹過手動把VHDL改為Verilog的事情,也就是複製貼上然後改改關鍵字並刪掉一些東西就行了。能這樣就改過來說明它們之間就只有形式上的區別。網上的那些說它們之間區別的帖子,把它們之間的區別說的似乎有很大,但我覺得這些都是在瞎扯。

阻塞賦值和非阻塞賦值。呵呵,我也不知道當初發明硬體建模語言的人為啥要整出這樣一些讓人費解的術語。要知道,有些學術術語如果用大白話說實際上是很簡單的,那些搞研究的人估計是為了故作高深,所以要發明一些新的讓人看不懂的術語來顯得自己好像水平很高。所以大家千萬不要被術語給唬住了。為啥要用阻塞和非阻塞這兩個術語來描述對組合邏輯電路和觸發器的模擬,這個我也不明白。我只知道=和<=在Verilog中是如何使用的。=是用在always@(*)塊和assign語句中寫組合邏輯電路的。<=只用在always@(posedge clk)塊中用來寫暫存器。always@(*)和assign之間沒啥區別,都生成組合邏輯電路。只是有時組合邏輯比較複雜,用assign語句一句話寫不完時會用always@(*)。區別就是always@(*)塊中被賦值的訊號要被定義成reg,而assign中被賦值的訊號則必須是wire,但它們卻都是生成組合邏輯電路。這就是Verilog一點不嚴謹的地方。不過這也沒啥大問題,就是容易把初學者搞糊塗。有人喜歡把組合電路和時序電路在程式碼中分開來寫,比如在always@(*)中寫NextState =一堆組合邏輯,然後再在always@(posedge clk)中只寫State <=NextState。不過我嫌這樣寫羅索,所以在我寫的程式碼中就只會出現always@(posedge clk)和assign。

可綜合和不可綜合可以直接理解為,可綜合的就是用來寫實際電路模組的,不可綜合就是用來寫模擬測試激勵的。可綜合的就是前面說的always@(posedge clk),always@(*),assign,再加上function塊這幾種語句。function塊是用來描述純組合電路的,是可綜合的。比如你要在程式碼中經常用到求最大值這個功能,就可以寫一個function [:] Max;。initial,task,for迴圈,#n延時,repeat(n)@等這些都只會在寫測試激勵時出現,是不可綜合的。可綜合的和不可綜合的語句都能在測試激勵中寫。這樣一說不就很清楚了。

3必須瞭解影象處理演算法的實現細節

現在呼叫OpenCV或Matlab中現成的影象處理函式就可以做影象處理。但這樣只能說你會用這些影象處理演算法,並不能說你會寫影象處理演算法。因為這些演算法具體是怎麼處理影象資料的,怎樣進行計算的你並不知道。要想用FPGA做影象處理,首先你得先會寫影象處理演算法,不管你用什麼語言寫,關鍵是不能直接呼叫現成的函式,而是要自己能寫出一個畫素、一個畫素點的處理過程。然後還需要知道你寫的影象處理演算法中,哪些是適合用FPGA來進行處理的。或者說用FPGA進行影象處理,和進行各種計算的優勢到底在哪裡。如果發現的確可以用FPGA加速,再來進行FPGA程式設計實現。

4FPGA做影象處理的技巧都在Block Ram的使用上

FPGA的最大優勢就是能對資料進行並行流水線處理。而實現這一點的關鍵就是要用FPGA內部的Block Ram對資料進行邊快取邊處理。注意,進行流水線處理是用不到DDR的,DDR沒有Ram那麼高的實時性,只能用來快取大量資料。要知道FPGA接的DDR速度和容量是遠沒有CPU上接的DDR快的。所以要發揮FPGA並行流水線處理的優勢,其所用的演算法也必須並行流水線化。把CPU上的演算法照搬到FPGA中,然後接個DDR當記憶體,這樣的做法並不能發揮FPGA的優勢。FPGA的優勢是並行流水線。那什麼樣的演算法可以並行流水線化呢?簡單的說只需要順序讀取資料進行處理的演算法都可以。比如像影象處理中用NxN的運算元進行濾波,取邊緣,膨脹腐蝕等。這些都是很適合用FPGA進行處理的。有些演算法看似不是順序讀取資料的,但改造一下之後也可以。比如連通域識別,具體可見我的另一篇文章《FPGA實現的連通域識別演算法升級》。

那麼用FPGA進行NxN的運算元法影象處理具體是怎樣實現的呢?以3x3的運算元為例,3x3的運算元要同時取3行的資料,所以先要用FPGA裡面的Block Ram快取上兩行的資料。當這一行資料來的時候同時去讀取Ram裡快取的上兩行資料,並把這3行資料一起移入3x3的移位暫存器中,然後對這3x3個暫存器中的值進行你所需要的運算元運算。之後再把這新一行的資料存回Ram中,原先最上面的那一行資料就被覆蓋丟棄了。簡單的說流程就是這樣的,N行的運算元只需要快取N-1行資料。Block Ram是FPGA裡最重要的資源,所以能省則省。

具體如何寫大家可以去參考我開源的程式碼,其實也沒有多複雜,程式碼並不長。LineBuffer.v這個模組是負責控制Block Ram讀寫的,它並沒有把Block Ram模組包含進去,是因為Block Ram是需要你自己用ISE或Vivado根據你的演算法需要來生成的。OperatorNxN.v這個模組包含了LineBuffer.v和Block Ram模組,負責把資料移入移位暫存器並進行運算元的計算。

這個Ram就相當於陣列,在軟體程式設計中我們獲取陣列中的資料只要寫個A[n]資料就來了,不需要關心任何細節問題。但在Verilog硬體程式設計中,資料是怎麼寫入Ram中的,然後又是怎麼讀出來的都需要你去描述,這裡面關鍵要處理的就是Ram的讀寫時序問題。所以在Verilog程式碼中,進行運算元計算的這塊程式碼看起來是和C語言中的差不多的。Verilog中最多的就是對Ram的讀寫操作和移位寄存這塊。要想用FPGA進行影象處理,要學會的也就是這些操作。

這幾個程式碼大家新建個工程把它們新增進去,並自己生成同樣大小的Block Ram就能跑模擬了,測試激勵和測試用的文字影象檔案都有。生成Block Ram時要注意選True Dual Port Ram,寬度和深度和我的程式碼中標註的一樣。輸出不需要用暫存器快取,ISE中預設沒有勾選,Vivado中勾上了,要去掉。注意FPGA中的Block Ram是有最小單位的,Xilinx 6系中是9k,7系中是18k,這就意味著如果你在7系中生成一個18x1025或19x1024的Ram就要消耗兩個18K的Block Ram模組。所以生成時要注意看最後的報告,告訴你到底用了多少塊。