-------------------------------------------------------------------------------
--
-- Simple OPB peripheral: a BRAM controller
--
-- Stephen A. Edwards
-- sedwards@cs.columbia.edu
--
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;

entity opb_bram is
  
  generic (
    C_OPB_AWIDTH : integer                   := 32;
    C_OPB_DWIDTH : integer                   := 32;
    C_BASEADDR   : std_logic_vector(0 to 31) := X"00000000";
    C_HIGHADDR   : std_logic_vector(0 to 31) := X"FFFFFFFF");

  port (
    OPB_Clk     : in  std_logic;
    OPB_Rst     : in  std_logic;
    OPB_ABus    : in  std_logic_vector(0 to C_OPB_AWIDTH-1);
    OPB_BE      : in  std_logic_vector(0 to C_OPB_DWIDTH/8-1);
    OPB_DBus    : in  std_logic_vector(0 to C_OPB_DWIDTH-1);
    OPB_RNW     : in  std_logic;
    OPB_select  : in  std_logic;
    OPB_seqAddr : in  std_logic;        -- Sequential Address
    Sln_DBus    : out std_logic_vector(0 to C_OPB_DWIDTH-1);
    Sln_errAck  : out std_logic;        -- (unused)
    Sln_retry   : out std_logic;        -- (unused)
    Sln_toutSup : out std_logic;        -- Timeout suppress
    Sln_xferAck : out std_logic);       -- Transfer acknowledge

end opb_bram;

architecture Behavioral of opb_bram is

  constant RAM_AWIDTH : integer := 9;  -- Number of address lines on the RAM
  constant RAM_DWIDTH : integer := 8;  -- Number of data lines on the RAM
  
  component RAMB4_S8
    port (
      DO   : out std_logic_vector(RAM_DWIDTH-1 downto 0);
      ADDR : in  std_logic_vector(RAM_AWIDTH-1 downto 0);
      CLK  : in  std_logic;
      DI   : in  std_logic_vector(RAM_DWIDTH-1 downto 0);
      EN   : in  std_logic;
      RST  : in  std_logic;
      WE   : in  std_logic);
  end component;

  signal RNW : std_logic;
  signal RAM_DI, RAM_DO : std_logic_vector(0 to RAM_DWIDTH-1);
  signal ABus : std_logic_vector(0 to RAM_AWIDTH-1);
  signal chip_select : std_logic;
  signal output_enable : std_logic;
  signal WE, RST : std_logic;

  -- Critical: Sln_xferAck is generated directly from state bit 0!
  constant STATE_BITS : integer := 3;
  constant Idle     : std_logic_vector(0 to STATE_BITS-1) := "000";
  constant Selected : std_logic_vector(0 to STATE_BITS-1) := "001";
  constant Read     : std_logic_vector(0 to STATE_BITS-1) := "011";
  constant Xfer     : std_logic_vector(0 to STATE_BITS-1) := "111";

  signal present_state, next_state : std_logic_vector(0 to STATE_BITS-1);

begin

  RAMBlock : RAMB4_S8
  port map (
    DO   => RAM_DO,
    ADDR => ABus,
    CLK  => OPB_Clk,
    DI   => RAM_DI,
    EN   => '1',
    RST  => RST,
    WE   => WE);

  register_opb_inputs: process (OPB_Clk, OPB_Rst)
  begin
    if OPB_Rst = '1' then
      RAM_DI <= (others => '0');
      ABus <= (others => '0');
      RNW <= '0';
    elsif OPB_Clk'event and OPB_Clk = '1' then
      RAM_DI <= OPB_DBus(0 to RAM_DWIDTH-1);
      ABus <= OPB_ABus(C_OPB_AWIDTH-3-(RAM_AWIDTH-1) to C_OPB_AWIDTH-3);
      RNW <= OPB_RNW;
    end if;
  end process register_opb_inputs;

  register_opb_outputs: process (OPB_Clk, OPB_Rst)
  begin
    if OPB_Rst = '1' then
      Sln_DBus(0 to RAM_DWIDTH-1) <= (others => '0');
    elsif OPB_Clk'event and OPB_Clk = '1' then
     if output_enable = '1' then
        Sln_DBus(0 to RAM_DWIDTH-1) <= RAM_DO;
     else
       Sln_DBus(0 to RAM_DWIDTH-1) <= (others => '0');
     end if;
    end if;
  end process register_opb_outputs;

  -- Unused outputs
  Sln_errAck  <= '0';
  Sln_retry   <= '0';
  Sln_toutSup <= '0';
  Sln_DBus(RAM_DWIDTH to C_OPB_DWIDTH-1) <= (others => '0');

  chip_select <=
    '1' when OPB_select = '1' and
        OPB_ABus(0 to C_OPB_AWIDTH-3-RAM_AWIDTH) =
        C_BASEADDR(0 to C_OPB_AWIDTH-3-RAM_AWIDTH) else
    '0';

  -- Sequential part of the FSM
  fsm_seq : process(OPB_Clk, OPB_Rst)
  begin
    if OPB_Rst = '1' then
      present_state <= Idle;
    elsif OPB_Clk'event and OPB_Clk = '1' then
      present_state <= next_state;
    end if;
  end process fsm_seq;

  -- Combinational part of the FSM
  fsm_comb : process(OPB_Rst, present_state, chip_select, OPB_Select, RNW)
  begin   
    RST <= '1';                         -- Default values
    WE <= '0';
    output_enable <= '0';
    if OPB_RST = '1' then
      next_state <= Idle;
    else
      case present_state is
        when Idle =>
          if chip_select = '1' then
            next_state <= Selected;
          else
            next_state <= Idle;
          end if;

        when Selected =>
          if OPB_Select = '1' then
            if RNW = '1' then
              RST <= '0';
              next_state <= Read;
            else
              WE <= '1';
              next_state <= Xfer;
            end if;
          else
            next_state <= Idle;
          end if;

        when Read =>
          if OPB_Select = '1' then
            output_enable <= '1';
            next_state <= Xfer;
          else
            next_state <= Idle;
          end if;

        -- State encoding is critical here: xfer must only be true here
        when Xfer =>
          next_state <= Idle;
          
        when others =>
          next_state <= Idle;
      end case;
    end if;
  end process fsm_comb;

  Sln_xferAck <= present_state(0);
  
end Behavioral;

-- Local Variables:
-- compile-command: "ghdl -a opb_bram.vhd"
-- End:
