diff --git a/cpu/alu.vhdl b/cpu/alu.vhdl new file mode 100644 index 0000000..a5e0814 --- /dev/null +++ b/cpu/alu.vhdl @@ -0,0 +1,70 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity alu is + port( + a: in std_logic_vector(15 downto 0); + b: in std_logic_vector(15 downto 0); + sel: in std_logic_vector(3 downto 0); + + flag: out std_logic; + q: out std_logic_vector(15 downto 0) + ); +end alu; + +architecture behavior of alu is +begin + +process(a, b, sel) is + variable tmp: std_logic_vector(31 downto 0); + variable idx: natural; +begin + + q <= x"0000"; + flag <= '0'; + tmp := x"00000000"; + idx := 0; + + case sel is + when "0011" => -- q = a + b, flag is carry + tmp(16 downto 0) := std_logic_vector(unsigned('0' & a) + unsigned(b)); + q <= tmp(15 downto 0); + flag <= tmp(16); + when "0100" => -- q = a - b, flag if a < b + tmp(16 downto 0) := std_logic_vector(unsigned('1' & a) - unsigned(b)); + q <= tmp(15 downto 0); + flag <= not tmp(16); + when "0101" => q <= a or b; + when "0110" => q <= a and b; + when "0111" => q <= not a; + when "1000" => q <= a xor b; + when "1001" => -- left shift by 1 + q <= a(14 downto 0) & '0'; + flag <= a(15); + when "1010" => -- right shift by b(3 downto 0) + idx := to_integer(unsigned(b(3 downto 0))); + tmp(15 - idx downto 0) := a(15 downto idx); + q <= tmp(15 downto 0); + flag <= a(0); + when "1011" => -- q = a * b + --tmp := std_logic_vector(unsigned(a) * unsigned(b)); + q <= tmp(15 downto 0); + when "1100" => -- flag if a = b + if (a = b) then + flag <= '1'; + else + flag <= '0'; + end if; + + --- room for a few more things: + -- - rotate + -- - not / nand + -- - gt... + + when others => + -- do nothing + end case; +end process; + +end behavior; diff --git a/cpu/cpu.vhdl b/cpu/cpu.vhdl new file mode 100644 index 0000000..84c64cb --- /dev/null +++ b/cpu/cpu.vhdl @@ -0,0 +1,197 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity cpu is + port( + clk: in std_logic; + rst: in std_logic; + + code_data: in std_logic_vector(15 downto 0); + code_addr: out std_logic_vector(15 downto 0); + + mem_in: in std_logic_vector(15 downto 0); + mem_out: out std_logic_vector(15 downto 0); + mem_addr: out std_logic_vector(15 downto 0); + mem_write: out std_logic; + mem_read: out std_logic + ); +end entity cpu; + +architecture behavior of cpu is + component alu is + port( + a: in std_logic_vector(15 downto 0); + b: in std_logic_vector(15 downto 0); + sel: in std_logic_vector(3 downto 0); + + flag: out std_logic; + q: out std_logic_vector(15 downto 0) + ); + end component; + + component reg is + port( + clk : in std_logic; + rst : in std_logic; + + d : in std_logic_vector(15 downto 0); + q : out std_logic_vector(15 downto 0) + ); + end component; + + signal alu_a: std_logic_vector(15 downto 0); + signal alu_b: std_logic_vector(15 downto 0); + signal alu_q: std_logic_vector(15 downto 0); + signal alu_sel: std_logic_vector(3 downto 0); + signal alu_flag: std_logic; + + signal load_reg_next, load_reg: std_logic_vector(15 downto 0); + signal load_addr_next, load_addr: std_logic_vector(15 downto 0); + + type regbank is array(0 to 15) of std_logic_vector(15 downto 0); + signal reg_d: regbank; + signal reg_q: regbank; + + type cpu_state_t is (RUN, LOAD, BRANCH); + signal cpu_state, cpu_state_next: cpu_state_t; +begin + cpu_alu: alu port map(a => alu_a, b => alu_b, sel => alu_sel, flag => alu_flag, q => alu_q); + + load_reg_r: reg port map(clk => clk, rst => rst, d => load_reg_next, q => load_reg); + load_addr_r: reg port map(clk => clk, rst => rst, d => load_addr_next, q => load_addr); + + allregs: + for i in 0 to 15 generate + regx: reg port map(clk => clk, rst => rst, d => reg_d(i), q => reg_q(i)); + end generate allregs; + + process(clk, rst) + begin + if rst = '1' then + cpu_state <= BRANCH; -- wait a cycle at first + elsif rising_edge(clk) then + cpu_state <= cpu_state_next; + end if; + end process; + + code_addr <= reg_q(14); + + process(code_data, reg_q, mem_in, alu_q, alu_flag, cpu_state, load_addr, load_reg) is + variable inst: std_logic_vector(15 downto 0); + variable regn_0: natural; + variable regn_1: natural; + variable regn_2: natural; + variable do_alu: std_logic; + begin + mem_write <= '0'; + mem_read <= '0'; + mem_addr <= x"0000"; + mem_out <= x"0000"; + + alu_sel <= "0000"; + alu_a <= x"0000"; + alu_b <= x"0000"; + + do_alu := '0'; + + for i in 0 to 15 loop + reg_d(i) <= reg_q(i); + end loop; + + cpu_state_next <= RUN; + + load_reg_next <= load_reg; + load_addr_next <= load_addr; + + case cpu_state is + when RUN => + reg_d(14) <= std_logic_vector(unsigned(reg_q(14)) + 2); + inst := code_data; + when LOAD => + inst := x"0000"; -- NOP + mem_addr <= load_addr; -- maintain this until we're done reading + if load_reg(3 downto 0) = x"e" then + cpu_state_next <= BRANCH; + else + reg_d(14) <= std_logic_vector(unsigned(reg_q(14)) + 2); + end if; + + regn_0 := to_integer(unsigned(load_reg(3 downto 0))); + reg_d(regn_0) <= mem_in; + when BRANCH => + inst := x"0000"; -- NOP + reg_d(14) <= std_logic_vector(unsigned(reg_q(14)) + 2); + end case; + + regn_0 := to_integer(unsigned(inst(11 downto 8))); + regn_1 := to_integer(unsigned(inst(7 downto 4))); + regn_2 := to_integer(unsigned(inst(3 downto 0))); + + case inst(15 downto 12) is + when "0000" => -- NOP + when "0001" => -- LOAD rn, [rm, imm] (imm is signed 4 bits) + mem_read <= '1'; + cpu_state_next <= LOAD; + reg_d(14) <= reg_q(14); -- halt the prefetcher + + load_addr_next <= std_logic_vector(signed(reg_q(regn_1)) + signed(inst(3 downto 0) & '0')); + mem_addr <= std_logic_vector(signed(reg_q(regn_1)) + signed(inst(3 downto 0) & '0')); + load_reg_next(3 downto 0) <= inst(11 downto 8); + when "0010" => -- STORE rn, [rm, imm] + mem_write <= '1'; + mem_addr <= std_logic_vector(signed(reg_q(regn_1)) + signed(inst(3 downto 0) & '0')); + mem_out <= reg_q(regn_0); + + --- ALU stuff + when "0011" => do_alu := '1'; -- ADD rd, rn, rm (rd := rn + rm) + when "0100" => do_alu := '1'; -- SUB rd, rn, rm (rd := rn - rm) + when "0101" => do_alu := '1'; -- OR rd, rn, rm (rd := rn or rm) + when "0110" => do_alu := '1'; -- AND rd, rn, rm (rd := rn and rm) + when "0111" => do_alu := '1'; -- NOT rd, rn (rd := not rn) + when "1000" => do_alu := '1'; -- XOR rd, rn, rm (rd := rn xor rm) + when "1001" => -- SETH rd, imm + reg_d(regn_0)(15 downto 8) <= inst(7 downto 0); + when "1010" => -- SHR rd, rn, imm (rd := rn >> imm) + alu_sel <= inst(15 downto 12); + alu_a <= reg_q(regn_1); + alu_b <= x"000" & inst(3 downto 0); + reg_d(regn_0) <= alu_q; + when "1011" => do_alu := '1'; -- MUL rd, rn, rm (rd := rn * rm) + + when "1100" => -- CMP rn, rm (flag := 1 if equal) + alu_sel <= "1100"; + alu_a <= reg_q(regn_0); + alu_b <= reg_q(regn_1); + reg_d(15)(0) <= alu_flag; + + when "1101" => -- BEQ [rn, imm] (jump to [rn, imm] if flag is set, imm is signed 8 bits) + if reg_q(15)(0) = '1' then + reg_d(14) <= std_logic_vector(signed(reg_q(regn_0)) + signed(inst(7 downto 0) & '0')); + cpu_state_next <= BRANCH; + end if; + when "1110" => -- SET rd, imm (rd := imm, imm is 8 bit) + reg_d(regn_0) <= x"00" & inst(7 downto 0); + when "1111" => -- BNEQ [rn, imm] + if reg_q(15)(0) = '0' then + reg_d(14) <= std_logic_vector(signed(reg_q(regn_0)) + signed(inst(7 downto 0) & '0')); + cpu_state_next <= BRANCH; + end if; + + when others => -- do nothing + end case; + + if do_alu = '1' then + -- 1:1 mapping + alu_sel <= inst(15 downto 12); + alu_a <= reg_q(regn_1); + alu_b <= reg_q(regn_2); + reg_d(regn_0) <= alu_q; + reg_d(15)(0) <= alu_flag; + if inst(11 downto 8) = x"e" then + cpu_state_next <= BRANCH; + end if; + end if; + end process; + +end behavior; diff --git a/cpu/cpu_test.vhdl b/cpu/cpu_test.vhdl new file mode 100644 index 0000000..05ceebf --- /dev/null +++ b/cpu/cpu_test.vhdl @@ -0,0 +1,123 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; +use std.textio.all; + +entity cpu_test is +end cpu_test; + +architecture rtl of cpu_test is + + component cpu is port( + clk: in std_logic; + rst: in std_logic; + + code_data: in std_logic_vector(15 downto 0); + code_addr: out std_logic_vector(15 downto 0); + + mem_in: in std_logic_vector(15 downto 0); + mem_out: out std_logic_vector(15 downto 0); + mem_addr: out std_logic_vector(15 downto 0); + mem_write: out std_logic; + mem_read: out std_logic + ); + end component; + + type romtype is array(0 to 11) of std_logic_vector(15 downto 0); + signal romdata: romtype := ( + x"0000", -- NOP + x"e02a", -- SET r0, 42 + x"e125", -- SET r1, 37 + x"2010", -- STORE r0, [r1] + x"1210", -- LOAD r2, [r1] + x"3322", -- ADD r3, r2, r2 + x"2310", -- STORE r3, [r1] + x"c020", -- CMP r0, r2 + x"de01", -- BEQ pc, 2 + x"0000", -- NOP + x"ee00", -- SET pc, 0 + x"0000" + ); + + signal finished, clk, rst: std_logic := '0'; + signal mem_write, mem_read: std_logic; + signal rom_data, rom_addr, mem_in, mem_out, mem_addr: std_logic_vector(15 downto 0); + +begin + dut: cpu port map(clk => clk, rst => rst, + code_data => rom_data, code_addr => rom_addr, + mem_in => mem_in, mem_out => mem_out, mem_addr => mem_addr, + mem_write => mem_write, mem_read => mem_read); + + -- clock + process + begin + if finished = '0' then + clk <= not clk; + wait for 5 ns; + else + clk <= '0'; + wait; + end if; + end process; + + -- rom + process(clk) is + variable code_index: natural; + variable data_index: natural; + constant alignment: positive := 16 / 8; + begin + if rising_edge(clk) then + code_index := to_integer(unsigned(rom_addr)) / alignment; + rom_data <= romdata(code_index); + end if; + end process; + + process(rom_addr) + constant alignment: positive := 16 / 8; + variable index: natural; + begin + end process; + + process + begin + rst <= '1'; + + wait for 1 ns; + assert(rom_addr=x"0000") report "Fail rst" severity error; + + rst <= '0'; + + wait for 10 ns; + assert(rom_addr=x"0002") report "Fail PC advance @00" severity error; + + wait for 30 ns; + assert(rom_addr=x"0008") report "Fail PC @06" severity error; + assert(mem_write='1') report "Fail set mem_write to 1" severity error; + assert(mem_addr=x"0025") report "Fail set mem_addr to 0x25" severity error; + assert(mem_out=x"002a") report "Fail set mem_out to 42" severity error; + + wait for 10 ns; + assert(rom_addr=x"000a") report "Fail PC @08" severity error; + assert(mem_write='0') report "Fail set mem_write to 0" severity error; + assert(mem_read='1') report "Fail set mem_read to 1" severity error; + assert(mem_addr=x"0025") report "Fail set mem_addr to 0x25" severity error; + mem_in <= x"002a"; + + wait for 30 ns; + assert(rom_addr=x"000e") report "Fail PC @0c" severity error; + assert(mem_write='1') report "Fail set mem_write to 1" severity error; + assert(mem_addr=x"0025") report "Fail set mem_addr to 0x25" severity error; + assert(mem_out=x"0054") report "Fail set mem_out to 84" severity error; + + wait for 40 ns; + assert(rom_addr=x"0016") report "Fail to branch" severity error; + + wait for 10 ns; + assert(rom_addr=x"0000") report "Fail to jump" severity error; + + assert false report "Test done." severity note; + finished <= '1'; + wait; + end process; +end rtl; diff --git a/cpu/makefile b/cpu/makefile new file mode 100644 index 0000000..6422672 --- /dev/null +++ b/cpu/makefile @@ -0,0 +1,18 @@ +all: sim +sim: test.ghw + +sim_sources = cpu_test.vhdl +sources = cpu.vhdl alu.vhdl reg.vhdl +test_entity = cpu_test + + +test.ghw: work-obj93.cf + ghdl -r $(test_entity) --wave=$@ + + +work-obj93.cf: $(sim_sources) $(sources) + ghdl -a $^ + +PHONY: sim + +.PRECIOUS: test.ghw diff --git a/cpu/reg.vhdl b/cpu/reg.vhdl new file mode 100644 index 0000000..30ede54 --- /dev/null +++ b/cpu/reg.vhdl @@ -0,0 +1,55 @@ +---------------------------------------------------------------------------------- +-- Company: +-- Engineer: +-- +-- Create Date: 02/13/2021 01:13:18 PM +-- Design Name: +-- Module Name: register - Behavioral +-- Project Name: +-- Target Devices: +-- Tool Versions: +-- Description: +-- +-- Dependencies: +-- +-- Revision: +-- Revision 0.01 - File Created +-- Additional Comments: +-- +---------------------------------------------------------------------------------- + + +library IEEE; +use IEEE.STD_LOGIC_1164.ALL; + +-- Uncomment the following library declaration if using +-- arithmetic functions with Signed or Unsigned values +--use IEEE.NUMERIC_STD.ALL; + +-- Uncomment the following library declaration if instantiating +-- any Xilinx leaf cells in this code. +--library UNISIM; +--use UNISIM.VComponents.all; + +entity reg is + Port ( d : in STD_LOGIC_VECTOR (15 downto 0); + q : out STD_LOGIC_VECTOR (15 downto 0); + rst : in STD_LOGIC; + clk : in STD_LOGIC); +end reg; + +architecture Behavioral of reg is +begin + + process(clk, rst) is + begin + if (rst = '1') then + q <= x"0000"; + else + if rising_edge(clk) then + q <= d; + end if; + end if; + end process; + +end Behavioral;