現在自作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