基于FPGA 的解決方案具有眾多優勢,其中之一就是能夠針對眼前的問題采用最佳的方式來進行數學算法。例如,如果響應時間至關重要,我們就簡化數學運算步驟。如果注重運算結果的精度,我們就使用更多的位來確保達到預期的精度。當然,很多新型FPGA 還具有嵌入式乘法器和DSP slice 的優勢,可用于在目標器件中獲得最佳的實現性能。
讓我們了解一下在FPGA 或其它可編程器件內開發數學函數所使用的規則與方法。
數字的表示方式
在一種設計方案中可以使用兩種數字表示方式,即定點數與浮點數。定點表示法中小數點位置固定不變,可以直接進行算數運算。定點數的主要缺點是如果要表示一個較大的數或者得到一個更精確的小數值,就需要使用若干個位。定點數由兩部分構成:整數和小數。
浮點表示法中小數點位置隨數值的大小在不同位置浮動。浮點數同樣也可分為兩部分:指數和尾數。這種表示方法類似于科學計數法,科學技術法是將一個數表示為A 乘以10 的B 次冪,其中A 為尾數、B 為指數。但在浮點數中,指數部分的基數是2,即A 乘以2 的B次冪。IEEE/ANSI 754-1985 標準對浮點數表示法進行了標準化。基本IEEE 浮點數使用8 位指數和24 位尾數。
由于浮點數的表示法存在一定的復雜性,我們作為設計人員應盡可能多地采用定點表示法。上述浮點數采用補碼表示法, 其無符號數表示范圍介于0.0 ~255.9906375 之間,有符號數表示范圍介于-128.9906375~ 127.9906375 之間。您在一種設計方案中既可以使用無符號數也可以使用有符號數,這通常取決于您所用的算法。無符號數的表示范圍為0 ~ 2n-1,始終表示正數。
相比之下,有符號數的表示范圍則取決于所采用的編碼方案,即符號數值表示法(即原碼)、1 的補碼(即反碼)或2 的補碼(即補碼)。
原碼中最左邊的位表示數的符號(0 為正,1 為負)。其余的位表示數值的大小。在這種表示方法中,正數和負數的絕對值相同,但是符號位不同。因此,原碼方案中存在正零和負零。
正數的反碼與其原碼的無符號數相同。負數的反碼為正數按位取反。
補碼是使用最廣泛的有符號數編碼方案。這里與其它兩種編碼方案一樣,正數與無符號數的表示形式相同,而負數的二進制表達式與絕對值相同的正數相加后等于0。計算負數補碼時,首先將正數按位取反,然后再加1。補碼允許您將兩個數的減法按照加法來處理。補碼可以表示的范圍是:
將一個數轉換為補碼格式的方法是按從右至左的順序按位遍歷,從遇到的第一個“1”開始將二進制位按位取反,而之前的二進制位保持不變。
定點運算
在定點數中,通常用x 和y 來區分整數位和小數位,其中x 表示整數位的數量,y 表示小數位的數量。例如,8,8 表示8 個整數位和8 個小數位;16,0 表示16 個整數位和0 個小數位。在很多情況下,您通常需要在設計階段根據浮點算法轉換來確定所需的整數和小數位數量。得益于FPGA 的靈活性,我們可以表達任意二進制長度的定點數;整數位的數量取決于需要存儲的最大整數值,而小數位的數量取決于最終結果的精度。我們利用以下公式來確定整數位的數量:
例如,要表示0.0 ~ 423.0 范圍內的數值,所需整數位的數量為:
這表示您需要9 個整數位,可以代表0 ~ 511 范圍內的數。利用16 個位來表示這個數時,可以有7 個位用于表示小數。利用下面的等式計算這種表達方式所能提供的精度:
您可以增加小數位的數量,進而提高定點數的精度。在設計過程中,我們有時希望只存儲小數(0,16), 這主要取決于您希望將精度提高到多少。利用216 進行擴展可能依然無法達到足夠高的精度。這種情況下,您可以用2 的冪次方來放大這個數,使這個數可以用16 個位來表示。然后,您可以在下一階段刪除這個比例因子。例如,為了用16 個位來表示1.45309806319x10-4,第一步需要將這個數與216 相乘。
只存儲結果的整數部分(9)將導致這個數的實際存儲值為1.37329101563x10-4(9 / 65536)。需要存儲的數值與實際存儲的數值之間差值較大,可能導致出現無法接受的錯誤計算結果。您可以按照比例因子2 來放大這個數,以獲取更精確的結果。結果介于32768-65535之間,因此仍然可以用一個16 位的數字來存儲。利用此前存儲1.45309806319x10-4 的實例,將這個數與比例因子228 相乘將產生一個可以用16 個位來存儲的數,并使預期的數值具有更高的精度。
假定在接下來的計算過程中您可以解決用比例因子228進行放大的問題, 那么結果的整數部分將給予您1.45308673382x10-4 的存儲結果,并使得計算結果具有更高精度。例如,將已擴展的數與一個16 個位格式為4,12 的數相乘,產生了4,40(28 + 12)形式的結果。但是,這個結果將以32 位來存儲。
定點規則
在執行加法、減法或除法時,2 個數的小數點必須對齊。這就是說您只可以將一個表示格式為x,8 的數與另一個表示格式也為x,8 的數相加、相減或相除。對具有不同格式的x 和y 進行算術運算時,您首先應保證小數點對齊。為了對齊不同格式的數字,您有兩個選擇:將帶有更多整數位的數與2X 相乘,或者將具有最小整數位的數除以2X。但是,除法會降低結果的精度,還可能導致結果超出容許公差。由于所有的數都可以利用兩種形式來存儲,這樣您在FPGA 中通過移位操作可以很方便地對數進行放大或縮小,其中左移或右移1 位分別放大或縮小了1 倍,實現十進制小數點的對齊。為了對兩個格式分別為8,8和9,7 的兩個數相加,如果可以接受最低有效位的丟失,則您可以利用比例因子21 來放大格式為9,7 的數,也可以將格式為8,8 的數縮小至格式為9,7。
例如,您打算將234.58 和312.732 這兩個數相加,而它們分別以8,8 和9,7 的格式來存儲。第一步,確定實際相加的16 位數。
從上可以看出,兩個加數分別為60052 和40029。但是,在相加之前,您必須對齊小數點。通過放大帶有更多整數位的數來對齊十進制小數點,您必須利用因子21 來放大9,7 格式的數。
然后,您通過執行加法來計算結果:
以10,8 格式(140110 / 28)表示,則為547.3046875。
當兩個數相乘時,您無需對齊小數點,因為乘法提供了范圍是X1 + X2,Y1 + Y2 的結果。將格式分別為14,2 和10,6 的兩個數相乘將得出一個整數位為24,小數位為8 的結果。
通過與除數的倒數相乘這種方法,在一個式子中您可以采用與小數相乘來代替除法。這種途徑可以顯著降低設計的復雜性。例如,將212.732(以9,7(40029)格式來表示)除以15,第一步是計算除數的倒數。
這個倒數必須被放大,以16 位數的形式來表示。
將這兩個數相乘,得出格式為9,23 的結果。
相除結果為:
當預期的結果是20.8488,如果結果的精度不夠高,則您可以利用一個更大的比例因子來放大這個倒數,以得到更精確的結果。因此,當可以與一個數的倒數相乘時,永遠不要除以這個數。
溢出問題
在實現算法時,結果必須不大于結果寄存器可以存儲的最大值。否則,就會發生溢出。當溢出發生時,存儲結果就會有誤,最高幾位會丟失。溢出的最簡單實例是將2個16 位的數相加,每個數的值都是65535,然后將結果存儲在16 位寄存器中。
上述計算將使得這個16 位結果寄存器中的值為65534,但這個結果不正確。防止溢出的最簡單方式是確定數學運算允許的最大值,利用這個方程來確定所需結果寄存器的大小。
如果您正在開發一個平均器,計算50 個16 位輸入值的平均值,則可以計算所需結果寄存器的大小。
仍然利用同一個方程,需要一個22 位結果寄存器來防止溢出的發生。您也必須注意,在處理有符號數時,如果遇到了負數,應該避免發生溢出。仍然利用此前的平均器實例,計算10 個有符號長度為16 位的數的平均值,返回一個16 位的結果。
因為很方便地將結果與除數倒數的擴展值相乘,您將這個數與1/10 ? 65536 = 6554 相乘來確定平均值。
這個數除以216 等于-32770, 但16 位的輸出結果無法正確地表示這個數。因此,模塊的設計過程必須考慮溢出,必須檢測溢出,以確保不會輸出不正確的結果。
現實世界的實現方式
假設您正在設計一個模塊,用于實現一個轉換氣壓的轉移函數,其中氣壓的單位是毫巴,海拔的單位是米。
輸入值的范圍是0 ~ 10 毫巴,分辨率是0.1 毫巴。模塊輸出要求精確到+/-0.01 米。因為模塊規范沒有確定輸入刻度,您可以通過下列等式來計算。
因此,為了實現最高的精度,您應將輸入數據的格式設置為4 個整數位,12 個小數位。開發這個模塊的下一步任務就是利用未擴展值并通過電子數據表計算出整個輸入范圍內轉換函數的預期結果。如果輸入范圍過大而無法獲得合理的結果,則計算可接受的點數量。例如, 您使用100 個條目來確定整個輸入范圍的預期結果。在您計算出最初的非擴展預期值之后,下一步是確定正確的常數比例因子,利用擴展值來計算預期的輸出結果。為了實現最高的精度,您應利用不同的因子來放大該式中每個常數。
多項式中第一個常數(A)的比例因子為:
多項式中第二個常數(B)的比例因子為:
因為最后的多項式常數(C)是一個純小數,所以利用比例因子216 來放大它。
通過這些比例因子用戶可以計算出擴展的電子數據表,如表1 所示。每一階段的計算結果將得出超過16 位的結果。
Cx2 的計算得出32 位、格式為4,12 + 4,12 = 8,24 的結果。然后與常數C 相乘,得出了48 位、格式為8,24 + 0,16 = 8,40 的結果。對于這個實例所要求的精度來說,利用40 位來表示小數有點多。因此,將這個計算結果除以232,以得出16 位、格式為8,8 的結果。在計算Bx 過程中,也將結果減小至16 位,以得出格式為5,11 的結果。
計算結果是Cx2,Bx 與A 列中對應數之和。但是,為了獲得正確的結果,您首先必須擴大A 和Cx2 ,并按x,11 格式對齊小數點,或者縮小Bx 的計算結果并按8,8格式對齊小數點,最終將小數點與A 和Cx2 的計算值的小數點對齊。
在這個例子中,我們將計算結果縮小23 倍,按8,8格式來對齊小數點。這種方法簡化了需要移位的數量,因此減小了實現這個實例所需邏輯單元的數量。注意如果您通過縮小來對齊小數點的方式而沒有實現要求的精度時,則必須擴大A 和Cx2 的計算結果來對齊小數點。在這個實例中,計算結果擴大了28。然后,您可以縮小這個結果,將其與從未擴展值中獲取的結果比較。實際計算結果和預期結果之間的差值表示精度,利用電子數據表中MAX() 和MIN() 命令來獲得計算結果的最大誤差和最小誤差,而您在電子數據表條目的整個范圍內都可以獲取計算結果的這兩個誤差。
當基于電子數據表的計算結果確認了您已經實現了所要求的精度,則可以編寫并仿真RTL 代碼。如果需要,您可以設計一個測試平臺,例如輸入值與電子數據表中的數據相同。這允許您將仿真輸出結果與基于電子數據表的計算結果進行比較,以確保采用了正確的RTL 實現方案。
RTL 實現方案
RTL 實例利用有符號并行數學運算在4 個時鐘周期之內即可計算出結果。因為采用了有符號的并行乘法,所以應該注意到必須正確地處理由乘法產生的額外符號位。
ENTITY transfer_function IS PORT(
sys_clk : IN std_logic;
reset : IN std_logic;
data : IN std_logic_vector(15 DOWNTO 0);
new_data : IN std_logic;
result : OUT std_logic_vector(15 DOWNTO
0);
new_res : OUT std_logic);
END ENTITY transfer_function;
ARCHITECTURE rtl OF transfer_function IS
-- this module performs the following
transfer function -0.0088x2 + 1.7673x +
131.29
-- input data is scaled 8,8, while the
output data will be scaled 8,8.
-- this module utilizes signed parallel
mathematics
TYPE control_state IS (idle, multiply,
add, result_op);
CONSTANT c : signed(16 DOWNTO 0) := to_
signed(-577,17);
CONSTANT b : signed(16 DOWNTO 0) := to_
signed(57910,17);
CONSTANT a : signed(16 DOWNTO 0) := to_
signed(33610,17);
SIGNAL current_state : control_state;
SIGNAL buf_data : std_logic; --used to
detect rising edge upon the new_data
SIGNAL squared : signed(33 DOWNTO 0); --
register holds input squared.
SIGNAL cx2 : signed(50 DOWNTO 0);
--register used to hold Cx2
SIGNAL bx : signed(33 DOWNTO 0); --
register used to hold bx
SIGNAL res_int : signed(16 DOWNTO 0);
--register holding the temporary result
BEGIN
fsm : PROCESS(reset, sys_clk)
BEGIN
IF reset = ‘1’ THEN
buf_data 《= ‘0’;
squared 《= (OTHERS =》 ‘0’);
cx2 《= (OTHERS =》 ‘0’);
bx 《= (OTHERS =》 ‘0’);
result 《= (OTHERS =》 ‘0’);
res_int 《= (OTHERS =》 ‘0’);
new_res 《= ‘0’;
current_state 《= idle;
ELSIF rising_edge(sys_clk) THEN
buf_data 《= new_data;
CASE current_state IS
WHEN idle =》
new_res 《= ‘0’;
IF (new_data = ‘1’) AND (buf_data
= ‘0’) THEN --detect rising edge
new data
squared 《= signed( ‘0’& data)
* signed(‘0’& data);
current_state 《= multiply;
ELSE
squared 《= (OTHERS =》‘0’);
current_state 《= idle;
END IF;
WHEN multiply =》
new_res 《= ‘0’;
cx2 《= (squared * c);
bx 《= (signed(‘0’& data)* b);
current_state 《= add;
WHEN add =》
new_res 《= ‘0’;
res_int 《= a + cx2(48 DOWNTO 32)
+
(“000”& bx(32 DOWNTO 19));
current_state 《= result_op;
WHEN result_op =》
result 《= std_logic_vector(res_
int (res_int‘high -1 DOWNTO 0));
new_res 《= ’0‘;
current_state 《= idle;
END CASE;
END IF;
END PROCESS;
END ARCHITECTURE rtl;
FPGA 架構成為了實現數學函數的理想工具,盡管實現算法需要具有更多的最初想法以及利用MATLAB? 或Excel 等系統級仿真工具來建模。一旦掌握了FPGA數學運算的一些基本知識,用戶就可以快速地實現數學算法。
評論
查看更多