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

-- some kind of square wave generator, writing 16-bit samples
-- to a memory address using DMA periodically
entity square is
  port
  (
    clk     : in std_logic;
    rst     : in std_logic;

    -- bus slave interface
    s_we      : in std_logic;
    s_addr    : in std_logic_vector(15 downto 0);
    s_din     : in std_logic_vector(15 downto 0);

    -- bus master interface (DMA!!)
    m_busy    : in std_logic;
    m_we      : out std_logic;
    m_addr    : out std_logic_vector(15 downto 0);
    m_dout    : out std_logic_vector(15 downto 0)
  );
end square;

--
-- Mem layout:
--  0x00: 16-bit unsigned period (units of 256 clock cycles)
--  0x02: 16-bit unsigned high value
--  0x04: 16-bit unsigned low value
--  0x06: 16-bit output address
--  0x08: flags: [enabled]

architecture Behavioral of square is

  -- all registers
  signal period, hi_val, lo_val, out_addr: std_logic_vector(15 downto 0);
  signal enabled: std_logic;

  signal counter: unsigned(23 downto 0);
  signal active: std_logic;

begin

  -- counter process
  -- drives counter, active
  process(clk, rst)
  begin

    if rst = '1' then
      counter <= to_unsigned(0, 24);
      active <= '0';
    elsif rising_edge(clk) then
      active <= '0';
      counter <= to_unsigned(0, 24);

      if enabled = '1' and unsigned(period) /= 0 then
        if counter(22 downto 7) = unsigned(period) then
          active <= '1';
        else
          counter <= counter + 1;
        end if;
      end if;
    end if;

  end process;

  -- signal generation
  -- drives m_we, m_addr, m_dout
  process(clk, rst)
    variable high: std_logic;
    variable deferred: std_logic;
  begin

    if rst = '1' then
      m_we <= '0';
      m_addr <= x"0000";
      m_dout <= x"0000";
      high := '0';
      deferred := '0';

    elsif rising_edge(clk) then
      m_we <= '0';

      if enabled = '1' then
        if active = '1' and m_busy = '1' then
          deferred := '1';
        elsif m_busy = '0' and (deferred = '1' or active = '1') then
          m_we <= '1';
          m_addr <= out_addr;
          if high = '1' then
            m_dout <= hi_val;
          else
            m_dout <= lo_val;
          end if;

          high := not high;
          deferred := '0';
        end if;
      end if;
    end if;

  end process;

  -- Bus slave process
  -- drives period, value, out_addr, enabled
  process(clk, rst)
  begin

    if rst = '1' then
      period <= x"0000";
      hi_val <= x"0000";
      lo_val <= x"0000";
      out_addr <= x"0000";
      enabled <= '0';

    elsif rising_edge(clk) then
      if s_we = '1' then
        case s_addr(3 downto 0) is
          when x"0" =>
            period <= s_din;
          when x"2" =>
            hi_val <= s_din;
          when x"4" =>
            lo_val <= s_din;
          when x"6" =>
            out_addr <= s_din;
          when x"8" =>
            enabled <= s_din(0);
          when others =>
        end case;
      end if;
    end if;

  end process;

end Behavioral;