From 6f57dd79572c177b775a934bee54ceb85a55c28a Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Sat, 7 Nov 2026 03:14:46 +0800 Subject: refactor: rename RTL source files --- rv64cpu/rtl/core/execute/decoder.sv | 672 ------------------------------------ 1 file changed, 672 deletions(-) delete mode 100644 rv64cpu/rtl/core/execute/decoder.sv (limited to 'rv64cpu/rtl/core/execute/decoder.sv') diff --git a/rv64cpu/rtl/core/execute/decoder.sv b/rv64cpu/rtl/core/execute/decoder.sv deleted file mode 100644 index c39a637..0000000 --- a/rv64cpu/rtl/core/execute/decoder.sv +++ /dev/null @@ -1,672 +0,0 @@ -// Richard CPU -// Copyright (C) 2026 Richard Qin - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// File path: rtl/core/decoder.sv -// Description: Instruction decoder for RV64IMAFD base ISA + Zicsr + Zifencei -// Translates 32-bit instructions into control signals for the pipeline. -// C is handled by the IF-stage decompressor before this decoder. - -module decoder - import rv64_pkg::*; -( - input logic [31:0] instr, // 32-bit instruction - input logic [1:0] priv_mode, // Current privilege mode from CSR - input logic mstatus_tsr, // Trap SRET in S-mode - input logic mstatus_tw, // Trap WFI in S/U-mode - input logic mstatus_tvm, // Trap supervisor memory-management ops in S-mode - - // Register File - output logic [4:0] rs1_addr, // Source register 1 address - output logic [4:0] rs2_addr, // Source register 2 address - output logic [4:0] rd_addr, // Destination register address - output logic reg_write_en, // Register file write enable - - // ALU Control - output alu_op_t alu_op, // ALU operation select - output logic alu_src1_sel, // 0 = rs1, 1 = PC - output logic alu_src2_sel, // 0 = rs2, 1 = immediate - - // Immediate - output logic [XLEN-1:0] imm, // Sign-extended immediate - - // Memory Access - output logic is_load, // MEM stage: read from memory - output logic is_store, // MEM stage: write to memory - output logic [2:0] mem_size, // funct3 → byte/half/word/double + sign - output logic is_amo, // A extension atomic memory operation - output amo_op_t amo_op, // LR/SC/AMO operation selector - output logic is_fp, - output logic fp_reg_write_en, - output logic fp_store_data_sel, - output fp_op_t fp_op, - output fp_fmt_t fp_fmt, - output logic [2:0] fp_rm, - - // Branch / Jump - output logic is_branch, // EX: conditional branch - output logic is_jump, // EX: unconditional jump (JAL/JALR) - output logic [2:0] branch_op, // funct3 → BEQ/BNE/BLT/BGE/BLTU/BGEU - - // Writeback Source - output logic [1:0] wb_sel, // 0 = ALU result, 1 = MEM data, 2 = PC+4 - - // RV64 Word Operations - output logic is_word_op, // EX: operate on lower 32 bits, sext result - - // System / Privilege - output logic is_csr, // CSR instruction - output logic [2:0] csr_op, // funct3 → CSRRW/CSRRS/CSRRC/CSRRWI/CSRRSI/CSRRCI - output logic is_ecall, // ECALL - output logic is_ebreak, // EBREAK - output logic is_mret, // MRET - output logic is_sret, // SRET - output logic is_wfi, // WFI - output logic is_sfence_vma, // SFENCE.VMA - output logic is_fence, // FENCE - output logic is_fence_i, // FENCE.I (Zifencei) - - // Error - output logic illegal_instr // Unrecognised or malformed instruction -); - - // ============================ - // Instruction Field Extraction - // ============================ - typedef enum logic [1:0] { - PRIV_MODE_U = 2'b00, - PRIV_MODE_S = 2'b01, - PRIV_MODE_M = 2'b11 - } priv_mode_t; - - logic [6:0] opcode; - logic [2:0] funct3; - logic [6:0] funct7; - logic [4:0] amo_funct5; - - assign opcode = instr[6:0]; - assign rd_addr = instr[11:7]; - assign funct3 = instr[14:12]; - assign rs1_addr = (opcode == OPCODE_LUI) ? 5'b00000 : instr[19:15]; - assign rs2_addr = instr[24:20]; - assign funct7 = instr[31:25]; - assign amo_funct5 = instr[31:27]; - - // ============================ - // Immediate Generation - // ============================ - logic [XLEN-1:0] imm_i, imm_s, imm_b, imm_u, imm_j; - logic [11:0] csr_addr_field; - - // I-type: instr[31:20] - assign imm_i = {{52{instr[31]}}, instr[31:20]}; - - // S-type: {instr[31:25], instr[11:7]} - assign imm_s = {{52{instr[31]}}, instr[31:25], instr[11:7]}; - - // B-type: {instr[31], instr[7], instr[30:25], instr[11:8], 1'b0} - assign imm_b = {{51{instr[31]}}, instr[31], instr[7], instr[30:25], instr[11:8], 1'b0}; - - // U-type: {instr[31:12], 12'b0} - assign imm_u = {{32{instr[31]}}, instr[31:12], 12'b0}; - - // J-type: {instr[31], instr[19:12], instr[20], instr[30:21], 1'b0} - assign imm_j = {{43{instr[31]}}, instr[31], instr[19:12], instr[20], instr[30:21], 1'b0}; - assign csr_addr_field = instr[31:20]; - - function automatic int priv_rank(logic [1:0] mode); - case (mode) - PRIV_MODE_U: return 0; - PRIV_MODE_S: return 1; - 2'b10: return 2; // Reserved/H-mode, treat as higher than S - default: return 3; // Machine mode (or anything else) highest - endcase - endfunction - - function automatic logic priv_ge(logic [1:0] cur, logic [1:0] req); - return priv_rank(cur) >= priv_rank(req); - endfunction - - function automatic logic csr_access_allowed(logic [1:0] cur, logic [11:0] addr); - logic [1:0] req_priv; - req_priv = addr[9:8]; - return priv_ge(cur, req_priv); - endfunction - - // ============================ - // Main Decode Logic - // ============================ - always_comb begin - // Default values (all signals inactive) - alu_op = ALU_ADD; - alu_src1_sel = 1'b0; // rs1 - alu_src2_sel = 1'b0; // rs2 - reg_write_en = 1'b0; - imm = '0; - is_load = 1'b0; - is_store = 1'b0; - mem_size = 3'b0; - is_amo = 1'b0; - amo_op = AMO_NONE; - is_fp = 1'b0; - fp_reg_write_en = 1'b0; - fp_store_data_sel = 1'b0; - fp_op = FP_OP_NONE; - fp_fmt = FP_FMT_S; - fp_rm = funct3; - is_branch = 1'b0; - is_jump = 1'b0; - branch_op = 3'b0; - wb_sel = 2'b00; // ALU result - is_word_op = 1'b0; - is_csr = 1'b0; - csr_op = 3'b0; - is_ecall = 1'b0; - is_ebreak = 1'b0; - is_mret = 1'b0; - is_sret = 1'b0; - is_wfi = 1'b0; - is_sfence_vma = 1'b0; - is_fence = 1'b0; - is_fence_i = 1'b0; - illegal_instr = 1'b0; - - case (opcode) - - // LUI — rd = imm_u - - OPCODE_LUI: begin - reg_write_en = 1'b1; - alu_src1_sel = 1'b0; // doesn't matter, ALU_ADD(0, imm_u) - alu_src2_sel = 1'b1; // immediate - imm = imm_u; - alu_op = ALU_ADD; // result = 0 + imm_u (rs1 forced to x0 externally or use alu_src1_sel trick) - // NOTE: EX stage should feed 0 to ALU operand 1 when alu_src1_sel==0 and opcode==LUI. - // Alternative: add a dedicated alu_src1_sel=2 for "zero". For now, the pipeline - // can detect LUI by opcode and zero out the operand. - end - - // AUIPC — rd = PC + imm_u - - - OPCODE_AUIPC: begin - reg_write_en = 1'b1; - alu_src1_sel = 1'b1; // PC - alu_src2_sel = 1'b1; // immediate - imm = imm_u; - alu_op = ALU_ADD; // result = PC + imm_u - end - - // ----------------- - // JAL — rd = PC+4; PC = PC + imm_j - OPCODE_JAL: begin - reg_write_en = 1'b1; - is_jump = 1'b1; - alu_src1_sel = 1'b1; // PC - alu_src2_sel = 1'b1; // immediate - imm = imm_j; - alu_op = ALU_ADD; // target = PC + imm_j - wb_sel = 2'b10; // writeback PC+4 - end - - // ------------------ - // JALR — rd = PC+4; PC = (rs1 + imm_i) & ~1 - - OPCODE_JALR: begin - reg_write_en = 1'b1; - is_jump = 1'b1; - alu_src1_sel = 1'b0; // rs1 - alu_src2_sel = 1'b1; // immediate - imm = imm_i; - alu_op = ALU_ADD; // target = rs1 + imm_i (LSB cleared by EX stage) - wb_sel = 2'b10; // writeback PC+4 - end - - // ----------------- - // Branch — compare rs1, rs2; if taken, PC = PC + imm_b - OPCODE_BRANCH: begin - reg_write_en = 1'b0; // branches never write rd - is_branch = 1'b1; - alu_src1_sel = 1'b1; // PC (ALU computes branch target) - alu_src2_sel = 1'b1; // immediate - imm = imm_b; - alu_op = ALU_ADD; // branch target = PC + imm_b - branch_op = funct3; // BEQ/BNE/BLT/BGE/BLTU/BGEU - end - - // ----------------- - // Load — rd = Mem[rs1 + imm_i] - OPCODE_LOAD: begin - reg_write_en = 1'b1; - is_load = 1'b1; - alu_src1_sel = 1'b0; // rs1 - alu_src2_sel = 1'b1; // immediate - imm = imm_i; - alu_op = ALU_ADD; // effective address = rs1 + imm_i - mem_size = funct3; // LB/LH/LW/LD/LBU/LHU/LWU - wb_sel = 2'b01; // writeback from memory - end - - // ----------------- - // Store — Mem[rs1 + imm_s] = rs2 - OPCODE_STORE: begin - reg_write_en = 1'b0; - is_store = 1'b1; - alu_src1_sel = 1'b0; // rs1 - alu_src2_sel = 1'b1; // immediate (NOT rs2 — rs2 is store data) - imm = imm_s; - alu_op = ALU_ADD; // effective address = rs1 + imm_s - mem_size = funct3; // SB/SH/SW/SD - end - - // ----------------- - // OP-IMM (I-type ALU) — rd = rs1 OP imm - // ADDI, SLTI, SLTIU, XORI, ORI, ANDI, SLLI, SRLI, SRAI - OPCODE_OP_IMM: begin - reg_write_en = 1'b1; - alu_src1_sel = 1'b0; // rs1 - alu_src2_sel = 1'b1; // immediate - imm = imm_i; - - case (funct3) - 3'b000: alu_op = ALU_ADD; // ADDI - 3'b010: alu_op = ALU_SLT; // SLTI - 3'b011: alu_op = ALU_SLTU; // SLTIU - 3'b100: alu_op = ALU_XOR; // XORI - 3'b110: alu_op = ALU_OR; // ORI - 3'b111: alu_op = ALU_AND; // ANDI - 3'b001: alu_op = ALU_SLL; // SLLI - 3'b101: alu_op = (funct7[5]) ? ALU_SRA : ALU_SRL; // SRAI / SRLI - default: alu_op = ALU_ADD; - endcase - end - - // ----------------- - // OP-IMM-32 (I-type ALU, 32-bit word variants for RV64) - // ADDIW, SLLIW, SRLIW, SRAIW - OPCODE_OP_IMM_32: begin - reg_write_en = 1'b1; - is_word_op = 1'b1; // EX stage: operate on lower 32 bits, sext result - alu_src1_sel = 1'b0; // rs1 - alu_src2_sel = 1'b1; // immediate - imm = imm_i; - - case (funct3) - 3'b000: alu_op = ALU_ADD; // ADDIW - 3'b001: alu_op = ALU_SLL; // SLLIW - 3'b101: alu_op = (funct7[5]) ? ALU_SRA : ALU_SRL; // SRAIW / SRLIW - default: begin - alu_op = ALU_ADD; - illegal_instr = 1'b1; - end - endcase - end - - // ----------------- - // OP (R-type ALU) — rd = rs1 OP rs2 - // ADD, SUB, SLL, SLT, SLTU, XOR, SRL, SRA, OR, AND - OPCODE_OP: begin - reg_write_en = 1'b1; - alu_src1_sel = 1'b0; // rs1 - alu_src2_sel = 1'b0; // rs2 - - if (funct7 == 7'b0000001) begin - case (funct3) - 3'b000: alu_op = ALU_MUL; - 3'b001: alu_op = ALU_MULH; - 3'b010: alu_op = ALU_MULHSU; - 3'b011: alu_op = ALU_MULHU; - 3'b100: alu_op = ALU_DIV; - 3'b101: alu_op = ALU_DIVU; - 3'b110: alu_op = ALU_REM; - 3'b111: alu_op = ALU_REMU; - default: illegal_instr = 1'b1; - endcase - end else begin - case (funct3) - 3'b000: alu_op = (funct7[5]) ? ALU_SUB : ALU_ADD; // SUB / ADD - 3'b001: alu_op = ALU_SLL; // SLL - 3'b010: alu_op = ALU_SLT; // SLT - 3'b011: alu_op = ALU_SLTU; // SLTU - 3'b100: alu_op = ALU_XOR; // XOR - 3'b101: alu_op = (funct7[5]) ? ALU_SRA : ALU_SRL; // SRA / SRL - 3'b110: alu_op = ALU_OR; // OR - 3'b111: alu_op = ALU_AND; // AND - default: alu_op = ALU_ADD; - endcase - end - end - - // ----------------- - // OP-32 (R-type ALU, 32-bit word variants for RV64) - // ADDW, SUBW, SLLW, SRLW, SRAW - OPCODE_OP_32: begin - reg_write_en = 1'b1; - is_word_op = 1'b1; - alu_src1_sel = 1'b0; // rs1 - alu_src2_sel = 1'b0; // rs2 - - if (funct7 == 7'b0000001) begin - case (funct3) - 3'b000: alu_op = ALU_MUL; - 3'b100: alu_op = ALU_DIV; - 3'b101: alu_op = ALU_DIVU; - 3'b110: alu_op = ALU_REM; - 3'b111: alu_op = ALU_REMU; - default: illegal_instr = 1'b1; - endcase - end else begin - case (funct3) - 3'b000: alu_op = (funct7[5]) ? ALU_SUB : ALU_ADD; // SUBW / ADDW - 3'b001: alu_op = ALU_SLL; // SLLW - 3'b101: alu_op = (funct7[5]) ? ALU_SRA : ALU_SRL; // SRAW / SRLW - default: begin - alu_op = ALU_ADD; - illegal_instr = 1'b1; - end - endcase - end - end - - // ----------------- - // MISC-MEM — FENCE / FENCE.I - OPCODE_MISC_MEM: begin - case (funct3) - 3'b000: is_fence = 1'b1; // FENCE (NOP in single-core in-order) - 3'b001: is_fence_i = 1'b1; // FENCE.I — flush I-Cache - default: illegal_instr = 1'b1; - endcase - end - - // ----------------- - // SYSTEM — ECALL / EBREAK / xRET / WFI / SFENCE.VMA / CSR* - OPCODE_SYSTEM: begin - if (funct3 == 3'b000) begin - // Privileged instructions (non-CSR) - reg_write_en = 1'b0; - case ({funct7, instr[24:20]}) // {funct7, rs2} - 12'b0000000_00000: is_ecall = 1'b1; // ECALL - 12'b0000000_00001: is_ebreak = 1'b1; // EBREAK - 12'b0011000_00010: begin // MRET - if (priv_mode == PRIV_MODE_M) - is_mret = 1'b1; - else - illegal_instr = 1'b1; - end - 12'b0001000_00010: begin // SRET - if (!priv_ge(priv_mode, PRIV_MODE_S)) - illegal_instr = 1'b1; - else if ((priv_mode == PRIV_MODE_S) && mstatus_tsr) - illegal_instr = 1'b1; - else - is_sret = 1'b1; - end - 12'b0001000_00101: begin // WFI - if ((priv_mode != PRIV_MODE_M) && mstatus_tw) - illegal_instr = 1'b1; - else - is_wfi = 1'b1; - end - default: begin - if (funct7 == 7'b0001001) begin - if (!priv_ge(priv_mode, PRIV_MODE_S)) - illegal_instr = 1'b1; - else if ((priv_mode == PRIV_MODE_S) && mstatus_tvm) - illegal_instr = 1'b1; - else - is_sfence_vma = 1'b1; // SFENCE.VMA - end else begin - illegal_instr = 1'b1; - end - end - endcase - end else begin - // CSR instructions - // funct3: 001=CSRRW, 010=CSRRS, 011=CSRRC, - // 101=CSRRWI, 110=CSRRSI, 111=CSRRCI - reg_write_en = 1'b1; - is_csr = 1'b1; - csr_op = funct3; - imm = imm_i; // CSR address = instr[31:20] (reuse imm_i path) - if (!csr_access_allowed(priv_mode, csr_addr_field)) begin - reg_write_en = 1'b0; - is_csr = 1'b0; - illegal_instr = 1'b1; - end - end - end - - // ----------------- - // AMO — A extension (LR/SC/AMO*) - OPCODE_AMO: begin - reg_write_en = 1'b1; - is_amo = 1'b1; - alu_src1_sel = 1'b0; - alu_src2_sel = 1'b1; - imm = '0; - alu_op = ALU_ADD; - wb_sel = 2'b01; - - unique case (funct3) - 3'b010: mem_size = 3'b010; // AMO*.W - 3'b011: mem_size = 3'b011; // AMO*.D - default: illegal_instr = 1'b1; - endcase - - unique case (amo_funct5) - 5'b00010: begin - amo_op = AMO_LR; - if (rs2_addr != 5'd0) begin - illegal_instr = 1'b1; - end - end - 5'b00011: amo_op = AMO_SC; - 5'b00001: amo_op = AMO_SWAP; - 5'b00000: amo_op = AMO_ADD; - 5'b00100: amo_op = AMO_XOR; - 5'b01100: amo_op = AMO_AND; - 5'b01000: amo_op = AMO_OR; - 5'b10000: amo_op = AMO_MIN; - 5'b10100: amo_op = AMO_MAX; - 5'b11000: amo_op = AMO_MINU; - 5'b11100: amo_op = AMO_MAXU; - default: begin - amo_op = AMO_NONE; - illegal_instr = 1'b1; - end - endcase - - if (illegal_instr) begin - reg_write_en = 1'b0; - is_amo = 1'b0; - amo_op = AMO_NONE; - end - end - - // ----------------- - // Floating-Point — F/D extensions - OPCODE_LOAD_FP: begin - is_fp = 1'b1; - is_load = 1'b1; - fp_reg_write_en = 1'b1; - alu_src1_sel = 1'b0; - alu_src2_sel = 1'b1; - imm = imm_i; - alu_op = ALU_ADD; - wb_sel = 2'b01; - unique case (funct3) - 3'b010: begin - mem_size = 3'b010; // FLW - fp_fmt = FP_FMT_S; - end - 3'b011: begin - mem_size = 3'b011; // FLD - fp_fmt = FP_FMT_D; - end - default: begin - is_fp = 1'b0; - fp_reg_write_en = 1'b0; - illegal_instr = 1'b1; - end - endcase - end - - OPCODE_STORE_FP: begin - is_fp = 1'b1; - is_store = 1'b1; - fp_store_data_sel = 1'b1; - alu_src1_sel = 1'b0; - alu_src2_sel = 1'b1; - imm = imm_s; - alu_op = ALU_ADD; - unique case (funct3) - 3'b010: begin - mem_size = 3'b010; // FSW - fp_fmt = FP_FMT_S; - end - 3'b011: begin - mem_size = 3'b011; // FSD - fp_fmt = FP_FMT_D; - end - default: begin - is_fp = 1'b0; - fp_store_data_sel = 1'b0; - illegal_instr = 1'b1; - end - endcase - end - - OPCODE_MADD, - OPCODE_MSUB, - OPCODE_NMSUB, - OPCODE_NMADD: begin - is_fp = 1'b1; - fp_reg_write_en = 1'b1; - fp_rm = funct3; - unique case (instr[26:25]) - 2'b00: fp_fmt = FP_FMT_S; - 2'b01: fp_fmt = FP_FMT_D; - default: illegal_instr = 1'b1; - endcase - unique case (opcode) - OPCODE_MADD: fp_op = FP_OP_MADD; - OPCODE_MSUB: fp_op = FP_OP_MSUB; - OPCODE_NMSUB: fp_op = FP_OP_NMSUB; - default: fp_op = FP_OP_NMADD; - endcase - if (illegal_instr) begin - is_fp = 1'b0; - fp_reg_write_en = 1'b0; - fp_op = FP_OP_NONE; - end - end - - OPCODE_OP_FP: begin - is_fp = 1'b1; - fp_rm = funct3; - unique case (instr[26:25]) - 2'b00: fp_fmt = FP_FMT_S; - 2'b01: fp_fmt = FP_FMT_D; - default: illegal_instr = 1'b1; - endcase - - unique case (funct7) - 7'b0000000, 7'b0000001: begin fp_op = FP_OP_ADD; fp_reg_write_en = 1'b1; end - 7'b0000100, 7'b0000101: begin fp_op = FP_OP_SUB; fp_reg_write_en = 1'b1; end - 7'b0001000, 7'b0001001: begin fp_op = FP_OP_MUL; fp_reg_write_en = 1'b1; end - 7'b0001100, 7'b0001101: begin fp_op = FP_OP_DIV; fp_reg_write_en = 1'b1; end - 7'b0101100, 7'b0101101: begin - fp_op = FP_OP_SQRT; - fp_reg_write_en = 1'b1; - if (rs2_addr != 5'd0) illegal_instr = 1'b1; - end - 7'b0010000, 7'b0010001: begin - fp_reg_write_en = 1'b1; - unique case (funct3) - 3'b000: fp_op = FP_OP_SGNJ; - 3'b001: fp_op = FP_OP_SGNJN; - 3'b010: fp_op = FP_OP_SGNJX; - default: illegal_instr = 1'b1; - endcase - end - 7'b0010100, 7'b0010101: begin - fp_reg_write_en = 1'b1; - unique case (funct3) - 3'b000: fp_op = FP_OP_MIN; - 3'b001: fp_op = FP_OP_MAX; - default: illegal_instr = 1'b1; - endcase - end - 7'b0100000, 7'b0100001: begin - fp_op = FP_OP_CVT_F2F; - fp_reg_write_en = 1'b1; - end - 7'b1010000, 7'b1010001: begin - reg_write_en = 1'b1; - unique case (funct3) - 3'b010: fp_op = FP_OP_EQ; - 3'b001: fp_op = FP_OP_LT; - 3'b000: fp_op = FP_OP_LE; - default: illegal_instr = 1'b1; - endcase - end - 7'b1100000, 7'b1100001: begin - fp_op = FP_OP_CVT_F2I; - reg_write_en = 1'b1; - end - 7'b1101000, 7'b1101001: begin - fp_op = FP_OP_CVT_I2F; - fp_reg_write_en = 1'b1; - end - 7'b1110000, 7'b1110001: begin - if (rs2_addr == 5'd0 && funct3 == 3'b000) begin - fp_op = FP_OP_MV_F2X; - reg_write_en = 1'b1; - end else if (rs2_addr == 5'd0 && funct3 == 3'b001) begin - fp_op = FP_OP_CLASS; - reg_write_en = 1'b1; - end else begin - illegal_instr = 1'b1; - end - end - 7'b1111000, 7'b1111001: begin - if (rs2_addr == 5'd0 && funct3 == 3'b000) begin - fp_op = FP_OP_MV_X2F; - fp_reg_write_en = 1'b1; - end else begin - illegal_instr = 1'b1; - end - end - default: illegal_instr = 1'b1; - endcase - - if (illegal_instr) begin - is_fp = 1'b0; - fp_reg_write_en = 1'b0; - reg_write_en = 1'b0; - fp_op = FP_OP_NONE; - end - end - - // ----------------- - // Unrecognised opcode - default: begin - illegal_instr = 1'b1; - end - - endcase - end - -endmodule -- cgit v1.2.3