The Compilers





The Compilers

The front-end portion of a compiler, consisting of a parser and an elaborator, processes the input circuit and builds an internal representation of the circuit. Specifically, a parser interprets the input according to the language's grammar and creates corresponding internal components to be used by the elaborator. For example, a module is parsed into an internal programming object that has fields for the module name, port name, port type, and a link list of all entities inside the module. The elaborator constructs a representation of the input circuit by connecting the internal components, substituting parameters, inlining or setting up parameter passing for functions and tasks, and others. For instance, the elaborator substitutes a module instantiation with the module's definition and connects the internal objects consistent with the circuit connectivity. Sometimes the elaborator applies optimization to the internal representation. The end result from an elaborator is a complete description of the input circuit sufficient to sustain all later processing and operations.

The back end, the soul of a simulator, determines the type of the simulator. The actual operations of the analysis stage vary from one type of simulator to another. For a cycle-based simulator, it performs clock domain analysis and levelization, whereas for an FPGA-based hardware simulator, in addition to the previous analysis it also partitions, places, and routes the circuit into FPGA chips. For this reason, an in-depth discussion of analytical stage is relegated to the sections on specific simulators.

The type of simulator also dictates the construction of code generation. There are four classes of generated code: interpreted code, high-level code, native code, and emulation code. The last three are sometimes referred to as compiled code.

In an interpreted simulator, the input circuit is compiled into an intermediate language for the interpreted simulator. The interpreted simulator can be regarded as a virtual machine that reads in instructions in the intermediate language, one instruction at a time. The effect of executing the interpreted object code creates the behavior of the circuit. The diagram in Figure depicts this interpreted simulation process. The interpreted simulator is a virtual machine with the interpreted code as its instructions. The instructions are fetched, one by one, then are decoded and executed. The interpreted simulator has a user interface to allow data examination and execution control. An example of interpreted code and the circuit it simulates is as follows:

      // circuit being simulated
      initial
      begin
      clk = 1'b0;
      #1 clk = ~clk;
      #1 clk = ~clk;
      #1 finish;
      end

      always @(clk)
      begin
         a = b & c;
         if (a == 1'b0)
            p = q << 3;
      end

      // generated interpreted code
      assign(clk, 0);
      invert(clk);
      evaluate(b1);
      invert(clk);
      evaluate(b1);
      exit();

      b1: //definition of routine b1
      {
         and(a,b,c);
         if(a,0) left_shift(p,q,3);
      }

2. Interpreted simulation structure and process


The functions in the interpreted codeassign(), invert(), evaluate() are instructions for the interpreted simulator. Note that the stimulus or test bench is compiled with the circuit.

Interpreted code is very portable. Compiled once, the code can run on any machine that has an interpreted simulator. However, its simulation is the slowest compared with the other three kinds, because of the extra layer of execution on top of the native machine.

A compiler can also compile a circuit into a high-level language such as C/C++. To simulate, the generated C/C++ description of the circuit is compiled with a C/C++ compiler and is run just like any other C/C++ program. A sample generated C program simulating the previous circuit is shown here. By analyzing the Verilog code, it is determined that the clock toggling statement can be combined with the always block. In general, such a simplification may not exist. In that case, the C code of the always block will have to be run on a separate thread that constantly monitors changes in the clock variable. A change on clk will trigger an evaluation of the C code representing the always block:

      main()
      {
         int clk;
         int i;
         int a, b, c, p, q;
         clk = 0;
         for (i=0; i<2; i++) {
            clk = (clk == 0) ? 1 : 0 ; // clk = ~clk;
            a = b & c; // always block
            if (a==0)
               p = q << 3;
      }

High-level code is not as portable as interpreted code because high-level code needs to be recompiled to the native language of the platform (for example, SUN workstations) every time it is simulated. This compile time can be long because the high-level code is usually very large. High-level code is portable to a certain degree, because the generated high-level code compiled from the circuit can be reused.

Native code compilation, skipping the intermediate language generation step (for example, C/C++ or interpreted code), directly produces the machine executable code for the platform. At the expense of portability, native code runs slightly faster than high-level code because of the more direct machine code optimizations. Both native code and high-level code are typically about 5 to 20 times faster than interpreted code. The major shortcoming for native code compilation is portability.

Finally, in hardware simulators/emulators/accelerators, the compiler generates the machine code for the hardware simulators/emulators. During simulation, a hardware simulator sometimes requires interaction with the host machine. An example of such an interaction is running C code in the host simultaneously with the circuit simulation in the hardware simulator (for example, PLI C code running on the host in lock step to compare result with that from the hardware simulator at the end of each cycle). (This is discussed further, later in the chapter.) This type of interaction is a major bottleneck in simulation performance. If the host-hardware interaction can be minimized, simulations on hardware are orders of magnitude faster than those in software simulators, typically in the range from 100 to 10,000 times. The disadvantages are long compilation time and capacity limitation (the maximum size of the circuit that can fit into the simulator). Figure summarizes the four types of simulators and their simulation processes.

3. Summary of four types of simulation processes


An interpreted simulation system has a clear separation between code of the compiled circuit and code of the simulator, with the circuit code feeding the simulator, as indicated in Figure. However, a compiled simulation system is composed of a single piece of compiled code that combines the circuit, the simulator, and a user interface program. The structure and execution flow of a compiled simulator is shown in Figure. The compiled code, the output of the compiler, has instruction memory that represents the circuit connectivity and its components' functionality, data memory that stores simulation values of nodes and variables in the circuit, and a simulation engine, sometimes called a simulation kernel, that manages scheduling, controls component evaluation, stores and retrieves simulation values, advances time, handles exceptions and interrupts, distributes tasks to processors in the case of a simulation using multiple processors, and other duties. To illustrate the simulation structure, consider a transition occurring at the primary input of a gate-level circuit. The simulation engine determines the fanin gates affected by the transition by following the connections of the primary input in the instruction memory, and places fanout gates in an evaluation queue. For each component in the queue, the evaluation starts by looking up its functionality in instruction memory and ends by storing the output values in data memory. When all events have been evaluated, the simulation engine advances the time.

4. Compiled simulation system structure


The simulation engine and the simulation control unit form the simulator, but the engine resides inside the compiled code, and the simulation control unit lies in a separate program. If high-level code is generated, the object code of the simulation engine is linked with the object code of the compiled circuit to produce a combined object code. If native code is generated, the final result is the executable. In running a simulation, the control unit is invoked first, then the compiled code is loaded. Through the control unit, the user directs the simulationfor example, specify the time steps a simulation is to be run, setting break points, inspecting node values, dumping out signal traces, and check-point. An example of a compiled simulator is presented in "Cycle-Based Simulators" on page 88.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows