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 {}