FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

自作CPUにベクトル命令を追加する実装検討 (12. cvfpuを使ったベクトルFPU命令の実装検討)

現在自作CPUではFPUの演算器としてIPのcvfpu(FPNew)を使用している。これはかなりまともに使える演算器IPだと思うので、現在サポートしようとしているベクトル命令のFPU演算命令もFPNewを使ってサポートしていきたい。

スカラFPUの場合はcvfpu内の各ユニットを細かく借用して実装したが、ベクトルFPUの場合はcvfpu全体を使ってインスタンス化していきたい。

そのためには、cvfpuのコンフィグレーションを理解する必要がある。

  • src/fpnew_pkg.sv
// FPU configuration: features
  typedef struct packed {
    int unsigned Width;
    logic        EnableVectors;
    logic        EnableNanBox;
    fmt_logic_t  FpFmtMask;
    ifmt_logic_t IntFmtMask;
  } fpu_features_t;

例えば、RV64Dの場合は以下のようにコンフィグレーションするらしい。

localparam fpu_features_t RV64D = '{
    Width:         64,
    EnableVectors: 1'b0,
    EnableNanBox:  1'b1,
    FpFmtMask:     5'b11000,
    IntFmtMask:    4'b0011
  };
  • width : 演算のデータ幅
  • EnableVectors : これを有効化すると、FpFmtMaskで定義されるデータ幅において、Widthよりも小さなデータ幅はWidth分だけSIMD展開されるように演算器が置かれる。
  • EnableNanBox : NanBoxingを有効化する
  • FpFmtMask : サポートする浮動小数点のフォーマットを指定する。
    • 4ビット目:FP32
    • 3ビット目:FP64
    • 2ビット目:FP16
    • 1ビット目:custom binary 8
    • 0ビット目:custom binary16alt
localparam fp_encoding_t [0:NUM_FP_FORMATS-1] FP_ENCODINGS  = '{
    '{8,  23}, // IEEE binary32 (single)
    '{11, 52}, // IEEE binary64 (double)
    '{5,  10}, // IEEE binary16 (half)
    '{5,  2},  // custom binary8
    '{8,  7}   // custom binary16alt
    // add new formats here
  };

これに基づいて、演算器が生成される。

まず、fpnew_top.sv内では、演算器の種類に応じてfpnew_opgroup_blockが生成されている。

   // -------------------------
   // Generate Operation Blocks
   // -------------------------
   for (genvar opgrp = 0; opgrp < int'(NUM_OPGROUPS); opgrp++) begin : gen_operation_groups
     localparam int unsigned NUM_OPS = fpnew_pkg::num_operands(fpnew_pkg::opgroup_e'(opgrp));

     logic in_valid;
     logic [NUM_FORMATS-1:0][NUM_OPS-1:0] input_boxed;

     assign in_valid = in_valid_i & (fpnew_pkg::get_opgroup(op_i) == fpnew_pkg::opgroup_e'(opgrp));

     // slice out input boxing
     always_comb begin : slice_inputs
       for (int unsigned fmt = 0; fmt < NUM_FORMATS; fmt++)
         input_boxed[fmt] = is_boxed[fmt][NUM_OPS-1:0];
     end

     fpnew_opgroup_block #(
       .OpGroup       ( fpnew_pkg::opgroup_e'(opgrp)    ),
       .Width         ( WIDTH                           ),
       .EnableVectors ( Features.EnableVectors          ),
       .PulpDivsqrt   ( PulpDivsqrt                     ),
       .FpFmtMask     ( Features.FpFmtMask              ),
       .IntFmtMask    ( Features.IntFmtMask             ),
       .FmtPipeRegs   ( Implementation.PipeRegs[opgrp]  ),
       .FmtUnitTypes  ( Implementation.UnitTypes[opgrp] ),
       .PipeConfig    ( Implementation.PipeConfig       ),
       .TagType       ( TagType                         ),
       .TrueSIMDClass ( TrueSIMDClass                   )
     ) i_opgroup_block (
  • fpnew_pkg.sv
   // Each FP operation belongs to an operation group
   typedef enum logic [1:0] {
     ADDMUL, DIVSQRT, NONCOMP, CONV
   } opgroup_e;

fpnew_opgroup_block内では、それぞれの演算器で、データ幅に応じた演算器が生成される。

  • fpnew_opgroup_block.sv
   // -------------------------
   // Generate Parallel Slices
   // -------------------------
   for (genvar fmt = 0; fmt < int'(NUM_FORMATS); fmt++) begin : gen_parallel_slices
     // Some constants for this format
     localparam logic ANY_MERGED = fpnew_pkg::any_enabled_multi(FmtUnitTypes, FpFmtMask);
     localparam logic IS_FIRST_MERGED =
         fpnew_pkg::is_first_enabled_multi(fpnew_pkg::fp_format_e'(fmt), FmtUnitTypes, FpFmtMask);

     // Generate slice only if format enabled
     if (FpFmtMask[fmt] && (FmtUnitTypes[fmt] == fpnew_pkg::PARALLEL)) begin : active_format

       logic in_valid;

       assign in_valid = in_valid_i & (dst_fmt_i == fmt); // enable selected format

       // Forward masks related to the right SIMD lane
       localparam int unsigned INTERNAL_LANES = fpnew_pkg::num_lanes(Width, fpnew_pkg::fp_format_e'(fmt), EnableVectors);
       logic [INTERNAL_LANES-1:0] mask_slice;
       always_comb for (int b = 0; b < INTERNAL_LANES; b++) mask_slice[b] = simd_mask_i[(NUM_LANES/INTERNAL_LANES)*b];

       fpnew_opgroup_fmt_slice #(
         .OpGroup       ( OpGroup                      ),
         .FpFormat      ( fpnew_pkg::fp_format_e'(fmt) ),
         .Width         ( Width                        ),
         .EnableVectors ( EnableVectors                ),
         .NumPipeRegs   ( FmtPipeRegs[fmt]             ),
         .PipeConfig    ( PipeConfig                   ),
         .TagType       ( TagType                      ),
         .TrueSIMDClass ( TrueSIMDClass                )
       ) i_fmt_slice (

さらに、データ幅とWIDTH値に応じていくつのレーンを生成するかを決定している。

  • fpnew_opgroup_fmt_slice.sv
   // Do not change
   localparam int unsigned NUM_OPERANDS = fpnew_pkg::num_operands(OpGroup),
   localparam int unsigned NUM_LANES    = fpnew_pkg::num_lanes(Width, FpFormat, EnableVectors),
   localparam type         MaskType     = logic [NUM_LANES-1:0],
   localparam int unsigned ExtRegEnaWidth = NumPipeRegs == 0 ? 1 : NumPipeRegs