Projects/2BIT/winter-semester/INP1/cpu.vhd
2026-04-14 19:28:46 +02:00

595 lines
No EOL
26 KiB
VHDL

-- 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;