Documentation

Table of contents

Abstraction Layers, Modules

zamiaCAD architecture

Flow

zamiaCAD flow

Build Process

zamiaCAD does contain a full VHDL compiler (a parser for Verilog is almost done, parsers for other hardware description languages like AHDL are planned) and project build (make) facility. The whole build process is directed by the BuildPath.txt file which informs the builder where to find sources and what libraries to put them into.

Since sources may be stored on high-latency network mount points (e.g. NFS or AFS mounts) zamiaCAD has a built-in file system cache (FSCache) which it uses to cache files and directory information on you local disk for speed.

Parsing all VHDL sources accessible through the directories configured in a project's BuildPath.txt can be very time consuming and is often not neccesary since a significant portion of the design units defined in those files might never actually get instantiated or used in the project (this is especially true for technology/simulation libraries). Therefore, zamiaCAD comes with a very high-speed VHDL indexer which will only extract information about which design units are declared in which files. The actual VHDL parser is then used in an on-demand manner only on local source files (those which reside in your eclipse workspace) and files that define design units which are actually needed during elaboration.

Build Process

Intermediate Layer

Overview of the most important ZIL/IG classes

Please note: At the time of this writing, the code base is still in a state of flux. Currently, the classes we are talking about here reside in a package called com.zamiacad.instgraph and carry the outdated IG prefix. The old com.zamiacad.zil package still exists and will be removed as soon as all legacy code has been ported to the new IG infrastructre (which then might be re-named to ZIL).

The image below gives you an overview of the class hierarchy of the ZIL/IG graph data structure in zamiaCAD . The diagram is not complete, many more classes exist below the ones depicted, this is just meant to give you a quick overview to get you started. Remember that if you're using the eclipse ide, you can always get a complete, auto-generated class hierarchy of any class by placing the cursor on the class name and press F4.

Class Diagram

IG/ZIL interpreter

During elaboration various static values have to be computed (e.g. range boundaries for the type system or conditions and loop ranges when unrolling generate statement). For this purpose a complete interpreter has been implemented in the package com.zamaicad.instgraph.interpreter. The interpreter is implemented as a stack machine and supports all operations that can occur during elaboration, including math and logic operators and (potentially recursive) subprogram calls.

Persisting the ZIL/IG layer

The whole ZIL/IG graph is designed in a way that is very scalable in terms of size and performance. Therefore, the graph is persisted in a nearly transparent way using ZDB (effectively by using plain old java serialization. In order to be able to do that the graph needs to partitioned in a smart way so individual piece can be persisted independently.

In ZIL/IG this is mainly done on the ContainerItem level: containers store declarative items such as Objects (Variables, Signals, Files, Constants), Types and Subprograms - but they never keep direct references to these objects but instead persist them in ZDB and just keep the database identifier (DBID - ZDB's equivalent to a primary key in a conventional relational database). Also, any other class that contains references to one of these objects stores DBIDs instead (this mainly applies to references to IGType) so we never persist these objects twice by accident.

For performance reasons, we cannot completely abstract away the effects of this persistence concept. However, in most places convenience functions like getType() exist which will silently and transparently do lazy loading on those objects which we cannot keep references on. In order to do that, IGItem (the base class of all ZIL/IG classes) has reference to the current ZDB instance plus a string that contains the name of the project it belongs to. Using these two pieces of data it is able to recover any reference to the ZamiaProject, the DUManager, the IGManager and of course ZDB itself by using ZDB's getOwner() method and the ZamiaProjectMap singleton.

ZDB

ZDB is zamiaCAD 's own built-on object database. ZDB is based on Java's serialization feature which it uses to persist any POJO (plain old java object). ZDB allows users to easily and efficiently

  • efficiently store and retrieve any kind of POJO
    • storing an object will give you a DBID (long) return value which you can use to retrieve the object later
  • update stored objects
    • if the object is still held in RAM cache this will result in a true update. Otherwise, the object will be re-persisted and the old version will become inaccessible (but will still allocate disk space until the database is compacted)
  • create and use simple String -> long indexes
    • the indices are typically very small and kept in RAM for the whole session for speed.

for speed, a certain number of objects (configurable at compile time) are kept in RAM using a recently used cache eviction strategy.

  • objects that implement the ZDBIDSaver interface will be informed when they are being persisted and when they are retrieved from disk. They will also be informed about their DBID.

Simulation

This article gives you an introduction and overview of zamiaCAD 's simulator infrastructure from a developer's perspective.

Please note: This article contains preliminary information. The ZIL/IG-based simulation infrastructure is under construction at the time of this writing.

ZIL/IG based simulation infrastructure

The ZIL/IG based simulation infrastructure resides in the com.zamiacad.instgraph.sim package.

zamiaCAD supports multiple simulator implementations, called simulator plugins. These plugins may be true simulators or waveform import plugins. Common to all of them is the interface com.zamiacad.instgraph.sim.IGISimulator which all simulator plugins need to implement.

Simulator plugins may rely on the ZIL/IG infrastructure in the Intermediate Layer. Of particular interest might be the math and logic operator implementations in IGStaticValue and the interpreter which resides in com.zamiacad.instgraph.interpreter.

Also, the types computed in the intermediate layer can directly be used for simulation.

Reference simulator

The reference simulator resides in com.zamiacad.instgraph.sim.ref and relies heavily on the Intermediate Layer infrastructure. Basically it just adds a timing model for signals to the existing interpreter.

VCD Import

The VCD import plugin resides in com.zamiacad.instgraph.sim.vcd. It comes with a built-in VCD parser built using JavaCC and has been tested using GHDL-generated VCD files.

RTL based reference simulator

The RTL reference simulator resides in the package com.zamacad.rtl.sim. It relies on ZIL/IG code for math and logic operations (IGStaticValue). The package com.zamiacad.rtl.sim.behaviors contains classes implementing the semantics of the various RTL Graph node types. The simulator itself is an event based simulator residing in com.zamiacad.rtl.sim.Simulator that fully supports the VHDL timing model.

For interpreter modules (non-synthesizable parts of the design) the simulator will fall back to the ZIL/IG interpreter in com.zamiacad.instgraph.interpreter.

At the time of this writing, no other simulators besides the reference simulator exist. However, the infrastructure is there to support multiple simulator implementations. A new simulator implementation would have to implement the com.zamiacad.rtl.sim.ISimulator interface.

Transforming a VHDL Model Into an RTL Graph

This article gives you a quick overview of how zamiaCAD will go about synthesizing an elaborated design into an RTL Graph. The RTL Graph primitives are described in RTL Modules.

A Closer Look at Synthesizable VHDL

As it turns out, VHDL code that is synthesizable according to IEEE 1076.6 basically consists of three constructs that are relevant (besides declarations and dependencies which have already been dealt with during parsing and elaboration):

  • Component Instantiations
    • These are basically handled during elaboration, we only need to preserve the information computed there in the RTL Graph
  • Process Statements
    • In the end, all they do (once we have inlined functions and unrolled loops) is drive signals
    • This is similar to a software compiler where in the end, all the program does is manipulate variable contents.
  • Signal Assignments
    • These can actually be turned into small equivalent processes

These statements are concurrent statements so the order is not important, we can treat them as an unordered set of statements while processing them.

So, basically all we have to do for each module (architecture/entity pair) is figure out what signal bindings are generated in it.

 
entity e is 
  port (A, B, C : in  bit; X, Y, Z: out bit);
end;
 
architecture arch of e is
  signal S1, S2, S3 : bit;
begin
 
  c1: foo port map (...);               -- Component instantiations
  even: if a = '1' generate
    I1 : foo generic map ...
  end generate;
 
  Z <= A xor B;                         -- Signal assignments
  X <= A and C WHEN f(a, b) ELSE '0';
 
  reg : process                         -- Process statements
  begin
    ...
  end process reg;
 
end;

Signal bindings

A Binding in zamiaCAD is a data structure holding information about

  • the values a signal is assigned
  • the condition(s) under which those assignments happen

Binding computation for signal assignments

The basic idea here is to create a 1:1 mapping of VHDL constructs to RTL graph nodes. e.g.

  • y <= a+b will create an adder
  • y <= a+b when c else a*b will create a multiplexer that has one adder and one multiplier node connected to its inputs

in a more advanced version of the algorithm scheduling and allocation techniques could be used at this point to optimize the resulting graph for area and/or speed.

The biggest problem with this approach is partial signal assignments, e.g.

 y[i] <= a or b;
z.foo <= a and b;

to solve that, zamiaCAD uses TargetOperation nodes in the resulting graph. These TargetOperation nodes accept and generate pairs of inputs/outputs - one data and one enable signal each. The enable signal will carry the information which of the data signal elements is actually "valid" at any point in time. Examples of constructs where TargetOperation nodes get generated are:

  • Array selection (variable/constant index, range)
  • Record field selection
  • Conditional selection (Cond, EMux)

Also, special nodes exists allowing to merging multiple TargetOperation bindings into one resulting signal using multiplexers.

Binding computation for VHDL processes

For processes, zamiaCAD has to deal with

  • Variables
  • Subprogram calls
  • Processes can infer memory
  • Order of statements matters

Therefore, a two-pass transformation algorithm is applied first:

  • Pass 1:
    • Inline subprograms
    • Unroll loops
    • Replace case statements by if statements
  • Pass 2:
    • Inline variables

The result of each pass can be dumped out as VHDL which makes debugging easier.

At this point we're left with signal assignments and if statements only. In a third and final pass we generate signal bindings. Since processes can produce synchroneous (clocked) and asynchroneous (latched) assignments, we need to expand the SignalBinding data structure to hold this information:

  • Async value
  • Async condition (null if unconditional)
  • Sync value
  • Sync condition
  • Clock

Inferring Memory

Once all bindings have been computed for a module, we have to decide which signals will need inferred memory elements:

  • AsyncData != null, AsyncEnable == null, SyncData == null
    • Pure combinational logic
  • AsyncData != null, AsyncEnable != null, SyncData == null
    • Latch
  • AsyncData == null, SyncData != null
    • FlipFlop
  • AsyncData != null, SyncData != null
    • FlipFlop with async set/reset

Testbenches / Dealing with non-synthesizable VHDL on the RT level

Some VHDL constructs are not synthesizable, e.g.

  • File access
  • Assert statements
 assert j<i report "internal error";
  • Wait for statements
 wait for 100 ns;

To preserve these in the RTL Graph (and in order to generate an executable model) zamiaCAD will insert interpreter components:

  • Abstract elements in the circuit graph
  • Simple, event based simulator driven stack machine
  • One interpreter for each non-synthesizable concurrent statement


Example

VHDL testbench:

 
architecture test of COUNTER_TB is
...
begin
 
  implementation: COUNTER port map (REG_IN => REG_IN, ENABLE => ENABLE, ...);
  
  clk <= not clk after period / 2;
 
  process
  begin
    wait until clk'event and clk = '0';
    ENABLE <= '0';
    MODE <= '0';
    res <= '1';
    wait until clk'event and clk = '0';
    ...

Generated interpreter code:

 
   PUSH 0000000000000000000000000000000000000001100001101010000
   WAIT FOR
   PUSH /COUNTER_TB(TEST)/intcomp_0:CLK
   PUSH /COUNTER_TB(TEST)/intcomp_0:CLK
   NOT
   DRIVE

generated RTL Graph:

Testbench RTL Graph

Synthesis Algorithm Overview

Synthesis Algorithm

FSM Recognition

  • Idea: Search bindings for state-signal candidates
  • State signal criteria:
    • No partial assignments
    • Only constant assignments
    • Register inferred
    • Has conditional assignments
  • Search for FSM outputs:
    • Signals which have conditional assignments depending

only on the state signal

=> Signal becomes FSM output signal

Example

VHDL Code:

 
architecture BEHAVIORAL of CONTROLER is
  type t_state is (s_reset, s_if1, s_if2, s_id, s_alu, s_ldih, ...);
  signal state, next_state : t_state;
begin
  reg : process
  begin
    wait until clk'event and clk = '1';
    state <= next_state;
  end process reg;
 
  transition : process (state, RES, IR, READY, Z_OUT)
  begin
    next_state                                                          <= state;  
    if res = '1' then
      next_state                                                        <=s_reset;
    else
      case state is
        when s_reset => next_state                                      <= s_if1;
        when s_if1   => if ready = '0' then next_state                  <= s_if2;  
    ...
  output: process
  begin
      ...
      when s_if2  => IR_EN     <= '1'; ADR_SEL <= '0'; RD <= '1';
      when s_id   => PC_EN     <= '1'; PC_MODE <= '0';
      when s_alu  => REGIN_SEL <= "00"; RF_LE <= '1'; RF_HE <= '1';
      when s_ldih => REGIN_SEL <= "10"; RF_HE <= '1';

Recovered FSM graph:

Fsm

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer