在FPGA設計中,一般的算數運算符都是按照無符號數進行的。那么需要有符號數計算的時候,該怎么辦呢?
很久很久以前也就是Verilog-2001還沒有出現時,是手動操作的,也就是說,對于一個8位的無符號數,比如reg [7:0] a; 我們手動把最高位當做符號位,剩余的7位則是數值位,整個二進制以補碼的形式表示。這個時候進入的二進制以補碼的形式表示,出來的二進制也以補碼的形式表示,在查看波形時,也以補碼的形式查看。總之,里面涉及到的二進制、二進制運算,都是基于二進制補碼的這個編碼形式。這個時候,這個模塊里面的變量、要處理的數都是有符號數,不要添上與無符號相關東西了,容易亂。無符號數的數據流想要進入這個模塊,得先經過補碼編碼后再輸進來。
后來,Verilog-2001出現了,當我們想處理有符號數時,再也不用我們手動認最高位了,因為這個時候的Verilog被定義得很“智能”了。是有符號還是無符號,就要看變量有沒有signed的聲明了。意思就是說,一個數進來,比如說8位,如果是 reg [7:0] a; 那么a就會被識別為無符號的8位數。如果是reg signed [7:0] a; 那么a就會被識別為有符號數,并且是識別為8位的二進制補碼。所謂的signed只有在參與運算的過程中才會有作用,再連線方面或是其他方面,沒有任何作用,因為都是補碼。
在Verilog-2001出現之后,一個模塊中出現有符號和無符號的處理問題也不大了,因為Verilog 2001提供了 s i g n e d ( ) 及 signed()及 signed()及signed()機制,能夠進行有符號與無符號之間的轉換(然而我還是不建議一個模塊里面同時處理有符號和無符號的數據)。
盡量不要使有符號數與無符號數進行混合計算。因為只要有一個無符號數的運算單元,整個算式將被將成無符號數進行計算。
下面是例子:
例子1:
input signed [7:0] a, b;
output signed [15:0] o;
assign o = a * b;
上面輸入兩個有符號數a和b,這兩個數都將被識別為二進制補碼(我們在查看波形的時候,也應該以補碼的形式查看)。這兩個數進行有符號數的乘法運算,得到的自然也是有符號數的積,因此這里的輸出也定義為有符號數。從這個例子中我們可以看到,操作數變量被定義為有符號數,那么操作的結果變量也應該被定義為有符號數。
例子2:
input [7:0] a, b;
output [15:0] o;
wire signed [15:0] o_sgn;
assugb o_sgn = $signed(a) * $signed(b);
assign o = $unsigned(o_sgn);
例子2中,輸入的是兩個無符號數,輸出也定義為無符號數。然而,(假設)應用時,發現只有一個有符號的乘法器可以使用,這個時候就要用Verilog中的signed()及 s i g n e d ( ) 機制 . 首先使用 signed()機制.首先使用 signed()機制.首先使用signed()把輸入變成有符號數,同時定義一個有符號的中間結果,這樣子就可以使用有符號的乘法器進行運算了。最后使用$unsigned()把有符號的中間結果轉換為無符號輸出。
例子3:
input signed [7:0] a, b;
input signed [8:0] o;
assign o = a + b; // Verilog會自動進行符號的擴展。
Verilog-2001會自動擴展符號位。意思是說,當我們進行有符號數進行運算的時候,我們不要手動轉換符號位了,Verilog會自動識別最高位為符號位,同時在運算過程中會自動進行符號位的擴展。因此,正負號的擴展時,應多加利用Verilog的implicity signed extension,避免手動進行轉換。
例子4:
input [7:0] a;
input signed [7:0] b;
output signed [15:0] o;// Don't do this: assign o = a * b;
// The $signed({1'b0, a}) can convert the unsigned number to signed number.
assign o = $signed({1'b0, a}) * b;
上述代碼中,輸入a被識別為無符號數,而輸入b則被識別為二進制補碼,輸出則被定義為二進制補碼輸出。在進行運算的時候,我們不能直接讓a、b直接相乘,因為在一個verilog敘述中只要有一個無號數的??運算元,整個算式將被當成無號數進行計算。這個時候,因為輸出是有符號的,我們最好把輸入也變成有符號的,方便運算。因此我們首先需要把無符號的輸入手動添加一個符號位后,使用 s i g n e d ( ) 把輸入轉換稱為與 b 一樣的有符號形式;如果不使用 signed()把輸入轉換稱為與b一樣的有符號形式;如果不使用 signed()把輸入轉換稱為與b一樣的有符號形式;如果不使用signed(),直接使用{1’b0,a}與b相乘,這樣還是無符號*有符號,因此還是需要把{1’b0,a}轉成有符號的形式。
同樣地,當乘以一個常數時,也要用$signed()把常數轉換一下:
input signed [7:0] a;
output signed [15:0] o;// Don't do this: assign o = a * 8'b10111111;
// Use $signed() system task
assign o = a * $signed(8'b10111111);
// or sb keyword.
assign o = a * 8'sb10111111;
還有一個需要的是,部分選擇(也就是進行位選的時候),位選運算過后的結果是無號數!!!就算是選擇的范圍包含整個register或wire,例子如下所示:
input signed [7:0] a;
input signed [7:0] b;
output signed [15:0] o1, o2;// Don't do this: assign o1 = a[7:0];//a[7:0]是得到的是無符號數,將一個無符號賦值給一個有符號數的時候,結果肯定會出錯
assign o1 = a;
// Don't do this: assign o2 = a[6:0] * b;
assign o2 = $signed(a[6:0]) + b