-- cpu.vhd: Simple 8-bit CPU (BrainFuck interpreter)
-- Copyright (C) 2024 Brno University of Technology,
--                    Faculty of Information Technology
-- Author(s): Roman Necas <xnecasr00 AT stud.fit.vutbr.cz>
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

-- ----------------------------------------------------------------------------
--                        Entity declaration
-- ----------------------------------------------------------------------------
entity cpu is
 port (
   CLK   : in std_logic;  -- hodinovy signal
   RESET : in std_logic;  -- asynchronni reset procesoru
   EN    : in std_logic;  -- povoleni cinnosti procesoru
 
   -- synchronni pamet RAM
   DATA_ADDR  : out std_logic_vector(12 downto 0); -- adresa do pameti
   DATA_WDATA : out std_logic_vector(7 downto 0); -- mem[DATA_ADDR] <- DATA_WDATA pokud DATA_EN='1'
   DATA_RDATA : in std_logic_vector(7 downto 0);  -- DATA_RDATA <- ram[DATA_ADDR] pokud DATA_EN='1'
   DATA_RDWR  : out std_logic;                    -- cteni (1) / zapis (0)
   DATA_EN    : out std_logic;                    -- povoleni cinnosti
   
   -- vstupni port
   IN_DATA   : in std_logic_vector(7 downto 0);   -- IN_DATA <- stav klavesnice pokud IN_VLD='1' a IN_REQ='1'
   IN_VLD    : in std_logic;                      -- data platna
   IN_REQ    : out std_logic;                     -- pozadavek na vstup data
   
   -- vystupni port
   OUT_DATA : out  std_logic_vector(7 downto 0);  -- zapisovana data
   OUT_BUSY : in std_logic;                       -- LCD je zaneprazdnen (1), nelze zapisovat
   OUT_INV  : out std_logic;                      -- pozadavek na aktivaci inverzniho zobrazeni (1)
   OUT_WE   : out std_logic;                      -- LCD <- OUT_DATA pokud OUT_WE='1' a OUT_BUSY='0'

   -- stavove signaly
   READY    : out std_logic;                      -- hodnota 1 znamena, ze byl procesor inicializovan a zacina vykonavat program
   DONE     : out std_logic                       -- hodnota 1 znamena, ze procesor ukoncil vykonavani programu (narazil na instrukci halt)
 );
end cpu;


-- ----------------------------------------------------------------------------
--                      Architecture declaration
-- ----------------------------------------------------------------------------
architecture behavioral of cpu is

    -- ASCII-based instruction set constants for BrainFuck language implementation
    -- Each constant represents a specific command in the instruction set
    constant INST_PTR_INC  : std_logic_vector(7 downto 0) := X"3E";  -- > : Move data pointer right
    constant INST_PTR_DEC  : std_logic_vector(7 downto 0) := X"3C";  -- < : Move data pointer left
    constant INST_VAL_INC  : std_logic_vector(7 downto 0) := X"2B";  -- + : Increment value at pointer
    constant INST_VAL_DEC  : std_logic_vector(7 downto 0) := X"2D";  -- - : Decrement value at pointer
    constant INST_WHILE_START : std_logic_vector(7 downto 0) := X"5B";  -- [ : Start loop if value != 0
    constant INST_WHILE_END : std_logic_vector(7 downto 0) := X"5D";  -- ] : End loop if value != 0
    constant INST_WRITE   : std_logic_vector(7 downto 0) := X"2E";  -- . : Output value at pointer
    constant INST_READ    : std_logic_vector(7 downto 0) := X"2C";  -- , : Input value to pointer
    constant INST_STORE   : std_logic_vector(7 downto 0) := X"24";  -- $ : Store value to temp register
    constant INST_LOAD    : std_logic_vector(7 downto 0) := X"21";  -- ! : Load value from temp register
    constant INST_HALT    : std_logic_vector(7 downto 0) := X"40";  -- @ : Halt program execution

    -- Finite State Machine (FSM) states definition
    -- These states control the CPU's operation sequence
    type fsm_state is (
        s_idle,             -- Initial state: CPU is inactive
        s_fetch,            -- Fetch: Read next instruction from memory
        s_decode,           -- Decode: Interpret the fetched instruction
        s_init_find,        -- Initialization: Search for program start (@)
        s_init_find_read,   -- Initialization: Read memory during @ search
        s_init_ptr,         -- Initialization: Set up data pointer
        s_ptr_inc,          -- Execution: Increment data pointer
        s_ptr_dec,          -- Execution: Decrement data pointer
        s_val_inc,          -- Value Increment: Start
        s_val_inc_2,        -- Value Increment: Read current value
        s_val_inc_3,        -- Value Increment: Write back
        s_val_dec,          -- Value Decrement: Start
        s_val_dec_2,        -- Value Decrement: Read current value
        s_val_dec_3,        -- Value Decrement: Write back
        s_store_val,        -- Memory: Store value to temporary register
        s_load_val,         -- Memory: Load from temporary register
        s_load_val_2,       -- Memory: Complete load operation
        s_while_start,      -- Loop: Begin processing loop start
        s_while_start_2,    -- Loop: Check if we should enter loop
        s_while_start_3,    -- Loop: Find matching end bracket
        s_while_start_4,    -- Loop: Forward navigation state
        s_while_start_5,    -- Loop: Process navigation
        s_while_start_6,    -- Loop: Handle nested loops (forward)
        s_while_end,        -- Loop: Begin processing loop end
        s_while_end_2,      -- Loop: Check if we should exit loop
        s_while_end_3,      -- Loop: Find matching start bracket
        s_while_end_4,      -- Loop: Backward navigation state
        s_while_end_5,      -- Loop: Process backward navigation
        s_while_end_6,      -- Loop: Handle nested loops (backward)
        s_read_wait,        -- I/O: Wait for input data
        s_read_store,       -- I/O: Store input data
        s_write_wait,       -- I/O: Wait for output ready
        s_write,            -- I/O: Perform write operation
        s_halt              -- Program End: Halt execution
    );

    -- Register and Control Signal Declarations
    -- Program Counter (PC) related signals
    signal pc_reg : std_logic_vector(12 downto 0) := (others => '0');    -- Stores instruction address
    signal pc_inc : std_logic := '0';                        -- Increment PC
    signal pc_dec : std_logic := '0';                        -- Decrement PC
    signal pc_clear : std_logic := '0';                      -- Reset PC to zero

    -- Data Pointer (PTR) related signals
    signal ptr_reg : std_logic_vector(12 downto 0) := (others => '0');   -- Stores data memory address
    signal ptr_inc_i : std_logic := '0';                     -- Increment PTR
    signal ptr_dec_i : std_logic := '0';                     -- Decrement PTR
    signal ptr_pc_load : std_logic := '0';                   -- Load PC value into PTR

    -- Loop Counter (CNT) related signals
    signal cnt_reg : std_logic_vector(7 downto 0) := (others => '0');    -- Tracks nested loop depth
    signal cnt_inc : std_logic := '0';                       -- Increment counter
    signal cnt_dec : std_logic := '0';                       -- Decrement counter
    signal cnt_clear : std_logic := '0';                     -- Reset counter
    
    -- Temporary Storage Register
    signal tmp_reg : std_logic_vector(7 downto 0) := (others => '0');    -- Temporary data storage
    signal tmp_ld : std_logic := '0';                        -- Load control for tmp_reg
    
    -- FSM State Registers
    signal pstate : fsm_state := s_idle;                     -- Current state
    signal nstate : fsm_state := s_idle;                     -- Next state

    -- Multiplexer Control Signals
    signal mx1_sel : std_logic := '0';                       -- Address MUX (0: PC, 1: PTR)
    signal mx2_sel : std_logic_vector(1 downto 0) := "00";   -- Data MUX selection
                                                            -- 00: Input data
                                                            -- 01: Decremented value
                                                            -- 10: Incremented value
                                                            -- 11: Temporary register

begin
    -- Program Counter (PC) Register Process
    -- Controls program execution flow by managing instruction address
    pc_reg_proc: process (CLK, RESET)
    begin
        if RESET = '1' then
            -- Synchronous reset: Initialize PC to 0
            pc_reg <= (others => '0');
        elsif rising_edge(CLK) then
            if EN = '1' then  -- Only update when CPU is enabled
                if pc_clear = '1' then
                    -- Clear operation takes precedence
                    pc_reg <= (others => '0');
                elsif pc_inc = '1' then
                    -- Increment PC for next instruction
                    pc_reg <= pc_reg + 1;
                elsif pc_dec = '1' then
                    -- Decrement PC (used in loop operations)
                    pc_reg <= pc_reg - 1;
                end if;
            end if;
        end if;
    end process;

    -- Data Pointer (PTR) Register Process
    -- Manages data memory addressing with wraparound functionality
    ptr_reg_proc: process (CLK, RESET)
    begin
        if RESET = '1' then
            -- Synchronous reset: Initialize PTR to 0
            ptr_reg <= (others => '0');
        elsif rising_edge(CLK) then
            if EN = '1' then  -- Only update when CPU is enabled
                if ptr_pc_load = '1' then
                    -- Load PC value into PTR (used during initialization)
                    ptr_reg <= pc_reg;  
                elsif ptr_inc_i = '1' then
                    -- Increment with wraparound at maximum value
                    if ptr_reg = "1111111111111" then
                        ptr_reg <= (others => '0');
                    else
                        ptr_reg <= ptr_reg + 1;
                    end if;
                elsif ptr_dec_i = '1' then
                    -- Decrement with wraparound at zero
                    if ptr_reg = "0000000000000" then
                        ptr_reg <= "1111111111111";
                    else
                        ptr_reg <= ptr_reg - 1;
                    end if;
                end if;
            end if;
        end if;
    end process;

    -- Loop Counter (CNT) Register Process
    -- Manages nested loop depth tracking
    cnt_reg_proc: process (CLK, RESET)
    begin
        if RESET = '1' then
            -- Synchronous reset: Initialize counter to 0
            cnt_reg <= (others => '0');
        elsif rising_edge(CLK) then
            if EN = '1' then  -- Only update when CPU is enabled
                if cnt_clear = '1' then
                    -- Clear counter (start of new loop processing)
                    cnt_reg <= (others => '0');
                elsif cnt_inc = '1' then
                    -- Increment counter (nested loop entry)
                    cnt_reg <= cnt_reg + 1;
                elsif cnt_dec = '1' and cnt_reg /= "00000000" then
                    -- Decrement counter (nested loop exit)
                    -- Only decrement if not already zero
                    cnt_reg <= cnt_reg - 1;
                end if;
            end if;
        end if;
    end process;

    -- Temporary (TMP) Register Process
    -- Provides temporary storage for data values
    tmp_reg_proc: process (CLK, RESET)
    begin
        if RESET = '1' then
            -- Synchronous reset: Clear temporary storage
            tmp_reg <= (others => '0');
        elsif rising_edge(CLK) then
            if EN = '1' and tmp_ld = '1' then
                -- Load new value from data memory when enabled
                tmp_reg <= DATA_RDATA;
            end if;
        end if;
    end process;

    -- FSM Present State Register Process
    -- Manages state transitions in the CPU's control unit
    fsm_pstate: process (CLK, RESET)
    begin
        if RESET = '1' then
            -- Synchronous reset: Return to idle state
            pstate <= s_idle;
        elsif rising_edge(CLK) then
            if EN = '1' then
                -- Update current state with next state
                pstate <= nstate;
            end if;
        end if;
    end process;

-- FSM Next State Logic & Output Logic
    -- This process handles:
    -- 1. State transitions based on current state and inputs
    -- 2. Control signal generation for each state
    -- 3. Output signal management
    fsm_nstate: process (pstate, DATA_RDATA, IN_VLD, OUT_BUSY, cnt_reg, EN)
    begin
        -- Default signal assignments to prevent latches
        -- Control signals are set to inactive by default
        nstate <= pstate;        -- Maintain current state unless changed
        pc_inc <= '0';           -- No PC increment
        pc_dec <= '0';           -- No PC decrement
        pc_clear <= '0';         -- No PC reset
        ptr_inc_i <= '0';        -- No PTR increment
        ptr_dec_i <= '0';        -- No PTR decrement
        ptr_pc_load <= '0';      -- No PTR loading from PC
        cnt_inc <= '0';          -- No counter increment
        cnt_dec <= '0';          -- No counter decrement
        cnt_clear <= '0';        -- No counter reset
        tmp_ld <= '0';           -- No temporary register load
        mx1_sel <= '0';          -- Address MUX defaults to PC
        mx2_sel <= "00";         -- Data MUX defaults to input data
        DATA_EN <= '0';          -- Memory access disabled
        DATA_RDWR <= '1';        -- Default to read operation
        IN_REQ <= '0';           -- No input request
        OUT_WE <= '0';           -- No output write
        OUT_DATA <= (others => '0'); -- Clear output data
        DONE <= '0';             -- Not done by default
        
        -- READY signal logic
        -- Only inactive during initialization states
        if pstate = s_idle or pstate = s_init_find or pstate = s_init_find_read then
            READY <= '0';
        else
            READY <= '1';
        end if;

        -- Main FSM state machine
        case pstate is
            -- Initial state: Wait for enable signal
            when s_idle =>
                if EN = '1' then
                    nstate <= s_init_find;  -- Start initialization
                    DATA_EN <= '1';         -- Enable memory access
                    DATA_RDWR <= '1';       -- Set to read mode
                end if;

            -- Initialization: Search for program start marker (@)
            when s_init_find =>
                DATA_EN <= '1';      -- Enable memory access
                DATA_RDWR <= '1';    -- Set to read mode
                nstate <= s_init_find_read;  -- Move to read state

            -- Read during initialization
            when s_init_find_read =>
                DATA_EN <= '1';
                DATA_RDWR <= '1';
                if DATA_RDATA = INST_HALT then  -- Found @ symbol
                    nstate <= s_init_ptr;
                    ptr_pc_load <= '1';    -- Save program start position
                else
                    pc_inc <= '1';         -- Continue searching
                    nstate <= s_init_find;
                end if;

            -- Initialize data pointer
            when s_init_ptr =>
                ptr_inc_i <= '1';    -- Move past program in memory
                pc_clear <= '1';     -- Reset PC to start of program
                nstate <= s_fetch;   -- Begin program execution

            -- Fetch cycle: Read next instruction
            when s_fetch =>
                DATA_EN <= '1';      -- Enable memory access
                DATA_RDWR <= '1';    -- Set to read mode
                nstate <= s_decode;  -- Move to decode state

            -- Decode cycle: Interpret instruction
            when s_decode =>
                DATA_EN <= '1';
                DATA_RDWR <= '1';
                
                -- Instruction decoding logic
                case DATA_RDATA is
                    when INST_PTR_INC =>       -- > : Move pointer right
                        nstate <= s_ptr_inc;
                    when INST_PTR_DEC =>       -- < : Move pointer left
                        nstate <= s_ptr_dec;
                    when INST_VAL_INC =>       -- + : Increment value
                        nstate <= s_val_inc;
                        mx1_sel <= '1';        -- Select PTR address
                    when INST_VAL_DEC =>       -- - : Decrement value
                        nstate <= s_val_dec;
                        mx1_sel <= '1';        -- Select PTR address
                    when INST_WRITE =>         -- . : Output value
                        nstate <= s_write_wait;
                        mx1_sel <= '1';        -- Select PTR address
                    when INST_READ =>          -- , : Input value
                        nstate <= s_read_wait;
                        IN_REQ <= '1';         -- Request input
                    when INST_STORE =>         -- $ : Store to temp
                        nstate <= s_store_val;
                        mx1_sel <= '1';        -- Select PTR address
                    when INST_LOAD =>          -- ! : Load from temp
                        nstate <= s_load_val;
                        mx1_sel <= '1';        -- Select PTR address
                    when INST_WHILE_START =>   -- [ : Loop start
                        nstate <= s_while_start;
                        mx1_sel <= '1';        -- Select PTR address
                    when INST_WHILE_END =>     -- ] : Loop end
                        nstate <= s_while_end;
                        mx1_sel <= '1';        -- Select PTR address
                    when INST_HALT =>          -- @ : Halt program
                        nstate <= s_halt;
                    when others =>             -- Invalid/ignore
                        nstate <= s_fetch;
                        pc_inc <= '1';         -- Skip invalid instruction
                end case;

            -- Pointer increment operation
            when s_ptr_inc =>
                ptr_inc_i <= '1';    -- Increment data pointer
                pc_inc <= '1';       -- Move to next instruction
                nstate <= s_fetch;   -- Return to fetch cycle

            -- Pointer decrement operation
            when s_ptr_dec =>
                ptr_dec_i <= '1';    -- Decrement data pointer
                pc_inc <= '1';       -- Move to next instruction
                nstate <= s_fetch;   -- Return to fetch cycle

            -- Value increment operation (3-stage)
            when s_val_inc =>
                DATA_EN <= '1';      -- Enable memory
                DATA_RDWR <= '1';    -- Read mode
                mx1_sel <= '1';      -- Use PTR address
                nstate <= s_val_inc_2;

            when s_val_inc_2 =>
                DATA_EN <= '1';
                DATA_RDWR <= '1';    -- Still reading
                mx1_sel <= '1';      -- Still using PTR
                nstate <= s_val_inc_3;

            when s_val_inc_3 =>
                DATA_EN <= '1';
                DATA_RDWR <= '0';    -- Switch to write mode
                mx1_sel <= '1';      -- Still using PTR
                mx2_sel <= "10";     -- Select incremented value
                pc_inc <= '1';       -- Next instruction
                nstate <= s_fetch;

            -- Value decrement operation (3-stage)
            when s_val_dec =>
                DATA_EN <= '1';      -- Enable memory
                DATA_RDWR <= '1';    -- Read mode
                mx1_sel <= '1';      -- Use PTR address
                nstate <= s_val_dec_2;

            when s_val_dec_2 =>
                DATA_EN <= '1';
                DATA_RDWR <= '1';    -- Still reading
                mx1_sel <= '1';      -- Still using PTR
                nstate <= s_val_dec_3;

            when s_val_dec_3 =>
                DATA_EN <= '1';
                DATA_RDWR <= '0';    -- Switch to write mode
                mx1_sel <= '1';      -- Still using PTR
                mx2_sel <= "01";     -- Select decremented value
                pc_inc <= '1';       -- Next instruction
                nstate <= s_fetch;

            -- Store value to temporary register
            when s_store_val =>
                DATA_EN <= '1';      -- Enable memory
                DATA_RDWR <= '1';    -- Read mode
                tmp_ld <= '1';       -- Enable temp register load
                pc_inc <= '1';       -- Next instruction
                nstate <= s_fetch;

            -- Load value from temporary register
            when s_load_val =>
                DATA_EN <= '1';      -- Enable memory
                DATA_RDWR <= '0';    -- Write mode
                mx1_sel <= '1';      -- Use PTR address
                mx2_sel <= "11";     -- Select temp register value
                pc_inc <= '1';       -- Next instruction
                nstate <= s_fetch;

            -- Input handling states
            when s_read_wait =>
                IN_REQ <= '1';       -- Request input
                if IN_VLD = '1' then -- Input data valid
                    nstate <= s_read_store;
                end if;

            when s_read_store =>
                DATA_EN <= '1';      -- Enable memory
                DATA_RDWR <= '0';    -- Write mode
                mx1_sel <= '1';      -- Use PTR address
                mx2_sel <= "00";     -- Select input data
                pc_inc <= '1';       -- Next instruction
                nstate <= s_fetch;

            -- Output handling states
            when s_write_wait =>
               DATA_EN <= '1';       -- Enable memory
               DATA_RDWR <= '1';     -- Read mode
               mx1_sel <= '1';       -- Use PTR address
               if OUT_BUSY = '0' then -- Output ready
                   nstate <= s_write;
               end if;

           when s_write =>
               OUT_WE <= '1';        -- Write to output
               OUT_DATA <= DATA_RDATA; -- Output current value
               pc_inc <= '1';        -- Next instruction
               nstate <= s_fetch;

            -- Loop start handling states
            when s_while_start =>
                DATA_EN <= '1';      -- Enable memory
                DATA_RDWR <= '1';    -- Read mode
                mx1_sel <= '1';      -- Use PTR address
                nstate <= s_while_start_2;

            when s_while_start_2 =>
                DATA_EN <= '1';
                DATA_RDWR <= '1';
                mx1_sel <= '1';
                nstate <= s_while_start_3;

            when s_while_start_3 =>
                if DATA_RDATA = "00000000" then  -- Skip loop if value is 0
                    pc_inc <= '1';              
                    cnt_clear <= '1';           
                    nstate <= s_while_start_4;
                else                    
                    pc_inc <= '1';      -- Enter loop
                    nstate <= s_fetch;
                end if;

            -- Loop start bracket matching states
            when s_while_start_4 =>    
                DATA_EN <= '1';
                DATA_RDWR <= '1';
                pc_inc <= '1';
                nstate <= s_while_start_5;

            when s_while_start_5 =>
                DATA_EN <= '1';
                DATA_RDWR <= '1';
                nstate <= s_while_start_6;

            when s_while_start_6 =>
                if DATA_RDATA = INST_WHILE_START then     -- Found nested [
                    cnt_inc <= '1';                       -- Increment nest level
                    nstate <= s_while_start_4;
                elsif DATA_RDATA = INST_WHILE_END then    -- Found ]
                    if cnt_reg = "00000000" then          -- Matching ] found
                        pc_inc <= '1';
                        nstate <= s_fetch;
                    else                                  -- Nested ] found
                        cnt_dec <= '1';
                        nstate <= s_while_start_4;
                    end if;
                else
                    nstate <= s_while_start_4;           -- Keep searching
                end if;

            -- Loop end handling states
            when s_while_end =>
                DATA_EN <= '1';      -- Enable memory
                DATA_RDWR <= '1';    -- Read mode
                mx1_sel <= '1';      -- Use PTR address
                nstate <= s_while_end_2;

            when s_while_end_2 =>
                DATA_EN <= '1';
                DATA_RDWR <= '1';
                mx1_sel <= '1';
                nstate <= s_while_end_3;

            when s_while_end_3 =>
                if DATA_RDATA /= "00000000" then  -- Continue loop if value not 0
                    pc_dec <= '1';              
                    cnt_clear <= '1';           
                    nstate <= s_while_end_4;
                else                    
                    pc_inc <= '1';      -- Exit loop
                    nstate <= s_fetch;
                end if;

            -- Loop end bracket matching states
            when s_while_end_4 =>
                DATA_EN <= '1';
                DATA_RDWR <= '1';
                pc_dec <= '1';     -- Search backwards
                nstate <= s_while_end_5;

            when s_while_end_5 =>
                DATA_EN <= '1';
                DATA_RDWR <= '1';
                nstate <= s_while_end_6;

            when s_while_end_6 =>
                if DATA_RDATA = INST_WHILE_END then      -- Found nested ]
                    cnt_inc <= '1';                      -- Increment nest level
                    nstate <= s_while_end_4;
                elsif DATA_RDATA = INST_WHILE_START then -- Found [
                    if cnt_reg = "00000000" then         -- Matching [ found
                        pc_inc <= '1';
                        nstate <= s_fetch;
                    else                                 -- Nested [ found
                        cnt_dec <= '1';
                        nstate <= s_while_end_4;
                    end if;
                else
                    nstate <= s_while_end_4;            -- Keep searching
                end if;

            -- Program termination state
            when s_halt =>
                DONE <= '1';           -- Signal program completion
                nstate <= s_halt;      -- Stay in halt state

            -- Fallback state (shouldn't occur)
            when others =>
                nstate <= s_idle;      -- Return to idle state

        end case;
    end process;

    -- Output signal assignments
    OUT_INV <= '0';   -- Display is always in normal mode
   
    -- Address Multiplexer (MX1)
    -- Selects between program counter (PC) and data pointer (PTR)
    DATA_ADDR <= ptr_reg when mx1_sel = '1' else pc_reg;

    -- Data Multiplexer (MX2)
    -- Selects between different data sources based on operation
    DATA_WDATA <= IN_DATA when mx2_sel = "00" else           -- Input data
                 DATA_RDATA - 1 when mx2_sel = "01" else     -- Decremented value
                 DATA_RDATA + 1 when mx2_sel = "10" else     -- Incremented value
                 tmp_reg;                                     -- Temporary register

end behavioral;