library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.alu_types.all; use work.decoder.all; entity state_machine is port( clk : in std_logic; opcode : in std_logic_vector(13 downto 0); reset : in std_logic; op : out alu_op; we_mem : out std_logic := '0'; re_mem : out std_logic := '0'; we_w : out std_logic := '0'; instr_ret : out std_logic := '0'; bit_select : out std_logic_vector(2 downto 0) := (others => '0'); data : out std_logic_vector(7 downto 0) := (others => '0'); pc : out std_logic_vector(12 downto 0); addr : out std_logic_vector(6 downto 0) := (others => '0'); use_literal : out std_logic; we_status : out std_logic := '0'; stack_push : out std_logic := '0'; stack_pop : out std_logic := '0'; stack_din : out std_logic_vector(12 downto 0) := (others => '0'); stack_dout : in std_logic_vector(12 downto 0); alu_skip : in std_logic; alu_result : in std_logic_vector(7 downto 0) ); end entity state_machine; architecture rtl of state_machine is type state_t is (IFetch, MRead, Execute, MWrite); signal pc_internal : std_logic_vector(12 downto 0) := (others => '0'); signal state : state_t := IFetch; signal pclath : std_logic_vector(4 downto 0) := (others => '0'); begin pc <= pc_internal; fsm : process(clk) variable instr : instruction_t; -- do we do a phantom nop after this instruction? variable do_phantom_nop : boolean := false; variable is_phantom : boolean := false; -- true when phantom came from DECFSZ/INCFSZ skip (must still increment PC) -- false when phantom came from GOTO/CALL/RETURN (PC already at target) variable is_skip_phantom : boolean := false; begin if rising_edge(clk) then re_mem <= '0'; we_mem <= '0'; we_w <= '0'; we_status <= '0'; instr_ret <= '0'; stack_push <= '0'; stack_pop <= '0'; if reset = '1' then pc_internal <= (others => '0'); pclath <= (others => '0'); state <= IFetch; do_phantom_nop := false; else case state is when IFetch => -- if we had a branch before, this cycle is a NOP. if do_phantom_nop then instr := instruction_decode((others => '0')); do_phantom_nop := false; is_phantom := true; -- is_skip_phantom carries over from whoever set do_phantom_nop else instr := instruction_decode(opcode); is_phantom := false; is_skip_phantom := false; end if; -- set use_literal based on decoded instruction, holds through MWrite if instr.class = LITERAL_OP then use_literal <= '1'; else use_literal <= '0'; end if; -- for literal operations data <= instr.k(7 downto 0); -- for memory-read/write operations addr <= instr.f; -- for bit-select operations bit_select <= instr.b; state <= Mread; when MRead => -- read memory addressed by the opcode -- MOVWF, CLRF, CLRW, NOP are write-only. if instr.class = BIT_OP or (instr.class = BYTE_OP and instr.op /= MOVWF and instr.op /= CLRF and instr.op /= CLRW and instr.op /= NOP) then re_mem <= '1'; end if; state <= Execute; when Execute => op <= instr.op; -- move the decoded instruction to the ALU if not is_phantom then -- increment PC or change address, different for different instructions. if instr.op = GOTO then pc_internal <= "00" & instr.k; do_phantom_nop := true; is_skip_phantom := false; elsif instr.op = CALL then -- push PC+1 to the stack stack_din <= std_logic_vector(unsigned(pc_internal) + 1); stack_push <= '1'; -- go to the function we called pc_internal <= "00" & instr.k; do_phantom_nop := true; is_skip_phantom := false; elsif instr.op = RETUR or instr.op = RETLW then -- pop the PC from stack. pc_internal <= stack_dout; stack_pop <= '1'; -- pop deferred to next cycle. do_phantom_nop := true; is_skip_phantom := false; else pc_internal <= std_logic_vector(unsigned(pc_internal) + 1); end if; elsif is_skip_phantom then -- skip phantom: PC was at the skipped instruction, step past it pc_internal <= std_logic_vector(unsigned(pc_internal) + 1); end if; -- branch phantom: PC already at target, no change needed state <= MWrite; when MWrite => -- write to correct memory based on class (skip for phantom NOP) if not is_phantom then case instr.class is when BYTE_OP => if instr.op /= NOP then if instr.d = '1' then we_mem <= '1'; -- Write to PCLATH (addr 0x0A): latch upper PC bits if instr.f = "0001010" then pclath <= alu_result(4 downto 0); end if; -- Write to PCL (addr 0x02): redirect PC, like a GOTO if instr.f = "0000010" then pc_internal <= pclath & alu_result; do_phantom_nop := true; is_skip_phantom := false; end if; else we_w <= '1'; end if; end if; when BIT_OP => -- NOT for the BTFSS or BTFSC instructions. if instr.op = BCF or instr.op = BSF then we_mem <= '1'; end if; when LITERAL_OP => if instr.op /= NOP and instr.op /= RETUR then we_w <= '1'; end if; when others => null; end case; end if; -- DECFSZ/INCFSZ/BTFSS/BTFSC: skip next instruction if ALU signals skip if not is_phantom and (instr.op = DECFSZ or instr.op = INCFSZ or instr.op = BTFSS or instr.op = BTFSC) and alu_skip = '1' then do_phantom_nop := true; is_skip_phantom := true; end if; if not is_phantom then we_status <= '1'; -- branch instructions complete over 2 cycles; pulse instr_ret only -- on the phantom NOP cycle (below), not here if instr.op /= GOTO and instr.op /= CALL and instr.op /= RETUR and instr.op /= RETLW then instr_ret <= '1'; end if; elsif not is_skip_phantom then -- branch phantom NOP: this is the true end of the 2-cycle instruction instr_ret <= '1'; end if; state <= IFetch; end case; end if; -- reset end if; end process fsm; end architecture rtl;