如何在Verilog中执行两个Q15值的划分,而不使用' /' (师)运营商?

时间:2017-01-02 23:07:23

标签: format verilog fixed-point

在FPGA的情况下,除法运算(/)是否昂贵?是否可以通过基本移位操作执行两个Q15格式编号(16位定点编号)的划分?

有人可以提供一些例子来帮助我吗?

提前致谢!

1 个答案:

答案 0 :(得分:0)

定点运算只是整数运算,抛出了一些比例.Q15是一个纯粹的小数格式,存储为带符号的16位整数,比例因子为2 15 ,能够表示区间[-1,1]中的值。很明显,除数的大小超过了被除数的大小时,除法中的商数超过可表示的范围时,除法仅在Q15中有意义。

在着手定制Verilog定点分区之前,您需要检查FPGA供应商的库产品,作为定点库,包括管道分区。还有一些可能相关的源项目,例如this one

当使用整数除法运算符进行定点除法时,我们需要调整除法除去比例因子的事实,即(a * 2 scale )/(b * 2 < sup> scale )=(a / b),而正确的定点结果是(a / b * 2 scale )。这可以通过将被除数乘以2 scale 来轻松解决,如下面的C实现:

int16_t div_q15 (int16_t dividend, int16_t divisor)
{
     return (int16_t)(((int32_t)dividend << 15) / (int32_t)divisor);
}

Wikipedia对如何使用加,减和移位操作逐位实现二进制除法进行了合理的研究。这些方法与小学教授的长手分工密切相关。对于FPGA,如this paper所指出的那样,如果经常使用非恢复方法,则使用非恢复方法,例如:

Nikolay Sorokin,“在FPGA上实现高速定点分频器”。 计算机科学与技术杂志技术,Vol。 2006年4月6日第1期,第8-11页。

这是C代码,显示了非恢复方法如何用于16位二进制补码操作数的划分:

/* bit-wise non-restoring two's complement division */
void int16_div (int16_t dividend, int16_t divisor, int16_t *quot, int16_t *rem)
{
    const int operand_bits = (int) (sizeof (int16_t) * CHAR_BIT);
    uint16_t d = (uint16_t)divisor;
    uint16_t nd = 0 - d; /* -divisor */
    uint16_t r, q = 0; /* remainder, quotient */
    uint32_t dd = (uint32_t)d << operand_bits; /* expanded divisor */
    uint32_t pp = dividend; /* partial remainder */
    int i;

    for (i = operand_bits - 1; i >= 0; i--) {
        if ((int32_t)(pp ^ dd) < 0) {
            q = (q << 1) + 0;   /* record quotient bit -1 (as 0) */
            pp = (pp << 1) + dd;
        } else {
            q = (q << 1) + 1;   /* record quotient bit +1 (as 1) */
            pp = (pp << 1) - dd;
        }
    }
    /* convert quotient from digit set {-1,1} to plain two's complement */
    q = (q << 1) + 1;

    /* remainder is upper half of partial remainder */
    r = (uint16_t)(pp >> operand_bits);

    /* fix up cases where we worked past a partial remainder of zero */
    if (r == d) { /* remainder equal to divisor */
        q = q + 1;
        r = 0;
    } else if (r == nd) { /* remainder equal to -divisor */
        q = q - 1;
        r = 0;
    }

    /* for truncating division, remainder must have same sign as dividend */
    if (r && ((int16_t)(dividend ^ r) < 0)) {
        if ((int16_t)q < 0) {
            q = q + 1;
            r = r - d;
        } else {
            q = q - 1;
            r = r + d;
        }
    }
    *quot = (int16_t)q;
    *rem  = (int16_t)r;
}

请注意,有多种方法可以处理非恢复分部中出现的各种特殊情况。例如,经常看到检测到零部分余数pp的代码,并且在这种情况下早期退出超过商位的循环。在这里,我假设FPGA实现将完全展开循环以创建流水线实现,在这种情况下,提前终止没有帮助。相反,最终修正适用于那些受忽略零部分余数影响的商数。

为了从上面创建一个Q15部门,我们只需做一个改变:结合股息的扩大规模。而不是:

uint32_t pp = dividend; /* partial remainder */

我们现在用这个:

uint32_t pp = dividend << 15; /* partial remainder; incorporate Q15 scaling */

生成的C代码(抱歉,我不会提供读取使用的Verilog代码)包括测试框架:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>
#include <math.h>

/* bit-wise non-restoring two's complement division */
void q15_div (int16_t dividend, int16_t divisor, int16_t *quot, int16_t *rem)
{
    const int operand_bits = (int) (sizeof (int16_t) * CHAR_BIT);
    uint16_t d = (uint16_t)divisor;
    uint16_t nd = 0 - d; /* -divisor */
    uint16_t r, q = 0; /* remainder, quotient */
    uint32_t dd = (uint32_t)d << operand_bits; /* expanded divisor */
    uint32_t pp = dividend << 15; /* partial remainder, incorporate Q15 scaling */
    int i;

    for (i = operand_bits - 1; i >= 0; i--) {
        if ((int32_t)(pp ^ dd) < 0) {
            q = (q << 1) + 0;   /* record quotient bit -1 (as 0) */
            pp = (pp << 1) + dd;
        } else {
            q = (q << 1) + 1;   /* record quotient bit +1 (as 1) */
            pp = (pp << 1) - dd;
        }
    }
    /* convert quotient from digit set {-1,1} to plain two's complement */
    q = (q << 1) + 1;

    /* remainder is upper half of partial remainder */
    r = (uint16_t)(pp >> operand_bits);

    /* fix up cases where we worked past a partial remainder of zero */
    if (r == d) { /* remainder equal to divisor */
        q = q + 1;
        r = 0;
    } else if (r == nd) { /* remainder equal to -divisor */
        q = q - 1;
        r = 0;
    }

    /* for truncating division, remainder must have same sign as dividend */
    if (r && ((int16_t)(dividend ^ r) < 0)) {
        if ((int16_t)q < 0) {
            q = q + 1;
            r = r - d;
        } else {
            q = q - 1;
            r = r + d;
        }
    }
    *quot = (int16_t)q;
    *rem  = (int16_t)r;
}

int main (void)
{
    uint16_t dividend, divisor, ref_q, res_q, res_r;
    double quot, fxscale = (1 << 15);

    dividend = 0;
    do {
        printf ("\r%04x", dividend);
        divisor = 1;
        do {
            quot = trunc (fxscale * (int16_t)dividend / (int16_t)divisor);
            /* Q15 can only represent numbers in [-1, 1) */
            if ((quot >= -1.0) && (quot < 1.0)) {
                ref_q = (int16_t)((((int32_t)(int16_t)dividend) << 15) / 
                                  ((int32_t)(int16_t)divisor));
                q15_div ((int16_t)dividend, (int16_t)divisor, 
                         (int16_t *)&res_q, (int16_t *)&res_r);
                if (res_q != ref_q) {
                    printf ("!r dividend=%04x (%f) divisor=%04x (%f)  res=%04x (%f)  ref=%04x (%f)\n", 
                            dividend, (int16_t)dividend / fxscale, 
                            divisor, (int16_t)divisor / fxscale, 
                            res_q, (int16_t)res_q / fxscale, 
                            ref_q, (int16_t)ref_q / fxscale);
                }
            }
            divisor++;
        } while (divisor);
        dividend++;
    } while (dividend);

    return EXIT_SUCCESS;
}