diff --git a/Cargo.toml b/Cargo.toml index fac0987c..a500967e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "rust-hdl-ok-core", "rust-hdl-fpga-support", "rust-hdl-bsp-alchitry-cu", + "rust-hdl-bsp-tangnano9k", "rust-hdl-bsp-ok-xem6010", "rust-hdl-bsp-ok-xem7010", "rust-hdl-x", diff --git a/rust-hdl-bsp-gowin/Cargo.toml b/rust-hdl-bsp-gowin/Cargo.toml new file mode 100644 index 00000000..381e0543 --- /dev/null +++ b/rust-hdl-bsp-gowin/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rust-hdl-bsp-gowin" +version = "0.46.0" +edition = "2024" +license = "MIT" +description = "Support crate for RustHDL - provides Board Support Package for the Gowin Tangnano9k board" +homepage = "https://github.com/samitbasu/rust-hdl" +repository = "https://github.com/samitbasu/rust-hdl" +keywords = ["fpga", "verilog", "hardware"] +authors = ["Samit Basu "] + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rust-hdl = { version = "0.46.0", path = "../rust-hdl", features = ["fpga"] } diff --git a/rust-hdl-bsp-gowin/src/lib.rs b/rust-hdl-bsp-gowin/src/lib.rs new file mode 100644 index 00000000..52de7096 --- /dev/null +++ b/rust-hdl-bsp-gowin/src/lib.rs @@ -0,0 +1 @@ +pub mod tangnano9k; diff --git a/rust-hdl-bsp-gowin/src/tangnano9k/mod.rs b/rust-hdl-bsp-gowin/src/tangnano9k/mod.rs new file mode 100644 index 00000000..5fe82f97 --- /dev/null +++ b/rust-hdl-bsp-gowin/src/tangnano9k/mod.rs @@ -0,0 +1,2 @@ +pub mod pins; +pub mod synth; diff --git a/rust-hdl-bsp-gowin/src/tangnano9k/pins.rs b/rust-hdl-bsp-gowin/src/tangnano9k/pins.rs new file mode 100644 index 00000000..5492a70a --- /dev/null +++ b/rust-hdl-bsp-gowin/src/tangnano9k/pins.rs @@ -0,0 +1,31 @@ +use rust_hdl::core::prelude::*; + +pub const CLOCK_SPEED_27MHZ: u64 = 27_000_000; + +pub fn clock() -> Signal { + let mut signal = Signal::default(); + signal.add_location(0, "52"); + // signal.add_signal_type(0, SignalType::LowVoltageCMOS_3v3); + signal.connect(); + signal +} + +pub fn lamp() -> Signal> { + let mut signal = Signal::default(); + let locs = ["10", "11", "13", "14", "15", "16"]; + for (i, loc) in locs.iter().enumerate() { + signal.add_location(i, loc); + // signal.add_signal_type(i, SignalType::LowVoltageCMOS_3v3); + } + signal +} + +pub fn button() -> Signal> { + let mut signal = Signal::default(); + let locs = ["3", "4"]; + for (i, loc) in locs.iter().enumerate() { + signal.add_location(i, loc); + // signal.add_signal_type(i, SignalType::LowVoltageCMOS_3v3); + } + signal +} diff --git a/rust-hdl-bsp-gowin/src/tangnano9k/synth.rs b/rust-hdl-bsp-gowin/src/tangnano9k/synth.rs new file mode 100644 index 00000000..233cfb72 --- /dev/null +++ b/rust-hdl-bsp-gowin/src/tangnano9k/synth.rs @@ -0,0 +1,78 @@ +use std::{ + fs::{create_dir_all, File}, + io::Write, + path::PathBuf, + process::{Command, Output}, + str::FromStr, +}; + +use rust_hdl::core::{check_error::check_all, prelude::*}; +use rust_hdl::fpga::toolchains::gowin::generate_cst; + +fn save_stdout(output: Output, dir: &PathBuf, basename: &str) -> Result<(), std::io::Error> { + let stdout = String::from_utf8(output.stdout).unwrap(); + let stderr = String::from_utf8(output.stderr).unwrap(); + let mut out_file = File::create(dir.clone().join(format!("{}.out", basename)))?; + out_file.write_all(stdout.as_bytes())?; + let mut err_file = File::create(dir.clone().join(format!("{}.err", basename)))?; + err_file.write_all(stderr.as_bytes())?; + Ok(()) +} + +pub fn generate_bitstream(mut uut: U, download: bool, prefix: &str, yosys: &str) { + uut.connect_all(); + check_all(&uut).unwrap(); + + let verilog_text = generate_verilog(&uut); + let cst_text = generate_cst(&uut); + let dir = PathBuf::from_str(prefix).unwrap(); + + create_dir_all(&dir).unwrap(); + + let mut v_file = File::create(dir.join("top.v")).unwrap(); + write!(v_file, "{}", verilog_text).unwrap(); + let mut cst_file = File::create(dir.join("top.cst")).unwrap(); + write!(cst_file, "{}", cst_text).unwrap(); + + let output = Command::new(format!("{yosys}/yosys")) + .current_dir(dir.clone()) + .arg("-p") + .arg("read_verilog top.v; synth_gowin -json top.json") + .output() + .unwrap(); + save_stdout(output, &dir, "yosys").unwrap(); + + let output = Command::new(format!("{yosys}/nextpnr-himbaechel")) + .current_dir(dir.clone()) + .args([ + "--json", + "top.json", + "--write", + "top.pnr.json", + "--device", + "GW1NR-LV9QN88PC6/I5", + "--vopt", + "family=GW1N-9C", + "--vopt", + "cst=top.cst", + ]) + .output() + .unwrap(); + save_stdout(output, &dir, "nextpnr").unwrap(); + + let output = Command::new(format!("{yosys}/gowin_pack")) + .current_dir(dir.clone()) + .args(["-d", "GW1N-9C", "-o", "top.fs", "top.pnr.json"]) + .output() + .unwrap(); + save_stdout(output, &dir, "pack").unwrap(); + + if download { + let output = Command::new(format!("{yosys}/openFPGALoader")) + .current_dir(dir.clone()) + .args(["-b", "tangnano9k", "-f", "top.fs"]) + .output() + .unwrap(); + save_stdout(output, &dir, "loader").unwrap(); + } +} diff --git a/rust-hdl-bsp-gowin/test/bsp_tangnano9k.rs b/rust-hdl-bsp-gowin/test/bsp_tangnano9k.rs new file mode 100644 index 00000000..cd1687c8 --- /dev/null +++ b/rust-hdl-bsp-gowin/test/bsp_tangnano9k.rs @@ -0,0 +1,46 @@ +use rust_hdl::prelude::*; +use rust_hdl_bsp_gowin::tangnano9k::{pins, synth}; + +#[derive(LogicBlock)] +pub struct Blinky { + pulser: Pulser, + clock: Signal, + lamp: Signal>, +} + +impl Default for Blinky { + fn default() -> Self { + Blinky { + pulser: Pulser::new( + pins::CLOCK_SPEED_27MHZ, + 1.0, + std::time::Duration::from_millis(250), + ), + clock: pins::clock(), + lamp: pins::lamp(), + } + } +} + +impl Logic for Blinky { + #[hdl_gen] + fn update(&mut self) { + self.pulser.enable.next = true; + self.pulser.clock.next = self.clock.val(); + + self.lamp.next = 0.into(); + if self.pulser.pulse.val() { + self.lamp.next = 0xff.into(); + } + } +} + +#[test] +fn main() { + synth::generate_bitstream( + Blinky::default(), + true, // auto download + target_path!("tangnano9k/blink"), + "", // yosys(or oss-cad-suite) path, "" or same "/home/xxx/software/oss-cad-suite/bin" + ); +} diff --git a/rust-hdl-fpga-support/src/toolchains/gowin.rs b/rust-hdl-fpga-support/src/toolchains/gowin.rs new file mode 100644 index 00000000..294ed453 --- /dev/null +++ b/rust-hdl-fpga-support/src/toolchains/gowin.rs @@ -0,0 +1,69 @@ +use rust_hdl_core::prelude::*; + +use super::map_signal_type_to_gowin_string; + +#[derive(Default)] +struct CSTGenerator { + path: NamedPath, + namespace: NamedPath, + cst: Vec, +} + +impl Probe for CSTGenerator { + fn visit_start_scope(&mut self, name: &str, _node: &dyn Block) { + self.path.push(name); + self.namespace.reset(); + } + + fn visit_start_namespace(&mut self, name: &str, _node: &dyn Block) { + self.namespace.push(name); + } + + fn visit_atom(&mut self, name: &str, signal: &dyn Atom) { + if self.path.len() == 1 { + let namespace = self.namespace.flat("$"); + let name = if namespace.is_empty() { + name.to_owned() + } else { + format!("{}${}", namespace, name) + }; + + for pin in &signal.constraints() { + let prefix = if signal.bits() == 1 { + format!("{}", name) + } else { + format!("{}[{}]", name, pin.index) + }; + + match &pin.constraint { + Constraint::Location(l) => { + self.cst.push(format!("IO_LOC \"{}\" {};", prefix, l)); + } + Constraint::Kind(k) => { + let name = map_signal_type_to_gowin_string(k); + self.cst + .push(format!("IO_PORT \"{}\" IO_TYPE={};", prefix, name)) + } + Constraint::Custom(s) => self.cst.push(s.clone()), + _ => { + panic!("Pin constraint type {:?} is unsupported!", pin.constraint) + } + } + } + } + } + + fn visit_end_namespace(&mut self, _name: &str, _node: &dyn Block) { + self.namespace.pop(); + } + + fn visit_end_scope(&mut self, _name: &str, _node: &dyn Block) { + self.path.pop(); + } +} + +pub fn generate_cst(uut: &U) -> String { + let mut cst = CSTGenerator::default(); + uut.accept("top", &mut cst); + cst.cst.join("\n") + "\n" +} diff --git a/rust-hdl-fpga-support/src/toolchains/mod.rs b/rust-hdl-fpga-support/src/toolchains/mod.rs index d73c868f..a179bf92 100644 --- a/rust-hdl-fpga-support/src/toolchains/mod.rs +++ b/rust-hdl-fpga-support/src/toolchains/mod.rs @@ -10,6 +10,24 @@ pub fn map_signal_type_to_lattice_string(k: &SignalType) -> &str { } } +pub fn map_signal_type_to_gowin_string(k: &SignalType) -> &str { + match k { + SignalType::LowVoltageCMOS_1v8 => "LVCMOS18", + SignalType::LowVoltageCMOS_3v3 => "LVCMOS33", + SignalType::StubSeriesTerminatedLogic_II => "SSTL18_II", + SignalType::DifferentialStubSeriesTerminatedLogic_II => "DIFF_SSTL18_II", + SignalType::StubSeriesTerminatedLogic_II_No_Termination => "SSTL18_II | IN_TERM=NONE", + SignalType::DifferentialStubSeriesTerminatedLogic_II_No_Termination => { + "DIFF_SSTL18_II | IN_TERM=NONE" + } + SignalType::Custom(c) => c, + SignalType::LowVoltageDifferentialSignal_2v5 => "LVDS_25", + SignalType::StubSeriesTerminatedLogic_1v5 => "SSTL15", + SignalType::LowVoltageCMOS_1v5 => "LVCMOS15", + SignalType::DifferentialStubSeriesTerminatedLogic_1v5 => "DIFF_SSTL15", + } +} + pub fn map_signal_type_to_xilinx_string(k: &SignalType) -> &str { match k { SignalType::LowVoltageCMOS_1v8 => "LVCMOS18",