mirror of
https://github.com/ParkerTenBroeck/hdl_sim.git
synced 2026-06-06 21:24:06 -04:00
added support for local editing with different editor
This commit is contained in:
parent
3cce2983a5
commit
0289d1171f
11 changed files with 2252 additions and 1096 deletions
|
|
@ -12,7 +12,7 @@ pub struct SimState{
|
||||||
}
|
}
|
||||||
|
|
||||||
static STATE: SimState = SimState{
|
static STATE: SimState = SimState{
|
||||||
switch: AtomicU32::new(512),
|
switch: AtomicU32::new(0),
|
||||||
button: AtomicU32::new(0),
|
button: AtomicU32::new(0),
|
||||||
led: AtomicU32::new(0),
|
led: AtomicU32::new(0),
|
||||||
segs: [const{AtomicU32::new(0)}; 4],
|
segs: [const{AtomicU32::new(0)}; 4],
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap, ffi::OsStr, ops::Deref, path::{Path, PathBuf}
|
||||||
ops::Deref,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
};
|
||||||
use tokio::process::{Child, Command};
|
use tokio::process::{Child, Command};
|
||||||
|
|
||||||
|
use crate::HResult;
|
||||||
|
|
||||||
async fn ensure_ok(child: Child) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn ensure_ok(child: Child) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let result = child.wait_with_output().await?;
|
let result = child.wait_with_output().await?;
|
||||||
if !result.status.success() {
|
if !result.status.success() {
|
||||||
|
|
@ -43,9 +43,9 @@ impl AsRef<Path> for TempDir {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build(
|
pub async fn copy_and_build(
|
||||||
files: HashMap<String, String>,
|
files: HashMap<String, String>,
|
||||||
) -> Result<TempDir, Box<dyn std::error::Error + Send + Sync>> {
|
) -> HResult<TempDir> {
|
||||||
use std::hash::*;
|
use std::hash::*;
|
||||||
let mut hasher = std::hash::DefaultHasher::default();
|
let mut hasher = std::hash::DefaultHasher::default();
|
||||||
for (key, value) in &files {
|
for (key, value) in &files {
|
||||||
|
|
@ -56,7 +56,7 @@ pub async fn build(
|
||||||
|
|
||||||
let mut work_dir = std::env::temp_dir();
|
let mut work_dir = std::env::temp_dir();
|
||||||
work_dir.push(format!("ghdl-relay-{hash:x?}"));
|
work_dir.push(format!("ghdl-relay-{hash:x?}"));
|
||||||
_ = std::fs::create_dir(&work_dir);
|
std::fs::create_dir_all(&work_dir)?;
|
||||||
let work_dir = TempDir(work_dir);
|
let work_dir = TempDir(work_dir);
|
||||||
|
|
||||||
for (name, contents) in &files {
|
for (name, contents) in &files {
|
||||||
|
|
@ -65,36 +65,51 @@ pub async fn build(
|
||||||
std::fs::write(path, contents)?;
|
std::fs::write(path, contents)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cmd = Command::new("ghdl");
|
build(&work_dir, &work_dir).await?;
|
||||||
|
|
||||||
|
Ok(work_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn build(path: &Path, src: &Path) -> HResult<()>{
|
||||||
|
std::fs::create_dir_all(path)?;
|
||||||
|
|
||||||
|
let mut cmd = Command::new("ghdl");
|
||||||
cmd.kill_on_drop(true);
|
cmd.kill_on_drop(true);
|
||||||
cmd.args(["-a", "-g", "--std=08"]);
|
cmd.args(["-i", "-g", "--std=08"]);
|
||||||
for name in files.keys() {
|
|
||||||
let mut path = work_dir.clone();
|
for file in src.read_dir().unwrap().flatten(){
|
||||||
path.push(name);
|
if Path::new(&file.file_name()).extension() == Some(OsStr::new("vhdl")) {
|
||||||
cmd.arg(path);
|
cmd.arg(file.path());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.arg(std::fs::canonicalize("../rtl/tb.vhdl")?);
|
cmd.arg(std::fs::canonicalize("../rtl/tb.vhdl")?);
|
||||||
|
|
||||||
cmd.stdin(std::process::Stdio::piped())
|
cmd.stdin(std::process::Stdio::piped())
|
||||||
.stdout(std::process::Stdio::piped())
|
.stdout(std::process::Stdio::piped())
|
||||||
.stderr(std::process::Stdio::piped());
|
.stderr(std::process::Stdio::piped());
|
||||||
|
|
||||||
cmd.current_dir(&work_dir);
|
cmd.current_dir(path);
|
||||||
ensure_ok(cmd.spawn()?).await?;
|
ensure_ok(cmd.spawn()?).await?;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let mut cmd = Command::new("ghdl");
|
let mut cmd = Command::new("ghdl");
|
||||||
cmd.kill_on_drop(true);
|
cmd.kill_on_drop(true);
|
||||||
cmd.args(["-e", "--std=08"]);
|
cmd.args(["-m", "--std=08"]);
|
||||||
cmd.arg(format!(
|
cmd.arg(format!(
|
||||||
"-Wl,{}",
|
"-Wl,{}",
|
||||||
std::fs::canonicalize("../conn/target/release/libvhdl_ui.a")?.display()
|
std::fs::canonicalize("../target/release/libvhdl_ui.a")?.display()
|
||||||
));
|
));
|
||||||
cmd.arg("tb");
|
cmd.arg("tb");
|
||||||
cmd.current_dir(&work_dir);
|
cmd.current_dir(path);
|
||||||
cmd.stdin(std::process::Stdio::piped())
|
cmd.stdin(std::process::Stdio::piped())
|
||||||
.stdout(std::process::Stdio::piped())
|
.stdout(std::process::Stdio::piped())
|
||||||
.stderr(std::process::Stdio::piped());
|
.stderr(std::process::Stdio::piped());
|
||||||
ensure_ok(cmd.spawn()?).await?;
|
ensure_ok(cmd.spawn()?).await?;
|
||||||
|
|
||||||
Ok(work_dir)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -5,43 +5,15 @@ use futures_util::{
|
||||||
SinkExt, StreamExt,
|
SinkExt, StreamExt,
|
||||||
stream::{SplitSink, SplitStream},
|
stream::{SplitSink, SplitStream},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use std::{path::PathBuf, time::Duration};
|
||||||
use std::{collections::HashMap, path::PathBuf, time::Duration};
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncBufReadExt, BufReader, Lines},
|
io::{AsyncBufReadExt, BufReader, Lines},
|
||||||
process::{Child, ChildStderr, ChildStdin, ChildStdout},
|
process::{Child, ChildStderr, ChildStdin, ChildStdout}, time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{build, run};
|
use crate::{ClientMsg, ServerMsg, build, run};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
|
||||||
enum ClientMsg {
|
|
||||||
Compile(Option<HashMap<String, String>>),
|
|
||||||
Start,
|
|
||||||
Stop,
|
|
||||||
Input {
|
|
||||||
/// 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 },
|
|
||||||
Start,
|
|
||||||
Stop,
|
|
||||||
Led(u32),
|
|
||||||
Seg0(u32),
|
|
||||||
Seg1(u32),
|
|
||||||
Seg2(u32),
|
|
||||||
Seg3(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Process {
|
struct Process {
|
||||||
process: Child,
|
process: Child,
|
||||||
|
|
||||||
|
|
@ -50,10 +22,6 @@ struct Process {
|
||||||
stdin: ChildStdin,
|
stdin: ChildStdin,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Mode {
|
|
||||||
SingleLocal,
|
|
||||||
Remote,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Handler {
|
struct Handler {
|
||||||
sender: SplitSink<WebSocket, Message>,
|
sender: SplitSink<WebSocket, Message>,
|
||||||
|
|
@ -68,11 +36,9 @@ struct Handler {
|
||||||
refresh_time: Duration,
|
refresh_time: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
type HResult<T> = Result<T, Box<dyn std::error::Error + Sync + Send>>;
|
|
||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
|
|
||||||
async fn local(socket: WebSocket, build: PathBuf, src: PathBuf) -> Self {
|
fn local(socket: WebSocket, build: PathBuf, src: PathBuf) -> Self {
|
||||||
let (sender, receiver) = socket.split();
|
let (sender, receiver) = socket.split();
|
||||||
Self {
|
Self {
|
||||||
sender,
|
sender,
|
||||||
|
|
@ -108,20 +74,18 @@ impl Handler {
|
||||||
_ = self.sender.send(Message::Text(serde_json::to_string(&ServerMsg::Stop).unwrap_or_default().into())).await;
|
_ = self.sender.send(Message::Text(serde_json::to_string(&ServerMsg::Stop).unwrap_or_default().into())).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_websocket_msg(&mut self, msg: ClientMsg) -> HResult<bool> {
|
async fn handle_websocket_msg(&mut self, msg: ClientMsg) {
|
||||||
match msg{
|
match msg{
|
||||||
ClientMsg::Compile(_) => todo!(),
|
|
||||||
ClientMsg::Start => self.run_program().await,
|
ClientMsg::Start => self.run_program().await,
|
||||||
ClientMsg::Stop => self.stop_process().await,
|
ClientMsg::Stop => self.stop_process().await,
|
||||||
ClientMsg::Input { switch, buttons } => {
|
ClientMsg::Input { switch, buttons } => {
|
||||||
if let Some(process) = &mut self.process{
|
if let Some(process) = &mut self.process{
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
process.stdin.write_all(format!("btn={}\n", buttons).as_bytes()).await?;
|
_ = process.stdin.write_all(format!("btn={}\n", buttons).as_bytes()).await;
|
||||||
process.stdin.write_all(format!("sw={}\n", switch).as_bytes()).await?;
|
_ = process.stdin.write_all(format!("sw={}\n", switch).as_bytes()).await;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Ok(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_websocket_receive(
|
async fn handle_websocket_receive(
|
||||||
|
|
@ -150,13 +114,16 @@ impl Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&mut self) -> Result<(), Box<dyn std::error::Error + Sync + Send>> {
|
async fn run(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
if let Some(process) = &mut self.process {
|
if let Some(process) = &mut self.process {
|
||||||
if let Ok(Some(_)) = process.process.try_wait(){
|
if let Ok(Some(_)) = process.process.try_wait(){
|
||||||
self.stop_process().await;
|
self.stop_process().await;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut print_deadline = Instant::now();
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
receive = self.receiver.next() => {
|
receive = self.receiver.next() => {
|
||||||
if self.handle_websocket_receive(receive).await{
|
if self.handle_websocket_receive(receive).await{
|
||||||
|
|
@ -190,7 +157,7 @@ impl Handler {
|
||||||
self.eprint(line).await;
|
self.eprint(line).await;
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
self.sender.send(Message::Text(serde_json::to_string(&msg)?.into())).await?;
|
_ = self.sender.send(Message::Text(serde_json::to_string(&msg).unwrap_or_default().into())).await;
|
||||||
},
|
},
|
||||||
Ok(None) => self.stop_process().await,
|
Ok(None) => self.stop_process().await,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
@ -199,8 +166,9 @@ impl Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = tokio::time::sleep(self.refresh_time) => {
|
_ = tokio::time::sleep_until(print_deadline) => {
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
print_deadline += self.refresh_time;
|
||||||
_ = process.stdin.write_all("\n".as_bytes()).await;
|
_ = process.stdin.write_all("\n".as_bytes()).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -211,20 +179,12 @@ impl Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn build_program(&mut self) {
|
async fn run_program(&mut self) {
|
||||||
let files = if let Some(Ok(Message::Text(msg))) = self.receiver.next().await
|
|
||||||
&& let Ok(files) = serde_json::from_str::<'_, HashMap<String, String>>(&msg)
|
|
||||||
{
|
|
||||||
files
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let artifact_dir = match build::build(files).await {
|
match build::build(&self.build_dir, &self.src_dir).await {
|
||||||
Ok(dir) => dir,
|
Ok(_) => {},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
_ = self
|
_ = self
|
||||||
.sender
|
.sender
|
||||||
|
|
@ -233,9 +193,7 @@ impl Handler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
async fn run_program(&mut self) {
|
|
||||||
let process = match run::run(&self.build_dir).await {
|
let process = match run::run(&self.build_dir).await {
|
||||||
Ok(process) => process,
|
Ok(process) => process,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
@ -247,6 +205,7 @@ impl Handler {
|
||||||
let stderr = BufReader::new(process.stderr).lines();
|
let stderr = BufReader::new(process.stderr).lines();
|
||||||
let stdin = process.stdin;
|
let stdin = process.stdin;
|
||||||
|
|
||||||
|
_ = self.sender.send(Message::Text(serde_json::to_string(&ServerMsg::Start).unwrap_or_default().into())).await;
|
||||||
self.process = Some(
|
self.process = Some(
|
||||||
Process { process: process.child, stderr, stdout, stdin }
|
Process { process: process.child, stderr, stdout, stdin }
|
||||||
)
|
)
|
||||||
|
|
@ -254,5 +213,5 @@ impl Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ws_handler(socket: WebSocket) {
|
pub async fn ws_handler(socket: WebSocket) {
|
||||||
Handler::local(socket, "target".into(), "src".into());
|
Handler::local(socket, "../target".into(), "../src".into()).run().await;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,12 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
extract::ws::{Message, WebSocket, WebSocketUpgrade},
|
extract::ws::WebSocketUpgrade,
|
||||||
routing::get,
|
routing::get,
|
||||||
};
|
};
|
||||||
use futures_util::{
|
|
||||||
stream::{SplitSink, SplitStream},
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::HashMap, net::SocketAddr, path::PathBuf, time::Duration};
|
use std::net::SocketAddr;
|
||||||
use tokio::{
|
|
||||||
io::{BufReader, Lines},
|
|
||||||
process::{Child, ChildStderr, ChildStdin, ChildStdout},
|
|
||||||
};
|
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
use crate::build::TempDir;
|
|
||||||
|
|
||||||
pub mod build;
|
pub mod build;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
pub mod local;
|
pub mod local;
|
||||||
|
|
@ -25,9 +16,13 @@ pub mod remote;
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/ws",
|
"/ws/local",
|
||||||
get(|ws: WebSocketUpgrade| async move { ws.on_upgrade(remote::ws_handler) }),
|
get(|ws: WebSocketUpgrade| async move { ws.on_upgrade(remote::ws_handler) }),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/ws/remote",
|
||||||
|
get(|ws: WebSocketUpgrade| async move { ws.on_upgrade(local::ws_handler) }),
|
||||||
|
)
|
||||||
.fallback_service(ServeDir::new("ui"));
|
.fallback_service(ServeDir::new("ui"));
|
||||||
|
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||||
|
|
@ -39,9 +34,8 @@ async fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ClientMsg {
|
pub enum ClientMsg {
|
||||||
Compile(Option<HashMap<String, String>>),
|
|
||||||
Start,
|
Start,
|
||||||
Stop,
|
Stop,
|
||||||
Input {
|
Input {
|
||||||
|
|
@ -65,25 +59,4 @@ pub enum ServerMsg<'a> {
|
||||||
Seg3(u32),
|
Seg3(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Process {
|
pub type HResult<T> = Result<T, Box<dyn std::error::Error + Sync + Send>>;
|
||||||
process: Child,
|
|
||||||
|
|
||||||
stderr: Lines<BufReader<ChildStderr>>,
|
|
||||||
stdout: Lines<BufReader<ChildStdout>>,
|
|
||||||
stdin: ChildStdin,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Handler {
|
|
||||||
sender: SplitSink<WebSocket, Message>,
|
|
||||||
receiver: SplitStream<WebSocket>,
|
|
||||||
|
|
||||||
build_dir: TempDir,
|
|
||||||
src_dir: PathBuf,
|
|
||||||
|
|
||||||
program: Option<PathBuf>,
|
|
||||||
process: Option<Process>,
|
|
||||||
|
|
||||||
refresh_time: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type HResult<T> = Result<T, Box<dyn std::error::Error + Sync + Send>>;
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ use axum::{
|
||||||
use futures_util::{
|
use futures_util::{
|
||||||
SinkExt, StreamExt,
|
SinkExt, StreamExt,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, time::Duration};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncBufReadExt, BufReader},
|
io::{AsyncBufReadExt, BufReader}, time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{ClientMsg, HResult, ServerMsg, build, run};
|
use crate::{ClientMsg, HResult, ServerMsg, build, run};
|
||||||
|
|
@ -23,7 +23,7 @@ pub async fn ws_handler(socket: WebSocket) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let artifact_dir = match build::build(files).await{
|
let artifact_dir = match build::copy_and_build(files).await{
|
||||||
Ok(dir) => dir,
|
Ok(dir) => dir,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
_ = sender.send(Message::Text(format!("Failed to build: {err}").into())).await;
|
_ = sender.send(Message::Text(format!("Failed to build: {err}").into())).await;
|
||||||
|
|
@ -42,6 +42,8 @@ pub async fn ws_handler(socket: WebSocket) {
|
||||||
let mut serr = BufReader::new(process.stderr).lines();
|
let mut serr = BufReader::new(process.stderr).lines();
|
||||||
|
|
||||||
let artifact_prefix = artifact_dir.to_str().unwrap_or("\0\0NOPE");
|
let artifact_prefix = artifact_dir.to_str().unwrap_or("\0\0NOPE");
|
||||||
|
|
||||||
|
let mut print_deadline = Instant::now();
|
||||||
|
|
||||||
let result: HResult<()> = async {
|
let result: HResult<()> = async {
|
||||||
loop{
|
loop{
|
||||||
|
|
@ -52,7 +54,6 @@ pub async fn ws_handler(socket: WebSocket) {
|
||||||
Some(Ok(Message::Text(msg))) => {
|
Some(Ok(Message::Text(msg))) => {
|
||||||
let input = serde_json::from_str::<'_, ClientMsg>(&msg)?;
|
let input = serde_json::from_str::<'_, ClientMsg>(&msg)?;
|
||||||
match input{
|
match input{
|
||||||
ClientMsg::Compile(_) => {},
|
|
||||||
ClientMsg::Start => {},
|
ClientMsg::Start => {},
|
||||||
ClientMsg::Stop => break,
|
ClientMsg::Stop => break,
|
||||||
ClientMsg::Input { switch, buttons } => {
|
ClientMsg::Input { switch, buttons } => {
|
||||||
|
|
@ -110,8 +111,9 @@ pub async fn ws_handler(socket: WebSocket) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = tokio::time::sleep(std::time::Duration::from_millis(30)) => {
|
_ = tokio::time::sleep_until(print_deadline) => {
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
print_deadline += Duration::from_millis(30);
|
||||||
process.stdin.write_all("\n".as_bytes()).await?;
|
process.stdin.write_all("\n".as_bytes()).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1286
relay/ui/app.js
1286
relay/ui/app.js
File diff suppressed because it is too large
Load diff
|
|
@ -7,50 +7,307 @@
|
||||||
<link rel="stylesheet" href="styles.css" />
|
<link rel="stylesheet" href="styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="topbar">
|
|
||||||
<div class="brand">
|
|
||||||
<div class="dot"></div>
|
|
||||||
<div>
|
|
||||||
<div class="title">Circuit Web UI</div>
|
|
||||||
<div class="subtitle">LEDs • HEX • Switches • Buttons • circuit.vhdl</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="controls">
|
|
||||||
<div class="status">
|
|
||||||
<span class="pill" id="wsPill">DISCONNECTED</span>
|
|
||||||
<span class="muted" id="wsUrlText"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- single toggle button now -->
|
|
||||||
<button id="connectToggleBtn">Connect</button>
|
|
||||||
|
|
||||||
<button id="sendInputsBtn" class="secondary" disabled>Send Inputs</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="grid">
|
<main class="grid">
|
||||||
<!-- Left: Editor -->
|
<section id="editorSection" class="card editor">
|
||||||
<section class="card editor">
|
|
||||||
<div class="cardHeader">
|
<div class="cardHeader">
|
||||||
<div class="cardTitle">circuit.vhdl</div>
|
<div class="cardTitle">VHDL</div>
|
||||||
<div class="cardActions">
|
<div class="cardActions">
|
||||||
<button id="loadExampleBtn" class="secondary">Load example</button>
|
<button id="loadExampleBtn" class="secondary">Load example</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- NEW: editor with line-number gutter -->
|
|
||||||
<div class="editorWrap">
|
<div class="editorWrap">
|
||||||
<pre id="lineGutter" class="lineGutter" aria-hidden="true"></pre>
|
<pre id="lineGutter" class="lineGutter" aria-hidden="true"></pre>
|
||||||
<textarea id="vhdlEditor" spellcheck="false"></textarea>
|
<textarea id="vhdlEditor" spellcheck="false"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="hint">
|
<section class="card inputs">
|
||||||
Saved locally as you type. On <b>Connect</b>, the UI sends <code>{"circuit.vhdl": "..."}</code> as the first WebSocket message.
|
<div class="cardHeader cardHeaderWrap">
|
||||||
|
<div class="cardTitle">Inputs</div>
|
||||||
|
|
||||||
|
<div class="panelTopStatus">
|
||||||
|
<span class="pill state-disabled" id="statusPill">DISABLED</span>
|
||||||
|
<label class="modeToggle" for="modeToggle">
|
||||||
|
<span class="modeLabel">Local</span>
|
||||||
|
<input id="modeToggle" type="checkbox" aria-label="Toggle remote mode" />
|
||||||
|
<span class="modeSlider" aria-hidden="true"></span>
|
||||||
|
<span class="modeLabel">Remote</span>
|
||||||
|
</label>
|
||||||
|
<button id="connectToggleBtn">Connect</button>
|
||||||
|
<button id="runToggleBtn" class="secondary" disabled>Start</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="twoCols">
|
||||||
|
<div class="block">
|
||||||
|
<div class="cardActions">
|
||||||
|
<button id="allSwOffBtn" class="secondary">All switches off</button>
|
||||||
|
<button id="allSwOnBtn" class="secondary">All switches on</button>
|
||||||
|
</div>
|
||||||
|
<div class="blockTitle">Switches (32, latched)</div>
|
||||||
|
<div id="switchGrid" class="ioGrid switchGrid">
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="0" title="Toggle switch 0"></div>
|
||||||
|
<label>SW[0]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="1" title="Toggle switch 1"></div>
|
||||||
|
<label>SW[1]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="2" title="Toggle switch 2"></div>
|
||||||
|
<label>SW[2]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="3" title="Toggle switch 3"></div>
|
||||||
|
<label>SW[3]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="4" title="Toggle switch 4"></div>
|
||||||
|
<label>SW[4]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="5" title="Toggle switch 5"></div>
|
||||||
|
<label>SW[5]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="6" title="Toggle switch 6"></div>
|
||||||
|
<label>SW[6]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="7" title="Toggle switch 7"></div>
|
||||||
|
<label>SW[7]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="8" title="Toggle switch 8"></div>
|
||||||
|
<label>SW[8]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="9" title="Toggle switch 9"></div>
|
||||||
|
<label>SW[9]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="10" title="Toggle switch 10"></div>
|
||||||
|
<label>SW[10]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="11" title="Toggle switch 11"></div>
|
||||||
|
<label>SW[11]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="12" title="Toggle switch 12"></div>
|
||||||
|
<label>SW[12]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="13" title="Toggle switch 13"></div>
|
||||||
|
<label>SW[13]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="14" title="Toggle switch 14"></div>
|
||||||
|
<label>SW[14]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="15" title="Toggle switch 15"></div>
|
||||||
|
<label>SW[15]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="16" title="Toggle switch 16"></div>
|
||||||
|
<label>SW[16]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="17" title="Toggle switch 17"></div>
|
||||||
|
<label>SW[17]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="18" title="Toggle switch 18"></div>
|
||||||
|
<label>SW[18]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="19" title="Toggle switch 19"></div>
|
||||||
|
<label>SW[19]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="20" title="Toggle switch 20"></div>
|
||||||
|
<label>SW[20]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="21" title="Toggle switch 21"></div>
|
||||||
|
<label>SW[21]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="22" title="Toggle switch 22"></div>
|
||||||
|
<label>SW[22]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="23" title="Toggle switch 23"></div>
|
||||||
|
<label>SW[23]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="24" title="Toggle switch 24"></div>
|
||||||
|
<label>SW[24]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="25" title="Toggle switch 25"></div>
|
||||||
|
<label>SW[25]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="26" title="Toggle switch 26"></div>
|
||||||
|
<label>SW[26]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="27" title="Toggle switch 27"></div>
|
||||||
|
<label>SW[27]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="28" title="Toggle switch 28"></div>
|
||||||
|
<label>SW[28]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="29" title="Toggle switch 29"></div>
|
||||||
|
<label>SW[29]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="30" title="Toggle switch 30"></div>
|
||||||
|
<label>SW[30]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<div class="toggle" data-bit="31" title="Toggle switch 31"></div>
|
||||||
|
<label>SW[31]</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<div class="blockTitle">Buttons (32, momentary)</div>
|
||||||
|
|
||||||
|
<div class="subBlockTitle">Standard BTN (0-7, 16-31)</div>
|
||||||
|
<div id="buttonGrid" class="ioGrid buttonGrid">
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="0" title="Momentary BTN 0"></button>
|
||||||
|
<label>BTN[0]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="1" title="Momentary BTN 1"></button>
|
||||||
|
<label>BTN[1]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="2" title="Momentary BTN 2"></button>
|
||||||
|
<label>BTN[2]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="3" title="Momentary BTN 3"></button>
|
||||||
|
<label>BTN[3]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="4" title="Momentary BTN 4"></button>
|
||||||
|
<label>BTN[4]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="5" title="Momentary BTN 5"></button>
|
||||||
|
<label>BTN[5]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="6" title="Momentary BTN 6"></button>
|
||||||
|
<label>BTN[6]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="7" title="Momentary BTN 7"></button>
|
||||||
|
<label>BTN[7]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="16" title="Momentary BTN 16"></button>
|
||||||
|
<label>BTN[16]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="17" title="Momentary BTN 17"></button>
|
||||||
|
<label>BTN[17]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="18" title="Momentary BTN 18"></button>
|
||||||
|
<label>BTN[18]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="19" title="Momentary BTN 19"></button>
|
||||||
|
<label>BTN[19]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="20" title="Momentary BTN 20"></button>
|
||||||
|
<label>BTN[20]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="21" title="Momentary BTN 21"></button>
|
||||||
|
<label>BTN[21]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="22" title="Momentary BTN 22"></button>
|
||||||
|
<label>BTN[22]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="23" title="Momentary BTN 23"></button>
|
||||||
|
<label>BTN[23]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="24" title="Momentary BTN 24"></button>
|
||||||
|
<label>BTN[24]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="25" title="Momentary BTN 25"></button>
|
||||||
|
<label>BTN[25]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="26" title="Momentary BTN 26"></button>
|
||||||
|
<label>BTN[26]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="27" title="Momentary BTN 27"></button>
|
||||||
|
<label>BTN[27]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="28" title="Momentary BTN 28"></button>
|
||||||
|
<label>BTN[28]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="29" title="Momentary BTN 29"></button>
|
||||||
|
<label>BTN[29]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="30" title="Momentary BTN 30"></button>
|
||||||
|
<label>BTN[30]</label>
|
||||||
|
</div>
|
||||||
|
<div class="ioCell">
|
||||||
|
<button class="momentary" type="button" data-bit="31" title="Momentary BTN 31"></button>
|
||||||
|
<label>BTN[31]</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="subBlockTitle">4x4 Keypad Matrix via BTN (8-15)</div>
|
||||||
|
<div id="keypadGrid" class="keypadGrid">
|
||||||
|
<button class="momentary keypadBtn" type="button" data-row-bit="8" data-col-bit="12" data-key="7">7</button>
|
||||||
|
<button class="momentary keypadBtn" type="button" data-row-bit="8" data-col-bit="13" data-key="8">8</button>
|
||||||
|
<button class="momentary keypadBtn" type="button" data-row-bit="8" data-col-bit="14" data-key="9">9</button>
|
||||||
|
<button class="momentary keypadBtn op" type="button" data-row-bit="8" data-col-bit="15" data-key="/">/</button>
|
||||||
|
|
||||||
|
<button class="momentary keypadBtn" type="button" data-row-bit="9" data-col-bit="12" data-key="4">4</button>
|
||||||
|
<button class="momentary keypadBtn" type="button" data-row-bit="9" data-col-bit="13" data-key="5">5</button>
|
||||||
|
<button class="momentary keypadBtn" type="button" data-row-bit="9" data-col-bit="14" data-key="6">6</button>
|
||||||
|
<button class="momentary keypadBtn op" type="button" data-row-bit="9" data-col-bit="15" data-key="*">*</button>
|
||||||
|
|
||||||
|
<button class="momentary keypadBtn" type="button" data-row-bit="10" data-col-bit="12" data-key="1">1</button>
|
||||||
|
<button class="momentary keypadBtn" type="button" data-row-bit="10" data-col-bit="13" data-key="2">2</button>
|
||||||
|
<button class="momentary keypadBtn" type="button" data-row-bit="10" data-col-bit="14" data-key="3">3</button>
|
||||||
|
<button class="momentary keypadBtn op" type="button" data-row-bit="10" data-col-bit="15" data-key="-">-</button>
|
||||||
|
|
||||||
|
<button class="momentary keypadBtn" type="button" data-row-bit="11" data-col-bit="12" data-key="0">0</button>
|
||||||
|
<button class="momentary keypadBtn" type="button" data-row-bit="11" data-col-bit="13" data-key=".">.</button>
|
||||||
|
<button class="momentary keypadBtn op" type="button" data-row-bit="11" data-col-bit="14" data-key="=">=</button>
|
||||||
|
<button class="momentary keypadBtn op" type="button" data-row-bit="11" data-col-bit="15" data-key="+">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Right: IO -->
|
|
||||||
<section class="card io">
|
<section class="card io">
|
||||||
<div class="cardHeader">
|
<div class="cardHeader">
|
||||||
<div class="cardTitle">Outputs</div>
|
<div class="cardTitle">Outputs</div>
|
||||||
|
|
@ -58,55 +315,480 @@
|
||||||
|
|
||||||
<div class="outputs">
|
<div class="outputs">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="blockTitle">LEDs (32)</div>
|
<div id="ledRow" class="ledRow">
|
||||||
<div id="ledRow" class="ledRow"></div>
|
<div class="ledGroup">
|
||||||
<div class="bitLabelRow" id="ledLabels"></div>
|
<div class="led" data-bit="0" title="LED[0]"></div>
|
||||||
|
<div class="led" data-bit="1" title="LED[1]"></div>
|
||||||
|
<div class="led" data-bit="2" title="LED[2]"></div>
|
||||||
|
<div class="led" data-bit="3" title="LED[3]"></div>
|
||||||
|
<div class="led" data-bit="4" title="LED[4]"></div>
|
||||||
|
<div class="led" data-bit="5" title="LED[5]"></div>
|
||||||
|
<div class="led" data-bit="6" title="LED[6]"></div>
|
||||||
|
<div class="led" data-bit="7" title="LED[7]"></div>
|
||||||
|
</div>
|
||||||
|
<div class="ledGroup">
|
||||||
|
<div class="led" data-bit="8" title="LED[8]"></div>
|
||||||
|
<div class="led" data-bit="9" title="LED[9]"></div>
|
||||||
|
<div class="led" data-bit="10" title="LED[10]"></div>
|
||||||
|
<div class="led" data-bit="11" title="LED[11]"></div>
|
||||||
|
<div class="led" data-bit="12" title="LED[12]"></div>
|
||||||
|
<div class="led" data-bit="13" title="LED[13]"></div>
|
||||||
|
<div class="led" data-bit="14" title="LED[14]"></div>
|
||||||
|
<div class="led" data-bit="15" title="LED[15]"></div>
|
||||||
|
</div>
|
||||||
|
<div class="ledGroup">
|
||||||
|
<div class="led" data-bit="16" title="LED[16]"></div>
|
||||||
|
<div class="led" data-bit="17" title="LED[17]"></div>
|
||||||
|
<div class="led" data-bit="18" title="LED[18]"></div>
|
||||||
|
<div class="led" data-bit="19" title="LED[19]"></div>
|
||||||
|
<div class="led" data-bit="20" title="LED[20]"></div>
|
||||||
|
<div class="led" data-bit="21" title="LED[21]"></div>
|
||||||
|
<div class="led" data-bit="22" title="LED[22]"></div>
|
||||||
|
<div class="led" data-bit="23" title="LED[23]"></div>
|
||||||
|
</div>
|
||||||
|
<div class="ledGroup">
|
||||||
|
<div class="led" data-bit="24" title="LED[24]"></div>
|
||||||
|
<div class="led" data-bit="25" title="LED[25]"></div>
|
||||||
|
<div class="led" data-bit="26" title="LED[26]"></div>
|
||||||
|
<div class="led" data-bit="27" title="LED[27]"></div>
|
||||||
|
<div class="led" data-bit="28" title="LED[28]"></div>
|
||||||
|
<div class="led" data-bit="29" title="LED[29]"></div>
|
||||||
|
<div class="led" data-bit="30" title="LED[30]"></div>
|
||||||
|
<div class="led" data-bit="31" title="LED[31]"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="blockTitle">HEX (4 digits, 7-seg + dp)</div>
|
<div id="hexRow" class="hexRow">
|
||||||
<div id="hexRow" class="hexRow"></div>
|
<div class="hexGroup">
|
||||||
<div class="hint small">
|
<div class="hexDigitWrap">
|
||||||
Assumes each digit is 8 bits: <code>bit0=a</code>, <code>1=b</code>, <code>2=c</code>, <code>3=d</code>, <code>4=e</code>, <code>5=f</code>, <code>6=g</code>, <code>7=dp</code>.
|
<div class="sevenSeg" data-digit="0">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[0]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="1">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[1]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="2">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[2]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="3">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[3]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="4">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[4]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="5">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[5]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="6">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[6]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="7">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[7]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="8">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[8]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="9">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[9]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="10">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[10]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="11">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[11]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="12">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[12]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="13">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[13]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="14">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[14]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="15">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[15]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="16">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[16]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="17">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[17]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="18">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[18]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="19">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[19]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="20">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[20]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="21">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[21]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="22">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[22]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="23">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[23]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="24">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[24]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="25">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[25]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="26">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[26]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="27">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[27]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="28">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[28]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="29">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[29]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="30">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[30]</div>
|
||||||
|
</div>
|
||||||
|
<div class="hexDigitWrap">
|
||||||
|
<div class="sevenSeg" data-digit="31">
|
||||||
|
<div class="seg a" data-seg="a"></div>
|
||||||
|
<div class="seg b" data-seg="b"></div>
|
||||||
|
<div class="seg c" data-seg="c"></div>
|
||||||
|
<div class="seg d" data-seg="d"></div>
|
||||||
|
<div class="seg e" data-seg="e"></div>
|
||||||
|
<div class="seg f" data-seg="f"></div>
|
||||||
|
<div class="seg g" data-seg="g"></div>
|
||||||
|
<div class="seg dp" data-seg="dp"></div>
|
||||||
|
</div>
|
||||||
|
<div class="digitLabel">SEG[31]</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Bottom-left: Inputs -->
|
|
||||||
<section class="card inputs">
|
|
||||||
<div class="cardHeader">
|
|
||||||
<div class="cardTitle">Inputs</div>
|
|
||||||
<div class="cardActions">
|
|
||||||
<button id="allSwOffBtn" class="secondary">All switches off</button>
|
|
||||||
<button id="allSwOnBtn" class="secondary">All switches on</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="twoCols">
|
|
||||||
<div class="block">
|
|
||||||
<div class="blockTitle">Switches (32, latched)</div>
|
|
||||||
<div id="switchGrid" class="ioGrid"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="block">
|
|
||||||
<div class="blockTitle">Buttons (32, momentary)</div>
|
|
||||||
<div id="buttonGrid" class="ioGrid"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="hint">
|
|
||||||
Switches toggle a bit. Buttons set the bit while pressed (mouse/touch) and clear on release.
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Bottom-right: Logs -->
|
|
||||||
<section class="card logs">
|
<section class="card logs">
|
||||||
<div class="cardHeader">
|
<div class="cardHeader">
|
||||||
<div class="cardTitle">Logs</div>
|
<div class="cardTitle">Logs</div>
|
||||||
<div class="cardActions">
|
<div class="cardActions">
|
||||||
<button id="clearLogBtn" class="secondary">Clear</button>
|
<button id="clearLogBtn" class="secondary">Clear</button>
|
||||||
<button id="autoscrollBtn" class="secondary">Autoscroll: on</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -114,6 +796,13 @@
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Optional runtime config hook:
|
||||||
|
// window.VHDL_UI_CONFIG = {
|
||||||
|
// mode: "local" | "remote",
|
||||||
|
// externalFiles: { "circuit.vhdl": "..." }
|
||||||
|
// };
|
||||||
|
</script>
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,396 +1,583 @@
|
||||||
:root{
|
:root {
|
||||||
--bg: #0b0f17;
|
--bg: #1e1e1e;
|
||||||
--card: #121a2a;
|
--surface: #252526;
|
||||||
--card2:#0f1726;
|
--surface-muted: #2a2a2b;
|
||||||
--text: #e6eefc;
|
--surface-header: #2d2d30;
|
||||||
--muted:#9fb0d0;
|
--surface-soft: #1f1f20;
|
||||||
--accent:#58a6ff;
|
--surface-soft-2: #2a2a2b;
|
||||||
--ok:#22c55e;
|
|
||||||
--bad:#ef4444;
|
--text: #d4d4d4;
|
||||||
--warn:#f59e0b;
|
--text-muted: #a9adb3;
|
||||||
--line:#1f2a44;
|
|
||||||
--shadow: 0 12px 30px rgba(0,0,0,.35);
|
--accent: #0e639c;
|
||||||
|
--accent-soft: #1177bb;
|
||||||
|
--accent-muted: #264f78;
|
||||||
|
--ok: #22c55e;
|
||||||
|
--bad: #ef4444;
|
||||||
|
--warn: #f59e0b;
|
||||||
|
|
||||||
|
--border-strong: #4d4d4d;
|
||||||
|
--border: #3c3c3c;
|
||||||
|
--border-soft: #343436;
|
||||||
|
|
||||||
|
--button-primary-bg: #0e639c;
|
||||||
|
--button-primary-bg-hover: #1177bb;
|
||||||
|
--button-secondary-bg: #33363a;
|
||||||
|
--button-secondary-bg-hover: #3f4349;
|
||||||
|
--button-border: #2f5f8f;
|
||||||
|
|
||||||
|
--pill-bg: #323234;
|
||||||
|
--pill-border: #5e6166;
|
||||||
|
--pill-connected-bg: #1f2c23;
|
||||||
|
--pill-connected-border: #407a4d;
|
||||||
|
--pill-disconnected-bg: #342325;
|
||||||
|
--pill-disconnected-border: #8d4f56;
|
||||||
|
|
||||||
|
--led-off-bg: #303033;
|
||||||
|
--led-off-border: #5b5d62;
|
||||||
|
--led-on-bg: #2a9d58;
|
||||||
|
--led-on-border: #5ccd88;
|
||||||
|
--led-group-bg: #242427;
|
||||||
|
--led-group-border: #4f5258;
|
||||||
|
|
||||||
|
--seg-off-bg: #3a3b3f;
|
||||||
|
--seg-on-bg: #f59e0b;
|
||||||
|
|
||||||
|
--toggle-off-bg: #3a3d41;
|
||||||
|
--toggle-off-knob: #c4c7cc;
|
||||||
|
--toggle-on-bg: #2f7d4d;
|
||||||
|
--toggle-on-knob: #7be8a5;
|
||||||
|
|
||||||
|
--momentary-bg: #373a3e;
|
||||||
|
--momentary-border: #60646b;
|
||||||
|
--momentary-down-bg: #365a7f;
|
||||||
|
--momentary-down-border: #5f8fc0;
|
||||||
|
--keypad-op-bg: #44484f;
|
||||||
|
--transparent: transparent;
|
||||||
|
--card-shadow: 0 10px 18px rgba(0, 0, 0, 0.35);
|
||||||
|
|
||||||
--radius: 14px;
|
--radius: 14px;
|
||||||
|
--radius-sm: 10px;
|
||||||
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
--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";
|
--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%; }
|
box-sizing: border-box;
|
||||||
body{
|
}
|
||||||
margin:0;
|
|
||||||
background: radial-gradient(1000px 600px at 20% -10%, rgba(88,166,255,.20), transparent 60%),
|
html,
|
||||||
radial-gradient(900px 500px at 90% 0%, rgba(34,197,94,.12), transparent 60%),
|
body {
|
||||||
var(--bg);
|
height: 100%;
|
||||||
color:var(--text);
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
font-family: var(--sans);
|
font-family: var(--sans);
|
||||||
}
|
}
|
||||||
|
|
||||||
.topbar{
|
button {
|
||||||
position: sticky;
|
padding: 8px 12px;
|
||||||
top:0;
|
border: 1px solid var(--button-border);
|
||||||
z-index: 10;
|
border-radius: var(--radius-sm);
|
||||||
display:flex;
|
background: var(--button-primary-bg);
|
||||||
justify-content:space-between;
|
color: var(--text);
|
||||||
align-items:center;
|
cursor: pointer;
|
||||||
padding:14px 16px;
|
transition: background 0.15s ease, transform 0.05s ease;
|
||||||
border-bottom:1px solid rgba(255,255,255,.06);
|
|
||||||
background: rgba(11,15,23,.8);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand{
|
button:hover {
|
||||||
display:flex;
|
background: var(--button-primary-bg-hover);
|
||||||
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{
|
button:active {
|
||||||
display:grid;
|
transform: translateY(1px);
|
||||||
grid-template-columns: 1.2fr 1fr;
|
|
||||||
grid-template-rows: 420px 1fr;
|
|
||||||
gap:14px;
|
|
||||||
padding:14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card{
|
button.secondary {
|
||||||
background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.03));
|
background: var(--button-secondary-bg);
|
||||||
border:1px solid rgba(255,255,255,.08);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
overflow:hidden;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardHeader{
|
button.secondary:hover {
|
||||||
display:flex;
|
background: var(--button-secondary-bg-hover);
|
||||||
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{
|
button:focus-visible {
|
||||||
display:flex;
|
outline: 1px solid var(--accent-soft);
|
||||||
flex-direction:column;
|
outline-offset: 1px;
|
||||||
}
|
}
|
||||||
textarea{
|
|
||||||
width:100%;
|
button:disabled {
|
||||||
height:100%;
|
opacity: 0.6;
|
||||||
padding:12px;
|
cursor: not-allowed;
|
||||||
background: rgba(0,0,0,.22);
|
}
|
||||||
color:var(--text);
|
|
||||||
border:0;
|
textarea {
|
||||||
outline:none;
|
width: 100%;
|
||||||
resize:none;
|
height: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 0;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--surface-soft);
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.35;
|
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{
|
.muted {
|
||||||
padding:12px;
|
font-size: 12px;
|
||||||
display:flex;
|
color: var(--text-muted);
|
||||||
flex-direction:column;
|
|
||||||
gap:14px;
|
|
||||||
}
|
|
||||||
.blockTitle{
|
|
||||||
font-size:12px;
|
|
||||||
color:var(--muted);
|
|
||||||
margin-bottom:8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ledRow{
|
.is-hidden {
|
||||||
display:grid;
|
display: none !important;
|
||||||
grid-template-columns: repeat(16, 1fr);
|
|
||||||
gap:6px;
|
|
||||||
}
|
}
|
||||||
.led{
|
|
||||||
|
.grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
flex: 1 1 520px;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--surface);
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardHeader {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid var(--border-soft);
|
||||||
|
background: var(--surface-header);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardHeaderWrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardTitle {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardActions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panelTopStatus {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-left: auto;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid var(--pill-border);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--pill-bg);
|
||||||
|
min-width: 104px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill.state-disabled {
|
||||||
|
border-color: var(--pill-disconnected-border);
|
||||||
|
background: var(--pill-disconnected-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill.state-connected {
|
||||||
|
border-color: color-mix(in srgb, var(--accent-soft) 75%, white 25%);
|
||||||
|
background: color-mix(in srgb, var(--accent-muted) 72%, black 28%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill.state-running {
|
||||||
|
border-color: var(--pill-connected-border);
|
||||||
|
background: var(--pill-connected-bg);
|
||||||
|
box-shadow: 0 0 0 1px color-mix(in srgb, var(--ok) 40%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modeToggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
user-select: none;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modeToggle input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modeSlider {
|
||||||
|
width: 36px;
|
||||||
|
height: 18px;
|
||||||
|
border: 1px solid var(--border-strong);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--toggle-off-bg);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modeSlider::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
left: 1px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--toggle-off-knob);
|
||||||
|
transition: transform 0.12s ease, background 0.12s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modeToggle input:checked + .modeSlider {
|
||||||
|
background: var(--accent-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modeToggle input:checked + .modeSlider::after {
|
||||||
|
transform: translateX(17px);
|
||||||
|
background: var(--accent-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockTitle {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subBlockTitle {
|
||||||
|
margin: 10px 0 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-top: 1px solid var(--border-soft);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
background: var(--surface-soft-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 420px;
|
||||||
|
flex: 1.4 1 620px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorWrap {
|
||||||
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 1/1;
|
height: 100%;
|
||||||
border-radius: 999px;
|
min-height: 0;
|
||||||
border:1px solid rgba(255,255,255,.14);
|
background: var(--surface-soft);
|
||||||
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);
|
.lineGutter {
|
||||||
border-color: rgba(34,197,94,.9);
|
width: 54px;
|
||||||
box-shadow: 0 0 14px rgba(34,197,94,.45);
|
margin: 0;
|
||||||
}
|
padding: 12px 10px 12px 12px;
|
||||||
.bitLabelRow{
|
overflow: hidden;
|
||||||
display:grid;
|
user-select: none;
|
||||||
grid-template-columns: repeat(16, 1fr);
|
text-align: right;
|
||||||
gap:6px;
|
border-right: 1px solid var(--border-soft);
|
||||||
margin-top:6px;
|
color: var(--text-muted);
|
||||||
color: var(--muted);
|
background: var(--surface-soft-2);
|
||||||
font-size: 10px;
|
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
opacity: .85;
|
font-size: 13px;
|
||||||
}
|
line-height: 1.35;
|
||||||
.bitLabelRow span{
|
|
||||||
text-align:center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hexRow{
|
textarea#vhdlEditor {
|
||||||
display:flex;
|
flex: 1;
|
||||||
gap:14px;
|
overflow: auto;
|
||||||
flex-wrap:wrap;
|
overscroll-behavior-y: none;
|
||||||
align-items:center;
|
background: var(--transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 7-seg display */
|
.io .outputs {
|
||||||
.sevenSeg{
|
display: flex;
|
||||||
width: 90px;
|
flex-direction: column;
|
||||||
height: 140px;
|
gap: 14px;
|
||||||
position: relative;
|
padding: 12px;
|
||||||
background: rgba(0,0,0,.18);
|
|
||||||
border:1px solid rgba(255,255,255,.10);
|
|
||||||
border-radius: 14px;
|
|
||||||
padding:10px;
|
|
||||||
}
|
}
|
||||||
.seg{
|
|
||||||
position:absolute;
|
.ledRow {
|
||||||
background: rgba(255,255,255,.10);
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ledGroup {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(8, minmax(0, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid var(--led-group-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
filter: drop-shadow(0 0 0 rgba(0,0,0,0));
|
background: var(--led-group-bg);
|
||||||
}
|
|
||||||
.seg.on{
|
|
||||||
background: rgba(88,166,255,.75);
|
|
||||||
filter: drop-shadow(0 0 8px rgba(88,166,255,.35));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* segment positions */
|
.led {
|
||||||
.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;
|
position: relative;
|
||||||
background: rgba(255,255,255,.10);
|
display: grid;
|
||||||
border:1px solid rgba(255,255,255,.14);
|
place-items: center;
|
||||||
cursor:pointer;
|
width: 100%;
|
||||||
}
|
border: 1px solid var(--led-off-border);
|
||||||
.toggle::after{
|
|
||||||
content:"";
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
position:absolute;
|
|
||||||
top:2px;
|
|
||||||
left:2px;
|
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(255,255,255,.45);
|
background: var(--led-off-bg);
|
||||||
transition: left .12s ease, background .12s ease;
|
aspect-ratio: 1 / 1;
|
||||||
}
|
|
||||||
.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{
|
.led::after {
|
||||||
width: 40px;
|
content: attr(data-bit);
|
||||||
height: 28px;
|
color: var(--text-muted);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 8px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.led.on {
|
||||||
|
border-color: var(--led-on-border);
|
||||||
|
background: var(--led-on-bg);
|
||||||
|
box-shadow: 0 0 6px color-mix(in srgb, var(--led-on-bg) 50%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hexRow {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 6px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hexGroup {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(8, minmax(0, 1fr));
|
||||||
|
gap: 4px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border:1px solid rgba(255,255,255,.14);
|
background: var(--surface-muted);
|
||||||
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{
|
.hexDigitWrap {
|
||||||
grid-column: 2 / 3;
|
display: flex;
|
||||||
display:flex;
|
flex-direction: column;
|
||||||
flex-direction:column;
|
align-items: center;
|
||||||
min-height: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.logView{
|
|
||||||
margin:0;
|
.sevenSeg {
|
||||||
padding:12px;
|
position: relative;
|
||||||
height:100%;
|
width: 100%;
|
||||||
overflow:auto;
|
max-width: 88px;
|
||||||
background: rgba(0,0,0,.22);
|
aspect-ratio: 88 / 136;
|
||||||
|
padding: 8%;
|
||||||
|
border: 1px solid var(--border-strong);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--surface-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.seg {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--seg-off-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.seg.on {
|
||||||
|
background: var(--seg-on-bg);
|
||||||
|
box-shadow: 0 0 4px color-mix(in srgb, var(--seg-on-bg) 45%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.seg.a { top: 8%; left: 21%; width: 58%; height: 7%; }
|
||||||
|
.seg.b { top: 14%; right: 11%; width: 10%; height: 34%; }
|
||||||
|
.seg.c { bottom: 14%; right: 11%; width: 10%; height: 34%; }
|
||||||
|
.seg.d { bottom: 8%; left: 21%; width: 58%; height: 7%; }
|
||||||
|
.seg.e { bottom: 14%; left: 11%; width: 10%; height: 34%; }
|
||||||
|
.seg.f { top: 14%; left: 11%; width: 10%; height: 34%; }
|
||||||
|
.seg.g { top: 46.5%; left: 21%; width: 58%; height: 7%; }
|
||||||
|
.seg.dp {
|
||||||
|
right: 5%;
|
||||||
|
bottom: 5%;
|
||||||
|
width: 9%;
|
||||||
|
height: 9%;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.digitLabel {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1.2 1 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twoCols {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 12px;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.ioGrid {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(64px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.ioCell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 64px;
|
||||||
|
min-height: 52px;
|
||||||
|
padding: 6px 4px;
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--surface-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ioCell label {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
position: relative;
|
||||||
|
width: 34px;
|
||||||
|
height: 20px;
|
||||||
|
border: 1px solid var(--border-strong);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--toggle-off-bg);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--toggle-off-knob);
|
||||||
|
transition: left 0.12s ease, background 0.12s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle.on {
|
||||||
|
background: var(--toggle-on-bg);
|
||||||
|
box-shadow: 0 0 0 1px color-mix(in srgb, var(--toggle-on-knob) 35%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle.on::after {
|
||||||
|
left: 16px;
|
||||||
|
background: var(--toggle-on-knob);
|
||||||
|
}
|
||||||
|
|
||||||
|
.momentary {
|
||||||
|
width: 34px;
|
||||||
|
height: 24px;
|
||||||
|
border: 1px solid var(--momentary-border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--momentary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.momentary.down {
|
||||||
|
border-color: var(--momentary-down-border);
|
||||||
|
background: var(--momentary-down-bg);
|
||||||
|
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-soft) 35%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.keypadGrid {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
grid-template-columns: repeat(4, minmax(50px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.keypadBtn {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 32px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keypadBtn.op {
|
||||||
|
background: var(--keypad-op-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.keypadBtn.op:hover {
|
||||||
|
background: var(--accent-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1 1 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logView {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
background: var(--surface-soft);
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive */
|
@media (max-width: 1100px) {
|
||||||
@media (max-width: 1100px){
|
.card {
|
||||||
.grid{
|
flex-basis: 100%;
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-rows: auto;
|
|
||||||
}
|
}
|
||||||
.inputs, .logs{
|
|
||||||
grid-column: auto;
|
.twoCols {
|
||||||
}
|
|
||||||
.twoCols{
|
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Editor w/ line numbers */
|
.panelTopStatus {
|
||||||
.editorWrap{
|
margin-left: auto;
|
||||||
display:flex;
|
width: auto;
|
||||||
width:100%;
|
justify-content: flex-end;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,119 +17,6 @@ port (
|
||||||
end circuit;
|
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
|
|
||||||
1 => x"B1", -- 1 => b
|
|
||||||
2 => x"10", -- a+b => out
|
|
||||||
3 => x"FE", -- out
|
|
||||||
4 => x"AE", -- out => a
|
|
||||||
5 => x"01", -- swap a/b
|
|
||||||
|
|
||||||
6 => x"3F", -- cmp 144, b
|
|
||||||
7 => x"90",
|
|
||||||
|
|
||||||
8 => x"C7", -- jump to 2 if 144 <= b
|
|
||||||
9 => x"02",
|
|
||||||
|
|
||||||
10 => x"FF", -- halt
|
|
||||||
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;
|
library ieee;
|
||||||
use ieee.std_logic_1164.all;
|
use ieee.std_logic_1164.all;
|
||||||
use ieee.numeric_std.all;
|
use ieee.numeric_std.all;
|
||||||
29
src/dram.vhdl
Normal file
29
src/dram.vhdl
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
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;
|
||||||
39
src/iram.vhdl
Normal file
39
src/iram.vhdl
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
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
|
||||||
|
1 => x"B1", -- 1 => b
|
||||||
|
2 => x"10", -- a+b => out
|
||||||
|
3 => x"FE", -- out
|
||||||
|
4 => x"AE", -- out => a
|
||||||
|
5 => x"01", -- swap a/b
|
||||||
|
|
||||||
|
6 => x"3F", -- cmp 144, b
|
||||||
|
7 => x"90",
|
||||||
|
|
||||||
|
8 => x"C7", -- jump to 2 if 144 <= b
|
||||||
|
9 => x"02",
|
||||||
|
|
||||||
|
10 => x"FF", -- halt
|
||||||
|
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;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue