-- $Id: pdp11_munit.vhd 1310 2022-10-27 16:15:50Z mueller $
-- SPDX-License-Identifier: GPL-3.0-or-later
-- Copyright 2006-2022 by Walter F.J. Mueller <W.F.J.Mueller@gsi.de>
--
------------------------------------------------------------------------------
-- Module Name:    pdp11_munit - syn
-- Description:    pdp11: mul/div unit for data (munit)
--
-- Dependencies:   -
-- Test bench:     tb/tb_pdp11_core (implicit)
-- Target Devices: generic
-- Tool versions:  ise 8.2-14.7; viv 2014.4-2022.1; ghdl 0.18-2.0.0
--
-- Synthesized (xst):
-- Date         Rev  ise         Target      flop lutl lutm slic t peri
-- 2014-07-12   569 14.7 131013  xc6slx16-2    30  154    0   46 s  6.8
-- 2014-07-11   568 14.7 131013  xc6slx16-2    28  123    0   47 s  5.6
--
-- Revision History: 
-- Date         Rev Version  Comment
-- 2022-10-25  1309   1.2.5  rename _gpr -> _gr
-- 2014-08-10   581   1.2.4  rename NEXT_ to N_; use c_cc_f_*
-- 2014-08-05   578   1.2.3  fix proc_div sensitivity list
-- 2014-08-03   577   1.2.2  use DTMP_POS rather signed(Q)>0 (xst bug for S-3)
-- 2014-07-26   575   1.2.1  fix proc_omux sensitivity list
-- 2014-07-12   569   1.2    merge DIV_ZERO+DIV_OVFL to DIV_QUIT; add S_DIV_SR
--                           BUGFIX: fix divide logic, dr+q max neg issues
-- 2011-11-18   427   1.1.1  now numeric_std clean
-- 2010-09-18   300   1.1    renamed from mbox
-- 2007-06-14    56   1.0.1  Use slvtypes.all
-- 2007-05-12    26   1.0    Initial version 
------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

use work.slvtypes.all;
use work.pdp11.all;

-- ----------------------------------------------------------------------------

entity pdp11_munit is                   -- mul/div unit for data (munit)
  port (
    CLK : in slbit;                     -- clock
    DSRC : in slv16;                    -- 'src' data in
    DDST : in slv16;                    -- 'dst' data in
    DTMP : in slv16;                    -- 'tmp' data in
    GR_DSRC : in slv16;                 -- 'src' data from GR
    FUNC : in slv2;                     -- function
    S_DIV : in slbit;                   -- s_opg_div state    (load dd_low)
    S_DIV_CN : in slbit;                -- s_opg_div_cn state (1st..16th cycle)
    S_DIV_CR : in slbit;                -- s_opg_div_cr state (remainder corr.)
    S_DIV_SR : in slbit;                -- s_opg_div_sr state (store remainder)
    S_ASH : in slbit;                   -- s_opg_ash state
    S_ASH_CN : in slbit;                -- s_opg_ash_cn state
    S_ASHC : in slbit;                  -- s_opg_ashc state
    S_ASHC_CN : in slbit;               -- s_opg_ashc_cn state
    SHC_TC : out slbit;                 -- last shc cycle (shc==0)
    DIV_CR : out slbit;                 -- division: remainder correction needed
    DIV_CQ : out slbit;                 -- division: quotient correction needed
    DIV_QUIT : out slbit;               -- division: abort (0/ or /0 or V=1)
    DOUT : out slv16;                   -- data output
    DOUTE : out slv16;                  -- data output extra
    CCOUT : out slv4                    -- condition codes out
  );
end pdp11_munit;

architecture syn of pdp11_munit is

  signal R_DD_L : slv16 := (others=>'0'); -- divident, low order part
  signal R_DDO_LT : slbit := '0';         -- original sign bit of divident
  signal R_MAXFIX : slbit := '0';         -- maxfix flag for division
  signal R_QO_LT : slbit := '0';          -- expected q sign for division
  signal R_DIV_V : slbit := '0';          -- V flag for division
  signal R_SHC : slv6 := (others=>'0');   -- shift counter for div and ash/c
  signal R_C1 : slbit := '0';             -- first cycle indicator
  signal R_MSBO : slbit := '0';           -- original sign bit for ash/c
  signal R_ASH_V : slbit := '0';          -- V flag for ash/c
  signal R_ASH_C : slbit := '0';          -- C flag for ash/c

  signal N_DD_L : slv16 := (others=>'0');
  signal N_DDO_LT : slbit := '0';
  signal N_MAXFIX : slbit := '0';
  signal N_QO_LT : slbit := '0';
  signal N_DIV_V : slbit := '0';
  signal N_SHC : slv6 := (others=>'0');
  signal N_C1 : slbit := '0';
  signal N_MSBO : slbit := '0';
  signal N_ASH_V : slbit := '0';
  signal N_ASH_C : slbit := '0';

  signal SHC_TC_L : slbit := '0';

  signal DDST_ZERO : slbit := '0';
  signal DDST_NMAX : slbit := '0';
  signal DSRC_ZERO : slbit := '0';
  signal DSRC_ONES : slbit := '0';
  signal DTMP_ZERO : slbit := '0';
  signal DTMP_POS  : slbit := '0';

  signal DOUT_DIV : slv16 := (others=>'0');
  signal DOUTE_DIV : slv16 := (others=>'0');

  alias DR : slv16 is DDST;             -- divisor  (in DDST)
  alias DD_H : slv16 is DSRC;           -- divident, high order part (in DSRC)
  alias Q : slv16 is DTMP;              -- quotient (accumulated in DTMP)

begin

  proc_regs: process (CLK)
  begin
    if rising_edge(CLK) then
      R_DD_L   <= N_DD_L;
      R_DDO_LT <= N_DDO_LT;
      R_MAXFIX <= N_MAXFIX;
      R_QO_LT  <= N_QO_LT;
      R_DIV_V  <= N_DIV_V;
      R_SHC    <= N_SHC;
      R_C1     <= N_C1;
      R_MSBO   <= N_MSBO;
      R_ASH_V  <= N_ASH_V;
      R_ASH_C  <= N_ASH_C;
    end if;
  end process proc_regs;
  
  proc_comm: process (DDST, DSRC, DTMP)
  begin
    
    DDST_ZERO <= '0';
    DDST_NMAX <= '0';
    DSRC_ZERO <= '0';
    DSRC_ONES <= '0';
    DTMP_ZERO <= '0';
    DTMP_POS  <= '0';

    if unsigned(DDST) = 0 then
      DDST_ZERO <= '1';
    end if;
    if DDST = "1000000000000000" then
      DDST_NMAX <= '1';
    end if;
    if unsigned(DSRC) = 0 then
      DSRC_ZERO <= '1';
    end if;
    if   signed(DSRC) = -1 then
      DSRC_ONES <= '1';
    end if;
    if unsigned(DTMP) = 0 then
      DTMP_ZERO <= '1';
    end if;
    if signed(DTMP) > 0 then
      DTMP_POS  <= '1';
    end if;
                     
  end process proc_comm;
                     
  proc_shc: process (DDST, R_SHC, R_C1,
                     S_DIV, S_DIV_CN, S_ASH, S_ASH_CN, S_ASHC, S_ASHC_CN)
  begin
    
    N_SHC    <= R_SHC;
    N_C1     <= R_C1;

    if S_ASH='1' or S_ASHC='1' then
      N_SHC <= DDST(5 downto 0);
      N_C1 <= '1';
    end if;
    if S_DIV = '1' then
      N_SHC <= "001111";
      N_C1 <= '1';
    end if;

    if S_DIV_CN='1' or S_ASH_CN='1' or S_ASHC_CN='1' then
      if R_SHC(5) = '0' then
        N_SHC <= slv(unsigned(R_SHC) - 1);
      else
        N_SHC <= slv(unsigned(R_SHC) + 1);
      end if;
      N_C1 <= '0';
    end if;

    SHC_TC_L <= '0';
    if unsigned(R_SHC) = 0 then
      SHC_TC_L <= '1';
    end if;
    
  end process proc_shc;
  
  proc_div: process (DDST, DSRC, DTMP, GR_DSRC, DR, DD_H, Q,
                     R_DD_L, R_DDO_LT, R_MAXFIX, R_QO_LT, R_DIV_V, R_SHC, R_C1, 
                     S_DIV, S_DIV_CN, S_DIV_CR, S_DIV_SR,
                     DDST_ZERO, DDST_NMAX, DSRC_ZERO, DTMP_ZERO, DTMP_POS)

    variable div_zero : slbit := '0';
    variable div_ovfl : slbit := '0';
    variable shftdd : slbit := '0';
    variable subadd : slbit := '0';

    variable dd_le : slbit := '0';
    variable dd_ge : slbit := '0';
    variable dd_gt : slbit := '0';
    
    variable qbit :   slbit := '0';
    variable qbit_1 : slbit := '0';
    variable qbit_n : slbit := '0';

    variable dd_h_old : slv16 := (others=>'0');  -- dd_h before add/sub
    variable dd_h_new : slv16 := (others=>'0');  -- dd_h after  add/sub
    
  begin
    
    N_DD_L   <= R_DD_L;
    N_DDO_LT <= R_DDO_LT;
    N_MAXFIX <= R_MAXFIX;
    N_QO_LT  <= R_QO_LT;
    N_DIV_V  <= R_DIV_V;

    div_zero := '0';
    div_ovfl := '0';

    qbit_1 := not (DR(15) xor DD_H(15)); -- !(dr<0 ^ dd_h<0)
    
    shftdd := not S_DIV_CR;
    if shftdd = '1' then
      dd_h_old := DD_H(14 downto 0) & R_DD_L(15);
    else
      dd_h_old := DD_H(15 downto 0);
    end if;
    
    if R_C1 = '1' then
      subadd := qbit_1;
    else
      subadd := Q(0);
    end if;
    
    if subadd = '0' then
      dd_h_new := slv(signed(dd_h_old) + signed(DR));
    else
      dd_h_new := slv(signed(dd_h_old) - signed(DR));
    end if;

    dd_le := '0';
    if signed(dd_h_new) <= 0 then
      dd_le := '1';                     -- set if dd_new_h <= 0
    end if;
    
    dd_ge := '0';
    if signed(dd_h_new) >= -1 then
      dd_ge := '1';                     -- set if dd_new_h >= -1
    end if;

    dd_gt := '0';
    if dd_h_new(15) = '0' and
       (unsigned(dd_h_new(14 downto 0))/=0 or
        unsigned(R_DD_L(14 downto 0))/=0)
    then
      dd_gt := '1';                     -- set if dd_new > 0
    end if;
    
    if R_DDO_LT = '0' then
      qbit_n := DR(15) xor not dd_h_new(15);  -- b_dr_lt ^ !b_dd_lt
    else
      if R_MAXFIX = '0' then
        qbit_n := DR(15) xor dd_gt;             -- b_dr_lt ^  b_dd_gt
      else
        qbit_n := dd_h_new(15);                 -- b_dd_lt
      end if;
    end if;    
    
    if S_DIV = '1' then
      N_DDO_LT <= DD_H(15);
      N_DD_L   <= GR_DSRC;
      N_MAXFIX <= '0';
      if DDST_NMAX = '1' and GR_DSRC = "0000000000000000" then
        N_MAXFIX <= '1';                   -- b_dr_nmax && (ddi_l == 0)
      end if;
      N_QO_LT <= DD_H(15) xor DR(15);      -- b_di_lt ^ b_dr_lt
    end if;
    
    if R_C1 = '1' then
      div_zero := DDST_ZERO or
                  (DSRC_ZERO and DTMP_ZERO); -- note: DTMP here still dd_low !

      if DDST_NMAX='0' and (DD_H(15) xor DD_H(14)) = '1' then
        div_ovfl := '1';                 -- !b_dr_nmax && (b_di_31 != b_di_30)
      end if;

      if R_DDO_LT = '0' then            -- if (!b_di_lt)
        if R_QO_LT = '0' then             -- if (!b_qo_lt)
          if dd_h_new(15) = '0' then        -- if (!b_dd_lt)
            div_ovfl := '1';
          end if;
        else                              -- else
          if dd_le = '0' then               -- if (!b_dd_le)
            div_ovfl := '1';
          end if;
        end if;
      else
        if R_QO_LT = '0' then            -- if (!b_qo_lt)
          if dd_gt = '0' then               -- if (!b_dd_gt)
            div_ovfl := '1';            
          end if;
        else                              -- else
          if dd_ge = '0' then               -- if (!b_dd_ge)
            div_ovfl := '1';            
          end if;
        end if;
      end if;
      N_DIV_V <= div_ovfl;
      
    elsif S_DIV_SR = '1' then
      if R_QO_LT='1' and DTMP_POS='1' then
        div_ovfl := '1';
      end if;
      N_DIV_V <= div_ovfl;
    end if;

    if S_DIV_CN = '1' then
      N_DD_L <= R_DD_L(14 downto 0) & '0';
    end if;

    if S_DIV_CN = '1' then
      qbit := qbit_n;
    else
      qbit := qbit_1;
    end if;

    DIV_QUIT   <= div_zero or div_ovfl;
    
    DIV_CR <= R_MAXFIX or       -- b_maxfix | (!(b_ddo_lt ^ (b_dr_lt ^ b_qbit)))
              (not (R_DDO_LT xor (DR(15) xor Q(0))));
    DIV_CQ <= R_MAXFIX or       -- b_maxfix | (b_ddo_lt ^ b_dr_lt)
              (R_DDO_LT xor DR(15));
    
    DOUT_DIV  <= dd_h_new;
    DOUTE_DIV <= Q(14 downto 0) & qbit;

  end process proc_div;
  
  proc_ash: process (R_MSBO, R_ASH_V, R_ASH_C, R_SHC, DSRC, DTMP, FUNC,
                     S_ASH, S_ASH_CN, S_ASHC, S_ASHC_CN, SHC_TC_L)
  begin
    
    N_MSBO   <= R_MSBO;
    N_ASH_V  <= R_ASH_V;
    N_ASH_C  <= R_ASH_C;

    if S_ASH='1' or S_ASHC='1' then
      N_MSBO <= DSRC(15);
      N_ASH_V <= '0';
      N_ASH_C <= '0';
    end if;
    
    if (S_ASH_CN='1' or S_ASHC_CN='1') and SHC_TC_L='0' then
      if R_SHC(5) = '0' then            -- left shift
        if (R_MSBO xor DSRC(14))='1' then
          N_ASH_V <= '1';
        end if;
        N_ASH_C <= DSRC(15);
      else                              -- right shift
        if FUNC = c_munit_func_ash then
          N_ASH_C <= DSRC(0);
        else
          N_ASH_C <= DTMP(0);
        end if;
      end if;    
    end if;
      
  end process proc_ash;  

  proc_omux: process (DSRC, DDST, DTMP, FUNC,
                      R_ASH_V, R_ASH_C, R_SHC, R_DIV_V, R_QO_LT,
                      DOUT_DIV, DOUTE_DIV,
                      DSRC_ZERO, DSRC_ONES, DTMP_ZERO, DDST_ZERO)
    
    variable prod : slv32 := (others=>'0');
    variable omux_sel : slv2 := "00";
    variable ash_dout0 : slbit := '0';

    variable mul_c : slbit := '0';

  begin

    prod := slv(signed(DSRC) * signed(DDST));

    case FUNC is
      when c_munit_func_mul =>
        omux_sel := "00";
      when c_munit_func_div =>
        omux_sel := "01";
      when c_munit_func_ash |c_munit_func_ashc =>
        if R_SHC(5) = '0' then
          omux_sel := "10";
        else
          omux_sel := "11";
        end if;
      when others => null;
    end case;

    if FUNC = c_munit_func_ash then
      ash_dout0 := '0';
    else
      ash_dout0 := DTMP(15);
    end if;

    case omux_sel is
      when "00"  =>                     -- MUL
        DOUT  <= prod(31 downto 16);
        DOUTE <= prod(15 downto 0); 
      when  "01" =>                     -- DIV
        DOUT  <= DOUT_DIV;
        DOUTE <= DOUTE_DIV; 
      when  "10" =>                     -- shift left
        DOUT  <= DSRC(14 downto 0) & ash_dout0;
        DOUTE <= DTMP(14 downto 0) & "0";
      when  "11" =>                     -- shift right
        DOUT  <= DSRC(15) & DSRC(15 downto 1);
        DOUTE <= DSRC(0) & DTMP(15 downto 1);
      when others => null;
    end case;
    
    mul_c := '0';                       -- MUL C codes is set if
    if DSRC(15) = '0' then
      if DSRC_ZERO='0' or DTMP(15)='1' then -- for positive results when
        mul_c := '1';                   --   product > 2^15-1
      end if;
    else                                -- for negative results when
      if DSRC_ONES='0' or DTMP(15)='0' then
        mul_c := '1';                   --   product < -2^15
      end if;
    end if;
    
    case FUNC is
      when c_munit_func_mul =>
        CCOUT(c_cc_f_n) <= DSRC(15);                -- N
        CCOUT(c_cc_f_z) <= DSRC_ZERO and DTMP_ZERO; -- Z
        CCOUT(c_cc_f_v) <= '0';                     -- V=0
        CCOUT(c_cc_f_c) <= mul_c;                   -- C

      when c_munit_func_div =>
        if DDST_ZERO = '1' then
          CCOUT(c_cc_f_n) <= '0';             -- N=0 if div/0
          CCOUT(c_cc_f_z) <= '1';             -- Z=1 if div/0
        elsif R_DIV_V = '1' then
          CCOUT(c_cc_f_n) <= R_QO_LT;         -- N (send expected sign)
          CCOUT(c_cc_f_z) <= '0';             -- Z (from unchanged reg) ??? veri
        else
          CCOUT(c_cc_f_n) <= DTMP(15);        -- N (from Q (DTMP))
          CCOUT(c_cc_f_z) <= DTMP_ZERO;       -- Z (from Q (DTMP)) ??? verify
        end if;
        CCOUT(c_cc_f_v) <= R_DIV_V or DDST_ZERO;  -- V
        CCOUT(c_cc_f_c) <= DDST_ZERO;             -- C (dst=0)

      when c_munit_func_ash =>
        CCOUT(c_cc_f_n) <= DSRC(15);           -- N
        CCOUT(c_cc_f_z) <= DSRC_ZERO;          -- Z
        CCOUT(c_cc_f_v) <= R_ASH_V;            -- V
        CCOUT(c_cc_f_c) <= R_ASH_C;            -- C

      when c_munit_func_ashc =>
        CCOUT(c_cc_f_n) <= DSRC(15);               -- N
        CCOUT(c_cc_f_z) <= DSRC_ZERO and DTMP_ZERO;-- Z
        CCOUT(c_cc_f_v) <= R_ASH_V;                -- V
        CCOUT(c_cc_f_c) <= R_ASH_C;                -- C

      when others => null;
    end case;        
        
  end process proc_omux;

  SHC_TC <= SHC_TC_L;
  
end syn;
