Polyphonyって何だろう(4)使用例:CORDIC with @pure & unroll

Python で書いた関数を Verilog HDL に変換する高位合成コンパイラである Polyphony を使ってみたいと思います。
今回はコンパイル時に python の計算値を埋め込むための @pure デコレーターと、コンパイルの最適化指示の unroll を使用した CORDIC を高位合成してみたいと思います。

Polyphony を使う上での注意点と期待

前回も記載しましたが、polyphony を使用する上で引っ掛かりそうな点を再度書いておきます。

  1. コメントに日本語を使わない ( utf-8 だとエラーになるようだ)
  2. y = 2 ** x とかは NG
  3. pure を使うには env.py の enable_pure = False を True に書き換える必要がある。
  4. 標準以外のライブラリーを import するとエラーになる ( numpy 等が使えない)
  5. Testbench で モジュールの戻り値を取得しないとシミュレーションができない。     NG: cordic(y,x) NG -> OK: r = cordic(y,x) ; print(r)

@pure デコレーターを使うと、コンパイル時にその関数を exec で実行して、結果を埋め込んでくれますが、@pure を付けた関数内でも使用できるライブラリーに制限があるようです。numpy は import しただけでエラーになるようです。math は使えるので最低限の関数は使えますが、できれば何でも使えるようになると便利だのですが。
@pure はコンパイル時に python を実行するだけなので、アルゴリズムデバッグのために、結果を処理するのには使えません。python でのアルゴリズムデバッグを考えると、単純にコンパイル時には読み飛ばしてくれるような表記方法があると便利だと思います。たとえば、現状は、print文での出力もformat処理して書き出せないので、コンパイル時にはコメントアウトする必要がありますが、無視するような指示ができれば、コメントアウトの必要がなくなります。また、結果を matplotlib でグラフ表示して確認したりもできるでしょう。今後に期待したいです。

CORDIC とは

CORDIC は、COordinate Rotation DIgital Computer の略で、反復的なシフト加算演算だけで、正弦関数、余弦関数、逆正弦関数の計算をする方法です。
下記東海大学遠藤研究室の「サルでも分かるCORDICアルゴリズム」が分かりやすいと思います。
http://teamcoil.sp.u-tokai.ac.jp/calculators/column/100224/index.html

ここでは、固定小数点でサイン、コサイン、アークタンジェントの計算を記述します。

Python コード (cordic.py) と簡単な説明

@pure がついている関数は、コンパイル時に実行されて結果のみが組み込まれます。CORDICで使用するテーブルを計算する部分、アルゴリズムの確認用に0度から90度まで5度置きの角度データとその正弦、余弦の固定小数点データを testbench 用に作成する関数を定義するのに使用しています。CORDICの反復計算部分で unroll 指示で最適化しています。

import math
import polyphony
from polyphony import module, pure, is_worker_running, rule, unroll
from polyphony import testbench
from polyphony.io import Port
from polyphony.typing import bit, bit9, bit24, int32
from polyphony.timing import clksleep, clkfence, wait_value, wait_rising

MAXINDEX = 14
FIXBIT = 20
TESTNUM = 19

@pure
def power(x):
    r = pow(2,x)
    return r

@pure
def float2bit(x):
    r = int(x*FIXMUL + 0.5)
    return r

@pure
def bit2float(x):
    r = x / FIXMUL
    return r

@pure
def mktable():
    theta = [0] * MAXINDEX
    for i in range(MAXINDEX):
        r = float2bit(math.degrees(math.atan(1/(2**i))))
        theta[i] = r
    return theta

@pure
def mkst():
    r = 1
    for i in range(MAXINDEX):
        r = r * (math.sqrt(1+1/(2**i)**2))
    s = int(1/r*FIXMUL+0.5)
    return s

@pure
def mksin(x):
    r = int(math.sin(math.radians(x))*FIXMUL+0.5)
    return r

@pure
def mkcos(x):
    r = int(math.cos(math.radians(x))*FIXMUL+0.5)
    return r

@pure
def mktdata():
    tdata = [0] * TESTNUM
    for i in range(TESTNUM):
        d = i * 5
        tdata[i] = float2bit(d)
    return tdata

@pure
def mksdata():
    s = [0] * TESTNUM
    for i in range(TESTNUM):
        d = i * 5
        s[i] = float2bit(math.sin(math.radians(d)))
    return s

@pure
def mkcdata():
    c = [0] * TESTNUM
    for i in range(TESTNUM):
        d = i * 5
        c[i] = float2bit(math.cos(math.radians(d)))
    return c

FIXMUL = power(FIXBIT)
ST = mkst()
THETA = mktable()
TDATA = mktdata()
SDATA = mksdata()
CDATA = mkcdata()

# %% Module
def cordic_atan(c,s):
    angle = 0
    for i in unroll(range(MAXINDEX)):
        if ( s < 0):
            c_next = c - (s >> i)
            s_next = s + (c >> i)
            angle += THETA[i]
        else:
            c_next = c + (s >> i)
            s_next = s - (c >> i)
            angle -= THETA[i]
        c = c_next
        s = s_next
    return -angle

def cordic_cs(x):
    c = ST
    s = 0
    angle = 0
    for i in unroll(range(MAXINDEX)):
        if (x >= angle):
            c_next = c - (s >> i)
            s_next = s + (c >> i)
            angle += THETA[i]
        else:
            c_next = c + (s >> i)
            s_next = s - (c >> i)
            angle -= THETA[i]
        c = c_next
        s = s_next
    return(c,s)

def cordic_sin(x):
    c,s = cordic_cs(x)
    return(s)

def cordic_cos(x):
    c,s = cordic_cs(x)
    return(c)

# %%
@testbench
def cordic_test():
    #atancheck
    print('sin')
    for i in range(TESTNUM):
        s = cordic_sin(TDATA[i])
        print(SDATA[i],s)
#        print((i * 5),bit2float(s))
    print('cos')
    for i in range(TESTNUM):
        c = cordic_cos(TDATA[i])
        print(CDATA[i],c)
#        print((i * 5),bit2float(c))
    print('atan')
    for i in range(TESTNUM):
        t = cordic_atan(CDATA[i],SDATA[i])
        print(TDATA[i],t)
#        print((i * 5),bit2float(t))

    clksleep(10)

# %%
cordic_test()

実行結果

Python でそのまま実行すると 下記のような出力が得られます。
python関数で計算した時の値とCORDICで計算した時の値を出力しています。CORDIC は反復計算で真値に収束させていく手法なので、打切り誤差が生じているのが分かると思います。

sin
0 80
91389 91455
182083 182013
271391 271315
358634 358653
443147 443039
524288 524385
601438 601530
674012 674080
741455 741489
803256 803199
858943 858880
908093 908039
950333 950382
985339 985330
1012847 1012868
1032646 1032659
1044586 1044580
1048576 1048577
cos
1048576 1048577
1044586 1044580
1032646 1032659
1012847 1012868
985339 985330
950333 950382
908093 908039
858943 858880
803256 803199
741455 741422
674012 674080
601438 601530
524288 524385
443147 443039
358634 358653
271391 271315
182083 182013
91389 91455
0 80
atan
0 4444
5242880 5246580
10485760 10481586
15728640 15724072
20971520 20972414
26214400 26207544
31457280 31463654
36700160 36706710
41943040 41948152
47185920 47188686
52428800 52423688
57671680 57665130
62914560 62908186
68157440 68164296
73400320 73399426
78643200 78647768
83886080 83890254
89128960 89125260
94371840 94367396

polyphony でコンパイルしてシミュレーションした時の結果が下記になります。unroll で結構速く動いています。

f:id:feynman911:20190123225210j:plain
CORDIC
実行環境:Win10 python3.7.1 polyphony0.3.4  iverilog0.9.7