├── .gitignore ├── LICENSE-APACHE ├── LICENSE-MIT ├── Readme.md ├── crates ├── Cargo.toml ├── pcb-rs-macros │ ├── Cargo.toml │ └── src │ │ ├── chip_derive.rs │ │ ├── lib.rs │ │ └── pcb_macro.rs ├── pcb-rs-traits │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── util.rs └── pcb-rs │ ├── Cargo.toml │ └── src │ └── lib.rs └── pcb-rs-tests ├── Cargo.toml └── src ├── chip_derive.rs ├── main.rs ├── pcb_derive.rs └── test_spec.rs /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) 2022 Yashodhan Joshi 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Yashodhan Joshi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Pcb-rs 2 | 3 | A library to easily wite Software Emulated Hardware 4 | 5 | --- 6 | 7 | This library provides two macros `Chip` and `pcb` which can be used to write software emulated hardware components. `Chip` is a derive macro which can be used on structs to automatically implement the necessary interfaces for the struct to be treated as a Hardware Chip, and you only need to implement the tick function which will be called on each clock cycle to run the logic of your chip. `pcb` macro is used to define a PCB , where you can connect multiple chips, and it will manage connecting pins of chips, verifying the connections and passing the data on the connected chip. 8 | 9 | One of the aims of this library is modularity and reusability, thus the pcb created can be further used as chips in some other pcb ans so on. 10 | 11 | There are some finer points to be noted when creating chips, and are [listed after the explanations](#notes). 12 | 13 | For examples showing use of these for implementing various chips, please check https://github.com/YJDoc2/pcb-rs-examples . 14 | 15 | ## Motivation 16 | 17 | The main motivation behind this is to help people who wish to explore hardware and low-level systems to do so easily. Learning about hardware design can be tricky for several reason : it is not easily accessible, you usually need breadboards, chips and stuff to implement basic circuits, and the more complex circuits you want to implement more complex it gets. For doing it in software, VHDL is a very powerful alternative : you can describe your hardware in it, and it can synthesize and run tests on your hardware. It can even convert it into a format which can be used directly with FPGAs to realize your circuit into actual hardware. But it is complex, a bit verbose and (personally) a bit scary to get into. 18 | 19 | This library aims to provide a simpler way to enter into the hardware realm, by allowing you to write it in the comfort of Rust, but also giving you taste of considerations one has to do while designing hardware. This sacrifices a lot of power VHDL gives, but it is a trade-off, and as this does not aim to "replace" or even be a substitute for something like VHDL, it is fine. 20 | 21 | ## Chip Macro 22 | 23 | This is a derive macro, which will implement the necessary traits for the struct to be used as a Hardware Chip. Here a hardware chip means that it can be used in a pcb-macro generated pcb as a component chip. You will need to annotate the struct members which are to be exposed as pins, and then implement the HardwareChip interface, which has the required `tick` function. This function is where the processing logic of the chip should reside. If used in a pcb generated by pcb-macro, this function will be called on each emulated clock-tick. 24 | 25 | #### Example usage 26 | 27 | This is a dummy example showing how you can use this macro to make a struct into a Chip. 28 | 29 | ```Rust 30 | use pcb_rs::*; 31 | 32 | // Add the derive(Chip), so it will implement the necessary interfaces for your struct 33 | #[derive(Chip)] 34 | struct MyChip{ 35 | 36 | // This indicates that this chip has a pin named 'pin1' of type 'u8' 37 | // and which is an input pin 38 | #[pin(input)] 39 | pub pin1 :u8, 40 | 41 | // This indicates that this chip has a pin named 'pin2' of type 'bool' 42 | // and which is an output pin 43 | #[pin(output)] 44 | pub pin2 :bool, 45 | 46 | // This indicates that this chip has a pin named 'pin3' of type 'String' 47 | // and which is an io pin, and its status is indicated by the 48 | // 'io_latch' member of this struct 49 | // Why this needs to be an option is explained after this codeblock 50 | #[pin(io,io_latch)] 51 | pub pin3 : Option, 52 | 53 | // Members not marked by #[pin(...)] are not exposed as pins 54 | // Thus the following are essentially an internal state for this chip 55 | 56 | io_latch:bool, 57 | some_data1:u8, 58 | some_data2:String 59 | } 60 | 61 | impl Chip for MyChip{ 62 | fn tick(&mut self){ 63 | // here you can implement the logic of the chip 64 | // this takes &mut self, so you can read values set for input pins 65 | // and set values for the output pins, so they can be sent to 66 | // other connected chips in a pcb 67 | } 68 | } 69 | ``` 70 | 71 | There are few things to note here : 72 | 73 | ##### Data type of pins 74 | 75 | Data type of pins is allowed to be any type. This is done so one can choose their own difficulty level : 76 | 77 | - you want to have 8 `bool` pins, sure. 78 | - want one `u8` instead ? easy. 79 | - Wan to send opcode in String format? Umm ok... 80 | 81 | please do not mis-use this! Try to keep the pin-data-type to simple inbuilt data types such as u8 etc. or at worst String or such owning data-types. If you _**HAVE**_ to make pin data type a struct or make sure to think once again, and then implement `Clone` on it. Enums are also fair game, as long as their components obey the above. Make sure to implement `clone` on it as well. 82 | 83 | In case you intend the chip to be used by others, pick one data type name representation, and stick to it. See [note on pin type](#note-on-pin-types) to understand why. The way I recommend is to use fully qualified path for rust std type, and just the type names for custom types which are also exposed by your lib. Although this is not a hard requirement, so as long as everyone using the chips agree on how the type name is qualified, it is ok. Although you chip might be used with other chips which can cause issues, and I really don't want to do an [xkcd-standards thing](https://xkcd.com/927/). 84 | 85 | ##### IO pins 86 | 87 | ###### data type 88 | 89 | When you need a pin of io type, for eg, data bus of a RAM, it must be set tristatable. What it meas is explained in detail in the [notes section](#trisatable-pins), but in short, if you want to connect multiple output pins to the same input pin, they MUST be tristatable. A trisatable pin is indicated when the outermost wrapping type is Option, and it kind-of color marks the pin, where it can only be connected to other tristatable pins, not non-tristatble pins. Theoratically there is One case where an io pin can be connected to multiple pins, without needing to be tristatable, when all other pins are of input type. This case can be reduced to a simpler case - make the io pin just output pin, as when io pin is also in input state, nothing will happen, as there is no input pin in the group. Thus one should change io to just output pin in such cases, and this library thus assumes all io pins would be connected to multiple input and output pins, and thus HAVE to be tristatable. 90 | 91 | The actual data sent/received by io pins would be the type enclosed in the Option, i.e. T in Option\ . Thus if you want the data-type to be Option\ itself, read the data types of pins point once again, and if you are still sure, wrap it in option, so the final type for the data pin will be Option>. 92 | 93 | ###### IO latch 94 | 95 | Every IO type pins has to have an associated io latch to indicate if the pin is in input state or output state. This must be a chip struct member, and of type bool. This is used by the pcb generated by pcb macro to make sure at runtime that the io pin is in correct state. See the pcb macro section for more details. The value indicates that : 96 | 97 | - if true, then the io pin is in input mode, thus a connected pin can be in output mode, in which case its value will be given to this pin 98 | - if false, then the io pin is in output mode, thus value of the io pin will be given to connected io pins which are in input state. 99 | 100 | The pcb check at runtime that at most one pin is in output mode. 101 | 102 | ## PCB macro 103 | 104 | This is a functional macro, and can be used to specify and get an implementation of multiple chip connections. This basically takes in a simple textual information of what chips are in the pcb, how they are connected, and what pins are exposed out of the pcb and creates a builder which logic to verify the chips given and a PCB struct, which implements the required traits. 105 | 106 | The chips are given and verified at runtime by the builder to produce the struct. 107 | 108 | ```rust 109 | use pcb_rs::*; 110 | 111 | // This will create two structs : 'MyPCBBuilder' and 'MyPCB' 112 | // both will be public ( with 'pub' access specifier). 113 | pcb!(MyPCB{ 114 | // These comments are allowed 115 | /* and these */ 116 | // but not doc comments 117 | 118 | // fist list all the chips that will be used in this pcb 119 | // the names are not required to be same as the actual chip struct 120 | chip c1; 121 | chip c2; 122 | chip c3; 123 | 124 | // now declare the connections 125 | // pin names MUST be same as the chip struct member names 126 | c1::pin1 - c2::pin2; 127 | c2::pin3 - c3::p1; 128 | c3::p2 - c1::pin2; 129 | 130 | // Now list the pins which should be exposed by the pcb 131 | expose c1::pin3 as pin1; 132 | expose c2::pin1,c3::p4 as p5; 133 | 134 | }); 135 | 136 | fn main(){ 137 | // these var names can be anything, I prefer to keep them same 138 | // as the chip names in the above declaration for simplicity 139 | let c1 = Box::new(MyChip1::default()); 140 | let c2 = Box::new(MyChip2::default()); 141 | let c3 = Box::new(MyChip3::default()); 142 | 143 | let temp = MyPCBBuilder::new(); 144 | // first param to add_chip is the chip name, 145 | // as declared in the pcb!(..) 146 | let pcb = temp.add_chip("c1",c1) 147 | .add_chip("c2",c2) 148 | .add_chip("c3",c3) 149 | .build().unwrap(); 150 | // do stuff with the pcb, generally put in a loop and call tick() 151 | } 152 | 153 | ``` 154 | 155 | The builder struct provides the `add_chip(name_str,boxed_chip)` function to add the chip to the pcb. In the `build()` call, it verifies the added chips, and validates that : 156 | 157 | - all the listed chips are added 158 | - The chip has the required pins as the the connections and exposed pins 159 | - The connected pins are of correct data type, and are of compatible types. See [pin-types](#note-on-pin-types) and [exposed pins](#note-on-exposed-pin-shorting). The compatible type here means that input pins can be connected to either output or io pins, output pins can be connected to either input or io pins , and io pins can be connected to io, input or output pins. Input to input and output to output connections are invalid. 160 | - The exposed pins are in correct setup, again see [exposed pins](#note-on-exposed-pin-shorting) 161 | 162 | The first chips section is mandatory, and one of the pin-connection or exposed pins section is necessary. See [pcb syntax](#syntax-of-the-pcb) for more details. 163 | 164 | The PCB struct generated will itself implement the `Chip` trait, and thus can be used as a Chip in some other pcb. 165 | 166 | ##### Note about pin value transfer 167 | 168 | The basic way pin values are transferred for connected pins is that in the tick function of pcb, it iterates over the added chips, and calls the tick function of them. Then it takes the value of pins which are connected and passes them to the connected pins. Note that the order of chips is not determinate, and should not be relied upon. 169 | 170 | The only guarantee this tick implementation makes is that after one call to tick of chips, values of connected pins will be given to the respective connected pins before the next call of the tick. There is no exact guarantee of when or in which order the values will be set. 171 | 172 | Another thing to note is that there will be exactly one clock-cycle delay for passing of the values from one chip to another, from the point of view of chips. Thus the values set to output pins in the clock-cycle ti will be seen by the connected chip at the clock-cycle ti+1. If you are expecting the data from another chip, such as cpu giving address to RAM and getting data back from it, it will necessarily take 2 clock-cycles to get the data on the data pins of ram, i.e. at tick ti the address will be set on the address pin by the cpu, it will be seen by the ram in the ti+1 the tick and it will place the data on its data pins in that tick function, which will be seen by the cpu on its data pins in the next tic, i.e. ti+2. 173 | 174 | As mentioned before, the chips themselves should not directly depend on the non-deterministic order of calling tick function, in case you specifically want race-conditions, a better option is to make a chip which will emulate this non-deterministic behavior, and wrap the chip which need the non-deterministic behavior inside this chip. Similar way should be used when you need chips which are to be ran at slower clock-speeds. 175 | 176 | ## Library exposed traits and PCB interfaces 177 | 178 | This library primarily exposed following traits, which are usually implemented by the macros, but can be manually implemented if required. 179 | 180 | ### ChipInterface 181 | 182 | This trait marks a struct to be a Chip, which can be used in a pcb. This is usually derived by the Chip macro. 183 | 184 | ```rust 185 | pub trait ChipInterface { 186 | /// gives a mapping from pin name to pin metadata 187 | fn get_pin_list(&self) -> HashMap<&'static str, PinMetadata>; 188 | 189 | /// returns value of a specific pin, typecasted to Any 190 | fn get_pin_value(&self, name: &str) -> Option>; 191 | 192 | /// sets value of a specific pin, from the given reference 193 | fn set_pin_value(&mut self, name: &str, val: &dyn Any); 194 | 195 | // The reason to include it in Chip interface, rather than anywhere else, 196 | // is that I couldn't find a more elegant solution that can either directly 197 | // implement on pin values which are typecasted to dyn Any. Thus the only way 198 | // that we can absolutely make sure if a pin is tristated or not is in the 199 | // Chip-level rather than the pin level. One major issue is that the data of 200 | // which type the pin is is only available in the Chip derive macro, and cannot be 201 | // used by the encompassing module in a way that will allow its usage in user programs 202 | // which does not depend on syn/quote libs. 203 | /// This is used to check if a tristatable pin is tristated or not 204 | fn is_pin_tristated(&self, name: &str) -> bool; 205 | 206 | /// This returns if the io pin is in input mode or not, and false for other pins 207 | fn in_input_mode(&self, name: &str) -> bool; 208 | } 209 | ``` 210 | 211 | ### Chip 212 | 213 | This has the actual logic of the cihp, and is always supposed to be manually implemented in case of chips. for pcb, the pcb! macro implemented this for the chip. 214 | 215 | ```rust 216 | pub trait Chip { 217 | /// this will be called on each clock tick by encompassing module (usually derived by pcb! macro) 218 | /// and should contain the logic which is to be "implemented" by the chip. 219 | /// 220 | /// Before calling this function the values of input pins wil be updated according to 221 | /// which other pins are connected to those, but does not guarantee 222 | /// what value will be set in case if multiple output pins are connected to a single input pin. 223 | /// 224 | /// After calling this function, and before the next call of this function, the values of 225 | /// output pins will be gathered by the encompassing module, to be given to the input pins before 226 | /// next call of this. 227 | /// 228 | /// Thus ideally this function should check values of its input pins, take according actions and 229 | /// set values of output pins. Although in case the chip itself needs to do something else, (eg logging etc) 230 | /// it can simply do that and not set any pin to output in its struct declaration. 231 | fn tick(&mut self) -> (); 232 | } 233 | ``` 234 | 235 | ### HardwareModule 236 | 237 | This is just a marker trait to indicate that a struct can be used as a chip. This is auto implemented for any type that implements `ChipInterface`,`Chip` and `Downcast` so, it is not needed to be manually implemented to anything. 238 | 239 | ```rust 240 | pub trait HardwareModule: ChipInterface + Chip + Downcast {} 241 | ``` 242 | 243 | ### PCB Builder interface 244 | 245 | The builder struct generated by the pcb! macro has following public functions : 246 | 247 | ```rust 248 | new() -> Builder 249 | ``` 250 | 251 | Creates a new builder 252 | 253 | ```rust 254 | add_chip(chip_name,boxed_hardware_module) -> () 255 | ``` 256 | 257 | This adds a chip to the pcb. The name must be same as in the chip list defined in the pcb!(...) and boxed_hardware_module is the actual chip, which implements the HardwareModule trait, in a Box. 258 | 259 | ```rust 260 | build(mut self)->std::result::Result 261 | ``` 262 | 263 | This validates the chips added, and if correct, returns the pcb struct containing the chips and functioning logic. 264 | 265 | ### PCB interface 266 | 267 | The pcb struct generated by the pcb! macro has the following public functions : 268 | 269 | ```rust 270 | get_chip(&self,chip_name)->Option<&T> 271 | ``` 272 | 273 | Returns immutable reference to the component chip with given name, if any. 274 | 275 | ```rust 276 | get_chip_mut(&mut self,chip_name)->Option<&mut T> 277 | ``` 278 | 279 | Returns mutable reference to the component chip of given name, if any. 280 | 281 | **Note** : For both of the above, as the pcb cannot know which chip type it is, one must manually type annotate the variable in which the returned chip is stored, i.e. : 282 | 283 | ```rust 284 | let t :&MyChip1 = pcb.get_chip("chip1").unwrap(); 285 | let t :&mut MyChip2 = pcb.get_chip_mut("chip2").unwrap(); 286 | ``` 287 | 288 | Apart from these, the PCB also implements the [ChipInterface](#chipinterface), so the functions of that are also available. See the examples in https://github.com/YJDoc2/pcb-rs-examples for using the get_value and set_value methods, which might be used frequently. 289 | 290 | ## Notes 291 | 292 | Alas, this is just a hardware simulating library, and thus has some edges where it cannot exactly simulate the real-world hardware. These notes show quirks of this library. 293 | 294 | ### Trisatable pins 295 | 296 | In real pcb, the individual pins can only transfer voltages, (thus bits), and are connected to each other. Here we allow pins to have more complex data types, at expense of possible runtime panics. (as technically the types are resolved at file level, two files can have two diff types which have same name, and while generating macros, types are not resoled, so we do not have enough information until runtime if both types are same or not. ) 297 | 298 | If we allow only single connection per pin, it can not only get complicated to implement chips which connect to multiple devices, but also it might not be possible to establish shared connection at all. For eg : in a particular system RAM must be connected to both CPU and a DMA module. Now if we don't allow multiple connections, we cannot connect data pins of RAM to both CPU and DMA. That means either only one can access the RAM, or we have to add a layer of indirection between RAM and other components such that CPU and DMA will request to this component and the pins of this component will be connect to RAM. Even then, in that component we cannot connect the data pin granting pin of that indirection chip to both, due to the same issue. That means we will need one pin for each connected component, and, some priority based method to tie brake if multiple components request access to data pin. This can turn quite inefficient as the number of components to be connected grows. 299 | 300 | In real world, such issue is solved by two methods : see [this](https://www.microchip.com/forums/m641935.aspx) for a good explanation. In this library, we use tristating. That way ideally only one of the connected chip will have a valid output (High or Low) and others will be in High-Z mode, where essentially that pin acts as if it is not connected at all. Although in case multiple chips connected to same pin do go in non-high-z state at the same point, it will cause issues, potentially burning of real chips. Also see [this](https://en.wikipedia.org/wiki/Three-state_logic). 301 | 302 | In case of this library, we use rust std::option::Option to indicate that a pin is tristatable, and multiple pins are allowed to connect to same pin only if all are tristatable. The case of multiple tristatable pins have Some(\_) at the same time, this is equivalent to multiple pins going high/low at the same time, and thus the code will panic at runtime, equivalent to the chip burning. 303 | 304 | The tristatable pin must have type wrapped in std::option::Option, and the std::option::option can be used with fully qualified path (std::option::Option / ::std::option::Option), or option::Option (using `use std::option` before) or directly Option. any other way to use will not be currently counted as a tristatable pin. 305 | 306 | They way this is implemented is not the best or elegant way, but that was the only feasible way I could find. 307 | 308 | ### Note on pin tristating 309 | 310 | The input pins must never be tristated (set to None), unless you want the input to be ignored. Tristating the input type tristatable pins will make the pcb not send them the value of connected output pins, as in real life tristating acts as if the pin is removed from the board, and the skipping, therefore, is meant to be similar to that behavior 311 | 312 | ### Syntax of the pcb! 313 | 314 | Note that the pin names cannot be rust keyword. 315 | The pcb! macro has three sections, and must be listed in the specific order. The semicolons are significant and required. There can be `//` comments and `/**/` comments in the macro, but not `///` comments (doc-comments). 316 | 317 | - First list of chip declaration in format `chip ;`. This is a required section, as a pcb without chips is not sensible. 318 | - Then the list of pin connection in format `:: - ::;` the `chip-name` correspond to the name by which chips are declared in the first section. The `pin-name` MUST be the same as the name of struct member which corresponds to that pin. 319 | - Finally the exposed pins in format `expose ::(,::)* as `. Here at least one `::` is needed after `expose` and multiple pins can be specified here as comma separated values. The `` after `as` will be used as the name of the pin exposed by the pcb, and should be used if this pcb is used as a chip in other pcbs. 320 | 321 | Out of this, either one of connection list or exposed pins MUST be specified, or both can be specified. 322 | 323 | See the [exposed pins](#note-on-exposed-pin-shorting) section of notes to see exact semantics of specifying multiple pins to be exposed as a single pin. 324 | 325 | ### Note on pin types 326 | 327 | Unfortunately as the type information is not resolved at macro expansion time (and there is not type to represent type (yet)), we use the type-string, to represent types in PinMetadata. Unfortunately that means that for connected pin of the chips, the types must be exactly same when treaded as string : 328 | 329 | - both u8 is valid, but 330 | 331 | - std::option::Option and Option would not be treated as he same type 332 | 333 | As their string representations are different. Note that this can result in errors at runtime (i.e. after building the pcb) as `Option` can mean two different types in two different files. I don't know how to solve that currently, so better to use fully explicit types except for primitive datatypes. 334 | 335 | ### Note on use of io pins 336 | 337 | This lib is a bit opinionated when it comes to io type of pins. One should declare a pin IO type only when it will be used for both input and output. Do not use io for every pin, and the latch variable of each io pin should be kept as a non-pin member of the struct, and it should not be exposed. It should strictly be of bool type. 338 | 339 | ### Note on exposed pin shorting 340 | 341 | pcb! allows exposing multiple pins as a single pin to mimic shorting of pins while exposing in real hardware. This is useful in cases such as when an input to a gate is exposed, and same value is connected to a not gate and then output of not gate is given to some other gate (in case of D flip-flop) ; or you might want the same input to be given to multiple chips. 342 | 343 | That said, there are some rules that must be followed when exposing multiple chips as a single one, ans if not followed, it is undefined behavior. 344 | 345 | - Only input types pins are allowed to be shorted when exposing. Output type pin shorting does not make sense, as the two outputs might be different, and there is not confirm way to decide which of those two values should be given as value of that shorted single exposed pin, without some arbitrary rule or restrictions, Thus only input type pins are allowed to be shorted and exposed. 346 | 347 | - If multiple pins are shorted and exposed, those pins must not be connected to any other pins in the PCB. This is done as : 348 | 349 | - if the internal pin to which any of the shorted pin is connected , is input type and not connected to any other pin, it should be also exposed in the same way 350 | - Else the pin is either output type or io type. Connecting to output type is incorrect because : 351 | - for output pins, it would mean that the value of the output pin should be given to the input pins, but we expose the input pins and short them so that the value can be set from outside, thus similar issue of tie breaking as point 1. 352 | - for io pins, the exposed pins must be also tristated, and when the io pins will be in output more same issue as above occurs 353 | 354 | - The exposed tristated pins will be set to the value only if they are not in tristated state (Some(...)) in their chips, else will be ignored when setting values received from outside. 355 | 356 | - As much as possible, tristated pins should not be shorted if possible, as in edge cases they might give undefined behavior 357 | 358 | --- 359 | 360 | ## License 361 | 362 | Licensed under either of 363 | 364 | - Apache License, Version 2.0 365 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 366 | - MIT license 367 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 368 | 369 | at your option. 370 | 371 | ## Contribution 372 | 373 | Unless you explicitly state otherwise, any contribution intentionally submitted 374 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 375 | dual licensed as above, without any additional terms or conditions. 376 | -------------------------------------------------------------------------------- /crates/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "pcb-rs", 4 | "pcb-rs-traits", 5 | "pcb-rs-macros" 6 | ] 7 | -------------------------------------------------------------------------------- /crates/pcb-rs-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pcb-rs-macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Yashodhan Joshi"] 6 | description = "Prco-macros for pcb-rs crate" 7 | license = "MIT OR Apache-2.0" 8 | readme = "../../Readme.md" 9 | homepage= "https://github.com/YJDoc2/pcb-rs" 10 | repository = "https://github.com/YJDoc2/pcb-rs" 11 | documentation = "https://github.com/YJDoc2/pcb-rs#readme" 12 | keywords = ["pcb","hardware","proc-macro","simulation","electronics"] 13 | categories = ["simulation"] 14 | 15 | [lib] 16 | name = "pcb_rs_macros" 17 | proc-macro = true 18 | 19 | [dependencies] 20 | syn = {version = "1.0.82", features=["extra-traits"] } 21 | quote = "1.0.10" 22 | proc-macro2 = "1.0.33" 23 | pcb-rs-traits = {path="../pcb-rs-traits",version = "0.1.0"} 24 | -------------------------------------------------------------------------------- /crates/pcb-rs-macros/src/chip_derive.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | const PIN_ATTRIBUTE: &str = "pin"; 5 | 6 | const INVALID_PIN_ATTR_ERR: &str = 7 | "invalid pin attribute, currently only #[pin(input|output|io,latch)] is supported"; 8 | 9 | const INVALID_LATCH_ERR:&str = "invalid pin attribute, expected a latch pin name for io pin type : #[pin(io,)]"; 10 | 11 | const PIN_TYPE_INPUT: &str = "input"; 12 | const PIN_TYPE_OUTPUT: &str = "output"; 13 | const PIN_TYPE_IO: &str = "io"; 14 | 15 | #[derive(Debug)] 16 | enum __PinType { 17 | Input, 18 | Output, 19 | IO(syn::Ident), 20 | } 21 | 22 | impl std::fmt::Display for __PinType { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | write!( 25 | f, 26 | "{}", 27 | match self { 28 | __PinType::Input => "Input", 29 | __PinType::Output => "Output", 30 | __PinType::IO(_) => "IO", 31 | } 32 | ) 33 | } 34 | } 35 | 36 | #[derive(Debug)] 37 | struct __PinMetadata<'a> { 38 | name: &'a syn::Ident, 39 | pin_type: __PinType, 40 | data_type: &'a syn::Type, 41 | } 42 | 43 | fn get_pin_attr(f: &syn::Field) -> &syn::Attribute { 44 | for attr in &f.attrs { 45 | if attr.path.segments.len() == 1 && attr.path.segments[0].ident == PIN_ATTRIBUTE { 46 | return attr; 47 | } 48 | } 49 | // as we have already filtered fields to which have attributes, and 50 | // fields which have attribute other than pin, will give compile-error from rust's side 51 | // not finding the attribute is essentially unreachable 52 | unreachable!() 53 | } 54 | 55 | fn get_compiler_error(t: T, m: U) -> TokenStream 56 | where 57 | T: quote::ToTokens, 58 | U: std::fmt::Display, 59 | { 60 | syn::Error::new_spanned(t, m).to_compile_error() 61 | } 62 | 63 | fn get_pin_type( 64 | nm: syn::NestedMeta, 65 | latch: Option, 66 | ) -> Result<__PinType, TokenStream> { 67 | match nm { 68 | syn::NestedMeta::Meta(syn::Meta::Path(mut path)) => { 69 | let ptype = path.segments.pop().unwrap().into_value().ident; 70 | match ptype.to_string().as_str() { 71 | PIN_TYPE_INPUT => Ok(__PinType::Input), 72 | PIN_TYPE_OUTPUT => Ok(__PinType::Output), 73 | PIN_TYPE_IO => { 74 | if let Some(syn::NestedMeta::Meta(syn::Meta::Path(mut path))) = latch { 75 | if path.segments.len() != 1 { 76 | return Err(get_compiler_error(path, INVALID_LATCH_ERR)); 77 | } 78 | let t = path.segments.pop().unwrap().into_value(); 79 | Ok(__PinType::IO(t.ident)) 80 | } else { 81 | return Err(get_compiler_error(path, INVALID_LATCH_ERR)); 82 | } 83 | } 84 | _ => return Err(get_compiler_error(ptype, INVALID_PIN_ATTR_ERR)), 85 | } 86 | } 87 | meta => return Err(get_compiler_error(meta, INVALID_PIN_ATTR_ERR)), 88 | } 89 | } 90 | 91 | fn get_pin_metadata<'a>(fields: &'a [&syn::Field]) -> Result>, TokenStream> { 92 | let mut ret = Vec::with_capacity(fields.len()); 93 | for field in fields { 94 | let pin_attr = get_pin_attr(field); 95 | match pin_attr.parse_meta() { 96 | Err(e) => return Err(e.to_compile_error()), 97 | Ok(syn::Meta::List(mut args)) => { 98 | let pin_type_attr; 99 | let latch; 100 | if args.nested.len() == 1 { 101 | latch = None; 102 | pin_type_attr = args.nested.pop().unwrap().into_value(); 103 | } else { 104 | // as pop removes in reverse order, first we get latch and then io 105 | latch = args.nested.pop().map(|s| s.into_value()); 106 | pin_type_attr = args.nested.pop().unwrap().into_value(); 107 | } 108 | 109 | let pin_type = get_pin_type(pin_type_attr, latch)?; 110 | ret.push(__PinMetadata { 111 | name: &field.ident.as_ref().unwrap(), 112 | pin_type: pin_type, 113 | data_type: &field.ty, 114 | }) 115 | } 116 | Ok(meta) => return Err(get_compiler_error(meta, INVALID_PIN_ATTR_ERR)), 117 | } 118 | } 119 | Ok(ret) 120 | } 121 | 122 | fn pin_is_tristatable(ty: &syn::Type) -> bool { 123 | // this is a soft check rather than a hard check if the pin is tristatable 124 | // or not. Technically users can define an `Option` struct/enum in their code 125 | // which will still set this tristatable as true. But this allows a quick check 126 | // later in pcb! generated module to see if a pin can be tristatable or not. 127 | // In case one does use such custom enum, it will fail to compile due to the way 128 | // is_tristated fn is implemented in the Chip derive macro 129 | match ty { 130 | syn::Type::Path(p) => { 131 | let segments: Vec<_> = p.path.segments.iter().collect(); 132 | // if the path is fully qualified, i.e. std::option::Option or ::std::option::Option 133 | if segments.len() >= 3 { 134 | return segments[0].ident == "std" 135 | && segments[1].ident == "option" 136 | && segments[2].ident == "Option"; 137 | } 138 | // is user has brought std::option in scope 139 | if segments.len() >= 2 { 140 | return segments[0].ident == "option" && segments[1].ident == "Option"; 141 | } 142 | // if user it using the "normal" way 143 | if segments.len() >= 1 { 144 | return segments[0].ident == "Option"; 145 | } 146 | 147 | false 148 | } 149 | _ => false, 150 | } 151 | } 152 | 153 | pub fn derive_chip_impl(name: &syn::Ident, data: &syn::DataStruct) -> TokenStream { 154 | let fields = match &data.fields { 155 | syn::Fields::Unit | syn::Fields::Unnamed(_) => { 156 | panic!("Chip derive is only supported for named field structs") 157 | } 158 | syn::Fields::Named(f) => &f.named, 159 | }; 160 | 161 | let pin_fields = { 162 | let mut ret = Vec::with_capacity(fields.len()); 163 | for field in fields { 164 | if field.attrs.len() != 0 { 165 | ret.push(field); 166 | } 167 | } 168 | ret 169 | }; 170 | let metadata = match get_pin_metadata(&pin_fields) { 171 | Result::Ok(md) => md, 172 | Result::Err(e) => return e, 173 | }; 174 | 175 | let pin_hashmap_arm = metadata.iter().map(|p| { 176 | let name = p.name.to_string(); 177 | let ptype = syn::Ident::new(&p.pin_type.to_string(), data.struct_token.span); 178 | // have to do that, as we can't access it as #p.data_type 179 | let __temp = p.data_type; 180 | let dtype = quote! {#__temp}.to_string(); 181 | 182 | let tristatable = pin_is_tristatable(__temp); 183 | 184 | quote! { 185 | #name, pcb_rs::PinMetadata{ 186 | pin_type:pcb_rs::PinType::#ptype, 187 | data_type:#dtype, 188 | tristatable:#tristatable 189 | } 190 | } 191 | }); 192 | 193 | let get_pin_match_arm = metadata.iter().map(|p| { 194 | let name = p.name; 195 | let name_string = p.name.to_string(); 196 | quote! { 197 | #name_string => std::option::Option::Some(std::boxed::Box::new(self.#name.clone())) 198 | 199 | } 200 | }); 201 | 202 | let set_pin_match_arm = metadata.iter().map(|p| { 203 | let __name = p.name; 204 | let name_string = p.name.to_string(); 205 | let dtype = p.data_type; 206 | let assertion_err_msg = format!("internal error in pcb_rs derive chip : value sent to chip {} pin {} is of incorrect type",name,__name); 207 | quote! { 208 | #name_string =>{ 209 | assert!(val.is::<#dtype>(), #assertion_err_msg); 210 | self.#__name = val.downcast_ref::<#dtype>().unwrap().clone(); 211 | } 212 | } 213 | }); 214 | 215 | let tristated_match_arm = metadata.iter().map(|p| { 216 | let name = p.name; 217 | let name_string = p.name.to_string(); 218 | let dtype = p.data_type; 219 | 220 | // This is the hard check of tristatability. In case the user tries to use some custom type also 221 | // named `Option`, then they will get an compile time error, as the match arms are incompatible 222 | if pin_is_tristatable(dtype) { 223 | quote! { 224 | #name_string => matches!(self.#name,std::option::Option::None) 225 | } 226 | } else { 227 | quote! {#name_string => false} 228 | } 229 | }); 230 | 231 | let latch_match_arm = metadata 232 | .iter() 233 | .filter(|s| matches!(s.pin_type, __PinType::IO(_))) 234 | .map(|p| { 235 | let name = p.name; 236 | let name_string = name.to_string(); 237 | let latch_pin = match &p.pin_type { 238 | __PinType::IO(pin) => pin, 239 | _ => unreachable!(), 240 | }; 241 | quote! { 242 | #name_string => self.#latch_pin 243 | } 244 | }); 245 | 246 | quote! { 247 | impl pcb_rs::ChipInterface for #name{ 248 | 249 | fn get_pin_list(&self) -> std::collections::HashMap<&'static str, pcb_rs::PinMetadata>{ 250 | use std::collections::HashMap; 251 | let mut ret = HashMap::new(); 252 | #(ret.insert(#pin_hashmap_arm );)* 253 | ret 254 | } 255 | 256 | 257 | fn get_pin_value(&self,name: &str) -> std::option::Option>{ 258 | use std::any::Any; 259 | use std::boxed::Box; 260 | match name{ 261 | #(#get_pin_match_arm,)* 262 | _ => std::option::Option::None 263 | } 264 | } 265 | 266 | 267 | fn set_pin_value(&mut self,name: &str, val: &dyn std::any::Any){ 268 | use std::any::Any; 269 | match name{ 270 | #(#set_pin_match_arm,)* 271 | _ => {} 272 | } 273 | } 274 | 275 | fn is_pin_tristated(&self,name:&str)->bool{ 276 | match name{ 277 | #(#tristated_match_arm,)* 278 | _ => {false} 279 | } 280 | } 281 | 282 | fn in_input_mode(&self,name:&str)->bool{ 283 | match name{ 284 | #(#latch_match_arm,)* 285 | _ => false 286 | } 287 | } 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /crates/pcb-rs-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod chip_derive; 2 | mod pcb_macro; 3 | 4 | use proc_macro::TokenStream; 5 | use syn::{parse_macro_input, DeriveInput}; 6 | 7 | #[proc_macro_derive(Chip, attributes(pin))] 8 | pub fn derive_chip(input: TokenStream) -> TokenStream { 9 | let ast = parse_macro_input!(input as DeriveInput); 10 | match &ast.data { 11 | syn::Data::Enum(_) | syn::Data::Union(_) => { 12 | panic!("Chip derive is only supported for structs") 13 | } 14 | 15 | syn::Data::Struct(chip_struct) => { 16 | return chip_derive::derive_chip_impl(&ast.ident, chip_struct).into(); 17 | } 18 | } 19 | } 20 | 21 | #[proc_macro] 22 | pub fn pcb(input: TokenStream) -> TokenStream { 23 | let input = parse_macro_input!(input as pcb_macro::PcbMacroInput); 24 | let output: proc_macro2::TokenStream = input.into(); 25 | output.into() 26 | } 27 | -------------------------------------------------------------------------------- /crates/pcb-rs-macros/src/pcb_macro.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use std::collections::{HashMap, HashSet}; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::{Result, Token}; 5 | // ! TODO Add better error reporting 6 | // ! TODO maybe refactor the pin validation fn, where it also sets the pin metadata? 7 | 8 | const CHIP_DEFINITION_KEYWORD: &str = "chip"; 9 | const PIN_EXPOSE_KEYWORD: &str = "expose"; 10 | 11 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 12 | struct __ChipPin { 13 | chip: String, 14 | pin: String, 15 | } 16 | 17 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 18 | struct __ExposedPins{ 19 | pins:Vec<__ChipPin>, 20 | as_name:String 21 | } 22 | 23 | #[derive(Debug)] 24 | pub struct PcbMacroInput { 25 | name: syn::Ident, 26 | chip_map: HashMap>, 27 | pin_connection_list: HashMap<__ChipPin, HashSet<__ChipPin>>, 28 | exposed_pins: Vec<__ExposedPins>, 29 | } 30 | 31 | impl Parse for PcbMacroInput { 32 | fn parse(input: ParseStream) -> Result { 33 | let name = syn::Ident::parse(input)?; 34 | let content; 35 | let _braces = syn::braced!(content in input); 36 | let mut kw; 37 | let mut chip_map: HashMap> = HashMap::new(); 38 | 39 | // this just stores a simple representation of connected pins, 40 | // we convert this into a better structure to store into the builder in the into function 41 | let mut pin_connection_list: HashMap<__ChipPin, HashSet<__ChipPin>> = HashMap::new(); 42 | 43 | let mut exposed_pins: Vec<__ExposedPins> = Vec::new(); 44 | 45 | // this parses the module 46 | loop { 47 | kw = syn::Ident::parse(&content)?; 48 | if kw != CHIP_DEFINITION_KEYWORD { 49 | break; 50 | } 51 | let module_name = syn::Ident::parse(&content)?; 52 | let _ = ::parse(&content)?; 53 | chip_map.insert(module_name.to_string(), Vec::new()); 54 | } 55 | 56 | if chip_map.is_empty() { 57 | return Err(syn::Error::new_spanned(&name,"cannot make pcb with no chips!")); 58 | 59 | } 60 | 61 | // we allow pcb with no connections as one might use pcb as a 62 | // convenient collection of chips 63 | 64 | // here the kw will actually point to name of chip, for pin connections 65 | // unless there are no connections, in which case it will break on first iter 66 | loop { 67 | if kw == PIN_EXPOSE_KEYWORD { 68 | break; 69 | } 70 | let chip1 = kw.to_string(); 71 | let _ = ::parse(&content)?; 72 | let pin1 = syn::Ident::parse(&content)?.to_string(); 73 | // pin connection token is - 74 | let _ = ::parse(&content); 75 | let chip2 = syn::Ident::parse(&content)?.to_string(); 76 | let _ = ::parse(&content)?; 77 | let pin2 = syn::Ident::parse(&content)?.to_string(); 78 | let _ = ::parse(&content)?; 79 | 80 | if (&chip1,&pin1) == (&chip2,&pin2){ 81 | let t = format!("attempted to connect a pin to itself : chip `{}` pin `{}` appears to have a self-connection, which is redundant",chip1,pin1); 82 | return Err(syn::Error::new_spanned(&chip1,t)); 83 | } 84 | 85 | if !chip_map.contains_key(&chip1) { 86 | let t = format!("use of undeclared chip {}", chip1); 87 | return Err(syn::Error::new_spanned(&chip1,t)); 88 | } 89 | 90 | if !chip_map.contains_key(&chip2) { 91 | let t = format!("use of undeclared chip {}", chip2); 92 | return Err(syn::Error::new_spanned(&chip2,t)); 93 | } 94 | 95 | // now we know for sure that both chips are declared and exists in the map 96 | 97 | let t = chip_map.get_mut(&chip1).unwrap(); 98 | t.push(pin1.clone()); 99 | let t = chip_map.get_mut(&chip2).unwrap(); 100 | t.push(pin2.clone()); 101 | 102 | let chip_pin1 = __ChipPin { 103 | chip: chip1, 104 | pin: pin1, 105 | }; 106 | let chip_pin2 = __ChipPin { 107 | chip: chip2, 108 | pin: pin2, 109 | }; 110 | 111 | if let Some(l) = pin_connection_list.get_mut(&chip_pin1) { 112 | // we first check if pin1 is already an entry, if so then add pin2 to its set 113 | l.insert(chip_pin2); 114 | } else if let Some(l) = pin_connection_list.get_mut(&chip_pin2) { 115 | // else we check if pin2 is already an entry 116 | l.insert(chip_pin1); 117 | } else { 118 | let mut _t = HashSet::new(); 119 | _t.insert(chip_pin2); 120 | pin_connection_list.insert(chip_pin1, _t); 121 | } 122 | 123 | // we have to parse it here for the next iteration 124 | 125 | match syn::Ident::parse(&content) { 126 | Result::Ok(i) => kw = i, 127 | Result::Err(_) => { 128 | return Ok(PcbMacroInput { 129 | name, 130 | pin_connection_list, 131 | chip_map, 132 | exposed_pins: Vec::new(), 133 | }) 134 | } 135 | } 136 | } 137 | // now here the kw should be exposed 138 | loop { 139 | let chip = syn::Ident::parse(&content)?.to_string(); 140 | let _ = ::parse(&content)?; 141 | let pin = syn::Ident::parse(&content)?.to_string(); 142 | if !chip_map.contains_key(&chip) { 143 | let t = format!("use of undeclared chip in expose pin : {}", chip); 144 | return Err(syn::Error::new_spanned(&chip,t)); 145 | } 146 | 147 | let mut pins = vec![__ChipPin{ 148 | chip,pin 149 | }]; 150 | // if we have something like `expose c1::p1,c2::p1 as p3;` 151 | if content.peek(Token![,]){ 152 | // we do have it like `expose c1::p1,c2::p1 as p3;` 153 | loop{ 154 | let _ = ::parse(&content)?; 155 | let chip = syn::Ident::parse(&content)?.to_string(); 156 | let _ = ::parse(&content)?; 157 | let pin = syn::Ident::parse(&content)?.to_string(); 158 | if !chip_map.contains_key(&chip) { 159 | let t = format!("use of undeclared chip in expose pin : {}", chip); 160 | return Err(syn::Error::new_spanned(&chip,t)); 161 | } 162 | pins.push(__ChipPin{chip,pin}); 163 | // if we have a comma, there are more pins, else we can exit the loop 164 | if content.peek(Token![,]){ 165 | continue; 166 | }else{ 167 | break; 168 | } 169 | } 170 | } 171 | // now there must be an `as` keyword 172 | let _ = ::parse(&content); 173 | let as_name = syn::Ident::parse(&content)?.to_string(); 174 | let _ = ::parse(&content); 175 | 176 | exposed_pins.push(__ExposedPins { pins, as_name }); 177 | match syn::Ident::parse(&content) { 178 | Result::Ok(i) => { 179 | if i != PIN_EXPOSE_KEYWORD { 180 | let t =format!("expected 'expose' found {} instead", i.to_string()); 181 | return Err(syn::Error::new_spanned(i,t)); 182 | } 183 | } 184 | // this just means we have completed the parsing 185 | Result::Err(_) => break, 186 | } 187 | } 188 | 189 | let mut temp = HashMap::new(); 190 | for ep in &exposed_pins{ 191 | for p in &ep.pins{ 192 | if temp.contains_key(p){ 193 | let previous = temp.get(p).unwrap(); 194 | let t = format!("pin exposed multiple times : chip {} pin {} is exposed as {} and {}", 195 | p.chip,p.pin,previous, ep.as_name 196 | ); 197 | return Err(syn::Error::new_spanned(&name,t)); 198 | }else{ 199 | temp.insert(p,&ep.as_name); 200 | } 201 | 202 | 203 | } 204 | } 205 | 206 | Ok(PcbMacroInput { 207 | name, 208 | pin_connection_list, 209 | chip_map, 210 | exposed_pins, 211 | }) 212 | } 213 | } 214 | 215 | impl Into for PcbMacroInput { 216 | fn into(self) -> proc_macro2::TokenStream { 217 | self.generate() 218 | } 219 | } 220 | 221 | impl PcbMacroInput { 222 | 223 | 224 | // This might be more efficiently implemented, I think this has worst case O(n^2)? 225 | fn get_short_pin_set(&self)->Vec>{ 226 | // first let us make a vec to store the initial pin connections 227 | // generated from given param 228 | let mut initial_collection = Vec::new(); 229 | 230 | // we fill that vec by pushing sets in the param, but adding the hashmap key to the set 231 | // as we need set of connected pin, and the key-value structure doesn't make a difference 232 | for (main,connected) in &self.pin_connection_list{ 233 | let mut t = connected.clone(); 234 | t.insert(main.clone()); 235 | initial_collection.push(t); 236 | } 237 | 238 | // this is the final return, which is the collection of groups of all the pins that are 239 | // shorted, i.e. connected electrically, so that voltage at any 240 | // one fo the pins in the individual group will affect rest of the pins in that group 241 | let mut shorted_pins = Vec::new(); 242 | 243 | loop{ 244 | 245 | // we take a set from the initial sets, if no sets are remaining, 246 | // work is done 247 | let mut set = match initial_collection.pop(){ 248 | Some(s)=>s, 249 | None=>break 250 | }; 251 | // a temp vector to store the groups which does not have any pins in common 252 | // with the set above 253 | let mut t = Vec::new(); 254 | 255 | // we check if any remaining set in the initial collection 256 | // has a pin common with the set under consideration, 257 | // if it does, we extend the set, else store that (remaining) set in 258 | // the temp vector 259 | for s in initial_collection{ 260 | if set.intersection(&s).next().is_some(){ 261 | set.extend(s.into_iter()); 262 | }else{ 263 | t.push(s); 264 | } 265 | } 266 | 267 | // not the set contains pins which are shorted, we store that in the return variable 268 | shorted_pins.push(set.into_iter().collect()); 269 | 270 | // set the initial collection to temp, so it contains next candidates to check 271 | initial_collection = t; 272 | } 273 | 274 | // return shorted pins 275 | shorted_pins 276 | } 277 | 278 | fn generate(self) -> proc_macro2::TokenStream { 279 | let pcb_name = &self.name; 280 | let builder_name = quote::format_ident!("{}Builder", pcb_name); 281 | 282 | let chip_names = self.chip_map.iter().map(|(name, _)| quote! {#name}); 283 | 284 | let chip_pin_check = self.chip_map.iter().map(|(name,pins)|{ 285 | if pins.is_empty(){ 286 | return quote!{}; 287 | } 288 | let pin_names = pins.iter().map(|n|{quote!{#n}}); 289 | let exposed_pins:Vec<_> = self.exposed_pins.iter() 290 | .flat_map(|ep|&ep.pins) 291 | .filter(|p|{p.chip == *name}) 292 | .map(|cp|&cp.pin) 293 | .map(|n|quote!{#n}) 294 | .collect(); 295 | let exposed_pin_check = if exposed_pins.len() == 0{ 296 | quote!{} 297 | }else{ 298 | let names = exposed_pins.iter(); 299 | quote!{ 300 | for pin in [#(#names),*]{ 301 | if !chip_pins.contains_key(pin){ 302 | return std::result::Result::Err(format!("Invalid chip added : chip {} expected to have pin named {}, not found",#name,pin)); 303 | } 304 | } 305 | } 306 | }; 307 | quote!{ 308 | let chip = self.added_chip_map.get(#name).unwrap(); 309 | let chip_pins = chip.get_pin_list(); 310 | for pin in [#(#pin_names),*]{ 311 | if !chip_pins.contains_key(pin){ 312 | return std::result::Result::Err(format!("Invalid chip added : chip {} expected to have pin named {}, not found",#name,pin)); 313 | } 314 | } 315 | #exposed_pin_check 316 | } 317 | }); 318 | 319 | // TODO improve this! 320 | // this will bind some variables to the actual entered chips for the builder 321 | let instantiate_chip_vars = self.chip_map.iter().map(|(name, _)| { 322 | if self.pin_connection_list.is_empty(){ 323 | quote!{} 324 | }else{ 325 | 326 | let __name = quote::format_ident!("_{}",&name); 327 | quote! {let #__name = self.added_chip_map.get(#name).unwrap().get_pin_list();} 328 | } 329 | }); 330 | 331 | 332 | 333 | let pin_connection_checks = self 334 | .pin_connection_list 335 | .iter() 336 | .map(|(pin, connected_pins)| { 337 | let _chip = &pin.chip; 338 | let _pin = &pin.pin; 339 | let chip_ident = quote::format_ident!("_{}",_chip); 340 | let connected_pin_iter = connected_pins.iter().map(|pin| { 341 | let __chip = &pin.chip; 342 | let __pin = &pin.pin; 343 | let chip_ident = quote::format_ident!("_{}",__chip); 344 | quote! { 345 | let __pin2 = #chip_ident.get(#__pin).unwrap(); 346 | if !__pin1.is_connectable(__pin2){ 347 | return std::result::Result::Err( 348 | format!("Invalid chip connection : cannot connect chip {}'s pin {} ({:?}) to chip {}'s pin {} ({:?})", 349 | #_chip,#_pin,__pin1,#__chip,#__pin,__pin2 350 | ) 351 | ) 352 | } 353 | self.pin_metadata_cache.insert(pcb_rs::ChipPin{ 354 | chip: #__chip, 355 | pin: #__pin, 356 | },*__pin2); 357 | } 358 | }); 359 | 360 | quote! { 361 | let __pin1 = #chip_ident.get(#_pin).unwrap(); 362 | self.pin_metadata_cache.insert(pcb_rs::ChipPin{ 363 | chip:#_chip, 364 | pin:#_pin 365 | },*__pin1); 366 | #(#connected_pin_iter)* 367 | } 368 | }); 369 | 370 | let shorted_pins = self.get_short_pin_set(); 371 | 372 | // TODO maybe move this to the parsing stage? 373 | for ep in &self.exposed_pins{ 374 | let pins = &ep.pins; 375 | if pins.len() == 1{ 376 | // ignore the check if non-shorting expose 377 | continue; 378 | } 379 | for pin in pins{ 380 | for sp in &shorted_pins{ 381 | if sp.contains(pin){ 382 | let error_msg = format!( 383 | "exposed shorted pins {:?} are also shorted with non-exposed pins {:?} which is not allowed", 384 | pins, 385 | sp 386 | ); 387 | return quote::quote_spanned!{self.name.span()=> 388 | compile_error!(#error_msg); 389 | }; 390 | 391 | } 392 | 393 | } 394 | } 395 | } 396 | 397 | let shorted_pins_tokens = shorted_pins.iter().map(|group|{ 398 | let g = group.iter().map(|cp|{ 399 | let chip = &cp.chip; 400 | let pin= &cp.pin; 401 | quote!{ 402 | pcb_rs::ChipPin{ 403 | chip:#chip, 404 | pin:#pin 405 | } 406 | } 407 | }); 408 | quote!{ 409 | std::vec![#(#g),*] 410 | } 411 | }); 412 | 413 | // TODO add a test to verify this 414 | let exposed_pin_type_check = { 415 | let instantiate_chip_vars = self.chip_map.iter().map(|(name, _)| { 416 | let __name = quote::format_ident!("_{}",&name); 417 | quote! {let #__name = self.added_chip_map.get(#name).unwrap().get_pin_list();} 418 | }); 419 | 420 | let checks = self.exposed_pins.iter().map(|ep|{ 421 | if ep.pins.len() == 1{ 422 | // skip for single (non-shorted) exposed pin 423 | return quote!{}; 424 | } 425 | let zeroth_chip = &ep.pins[0].chip; 426 | let zeroth_pin = &ep.pins[0].pin; 427 | let zeroth_chip_ident = quote::format_ident!("_{}",zeroth_chip); 428 | let zeroth_extracted = quote!{ 429 | let first_type = &#zeroth_chip_ident.get(#zeroth_pin).unwrap().data_type; 430 | }; 431 | let pin_checks = ep.pins.iter().map(|p|{ 432 | let _chip = &p.chip; 433 | let _pin = &p.pin; 434 | let chip_ident = quote::format_ident!("_{}",_chip); 435 | quote!{ 436 | let md = #chip_ident.get(#_pin).unwrap(); 437 | if !matches!(md.pin_type,pcb_rs::PinType::Input){ 438 | return std::result::Result::Err(format!( 439 | "chip {} pin {} is expected to be input type, as it is exposed and shorted with other pins, but was not. only input type pins are allowed to be shorted when exposing", 440 | #_chip,#_pin 441 | )); 442 | } 443 | if md.data_type != *first_type{ 444 | return std::result::Result::Err(format!( 445 | "chip {} pin {} is expected to be of {} type, as it is shorted with pin of that type, but was found to be of {} type", 446 | #_chip,#_pin,first_type,md.data_type 447 | )); 448 | } 449 | } 450 | }); 451 | quote!{ 452 | #zeroth_extracted 453 | #(#pin_checks)* 454 | } 455 | }); 456 | 457 | 458 | quote!{ 459 | #(#instantiate_chip_vars)* 460 | #(#checks)* 461 | } 462 | }; 463 | 464 | // ci is ChipInterface 465 | 466 | let ci_pin_map = self.exposed_pins.iter().map(|ep|{ 467 | // note that metadata for all shorted exposed pins must be same, which 468 | // will be verified at building time, so we can just give metadata of first pin 469 | // and in case there is a single pin, it will be 0th 470 | let pin_name = &ep.pins[0].pin; 471 | let chip_name = &ep.pins[0].chip; 472 | let as_name = &ep.as_name; 473 | quote!{ 474 | let __chip = self.chips.get(#chip_name).unwrap(); 475 | let md = __chip.get_pin_list().get(#pin_name).unwrap().clone(); 476 | ret.insert(#as_name,md); 477 | } 478 | }); 479 | 480 | let ci_get_value = self.exposed_pins.iter().map(|ep|{ 481 | // note that the shorted pins must be of input type, shorting multiple o/p 482 | // is not valid and will error at building time 483 | // thus, this is only valid when all pins are input type, and as all are shorted, 484 | // we can just give 0th pin's value 485 | let pin_name = &ep.pins[0].pin; 486 | let chip_name = &ep.pins[0].chip; 487 | let as_name = &ep.as_name; 488 | quote!{ 489 | #as_name =>{ 490 | let __chip = self.chips.get(#chip_name).unwrap(); 491 | return __chip.get_pin_value(#pin_name); 492 | } 493 | } 494 | }); 495 | 496 | let ci_set_value = self.exposed_pins.iter().map(|ep|{ 497 | let t = ep.pins.iter().map(|cp|{ 498 | let pin_name = &cp.pin; 499 | let chip_name = &cp.chip; 500 | quote!{ 501 | let __chip = self.chips.get_mut(#chip_name).unwrap(); 502 | if ! __chip.is_pin_tristated(#chip_name){ 503 | __chip.set_pin_value(#pin_name,val); 504 | } 505 | } 506 | }); 507 | let as_name = &ep.as_name; 508 | quote!{ 509 | #as_name =>{ 510 | #(#t)* 511 | return; 512 | } 513 | } 514 | }); 515 | 516 | let ci_pin_tristated = self.exposed_pins.iter().map(|ep|{ 517 | // as the only pins to be grouped in expose are of input type, 518 | // so it does not make sense to ask if the pins are tristated or not, 519 | // as that majorly matters when the pin is output type. 520 | // and when setting pins values, pins which are tristated are ignored anyways, 521 | // so we can just return false in that case 522 | let as_name = &ep.as_name; 523 | if ep.pins.len() == 1{ 524 | let chip_name = &ep.pins[0].chip; 525 | let pin_name = &ep.pins[0].pin; 526 | quote!{ 527 | #as_name =>{ 528 | let __chip = self.chips.get(#chip_name).unwrap(); 529 | return __chip.is_pin_tristated(#pin_name); 530 | } 531 | } 532 | }else{ 533 | quote!{ 534 | #as_name =>{ 535 | false 536 | } 537 | } 538 | } 539 | 540 | }); 541 | 542 | let ci_pin_input_mode = self.exposed_pins.iter().map(|ep|{ 543 | // as only input type pins are allowed to be shorted , in case there are shorted pins, 544 | // they will have to be in input type 545 | let as_name = &ep.as_name; 546 | if ep.pins.len() == 1{ 547 | let chip_name = &ep.pins[0].chip; 548 | let pin_name = &ep.pins[0].pin; 549 | quote!{ 550 | #as_name =>{ 551 | let __chip = self.chips.get(#chip_name).unwrap(); 552 | return __chip.in_input_mode(#pin_name); 553 | } 554 | } 555 | }else{ 556 | quote!{ 557 | #as_name =>{ 558 | return true; 559 | } 560 | } 561 | } 562 | 563 | }); 564 | 565 | quote! { 566 | 567 | pub struct #builder_name{ 568 | added_chip_map:std::collections::HashMap>, 569 | shorted_pins:std::vec::Vec>, 570 | pin_metadata_cache:std::collections::HashMap 571 | } 572 | 573 | impl #builder_name{ 574 | 575 | pub fn new()->Self{ 576 | let shorted = std::vec![#(#shorted_pins_tokens),*]; 577 | Self{ 578 | added_chip_map:std::collections::HashMap::new(), 579 | shorted_pins:shorted, 580 | pin_metadata_cache:std::collections::HashMap::new() 581 | } 582 | } 583 | 584 | pub fn add_chip(mut self,name:&str,chip: std::boxed::Box)->Self{ 585 | self.added_chip_map.insert(name.to_string(),chip); 586 | self 587 | } 588 | 589 | pub fn build(mut self)->std::result::Result<#pcb_name, std::string::String>{ 590 | self.check_added_all_chips()?; 591 | self.check_valid_chips()?; 592 | // this will validate pin connections as well as set up 593 | // the pin metadata in hashmap 594 | self.check_valid_pin_connection()?; 595 | self.check_exposed_pin_types()?; 596 | let pin_connections = self.get_pin_connections()?; 597 | 598 | std::result::Result::Ok(#pcb_name{ 599 | chips:self.added_chip_map, 600 | pin_connections 601 | }) 602 | } 603 | 604 | fn check_added_all_chips(&self)-> std::result::Result<(),std::string::String>{ 605 | for chip in [#(#chip_names),*]{ 606 | if !self.added_chip_map.contains_key(chip){ 607 | return std::result::Result::Err(format!("chip {} defined in pcb design, but not added",chip)) 608 | } 609 | } 610 | std::result::Result::Ok(()) 611 | } 612 | fn check_valid_chips(&self)-> std::result::Result<(),std::string::String>{ 613 | #(#chip_pin_check)* 614 | std::result::Result::Ok(()) 615 | } 616 | 617 | // yes this does two things by also setting the chip metadata in hashmap, but otherwise there 618 | // would have been a lot of code duplication, so go with it for now 619 | fn check_valid_pin_connection(&mut self)->std::result::Result<(),std::string::String>{ 620 | #(#instantiate_chip_vars)* 621 | #(#pin_connection_checks)* 622 | 623 | std::result::Result::Ok(()) 624 | } 625 | 626 | fn check_exposed_pin_types(&self)->std::result::Result<(),std::string::String>{ 627 | #exposed_pin_type_check 628 | 629 | std::result::Result::Ok(()) 630 | } 631 | 632 | // This function can be optimized a bit by removing multiple iter() and map() calls 633 | // some of might be redundant 634 | fn get_pin_connections(&self)->std::result::Result,std::string::String>{ 635 | use std::vec::Vec; 636 | use pcb_rs::{ChipPin,PinType,ConnectedPins,PinMetadata}; 637 | 638 | let mut ret:Vec = Vec::with_capacity(self.shorted_pins.len()); 639 | for group in &self.shorted_pins{ 640 | let input_pins = group.iter().filter(|pin|{ 641 | let md = self.pin_metadata_cache.get(pin).unwrap(); 642 | matches!(md.pin_type,pcb_rs::PinType::Input) || matches!(md.pin_type,pcb_rs::PinType::IO) 643 | }).map(|pin|(*pin,self.pin_metadata_cache.get(pin).unwrap())).collect(); 644 | 645 | let output_pins = group.iter().filter(|pin|{ 646 | let md = self.pin_metadata_cache.get(pin).unwrap(); 647 | matches!(md.pin_type,pcb_rs::PinType::Output) || matches!(md.pin_type,pcb_rs::PinType::IO) 648 | }).map(|pin|(*pin,self.pin_metadata_cache.get(pin).unwrap())).collect(); 649 | 650 | ret.push(pcb_rs::get_pin_group(input_pins,output_pins)?); 651 | } 652 | 653 | Ok(ret) 654 | } 655 | 656 | } 657 | 658 | pub struct #pcb_name{ 659 | chips:std::collections::HashMap>, 660 | pin_connections:std::vec::Vec 661 | } 662 | 663 | impl #pcb_name{ 664 | pub fn get_chip<'s,T:pcb_rs::HardwareModule>(&'s self,chip:&str)->std::option::Option<&'s T>{ 665 | match self.chips.get(chip){ 666 | std::option::Option::None => None, 667 | Some(c)=>{ 668 | c.downcast_ref() 669 | } 670 | } 671 | } 672 | 673 | pub fn get_chip_mut<'s,T:pcb_rs::HardwareModule>(&'s mut self,chip:&str)->std::option::Option<&'s mut T>{ 674 | match self.chips.get_mut(chip){ 675 | std::option::Option::None => None, 676 | Some(c)=>{ 677 | c.downcast_mut() 678 | } 679 | } 680 | } 681 | } 682 | 683 | impl pcb_rs::ChipInterface for #pcb_name{ 684 | 685 | fn get_pin_list(&self) -> std::collections::HashMap<&'static str, pcb_rs::PinMetadata>{ 686 | let mut ret = std::collections::HashMap::new(); 687 | #(#ci_pin_map)* 688 | ret 689 | } 690 | 691 | fn get_pin_value(&self, name: &str) -> std::option::Option>{ 692 | match name{ 693 | #(#ci_get_value),* 694 | _ => None 695 | } 696 | } 697 | 698 | fn set_pin_value(&mut self, name: &str, val: &dyn std::any::Any){ 699 | match name{ 700 | #(#ci_set_value),* 701 | _ => {} 702 | } 703 | } 704 | 705 | fn is_pin_tristated(&self, name: &str) -> bool{ 706 | match name{ 707 | #(#ci_pin_tristated),* 708 | _ => false 709 | } 710 | } 711 | 712 | fn in_input_mode(&self, name: &str) -> bool{ 713 | match name{ 714 | #(#ci_pin_input_mode),* 715 | _ => false 716 | } 717 | } 718 | } 719 | 720 | impl pcb_rs::Chip for #pcb_name{ 721 | fn tick(&mut self){ 722 | use std::any::Any; 723 | use std::option::Option; 724 | use pcb_rs::{ChipPin,PinType,ConnectedPins,PinMetadata}; 725 | 726 | 727 | for chip in self.chips.values_mut(){ 728 | chip.tick(); 729 | } 730 | 731 | for connection in &self.pin_connections{ 732 | match connection{ 733 | ConnectedPins::Pair{source,destination}=>{ 734 | // because we have made sure the chips and pins exist properly, 735 | // we can unwrap directly 736 | // also this is simplest, as there is a single input and single output pin, 737 | // both of which are of respective types, so even if they're tristated, 738 | // their data types will match, and there won't be an issue 739 | // TODO implement a test to verify this 740 | let src = self.chips.get(source.chip).unwrap(); 741 | let val = src.get_pin_value(source.pin).unwrap(); 742 | // we have to take it as ref, otherwise the box is passed around, 743 | // instead of the data which we want 744 | let data_ref = val.as_ref(); 745 | let dest = self.chips.get_mut(destination.chip).unwrap(); 746 | dest.set_pin_value(destination.pin,data_ref); 747 | } 748 | ConnectedPins::Broadcast{source,destinations}=>{ 749 | // now this can get tricky, as the source pin might be of type 750 | // io, so it can be present in destinations as well, so we have to skip it 751 | // as well as check that if there is any destination pin that is 752 | // io type, then it is set to input mode 753 | // also we do not check if the source pin, if of io type 754 | // is set to input mode or not, the destination pins will get 755 | // whatever its value is regardless 756 | let chip = self.chips.get(source.chip).unwrap(); 757 | let val = chip.get_pin_value(source.pin).unwrap(); 758 | let data_ref = val.as_ref(); 759 | for dest in destinations{ 760 | if dest == source{ 761 | // accounts for the io type source pin 762 | continue; 763 | } 764 | let chip = self.chips.get_mut(dest.chip).unwrap(); 765 | // we don't have to check if any other pin is of io type, because if it was 766 | // then taht set-up would be in the tristated group 767 | chip.set_pin_value(dest.pin,data_ref); 768 | } 769 | } 770 | ConnectedPins::Tristated{sources,destinations}=>{ 771 | let mut val = Option::None; 772 | let mut active_chip = ChipPin{ 773 | chip: "unknown", 774 | pin: "unknown", 775 | }; 776 | for src in sources{ 777 | let chip = self.chips.get(src.chip).unwrap(); 778 | // input mode check if specifically for io pins, which would be present in 779 | // both sources and destinations, and if one want to get the data in io pin 780 | // the pin must not be in tristated mode, but must be in input mode 781 | if !chip.in_input_mode(src.pin) && !chip.is_pin_tristated(src.pin){ 782 | if val.is_some(){ 783 | panic!("Multiple pins found active at the same time in a tristated group : pin {:?} and pin {:?} in group {:?}. Only one pin in a tristated group can be active at a time",src, active_chip,connection); 784 | } 785 | active_chip = *src; 786 | val = Option::Some(chip.get_pin_value(src.pin).unwrap()); 787 | } 788 | } 789 | if let Some(val) = val{ 790 | let data_ref = val.as_ref(); 791 | for dest in destinations{ 792 | // skip in case the pin is io type and present in both source and destinations 793 | if *dest == active_chip{ 794 | continue; 795 | } 796 | let chip = self.chips.get_mut(dest.chip).unwrap(); 797 | // skip tristated pins 798 | if chip.is_pin_tristated(dest.pin){ 799 | continue; 800 | } 801 | chip.set_pin_value(dest.pin,data_ref); 802 | } 803 | } 804 | 805 | } 806 | } 807 | } 808 | 809 | } 810 | } 811 | } 812 | } 813 | } 814 | -------------------------------------------------------------------------------- /crates/pcb-rs-traits/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pcb-rs-traits" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Yashodhan Joshi"] 6 | description = "Traits and common structs for pcb-rs" 7 | license = "MIT OR Apache-2.0" 8 | readme = "../../Readme.md" 9 | homepage= "https://github.com/YJDoc2/pcb-rs" 10 | repository = "https://github.com/YJDoc2/pcb-rs" 11 | documentation = "https://github.com/YJDoc2/pcb-rs#readme" 12 | keywords = ["pcb","hardware","proc-macro","simulation","electronics"] 13 | categories = ["simulation"] 14 | 15 | [lib] 16 | name = "pcb_rs_traits" 17 | 18 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 19 | 20 | [dependencies] 21 | downcast-rs = "1.2.0" 22 | -------------------------------------------------------------------------------- /crates/pcb-rs-traits/src/lib.rs: -------------------------------------------------------------------------------- 1 | use downcast_rs::{impl_downcast, Downcast}; // TODO someday remove this dependency and implement the feature in this crate itself 2 | use std::any::Any; 3 | use std::collections::HashMap; 4 | 5 | mod util; 6 | pub use util::get_pin_group; 7 | 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 9 | pub 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 20 | pub 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 30 | pub 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 44 | pub 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, 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, 60 | destinations: Vec, 61 | }, 62 | } 63 | 64 | impl 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 | 81 | impl 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 106 | pub 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>; 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 134 | pub 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/ 157 | pub trait HardwareModule: ChipInterface + Chip + Downcast {} 158 | 159 | impl_downcast!(HardwareModule); 160 | 161 | impl HardwareModule for T where T: ChipInterface + Chip + Downcast {} 162 | -------------------------------------------------------------------------------- /crates/pcb-rs-traits/src/util.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub fn get_pin_group( 4 | input: Vec<(ChipPin, &PinMetadata)>, 5 | output: Vec<(ChipPin, &PinMetadata)>, 6 | ) -> Result { 7 | // super basic, there is a single input pin and single output pin 8 | if input.len() == 1 && output.len() == 1 { 9 | return Ok(ConnectedPins::Pair { 10 | source: output[0].0, 11 | destination: input[0].0, 12 | }); 13 | } 14 | 15 | let all_tristatable = 16 | input.iter().all(|(_, md)| md.tristatable) && output.iter().all(|(_, md)| md.tristatable); 17 | let any_tristatable = 18 | input.iter().any(|(_, md)| md.tristatable) && output.iter().any(|(_, md)| md.tristatable); 19 | 20 | // TODO validate these in tests later 21 | 22 | // here, there are multiple output pins, and not all are tristated 23 | if !all_tristatable && output.len() > 1 { 24 | let temp: Vec<&PinMetadata> = output 25 | .into_iter() 26 | .chain(input.into_iter()) 27 | .map(|(_, md)| md) 28 | .collect(); 29 | return Result::Err(format!( 30 | "multiple output pins found in a non-tristated pin group : {:#?}\nOnly groups where all pins are tristatable are allowed to have multiple output pins" 31 | ,temp)); 32 | } 33 | 34 | // I'm not sure if this condition can ever occur, as we check each connected pin pairing, 35 | // and tristatable mark is like a colour, so that only tristatable pin can be connected to tristatable pins 36 | // but it is put here as a safety measure 37 | if !all_tristatable && any_tristatable { 38 | let temp: Vec<&PinMetadata> = output 39 | .into_iter() 40 | .chain(input.into_iter()) 41 | .map(|(_, md)| md) 42 | .collect(); 43 | return Result::Err(format!( 44 | "these pins are shorted, but not all are tristatable : {:#?}\nIf any pin a a shourted pin group is tristatable, then all must be tristatable" 45 | ,temp 46 | )); 47 | } 48 | 49 | // single output pin connected to multiple input pins, this is a broadcast 50 | if output.len() == 1 { 51 | return Ok(ConnectedPins::Broadcast { 52 | source: output[0].0, 53 | destinations: input.into_iter().map(|(p, _)| p).collect(), 54 | }); 55 | } 56 | 57 | Ok(ConnectedPins::Tristated { 58 | sources: output.into_iter().map(|(p, _)| p).collect(), 59 | destinations: input.into_iter().map(|(p, _)| p).collect(), 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /crates/pcb-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pcb-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Yashodhan Joshi"] 6 | description = "A library to easily wite Software Emulated Hardware" 7 | license = "MIT OR Apache-2.0" 8 | readme = "../../Readme.md" 9 | homepage= "https://github.com/YJDoc2/pcb-rs" 10 | repository = "https://github.com/YJDoc2/pcb-rs" 11 | documentation = "https://github.com/YJDoc2/pcb-rs#readme" 12 | keywords = ["pcb","hardware","proc-macro","simulation","electronics"] 13 | categories = ["simulation"] 14 | 15 | [lib] 16 | name = "pcb_rs" 17 | 18 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 19 | 20 | [dependencies] 21 | pcb-rs-macros = { path = "../pcb-rs-macros", version = "0.1.0" } 22 | pcb-rs-traits = { path = "../pcb-rs-traits", version = "0.1.0" } 23 | -------------------------------------------------------------------------------- /crates/pcb-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use pcb_rs_macros::*; 2 | pub use pcb_rs_traits::*; 3 | -------------------------------------------------------------------------------- /pcb-rs-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pcb-rs-tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Yashodhan Joshi"] 6 | description = "testbench for pcb-rs macros" 7 | license = "MIT OR Apache-2.0" 8 | readme = "../README.md" 9 | homepage= "https://github.com/YJDoc2/pcb-rs" 10 | repository = "https://github.com/YJDoc2/pcb-rs" 11 | documentation = "https://github.com/YJDoc2/pcb-rs#readme" 12 | keywords = ["pcb","hardware","proc-macro","simulation","electronics"] 13 | categories = ["simulation"] 14 | 15 | 16 | [dependencies] 17 | pcb-rs = { path = "../crates/pcb-rs" , version = "0.1.0"} -------------------------------------------------------------------------------- /pcb-rs-tests/src/chip_derive.rs: -------------------------------------------------------------------------------- 1 | use pcb_rs::{Chip, ChipInterface}; 2 | 3 | #[derive(Clone, Copy)] 4 | pub enum MemState { 5 | Active, 6 | Inactive, 7 | } 8 | 9 | impl Default for MemState { 10 | fn default() -> Self { 11 | MemState::Inactive 12 | } 13 | } 14 | 15 | #[derive(Clone, Copy)] 16 | pub enum MemMode { 17 | Read, 18 | Write, 19 | } 20 | 21 | impl Default for MemMode { 22 | fn default() -> Self { 23 | MemMode::Read 24 | } 25 | } 26 | 27 | #[derive(Chip, Default)] 28 | pub struct Processor { 29 | #[pin(output)] 30 | address_bus: u8, 31 | #[pin(input)] 32 | intr: bool, 33 | #[pin(io)] 34 | data_bus: Option, 35 | #[pin(output)] 36 | mem_state: MemState, 37 | #[pin(output)] 38 | mem_mode: MemMode, 39 | 40 | instr_cache: Vec, 41 | AX: u8, 42 | BX: u8, 43 | IP: u8, 44 | } 45 | 46 | fn main() { 47 | let mut p = Box::new(Processor::default()); 48 | let _p = p.as_mut() as &mut dyn ChipInterface; 49 | println!("{:#?}", p.get_pin_list()); 50 | println!( 51 | "{:?}", 52 | p.get_pin_value("data_bus") 53 | .unwrap() 54 | .downcast_ref::>() 55 | ); 56 | p.set_pin_value("data_bus", &Some(5_u8)); 57 | println!( 58 | "{:?}", 59 | p.get_pin_value("data_bus") 60 | .unwrap() 61 | .downcast_ref::>() 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /pcb-rs-tests/src/main.rs: -------------------------------------------------------------------------------- 1 | use pcb_rs::{pcb, Chip}; 2 | 3 | #[derive(Chip, Default)] 4 | struct TestChip1 { 5 | #[pin(input)] 6 | pin1: u8, 7 | #[pin(output)] 8 | pin2: bool, 9 | #[pin(output)] 10 | pin3: String, 11 | 12 | #[pin(input)] 13 | short_expose_check1: u8, 14 | #[pin(output)] 15 | short_expose_check2: u8, 16 | 17 | internal_1: String, 18 | internal_2: u8, 19 | } 20 | 21 | impl Chip for TestChip1 { 22 | fn tick(&mut self) {} 23 | } 24 | 25 | #[derive(Chip, Default)] 26 | struct TestChip2 { 27 | #[pin(output)] 28 | pin1: u8, 29 | #[pin(input)] 30 | pin2: bool, 31 | #[pin(input)] 32 | pin3: String, 33 | 34 | #[pin(input)] 35 | short_expose_check1: u8, 36 | #[pin(output)] 37 | short_expose_check2: u8, 38 | 39 | internal_1: String, 40 | internal_2: bool, 41 | } 42 | 43 | impl Chip for TestChip2 { 44 | fn tick(&mut self) {} 45 | } 46 | 47 | pcb!(TestPCB{ 48 | chip tc1; 49 | chip tc2; 50 | 51 | tc1::pin1 - tc2::pin1; 52 | tc1::pin2 - tc2::pin2; 53 | tc1::pin3 - tc2::pin3; 54 | 55 | expose tc1::short_expose_check1,tc2::short_expose_check1 as sec1; 56 | 57 | }); 58 | fn main() { 59 | let tc1 = Box::new(TestChip1::default()); 60 | let tc2 = Box::new(TestChip2::default()); 61 | 62 | let temp = TestPCBBuilder::new() 63 | .add_chip("tc1", tc1) 64 | .add_chip("tc2", tc2); 65 | let mut test_pcb = temp.build().unwrap(); 66 | let t: &TestChip1 = test_pcb.get_chip("tc1").unwrap(); 67 | let t: &mut TestChip2 = test_pcb.get_chip_mut("tc2").unwrap(); 68 | 69 | for _ in 0..8 { 70 | test_pcb.tick(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pcb-rs-tests/src/pcb_derive.rs: -------------------------------------------------------------------------------- 1 | use pcb_rs::pcb; 2 | 3 | pcb!(BasicComputer { 4 | chip processor; 5 | chip memory; 6 | chip dma; 7 | 8 | memory::address - processor::address_bus; 9 | memory::address - dma::address_bus; 10 | processor::data_bus - memory::data; 11 | processor::mem_mode - memory::op_mode; 12 | processor::mem_state - memory::active; 13 | memory::data - dma::data_bus; 14 | 15 | expose memory::address; 16 | expose memory::data; 17 | expose memory::op_mode; 18 | expose memory::active; 19 | }); 20 | 21 | fn main() {} 22 | -------------------------------------------------------------------------------- /pcb-rs-tests/src/test_spec.rs: -------------------------------------------------------------------------------- 1 | // This is a temp file to write how I want the final version to look like 2 | 3 | #[derive(Clone, Copy)] 4 | pub enum MemState { 5 | Active, 6 | Inactive, 7 | } 8 | 9 | #[derive(Clone, Copy)] 10 | pub enum MemMode { 11 | Read, 12 | Write, 13 | } 14 | 15 | #[derive(Chip, Default)] 16 | pub struct Processor { 17 | #[pin(output)] 18 | address_bus: u8, 19 | #[pin(input)] 20 | intr: bool, 21 | #[pin(io)] 22 | data_bus: u8, 23 | #[pin(output)] 24 | mem_state: MemState, 25 | #[pin(output)] 26 | mem_mode: MemMode, 27 | 28 | instr_cache: Vec, 29 | AX: u8, 30 | BX: u8, 31 | IP: u8, 32 | } 33 | 34 | impl HWModule for Processor { 35 | fn tick(&mut self) { 36 | // code to read instruction from cache, if 37 | // seg fault issue / incomplete instruction, 38 | // set to read more instruction from mem 39 | } 40 | } 41 | 42 | #[derive(Chip, Default)] 43 | pub struct Memory { 44 | #[pin(input)] 45 | op_mode: MemMode, 46 | #[pin(input)] 47 | address: u8, 48 | #[pin(io)] 49 | data: u8, 50 | #[pin(input)] 51 | active: MemState, 52 | 53 | mem: [u8; 255], 54 | } 55 | 56 | impl HWModule for Memory { 57 | fn tick(&mut self) { 58 | // code to see if the state pin is set to state active 59 | // and the take appropriate action according to mode 60 | } 61 | } 62 | 63 | pcb!(BasicComputer{ 64 | chip processor; 65 | chip memory; 66 | 67 | processor::address_bus - memory::address; 68 | processor::data_bus - memory::data; 69 | processor::mem_mode - memory::op_mode; 70 | processor::mem_state - memory::active; 71 | 72 | expose memory::address; 73 | expose memory::data; 74 | expose memory::op_mode; 75 | expose memory::active; 76 | }); 77 | 78 | fn get_basic_computer() -> BasicComputer { 79 | let memory = Box::new(Memory::default()); 80 | let processor = Box::new(Processor::default()); 81 | BasicComputerBuilder::default() 82 | .add_module("processor", processor) 83 | .add_module("memory", memory) 84 | .build() 85 | } 86 | 87 | fn main() { 88 | let mut basic_computer = get_basic_computer(); 89 | { 90 | let processor: &mut Processor = basic_computer.get_module("processor").unwrap(); 91 | // do something, maybe manually set IP etc 92 | } 93 | { 94 | let memory: &mut Memory = basic_computer.get_module("memory").unwrap(); 95 | // maybe set data in the memory manually 96 | } 97 | loop { 98 | basic_computer.tick(); 99 | } 100 | } 101 | --------------------------------------------------------------------------------