pcb_rs_traits/
lib.rs

1use downcast_rs::{impl_downcast, Downcast}; // TODO someday remove this dependency and implement the feature in this crate itself
2use std::any::Any;
3use std::collections::HashMap;
4
5mod util;
6pub use util::get_pin_group;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum PinType {
10    Input,
11    Output,
12    IO,
13}
14#[derive(Debug, Clone, Copy)]
15/// This will store the metadata of the pin, for the encompassing
16/// module (usually generated using pcb!) to use. Reason that the data_type is
17/// &'static str is that when deriving the Chip using Chip derive macro,
18/// or even when hand-implementing ChipInterface, the data type of the
19/// pin will be known in advance. Name is not stored here as it will be the key of hashmap
20pub struct PinMetadata {
21    pub pin_type: PinType,
22    pub data_type: &'static str,
23    pub tristatable: bool,
24}
25
26#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
27/// Used to represent a specific pin of a specific chip
28/// in pcb! generated struct. Both fields are 'static , because
29/// when generating we know the names, and thus can be stored as static strings
30pub struct ChipPin {
31    pub chip: &'static str,
32    pub pin: &'static str,
33}
34
35#[derive(Debug)]
36// NOTE: if you don't understand rest of this comment it is fine, even I'm a bit confused after writing it!
37// Note that in terms of the chip, a pin will be `output`, i.e. it will give out some data after tick()
38// ant the pins which are connected to such pins will be `input` pins, as they take the data before tick()
39// for the encompassing hardware module, this definition is kind of reversed in a sense, the pin which give out output are
40// are the input pins (termed source here) as we take their value after tick() and give it to some other pins, i.e.
41// they provide some input to other chips, and the pins which take it are output pins (termed destination),
42// i.e. they are supposed to receive the output. It is confusing, so here I just called it source and destination
43/// This enum represents three types of pin connections possible
44pub enum ConnectedPins {
45    /// indicates a one-to-one connections of a pin from a chip to another pin of a chip
46    Pair {
47        source: ChipPin,
48        destination: ChipPin,
49    },
50    /// indicates a group of shorted pins ,where a single pin is output type,
51    /// and its value is broadcasted to rest of the pins, which are either input or io types
52    Broadcast {
53        source: ChipPin,
54        destinations: Vec<ChipPin>,
55    },
56    /// indicates the group of connected tristated pins, with set of output/io types pins
57    /// connected to set of input/io pins
58    Tristated {
59        sources: Vec<ChipPin>,
60        destinations: Vec<ChipPin>,
61    },
62}
63
64impl PinMetadata {
65    pub fn is_connectable(&self, other: &PinMetadata) -> bool {
66        let both_input =
67            matches!(self.pin_type, PinType::Input) && matches!(other.pin_type, PinType::Input);
68        let both_output =
69            matches!(self.pin_type, PinType::Output) && matches!(other.pin_type, PinType::Output);
70        let both_tristatable = self.tristatable ^ other.tristatable;
71        let both_same_type = self.data_type == other.data_type;
72
73        // for pins to be connectable, both should NOT be input, both should NOT be output
74        // and both either should or should not be tristatable, the xor gives true if one is and one isn't
75        // so we check for it being false as well, and both should be of same type
76
77        return !both_input && !both_output && !both_tristatable && both_same_type;
78    }
79}
80
81impl std::fmt::Display for PinType {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        write!(
84            f,
85            "{}",
86            match self {
87                PinType::Input => "Input",
88                PinType::Output => "Output",
89                PinType::IO => "IO",
90            }
91        )
92    }
93}
94
95// Ideally we should enforce that the value type should be Any+Clone,
96// as we will call clone in the set_pin_value,
97// but adding clone to is not allowed due to object safety
98// so instead we only use Any, and depend on the fact that
99// before using clone, we will be converting the trait object back to concrete
100// so clone can be called, and if the type itself does not implement clone,
101// that call will fail at compile time
102
103/// This is the interface which should be exposed by the chip struct,
104/// and will be used by the pcb module. This is meant to be implemented
105/// by the #[Derive(Chip)] macro, but can also be manually implemented if needed
106pub trait ChipInterface {
107    /// gives a mapping from pin name to pin metadata
108    fn get_pin_list(&self) -> HashMap<&'static str, PinMetadata>;
109
110    /// returns value of a specific pin, typecasted to Any
111    fn get_pin_value(&self, name: &str) -> Option<Box<dyn Any>>;
112
113    /// sets value of a specific pin, from the given reference
114    fn set_pin_value(&mut self, name: &str, val: &dyn Any);
115
116    // The reason to include it in Chip interface, rather than anywhere else,
117    // is that I couldn't find a more elegant solution that can either directly
118    // implement on pin values which are typecasted to dyn Any. Thus the only way
119    // that we can absolutely make sure if a pin is tristated or not is in the
120    // Chip-level rather than the pin level. One major issue is that the data of
121    // which type the pin is is only available in the Chip derive macro, and cannot be
122    // used by the encompassing module in a way that will allow its usage in user programs
123    // which does not depend on syn/quote libs.
124    /// This is used to check if a tristatable pin is tristated or not
125    fn is_pin_tristated(&self, name: &str) -> bool;
126
127    /// This returns if the io pin is in input mode or not, and false for other pins
128    fn in_input_mode(&self, name: &str) -> bool;
129}
130
131/// This is intended to be implemented manually by user
132/// of the library. This provides the functionality of
133/// actually "running" the logic of the chip
134pub trait Chip {
135    /// this will be called on each clock tick by encompassing module (usually derived by pcb! macro)
136    /// and should contain the logic which is to be "implemented" by the chip.
137    ///
138    /// Before calling this function the values of input pins wil be updated according to
139    /// which other pins are connected to those, but does not guarantee
140    /// what value will be set in case if multiple output pins are connected to a single input pin.
141    ///
142    /// After calling this function, and before the next call of this function, the values of
143    /// output pins will be gathered by the encompassing module, to be given to the input pins before
144    /// next call of this.
145    ///
146    /// Thus ideally this function should check values of its input pins, take according actions and
147    /// set values of output pins. Although in case the chip itself needs to do something else, (eg logging etc)
148    /// it can simply do that and not set any pin to output in its struct declaration.
149    fn tick(&mut self) -> ();
150}
151
152/// This trait is used to create trait objects to store in the pcb created by the pbc! macro
153/// This currently uses downcast-rs so we can store the chips as dyn HardwareModule, but
154/// are able to downcast to concrete type if needed by user.
155// TODO this functionality should be implementable in this crate itself without needing downcast-rs,
156// TODO but I tried and couldn't  so using it to save time for now \O/
157pub trait HardwareModule: ChipInterface + Chip + Downcast {}
158
159impl_downcast!(HardwareModule);
160
161impl<T> HardwareModule for T where T: ChipInterface + Chip + Downcast {}