mirror of
https://github.com/ParkerTenBroeck/hdl_sim.git
synced 2026-06-06 21:24:06 -04:00
added verilog support
This commit is contained in:
parent
5746846896
commit
c3a3e89082
20 changed files with 633 additions and 88 deletions
|
|
@ -5,6 +5,8 @@
|
|||
llvmPackages.bintools
|
||||
rustup
|
||||
ghdl-llvm
|
||||
verilator
|
||||
python3
|
||||
];
|
||||
RUSTC_VERSION = "nightly";
|
||||
# https://github.com/rust-lang/rust-bindgen#environment-variables
|
||||
|
|
|
|||
19
examples/example.v
Normal file
19
examples/example.v
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Do not modify the following module interface.
|
||||
module circuit (
|
||||
input wire clk, // 500 Hz, period 2 ms
|
||||
input wire [31:0] btn,
|
||||
input wire [31:0] sw,
|
||||
output reg [31:0] led = 32'h00000000,
|
||||
output wire [31:0] segv,
|
||||
output wire [31:0] segs
|
||||
);
|
||||
reg [31:0] counter = 32'h00000000;
|
||||
|
||||
assign segv = 32'h00000000;
|
||||
assign segs = 32'h00000000;
|
||||
|
||||
always @(posedge clk) begin
|
||||
counter <= counter + 32'd1;
|
||||
led <= counter ^ sw ^ btn;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -12,11 +12,11 @@ pkgs.rustPlatform.buildRustPackage {
|
|||
cargoBuildFlags = [ "-p" "relay" ];
|
||||
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
buildInputs = [ pkgs.ghdl-llvm pkgs.zlib ];
|
||||
buildInputs = [ pkgs.ghdl-llvm pkgs.verilator pkgs.python3 pkgs.zlib ];
|
||||
|
||||
postFixup = ''
|
||||
wrapProgram $out/bin/relay \
|
||||
--prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.ghdl-llvm pkgs.glib.dev ]} \
|
||||
--prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.ghdl-llvm pkgs.verilator pkgs.python3 pkgs.glib.dev ]} \
|
||||
--prefix LIBRARY_PATH : ${pkgs.lib.makeLibraryPath [ pkgs.zlib ]} \
|
||||
--prefix LD_LIBRARY_PATH : ${pkgs.lib.makeLibraryPath [ pkgs.zlib ]}
|
||||
'';
|
||||
|
|
|
|||
35
relay/shim/verilog.c
Normal file
35
relay/shim/verilog.c
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#include <cstdint>
|
||||
#include "Vcircuit.h"
|
||||
#include "verilated.h"
|
||||
|
||||
extern "C" void ffi_init();
|
||||
extern "C" std::uint32_t ffi_get_sw();
|
||||
extern "C" std::uint32_t ffi_get_btn();
|
||||
extern "C" void ffi_set_outputs(std::uint32_t led, std::uint32_t segv, std::uint32_t segs);
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
Verilated::commandArgs(argc, argv);
|
||||
|
||||
Vcircuit top;
|
||||
top.clk = 0;
|
||||
top.btn = 0;
|
||||
top.sw = 0;
|
||||
|
||||
ffi_init();
|
||||
|
||||
while (true) {
|
||||
top.sw = ffi_get_sw();
|
||||
top.btn = ffi_get_btn();
|
||||
|
||||
top.clk = 0;
|
||||
top.eval();
|
||||
ffi_set_outputs(top.led, top.segv, top.segs);
|
||||
|
||||
top.sw = ffi_get_sw();
|
||||
top.btn = ffi_get_btn();
|
||||
|
||||
top.clk = 1;
|
||||
top.eval();
|
||||
ffi_set_outputs(top.led, top.segv, top.segs);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
111
relay/ui/app.js
111
relay/ui/app.js
|
|
@ -1,4 +1,6 @@
|
|||
const LS_KEY_VHDL = "circuit_ui:circuit.vhdl";
|
||||
const LS_KEY_VERILOG = "circuit_ui:circuit.v";
|
||||
const LS_KEY_EDITOR_LANGUAGE = "circuit_ui:editor_language";
|
||||
const LS_KEY_MODE = "circuit_ui:mode";
|
||||
|
||||
const EXAMPLE_VHDL_TEXT = `library ieee;
|
||||
|
|
@ -28,6 +30,27 @@ begin
|
|||
end process;
|
||||
end description;`;
|
||||
|
||||
const EXAMPLE_VERILOG_TEXT = `// Do not modify the following module interface.
|
||||
module circuit (
|
||||
input wire clk, // 500 Hz, period 2 ms
|
||||
input wire [31:0] btn,
|
||||
input wire [31:0] sw,
|
||||
output reg [31:0] led = 32'h00000000,
|
||||
output wire [31:0] segv,
|
||||
output wire [31:0] segs
|
||||
);
|
||||
reg [31:0] counter = 32'h00000000;
|
||||
|
||||
assign segv = 32'h00000000;
|
||||
assign segs = 32'h00000000;
|
||||
|
||||
always @(posedge clk) begin
|
||||
counter <= counter + 32'd1;
|
||||
led <= counter ^ sw ^ btn;
|
||||
end
|
||||
endmodule
|
||||
`;
|
||||
|
||||
function getDomRefs() {
|
||||
return {
|
||||
statusPill: document.getElementById("statusPill"),
|
||||
|
|
@ -37,6 +60,7 @@ function getDomRefs() {
|
|||
|
||||
editorSection: document.getElementById("editorSection"),
|
||||
vhdlEditor: document.getElementById("vhdlEditor"),
|
||||
editorLanguage: document.getElementById("editorLanguage"),
|
||||
lineGutter: document.getElementById("lineGutter"),
|
||||
loadExampleBtn: document.getElementById("loadExampleBtn"),
|
||||
|
||||
|
|
@ -147,9 +171,18 @@ class LogController {
|
|||
}
|
||||
|
||||
class EditorController {
|
||||
constructor({ editorSection, vhdlEditor, lineGutter, loadExampleBtn, enabled, externalFiles }) {
|
||||
constructor({
|
||||
editorSection,
|
||||
vhdlEditor,
|
||||
editorLanguage,
|
||||
lineGutter,
|
||||
loadExampleBtn,
|
||||
enabled,
|
||||
externalFiles,
|
||||
}) {
|
||||
this.editorSection = editorSection;
|
||||
this.vhdlEditor = vhdlEditor;
|
||||
this.editorLanguage = editorLanguage;
|
||||
this.lineGutter = lineGutter;
|
||||
this.loadExampleBtn = loadExampleBtn;
|
||||
|
||||
|
|
@ -157,6 +190,7 @@ class EditorController {
|
|||
this.externalFiles = externalFiles && typeof externalFiles === "object" ? externalFiles : null;
|
||||
this.saveTimer = null;
|
||||
this.initialized = false;
|
||||
this.language = "vhdl";
|
||||
}
|
||||
|
||||
init() {
|
||||
|
|
@ -180,23 +214,32 @@ class EditorController {
|
|||
if (this.initialized) return;
|
||||
this.initialized = true;
|
||||
|
||||
const saved = this.loadFromLocalStorage();
|
||||
this.vhdlEditor.value = saved !== null ? saved : EXAMPLE_VHDL_TEXT;
|
||||
this.language = this.loadLanguageFromLocalStorage();
|
||||
this.editorLanguage.value = this.language;
|
||||
this.vhdlEditor.value = this.getCurrentBuffer();
|
||||
|
||||
this.loadExampleBtn.addEventListener("click", () => {
|
||||
this.vhdlEditor.value = EXAMPLE_VHDL_TEXT;
|
||||
this.saveToLocalStorageDebounced();
|
||||
this.vhdlEditor.value = this.getExampleText(this.language);
|
||||
this.saveCurrentBufferDebounced();
|
||||
this.updateLineNumbers();
|
||||
});
|
||||
|
||||
this.vhdlEditor.addEventListener("input", () => {
|
||||
this.saveToLocalStorageDebounced();
|
||||
this.saveCurrentBufferDebounced();
|
||||
this.updateLineNumbers();
|
||||
});
|
||||
|
||||
this.vhdlEditor.addEventListener("scroll", () => {
|
||||
this.lineGutter.scrollTop = this.vhdlEditor.scrollTop;
|
||||
});
|
||||
|
||||
this.editorLanguage.addEventListener("change", () => {
|
||||
this.saveCurrentBuffer();
|
||||
this.language = this.editorLanguage.value === "verilog" ? "verilog" : "vhdl";
|
||||
this.persistLanguage();
|
||||
this.vhdlEditor.value = this.getCurrentBuffer();
|
||||
this.updateLineNumbers();
|
||||
});
|
||||
}
|
||||
|
||||
getFilesPayload() {
|
||||
|
|
@ -204,9 +247,9 @@ class EditorController {
|
|||
return this.externalFiles ? { ...this.externalFiles } : {};
|
||||
}
|
||||
|
||||
return {
|
||||
"circuit.vhdl": this.vhdlEditor.value ?? "",
|
||||
};
|
||||
return this.language === "verilog"
|
||||
? { "circuit.v": this.vhdlEditor.value ?? "" }
|
||||
: { "circuit.vhdl": this.vhdlEditor.value ?? "" };
|
||||
}
|
||||
|
||||
updateLineNumbers() {
|
||||
|
|
@ -221,27 +264,61 @@ class EditorController {
|
|||
this.lineGutter.textContent = gutterText;
|
||||
}
|
||||
|
||||
saveToLocalStorageDebounced() {
|
||||
saveCurrentBufferDebounced() {
|
||||
if (this.saveTimer) clearTimeout(this.saveTimer);
|
||||
|
||||
this.saveTimer = setTimeout(() => {
|
||||
try {
|
||||
localStorage.setItem(LS_KEY_VHDL, this.vhdlEditor.value ?? "");
|
||||
} catch {
|
||||
// Ignore localStorage failures.
|
||||
}
|
||||
this.saveCurrentBuffer();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
loadFromLocalStorage() {
|
||||
saveCurrentBuffer() {
|
||||
const key = this.language === "verilog" ? LS_KEY_VERILOG : LS_KEY_VHDL;
|
||||
try {
|
||||
const saved = localStorage.getItem(LS_KEY_VHDL);
|
||||
localStorage.setItem(key, this.vhdlEditor.value ?? "");
|
||||
} catch {
|
||||
// Ignore localStorage failures.
|
||||
}
|
||||
}
|
||||
|
||||
loadLanguageFromLocalStorage() {
|
||||
try {
|
||||
const saved = localStorage.getItem(LS_KEY_EDITOR_LANGUAGE);
|
||||
if (saved === "verilog" || saved === "vhdl") return saved;
|
||||
} catch {
|
||||
// Ignore localStorage failures.
|
||||
}
|
||||
return "vhdl";
|
||||
}
|
||||
|
||||
persistLanguage() {
|
||||
try {
|
||||
localStorage.setItem(LS_KEY_EDITOR_LANGUAGE, this.language);
|
||||
} catch {
|
||||
// Ignore localStorage failures.
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentBuffer() {
|
||||
const saved = this.loadBufferFromLocalStorage(this.language);
|
||||
if (saved !== null) return saved;
|
||||
return this.getExampleText(this.language);
|
||||
}
|
||||
|
||||
loadBufferFromLocalStorage(language) {
|
||||
const key = language === "verilog" ? LS_KEY_VERILOG : LS_KEY_VHDL;
|
||||
try {
|
||||
const saved = localStorage.getItem(key);
|
||||
if (saved !== null) return saved;
|
||||
} catch {
|
||||
// Ignore localStorage failures.
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getExampleText(language) {
|
||||
return language === "verilog" ? EXAMPLE_VERILOG_TEXT : EXAMPLE_VHDL_TEXT;
|
||||
}
|
||||
}
|
||||
|
||||
class OutputController {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,13 @@
|
|||
<main class="grid">
|
||||
<section id="editorSection" class="card editor">
|
||||
<div class="cardHeader">
|
||||
<div class="cardTitle">VHDL</div>
|
||||
<div class="cardTitle">HDL</div>
|
||||
<div class="cardActions">
|
||||
<label class="editorLanguageLabel" for="editorLanguage">Language</label>
|
||||
<select id="editorLanguage" class="editorLanguageSelect" aria-label="Select HDL language">
|
||||
<option value="vhdl">VHDL</option>
|
||||
<option value="verilog">Verilog</option>
|
||||
</select>
|
||||
<button id="loadExampleBtn" class="secondary">Load example</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -300,6 +300,26 @@ textarea {
|
|||
background: var(--surface-soft);
|
||||
}
|
||||
|
||||
.editorLanguageLabel {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.editorLanguageSelect {
|
||||
min-width: 104px;
|
||||
padding: 7px 10px;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: 10px;
|
||||
color: var(--text);
|
||||
background: var(--surface-soft);
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.editorLanguageSelect:focus {
|
||||
outline: 2px solid var(--accent-soft);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.lineGutter {
|
||||
width: 54px;
|
||||
margin: 0;
|
||||
|
|
|
|||
101
src_verilog/bcd.v
Normal file
101
src_verilog/bcd.v
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
module bcd (
|
||||
input wire clk,
|
||||
input wire signed [22:0] num, // sfixed(15 downto -7)
|
||||
input wire en,
|
||||
output reg [63:0] seg
|
||||
);
|
||||
|
||||
function [7:0] seg_encode;
|
||||
input [3:0] d;
|
||||
begin
|
||||
case (d)
|
||||
4'd0: seg_encode = 8'b00111111;
|
||||
4'd1: seg_encode = 8'b00000110;
|
||||
4'd2: seg_encode = 8'b01011011;
|
||||
4'd3: seg_encode = 8'b01001111;
|
||||
4'd4: seg_encode = 8'b01100110;
|
||||
4'd5: seg_encode = 8'b01101101;
|
||||
4'd6: seg_encode = 8'b01111101;
|
||||
4'd7: seg_encode = 8'b00000111;
|
||||
4'd8: seg_encode = 8'b01111111;
|
||||
4'd9: seg_encode = 8'b01101111;
|
||||
default: seg_encode = 8'b00000000;
|
||||
endcase
|
||||
end
|
||||
endfunction
|
||||
|
||||
function [3:0] to_bcd_digit;
|
||||
input integer value;
|
||||
integer remainder;
|
||||
begin
|
||||
remainder = value % 10;
|
||||
case (remainder)
|
||||
0: to_bcd_digit = 4'd0;
|
||||
1: to_bcd_digit = 4'd1;
|
||||
2: to_bcd_digit = 4'd2;
|
||||
3: to_bcd_digit = 4'd3;
|
||||
4: to_bcd_digit = 4'd4;
|
||||
5: to_bcd_digit = 4'd5;
|
||||
6: to_bcd_digit = 4'd6;
|
||||
7: to_bcd_digit = 4'd7;
|
||||
8: to_bcd_digit = 4'd8;
|
||||
9: to_bcd_digit = 4'd9;
|
||||
default: to_bcd_digit = 4'd0;
|
||||
endcase
|
||||
end
|
||||
endfunction
|
||||
|
||||
integer scaled_hundredths;
|
||||
integer magnitude;
|
||||
integer frac_hundredths;
|
||||
integer tmp;
|
||||
integer j;
|
||||
reg negative;
|
||||
reg [3:0] digits [0:7];
|
||||
reg [63:0] out_seg;
|
||||
|
||||
always @* begin
|
||||
if (!en) begin
|
||||
seg = 64'b0;
|
||||
end else begin
|
||||
out_seg = 64'b0;
|
||||
|
||||
// num is Q16.7 fixed-point, so value = num / 128.
|
||||
// Round to nearest hundredth.
|
||||
if (num < 0) begin
|
||||
scaled_hundredths = ((num * 100) - 64) / 128;
|
||||
end else begin
|
||||
scaled_hundredths = ((num * 100) + 64) / 128;
|
||||
end
|
||||
|
||||
negative = (scaled_hundredths < 0);
|
||||
|
||||
if (negative) begin
|
||||
magnitude = -scaled_hundredths;
|
||||
end else begin
|
||||
magnitude = scaled_hundredths;
|
||||
end
|
||||
|
||||
frac_hundredths = magnitude % 100;
|
||||
tmp = magnitude;
|
||||
|
||||
for (j = 0; j < 8; j = j + 1) begin
|
||||
digits[j] = to_bcd_digit(tmp);
|
||||
tmp = tmp / 10;
|
||||
end
|
||||
|
||||
for (j = 0; j < 7; j = j + 1) begin
|
||||
out_seg[(7 - j) * 8 +: 8] = seg_encode(digits[j]);
|
||||
end
|
||||
|
||||
out_seg[5 * 8 + 7] = 1'b1;
|
||||
|
||||
if (negative) begin
|
||||
out_seg[6] = 1'b1;
|
||||
end
|
||||
|
||||
seg = out_seg;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
56
src_verilog/example.v
Normal file
56
src_verilog/example.v
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// Do not modify the following module interface.
|
||||
module circuit (
|
||||
input wire clk, // 500 Hz, period 2 ms
|
||||
input wire [31:0] btn,
|
||||
input wire [31:0] sw,
|
||||
output wire [31:0] led,
|
||||
output wire [31:0] segv,
|
||||
output wire [31:0] segs
|
||||
);
|
||||
|
||||
wire [3:0] dig;
|
||||
wire dig_e;
|
||||
wire dot;
|
||||
wire eq;
|
||||
wire [3:0] op;
|
||||
wire op_e;
|
||||
|
||||
wire [63:0] seg_a;
|
||||
wire [2:0] segs_mux;
|
||||
wire signed [22:0] sw_fixed;
|
||||
|
||||
assign led = 32'b0;
|
||||
assign segs = {29'b0, segs_mux};
|
||||
|
||||
// Convert signed 16-bit integer to signed Q16.7 fixed-point.
|
||||
assign sw_fixed = {sw[15:0], 7'b0};
|
||||
|
||||
keypad_input keypad_input_inst (
|
||||
.clk(clk),
|
||||
.keypad(btn[15:8]),
|
||||
.dig(dig),
|
||||
.dig_e(dig_e),
|
||||
.dot(dot),
|
||||
.eq(eq),
|
||||
.op(op),
|
||||
.op_e(op_e)
|
||||
);
|
||||
|
||||
bcd bcd_inst (
|
||||
.clk(clk),
|
||||
.num(sw_fixed),
|
||||
.en(1'b1),
|
||||
.seg(seg_a)
|
||||
);
|
||||
|
||||
seg_plex seg_plex_inst (
|
||||
.clk(clk),
|
||||
.seg0(seg_a),
|
||||
.seg1(64'h0000000000000000),
|
||||
.seg2(64'h0000000000000000),
|
||||
.seg3(64'h0000000000000000),
|
||||
.segv(segv),
|
||||
.segs(segs_mux)
|
||||
);
|
||||
|
||||
endmodule
|
||||
55
src_verilog/keypad_input.v
Normal file
55
src_verilog/keypad_input.v
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
module keypad_input (
|
||||
input wire clk,
|
||||
input wire [7:0] keypad,
|
||||
|
||||
output reg [3:0] dig,
|
||||
output reg dig_e,
|
||||
|
||||
output reg dot,
|
||||
output reg eq,
|
||||
|
||||
output reg [3:0] op,
|
||||
output reg op_e
|
||||
);
|
||||
|
||||
reg [7:0] keypad_curr = 8'b00000000;
|
||||
|
||||
always @(posedge clk or negedge clk) begin
|
||||
if (clk && (keypad != keypad_curr)) begin
|
||||
case (keypad)
|
||||
8'b10001000: begin op <= 4'b0001; op_e <= 1'b1; end
|
||||
8'b10000100: begin eq <= 1'b1; end
|
||||
8'b10000010: begin dot <= 1'b1; end
|
||||
8'b10000001: begin dig <= 4'b0000; dig_e <= 1'b1; end
|
||||
|
||||
8'b01001000: begin op <= 4'b0010; op_e <= 1'b1; end
|
||||
8'b01000100: begin dig <= 4'b0011; dig_e <= 1'b1; end
|
||||
8'b01000010: begin dig <= 4'b0010; dig_e <= 1'b1; end
|
||||
8'b01000001: begin dig <= 4'b0001; dig_e <= 1'b1; end
|
||||
|
||||
8'b00101000: begin op <= 4'b0011; op_e <= 1'b1; end
|
||||
8'b00100100: begin dig <= 4'b0110; dig_e <= 1'b1; end
|
||||
8'b00100010: begin dig <= 4'b0101; dig_e <= 1'b1; end
|
||||
8'b00100001: begin dig <= 4'b0100; dig_e <= 1'b1; end
|
||||
|
||||
8'b00011000: begin op <= 4'b0100; op_e <= 1'b1; end
|
||||
8'b00010100: begin dig <= 4'b1000; dig_e <= 1'b1; end
|
||||
8'b00010010: begin dig <= 4'b1000; dig_e <= 1'b1; end
|
||||
8'b00010001: begin dig <= 4'b0111; dig_e <= 1'b1; end
|
||||
|
||||
default: begin end
|
||||
endcase
|
||||
keypad_curr <= keypad;
|
||||
end
|
||||
|
||||
if (!clk && (keypad != keypad_curr)) begin
|
||||
dig <= 4'b0000;
|
||||
dig_e <= 1'b0;
|
||||
eq <= 1'b0;
|
||||
dot <= 1'b0;
|
||||
op <= 4'b0000;
|
||||
op_e <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
32
src_verilog/seg_plex.v
Normal file
32
src_verilog/seg_plex.v
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
module seg_plex (
|
||||
input wire clk,
|
||||
|
||||
input wire [63:0] seg0,
|
||||
input wire [63:0] seg1,
|
||||
input wire [63:0] seg2,
|
||||
input wire [63:0] seg3,
|
||||
|
||||
output reg [31:0] segv,
|
||||
output reg [2:0] segs
|
||||
);
|
||||
|
||||
reg [2:0] counter = 3'b000;
|
||||
|
||||
always @(posedge clk) begin
|
||||
case (counter)
|
||||
3'd0: segv <= seg0[31:0];
|
||||
3'd1: segv <= seg0[63:32];
|
||||
3'd2: segv <= seg1[31:0];
|
||||
3'd3: segv <= seg1[63:32];
|
||||
3'd4: segv <= seg2[31:0];
|
||||
3'd5: segv <= seg2[63:32];
|
||||
3'd6: segv <= seg3[31:0];
|
||||
3'd7: segv <= seg3[63:32];
|
||||
default: segv <= 32'b0;
|
||||
endcase
|
||||
|
||||
counter <= counter + 3'd1;
|
||||
segs <= counter;
|
||||
end
|
||||
|
||||
endmodule
|
||||
Loading…
Add table
Add a link
Reference in a new issue