Algorithm 将整数除以固定常数的最快方法是什么?

Algorithm 将整数除以固定常数的最快方法是什么?,algorithm,division,system-verilog,Algorithm,Division,System Verilog,我有一个16位的数字,我想除以100。假设是50000美元。目标是获得500英镑。然而,我试图避免在我的FPGA上使用推断除法器,因为它们会破坏时序要求。结果不一定要准确;近似值就可以了 我尝试过硬件乘法0.01,但不支持实数。我现在正在研究流水线除法器,但我希望不会出现这种情况。概念上:乘以655(=65536/100),然后右移16位。当然,在硬件中,右移是免费的 如果需要更快,可以将除法硬连接为除法之和乘以二的幂(移位)。例如: 在C代码中,上面的最后一个示例是: uint16_t div

我有一个16位的数字,我想除以100。假设是50000美元。目标是获得500英镑。然而,我试图避免在我的FPGA上使用推断除法器,因为它们会破坏时序要求。结果不一定要准确;近似值就可以了


我尝试过硬件乘法0.01,但不支持实数。我现在正在研究流水线除法器,但我希望不会出现这种情况。

概念上:乘以655(=65536/100),然后右移16位。当然,在硬件中,右移是免费的

如果需要更快,可以将除法硬连接为除法之和乘以二的幂(移位)。例如:

在C代码中,上面的最后一个示例是:

uint16_t divideBy100 (uint16_t input)
{
    return (input >> 7) + (input >> 9) + (input >> 12);
}
假设

  • 整数除法用于截断,而不是四舍五入(例如599/ 100=5)
  • FPGA中有一个16x16的乘法器是可以的(上有一个固定值) 一个输入)
然后,您可以通过实现一个16x16无符号乘法器来获得精确值,其中一个输入是0xA3D7,另一个输入是您的16位数字。将0x8000添加到32位乘积中,您的结果将位于高位10位

在C代码中,算法如下所示

uint16_t divideBy100( uint16_t input )
{
    uint32_t temp;

    temp = input;
    temp *= 0xA3D7;     // compute the 32-bit product of two 16-bit unsigned numbers
    temp += 0x8000;     // adjust the 32-bit product since 0xA3D7 is actually a little low
    temp >>= 22;        // the upper 10-bits are the answer

    return( (uint16_t)temp );
}

一般情况下,您可以乘以逆和移位。编译器一直都在这样做,即使对于软件也是如此。
这里有一个页面可以帮你做到这一点:
在您的例子中,这似乎是乘以0x431BDE83,然后右移17


这里有一个解释:

乘以倒数通常是一个很好的方法,正如您所指出的,尽管不支持实数。您需要使用定点实数而不是浮点实数

Verilog没有固定点的定义,但它只使用一个字长,您可以决定有多少位是整数,有多少位是小数

二进制的0.01(0.0098876953125)将是
0\u000000 1010001
。单词长度越大,精度越高

// 1Int, 13Frac
wire ONE_HUNDREDTH = 14'b0_0000001010001 ; 

input  a         [15:0];    //Integer (no fractional bits)
output result [15+14:0];    //13 fractional bits inherited form ONE_HUNDREDTH
output result_int [15:0];   //Integer result

always @* begin
  result     = ONE_HUNDREDTH * a;
  result_int = result >>> 13;
end
使用ruby gem定点完成实数到二进制的转换

ruby irb会话(通过
gem安装固定点安装):


由于65536/100不是一个精确的值,您需要进行一些分析,以确保结果在您的误差范围内。更多的位可能会有所帮助,或者你可以用更少的位来解决问题。你是在暗示实现一个移位-加法体系结构,用“除以二的幂作为除法的和”吗?在多个时钟周期内实现这一点是很常见的。@Morgan,你当然可以使用流水线技术,每个周期进行一次加法,或者你可以使用一些不动产来制作一个3输入加法器,然后在一个周期内进行加法。@Morgan实际上先除后加是非常不准确的-对于0-65535的输入范围,36016被1(两个方向)关闭和5580的差距是2!如果将表达式更改为
((n@Mark,是的,尽管有C代码,但在我的脑海中,我想象的是一个加法器,在“小数点”的右边扩展到12位,即a(16-7+12=)21位加法器。您的表达式是一个很好的表达方式,因为它清楚地表明,我们需要至少16+5位才能达到完全的精度。感谢您的分析。请注意,原始问题包括“结果不必精确,近似即可。”这可能适用于装配级优化,但OPs“计时”指的是通过逻辑的传播延迟。与此处表示的数字相乘将导致需要一个大的乘法器,32x32,这可能不可用。@Morgan:我理解,但我打算为实现该目标简化一个级别.乘法逻辑比除法逻辑更容易进行近似和优化,因此,如果OP想要在速度和精度之间进行权衡,则需要操纵常数和右移以满足该要求。
// 1Int, 13Frac
wire ONE_HUNDREDTH = 14'b0_0000001010001 ; 

input  a         [15:0];    //Integer (no fractional bits)
output result [15+14:0];    //13 fractional bits inherited form ONE_HUNDREDTH
output result_int [15:0];   //Integer result

always @* begin
  result     = ONE_HUNDREDTH * a;
  result_int = result >>> 13;
end
require 'fixed_point'
#Unsigned, 1 Integer bit, 13 fractional bits
format  = FixedPoint::Format.new(0, 1, 13)
fix_num = FixedPoint::Number.new(0.01, format )
 => 0.0098876953125
fix_num.to_b
 => "0.0000001010001"