FPGA
python3
Polyphony

[簡易版]固定小数点 浮動小数点

ずいぶんと昔の話になる。千里に住む従妹から指摘されたことがある。「東京は遅れている。何で自動改札が設置されないのかと。」関西では随分と前から自動改札が導入されていた。何が理由か知らないが関東では自動改札はなかなか導入されなかった。

いまや Suica に Apply Pay である。

あれば便利だが諸事情で導入されないものというものがあるのだ。

Polyphony は Python の掛算をそのまま Verilog-HDL のコードにコンパイルする。四則演算は Verilog-HDL の機能に準ずることになる。浮動小数点には簡単に対応できない。

このデメリットはいろんなところに波及するのだが、かといって、他の高位合成が浮動小数点機能において優れているかというとそうでもない。FPGA をベースにしている限りにおいてなかなかメリットにできていないようにも感じる。

Polyphony で固定小数点と浮動小数点と実装にチャレンジしてみよう。

固定小数点

固定小数点の足し算はただの足し算と同じなので掛け算を考えよう。

fix32.py
from polyphony import testbench, module, is_worker_running
from polyphony.timing import clksleep
from polyphony.io import Port
from polyphony.typing import bit, bit31, bit32, bit54

def fix32_mul(a:bit32, b:bit32, fn)->bit32:
    s:bit32 = (((a >> 31) & 1) ^ ((b >> 31) & 1)) << 31
    #print('sign:', s)
    a54:bit54 = a & 0x7FFFFFFF
    b54:bit54 = b & 0x7FFFFFFF
    #print('{0:08x} {1:08x} {2:08x}'.format(a54, b54, a54*b54))
    rv32:bit32 = ((a54 * b54) >> 23) & 0x8FFFFFFF 
    return (s | rv32)

def fix32_mul_9_23(a:bit32, b:bit32)->bit32:
    return fix32_mul(a, b, 23)

@testbench        
def test():
    n = fix32_mul_9_23(5 << 23, (4 << 23) + (1 << 22))
    print(n >> (23 + 8))
    print((n >> 23) & 0xFF)
    print(n & 0x7FFFFF)

    n = fix32_mul_9_23((0x100 + 5) << 23, (4 << 23) + (1 << 22))
    print(n >> (23 + 8))
    print((n >> 23) & 0xFF)
    print(n & 0x7FFFFF)

    n = fix32_mul_9_23((0x100 + 5) << 23, ((0x100 + 4) << 23) + (1 << 22))
    print(n >> (23 + 8))
    print((n >> 23) & 0xFF)
    print(n & 0x7FFFFF)

    n = fix32_mul_9_23((0x000 + 5) << 23, ((0x100 + 4) << 23) + (1 << 22))
    print(n >> (23 + 8))
    print((n >> 23) & 0xFF)
    print(n & 0x7FFFFF)

test()

Polyphony は関数の呼び出しをインライン化する。fix32_mul_9_23 は fix32 を呼び出しているが、これはコストゼロだ。テストベンチはもっと増やさないと実アプリケーションに使うのには不安だ。

浮動小数点

float32.py
from polyphony import testbench, module, is_worker_running
from polyphony.timing import clksleep
from polyphony.io import Port
from polyphony.typing import bit, bit31, bit32, bit54

def fix32_mul(a:bit32, b:bit32, fn)->bit32:
    s:bit32 = (((a >> 31) & 1) ^ ((b >> 31) & 1)) << 31
    #print('sign:', s)
    a54:bit54 = a & 0x7FFFFFFF
    b54:bit54 = b & 0x7FFFFFFF
    #print('{0:08x} {1:08x} {2:08x}'.format(a54, b54, a54*b54))
    rv32:bit32 = ((a54 * b54) >> 23) & 0x8FFFFFFF 
    return (s | rv32)

def fix32_mul_9_23(a:bit32, b:bit32)->bit32:
    return fix32_mul(a, b, 23)

@testbench        
def test():
    n = fix32_mul_9_23(5 << 23, (4 << 23) + (1 << 22))
    print(n >> (23 + 8))
    print((n >> 23) & 0xFF)
    print(n & 0x7FFFFF)

    n = fix32_mul_9_23((0x100 + 5) << 23, (4 << 23) + (1 << 22))
    print(n >> (23 + 8))
    print((n >> 23) & 0xFF)
    print(n & 0x7FFFFF)

    n = fix32_mul_9_23((0x100 + 5) << 23, ((0x100 + 4) << 23) + (1 << 22))
    print(n >> (23 + 8))
    print((n >> 23) & 0xFF)
    print(n & 0x7FFFFF)

    n = fix32_mul_9_23((0x000 + 5) << 23, ((0x100 + 4) << 23) + (1 << 22))
    print(n >> (23 + 8))
    print((n >> 23) & 0xFF)
    print(n & 0x7FFFFF)

test()

そもそもの浮動小数点の数字を作るの苦労する。Python 的にも効率よく書けていない。それでも出来てしまうところが高位合成の良いところだ。IEEE で規定されているが普通の CPU ではなかなかサポートしてない規格(たとえば 128bit?) なども比較的簡単にできそうだ。

実用度は?

数値計算に必要とされる基礎技術を高位合成で使おうと思うと、もうひとひねり必要そうだ。例えば、精度付きの演算などは FPGA での価値が高いと予測している。今後はそういったライブラリの提供も重要になってくるだろう。

最早、Polyphony は単に高位合成がでますよというツールではない。パイプラインと unroll の機能を入れたことで大きく適用範囲は広がった。

とはいえ「コンパイラあります」の張り紙をしているだけで、誰かが気が付いて、積極的に使ってくれるというものでもなかろう。ライブラリの充実をして、使えるツールにするのにはあともう一歩の前進が必要だ。

おまけ

検証用の C コード

float32.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int
main(int argc, char **argv)
{
    int a_sign, a_exp, a_fract;
    int b_sign, b_exp, b_fract;
    uint32_t a_int, b_int, rv_int;
    float a_float, b_float, rv_float;

    a_sign = atoi(argv[1]);
    a_exp = atoi(argv[2]);
    a_fract = atoi(argv[3]);

    a_int = ((a_sign & 1) << 31) | ((a_exp & 0xFF) << 23) | (a_fract & 0x7FFFFF);

    b_sign = atoi(argv[4]);
    b_exp = atoi(argv[5]);
    b_fract = atoi(argv[6]);

    b_int = ((b_sign & 1) << 31) | ((b_exp & 0xFF) << 23) | (b_fract & 0x7FFFFF);

    a_float = *((float *)&a_int);
    b_float = *((float *)&b_int);
    rv_float = a_float + b_float;
    printf("%f + %f => %f\n", a_float, b_float, rv_float);
    rv_int = *((uint32_t*)(&rv_float));
    printf("%08x\n", rv_int);
    printf("%d %d %d\n", (rv_int >> 31) & 1, (rv_int >> 23) & 0xFF, rv_int & 0x7FFFFF);

}