Practical question about SOLID programming practises - solid-principles

A practical question about Dependency Inversion Principle:
We want to build our systems in many libraries or DLLs.
If the components or classes of a lower level library should depend upon an abstraction, be it an Iinterface or pure abstract class and the callee executable or higher-level library should also depend upon that abstraction rather than the concrete class then into which library should the abstraction be compiled?
Yes of course, the concrete class is wired and provided by a factory...
Logically, it belongs in the executable or higher level library but perhaps for practical purposes it should be compiled into the lower level library.

The most flexible (modular) approach is to compile the abstraction into its own binary file (library). This allows anyone to use and/or extend the abstraction without inheriting any implementation details. Ideally, this means without inheriting any transitive dependencies.
If the abstraction will only be consumed by one client, you may safely compile it along with its consumer, to reduce the amount of binary files. This will keep the concrete implementation(s) decoupled from the client.
The one thing you should not do is compile the abstraction along with any of its implementations. That would couple every client to the implementation code as well as its transitive dependencies.

Related

Chisel Output with SystemVerilog Interfaces/Structs

I'm finding when generating Verilog output from the Chisel framework, all of the 'structure' defined in the chisel framework is lost at the interface.
This is problematic for instantiating this work in larger SystemVerilog designs.
Are there any extensions or features in Chisel to support this better? For example, automatically converting Chisel "Bundle" objects into SystemVerilog 'struct' ports.
Or creating SV enums, when the Chisel code is written using the Enum class.
Currently, no. However, both suggestions sound like very good candidates for discussion for future implementation in Chisel/FIRRTL.
SystemVerilog Struct Generation
Most Chisel code instantiated inside Verilog/SystemVerilog will use some interface wrapper that deals with converting the necessary signal names that the instantiator wants to use into Chisel-friendly names. As one example of doing this see AcceleratorWrapper. That instantiates a specific accelerator and does the connections to the Verilog names the instantiator expects. You can't currently do this with SystemVerilog structs, but you could accomplish the same thing with a SystemVerilog wrapper that maps the SystemVerilog structs to deterministic Chisel names. This is the same type of problem/solution that most people encounter/solve when integrating external IP in their project.
Kludges aside, what you're talking about is possible in the future...
Some explanation is necessary as to why this is complex:
Chisel is converted to FIRRTL. FIRRTL is then lowered to a reduced subset of FIRRTL called "low" FIRRTL. Low FIRRTL is then mapped to Verilog. Part of this lowering process flattens all bundles using uniquely determined names (typically a.b.c will lower to a_b_c but will be uniquified if a namespace conflict due to the lowering would result). Verilog has no support for structs, so this has to happen. Additionally, and more critically, some optimizations happen at the Low FIRRTL level like Constant Propagation and Dead Code Elimination that are easier to write and handle there.
However, SystemVerilog or some other language that a FIRRTL backend is targeting that supports non-flat types benefits from using the features of that language to produce more human-readable output. There are two general approaches for rectifying this:
Lowered types retain information about how they were originally constructed via annotations and the SystemVerilog emitter reconstructs those. This seems inelegant due to lowering and then un-lowering.
The SystemVerilog emitter uses a different sequence of FIRRTL transforms that does not go all the way to Low FIRRTL. This would require some of the optimizing transforms run on Low FIRRTL to be rewritten to work on higher forms. This is tractable, but hard.
If you want some more information on what passes are run during each compiler phase, take a look at LoweringCompilers.scala
Enumerated Types
What you mention for Enum is planned for the Verilog backend. The idea here was to have Enums emit annotations describing what they are. The Verilog emitter would then generate localparams. The preliminary work for annotation generation was added as part of StrongEnum (chisel3#885/chisel3#892), but the annotations portion had to be later backed out. A solution to this is actively being worked on. A subsequent PR to FIRRTL will then augment the Verilog emitter to use these. So, look for this going forward.
On Contributions and Outreach
For questions like this with (currently) negative answers, feel free to file an issue on the respective Chisel3 or FIRRTL repository. And even better than that is an RFC followed by an implementation.

How to specify chisel’s post-processor?

Quote from libcores wiki
One post-processor generates a Verilog that is tuned for FPGA execution. A second generates Verilog that is tuned for ASIC.
Is this true? How to specify which post-processor to use?
I noticed that we can send an option ‘-X xxx’ to chisel, in which ‘xxx’ can be high, middle, low, verilog... Is this related? What’s the exact meaning of these ‘compilers’?
Thank you!
Very narrowly addressing your latter question, the -X/--compiler command line argument determines which FIRRTL compiler and emitter to use.
The Chisel3 compiler generates CHIRRTL (a high level form of the FIRRTL intermediate representation). The FIRRTL intermediate representation (IR), described in more detail in a UC Berkeley Technical Report, is a simple language for describing a circuit.
The FIRRTL compiler, broadly, is moving a circuit, represented in the FIRRTL IR, from a high-level representation (what is described in the specification) to a mid-level representation, and finally to a low-level representation that will easily map to Verilog. The FIRRTL compiler can elect to stop early at High FIRRTL, Mid FIRRTL, or Low FIRRTL or going all the way to Verilog. That -X/--compiler argument is telling it if you want to exit early and only target one of these representations.
Note: CHIRRTL will eventually be removed and High FIRRTL will be emitted directly by the Chisel compiler.
I'm not fully familiar with the librecores flow, but glancing over https://github.com/librecores/riscv-sodor I don't see any post-processing scripts. It might be worth filing an issue on the repo to ask for clarification on that point.
For Chisel designs in general, people use transforms on the IR to specialize the code for FPGA vs. ASIC. The most common one is with handling memory structures. The behavioral memories emitted by default work well for FPGAs as they are correctly inferred as BRAMs. For ASICs, there is a standard transform to replace memories with blackboxed interfaces such that the user can provide implementations that use SRAM macros from their given implementation technology.

What is a runtime environment for supposedly "no-overhead" systems languages?

Specifically, I'm talking more about C++ and Rust than others. I don't understand how C++ has a "runtime" in the sense that Java and C# have a runtime--while Java and C# run on top of a virtual machine with its own encapsulated abstractions and such, I don't get how C++ might have one.
Take virtual tables for C++, for example. Do we consider dynamic_cast<type> a part of C++'s runtime functionality or are we talking about C++'s structure for vtables in general? Can we consider new and delete a part of the C++ runtime environment? What exactly constitutes a runtime?
For example, here we have a Rust article on its own runtime, which describes it as :
The Rust runtime can be viewed as a collection of code which enables
services like I/O, task spawning, TLS, etc. It's essentially an
ephemeral collection of objects which enable programs to perform
common tasks more easily.
But is this not the function of a standard library or language features, not an actual runtime? What constitutes this very thin but existent runtime? Even Bjarne expresses his thoughts that C++ has "zero-overhead abstraction", but if C++ has a runtime, does this not imply that C++ does indeed have some sort of "backend" code to orchestrate its own very light but still existent abstractions?
TL;DR: What is a runtime and/or runtime environment in the context of languages like C++ and Rust that have supposedly "zero-overhead" and don't have "heavy" runtimes like Java or C#?
Edit: I suspect that I'm just missing something about semantics here...
C++ requires a few things that aren't required in something like C.
For example, it typically involves some overhead for exception handling. Although it may not be strictly required, most systems have at least a tiny bit of a top-level exception handler to tell you that the program shut down if an exception was thrown but not caught anywhere.
It's open to question whether it qualifies as "runtime environment", but the compiler also generates code to search up the stack and find a handler for a particular exception when one is thrown.
On one hand, this is exceptionally tiny (bordering on negligible) compared to something like a complete JVM. On the other hand, it's quite large and complex relative to what happens by default in something like a JVM or Microsoft's CLR.
As to zero overhead...well, it depends a bit on your viewpoint. Exception handling code can normally be moved out of the main stream of the code, so it doesn't impose any overhead in terms of execution speed as long as no exception is thrown. It does, however, require extra code so there can be (often is) quite a bit of overhead if you look at executable sizes. Just for example, doing a quick look at a "hello world" program, it looks like turning off exception handling reduces the executable size by about 2 kilobytes with VC++.
Admittedly, 2K isn't a whole lot of extra code--on the other hand, that's just what's added to essentially the most trivial program humanly possible. For a program that actually does something, it's undoubtedly more.
In the end, it's not enough that most people really have a reason to care, but it does exist nonetheless.
As to how this is handled, it involves a combination of code that's linked in from the standard library and code generated by the compiler (but the exact details vary with the implementation--for example, most 32-bit Windows compilers used Microsoft's Structured Exception Handling (in which case the operating system provides part of the code) but for 64-bit Windows, I believe all of them deal with exception handling on their own (which increases executable sizes more, but reduces overhead in terms of speed).

Core of Verifier in Isabelle/HOL

Question
What is the core algorithm of the Isabelle/HOL verifier?
I'm looking for something on the level of a scheme metacircular evaluator.
Clarification
I'm only interested in the Verifier , not the strategies for automated theorem proving.
Context
I want to implement a simple proof verifier from scratch (purely for education reasons, not for production use.)
I want to understand the core Verifier algorithm of Isabelle/HOL. I don't care about the strategies / code used for automated theorem proving.
I have a suspicion that the core Verifier algorithm is very simple (and elegant). However, I can't find it.
Thanks!
Isabelle is a member of the "LCF family" of proof checkers, which means you have a special module --- the inference kernel -- where all inferences are run through to produce values of the abstract datatype thm. This is a bit like an operating system kernel processing system calls. Everything you can produce this way is "correct by construction" relative to the correctness of the kernel implementation. Since the programming language environment of the prover (Standard ML) has very strong static type-correctness properties, the correctness-by-construction of the inference kernel carries over to the rest of the proof assistant implementation, which can be quite huge.
So in principle you have a relatively small "trusted kernel" part and a really big "application user-space". In particular, most of Isabelle/HOL is actually a big collection of library theories and add-on tools (mostly in SML) in Isabelle user-land.
In Isabelle, the kernel infrastructure is quite complex, but still very small compared to the rest of the system. The kernel is in fact layered into a "micro kernel" (the Thm module) and a "nano kernel" (the Context module). Thm produces thm values in the sense of Milner's LCF-approach, and Context takes care of theory certficates for any results you produce, as well as proof contexts for local reasoning (notably in the Isar proof language).
If you want to learn more about LCF-style provers, I recommend looking also at HOL-Light which is probably the smallest realistic system of the LCF-family, realistic in the sense that people have done big applications with it. HOL-Light has the big advantage that its implementation can be easily understood, but this minimalism also has some disdavantages: it does not fully protect the user from doing non-sense in its ML environment, which is OCaml instead of SML. For various technical reasons, OCaml is not as "safe" by default as Standard ML.
If you untar the Isabelle sources, e.g.
http://isabelle.in.tum.de/dist/Isabelle2013_linux.tar.gz
you will find the core definitions in
src/Pure/thm.ML
And, there is such a project already you want to tackle:
http://www.proof-technologies.com/holzero/
added later: another, more serious project is
https://team.inria.fr/parsifal/proofcert/

Can First-class functions in Scala be a concern for allocating a large PermGen Space in JVM?

Regarding first-class functions in Scala, it is written in the book Programming by Scala:
A function literal is compiled into a
class that when instantiated at
run-time is a function value.
When there will be many first-class functions used in a program, will this affect the JVM's PermGen space? because instead of simple functions the compiler is generating classes for each variation of the function value (e.g. in the case of varied definitions of partially applied functions).
The memory profile is certainly going to be different than that of normal Java programs, though you can tune pretty much any memory parameter on the JVM.
All I can say, however, is that in one year of deep involvement in the Scala community, I have never seen anyone complain about this.
I don't have substantiation for this, but my feeling is that if you're writing any non-trivial program, the amount of space taken up for your program's "real" data will vastly dwarf the amount of space taken up by a few extra function-as-class definitions.
In other words, I wouldn't worry about it.
It is a proven mathematical fact that the number of classes you generate with first-class functions will be able to asymptotically approach, but never surpass, the number of compiled classes in the full Spring distribution. Don't worry, those pioneers will deal with the permgen issues first!