blob: c51f1613559465f5d01d2677938f34f5fd5050df [file]
// Copyright 2025 Google LLC
//
// 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.
package kelvin.float
import common._
import chisel3._
import chisel3.util._
import kelvin.{RegfileWriteDataIO, Parameters}
object FloatCore {
def apply(p: Parameters): FloatCore = {
return Module(new FloatCore(p))
}
}
// TODO(atv): Investigate importing these from fpnew RTL.
object FpNewConfig {
val NUM_OPERANDS = 3
val WIDTH = 32
val OP_BITS = 4
}
// Corresponding SystemVerilog enum `opgroup_e` can be found at
// external/cvfpu/src/fpnew_pkg.sv:119
object FpNewOperation extends ChiselEnum {
val FMADD = Value(0.U(FpNewConfig.OP_BITS.W))
val FNMSUB = Value(1.U(FpNewConfig.OP_BITS.W))
val ADD = Value(2.U(FpNewConfig.OP_BITS.W))
val MUL = Value(3.U(FpNewConfig.OP_BITS.W))
val DIV = Value(4.U(FpNewConfig.OP_BITS.W))
val SQRT = Value(5.U(FpNewConfig.OP_BITS.W))
val SGNJ = Value(6.U(FpNewConfig.OP_BITS.W))
val MINMAX = Value(7.U(FpNewConfig.OP_BITS.W))
val CMP = Value(8.U(FpNewConfig.OP_BITS.W))
val CLASSIFY = Value(9.U(FpNewConfig.OP_BITS.W))
val F2F = Value(10.U(FpNewConfig.OP_BITS.W))
val F2I = Value(11.U(FpNewConfig.OP_BITS.W))
val I2F = Value(12.U(FpNewConfig.OP_BITS.W))
val CPKAB = Value(13.U(FpNewConfig.OP_BITS.W))
val CPKCD = Value(14.U(FpNewConfig.OP_BITS.W))
// This isn't a real FPNEW operation. Don't present it to the core.
val STORE = Value(15.U(FpNewConfig.OP_BITS.W))
}
// Corresponding SystemVerilog enum `roundmode_e` can be found at
// external/cvfpu/src/fpnew_pkg.sv:130
// See the RISC-V Unprivileged spec, Chapter 20.2 for details
// on rounding modes.
object FpNewRoundingMode extends ChiselEnum {
val RNE = Value(0.U(3.W)) // Round to nearest, ties to even
val RTZ = Value(1.U(3.W)) // Round to zero
val RDN = Value(2.U(3.W)) // Round down (towards -inf)
val RUP = Value(3.U(3.W)) // Round up (towards +inf)
val RMM = Value(4.U(3.W)) // Round to nearest, ties to max magnitude
val ROD = Value(5.U(3.W)) // FPNEW-only, round to odd
val DYN = Value(7.U(3.W)) // Dynamic rounding mode (embedded in instruction)
}
object GenerateCoreShimSource {
def apply(p: Parameters): String = {
var moduleInterface = """module FloatCoreWrapper(
| input logic clk_i,
| input logic rst_ni,
|""".stripMargin
moduleInterface += " input logic in_valid_i,\n"
moduleInterface += " output logic in_ready_o,\n"
for (i <- 0 until FpNewConfig.NUM_OPERANDS) {
moduleInterface += " input logic [WIDTH-1:0] operands_i_GENI,\n"
.replaceAll("GENI", i.toString)
.replaceAll("WIDTH", FpNewConfig.WIDTH.toString)
}
moduleInterface += " input logic[OP_BITS-1:0] op_i,\n".replaceAll("OP_BITS", FpNewConfig.OP_BITS.toString)
moduleInterface += " input logic op_mod_i,\n"
moduleInterface += " input logic[2:0] rnd_mode_i,\n"
moduleInterface += " input logic flush_i,\n"
moduleInterface += " output logic out_valid_o,\n"
moduleInterface += " input logic out_ready_i,\n"
moduleInterface += " output logic[WIDTH-1:0] result_o,\n".replaceAll("WIDTH", FpNewConfig.WIDTH.toString)
moduleInterface += " output logic[4:0] status_o,\n"
moduleInterface += " output logic busy_o,\n"
// Drop final ",\n"
moduleInterface = moduleInterface.dropRight(2)
moduleInterface += ");\n\n"
var coreInstantiation = " logic [NUM_OPERANDS-1:0][WIDTH-1:0] operands_i;\n"
.replaceAll("NUM_OPERANDS", FpNewConfig.NUM_OPERANDS.toString)
.replaceAll("WIDTH", FpNewConfig.WIDTH.toString)
for (i <- 0 until FpNewConfig.NUM_OPERANDS) {
coreInstantiation += " assign operands_i[GENI] = operands_i_GENI;\n".replaceAll("GENI", i.toString)
}
coreInstantiation += """ localparam fpnew_pkg::fpu_implementation_t impl = '{
| PipeRegs: '{default: 'd3},
| UnitTypes: '{'{default: fpnew_pkg::PARALLEL}, // ADDMUL
| '{default: fpnew_pkg::MERGED}, // DIVSQRT
| '{default: fpnew_pkg::PARALLEL}, // NONCOMP
| '{default: fpnew_pkg::MERGED}}, // CONV
| PipeConfig: fpnew_pkg::DISTRIBUTED
|};
|""".stripMargin
coreInstantiation += """ fpnew_top#(
| .Features(fpnew_pkg::RV32F),
| .Implementation(impl),
| .PulpDivsqrt(PULP_DIVSQRT)
| ) core(
| .clk_i(clk_i),
| .rst_ni(rst_ni),
| .operands_i(operands_i),
| .rnd_mode_i(fpnew_pkg::roundmode_e'(rnd_mode_i)),
| .op_i(fpnew_pkg::operation_e'(op_i)),
| .op_mod_i(op_mod_i),
| .src_fmt_i(fpnew_pkg::FP32),
| .dst_fmt_i(fpnew_pkg::FP32),
| .int_fmt_i(fpnew_pkg::INT32),
| .vectorial_op_i(1'b0),
| .tag_i(1'b0),
| .simd_mask_i(1'b0),
| .in_valid_i(in_valid_i),
| .flush_i(flush_i),
| .out_ready_i(out_ready_i),
| .in_ready_o(in_ready_o),
| .result_o(result_o),
| .status_o(status_o),
| .tag_o(),
| .out_valid_o(out_valid_o),
| .busy_o(busy_o)
| );
|""".replaceAll("PULP_DIVSQRT", p.floatPulpDivsqrt.toString).stripMargin
moduleInterface + coreInstantiation + "endmodule\n"
}
}
class FloatCoreWrapper(p: Parameters) extends BlackBox with HasBlackBoxInline
with HasBlackBoxResource {
val io = IO(new Bundle {
val clk_i = Input(Clock())
val rst_ni = Input(AsyncReset())
val in_valid_i = Input(Bool())
val in_ready_o = Output(Bool())
val operands_i = Input(Vec(FpNewConfig.NUM_OPERANDS, UInt(FpNewConfig.WIDTH.W)))
val op_i = Input(UInt(FpNewConfig.OP_BITS.W))
val op_mod_i = Input(Bool())
val rnd_mode_i = Input(UInt(3.W))
val flush_i = Input(Bool())
val out_valid_o = Output(Bool())
val out_ready_i = Input(Bool())
val result_o = Output(UInt(FpNewConfig.WIDTH.W))
val status_o = Output(UInt(5.W)) // fflags
val busy_o = Output(Bool())
})
addResource("external/common_cells/include/common_cells/registers.svh")
addResource("external/common_cells/src/cf_math_pkg.sv")
addResource("external/common_cells/src/lzc.sv")
addResource("external/common_cells/src/rr_arb_tree.sv")
addResource("external/cvfpu/src/fpnew_pkg.sv")
addResource("external/cvfpu/src/fpnew_cast_multi.sv")
addResource("external/cvfpu/src/fpnew_classifier.sv")
if (p.floatPulpDivsqrt == 0) {
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/clk/rtl/gated_clk_cell.v")
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/fdsu/rtl/pa_fdsu_ctrl.v")
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/fdsu/rtl/pa_fdsu_ff1.v")
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/fdsu/rtl/pa_fdsu_pack_single.v")
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/fdsu/rtl/pa_fdsu_prepare.v")
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/fdsu/rtl/pa_fdsu_round_single.v")
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/fdsu/rtl/pa_fdsu_special.v")
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/fdsu/rtl/pa_fdsu_srt_single.v")
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/fdsu/rtl/pa_fdsu_top.v")
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/fpu/rtl/pa_fpu_dp.v")
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/fpu/rtl/pa_fpu_frbus.v")
addResource("external/cvfpu/vendor/opene906/E906_RTL_FACTORY/gen_rtl/fpu/rtl/pa_fpu_src_type.v")
addResource("external/cvfpu/src/fpnew_divsqrt_th_32.sv")
} else {
addResource("external/fpu_div_sqrt_mvp/hdl/defs_div_sqrt_mvp.sv")
addResource("external/fpu_div_sqrt_mvp/hdl/iteration_div_sqrt_mvp.sv")
addResource("external/fpu_div_sqrt_mvp/hdl/control_mvp.sv")
addResource("external/fpu_div_sqrt_mvp/hdl/norm_div_sqrt_mvp.sv")
addResource("external/fpu_div_sqrt_mvp/hdl/preprocess_mvp.sv")
addResource("external/fpu_div_sqrt_mvp/hdl/nrbd_nrsc_mvp.sv")
addResource("external/fpu_div_sqrt_mvp/hdl/div_sqrt_top_mvp.sv")
addResource("external/fpu_div_sqrt_mvp/hdl/div_sqrt_mvp_wrapper.sv")
addResource("external/cvfpu/src/fpnew_divsqrt_multi.sv")
}
addResource("external/cvfpu/src/fpnew_fma.sv")
addResource("external/cvfpu/src/fpnew_fma_multi.sv")
addResource("external/cvfpu/src/fpnew_noncomp.sv")
addResource("external/cvfpu/src/fpnew_opgroup_block.sv")
addResource("external/cvfpu/src/fpnew_opgroup_fmt_slice.sv")
addResource("external/cvfpu/src/fpnew_opgroup_multifmt_slice.sv")
addResource("external/cvfpu/src/fpnew_rounding.sv")
addResource("external/cvfpu/src/fpnew_top.sv")
setInline("FloatCoreWrapper.sv", GenerateCoreShimSource(p))
}
class FloatCore(p: Parameters) extends Module {
val io = IO(new FloatCoreIO(p))
val instQueue = Module(new Queue(new FloatInstruction, 1))
instQueue.io.enq <> MakeDecoupled(io.inst.valid, instQueue.io.count === 0.U, io.inst.bits)
val inst = instQueue.io.deq
io.inst.ready := (instQueue.io.count === 0.U)
val rstn = (!reset.asBool).asAsyncReset
val floatCoreWrapper = Module(new FloatCoreWrapper(p))
floatCoreWrapper.io.clk_i := clock
floatCoreWrapper.io.rst_ni := rstn
val opfp_operation = MuxLookup(inst.bits.funct5, FpNewOperation.ADD)(Seq(
// FPNEW expects the same `opcode_i` for add and sub,
// with a different value of `op_mod_i`.
"b00000".U -> FpNewOperation.ADD,
"b00001".U -> FpNewOperation.ADD,
"b00010".U -> FpNewOperation.MUL,
"b00011".U -> FpNewOperation.DIV,
"b01011".U -> FpNewOperation.SQRT,
"b00100".U -> FpNewOperation.SGNJ,
"b00101".U -> FpNewOperation.MINMAX,
"b11000".U -> FpNewOperation.F2I,
"b10100".U -> FpNewOperation.CMP,
"b11100".U -> FpNewOperation.CLASSIFY,
"b11010".U -> FpNewOperation.I2F,
))
val opfp_mod = MuxLookup(inst.bits.funct5, 0.U(1.W))(Seq(
"b00000".U -> 0.U(1.W), // ADD
"b00001".U -> 1.U(1.W), // SUB
"b00100".U -> 1.U(1.W), // FpNewOperation.SGNJ, Nan-Boxing,
"b11000".U -> inst.bits.rs2(0), // F2I -- 0 is signed, 1 is unsigned
"b11010".U -> inst.bits.rs2(0), // I2F, same sign behaviour as above
))
val op_i = MuxLookup(inst.bits.opcode, FpNewOperation.ADD)(Seq(
FloatOpcode.OPFP -> opfp_operation,
FloatOpcode.MADD -> FpNewOperation.FMADD,
FloatOpcode.MSUB -> FpNewOperation.FMADD,
FloatOpcode.NMADD -> FpNewOperation.FNMSUB,
FloatOpcode.NMSUB -> FpNewOperation.FNMSUB,
FloatOpcode.STOREFP -> FpNewOperation.STORE,
))
val op_mod_i = MuxLookup(inst.bits.opcode, 0.U(1.W))(Seq(
FloatOpcode.OPFP -> opfp_mod,
FloatOpcode.MADD -> 0.U(1.W),
FloatOpcode.MSUB -> 1.U(1.W),
FloatOpcode.NMADD -> 1.U(1.W),
FloatOpcode.NMSUB -> 0.U(1.W),
))
// For more details on which ports are used by each operation,
// consult the README for fpnew.
val read_port_0_valid =
MuxOR(op_i =/= FpNewOperation.ADD, true.B) // All ops but ADD/SUB use op0
val read_port_1_valid = op_i.isOneOf(FpNewOperation.FMADD, FpNewOperation.FNMSUB) ||
(
inst.bits.opcode === FloatOpcode.OPFP &&
!opfp_operation.isOneOf(FpNewOperation.SQRT, FpNewOperation.CLASSIFY, FpNewOperation.F2I, FpNewOperation.I2F)
)
val read_port_2_valid = op_i.isOneOf(FpNewOperation.FMADD, FpNewOperation.FNMSUB) ||
(inst.bits.opcode === FloatOpcode.OPFP && opfp_operation === FpNewOperation.ADD)
val read_ports_valid = VecInit(Seq(
read_port_0_valid,
read_port_1_valid,
read_port_2_valid,
))
for (i <- 0 until FpNewConfig.NUM_OPERANDS) {
io.read_ports(i).valid := read_ports_valid(i) && inst.valid
if (i == 0) {
floatCoreWrapper.io.operands_i(0) :=
Mux((inst.bits.opcode === FloatOpcode.OPFP) && (opfp_operation === FpNewOperation.I2F),
io.rs1.data,
io.read_ports(0).data.asWord)
} else {
floatCoreWrapper.io.operands_i(i) := io.read_ports(i).data.asWord
}
}
val fmv_x_w = inst.valid && (inst.bits.opcode === FloatOpcode.OPFP) && (inst.bits.funct5 === "b11100".U) && (inst.bits.rm === "b000".U)
val fmv_w_x = inst.valid && (inst.bits.opcode === FloatOpcode.OPFP) && (inst.bits.funct5 === "b11110".U) && (inst.bits.rm === "b000".U)
val fmv = (fmv_x_w || fmv_w_x)
val storefp = (inst.valid && (inst.bits.opcode === FloatOpcode.STOREFP))
val op0_addr = inst.bits.rs1
val op1_addr = Mux(op_i === FpNewOperation.ADD, inst.bits.rs1, inst.bits.rs2)
val op2_addr = Mux(op_i === FpNewOperation.ADD, inst.bits.rs2, inst.bits.rs3)
io.read_ports(0).addr := op0_addr
io.read_ports(1).addr := op1_addr
io.read_ports(2).addr := op2_addr
floatCoreWrapper.io.op_i := op_i.asUInt
floatCoreWrapper.io.op_mod_i := op_mod_i
val (inst_rm, inst_rm_valid) = FpNewRoundingMode.safe(inst.bits.rm)
val (csr_rm, csr_rm_valid) = FpNewRoundingMode.safe(io.csr.out.frm)
assert(csr_rm_valid)
floatCoreWrapper.io.rnd_mode_i := Mux(inst_rm === FpNewRoundingMode.DYN, csr_rm, inst_rm).asUInt
// Track whether an instruction has been accepted by the input side of fpnew.
// This allows us to unblock dispatch immediately,
// while waiting on an instruction that may take multiple cycles to execute (e.g. DIV/SQRT).
val fpuActive = RegInit(false.B)
fpuActive := MuxCase(fpuActive, Seq(
inst.fire -> false.B,
(floatCoreWrapper.io.in_valid_i && floatCoreWrapper.io.in_ready_o) -> true.B,
))
floatCoreWrapper.io.flush_i := false.B
floatCoreWrapper.io.in_valid_i := (inst.valid && !fmv) && !fpuActive
io.write_ports(0).valid := ((floatCoreWrapper.io.out_valid_o && inst.fire && !inst.bits.scalar_rd) || fmv_w_x) && !storefp
io.write_ports(0).addr := inst.bits.rd
io.write_ports(0).data := Fp32.fromWord(Mux(fmv_w_x, io.rs1.data, floatCoreWrapper.io.result_o))
io.write_ports(1).valid := io.lsu_rd.valid
io.write_ports(1).addr := io.lsu_rd.bits.addr
io.write_ports(1).data := Fp32.fromWord(io.lsu_rd.bits.data)
io.csr.in.fflags.valid := (floatCoreWrapper.io.out_valid_o && inst.fire && !fmv)
io.csr.in.fflags.bits := floatCoreWrapper.io.status_o
val scalar_rd_pre_pipe = Wire(Decoupled(new RegfileWriteDataIO))
scalar_rd_pre_pipe.valid := (((floatCoreWrapper.io.in_valid_i && floatCoreWrapper.io.in_ready_o) || fpuActive) && floatCoreWrapper.io.out_valid_o && floatCoreWrapper.io.out_ready_i && inst.bits.scalar_rd) || (fmv_x_w)
scalar_rd_pre_pipe.bits.addr := inst.bits.rd
scalar_rd_pre_pipe.bits.data := Mux(fmv_x_w, io.read_ports(0).data.asWord, floatCoreWrapper.io.result_o)
val scalar_rd_pipe = Queue(scalar_rd_pre_pipe, 2, false)
io.scalar_rd <> scalar_rd_pipe
floatCoreWrapper.io.out_ready_i := (inst.valid && inst.bits.scalar_rd && scalar_rd_pre_pipe.ready) || (inst.valid && !inst.bits.scalar_rd)
inst.ready := (((floatCoreWrapper.io.in_ready_o && floatCoreWrapper.io.in_valid_i) || fpuActive) && floatCoreWrapper.io.out_ready_i && floatCoreWrapper.io.out_valid_o) || fmv
}