From 363944d417aeee56959945d51fa832951ca07c7b Mon Sep 17 00:00:00 2001 From: Paul Mathieu Date: Wed, 17 Feb 2021 13:20:30 -0800 Subject: [PATCH] Initial commit --- first/adder.vhdl | 57 +++ first/adder_test.vhdl | 48 ++ first/alu.vhdl | 70 +++ first/blinky.s | 22 + first/boot_rom.vhdl | 142 ++++++ first/cc/blinky.c | 31 ++ first/clock.vhdl | 20 + first/cpu.vhdl | 197 ++++++++ first/cpu_test.vhdl | 147 ++++++ first/dff.vhdl | 26 ++ first/dff_test.vhdl | 59 +++ first/hello.vhdl | 191 ++++++++ first/or.vhdl | 15 + first/or_test.vhdl | 54 +++ first/ram.vhdl | 49 ++ first/regfile.vhdl | 52 +++ first/register.vhdl | 55 +++ first/rom.vhdl | 39 ++ first/rom_test.vhdl | 66 +++ first/top.vhdl | 73 +++ tools/as.ebnf | 45 ++ tools/as.py | 186 ++++++++ tools/cc.ebnf | 159 +++++++ tools/cc.py | 997 +++++++++++++++++++++++++++++++++++++++++ tools/crt0.s | 10 + tools/ld.py | 100 +++++ tools/makefile | 17 + tools/obj.proto | 23 + tools/requirements.txt | 2 + uart/echo.c | 10 + uart/testtx.c | 32 ++ uart/testtx.s | 42 ++ uart/uart.c | 23 + uart/uart.h | 13 + uart/uart.vhdl | 246 ++++++++++ 35 files changed, 3318 insertions(+) create mode 100644 first/adder.vhdl create mode 100644 first/adder_test.vhdl create mode 100644 first/alu.vhdl create mode 100644 first/blinky.s create mode 100644 first/boot_rom.vhdl create mode 100644 first/cc/blinky.c create mode 100644 first/clock.vhdl create mode 100644 first/cpu.vhdl create mode 100644 first/cpu_test.vhdl create mode 100644 first/dff.vhdl create mode 100644 first/dff_test.vhdl create mode 100644 first/hello.vhdl create mode 100644 first/or.vhdl create mode 100644 first/or_test.vhdl create mode 100644 first/ram.vhdl create mode 100644 first/regfile.vhdl create mode 100644 first/register.vhdl create mode 100644 first/rom.vhdl create mode 100644 first/rom_test.vhdl create mode 100644 first/top.vhdl create mode 100644 tools/as.ebnf create mode 100644 tools/as.py create mode 100644 tools/cc.ebnf create mode 100644 tools/cc.py create mode 100644 tools/crt0.s create mode 100644 tools/ld.py create mode 100644 tools/makefile create mode 100644 tools/obj.proto create mode 100644 tools/requirements.txt create mode 100644 uart/echo.c create mode 100644 uart/testtx.c create mode 100644 uart/testtx.s create mode 100644 uart/uart.c create mode 100644 uart/uart.h create mode 100644 uart/uart.vhdl diff --git a/first/adder.vhdl b/first/adder.vhdl new file mode 100644 index 0000000..7656079 --- /dev/null +++ b/first/adder.vhdl @@ -0,0 +1,57 @@ +library IEEE; +use IEEE.std_logic_1164.all; + +entity adder is + port( + a : in std_logic; + b : in std_logic; + c_in : in std_logic; + + q : out std_logic; + c_out : out std_logic + ); +end entity adder; + +architecture behavior of adder is + +begin + + q <= (a xor b) xor c_in; + c_out <= (a and b) or (a and c_in) or (b and c_in); + +end architecture behavior; + +--- +library IEEE; +use IEEE.std_logic_1164.all; + +entity vect_adder is + generic(SIZE: natural := 16); + port( + a : in std_logic_vector(SIZE-1 downto 0); + b : in std_logic_vector(SIZE-1 downto 0); + c_in : in std_logic; + + q : out std_logic_vector(SIZE-1 downto 0); + c_out : out std_logic + ); +end entity vect_adder; + +architecture behavior of vect_adder is + + component adder port(a, b, c_in: in std_logic; q, c_out: out std_logic); + end component; + + signal carry: std_logic_vector(SIZE downto 0); + +begin + + adders: + for i in 0 to SIZE-1 generate + addx: adder port map(a(i), b(i), carry(i), q(i), carry(i + 1)); + end generate adders; + + carry(0) <= c_in; + c_out <= carry(SIZE); + +end architecture behavior; diff --git a/first/adder_test.vhdl b/first/adder_test.vhdl new file mode 100644 index 0000000..b83d82c --- /dev/null +++ b/first/adder_test.vhdl @@ -0,0 +1,48 @@ +library IEEE; +use IEEE.std_logic_1164.all; +use std.textio.all; + +entity vect_adder_test is + end vect_adder_test; + +architecture rtl of vect_adder_test is + + component vect_adder is + port( + a, b : in std_logic_vector(15 downto 0); + c_in : in std_logic; + + q : out std_logic_vector(15 downto 0); + c_out : out std_logic + ); + end component; + + signal a, b, q: std_logic_vector(15 downto 0); + signal c_in, c_out: std_logic; + +begin + dut: vect_adder port map(a, b, c_in, q, c_out); + + process + begin + + a <= "0000000000000001"; + b <= "0000000000000001"; + c_in <= '0'; + + wait for 1 ns; + assert(q="0000000000000010") report "Fail 1+1" severity error; + assert(c_out='0') report "Fail carry 1+1" severity error; + + a <= "1111111111111111"; + b <= "0000000000000001"; + c_in <= '0'; + + wait for 1 ns; + assert(q="0000000000000000") report "Fail 0xffff + 1" severity error; + assert(c_out='1') report "Fail carry 0xffff + 1" severity error; + -- end + assert false report "Test done." severity note; + wait; + end process; +end rtl; diff --git a/first/alu.vhdl b/first/alu.vhdl new file mode 100644 index 0000000..a5e0814 --- /dev/null +++ b/first/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/first/blinky.s b/first/blinky.s new file mode 100644 index 0000000..eab1ab4 --- /dev/null +++ b/first/blinky.s @@ -0,0 +1,22 @@ +begin: + set r1, 128 // address of the LED + set r2, 0 + set r3, 1 + set r4, 255 // outer counter limit + +loop: + store r2, [r1] + add r2, r2, r3 + set r13, loop + +delay: + set r10, 0 + set r11, 0 + +delay_loop: + add r10, r10, r3 + bneq delay_loop // flag will be 1 when it wraps + add r11, r11, r3 + cmp r11, r4 + bneq delay_loop + set pc, loop diff --git a/first/boot_rom.vhdl b/first/boot_rom.vhdl new file mode 100644 index 0000000..f317eb4 --- /dev/null +++ b/first/boot_rom.vhdl @@ -0,0 +1,142 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity boot_rom is + generic + ( + addressWidth : in positive := 16; + busWidth : in positive := 16 + ); + port + ( + clk: in std_logic; + code_addr : in std_logic_vector(15 downto 0); + code_out : out std_logic_vector(15 downto 0); + + data_addr : in std_logic_vector(15 downto 0); + data_out : out std_logic_vector(15 downto 0) + ); +end boot_rom; + +architecture Behavioral of boot_rom is + constant alignment: positive := busWidth / 8; + constant romsize: natural := 92; + + type romtype is array(0 to romsize - 1) of std_logic_vector(15 downto 0); + signal romdata: romtype := ( + + x"ec00", +x"9c11", +x"e28c", +x"e302", +x"3de3", +x"5e22", +x"5eee", +x"e012", +x"90c0", +x"e102", +x"1000", +x"6201", +x"e100", +x"4021", +x"5edd", +x"e012", +x"90c0", +x"e101", +x"1000", +x"6201", +x"e100", +x"4021", +x"5edd", +x"2dcf", +x"e404", +x"4cc4", +x"e11e", +x"20c0", +x"e002", +x"3de0", +x"5e11", +x"e200", +x"c200", +x"fe02", +x"e101", +x"e000", +x"c010", +x"de06", +x"10c0", +x"c000", +x"dee2", +x"e010", +x"90c0", +x"12c0", +x"2200", +x"e204", +x"3cc2", +x"1ecf", +x"2dcf", +x"e402", +x"4cc4", +x"e00e", +x"e102", +x"3de1", +x"5e00", +x"e200", +x"c200", +x"fe02", +x"e101", +x"e000", +x"c010", +x"de04", +x"c000", +x"dee6", +x"e010", +x"90c0", +x"1000", +x"e202", +x"3cc2", +x"1ecf", +x"2dcf", +x"e404", +x"4cc4", +x"e001", +x"e100", +x"c100", +x"de16", +x"e160", +x"e002", +x"3de0", +x"5e11", +x"e12e", +x"20c0", +x"e202", +x"3de2", +x"5e11", +x"c000", +x"dee2", +x"e000", +x"e104", +x"3cc1", +x"1ecf" + + ); +begin + + process(clk) is + variable code_index: natural; + variable data_index: natural; + begin + if rising_edge(clk) then + code_index := to_integer(unsigned(code_addr)) / alignment; + if code_index < romsize then + code_out <= romdata(code_index); + else + code_out <= x"0000"; + end if; + + data_index := to_integer(unsigned(data_addr)) / alignment; + data_out <= romdata(data_index); + end if; + end process; + +end Behavioral; diff --git a/first/cc/blinky.c b/first/cc/blinky.c new file mode 100644 index 0000000..17c0b98 --- /dev/null +++ b/first/cc/blinky.c @@ -0,0 +1,31 @@ +// let's write blinky.c + +#define LED_ADDR ((volatile char*)0xC000) + +#define led_write(x) (*LED_ADDR = (x)) + +#define CYCLES_PER_MS 6666 // ish + +void busy_sleep_1ms() { + for (int i = 0; i < CYCLES_PER_MS; ++i) { + // nothing + } +} + +/** waits a general amount of time */ +void busy_sleep(int ms) { + for (int i = 0; i < ms; ++i) { + busy_sleep_1ms(); + } +} + +int main() { + int onoff = 0; + while (1) { + led_write(onoff); + busy_sleep(100); + onoff++; + } + + return 0; // actually unreachable +} diff --git a/first/clock.vhdl b/first/clock.vhdl new file mode 100644 index 0000000..12d9e50 --- /dev/null +++ b/first/clock.vhdl @@ -0,0 +1,20 @@ +library ieee; +use ieee.std_logic_1164.all; + +entity clock is + port ( clk: out std_logic); +end clock; + +architecture behaviour of clock +is + constant clk_period : time := 10 ns; +begin + -- Clock process definition + clk_process: process + begin + clk <= '0'; + wait for clk_period/2; + clk <= '1'; + wait for clk_period/2; + end process; +end behaviour; diff --git a/first/cpu.vhdl b/first/cpu.vhdl new file mode 100644 index 0000000..7ab2b57 --- /dev/null +++ b/first/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))); + 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))); + 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/first/cpu_test.vhdl b/first/cpu_test.vhdl new file mode 100644 index 0000000..1ed5553 --- /dev/null +++ b/first/cpu_test.vhdl @@ -0,0 +1,147 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity test_rom is + generic + ( + addressWidth : in positive := 16; + busWidth : in positive := 16 + ); + port + ( + address : in std_logic_vector(addressWidth - 1 downto 0); + dataOut : out std_logic_vector(busWidth - 1 downto 0) + ); +end test_rom; + +architecture Behavioral of test_rom is + constant alignment: positive := busWidth / 8; + +--- type romtype is array(0 to 14) of std_logic_vector(15 downto 0); +--- signal romdata: romtype := ( +---x"e180", +---x"e200", +---x"e301", +---x"e4ff", +---x"2210", +---x"3223", +---x"ed08", +---x"ea00", +---x"eb00", +---x"3aa3", +---x"fefe", +---x"3bb3", +---x"c0b4", +---x"fef8", +---x"ee08" +---); + + type romtype is array(0 to 10) 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"de04", -- BEQ pc, 4 + x"0000", -- NOP + x"ee00" -- SET pc, 0 + ); +begin + + process(address) is + variable index: natural; + begin + index := to_integer(unsigned(address)) / alignment; + dataOut <= romdata(index); + end process; + +end Behavioral; + +--- + +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 clock is port(clk: out std_logic); + end component; + + 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 + ); + end component; + + component test_rom is port ( + address : in std_logic_vector(15 downto 0); + dataOut : out std_logic_vector(15 downto 0) + ); + end component; + + signal clk, rst, mem_write: std_logic; + signal rom_data, rom_addr, mem_in, mem_out, mem_addr: std_logic_vector(15 downto 0); + +begin + heartbeat: clock port map(clk); + dut: cpu port map(clk, rst, rom_data, rom_addr, mem_in, mem_out, mem_addr, mem_write); + rom: test_rom port map(rom_addr, rom_data); + + 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 20 ns; + assert(rom_addr=x"0006") 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"0008") report "Fail PC @08" severity error; + assert(mem_write='0') report "Fail set mem_write to 0" severity error; + assert(mem_addr=x"0025") report "Fail set mem_addr to 0x25" severity error; + mem_in <= x"002a"; + + wait for 20 ns; + assert(rom_addr=x"000c") 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 30 ns; + assert(rom_addr=x"0014") 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; + wait; + end process; +end rtl; diff --git a/first/dff.vhdl b/first/dff.vhdl new file mode 100644 index 0000000..f756f93 --- /dev/null +++ b/first/dff.vhdl @@ -0,0 +1,26 @@ +library IEEE; +use IEEE.std_logic_1164.all; + +entity dff is + port( + clk : in std_logic; + rst : in std_logic; + + d : in std_logic; + q : out std_logic +); +end entity dff; + +architecture behavior of dff is +begin + process(clk, rst) is + begin + if (rst = '1') then + q <= '0'; + else + if rising_edge(clk) then + q <= d; + end if; + end if; + end process; +end architecture behavior; diff --git a/first/dff_test.vhdl b/first/dff_test.vhdl new file mode 100644 index 0000000..47a487e --- /dev/null +++ b/first/dff_test.vhdl @@ -0,0 +1,59 @@ +-- Simple OR gate design +library IEEE; +use IEEE.std_logic_1164.all; +use std.textio.all; + +entity dff_test is + end dff_test; + +architecture rtl of dff_test is + + component clock is + port(clk: out std_logic); + end component; + + component dff is + port( + clk: in std_logic; + rst: in std_logic; + + d: in std_logic; + q: out std_logic + ); + end component; + + signal d, q, clk, rst: std_logic; + +begin + heartbeat: clock port map(clk); + dut: dff port map(clk, rst, d, q); + + process + begin + rst <= '1'; + + wait for 10 ns; + assert(q='0') report "Fail rst" severity error; + + rst <= '0'; + d <= '1'; + wait for 10 ns; + assert(q='1') report "Fail d=1" severity error; + + rst <= '1'; + wait for 1 ns; + assert(q='0') report "Async rst fail" severity error; + rst <= '0'; + d <= '1'; + wait for 9 ns; + + d <= '0'; + wait for 1 ns; + assert(q='1') report "Fail clk sync" severity error; + wait for 9 ns; + assert(q='0') report "Fail d=0" severity error; + + assert false report "Test done." severity note; + wait; + end process; +end rtl; diff --git a/first/hello.vhdl b/first/hello.vhdl new file mode 100644 index 0000000..3d15e2a --- /dev/null +++ b/first/hello.vhdl @@ -0,0 +1,191 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; +use std.textio.all; + +entity hello is + port( + clk: in std_logic; + rst: in std_logic; + + led: out std_logic_vector(7 downto 0); + + uart_rx: in std_logic; + uart_tx: out std_logic + ); +end hello; + +architecture rtl of hello 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; + +-- component boot_rom IS +-- PORT ( +-- clka : IN STD_LOGIC; +-- addra : IN STD_LOGIC_VECTOR(7 DOWNTO 0); +-- douta : OUT STD_LOGIC_VECTOR(15 DOWNTO 0); +-- clkb : IN STD_LOGIC; +-- addrb : IN STD_LOGIC_VECTOR(7 DOWNTO 0); +-- doutb : OUT STD_LOGIC_VECTOR(15 DOWNTO 0) +-- ); +-- END component; + + +--COMPONENT ram_mem +-- PORT ( +-- clka : IN STD_LOGIC; +-- wea : IN STD_LOGIC_VECTOR(0 DOWNTO 0); +-- addra : IN STD_LOGIC_VECTOR(7 DOWNTO 0); +-- dina : IN STD_LOGIC_VECTOR(15 DOWNTO 0); +-- douta : OUT STD_LOGIC_VECTOR(15 DOWNTO 0) +-- ); +--END COMPONENT; + + component ram is + generic ( + addressWidth : in positive := 16; + busWidth : in positive := 16; + size : in positive := 1024 + ); + port ( + clk : in std_logic; + address : in std_logic_vector(addressWidth - 1 downto 0); + writeEnable : in std_logic; + dataIn : in std_logic_vector(busWidth - 1 downto 0); + dataOut : out std_logic_vector(busWidth - 1 downto 0) + ); + end component; + + component boot_rom is port ( + clk: in std_logic; + + code_addr : in std_logic_vector(15 downto 0); + code_out : out std_logic_vector(15 downto 0); + + data_addr : in std_logic_vector(15 downto 0); + data_out : out std_logic_vector(15 downto 0) + ); + end component; + + component uart is + port + ( + clk : in std_logic; + rst : in std_logic; + + -- hardware + rx_pin : in std_logic; + tx_pin : out std_logic; + + -- bus interface + we : in std_logic; + re : in std_logic; + addr : in std_logic_vector(15 downto 0); + din : in std_logic_vector(15 downto 0); + dout : out std_logic_vector(15 downto 0) + ); + end component; + + signal mem_write, mem_read: std_logic; + signal rom_code_addr, rom_code_out, mem_in, mem_out, mem_addr: std_logic_vector(15 downto 0); + signal rom_data_addr, rom_data_out: std_logic_vector(15 downto 0); + + signal uart_din, uart_dout, uart_addr: std_logic_vector(15 downto 0); + signal uart_we, uart_re: std_logic; + + signal bus_write, bus_read: std_logic; + signal bus_mosi, bus_miso, bus_addr: std_logic_vector(15 downto 0); + + signal led_r, led_next: std_logic_vector(7 downto 0); + +begin + cpu0: cpu port map(clk, rst, rom_code_out, rom_code_addr, bus_miso, bus_mosi, bus_addr, bus_write, bus_read); +-- rom: boot_rom port map( +-- clka => clk, addra => rom_code_addr(8 downto 1), douta => rom_code_out, +-- clkb => clk, addrb => rom_data_addr(8 downto 1), doutb => rom_data_out +-- ); +-- mem: ram_mem port map(clka => clk, wea(0) => mem_write, addra => mem_addr(8 downto 1), dina => mem_in, douta => mem_out); + rom: boot_rom port map(clk => clk, code_addr => rom_code_addr, code_out => rom_code_out, + data_addr => rom_data_addr, data_out => rom_data_out); + mem: ram port map(clk => clk, address => mem_addr, writeEnable => mem_write, dataIn => mem_in, dataOut => mem_out); + + uart0: uart port map(clk => clk, rst => rst, rx_pin => uart_rx, tx_pin => uart_tx, + addr => uart_addr, din => uart_din, dout => uart_dout, re => uart_re, we => uart_we); + + -- system map + -- 0x0000 - 0x0fff ROM + -- 0x1000 - 0x1fff RAM + -- 0xc000 - 0xc000 GPIO? + + led <= led_r; + + process(clk, rst) + begin + if rising_edge(clk) then + led_r <= led_next; + end if; + + if rst = '1' then + led_r <= x"00"; + end if; + end process; + + process(bus_addr, bus_mosi, bus_write, mem_out, rst, rom_data_out, led_r, bus_read) + begin + + bus_miso <= x"0000"; + + rom_data_addr <= x"0000"; + + mem_addr <= x"0000"; + mem_in <= x"0000"; + mem_write <= '0'; + + led_next <= led_r; + + uart_din <= x"0000"; + uart_addr <= x"0000"; + uart_we <= '0'; + uart_re <= '0'; + + case bus_addr(15 downto 12) is + when x"0" => + bus_miso <= rom_data_out; + rom_data_addr <= bus_addr and x"0fff"; + when x"1" => + mem_in <= bus_mosi; + bus_miso <= mem_out; + mem_addr <= bus_addr and x"0fff"; + mem_write <= bus_write; + when x"c" => + case bus_addr(7 downto 4) is + when x"0" => -- LED + if bus_write = '1' then + led_next <= bus_mosi(7 downto 0); + end if; + when x"1" => -- UART + uart_din <= bus_mosi; + bus_miso <= uart_dout; + uart_addr <= bus_addr and x"000f"; + uart_we <= bus_write; + uart_re <= bus_read; + when others => + end case; + when others => + end case; + end process; + +end rtl; diff --git a/first/or.vhdl b/first/or.vhdl new file mode 100644 index 0000000..3fe8009 --- /dev/null +++ b/first/or.vhdl @@ -0,0 +1,15 @@ +-- Simple OR gate design +library IEEE; +use IEEE.std_logic_1164.all; + +entity or_gate is +port( + a: in std_logic; + b: in std_logic; + q: out std_logic); +end or_gate; + +architecture rtl of or_gate is +begin + q <= a or b; +end rtl; diff --git a/first/or_test.vhdl b/first/or_test.vhdl new file mode 100644 index 0000000..5c0b2e2 --- /dev/null +++ b/first/or_test.vhdl @@ -0,0 +1,54 @@ +-- Simple OR gate design +library IEEE; +use IEEE.std_logic_1164.all; +use std.textio.all; + +entity or_test is +end or_test; + +architecture rtl of or_test is + +component or_gate is + port( + a: in std_logic; + b: in std_logic; + q: out std_logic + ); +end component; + +signal a_in, b_in, q_out: std_logic; + +begin + dut: or_gate port map(a_in, b_in, q_out); + + process + begin + + a_in <= '0'; + b_in <= '0'; + wait for 1 ns; + assert(q_out='0') report "Fail 0/0" severity error; + + a_in <= '0'; + b_in <= '1'; + wait for 1 ns; + assert(q_out='1') report "Fail 0/1" severity error; + + a_in <= '1'; + b_in <= 'X'; + wait for 1 ns; + assert(q_out='1') report "Fail 1/X" severity error; + + a_in <= '1'; + b_in <= '1'; + wait for 1 ns; + assert(q_out='1') report "Fail 1/1" severity error; + + -- Clear inputs + a_in <= '0'; + b_in <= '0'; + + assert false report "Test done." severity note; + wait; + end process; +end rtl; diff --git a/first/ram.vhdl b/first/ram.vhdl new file mode 100644 index 0000000..83cf154 --- /dev/null +++ b/first/ram.vhdl @@ -0,0 +1,49 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +-- Aligned IO only. +-- Unaligned address bits are ignored. +-- E.g. on a 16-bit bus, last address bit is ignored. +entity ram is + generic + ( + addressWidth : in positive := 16; + busWidth : in positive := 16; + size : in positive := 1024 + ); + port + ( + clk : in std_logic; + address : in std_logic_vector(addressWidth - 1 downto 0); + writeEnable : in std_logic; + dataIn : in std_logic_vector(busWidth - 1 downto 0); + dataOut : out std_logic_vector(busWidth - 1 downto 0) + ); +end ram; + +architecture Behavioral of ram is + constant alignment : positive := busWidth / 8; + constant ramSize : positive := size / alignment; + + type ramType is array(natural range <>) of std_logic_vector(busWidth - 1 downto 0); + subtype ramRange is natural range 0 to ramSize; + + signal mem : ramType(ramRange); +begin + process(clk) + variable index : ramRange; + begin + if (rising_edge(clk)) + then + index := to_integer(unsigned(address)) / alignment; + + if (writeEnable = '1') + then + mem(index) <= dataIn; + end if; + + dataOut <= mem(index); + end if; + end process; +end Behavioral; diff --git a/first/regfile.vhdl b/first/regfile.vhdl new file mode 100644 index 0000000..95e57d8 --- /dev/null +++ b/first/regfile.vhdl @@ -0,0 +1,52 @@ +library IEEE; +use IEEE.STD_LOGIC_1164.ALL; +use ieee.numeric_std.all; + +entity regfile is + Port ( outregna: in std_logic_vector(3 downto 0); + outregda: out std_logic_vector(15 downto 0); + + outregnb: in std_logic_vector(3 downto 0); + outregdb: out std_logic_vector(15 downto 0); + + inregn: in std_logic_vector(3 downto 0); + inregd: in std_logic_vector(15 downto 0); + inwe: in std_logic; + + rst : in STD_LOGIC; + clk : in STD_LOGIC + ); +end regfile; + +architecture Behavioral of regfile is + component 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 component; + + type regbank is array(0 to 15) of std_logic_vector(15 downto 0); + signal regd: regbank; + signal regq: regbank; +begin + + regs: + for i in 0 to 15 generate + regx: reg port map(d => regd(i), q => regq(i), rst => rst, clk => clk); + end generate; + + outregda <= regq(to_integer(unsigned(outregna))); + outregdb <= regq(to_integer(unsigned(outregnb))); + + process(inregn, inregd, regq, inwe) + begin + for i in 0 to 15 loop + regd(i) <= regq(i); + if inwe = '1' then + regd(to_integer(unsigned(inregn))) <= inregd; + end if; + end loop; + end process; + +end Behavioral; diff --git a/first/register.vhdl b/first/register.vhdl new file mode 100644 index 0000000..30ede54 --- /dev/null +++ b/first/register.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; diff --git a/first/rom.vhdl b/first/rom.vhdl new file mode 100644 index 0000000..c6f5e99 --- /dev/null +++ b/first/rom.vhdl @@ -0,0 +1,39 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +-- Aligned IO only. +-- Unaligned address bits are ignored. +-- E.g. on a 16-bit bus, last address bit is ignored. +entity rom is + generic + ( + addressWidth : in positive := 16; + busWidth : in positive := 16 + ); + port + ( + address : in unsigned(addressWidth - 1 downto 0); + dataOut : out std_logic_vector(busWidth - 1 downto 0) + ); +end rom; + +architecture Behavioral of rom is + constant alignment : positive := busWidth / 8; + + type romtype is array(0 to 2) of std_logic_vector(15 downto 0); + signal romdata: romtype := ( + x"0002", + x"0004", + x"0000" + ); +begin + + process(address) is + variable index: natural; + begin + index := to_integer(unsigned(address)) / alignment; + dataOut <= romdata(index); + end process; + +end Behavioral; diff --git a/first/rom_test.vhdl b/first/rom_test.vhdl new file mode 100644 index 0000000..a1c50d1 --- /dev/null +++ b/first/rom_test.vhdl @@ -0,0 +1,66 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; +use std.textio.all; + +entity rom_test is + end rom_test; + +architecture rtl of rom_test is + + component clock is + port(clk: out std_logic); + end component; + + component dff is + port( + clk: in std_logic; + rst: in std_logic; + + d: in std_logic; + q: out std_logic + ); + end component; + + component rom is port ( + address : in unsigned(15 downto 0); + dataOut : out std_logic_vector(15 downto 0) + ); + end component; + + signal clk, rst: std_logic; + signal romout, regq: std_logic_vector(15 downto 0); + signal addr: unsigned(15 downto 0); + +begin + heartbeat: clock port map(clk); + dut: rom port map(addr, romout); + + reg: + for i in 15 downto 0 generate + regx: dff port map(clk, rst, romout(i), regq(i)); + end generate reg; + + process(regq) begin + addr <=unsigned(regq); + end process; + + process + begin + rst <= '1'; + + wait for 1 ns; + assert(regq(0)='0') report "Fail rst" severity error; + + rst <= '0'; + + wait for 10 ns; + assert(to_integer(unsigned(regq))=2) report "Fail rom read @0" severity error; + + wait for 10 ns; + assert(to_integer(unsigned(regq))=4) report "Fail rom read @2" severity error; + + assert false report "Test done." severity note; + wait; + end process; +end rtl; diff --git a/first/top.vhdl b/first/top.vhdl new file mode 100644 index 0000000..8dbf6ea --- /dev/null +++ b/first/top.vhdl @@ -0,0 +1,73 @@ +---------------------------------------------------------------------------------- +-- Company: +-- Engineer: +-- +-- Create Date: 02/13/2021 12:09:57 AM +-- Design Name: +-- Module Name: top - 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 top is + Port ( clk : in STD_LOGIC; + rst_n : in STD_LOGIC; + led : out STD_LOGIC_VECTOR (7 downto 0); + usb_rx: in std_logic; + usb_tx: out std_logic + ); +end top; + +architecture Behavioral of top is +component hello is port( + clk: in std_logic; + rst: in std_logic; + + led: out std_logic_vector(7 downto 0); + + uart_rx: in std_logic; + uart_tx: out std_logic + + ); +end component; + +component reset_conditioner is port( +clk: in std_logic; +rin: in std_logic; +rout: out std_logic +); +end component; + +signal rst, rstraw: std_logic; + +begin + +rstraw <= not rst_n; +reset: reset_conditioner port map(clk => clk, rin => rstraw, rout => rst); +stuff: hello port map(clk => clk, rst => rst, led => led, + uart_rx => usb_rx, uart_tx => usb_tx); + + +end Behavioral; diff --git a/tools/as.ebnf b/tools/as.ebnf new file mode 100644 index 0000000..9b6d5eb --- /dev/null +++ b/tools/as.ebnf @@ -0,0 +1,45 @@ +COMMENT: /\/\/[^\n]*/ + +!opcode: "nop"i + | "load"i + | "store"i + | "add"i + | "sub"i + | "and"i + | "or"i + | "not"i + | "xor"i + | "seth"i + | "shr"i + | "cmp"i + | "mul"i + | "beq"i + | "set"i + | "bneq"i + +!register: REGISTER + | "sp"i + | "lr"i + | "pc"i + +LABEL: /[a-z_]\w*/i + +REGISTER: /[Rr]\d+/ + +start: line* +?line: label | statement | raw_word | symbol_def +label: LABEL ":" +statement: opcode [param ("," param)*] +!label_ref: LABEL +!immediate: SIGNED_NUMBER | HEX_LITTERAL +?param: register | label_ref | reg_offset | immediate +reg_offset: "[" register ("," immediate)? "]" +raw_word: ".word" immediate +symbol_def: ".global" label_ref + +HEX_LITTERAL: /0x[a-fA-F0-9]+/ + +%import common.WS +%import common.SIGNED_NUMBER +%ignore COMMENT +%ignore WS diff --git a/tools/as.py b/tools/as.py new file mode 100644 index 0000000..5274f18 --- /dev/null +++ b/tools/as.py @@ -0,0 +1,186 @@ +import argparse +import enum +import lark +import struct +import sys + +import obj_pb2 + +GRAMMAR_FILE = '/home/paulmathieu/vhdl/bin/as.ebnf' + +opcodes = { + 'nop' : lambda: '0000', + 'load' : lambda p0, p1, p2: f'1{p0:x}{p1:x}{(p2 >> 1)&0xf:x}', + 'store': lambda p0, p1, p2: f'2{p0:x}{p1:x}{(p2 >> 1)&0xf:x}', + 'add' : lambda p0, p1, p2: f'3{p0:x}{p1:x}{p2:x}', + 'sub' : lambda p0, p1, p2: f'4{p0:x}{p1:x}{p2:x}', + 'or' : lambda p0, p1, p2: f'5{p0:x}{p1:x}{p2:x}', + 'and' : lambda p0, p1, p2: f'6{p0:x}{p1:x}{p2:x}', + 'not' : lambda p0, p1: f'7{p0:x}{p1:x}0', + 'xor' : lambda p0, p1, p2: f'8{p0:x}{p1:x}{p2:x}', + 'seth' : lambda p0, p1: f'9{p0:x}{p1&0xff:02x}', + 'shr' : lambda p0, p1, p2: f'a{p0:x}{p1:x}{p2:x}', + 'mul' : lambda p0, p1, p2: f'b{p0:x}{p1:x}{p2:x}', + 'cmp' : lambda p0, p1: f'c{p0:x}{p1:x}0', + 'beq' : lambda p0, p1: f'd{p0:x}{p1&0xff:02x}', + 'set' : lambda p0, p1: f'e{p0:x}{p1&0xff:02x}', + 'bneq' : lambda p0, p1: f'f{p0:x}{p1&0xff:02x}', + '.word': lambda p0: f'{p0:04x}', + } + + +class AsTransformer(lark.Transformer): + def __init__(self): + self.addr = 0 + + def statement(self, s): + opcode, *params = s + addr = self.addr + self.addr += 2 + return ('OP', addr, opcode, *params) + + def opcode(self, o): + (o, ) = o + return str(o) + + def reg_offset(self, r): + if len(r) < 2: + return r[0], 0 + return tuple(r[:]) + + LABEL = str + HEX_LITTERAL = lambda _, x: int(x[2:], 16) + + def label(self, l): + (l,) = l + return ('LBL', l, self.addr) + + def label_ref(self, l): + (l,) = l + return l + + def raw_word(self, w): + (w,) = w + addr = self.addr + self.addr += 2 + return ('OP', addr, '.word', w) + + def register(self, r): + (r,) = r + if r == 'pc': + return 14 + if r == 'lr': + return 13 + if r == 'sp': + return 12 + return int(r[1:]) + + def immediate(self, i): + (i,) = i + return int(i) + + def symbol_def(self, s): + (sym,) = s + return ('SYM', s[0]) + + start = list + + +def generate_ops(ops, labels, relocs): + def filter_label(params, pc): + for p in params: + if isinstance(p, str): # label ref + if len(params) == 1: # branch + yield 14 # pc + yield labels[p] - pc - 2 + else: # set, allow relocs here + relocs.append((pc, p)) + yield 0xff + elif isinstance(p, tuple): # reg offset + yield p[0] + yield p[1] + else: + yield p + + for op in ops: + addr = op[0] + fmt = opcodes[op[1]] + params = filter_label(op[2:], addr) + yield fmt(*params) + + +def larkparse(f, debug=False): + with open(GRAMMAR_FILE) as g: + asparser = lark.Lark(g.read()) + tree = asparser.parse(f.read()) + if debug: + print(tree.pretty()) + lines = AsTransformer().transform(tree) + labels = {l[1]: l[2] for l in lines if l[0] == 'LBL'} + ops = [l[1:] for l in lines if l[0] == 'OP'] + syms = [(l[1], labels[l[1]]) for l in lines if l[0] == 'SYM'] + relocs = [] + return [int(x, 16) for x in generate_ops(ops, labels, relocs)], syms, relocs + + +# any record: >BHs, type, length, data +# header: >cccc, "pol0" +# text record: >BHs, name, text_len, text +# reloc record: >pHip, txt_rec_name, offset, target + +def write_obj(fout, ops, syms, relocs): + obj = obj_pb2.ObjFile(header=obj_pb2.Header(magic='pol0')) + + ends = [s[1] for s in syms[1:]] + [2 * len(ops)] + data = b''.join(struct.pack('>H', op) for op in ops) + + for (name, begin), end in zip(syms, ends): + section = obj_pb2.Section(name=name, text=data[begin:end]) + obj.sections.append(section) + for offs, target in relocs: + if offs >= begin and offs < end: + section_off = offs - begin + reloc = obj_pb2.Reloc(section=name, offset=section_off, target=target) + obj.relocs.append(reloc) + + fout.write(obj.SerializeToString()) + + + +def parse_args(): + parser = argparse.ArgumentParser(description='Assemble.') + group = parser.add_mutually_exclusive_group() + group.add_argument('--vhdl', action='store_true', + help='output VHDL code') + group.add_argument('--coe', action='store_true', + help='output a Vivado COE file') + parser.add_argument('--debug', action='store_true', + help='print the AST') + parser.add_argument('--input', '-c', type=argparse.FileType('r'), + default=sys.stdin, help='input file (default: stdin)') + parser.add_argument('--output', '-o', type=argparse.FileType('wb'), + default=sys.stdout.buffer, help='output file') + return parser.parse_args() + + +def main(): + args = parse_args() + + ops, syms, relocs = larkparse(args.input, debug=args.debug) + + if args.vhdl: + print(',\n'.join(f'x"{x:04x}"' for x in ops)) + for addr, target in relocs: + print(f'-- reloc: {target} @{addr}') + elif args.coe: + assert not relocs, "relocs not supported with coe output" + print('memory_initialization_radix=16;') + print('memory_initialization_vector=') + code = ',\n'.join(f'{x:04x}' for x in ops) + print(f'{code};') + else: + write_obj(args.output, ops, syms, relocs) + + +if __name__ == "__main__": + main() diff --git a/tools/cc.ebnf b/tools/cc.ebnf new file mode 100644 index 0000000..a3ba3d6 --- /dev/null +++ b/tools/cc.ebnf @@ -0,0 +1,159 @@ +start: top_level* + +?top_level: fun_decl + | global_var + | fun_def + | struct_def + | typedef + +fun_decl: fun_prot ";" +fun_prot: type symbol "(" [fun_param ("," fun_param)*] ")" +fun_param: type [symbol] + +global_var: type symbol sized_array* empty_array? [ "=" litteral ] ";" + +fun_def: fun_prot body + +typedef: "typedef" type IDENTIFIER ";" + +body: "{" statement* "}" + +statement: if_stat + | while_stat + | for_stat + | do_while_stat + | "break" ";" -> break + | "continue" ";" -> continue + | "goto" label ";" -> goto // yay \o/ + | "return" expression ";" -> return_stat + | local_var ";" + | local_array + | expression ";" + | body + +if_stat: "if" "(" expression ")" statement ["else" statement] +while_stat: "while" "(" expression ")" statement +for_stat: "for" "(" local_var? ";" expression? ";" iter_expression? ")" statement +do_while_stat: "do" statement "while" "(" expression ")" statement + +iter_expression: expression +local_var: type symbol initializer? +local_array: type symbol sized_array* (sized_array | empty_array) initializer? ";" +empty_array: "[" "]" +sized_array: "[" array_size "]" +initializer: "=" (expression | initializer_list) +initializer_list: "{" [init_list_field ("," init_list_field)* ","? ] "}" +?init_list_field: "." field "=" expression + | expression + +// precedence from https://en.cppreference.com/w/c/language/operator_precedence + +?expression: comma_expr + +?comma_expr: prec14_expr + | comma_expr "," prec14_expr + +?prec14_expr: prec13_expr + | prec2_expr "=" prec14_expr -> assignment + | prec2_expr "+=" prec14_expr + | prec2_expr "-=" prec14_expr + | prec2_expr "*=" prec14_expr + | prec2_expr "/=" prec14_expr + | prec2_expr "%=" prec14_expr + | prec2_expr "<<=" prec14_expr + | prec2_expr ">>=" prec14_expr + | prec2_expr "&=" prec14_expr + | prec2_expr "^=" prec14_expr + | prec2_expr "|=" prec14_expr + +?prec13_expr: prec12_expr + | prec12_expr "?" prec13_expr ":" prec13_expr + +?prec12_expr: prec11_expr + | prec12_expr "||" prec11_expr +?prec11_expr: prec10_expr + | prec11_expr "&&" prec10_expr +?prec10_expr: prec9_expr + | prec10_expr "|" prec9_expr +?prec9_expr: prec8_expr + | prec9_expr "^" prec8_expr +?prec8_expr: prec7_expr + | prec8_expr "&" prec7_expr -> _and // reserved work in python +?prec7_expr: prec6_expr + | prec7_expr "==" prec6_expr -> eq + | prec7_expr "!=" prec6_expr -> neq +?prec6_expr: prec5_expr + | prec6_expr "<" prec5_expr -> lt + | prec6_expr "<=" prec5_expr -> lte + | prec6_expr ">" prec5_expr -> gt + | prec6_expr ">=" prec5_expr -> gte +?prec5_expr: prec4_expr + | prec5_expr "<<" prec4_expr -> shl + | prec5_expr ">>" prec4_expr -> shr +?prec4_expr: prec3_expr + | prec4_expr "+" prec3_expr -> add + | prec4_expr "-" prec3_expr -> su +?prec3_expr: prec2_expr + | prec3_expr "*" prec2_expr -> mul + | prec3_expr "/" prec2_expr -> div + | prec3_expr "%" prec2_expr -> mod +?prec2_expr: prec1_expr + | "++" prec2_expr -> pre_increment + | "--" prec2_expr -> pre_decrement + | "+" prec2_expr + | "-" prec2_expr + | "!" prec2_expr -> bool_not + | "~" prec2_expr + | "(" type ")" prec2_expr -> cast + | "*" prec2_expr -> dereference + | "&" prec2_expr -> address_of + | "sizeof" prec2_expr -> sizeof + +?prec1_expr: litteral + | identifier + | "(" expression ")" + | prec1_expr "++" -> post_increment + | prec1_expr "--" -> post_decrement + | prec1_expr "(" expression? ")" -> fun_call + | prec1_expr "[" expression "]" -> array_item + | prec1_expr "." field -> field_access + | prec1_expr "->" field -> pointer_access + + + + +label: IDENTIFIER +litteral: SIGNED_NUMBER | ESCAPED_STRING | HEX_LITTERAL +field: IDENTIFIER +identifier: IDENTIFIER +?symbol: IDENTIFIER +?type: type_qualifier* IDENTIFIER + | struct_type + | type "*" -> pointer +?array_size: INT + +?type_qualifier: "volatile" -> volatile + | "const" -> const + | "static" -> static + | "extern" -> extern + +struct_type: "struct" (IDENTIFIER | IDENTIFIER? struct_body) +struct_def: "struct" IDENTIFIER struct_body ";" +struct_body: "{" struct_field* "}" +struct_field: type IDENTIFIER sized_array* ";" + +IDENTIFIER: /[_a-zA-Z]\w*/ +COMMENT: /\/\/.*/ +HEX_LITTERAL: /0x[a-fA-F0-9]+/ + + +%import common.WS +%import common.CNAME +%import common.C_COMMENT +%import common.SIGNED_NUMBER +%import common.INT +%import common.ESCAPED_STRING + +%ignore COMMENT +%ignore C_COMMENT +%ignore WS diff --git a/tools/cc.py b/tools/cc.py new file mode 100644 index 0000000..b315b1f --- /dev/null +++ b/tools/cc.py @@ -0,0 +1,997 @@ +import collections +import contextlib +import re +import sys + +import lark + +GRAMMAR_FILE = '/home/paulmathieu/vhdl/bin/cc.ebnf' + + +class Scope: + def __init__(self): + self.symbols = {} + self.parent = None + + +class Variable: + def __init__(self, type, name, volatile=False, addr_reg=None): + self.type = type + self.name = name + self.volatile = volatile + self.addr_reg = addr_reg + + def __repr__(self): + return f'' + + @classmethod + def from_def(cls, tree): + volatile = False + type = tree.children[0] + if isinstance(type, lark.Tree): + for c in type.children: + if c == "volatile": + volatile = True + name = tree.children[1] + return cls(type, name, volatile=volatile) + + @classmethod + def from_dereference(cls, reg): + return cls('deref', 'deref', addr_reg=reg) + + +# - all registers pointing to unwritten stuff can be dropped as soon as we're +# done with them: +# - already fed them into the operations +# - end of statement +# - maybe should have a separate storeage for special registers? +# need ways to: +# - assign a list of registers into r0, ... rn for function call +# - ... and run all callbacks +# - get the register for an identifier +# - dereference an expression: +# - essentially turn a temp into an lvalue-address +# - if read, need a second reg for its value +# - mark a variable to have its address in a register +# - delay reading the identifier if it's lhs +# - store the value to memory when registers are claimed +# - if it was modified through: +# - assignment +# - pre-post *crement +# - retrieve the memory type: +# - stack +# - absolute +# - register +# - or just store the value when the variable is assigned +# - get a temporary register +# - return it +# - I guess dereferencing is an upgrade + + def type(self, tree): + print(tree) + + +class RegBank: + def __init__(self, logger=None): + self.reset() + self.log = logger or print + + def reset(self): + self.available = [f'r{i}' for i in range(12)] + self.vars = {} + self.varregs = {} + self.cleanup = collections.defaultdict(list) + + def take(self, reg=None): + if reg is not None: + if reg not in self.available: + self.evict(self.var(reg)) + self.available.remove(reg) + return reg + if not self.available: + assert self.vars, "nothing to clean, no more regs :/" + # storing one random var + var = self.vars.keys()[0] + self.evict(var) + return self.available.pop(0) + + def give(self, reg): + if reg in self.varregs: + # Need to call evict() with the var to free it. + return + self.available.insert(0, reg) + + def loaded(self, var, reg, cleanup=None): + """Tells the regbank some variable was loaded into the given register.""" + self.vars[var] = reg + self.varregs[reg] = var + self.take(reg) + if cleanup is not None: + self.log(f'recording cleanup for {reg}({var.name})') + self.cleanup[reg].append(cleanup) + + def stored(self, var): + """Tells the regbank the given var was stored to memory, register can be freed.""" + assert var in self.vars + reg = self.vars.pop(var) + del self.varregs[reg] + self.give(reg) + + def load(self, var): + """Returns the reg associated with the var, or a new reg if none was, + and True if the var was created, False otherwise.""" + self.log(f'vars: {self.vars}, varregs: {self.varregs}') + if var not in self.vars: + reg = self.take() + self.vars[var] = reg + self.varregs[reg] = var + return reg, True + return self.vars[var], False + + def assign(self, var, reg, cleanup=None): + """Assign a previously-used register to a variable.""" + if var in self.vars: + self.stored(var) + if reg in self.varregs: + for cb in self.cleanup.pop(reg, []): + cb(reg) + self.stored(self.varregs[reg]) + self.vars[var] = reg + self.varregs[reg] = var + if cleanup is not None: + self.cleanup[reg].append(cleanup) + + def var(self, reg): + return self.varregs.get(reg, None) + + def evict(self, var): + """Runs var callbacks & frees the register.""" + if var not in self.vars: + return + reg = self.vars[var] + for cb in self.cleanup.pop(var, []): + cb(reg) + self.stored(var) + + def flush_all(self): + for reg in list(self.cleanup): + self.log(f'flushing {reg}({self.varregs[reg].name})') + for cb in self.cleanup.pop(reg): + cb(reg) + self.reset() + + def swapped(self, reg0, reg1): + var0 = self.varregs.get(reg0, None) + var1 = self.varregs.get(reg1, None) + + if var0 is not None: + self.stored(var0) + elif reg0 not in self.available: + self.give(reg0) + + if var1 is not None: + self.stored(var1) + elif reg1 not in self.available: + self.give(reg1) + + if var0 is not None: + self.loaded(var0, reg1) + if var1 is not None: + self.loaded(var1, reg0) + + @contextlib.contextmanager + def borrow(self, howmany): + regs = [self.take() for i in range(howmany)] + yield regs + for reg in regs: + self.give(reg) + + +class FunctionSpec: + def __init__(self, fun_prot): + self.return_type = fun_prot.children[0] + self.name = fun_prot.children[1] + self.param_types = [x.children[0] for x in fun_prot.children[2:]] + + def __repr__(self): + params = ', '.join(self.param_types) + return f'' + + +class Function: + def __init__(self, fun_prot): + self.locals = {} + self.spec = FunctionSpec(fun_prot) + self.params = [Variable(*x.children) for x in fun_prot.children[2:]] + self.ret = None + self.nextstack = 0 + self.statements = [] + self.regs = RegBank(logger=self.log) + self.deferred_ops = [] + self.fun_calls = 0 + self.ops = [] + + def log(self, line): + self.ops.append(lambda: [f'// {line}']) + + @property + def stack_usage(self): + return self.nextstack + 2 + + def get_stack(self, size=2): + stk = self.nextstack + self.nextstack += size + return stk + + def param_dict(self): + return {p.name: p for p in self.params} + + def __repr__(self): + return repr(self.spec) + + def synth(self): + if self.fun_calls > 0: + preamble = [f'store lr, [sp, -2]', + f'set r4, {self.stack_usage}', + f'sub sp, sp, r4'] + else: + preamble = [] + + ops = preamble + for op in self.ops: + ops += op() + indented = [f' {x}' if x[-1] == ':' else f' {x}' for x in ops] + return [f'.global {self.spec.name}', + f'{self.spec.name}:'] + indented + + +class CcTransform(lark.visitors.Transformer): + def _binary_op(litt): + @lark.v_args(tree=True) + def _f(self, tree): + left, right = tree.children + if left.data == 'litteral' and right.data == 'litteral': + tree.data = 'litteral' + tree.children = [litt(left.children[0], right.children[0])] + return tree + return _f + + def array_item(self, children): + # transform blarg[foo] into *(blarg + foo) because reasons + addop = lark.Tree('add', children) + return lark.Tree('dereference', [addop]) + + # operations on litterals can be done by the compiler + add = _binary_op(lambda a, b: a+b) + sub = _binary_op(lambda a, b: a-b) + mul = _binary_op(lambda a, b: a*b) + shl = _binary_op(lambda a, b: a<> 8) & 0xff + lo = (self.imm16 >> 0) & 0xff + if hi != 0: + return [f'set {reg}, {lo}', + f'seth {reg}, {hi}'] + else: + return [f'set {reg}, {lo}'] + +class Swap(AsmOp): + scratch_need = 1 + + def __init__(self, a0, a1): + self.a0 = a0 + self.a1 = a1 + + def synth(self, scratches): + (sc0,) = scratches + return [f'or {sc0}, {self.a0}, {self.a0}', + f'or {self.a0}, {self.a1}, {self.a1}', + f'or {self.a1}, {sc0}, {sc0}'] + + +class IfOp(AsmOp): + scratch_need = 1 + + def __init__(self, fun, op): + self.fun = fun + self.cond, mark, self.has_else = op + + self.then_mark = f'_then_{mark}' + self.else_mark = f'_else_{mark}' + self.endif_mark = f'_endif_{mark}' + + def synth(self, scratches): + sc0 = scratches[0] + if self.has_else: + return [f'set {sc0}, 0', + f'cmp {sc0}, {self.cond}', # flag if cond == 0 + f'beq {self.else_mark}'] + else: + return [f'set {sc0}, 0', + f'cmp {sc0}, {self.cond}', + f'beq {self.endif_mark}'] + + def synth_else(self): + return [f'cmp r0, r0', # trick because beq is better than "or pc, ." + f'beq {self.endif_mark}', + f'{self.else_mark}:'] + + def synth_endif(self): + return [f'{self.endif_mark}:'] + + +class WhileOp(AsmOp): + scratch_need = 1 + + @staticmethod + def synth_loop(mark): + loop_mark = f'_loop_{mark}' + return [f'{loop_mark}:'] + + def __init__(self, cond, mark): + self.cond = cond + self.loop_mark = f'_loop_{mark}' + self.endwhile_mark = f'_endwhile_{mark}' + + def synth(self, scratches): + sc0 = scratches[0] + return [f'set {sc0}, 0', + f'cmp {sc0}, {self.cond}', + f'beq {self.endwhile_mark}'] + + def synth_endwhile(self): + return [f'cmp r0, r0', + f'beq {self.loop_mark}', + f'{self.endwhile_mark}:'] + + +class Delayed(AsmOp): + def __init__(self, out_cb): + self.out_cb = out_cb + + @property + def out(self): + return self.out_cb() + + def synth(self): + return [] + + +class CcInterp(lark.visitors.Interpreter): + def __init__(self): + self.global_scope = Scope() + self.cur_scope = self.global_scope + self.cur_fun = None + self.funs = [] + self.next_reg = 0 + self.next_marker = 0 + + def _lookup_symbol(self, s): + scope = self.cur_scope + while scope is not None: + if s in scope.symbols: + return scope.symbols[s] + scope = scope.parent + return None + + def _get_reg(self): + return self.cur_fun.regs.take() + + def _get_marker(self): + mark = self.next_marker + self.next_marker += 1 + return mark + + def _synth(self, op): + with self.cur_fun.regs.borrow(op.scratch_need) as scratches: + self._log(f'{op.__class__.__name__}') + self.cur_fun.ops.append(lambda: op.synth(scratches)) + + def _load(self, ident): + s = self._lookup_symbol(ident) + assert s is not None, f'unknown identifier {ident}' + if isinstance(s, FunctionSpec) or s in self.global_scope.symbols: + reg = self._get_reg() + return SetAddr(self.cur_fun, [reg, ident]) + else: + if s.volatile: + self._log(f'loading volatile {s}') + return Load(self.cur_fun, [reg, s]) + reg, created = self.cur_fun.regs.load(s) + if created: + return Load(self.cur_fun, [reg, s]) + else: + self._log(f'{s} was already in {reg}') + return Reg(reg) + + def identifier(self, tree): + # TODO: not actually load the value until it's used in an expression + # could have the op.out() function have a side effect that does that + # if it's an assignment, we need to make a Variable with the proper + # address and assign the register to it. + # If it's volatile, we need to flush it. + def delayed_load(): + self._log(f'delay-loading {tree.children[0]}') + op = self._load(tree.children[0]) + self._synth(op) + return op.out + + tree.op = Delayed(delayed_load) + tree.var = self._lookup_symbol(tree.children[0]) + + def litteral(self, tree): + imm = tree.children[0] + reg = self._get_reg() + assert self.cur_fun is not None + tree.op = Set16Imm(self.cur_fun, [reg, imm]) + self._synth(tree.op) + + def _assign(self, left, right): + # need to make sure there's a variable and either: + # - to store it, or + # - to mark a register for it + if left.volatile: + self._synth(Store(self.cur_fun, [right, left])) + self.cur_fun.stored(left) + return + self._log(f'assigning {left} = {right}') +# cleanup = lambda reg: self._synth(Store(self.cur_fun, [reg, left])) + self.cur_fun.regs.assign(left, right) + self._synth(Store(self.cur_fun, [right, left])) + + def assignment(self, tree): + self.visit_children(tree) + val = tree.children[1].op.out + + # left hand side is an lvalue, retrieve it + var = tree.children[0].var + self._assign(var, val) + + def global_var(self, tree): + self.visit_children(tree) + var = Variable.from_def(tree) + self.global_scope.symbols[var.name] = var + val = 0 + if len(tree.children) > 2: + val = tree.children[2].children[0].value + + def fun_decl(self, tree): + fun = FunctionSpec(tree.children[0]) + self.cur_scope.symbols[fun.name] = fun + + def _prep_fun_call(self, fn_reg, params): + """Move all params to r0-rn.""" + def swap(old, new): + if old == new: + return + oldpos = params.index(old) + try: + newpos = params.index(new) + except ValueError: + params[oldpos] = new + else: + params[newpos], params[oldpos] = params[oldpos], params[newpos] + self._synth(Swap(old, new)) + + if fn_reg in [f'r{i}' for i in range(len(params))]: + new_fn = f'r{len(params)}' + self._synth(Swap(fn_reg, new_fn)) + fn_reg = new_fn + for i, param in enumerate(params): + new = f'r{i}' + swap(param, new) + return fn_reg + + def fun_call(self, tree): + self.cur_fun.fun_calls += 1 + self.visit_children(tree) + fn_reg = tree.children[0].op.out + param_regs = [c.op.out for c in tree.children[1:]] + self.cur_fun.regs.flush_all() + for i in range(len(param_regs)): + self.cur_fun.regs.take(f'r{i}') + fn_reg = self._prep_fun_call(fn_reg, param_regs) + self.cur_fun.regs.take(fn_reg) + tree.op = FnCall(self.cur_fun, [fn_reg, param_regs]) + self._synth(tree.op) + self.cur_fun.regs.reset() + self.cur_fun.regs.take('r0') + + def statement(self, tree): + self.visit_children(tree) + if self.cur_fun.deferred_ops: + self._log(f'deferred logic: {len(self.cur_fun.deferred_ops)}') + for op in self.cur_fun.deferred_ops: + self._synth(op) + self.cur_fun.deferred_ops = [] + + iter_expression = statement + + def if_stat(self, tree): + self.visit(tree.children[0]) + mark = self._get_marker() + has_else = len(tree.children) > 2 + op = IfOp(self.cur_fun, [tree.children[0].op.out, mark, has_else]) + self._synth(op) + self.visit(tree.children[1]) + if has_else: + self.cur_fun.ops.append(op.synth_else) + self.visit(tree.children[2]) + self.cur_fun.ops.append(op.synth_endif) + + def while_stat(self, tree): + mark = self._get_marker() + self.cur_fun.ops.append(lambda: WhileOp.synth_loop(mark)) + begin_vars = dict(self.cur_fun.regs.vars) + self.visit(tree.children[0]) + op = WhileOp(tree.children[0].op.out, mark) + self._synth(op) + self.visit(tree.children[1]) + for v, r in begin_vars.items(): + rvars = self.cur_fun.regs.vars + if v not in rvars or rvars[v] != r: + self._log(f'loading missing var {v}') + self._synth(Load(self.cur_fun, [r, v])) + self.cur_fun.ops.append(op.synth_endwhile) + + def for_stat(self, tree): + mark = self._get_marker() + self.visit(tree.children[0]) # initialization + self.cur_fun.ops.append(lambda: WhileOp.synth_loop(mark)) + begin_vars = dict(self.cur_fun.regs.vars) + self.visit(tree.children[1]) + op = WhileOp(tree.children[1].op.out, mark) + self._synth(op) + self.visit(tree.children[3]) + self.visit(tree.children[2]) # 3rd statement + for v, r in begin_vars.items(): + rvars = self.cur_fun.regs.vars + if v not in rvars or rvars[v] != r: + self._log(f'loading missing var {v}') + self._synth(Load(self.cur_fun, [r, v])) + self.cur_fun.ops.append(op.synth_endwhile) + + def _unary_op(op): + def _f(self, tree): + self.visit_children(tree) + operand = tree.children[0].op.out + reg = self.cur_fun.regs.take() + tree.op = op(self.cur_fun, [reg, operand]) + self._synth(tree.op) + self.cur_fun.regs.give(operand) + return _f + + def dereference(self, tree): + self.visit_children(tree) + reg = tree.children[0].op.out + var = Variable.from_dereference(reg) + self._log(f'making var {var} from derefing reg {reg}') + tree.var = var + def delayed_load(): + self._log(f'delay-loading {tree.children[0]}') + op = Load(self.cur_fun, [reg, var]) + self._synth(op) + return op.out + tree.op = Delayed(delayed_load) + + def post_increment(self, tree): + self.visit_children(tree) + tree.op = Reg(tree.children[0].op.out) + var = tree.children[0].var + reg = tree.op.out + self.cur_fun.deferred_ops.append(Incr(self.cur_fun, [reg, reg])) + self.cur_fun.deferred_ops.append(Store(self.cur_fun, [reg, var])) + + pre_increment = _unary_op(Incr) + bool_not = _unary_op(BoolNot) + + def _binary_op(op): + def _f(self, tree): + self.visit_children(tree) + left, right = (x.op.out for x in tree.children) + dest = self.cur_fun.regs.take() + tree.op = op(self.cur_fun, [dest, left, right]) + self._synth(tree.op) + self.cur_fun.regs.give(left) + self.cur_fun.regs.give(right) + return _f + + add = _binary_op(AddOp) + sub = _binary_op(SubOp) + mul = _binary_op(MulOp) + _and = _binary_op(AndOp) + # ... + gt = _binary_op(GtOp) + lt = _binary_op(LtOp) + neq = _binary_op(NeqOp) + + def _forward_op(self, tree): + self.visit_children(tree) + tree.op = tree.children[0].op + + def cast(self, tree): + self.visit_children(tree) + tree.op = tree.children[1].op + + def _log(self, line): + self.cur_fun.log(line) + + def local_var(self, tree): + self.visit_children(tree) + assert self.cur_fun is not None + assert self.cur_scope is not None + var = Variable.from_def(tree) + var.stackaddr = self.cur_fun.get_stack() # will have to invert + self.cur_scope.symbols[var.name] = var + self.cur_fun.locals[var.name] = var + if len(tree.children) > 2: + initval = tree.children[2].children[0].op.out + cleanup = lambda reg: self._synth(Store(self.cur_fun, [reg, var])) + self.cur_fun.regs.assign(var, initval, cleanup) + self._log(f'assigning {var} = {initval}') + + def fun_def(self, tree): + prot, body = tree.children + fun = Function(prot) + assert self.cur_fun is None + self.cur_fun = fun + + self.cur_scope.symbols[fun.spec.name] = fun.spec + + params = fun.param_dict() + + def getcleanup(var): + return lambda reg: self._synth(Store(self.cur_fun, [reg, var])) + + for i, param in enumerate(fun.params): + fun.locals[param.name] = param + param.stackaddr = fun.get_stack() + self._log(f'param [sp, {param.stackaddr}]: {param.name} in r{i}') + cleanup = getcleanup(param) + fun.regs.loaded(param, f'r{i}', cleanup) + + fun_scope = Scope() + fun_scope.parent = self.cur_scope + fun_scope.symbols.update(params) + + body.scope = fun_scope + self.visit_children(tree) + + if fun.ret is None: + if fun.spec.name == 'main': + self._synth(Set16Imm(fun, ['r0', 0])) + self._synth(ReturnReg(fun, ['r0'])) + elif fun.spec.return_type == 'void': + self._synth(ReturnReg(fun, ['r0'])) + else: + assert fun.ret is not None + self.cur_fun = None + self.funs.append(fun) + + def body(self, tree): + bscope = getattr(tree, 'scope', Scope()) + bscope.parent = self.cur_scope + self.cur_scope = bscope + self.visit_children(tree) + self.cur_scope = bscope.parent + + def return_stat(self, tree): + assert self.cur_fun is not None + self.cur_fun.ret = True + self.visit_children(tree) + expr_reg = tree.children[0].op.out + tree.op = ReturnReg(self.cur_fun, [expr_reg]) + self._synth(tree.op) + +preamble = [f'_start:', + f'xor r0, r0, r0', + f'xor r1, r1, r1', + f'set sp, 0', + f'seth sp, {0x11}', # 256 bytes of stack ought to be enough + f'set r2, main', + f'set r3, 2', + f'add lr, pc, r3', + f'or pc, r2, r2', + f'or pc, pc, pc // loop forever', + ] + +def filter_dupes(ops): + dupe_re = re.compile(r'or (r\d+), \1, \1') + for op in ops: + if dupe_re.search(op): + continue + yield op + +def parse_tree(tree): + tr = CcTransform() + tree = tr.transform(tree) + +# print(tree.pretty()) + + inte = CcInterp() + inte.visit(tree) + + out = [] + + for fun in inte.funs: + out += fun.synth() + out.append('') + return '\n'.join(filter_dupes(out)) + + +def larkparse(f): + with open(GRAMMAR_FILE) as g: + asparser = lark.Lark(g.read()) + tree = asparser.parse(f.read()) + return parse_tree(tree) + + +if __name__ == "__main__": +# print(',\n'.join(parse(sys.stdin))) + print(larkparse(sys.stdin)) diff --git a/tools/crt0.s b/tools/crt0.s new file mode 100644 index 0000000..b8da634 --- /dev/null +++ b/tools/crt0.s @@ -0,0 +1,10 @@ +.global _start +_start: +set sp, 0 +seth sp, 0x01 +set r2, main +nop +set r3, 2 +add lr, pc, r3 +or pc, r2, r2 +or pc, pc, pc // loop forever diff --git a/tools/ld.py b/tools/ld.py new file mode 100644 index 0000000..dd971d3 --- /dev/null +++ b/tools/ld.py @@ -0,0 +1,100 @@ +import argparse +import struct +import sys + +import obj_pb2 + +CRT0_FILE = '/home/paulmathieu/vhdl/bin/crt0.o' + + +def parse_objs(objs): + sections = [] + relocs = [] + + for obj in objs: + of = obj_pb2.ObjFile() + of.ParseFromString(obj.read()) + sections += of.sections + relocs += of.relocs + + return sections, relocs + + +def map_sections(sections): + secmap = [] + + addr = 0 + + # _start goes first + for sec in sections: + if sec.name == '_start': + secmap.append((addr, sec)) + addr += len(sec.text) + sections.remove(sec) + break + assert secmap, "could not find symbol _start :/" + + for sec in sections: + secmap.append((addr, sec)) + addr += len(sec.text) + + return secmap + + +def do_relocs(secmap, relocs): + namemap = {s[1].name: s for s in secmap} + for reloc in relocs: + assert reloc.section in namemap + assert reloc.target in namemap + _, sec = namemap[reloc.section] + # the reloc hex should look like /e.ff0000/ + buff = bytearray(sec.text) + target_addr = namemap[reloc.target][0] + reg = buff[reloc.offset] & 0xf + buff[reloc.offset+0:reloc.offset+4] = [ + 0xf0 | reg, (target_addr >> 0) & 0xff, + 0x90 | reg, (target_addr >> 8) & 0xff, + ] + sec.text = bytes(buff) + + +def dump(secmap): + out = bytearray() + for _, sec in secmap: + out += sec.text + return out + + +def parse_args(): + parser = argparse.ArgumentParser(description='Assemble.') + parser.add_argument('--debug', action='store_true', + help='print debug info') + parser.add_argument('objfiles', metavar='O', nargs='+', + type=argparse.FileType('rb'), + help='input file (default: stdin)') + parser.add_argument('--output', '-o', type=argparse.FileType('wb'), + default=sys.stdout.buffer, help='output file') + parser.add_argument('--vhdl', action='store_true', + help='vhdl output') + return parser.parse_args() + + +def main(): + args = parse_args() + + with open(CRT0_FILE, 'rb') as crt0: + sections, relocs = parse_objs(args.objfiles + [crt0]) + + sectionmap = map_sections(sections) + do_relocs(sectionmap, relocs) + text = dump(sectionmap) + + if args.vhdl: + args.output.write(',\n'.join(f'x"{x:04x}"' for x in + struct.unpack(f'>{len(text) // 2}H', text)).encode()) + else: + args.output.write(text) + + +if __name__ == "__main__": + main() diff --git a/tools/makefile b/tools/makefile new file mode 100644 index 0000000..4bfec02 --- /dev/null +++ b/tools/makefile @@ -0,0 +1,17 @@ +all: crt0.o + +crt0.o: crt0.s as.py + python as.py -c $< -o $@ + +as.py: obj_pb2.py + +obj_pb2.py: obj.proto + protoc --python_out=. $< + +PHONY: clean distclean + +clean: + rm -rf crt0.o + +distclean: clean + rm -rf obj_pb2.py __pycache__ diff --git a/tools/obj.proto b/tools/obj.proto new file mode 100644 index 0000000..d046692 --- /dev/null +++ b/tools/obj.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +message Section { + string name = 1; + bytes text = 2; +} + +// there is always exactly ONE header with a magic "pol0" at the start +message Header { + string magic = 1; +} + +message Reloc { + string section = 1; + int32 offset = 2; + string target = 3; +} + +message ObjFile { + Header header = 1; + repeated Section sections = 2; + repeated Reloc relocs = 3; +} diff --git a/tools/requirements.txt b/tools/requirements.txt new file mode 100644 index 0000000..be4b217 --- /dev/null +++ b/tools/requirements.txt @@ -0,0 +1,2 @@ +protobuf +lark diff --git a/uart/echo.c b/uart/echo.c new file mode 100644 index 0000000..4d78baf --- /dev/null +++ b/uart/echo.c @@ -0,0 +1,10 @@ +#include "uart.h" + +int main() { + while (1) { + char c = uart_read(); + uart_write(c); + } + + // never returns +} diff --git a/uart/testtx.c b/uart/testtx.c new file mode 100644 index 0000000..4b85ddc --- /dev/null +++ b/uart/testtx.c @@ -0,0 +1,32 @@ +#define UART_BASE 0xc010 +#define UART_DATA ((volatile char*) (UART_BASE + 0x00)) +#define UART_STATUS ((volatile char*) (UART_BASE + 0x02)) + +#define UART_STATUS_RXNE_pos 1 +#define UART_STATUS_RXNE_msk (1 << UART_STATUS_RXNE_pos) +#define UART_STATUS_TXE_pos 0 +#define UART_STATUS_TXE_msk (1 << UART_STATUS_TXE_pos) + +const char hello[] = "Hello, world!"; + +int uart_tx_available() { + return (*UART_STATUS & UART_STATUS_TXE_msk) != 0; +} + +void uart_send(const char* data, int len) { + for (int i = 0; i < len; ++i) { + while (!uart_tx_available()) { + } + + *UART_DATA = data[i]; + } +} + +int main() { + uart_send(hello, sizeof(hello)); + + while (1) { + } + + // never returns +} diff --git a/uart/testtx.s b/uart/testtx.s new file mode 100644 index 0000000..42bbfc0 --- /dev/null +++ b/uart/testtx.s @@ -0,0 +1,42 @@ +start_: + set r0, main + or pc, r0, r0 + +hello: + .word 0x4865 + .word 0x6c6c + .word 0x6f2c + .word 0x2077 + .word 0x6f72 + .word 0x6c64 + .word 0x2100 + +uart_send: // r0 is pointer to data, r1, length + set r2, 0 + set r3, 0x10 + seth r3, 0xc0 + set r4, 1 + loop0: + load r5, [r3, 2] + and r5, r5, r4 + cmp r5, r2 + beq loop0 + load r5, [r0] + and r6, r0, r4 + cmp r6, r4 + beq [pc, 2] + shr r5, r5, 8 + store r5, [r3] + add r0, r0, r4 + sub r1, r1, r4 + cmp r1, r2 + bneq loop0 + or pc, lr, lr + +main: + set r0, hello + set r1, 13 + set r2, uart_send + set r3, main + or lr, r3, r3 + or pc, r2, r2 diff --git a/uart/uart.c b/uart/uart.c new file mode 100644 index 0000000..debb0b7 --- /dev/null +++ b/uart/uart.c @@ -0,0 +1,23 @@ +#include "uart.h" + +int uart_rx_available() { + return (*UART_STATUS & UART_STATUS_RXNE_msk) != 0; +} + +int uart_tx_available() { + return (*UART_STATUS & UART_STATUS_TXE_msk) != 0; +} + +void uart_write(const char c) { + while (!uart_tx_available()) { + } + + *UART_DATA = c; +} + +char uart_read() { + while (!uart_rx_available()) { + } + + return *UART_DATA; +} diff --git a/uart/uart.h b/uart/uart.h new file mode 100644 index 0000000..726691d --- /dev/null +++ b/uart/uart.h @@ -0,0 +1,13 @@ +#define UART_BASE 0xc010 +#define UART_DATA ((volatile char*) (UART_BASE + 0x00)) +#define UART_STATUS ((volatile char*) (UART_BASE + 0x02)) + +#define UART_STATUS_RXNE_pos 1 +#define UART_STATUS_RXNE_msk (1 << UART_STATUS_RXNE_pos) +#define UART_STATUS_TXE_pos 0 +#define UART_STATUS_TXE_msk (1 << UART_STATUS_TXE_pos) + +int uart_rx_available(); +int uart_tx_available(); +void uart_write(const char c); +char uart_read(); diff --git a/uart/uart.vhdl b/uart/uart.vhdl new file mode 100644 index 0000000..7313d3b --- /dev/null +++ b/uart/uart.vhdl @@ -0,0 +1,246 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity uart is + port + ( + clk : in std_logic; + rst : in std_logic; + + -- hardware + rx_pin : in std_logic; + tx_pin : out std_logic; + + -- bus interface + we : in std_logic; + re : in std_logic; + addr : in std_logic_vector(15 downto 0); + din : in std_logic_vector(15 downto 0); + dout : out std_logic_vector(15 downto 0) + ); +end uart; + +-- +-- Mem layout: +-- 0x00: 8-bit read/write +-- 0x02: flags: [rxne, txe] + +-- Rx FIFO 4 bytes long +-- Tx FIFO 4 bytes long + +-- Mnemonic: receive from the left, transmit to the right + +architecture Behavioral of uart is + constant BAUD: positive := 115_200; + constant SYSFREQ: natural := 100_000_000; + constant CLKCNT: natural := SYSFREQ / BAUD; + + type Fifo is array(integer range <>) of std_logic_vector(7 downto 0); + + ---- all dffs below + + signal rxfifo : Fifo(0 to 3); + signal txfifo : Fifo(3 downto 0); + + signal rxcnt : unsigned(2 downto 0); + signal txcnt : unsigned(2 downto 0); + + signal sysclk : unsigned(9 downto 0); -- up to 868 counts + signal uarten : std_logic; -- 1 when state machines can do stuff + + type RxState_t is (IDLE, SHIFT_IN); + type TxState_t is (IDLE, SHIFT_OUT); + + signal rxstate: RxState_t; + signal txstate: TxState_t; + + signal txpopped : std_logic; + signal rxpushed : std_logic; + + signal txshift : std_logic_vector(7 downto 0); + signal rxshift : std_logic_vector(7 downto 0); + + signal txshiftcnt : unsigned(3 downto 0); + signal rxshiftcnt : unsigned(3 downto 0); +begin + + -- rx process + -- drives rxstate, rxpushed, rxshift, rxshiftcnt, rxfifo + process(clk, rst) + begin + if rst = '1' then + rxstate <= IDLE; + rxpushed <= '0'; + rxshift <= x"00"; + rxshiftcnt <= "0000"; + + for i in 0 to 3 loop + rxfifo(i) <= x"00"; + end loop; + elsif rising_edge(clk) and uarten = '1' then + rxpushed <= '0'; + + case rxstate is + when IDLE => + if rx_pin = '0' then -- start bit!! (hopefully) + rxshift <= x"00"; + rxshiftcnt <= "0000"; + rxstate <= SHIFT_IN; + end if; + when SHIFT_IN => + if rxshiftcnt = 8 then + -- by now we should be seeing the stop bit, check + if rx_pin = '1' then -- all good, push away + for i in 2 downto 0 loop -- right to left + rxfifo(i + 1) <= rxfifo(i); + end loop; + rxfifo(0) <= rxshift; + rxpushed <= '1'; + end if; + rxstate <= IDLE; -- either way, we're done + else + rxshift <= rx_pin & rxshift(7 downto 1); + rxshiftcnt <= rxshiftcnt + 1; + end if; + end case; + end if; + end process; + + -- tx process + -- drives txstate, txpopped, txshift, txshiftcnt, tx_pin + process(clk, rst) + begin + if rst = '1' then + txstate <= IDLE; + txpopped <= '0'; + txshift <= x"00"; + txshiftcnt <= "0000"; + tx_pin <= '1'; + elsif rising_edge(clk) and uarten = '1' then + txpopped <= '0'; + + case txstate is + when IDLE => + if txcnt > 0 then + txshiftcnt <= "0000"; + tx_pin <= '0'; -- start bit + txstate <= SHIFT_OUT; + txshift <= txfifo(0); + txpopped <= '1'; + else + tx_pin <= '1'; + end if; + when SHIFT_OUT => + if txshiftcnt = 8 then + tx_pin <= '1'; + txstate <= IDLE; + else + txshiftcnt <= txshiftcnt + 1; + tx_pin <= txshift(0); + txshift(6 downto 0) <= txshift(7 downto 1); + end if; + end case; + end if; + end process; + + process(clk, rst) -- drives sysclk, uarten + begin + if rst = '1' then + sysclk <= to_unsigned(0, 10); + uarten <= '0'; + elsif rising_edge(clk) then + if sysclk = to_unsigned(CLKCNT - 1, 10) then + sysclk <= to_unsigned(0, 10); + uarten <= '1'; + else + sysclk <= sysclk + 1; + uarten <= '0'; + end if; + end if; + end process; + + process(clk, rst) -- drives dout, rxcnt, txcnt, txfifo + variable txn: unsigned(2 downto 0); + variable rxn: unsigned(2 downto 0); + + variable txpopdone : std_logic := '0'; -- latch + variable rxpushdone : std_logic := '0'; -- latch + begin + rxn := rxcnt; + txn := txcnt; + + if rst = '1' then + for i in 0 to 3 loop + txfifo(i) <= x"00"; + end loop; + + rxcnt <= "000"; + txcnt <= "000"; + + txpopdone := '0'; + rxpushdone := '0'; + elsif rising_edge(clk) then + dout <= x"0000"; + + -- Fifo grooming + if txpopped = '1' then + if txpopdone = '0' then + -- shift our fifo to the right + for i in 0 to 2 loop -- 0 to 2 is right to left + txfifo(i) <= txfifo(i + 1); + end loop; + txpopdone := '1'; + txn := txcnt - 1; + end if; + else + txpopdone := '0'; + end if; + + if rxpushed = '1' then + if rxpushdone = '0' then + -- our fifo was already moved, just update rxn + if rxcnt < 4 then + rxn := rxcnt + 1; + end if; + rxpushdone := '1'; + end if; + else + rxpushdone := '0'; + end if; + + txcnt <= txn; + rxcnt <= rxn; + + -- logic here + case addr is + when x"0000" => + if we = '1' then + if to_integer(txn) < 4 then + txfifo(to_integer(txn)) <= din(7 downto 0); + txcnt <= txn + 1; + end if; + elsif re = '1' then + if to_integer(rxn) > 0 then + dout(7 downto 0) <= rxfifo(to_integer(rxn) - 1); + rxcnt <= rxn - 1; + end if; + end if; + when x"0002" => + if to_integer(rxn) > 0 then + dout(1) <= '1'; + else + dout(1) <= '0'; + end if; + if to_integer(txn) = 0 then + dout(0) <= '1'; + else + dout(0) <= '0'; + end if; + when others => + -- nada + end case; + end if; + end process; + +end Behavioral;