論理回路デザイン
ArchiTek home page
コーディング

コード(半精度乗算RTL)

/* ****************************** MODULE PREAMBLE ******************************

        Copyright (c) 2012, ArchiTek
        This document constitutes confidential and proprietary information
        of ArchiTek. All rights reserved.
*/

// ******************************* MODULE HEADER *******************************

module fmul (
        iVld,
        iStall,
        iA,
        iB,

        oVld,
        oStall,
        oY,

        reset,
        clk

        );

// *************************** I/O DECLARATIONS ********************************

        // Pipe Input
        input                   iVld;
        output                  iStall;
        input   [15:0]          iA;                     // Operand A
        input   [15:0]          iB;                     // Operand B

        // Pipe Output
        output                  oVld;
        input                   oStall;
        output  [15:0]          oY;                     // Result

        // Utility
        input                   reset;
        input                   clk;

// ************************** LOCAL DECLARATIONS *******************************

        // 便利信号の定義と同時に値を代入
        wire                    aSign           = iA[15];
        wire    [4:0]           aExpo           = iA[14:10];
        wire    [4:0]           aExp            = |aExpo ? aExpo : 5'h0f;
        wire    [10:0]          aFrac           = {|aExpo, iA[9:0]};
        wire                    aZero           = ~|iA[14:0];

        wire                    bSign           = iB[15];
        wire    [4:0]           bExpo           = iB[14:10];
        wire    [4:0]           bExp            = |bExpo ? bExpo : 5'h0f;
        wire    [10:0]          bFrac           = {|bExpo, iB[9:0]};
        wire                    bZero           = ~|iB[14:0];

        wire                    expMin;
        wire    [6:0]           expTmp;
        wire                    expShift;

        // 2ステージ分の信号を定義、末尾DはFFのD端子信号(確定ではない)
        reg                     vld_0;
        reg                     vld_1;
        wire                    stall_0;
        wire                    stall_1;

        reg                     ySign_0;
        reg                     ySign_1;

        reg     [6:0]           yExp_0;
        reg     [4:0]           yExp_1;
        reg     [4:0]           yExp_1D;

        reg     [12:0]          yFrac_0;
        wire    [21:0]          yFrac_0D;
        reg     [9:0]           yFrac_1;
        wire    [11:0]          yFrac_1D;

// ****************************** MODULE BODY **********************************

// -----------------------------------------------------------------------------
// Valid Path

// ---- Input
// 後方のStallを論理に加えバッファ型のステージに(以下同じ)
// 添え字の番号は、0をスタートに番号付けしたステージ後方の境界を示す
assign iStall           = vld_0 & stall_0;

// ---- Stage 0
always @(posedge clk)
        if (reset)
                vld_0           <= #1 1'b0;
        else if (!iStall)
                vld_0           <= #1 iVld;

assign stall_0          = vld_1 & stall_1;

// ---- Stage 1
always @(posedge clk)
        if (reset)
                vld_1           <= #1 1'b0;
        else if (!stall_0)
                vld_1           <= #1 vld_0;

assign stall_1          = oStall;

// ---- Alias
// ステージの結果を出力信号にマッピング
assign oVld             = vld_1;

// -----------------------------------------------------------------------------
// Sign Path

// ---- Stage 0 - Result
// 符号は単純な排他的論理和
always @(posedge clk)
        if (!iStall)
                ySign_0         <= #1 aSign ^ bSign;

// ---- Stage 1 - Delay
// 単にパイプするだけ
always @(posedge clk)
        if (!stall_0)
                ySign_1         <= #1 ySign_0;

// ---- Alias
// ステージの結果を出力信号にマッピング
assign oY[15]           = ySign_1;

// -----------------------------------------------------------------------------
// Exp Path

// ---- Stage 0 - Sum of Exp
// オペランドのいずれかが0なら指数も0、それ以外はBias分を引いた合計に
// 合計結果を2ビット拡張し、アンダーフローはMSB、オーバーフローはMSBの次のビットが表現
always @(posedge clk)
        if (!iStall)
                yExp_0          <= #1 (aZero | bZero)
                                        ? 7'd0
                                        : {2'd0, aExp} + {2'd0, bExp} - 7'h0f;

// ---- Stage 1 - Carry Fix
// 仮数部の計算結果が0およびアンダーフローの場合指数は0、計算結果ラッチ
always @(posedge clk)
        if (!stall_0)
                yExp_1          <= #1 yExp_1D;

always @(
        expTmp or
        expMin or
        yFrac_1D
        )
        casex ({|yFrac_1D, expMin})
                2'b0x,
                2'b11:  yExp_1D         = 5'h00;
                2'b10:  yExp_1D         = expTmp[4:0];
        endcase

// アンダーフロー
assign expMin           = expTmp[6];

// 仮数部どうしの1.xxxと1.xxxを掛けると指数はデフォルトで1加算(結果のMSBは2の位なので)
// 仮数部のMSBが0なら仮数部は左寄せするため、桁上がり分を補正(-1)
// ただし、仮数部のMSBが0でもまるめで桁上がりの可能性を仮数部の左寄せの結果のMSBを見て判断
assign expTmp           = yExp_0 - {6'd0, expShift} + 7'd1;
assign expShift         = expShiftFunc(yFrac_0) - yFrac_1D[11];

// ---- Alias
// ステージの結果を出力信号にマッピング
assign oY[14:10]        = yExp_1;

// -----------------------------------------------------------------------------
// Frac Path

// ---- Stage0 - Separate Mul
// 仮数部の乗算結果22ビットのうち上位13ビットをラッチ
always @(posedge clk)
        if (!iStall)
                yFrac_0         <= #1 yFrac_0D[21:9];

// 高速化のためEDAベンダーの用意する掛け算ライブラリを使う場合もある
assign yFrac_0D         = aFrac * bFrac;

// ---- Stage1 - Carry Fix and Remove Hidden MSB
// ラッチ時は最終的な左寄せを行う(左寄せ2回目)
always @(posedge clk)
        if (!stall_0)
                yFrac_1         <= #1 yFrac_1D[11]
                                        ? yFrac_1D[10:1]
                                        : yFrac_1D[9:0];

// 左寄せを行う(左寄せ1回目)
// MSBは指数の補正に利用する(これがなければFunction内で左寄せの処理は閉じれる)
assign yFrac_1D         = fracShiftFunc(yFrac_0);

// ---- Alias
// ステージの結果を出力信号にマッピング
assign oY[9:0]          = yFrac_1;

// ************************** FUNCTIONS and TASKS ******************************

// 仮数部のMSBをチェック、非正規化仮数部対応時は全ビットのチェックが必要
function expShiftFunc;
        input   [12:0]          frac;

        expShiftFunc    = !frac[12];
endfunction

// 左寄せの1回目とまるめ、まるめは左寄せ数により参照ビットが異なる
// 入力MSBが1ならまるめてもオーバーフローしない(carry信号は0)
// 入力MSBが0ならまるめるとオーバーフローする可能性がある(その場合carry信号は1)
// 非正規化仮数部対応時は全ビットのチェックと左寄せが必要で論理が深い
function [11:0] fracShiftFunc;
        input   [12:0]          frac;
        reg                     carry;
        reg     [10:0]          result;
        reg     [10:0]          adder0;
        reg     [10:0]          adder1;

        begin
                adder0          = {10'd0, frac[1]};
                adder1          = {10'd0, frac[0]};

                carry           = (frac[12] == 1'b0) & (&frac[11:0]);

                casex (frac[12])
                        1'h1:   result          = frac[12:2] + adder0;
                        1'h0:   result          = frac[11:1] + adder1;
                endcase

                fracShiftFunc   = {carry, result};
        end
endfunction

endmodule

// *****************************************************************************
        

コード(半精度加算RTL:ご参考)

/* ****************************** MODULE PREAMBLE ******************************

        Copyright (c) 2011, ArchiTek
        This document constitutes confidential and proprietary information
        of ArchiTek. All rights reserved.
        // 指数シフトが2以上あれば正規化が簡単になり、1以下であれば最初の桁合わせが簡単になる
        // 前者は最終段の正規化を前段に入れ込むことができ、後者は初段で桁合わせと加算を実行できる
        // それぞれ専用パスで回路を作れば、演算器は倍になるがレイテンシは2から1にすることができる
        // ここではその手法を採用していない
*/

// ******************************* MODULE HEADER *******************************

module fadd (
        iVld,
        iStall,
        iA,
        iB,

        oVld,
        oStall,
        oY,

        reset,
        clk

        );

// *************************** I/O DECLARATIONS ********************************

        // Pipe Input
        input                   iVld;
        output                  iStall;
        input   [15:0]          iA;                     // Operand A
        input   [15:0]          iB;                     // Operand B

        // Pipe Output
        output                  oVld;
        input                   oStall;
        output  [15:0]          oY;                     // Result

        // Utility
        input                   reset;
        input                   clk;

// ************************** LOCAL DECLARATIONS *******************************

        // 便利信号の定義と同時に値を代入
        // allDirはオペランドの大きい方から小さい方を引くための判断に使う
        // expDirはオペランドどうしの指数を合わせるため、指数の大小の判断に使う
        wire                    aSign           = iA[15];
        wire    [4:0]           aExpo           = iA[14:10];
        wire    [4:0]           aExp            = |aExpo ? aExpo : 5'h0f;
        wire    [10:0]          aFrac           = {|aExpo, iA[9:0]};
        wire                    aZero           = ~|iA[14:0];

        wire                    bSign           = iB[15];
        wire    [4:0]           bExpo           = iB[14:10];
        wire    [4:0]           bExp            = |bExpo ? bExpo : 5'h0f;
        wire    [10:0]          bFrac           = {|bExpo, iB[9:0]};
        wire                    bZero           = ~|iB[14:0];

        wire                    allDir          = ({aExp, aFrac} < {bExp, bFrac});
        wire                    expDir          = (aExp < bExp);

        wire    [4:0]           expAbs          = expDir ? bExp - aExp : aExp - bExp;
        wire    [3:0]           expShift;

        // 2ステージ分の信号を定義、末尾DはFFのD端子信号(確定ではない)
        reg                     vld_0;
        reg                     vld_1;
        wire                    stall_0;
        wire                    stall_1;

        reg                     ySign_0;
        reg                     ySign_0D;
        reg                     ySign_1;
        reg                     yInv_0;
        wire                    yInv_0D;
        reg                     aInv_0D;
        reg                     bInv_0D;

        reg     [4:0]           yExp_0;
        reg     [4:0]           yExp_1;
        reg     [4:0]           yExp_0D;
        wire    [4:0]           yExp_1D;

        reg     [10:0]          aFrac_0;
        reg     [10:0]          bFrac_0;
        wire    [10:0]          aFrac_0D;
        wire    [10:0]          bFrac_0D;
        reg     [10:0]          aFracBar;
        reg     [10:0]          bFracBar;
        reg     [10:0]          pFrac;
        wire    [10:0]          qFrac;

        reg     [9:0]           yFrac_1;
        wire    [11:0]          yFrac_1D;
        wire    [13:0]          yFracTmp;
        reg     [1:0]           yFracRound_0;
        wire    [1:0]           yFracRound_0D;

// ****************************** MODULE BODY **********************************

// -----------------------------------------------------------------------------
// Valid Path

// ---- Input
// 後方のStallを論理に加えバッファ型のステージに(以下同じ)
// 添え字の番号は、0をスタートに番号付けしたステージ後方の境界を示す
assign iStall           = vld_0 & stall_0;

// ---- Stage 0
always @(posedge clk)
        if (reset)
                vld_0           <= #1 1'b0;
        else if (!iStall)
                vld_0           <= #1 iVld;

assign stall_0          = vld_1 & stall_1;

// ---- Stage 1
always @(posedge clk)
        if (reset)
                vld_1           <= #1 1'b0;
        else if (!stall_0)
                vld_1           <= #1 vld_0;

assign stall_1          = oStall;

// ---- Alias
// ステージの結果を出力信号にマッピング
assign oVld             = vld_1;

// -----------------------------------------------------------------------------
// Sign Path

// ---- Stage 0 - Result
// オペランドの符号、0、大小関係から結果の符号を判断
// 特に減算の場合(符号が排他的)、いずれのオペランドの補数をとるかも決定
always @(posedge clk)
        if (!iStall) begin
                ySign_0         <= #1 ySign_0D;
                yInv_0          <= #1 yInv_0D;
        end

always @(
        allDir or
        aSign or
        bSign or
        aZero or
        bZero
        )
        casex ({aZero, bZero, aSign, bSign, allDir})
                5'b1xx0x:       // B's Sign
                        {ySign_0D, bInv_0D, aInv_0D}    = 3'b000;

                5'b1xx1x:       // B's Sign
                        {ySign_0D, bInv_0D, aInv_0D}    = 3'b100;

                5'b010xx:       // A's Sign
                        {ySign_0D, bInv_0D, aInv_0D}    = 3'b000;

                5'b011xx:       // A's Sign
                        {ySign_0D, bInv_0D, aInv_0D}    = 3'b100;

                5'b0000x:       // + (|A|+|B|)
                        {ySign_0D, bInv_0D, aInv_0D}    = 3'b000;

                5'b0011x:       // - (|A|+|B|)
                        {ySign_0D, bInv_0D, aInv_0D}    = 3'b100;

                5'b00010:       // + (|A|-|B|)
                        {ySign_0D, bInv_0D, aInv_0D}    = 3'b010;

                5'b00011:       // - (|B|-|A|)
                        {ySign_0D, bInv_0D, aInv_0D}    = 3'b101;

                5'b00100:       // - (|A|-|B|)
                        {ySign_0D, bInv_0D, aInv_0D}    = 3'b110;

                5'b00101:       // + (|B|-|A|)
                        {ySign_0D, bInv_0D, aInv_0D}    = 3'b001;
        endcase

assign yInv_0D          = aInv_0D | bInv_0D;

// ---- Stage 1 - Delay
always @(posedge clk)
        if (!stall_0)
                ySign_1         <= #1 ySign_0;

// ---- Alias
// ステージの結果を出力信号にマッピング
assign oY[15]           = ySign_1;

// -----------------------------------------------------------------------------
// Exp Path

// ---- Stage 0 - Put Large Exp
// 指数の大小関係から大きい方をラッチ、0の場合は他方をラッチ
always @(posedge clk)
        if (!iStall)
                yExp_0          <= #1 yExp_0D;

always @(
        expDir or
        aExp or
        bExp or
        aZero or
        bZero
        )
        casex ({aZero, bZero, expDir})
                3'b1xx: yExp_0D         = bExp;         // B's Exp
                3'b01x: yExp_0D         = aExp;         // A's Exp
                3'b000: yExp_0D         = aExp;         // A's Exp
                3'b001: yExp_0D         = bExp;         // B's Exp
        endcase

// ---- Stage 1 - Normalize
always @(posedge clk)
        if (!stall_0)
                yExp_1          <= #1 yExp_1D;

// 仮数部どうしの1.xxxと1.xxxを足すと指数はデフォルトで1加算(結果のMSBは2の位なので)
// 仮数部の左寄せ数はFunctionでチェック、その分の桁上がり分を補正
// ただし、仮数部のMSBが0でもまるめで桁上がりの可能性を仮数部の左寄せの結果のMSBを見て判断
assign yExp_1D          = |yFrac_1D
                                ? yExp_0 - {1'd0, expShift} + 5'd1
                                : 5'h00;

assign expShift         = expShiftFunc(yFracTmp) - {3'd0, yFrac_1D[11]};

// ---- Alias
// ステージの結果を出力信号にマッピング
assign oY[14:10]        = yExp_1;

// -----------------------------------------------------------------------------
// Frac Path

// ---- Stage0 - Fix Same Column
// 指数部の桁合わせをした仮数部とまるめ情報をラッチ
always @(posedge clk)
        if (!iStall) begin
                aFrac_0         <= #1 aFrac_0D;
                bFrac_0         <= #1 bFrac_0D;
                yFracRound_0    <= #1 yFracRound_0D;
        end

// 指数の大小関係からオペランドAが小さいなら下記の右寄せした結果を代入
always @(
        expDir or
        aZero or
        bZero or
        aFrac or
        qFrac
        )
        casex ({bZero, aZero, expDir})
                3'bx1x: aFracBar        = 11'h000;
                3'b001: aFracBar        = qFrac;
                default:
                        aFracBar        = aFrac;
        endcase

// 指数の大小関係からオペランドBが小さいなら下記の右寄せした結果を代入
always @(
        expDir or
        aZero or
        bZero or
        bFrac or
        qFrac
        )
        casex ({bZero, aZero, expDir})
                3'b1xx: bFracBar        = 11'h000;
                3'b000: bFracBar        = qFrac;
                default:
                        bFracBar        = bFrac;
        endcase

// 指数の小さい方を大きい方に合わせるため、仮数部を右寄せ
always @(
        expDir or
        aZero or
        bZero or
        aFrac or
        bFrac
        )
        casex ({bZero, aZero, expDir})
                3'bx1x: pFrac           = aFrac;
                3'b10x: pFrac           = bFrac;
                3'b001: pFrac           = aFrac;
                3'b000: pFrac           = bFrac;
        endcase

assign qFrac            = bsftFunc(pFrac, expAbs);

// 符号のところで求めた仮数部の補数の必要性から、先ずは1の補数を計算
// 補数を取るのはどちらか一方で、2つ同時はない
assign aFrac_0D         = compFunc(aFracBar, aInv_0D);
assign bFrac_0D         = compFunc(bFracBar, bInv_0D);

// 下記の右寄せで捨てられるビットのMSBの2ビットをまるめ情報として取得
// 同時に補数の必要性を見て1の補数を実行(減算時は引く方が小さくなっている)
assign yFracRound_0D    = {2{yInv_0D}} ^ roundFunc(pFrac, expAbs);

// ---- Stage1 - 2's Comlemet Add become always plus value
// ラッチ時は最終的な左寄せを行う(左寄せ2回目)
always @(posedge clk)
        if (!stall_1)
                yFrac_1         <= #1 yFrac_1D[11]
                                        ? yFrac_1D[10:1]
                                        : yFrac_1D[9:0];

// 左寄せを行う(左寄せ1回目)
// MSBは指数の補正に利用する(これがなければFunction内で左寄せの処理は閉じれる)
assign yFrac_1D         = fracShiftFunc(yFracTmp);

// 加算の本体、まるめと2の補数の+1をまとめて計算
assign yFracTmp         = {1'd0, aFrac_0, 2'd0}
                        + {1'd0, bFrac_0, 2'd0}
                        + {12'd0, yFracRound_0}
                        + {yInv_0, 12'd0, yInv_0};

// ---- Alias
// ステージの結果を出力信号にマッピング
assign oY[9:0]          = yFrac_1;

// ************************** FUNCTIONS and TASKS ******************************

// 1の補数
function [10:0] compFunc;
        input   [10:0]          frac;
        input                   sign;

        begin
                compFunc        = sign ? ~frac : frac;
        end
endfunction

// 指数に従ったバレルシフト、指数はオペランドの指数の差分の絶対値(Priority encoder)
function [10:0] bsftFunc;
        input   [10:0]          frac;
        input   [4:0]           exp;
        reg     [5:0]           idx;
        reg     [10:0]          result;
        integer                 i;

        begin
                for (i=0; i<11; i=i+1) begin
                        idx             = {1'b0, exp} + i[5:0];
                        if (idx > 6'd10)
                                result[i]       = 1'b0;
                        else
                                result[i]       = frac[idx];
                end

                bsftFunc        = result;
        end
endfunction

// 上記バレルシフトで捨てるビットの上位2ビットを選択(Priority encoder)
function [1:0] roundFunc;
        input   [10:0]          frac;
        input   [4:0]           exp;
        reg     [5:0]           idx;
        reg     [10:0]          result;
        integer                 i;

        begin
                for (i=0; i<11; i=i+1) begin
                        idx             = {1'b0, exp} + i[5:0] - 6'd11;
                        if (idx[5] | (idx[4:0] > 5'd10))
                                result[i]       = 1'b0;
                        else
                                result[i]       = frac[idx];
                end

                roundFunc       = result[10:9];
        end
endfunction

// 仮数部の有効桁をチェック、全ビットのチェックを実施(Priority encoder)
function [3:0] expShiftFunc;
        input   [13:0]          frac;
        reg     [3:0]           idx;
        integer                 i;

        begin
                idx             = 4'd10;

                for (i=0; i<12; i=i+1)
                        if (frac[i+2])
                                idx             = i[3:0];

                expShiftFunc    = 4'd11 - idx;
        end
endfunction

// 左寄せの1回目とまるめ、まるめは左寄せ数により参照ビットが異なる
// 入力MSBが1ならまるめてもオーバーフローしない(carry信号は0)
// 入力MSBが0ならまるめるとオーバーフローする可能性がある(その場合carry信号は1)
// 減算の結果、上位のビットの多くが0に連なる場合があるので、全ビットのチェックが必要
// このあたりを工夫すれば偶数丸め可能
function [11:0] fracShiftFunc;
        input   [13:0]          frac;
        reg     [11:0]          result;
        reg     [11:0]          adder0;
        reg     [11:0]          adder1;
        reg     [11:0]          adder2;

        begin
                adder0          = {11'd0, frac[2]};
                adder1          = {11'd0, frac[1]};
                adder2          = {11'd0, frac[0]};

                casex (frac)
                        { 1'h1, 13'hxxxx}:      result  = {1'd0, frac[13:3]} + adder0;
                        { 2'h1,  12'hxxx}:      result  = {1'd0, frac[12:2]} + adder1;
                        { 3'h1,  11'hxxx}:      result  = {1'd0, frac[11:1]} + adder2;
                        { 4'h1,  10'hxxx}:      result  = {1'd0, frac[10:0]         };
                        { 5'h01,  9'hxxx}:      result  = {1'd0, frac[9:0],     1'd0};
                        { 6'h01,   8'hxx}:      result  = {1'd0, frac[8:0],     2'd0};
                        { 7'h01,   7'hxx}:      result  = {1'd0, frac[7:0],     3'd0};
                        { 8'h01,   6'hxx}:      result  = {1'd0, frac[6:0],     4'd0};
                        { 9'h001,  5'hxx}:      result  = {1'd0, frac[5:0],     5'd0};
                        {10'h001,   4'hx}:      result  = {1'd0, frac[4:0],     6'd0};
                        {11'h001,   3'hx}:      result  = {1'd0, frac[3:0],     7'd0};
                        {12'h001,   2'hx}:      result  = {1'd0, frac[2:0],     8'd0};
                        {13'h001,   1'hx}:      result  = {1'd0, frac[1:0],     9'd0};
                        default:                result  = {1'd0,               11'd0};
                endcase

                fracShiftFunc   = result;
        end
endfunction

endmodule

// *****************************************************************************
        

回路デザイン > 設計例 [浮動小数点] > コーディング    次のページ(テスト)   このページのTOP ▲

[1]
理解とメンテナンスし易さを優先してモジュールの設計にあたっています。しかもここで示したスタイルは一例なので、他と記述スタイルが違うことがあってもご容赦下さい。
[2]
加算器のRTLも参考に記載します。加算器の方は、符号の調整→桁揃え→加算→正規化になり複雑になります。減算により上位の値で0が続く場合があるため正規化は必須です。演算に等しくタイミングのボトルネックになりえます。
[3]
単精度は、指数と仮数のビットが増えます。タイミングの要請によっては、乗算ステージ分けたりします。さらに最近接まるめやNaN、Infinityのサポートが加わると、実際は複雑なRTLになります。
[4]
まるめは精度の維持とって重要で、限られた仮数の半精度では必須だと思います。しかし回路規模等優先にして、あえて切り捨てを使用することもあります。現にIEEE754では切り捨てモードがあります。サンプルRTLからまるめを外すのは至極簡単です。
[5]
オペランドのいずれかがNaNなら結果もNaNです。他、無限から無限を引くとNaN、無限と0を掛けるとNaNになります。最初そのような判定フラグを作り、パイプラインに流し最終結果を調整するなど実装はそんなに難しくありません。