diff --git a/conn/src/lib.rs b/conn/src/lib.rs index 4798ac8..dad0f83 100644 --- a/conn/src/lib.rs +++ b/conn/src/lib.rs @@ -11,7 +11,7 @@ pub struct SimState{ } static STATE: SimState = SimState{ - switch: AtomicU32::new(0), + switch: AtomicU32::new(512), button: AtomicU32::new(0), led: AtomicU32::new(0), hex: AtomicU32::new(0), @@ -52,9 +52,10 @@ pub extern "C" fn ffi_get_key() -> u32 { #[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)) + eprintln!("LED={}", STATE.led.load(Ordering::Relaxed)) } if STATE.hex.swap(hex, Ordering::Relaxed) != hex{ - println!("HEX={:#x?}", STATE.hex.load(Ordering::Relaxed)) + eprintln!("HEX={}", STATE.hex.load(Ordering::Relaxed)) } + std::thread::sleep(std::time::Duration::from_millis(1)); } \ No newline at end of file diff --git a/relay/.gitignore b/relay/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/relay/.gitignore @@ -0,0 +1 @@ +/target diff --git a/relay/Cargo.lock b/relay/Cargo.lock new file mode 100644 index 0000000..94b22e5 --- /dev/null +++ b/relay/Cargo.lock @@ -0,0 +1,928 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "base64", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "relay" +version = "0.1.0" +dependencies = [ + "axum", + "futures-util", + "serde", + "serde_json", + "tokio", + "tower-http", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "zerocopy" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/relay/Cargo.toml b/relay/Cargo.toml new file mode 100644 index 0000000..0e17f78 --- /dev/null +++ b/relay/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "relay" +version = "0.1.0" +edition = "2024" + +[dependencies] +axum = { version = "0.8", features = ["ws"] } +tokio = { version = "1", features = ["rt-multi-thread", "macros", "process", "io-util", "sync"] } +tower-http = { version = "0.6", features = ["fs", "trace"] } +futures-util = "0.3" +serde = { version = "1", features = ["derive"] } +serde_json = "1" \ No newline at end of file diff --git a/relay/src/build.rs b/relay/src/build.rs new file mode 100644 index 0000000..e5298c6 --- /dev/null +++ b/relay/src/build.rs @@ -0,0 +1,92 @@ +use std::{collections::HashMap, ops::Deref, path::{Path, PathBuf}}; +use tokio::{ + process::{Child, Command}, +}; + +async fn ensure_ok(child: Child) -> Result<(), Box>{ + let result = child.wait_with_output().await?; + if !result.status.success(){ + return Err(format!("{}\n{}", String::from_utf8_lossy(&result.stdout), String::from_utf8_lossy(&result.stderr)))? + } + Ok(()) +} + +pub struct TempDir(PathBuf); +impl Drop for TempDir{ + fn drop(&mut self) { + _ = std::fs::remove_dir_all(&self.0) + } +} + +impl Deref for TempDir{ + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef for TempDir{ + fn as_ref(&self) -> &PathBuf { + &self.0 + } +} +impl AsRef for TempDir{ + fn as_ref(&self) -> &Path { + &self.0 + } +} + + +pub async fn build(files: HashMap) -> Result>{ + use std::hash::*; + let mut hasher = std::hash::DefaultHasher::default(); + for (key, value) in &files{ + key.hash(&mut hasher); + value.hash(&mut hasher); + } + let hash = hasher.finish(); + + let mut work_dir = std::env::temp_dir(); + work_dir.push(format!("ghdl-relay-{hash:x?}")); + _ = std::fs::create_dir(&work_dir); + let work_dir = TempDir(work_dir); + + + for (name, contents) in &files{ + let mut path = work_dir.clone(); + path.push(name); + std::fs::write(path, contents)?; + } + + + let mut cmd = Command::new("ghdl"); + cmd.kill_on_drop(true); + cmd.args(["-a", "-g", "--std=08"]); + for name in files.keys(){ + let mut path = work_dir.clone(); + path.push(name); + cmd.arg(path); + } + cmd.arg(std::fs::canonicalize("../rtl/tb.vhdl")?); + + cmd.stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + cmd.current_dir(&work_dir); + ensure_ok(cmd.spawn()?).await?; + + let mut cmd = Command::new("ghdl"); + cmd.kill_on_drop(true); + cmd.args(["-e", "--std=08"]); + cmd.arg(format!("-Wl,{}", std::fs::canonicalize("../conn/target/release/libvhdl_ui.a")?.display())); + cmd.arg("tb"); + cmd.current_dir(&work_dir); + cmd.stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + ensure_ok(cmd.spawn()?).await?; + + Ok(work_dir) +} \ No newline at end of file diff --git a/relay/src/main.rs b/relay/src/main.rs new file mode 100644 index 0000000..8824596 --- /dev/null +++ b/relay/src/main.rs @@ -0,0 +1,153 @@ +use axum::{ + Router, + extract::ws::{Message, WebSocket, WebSocketUpgrade}, + routing::get, +}; +use futures_util::{SinkExt, StreamExt}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, net::SocketAddr}; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, +}; +use tower_http::services::ServeDir; + +pub mod build; +pub mod run; + + +#[tokio::main] +async fn main() { + let app = Router::new() + .route( + "/ws", + get(|ws: WebSocketUpgrade| async move { ws.on_upgrade(ws_handler) }), + ) + .fallback_service(ServeDir::new("ui")); + + let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); + println!("Open UI: http://{}/", addr); + + axum::serve(tokio::net::TcpListener::bind(addr).await.unwrap(), app) + .await + .unwrap(); +} + + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +struct ClientInput{ + /// bitfield of 32 switches + switch: u32, + /// bitfield of 32 buttons + buttons: u32 +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +enum ServerMsg<'a> { + Log { stream: &'a str, line: &'a str }, + /// bitfield of 32 leds + Led(u32), + /// bitfield of 4 hex displays, 7 segment with decimal + Hex(u32) +} + +async fn ws_handler(socket: WebSocket) { + let (mut sender, mut receiver) = socket.split(); + + let files = if let Some(Ok(Message::Text(msg))) = receiver.next().await + && let Ok(files) = serde_json::from_str::<'_, HashMap>(&msg) + { + files + } else { + return; + }; + + + let artifact_dir = match build::build(files).await{ + Ok(dir) => dir, + Err(err) => { + _ = sender.send(Message::Text(format!("Failed to build: {err}").into())).await; + return; + }, + }; + + let (_process, mut sin, sout, serr) = match run::run(&artifact_dir).await{ + Ok(process) => process, + Err(err) => { + _ = sender.send(Message::Text(format!("Failed to run: {err}").into())).await; + return; + }, + }; + let mut sout = BufReader::new(sout).lines(); + let mut serr = BufReader::new(serr).lines(); + + let artifact_prefix = artifact_dir.to_str().unwrap_or("\0\0NOPE"); + + let result: Result<(), Box> = async { + loop{ + tokio::select! { + receive = receiver.next() => { + match receive{ + Some(Ok(Message::Close(_))) => break, + Some(Ok(Message::Text(msg))) => { + let input = serde_json::from_str::<'_, ClientInput>(&msg)?; + use tokio::io::AsyncWriteExt; + sin.write_all(format!("key={}\n", input.buttons).as_bytes()).await?; + sin.write_all(format!("sw={}\n", input.switch).as_bytes()).await?; + }, + Some(Ok(_)) => {}, + Some(Err(err)) => Err(err)?, + _ => break, + } + + } + out = sout.next_line() => { + match out{ + Ok(Some(line)) => { + + let msg = ServerMsg::Log { + stream: "stdout", + line: line.strip_prefix(artifact_prefix).unwrap_or(&line), + }; + sender.send(Message::Text(serde_json::to_string(&msg)?.into())).await?; + }, + Ok(None) => break, + Err(err) => { + Err(format!("Failed to read proccess sout: {err}"))?; + } + } + } + err = serr.next_line() => { + match err{ + Ok(Some(line)) => { + let msg = if let Some(repr) = line.strip_prefix("LED="){ + ServerMsg::Led(repr.parse().unwrap_or(0)) + }else if let Some(repr) = line.strip_prefix("HEX="){ + ServerMsg::Hex(repr.parse().unwrap_or(0)) + }else{ + ServerMsg::Log { + stream: "stderr", + line: line.strip_prefix(artifact_prefix).unwrap_or(&line), + } + }; + sender.send(Message::Text(serde_json::to_string(&msg)?.into())).await?; + }, + Ok(None) => break, + Err(err) => { + Err(format!("Failed to read proccess serr: {err}"))? + } + } + } + } + } + Ok(()) + }.await; + + match result{ + Ok(_) => {}, + Err(err) => { + _ = sender.send(Message::Text(format!("{err}").into())).await; + }, + } +} \ No newline at end of file diff --git a/relay/src/run.rs b/relay/src/run.rs new file mode 100644 index 0000000..a2eed37 --- /dev/null +++ b/relay/src/run.rs @@ -0,0 +1,31 @@ +use std::{path::Path}; + +use tokio::process::{Child, ChildStdin, ChildStdout, ChildStderr, Command}; + +pub struct Process{ + child: Child, +} + +impl Drop for Process{ + fn drop(&mut self) { + _ = self.child.start_kill() + } +} + +pub async fn run(artifact_dir: &Path) -> Result<(Process, ChildStdin, ChildStdout, ChildStderr), Box>{ + let mut cmd = Command::new("ghdl"); + cmd.args(["-r", "--std=08", "tb", "--stop-delta=2147483647", "--unbuffered"]); + cmd.current_dir(artifact_dir); + + cmd.stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + let mut child = cmd.spawn()?; + + let stdin = child.stdin.take().ok_or("no stdin")?; + let stdout = child.stdout.take().ok_or("no stdout")?; + let stderr = child.stderr.take().ok_or("no stderr")?; + + Ok((Process { child }, stdin, stdout, stderr)) +} \ No newline at end of file diff --git a/relay/ui/app.js b/relay/ui/app.js new file mode 100644 index 0000000..dd0907f --- /dev/null +++ b/relay/ui/app.js @@ -0,0 +1,468 @@ +// ----- LocalStorage keys ----- +const LS_KEY_VHDL = "circuit_ui:circuit.vhdl"; + +// ----- State ----- +let ws = null; +let isConnected = false; + +let switches = 0 >>> 0; // u32 +let buttons = 0 >>> 0; // u32 + +let autoscroll = true; + +// ----- DOM ----- +const wsPill = document.getElementById("wsPill"); +const wsUrlText = document.getElementById("wsUrlText"); + +const connectToggleBtn = document.getElementById("connectToggleBtn"); +const sendInputsBtn = document.getElementById("sendInputsBtn"); + +const vhdlEditor = document.getElementById("vhdlEditor"); +const lineGutter = document.getElementById("lineGutter"); +const loadExampleBtn = document.getElementById("loadExampleBtn"); + +const ledRow = document.getElementById("ledRow"); +const ledLabels = document.getElementById("ledLabels"); +const hexRow = document.getElementById("hexRow"); + +const switchGrid = document.getElementById("switchGrid"); +const buttonGrid = document.getElementById("buttonGrid"); + +const logView = document.getElementById("logView"); +const clearLogBtn = document.getElementById("clearLogBtn"); +const autoscrollBtn = document.getElementById("autoscrollBtn"); + +const allSwOffBtn = document.getElementById("allSwOffBtn"); +const allSwOnBtn = document.getElementById("allSwOnBtn"); + +// ----- Helpers ----- +function wsUrl() { + const proto = (location.protocol === "https:") ? "wss" : "ws"; + return `${proto}://${location.host}/ws`; +} + +function setStatus(connected) { + isConnected = connected; + wsPill.textContent = connected ? "CONNECTED" : "DISCONNECTED"; + wsPill.style.borderColor = connected ? "rgba(34,197,94,.6)" : "rgba(239,68,68,.6)"; + wsPill.style.background = connected ? "rgba(34,197,94,.14)" : "rgba(239,68,68,.10)"; + + sendInputsBtn.disabled = !connected; + + // single button label + connectToggleBtn.textContent = connected ? "Disconnect" : "Connect"; + connectToggleBtn.classList.toggle("secondary", connected); +} + +function appendLog(stream, line) { + const prefix = stream === "stderr" ? "[stderr]" : "[stdout]"; + logView.textContent += `${prefix} ${line}\n`; + if (autoscroll) { + logView.scrollTop = logView.scrollHeight; + } +} + +function clearLogs() { + logView.textContent = ""; +} + +function resetOutputsVisuals() { + // reset LED/HEX visuals to 0 immediately + setLeds(0); + setHex(0); +} + +function u32BitGet(x, i) { + return ((x >>> i) & 1) === 1; +} + +function u32BitSet(x, i, on) { + if (on) return (x | (1 << i)) >>> 0; + return (x & ~(1 << i)) >>> 0; +} + +function sendClientInput() { + if (!ws || ws.readyState !== WebSocket.OPEN) return; + + const msg = { + type: "client_input", + switch: switches >>> 0, + buttons: buttons >>> 0, + }; + ws.send(JSON.stringify(msg)); +} + +function parseHexDigits(hexU32) { + const d0 = (hexU32 >>> 0) & 0xFF; + const d1 = (hexU32 >>> 8) & 0xFF; + const d2 = (hexU32 >>> 16) & 0xFF; + const d3 = (hexU32 >>> 24) & 0xFF; + return [d0, d1, d2, d3]; +} + +// ----- Line numbers ----- +function updateLineNumbers() { + const text = vhdlEditor.value || ""; + // count lines: number of '\n' + 1 (even empty text -> 1 line) + const lines = text.length ? (text.split("\n").length) : 1; + + // Build as one string for performance + let out = ""; + for (let i = 1; i <= lines; i++) out += i + "\n"; + lineGutter.textContent = out; + + syncGutterScroll(); +} + +function syncGutterScroll() { + // keep gutter aligned to editor scroll + lineGutter.scrollTop = vhdlEditor.scrollTop; +} + +// ----- LocalStorage save (debounced) ----- +let saveTimer = null; +function saveEditorToLocalStorageDebounced() { + if (saveTimer) clearTimeout(saveTimer); + saveTimer = setTimeout(() => { + try { + localStorage.setItem(LS_KEY_VHDL, vhdlEditor.value ?? ""); + } catch (e) { + // ignore storage failures (private mode, quota) + } + }, 250); +} + +function loadEditorFromLocalStorage() { + try { + const saved = localStorage.getItem(LS_KEY_VHDL); + if (saved !== null) return saved; + } catch {} + return null; +} + +// ----- UI Builders ----- +function buildLeds() { + ledRow.innerHTML = ""; + ledLabels.innerHTML = ""; + + for (let i = 0; i < 32; i++) { + const el = document.createElement("div"); + el.className = "led"; + el.title = `LED[${i}]`; + el.dataset.bit = String(i); + ledRow.appendChild(el); + + const lab = document.createElement("span"); + lab.textContent = String(i); + ledLabels.appendChild(lab); + } +} + +function setLeds(bitsU32) { + for (const el of ledRow.children) { + const i = Number(el.dataset.bit); + el.classList.toggle("on", u32BitGet(bitsU32 >>> 0, i)); + } +} + +function makeSevenSeg(digitIndex) { + const wrap = document.createElement("div"); + + const disp = document.createElement("div"); + disp.className = "sevenSeg"; + disp.dataset.digit = String(digitIndex); + + const segs = ["a","b","c","d","e","f","g","dp"]; + for (const s of segs) { + const seg = document.createElement("div"); + seg.className = `seg ${s}`; + seg.dataset.seg = s; + disp.appendChild(seg); + } + + const label = document.createElement("div"); + label.className = "digitLabel"; + label.textContent = `HEX[${digitIndex}]`; + + wrap.appendChild(disp); + wrap.appendChild(label); + return wrap; +} + +function buildHex() { + hexRow.innerHTML = ""; + for (let i = 0; i < 4; i++) { + hexRow.appendChild(makeSevenSeg(i)); + } +} + +function setHex(hexU32) { + const digits = parseHexDigits(hexU32 >>> 0); + + for (let i = 0; i < 4; i++) { + const byte = digits[i] & 0xFF; + const disp = hexRow.querySelector(`.sevenSeg[data-digit="${i}"]`); + if (!disp) continue; + + const map = { a:0, b:1, c:2, d:3, e:4, f:5, g:6, dp:7 }; + + for (const segEl of disp.querySelectorAll(".seg")) { + const name = segEl.dataset.seg; + const bit = map[name]; + const on = ((byte >>> bit) & 1) === 1; + segEl.classList.toggle("on", on); + } + } +} + +function buildSwitches() { + switchGrid.innerHTML = ""; + for (let i = 0; i < 32; i++) { + const cell = document.createElement("div"); + cell.className = "ioCell"; + + const label = document.createElement("label"); + label.textContent = `SW[${i}]`; + + const toggle = document.createElement("div"); + toggle.className = "toggle"; + toggle.dataset.bit = String(i); + toggle.title = `Toggle switch ${i}`; + + toggle.addEventListener("click", () => { + const bit = Number(toggle.dataset.bit); + const now = !u32BitGet(switches, bit); + switches = u32BitSet(switches, bit, now); + toggle.classList.toggle("on", now); + sendClientInput(); + }); + + cell.appendChild(toggle); + cell.appendChild(label); + switchGrid.appendChild(cell); + } +} + +function syncSwitchesUI() { + for (const toggle of switchGrid.querySelectorAll(".toggle")) { + const i = Number(toggle.dataset.bit); + toggle.classList.toggle("on", u32BitGet(switches, i)); + } +} + +function buildButtons() { + buttonGrid.innerHTML = ""; + for (let i = 0; i < 32; i++) { + const cell = document.createElement("div"); + cell.className = "ioCell"; + + const label = document.createElement("label"); + label.textContent = `KEY[${i}]`; + + const btn = document.createElement("button"); + btn.className = "momentary"; + btn.type = "button"; + btn.textContent = "press"; + btn.dataset.bit = String(i); + btn.title = `Momentary button ${i}`; + + const press = () => { + const bit = Number(btn.dataset.bit); + buttons = u32BitSet(buttons, bit, true); + btn.classList.add("down"); + sendClientInput(); + }; + + const release = () => { + const bit = Number(btn.dataset.bit); + buttons = u32BitSet(buttons, bit, false); + btn.classList.remove("down"); + sendClientInput(); + }; + + // Mouse + btn.addEventListener("mousedown", (e) => { e.preventDefault(); press(); }); + btn.addEventListener("mouseup", (e) => { e.preventDefault(); release(); }); + btn.addEventListener("mouseleave",(e) => { e.preventDefault(); release(); }); + + // Touch + btn.addEventListener("touchstart",(e) => { e.preventDefault(); press(); }, {passive:false}); + btn.addEventListener("touchend", (e) => { e.preventDefault(); release(); }, {passive:false}); + btn.addEventListener("touchcancel",(e)=> { e.preventDefault(); release(); }, {passive:false}); + + cell.appendChild(btn); + cell.appendChild(label); + buttonGrid.appendChild(cell); + } +} + +// ----- WebSocket ----- +function connect() { + if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return; + + // Requirement (3): reset logs + outputs when connect is pressed + clearLogs(); + resetOutputsVisuals(); + + const url = wsUrl(); + wsUrlText.textContent = url; + + ws = new WebSocket(url); + + ws.addEventListener("open", () => { + setStatus(true); + appendLog("stdout", "WebSocket connected."); + + // First message MUST be the file map + const files = { + "circuit.vhdl": vhdlEditor.value ?? "" + }; + ws.send(JSON.stringify(files)); + + // Push initial input state + sendClientInput(); + }); + + ws.addEventListener("message", (ev) => { + let parsed = null; + try { + parsed = JSON.parse(ev.data); + } catch { + appendLog("stderr", String(ev.data)); + return; + } + + + if (parsed.log !== undefined) { + appendLog(parsed.log.stream ?? "stdout", parsed.log.line ?? ""); + return; + } + + if (parsed.led !== undefined) { + const v = (parsed.led ?? parsed.value ?? parsed[0] ?? parsed["0"] ?? 0) >>> 0; + setLeds(v); + return; + } + + if (parsed.hex !== undefined) { + const v = (parsed.hex ?? parsed.value ?? parsed[0] ?? parsed["0"] ?? 0) >>> 0; + setHex(v); + return; + } + + appendLog("stderr", `Unknown msg: ${ev.data}`); + }); + + ws.addEventListener("close", () => { + appendLog("stderr", "WebSocket closed."); + setStatus(false); + }); + + ws.addEventListener("error", () => { + appendLog("stderr", "WebSocket error."); + setStatus(false); + }); +} + +function disconnect() { + if (ws) ws.close(); +} + +function toggleConnect() { + if (isConnected) disconnect(); + else connect(); +} + +// ----- Wire up controls ----- +connectToggleBtn.addEventListener("click", toggleConnect); + +sendInputsBtn.addEventListener("click", () => { + appendLog("stdout", `Manual send: sw=${switches >>> 0} key=${buttons >>> 0}`); + sendClientInput(); +}); + +clearLogBtn.addEventListener("click", () => { clearLogs(); }); + +autoscrollBtn.addEventListener("click", () => { + autoscroll = !autoscroll; + autoscrollBtn.textContent = `Autoscroll: ${autoscroll ? "on" : "off"}`; +}); + +allSwOffBtn.addEventListener("click", () => { + switches = 0 >>> 0; + syncSwitchesUI(); + sendClientInput(); +}); + +allSwOnBtn.addEventListener("click", () => { + switches = 0xFFFF_FFFF >>> 0; + syncSwitchesUI(); + sendClientInput(); +}); + +loadExampleBtn.addEventListener("click", () => { + vhdlEditor.value = +`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(31 downto 0); -- active low + sw: in std_logic_vector(31 downto 0); -- active high + led: out std_logic_vector(31 downto 0) := (others => '0'); -- active high + hex: out std_logic_vector(31 downto 0) := (others => '0') -- active low + ); +end circuit; + + +architecture description of circuit is + signal counter: unsigned(31 downto 0) := x"00000000"; +begin + led <= std_logic_vector(counter); + process(clk) + begin + counter <= counter+1; + end process; +end description;`; + + saveEditorToLocalStorageDebounced(); + updateLineNumbers(); +}); + +// Editor events: save + line numbers + gutter sync +vhdlEditor.addEventListener("input", () => { + saveEditorToLocalStorageDebounced(); + updateLineNumbers(); +}); + +vhdlEditor.addEventListener("scroll", () => { + syncGutterScroll(); +}); + +// ----- Init ----- +(function init() { + wsUrlText.textContent = wsUrl(); + + buildLeds(); + buildHex(); + buildSwitches(); + buildButtons(); + + resetOutputsVisuals(); + setStatus(false); + + // Load from localStorage if present + const saved = loadEditorFromLocalStorage(); + if (saved !== null) { + vhdlEditor.value = saved; + } else { + vhdlEditor.value = +`-- circuit.vhdl +-- Paste your circuit here. The UI will send it on Connect.`; + } + + updateLineNumbers(); +})(); \ No newline at end of file diff --git a/relay/ui/index.html b/relay/ui/index.html new file mode 100644 index 0000000..4556b5c --- /dev/null +++ b/relay/ui/index.html @@ -0,0 +1,119 @@ + + + + + + VHDL Circuit UI + + + +
+
+
+
+
Circuit Web UI
+
LEDs • HEX • Switches • Buttons • circuit.vhdl
+
+
+ +
+
+ DISCONNECTED + +
+ + + + + +
+
+ +
+ +
+
+
circuit.vhdl
+
+ +
+
+ + +
+ + +
+ +
+ Saved locally as you type. On Connect, the UI sends {"circuit.vhdl": "..."} as the first WebSocket message. +
+
+ + +
+
+
Outputs
+
+ +
+
+
LEDs (32)
+
+
+
+ +
+
HEX (4 digits, 7-seg + dp)
+
+
+ Assumes each digit is 8 bits: bit0=a, 1=b, 2=c, 3=d, 4=e, 5=f, 6=g, 7=dp. +
+
+
+
+ + +
+
+
Inputs
+
+ + +
+
+ +
+
+
Switches (32, latched)
+
+
+ +
+
Buttons (32, momentary)
+
+
+
+ +
+ Switches toggle a bit. Buttons set the bit while pressed (mouse/touch) and clear on release. +
+
+ + +
+
+
Logs
+
+ + +
+
+ +

+    
+
+ + + + \ No newline at end of file diff --git a/relay/ui/styles.css b/relay/ui/styles.css new file mode 100644 index 0000000..830bf2c --- /dev/null +++ b/relay/ui/styles.css @@ -0,0 +1,396 @@ +:root{ + --bg: #0b0f17; + --card: #121a2a; + --card2:#0f1726; + --text: #e6eefc; + --muted:#9fb0d0; + --accent:#58a6ff; + --ok:#22c55e; + --bad:#ef4444; + --warn:#f59e0b; + --line:#1f2a44; + --shadow: 0 12px 30px rgba(0,0,0,.35); + --radius: 14px; + --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji"; +} + +*{ box-sizing:border-box; } +html,body{ height:100%; } +body{ + margin:0; + background: radial-gradient(1000px 600px at 20% -10%, rgba(88,166,255,.20), transparent 60%), + radial-gradient(900px 500px at 90% 0%, rgba(34,197,94,.12), transparent 60%), + var(--bg); + color:var(--text); + font-family: var(--sans); +} + +.topbar{ + position: sticky; + top:0; + z-index: 10; + display:flex; + justify-content:space-between; + align-items:center; + padding:14px 16px; + border-bottom:1px solid rgba(255,255,255,.06); + background: rgba(11,15,23,.8); + backdrop-filter: blur(10px); +} + +.brand{ + display:flex; + gap:12px; + align-items:center; +} +.brand .dot{ + width:12px;height:12px;border-radius:50%; + background: var(--accent); + box-shadow: 0 0 18px rgba(88,166,255,.7); +} +.title{ font-weight:700; letter-spacing:.2px; } +.subtitle{ font-size:12px; color:var(--muted); margin-top:2px; } + +.controls{ + display:flex; + gap:10px; + align-items:center; +} +.status{ + display:flex; + gap:8px; + align-items:center; + margin-right:6px; +} +.pill{ + font-size:12px; + padding:6px 10px; + border-radius:999px; + border:1px solid rgba(255,255,255,.12); + background: rgba(255,255,255,.04); +} +.muted{ color:var(--muted); font-size:12px; } + +button{ + border:1px solid rgba(255,255,255,.14); + background: rgba(88,166,255,.14); + color:var(--text); + padding:8px 12px; + border-radius:10px; + cursor:pointer; + transition: transform .05s ease, background .2s ease; +} +button:hover{ background: rgba(88,166,255,.20); } +button:active{ transform: translateY(1px); } +button.secondary{ + background: rgba(255,255,255,.06); +} +button.secondary:hover{ background: rgba(255,255,255,.10); } +button:disabled{ + opacity:.55; + cursor:not-allowed; +} + +.grid{ + display:grid; + grid-template-columns: 1.2fr 1fr; + grid-template-rows: 420px 1fr; + gap:14px; + padding:14px; +} + +.card{ + background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.03)); + border:1px solid rgba(255,255,255,.08); + border-radius: var(--radius); + box-shadow: var(--shadow); + overflow:hidden; + min-height: 0; +} + +.cardHeader{ + display:flex; + justify-content:space-between; + align-items:center; + padding:12px 12px; + border-bottom:1px solid rgba(255,255,255,.06); + background: rgba(0,0,0,.15); +} +.cardTitle{ font-weight:700; } +.cardActions{ display:flex; gap:8px; } + +.editor{ + display:flex; + flex-direction:column; +} +textarea{ + width:100%; + height:100%; + padding:12px; + background: rgba(0,0,0,.22); + color:var(--text); + border:0; + outline:none; + resize:none; + font-family: var(--mono); + font-size: 13px; + line-height: 1.35; +} +.hint{ + padding:10px 12px; + border-top:1px solid rgba(255,255,255,.06); + color:var(--muted); + font-size:12px; + background: rgba(0,0,0,.12); +} +.hint.small{ font-size:11px; } + +.io .outputs{ + padding:12px; + display:flex; + flex-direction:column; + gap:14px; +} +.blockTitle{ + font-size:12px; + color:var(--muted); + margin-bottom:8px; +} + +.ledRow{ + display:grid; + grid-template-columns: repeat(16, 1fr); + gap:6px; +} +.led{ + width: 100%; + aspect-ratio: 1/1; + border-radius: 999px; + border:1px solid rgba(255,255,255,.14); + background: rgba(255,255,255,.08); + box-shadow: inset 0 0 0 2px rgba(0,0,0,.18); +} +.led.on{ + background: rgba(34,197,94,.70); + border-color: rgba(34,197,94,.9); + box-shadow: 0 0 14px rgba(34,197,94,.45); +} +.bitLabelRow{ + display:grid; + grid-template-columns: repeat(16, 1fr); + gap:6px; + margin-top:6px; + color: var(--muted); + font-size: 10px; + font-family: var(--mono); + opacity: .85; +} +.bitLabelRow span{ + text-align:center; +} + +.hexRow{ + display:flex; + gap:14px; + flex-wrap:wrap; + align-items:center; +} + +/* 7-seg display */ +.sevenSeg{ + width: 90px; + height: 140px; + position: relative; + background: rgba(0,0,0,.18); + border:1px solid rgba(255,255,255,.10); + border-radius: 14px; + padding:10px; +} +.seg{ + position:absolute; + background: rgba(255,255,255,.10); + border-radius: 8px; + filter: drop-shadow(0 0 0 rgba(0,0,0,0)); +} +.seg.on{ + background: rgba(88,166,255,.75); + filter: drop-shadow(0 0 8px rgba(88,166,255,.35)); +} + +/* segment positions */ +.seg.a{ top:10px; left:18px; width:54px; height:12px; } +.seg.d{ bottom:10px; left:18px; width:54px; height:12px; } +.seg.g{ top:64px; left:18px; width:54px; height:12px; } + +.seg.f{ top:18px; left:10px; width:12px; height:54px; } +.seg.b{ top:18px; right:10px; width:12px; height:54px; } + +.seg.e{ bottom:18px; left:10px; width:12px; height:54px; } +.seg.c{ bottom:18px; right:10px; width:12px; height:54px; } + +.seg.dp{ + width:12px; height:12px; + bottom:10px; right:10px; + border-radius: 999px; +} + +.digitLabel{ + margin-top:8px; + font-family: var(--mono); + font-size: 11px; + color: var(--muted); + text-align:center; +} + +.inputs{ + grid-column: 1 / 2; + display:flex; + flex-direction:column; + min-height: 0; +} +.twoCols{ + padding:12px; + display:grid; + grid-template-columns: 1fr 1fr; + gap:12px; + min-height: 0; +} +.ioGrid{ + display:grid; + grid-template-columns: repeat(8, 1fr); + gap:8px; +} +.ioCell{ + display:flex; + flex-direction:column; + gap:6px; + align-items:center; + padding:8px 6px; + background: rgba(0,0,0,.16); + border:1px solid rgba(255,255,255,.08); + border-radius:12px; +} +.ioCell label{ + font-family: var(--mono); + font-size: 11px; + color: var(--muted); +} + +.toggle{ + width: 38px; + height: 22px; + border-radius: 999px; + position: relative; + background: rgba(255,255,255,.10); + border:1px solid rgba(255,255,255,.14); + cursor:pointer; +} +.toggle::after{ + content:""; + width: 16px; + height: 16px; + position:absolute; + top:2px; + left:2px; + border-radius: 999px; + background: rgba(255,255,255,.45); + transition: left .12s ease, background .12s ease; +} +.toggle.on{ + background: rgba(34,197,94,.24); + border-color: rgba(34,197,94,.55); +} +.toggle.on::after{ + left: 20px; + background: rgba(34,197,94,.75); +} + +.momentary{ + width: 40px; + height: 28px; + border-radius: 10px; + border:1px solid rgba(255,255,255,.14); + background: rgba(255,255,255,.08); + cursor:pointer; +} +.momentary.down{ + background: rgba(245,158,11,.25); + border-color: rgba(245,158,11,.55); + box-shadow: 0 0 12px rgba(245,158,11,.18); +} + +.logs{ + grid-column: 2 / 3; + display:flex; + flex-direction:column; + min-height: 0; +} +.logView{ + margin:0; + padding:12px; + height:100%; + overflow:auto; + background: rgba(0,0,0,.22); + font-family: var(--mono); + font-size: 12px; + line-height: 1.35; + white-space: pre-wrap; + word-break: break-word; +} + +/* Responsive */ +@media (max-width: 1100px){ + .grid{ + grid-template-columns: 1fr; + grid-template-rows: auto; + } + .inputs, .logs{ + grid-column: auto; + } + .twoCols{ + grid-template-columns: 1fr; + } +} + +/* Editor w/ line numbers */ +.editorWrap{ + display:flex; + width:100%; + height:100%; + min-height: 0; + background: rgba(0,0,0,.22); +} + +.lineGutter{ + margin:0; + padding:12px 10px 12px 12px; + width: 54px; /* gutter width */ + overflow:hidden; + user-select:none; + text-align:right; + color: rgba(159,176,208,.85); + font-family: var(--mono); + font-size: 13px; + line-height: 1.35; + border-right: 1px solid rgba(255,255,255,.06); + background: rgba(0,0,0,.10); +} + +#lineGutter .ln { display:block; } + +textarea#vhdlEditor{ + overscroll-behavior-y: none; + flex:1; + height:100%; + padding:12px; + background: transparent; /* uses editorWrap background */ + color:var(--text); + border:0; + outline:none; + resize:none; + font-family: var(--mono); + font-size: 13px; + line-height: 1.35; + overflow:auto; +} diff --git a/rtl/circuit copy._vhdl b/rtl/circuit copy._vhdl new file mode 100644 index 0000000..20502cc --- /dev/null +++ b/rtl/circuit copy._vhdl @@ -0,0 +1,352 @@ +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) := "000000000"; +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"FE", -- out + 5 => x"AE", -- out => a + 6 => x"10", -- a+b => out + 7 => x"FE", -- out + 8 => x"BE", -- out => b + 9 => x"D0", -- jump to 3 + 10 => 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) := "00000000"; + + 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) := "00000000"; + signal alu_func : unsigned(3 downto 0) := "0000"; + signal alu_carry, alu_zero, alu_gt, alu_lt, alu_eq : std_logic := '0'; + signal alu_tmp: unsigned(8 downto 0) := "000000000"; + + 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 + ); + + with alu_func select + alu_tmp <= ("0"&alu_a) + ("0"&alu_b) when x"0", + ("0"&alu_a) + ("0"&alu_b) + (x"00"&flag_carry) when x"1", + ("0"&alu_a) - ("0"&alu_b) when x"2", + ("0"&alu_a) - ("0"&alu_b) - (x"00"&flag_carry) when x"3", + ("0"&alu_a) and ("0"&alu_b) when x"4", + ("0"&alu_a) or ("0"&alu_b) when x"5", + ("0"&alu_a) xor ("0"&alu_b) when x"6", + "0"&x"00" when others; + + alu_zero <= '1' when alu_tmp = 0 else '0'; + alu_eq <= '1' when alu_a = alu_b else '0'; + alu_lt <= '1' when alu_a < alu_b else '0'; + alu_gt <= '1' when alu_a > alu_b else '0'; + alu_carry <= alu_tmp(8); + alu_o <= alu_tmp(7 downto 0); + + -- 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_bus(7 downto 4) = x"1" then + alu_func <= inst_bus(3 downto 0); + alu_a <= reg_a; + alu_b <= reg_b; + reg_out <= alu_o; + end if; + -- alu operations a,imm + if inst_bus(7 downto 4) = x"2" then + alu_func <= inst_bus(3 downto 0); + alu_a <= reg_a; + alu_b <= x"00"; + reg_pc <= reg_pc+1; + end if; + -- alu operations imm,b + if inst_bus(7 downto 4) = x"3" then + alu_func <= inst_bus(3 downto 0); + alu_a <= x"00"; + 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; + + -- out + when x"FE" => report " out = " & integer'image(to_integer(unsigned(reg_out))); + -- 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/circuit._vhdl b/rtl/circuit._vhdl index ef367ed..9da6efc 100644 --- a/rtl/circuit._vhdl +++ b/rtl/circuit._vhdl @@ -6,19 +6,18 @@ use ieee.numeric_std.all; 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 + 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) := (others => '0'); -- active high + hex: out std_logic_vector(31 downto 0) := (others => '0') -- active low ); end circuit; architecture description of circuit is - signal counter: unsigned(9 downto 0) := "0000000000"; + signal counter: unsigned(31 downto 0) := x"00000000"; begin - led <= std_logic_vector(counter(9 downto 0)); + led <= std_logic_vector(counter); process(clk) begin counter <= counter+1; diff --git a/rtl/circuit.vhdl b/rtl/circuit.vhdl index 72c6944..1efbfc2 100644 --- a/rtl/circuit.vhdl +++ b/rtl/circuit.vhdl @@ -5,325 +5,23 @@ use ieee.numeric_std.all; -- Do not modify the following entity block entity circuit is port ( - clk: in std_logic; + clk: in std_logic; -- 500 Hz, period 2 ms 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 + led: out std_logic_vector(31 downto 0) := (others => '0'); -- active high + hex: out std_logic_vector(31 downto 0) := (others => '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; - - + signal counter: unsigned(9 downto 0) := "0000000000"; 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); + led(9 downto 0) <= std_logic_vector(counter); + -- led(10) <= clk; + process(sw(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; - + counter <= counter+1; + report "meow"; end process; end description; \ No newline at end of file diff --git a/rtl/tb.vhdl b/rtl/tb.vhdl index 6831d75..4df30f2 100644 --- a/rtl/tb.vhdl +++ b/rtl/tb.vhdl @@ -71,18 +71,18 @@ begin variable sw_i : integer; variable key_i : integer; begin - ffi_init; -- starts Rust listener thread + ffi_init; wait for 0 ns; - while true loop wait until rising_edge(clk) or falling_edge(clk); + wait for 0 ns; 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)); + sw <= std_logic_vector(to_signed(sw_i, 32)); + key <= std_logic_vector(to_signed(key_i, 32)); ffi_set_outputs( to_integer(unsigned(clean_slv(led))), diff --git a/run.sh b/run.sh index 56c3e0f..3cfef6f 100755 --- a/run.sh +++ b/run.sh @@ -3,7 +3,6 @@ set -euo pipefail mkdir -p build -# Build Rust shared library pushd conn >/dev/null cargo build --release popd >/dev/null @@ -14,21 +13,10 @@ LIBSRC="../conn/target/release/libvhdl_ui.a" LIBDIR="../conn/target/release" -ghdl -a --std=08 ../rtl/*.vhdl +ghdl -a -g --std=08 ../rtl/*.vhdl ghdl -e --std=08 \ - -Wl,"$LIBSRC" \ - -Wl,-Wl,-rpath -Wl,-Wl,$LIBDIR \ - tb + -Wl,$LIBSRC \ + 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 +ghdl -r --std=08 tb --stop-delta=2147483647 \ No newline at end of file diff --git a/run_relay.sh b/run_relay.sh new file mode 100755 index 0000000..2846bf1 --- /dev/null +++ b/run_relay.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +pushd conn >/dev/null +cargo build --release +popd >/dev/null + + +pushd relay >/dev/null +cargo run --release +popd >/dev/null \ No newline at end of file