To a certain extent, Integrated Circuit designers these days are really software designers. They enter code (RTL) that gets compiled. They write code (C, System C, Verilog, VERA, whatever) to verify the funtionality of the RTL. The people who do the compilation, that is the implementation, write all sorts of scripts to make it happen.
The big difference between the software and hardware jobs is what happens to the fruit of your labors. For a software person, the result is virtual. There are bits whose existence is fleeting and abstract. The hardware person creates something that is solid and tangible. For a software designer, software is the goal. For a hardware designer, software is the means to a goal.
Because the results are so much different, so are the requirements. Nothing makes me feel more secure in my job than an engineer that makes the statement something like: "It simulates, therefore it must work." I have heard mutterings like this on several occassions.
One of the more interesting of these types of events happened some number of years ago when an engineer came to me with an issue. Specifically, he had coded a 46-bit accumulator. He had then synthesized this accumulator in a 130 nm process. The accumulator did not resolve in 1ns. What should he do? I then suggested that there was no way he could design a 46 bit accumulator to resolve in 1ns, and that he would have to pipeline the operation. He didn't have a clue about pipelining.
Then he asked the question that was really bugging him: "Why didn't the Verilog simulator tell me there was an issue?" I tried to explain that RTL is merely a functional representation of hardware. It does NOT contain timing information. You have to have a feel for what the RTL is going to create. I don't know how you develop that expertise, except for practice. He went away unconvinced and irritated, because, after all, it had simulated, therefore it ought to have worked.
I have had the opportunity to see a handful of ugly RTL implementations in the past. These are a few.
Three-state mess:
I once was exposed to a piece of code in which the author was trying to implement a multiplexer function. The operation looked something like:
assign out = (sel == 0) ? in1 : 8'hzz;
assign out = (sel == 1) ? in2 : 8'hzz;
assign out = (sel == 2) ? in3 : 8'hzz;
assign out = (sel == 3) ? in4 : 8'hzz;
Given that select is a 2 bit vector, I have functionally created a four input mux. What got synthesized into hardware is a three-state bus, with 4 drivers on each net. (Just for the record, the case that I saw in real code was a lot bigger that this). Not only is the implementation huge, because typically three state drivers are big, to drive big busses, but there isn't really any improvement that can be made via synthesis, because synthesis tools do not usually touch three-state buses.
Lesson: Anytime a "Z" appears in your RTL, be very cautions.
Magnitude vs. Equality:
I once ran accross a huge compare function. Ultimately the coder was trying to determine if a data bus ever equaled any of several preset values. He coded something that looked like this:
(Oh, for the record, the "or" signs keep disappearing from my html. Imagine they are there between terms).
assign hit = ((in > 6) & (in <9))
((in > 8'h17) & (in < 8'h1a))
((in > 8'h26) & (in < 8'h29))
((in > 8'h35) & (in < 8'h38))
((in > 8'h44) & (in < 8'h47))
((in > 8'h53) & (in < 8'h56))
((in > 8'h62) & (in < 8'h65))
((in > 8'h71) & (in < 8'h74))
((in > 8'h80) & (in < 8'h83))
((in > 8'h9f) & (in < 8'ha2))
((in > 8'hae) & (in < 8'hb1))
((in > 8'hbd) & (in < 8'hc0))
((in > 8'hcc) & (in < 8'hcf))
((in > 8'hdb) & (in < 8'hdd))
((in > 8'hea) & (in < 8'hed))
((in > 8'hfc) & (in < 8'hfe));
For the record there were hundreds of compares in the real code. The problem with this code is that magnitude compares are fairly complex things. They require a carry or borrow operation that forces each bit in the compare to interact with each other bit. If you are using design compiler, it goes out and grabs a Design Ware component for each compare. In order to optimize this code, it does something called resource sharing. This is compute intensive.
I suggested that the designer replace his code with something like the following:
assign hit = ((in == 7) (in == 8)
(in == 8'h18) (in == 8'h19)
(in == 8'h27) (in == 8'h28)
(in == 8'h36) (in == 8'h37)
(in == 8'h45) (in == 8'h46)
(in == 8'h54) (in == 8'h55)
(in == 8'h63) (in == 8'h64)
(in == 8'h72) (in == 8'h73)
(in == 8'h81) (in == 8'h82)
(in == 8'ha0) (in == 8'ha1)
(in == 8'haf) (in == 8'hb0)
(in == 8'hbe) (in == 8'hbf)
(in == 8'hcd) (in == 8'hce)
(in == 8'hdc)
(in == 8'heb) (in == 8'hec)
(in == 8'hfd)) ;
The two sets of code are equivalent logically. (I checked with a formal verification tool). I ran it in design compiler to see what the results would be, using a TSMC 130 nm library. I gave it fairly agressive timing goals (300 ps) and set the max area at 0.Magnitude
comparator result:
- Area 332
- Compile time 10 sec
- Violated timing by 10 ps.
Equality comparator result:
- Area 228
- Compile time 5 sec
- Met timing
I then ran again, using compile_ultra.
Magnitude comparator:
- Area 240
- Compile time 33 sec
- Met timing
Equality Comparator:
- Area 188
- Compile time 7 sec
- Met timing
So, what is the big deal about a few seconds of compile time, or a few square microns. Well, for one thing, the original case that I ran into had hundreds of terms, not the handful I demonstrated here. So compile time that had been hours, became minutes. Secondly, it is a way of thinking. Any time you can make it easier on the tools, you will make things simpler on yourself. If a designer of a large block is conscious of these types of issues, he can make the implementation of that block a lot less painful.
Lesson: You need to understand a little about how your tools think.
Note: You can take this lesson too far. I have known of teams that want to turn all of their code into sum of products to make sure they know what the tool is going to produce. I don't think this is a good return on investment.
Muxing Mess
I was once asked to work on a program that was "almost finished." The team had already put together the Verilog, simulated it to demonstrate that it worked, and synthesized it to prove that it was close to meeting timing. All we needed to do was to do the physical implementation. The problem with this design was that it contained a 64 X 266 bit multiplexer.
This synthesized just fine in Design Compiler, because there are not many levels of logic, and from a statistical wireload perspective, it was not very challenging. The reality of the design however is that multiplexing operations are difficult in standard cells. Big muxes are really difficult, because of the routing issues. So, the design that seemed easy in DC would not converge when we went to the physical tools. The result was that the solution had to be rearchitected, and took several more months of work.
If you want to look at more details, check out:
http://www.designcon.com/infovault/paper.asp?PAPER_ID=87
Lesson: You need to understand the physical ramifications of design decisions.