· 

IEEE754をかじる(2)

IEEE754はコンピュータ内での浮動小数点数の表現方法に関する決まり事です。FPGAでフィルタを実現するにあたって、計算結果の出力時にこの知識が必要です。Intel(ALTERA)のFPGAをQuartusで記述するなら、ALTFP_CONVERTっていうIPがありますが、自力でどうするかを考える。

C言語にて0以上1未満の浮動小数点数を指定したビット幅の整数に変換することができたので、VHDLで実装します。

一度C言語で実装しているので、ちゃちゃっと。 ただし16bit限定。16bitより小さくするなら、結果をさらに右シフトする、FPGAハード的には内部結線しないとか、外部結線しないとかってことで。

 

float2uint.vhd

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
--use ieee.numeric_std.all;

entity Float2UI32 is
    port (
        i_src : in std_logic_vector(31 downto 0);
        o_ui32 : out std_logic_vector(31 downto 0)
    );
end Float2UI32;

architecture ppl_type of Float2UI32 is
    signal nbit : std_logic_vector(7 downto 0):=X"10";
    signal sign1 : std_logic;
    signal expn8 : std_logic_vector(7 downto 0);
    signal frac23 : std_logic_vector(31 downto 0);

    -- "https://github.com/ghdl/ghdl/blob/master/libraries/ieee/numeric_std-body.vhdl"
    function XSLL (ARG: STD_LOGIC_VECTOR; COUNT: NATURAL) return STD_LOGIC_VECTOR is
        constant ARG_L: INTEGER := ARG'LENGTH-1;
        alias XARG: STD_LOGIC_VECTOR(ARG_L downto 0) is ARG;
        variable RESULT: STD_LOGIC_VECTOR(ARG_L downto 0) := (others => '0');
    begin
        if COUNT <= ARG_L then
            RESULT(ARG_L downto COUNT) := XARG(ARG_L-COUNT downto 0);
        end if;
        return RESULT;
    end XSLL;

    function XSRL (ARG: STD_LOGIC_VECTOR; COUNT: NATURAL) return STD_LOGIC_VECTOR is
        constant ARG_L: INTEGER := ARG'LENGTH-1;
        alias XARG: STD_LOGIC_VECTOR(ARG_L downto 0) is ARG;
        variable RESULT: STD_LOGIC_VECTOR(ARG_L downto 0) := (others => '0');
    begin
        if COUNT <= ARG_L then
            RESULT(ARG_L-COUNT downto 0) := XARG(ARG_L downto COUNT);
        end if;
        return RESULT;
    end XSRL;

begin
    sign1<=i_src(31);
    expn8<=i_src(30 downto 23);
    frac23<="000000000"&i_src(22 downto 0);
    o_ui32<=X"00000000" when expn8+nbit<127 else
        XSLL(X"00000001",0) or XSRL(frac23,23) when expn8+nbit=127 else
        XSLL(X"00000001",1) or XSRL(frac23,22) when expn8+nbit=128 else
        XSLL(X"00000001",2) or XSRL(frac23,21) when expn8+nbit=129 else
        XSLL(X"00000001",3) or XSRL(frac23,20) when expn8+nbit=130 else
        XSLL(X"00000001",4) or XSRL(frac23,19) when expn8+nbit=131 else
        XSLL(X"00000001",5) or XSRL(frac23,18) when expn8+nbit=132 else
        XSLL(X"00000001",6) or XSRL(frac23,17) when expn8+nbit=133 else
        XSLL(X"00000001",7) or XSRL(frac23,16) when expn8+nbit=134 else
        XSLL(X"00000001",8) or XSRL(frac23,15) when expn8+nbit=135 else
        XSLL(X"00000001",9) or XSRL(frac23,14) when expn8+nbit=136 else
        XSLL(X"00000001",10) or XSRL(frac23,13) when expn8+nbit=137 else
        XSLL(X"00000001",11) or XSRL(frac23,12) when expn8+nbit=138 else
        XSLL(X"00000001",12) or XSRL(frac23,11) when expn8+nbit=139 else
        XSLL(X"00000001",13) or XSRL(frac23,10) when expn8+nbit=140 else
        XSLL(X"00000001",14) or XSRL(frac23,9) when expn8+nbit=141 else
        XSLL(X"00000001",15) or XSRL(frac23,8) when expn8+nbit=142 else
        X"FFFFFFFF";
end;

expn+nbit-127が0以上15未満であることが所望のビット数内に収めるための条件ってことは、expn+nbitは127, 128, 129, ..., 142しかあり得ないので、全条件を羅列して一発判定。左シフト、右シフトがうちのghdlには実装されてなかったので、functionとして追加。ここから持ってきた。このXSLLとXSRLは第2引数はconstantじゃないといけないってのもあって、全条件をwhenで記述したってのもある。結果的にはC言語よりもすんなりいったね。

では、テストベンチを作る。

 

float2uint_tb.vhd

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.std_logic_unsigned.ALL;

ENTITY float2uint_tb IS
END float2uint_tb;

ARCHITECTURE SIM OF float2uint_tb IS
    COMPONENT Float2UI32 IS
        port (
            i_src : in std_logic_vector(31 downto 0);
            o_ui32 : out std_logic_vector(31 downto 0)
        );
    END COMPONENT;

    SIGNAL src : std_logic_vector(31 DOWNTO 0) := X"00000000";
    SIGNAL ui32 : std_logic_vector(31 DOWNTO 0);
    SIGNAL clksys : STD_LOGIC:='0';
    CONSTANT STEPSYS : TIME := 20 ns;
    SIGNAL clk_count : INTEGER RANGE 0 TO 1048575:=0;
    CONSTANT clk_max : INTEGER := 101;
    subtype ui32_value is std_logic_vector(31 downto 0);
    type ui32_values_type is array(0 to 100) of ui32_value;
    constant ui32_values : ui32_values_type:=(
        X"3F1B5976",X"3E09B808",X"3EAF9985",X"3F65D328",X"3F1E7567",X"3F5C86C9",X"3F496F94",X"3F59E4F8",X"3F74CF79",X"3F038873",
        X"3F14BC71",X"3DCD6D8B",X"3E684348",X"3ECBC497",X"3ECBF63C",X"3F4A92D0",X"3E30C466",X"3E01EF5B",X"3ED009D4",X"3EA9F195",
        X"3EFC3D2B",X"3EF9B064",X"3E42504F",X"3DA95955",X"3F33B1AE",X"3F648BEB",X"3F573486",X"3F14545A",X"3DF1EE69",X"3DC46A1A",
        X"3D43D250",X"3F399743",X"3E6BED15",X"3EC813CF",X"3F1F6A6B",X"3F5970AC",X"3E812161",X"3ED1B3FE",X"3F3355A4",X"3E5580A7",
        X"3F6C6272",X"3E90242A",X"3E9E1BB6",X"3E19CD11",X"3F2DF460",X"3F3508F9",X"3F710614",X"3F5A257A",X"3F5584D0",X"3EB215FC",
        X"3E3C7910",X"3EA746CB",X"3F55E330",X"3EBF64B0",X"3ED19D21",X"3F0994DE",X"3E887C85",X"3E80062C",X"3DEF49C4",X"3EC4F820",
        X"3EB120B3",X"3E289976",X"3DE09A98",X"3F138B9F",X"3F0E3045",X"3F3B7DBE",X"3ED9F896",X"3F4EC0F5",X"3E115EF4",X"3E0147BC",
        X"3C8423DC",X"3D85D17A",X"3ED0C808",X"3EA65DF4",X"3E5CB5CD",X"3DB2C322",X"3D037F35",X"3E20CE1E",X"3F707DDE",X"3F5DBCC3",
        X"3F013E86",X"3DFCE10F",X"3E4580A4",X"3EAE436C",X"3EFE9CF3",X"3F1A2EB9",X"3F60B694",X"3F438CBC",X"3F5A31D0",X"3F7E9FCD",
        X"3E182331",X"3E4B08A4",X"3E2318A8",X"3E84383E",X"3F464DC8",X"3F36F66F",X"3F7D99DD",X"3E4D284B",X"3F05B764",X"3E07C668",
        X"3EA73804"
    );
BEGIN
    Float2UI32_inst : COMPONENT Float2UI32 PORT MAP(
        i_src=>src,
        o_ui32=>ui32 
    );
    clksys <= not(clksys) after STEPSYS/2;
    process(clksys)begin
        if clksys'event and clksys='0' then
            if clk_count=clk_max then
                assert false
                report "Finish"
                severity failure;       
            end if;                
            src<=ui32_values(clk_count);
            clk_count<=clk_count+1;
        end if;
    end process;
END;

Makefile

VHDLC=/usr/bin/ghdl
SRC=float2uint.vhd
TBSRC=float2uint_tb.vhd
VCD=float2uint.vcd
SIMRTL=float2uint_tb

all :
        @make comp
        @make tb
        @make tb_e
        @make vcd

comp :
        $(VHDLC) -a --ieee=synopsys -fexplicit $(SRC)

tb :
        $(VHDLC) -a --ieee=synopsys -fexplicit $(TBSRC)

tb_e :
        $(VHDLC) -e --ieee=synopsys -fexplicit $(SIMRTL)

vcd :
        $(VHDLC) -r --ieee=synopsys -fexplicit $(SIMRTL) --vcd=$(VCD)

テストベンチでは1clockごとに入力を変えていくが、この入力値はどう作ったかというと、

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>

typedef struct{
    uint32_t frac:23;
    uint32_t expn:8;
    uint32_t sign:1;
}tIEEE754;

typedef union{
    uint8_t asui8[4];
    uint32_t asui32;
    float asFloat;
    tIEEE754 asIEEE754;
}uFloat;

int main(int argc,char** argv){
    uint32_t nbit=16;
    uint32_t nshift;
    uint32_t ans;
    uFloat ufloat;
    int rnd;
    float rnd_as_float,rnd_max_as_float;
    rnd_max_as_float=(float)RAND_MAX;
    srand((unsigned)time(NULL));
    for(uint32_t i=0;i<101;i++){
        rnd=rand();
        rnd_as_float=((float)rnd)/rnd_max_as_float;
        ufloat.asFloat=rnd_as_float;
        printf("%d\t",i);
        printf("%08X\t",ufloat.asui32);
        printf("%.12f\t",ufloat.asFloat);
        printf("%.12f\t",ufloat.asFloat*(1<<nbit));
        if(ufloat.asIEEE754.expn+nbit>=127){
            nshift=ufloat.asIEEE754.expn+nbit-127;
            ans=(1<<(nshift))+(ufloat.asIEEE754.frac>>(23-nshift));
        }else{
            ans=0;
        }
        printf("%d\t",ans);
        printf("%08X\t",ans);
        printf("X\"%08X\",\n",ufloat.asui32);
    }
}

これを実行して出てきたものを加工した。

で、テストベンチを走らせた結果はこうなる。

答え合わせをすると、

あってるっぽい!クロックなしの論理回路だけで実現できたー!ktkr!がんばれば報われる。いや、報われないことも多い。たくさんの報われないことがあって、少しの報われることがある。どんだけがんばればいいんやー