_origen/
tester.rs

1use super::pins::pin_header::PinHeader;
2use super::timesets::timeset::Timeset;
3use crate::pins::vec_to_ppin_ids;
4use origen::core::tester::TesterSource;
5use origen::testers::SupportedTester;
6use origen::Error;
7use origen::{Operation, STATUS, TEST};
8use pyo3::prelude::*;
9use pyo3::types::{PyAny, PyDict, PyTuple};
10use std::collections::HashMap;
11
12pub fn define(py: Python, m: &PyModule) -> PyResult<()> {
13    let subm = PyModule::new(py, "tester")?;
14    subm.add_class::<PyTester>()?;
15    pyapi_metal::alias_method_apply_to_set!(subm, "PyTester", "timeset");
16    pyapi_metal::alias_method_apply_to_set!(subm, "PyTester", "pin_header");
17
18    m.add_submodule(subm)?;
19    Ok(())
20}
21
22#[pyclass(subclass)]
23/// Python interface for the tester backend.
24pub struct PyTester {
25    python_testers: HashMap<SupportedTester, PyObject>,
26    instantiated_testers: HashMap<SupportedTester, PyObject>,
27    // TODO support metadata on testers
28    _metadata: Vec<PyObject>,
29}
30
31#[pymethods]
32impl PyTester {
33    #[new]
34    fn new() -> PyResult<Self> {
35        origen::tester().init()?;
36        Ok(PyTester {
37            python_testers: HashMap::new(),
38            instantiated_testers: HashMap::new(),
39            _metadata: vec![],
40        })
41    }
42
43    fn _start_eq_block(&self, testers: Vec<&str>) -> PyResult<(usize, usize, Vec<String>)> {
44        let mut ts: Vec<SupportedTester> = vec![];
45        let mut clean_testers: Vec<String> = vec![];
46        for t in testers {
47            let st = SupportedTester::new(t)?;
48            clean_testers.push(st.to_string());
49            ts.push(st);
50        }
51        let refs = origen::tester().start_tester_eq_block(ts)?;
52        Ok((refs.0, refs.1, clean_testers))
53    }
54
55    fn _end_eq_block(&self, pat_ref_id: usize, prog_ref_id: usize) -> PyResult<()> {
56        origen::tester().end_tester_eq_block(pat_ref_id, prog_ref_id)?;
57        Ok(())
58    }
59
60    fn _start_neq_block(&self, testers: Vec<&str>) -> PyResult<(usize, usize, Vec<String>)> {
61        let mut ts: Vec<SupportedTester> = vec![];
62        let mut clean_testers: Vec<String> = vec![];
63        for t in testers {
64            let st = SupportedTester::new(t)?;
65            clean_testers.push(st.to_string());
66            ts.push(st);
67        }
68        let refs = origen::tester().start_tester_neq_block(ts)?;
69        Ok((refs.0, refs.1, clean_testers))
70    }
71
72    fn _end_neq_block(&self, pat_ref_id: usize, prog_ref_id: usize) -> PyResult<()> {
73        origen::tester().end_tester_neq_block(pat_ref_id, prog_ref_id)?;
74        Ok(())
75    }
76
77    /// Prints out the AST for the current flow to the console (for debugging)
78    #[getter]
79    fn ast(&self) -> PyResult<()> {
80        if Operation::GenerateFlow == STATUS.operation() {
81            println!("{}", origen_metal::FLOW.to_string());
82        } else {
83            println!("{}", origen::TEST.to_string());
84        }
85        Ok(())
86    }
87
88    /// Write out the AST to the given file (for debugging)
89    fn ast_to_file(&self, file: &str) -> PyResult<()> {
90        let contents = {
91            if Operation::GenerateFlow == STATUS.operation() {
92                origen_metal::FLOW.to_string()
93            } else {
94                origen::TEST.to_string()
95            }
96        };
97        std::fs::write(file, contents)?;
98        Ok(())
99    }
100
101    /// This resets the tester, clearing all loaded targets and any other state, making
102    /// it ready for a fresh target load.
103    /// This should only be called from Python code for testing, it will be called automatically
104    /// by Origen before loading targets.
105    fn reset(_self: PyRef<Self>) -> PyResult<()> {
106        Ok(origen::tester().reset()?)
107    }
108
109    /// This is called by Origen at the start of a generate command, it should never be called by
110    /// application code
111    fn _prepare_for_generate(&self) -> PyResult<()> {
112        origen::tester().prepare_for_generate()?;
113        Ok(())
114    }
115
116    fn _stats(&self) -> PyResult<Vec<u8>> {
117        Ok(origen::tester().stats.to_pickle())
118    }
119
120    #[getter]
121    /// Property for the current :class:`_origen.dut.timesets.Timeset` or None, if no timeset has been set.
122    /// Set to ``None`` to clear the current timeset.
123    ///
124    /// Returns:
125    ///     :class:`_origen.dut.timesets.Timeset` or ``None``
126    ///
127    /// >>> # Initially no timeset has been set
128    /// >>> origen.tester.timeset
129    /// None
130    /// >>> origen.tester.timeset = origen.dut.timesets.Timeset['my_timeset']
131    /// origen.dut.timesets.Timeset['my_timeset']
132    /// >>> origen.tester.timeset
133    /// origen.dut.timesets.Timeset['my_timeset']
134    /// >>> # Clear the current timeset
135    /// >>> origen.tester.timeset = None
136    /// None
137    /// >>> origen.tester.timeset
138    /// None
139    ///
140    /// See Also
141    /// --------
142    /// * :meth:`set_timeset`
143    /// * :class:`_origen.dut.timesets.Timeset`
144    /// * :ref:`Timing <guides/testers/timing:Timing>`
145    fn get_timeset(&self, py: Python) -> PyResult<PyObject> {
146        let tester = origen::tester();
147        let dut = origen::dut();
148        if let Some(t) = tester.get_timeset(&dut) {
149            Ok(Py::new(
150                py,
151                Timeset {
152                    name: t.name.clone(),
153                    model_id: t.model_id,
154                },
155            )
156            .unwrap()
157            .to_object(py))
158        } else {
159            Ok(py.None())
160        }
161    }
162
163    #[setter]
164    // Note - do not add doc strings here. Add to get_timeset above.
165    fn timeset(&self, py: Python, timeset: &PyAny) -> PyResult<()> {
166        let (model_id, timeset_name);
167
168        // If the timeset is a string, assume its a timeset name on the DUT.
169        // If not, it should be either None, to clear the timeset,
170        // or a timeset object, in which case we'll look up the name and model ID and go from there.
171        if let Ok(_timeset) = timeset.extract::<String>() {
172            model_id = 0;
173            timeset_name = _timeset;
174        } else {
175            if timeset.get_type().qualname()? == "NoneType" {
176                {
177                    let mut tester = origen::TESTER.lock().unwrap();
178                    tester.clear_timeset()?;
179                }
180                self.issue_callbacks(py, "clear_timeset")?;
181                return Ok(());
182            } else if timeset.get_type().qualname()? == "Timeset" {
183                let obj = timeset.to_object(py);
184                model_id = obj
185                    .getattr(py, "__origen__model_id__")?
186                    .extract::<usize>(py)?;
187                timeset_name = obj.getattr(py, "name")?.extract::<String>(py)?;
188            } else {
189                return type_error!(format!("Could not interpret 'timeset' argument as String or _origen.dut.timesets.Timeset object! (class '{}')", timeset.get_type().qualname()?));
190            }
191        }
192
193        {
194            {
195                let mut tester = origen::TESTER.lock().unwrap();
196                let dut = origen::DUT.lock().unwrap();
197                tester.set_timeset(&dut, model_id, &timeset_name)?;
198            }
199            self.issue_callbacks(py, "set_timeset")?;
200        }
201        Ok(())
202    }
203
204    /// set_timeset(timeset)
205    ///
206    /// Sets the timeset.
207    ///
208    /// >>> origen.tester.set_timeset(origen.dut.timesets['my_timeset'])
209    /// origen.tester.timesets['my_timeset']
210    ///
211    /// Parameters:
212    ///     timeset (_origen.dut.timesets.Timeset, None): Timeset to set as current, or ``None`` to clear
213    ///
214    /// See Also
215    /// --------
216    /// * :meth:`timeset`
217    /// * :class:`_origen.dut.timesets.Timeset`
218    /// * :ref:`Timing <guides/testers/timing:Timing>`
219    fn apply_timeset(&self, py: Python, timeset: &PyAny) -> PyResult<PyObject> {
220        self.timeset(py, timeset)?;
221        self.get_timeset(py)
222    }
223
224    #[getter]
225    fn get_pin_header(&self, py: Python) -> PyResult<PyObject> {
226        let tester = origen::tester();
227        let dut = origen::dut();
228
229        if let Some(header) = tester.get_pin_header(&dut) {
230            Ok(Py::new(
231                py,
232                PinHeader {
233                    name: header.name.clone(),
234                    model_id: header.model_id,
235                },
236            )
237            .unwrap()
238            .to_object(py))
239        } else {
240            Ok(py.None())
241        }
242    }
243
244    #[setter]
245    fn pin_header(&self, py: Python, pin_header: &PyAny) -> PyResult<()> {
246        let (model_id, pin_header_name);
247
248        if pin_header.get_type().qualname()? == "NoneType" {
249            {
250                let mut tester = origen::TESTER.lock().unwrap();
251                tester.clear_pin_header()?;
252            }
253            self.issue_callbacks(py, "clear_pin_header")?;
254            return Ok(());
255        } else if pin_header.get_type().qualname()? == "PinHeader" {
256            let obj = pin_header.to_object(py);
257            model_id = obj
258                .getattr(py, "__origen__model_id__")?
259                .extract::<usize>(py)?;
260            pin_header_name = obj.getattr(py, "name")?.extract::<String>(py)?;
261        } else {
262            return type_error!(format!("Could not interpret 'pin_header' argument as _origen.dut.Pins.PinHeader object! (class '{}')", pin_header.get_type().qualname()?));
263        }
264
265        {
266            {
267                let mut tester = origen::TESTER.lock().unwrap();
268                let dut = origen::DUT.lock().unwrap();
269                tester.set_pin_header(&dut, model_id, &pin_header_name)?;
270            }
271            self.issue_callbacks(py, "set_pin_header")?;
272        }
273        Ok(())
274    }
275
276    fn apply_pin_header(&self, py: Python, pin_header: &PyAny) -> PyResult<PyObject> {
277        self.pin_header(py, pin_header)?;
278        self.get_pin_header(py)
279    }
280
281    /// cc(comment: str) -> self
282    ///
283    /// Inserts a single-line comment into the AST.
284    ///
285    /// >>> origen.tester.cc("my comment")
286    /// <self>
287    /// >>> origen.tester.cc("my first comment").cc("my second comment")
288    /// <self>
289    ///
290    /// See Also
291    /// --------
292    /// * {{ link_to('prog-gen:comments', 'Commenting pattern source') }}
293    /// * {{ link_to('pat-gen:comments', 'Commenting program source') }}
294    fn cc(slf: PyRef<Self>, py: Python, comment: &str) -> PyResult<Py<Self>> {
295        {
296            let mut tester = origen::tester();
297            tester.cc(&comment)?;
298        }
299        slf.issue_callbacks(py, "cc")?;
300        Ok(slf.into())
301    }
302
303    #[pyo3(text_signature = "($self, header_comments)")]
304    pub fn generate_pattern_header(&self, header_comments: &PyDict) -> PyResult<()> {
305        let tester = origen::tester();
306        Ok(tester.generate_pattern_header(
307            match header_comments.get_item("app")? {
308                Some(comments) => Some(comments.extract::<Vec<String>>()?),
309                None => None,
310            },
311            match header_comments.get_item("pattern")? {
312                Some(comments) => Some(comments.extract::<Vec<String>>()?),
313                None => None,
314            },
315        )?)
316    }
317
318    fn end_pattern(&self) -> PyResult<()> {
319        let tester = origen::tester();
320        Ok(tester.end_pattern()?)
321    }
322
323    fn issue_callbacks(&self, py: Python, func: &str) -> PyResult<()> {
324        // Get the current targeted testers
325        let targets;
326        {
327            let tester = origen::tester();
328            targets = tester.targets().clone();
329        }
330
331        // issue callbacks in the order which they were targeted
332        for (i, t) in targets.iter().enumerate() {
333            match t {
334                TesterSource::External(g) => {
335                    // External testers which the backend can't render itself. Need to render them here.
336                    match self.instantiated_testers.get(g) {
337                        Some(inst) => {
338                            // The tester here is a PyObject - a handle on the class itself.
339                            // Instantiate it and call its render method with the AST.
340                            let last_node = TEST.get(0).unwrap().to_pickle();
341                            let args =
342                                PyTuple::new(py, &[func.to_object(py), last_node.to_object(py)]);
343
344                            // The issue callback function is located in origen.generator.tester_api.TesterAPI
345                            // Easier to handle the actual calls there and since its all happening in the Python domain, doesn't really matter
346                            // whether it happens here or there.
347                            inst.call_method1(py, "__origen__issue_callback__", args)?;
348                        }
349                        None => {
350                            return Err(PyErr::from(Error::new(&format!(
351                                "Something's gone wrong and Python tester {} cannot be found!",
352                                g
353                            ))))
354                        }
355                    }
356                }
357                _ => {
358                    let mut tester = origen::tester();
359                    tester.issue_callback_at(i)?;
360                }
361            }
362        }
363        Ok(())
364    }
365
366    /// cycle(**kwargs) -> self
367    #[pyo3(signature=(**kwargs))]
368    fn cycle(slf: PyRef<Self>, py: Python, kwargs: Option<&PyDict>) -> PyResult<Py<Self>> {
369        {
370            let mut tester = origen::tester();
371            let mut repeat = None;
372            if let Some(_kwargs) = kwargs {
373                if let Some(_kwarg) = _kwargs.get_item("repeat")? {
374                    repeat = Some(_kwarg.extract::<usize>()?);
375                }
376            }
377            tester.cycle(repeat)?;
378        }
379        slf.issue_callbacks(py, "cycle")?;
380
381        Ok(slf.into())
382    }
383
384    fn repeat(slf: PyRef<Self>, py: Python, count: usize) -> PyResult<Py<Self>> {
385        let kwargs = PyDict::new(py);
386        kwargs.set_item("repeat", count)?;
387        Self::cycle(slf, py, Some(&kwargs))
388    }
389
390    #[pyo3(signature=(
391        label = None,
392        symbol = None,
393        pins = None,
394        cycles = None,
395        mask = None,
396    ))]
397    fn overlay(
398        slf: PyRef<Self>,
399        py: Python,
400        label: Option<String>,
401        symbol: Option<String>,
402        pins: Option<Vec<&PyAny>>,
403        cycles: Option<usize>,
404        mask: Option<num_bigint::BigUint>,
405    ) -> PyResult<Py<Self>> {
406        let pin_ids;
407        {
408            if let Some(p) = pins {
409                crate::dut::PyDUT::ensure_pins("dut")?;
410                let dut = origen::dut();
411                pin_ids = Some(vec_to_ppin_ids(&dut, p)?);
412            } else {
413                pin_ids = None
414            }
415        }
416        {
417            let tester = origen::tester();
418            tester.overlay(&origen::Overlay::new(label, symbol, cycles, mask, pin_ids)?)?;
419        }
420        slf.issue_callbacks(py, "overlay")?;
421        Ok(slf.into())
422    }
423
424    #[pyo3(signature=(symbol=None, cycles=None, mask=None, pins=None))]
425    fn capture(
426        slf: PyRef<Self>,
427        py: Python,
428        symbol: Option<String>,
429        cycles: Option<usize>,
430        mask: Option<num_bigint::BigUint>,
431        pins: Option<Vec<&PyAny>>,
432    ) -> PyResult<Py<Self>> {
433        let pin_ids;
434        {
435            if let Some(p) = pins {
436                crate::dut::PyDUT::ensure_pins("dut")?;
437                let dut = origen::dut();
438                pin_ids = Some(vec_to_ppin_ids(&dut, p)?);
439            } else {
440                pin_ids = None
441            }
442        }
443        {
444            let tester = origen::tester();
445            tester.capture(&origen::Capture::new(symbol, cycles, mask, pin_ids)?)?;
446        }
447        slf.issue_callbacks(py, "capture")?;
448        Ok(slf.into())
449    }
450
451    fn register_tester(&mut self, py: Python, g: &PyAny) -> PyResult<()> {
452        let mut tester = origen::tester();
453
454        let obj = g.to_object(py);
455        let mut n = obj.getattr(py, "__module__")?.extract::<String>(py)?;
456        n.push_str(&format!(
457            ".{}",
458            obj.getattr(py, "__qualname__")?.extract::<String>(py)?
459        ));
460
461        let t_id = tester.register_external_tester(&n)?;
462        self.python_testers.insert(t_id, obj);
463        Ok(())
464    }
465
466    #[pyo3(signature=(*testers))]
467    fn target(&mut self, py: Python, testers: &PyTuple) -> PyResult<Vec<String>> {
468        if testers.len() > 0 {
469            let mut tester = origen::tester();
470            for g in testers.iter() {
471                // Accept either a string name or the actual class of the tester
472                if let Ok(name) = g.extract::<String>() {
473                    tester.target(SupportedTester::new(&name)?)?;
474                } else {
475                    let obj = g.to_object(py);
476                    let mut n = obj.getattr(py, "__module__")?.extract::<String>(py)?;
477                    n.push_str(&format!(
478                        ".{}",
479                        obj.getattr(py, "__qualname__")?.extract::<String>(py)?
480                    ));
481                    // Assume a tester loaded via a class is a custom tester
482                    let t = tester.target(SupportedTester::new(&format!("CUSTOM::{}", n))?)?;
483                    match t {
484                        TesterSource::External(gen) => {
485                            let klass = self.python_testers.get(gen).unwrap();
486                            let inst = klass.call0(py)?;
487                            self.instantiated_testers.insert(gen.to_owned(), inst);
488                        }
489                        _ => {}
490                    }
491                }
492            }
493        }
494        self.targets()
495    }
496
497    #[getter]
498    fn targets(&self) -> PyResult<Vec<String>> {
499        let tester = origen::tester();
500        Ok(tester.targets_as_strs().clone())
501    }
502
503    /// Attempts to render the pattern on all targeted testers and returns paths to the
504    /// output files that have been created.
505    /// There is no need for the Python side to do anything with those, but they are returned
506    /// in case they are useful in future.
507    /// Continue on fail means that any errors will be logged but Origen will continue, if false
508    /// it will blow up and immediately return an error to Python.
509    #[pyo3(signature=(continue_on_fail=false))]
510    fn render_pattern(&self, py: Python, continue_on_fail: bool) -> PyResult<Vec<String>> {
511        if origen::LOGGER.has_keyword("show_unprocessed_ast") {
512            origen::LOGGER.info("Showing Unprocessed AST");
513            origen::LOGGER.info(&format!("{:?}", origen::TEST));
514        }
515        let mut rendered_patterns: Vec<String> = vec![];
516        let targets;
517        {
518            let tester = origen::tester();
519            targets = tester.targets().clone();
520        }
521        for (i, t) in targets.iter().enumerate() {
522            match t {
523                TesterSource::External(g) => {
524                    // External testers which the backend can't render itself. Need to render them here.
525                    match self.instantiated_testers.get(g) {
526                        Some(inst) => {
527                            // The tester here is a PyObject - a handle on the class itself.
528                            // Instantiate it and call its render method with the AST.
529                            let _pat = inst.call_method0(py, "render_pattern")?;
530                            // TODO - How do we convert this to a path to do the diffing?
531                        }
532                        None => {
533                            // Don't bother masking this type of error, this should be fatal
534                            let msg = format!(
535                                "Something's gone wrong and Python tester {} cannot be found!",
536                                g
537                            );
538                            return Err(PyErr::from(Error::new(&msg)));
539                        }
540                    }
541                }
542                _ => {
543                    let mut tester = origen::tester();
544                    let pat = tester.render_pattern_for_target_at(i, true);
545                    match pat {
546                        Err(e) => {
547                            let msg = e.to_string();
548                            if continue_on_fail {
549                                STATUS.inc_unhandled_error_count();
550                                log_error!("{}", &msg);
551                            } else {
552                                return Err(PyErr::from(Error::new(&msg)));
553                            }
554                        }
555                        Ok(paths) => {
556                            for path in &paths {
557                                rendered_patterns.push(format!("{}", path.display()));
558                            }
559                        }
560                    }
561                }
562            }
563        }
564        Ok(rendered_patterns)
565    }
566
567    #[getter]
568    fn testers(&self) -> PyResult<Vec<String>> {
569        Ok(SupportedTester::all_names())
570    }
571}