-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- Copyright 2025 VLSI Solution Oy
-- SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
--
-- Licensed under the Solderpad Hardware License v 2.1 (the "License");
-- you may not use this file except in compliance with the License, or,
-- at your option, the Apache License version 2.0.
-- You may obtain a copy of the License at
--
--    https://solderpad.org/licenses/SHL-2.1/
--
-- Unless required by applicable law or agreed to in writing, any work
-- 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.
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--                               RISCV core
--                                 V1.1
--
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-- - - - - - - -
--  libraries
-- - - - - - - -
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use work.rv_pkg.all;

entity rv_divider is
  port ( clk_i        : in  std_logic;
         xrst_i       : in  std_logic;  -- syncr reset
         -- inputs
         valid_i      : in  std_logic;
         div_op_p1_i  : in  div_op_t;   -- div, divu, rem, remu
         rs1_dat_i    : in  slv_31_00;
         rs2_dat_i    : in  slv_31_00;
         --  outputs
         wb_valid_o   : out std_logic;
         wb_value_o   : out slv_31_00
         );
end entity rv_divider;


architecture rtl of rv_divider is

  -- - - - - - - - - - -
  --  registers / wires
  -- - - - - - - - - - -
  signal valid_q          : std_logic;
  signal wb_result_q      : slv_31_00;

  -- - - - - -
  --  divider
  -- - - - - -
  signal div_rem_instr_w  : std_logic;

  signal signed_op_w      : std_logic;
  signal div_op_w         : std_logic;

  signal dividend_q       : slv_31_00;
  signal divisor_q        : slv_62_00;
  signal quotient_q       : slv_31_00;
  signal q_mask_q         : slv_31_00;
  signal div_instr_q      : std_logic;
  signal div_busy_q       : std_logic;
  signal invert_res_q     : std_logic;

  signal div_start_w      : std_logic;
  signal div_complete_w   : std_logic;

  signal div_result_w     : slv_31_00;

begin

  -- - - - - -
  --  divider
  -- - - - - -

  div_rem_instr_w  <= div_op_p1_i.i_div or div_op_p1_i.i_divu or
                      div_op_p1_i.i_rem or div_op_p1_i.i_remu;

  signed_op_w      <= div_op_p1_i.i_div or div_op_p1_i.i_rem;

  div_op_w         <= div_op_p1_i.i_div or div_op_p1_i.i_divu;

  div_start_w      <= valid_i and div_rem_instr_w;

  div_complete_w   <= is_zero(q_mask_q) and div_busy_q;


  wb_valid_o       <= valid_q;
  wb_value_o       <= wb_result_q;

  --
  -- 34/18 cycle division
  --
  p_div : process (clk_i)
    variable divisor_v    : slv_62_00;
    variable dividend_v   : slv_31_00;
    variable quotient_v   : slv_31_00;
    variable q_mask_v     : slv_31_00;
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        div_busy_q    <= '0';
        dividend_q    <= (others => '0');     -- 0x00000000
        divisor_q     <= (others => '0');     -- 0x00000000 00000000 (63 bit)
        invert_res_q  <= '0';
        quotient_q    <= (others => '0');     -- 0x00000000
        q_mask_q      <= (others => '0');     -- 0x00000000
        div_instr_q   <= '0';
      else
        if (div_start_w = '1') then
          div_busy_q     <= '1';
          div_instr_q    <= div_op_w;

          if (signed_op_w = '1' and rs1_dat_i(31) = '1') then
            dividend_q <= slv_t( - signed(rs1_dat_i));
          else
            dividend_q <= rs1_dat_i;
          end if;

          if (signed_op_w = '1' and rs2_dat_i(31) = '1') then
            -- [62:0]
            divisor_q <= slv_t( - (signed(rs2_dat_i))) &
                         (30 downto 0 => '0'); -- -rs2_dat_i & 31'b0
          else
            divisor_q <= rs2_dat_i & (30 downto 0 => '0');
          end if;

          if ( ( div_op_p1_i.i_div = '1' and
                 (rs1_dat_i(31) /= rs2_dat_i(31)) and
                 not_zero(rs2_dat_i) )   or
               ( div_op_p1_i.i_rem = '1' and rs1_dat_i(31) = '1') ) then
            invert_res_q  <= '1';
          else
            invert_res_q  <= '0';
          end if;

          quotient_q     <= (others => '0');
          q_mask_q       <= X"80000000";
        elsif (div_complete_w = '1') then
               div_busy_q <= '0';
        elsif (div_busy_q = '1') then
          --
          -- 32-cycle divider
          --
          -- if (divisor_q <= ( (30 downto 0 => '0') & dividend_q) ) then
          --   dividend_q <= slv_t(
          --     signed(dividend_q) - signed(divisor_q(31 downto 0)) );
          --   quotient_q <= quotient_q or q_mask_q;
          -- end if;
          --
          -- divisor_q <= '0' & divisor_q(62 downto 1);
          -- q_mask_q  <= '0' & q_mask_q(31 downto 1);
          --
          --
          -- 16-cycle divider
          --
          divisor_v    := divisor_q;
          dividend_v   := dividend_q;
          quotient_v   := quotient_q;
          q_mask_v     := q_mask_q;

          if (divisor_v <= ( (30 downto 0 => '0') & dividend_v) ) then
            dividend_v := slv_t(signed(dividend_v) -
                                signed(divisor_v(31 downto 0)) );
            quotient_v := quotient_v or q_mask_v;
          end if;
          divisor_v := '0' & divisor_v(62 downto 1);
          q_mask_v  := '0' & q_mask_v(31 downto 1);
          --
          if (divisor_v <= ( (30 downto 0 => '0') & dividend_v) ) then
            dividend_v := slv_t(signed(dividend_v) -
                                signed(divisor_v(31 downto 0)) );
            quotient_v := quotient_v or q_mask_v;
          end if;
          divisor_v := '0' & divisor_v(62 downto 1);
          q_mask_v  := '0' & q_mask_v(31 downto 1);
          --
          divisor_q  <= divisor_v;
          dividend_q <= dividend_v;
          quotient_q <= quotient_v;
          q_mask_q   <= q_mask_v;
        end if;
      end if;
    end if;
  end process p_div;


  p_c_div_res : process (div_instr_q, invert_res_q, quotient_q, dividend_q)
  begin
    div_result_w <= (others => '0');
    if (div_instr_q = '1') then
      if (invert_res_q = '1') then
        div_result_w  <= slv_t(- signed(quotient_q));
      else
        div_result_w <=  quotient_q;
      end if;
    else
      if (invert_res_q = '1') then
        div_result_w <= slv_t(- signed(dividend_q));
      else
        div_result_w <= dividend_q;
      end if;
    end if;
  end process p_c_div_res;


  p_wb_res : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        valid_q        <= '0';
        wb_result_q    <= (others => '0');
      else
        valid_q        <= div_complete_w;
        if (div_complete_w = '1') then
          wb_result_q  <= div_result_w;
        end if;
      end if;
    end if;
  end process p_wb_res;

end rtl;
