LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;
USE work.io_lcd_pins.all;
USE work.io_switches_pins.all;

ENTITY e_system IS
    PORT (
        clk:              IN  std_logic;
        pin_o_leds:       OUT std_logic_vector(7 DOWNTO 0);
        pin_o_lcd:        OUT t_io_lcd_pins;
        pin_i_switches:   IN  t_io_switches_pins;
        pin_i_uart_rx:    IN  std_logic;
        pin_o_uart_tx:    OUT std_logic;
        pin_o_eth_nrst:   OUT std_logic;
        pin_i_eth_rx_clk: IN  std_logic;
        pin_i_eth_rxd:    IN  std_logic_vector(4 DOWNTO 0);
        pin_i_eth_rx_dv:  IN  std_logic;
        pin_i_eth_crs:    IN  std_logic;
        pin_i_eth_col:    IN  std_logic;
        pin_i_eth_tx_clk: IN  std_logic;
        pin_o_eth_txd:    OUT std_logic_vector(3 DOWNTO 0);
        pin_o_eth_tx_en:  OUT std_logic
    );
END ENTITY e_system;

ARCHITECTURE a_system OF e_system IS

    CONSTANT c_instr_addr_width: natural := 13;
    CONSTANT c_data_addr_width:  natural := 13;

    SIGNAL rst: std_logic := '0';

    SIGNAL s_instr_addr:   std_logic_vector(31 DOWNTO 0);
    SIGNAL s_instr_data:   std_logic_vector(31 DOWNTO 0);
    SIGNAL s_dbus_addr:    std_logic_vector(31 DOWNTO 0);
    SIGNAL s_dbus_rd_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_dbus_wr_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_dbus_wr_en:   std_logic_vector( 3 DOWNTO 0);
    SIGNAL s_data_addr:    std_logic_vector(31 DOWNTO 0);
    SIGNAL s_data_rd_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_data_wr_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_data_wr_en:   std_logic_vector( 3 DOWNTO 0);
    SIGNAL s_leds_rd_data: std_logic_vector( 7 DOWNTO 0);
    SIGNAL s_leds_wr_data: std_logic_vector( 7 DOWNTO 0);
    SIGNAL s_leds_wr_en:   std_logic;
    SIGNAL s_lcd_rd_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_lcd_wr_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_lcd_wr_en:   std_logic_vector( 3 DOWNTO 0);
    SIGNAL s_switches_addr:    std_logic_vector( 2 DOWNTO 0);
    SIGNAL s_switches_rd_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_uart_addr:    std_logic_vector( 3 DOWNTO 0);
    SIGNAL s_uart_rd_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_uart_wr_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_uart_wr_en:   std_logic_vector( 3 DOWNTO 0);
    SIGNAL s_eth_addr:    std_logic_vector( 3 DOWNTO 0);
    SIGNAL s_eth_rd_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_eth_wr_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_eth_wr_en:   std_logic_vector( 3 DOWNTO 0);
    SIGNAL s_cyc_cnt_rd_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_cyc_cnt_wr_data: std_logic_vector(31 DOWNTO 0);
    SIGNAL s_cyc_cnt_wr_en:   std_logic;

    COMPONENT e_mips_core IS
        PORT (
            rst:            IN  std_logic;
            clk:            IN  std_logic;
            i_stall:        IN  std_logic;
            o_instr_addr:   OUT std_logic_vector(31 DOWNTO 0);
            i_instr_data:   IN  std_logic_vector(31 DOWNTO 0);
            o_data_addr:    OUT std_logic_vector(31 DOWNTO 0);
            i_data_rd_data: IN  std_logic_vector(31 DOWNTO 0);
            o_data_wr_data: OUT std_logic_vector(31 DOWNTO 0);
            o_data_wr_en:   OUT std_logic_vector( 3 DOWNTO 0)
        );
    END COMPONENT e_mips_core;

    COMPONENT e_rom IS
        GENERIC (
            addr_width: natural
        );
        PORT (
            clk:    IN  std_logic;
            i_addr: IN  std_logic_vector(addr_width - 1 DOWNTO 0);
            o_data: OUT std_logic_vector(            31 DOWNTO 0)
        );
    END COMPONENT e_rom;

    COMPONENT e_ram_0 IS
        GENERIC (
            addr_width: natural
        );
        PORT (
            clk:       IN  std_logic;
            i_addr:    IN  std_logic_vector(addr_width - 1 DOWNTO 0);
            o_rd_data: OUT std_logic_vector(             7 DOWNTO 0);
            i_wr_data: IN  std_logic_vector(             7 DOWNTO 0);
            i_wr_en:   IN  std_logic
        );
    END COMPONENT e_ram_0;

    COMPONENT e_ram_1 IS
        GENERIC (
            addr_width: natural
        );
        PORT (
            clk:       IN  std_logic;
            i_addr:    IN  std_logic_vector(addr_width - 1 DOWNTO 0);
            o_rd_data: OUT std_logic_vector(             7 DOWNTO 0);
            i_wr_data: IN  std_logic_vector(             7 DOWNTO 0);
            i_wr_en:   IN  std_logic
        );
    END COMPONENT e_ram_1;

    COMPONENT e_ram_2 IS
        GENERIC (
            addr_width: natural
        );
        PORT (
            clk:       IN  std_logic;
            i_addr:    IN  std_logic_vector(addr_width - 1 DOWNTO 0);
            o_rd_data: OUT std_logic_vector(             7 DOWNTO 0);
            i_wr_data: IN  std_logic_vector(             7 DOWNTO 0);
            i_wr_en:   IN  std_logic
        );
    END COMPONENT e_ram_2;

    COMPONENT e_ram_3 IS
        GENERIC (
            addr_width: natural
        );
        PORT (
            clk:       IN  std_logic;
            i_addr:    IN  std_logic_vector(addr_width - 1 DOWNTO 0);
            o_rd_data: OUT std_logic_vector(             7 DOWNTO 0);
            i_wr_data: IN  std_logic_vector(             7 DOWNTO 0);
            i_wr_en:   IN  std_logic
        );
    END COMPONENT e_ram_3;

    COMPONENT e_io_leds IS
        PORT (
            rst:        IN  std_logic;
            clk:        IN  std_logic;
            o_rd_data:  OUT std_logic_vector(7 DOWNTO 0);
            i_wr_data:  IN  std_logic_vector(7 DOWNTO 0);
            i_wr_en:    IN  std_logic;
            pin_o_leds: OUT std_logic_vector(7 DOWNTO 0)
        );
    END COMPONENT e_io_leds;

    COMPONENT e_io_lcd IS
        PORT (
            rst:       IN  std_logic;
            clk:       IN  std_logic;
            o_rd_data: OUT std_logic_vector(31 DOWNTO 0);
            i_wr_data: IN  std_logic_vector(31 DOWNTO 0);
            i_wr_en:   IN  std_logic_vector( 3 DOWNTO 0);
            pin_o_lcd: OUT t_io_lcd_pins
        );
    END COMPONENT e_io_lcd;

    COMPONENT e_io_switches IS
        PORT (
            rst:            IN  std_logic;
            clk:            IN  std_logic;
            i_addr:         IN  std_logic_vector( 0 DOWNTO 0);
            o_rd_data:      OUT std_logic_vector(31 DOWNTO 0);
            pin_i_switches: IN  t_io_switches_pins
        );
    END COMPONENT e_io_switches;

    COMPONENT e_io_uart IS
        PORT (
            rst:       IN  std_logic;
            clk:       IN  std_logic;
            i_addr:    IN  std_logic_vector( 1 DOWNTO 0);
            o_rd_data: OUT std_logic_vector(31 DOWNTO 0);
            i_wr_data: IN  std_logic_vector(31 DOWNTO 0);
            i_wr_en:   IN  std_logic_vector( 3 DOWNTO 0);
            pin_i_rx:  IN  std_logic;
            pin_o_tx:  OUT std_logic
        );
    END COMPONENT e_io_uart;

    COMPONENT e_io_eth IS
        PORT (
            rst:          IN  std_logic;
            clk:          IN  std_logic;
            i_addr:       IN  std_logic_vector( 1 DOWNTO 0);
            o_rd_data:    OUT std_logic_vector(31 DOWNTO 0);
            i_wr_data:    IN  std_logic_vector(31 DOWNTO 0);
            i_wr_en:      IN  std_logic_vector( 3 DOWNTO 0);
            pin_o_nrst:   OUT std_logic;
            pin_i_rx_clk: IN  std_logic;
            pin_i_rxd:    IN  std_logic_vector(4 DOWNTO 0);
            pin_i_rx_dv:  IN  std_logic;
            pin_i_crs:    IN  std_logic;
            pin_i_col:    IN  std_logic;
            pin_i_tx_clk: IN  std_logic;
            pin_o_txd:    OUT std_logic_vector(3 DOWNTO 0);
            pin_o_tx_en:  OUT std_logic
        );
    END COMPONENT e_io_eth;

    COMPONENT e_io_cyc_cnt IS
        PORT (
            rst:       IN  std_logic;
            clk:       IN  std_logic;
            o_rd_data: OUT std_logic_vector(31 DOWNTO 0);
            i_wr_data: IN  std_logic_vector(31 DOWNTO 0);
            i_wr_en:   IN  std_logic
        );
    END COMPONENT e_io_cyc_cnt;

BEGIN

    core: e_mips_core
        PORT MAP (
            rst            => rst,
            clk            => clk,
            i_stall        => '0',
            o_instr_addr   => s_instr_addr,
            i_instr_data   => s_instr_data,
            o_data_addr    => s_dbus_addr,
            i_data_rd_data => s_dbus_rd_data,
            o_data_wr_data => s_dbus_wr_data,
            o_data_wr_en   => s_dbus_wr_en
        );

    instr: e_rom
        GENERIC MAP (
            addr_width => c_instr_addr_width - 2
        )
        PORT MAP (
            clk    => clk,
            i_addr => s_instr_addr(c_instr_addr_width - 1 DOWNTO 2),
            o_data => s_instr_data
        );

    p_dbus: PROCESS(s_dbus_addr, s_dbus_wr_data, s_dbus_wr_en,
                    s_data_rd_data,
                    s_leds_rd_data,
                    s_lcd_rd_data,
                    s_switches_rd_data,
                    s_uart_rd_data,
                    s_eth_rd_data,
                    s_cyc_cnt_rd_data)
        VARIABLE v_wr_en_word: std_logic;
    BEGIN
        v_wr_en_word := s_dbus_wr_en(0) AND s_dbus_wr_en(1) AND
                        s_dbus_wr_en(2) AND s_dbus_wr_en(3);
        s_dbus_rd_data <= (OTHERS => '0');
        s_data_addr    <= (OTHERS => '0');
        s_data_wr_data <= (OTHERS => '0');
        s_data_wr_en   <= (OTHERS => '0');
        s_leds_wr_data <= (OTHERS => '0');
        s_leds_wr_en   <= '0';
        s_lcd_wr_data <= (OTHERS => '0');
        s_lcd_wr_en   <= (OTHERS => '0');
        s_switches_addr <= (OTHERS => '0');
        s_uart_addr    <= (OTHERS => '0');
        s_uart_wr_data <= (OTHERS => '0');
        s_uart_wr_en   <= (OTHERS => '0');
        s_eth_addr    <= (OTHERS => '0');
        s_eth_wr_data <= (OTHERS => '0');
        s_eth_wr_en   <= (OTHERS => '0');
        s_cyc_cnt_wr_data <= (OTHERS => '0');
        s_cyc_cnt_wr_en   <= '0';
        IF s_dbus_addr(31) = '0' THEN
            s_dbus_rd_data <= s_data_rd_data;
            s_data_addr    <= s_dbus_addr;
            s_data_wr_data <= s_dbus_wr_data;
            s_data_wr_en   <= s_dbus_wr_en;
        ELSIF s_dbus_addr(31 DOWNTO 16) = X"8000" THEN
            CASE s_dbus_addr(15 DOWNTO 8) IS
                WHEN X"00" =>
                    s_dbus_rd_data <= X"000000" & s_leds_rd_data;
                    s_leds_wr_data <= s_dbus_wr_data(7 DOWNTO 0);
                    s_leds_wr_en   <= s_dbus_wr_en(0);
                WHEN X"01" =>
                    s_dbus_rd_data <= s_lcd_rd_data;
                    s_lcd_wr_data  <= s_dbus_wr_data;
                    s_lcd_wr_en    <= s_dbus_wr_en;
                WHEN X"02" =>
                    s_dbus_rd_data  <= s_switches_rd_data;
                    s_switches_addr <= s_dbus_addr(2 DOWNTO 0);
                WHEN X"03" =>
                    s_dbus_rd_data <= s_uart_rd_data;
                    s_uart_addr    <= s_dbus_addr(3 DOWNTO 0);
                    s_uart_wr_data <= s_dbus_wr_data;
                    s_uart_wr_en   <= s_dbus_wr_en;
                WHEN X"04" =>
                    s_dbus_rd_data <= s_eth_rd_data;
                    s_eth_addr     <= s_dbus_addr(3 DOWNTO 0);
                    s_eth_wr_data  <= s_dbus_wr_data;
                    s_eth_wr_en    <= s_dbus_wr_en;
                WHEN X"10" =>
                    s_dbus_rd_data    <= s_cyc_cnt_rd_data;
                    s_cyc_cnt_wr_data <= s_dbus_wr_data;
                    s_cyc_cnt_wr_en   <= v_wr_en_word;
                WHEN OTHERS => NULL;
            END CASE;
        END IF;
    END PROCESS p_dbus;

    data_0: e_ram_0
        GENERIC MAP (
            addr_width => c_data_addr_width - 2
        )
        PORT MAP (
            clk       => clk,
            i_addr    => s_data_addr(c_data_addr_width - 1 DOWNTO 2),
            o_rd_data => s_data_rd_data(7 DOWNTO 0),
            i_wr_data => s_data_wr_data(7 DOWNTO 0),
            i_wr_en   => s_data_wr_en(0)
        );

    data_1: e_ram_1
        GENERIC MAP (
            addr_width => c_data_addr_width - 2
        )
        PORT MAP (
            clk       => clk,
            i_addr    => s_data_addr(c_data_addr_width - 1 DOWNTO 2),
            o_rd_data => s_data_rd_data(15 DOWNTO 8),
            i_wr_data => s_data_wr_data(15 DOWNTO 8),
            i_wr_en   => s_data_wr_en(1)
        );

    data_2: e_ram_2
        GENERIC MAP (
            addr_width => c_data_addr_width - 2
        )
        PORT MAP (
            clk       => clk,
            i_addr    => s_data_addr(c_data_addr_width - 1 DOWNTO 2),
            o_rd_data => s_data_rd_data(23 DOWNTO 16),
            i_wr_data => s_data_wr_data(23 DOWNTO 16),
            i_wr_en   => s_data_wr_en(2)
        );

    data_3: e_ram_3
        GENERIC MAP (
            addr_width => c_data_addr_width - 2
        )
        PORT MAP (
            clk       => clk,
            i_addr    => s_data_addr(c_data_addr_width - 1 DOWNTO 2),
            o_rd_data => s_data_rd_data(31 DOWNTO 24),
            i_wr_data => s_data_wr_data(31 DOWNTO 24),
            i_wr_en   => s_data_wr_en(3)
        );

    leds: e_io_leds
        PORT MAP (
            rst        => rst,
            clk        => clk,
            o_rd_data  => s_leds_rd_data,
            i_wr_data  => s_leds_wr_data,
            i_wr_en    => s_leds_wr_en,
            pin_o_leds => pin_o_leds
        );

    lcd: e_io_lcd
        PORT MAP (
            rst       => rst,
            clk       => clk,
            o_rd_data => s_lcd_rd_data,
            i_wr_data => s_lcd_wr_data,
            i_wr_en   => s_lcd_wr_en,
            pin_o_lcd => pin_o_lcd
        );

    switches: e_io_switches
        PORT MAP (
            rst            => rst,
            clk            => clk,
            i_addr         => s_switches_addr(2 DOWNTO 2),
            o_rd_data      => s_switches_rd_data,
            pin_i_switches => pin_i_switches
        );

    uart: e_io_uart
        PORT MAP (
            rst       => rst,
            clk       => clk,
            i_addr    => s_uart_addr(3 DOWNTO 2),
            o_rd_data => s_uart_rd_data,
            i_wr_data => s_uart_wr_data,
            i_wr_en   => s_uart_wr_en,
            pin_i_rx  => pin_i_uart_rx,
            pin_o_tx  => pin_o_uart_tx
        );

    eth: e_io_eth
        PORT MAP (
            rst          => rst,
            clk          => clk,
            i_addr       => s_eth_addr(3 DOWNTO 2),
            o_rd_data    => s_eth_rd_data,
            i_wr_data    => s_eth_wr_data,
            i_wr_en      => s_eth_wr_en,
            pin_o_nrst   => pin_o_eth_nrst,
            pin_i_rx_clk => pin_i_eth_rx_clk,
            pin_i_rxd    => pin_i_eth_rxd,
            pin_i_rx_dv  => pin_i_eth_rx_dv,
            pin_i_crs    => pin_i_eth_crs,
            pin_i_col    => pin_i_eth_col,
            pin_i_tx_clk => pin_i_eth_tx_clk,
            pin_o_txd    => pin_o_eth_txd,
            pin_o_tx_en  => pin_o_eth_tx_en
        );

    cyc_cnt: e_io_cyc_cnt
        PORT MAP (
            rst       => rst,
            clk       => clk,
            o_rd_data => s_cyc_cnt_rd_data,
            i_wr_data => s_cyc_cnt_wr_data,
            i_wr_en   => s_cyc_cnt_wr_en
        );

END ARCHITECTURE a_system;