In early July, Cadence announced their new “C2S” C-to-silicon compiler. This event was marked with some excitement and blogging in the EDA space (SCDSource, EDN-Wilson, CDM-Martin, to give some links for more reading). At core, I agree that what they are doing is fairly cool — taking an essentially hardware-unrelated sequential program in C and creating hardware from it. The kind of heavy technology that I have come to admire in the EDA space.
But I have to ask: why start with C?
Productivity by Abstraction
The motivation given in the marketing materials from Cadence is “productivity”, up to ten times more productivity or 90% of design time reduced. The key idea appears to be that C/C++/SystemC is more abstract than Verilog/VHDL, and therefore more design is produced in less time (see Cadence C2Silicon Datasheet).
Similar (in spirit) products have similar motivations, with various numbers of how much better things are. For example, Forte’s Cynthesizer claims “2X-4X faster implementation from spec to netlist over RTL“.
Materials for Mentor’s Catapult tool says it the best:
Using industry standard pure ANSI C++ to describe functional intent, designers move up to a more productive abstraction level for designing complex ASIC or FPGA hardware typically found in next-generation, compute-intensive applications.
Single C++ source unites system designer and hardware designer
So the idea from a large part of the EDA community seems to be that using a C-family language offers benefits of abstraction as well as a way to communicate with software people.
Is C the Right Answer?
What is funny with this obsession with raising the level of abstraction is that you keep ending up in the C family of languages. C, which is considered a “high-level assembler” by many people, and even worse, C++ which is a hard-to-parse semantic nightmare that most CS people that I know would rather do without.
I was reminded of an old joke from my early university CS days, about how to shoot yourself in the foot in various languages. The part on C++ is especially telling. From http://www-users.cs.york.ac.uk/susan/joke/foot.htm:
- You shoot yourself in the foot.
- You accidentally create a dozen instances of yourself and shoot them all in the foot. Providing emergency medical assistance is impossible since you can’t tell which are bitwise copies and which are just pointing at others and saying “That’s me, over there.”
So, putting on my Computer Science hat, I find the idea of using C as a raised level of abstraction would have been considered a bad joke when I was an undergraduate. In essence, C codies procedural programming as it was understood in the early 1970s, when compilers were very weak and a language that could do all that assembler could do was badly needed to write operating systems in a high-level language. In many ways, it was a step back compared to Pascal, Fortran, or Cobol. But it had the power to do anything, tended to result in faster code than other languages, and was available for more machines and operating systems than any other language. C++ then added objects (good), templates (good), but also tried to maintain the close-to-the-machine style of C (bad), multiple inheritance (complex), resulting in something very complex but decently useful and still being mostly like old C (despite having innumerable little detail differences in semantics compared to plain C).
So for a lot of good reasons, C/C++ is the de-facto standard language when you actually get down to the gritty job of getting a new language or operating system to run. It is the language to implement run-time systems, operating systems, new interpreted languages, etc. For the embedded space, C/C++ is often the only language available for a particular chip/OS combination. It is usually the best supported with the most compilers, the highest investment in compilers, and the most users of the compilers.
I have worked for a C compiler company called IAR Systems, and I appreciate the great engineering effort, skill, and pure intellectual fun that goes into creating C compilers that generate code that work well on resource-constrained embedded systems (doing C on an Intel 8051 is no mean feat). Compilers, that are good enough to wean embedded people off of assembly language.
But today, in general, I think that C/C++ is not the language anybody choses for a project if the goal is abstraction and greater productivity. Instead, you go for languages that are much more productive and that raises your productivity a few times over plain C/C++. Some typical examples:
- Languages that do away with memory management, like Java.
- Languages that use virtual machine technology to ease porting across platforms, like Java, C#.net, Prolog, Python, Perl.
- Languages that use dynamic typing or even duck typing, like Python and Ruby.
- Languages that feature concurrency as a primary design features, like Erlang.
- Functional languages with type inference, like ML and Haskell.
- Graphical modeling tools that generate skeleton code, like UML.
- Graphical domain-specific modeling tools that generate final code, like Matlab, Labview, and VisualState.
- Constriant-resolution-based languages, like Oz.
- Narrowly focused domain-specific languages, like CoWare LISA and Virtutech DML.
- In-house very focused languages, usually not “Turing complete”.
When I was studying CS, the basic assumption was that languages are tools, not religion. Any computer science major worth her or his salt should be able to learn any language in a short time, and you should use the language most appropriate for the task at hand. Using the same language all over the place because it is a “standard” is absolutely inefficient, from a software programming perspective.
In practice, large software systems in the embedded and desktop space tend to be constructed from around ten or so different languages (typically, you find C, C++, Java, some macro expanders like M4, string processing and file generation in perl, funky makefiles, some scripting in shell script, Python, VisualBasic, etc.). This is not anarchy, it is professionalism. If you asked a carpenter to use a hammer for all tasks, he would be pretty sad — different tools are good for different things, and it is the same with languages.
So Why C in EDA?
Today, EDA companies are moving from hardware design into partially software design, as SoC designs become more complex and software becomes a greater part of the overall system value. In this process, C/C++ and SystemC seem to be the language of choice.
I find this strange, considering the proud tradition of language inventions that you find in EDA. VHDL and Verilog were uniquely new things when they appeared, languages to describe hardware on hardware terms, and not software on instruction-set terms. Later, you have more abstract language like Bluespec and HandelC. There is a tradition, it seems, of very large and sophisticated compilers that take complicated inputs and transform them into hardware.
Using C/C++ as the input language really makes no technical sense, as it is very hard to parse and understand well in general. The restrictions imposed by the requirements of synthesis limits the C you can input quite severly, if you ask me.
If one idea for starting with C/C++ was to take a piece of “generic” source code and then compile it either to software or hardware, depending on system partitioning, I cannot see that working too well. Software-C tends to use constructs that are not appropriate for hardware synthesis like pointers and recursion. Hardware-C does not look likely to generate particuarly elegant or simple constructs when compiled to software.
So since the code is still going to be special for doing synthesis, why not use an altogether more elegant language? That seems to be what SystemVerilog is about to some extent, and what BlueSpec, for example, are doing. Or the tools to do hardware from UML.
I really do not understand how C/C++ came to be seen as the answer to what software is. Maybe because EDA companies tend to meet with the lowest-level software engineers? And these engineers are certainly only using C since they are running on very bare hardware and cannot assume the existence of rich run-time environment and virtual machines.
Note that I do like the core idea of C-based synthesis, which is using C with restrictions, discipline, and coding patterns to ensure quality final code and communicate programmer intent to the compiler.
When I was with IAR, I taught several courses on how to get good small code out of an embedded C compiler (see for example my ESC 2001 paper). Basically, it comes down to writing “boring” C code that looks a whole lot like Pascal, and which does not rely on fancy semantic intricacies or old rules like using “which” rather than “for”. The IMEC CleanC guidelines are quite recent, but follow much of the same ideas to enable automatic parallelization of code for MPSoC designs.
But doing this is really a work-around for a poor initial language, if you look at the problem without preconceptions.
The Sensible Starting Point
What I think would make more sense is to start with some higher level of abstraction and then generate software code or hardware design from it.
This starting point should really be a parallel language, in some way, shape, or form. Using a sequential language like C as the starting point is fundamentally broken in this age of pervasive multicore systems. Future software will be written to run on parallel machines as the common case, and programs should expose the natural parallelism present in the problem being tackled. Taking a naturally parallel problem, packing it into disciplined sequential C, and then having a compiler discover the parallelism again is really a huge waste of effort.
The starting language might not necessarily be explicitly parallel (no need to scream about Occam or Ada tasking), it could just be domain-natural like Labview (which has been proven to be compilable down to parallel code). I do think that local memory + message passing + built-in task handling like Erlang looks like a very good approach for hardware design and software design, as it makes it possible to use sequential descriptions where they make sense and expose natural parallelism where it makes sense.
The starting language should also be as simple as possible in terms of how many different ways you can express things. In C, you can do “i++;”, “++i;”, “i+=1;”, “i=i+1;” to increment a variable. Why have more than one operation for this? The original point in C was to support various machine operations directly in the source language, as you could not trust the compiler to figure things out. Today, compilers can figure these things, so there is no point to be able to express the same thing in more than one very regular and easy-to-read way.
Another important aspect of modern software engineering is to support quick iterations, quick changes, and agile and extreme programming theories. This comes down to languages and environments that work even when systems are incomplete, and where it is easy to stub things out and later put in the details. C/C++ does not do this terribly well due to the use of static typing, static checking, and lack of default implementatioins for things which have not yet been filled in.
The language should also be designed to run on some virtual machine, as that helps portability and understanding programs. It also makes it a whole lot easier to write a reference compiler to integrate the language into simulation environments.
So where does this put me? I think these are my main points:
- C-to-hardware synthesis is pretty impressive technology
- But why use C?
- C is the EDA high-level darling
- C is sequential and complicated
- C is very low-level from the perspective of a software professional
- A better input language should be concurrent/parallel and simple
- A better input language should be designed for modern agile and extreme programming styles
- EDA companies should really look at the leading edge of software engineering for inspiration, rather than what conservative embedded C programmers are doing.
- VM-based languages are good
I guess this goes into the “rant” bin…