Design Style Guide ------------------ Original: 18-APR-1995 Latest: 24-APR-1995 Introduction ------------ This document tries to describe the design style that we will use for the D0 Run II upgrade. The term "design style" is meant to encompass concepts like: - at what "levels" do we describe the system - what design entry mode is appropriate for each level and/or specific design component (e.g. FPGA) - what conventions should we follow in each design entry mode It is important to have a style guide for many reasons, for example: - it will help us generally organize the attack - it will help us to support multiple designers working on the same things - it will help us to write synthesizable VHDL - it will help us to maintain/modify the system This document is currently in a very rough draft form. "Levels" of design ------------------ It is very common to picture designing in a hierarchical (top-down) fashion. The description at any level (whether it is VHDL or schematics or some other description), while still describing the system, has unique functions specific to that level. For example, one could imagine a high-level VHDL description of the Frameworks, then a behavioral VHDL description of each card, then a behavioral VHDL description of each FPGA, and then a synthesizable VHDL description of each FPGA. Simulation could be run on each of these levels (as well as the final placed and routed FPGA's). Taking a closer look at the various levels: (1) VHDL description of both Frameworks This would be a full description of both Frameworks. We do not need to do this in order to learn how to partition the design or get started on making the FPGA's. But this may still be a very good thing to do if we keep the following points in mind: - this could be the simulator (or at least a "verifier" if not the full-blown simulator. Philippe lists many good reasons why this description is probably not well suited to being the simulator. Note that the simulator has to do a lot that the hardware doesn't, for example deal with Zebra banks). - this could be documentation of the system. - this could be already partitioned into "cards" (but not FPGA's). This will allow us to "drop in" a fancier VHDL description of any particular card, and use the same test vectors, etc. Also partitioning into cards gives us a handle on how to start, and allows parallel development of code. The general idea would be to not write a completely abstract model, but to use some of what we know to be the structure of the system (probably at the card level). This would NOT be a description which would ever be connected to the "FPGA-level" descriptions. Here we do not worry about things like VME interface, but we do implement programming (and possibly readout) as more abstract concepts. (2) behavioral VHDL description of each card If we build the top level description as described above, then we end up writing behavioral VHDL descriptions of each card anyway. In fact, we may end up writing these first and then "connecting" them together to make the top level description. These descriptions would also serve as documentation for the functions of the cards. Here we would NOT try to partition the cards into FPGA's. Here we do not worry about things like VME interface, but we do implement programming (and possibly readout) as more abstract concepts. (3) structural VHDL description of each card We could also have VHDL descriptions of each card which are partitioned into FPGA's. Without these, we don't have a "chain" from the lowest level to the highest level of the design. If we had infinite time, we would probably make these descriptions. But I expect that we will not find this "chain" idea actually useful enough to justify the effort required. (4) behavioral VHDL description of each FPGA. If we have structural VHDL descriptions of each card, they would naturally include behavioral VHDL descriptions of each FPGA. If we don't have these structural card-level descriptions, then we would consider the behavioral VHDL for each FPGA to be a new top-level design. We could use the behavioral VHDL to generate test vectors for the synthesizable VHDL. Do we only want to describe the high speed paths, or do we want to worry about VME interfaces, etc. at this level? (5) synthesizable VHDL description of each synthesizable FPGA. If the synthesizers were infinitely smart, and our FPGA's had infinite capacity and no speed constraints, then we would just synthesize the behavioral VHDL descriptions of each FPGA. But that's not going to work, so we need another lower-level VHDL description of the FPGA's that we will synthesize. Here we absolutely need to describe all details of the FPGA's, including fast/slow readout, programming, etc. (6) "other" description of each non-synthesizable FPGA Some of the FPGA's are not thought to be synthesizable given the speed and area constraints. An example is the And-Or Network FPGA. These FPGA's will have to be produced via some other method, possibly schematics, and hand-placed using "hints" in the FPGA place/route software. We should make an effort to ensure that these descriptions can still be simulated and compared to their associated behavioral VHDL descriptions. Here we absolutely need to describe all details of the FPGA's, including fast/slow readout, programming, etc. The full details of these descriptions are not absolutely clear (e.g. when do we start to care about readout, VME interface, programming, etc), but it is likely that the description will look something like the above. We should think carefully about which FPGA's are synthesizable, and how to handle the ones that aren't. Choosing the wrong design path for one of these parts can mean the difference between success and failure, and we don't have a lot of experience in this are. We need to be very careful and think hard about this topic for each part of the design. More on the VHDL top-level definition/simulation ----------------------------------------------- How do we organize the VHDL definition/simulation for the Run II Frameworks (i.e. elements (1) and (2) above). One idea: Write a "package" which contains all variable types, etc. Compile this into a library, which can be included in all source files using the LIBRARY and USE constructs. The library would be something like: LIBRARY run_ii.data_types. Write bevahioral models for all cards. Compile and test these individual cards. The whole collection of cards can be compiled into a library which can then be used in all source files using the LIBRARY and USE constructs. The library would be something like: LIBRARY run_ii.cards. The cards would need access to the variable types library. Write a structural model for the L1 Framework and the L2 Framework (as separate entities). Use the run_ii.cards library (not clear whether that "automatically" picks up run_ii.data_types or whether this library needs to be called out explicitly). Compile this into a new library, something like run_ii.systems Write a unified structural model for the entire system. Use the run_ii.systems libraries (not clear whether that "automatically" picks up run_ii.data_types and run_ii.cards or whether these need to be called out explicitly). This would then be the "top-level" simulator. Graphically: ----------------- | Frameworks | ----------------- ^ ^ | | ------- ------- | L1 | | L2 | (L1 and L2 are both in run_ii.systems) ------- ------- ^ ^ ^ ^ | __\ _/ | | / \ | --------- -------------- | all of| | all of | Cards are in run_ii.cards | the |<-| the | Data types are in run_ii.types | Cards | | Data Types | --------- -------------- The cards include: And-Or Term Receiver Module (AOTRM) And-Or Network Module (AONM) Trigger Decision Module (TDM) Framework Output Module (FOM) (L1 and L2 versions?) Scaler Module-Monitor Readout (SMMR) Scaler Module-Data Block Readout (SMDBR) Special Functions Module (SMF) Error Map Module (EMM) L2 Processor Receiver Module (L2PRM) L1-L2 Interface Module (L1L2IM) L2 Processing Done Module (L2PDM) L2 Decision Module (L2DM) L2 Accept/Reject Module (L2ARM) (can this be made the same as FOM?) The data types include: How detailed is this definition/simulation? This is not at all clear. Some obvious outstanding questions: - do we simulate "slow" signals i.e. VME interface - my feeling is no. - how do we feed trigger programming information to simulator. - if this were the real simulator, it would need to read "COOR->TCC" messages from a text file, and do whatever was necessary to feed programming information to "cards." I expect that the cards would receive programming information on (rather high-level) ports which would NOT correspond to any low-level structure in the system. We would make a module which read programming messages and had direct lines to feed high-level programming information to each card. There are a lot of other ways to do this but my guess is that this is the cleanest. - do we simulate readout, either fast or slow? - again my feeling is no, but it could be done using a mechanism similar to the programming method described above (i.e. "high-level" ports which don't correspond to any low-level structure in the system). Note that this structure makes the card "entities" not match the actual card inputs/output. We should take care to have the high-speed (i.e. 128 in, 64 out, 16 "bidirectional," and ? parallel timing signals) ports match, so we have a method for comparing simulation results, but we should not be too worried about "extra" ports like direct input of programming information or some missing ports like VME interface. This is another possible roadblock in making a fully unified "top-to-bottom" simulator. But I don't really think that a full top-to-bottom simulator is what we should be concentrating on. How to proceed with the definition/simulator? The following steps sound rational. After each step is complete we should have a serious 3-way review of the progress to date, looking for problems and new ideas. (1) Sketch out CARD.TXT for each card. It needs to contain - "real" high-speed inputs/outputs/clocks - "virtual" programming information inputs - description of its functionality (without worrying about readout, VME, etc yet) (2) From these .TXT descriptions, define the variable types package. (3) From these .TXT descriptions and the variable types package, generate VHDL entities for each card. Also generate a VHDL entity for the "programming module" which uses VHDL TextIO to read programming information and generates the "virtual" programming data for each card. We do NOT need to (although we could) write architectures yet for these cards, but we DO need to know what constructs can be simulated in QuickSim. (4) Now write VHDL entities and structural architectures for the L1_Framework and L2_Framework. This does not require any really fancy VHDL knowledge, nor do we need to do more system design before proceeding. We already know what cards we will use and how they will be connected. Of course we should be open to new ideas discovered when building these entity/architecture pairs. (5) Now write VHDL entities and structural architectures for the L1_L2_Framework_System. Again no really fancy VHDL knowledge is required, nor is more system design work. (6) Now go back and in parallel write architectures for the cards. The advantage of waiting to write architectures is that we can have the system defined before writing behavioral VHDL code, we can all work in parallel KNOWING the target (rather than just hoping that magically things will fit together), and we all will have more time to learn VHDL before writing behavioral code. The danger with waiting to write architectures is that we might find that something is missing or incorrect in the entity. But careful reviews should minimize the chance of this, and since the design is compartmentalized changes should be straightforward. I.e. changing the programming input for the And-Or Network Module only involves touching the And-Or Network Module and the "programming module" (and perhaps the variable types). (7) Now simulations can be run on the whole system. These simulations will be logic-only, and not include timing information. Synthesizable VHDL ------------------ What does "synthesizable" VHDL mean? It means that the VHDL source can be converted into circuitry. Target architecture specific libraries are generally required, as are constraints (hints) imposed by the designer. Synthesizers appear to be able to interpret a wide variety of VHDL architecture styles (i.e. behavioral, structural, and dataflow). But there are some things that will be difficult or impossible to synthesize, or will produce inefficient circuitry. Examples are: - a <= b + c if b and c are real, or integers which have not been constrained to be a reasonable size - loops with variable bounds - ??? VHDL coding style ----------------- Since we all will be working with each other's code, we should follow a consistent VHDL coding style. We want to have clear, easy-to-understand code which efficiently performs the functions required at its level (i.e. simulation code should simulate efficiently, synthesizable code should generate efficient circuitry). There are entire textbooks on the ways to write VHDL (we have not a few). Below I am concentrating not on large topics like "programming style" (because I don't want to write another textbook), but more closely focused topics like variable names, etc. This list is completely disorganized and has not been "ratified." (1) Use good variable names, lengthy variable names are OK. Be careful here to capture the flavor of the variable in its name. We have all been stung by a variable name that is almost, but not quite, right. It is especially tricky when 2 variables reflect slightly different concepts. (2) Define our own types when it is useful. If something will only ever be a 16 bit quantity then don't just make it an INTEGER. This will help us keep track of variables, etc., and will make the synthesizer's job easier. (3) Use enumerated types. Synthesizers appear to be able to handle enumerated types with ease. (4) When defining a type, put "type" in the type name. This will help people distinguish variables from types. (This kind of thing has been helpful in assembly language code to distinguish values from locations from pointers, but is it useful in higher level languages?) Same for configuration, etc. (5) When defining a process, if the sensitivity list is optional then do NOT leave it out. This will help both readability and simulation efficiency. Example: a d-latch should only be sensitive to CLOCK, not D. Do not use implied WAIT ON . (6) When defining a process, if the label is optional then do NOT leave it out. Try to give each process a good label, again length is not a problem. (7) In structural VHDL, make the component definition in the higher-level architecture EXACTLY match the component definition in the lower-level entity. This makes the configurations simpler. (8) We should define a "package" which contains our variable types, etc. This will eliminate multiple type definitions for the "same" thing (as would happen if we all just worked in parallel on our own stuff). All VHDL code would be "linked" against this package. (9) When specifying bussed signals, put the MSB on the "left." i.e. the label should be bus(63:0) in Design Architect, and the type should be qsim_state_vector (63 downto 0) in VHDL. (10) We should keep the limitations of QuickSim in mind. QuickSim is significantly slower dealing with multiple-dimensioned arrays than with equivalent-sized single-dimensioned arrays. But, multiple-dimensioned arrays are very convenient. What do we do? (11) Do not ignore clocks. Clocks should be explicitly included in the high-level behavioral description of the system. This is a big difference between FORTRAN and VHDL simulators. Whenever we use concurrent signal assignment statements, we need clocks to enforce order-dependencies.