一:定義
脈動陣列:數據流同步流過相鄰的二維陣列單元的處理器結構,一般不同方向流過不同數據。如下圖:
二維不同數據在同一時鐘下依次輸入每個處理單元,而后完成乘法并存在其寄存器中。
二:舉例
三:設計
結構:
單個PE的代碼
module pe(clk, reset, coeff, in_x, in_y, out_x, out_y);
parameter size = 8;
input?clk, reset;
input?[size-1:0]?in_x, coeff;
input?[size+size-1:0]?in_y;
output?[size-1:0]?out_x;
output?[size+size-1:0]?out_y;
reg?[size+size-1:0]?out_y;
reg?[size-1:0]?out_x;
always@(posedge clk)
begin
?if(reset) begin
?out_x <= 0;
?out_y <= 0;
?end
?else begin
?out_y <= in_y + (in_x * coeff);
?out_x <= in_x;
?end
end
endmodule
?
四個PE,其余類推
//***** main ****************************
module systolic(clk, reset, input_x, output_y);
parameter?size = 8;
input?clk, reset;
input?[size-1:0]?input_x;
output [size+size-1:0]?output_y;
wire?[size-1:0]?pe0_x, pe1_x, pe2_x, pe3_x;
wire?[size+size-1:0]?pe1_y, pe2_y, pe3_y;
wire?[size-1:0]?h0 = 8'h01;
wire?[size-1:0]?h1 = 8'h01;
wire?[size-1:0]?h2 = 8'h01;
wire?[size-1:0]?h3 = 8'h01;
wire?[size+size-1:0]?pe4_y = 16'h0000;
pe pe_0(clk, reset, h0, input_x, pe1_y, pe0_x, output_y);
pe pe_1(clk, reset, h1, pe0_x, pe2_y, pe1_x, pe1_y);
pe pe_2(clk, reset, h2, pe1_x, pe3_y, pe2_x, pe2_y);
pe pe_3(clk, reset, h3, pe2_x, pe4_y, pe3_x, pe3_y);
endmodule
=========================================================================================================
脈動陣列(Systolic Array)計算矩陣乘法(Array Multiplication)
下一個目標是實現流水線輸出,提升硬件資源的利用率。
脈動陣列(Systolic Array):數據流同步流過相鄰的二維陣列單元的處理器結構,一般不同方向流過不同數據。
結構:
矩陣計算:
C語言描述每個輸出矩陣中的值:
For I = 1 to N
?For J = 1 to N
?For K = 1 to N
?C[I,J] = C[I,J] + A[J,K] * B[K,J];
運用N x N processing units,輸入數據呈批次輸入:
二維不同數據在同一時鐘下依次輸入每個處理單元,而后完成乘法并存在其寄存器中。
其中每個PE(處理單元)結構如下:
是一個乘加單元?c=c+(a*b)
例子:計算兩個3×3矩陣的乘積
結構:
在CLK驅動下的每一個步驟如下:
Clk1:
?
Clk2:
Clk3:
Clk4:
Clk5:
Clk6:
Clk7:
Clk8:輸出
功能仿真圖:
在start?上升沿到來后的第一個CLK上升沿開始計數
Count_start高電平期間
Cout=1時,準備a11和b11;
Cout=2時,將數據打入寄存器,并計數出a11*b11;
Cout=3時,計數a11*b11+a12*b21
Cout=4時,計數a11*b11+a12*b21+a13*b31
Cout=5時,用寄存器打一拍輸出Y11。
其他類似。
時序仿真圖:
連續運算,中間忘了將乘加單元寄存器清零的情況,功能仿真:
每次計算出結果后清零寄存器,修改后的功能仿真圖:
數據在送入運算單元之前,采用寄存器打一拍,功能仿真圖:
狀態機便于實現控制。
狀態機控制:功能仿真
時序仿真圖:
二維流水線結構矩陣乘法(Array Multiplication)
上一篇文中建立了矩陣乘法運算的數據路徑,從仿真結構中可以看出整個計算方案的可行性,但是存在一個問題,就是硬件運算單元的資源利用率不高。這是什么意思呢?就是說,在每次計算兩個3*3矩陣的乘法之前,需要將整個運算單元中的每個寄存器都清零,但是9個輸出結果不是在同一個時刻輸出,有先有后。當最先出結果的P11計算出第一個結果之后,它就不再輸出新值了。其實這時硬件電路是存在的,而且是在不斷計算出新值,只是這些值不是我們需要的有效值。那么如果將這些硬件資源完全被利用起來,讓它們在最短的時間間隔里都有有效正確數據輸出呢。
其實要說的就是流水線設計思想,我們只需要當P11單元計算出新的值,下個CLK將其計算結果輸出(只要有另一個機制接收這些值),然后將其清零(如果不清零,那個會累加了上次的計算結果),再然后就可以將下個要計算的兩個3*3矩陣對應位置上面的新值輸入給P11單元,讓其計算兩個數的乘積。
其他單元類似,思想就是在輸出結果后立即做清零和賦新值,不必等整個計算單元都出結果之后才開始進行下個矩陣乘法的計算。
那么從大局來看,就出現個這樣的情況:在上個矩陣計算還沒有完全計算出9個值的時候(右下角部分還在計算),左上角的單元就已經開始下個矩陣乘法的計算。關于輸出就是,每個CLK都有有效的數據輸出,產生了流水輸出。而這些輸出是從這整個二維矩陣計算單元的某些地方同時輸出的,就像水流即向下流也向右流,只是在這里水流對應了數據輸出。
回顧一下這樣做的目的是什么:為了增加整個運算單元的利用率。做了改進,我們可以看出,每個CLK到來時,每個計算單元中的乘法和加法器的運算都是有效的計算。我們一定要記住FPGA做運算和CPU做計算的不同點。FPGA做運算,那些設計好的運算單元,不管你對這些運算單元操作或者不操作,它們都已經以硬件電路的形式存在了,也就是說,每個CLK到來時,它們都進行了一次計算。那么如何有效的利用這些運算單元,就是不要讓它們做無效的計算,讓它們每一次的計算都對我們來說是有效的,是一個對整個單元輸出有影響的計算。而想實現這樣的功能流水線設計方法是必須要采用的。只是有時候單向流水(一維)更容易被我們所理解。其實這二維運算單元也是如此,也可以產生流水。
相對于不采用流水線操作,數據路徑不必做太多修改,主要是控制單元更為復雜,要考慮何時對某個單元清零,何時再對其賦值,何時把數據讀走等。這些需要比較精準的調度。有人會想,那么這樣是不是需要更多狀態機狀態?答案是肯定的。那么是不是需要更多的硬件資源的開銷呢?答案也是肯定的。那么這種流水線的優勢在哪呢?其實是速度上面的優勢,有時候在不能提高整個系統的工作頻率的情況下,再消耗一點資源來提高其它部分(運算單元)的利用率也是一個很好的提高運算速度的辦法。(注:這并不等同于面積與速度的法則)其實這也是一個比較好的,硬件算法優化方案。我們可以將其歸納為以優化算法路徑的算法優化方法。
下面是仿真結果:
?
矩陣還是之前的矩陣。可以看出每個CLK都有2-3個有效輸出。
增加了Valid信號,當其為高電平時,說明對應運算單元輸出是有效值。其實在算法成熟之后,這些Valid信號是可以撤銷的,因為我們完全知道了,輸出的規律,只需要在特定的時間讀走數據即可。而且我們可以將這些數據合并到幾根連續有效的總線上面。讓每個總線上面每個CLK都是有效值。(筆者將按照此方法,繼續優化輸出總線)
下面是對輸入最大值時的輸出仿真,主要是看數據會不會益處:
測試8bits輸入最大值255對應的輸出值:
時序仿真:
255*255+255*255+255*255=195075
255*254+255*254+255*254=194310
編輯:黃飛
?
評論
查看更多