Files
vortex/hw/rtl/fpu/VX_fpu_exp.sv
T

238 lines
8.0 KiB
Systemverilog

// Copyright © 2019-2023
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
`include "VX_fpu_define.vh"
module VX_fpu_exp import VX_fpu_pkg::*; #(
parameter NUM_LANES = 1,
parameter TAGW = 1
) (
input wire clk,
input wire reset,
output wire ready_in,
input wire valid_in,
input wire [NUM_LANES-1:0] lane_mask,
input wire [TAGW-1:0] tag_in,
input wire [NUM_LANES-1:0][31:0] dataa,
output wire [NUM_LANES-1:0][31:0] result,
output wire has_fflags,
output wire [`FP_FLAGS_BITS-1:0] fflags,
output wire [TAGW-1:0] tag_out,
input wire ready_out,
output wire valid_out
);
wire stall = ~ready_out && valid_out;
wire enable = ~stall;
fflags_t [NUM_LANES-1:0] per_lane_fflags;
wire [NUM_LANES-1:0] lane_mask_out;
// Convert finite FP32 to signed Q12. Values outside the useful FP32 exp
// input range are saturated; the range-reduction logic below will turn
// them into the appropriate overflow or underflow result.
function automatic signed [23:0] f32_to_q12;
input [31:0] value;
reg [7:0] exponent;
reg [23:0] mantissa;
reg [63:0] magnitude;
integer shift;
begin
exponent = value[30:23];
mantissa = {1'b1, value[22:0]};
magnitude = 0;
if (exponent == 0) begin
f32_to_q12 = 24'sd0;
end else begin
shift = integer'(exponent) - 138;
if (shift >= 0) begin
if (shift < 40)
magnitude = {40'd0, mantissa} << shift;
else
magnitude = 64'h7fffff;
end else if (shift > -64) begin
magnitude = {40'd0, mantissa} >> (-shift);
end
if (magnitude > 64'h7fffff)
magnitude = 64'h7fffff;
f32_to_q12 = value[31]
? -$signed({1'b0, magnitude[22:0]})
: $signed({1'b0, magnitude[22:0]});
end
end
endfunction
// Q20 samples of exp(index / 16). The same LUT/interpolation structure is
// used by the TMEM softmax engine. Range reduction guarantees index <= 11.
function automatic [23:0] exp_frac_lut;
input [3:0] index;
begin
case (index)
4'd0: exp_frac_lut = 24'd1048576;
4'd1: exp_frac_lut = 24'd1116203;
4'd2: exp_frac_lut = 24'd1188192;
4'd3: exp_frac_lut = 24'd1264824;
4'd4: exp_frac_lut = 24'd1346398;
4'd5: exp_frac_lut = 24'd1433233;
4'd6: exp_frac_lut = 24'd1525669;
4'd7: exp_frac_lut = 24'd1624066;
4'd8: exp_frac_lut = 24'd1728810;
4'd9: exp_frac_lut = 24'd1840308;
4'd10: exp_frac_lut = 24'd1958998;
4'd11: exp_frac_lut = 24'd2085342;
default: exp_frac_lut = 24'd2219835;
endcase
end
endfunction
function automatic [23:0] interpolate_exp_frac;
input [3:0] index;
input [7:0] remainder;
reg [23:0] lower;
reg [23:0] upper;
reg [31:0] delta;
reg [31:0] product;
reg [31:0] interpolated;
begin
lower = exp_frac_lut(index);
upper = exp_frac_lut(index + 1'b1);
delta = {8'd0, upper} - {8'd0, lower};
product = delta * remainder;
interpolated = {8'd0, lower} + ((product + 32'd128) >> 8);
interpolate_exp_frac = interpolated[23:0];
end
endfunction
// Synthesizable FP32 exponential approximation.
//
// x = k*ln(2) + r, 0 <= r < ln(2)
// exp(x) = 2^k * exp(r)
//
// r is evaluated using the softmax-style Q12/LUT/interpolation datapath.
// The packed return value is {NV,DZ,OF,UF,NX,result[31:0]}.
function automatic [36:0] exp_approx_f32;
input [31:0] value;
reg signed [23:0] x_q12;
integer signed k;
integer signed rem_q12;
integer signed exp_field;
integer shift;
reg [23:0] exp_r_q20;
reg [23:0] significand;
reg [23:0] shifted;
reg [23:0] discarded_mask;
reg [23:0] discarded;
reg [23:0] halfway;
reg [22:0] mantissa;
reg [31:0] fp_result;
reg [4:0] fp_flags;
begin
fp_result = 32'h3f800000;
fp_flags = 5'b00000;
if (value[30:23] == 8'hff) begin
if (value[22:0] != 0) begin
fp_result = 32'h7fc00000;
fp_flags[4] = ~value[22];
end else if (value[31]) begin
fp_result = 32'h00000000;
end else begin
fp_result = 32'h7f800000;
end
end else if (value[30:0] != 0) begin
x_q12 = f32_to_q12(value);
k = integer'($signed(x_q12)) / 2839;
rem_q12 = integer'($signed(x_q12)) - k * 2839;
if (rem_q12 < 0) begin
k = k - 1;
rem_q12 = rem_q12 + 2839;
end
exp_r_q20 = interpolate_exp_frac(
4'(rem_q12 >> 8), 8'(rem_q12));
significand = {1'b1, exp_r_q20[19:0], 3'b000};
exp_field = 127 + k;
fp_flags[0] = 1'b1;
if (exp_field >= 255) begin
fp_result = 32'h7f800000;
fp_flags[2] = 1'b1;
end else if (exp_field > 0) begin
mantissa = significand[22:0];
fp_result = {1'b0, 8'(exp_field), mantissa};
end else begin
shift = 1 - exp_field;
fp_flags[1] = 1'b1;
if (shift >= 25) begin
fp_result = 32'h00000000;
end else begin
shifted = significand >> shift;
discarded_mask = (24'd1 << shift) - 1'b1;
discarded = significand & discarded_mask;
halfway = 24'd1 << (shift - 1);
if ((discarded > halfway)
|| ((discarded == halfway) && shifted[0]))
shifted = shifted + 1'b1;
if (shifted[23])
fp_result = 32'h00800000;
else
fp_result = {9'd0, shifted[22:0]};
end
end
end
exp_approx_f32 = {fp_flags, fp_result};
end
endfunction
VX_shift_register #(
.DATAW (1 + NUM_LANES + TAGW),
.DEPTH (`LATENCY_FEXP),
.RESETW (1)
) shift_reg (
.clk (clk),
.reset (reset),
.enable (enable),
.data_in ({valid_in, lane_mask, tag_in}),
.data_out ({valid_out, lane_mask_out, tag_out})
);
assign ready_in = enable;
for (genvar i = 0; i < NUM_LANES; ++i) begin
wire [36:0] exp_approx = exp_approx_f32(dataa[i]);
VX_shift_register #(
.DATAW (32 + $bits(fflags_t)),
.DEPTH (`LATENCY_FEXP)
) shift_req_exp (
.clk (clk),
`UNUSED_PIN (reset),
.enable (enable),
.data_in ({exp_approx[31:0], exp_approx[36:32]}),
.data_out ({result[i], per_lane_fflags[i]})
);
end
assign has_fflags = 1;
`FPU_MERGE_FFLAGS(fflags, per_lane_fflags, lane_mask_out, NUM_LANES);
endmodule