-- cpu.vhd: Simple 8-bit CPU (BrainFuck interpreter) -- Copyright (C) 2024 Brno University of Technology, -- Faculty of Information Technology -- Author(s): Roman Necas -- 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;