This commit is contained in:
ParkerTenBroeck 2026-03-09 09:03:58 -04:00
parent b4841e6f40
commit e6c5947949
9 changed files with 156 additions and 63 deletions

View file

@ -1,7 +1,11 @@
[package] [package]
name = "vhdl_ui" name = "vhdl_ui"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[lib] [lib]
crate-type = ["staticlib"] crate-type = ["staticlib"]
[lints.clippy]
identity_op = "allow"

View file

@ -1,22 +1,40 @@
use std::{ use std::{
io::{BufRead, BufReader}, io::{BufRead, BufReader},
sync::atomic::{AtomicU32, Ordering}, sync::atomic::{AtomicU8, AtomicU32, AtomicU64, Ordering},
}; };
#[repr(u8)]
pub enum Sig {
SU = 0,
SX = 1,
S0 = 2,
S1 = 3,
SZ = 4,
SW = 5,
SL = 6,
SH = 7,
SD = 8
}
pub struct SimState{ pub struct SimState{
switch: AtomicU32, switch: AtomicU32,
button: AtomicU32, button: AtomicU32,
led: AtomicU32, led: AtomicU32,
hex: AtomicU32, segs: [AtomicU32; 4],
updated: AtomicU8,
} }
static STATE: SimState = SimState{ static STATE: SimState = SimState{
switch: AtomicU32::new(512), switch: AtomicU32::new(512),
button: AtomicU32::new(0), button: AtomicU32::new(0),
led: AtomicU32::new(0), led: AtomicU32::new(0),
hex: AtomicU32::new(0), segs: [const{AtomicU32::new(0)}; 4],
updated: AtomicU8::new(0)
}; };
/// default 1 ms
static SLEEP_NANOS: AtomicU64 = AtomicU64::new(1_000_000);
fn client() { fn client() {
let reader = BufReader::new(std::io::stdin()); let reader = BufReader::new(std::io::stdin());
for line in reader.lines().map_while(Result::ok) { for line in reader.lines().map_while(Result::ok) {
@ -25,37 +43,65 @@ fn client() {
if let Ok(n) = v.parse::<u32>() { if let Ok(n) = v.parse::<u32>() {
STATE.switch.store(n, Ordering::Relaxed); STATE.switch.store(n, Ordering::Relaxed);
} }
} else if let Some(v) = line.strip_prefix("key=") { } else if let Some(v) = line.strip_prefix("btn=") {
if let Ok(n) = v.parse::<u32>() { if let Ok(n) = v.parse::<u32>() {
STATE.button.store(n, Ordering::Relaxed); STATE.button.store(n, Ordering::Relaxed);
} }
} else if let value = STATE.updated.swap(0, Ordering::Relaxed) && value!=0 {
if (value >> 0) & 1 == 1{
eprintln!("led={}", STATE.led.load(Ordering::Relaxed));
}
for i in 0..4{
if (value >> (i+1)) & 1 == 1{
eprintln!("seg{i}={}", STATE.segs[i].load(Ordering::Relaxed));
}
}
} }
} }
} }
#[no_mangle] #[unsafe(no_mangle)]
pub extern "C" fn ffi_init() { pub extern "C" fn ffi_init() {
std::thread::Builder::new().name("client".into()).spawn(client).expect("Failed to spawn client thread"); std::thread::Builder::new().name("client".into()).spawn(client).expect("Failed to spawn client thread");
for arg in std::env::args(){
if let Some(arg) = arg.strip_prefix("--cycle_sleep").or(arg.strip_prefix("-c")){
if let Ok(nanos) = arg.trim().parse::<u64>(){
SLEEP_NANOS.store(nanos, Ordering::Relaxed);
}else{
eprintln!("cycle sleep(ns) failed to parse");
}
}
}
eprintln!("[ffi] initialzied"); eprintln!("[ffi] initialzied");
} }
#[no_mangle] #[unsafe(no_mangle)]
pub extern "C" fn ffi_get_sw() -> u32 { pub extern "C" fn ffi_get_sw() -> u32 {
STATE.switch.load(Ordering::Relaxed) STATE.switch.load(Ordering::Relaxed)
} }
#[no_mangle] #[unsafe(no_mangle)]
pub extern "C" fn ffi_get_key() -> u32 { pub extern "C" fn ffi_get_btn() -> u32 {
STATE.button.load(Ordering::Relaxed) STATE.button.load(Ordering::Relaxed)
} }
#[no_mangle] #[unsafe(no_mangle)]
pub extern "C" fn ffi_set_outputs(led: u32, hex: u32) { pub extern "C" fn ffi_set_outputs(led: u32, seg0: u32, seg1: u32, seg2: u32, seg3: u32) {
if STATE.led.swap(led, Ordering::Relaxed) != led{ let o_led = STATE.led.swap(led, Ordering::Relaxed) != led;
eprintln!("LED={}", STATE.led.load(Ordering::Relaxed))
} let o_seg0 = STATE.segs[0].swap(seg0, Ordering::Relaxed) != seg0;
if STATE.hex.swap(hex, Ordering::Relaxed) != hex{ let o_seg1 = STATE.segs[1].swap(seg1, Ordering::Relaxed) != seg1;
eprintln!("HEX={}", STATE.hex.load(Ordering::Relaxed)) let o_seg2 = STATE.segs[2].swap(seg2, Ordering::Relaxed) != seg2;
} let o_seg3 = STATE.segs[3].swap(seg3, Ordering::Relaxed) != seg3;
std::thread::sleep(std::time::Duration::from_millis(1));
let to_set = (o_led as u8) << 0
| (o_seg0 as u8) << 1
| (o_seg1 as u8) << 2
| (o_seg2 as u8) << 3
| (o_seg3 as u8) << 4;
STATE.updated.fetch_or(to_set, Ordering::Relaxed);
std::thread::sleep(std::time::Duration::from_nanos(SLEEP_NANOS.load(Ordering::Relaxed)));
} }

View file

@ -6,10 +6,13 @@ use ieee.numeric_std.all;
entity circuit is entity circuit is
port ( port (
clk: in std_logic; clk: in std_logic;
key: in std_logic_vector(31 downto 0); -- active low btn: in std_logic_vector(31 downto 0);
sw: in std_logic_vector(31 downto 0); -- active high sw: in std_logic_vector(31 downto 0);
led: out std_logic_vector(31 downto 0); -- active high led: out std_logic_vector(31 downto 0);
hex: out std_logic_vector(31 downto 0) seg0: out std_logic_vector(31 downto 0);
seg1: out std_logic_vector(31 downto 0);
seg2: out std_logic_vector(31 downto 0);
seg3: out std_logic_vector(31 downto 0)
); );
end circuit; end circuit;
@ -181,11 +184,11 @@ architecture description of circuit is
begin begin
hex(6 downto 0) <= not dec7seg(reg_out(7 downto 4)); seg0(6 downto 0) <= not dec7seg(reg_out(7 downto 4));
hex(14 downto 8) <= not dec7seg(reg_out(3 downto 0)); seg0(14 downto 8) <= not dec7seg(reg_out(3 downto 0));
hex(22 downto 16) <= not dec7seg(reg_pc(7 downto 4)); seg0(22 downto 16) <= not dec7seg(reg_pc(7 downto 4));
hex(30 downto 24) <= not dec7seg(reg_pc(3 downto 0)); seg0(30 downto 24) <= not dec7seg(reg_pc(3 downto 0));
led(7 downto 0) <= std_logic_vector(reg_a); led(7 downto 0) <= std_logic_vector(reg_a);
led(23 downto 16) <= std_logic_vector(reg_b); led(23 downto 16) <= std_logic_vector(reg_b);
@ -196,7 +199,7 @@ begin
led(11) <= flag_gt; led(11) <= flag_gt;
led(12) <= flag_carry; led(12) <= flag_carry;
clock <= clk when sw(9) = '1' else key(0); clock <= clk when sw(9) = '1' else btn(0);
ram_inst : entity work.inst_ram_8x256 ram_inst : entity work.inst_ram_8x256
port map( port map(

View file

@ -6,10 +6,13 @@ use ieee.numeric_std.all;
entity circuit is entity circuit is
port ( port (
clk: in std_logic; -- 500 Hz, period 2 ms clk: in std_logic; -- 500 Hz, period 2 ms
key: in std_logic_vector(31 downto 0); -- active high btn: in std_logic_vector(31 downto 0);
sw: in std_logic_vector(31 downto 0); -- active high sw: in std_logic_vector(31 downto 0);
led: out std_logic_vector(31 downto 0) := (others => '0'); -- active high led: out std_logic_vector(31 downto 0) := (others => '0');
hex: out std_logic_vector(31 downto 0) := (others => '0') -- active high seg0: out std_logic_vector(31 downto 0);
seg1: out std_logic_vector(31 downto 0);
seg2: out std_logic_vector(31 downto 0);
seg3: out std_logic_vector(31 downto 0)
); );
end circuit; end circuit;

View file

@ -46,10 +46,11 @@ struct ClientInput{
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
enum ServerMsg<'a> { enum ServerMsg<'a> {
Log { stream: &'a str, line: &'a str }, Log { stream: &'a str, line: &'a str },
/// bitfield of 32 leds
Led(u32), Led(u32),
/// bitfield of 4 hex displays, 7 segment with decimal Seg0(u32),
Hex(u32) Seg1(u32),
Seg2(u32),
Seg3(u32)
} }
async fn ws_handler(socket: WebSocket) { async fn ws_handler(socket: WebSocket) {
@ -93,14 +94,13 @@ async fn ws_handler(socket: WebSocket) {
Some(Ok(Message::Text(msg))) => { Some(Ok(Message::Text(msg))) => {
let input = serde_json::from_str::<'_, ClientInput>(&msg)?; let input = serde_json::from_str::<'_, ClientInput>(&msg)?;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
process.stdin.write_all(format!("key={}\n", input.buttons).as_bytes()).await?; process.stdin.write_all(format!("btn={}\n", input.buttons).as_bytes()).await?;
process.stdin.write_all(format!("sw={}\n", input.switch).as_bytes()).await?; process.stdin.write_all(format!("sw={}\n", input.switch).as_bytes()).await?;
}, },
Some(Ok(_)) => {}, Some(Ok(_)) => {},
Some(Err(err)) => Err(err)?, Some(Err(err)) => Err(err)?,
_ => break, _ => break,
} }
} }
out = sout.next_line() => { out = sout.next_line() => {
match out{ match out{
@ -121,10 +121,16 @@ async fn ws_handler(socket: WebSocket) {
err = serr.next_line() => { err = serr.next_line() => {
match err{ match err{
Ok(Some(line)) => { Ok(Some(line)) => {
let msg = if let Some(repr) = line.strip_prefix("LED="){ let msg = if let Some(repr) = line.strip_prefix("led="){
ServerMsg::Led(repr.parse().unwrap_or(0)) ServerMsg::Led(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("HEX="){ }else if let Some(repr) = line.strip_prefix("seg0="){
ServerMsg::Hex(repr.parse().unwrap_or(0)) ServerMsg::Seg0(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg1="){
ServerMsg::Seg1(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg2="){
ServerMsg::Seg2(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg3="){
ServerMsg::Seg3(repr.parse().unwrap_or(0))
}else{ }else{
ServerMsg::Log { ServerMsg::Log {
stream: "stderr", stream: "stderr",
@ -139,6 +145,10 @@ async fn ws_handler(socket: WebSocket) {
} }
} }
} }
_ = tokio::time::sleep(std::time::Duration::from_millis(30)) => {
use tokio::io::AsyncWriteExt;
process.stdin.write_all("\n".as_bytes()).await?;
}
} }
} }
Ok(()) Ok(())

View file

@ -11,7 +11,8 @@ pub struct Process{
pub async fn run(artifact_dir: &Path) -> Result<Process, Box<dyn std::error::Error + Send + Sync>>{ pub async fn run(artifact_dir: &Path) -> Result<Process, Box<dyn std::error::Error + Send + Sync>>{
let mut cmd = Command::new("ghdl"); let mut cmd = Command::new("ghdl");
cmd.args(["-r", "--std=08", "tb", "--stop-delta=2147483647", "--unbuffered"]); cmd.args(["-r", "--std=08", "tb", "--stop-delta=4294967296", "--unbuffered", "--"]);
cmd.args(std::env::args_os());
cmd.current_dir(artifact_dir); cmd.current_dir(artifact_dir);
cmd.kill_on_drop(true); cmd.kill_on_drop(true);

View file

@ -69,7 +69,7 @@ function clearLogs() {
function resetOutputsVisuals() { function resetOutputsVisuals() {
// reset LED/HEX visuals to 0 immediately // reset LED/HEX visuals to 0 immediately
setLeds(0); setLeds(0);
setHex(0); setSeg(0);
} }
function u32BitGet(x, i) { function u32BitGet(x, i) {
@ -196,7 +196,7 @@ function buildHex() {
} }
} }
function setHex(hexU32) { function setSeg(hexU32, seg) {
const digits = parseHexDigits(hexU32 >>> 0); const digits = parseHexDigits(hexU32 >>> 0);
for (let i = 0; i < 4; i++) { for (let i = 0; i < 4; i++) {
@ -344,9 +344,27 @@ function connect() {
return; return;
} }
if (parsed.hex !== undefined) { if (parsed.seg0 !== undefined) {
const v = (parsed.hex ?? parsed.value ?? parsed[0] ?? parsed["0"] ?? 0) >>> 0; const v = (parsed.seg0 ?? parsed.value ?? parsed[0] ?? parsed["0"] ?? 0) >>> 0;
setHex(v); setSeg(v, 0);
return;
}
if (parsed.seg1 !== undefined) {
const v = (parsed.seg1 ?? parsed.value ?? parsed[0] ?? parsed["0"] ?? 0) >>> 0;
setSeg(v, 1);
return;
}
if (parsed.seg2 !== undefined) {
const v = (parsed.seg3 ?? parsed.value ?? parsed[0] ?? parsed["0"] ?? 0) >>> 0;
setSeg(v, 2);
return;
}
if (parsed.seg3 !== undefined) {
const v = (parsed.seg3 ?? parsed.value ?? parsed[0] ?? parsed["0"] ?? 0) >>> 0;
setSeg(v, 3);
return; return;
} }

View file

@ -2,20 +2,22 @@ library ieee;
use ieee.std_logic_1164.all; use ieee.std_logic_1164.all;
use ieee.numeric_std.all; use ieee.numeric_std.all;
entity tb is entity tb is
end entity; end entity;
architecture sim of tb is architecture sim of tb is
signal clk : std_logic := '0'; signal clk : std_logic := '0';
signal key : std_logic_vector(31 downto 0) := (others => '0'); signal btn : std_logic_vector(31 downto 0) := (others => '0');
signal sw : std_logic_vector(31 downto 0) := (others => '0'); signal sw : std_logic_vector(31 downto 0) := (others => '0');
signal led : 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'); signal seg0 : std_logic_vector(31 downto 0) := (others => '0');
signal seg1 : std_logic_vector(31 downto 0) := (others => '0');
signal seg2 : std_logic_vector(31 downto 0) := (others => '0');
signal seg3 : 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 procedure ffi_init is
begin begin
end procedure; end procedure;
@ -29,13 +31,13 @@ architecture sim of tb is
attribute foreign of ffi_get_sw : function is attribute foreign of ffi_get_sw : function is
"VHPIDIRECT ffi_get_sw"; "VHPIDIRECT ffi_get_sw";
function ffi_get_key return integer is function ffi_get_btn return integer is
begin begin
return 0; return 0;
end function; end function;
attribute foreign of ffi_get_key : function is "VHPIDIRECT ffi_get_key"; attribute foreign of ffi_get_btn : function is "VHPIDIRECT ffi_get_btn";
procedure ffi_set_outputs(led_i : integer; hex_i : integer) is procedure ffi_set_outputs(led_i: integer; seg0_i: integer; seg1_i: integer; seg2_i: integer; seg3_i: integer) is
begin begin
end procedure; end procedure;
attribute foreign of ffi_set_outputs : procedure is attribute foreign of ffi_set_outputs : procedure is
@ -58,10 +60,13 @@ begin
dut: entity work.circuit dut: entity work.circuit
port map ( port map (
clk => clk, clk => clk,
key => key, btn => btn,
sw => sw, sw => sw,
led => led, led => led,
hex => hex seg0 => seg0,
seg1 => seg1,
seg2 => seg2,
seg3 => seg3
); );
-- 500 Hz clock (2 ms period) -- 500 Hz clock (2 ms period)
@ -69,7 +74,7 @@ begin
process process
variable sw_i : integer; variable sw_i : integer;
variable key_i : integer; variable btn_i : integer;
begin begin
ffi_init; ffi_init;
wait for 0 ns; wait for 0 ns;
@ -79,14 +84,17 @@ begin
wait for 0 ns; wait for 0 ns;
sw_i := ffi_get_sw; sw_i := ffi_get_sw;
key_i := ffi_get_key; btn_i := ffi_get_btn;
sw <= std_logic_vector(to_signed(sw_i, 32)); sw <= std_logic_vector(to_signed(sw_i, 32));
key <= std_logic_vector(to_signed(key_i, 32)); btn <= std_logic_vector(to_signed(btn_i, 32));
ffi_set_outputs( ffi_set_outputs(
to_integer(unsigned(clean_slv(led))), to_integer(unsigned(clean_slv(led))),
to_integer(unsigned(clean_slv(hex))) to_integer(unsigned(clean_slv(seg0))),
to_integer(unsigned(clean_slv(seg1))),
to_integer(unsigned(clean_slv(seg2))),
to_integer(unsigned(clean_slv(seg3)))
); );
end loop; end loop;
end process; end process;

View file

@ -7,5 +7,5 @@ popd >/dev/null
pushd relay >/dev/null pushd relay >/dev/null
cargo run --release cargo run --release -- "$@"
popd >/dev/null popd >/dev/null