use std.textio.all;

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

entity spi_flash is
  port(
    -- Inputs
    spi_clk_i : in std_logic;
    spi_csn_i : in std_logic;
    spi_mosi_i : in std_logic;
    spi_miso_o : out std_logic
    );
end spi_flash;

architecture RTL of spi_flash is
  type mem_t is array(natural range <>) of std_logic_vector (7 downto 0);

  subtype nibble_t is std_logic_vector(3 downto 0);
  
  function c2n (c : character) return nibble_t is
  begin
    case c is
      when '0' => return x"0";
      when '1' => return x"1";
      when '2' => return x"2";
      when '3' => return x"3";
      when '4' => return x"4";
      when '5' => return x"5";
      when '6' => return x"6";
      when '7' => return x"7";
      when '8' => return x"8";
      when '9' => return x"9";
      when 'a' => return x"a";
      when 'b' => return x"b";
      when 'c' => return x"c";
      when 'd' => return x"d";
      when 'e' => return x"e";
      when 'f' => return x"f";
      when others =>
        assert false report "bad character in c2n" severity failure;
    end case;
  end c2n;

  procedure read_mem (mem : out mem_t; filename : string)
  is
    FILE f_in  : text;
    variable l : line;
    variable addr : natural;
    variable off : natural;
    variable p : natural;
    variable byte : std_logic_vector (0 to 7);
  begin
    --  Read mem
    file_open(f_in, filename, read_mode);

    addr := 0;
    off := 0;
    while not endfile(f_in) loop
      readline (f_in, l);

      assert l'length = 56;
      assert l(1) = '0';
      assert l(2) = '0';
      assert l(7) = ':';
      assert l(8) = ' ';
      p := 9;
      for j in 0 to 15 loop
        assert l(p) = ' ';
        byte(0 to 3) := c2n (l(p + 1));
        byte(4 to 7) := c2n (l(p + 2));
        p := p + 3;
        mem(addr) := byte;
        addr := addr + 1;
      end loop;
      assert p = 57;
    end loop;
    file_close(f_in);
  end read_mem;
begin
    process
      variable mem: mem_t ((4096 + 1024) - 1 downto 0) := (others => (others => '0'));
      type t_state is (S_RESET, S_CMD, S_ADDR, S_RD);
      variable state : t_state;
      variable cmd : std_logic_vector(0 to 7);
      variable len : natural;
      variable addri : natural;
    begin
      read_mem (mem, "spi.dat"); -- "../../../sw/fip_urv/fip_urv.mem");
      state := S_RESET;

      loop
        wait on spi_clk_i;
        if spi_csn_i = '1' then
          state := S_RESET;
        else
          case state is
            when S_RESET =>
              if spi_clk_i = '1' then
                cmd(0) := spi_mosi_i;
                len := 1;
                state := S_CMD;
              end if;
            when S_CMD =>
              if spi_clk_i = '1' then
                cmd(len) := spi_mosi_i;
                if len = 7 then
                  state := S_ADDR;
                  addri := 0;
                  len := 0;
                else
                  len := len + 1;
                end if;
              end if;
            when S_ADDR =>
              if spi_clk_i = '1' then
                addri := addri * 2;
                if spi_mosi_i = '1' then
                  addri := addri + 1;
                end if;
                if len = 23 then
                  state := S_RD;
                  len := 0;
                else
                  len := len + 1;
                end if;
              end if;
            when S_RD =>
              if spi_clk_i = '0' then
                if addri > mem'high then
                  spi_miso_o <= 'X';
                else
                  spi_miso_o <= mem(addri)(7 - len);
                end if;
                if len = 7 then
                  len := 0;
                  addri := addri + 1;
                else
                  len := len + 1;
                end if;
              end if;
            end case;
          end if;
      end loop;
    end process;
end RTL;
