Initial commit

This commit is contained in:
Paul Mathieu 2021-02-17 13:20:30 -08:00
commit 363944d417
35 changed files with 3318 additions and 0 deletions

57
first/adder.vhdl Normal file
View File

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

48
first/adder_test.vhdl Normal file
View File

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

70
first/alu.vhdl Normal file
View File

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

22
first/blinky.s Normal file
View File

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

142
first/boot_rom.vhdl Normal file
View File

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

31
first/cc/blinky.c Normal file
View File

@ -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
}

20
first/clock.vhdl Normal file
View File

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

197
first/cpu.vhdl Normal file
View File

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

147
first/cpu_test.vhdl Normal file
View File

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

26
first/dff.vhdl Normal file
View File

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

59
first/dff_test.vhdl Normal file
View File

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

191
first/hello.vhdl Normal file
View File

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

15
first/or.vhdl Normal file
View File

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

54
first/or_test.vhdl Normal file
View File

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

49
first/ram.vhdl Normal file
View File

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

52
first/regfile.vhdl Normal file
View File

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

55
first/register.vhdl Normal file
View File

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

39
first/rom.vhdl Normal file
View File

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

66
first/rom_test.vhdl Normal file
View File

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

73
first/top.vhdl Normal file
View File

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

45
tools/as.ebnf Normal file
View File

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

186
tools/as.py Normal file
View File

@ -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()

159
tools/cc.ebnf Normal file
View File

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

997
tools/cc.py Normal file
View File

@ -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'<Var: {self.type} {self.name}>'
@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'<Function: {self.return_type} {self.name}({params})>'
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<<b)
IDENTIFIER = str
SIGNED_NUMBER = int
HEX_LITTERAL = lambda _, x: int(x[2:], 16)
class AsmOp:
scratch_need = 0
def synth(self, scratches):
return [f'nop']
@property
def out(self):
return None
class Reg:
scratch_need = 0
def __init__(self, reg):
self.out = reg
def synth(self, scratches):
return []
class BinOp(AsmOp):
scratch_need = 0
def __init__(self, fun, ops):
self.fun = fun
self.dest, self.left, self.right = ops
@property
def out(self):
return self.dest
def make_cpu_bin_op(cpu_op):
class _C(BinOp):
def synth(self, _):
return [f'{cpu_op} {self.dest}, {self.left}, {self.right}']
return _C
AddOp = make_cpu_bin_op('add')
SubOp = make_cpu_bin_op('sub')
MulOp = make_cpu_bin_op('mul')
# no div
# no mod either
AndOp = make_cpu_bin_op('and')
orOp = make_cpu_bin_op('or')
XorOp = make_cpu_bin_op('xor')
def combo(UnOp2, Op1):
"""Apply UnOp2 on the output of Op1."""
class _C(AsmOp):
scratch_need = max(Op1.scratch_need, UnOp2.scratch_need)
@property
def out(self):
return self.op2.out
def __init__(self, fun, ops):
self.op1 = Op1(fun, ops)
self.op2 = Op2(fun, self.op1.out)
def synth(self, scratches):
self.op1.synth(scratches)
self.op2.synth(scratches)
return _C
class LtOp(BinOp):
scratch_need = 1
def synth(self, scratches):
sc0 = scratches[0]
return [f'set {self.dest}, 0',
f'sub {sc0}, {self.left}, {self.right}',
f'bneq [pc, 2]',
f'set {self.dest}, 1']
class GtOp(LtOp):
def __init__(self, fun, ops):
dest, left, right = ops
super(GtOp, self).__init__(fun, [dest, right, left])
class UnOp(AsmOp):
scratch_need = 0
def __init__(self, fun, ops):
self.fun = fun
self.dest, self.operand = ops
@property
def out(self):
return self.dest
class Incr(UnOp):
scratch_need = 1
def synth(self, scratches):
sc0 = scratches[0]
return [f'set {sc0}, 1',
f'add {self.dest}, {self.operand}, {sc0}',
f'or {self.operand}, {self.dest}, {self.dest}']
class NotOp(UnOp):
def synth(self, scratches):
return [f'not {self.dest}, {self.operand}']
class BoolNot(UnOp):
def synth(self, scratches):
return [f'set {self.dest}, 0',
f'cmp {self.dest}, {self.operand}',
f'bneq [pc, 2]',
f'set {self.dest}, 1']
class NeqOp(BinOp):
def synth(self, scratches):
return [f'sub {self.dest}, {self.left}, {self.right}']
EqOp = combo(BoolNot, NeqOp)
LeOp = combo(BoolNot, GtOp)
GeOp = combo(BoolNot, LtOp)
class FnCall(AsmOp):
scratch_need = 1
def __init__(self, fun, ops):
self.fun = fun
self.dest_fn, self.params = ops
@property
def out(self):
return 'r0'
def synth(self, scratches):
out = []
sc0 = scratches[0]
fn = self.dest_fn
return out + [f'set {sc0}, 2',
f'add lr, pc, {sc0}',
f'or pc, {fn}, {fn}']
class ReturnReg(AsmOp):
scratch_need = 1
def __init__(self, fun, ops):
self.fun = fun
(self.ret_reg,) = ops
def synth(self, scratches):
if self.fun.fun_calls == 0:
return [f'or r0, {self.ret_reg}, {self.ret_reg}',
f'or pc, lr, lr']
sc0 = scratches[0]
stack_usage = self.fun.stack_usage
ret = self.ret_reg
assert stack_usage < 255
return [f'set {sc0}, {stack_usage}',
f'add sp, sp, {sc0}',
f'or r0, {ret}, {ret}',
f'load pc, [sp, -2] // return']
class Load(AsmOp):
scratch_need = 0
def __init__(self, fun, ops):
self.fun = fun
self.dest, self.var = ops
@property
def out(self):
return self.dest
def synth(self, scratches):
reg = self.dest
if self.var.name in self.fun.locals:
src = self.var.stackaddr
return [f'load {reg}, [sp, {src}]']
elif self.var.addr_reg is not None:
return [f'load {reg}, [{self.var.addr_reg}]']
else:
return [f'set {reg}, {self.var.name}',
f'nop // in case we load a far global',
f'load {reg}, [{reg}]']
class Store(AsmOp):
scratch_need = 1
def __init__(self, fun, ops):
self.fun = fun
self.src, self.var = ops
@property
def out(self):
return None
def synth(self, scratches):
(sc,) = scratches
reg = self.src
if self.var.name in self.fun.locals:
dst = self.var.stackaddr
self.fun.log(f'storing {self.var}({reg}) to [sp, {dst}]')
return [f'store {reg}, [sp, {dst}]']
elif self.var.addr_reg is not None:
return [f'store {reg}, [{self.var.addr_reg}]']
return [f'set {sc}, {self.var.name}',
f'nop // you know, in case blah',
f'store {reg}, [{sc}]']
class Assign(AsmOp):
scratch_need = 0
def __init__(self, fun, ops):
self.fun = fun
self.src, self.var = ops
@property
def out(self):
return self.var
def synth(self, scratches):
return [f'or {self.var}, {self.src}, {self.src}']
class SetAddr(AsmOp):
scratch_need = 0
def __init__(self, fun, ops):
self.dest, self.ident = ops
@property
def out(self):
return self.dest
def synth(self, scratches):
reg = self.dest
return [f'set {reg}, {self.ident}',
f'nop // placeholder for a long address']
class Set16Imm(AsmOp):
scratch_need = 0
def __init__(self, fun, ops):
self.dest, self.imm16 = ops
@property
def out(self):
return self.dest
def synth(self, scratches):
reg = self.dest
hi = (self.imm16 >> 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))

10
tools/crt0.s Normal file
View File

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

100
tools/ld.py Normal file
View File

@ -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()

17
tools/makefile Normal file
View File

@ -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__

23
tools/obj.proto Normal file
View File

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

2
tools/requirements.txt Normal file
View File

@ -0,0 +1,2 @@
protobuf
lark

10
uart/echo.c Normal file
View File

@ -0,0 +1,10 @@
#include "uart.h"
int main() {
while (1) {
char c = uart_read();
uart_write(c);
}
// never returns
}

32
uart/testtx.c Normal file
View File

@ -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
}

42
uart/testtx.s Normal file
View File

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

23
uart/uart.c Normal file
View File

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

13
uart/uart.h Normal file
View File

@ -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();

246
uart/uart.vhdl Normal file
View File

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