mirror of
https://github.com/ParkerTenBroeck/hdl_sim.git
synced 2026-06-07 05:28:45 -04:00
added verilog support
This commit is contained in:
parent
5746846896
commit
c3a3e89082
20 changed files with 633 additions and 88 deletions
|
|
@ -9,7 +9,20 @@ use tokio::process::{Child, Command};
|
|||
use crate::HResult;
|
||||
|
||||
const EMBEDDED_VHDL_UI_LIB: &[u8] = include_bytes!(env!("EMBEDDED_VHDL_CONN_LIB_PATH"));
|
||||
const EMBEDDED_TB_VHDL: &str = include_str!("../../rtl/tb.vhdl");
|
||||
const EMBEDDED_TB_VHDL: &str = include_str!("../shim/shim.vhdl");
|
||||
const EMBEDDED_TB_VERILATOR: &str = include_str!("../shim/verilog.c");
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Simulator {
|
||||
Ghdl,
|
||||
Verilator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BuildArtifact {
|
||||
pub simulator: Simulator,
|
||||
pub run_target: PathBuf,
|
||||
}
|
||||
|
||||
async fn ensure_ok(child: Child) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let result = child.wait_with_output().await?;
|
||||
|
|
@ -23,6 +36,137 @@ async fn ensure_ok(child: Child) -> Result<(), Box<dyn std::error::Error + Send
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn source_files(src: &Path) -> HResult<Vec<PathBuf>> {
|
||||
let mut files = Vec::new();
|
||||
for file in src.read_dir()?.flatten() {
|
||||
let path = file.path();
|
||||
if path.is_file() {
|
||||
files.push(path.canonicalize()?);
|
||||
}
|
||||
}
|
||||
files.sort();
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
fn detect_simulator(files: &[PathBuf]) -> HResult<Simulator> {
|
||||
let mut has_vhdl = false;
|
||||
let mut has_verilog = false;
|
||||
|
||||
for file in files {
|
||||
match file.extension().and_then(OsStr::to_str) {
|
||||
Some("vhdl" | "vhd") => has_vhdl = true,
|
||||
Some("v" | "sv") => has_verilog = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match (has_vhdl, has_verilog) {
|
||||
(true, false) => Ok(Simulator::Ghdl),
|
||||
(false, true) => Ok(Simulator::Verilator),
|
||||
(true, true) => Err("mixed VHDL and Verilog sources are not supported yet".into()),
|
||||
(false, false) => Err("no VHDL or Verilog source files found".into()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_with_ghdl(
|
||||
build: &Path,
|
||||
files: &[PathBuf],
|
||||
embedded_lib_path: &Path,
|
||||
) -> HResult<BuildArtifact> {
|
||||
let embedded_tb_path = build.join("tb.vhdl");
|
||||
std::fs::write(&embedded_tb_path, EMBEDDED_TB_VHDL)?;
|
||||
|
||||
let mut cmd = Command::new("ghdl");
|
||||
cmd.kill_on_drop(true);
|
||||
cmd.args(["-i", "-g", "--std=08"]);
|
||||
|
||||
for file in files {
|
||||
if matches!(
|
||||
file.extension().and_then(OsStr::to_str),
|
||||
Some("vhdl" | "vhd")
|
||||
) {
|
||||
cmd.arg(file);
|
||||
}
|
||||
}
|
||||
cmd.arg(&embedded_tb_path.canonicalize()?);
|
||||
|
||||
cmd.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
|
||||
cmd.current_dir(build);
|
||||
ensure_ok(cmd.spawn()?).await?;
|
||||
|
||||
let mut cmd = Command::new("ghdl");
|
||||
cmd.kill_on_drop(true);
|
||||
cmd.args(["-m", "--std=08"]);
|
||||
cmd.arg(format!(
|
||||
"-Wl,{}",
|
||||
embedded_lib_path.canonicalize()?.display()
|
||||
));
|
||||
cmd.arg("tb");
|
||||
cmd.current_dir(build);
|
||||
cmd.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
ensure_ok(cmd.spawn()?).await?;
|
||||
|
||||
Ok(BuildArtifact {
|
||||
simulator: Simulator::Ghdl,
|
||||
run_target: build.join("tb"),
|
||||
})
|
||||
}
|
||||
|
||||
async fn build_with_verilator(
|
||||
build: &Path,
|
||||
files: &[PathBuf],
|
||||
embedded_lib_path: &Path,
|
||||
) -> HResult<BuildArtifact> {
|
||||
let embedded_tb_path = build.join("tb.cpp");
|
||||
let obj_dir = build.join("obj_dir");
|
||||
std::fs::write(&embedded_tb_path, EMBEDDED_TB_VERILATOR)?;
|
||||
std::fs::create_dir_all(&obj_dir)?;
|
||||
|
||||
let mut cmd = Command::new("verilator");
|
||||
cmd.kill_on_drop(true);
|
||||
cmd.args(["--cc", "--exe", "--top-module", "circuit", "--Mdir"]);
|
||||
cmd.arg(&obj_dir);
|
||||
cmd.args(["-o", "tb"]);
|
||||
cmd.args([
|
||||
"-LDFLAGS",
|
||||
&embedded_lib_path.canonicalize()?.display().to_string(),
|
||||
]);
|
||||
cmd.arg(&embedded_tb_path);
|
||||
|
||||
for file in files {
|
||||
if matches!(file.extension().and_then(OsStr::to_str), Some("v" | "sv")) {
|
||||
cmd.arg(file);
|
||||
}
|
||||
}
|
||||
|
||||
cmd.current_dir(build);
|
||||
cmd.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
ensure_ok(cmd.spawn()?).await?;
|
||||
|
||||
let mut cmd = Command::new("make");
|
||||
cmd.kill_on_drop(true);
|
||||
cmd.args(["-C"]);
|
||||
cmd.arg(&obj_dir);
|
||||
cmd.args(["-f", "Vcircuit.mk", "-j", "1"]);
|
||||
cmd.current_dir(build);
|
||||
cmd.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
ensure_ok(cmd.spawn()?).await?;
|
||||
|
||||
Ok(BuildArtifact {
|
||||
simulator: Simulator::Verilator,
|
||||
run_target: obj_dir.join("tb"),
|
||||
})
|
||||
}
|
||||
|
||||
pub struct TempDir(PathBuf);
|
||||
impl Drop for TempDir {
|
||||
fn drop(&mut self) {
|
||||
|
|
@ -49,7 +193,12 @@ impl AsRef<Path> for TempDir {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn copy_and_build(files: HashMap<String, String>) -> HResult<TempDir> {
|
||||
pub struct TempBuild {
|
||||
pub dir: TempDir,
|
||||
pub artifact: BuildArtifact,
|
||||
}
|
||||
|
||||
pub async fn copy_and_build(files: HashMap<String, String>) -> HResult<TempBuild> {
|
||||
use std::hash::*;
|
||||
let mut hasher = std::hash::DefaultHasher::default();
|
||||
for (key, value) in &files {
|
||||
|
|
@ -59,7 +208,7 @@ pub async fn copy_and_build(files: HashMap<String, String>) -> HResult<TempDir>
|
|||
let hash = hasher.finish();
|
||||
|
||||
let mut work_dir = std::env::temp_dir();
|
||||
work_dir.push(format!("ghdl-relay-{hash:x?}"));
|
||||
work_dir.push(format!("hdl-relay-{hash:x?}"));
|
||||
std::fs::create_dir_all(&work_dir)?;
|
||||
let work_dir = TempDir(work_dir);
|
||||
|
||||
|
|
@ -69,49 +218,23 @@ pub async fn copy_and_build(files: HashMap<String, String>) -> HResult<TempDir>
|
|||
std::fs::write(path, contents)?;
|
||||
}
|
||||
|
||||
build(&work_dir, &work_dir).await?;
|
||||
let artifact = build(&work_dir, &work_dir).await?;
|
||||
|
||||
Ok(work_dir)
|
||||
Ok(TempBuild {
|
||||
dir: work_dir,
|
||||
artifact,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn build(build: &Path, src: &Path) -> HResult<()> {
|
||||
std::fs::create_dir_all(build)?;
|
||||
pub async fn build(build: &Path, src: &Path) -> HResult<BuildArtifact> {
|
||||
let build = build.canonicalize()?;
|
||||
let src = src.canonicalize()?;
|
||||
std::fs::create_dir_all(&build)?;
|
||||
let embedded_lib_path = build.join("libvhdl_conn.a");
|
||||
let embedded_tb_path = build.join("tb.vhdl");
|
||||
std::fs::write(&embedded_lib_path, EMBEDDED_VHDL_UI_LIB)?;
|
||||
std::fs::write(&embedded_tb_path, EMBEDDED_TB_VHDL)?;
|
||||
|
||||
let mut cmd = Command::new("ghdl");
|
||||
cmd.kill_on_drop(true);
|
||||
cmd.args(["-i", "-g", "--std=08"]);
|
||||
|
||||
for file in src.read_dir().unwrap().flatten() {
|
||||
if Path::new(&file.file_name()).extension() == Some(OsStr::new("vhdl")) {
|
||||
cmd.arg(file.path().canonicalize()?);
|
||||
}
|
||||
let files = source_files(&src)?;
|
||||
match detect_simulator(&files)? {
|
||||
Simulator::Ghdl => build_with_ghdl(&build, &files, &embedded_lib_path).await,
|
||||
Simulator::Verilator => build_with_verilator(&build, &files, &embedded_lib_path).await,
|
||||
}
|
||||
cmd.arg(&embedded_tb_path.canonicalize()?);
|
||||
|
||||
cmd.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
|
||||
cmd.current_dir(build);
|
||||
ensure_ok(cmd.spawn()?).await?;
|
||||
|
||||
let mut cmd = Command::new("ghdl");
|
||||
cmd.kill_on_drop(true);
|
||||
cmd.args(["-m", "--std=08"]);
|
||||
cmd.arg(format!(
|
||||
"-Wl,{}",
|
||||
embedded_lib_path.canonicalize()?.display()
|
||||
));
|
||||
cmd.arg("tb");
|
||||
cmd.current_dir(build);
|
||||
cmd.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
ensure_ok(cmd.spawn()?).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ use axum::{
|
|||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
time::Duration,
|
||||
net::{IpAddr, SocketAddr}, path::PathBuf, time::Duration
|
||||
};
|
||||
|
||||
pub mod build;
|
||||
|
|
@ -45,12 +44,13 @@ async fn not_found() -> impl IntoResponse {
|
|||
(StatusCode::NOT_FOUND, "not found")
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
struct Config {
|
||||
ip: IpAddr,
|
||||
port: u16,
|
||||
update_ms: u64,
|
||||
workspace_ws: bool,
|
||||
workspace_src: PathBuf,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
|
@ -59,7 +59,8 @@ impl Default for Config {
|
|||
ip: IpAddr::from([127, 0, 0, 1]),
|
||||
port: 8080,
|
||||
update_ms: 30,
|
||||
workspace_ws: true,
|
||||
workspace_ws: false,
|
||||
workspace_src: "./src".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,6 +93,10 @@ fn parse_config_from_args() -> Result<Config, String> {
|
|||
"--workspace" => {
|
||||
cfg.workspace_ws = true;
|
||||
}
|
||||
"--workspace-src" => {
|
||||
cfg.workspace_ws = true;
|
||||
cfg.workspace_src = args.next().ok_or("missing value for --workspace-src")?.into();
|
||||
}
|
||||
"--help" | "-h" => {
|
||||
return Err(
|
||||
"usage: relay [--ip <ip>] [--port <port>] [--update-ms <ms>] [--workspace]"
|
||||
|
|
@ -144,8 +149,9 @@ async fn main() {
|
|||
"/ws/workspace",
|
||||
get(move |ws: WebSocketUpgrade| {
|
||||
let update_interval = update_interval;
|
||||
let workspace_src = cfg.workspace_src;
|
||||
async move {
|
||||
ws.on_upgrade(move |socket| workspace::ws_handler(socket, update_interval))
|
||||
ws.on_upgrade(move |socket| workspace::ws_handler(socket, workspace_src.clone(), update_interval))
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ use std::path::Path;
|
|||
|
||||
use tokio::process::{Child, ChildStderr, ChildStdin, ChildStdout, Command};
|
||||
|
||||
use crate::build::{BuildArtifact, Simulator};
|
||||
|
||||
pub struct Process {
|
||||
pub child: Child,
|
||||
pub stdin: ChildStdin,
|
||||
|
|
@ -9,16 +11,26 @@ pub struct Process {
|
|||
pub stderr: ChildStderr,
|
||||
}
|
||||
|
||||
pub async fn run(artifact_dir: &Path) -> Result<Process, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut cmd = Command::new("ghdl");
|
||||
cmd.args([
|
||||
"-r",
|
||||
"--std=08",
|
||||
"tb",
|
||||
"--stop-delta=4294967296",
|
||||
"--unbuffered",
|
||||
"--",
|
||||
]);
|
||||
pub async fn run(
|
||||
artifact_dir: &Path,
|
||||
artifact: &BuildArtifact,
|
||||
) -> Result<Process, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut cmd = match artifact.simulator {
|
||||
Simulator::Ghdl => {
|
||||
let mut cmd = Command::new("ghdl");
|
||||
cmd.args([
|
||||
"-r",
|
||||
"--std=08",
|
||||
"tb",
|
||||
"--stop-delta=4294967296",
|
||||
"--unbuffered",
|
||||
"--",
|
||||
]);
|
||||
cmd
|
||||
}
|
||||
Simulator::Verilator => Command::new(&artifact.run_target),
|
||||
};
|
||||
|
||||
cmd.args(std::env::args_os());
|
||||
cmd.current_dir(artifact_dir);
|
||||
cmd.kill_on_drop(true);
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
|
|||
return;
|
||||
};
|
||||
|
||||
let artifact_dir = match build::copy_and_build(files).await {
|
||||
Ok(dir) => dir,
|
||||
let temp_build = match build::copy_and_build(files).await {
|
||||
Ok(build) => build,
|
||||
Err(err) => {
|
||||
_ = sender
|
||||
.send(Message::Text(format!("Failed to build: {err}").into()))
|
||||
|
|
@ -29,7 +29,10 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
|
|||
}
|
||||
};
|
||||
|
||||
let mut process = match run::run(&artifact_dir).await {
|
||||
let artifact_dir = temp_build.dir;
|
||||
let artifact = temp_build.artifact;
|
||||
|
||||
let mut process = match run::run(&artifact_dir, &artifact).await {
|
||||
Ok(process) => process,
|
||||
Err(err) => {
|
||||
_ = sender
|
||||
|
|
@ -71,7 +74,6 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
|
|||
out = sout.next_line() => {
|
||||
match out{
|
||||
Ok(Some(line)) => {
|
||||
|
||||
let msg = ServerMsg::Log {
|
||||
stream: "stdout",
|
||||
line: line.strip_prefix(artifact_prefix).unwrap_or(&line),
|
||||
|
|
@ -114,7 +116,7 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
|
|||
print_deadline += refresh_time;
|
||||
process.stdin.write_all("\n".as_bytes()).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}.await;
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ impl Handler {
|
|||
Ok(Some(line)) => {
|
||||
let msg = if let Some(repr) = line.strip_prefix("led="){
|
||||
ServerMsg::Led(repr.parse().unwrap_or(0))
|
||||
}else if let Some(repr) = line.strip_prefix("seg=")
|
||||
}else if let Some(repr) = line.strip_prefix("seg=")
|
||||
&& let Some((val, idx)) = repr.split_once(";") {
|
||||
ServerMsg::Seg{
|
||||
value: val.parse().unwrap_or(0),
|
||||
|
|
@ -197,15 +197,15 @@ impl Handler {
|
|||
}
|
||||
|
||||
async fn run_program(&mut self) {
|
||||
match build::build(&self.build_dir, &self.src_dir).await {
|
||||
Ok(_) => {}
|
||||
let artifact = match build::build(&self.build_dir, &self.src_dir).await {
|
||||
Ok(artifact) => artifact,
|
||||
Err(err) => {
|
||||
_ = self.eprint(format!("Failed to build: {err}")).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let process = match run::run(&self.build_dir).await {
|
||||
let process = match run::run(&self.build_dir, &artifact).await {
|
||||
Ok(process) => process,
|
||||
Err(err) => {
|
||||
self.eprint(format!("Failed to run: {err}")).await;
|
||||
|
|
@ -233,8 +233,8 @@ impl Handler {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
|
||||
Handler::workspace(socket, "./target".into(), "./src".into(), refresh_time)
|
||||
pub async fn ws_handler(socket: WebSocket, workspace_src: PathBuf, refresh_time: Duration) {
|
||||
Handler::workspace(socket, "./target".into(), workspace_src, refresh_time)
|
||||
.run()
|
||||
.await;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue