gt: 						BigInteger counter = r.lowerEndpoint();
context: /*******************************************************************************
   * PSHDL is a library and (trans-)compiler for PSHDL input. It generates
   *     output suitable for implementation or simulation of it.
   *
   *     Copyright (C) 2013 Karsten Becker (feedback (at) pshdl (dot) org)
   *
   *     This program is free software: you can redistribute it and/or modify
   *     it under the terms of the GNU General Public License as published by
   *     the Free Software Foundation, either version 3 of the License, or
   *     (at your option) any later version.
   *
   *     This program is distributed in the hope that it will be useful,
   *     but WITHOUT ANY WARRANTY; without even the implied warranty of
   *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *     GNU General Public License for more details.
   *
   *     You should have received a copy of the GNU General Public License
   *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
   *
   *     This License does not grant permission to use the trade names, trademarks,
   *     service marks, or product names of the Licensor, except as required for
   *     reasonable and customary use in describing the origin of the Work.
   *
   * Contributors:
   *     Karsten Becker - initial API and implementation
   ******************************************************************************/
  package org.pshdl.model.simulation;
  
  import java.math.BigInteger;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.LinkedHashMap;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.SortedSet;
  import java.util.concurrent.atomic.AtomicInteger;
  
  import org.pshdl.interpreter.JavaPSHDLLib.TimeUnit;
  import org.pshdl.model.HDLAnnotation;
  import org.pshdl.model.HDLArithOp;
  import org.pshdl.model.HDLArithOp.HDLArithOpType;
  import org.pshdl.model.HDLArrayInit;
  import org.pshdl.model.HDLAssignment;
  import org.pshdl.model.HDLAssignment.HDLAssignmentType;
  import org.pshdl.model.HDLBlock;
  import org.pshdl.model.HDLDeclaration;
  import org.pshdl.model.HDLEqualityOp;
  import org.pshdl.model.HDLEqualityOp.HDLEqualityOpType;
  import org.pshdl.model.HDLExpression;
  import org.pshdl.model.HDLForLoop;
  import org.pshdl.model.HDLFunction;
  import org.pshdl.model.HDLFunctionCall;
  import org.pshdl.model.HDLIfStatement;
  import org.pshdl.model.HDLIfStatement.TreeSide;
  import org.pshdl.model.HDLInterfaceInstantiation;
  import org.pshdl.model.HDLLiteral;
  import org.pshdl.model.HDLManip;
  import org.pshdl.model.HDLManip.HDLManipType;
  import org.pshdl.model.HDLPackage;
  import org.pshdl.model.HDLPrimitive;
  import org.pshdl.model.HDLRange;
  import org.pshdl.model.HDLReference;
  import org.pshdl.model.HDLResolvedRef;
  import org.pshdl.model.HDLStatement;
  import org.pshdl.model.HDLSwitchCaseStatement;
  import org.pshdl.model.HDLSwitchStatement;
  import org.pshdl.model.HDLTernary;
  import org.pshdl.model.HDLType;
  import org.pshdl.model.HDLUnit;
  import org.pshdl.model.HDLVariable;
  import org.pshdl.model.HDLVariableDeclaration;
  import org.pshdl.model.HDLVariableDeclaration.HDLDirection;
  import org.pshdl.model.HDLVariableRef;
  import org.pshdl.model.IHDLObject;
  import org.pshdl.model.evaluation.ConstantEvaluate;
  import org.pshdl.model.evaluation.HDLEvaluationContext;
  import org.pshdl.model.extensions.FullNameExtension;
  import org.pshdl.model.extensions.RangeExtension;
  import org.pshdl.model.extensions.TypeExtension;
  import org.pshdl.model.simulation.RangeTool.RangeVal;
  import org.pshdl.model.utils.HDLLibrary;
  import org.pshdl.model.utils.HDLQualifiedName;
  import org.pshdl.model.utils.HDLQuery;
  import org.pshdl.model.utils.Insulin;
  import org.pshdl.model.utils.ModificationSet;
  import org.pshdl.model.utils.Refactoring;
  
  import com.google.common.base.Optional;
  import com.google.common.collect.Lists;
  import com.google.common.collect.Maps;
  import com.google.common.collect.Range;
  
  public class HDLSimulator {
  
  	public static HDLUnit createSimulationModel(HDLUnit unit, HDLEvaluationContext context, String src, char separator) {
  		if ((unit.getSimulation() != null) && unit.getSimulation()) {
  			return createTestbenchModel(unit, context, src, separator);
  		}
  		return createRegularSimModel(unit, context, src, separator);
  	}
  
  	private static HDLUnit createRegularSimModel(HDLUnit unit, HDLEvaluationContext context, String src, char separator) {
  		HDLUnit insulin = Insulin.transform(unit, src);
  		final HDLPackage pkg = flattenAll(context, insulin, separator);
  		insulin = getSingleUnit(pkg);
  		insulin = convertArrayInits(context, insulin);
  		insulin = unrollForLoops(context, insulin);
  		insulin = createBitRanges(context, insulin);
  		insulin = literalBitRanges(context, insulin);
  		insulin = flattenMath(context, insulin);
  		insulin = convertTernary(context, insulin);
  		insulin = removeDoubleAssignments(context, insulin);
  		insulin = removeDeadLeaves(context, insulin);
  		insulin.validateAllFields(insulin.getContainer(), true);
  		return insulin;
  	}
  
  	private static HDLUnit flattenMath(HDLEvaluationContext context, HDLUnit insulin) {
  		HDLUnit newUnit = insulin;
  		HDLUnit oldUnit = insulin;
  		do {
  			oldUnit = newUnit;
  			final ModificationSet ms = new ModificationSet();
  			final HDLExpression[] calls = oldUnit.getAllObjectsOf(HDLExpression.class, true);
  			for (final HDLExpression call : calls) {
  				if (call instanceof HDLLiteral) {
  					continue;
  				}
  				if (call instanceof HDLReference) {
  					if (call.getContainingFeature() == HDLAssignment.fLeft) {
  						continue;
  					}
  				}
  				final Optional<BigInteger> value = ConstantEvaluate.valueOf(call, context);
  				if (value.isPresent()) {
  					ms.replace(call, HDLLiteral.get(value.get()));
  				}
  			}
  			newUnit = ms.apply(oldUnit);
  		} while (newUnit != oldUnit);
  		return newUnit;
  	}
  
  	public static final HDLAnnotation TB_VAR = new HDLAnnotation().setName("@TestbenchVar");
  	public static final HDLAnnotation TB_UNIT = new HDLAnnotation().setName("@Testbench");
  
  	/**
  	 * General outline:
  	 * <ul>
  	 * <li>Create a global variable $time
  	 * <li>Foreach process create 2 variables: process_state, process_time
  	 * <li>Split process into state machine
  	 * </ul>
  	 * Execution then by:
  	 * <ul>
  	 * <li>Run all processes methods until $process_time > $time or $process_state < 0 (is blocked)
  	 * <li>Run logic
  	 * </ul>
  	 */
  	private static HDLUnit createTestbenchModel(HDLUnit unit, HDLEvaluationContext context, String src, char separator) {
  		HDLUnit insulin = Insulin.transform(unit, src);
  		insulin = rewriteTimeBase(insulin);
  		insulin = addTimeVar(insulin);
  		insulin = rewriteProcesses(insulin);
  		insulin = annotateVariableDeclarations(insulin);
  		insulin = insulin.addAnnotations(TB_UNIT).copyDeepFrozen(insulin.getContainer());
  		insulin.validateAllFields(insulin.getContainer(), true);
  		return createRegularSimModel(insulin, context, src, separator);
  	}
  
  	/**
  	 * Brings all waitFor calls to the smallest time unit. It also adds a TimeBase annotation.
  	 *
  	 * @param insulin
  	 * @return
  	 */
  	private static HDLUnit rewriteTimeBase(HDLUnit insulin) {
  		final Collection<HDLFunctionCall> waits = HDLQuery.select(HDLFunctionCall.class).from(insulin).whereObj().fullNameIs(HDLQualifiedName.create("pshdl", "waitFor")).getAll();
  		int minBase = Integer.MAX_VALUE;
  		final HDLEvaluationContext enumContext = new HDLEvaluationContext();
  		enumContext.enumAsInt = true;
  		for (final HDLFunctionCall call : waits) {
  			final HDLExpression timeUnit = call.getParams().get(1);
  			final Optional<BigInteger> timeUnitValue = ConstantEvaluate.valueOf(timeUnit, enumContext);
  			if (timeUnitValue.isPresent()) {
  				minBase = Math.min(timeUnitValue.get().intValue(), minBase);
  			}
  		}
  		if (minBase == Integer.MAX_VALUE) {
  			return insulin;
  		}
  		final ModificationSet ms = new ModificationSet();
  		ms.addTo(insulin, HDLUnit.fAnnotations, new HDLAnnotation().setName("@TimeBase").setValue(TimeUnit.values()[minBase].name()));
  		for (final HDLFunctionCall call : waits) {
  			final HDLExpression arg = call.getParams().get(0);
  			HDLExpression amount = arg;
  			final HDLExpression timeUnit = call.getParams().get(1);
  			final Optional<BigInteger> timeUnitValue = ConstantEvaluate.valueOf(timeUnit, enumContext);
  			if (timeUnitValue.isPresent()) {
  				final int timeBaseInt = timeUnitValue.get().intValue();
  				for (int i = minBase; i < timeBaseInt; i++) {
  					amount = new HDLArithOp().setLeft(amount).setType(HDLArithOpType.MUL).setRight(HDLLiteral.get(1000));
  				}
  				if (arg != amount) {
  					ms.replace(arg, amount);
  				}
  			}
  		}
  		return ms.apply(insulin);
  	}
  
  	private static HDLUnit annotateVariableDeclarations(HDLUnit insulin) {
  		final ModificationSet ms = new ModificationSet();
  		final HDLVariableDeclaration[] hvds = insulin.getAllObjectsOf(HDLVariableDeclaration.class, true);
  		for (final HDLVariableDeclaration hdlVariableDeclaration : hvds) {
  			if (hdlVariableDeclaration.getAnnotation(TB_VAR.getName()) == null) {
  				ms.replace(hdlVariableDeclaration, hdlVariableDeclaration.addAnnotations(TB_VAR));
  			}
  		}
  		return ms.apply(insulin);
  	}
  
  	private static HDLUnit rewriteProcesses(HDLUnit insulin) {
  		final ModificationSet ms = new ModificationSet();
  		final Collection<HDLBlock> processes = HDLQuery.select(HDLBlock.class).from(insulin).where(HDLBlock.fProcess).isEqualTo(Boolean.TRUE).getAll();
  		for (final HDLBlock process : processes) {
  			final HDLQualifiedName fqn = FullNameExtension.fullNameOf(process);
  			HDLVariableDeclaration processNextTimeDecl = new HDLVariableDeclaration().setType(HDLPrimitive.getUint().setWidth(HDLLiteral.get(64)));
  			final HDLVariable processNexTimeVar = new HDLVariable().setName("$process_time_next_" + fqn.getLastSegment()).setDefaultValue(HDLLiteral.get(0));
  			processNextTimeDecl = processNextTimeDecl.addVariables(processNexTimeVar);
  			HDLVariableDeclaration processStateDecl = new HDLVariableDeclaration().setType(HDLPrimitive.getInteger());
  			final HDLVariable processStateVar = new HDLVariable().setName("$process_state_" + fqn.getLastSegment()).setDefaultValue(HDLLiteral.get(0));
  			processStateDecl = processStateDecl.addVariables(processStateVar);
  			ms.addTo(insulin, HDLUnit.fInits, processNextTimeDecl, processStateDecl);
  			final Map<Integer, List<HDLStatement>> cases = Maps.newLinkedHashMap();
  			int currentCase = 0;
  			cases.put(currentCase, Lists.<HDLStatement> newArrayList());
  			for (final HDLStatement stmnt : process.getStatements()) {
  				currentCase = processStatement(cases, stmnt, currentCase, processStateVar, processNexTimeVar, ms);
  			}
  			cases.get(currentCase).add(new HDLAssignment().setLeft(processStateVar.asHDLRef()).setRight(HDLLiteral.get(0)));
  			final HDLSwitchStatement processSwitch = new HDLSwitchStatement().setCaseExp(processStateVar.asHDLRef());
  			final List<HDLSwitchCaseStatement> caseList = Lists.newArrayList();
  			for (final Entry<Integer, List<HDLStatement>> e : cases.entrySet()) {
  				final HDLSwitchCaseStatement caseStatement = new HDLSwitchCaseStatement().setLabel(HDLLiteral.get(e.getKey())).setDos(e.getValue());
  				caseList.add(caseStatement);
  			}
  			final HDLSwitchStatement finalSwitch = processSwitch.setCases(caseList);
  			ms.replace(process, new HDLBlock().setProcess(true).addStatements(finalSwitch));
  		}
  		return ms.apply(insulin);
  	}
  
  	private static int processStatement(Map<Integer, List<HDLStatement>> cases, HDLStatement stmnt, int currentCase, HDLVariable processStateVar, HDLVariable processNexTimeVar,
  			ModificationSet ms) {
  		final List<HDLStatement> currentCaseList = cases.get(currentCase);
  		switch (stmnt.getClassType()) {
  		case HDLAssignment:
  			currentCaseList.add(stmnt);
  			break;
  		case HDLFunctionCall:
  			final HDLFunctionCall hfc = (HDLFunctionCall) stmnt;
  			final Optional<HDLFunction> func = hfc.resolveFunction();
  			if (func.isPresent()) {
  				final HDLQualifiedName fqn = FullNameExtension.fullNameOf(func.get());
  				final ArrayList<HDLExpression> params = hfc.getParams();
  				if (fqn.equals(HDLQualifiedName.create("pshdl", "waitFor"))) {
  					final HDLExpression amount = params.get(0);
  					currentCase = waitFor(cases, currentCase, processStateVar, processNexTimeVar, amount);
  				}
  				if (fqn.equals(HDLQualifiedName.create("pshdl", "waitUntil"))) {
  					final int waitCase = currentCase = newCase(cases, currentCase);
  					insertJump(currentCaseList, processStateVar, waitCase);
  					final List<HDLStatement> list = cases.get(waitCase);
  					currentCase = newCase(cases, currentCase);
  					final HDLTernary tern = new HDLTernary().setIfExpr(params.get(0))//
  							.setThenExpr(toInteger(currentCase))//
  							.setElseExpr(toInteger(-(waitCase + 1)));
  					list.add(new HDLAssignment().setLeft(processStateVar.asHDLRef()).setRight(tern));
  				}
  				if (fqn.equals(HDLQualifiedName.create("pshdl", "wait"))) {
  					// Create a new case so that the goto 0 case has some place
  					// to go
  					currentCase = newCase(cases, currentCase);
  					insertJump(currentCaseList, processStateVar, Integer.MAX_VALUE);
  				}
  				if (fqn.equals(HDLQualifiedName.create("pshdl", "pulse"))) {
  					final HDLReference var = (HDLReference) params.get(0);
  					final HDLExpression amount = params.get(1);
  					final HDLAssignment setZero = new HDLAssignment().setLeft(var).setRight(HDLLiteral.get(0));
  					processStatement(cases, setZero, currentCase, processStateVar, processNexTimeVar, ms);
  					currentCase = waitFor(cases, currentCase, processStateVar, processNexTimeVar, amount);
  					final HDLAssignment setOne = new HDLAssignment().setLeft(var).setRight(HDLLiteral.get(1));
  					processStatement(cases, setOne, currentCase, processStateVar, processNexTimeVar, ms);
  					currentCase = waitFor(cases, currentCase, processStateVar, processNexTimeVar, amount);
  				}
  
  			}
  			break;
  		case HDLIfStatement: {
  			final HDLIfStatement ifStmnt = (HDLIfStatement) stmnt;
  			final int thenCase = currentCase = newCase(cases, currentCase);
  			for (final HDLStatement hdlStatement : ifStmnt.getThenDo()) {
  				currentCase = processStatement(cases, hdlStatement, currentCase, processStateVar, processNexTimeVar, ms);
  			}
  			final int thenExitCase = currentCase;
  			final int elseCase = currentCase = newCase(cases, currentCase);
  			for (final HDLStatement hdlStatement : ifStmnt.getElseDo()) {
  				currentCase = processStatement(cases, hdlStatement, currentCase, processStateVar, processNexTimeVar, ms);
  			}
  			final int elseExitCase = currentCase;
  			final int targetCase = currentCase = newCase(cases, currentCase);
  			final HDLTernary tern = new HDLTernary().setIfExpr(ifStmnt.getIfExp())//
  					.setThenExpr(HDLLiteral.get(thenCase)) //
  					.setElseExpr(HDLLiteral.get(elseCase));
  			final HDLAssignment switchJump = new HDLAssignment().setLeft(processStateVar.asHDLRef()).setRight(tern);
  			currentCaseList.add(switchJump);
  			insertJump(cases.get(thenExitCase), processStateVar, targetCase);
  			insertJump(cases.get(elseExitCase), processStateVar, targetCase);
  			break;
  		}
  		case HDLForLoop: {
  			final HDLForLoop loop = (HDLForLoop) stmnt;
  			final String fqn = FullNameExtension.fullNameOf(loop.getParam()).toString('_');
  			final HDLVariable loopVar = loop.getParam().setName(fqn);
  			final HDLRange range = loop.getRange().get(0);
  			new HDLAssignment().setLeft(loopVar.asHDLRef()).setRight(range.getFrom());
  			int condCase;
  			if (currentCaseList.isEmpty()) {
  				condCase = currentCase;
  			} else {
  				condCase = currentCase = newCase(cases, currentCase);
  				insertJump(currentCaseList, processStateVar, condCase);
  			}
  			HDLVariableDeclaration idxDecl = new HDLVariableDeclaration().setType(HDLPrimitive.getNatural());
  			final HDLVariable idxVar = new HDLVariable().setName(fqn);
  			idxDecl = idxDecl.addVariables(idxVar);
  			ms.addTo(loop.getContainer(HDLUnit.class), HDLUnit.fInits, idxDecl);
  			final HDLForLoop newLoop = Refactoring.renameVariable(loop.getParam(), fqn, loop);
  			final int execCase = currentCase = newCase(cases, currentCase);
  			for (final HDLStatement doStatement : newLoop.getDos()) {
  				currentCase = processStatement(cases, doStatement, currentCase, processStateVar, processNexTimeVar, ms);
  			}
  			final List<HDLStatement> caseList = cases.get(currentCase);
  			caseList.add(new HDLAssignment().setLeft(idxVar.asHDLRef()).setType(HDLAssignmentType.ADD_ASSGN).setRight(HDLLiteral.get(1)));
  			insertJump(caseList, processStateVar, condCase);
  			final int exitCase = currentCase = newCase(cases, currentCase);
  			final HDLTernary tern = new HDLTernary().setIfExpr(new HDLEqualityOp().setLeft(loopVar.asHDLRef()).setType(HDLEqualityOpType.GREATER_EQ).setRight(range.getTo()))//
  					.setThenExpr(HDLLiteral.get(execCase)) //
  					.setElseExpr(HDLLiteral.get(exitCase));
  			final HDLAssignment switchJump = new HDLAssignment().setLeft(processStateVar.asHDLRef()).setRight(tern);
  			cases.get(condCase).add(switchJump);
  			break;
  		}
  		case HDLBlock: {
  			final HDLBlock block = (HDLBlock) stmnt;
  			for (final HDLStatement sub : block.getStatements()) {
  				processStatement(cases, sub, currentCase, processStateVar, processNexTimeVar, ms);
  			}
  			break;
  		}
  		case HDLSwitchStatement:
  			throw new IllegalArgumentException("Switch cases are currently not supported in Testbench processes");
  		default:
  			break;
  		}
  		return currentCase;
  	}
  
  	public static HDLManip toInteger(int target) {
  		return new HDLManip().setTarget(HDLLiteral.get(target)).setType(HDLManipType.CAST).setCastTo(HDLPrimitive.getInteger());
  	}
  
  	public static int waitFor(Map<Integer, List<HDLStatement>> cases, int currentCase, HDLVariable processStateVar, HDLVariable processNexTimeVar, HDLExpression amount) {
  		final List<HDLStatement> currentCaseList = cases.get(currentCase);
  		currentCaseList.add(new HDLAssignment().setLeft(processNexTimeVar.asHDLRef()).setType(HDLAssignmentType.ADD_ASSGN).setRight(amount));
  		final int timeCase = currentCase = newCase(cases, currentCase);
  		insertJump(currentCaseList, processStateVar, timeCase);
  		return currentCase;
  	}
  
  	public static void insertJump(final List<HDLStatement> caseList, HDLVariable processStateVar, final long targetCase) {
  		caseList.add(new HDLAssignment().setLeft(processStateVar.asHDLRef()).setRight(HDLLiteral.get(targetCase)));
  	}
  
  	public static int newCase(Map<Integer, List<HDLStatement>> cases, int currentCase) {
  		final int targetCase = currentCase + 1;
  		cases.put(targetCase, Lists.<HDLStatement> newArrayList());
  		return targetCase;
  	}
  
  	private static HDLUnit addTimeVar(HDLUnit insulin) {
  		final ModificationSet ms = new ModificationSet();
  		HDLVariableDeclaration hvd = new HDLVariableDeclaration().setType(HDLPrimitive.getUint().setWidth(HDLLiteral.get(64)));
  		hvd = hvd.addVariables(new HDLVariable().setName("$time").setDefaultValue(HDLLiteral.get(0)));
  		ms.addTo(insulin, HDLUnit.fInits, hvd);
  		// TODO Unify time base
  		return ms.apply(insulin);
  	}
  
  	private static HDLUnit getSingleUnit(final HDLPackage pkg) {
  		final ArrayList<HDLUnit> units = pkg.getUnits();
  		if (units.size() != 1) {
  			throw new IllegalArgumentException("Something went wrong, found more or less than 1 HDLUnit");
  		}
  		return units.get(0);
  	}
  
  	private static HDLUnit removeDeadLeaves(HDLEvaluationContext context, HDLUnit insulin) {
  		final HDLIfStatement[] ifs = insulin.getAllObjectsOf(HDLIfStatement.class, true);
  		final ModificationSet ms = new ModificationSet();
  		for (final HDLIfStatement hif : ifs) {
  			final HDLExpression ifExp = hif.getIfExp();
  			final Optional<BigInteger> valueOf = ConstantEvaluate.valueOf(ifExp, context);
  			if (valueOf.isPresent()) {
  				if (valueOf.get().equals(BigInteger.ZERO)) {
  					final ArrayList<HDLStatement> elseDo = hif.getElseDo();
  					ms.replace(hif, elseDo.toArray(new HDLStatement[elseDo.size()]));
  				} else {
  					final ArrayList<HDLStatement> thenDo = hif.getThenDo();
  					ms.replace(hif, thenDo.toArray(new HDLStatement[thenDo.size()]));
  				}
  			}
  		}
  		return ms.apply(insulin);
  	}
  
  	private static HDLUnit convertArrayInits(HDLEvaluationContext context, HDLUnit insulin) {
  		final ModificationSet ms = new ModificationSet();
  		final HDLArrayInit[] inits = insulin.getAllObjectsOf(HDLArrayInit.class, true);
  		for (final HDLArrayInit arrayInit : inits) {
  			final IHDLObject container = arrayInit.getContainer();
  			switch (container.getClassType()) {
  			case HDLArrayInit:
  				break;
  			case HDLAssignment:
  				final HDLReference left = ((HDLAssignment) container).getLeft();
  				if (!(left instanceof HDLVariableRef)) {
  					throw new IllegalArgumentException("Unsupported assignment target for ArrayInit:" + left);
  				}
  				final HDLVariableRef varRef = (HDLVariableRef) left;
  				addInit(varRef, arrayInit, container, ms);
  				ms.remove(container);
  				break;
  			case HDLVariable:
  				final HDLVariable var = (HDLVariable) container;
  				addInit(var.asHDLRef(), arrayInit, var.getContainer(), ms);
  				// ms.replace(var, var.setDefaultValue(null));
  				break;
  			case HDLRegisterConfig:
  				// resetValues are taken care of later
  				break;
  			default:
  				throw new IllegalArgumentException("Unsupported container for ArrayInit:" + container);
  			}
  		}
  		return ms.apply(insulin);
  	}
  
  	private static void addInit(HDLVariableRef varRef, HDLArrayInit arrayInit, IHDLObject container, ModificationSet ms) {
  		final ArrayList<HDLExpression> exp = arrayInit.getExp();
  		for (int i = 0; i < exp.size(); i++) {
  			final HDLExpression subExp = exp.get(i);
  			final HDLVariableRef addArray = varRef.addArray(HDLLiteral.get(i));
  			if (subExp instanceof HDLArrayInit) {
  				final HDLArrayInit subArray = (HDLArrayInit) subExp;
  				addInit(addArray, subArray, container, ms);
  			} else {
  				final HDLAssignment ass = new HDLAssignment().setLeft(addArray).setRight(subExp);
  				ms.insertAfter(container, ass);
  			}
  		}
  	}
  
  	private static HDLPackage flattenAll(HDLEvaluationContext context, HDLUnit insulin, char separator) {
  		HDLInterfaceInstantiation[] hii = null;
  		HDLPackage res = new HDLPackage();
  		int count = 0;
  		while ((hii = insulin.getAllObjectsOf(HDLInterfaceInstantiation.class, true)).length > 0) {
  			if (count > 50) {
  				throw new IllegalArgumentException("It appears that the number of inlined units is exceptionally large. Maybe a recursive inclusion?");
  			}
  			count++;
  			final HDLLibrary library = insulin.getLibrary();
  			final HDLInterfaceInstantiation hi = hii[0];
  			final HDLQualifiedName asRef = hi.resolveHIf().get().asRef();
  			HDLUnit unit = library.getUnit(asRef);
  			if (unit == null) {
  				throw new IllegalArgumentException("Can not find unit for interface:" + asRef);
  			}
  			unit = Insulin.transform(unit, library.getSrc(asRef));
  			final HDLEvaluationContext hiContext = hi.getContext(HDLEvaluationContext.createDefault(unit));
  			final HDLPackage subUnit = flattenAll(hiContext, unit, separator);
  			for (final HDLDeclaration decl : subUnit.getDeclarations()) {
  				res = res.addDeclarations(decl);
  			}
  			final HDLPackage iuRes = Refactoring.inlineUnit(insulin, hi, getSingleUnit(subUnit), separator);
  			for (final HDLDeclaration decl : iuRes.getDeclarations()) {
  				res = res.addDeclarations(decl);
  			}
  			insulin = getSingleUnit(iuRes);
  		}
  		res = res.addUnits(insulin);
  		return res;
  	}
  
  	private static class InitTuple {
  		public final HDLReference ref;
  		public final IHDLObject container;
  
  		public InitTuple(HDLReference ref, IHDLObject container) {
  			super();
  			this.ref = ref;
  			this.container = container;
  		}
  
  		@Override
  		public int hashCode() {
  			final int prime = 31;
  			int result = 1;
  			result = (prime * result) + ((container == null) ? 0 : container.hashCode());
  			result = (prime * result) + ((ref == null) ? 0 : ref.hashCode());
  			return result;
  		}
  
  		@Override
  		public boolean equals(Object obj) {
  			if (this == obj) {
  				return true;
  			}
  			if (obj == null) {
  				return false;
  			}
  			if (getClass() != obj.getClass()) {
  				return false;
  			}
  			final InitTuple other = (InitTuple) obj;
  			if (container != other.container) {
  				return false;
  			}
  			if (ref == null) {
  				if (other.ref != null) {
  					return false;
  				}
  			} else if (!ref.equals(other.ref)) {
  				return false;
  			}
  			return true;
  		}
  
  	}
  
  	private static HDLUnit removeDoubleAssignments(HDLEvaluationContext context, HDLUnit insulin) {
  		final ModificationSet ms = new ModificationSet();
  		final HDLAssignment[] asss = insulin.getAllObjectsOf(HDLAssignment.class, true);
  		final Map<InitTuple, HDLAssignment> scopeWrites = Maps.newLinkedHashMap();
  		for (final HDLAssignment hdlAssignment : asss) {
  			final IHDLObject container = hdlAssignment.getContainer();
  			final InitTuple it = new InitTuple(hdlAssignment.getLeft(), container);
  			final HDLAssignment otherAss = scopeWrites.get(it);
  			if (otherAss != null) {
  				if (container instanceof HDLIfStatement) {
  					final HDLIfStatement hdlIfStatement = (HDLIfStatement) container;
  					final TreeSide oSide = hdlIfStatement.treeSide(otherAss);
  					final TreeSide tSide = hdlIfStatement.treeSide(hdlAssignment);
  					if (oSide == tSide) {
  						ms.remove(otherAss);
  					}
  				} else {
  					ms.remove(otherAss);
  				}
  			}
  			scopeWrites.put(it, hdlAssignment);
  		}
  		return ms.apply(insulin);
  	}
  
  	private static AtomicInteger tempID = new AtomicInteger();
  
  	private static HDLUnit convertTernary(HDLEvaluationContext context, HDLUnit insulin) {
  		final ModificationSet ms = new ModificationSet();
  		final HDLTernary[] ternaries = insulin.getAllObjectsOf(HDLTernary.class, true);
  		for (final HDLTernary ternary : ternaries) {
  			final Optional<? extends HDLType> typeOf = TypeExtension.typeOf(ternary);
  			final String name = "$tmp_" + tempID.getAndIncrement();
  			final HDLVariable var = new HDLVariable().setName(name);
  			final HDLVariableDeclaration hvd = new HDLVariableDeclaration().setType(typeOf.get()).addVariables(var);
  			final HDLAssignment newAss = new HDLAssignment().setLeft(var.asHDLRef());
  			final HDLIfStatement ifStatement = new HDLIfStatement()//
  					.setIfExp(ternary.getIfExpr())//
  					.addThenDo(newAss.setRight(ternary.getThenExpr()))//
  					.addElseDo(newAss.setRight(ternary.getElseExpr()));
  			ms.replace(ternary, var.asHDLRef());
  			ms.insertBefore(ternary.getContainer(HDLStatement.class), hvd, ifStatement);
  		}
  		return ms.apply(insulin);
  	}
  
  	private static HDLUnit literalBitRanges(HDLEvaluationContext context, HDLUnit insulin) {
  		final ModificationSet ms = new ModificationSet();
  		final HDLRange[] ranges = insulin.getAllObjectsOf(HDLRange.class, true);
  		for (final HDLRange hdlRange : ranges) {
  			final Optional<BigInteger> toBig = ConstantEvaluate.valueOf(hdlRange.getTo(), context);
  			if (toBig.isPresent()) {
  				final HDLExpression from = hdlRange.getFrom();
  				if (from != null) {
  					final Optional<BigInteger> fromBig = ConstantEvaluate.valueOf(from, context);
  					if (!fromBig.isPresent()) {
  						throw new IllegalArgumentException("Given the context it should always be non null");
  					}
  					ms.replace(hdlRange, hdlRange.setFrom(HDLLiteral.get(fromBig.get())).setTo(HDLLiteral.get(toBig.get())));
  				} else {
  					ms.replace(hdlRange, hdlRange.setTo(HDLLiteral.get(toBig.get())));
  				}
  			} else {
  				// ms.replace(hdlRange, hdlRange.setTo(HDLLiteral.get(-1)));
  			}
  		}
  		return ms.apply(insulin);
  	}
  
  	private static HDLUnit createBitRanges(HDLEvaluationContext context, HDLUnit insulin) {
  		final HDLVariableRef[] refs = insulin.getAllObjectsOf(HDLVariableRef.class, true);
  		final Map<HDLQualifiedName, List<RangeVal>> ranges = new LinkedHashMap<>();
  		final Map<HDLQualifiedName, Range<BigInteger>> fullRanges = new LinkedHashMap<>();
  		for (final HDLVariableRef ref : refs) {
  			if (ref.getContainer() instanceof HDLAssignment) {
  				final HDLAssignment ass = (HDLAssignment) ref.getContainer();
  				if (ass.getLeft() == ref) {
  					final Optional<HDLVariable> resolved = ref.resolveVar();
  					if (!resolved.isPresent()) {
  						throw new IllegalArgumentException("Can not resolve:" + ref.getVarRefName());
  					}
  					final HDLVariable resolveVar = resolved.get();
  					if (resolveVar.getDirection() != HDLDirection.IN) {
  						final HDLQualifiedName varRefName = ref.getVarRefName();
  						if (ref.getBits().size() > 0) {
  							List<RangeVal> set = ranges.get(varRefName);
  							if (set == null) {
  								set = new LinkedList<>();
  								ranges.put(varRefName, set);
  							}
  							for (final HDLRange r : ref.getBits()) {
  								final Optional<Range<BigInteger>> determineRange = RangeExtension.rangeOf(r, context);
  								if (!determineRange.isPresent()) {
  									throw new IllegalArgumentException("Can not determine Range for:" + r);
  								}
  								set.add(new RangeVal(determineRange.get().lowerEndpoint(), 1));
  								set.add(new RangeVal(determineRange.get().upperEndpoint(), -1));
  							}
  						} else {
  							final HDLExpression width = TypeExtension.getWidth(resolveVar);
  							if (width != null) {
  								final Optional<BigInteger> bWidth = ConstantEvaluate.valueOf(width, context);
  								if (!bWidth.isPresent()) {
  									throw new IllegalArgumentException("Given the context this should be constant");
  								}
  								fullRanges.put(varRefName, RangeTool.createRange(BigInteger.ZERO, bWidth.get().subtract(BigInteger.ONE)));
  							}
  						}
  					}
  				}
  			}
  		}
  		final ModificationSet ms = new ModificationSet();
  		final Map<HDLQualifiedName, SortedSet<Range<BigInteger>>> splitRanges = new LinkedHashMap<>();
  		for (final Map.Entry<HDLQualifiedName, List<RangeVal>> entry : ranges.entrySet()) {
  			final List<RangeVal> value = entry.getValue();
  			final HDLQualifiedName varName = entry.getKey();
  			if (fullRanges.containsKey(varName)) {
  				final Range<BigInteger> fullWidth = fullRanges.get(varName);
  				value.add(new RangeVal(fullWidth.lowerEndpoint(), 1));
  				value.add(new RangeVal(fullWidth.upperEndpoint(), -1));
  			}
  			final SortedSet<Range<BigInteger>> split = RangeTool.split(value);
  			splitRanges.put(varName, split);
  		}
  		// Change bit access to broken down ranges
  		for (final HDLVariableRef ref : refs) {
  			final SortedSet<Range<BigInteger>> list = splitRanges.get(ref.getVarRefName());
  			if (list != null) {
  				final ArrayList<HDLRange> newRanges = new ArrayList<>();
  				if (!ref.getBits().isEmpty()) {
  					for (final HDLRange bit : ref.getBits()) {
  						if (bit.getFrom() != null) { // Singular ranges don't do
  							// anything
  							final Optional<Range<BigInteger>> range = RangeExtension.rangeOf(bit, context);
  							if (!range.isPresent()) {
  								throw new IllegalArgumentException("Can not determine Range of:" + bit);
  							}
  							for (final Range<BigInteger> newRange : list) {
  								if (range.get().isConnected(newRange)) {
  									newRanges.add(0, createRange(newRange));
  								}
  							}
  						} else {
  							newRanges.add(0, bit);
  						}
  					}
  				} else {
  					for (final Range<BigInteger> vRange : list) {
  						newRanges.add(0, createRange(vRange));
  					}
  				}
  				if (newRanges.size() != 0) {
  					ms.replace(ref, ref.setBits(newRanges));
  				}
  			}
  		}
  		final HDLUnit apply = ms.apply(insulin);
  		return Insulin.handleMultiBitAccess(apply, context);
  	}
  
  	private static HDLRange createRange(Range<BigInteger> newRange) {
  		if (newRange.lowerEndpoint().equals(newRange.upperEndpoint())) {
  			return new HDLRange().setTo(HDLLiteral.get(newRange.upperEndpoint()));
  		}
  		return new HDLRange().setTo(HDLLiteral.get(newRange.lowerEndpoint())).setFrom(HDLLiteral.get(newRange.upperEndpoint()));
  	}
  
  	/**
  	 * Unrolls a loop and creates the statements with an accordingly replaced loop parameter
  	 *
  	 * @param context
  	 * @param unit
  	 * @return
  	 */
  	private static HDLUnit unrollForLoops(HDLEvaluationContext context, HDLUnit unit) {
  		HDLUnit newUnit = unit;
  		int count = 0;
  		do {
  			if (count > 50) {
  				throw new IllegalArgumentException("Something went wrong while unrolling the loops");
  			}
  			count++;
  			unit = newUnit;
  			final HDLForLoop[] loops = unit.getAllObjectsOf(HDLForLoop.class, true);
  			final ModificationSet ms = new ModificationSet();
  			for (final HDLForLoop loop : loops) {
  				final HDLVariable param = loop.getParam();
  				final Range<BigInteger> r = RangeExtension.rangeOfForced(loop.getRange().get(0), context, "PSEX");
  				final List<HDLStatement> newStmnts = new ArrayList<>();
  				for (final HDLStatement stmnt : loop.getDos()) {
  					final Collection<HDLVariableRef> refs = HDLQuery.select(HDLVariableRef.class).from(stmnt).where(HDLResolvedRef.fVar).lastSegmentIs(param.getName()).getAll();
  					if (refs.size() == 0) {
  						newStmnts.add(stmnt);
  					} else {
  