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