1#[cfg(feature = "fpga")]
16use std::path::Path;
17use std::path::PathBuf;
18
19use rlx_ir::Graph;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum ExportTarget {
24 #[cfg(feature = "fpga")]
26 Fpga,
27}
28
29impl ExportTarget {
30 pub fn name(self) -> &'static str {
31 match self {
32 #[cfg(feature = "fpga")]
33 Self::Fpga => "fpga",
34 }
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct ExportOptions {
41 pub out_dir: PathBuf,
43 #[cfg(feature = "fpga")]
45 pub fpga: rlx_fpga::FpgaExportConfig,
46}
47
48impl ExportOptions {
49 pub fn new(out_dir: impl Into<PathBuf>) -> Self {
50 Self {
51 out_dir: out_dir.into(),
52 #[cfg(feature = "fpga")]
53 fpga: rlx_fpga::FpgaExportConfig::default(),
54 }
55 }
56
57 #[cfg(feature = "fpga")]
58 pub fn with_fpga(mut self, cfg: rlx_fpga::FpgaExportConfig) -> Self {
59 self.fpga = cfg;
60 self
61 }
62
63 #[cfg(feature = "fpga")]
64 pub fn fpga_generic(out_dir: impl Into<PathBuf>) -> Self {
65 Self::new(out_dir).with_fpga(
66 rlx_fpga::FpgaExportConfig::default().with_hw_target(rlx_fpga::HwTarget::Generic),
67 )
68 }
69}
70
71#[derive(Debug, Clone)]
73pub struct ExportedArtifacts {
74 pub target: ExportTarget,
75 pub out_dir: PathBuf,
76 pub files: Vec<String>,
77 pub estimate_summary: Option<String>,
78 pub optimize_summary: Option<String>,
79}
80
81#[cfg(feature = "fpga")]
90pub struct ExportSession {
91 opts: ExportOptions,
92}
93
94#[cfg(feature = "fpga")]
95impl ExportSession {
96 pub fn fpga(out_dir: impl Into<PathBuf>) -> Self {
98 Self {
99 opts: ExportOptions::fpga_generic(out_dir),
100 }
101 }
102
103 pub fn with_config(mut self, cfg: rlx_fpga::FpgaExportConfig) -> Self {
104 self.opts.fpga = cfg;
105 self
106 }
107
108 pub fn quant_mode(mut self, mode: rlx_fpga::ExportQuantMode) -> Self {
109 self.opts.fpga = self.opts.fpga.with_quant_mode(mode);
110 self
111 }
112
113 pub fn hw_target(mut self, hw: rlx_fpga::HwTarget) -> Self {
114 self.opts.fpga = self.opts.fpga.with_hw_target(hw);
115 self
116 }
117
118 pub fn output_kind(mut self, kind: rlx_fpga::OutputKind) -> Self {
119 self.opts.fpga = self.opts.fpga.with_output_kind(kind);
120 self
121 }
122
123 pub fn io(mut self, io: rlx_fpga::IoConfig) -> Self {
124 self.opts.fpga = self.opts.fpga.with_io(io);
125 self
126 }
127
128 pub fn bind_input(mut self, name: impl Into<String>) -> Self {
129 self.opts.fpga = self.opts.fpga.bind_input(name);
130 self
131 }
132
133 pub fn bind_outputs<I, S>(mut self, names: I) -> Self
134 where
135 I: IntoIterator<Item = S>,
136 S: Into<String>,
137 {
138 self.opts.fpga = self.opts.fpga.bind_outputs(names);
139 self
140 }
141
142 pub fn bind_sideband_inputs<I, S>(mut self, names: I) -> Self
143 where
144 I: IntoIterator<Item = S>,
145 S: Into<String>,
146 {
147 self.opts.fpga = self.opts.fpga.bind_sideband_inputs(names);
148 self
149 }
150
151 pub fn sideband(mut self, spec: rlx_fpga::SidebandSpec) -> Self {
152 self.opts.fpga = self.opts.fpga.sideband(spec);
153 self
154 }
155
156 pub fn legalize(mut self, opts: rlx_fpga::LegalizeOptions) -> Self {
157 self.opts.fpga = self.opts.fpga.with_legalize(opts);
158 self
159 }
160
161 pub fn export(&self, graph: &Graph) -> Result<ExportedArtifacts, String> {
162 export_graph(graph, ExportTarget::Fpga, &self.opts)
163 }
164
165 pub fn export_model(&self, model: &rlx_fpga::Model) -> Result<ExportedArtifacts, String> {
166 let result = rlx_fpga::export_model(model, &self.opts.fpga, &self.opts.out_dir)
167 .map_err(|e| e.to_string())?;
168 Ok(ExportedArtifacts {
169 target: ExportTarget::Fpga,
170 out_dir: result.out_dir,
171 files: result.files,
172 estimate_summary: Some(result.estimate.summary()),
173 optimize_summary: Some(result.optimize_summary),
174 })
175 }
176}
177
178pub fn export_graph(
180 graph: &Graph,
181 target: ExportTarget,
182 opts: &ExportOptions,
183) -> Result<ExportedArtifacts, String> {
184 #[cfg(feature = "fpga")]
185 {
186 match target {
187 ExportTarget::Fpga => export_fpga(graph, opts),
188 }
189 }
190 #[cfg(not(feature = "fpga"))]
191 {
192 let _ = (graph, target, opts);
193 Err("no export targets enabled; enable the `fpga` feature".into())
194 }
195}
196
197#[cfg(feature = "fpga")]
198fn export_fpga(graph: &Graph, opts: &ExportOptions) -> Result<ExportedArtifacts, String> {
199 let result = rlx_fpga::export_graph(graph, &opts.fpga, &opts.out_dir)?;
200 Ok(ExportedArtifacts {
201 target: ExportTarget::Fpga,
202 out_dir: result.out_dir,
203 files: result.files,
204 estimate_summary: Some(result.estimate.summary()),
205 optimize_summary: Some(result.optimize_summary),
206 })
207}
208
209#[cfg(feature = "fpga")]
211pub fn export_tinyconv_mnist(
212 out_dir: &Path,
213 cfg: &rlx_fpga::FpgaExportConfig,
214) -> Result<ExportedArtifacts, String> {
215 let model = rlx_fpga::tinyconv_mnist_from_cortexm();
216 let result = rlx_fpga::export_model(&model, cfg, out_dir).map_err(|e| e.to_string())?;
217 Ok(ExportedArtifacts {
218 target: ExportTarget::Fpga,
219 out_dir: result.out_dir,
220 files: result.files,
221 estimate_summary: Some(result.estimate.summary()),
222 optimize_summary: Some(result.optimize_summary),
223 })
224}
225
226#[cfg(all(test, feature = "fpga"))]
227mod tests {
228 use super::*;
229 use rlx_fpga::ir::to_graph;
230 use rlx_fpga::tinyconv_mnist_from_cortexm;
231
232 #[test]
233 fn export_session_tinyconv_generic() {
234 let model = tinyconv_mnist_from_cortexm();
235 let ir = to_graph(&model);
236 let dir = tempfile::tempdir().unwrap();
237 let arts = ExportSession::fpga(dir.path())
238 .quant_mode(rlx_fpga::ExportQuantMode::Int8)
239 .export(&ir.graph)
240 .expect("export");
241 assert!(dir.path().join("top.sv").is_file());
242 assert!(dir.path().join("synth.sh").is_file());
243 assert!(arts.files.iter().any(|f| f == "top.sv"));
244 }
245
246 #[test]
247 fn export_ecp5_emits_board_shell() {
248 let dir = tempfile::tempdir().unwrap();
249 let cfg =
250 rlx_fpga::FpgaExportConfig::default().with_hw_target(rlx_fpga::HwTarget::ecp5_25k());
251 export_tinyconv_mnist(dir.path(), &cfg).unwrap();
252 assert!(dir.path().join("board_top.sv").is_file());
253 assert!(dir.path().join("constraints.lpf").is_file());
254 }
255}