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
|
llvmPackages.bintools
|
||||||
rustup
|
rustup
|
||||||
ghdl-llvm
|
ghdl-llvm
|
||||||
|
verilator
|
||||||
|
python3
|
||||||
];
|
];
|
||||||
RUSTC_VERSION = "nightly";
|
RUSTC_VERSION = "nightly";
|
||||||
# https://github.com/rust-lang/rust-bindgen#environment-variables
|
# 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" ];
|
cargoBuildFlags = [ "-p" "relay" ];
|
||||||
|
|
||||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||||
buildInputs = [ pkgs.ghdl-llvm pkgs.zlib ];
|
buildInputs = [ pkgs.ghdl-llvm pkgs.verilator pkgs.python3 pkgs.zlib ];
|
||||||
|
|
||||||
postFixup = ''
|
postFixup = ''
|
||||||
wrapProgram $out/bin/relay \
|
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 LIBRARY_PATH : ${pkgs.lib.makeLibraryPath [ pkgs.zlib ]} \
|
||||||
--prefix LD_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;
|
use crate::HResult;
|
||||||
|
|
||||||
const EMBEDDED_VHDL_UI_LIB: &[u8] = include_bytes!(env!("EMBEDDED_VHDL_CONN_LIB_PATH"));
|
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>> {
|
async fn ensure_ok(child: Child) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let result = child.wait_with_output().await?;
|
let result = child.wait_with_output().await?;
|
||||||
|
|
@ -23,6 +36,137 @@ async fn ensure_ok(child: Child) -> Result<(), Box<dyn std::error::Error + Send
|
||||||
Ok(())
|
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);
|
pub struct TempDir(PathBuf);
|
||||||
impl Drop for TempDir {
|
impl Drop for TempDir {
|
||||||
fn drop(&mut self) {
|
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::*;
|
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 {
|
||||||
|
|
@ -59,7 +208,7 @@ pub async fn copy_and_build(files: HashMap<String, String>) -> HResult<TempDir>
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
let mut work_dir = std::env::temp_dir();
|
let mut work_dir = std::env::temp_dir();
|
||||||
work_dir.push(format!("ghdl-relay-{hash:x?}"));
|
work_dir.push(format!("hdl-relay-{hash:x?}"));
|
||||||
std::fs::create_dir_all(&work_dir)?;
|
std::fs::create_dir_all(&work_dir)?;
|
||||||
let work_dir = TempDir(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)?;
|
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<()> {
|
pub async fn build(build: &Path, src: &Path) -> HResult<BuildArtifact> {
|
||||||
std::fs::create_dir_all(build)?;
|
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_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_lib_path, EMBEDDED_VHDL_UI_LIB)?;
|
||||||
std::fs::write(&embedded_tb_path, EMBEDDED_TB_VHDL)?;
|
let files = source_files(&src)?;
|
||||||
|
match detect_simulator(&files)? {
|
||||||
let mut cmd = Command::new("ghdl");
|
Simulator::Ghdl => build_with_ghdl(&build, &files, &embedded_lib_path).await,
|
||||||
cmd.kill_on_drop(true);
|
Simulator::Verilator => build_with_verilator(&build, &files, &embedded_lib_path).await,
|
||||||
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()?);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
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 serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
net::{IpAddr, SocketAddr},
|
net::{IpAddr, SocketAddr}, path::PathBuf, time::Duration
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod build;
|
pub mod build;
|
||||||
|
|
@ -45,12 +44,13 @@ async fn not_found() -> impl IntoResponse {
|
||||||
(StatusCode::NOT_FOUND, "not found")
|
(StatusCode::NOT_FOUND, "not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Config {
|
struct Config {
|
||||||
ip: IpAddr,
|
ip: IpAddr,
|
||||||
port: u16,
|
port: u16,
|
||||||
update_ms: u64,
|
update_ms: u64,
|
||||||
workspace_ws: bool,
|
workspace_ws: bool,
|
||||||
|
workspace_src: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
|
@ -59,7 +59,8 @@ impl Default for Config {
|
||||||
ip: IpAddr::from([127, 0, 0, 1]),
|
ip: IpAddr::from([127, 0, 0, 1]),
|
||||||
port: 8080,
|
port: 8080,
|
||||||
update_ms: 30,
|
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" => {
|
"--workspace" => {
|
||||||
cfg.workspace_ws = true;
|
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" => {
|
"--help" | "-h" => {
|
||||||
return Err(
|
return Err(
|
||||||
"usage: relay [--ip <ip>] [--port <port>] [--update-ms <ms>] [--workspace]"
|
"usage: relay [--ip <ip>] [--port <port>] [--update-ms <ms>] [--workspace]"
|
||||||
|
|
@ -144,8 +149,9 @@ async fn main() {
|
||||||
"/ws/workspace",
|
"/ws/workspace",
|
||||||
get(move |ws: WebSocketUpgrade| {
|
get(move |ws: WebSocketUpgrade| {
|
||||||
let update_interval = update_interval;
|
let update_interval = update_interval;
|
||||||
|
let workspace_src = cfg.workspace_src;
|
||||||
async move {
|
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 tokio::process::{Child, ChildStderr, ChildStdin, ChildStdout, Command};
|
||||||
|
|
||||||
|
use crate::build::{BuildArtifact, Simulator};
|
||||||
|
|
||||||
pub struct Process {
|
pub struct Process {
|
||||||
pub child: Child,
|
pub child: Child,
|
||||||
pub stdin: ChildStdin,
|
pub stdin: ChildStdin,
|
||||||
|
|
@ -9,7 +11,12 @@ pub struct Process {
|
||||||
pub stderr: ChildStderr,
|
pub stderr: ChildStderr,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(artifact_dir: &Path) -> Result<Process, Box<dyn std::error::Error + Send + Sync>> {
|
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");
|
let mut cmd = Command::new("ghdl");
|
||||||
cmd.args([
|
cmd.args([
|
||||||
"-r",
|
"-r",
|
||||||
|
|
@ -19,6 +26,11 @@ pub async fn run(artifact_dir: &Path) -> Result<Process, Box<dyn std::error::Err
|
||||||
"--unbuffered",
|
"--unbuffered",
|
||||||
"--",
|
"--",
|
||||||
]);
|
]);
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
Simulator::Verilator => Command::new(&artifact.run_target),
|
||||||
|
};
|
||||||
|
|
||||||
cmd.args(std::env::args_os());
|
cmd.args(std::env::args_os());
|
||||||
cmd.current_dir(artifact_dir);
|
cmd.current_dir(artifact_dir);
|
||||||
cmd.kill_on_drop(true);
|
cmd.kill_on_drop(true);
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let artifact_dir = match build::copy_and_build(files).await {
|
let temp_build = match build::copy_and_build(files).await {
|
||||||
Ok(dir) => dir,
|
Ok(build) => build,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
_ = sender
|
_ = sender
|
||||||
.send(Message::Text(format!("Failed to build: {err}").into()))
|
.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,
|
Ok(process) => process,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
_ = sender
|
_ = sender
|
||||||
|
|
@ -71,7 +74,6 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
|
||||||
out = sout.next_line() => {
|
out = sout.next_line() => {
|
||||||
match out{
|
match out{
|
||||||
Ok(Some(line)) => {
|
Ok(Some(line)) => {
|
||||||
|
|
||||||
let msg = ServerMsg::Log {
|
let msg = ServerMsg::Log {
|
||||||
stream: "stdout",
|
stream: "stdout",
|
||||||
line: line.strip_prefix(artifact_prefix).unwrap_or(&line),
|
line: line.strip_prefix(artifact_prefix).unwrap_or(&line),
|
||||||
|
|
|
||||||
|
|
@ -197,15 +197,15 @@ impl Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_program(&mut self) {
|
async fn run_program(&mut self) {
|
||||||
match build::build(&self.build_dir, &self.src_dir).await {
|
let artifact = match build::build(&self.build_dir, &self.src_dir).await {
|
||||||
Ok(_) => {}
|
Ok(artifact) => artifact,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
_ = self.eprint(format!("Failed to build: {err}")).await;
|
_ = self.eprint(format!("Failed to build: {err}")).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let process = match run::run(&self.build_dir).await {
|
let process = match run::run(&self.build_dir, &artifact).await {
|
||||||
Ok(process) => process,
|
Ok(process) => process,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.eprint(format!("Failed to run: {err}")).await;
|
self.eprint(format!("Failed to run: {err}")).await;
|
||||||
|
|
@ -233,8 +233,8 @@ impl Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
|
pub async fn ws_handler(socket: WebSocket, workspace_src: PathBuf, refresh_time: Duration) {
|
||||||
Handler::workspace(socket, "./target".into(), "./src".into(), refresh_time)
|
Handler::workspace(socket, "./target".into(), workspace_src, refresh_time)
|
||||||
.run()
|
.run()
|
||||||
.await;
|
.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_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 LS_KEY_MODE = "circuit_ui:mode";
|
||||||
|
|
||||||
const EXAMPLE_VHDL_TEXT = `library ieee;
|
const EXAMPLE_VHDL_TEXT = `library ieee;
|
||||||
|
|
@ -28,6 +30,27 @@ begin
|
||||||
end process;
|
end process;
|
||||||
end description;`;
|
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() {
|
function getDomRefs() {
|
||||||
return {
|
return {
|
||||||
statusPill: document.getElementById("statusPill"),
|
statusPill: document.getElementById("statusPill"),
|
||||||
|
|
@ -37,6 +60,7 @@ function getDomRefs() {
|
||||||
|
|
||||||
editorSection: document.getElementById("editorSection"),
|
editorSection: document.getElementById("editorSection"),
|
||||||
vhdlEditor: document.getElementById("vhdlEditor"),
|
vhdlEditor: document.getElementById("vhdlEditor"),
|
||||||
|
editorLanguage: document.getElementById("editorLanguage"),
|
||||||
lineGutter: document.getElementById("lineGutter"),
|
lineGutter: document.getElementById("lineGutter"),
|
||||||
loadExampleBtn: document.getElementById("loadExampleBtn"),
|
loadExampleBtn: document.getElementById("loadExampleBtn"),
|
||||||
|
|
||||||
|
|
@ -147,9 +171,18 @@ class LogController {
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditorController {
|
class EditorController {
|
||||||
constructor({ editorSection, vhdlEditor, lineGutter, loadExampleBtn, enabled, externalFiles }) {
|
constructor({
|
||||||
|
editorSection,
|
||||||
|
vhdlEditor,
|
||||||
|
editorLanguage,
|
||||||
|
lineGutter,
|
||||||
|
loadExampleBtn,
|
||||||
|
enabled,
|
||||||
|
externalFiles,
|
||||||
|
}) {
|
||||||
this.editorSection = editorSection;
|
this.editorSection = editorSection;
|
||||||
this.vhdlEditor = vhdlEditor;
|
this.vhdlEditor = vhdlEditor;
|
||||||
|
this.editorLanguage = editorLanguage;
|
||||||
this.lineGutter = lineGutter;
|
this.lineGutter = lineGutter;
|
||||||
this.loadExampleBtn = loadExampleBtn;
|
this.loadExampleBtn = loadExampleBtn;
|
||||||
|
|
||||||
|
|
@ -157,6 +190,7 @@ class EditorController {
|
||||||
this.externalFiles = externalFiles && typeof externalFiles === "object" ? externalFiles : null;
|
this.externalFiles = externalFiles && typeof externalFiles === "object" ? externalFiles : null;
|
||||||
this.saveTimer = null;
|
this.saveTimer = null;
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
|
this.language = "vhdl";
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|
@ -180,23 +214,32 @@ class EditorController {
|
||||||
if (this.initialized) return;
|
if (this.initialized) return;
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
|
||||||
const saved = this.loadFromLocalStorage();
|
this.language = this.loadLanguageFromLocalStorage();
|
||||||
this.vhdlEditor.value = saved !== null ? saved : EXAMPLE_VHDL_TEXT;
|
this.editorLanguage.value = this.language;
|
||||||
|
this.vhdlEditor.value = this.getCurrentBuffer();
|
||||||
|
|
||||||
this.loadExampleBtn.addEventListener("click", () => {
|
this.loadExampleBtn.addEventListener("click", () => {
|
||||||
this.vhdlEditor.value = EXAMPLE_VHDL_TEXT;
|
this.vhdlEditor.value = this.getExampleText(this.language);
|
||||||
this.saveToLocalStorageDebounced();
|
this.saveCurrentBufferDebounced();
|
||||||
this.updateLineNumbers();
|
this.updateLineNumbers();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.vhdlEditor.addEventListener("input", () => {
|
this.vhdlEditor.addEventListener("input", () => {
|
||||||
this.saveToLocalStorageDebounced();
|
this.saveCurrentBufferDebounced();
|
||||||
this.updateLineNumbers();
|
this.updateLineNumbers();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.vhdlEditor.addEventListener("scroll", () => {
|
this.vhdlEditor.addEventListener("scroll", () => {
|
||||||
this.lineGutter.scrollTop = this.vhdlEditor.scrollTop;
|
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() {
|
getFilesPayload() {
|
||||||
|
|
@ -204,9 +247,9 @@ class EditorController {
|
||||||
return this.externalFiles ? { ...this.externalFiles } : {};
|
return this.externalFiles ? { ...this.externalFiles } : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return this.language === "verilog"
|
||||||
"circuit.vhdl": this.vhdlEditor.value ?? "",
|
? { "circuit.v": this.vhdlEditor.value ?? "" }
|
||||||
};
|
: { "circuit.vhdl": this.vhdlEditor.value ?? "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLineNumbers() {
|
updateLineNumbers() {
|
||||||
|
|
@ -221,27 +264,61 @@ class EditorController {
|
||||||
this.lineGutter.textContent = gutterText;
|
this.lineGutter.textContent = gutterText;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveToLocalStorageDebounced() {
|
saveCurrentBufferDebounced() {
|
||||||
if (this.saveTimer) clearTimeout(this.saveTimer);
|
if (this.saveTimer) clearTimeout(this.saveTimer);
|
||||||
|
|
||||||
this.saveTimer = setTimeout(() => {
|
this.saveTimer = setTimeout(() => {
|
||||||
try {
|
this.saveCurrentBuffer();
|
||||||
localStorage.setItem(LS_KEY_VHDL, this.vhdlEditor.value ?? "");
|
|
||||||
} catch {
|
|
||||||
// Ignore localStorage failures.
|
|
||||||
}
|
|
||||||
}, 250);
|
}, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFromLocalStorage() {
|
saveCurrentBuffer() {
|
||||||
|
const key = this.language === "verilog" ? LS_KEY_VERILOG : LS_KEY_VHDL;
|
||||||
try {
|
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;
|
if (saved !== null) return saved;
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore localStorage failures.
|
// Ignore localStorage failures.
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getExampleText(language) {
|
||||||
|
return language === "verilog" ? EXAMPLE_VERILOG_TEXT : EXAMPLE_VHDL_TEXT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OutputController {
|
class OutputController {
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,13 @@
|
||||||
<main class="grid">
|
<main class="grid">
|
||||||
<section id="editorSection" class="card editor">
|
<section id="editorSection" class="card editor">
|
||||||
<div class="cardHeader">
|
<div class="cardHeader">
|
||||||
<div class="cardTitle">VHDL</div>
|
<div class="cardTitle">HDL</div>
|
||||||
<div class="cardActions">
|
<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>
|
<button id="loadExampleBtn" class="secondary">Load example</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,26 @@ textarea {
|
||||||
background: var(--surface-soft);
|
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 {
|
.lineGutter {
|
||||||
width: 54px;
|
width: 54px;
|
||||||
margin: 0;
|
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