updated segment interface

This commit is contained in:
Parker TenBroeck 2026-03-12 21:49:21 -04:00
parent 4773ab5e9e
commit 5746846896
9 changed files with 183 additions and 195 deletions

View file

@ -9,10 +9,8 @@ port (
btn: in std_logic_vector(31 downto 0); btn: in std_logic_vector(31 downto 0);
sw: in std_logic_vector(31 downto 0); sw: in std_logic_vector(31 downto 0);
led: out std_logic_vector(31 downto 0) := (others => '0'); led: out std_logic_vector(31 downto 0) := (others => '0');
seg0: out std_logic_vector(31 downto 0); segv: out std_logic_vector(31 downto 0);
seg1: out std_logic_vector(31 downto 0); segs: out std_logic_vector(31 downto 0)
seg2: out std_logic_vector(31 downto 0);
seg3: out std_logic_vector(31 downto 0)
); );
end circuit; end circuit;

View file

@ -1,22 +1,23 @@
use std::{ use std::{
io::{BufRead, BufReader}, io::{BufRead, BufReader},
sync::atomic::{AtomicU8, AtomicU32, AtomicU64, Ordering}, sync::atomic::{AtomicU32, AtomicU64, Ordering},
}; };
pub struct SimState{ pub struct SimState {
switch: AtomicU32, switch: AtomicU32,
button: AtomicU32, button: AtomicU32,
led: AtomicU32, led: AtomicU32,
segs: [AtomicU32; 4], /// represents 4 segments (each byte being one segment)
updated: AtomicU8, segs: [AtomicU32; 8],
updated: AtomicU32,
} }
static STATE: SimState = SimState{ static STATE: SimState = SimState {
switch: AtomicU32::new(0), 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) }; 8],
updated: AtomicU8::new(0) updated: AtomicU32::new(0),
}; };
/// default 1 ms /// default 1 ms
@ -34,13 +35,15 @@ fn client() {
if let Ok(n) = v.parse::<u32>() { if let Ok(n) = v.parse::<u32>() {
STATE.button.store(n, Ordering::Relaxed); STATE.button.store(n, Ordering::Relaxed);
} }
} else if let value = STATE.updated.swap(0, Ordering::Relaxed) && value!=0 { } else if let value = STATE.updated.swap(0, Ordering::Relaxed)
if (value >> 0) & 1 == 1{ && value != 0
{
if (value >> 0) & 1 == 1 {
eprintln!("led={}", STATE.led.load(Ordering::Relaxed)); eprintln!("led={}", STATE.led.load(Ordering::Relaxed));
} }
for i in 0..4{ for i in 0..STATE.segs.len() {
if (value >> (i+1)) & 1 == 1{ if (value >> (i + 1)) & 1 == 1 {
eprintln!("seg{i}={}", STATE.segs[i].load(Ordering::Relaxed)); eprintln!("seg={};{i}", STATE.segs[i].load(Ordering::Relaxed));
} }
} }
} }
@ -49,12 +52,15 @@ fn client() {
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn ffi_init() { pub extern "C" fn ffi_init() {
std::thread::Builder::new().name("client".into()).spawn(client).expect("Failed to spawn client thread"); std::thread::Builder::new()
for arg in std::env::args(){ .name("client".into())
if let Some(arg) = arg.strip_prefix("--cycle_sleep").or(arg.strip_prefix("-c")){ .spawn(client)
if let Ok(nanos) = arg.trim().parse::<u64>(){ .expect("Failed to spawn client thread");
for arg in std::env::args() {
if let Some(arg) = arg.strip_prefix("--cycle_sleep").or(arg.strip_prefix("-c")) {
if let Ok(nanos) = arg.trim().parse::<u64>() {
SLEEP_NANOS.store(nanos, Ordering::Relaxed); SLEEP_NANOS.store(nanos, Ordering::Relaxed);
}else{ } else {
eprintln!("cycle sleep(ns) failed to parse"); eprintln!("cycle sleep(ns) failed to parse");
} }
} }
@ -73,22 +79,19 @@ pub extern "C" fn ffi_get_btn() -> u32 {
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn ffi_set_outputs(led: u32, seg0: u32, seg1: u32, seg2: u32, seg3: u32) { pub extern "C" fn ffi_set_outputs(led: u32, segv: u32, segs: u32) {
let o_led = STATE.led.swap(led, Ordering::Relaxed) != led; let o_led = STATE.led.swap(led, Ordering::Relaxed) != led;
let o_seg0 = STATE.segs[0].swap(seg0, Ordering::Relaxed) != seg0; let mut to_set = o_led as u32;
let o_seg1 = STATE.segs[1].swap(seg1, Ordering::Relaxed) != seg1;
let o_seg2 = STATE.segs[2].swap(seg2, Ordering::Relaxed) != seg2;
let o_seg3 = STATE.segs[3].swap(seg3, Ordering::Relaxed) != seg3;
let to_set = (o_led as u8) << 0 if let Some(place) = STATE.segs.get(segs as usize) {
| (o_seg0 as u8) << 1 let seg = place.swap(segv, Ordering::Relaxed) != segv;
| (o_seg1 as u8) << 2 to_set |= (seg as u32 + 1) << segs
| (o_seg2 as u8) << 3 }
| (o_seg3 as u8) << 4;
STATE.updated.fetch_or(to_set, Ordering::Relaxed); STATE.updated.fetch_or(to_set, Ordering::Relaxed);
std::thread::sleep(std::time::Duration::from_nanos(SLEEP_NANOS.load(Ordering::Relaxed))); std::thread::sleep(std::time::Duration::from_nanos(
SLEEP_NANOS.load(Ordering::Relaxed),
));
} }

View file

@ -1,5 +1,8 @@
use std::{ use std::{
collections::HashMap, ffi::OsStr, ops::Deref, path::{Path, PathBuf} collections::HashMap,
ffi::OsStr,
ops::Deref,
path::{Path, PathBuf},
}; };
use tokio::process::{Child, Command}; use tokio::process::{Child, Command};
@ -46,9 +49,7 @@ impl AsRef<Path> for TempDir {
} }
} }
pub async fn copy_and_build( pub async fn copy_and_build(files: HashMap<String, String>) -> HResult<TempDir> {
files: HashMap<String, String>,
) -> 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 {
@ -73,8 +74,7 @@ pub async fn copy_and_build(
Ok(work_dir) Ok(work_dir)
} }
pub async fn build(build: &Path, src: &Path) -> HResult<()> {
pub async fn build(build: &Path, src: &Path) -> HResult<()>{
std::fs::create_dir_all(build)?; std::fs::create_dir_all(build)?;
let embedded_lib_path = build.join("libvhdl_conn.a"); let embedded_lib_path = build.join("libvhdl_conn.a");
let embedded_tb_path = build.join("tb.vhdl"); let embedded_tb_path = build.join("tb.vhdl");
@ -85,7 +85,7 @@ pub async fn build(build: &Path, src: &Path) -> HResult<()>{
cmd.kill_on_drop(true); cmd.kill_on_drop(true);
cmd.args(["-i", "-g", "--std=08"]); cmd.args(["-i", "-g", "--std=08"]);
for file in src.read_dir().unwrap().flatten(){ for file in src.read_dir().unwrap().flatten() {
if Path::new(&file.file_name()).extension() == Some(OsStr::new("vhdl")) { if Path::new(&file.file_name()).extension() == Some(OsStr::new("vhdl")) {
cmd.arg(file.path().canonicalize()?); cmd.arg(file.path().canonicalize()?);
} }
@ -99,7 +99,6 @@ pub async fn build(build: &Path, src: &Path) -> HResult<()>{
cmd.current_dir(build); cmd.current_dir(build);
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(["-m", "--std=08"]); cmd.args(["-m", "--std=08"]);

View file

@ -29,7 +29,10 @@ async fn serve_styles() -> impl IntoResponse {
async fn serve_app_js() -> impl IntoResponse { async fn serve_app_js() -> impl IntoResponse {
( (
[(header::CONTENT_TYPE, "application/javascript; charset=utf-8")], [(
header::CONTENT_TYPE,
"application/javascript; charset=utf-8",
)],
UI_APP_JS, UI_APP_JS,
) )
} }
@ -91,7 +94,8 @@ fn parse_config_from_args() -> Result<Config, String> {
} }
"--help" | "-h" => { "--help" | "-h" => {
return Err( return Err(
"usage: relay [--ip <ip>] [--port <port>] [--update-ms <ms>] [--workspace]".into(), "usage: relay [--ip <ip>] [--port <port>] [--update-ms <ms>] [--workspace]"
.into(),
); );
} }
_ => { _ => {
@ -116,18 +120,12 @@ async fn main() {
}; };
let update_interval = Duration::from_millis(cfg.update_ms); let update_interval = Duration::from_millis(cfg.update_ms);
let mut app = Router::new() let mut app =
.route( Router::new()
"/", .route("/", get(move || async move { serve_index().await }))
get(move || {
async move { serve_index().await }
}),
)
.route( .route(
"/index.html", "/index.html",
get(move || { get(move || async move { serve_index().await }),
async move { serve_index().await }
}),
) )
.route("/styles.css", get(serve_styles)) .route("/styles.css", get(serve_styles))
.route("/app.js", get(serve_app_js)) .route("/app.js", get(serve_app_js))
@ -183,10 +181,7 @@ pub enum ServerMsg<'a> {
Start, Start,
Stop, Stop,
Led(u32), Led(u32),
Seg0(u32), Seg { value: u32, index: u32 },
Seg1(u32),
Seg2(u32),
Seg3(u32),
} }
pub type HResult<T> = Result<T, Box<dyn std::error::Error + Sync + Send>>; pub type HResult<T> = Result<T, Box<dyn std::error::Error + Sync + Send>>;

View file

@ -1,12 +1,9 @@
use axum::{ use axum::extract::ws::{Message, WebSocket};
extract::ws::{Message, WebSocket}, use futures_util::{SinkExt, StreamExt};
};
use futures_util::{
SinkExt, StreamExt,
};
use std::{collections::HashMap, time::Duration}; use std::{collections::HashMap, time::Duration};
use tokio::{ use tokio::{
io::{AsyncBufReadExt, BufReader}, time::Instant, io::{AsyncBufReadExt, BufReader},
time::Instant,
}; };
use crate::{ClientMsg, HResult, ServerMsg, build, run}; use crate::{ClientMsg, HResult, ServerMsg, build, run};
@ -22,21 +19,24 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
return; return;
}; };
let artifact_dir = match build::copy_and_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;
return; return;
}, }
}; };
let mut process = match run::run(&artifact_dir).await{ let mut process = match run::run(&artifact_dir).await {
Ok(process) => process, Ok(process) => process,
Err(err) => { Err(err) => {
_ = sender.send(Message::Text(format!("Failed to run: {err}").into())).await; _ = sender
.send(Message::Text(format!("Failed to run: {err}").into()))
.await;
return; return;
}, }
}; };
let mut sout = BufReader::new(process.stdout).lines(); let mut sout = BufReader::new(process.stdout).lines();
let mut serr = BufReader::new(process.stderr).lines(); let mut serr = BufReader::new(process.stderr).lines();
@ -89,14 +89,12 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
Ok(Some(line)) => { Ok(Some(line)) => {
let msg = if let Some(repr) = line.strip_prefix("led="){ let msg = if let Some(repr) = line.strip_prefix("led="){
ServerMsg::Led(repr.parse().unwrap_or(0)) ServerMsg::Led(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg0="){ }else if let Some(repr) = line.strip_prefix("seg=")
ServerMsg::Seg0(repr.parse().unwrap_or(0)) && let Some((val, idx)) = repr.split_once(";") {
}else if let Some(repr) = line.strip_prefix("seg1="){ ServerMsg::Seg{
ServerMsg::Seg1(repr.parse().unwrap_or(0)) value: val.parse().unwrap_or(0),
}else if let Some(repr) = line.strip_prefix("seg2="){ index: idx.parse().unwrap_or(0)
ServerMsg::Seg2(repr.parse().unwrap_or(0)) }
}else if let Some(repr) = line.strip_prefix("seg3="){
ServerMsg::Seg3(repr.parse().unwrap_or(0))
}else{ }else{
ServerMsg::Log { ServerMsg::Log {
stream: "stderr", stream: "stderr",
@ -121,10 +119,10 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
Ok(()) Ok(())
}.await; }.await;
match result{ match result {
Ok(_) => {}, Ok(_) => {}
Err(err) => { Err(err) => {
_ = sender.send(Message::Text(format!("{err}").into())).await; _ = sender.send(Message::Text(format!("{err}").into())).await;
}, }
} }
} }

View file

@ -1,5 +1,6 @@
use axum::{ use axum::{
Error, extract::ws::{Message, WebSocket} Error,
extract::ws::{Message, WebSocket},
}; };
use futures_util::{ use futures_util::{
SinkExt, StreamExt, SinkExt, StreamExt,
@ -8,12 +9,12 @@ use futures_util::{
use std::{path::PathBuf, time::Duration}; use std::{path::PathBuf, time::Duration};
use tokio::{ use tokio::{
io::{AsyncBufReadExt, BufReader, Lines}, io::{AsyncBufReadExt, BufReader, Lines},
process::{Child, ChildStderr, ChildStdin, ChildStdout}, time::Instant, process::{Child, ChildStderr, ChildStdin, ChildStdout},
time::Instant,
}; };
use crate::{ClientMsg, ServerMsg, build, run}; use crate::{ClientMsg, ServerMsg, build, run};
struct Process { struct Process {
process: Child, process: Child,
@ -22,7 +23,6 @@ struct Process {
stdin: ChildStdin, stdin: ChildStdin,
} }
struct Handler { struct Handler {
sender: SplitSink<WebSocket, Message>, sender: SplitSink<WebSocket, Message>,
receiver: SplitStream<WebSocket>, receiver: SplitStream<WebSocket>,
@ -36,7 +36,6 @@ struct Handler {
} }
impl Handler { impl Handler {
fn workspace(socket: WebSocket, build: PathBuf, src: PathBuf, refresh_time: Duration) -> Self { fn workspace(socket: WebSocket, build: PathBuf, src: PathBuf, refresh_time: Duration) -> Self {
let (sender, receiver) = socket.split(); let (sender, receiver) = socket.split();
Self { Self {
@ -55,7 +54,12 @@ impl Handler {
stream: "stdout", stream: "stdout",
line: msg.as_ref(), line: msg.as_ref(),
}; };
_ = self.sender.send(Message::Text(serde_json::to_string(&msg).unwrap_or_default().into())).await; _ = self
.sender
.send(Message::Text(
serde_json::to_string(&msg).unwrap_or_default().into(),
))
.await;
} }
pub async fn eprint(&mut self, msg: impl AsRef<str>) { pub async fn eprint(&mut self, msg: impl AsRef<str>) {
@ -64,36 +68,51 @@ impl Handler {
stream: "stderr", stream: "stderr",
line: msg.as_ref(), line: msg.as_ref(),
}; };
_ = self.sender.send(Message::Text(serde_json::to_string(&msg).unwrap_or_default().into())).await; _ = self
.sender
.send(Message::Text(
serde_json::to_string(&msg).unwrap_or_default().into(),
))
.await;
} }
async fn stop_process(&mut self) { async fn stop_process(&mut self) {
self.process = None; self.process = None;
_ = 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) { async fn handle_websocket_msg(&mut self, msg: ClientMsg) {
match msg{ match msg {
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
_ = process.stdin.write_all(format!("sw={}\n", switch).as_bytes()).await; .stdin
.write_all(format!("btn={}\n", buttons).as_bytes())
.await;
_ = process
.stdin
.write_all(format!("sw={}\n", switch).as_bytes())
.await;
}
} }
},
} }
} }
async fn handle_websocket_receive( async fn handle_websocket_receive(&mut self, msg: Option<Result<Message, Error>>) -> bool {
&mut self,
msg: Option<Result<Message, Error>>,
) -> bool {
match msg { match msg {
Some(Ok(Message::Close(_))) => true, Some(Ok(Message::Close(_))) => true,
Some(Ok(Message::Text(msg))) => { Some(Ok(Message::Text(msg))) => {
let msg = match serde_json::from_str(msg.as_str()){ let msg = match serde_json::from_str(msg.as_str()) {
Ok(msg) => msg, Ok(msg) => msg,
Err(err) => { Err(err) => {
self.eprint(format!("Client message error {err}")).await; self.eprint(format!("Client message error {err}")).await;
@ -107,7 +126,7 @@ impl Handler {
Some(Err(err)) => { Some(Err(err)) => {
self.eprint(format!("Client websocket error {err}")).await; self.eprint(format!("Client websocket error {err}")).await;
true true
}, }
None => true, None => true,
} }
} }
@ -115,7 +134,7 @@ impl Handler {
async fn run(&mut self) { 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;
} }
@ -143,14 +162,12 @@ impl Handler {
Ok(Some(line)) => { Ok(Some(line)) => {
let msg = if let Some(repr) = line.strip_prefix("led="){ let msg = if let Some(repr) = line.strip_prefix("led="){
ServerMsg::Led(repr.parse().unwrap_or(0)) ServerMsg::Led(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg0="){ }else if let Some(repr) = line.strip_prefix("seg=")
ServerMsg::Seg0(repr.parse().unwrap_or(0)) && let Some((val, idx)) = repr.split_once(";") {
}else if let Some(repr) = line.strip_prefix("seg1="){ ServerMsg::Seg{
ServerMsg::Seg1(repr.parse().unwrap_or(0)) value: val.parse().unwrap_or(0),
}else if let Some(repr) = line.strip_prefix("seg2="){ index: idx.parse().unwrap_or(0)
ServerMsg::Seg2(repr.parse().unwrap_or(0)) }
}else if let Some(repr) = line.strip_prefix("seg3="){
ServerMsg::Seg3(repr.parse().unwrap_or(0))
}else{ }else{
self.eprint(line).await; self.eprint(line).await;
continue; continue;
@ -170,9 +187,9 @@ impl Handler {
_ = process.stdin.write_all("\n".as_bytes()).await; _ = process.stdin.write_all("\n".as_bytes()).await;
} }
} }
}else{ } else {
let res = self.receiver.next().await; let res = self.receiver.next().await;
if self.handle_websocket_receive(res).await{ if self.handle_websocket_receive(res).await {
break; break;
} }
} }
@ -180,9 +197,8 @@ impl Handler {
} }
async fn run_program(&mut self) { async fn run_program(&mut self) {
match build::build(&self.build_dir, &self.src_dir).await { match build::build(&self.build_dir, &self.src_dir).await {
Ok(_) => {}, Ok(_) => {}
Err(err) => { Err(err) => {
_ = self.eprint(format!("Failed to build: {err}")).await; _ = self.eprint(format!("Failed to build: {err}")).await;
return; return;
@ -200,13 +216,25 @@ 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
self.process = Some( .sender
Process { process: process.child, stderr, stdout, stdin } .send(Message::Text(
) serde_json::to_string(&ServerMsg::Start)
.unwrap_or_default()
.into(),
))
.await;
self.process = Some(Process {
process: process.child,
stderr,
stdout,
stdin,
})
} }
} }
pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) { pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
Handler::workspace(socket, "./target".into(), "./src".into(), refresh_time).run().await; Handler::workspace(socket, "./target".into(), "./src".into(), refresh_time)
.run()
.await;
} }

View file

@ -12,10 +12,8 @@ port (
btn: in std_logic_vector(31 downto 0); btn: in std_logic_vector(31 downto 0);
sw: in std_logic_vector(31 downto 0); sw: in std_logic_vector(31 downto 0);
led: out std_logic_vector(31 downto 0) := (others => '0'); led: out std_logic_vector(31 downto 0) := (others => '0');
seg0: out std_logic_vector(31 downto 0); segv: out std_logic_vector(31 downto 0);
seg1: out std_logic_vector(31 downto 0); segs: out std_logic_vector(31 downto 0)
seg2: out std_logic_vector(31 downto 0);
seg3: out std_logic_vector(31 downto 0)
); );
end circuit; end circuit;
@ -250,6 +248,8 @@ class OutputController {
constructor({ ledRow, hexRow }) { constructor({ ledRow, hexRow }) {
this.ledRow = ledRow; this.ledRow = ledRow;
this.hexRow = hexRow; this.hexRow = hexRow;
// Each backend segN value encodes 4 seven-segment displays, one byte each.
this.segDisplaysPerGroup = 4;
this.ledEls = Array.from(this.ledRow.querySelectorAll(".led[data-bit]")); this.ledEls = Array.from(this.ledRow.querySelectorAll(".led[data-bit]"));
this.segDisplays = Array.from(this.hexRow.querySelectorAll(".sevenSeg[data-digit]")); this.segDisplays = Array.from(this.hexRow.querySelectorAll(".sevenSeg[data-digit]"));
@ -275,37 +275,12 @@ class OutputController {
return true; return true;
} }
let handledSegment = false; if (parsed.seg !== undefined){
const groupBytes = parseSegRowBytes(parsed.seg.value);
// Row mapping: this.setSegGroup(parsed.seg.index, groupBytes);
// seg0 -> displays 0..7
// seg1 -> displays 8..15
// seg2 -> displays 16..23
// seg3 -> displays 24..31
for (let row = 0; row < 4; row += 1) {
const key = `seg${row}`;
if (parsed[key] === undefined) continue;
const rowBytes = parseSegRowBytes(parsed[key]);
if (!rowBytes) continue;
this.setSegRow(row, rowBytes);
handledSegment = true;
}
// Backward-compat path for a single 32-bit value (fills first 4 displays).
if (!handledSegment && parsed.seg !== undefined) {
const bytes = parseHexDigits(Number(parsed.seg) >>> 0);
this.setSegRow(0, bytes);
handledSegment = true;
}
if (handledSegment) {
this.renderAllSegments(); this.renderAllSegments();
return true; return true;
} }
return false;
} }
setLeds(bitsU32) { setLeds(bitsU32) {
@ -329,9 +304,9 @@ class OutputController {
} }
} }
setSegRow(rowIndex, bytes) { setSegGroup(groupIndex, bytes) {
const base = rowIndex * 8; const base = groupIndex * this.segDisplaysPerGroup;
for (let i = 0; i < 8; i += 1) { for (let i = 0; i < this.segDisplaysPerGroup; i += 1) {
const dst = base + i; const dst = base + i;
if (dst >= this.segBytes.length) break; if (dst >= this.segBytes.length) break;
this.segBytes[dst] = bytes[i] & 0xff; this.segBytes[dst] = bytes[i] & 0xff;

View file

@ -12,10 +12,8 @@ architecture sim of tb is
signal btn : std_logic_vector(31 downto 0) := (others => '0'); signal btn : std_logic_vector(31 downto 0) := (others => '0');
signal sw : std_logic_vector(31 downto 0) := (others => '0'); signal sw : std_logic_vector(31 downto 0) := (others => '0');
signal led : std_logic_vector(31 downto 0) := (others => '0'); signal led : std_logic_vector(31 downto 0) := (others => '0');
signal seg0 : std_logic_vector(31 downto 0) := (others => '0'); signal segv : std_logic_vector(31 downto 0) := (others => '0');
signal seg1 : std_logic_vector(31 downto 0) := (others => '0'); signal segs : std_logic_vector(31 downto 0) := (others => '0');
signal seg2 : std_logic_vector(31 downto 0) := (others => '0');
signal seg3 : std_logic_vector(31 downto 0) := (others => '0');
procedure ffi_init is procedure ffi_init is
@ -37,7 +35,7 @@ architecture sim of tb is
end function; end function;
attribute foreign of ffi_get_btn : function is "VHPIDIRECT ffi_get_btn"; attribute foreign of ffi_get_btn : function is "VHPIDIRECT ffi_get_btn";
procedure ffi_set_outputs(led_i: integer; seg0_i: integer; seg1_i: integer; seg2_i: integer; seg3_i: integer) is procedure ffi_set_outputs(led_i: integer; segv_i: integer; segs_i: integer) is
begin begin
end procedure; end procedure;
attribute foreign of ffi_set_outputs : procedure is attribute foreign of ffi_set_outputs : procedure is
@ -63,10 +61,8 @@ begin
btn => btn, btn => btn,
sw => sw, sw => sw,
led => led, led => led,
seg0 => seg0, segv => segv,
seg1 => seg1, segs => segs
seg2 => seg2,
seg3 => seg3
); );
-- 500 Hz clock (2 ms period) -- 500 Hz clock (2 ms period)
@ -91,10 +87,8 @@ begin
ffi_set_outputs( ffi_set_outputs(
to_integer(signed(clean_slv(led))), to_integer(signed(clean_slv(led))),
to_integer(signed(clean_slv(seg0))), to_integer(signed(clean_slv(segv))),
to_integer(signed(clean_slv(seg1))), to_integer(signed(clean_slv(segs)))
to_integer(signed(clean_slv(seg2))),
to_integer(signed(clean_slv(seg3)))
); );
end loop; end loop;
end process; end process;

View file

@ -9,10 +9,8 @@ port (
btn: in std_logic_vector(31 downto 0); btn: in std_logic_vector(31 downto 0);
sw: in std_logic_vector(31 downto 0); sw: in std_logic_vector(31 downto 0);
led: out std_logic_vector(31 downto 0); led: out std_logic_vector(31 downto 0);
seg0: out std_logic_vector(31 downto 0); segv: out std_logic_vector(31 downto 0);
seg1: out std_logic_vector(31 downto 0); segs: out std_logic_vector(31 downto 0)
seg2: out std_logic_vector(31 downto 0);
seg3: out std_logic_vector(31 downto 0)
); );
end circuit; end circuit;
@ -71,11 +69,11 @@ architecture description of circuit is
begin begin
seg0(6 downto 0) <= dec7seg(reg_out(7 downto 4)); segs(6 downto 0) <= dec7seg(reg_out(7 downto 4));
seg0(14 downto 8) <= dec7seg(reg_out(3 downto 0)); segs(14 downto 8) <= dec7seg(reg_out(3 downto 0));
seg0(22 downto 16) <= dec7seg(reg_pc(7 downto 4)); segs(22 downto 16) <= dec7seg(reg_pc(7 downto 4));
seg0(30 downto 24) <= dec7seg(reg_pc(3 downto 0)); segs(30 downto 24) <= dec7seg(reg_pc(3 downto 0));
led(7 downto 0) <= std_logic_vector(reg_a); led(7 downto 0) <= std_logic_vector(reg_a);
led(23 downto 16) <= std_logic_vector(reg_b); led(23 downto 16) <= std_logic_vector(reg_b);