diff --git a/default.nix b/default.nix index 4f73d5d..348521b 100644 --- a/default.nix +++ b/default.nix @@ -5,6 +5,8 @@ llvmPackages.bintools rustup ghdl-llvm + verilator + python3 ]; RUSTC_VERSION = "nightly"; # https://github.com/rust-lang/rust-bindgen#environment-variables diff --git a/examples/example.v b/examples/example.v new file mode 100644 index 0000000..96d53a8 --- /dev/null +++ b/examples/example.v @@ -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 diff --git a/relay.nix b/relay.nix index 3a88d9e..db1f7b8 100644 --- a/relay.nix +++ b/relay.nix @@ -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 ]} ''; diff --git a/rtl/tb.vhdl b/relay/shim/shim.vhdl similarity index 100% rename from rtl/tb.vhdl rename to relay/shim/shim.vhdl diff --git a/relay/shim/verilog.c b/relay/shim/verilog.c new file mode 100644 index 0000000..7cb8b95 --- /dev/null +++ b/relay/shim/verilog.c @@ -0,0 +1,35 @@ +#include +#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); + } +} \ No newline at end of file diff --git a/relay/src/build.rs b/relay/src/build.rs index 5df5e99..14f59db 100644 --- a/relay/src/build.rs +++ b/relay/src/build.rs @@ -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> { let result = child.wait_with_output().await?; @@ -23,6 +36,137 @@ async fn ensure_ok(child: Child) -> Result<(), Box HResult> { + 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 { + 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 { + 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 { + 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 for TempDir { } } -pub async fn copy_and_build(files: HashMap) -> HResult { +pub struct TempBuild { + pub dir: TempDir, + pub artifact: BuildArtifact, +} + +pub async fn copy_and_build(files: HashMap) -> HResult { 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) -> HResult 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) -> HResult 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 { + 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(()) } diff --git a/relay/src/main.rs b/relay/src/main.rs index cf78c22..ba44a60 100644 --- a/relay/src/main.rs +++ b/relay/src/main.rs @@ -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 { "--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 ] [--port ] [--update-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)) } }), ); diff --git a/relay/src/run.rs b/relay/src/run.rs index 08c898b..ccf7b63 100644 --- a/relay/src/run.rs +++ b/relay/src/run.rs @@ -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> { - 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> { + 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); diff --git a/relay/src/uploaded.rs b/relay/src/uploaded.rs index c5703bd..0571905 100644 --- a/relay/src/uploaded.rs +++ b/relay/src/uploaded.rs @@ -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; diff --git a/relay/src/workspace.rs b/relay/src/workspace.rs index f99b540..e609f9d 100644 --- a/relay/src/workspace.rs +++ b/relay/src/workspace.rs @@ -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; } diff --git a/relay/ui/app.js b/relay/ui/app.js index 807c658..cda0a1e 100644 --- a/relay/ui/app.js +++ b/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 { diff --git a/relay/ui/index.html b/relay/ui/index.html index 13537d1..589eb6e 100644 --- a/relay/ui/index.html +++ b/relay/ui/index.html @@ -10,8 +10,13 @@
-
VHDL
+
HDL
+ +
diff --git a/relay/ui/styles.css b/relay/ui/styles.css index 800e2d3..9d2183c 100644 --- a/relay/ui/styles.css +++ b/relay/ui/styles.css @@ -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; diff --git a/src_verilog/bcd.v b/src_verilog/bcd.v new file mode 100644 index 0000000..3154870 --- /dev/null +++ b/src_verilog/bcd.v @@ -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 diff --git a/src_verilog/example.v b/src_verilog/example.v new file mode 100644 index 0000000..5dfcfc3 --- /dev/null +++ b/src_verilog/example.v @@ -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 diff --git a/src_verilog/keypad_input.v b/src_verilog/keypad_input.v new file mode 100644 index 0000000..8999551 --- /dev/null +++ b/src_verilog/keypad_input.v @@ -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 diff --git a/src_verilog/seg_plex.v b/src_verilog/seg_plex.v new file mode 100644 index 0000000..cad57f7 --- /dev/null +++ b/src_verilog/seg_plex.v @@ -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 diff --git a/src/cpu.vhdl b/src_vhdl/cpu.vhdl similarity index 100% rename from src/cpu.vhdl rename to src_vhdl/cpu.vhdl diff --git a/src/dram.vhdl b/src_vhdl/dram.vhdl similarity index 100% rename from src/dram.vhdl rename to src_vhdl/dram.vhdl diff --git a/src/iram.vhdl b/src_vhdl/iram.vhdl similarity index 100% rename from src/iram.vhdl rename to src_vhdl/iram.vhdl