From a266096f32097146b9ce39fce839dbceeff3c4a3 Mon Sep 17 00:00:00 2001 From: ParkerTenBroeck <51721964+ParkerTenBroeck@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:26:08 -0500 Subject: [PATCH] first --- .gitignore | 1 + conn/.gitignore | 1 + conn/Cargo.lock | 7 + conn/Cargo.toml | 7 + conn/src/lib.rs | 60 +++++++++ default.nix | 34 +++++ rtl/circuit._vhdl | 26 ++++ rtl/circuit.vhdl | 329 ++++++++++++++++++++++++++++++++++++++++++++++ rtl/tb.vhdl | 94 +++++++++++++ run.sh | 34 +++++ 10 files changed, 593 insertions(+) create mode 100644 .gitignore create mode 100644 conn/.gitignore create mode 100644 conn/Cargo.lock create mode 100644 conn/Cargo.toml create mode 100644 conn/src/lib.rs create mode 100644 default.nix create mode 100644 rtl/circuit._vhdl create mode 100644 rtl/circuit.vhdl create mode 100644 rtl/tb.vhdl create mode 100755 run.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build diff --git a/conn/.gitignore b/conn/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/conn/.gitignore @@ -0,0 +1 @@ +/target diff --git a/conn/Cargo.lock b/conn/Cargo.lock new file mode 100644 index 0000000..9f08bda --- /dev/null +++ b/conn/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "vhdl_ui" +version = "0.1.0" diff --git a/conn/Cargo.toml b/conn/Cargo.toml new file mode 100644 index 0000000..69c23d2 --- /dev/null +++ b/conn/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "vhdl_ui" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["staticlib"] \ No newline at end of file diff --git a/conn/src/lib.rs b/conn/src/lib.rs new file mode 100644 index 0000000..4798ac8 --- /dev/null +++ b/conn/src/lib.rs @@ -0,0 +1,60 @@ +use std::{ + io::{BufRead, BufReader}, + sync::atomic::{AtomicU32, Ordering}, +}; + +pub struct SimState{ + switch: AtomicU32, + button: AtomicU32, + led: AtomicU32, + hex: AtomicU32, +} + +static STATE: SimState = SimState{ + switch: AtomicU32::new(0), + button: AtomicU32::new(0), + led: AtomicU32::new(0), + hex: AtomicU32::new(0), +}; + +fn client() { + let reader = BufReader::new(std::io::stdin()); + for line in reader.lines().map_while(Result::ok) { + let line = line.trim(); + if let Some(v) = line.strip_prefix("sw=") { + if let Ok(n) = v.parse::() { + STATE.switch.store(n, Ordering::Relaxed); + } + } else if let Some(v) = line.strip_prefix("key=") { + if let Ok(n) = v.parse::() { + STATE.button.store(n, Ordering::Relaxed); + } + } + } +} + +#[no_mangle] +pub extern "C" fn ffi_init() { + std::thread::Builder::new().name("client".into()).spawn(client).expect("Failed to spawn client thread"); + eprintln!("[ffi] initialzied"); +} + +#[no_mangle] +pub extern "C" fn ffi_get_sw() -> u32 { + STATE.switch.load(Ordering::Relaxed) +} + +#[no_mangle] +pub extern "C" fn ffi_get_key() -> u32 { + STATE.button.load(Ordering::Relaxed) +} + +#[no_mangle] +pub extern "C" fn ffi_set_outputs(led: u32, hex: u32) { + if STATE.led.swap(led, Ordering::Relaxed) != led{ + println!("LED={:#x?}", STATE.led.load(Ordering::Relaxed)) + } + if STATE.hex.swap(hex, Ordering::Relaxed) != hex{ + println!("HEX={:#x?}", STATE.hex.load(Ordering::Relaxed)) + } +} \ No newline at end of file diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..4f73d5d --- /dev/null +++ b/default.nix @@ -0,0 +1,34 @@ +{ pkgs ? import {} }: + pkgs.mkShell rec { + buildInputs = with pkgs; [ + # Replace llvmPackages with llvmPackages_X, where X is the latest LLVM version (at the time of writing, 16) + llvmPackages.bintools + rustup + ghdl-llvm + ]; + RUSTC_VERSION = "nightly"; + # https://github.com/rust-lang/rust-bindgen#environment-variables + LIBCLANG_PATH = pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ]; + shellHook = '' + export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin + export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/ + ''; + # Add precompiled library to rustc search path + RUSTFLAGS = (builtins.map (a: ''-L ${a}/lib'') [ + # add libraries here (e.g. pkgs.libvmi) + ]); + # Add glibc, clang, glib and other headers to bindgen search path + BINDGEN_EXTRA_CLANG_ARGS = + # Includes with normal include path + (builtins.map (a: ''-I"${a}/include"'') [ + # add dev libraries here (e.g. pkgs.libvmi.dev) + pkgs.glibc.dev + ]) + # Includes with special directory paths + ++ [ + ''-I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"'' + ''-I"${pkgs.glib.dev}/include/glib-2.0"'' + ''-I${pkgs.glib.out}/lib/glib-2.0/include/'' + ]; + + } diff --git a/rtl/circuit._vhdl b/rtl/circuit._vhdl new file mode 100644 index 0000000..ef367ed --- /dev/null +++ b/rtl/circuit._vhdl @@ -0,0 +1,26 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +-- Do not modify the following entity block +entity circuit is +port ( + clk: in std_logic; -- 500 Hz, period 2 ms + key: in std_logic_vector(3 downto 0); -- active low + sw: in std_logic_vector(9 downto 0); -- active high + led: out std_logic_vector(9 downto 0) := (others => '0'); -- active high + hex0: out std_logic_vector(6 downto 0) := (others => '0'); -- active low + hex1: out std_logic_vector(6 downto 0) := (others => '0') -- active low + ); +end circuit; + + +architecture description of circuit is + signal counter: unsigned(9 downto 0) := "0000000000"; +begin + led <= std_logic_vector(counter(9 downto 0)); + process(clk) + begin + counter <= counter+1; + end process; +end description; \ No newline at end of file diff --git a/rtl/circuit.vhdl b/rtl/circuit.vhdl new file mode 100644 index 0000000..72c6944 --- /dev/null +++ b/rtl/circuit.vhdl @@ -0,0 +1,329 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +-- Do not modify the following entity block +entity circuit is +port ( + clk: in std_logic; + key: in std_logic_vector(31 downto 0); -- active low + sw: in std_logic_vector(31 downto 0); -- active high + led: out std_logic_vector(31 downto 0); -- active high + hex: out std_logic_vector(31 downto 0) -- active low + ); +end circuit; + + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity alu is + Port ( + func: in unsigned(3 downto 0); + a: in unsigned(7 downto 0); + b: in unsigned(7 downto 0); + carry_in: in std_logic; + o: out unsigned(7 downto 0); + carry_out: out std_logic; + zero: out std_logic; + gt: out std_logic; + lt: out std_logic; + eq: out std_logic + ); +end alu; + +architecture Behavioral of alu is + signal tmp: unsigned(8 downto 0); +begin + with func select + tmp <= ("0"&a) + ("0"&b) when x"0", + ("0"&a) + ("0"&b) + (x"00"&carry_in) when x"1", + ("0"&a) - ("0"&b) when x"2", + ("0"&a) - ("0"&b) - (x"00"&carry_in) when x"3", + ("0"&a) and ("0"&b) when x"4", + ("0"&a) or ("0"&b) when x"5", + ("0"&a) xor ("0"&b) when x"6", + "0"&x"00" when others; + + zero <= '1' when tmp = 0 else '0'; + eq <= '1' when a = b else '0'; + lt <= '1' when a < b else '0'; + gt <= '1' when a > b else '0'; + carry_out <= tmp(8); + o <= tmp(7 downto 0); + +end Behavioral ; -- Behavioral + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity ram_8x256 is + Port ( + clk : in std_logic; + we : in std_logic; -- write enable + addr : in unsigned(7 downto 0); -- 8-bit address + din : in unsigned(7 downto 0); -- data input + dout : out unsigned(7 downto 0) -- data output + ); +end ram_8x256; + +architecture Behavioral of ram_8x256 is + type ram_type is array (0 to 255) of unsigned(7 downto 0); + signal ram : ram_type := (others => x"AB"); +begin + process(clk) + begin + if rising_edge(clk) then + if we = '1' then + ram(to_integer(unsigned(addr))) <= din; + end if; + + dout <= ram(to_integer(unsigned(addr))); + end if; + end process; +end Behavioral; + + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity inst_ram_8x256 is + Port ( + clk : in std_logic; + addr : in unsigned(7 downto 0); -- 8-bit address + dout : out unsigned(7 downto 0) -- data output + ); +end inst_ram_8x256; + +architecture Behavioral of inst_ram_8x256 is + type ram_type is array (0 to 255) of unsigned(7 downto 0); + signal ram : ram_type := ( + 0 => x"A0", -- 0 => a + 2 => x"B1", -- 1 => b + 3 => x"10", -- a+b => out + 4 => x"AE", -- out => a + 5 => x"10", -- a+b => out + 6 => x"BE", -- out => b + 7 => x"D0", -- jump to 3 + 8 => x"03", + others => (others => '0') + ); +begin + process(clk) + begin + if rising_edge(clk) or falling_edge(clk) then + dout <= ram(to_integer(unsigned(addr))); + end if; + end process; +end Behavioral; + + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + + +architecture description of circuit is + signal clock: std_logic; + + signal reg_pc: unsigned(7 downto 0) := "00000000"; + signal reg_a: unsigned(7 downto 0) := "00000000"; + signal reg_b: unsigned(7 downto 0) := "00000000"; + signal reg_out: unsigned(7 downto 0) := "00000000"; + + signal inst_reg: unsigned(7 downto 0); + signal inst_bus: unsigned(7 downto 0); + + signal data_read: unsigned(7 downto 0); + signal data_write: unsigned(7 downto 0); + + signal data_addr: unsigned(7 downto 0) := "00000000"; + signal data_write_e: std_logic := '0'; + + signal flag_carry: std_logic := '0'; + signal flag_lt: std_logic := '0'; + signal flag_gt: std_logic := '0'; + signal flag_eq: std_logic := '0'; + signal flag_zero: std_logic := '0'; + + signal alu_a, alu_b, alu_o : unsigned(7 downto 0); + signal alu_func : unsigned(3 downto 0); + signal alu_carry, alu_zero, alu_gt, alu_lt, alu_eq : std_logic; + + function dec7seg(val: unsigned(3 downto 0)) return std_logic_vector is + begin + case val is + when "0000"=> return "1000000"; --0 + when "0001"=> return "1111001"; --1 + when "0010"=> return "0100100"; --2 + when "0011"=> return "0110000"; --3 + when "0100"=> return "0011001"; --4 + when "0101"=> return "0010010"; --5 + when "0110"=> return "0000010"; --6 + when "0111"=> return "1111000"; --7 + when "1000"=> return "0000000"; --8 + when "1001"=> return "0011000"; --9 + when "1010"=> return "0001000"; --A + when "1011"=> return "0000011"; --B + when "1100"=> return "1000110"; --C + when "1101"=> return "0100001"; --D + when "1110"=> return "0000110"; --E + when "1111"=> return "0001110"; --F + when others=> return "1111111"; --- + end case; + end function; + + +begin + + -- hex(7 downto 4) <= dec7seg(reg_out(7 downto 4)); + -- hex(3 downto 0) <= dec7seg(reg_out(3 downto 0)); + + clock <= clk when sw(9) = '1' else sw(8); + + ram_inst : entity work.inst_ram_8x256 + port map( + clk => clk, + addr => reg_pc, + dout => inst_bus + ); + + ram_data : entity work.ram_8x256 + port map( + clk => clk, + we => data_write_e, + addr => data_addr, + din => data_write, + dout => data_read + ); + + alu : entity work.alu + port map( + func => alu_func, + a => alu_a, + b => alu_b, + carry_in => flag_carry, + o => alu_o, + carry_out => alu_carry, + zero => alu_zero, + gt => alu_gt, + lt => alu_lt, + eq => alu_eq + ); + + process(clock) + variable out_extended : unsigned(8 downto 0); + begin + + if rising_edge(clock) then + inst_reg <= inst_bus; + data_write_e <= '0'; + + report "begin reg_a = " & integer'image(to_integer(unsigned(reg_a))) + & " reg_b = " & integer'image(to_integer(unsigned(reg_b))) + & " reg_out = " & integer'image(to_integer(unsigned(reg_out))) + & " reg_pc = " & integer'image(to_integer(unsigned(reg_pc))) + & " inst_bus = " & integer'image(to_integer(unsigned(inst_bus))); + + -- alu operations a,b + if inst_reg(7 downto 4) = x"1" then + alu_func <= inst_reg(3 downto 0); + alu_a <= reg_a; + alu_b <= reg_b; + reg_out <= alu_o; + end if; + -- alu operations a,imm + if inst_reg(7 downto 4) = x"2" then + alu_func <= inst_reg(3 downto 0); + alu_a <= reg_a; + reg_pc <= reg_pc+1; + end if; + -- alu operations imm,b + if inst_reg(7 downto 4) = x"3" then + alu_func <= inst_reg(3 downto 0); + alu_b <= reg_b; + reg_pc <= reg_pc+1; + end if; + + case inst_bus is + -- nop + when x"00" => null; + + + -- 0 => a + when x"A0" => reg_a <= x"00"; + -- 1 => a + when x"A1" => reg_a <= x"01"; + -- mem[reg b] => a + when x"AC" => + data_addr <= reg_b; + -- out => a + when x"AE" => reg_a <= reg_out; + -- immediate => a + when x"AF" => reg_pc <= reg_pc+1; + + -- 0 => b + when x"B0" => reg_b <= x"00"; + -- 1 => b + when x"B1" => reg_b <= x"01"; + -- mem[reg a] => b + when x"BC" => + data_addr <= reg_b; + -- out => b + when x"BE" => reg_b <= reg_out; + -- immediate => b + when x"BF" => reg_pc <= reg_pc+1; + + -- conditional + + -- jump imm addr abs + when x"D0" => reg_pc <= reg_pc+1; + -- jump imm addr rel + when x"D1" => reg_pc <= reg_pc+1; + -- jump addr reg a + when x"DA" => reg_pc <= reg_a-1; + -- jump addr reg b + when x"DB" => reg_pc <= reg_b-1; + + -- halt + when x"FF" => reg_pc <= reg_pc-1; + + when others => null; + end case; + end if; + + if falling_edge(clock) then + case inst_reg is + when x"AC" => reg_a <= data_read; + when x"AF" => reg_a <= inst_bus; + + when x"BC" => reg_b <= data_read; + when x"BF" => reg_b <= inst_bus; + + when others => null; + end case; + + + case inst_reg is + -- jump imm addr abs + when x"D0" => reg_pc <= inst_bus; + -- jump imm addr rel + when x"D1" => reg_pc <= reg_pc+inst_bus; + + when others => reg_pc <= reg_pc+1; + end case; + + report "end reg_a = " & integer'image(to_integer(unsigned(reg_a))) + & " reg_b = " & integer'image(to_integer(unsigned(reg_b))) + & " reg_out = " & integer'image(to_integer(unsigned(reg_out))) + & " reg_pc = " & integer'image(to_integer(unsigned(reg_pc))) + & " inst_bus = " & integer'image(to_integer(unsigned(inst_bus))); + + + end if; + + end process; +end description; \ No newline at end of file diff --git a/rtl/tb.vhdl b/rtl/tb.vhdl new file mode 100644 index 0000000..6831d75 --- /dev/null +++ b/rtl/tb.vhdl @@ -0,0 +1,94 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity tb is +end entity; + +architecture sim of tb is + signal clk : std_logic := '0'; + signal key : std_logic_vector(31 downto 0) := (others => '1'); -- active low + signal sw : std_logic_vector(31 downto 0) := (others => '0'); + + signal led : std_logic_vector(31 downto 0) := (others => '0'); + signal hex : std_logic_vector(31 downto 0) := (others => '0'); + + + -- Foreign subprograms MUST be declared in the declarative region (here), + -- and MUST have a body (even dummy) to satisfy VHDL. + procedure ffi_init is + begin + end procedure; + attribute foreign of ffi_init : procedure is + "VHPIDIRECT ffi_init"; + + function ffi_get_sw return integer is + begin + return 0; + end function; + attribute foreign of ffi_get_sw : function is + "VHPIDIRECT ffi_get_sw"; + + function ffi_get_key return integer is + begin + return 0; + end function; + attribute foreign of ffi_get_key : function is "VHPIDIRECT ffi_get_key"; + + procedure ffi_set_outputs(led_i : integer; hex_i : integer) is + begin + end procedure; + attribute foreign of ffi_set_outputs : procedure is + "VHPIDIRECT ffi_set_outputs"; + + function clean_slv(v : std_logic_vector) return std_logic_vector is + variable r : std_logic_vector(v'range); + begin + for i in v'range loop + if v(i) = '1' then + r(i) := '1'; + else + r(i) := '0'; + end if; + end loop; + return r; + end function; + +begin + dut: entity work.circuit + port map ( + clk => clk, + key => key, + sw => sw, + led => led, + hex => hex + ); + + -- 500 Hz clock (2 ms period) + clk <= not clk after 1 ms; + + process + variable sw_i : integer; + variable key_i : integer; + begin + ffi_init; -- starts Rust listener thread + wait for 0 ns; + + + while true loop + wait until rising_edge(clk) or falling_edge(clk); + + sw_i := ffi_get_sw; + key_i := ffi_get_key; + + sw <= std_logic_vector(to_unsigned(sw_i, 32)); + key <= std_logic_vector(to_unsigned(key_i, 32)); + + ffi_set_outputs( + to_integer(unsigned(clean_slv(led))), + to_integer(unsigned(clean_slv(hex))) + ); + end loop; + end process; + +end architecture; \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..56c3e0f --- /dev/null +++ b/run.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +mkdir -p build + +# Build Rust shared library +pushd conn >/dev/null +cargo build --release +popd >/dev/null + +pushd build >/dev/null + +LIBSRC="../conn/target/release/libvhdl_ui.a" +LIBDIR="../conn/target/release" + + +ghdl -a --std=08 ../rtl/*.vhdl + +ghdl -e --std=08 \ + -Wl,"$LIBSRC" \ + -Wl,-Wl,-rpath -Wl,-Wl,$LIBDIR \ + tb + + +echo "=== Running sim ===" +echo "Connect and stream inputs using:" +echo " nc 127.0.0.1 5555" +echo "Then type lines like:" +echo " sw=1" +echo " key=15" +echo " key=7 (press KEY3 if bit3 becomes 0, etc; active-low)" +echo + +ghdl -r --std=08 tb \ No newline at end of file