#pragma once

#include <fstream>
#include <streambuf>
#include <sstream>

#include <iostream>

#include "expression.h"
#include "llvm_codegen.h"
#include "verilog_backend.h"

using namespace dbhc;
using namespace std;
using namespace llvm;

namespace ahaHLS {

  static inline
  bool oneCharToken(const char c) {
    vector<char> chars = {'{', '}', ';', ')', '(', ',', '[', ']', ':', '-', '&', '+', '=', '>', '<', '*', '.', '%'};
    return dbhc::elem(c, chars);
  }

  template<typename T>
  class ParseState {
    std::vector<T> ts;
    int pos;

  public:

    ParseState(const std::vector<T>& toks) : ts(toks), pos(0) {}

    int currentPos() const { return pos; }
    void setPos(const int position) { pos = position; }

    bool nextCharIs(const Token t) const {

      return !atEnd() && (peekChar() == t);
    }

    T peekChar(const int offset) const {
      assert(((int) ts.size()) > (pos + offset));
      return ts[pos + offset];
    }

    T peekChar() const { return peekChar(0); }

    T parseChar() {
      assert(((int) ts.size()) > pos);

      T next = ts[pos];
      pos++;
      return next;
    }

    bool atEnd() const {
      return pos == ((int) ts.size());
    }

    int remainderSize() const {
      return ((int) ts.size()) - pos;
    }

    std::string remainder() const {
      stringstream ss;
      for (int i = pos; i < ts.size(); i++) {
        ss << ts.at(i) << " ";
      }
      return ss.str();
      //return ts.substr(pos);
    }

  };

  typedef ParseState<char> TokenState;

  static inline
  bool isBinop(const Token t) {
    //vector<string> binopStrings{"=", "==", "+", "&", "-", "/", "^", "%", "&&", "||", "<=", ">=", "<", ">", "*"};
    vector<string> binopStrings{"==", "+", "&", "-", "/", "^", "%", "&&", "||", "<=", ">=", "<", ">", "*", "%"};
    return elem(t.getStr(), binopStrings);
  }

  static inline
  bool isWhitespace(const char c) {
    return isspace(c);
  }

  template<typename OutType, typename TokenType, typename Parser>
  std::vector<OutType> many(Parser p, ParseState<TokenType>& tokens) {
    std::vector<OutType> stmts;

    while (true) {
      auto res = p(tokens);
      if (!res.has_value()) {
        break;
      }

      stmts.push_back(res.get_value());
    }

    return stmts;
  }

  template<typename OutType, typename TokenType, typename Parser>
  maybe<OutType> tryParse(Parser p, ParseState<TokenType>& tokens) {
    int lastPos = tokens.currentPos();
    //cout << "lastPos = " << lastPos << endl;

    maybe<OutType> val = p(tokens);
    if (val.has_value()) {
      return val;
    }

    tokens.setPos(lastPos);

    //cout << "try failed, now pos = " << tokens.currentPos() << endl;
    return maybe<OutType>();
  }

  template<typename OutType, typename TokenType, typename StatementParser, typename SepParser>
  std::vector<OutType>
  sepBy(StatementParser stmt, SepParser sep, ParseState<TokenType>& tokens) {
    std::vector<OutType> stmts;

    while (!tokens.atEnd()) {
      stmts.push_back(stmt(tokens));
      sep(tokens);
    }

    return stmts;
  }

  template<typename OutType, typename SepType, typename TokenType, typename StatementParser, typename SepParser>
  std::vector<OutType>
  sepBtwn(StatementParser stmt, SepParser sep, ParseState<TokenType>& tokens) {
    std::vector<OutType> stmts;

    while (true) {
      OutType nextStmt = stmt(tokens);
      stmts.push_back(nextStmt);
      auto nextSep = tryParse<SepType>(sep, tokens);
      if (!nextSep.has_value()) {
        break;
      }
    }

    return stmts;
  }

  template<typename OutType, typename SepType, typename TokenType, typename StatementParser, typename SepParser>
  std::vector<OutType>
  sepBtwn0(StatementParser stmt, SepParser sep, ParseState<TokenType>& tokens) {
    std::vector<OutType> stmts;

    maybe<OutType> nextStmt = stmt(tokens);
    if (!nextStmt.has_value()) {
      return {};
    } else {
      stmts.push_back(nextStmt.get_value());
    }

    auto nextSep = tryParse<SepType>(sep, tokens);
    if (!nextSep.has_value()) {
      return stmts;
    }

    while (true) {
      maybe<OutType> nextStmt = stmt(tokens);
      assert(nextStmt.has_value());
      stmts.push_back(nextStmt.get_value());
      auto nextSep = tryParse<SepType>(sep, tokens);
      if (!nextSep.has_value()) {
        break;
      }
    }

    return stmts;
  }

  template<typename F>
  maybe<Token> consumeWhile(TokenState& state, F shouldContinue) {
    string tok = "";
    while (!state.atEnd() && shouldContinue(state.peekChar())) {
      tok += state.parseChar();
    }
    if (tok.size() > 0) {
      return Token(tok);
    } else {
      return maybe<Token>();
    }
  }

  static inline
  bool isUnderscore(const char c) { return c == '_'; }

  static inline
  bool isAlphaNum(const char c) { return isalnum(c); }

  maybe<Token> parseStr(const std::string target, TokenState& chars);
  maybe<Token> parseComment(TokenState& state);
  std::function<maybe<Token>(TokenState& chars)> mkParseStr(const std::string str);

  static inline
  void consumeWhitespace(TokenState& state) {
    while (true) {
      auto commentM = tryParse<Token>(parseComment, state);
      if (!commentM.has_value()) {
        auto ws = consumeWhile(state, isWhitespace);
        if (!ws.has_value()) {
          return;
        }
      }
    }
  }

  static inline
  Token parse_token(TokenState& state) {
    if (isalnum(state.peekChar())) {
      auto res = consumeWhile(state, [](const char c) { return isAlphaNum(c) || isUnderscore(c); });
      assert(res.has_value());
      return res.get_value();
    } else if (oneCharToken(state.peekChar())) {
      maybe<Token> result = tryParse<Token>(mkParseStr("=="), state);
      if (result.has_value()) {
        return result.get_value();
      }

      result = tryParse<Token>(mkParseStr("<="), state);
      if (result.has_value()) {
        return result.get_value();
      }

      result = tryParse<Token>(mkParseStr(">="), state);
      if (result.has_value()) {
        return result.get_value();
      }

      result = tryParse<Token>(mkParseStr("->"), state);
      if (result.has_value()) {
        return result.get_value();
      }

      char res = state.parseChar();
      string r;
      r += res;
      return Token(r, TOKEN_TYPE_SYMBOL);
    } else {
      cout << "Cannot tokenize " << state.remainder() << endl;
      assert(false);
    }

  }

  static inline
  std::vector<Token> tokenize(const std::string& classCode) {
    vector<char> chars;
    for (int i = 0; i < (int) classCode.size(); i++) {
      chars.push_back(classCode[i]);
    }
    TokenState state(chars);
    vector<Token> tokens;

    while (!state.atEnd()) {
      //cout << "Next char = " << state.peekChar() << endl;
      consumeWhitespace(state);

      if (state.atEnd()) {
        break;
      }

      Token t = parse_token(state);
      //cout << "Next char after token = " << state.peekChar() << endl;
      tokens.push_back(t);
    }

    return tokens;
  }

  enum SynthCppTypeKind {
    SYNTH_CPP_TYPE_KIND_STRUCT,
    SYNTH_CPP_TYPE_KIND_VOID,
    SYNTH_CPP_TYPE_KIND_POINTER,
    SYNTH_CPP_TYPE_KIND_LABEL,
    SYNTH_CPP_TYPE_KIND_BITS,
    SYNTH_CPP_TYPE_KIND_ARRAY
  };

  class SynthCppType {
  public:

    virtual SynthCppTypeKind getKind() const { assert(false); }

    virtual ~SynthCppType() {
    }

    virtual void print(std::ostream& out) const = 0;

  };

  static inline
  std::ostream& operator<<(std::ostream& out, const SynthCppType& tp) {
    tp.print(out);
    return out;
  }

  class TemplateType : public SynthCppType {
  public:
    std::vector<Expression*> exprs;

    TemplateType(const std::vector<Expression*>& exprs_) : exprs(exprs_) {}

    ~TemplateType() {
      for (auto e : exprs) {
        delete e;
      }
    }
  };

  class SynthCppStructType : public SynthCppType {
  public:

    Token name;

    SynthCppStructType(const Token name_) : name(name_) {}

    std::string getName() const { return name.getStr(); }

    virtual SynthCppTypeKind getKind() const override { return SYNTH_CPP_TYPE_KIND_STRUCT; }

    static bool classof(const SynthCppType* const tp) { return tp->getKind() == SYNTH_CPP_TYPE_KIND_STRUCT; }

    virtual void print(std::ostream& out) const override {
      out << name;
    }

  };

  class SynthCppArrayType : public SynthCppType {
  public:

    SynthCppType* underlying;
    Expression* len;

    SynthCppArrayType(SynthCppType* underlying_, Expression* len_) : underlying(underlying_), len(len_) {}

    virtual SynthCppTypeKind getKind() const override { return SYNTH_CPP_TYPE_KIND_ARRAY; }

    static bool classof(const SynthCppType* const tp) { return tp->getKind() == SYNTH_CPP_TYPE_KIND_ARRAY; }

    virtual void print(std::ostream& out) const override {
      out << *underlying << "[" << *len << "]";
    }

  };

  class LabelType : public SynthCppType {
  public:
    static bool classof(const SynthCppType* const tp) {
      return tp->getKind() == SYNTH_CPP_TYPE_KIND_LABEL;
    }

    virtual SynthCppTypeKind getKind() const override { return SYNTH_CPP_TYPE_KIND_LABEL; }

  class SynthCppPointerType : public SynthCppType {
  public:

    SynthCppType* elementType;
    SynthCppPointerType(SynthCppType* elem_) : elementType(elem_) {}

    SynthCppType* getElementType() const { return elementType; }

    static bool classof(const SynthCppType* const tp) { return tp->getKind() == SYNTH_CPP_TYPE_KIND_POINTER; }

    virtual SynthCppTypeKind getKind() const override { return SYNTH_CPP_TYPE_KIND_POINTER; }

    virtual void print(std::ostream& out) const override {
      out << *getElementType() << "&";
    }

  };

  class SynthCppDataType : public SynthCppType {
  public:

    static bool classof(const SynthCppType* const tp) { return (tp->getKind() == SYNTH_CPP_TYPE_KIND_BITS) ||
        (tp->getKind() == SYNTH_CPP_TYPE_KIND_VOID); }

    virtual void print(std::ostream& out) const override {
      out << "data_type";
    }

  };

  class SynthCppBitsType : public SynthCppDataType {
  public:

    int width;

    SynthCppBitsType(const int width_) : width(width_) {}

    int getWidth() const { return width; }

    static bool classof(const SynthCppType* const tp) { return (tp->getKind() == SYNTH_CPP_TYPE_KIND_BITS); }

    virtual SynthCppTypeKind getKind() const override { return SYNTH_CPP_TYPE_KIND_BITS; }

    virtual void print(std::ostream& out) const override {
      out << "bits[" << getWidth() << "]";
    }

  };

  class VoidType : public SynthCppDataType {
  public:

    static bool classof(const SynthCppType* const tp) { return tp->getKind() == SYNTH_CPP_TYPE_KIND_VOID; }

    virtual SynthCppTypeKind getKind() const override { return SYNTH_CPP_TYPE_KIND_VOID; }

    virtual void print(std::ostream& out) const override {
      out << "void";
    }

  };

  enum StatementKind {
    STATEMENT_KIND_CLASS_DECL,
    STATEMENT_KIND_IF,
    STATEMENT_KIND_BLOCK,
    STATEMENT_KIND_PIPELINE,
    STATEMENT_KIND_FUNCTION_DECL,
    STATEMENT_KIND_HAZARD_DECL,
    STATEMENT_KIND_ARG_DECL,
    STATEMENT_KIND_STRUCT_DECL,
    STATEMENT_KIND_ASSIGN,
    STATEMENT_KIND_ASSIGN_DECL,
    STATEMENT_KIND_FOR,
    STATEMENT_KIND_EXPRESSION,
    STATEMENT_KIND_RETURN,
    STATEMENT_KIND_DO_WHILE,
  };

  class Statement {
  public:
    bool hasL;
    Token label;

    Statement() : hasL(false) {}

    virtual void print(std::ostream& out) const {
      assert(false);
    }

    Token getLabel() const { return label; }

    bool hasLabel() const { return hasL; }

    void setLabel(const Token l) {
      hasL = true;
      label = l;
    }

    virtual StatementKind getKind() const {
      assert(false);
    }

    virtual ~Statement() {
    }

  };

  static inline
  std::ostream& operator<<(std::ostream& out, const Statement& stmt) {
    stmt.print(out);
    return out;
  }

  class ClassDecl : public Statement {
  public:
    Token name;
    std::vector<Statement*> body;

    ClassDecl(Token name_, std::vector<Statement*>& body_) :
      name(name_), body(body_) {}

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_CLASS_DECL;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_CLASS_DECL;
    }

    virtual void print(std::ostream& out) const {
      out << "class " << name << "{ INSERT_BODY; }";
    }

  };

  class AssignStmt : public Statement {
  public:
    //Token var;
    Expression* lhs;
    Expression* expr;

    //AssignStmt(Token lhs_, Expression* expr_) : lhs(lhs_), expr(expr_) {}
    AssignStmt(Expression* lhs_, Expression* expr_) : lhs(lhs_), expr(expr_) {}

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_ASSIGN;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_ASSIGN;
    }

    virtual void print(std::ostream& out) const {
      out << *lhs << " = " << *expr;
    }

  };

  class AssignDeclStmt : public Statement {
  public:
    //Token var;
    SynthCppType* tp;
    Expression* lhs;
    Expression* expr;

    //AssignStmt(Token lhs_, Expression* expr_) : lhs(lhs_), expr(expr_) {}
    AssignDeclStmt(SynthCppType* tp_, Expression* lhs_, Expression* expr_) : tp(tp_), lhs(lhs_), expr(expr_) {}

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_ASSIGN_DECL;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_ASSIGN_DECL;
    }

    virtual void print(std::ostream& out) const {
      out << *tp << " " << *lhs << " = " << *expr << ";";
    }

  };

  class BlockStmt : public Statement {
  public:
    vector<Statement*> body;

    BlockStmt(const std::vector<Statement*>& body_) : body(body_) {}

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_BLOCK;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_BLOCK;
    }

    virtual void print(std::ostream& out) const {
      out << "{" << endl;
      for (auto stmt : body) {
        out << tab(1) << *stmt << endl;
      }
      out << "}" << endl;
    }

  };

  class IfStmt : public Statement {
  public:
    Expression* test;
    Statement* body;
    Statement* elseClause;

    IfStmt(Expression* const test_, Statement* const body_, Statement* const elseClause_) : test(test_), body(body_), elseClause(elseClause_) {}

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_IF;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_IF;
    }

    virtual void print(std::ostream& out) const {
      out << "IF (INSERT) { INSERT; }";
    }

  };

  class ForStmt : public Statement {
  public:
    Statement* init;
    Expression* exitTest;
    Statement* update;
    std::vector<Statement*> stmts;

    ForStmt(Statement* init_,
            Expression* exitTest_,
            Statement* update_,
            std::vector<Statement*>& stmts_) :
      init(init_), exitTest(exitTest_), update(update_), stmts(stmts_) {}

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_FOR;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_FOR;
    }

    virtual void print(std::ostream& out) const {
      out << "for (INSERT) { INSERT; }";
    }

  };

  class ExpressionStmt : public Statement {
  public:
    Expression* expr;

    ExpressionStmt(Expression* expr_) : expr(expr_) {}

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_EXPRESSION;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_EXPRESSION;
    }

    virtual void print(std::ostream& out) const {
      out << *expr;
    }
  };

  maybe<Statement*> parseStatement(ParseState<Token>& tokens);

  class ArgumentDecl : public Statement {
  public:

    SynthCppType* tp;
    Token name;
    Expression* arraySize;

    ArgumentDecl(SynthCppType* tp_, Token name_) : tp(tp_), name(name_), arraySize(nullptr) {}
    ArgumentDecl(SynthCppType* tp_, Token name_, Expression* arraySize_) :
      tp(tp_), name(name_), arraySize(arraySize_) {}

    virtual void print(std::ostream& out) const {
      out << "ARGDECL " << name;
    }

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_ARG_DECL;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_ARG_DECL;
    }

  };

  class ReturnStmt : public Statement {
  public:
    Expression* returnVal;

    ReturnStmt() : returnVal(nullptr) {}
    ReturnStmt(Expression* expr) : returnVal(expr) {}

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_RETURN;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_RETURN;
    }

    virtual void print(std::ostream& out) const {
      out << "return " << *returnVal;
    }

  };

  class StructDecl : public Statement {
  public:
    Token name;
    std::vector<ArgumentDecl*> fields;

    StructDecl(Token name_,
               std::vector<ArgumentDecl*>& fields_) :
      name(name_),
      fields(fields_) {}

    std::string getName() const { return name.getStr(); }

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_STRUCT_DECL;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_STRUCT_DECL;
    }
  };

  class FunctionDecl : public Statement {
  public:
    SynthCppType* returnType;
    Token name;
    std::vector<ArgumentDecl*> args;
    std::vector<Statement*> body;

    FunctionDecl(SynthCppType* returnType_,
                 Token name_,
                 std::vector<ArgumentDecl*>& args_,
                 std::vector<Statement*>& body_) :
      returnType(returnType_),
      name(name_),
      args(args_),
      body(body_) {}

    std::string getName() const { return name.getStr(); }

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_FUNCTION_DECL;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_FUNCTION_DECL;
    }

  };

  class HazardDecl : public Statement {
  public:
    std::vector<ArgumentDecl*> args;
    std::vector<Statement*> body;

    HazardDecl(std::vector<ArgumentDecl*>& args_,
               std::vector<Statement*>& body_) :
      args(args_),
      body(body_) {}

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_HAZARD_DECL;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_HAZARD_DECL;
    }

  };

  class ParserModule {
    std::vector<Statement*> stmts;

  public:

    ParserModule(const std::vector<Statement*>& stmts_) : stmts(stmts_) {}

    std::vector<Statement*> getStatements() const { return stmts; }

    ~ParserModule() {
      for (auto stmt : stmts) {
        delete stmt;
      }
    }
  };

  static inline
  std::ostream& operator<<(std::ostream& out, const ParserModule& mod) {
    return out;
  }

  static inline
  void parseStmtEnd(ParseState<Token>& tokens) {
    //cout << "Statement end token == " << tokens.peekChar() << endl;
    assert(tokens.parseChar() == Token(";"));
  }

  static inline
  maybe<Token> parseComma(ParseState<Token>& tokens) {
    Token t = tokens.parseChar();
    if (t.getStr() == ",") {
      return t;
    }

    return maybe<Token>();
  }

  static inline
  maybe<Token> parseSemicolon(ParseState<Token>& tokens) {
    Token t = tokens.parseChar();
    if (t.getStr() == ";") {
      return t;
    }

    return maybe<Token>();
  }

  static inline
  maybe<Identifier*> parseId(ParseState<Token>& tokens) {
    Token t = tokens.parseChar();
    if (t.isId()) {
      return new Identifier(t);
    }

    return maybe<Identifier*>();
  }

  maybe<FunctionCall*> parseFunctionCall(ParseState<Token>& tokens);

  maybe<Expression*> parseFieldAccess(ParseState<Token>& tokens);

  static inline
  maybe<Expression*> parseMethodCall(ParseState<Token>& tokens) {
    Token t = tokens.parseChar();
    if (!t.isId()) {
      return maybe<Expression*>();
    }

    Token delim = tokens.parseChar();
    if ((delim != Token("->")) &&
        (delim != Token("."))) {
      return maybe<Expression*>();
    }

    //cout << "-- In method call, parsing function call " << tokens.remainder() << endl;
    maybe<FunctionCall*> fCall = parseFunctionCall(tokens);
    if (fCall.has_value()) {
      return new MethodCall(t, fCall.get_value());
    }

    return maybe<Expression*>();
  }

  Expression* parseExpression(ParseState<Token>& tokens);
  maybe<Expression*> parseExpressionMaybe(ParseState<Token>& tokens);

  static inline
  maybe<Expression*> parsePrimitiveExpressionMaybe(ParseState<Token>& tokens) {
    //cout << "-- Parsing primitive expression " << tokens.remainder() << endl;

    if (tokens.atEnd()) {
      return maybe<Expression*>();
    }

    if (tokens.nextCharIs(Token("("))) {
      tokens.parseChar();

      //cout << "Inside parens " << tokens.remainder() << endl;
      auto inner = parseExpressionMaybe(tokens);
      if (inner.has_value()) {
        if (tokens.nextCharIs(Token(")"))) {
          tokens.parseChar();
          return inner;
        }
      }
      return maybe<Expression*>();
    }

    auto fCall = tryParse<FunctionCall*>(parseFunctionCall, tokens);
    if (fCall.has_value()) {
      return fCall.get_value();
    }

    //cout << "---- Trying to parse method call " << tokens.remainder() << endl;
    auto mCall = tryParse<Expression*>(parseMethodCall, tokens);
    if (mCall.has_value()) {
      return mCall;
    }

    auto faccess = tryParse<Expression*>(parseFieldAccess, tokens);
    if (faccess.has_value()) {
      return faccess;
    }

    // Try parsing a function call
    // If that does not work try to parse an identifier
    // If that does not work try parsing a parenthesis
    auto id = tryParse<Identifier*>(parseId, tokens);
    if (id.has_value()) {
      return id.get_value();
    }

    //cout << "Expressions = " << tokens.remainder() << endl;
    if (!tokens.atEnd() && tokens.peekChar().isNum()) {
      return new IntegerExpr(tokens.parseChar().getStr());
    }

    return maybe<Expression*>();
  }

  static inline
  int precedence(Token op) {
    map<string, int> prec{{"+", 100}, {"==", 99}, {"-", 100}, {"*", 100}, {"<", 99}, {">", 99}, {"<=", 99}, {">=", 99}, {"%", 100}};
    assert(contains_key(op.getStr(), prec));
    return map_find(op.getStr(), prec);
  }

  static inline
  Expression* popOperand(vector<Expression*>& postfixString) {
    assert(postfixString.size() > 0);

    Expression* top = postfixString.back();
    postfixString.pop_back();

    auto idM = extractM<Identifier>(top);
    if (idM.has_value() && isBinop(idM.get_value()->getName())) {
      auto rhs = popOperand(postfixString);
      auto lhs = popOperand(postfixString);
      return new BinopExpr(lhs, idM.get_value()->getName(), rhs);
    }

    return top;
  }

  static inline
  int getDataWidth(const std::string& name) {
    if (hasPrefix(name, "bit_")) {
      return stoi(name.substr(4));
    } else if (hasPrefix(name, "sint_")) {
      return stoi(name.substr(5));
    } else {
      assert(hasPrefix(name, "uint_"));
      return stoi(name.substr(5));
    }
  }

  static inline
  maybe<SynthCppType*> parseBaseType(ParseState<Token>& tokens) {
    if (tokens.peekChar().isId()) {

      //cout << tokens.peekChar() << " is id" << endl;

      Token tpName = tokens.parseChar();

      if (tokens.atEnd() || (tokens.peekChar() != Token("<"))) {
        string name = tpName.getStr();
        SynthCppType* base = nullptr;
        if (hasPrefix(name, "bit_") || hasPrefix(name, "sint_") || hasPrefix(name, "uint_")) {
          int width = getDataWidth(name);
          base = new SynthCppBitsType(width);
        } else {
          base = new SynthCppStructType(tpName);
        }

        assert(base != nullptr);

        if (tokens.nextCharIs(Token("["))) {
          cout << "Got array start at " << tokens.remainder() << endl;
          tokens.parseChar();
          auto arraySize = parseExpressionMaybe(tokens);
          if (!arraySize.has_value() || !tokens.nextCharIs(Token("]"))) {
            cout << "No value for array size?" << endl;
            return maybe<SynthCppType*>();
          } else {
            tokens.parseChar();
            return new SynthCppArrayType(base, arraySize.get_value());
          }
        } else {
          return base; //new SynthCppStructType(tpName);
        }
      }

    }

    if (tokens.peekChar() == Token("void")) {
      tokens.parseChar();
      return new VoidType();
    }

    return maybe<SynthCppType*>();
  }

  static inline
  maybe<SynthCppType*> parseType(ParseState<Token>& tokens) {
    auto tp = parseBaseType(tokens);

    // Check if its a pointer
    if (!tokens.atEnd() && (tokens.peekChar() == Token("&"))) {
      tokens.parseChar();
      return new SynthCppPointerType(tp.get_value());
    }

    return tp;
  }

  static inline
  maybe<ArgumentDecl*> parseArgDeclMaybe(ParseState<Token>& tokens) {
    // cout << "Parsing arg declaration = " << tokens.remainder() << endl;
    // cout << "Remaining tokens = " << tokens.remainderSize() << endl;
    maybe<SynthCppType*> tp = parseType(tokens);

    if (!tp.has_value()) {
      return maybe<ArgumentDecl*>();
    }

    //cout << "After parsing type = " << tokens.remainder() << endl;

    Token argName = tokens.parseChar();

    if (!argName.isId()) {
      return maybe<ArgumentDecl*>();
    }

    //cout << "After parsing expression = " << tokens.remainder() << endl;

    if (tokens.peekChar() == Token("[")) {
      tokens.parseChar();

      auto e = parseExpressionMaybe(tokens);

      //cout << "After parsing expression = " << tokens.remainder() << endl;
      if (!e.has_value()) {
        return maybe<ArgumentDecl*>();
      }

      if (tokens.peekChar() == Token("]")) {
        tokens.parseChar();

        return new ArgumentDecl(tp.get_value(), argName, e.get_value());
      } else {
        return maybe<ArgumentDecl*>();
      }

    }

    return new ArgumentDecl(tp.get_value(), argName);
  }

  static inline
  ArgumentDecl* parseArgDecl(ParseState<Token>& tokens) {
    auto d = parseArgDeclMaybe(tokens);
    if (d.has_value()) {
      return d.get_value();
    }

    assert(false);
  }

  static inline
  maybe<Statement*> parseFuncDecl(ParseState<Token>& tokens) {
    maybe<SynthCppType*> tp = tryParse<SynthCppType*>(parseType, tokens);
    if (!tp.has_value()) {
      return maybe<Statement*>();
    }

    // Create function declaration
    Token funcName = tokens.parseChar();

    if (!funcName.isId()) {
      return maybe<Statement*>();
    }

    if (tokens.peekChar() == Token("(")) {
      assert(tokens.parseChar() == Token("("));

      vector<ArgumentDecl*> classStmts =
        sepBtwn0<ArgumentDecl*, Token>(parseArgDeclMaybe, parseComma, tokens);

      assert(tokens.parseChar() == Token(")"));

      assert(tokens.parseChar() == Token("{"));

      vector<Statement*> funcStmts =
        many<Statement*>(parseStatement, tokens);

      assert(tokens.parseChar() == Token("}"));

      return new FunctionDecl(tp.get_value(), funcName, classStmts, funcStmts);
    }

    return maybe<Statement*>();

  }

  static inline
  maybe<Token> parseLabel(ParseState<Token>& tokens) {
    if (tokens.atEnd()) {
      return maybe<Token>();
    }

    Token t = tokens.parseChar();
    if (!t.isId()) {
      return maybe<Token>();
    }

    if (tokens.atEnd()) {
      return maybe<Token>();
    }

    Token semi = tokens.parseChar();
    if (semi != Token(":")) {
      return maybe<Token>();
    }

    return t;
  }

  static inline
  maybe<Statement*> parseFunctionCallStmt(ParseState<Token>& tokens) {
    maybe<FunctionCall*> p = parseFunctionCall(tokens);
    if (!p.has_value()) {
      return maybe<Statement*>();
    }

    Token semi = tokens.parseChar();
    if (semi != Token(";")) {
      return maybe<Statement*>();
    }

    return new ExpressionStmt(static_cast<FunctionCall*>(p.get_value()));
  }

  static inline
  maybe<Statement*> parseAssignStmt(ParseState<Token>& tokens) {
    //cout << "Starting parse assign \" " << tokens.remainder() << "\"" << endl;

    if (tokens.atEnd()) {
      return maybe<Statement*>();
    }

    maybe<Expression*> lhs = parseExpressionMaybe(tokens);
    if (!lhs.has_value()) {
      return maybe<Statement*>();
    }
    // Token id = tokens.peekChar();
    // if (!id.isId()) {
    //   return maybe<Statement*>();
    // }
    // tokens.parseChar();

    //cout << "After name remainder is \"" << tokens.remainder() << "\"" << endl;

    if (!tokens.nextCharIs(Token("="))) {
      return maybe<Statement*>();
    }
    tokens.parseChar();

    //cout << "Remaining after eq is " << tokens.remainder() << endl;
    auto r = parseExpressionMaybe(tokens);
    if (!r.has_value()) {
      cout << "No expr" << endl;
      return maybe<Statement*>();
    }

    //return new AssignStmt(id, r.get_value());
    return new AssignStmt(lhs.get_value(), r.get_value());

  }

  class PipelineBlock : public Statement {
  public:
    Expression* ii;
    vector<Statement*> body;

    PipelineBlock(Expression* e_,
                  std::vector<Statement*>& stmts_) : ii(e_), body(stmts_) {}

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_PIPELINE;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_PIPELINE;
    }

  };

  class DoWhileLoop : public Statement {
  public:

    Expression* test;
    vector<Statement*> body;

    DoWhileLoop(Expression* e_,
                std::vector<Statement*>& stmts_) : test(e_), body(stmts_) {}

    static bool classof(const Statement* const stmt) {
      return stmt->getKind() == STATEMENT_KIND_DO_WHILE;
    }

    virtual StatementKind getKind() const {
      return STATEMENT_KIND_DO_WHILE;
    }

  };

  static inline
  maybe<Statement*> parseDoWhileLoop(ParseState<Token>& tokens) {
    //cout << "Parsing do = " << tokens.remainder() << endl;

    if (tokens.nextCharIs(Token("do"))) {
      tokens.parseChar();

      if (!tokens.nextCharIs(Token("{"))) {
        return maybe<Statement*>();
      }
      tokens.parseChar();

      vector<Statement*> stmts =
        many<Statement*>(parseStatement, tokens);

      if (!tokens.nextCharIs(Token("}"))) {
        return maybe<Statement*>();
      }
      tokens.parseChar();

      if (!tokens.nextCharIs(Token("while"))) {
        return maybe<Statement*>();
      }
      tokens.parseChar();

      maybe<Expression*> expr = parseExpression(tokens);
      if (expr.has_value()) {

        if (!tokens.nextCharIs(Token(";"))) {
          return maybe<Statement*>();
        }
        tokens.parseChar();

        return new DoWhileLoop(expr.get_value(), stmts);
      }

      return maybe<Statement*>();

    }

    return maybe<Statement*>();
  }

  static inline
  ParserModule parse(const std::vector<Token>& tokens) {
    ParseState<Token> pm(tokens);
    vector<Statement*> stmts =
      many<Statement*>(parseStatement, pm);

    if (!pm.atEnd()) {
      cout << "Error: Not at end of module after parsing, remainder = " << endl;
      cout << pm.remainder() << endl;
    }
    assert(pm.atEnd());

    ParserModule m(stmts);

    return m;
  }

  // llvm::Type* llvmPointerFor(SynthCppType* const tp) {
  //   //return llvmTypeFor(tp)->getPointerTo();
  //   // if (SynthCppStructType::classof(tp)) {
  //   //   return structType(static_cast<SynthCppStructType* const>(tp)->getName())->getPointerTo();
  //   // } else {
  //   //   cout << "Getting llvm pointer for " << *tp << endl;
  //   //   assert(false);
  //   // }
  // }

  static inline
  bool isPrimitiveStruct(SynthCppStructType*  const st) {
    string name = st->getName();
    return hasPrefix(name, "bit_") || hasPrefix(name, "sint_") || hasPrefix(name, "uint_");
  }

  static inline
  int getWidth(SynthCppStructType* tp) {
    assert(isPrimitiveStruct(tp));
    string name = tp->getName();
    if (hasPrefix(name, "bit_")) {
      return stoi(name.substr(4));
    } else if (hasPrefix(name, "sint_")) {
      return stoi(name.substr(5));
    } else {
      assert(hasPrefix(name, "uint_"));
      return stoi(name.substr(5));
    }
  }

  class SynthCppFunction {
  public:
    Token nameToken;
    llvm::Function* func;
    std::map<std::string, SynthCppType*> symtab;
    SynthCppType* retType;
    ExecutionConstraints* constraints;
    set<PipelineSpec> pipelines;

    bool hasReturnType() {
      return retType != nullptr;
    }

    bool hasReturnValue() {
      return (retType != nullptr) && (!VoidType::classof(retType));
    }

    SynthCppType* returnType() {
      assert(retType != nullptr);
      return retType;
    }

    llvm::Function* llvmFunction() { return func; }

    std::string getName() const {
      return nameToken.getStr();
    }
  };

  static inline
  std::ostream& operator<<(std::ostream& out, const SynthCppFunction& f) {
    out << " " << f.getName() << "(ARGS) {" << endl;
    out << "}" << endl;

    return out;
  }

  class SynthCppClass {
  public:

    Token name;
    std::map<std::string, SynthCppType*> memberVars;
    std::map<std::string, SynthCppFunction*> methods;
    vector<HazardSpec> hazards;
    StructType* llvmTp;
    std::vector<std::string> fieldOrder;

    StructType* llvmStructType() { return llvmTp; }

    std::string getName() const { return name.getStr(); }

    SynthCppType* getType() { return new SynthCppStructType(name); }

    SynthCppFunction* getMethod(const Token name) {
      cout << "Getting method for " << name << endl;
      return map_find(name.getStr(), methods);
    }

    int getPortWidth(Token vName) {
      for (auto v : memberVars) {
        cout << "Checking member var " << v.first << endl;
        if (v.first == vName.getStr()) {
          cout << "Found member variable " << v.first << " with type " << *(v.second) << endl;
          assert(SynthCppStructType::classof(v.second));
          string name = sc<SynthCppStructType>(v.second)->getName();
          assert(hasPrefix(name, "input_") || (hasPrefix(name, "output_")));
          if (hasPrefix(name, "input_")) {
            return stoi(name.substr(string("input_").size()));
          } else {
            return stoi(name.substr(string("output_").size()));
          }
        }
      }

      cout << "Error: No member named " << vName << " in " << name << endl;
      assert(false);
    }
  };

  static inline
  std::ostream& operator<<(std::ostream& out, const SynthCppClass& mod) {
    out << "class " << mod.getName() << "{" << endl;
    for (auto v : mod.methods) {
      out << tab(1) << v.first << " : " << *(v.second) << ";" << endl;
    }
    out << "};" << endl;

    return out;
  }

  static inline
  bool isInputPort(SynthCppType* const tp) {
    auto ps = extractM<SynthCppStructType>(tp);
    if (!ps.has_value()) {
      return false;
    }

    string name = ps.get_value()->getName();
    if (hasPrefix(name, "input_")) {
      return true;
    }

    return false;
  }

  static inline
  bool isOutputPort(SynthCppType* const tp) {
    auto ps = extractM<SynthCppStructType>(tp);
    if (!ps.has_value()) {
      return false;
    }

    string name = ps.get_value()->getName();
    if (hasPrefix(name, "output_")) {
      return true;
    }

    return false;
  }

  static inline
  bool isPortType(SynthCppType* const tp) {
    return isOutputPort(tp) || isInputPort(tp);
  }

  static inline
  int portWidth(SynthCppType* const tp) {
    assert(isPortType(tp));
    auto portStruct = extract<SynthCppStructType>(tp);

    string name = portStruct->getName();
    if (isInputPort(tp)) {
      string prefix = "input_";
      string width = name.substr(prefix.size());
      return stoi(width);
    } else {
      assert(isOutputPort(tp));

      string prefix = "output_";
      string width = name.substr(prefix.size());
      return stoi(width);
    }
  }

  static inline
  SynthCppStructType* extractBaseStructType(SynthCppType* tp) {
    if (SynthCppStructType::classof(tp)) {
      return sc<SynthCppStructType>(tp);
    }

    assert(SynthCppPointerType::classof(tp));
    auto pTp = sc<SynthCppPointerType>(tp);
    auto underlying = pTp->getElementType();

    assert(SynthCppStructType::classof(underlying));

    return sc<SynthCppStructType>(underlying);
  }

  class SymbolTable {
  public:
    std::vector<std::map<std::string, SynthCppType*>* > tableStack;

    void pushTable(std::map<std::string, SynthCppType*>* table) {
      tableStack.push_back(table);
    }

    void popTable() {
      tableStack.pop_back();
    }

    void setType(const std::string& name, SynthCppType* tp) {
      tableStack.back()->insert({name, tp});
    }

    void print(std::ostream& out) {
      for (auto ts : tableStack) {
        out << tab(1) << "Table stack" << endl;
        for (auto str : *ts) {
          out << tab(2) << "symbol = " << str.first << endl;
        }
      }
    }

    SynthCppType* getType(const std::string& str) {
      int depth = ((int) tableStack.size()) - 1;

      for (int i = depth; i >= 0; i--) {
        if (contains_key(str, *(tableStack.at(i)))) {
          return map_find(str, *(tableStack.at(i)));
        }
      }

      cout << "Error: Cannot find type for " << str << endl;
      print(cout);
      assert(false);
    }

  };

  class CodeGenState {
  public:

    SynthCppClass* activeClass;
    SynthCppFunction* activeFunction;
    BasicBlock* activeBlock;
    int globalNum;
    std::map<std::string, SynthCppType*> globalVars;

    SymbolTable symtab;

    bool inPipeline;
    PipelineSpec activePipeline;

    CodeGenState() : activeBlock(nullptr), inPipeline(false) {
      symtab.pushTable(&globalVars);
    }

    bool inTopLevel() const { return activeBlock == nullptr; }

    BasicBlock& getActiveBlock() const { return *activeBlock; }
    void setActiveBlock(BasicBlock* blk) {
      activeBlock = blk;
    }

    IRBuilder<> builder() { return IRBuilder<>(activeBlock); }

    void pushClassContext(SynthCppClass* ac) {
      activeClass = ac;
    }

    void popClassContext() {
      activeClass = nullptr;
    }

    SynthCppClass* getActiveClass() {
      return activeClass;
    }
  };

  pair<string, int> extractDefault(Statement* stmt);

  class SynthCppModule {

    HardwareConstraints hcs;
    InterfaceFunctions interfaces;

  public:

    llvm::LLVMContext context;
    unique_ptr<llvm::Module> mod;
    std::vector<SynthCppClass*> classes;
    std::vector<SynthCppFunction*> functions;
    std::set<BasicBlock*> blocksToPipeline;

    CodeGenState cgs;
    SynthCppFunction* activeFunction;
    int globalNum;
    std::map<Token, Instruction*> labelsToInstructions;
    std::map<Token, BasicBlock*> labelsToBlockStarts;
    std::map<Token, BasicBlock*> labelsToBlockEnds;
    std::map<llvm::Type*, StructDecl*> structDefs;
    std::map<llvm::GlobalVariable*, int> globalVarValues;

    std::string uniqueNumString() {
      auto s = std::to_string(globalNum);
      globalNum++;
      return s;
    }

    void genLLVMCopyTo(llvm::Value* receiver,
                       llvm::Value* source) {

      cgs.builder().CreateCall(mkFunc({receiver->getType(), source->getType()}, voidType(), "copy_" + typeString(receiver->getType())), {receiver, source});
    }

    llvm::Value* bvCastTo(Value* value, Type* const destTp) {
      int vWidth = getTypeBitWidth(value->getType());
      int rWidth = getTypeBitWidth(destTp);

      if (vWidth == rWidth) {
        return value;
      }

      if (vWidth < rWidth) {
        return cgs.builder().CreateSExt(value, destTp);
      }

      assert(vWidth > rWidth);

      return cgs.builder().CreateTrunc(value, destTp);
    }

    int constantLength(Expression* expr);

    llvm::Type* llvmTypeFor(SynthCppType* const tp) {
      if (SynthCppPointerType::classof(tp)) {
        return llvmTypeFor(sc<SynthCppPointerType>(tp)->getElementType())->getPointerTo();
        //return llvmPointerFor(static_cast<SynthCppPointerType* const>(tp)->getElementType());
      } else if (VoidType::classof(tp)) {
        return voidType();
      } else if (SynthCppBitsType::classof(tp)) {
        return intType(extract<SynthCppBitsType>(tp)->getWidth());
      } else if (SynthCppArrayType::classof(tp)) {
        auto* arrTp = sc<SynthCppArrayType>(tp);

        int len = constantLength(arrTp->len);
        return ArrayType::get(llvmTypeFor(arrTp->underlying), len);
      } else {
        assert(SynthCppStructType::classof(tp));
        auto st = static_cast<SynthCppStructType* const>(tp);

        for (auto c : getClasses()) {
          if (c->getName() == st->getName()) {
            return c->llvmStructType();
          }
        }

        cout << "Could not get llvm type for SynthCppStructType = " << *st << endl;

        Type* argTp = structType(st->getName());
        return argTp;

      }
    }

    // argNum could be 1 if the function being synthesized is actually a
    // method
    void setArgumentSymbols(IRBuilder<>& b,
                            map<string, SynthCppType*>& symtab,
                            vector<ArgumentDecl*>& args,
                            Function* f,
                            int argOffset) {

      cout << "f = " << endl;
      cout << valueString(f) << endl;
      cout << "argOffset = " << argOffset << endl;
      //int argNum = argOffset;
      int argNum = 0;
      for (auto argDecl : args) {
        cout << "\targ = " << argDecl->name << endl;

        // Create local copy if argument is passed by value
        // For arguments originally passed by pointer:
        // - Add the underlying type to the value map (setValue)
        // For arguments originally passed by value
        // - Create internal copy
        symtab[argDecl->name.getStr()] = argDecl->tp;
        cout << "Setting " << argDecl->name.getStr() << " in symbol table" << endl;
        if (!SynthCppPointerType::classof(argDecl->tp)) {
          auto val = cgs.builder().CreateAlloca(llvmTypeFor(argDecl->tp));
          cgs.builder().CreateStore(getArg(f, argNum + argOffset), val);
          setValue(argDecl->name, val);
        } else {
          setValue(argDecl->name, getArg(f, argNum + argOffset));
        }

        argNum++;
      }

    }

    llvm::Value* structFieldPtr(llvm::Value* baseVal, const std::string& fieldName);

    llvm::Value* genFieldAccess(FieldAccess* const e);
    void addHazard(HazardDecl* hazard, ModuleSpec& cSpec);
    void addMethodDecl(FunctionDecl* decl, ModuleSpec& cSpec);

    void addAssignDecl(AssignDeclStmt* const stmt);
    void addStructDecl(StructDecl* const stmt);

    vector<Type*> functionInputs(FunctionDecl* fd) {
      vector<Type*> inputTypes;
      for (auto argDecl : fd->args) {
        cout << "\targ = " << argDecl->name << endl;
        Type* argTp = llvmTypeFor(argDecl->tp);

        if (!SynthCppPointerType::classof(argDecl->tp)) {
          assert(SynthCppDataType::classof(argDecl->tp));
          inputTypes.push_back(argTp);
        } else {
          inputTypes.push_back(argTp);
        }
      }

      return inputTypes;
    }

    SynthCppModule(ParserModule& parseRes) {
      activeFunction = nullptr;

      globalNum = 0;
      hcs = standardConstraints();

      mod = llvm::make_unique<Module>("synth_cpp", context);
      setGlobalLLVMContext(&context);
      setGlobalLLVMModule(mod.get());

      for (auto stmt : parseRes.getStatements()) {

        if (ClassDecl::classof(stmt)) {
          ClassDecl* decl = static_cast<ClassDecl* const>(stmt);
          ModuleSpec cSpec;

          SynthCppClass* c = new SynthCppClass();
          c->name = decl->name;
          cgs.symtab.pushTable(&(c->memberVars));
          cgs.pushClassContext(c);

          vector<Type*> fields;

          for (auto subStmt: decl->body) {
            if (ArgumentDecl::classof(subStmt)) {
              auto decl = sc<ArgumentDecl>(subStmt);
              c->memberVars[decl->name.getStr()] = decl->tp;
              c->fieldOrder.push_back(decl->name.getStr());

            } else if (FunctionDecl::classof(subStmt)) {
              auto methodFuncDecl = sc<FunctionDecl>(subStmt);
              addMethodDecl(methodFuncDecl, cSpec);
            } else if (HazardDecl::classof(subStmt)) {
              cout << "Found hazard" << endl;
              auto haz = sc<HazardDecl>(subStmt);
              addHazard(haz, cSpec);
            } else {
              assert(subStmt != nullptr);
              cout << "Unsupported statement in class, stmt kind: "  << subStmt->getKind() << endl;
              cout << *stmt << endl;
              assert(false);
            }
          }
          classes.push_back(c);

          // TODO: Wrap this up in to a function
          // Add interface class module spec to hardware constraints
          bool notIClass = true;
          for (auto vTp : c->memberVars) {
            auto tp = vTp.second;
            if (isPortType(tp)) {
              notIClass = false;
              if (isOutputPort(tp)) {
                addOutputPort(cSpec.ports, portWidth(tp), vTp.first);
              } else {
                assert(isInputPort(tp));

                // Set non-default values to be insensitive
                string portName = vTp.first;
                if (!contains_key(portName, cSpec.defaultValues)) {
                  cSpec.insensitivePorts.insert(portName);
                }
                addInputPort(cSpec.ports, portWidth(tp), vTp.first);
              }
            }
          }

          // If this is not an interface class then do not make it an opaque struct
          if (notIClass) {
            for (auto vTp : c->memberVars) {
              fields.push_back(llvmTypeFor(vTp.second));
            }

            c->llvmTp = StructType::create(fields, c->getName(), true);

            cSpec = registerModSpec(getTypeBitWidth(c->llvmTp));
          } else {

            cSpec.name = c->getName();
            cSpec.hasClock = true;
            cSpec.hasRst = true;

            c->llvmTp = structType(c->getName());
          }

          cout << "class has name " << c->getName() << endl;
          hcs.typeSpecs[c->getName()] = [cSpec](StructType* tp) { return cSpec; };
          hcs.hasTypeSpec(c->getName());

          cgs.symtab.popTable();
          cgs.popClassContext();

        } else if (FunctionDecl::classof(stmt)) {
          auto fd = static_cast<FunctionDecl*>(stmt);
          vector<Type*> inputTypes = functionInputs(fd);
          auto sf = new SynthCppFunction();
          sf->nameToken = fd->name;
          //activeSymtab = &(sf->symtab);
          cgs.symtab.pushTable(&(sf->symtab));

          // Add return type as first argument
          llvm::Function* f = mkFunc(inputTypes, llvmTypeFor(fd->returnType), sf->getName());

          // Add this function to the interface functions
          interfaces.addFunction(f);
          sf->constraints = &interfaces.getConstraints(f);

          auto bb = addBB("entry_block", f);
          cgs.setActiveBlock(bb);

          //IRBuilder<> b(bb);

          //bool hasReturn = sf->hasReturnValue();
          auto bd = cgs.builder();
          setArgumentSymbols(bd, sf->symtab, fd->args, f, 0);

          // Now need to iterate over all statements in the body creating labels
          // that map to starts and ends of statements
          // and also adding code for each statement to the resulting function, f.
          sf->func = f;
          activeFunction = sf;
          cout << "# of statements = " << fd->body.size() << endl;
          for (auto stmt : fd->body) {
            cout << "Statement" << endl;
            genLLVM(stmt);
          }

          BasicBlock* activeBlock = &(cgs.getActiveBlock());
          //&(activeFunction->llvmFunction()->getEntryBlock());
          if (activeBlock->getTerminator() == nullptr) {
            cout << "Basic block " << valueString(activeBlock) << "has no terminator" << endl;
            cgs.builder().CreateRet(nullptr);
          }

          // Set all calls to be sequential by default
          //sequentialCalls(f, interfaces.getConstraints(f));

          functions.push_back(sf);
          activeFunction = nullptr;

          sanityCheck(f);

          cgs.symtab.popTable();
        } else if (StructDecl::classof(stmt)) {
          addStructDecl(sc<StructDecl>(stmt));
        } else if (AssignDeclStmt::classof(stmt)) {
          addAssignDecl(sc<AssignDeclStmt>(stmt));
        } else {
          cout << "Error: Unsupported statement kind " << stmt->getKind() << " in module" << endl;
          assert(false);
        }
      }

    }

    map<std::string, llvm::Value*> valueMap;

    bool hasValue(Token t) const {
      return contains_key(t.getStr(), valueMap);
    }

    llvm::Value* getValueFor(Token t) {
      cout << "Getting value for " << t.getStr() << endl;
      assert(contains_key(t.getStr(), valueMap));
      return map_find(t.getStr(), valueMap);
    }

    void setValue(Token t, llvm::Value* v) {
      valueMap[t.getStr()] = v;
      assert(contains_key(t.getStr(), valueMap));
    }

    EventTime eventFromLabel(string arg0Name, const string& labelName) {
      if (contains_key(Token(arg0Name), labelsToInstructions)) {
        Instruction* instrLabel = map_find(Token(arg0Name), labelsToInstructions);
        cout << "Instruction labeled = " << valueString(instrLabel) << endl;

        if (labelName == "start") {
          return {ExecutionAction(instrLabel), false, 0};
        } else {
          assert(labelName == "end");
          return {ExecutionAction(instrLabel), true, 0};
        }
      } else {
        assert(contains_key(Token(arg0Name), labelsToBlockStarts));
        assert(contains_key(Token(arg0Name), labelsToBlockEnds));

        if (labelName == "start") {
          BasicBlock* blkLabel = map_find(Token(arg0Name), labelsToBlockStarts);
          return {ExecutionAction(blkLabel), false, 0};
        } else {
          assert(labelName == "end");

          BasicBlock* blkLabel = map_find(Token(arg0Name), labelsToBlockEnds);
          return {ExecutionAction(blkLabel), true, 0};
        }
      }
    }

    EventTime parseEventTime(Expression* const e) {
      auto mBop = extractM<BinopExpr>(e);
      if (mBop.has_value()) {
        auto bop = mBop.get_value();
        Token op = bop->op;
        EventTime tm = parseEventTime(bop->lhs);
        cout << "Integer rhs type = " << bop->rhs->getKind() << endl;
        IntegerExpr* val = extract<IntegerExpr>(bop->rhs);

        return tm + val->getInt();
      } else {
        cout << "Call type = " << e->getKind() << endl;
        auto labelCall = extract<FunctionCall>(e);
        string name = labelCall->funcName.getStr();
        cout << "name = " << name << endl;
        assert((name == "start") || (name == "end"));

        Expression* arg0 = labelCall->args.at(0);
        cout << "arg0Kind = " << arg0->getKind() << endl;
        auto arg0Id = extract<Identifier>(arg0);
        string arg0Name = arg0Id->getName();

        cout << "got arg0 name = " << arg0Name << endl;

        return eventFromLabel(arg0Name, name);
      }
    }

    ExecutionConstraint* parseConstraint(Expression* const e) {
      BinopExpr* bop = extract<BinopExpr>(e);
      Token op = bop->op;
      cout << "Parsing binop = " << op << endl;
      assert(isComparator(op));

      Expression* lhs = bop->lhs;
      EventTime lhsTime = parseEventTime(lhs);
      Expression* rhs = bop->rhs;
      EventTime rhsTime = parseEventTime(rhs);

      if (op.getStr() == "==") {
        return lhsTime == rhsTime;
      } else if (op.getStr() == "<") {
        return lhsTime < rhsTime;
      } else {
        assert(false);
      }
    }

    bool isData(Type* const tp) {
      if (IntegerType::classof(tp)) {
        return true;
      }

      if (StructType::classof(tp)) {
        StructType* stp = dyn_cast<StructType>(tp);
        return stp->elements().size() > 0;
      }

      return false;
    }

    llvm::Value* genLLVM(Expression* const e);

    SynthCppType* getTypeForId(const Token id) {
      if (id.getStr() == "this") {
        assert(cgs.getActiveClass() != nullptr);
        return new SynthCppPointerType(cgs.getActiveClass()->getType());
      }
      return cgs.symtab.getType(id.getStr());
    }

    SynthCppClass* getClass(const std::string id) {
      for (auto c : classes) {
        if (c->getName() == id) {
          return c;
        }
      }

      cout << "Error: Cannot find class with name " << id << endl;
      assert(false);
    }

    SynthCppClass* getClassByName(const Token id) {
      for (auto c : classes) {
        if (c->getName() == id.getStr()) {
          return c;
        }
      }

      cout << "Error: Cannot find class with name " << id << endl;
      assert(false);
    }

    SynthCppClass* getClass(StructType* const classType) {
      for (auto c : classes) {
        cout << "Candidate class = " << c->getName() << endl;
        if (c->getName() == classType->getName()) {
          return c;
        }
      }

      // If token has the type of the class we are currently
      // parsing
      if (cgs.getActiveClass()->getName() == classType->getName()) {
        return cgs.getActiveClass();
      }

      assert(false);
    }

    SynthCppClass* getClass(const Token id) {
      string idName = id.getStr();
      cout << "Getting class for " << idName << endl;
      SynthCppType* tp = getTypeForId(id);

      cout << "Got type for " << idName << endl;

      SynthCppStructType* classType = extractBaseStructType(tp);

      assert(StructType::classof(llvmTypeFor(classType)));
      return getClass(dyn_cast<StructType>(llvmTypeFor(classType)));

      // cout << "Got class type = " << *classType << endl;

      // for (auto c : classes) {
      //   cout << "Candidate class = " << c->name << endl;
      //   if (c->name == classType->name) {
      //     return c;
      //   }
      // }

      // // If token has the type of the class we are currently
      // // parsing
      // if (cgs.getActiveClass()->name == classType->name) {
      //   return cgs.getActiveClass();
      // }

      // assert(false);
    }

    BasicBlock*
    addBB(const std::string& name, llvm::Function* const f);

    void pushPipeline(const int II);
    void popPipeline();

    void genLLVM(BlockStmt* const blk);
    void genLLVM(ForStmt* const stmt) {
      Statement* init = stmt->init;
      Expression* exitTest = stmt->exitTest;
      Statement* update = stmt->update;
      auto stmts = stmt->stmts;

      auto bd = cgs.builder();

      // Need entry block to check
      auto loopTestBlock = addBB( "for_blk_init_test_" + uniqueNumString(), activeFunction->llvmFunction());
      auto loopBodyBlock = addBB( "for_body_" + uniqueNumString(), activeFunction->llvmFunction());
      auto nextBlock = addBB( "for_body_" + uniqueNumString(), activeFunction->llvmFunction());

      genLLVM(init);
      bd.CreateBr(loopTestBlock);

      cgs.setActiveBlock(loopTestBlock);
      auto testCond = genLLVM(exitTest);
      cgs.builder().CreateCondBr(testCond, loopBodyBlock, nextBlock);

      cgs.setActiveBlock(loopBodyBlock);
      for (auto stmt : stmts) {
        genLLVM(stmt);
      }
      genLLVM(update);
      cgs.builder().CreateBr(loopTestBlock);

      // Need major work block

      // Need exit block
      cgs.setActiveBlock(nextBlock);
    }

    void genLLVM(ArgumentDecl* const decl) {
      auto bd = cgs.builder();

      string valName = decl->name.getStr();
      Type* tp = llvmTypeFor(decl->tp);
      // TODO: add to name map?
      cgs.symtab.setType(valName, decl->tp);
      auto n = bd.CreateAlloca(tp, nullptr, valName);
      // Add to constraints?
      int width = getTypeBitWidth(tp);
      setMemSpec(n, getHardwareConstraints(), {0, 0, 1, 1, width, 1, false, {{{"width", std::to_string(width)}}, "register"}});
      setValue(decl->name, n);
    }

    void genLLVM(AssignStmt* const stmt) {

      // Note: Should really be an expression
      //Token t = stmt->var;
      Expression* lhs = stmt->lhs;
      Expression* newVal = stmt->expr;

      Value* v = genLLVM(newVal);

      if (stmt->hasLabel()) {
        Token l = stmt->label;
        cout << "Label on assign = " << stmt->label << endl;

        // TODO: Remove this hack and replace with something more general
        //BasicBlock* blk = &(activeFunction->llvmFunction()->getEntryBlock());
        BasicBlock* blk = &(cgs.getActiveBlock());
        cout << "Block for label = " << valueString(blk) << endl;
        Instruction* last = &(blk->back());

        cout << "Last instruction" << valueString(last) << endl;
        labelsToInstructions[stmt->label] = last;
      }

      Value* tV = pointerToLocation(lhs); //getValueFor(t);

      genSetCode(tV, v);
    }

    llvm::Value* pointerToLocation(Expression* const e);

    void genLLVM(ReturnStmt* const stmt) {
      auto bd = cgs.builder();

      Expression* retVal = stmt->returnVal;
      Value* v = nullptr;
      if (retVal != nullptr) {
        v = genLLVM(retVal);
      }
      bd.CreateRet(v);

      if (stmt->hasLabel()) {
        Token l = stmt->label;
        cout << "Label on assign = " << stmt->label << endl;
        BasicBlock* blk = &(cgs.getActiveBlock());
        cout << "Block for label = " << valueString(blk) << endl;
        Instruction* last = &(blk->back());

        cout << "Last instruction" << valueString(last) << endl;
        labelsToInstructions[stmt->label] = last;
      }

    }

    void genSetCode(Value* receiver, Value* value) {
      auto bd = cgs.builder();
      // Check types?

      Type* rType = receiver->getType();
      Type* vType = value->getType();

      cout << "receiver = " << valueString(receiver) << endl;
      cout << "value    = " << valueString(value) << endl;
      cout << "rType = " << typeString(rType) << endl;
      cout << "vType = " << typeString(vType) << endl;

      if (rType == vType->getPointerTo()) {
        bd.CreateStore(value, receiver);
      } else {
        // Cast values of different lengths
        assert(PointerType::classof(rType));
        auto underlying = dyn_cast<PointerType>(rType)->getElementType();
        if (isData(underlying) &&
            isData(vType)) {

          auto valueCast = bvCastTo(value, underlying);
          assert(rType == valueCast->getType()->getPointerTo());
          bd.CreateStore(valueCast, receiver);
        } else {
          cout << "Error: Incompatible types in assignment, receiver "
               << valueString(receiver) << ", value " << valueString(value) << endl;
        }
      }
    }

    void genLLVM(DoWhileLoop* stmt) {
      cout << "Do while loop" << endl;
      Expression* test = stmt->test;
      vector<Statement*> stmts = stmt->body;

      auto lastBlock = cgs.builder();
      auto loopBlock =
        addBB("while_loop_" + uniqueNumString(), activeFunction->llvmFunction());
      lastBlock.CreateBr(loopBlock);

      cgs.setActiveBlock(loopBlock);

      // Create exit block
      auto nextBlock =
        addBB("after_while_" + uniqueNumString(), activeFunction->llvmFunction());

      for (auto stmt : stmts) {
        genLLVM(stmt);
      }

      // End of loop
      auto exitCond = genLLVM(test);
      cgs.builder().CreateCondBr(exitCond, loopBlock, nextBlock);

      cgs.setActiveBlock(nextBlock);

      if (stmt->hasLabel()) {
        Token label = stmt->getLabel();
        labelsToBlockStarts[label] = loopBlock;
        labelsToBlockEnds[label] = loopBlock;
      }
    }

    void genLLVM(PipelineBlock* const stmt);
    void genLLVM(IfStmt* const stmt);

    void genLLVM(Statement* const stmt) {
      assert(stmt != nullptr);

      auto bd = cgs.builder();

      if (ExpressionStmt::classof(stmt)) {
        auto es = static_cast<ExpressionStmt* const>(stmt);
        Expression* e = es->expr;
        genLLVM(e);

        if (stmt->hasLabel()) {
          Token l = stmt->label;
          cout << "Label = " << stmt->label << endl;
          //BasicBlock* blk = &(activeFunction->llvmFunction()->getEntryBlock());
          BasicBlock* blk = &(cgs.getActiveBlock());
          cout << "Block for label = " << valueString(blk) << endl;
          Instruction* last = &(blk->back());

          cout << "Last instruction" << valueString(last) << endl;
          labelsToInstructions[stmt->label] = last;
        }

      } else if (ArgumentDecl::classof(stmt)) {
        auto decl = static_cast<ArgumentDecl* const>(stmt);
        genLLVM(decl);
      } else if (ForStmt::classof(stmt)) {
        auto loop = static_cast<ForStmt* const>(stmt);
        genLLVM(loop);
      } else if (AssignStmt::classof(stmt)) {
        auto asg = static_cast<AssignStmt* const>(stmt);
        genLLVM(asg);

      } else if (ReturnStmt::classof(stmt)) {
        genLLVM(sc<ReturnStmt>(stmt));
      } else if (DoWhileLoop::classof(stmt)) {
        genLLVM(sc<DoWhileLoop>(stmt));
      } else if (PipelineBlock::classof(stmt)) {
        genLLVM(sc<PipelineBlock>(stmt));
      } else if (IfStmt::classof(stmt)) {
        genLLVM(sc<IfStmt>(stmt));
      } else if (BlockStmt::classof(stmt)) {
        genLLVM(sc<BlockStmt>(stmt));
      } else {
        // Add support for variable declarations, assignments, and for loops
        cout << "No support for code generation for statement" << endl;
        assert(false);
      }

      if (stmt->hasLabel()) {
        cout << "Label = " << stmt->label << endl;
        // How do I tag statement starts and ends with a label?
        // Crude: Track the active block in CodeGenState and just attach
        // the start and end to the last instruction?
      }

    }

    SynthCppFunction* builtinStub(std::string name, std::vector<llvm::Type*>& args, SynthCppType* retType) {
      SynthCppFunction* stub = new SynthCppFunction();
      stub->nameToken = Token(name);
      stub->retType = retType;
      stub->func = mkFunc(args, llvmTypeFor(retType), name);

      return stub;
    }

    SynthCppFunction* getFunction(const std::string& name) {
      cout << "Getting function for " << name << endl;

      // TODO: Generalize to work for any width input
      if (name == "set_port") {
        vector<Type*> inputs =
          {intType(32)->getPointerTo(), intType(32)->getPointerTo()};
        SynthCppFunction* stb = builtinStub("set_port", inputs, new VoidType());
        return stb;
      }

      if (name == "add_constraint") {
        vector<Type*> inputs =
          {intType(32)->getPointerTo()};
        SynthCppFunction* stb = builtinStub("add_constraint", inputs, new VoidType());

        return stb;
      }

      if (name == "start") {
        vector<Type*> inputs =
          {intType(32)->getPointerTo()};
        SynthCppFunction* stb = builtinStub("start", inputs, new SynthCppStructType(Token("bit_32")));
        return stb;
      }

      if (name == "end") {
        vector<Type*> inputs =
          {intType(32)->getPointerTo()};
        SynthCppFunction* stb = builtinStub("end", inputs, new SynthCppStructType(Token("bit_32")));
        return stb;
      }

      if (name == "read_port") {
        vector<Type*> inputs =
          {intType(32)->getPointerTo()};
        SynthCppFunction* stb = builtinStub("read_port", inputs, new SynthCppStructType(Token("bit_32")));
        return stb;
      }

      for (auto f : functions) {
        cout << "f name = " << f->getName() << endl;
        cout << "name   = " << name << endl;
        if (f->getName() == name) {
          return f;
        }
      }
      assert(false);
    }

    HardwareConstraints& getHardwareConstraints() {
      return hcs;
    }

    InterfaceFunctions& getInterfaceFunctions() {
      return interfaces;
    }

    std::set<BasicBlock*>& getBlocksToPipeline() {
      return blocksToPipeline;
    }

    std::vector<SynthCppClass*> getClasses() const {
      return classes;
    }

    std::vector<SynthCppFunction*> getFunctions() const {
      return functions;
    }

  };

  MicroArchitecture
  synthesizeVerilog(SynthCppModule& scppMod, const std::string& funcName);

  MicroArchitecture
  synthesizeVerilog(SynthCppModule& scppMod,
                    const std::string& funcName,
                    VerilogDebugInfo& info);

  STG buildSTGFor(SynthCppModule& scppMod, SynthCppFunction* const f);
  STG buildSTGFor(SynthCppModule& mod, const std::string& funcName);
  STG scheduleBanzai(SynthCppModule& mod, const std::string& funcName);

  void optimizeStores(llvm::Function* f);
  void optimizeModuleLLVM(llvm::Module& mod);

  void clearExecutionConstraints(llvm::Function* const f,
                                 ExecutionConstraints& exec);

}

    virtual void print(std::ostream& out) const override {
      out << "label_type";


#pragma once

#include <llvm/IR/Constants.h>

#include "microarchitecture.h"

namespace ahaHLS {

  class AlwaysBlock {
  public:
    std::vector<std::string> triggers;
    std::string body;
  };

  static inline
  void print(std::ostream& out, int level, const Memory& b) {
    out << tab(level) << "reg [" + std::to_string(b.width - 1) << " : 0] "
        << b.name << "[" << std::to_string(b.depth - 1) << ": 0];" << std::endl;
  }

  static inline
  void print(std::ostream& out, int level, const AlwaysBlock& b) {

    if (b.triggers.size() == 1) {
      out << tab(level) << "always @(posedge " << b.triggers[0] << ") begin" << std::endl;
      out << tab(level + 1) << b.body << std::endl;
      out << tab(level) << "end" << std::endl;
    } else {
      out << tab(level) << "always @(*) begin" << std::endl;
      out << tab(level + 1) << b.body << std::endl;
      out << tab(level) << "end" << std::endl;

    }
  }

  class AlwaysDelayBlock {
  public:
    int delay;
    std::string body;
  };

  static inline
  void print(std::ostream& out, int level, const AlwaysDelayBlock& b) {
    out << tab(level) << "always #" << b.delay << " begin" << std::endl;
    out << tab(level + 1) << b.body << std::endl;
    out << tab(level) << "end" << std::endl;
  }

  class TestBenchSpec {
  public:
    std::map<std::string, std::vector<int> > memoryInit;
    std::map<std::string, std::vector<int> > memoryExpected;
    int runCycles;
    int maxCycles;
    std::string name;

    std::map<int, std::vector<std::string> > actionsOnCycles;
    std::map<int, std::vector<std::string> > actionsInCycles;

    std::vector<std::string> actionsOnConditions;

    std::set<std::string> settableWires;

    bool useModSpecs;

    TestBenchSpec() {
      maxCycles = 400;
      settableWires.insert("clk");
      settableWires.insert("rst");
      useModSpecs = false;
    }

    void actionOnCondition(const std::string& condition,
                           const std::string& action) {
      actionsOnConditions.push_back("if " + parens(condition) + " begin " + action + " end ");
    }

    void setArgPort(llvm::Argument* arg, std::string port, int cycleNo, std::string value);
    void setArgPort(llvm::Argument* arg, std::string port, int cycleNo, const int value);
    void settablePort(llvm::Argument* arg, std::string port);
  };

  void emitVerilogTestBench(const TestBenchSpec& tb,
                            MicroArchitecture& arch,
                            const std::map<std::string, int>& layout);

  class VerilogComponents {
  public:
    std::vector<Wire> wiresToWatch;
    std::vector<Memory> memories;
    std::vector<Wire> debugWires;
    std::vector<std::pair<std::string, std::string> > debugAssigns;
    std::vector<AlwaysBlock> blocks;
    std::vector<AlwaysDelayBlock> delayBlocks;
    std::vector<std::string> initStmts;
    std::vector<ModuleInstance> instances;
  };

  typedef VerilogComponents VerilogDebugInfo;

  void emitModule(std::ostream& out,
                  const std::string& name,
                  std::vector<Port>& ports,
                  VerilogComponents& comps);

  static inline void
  addAlwaysBlock(const std::vector<std::string>& triggers,
                 const std::string& body,
                 VerilogDebugInfo& info) {
    info.blocks.push_back({triggers, body});
  }

  static inline void
  addWirePrintoutIf(const std::string& condition,
                    const std::string& wireName,
                    VerilogDebugInfo& info) {
    addAlwaysBlock({"clk"}, "if (" + condition + ") begin $display(\"" + wireName + " == %d\", " + wireName + "); end", info);
  }

  static inline void
  addWirePrintout(const std::string& wireName,
                  VerilogDebugInfo& info) {
    addAlwaysBlock({"clk"}, "$display(\"" + wireName + " == %d\", " + wireName + ");", info);
  }

  static inline void
  addGlobalStateWirePrintout(const std::string& wireName,
                             VerilogDebugInfo& info) {
    addAlwaysBlock({"clk"}, "$display(\"global_state == %d, " + wireName + " == %d\", global_state, " + wireName + ");", info);
  }

  std::string assertString(const std::string& condition);

  void addAssert(const std::string& condition,
                 VerilogDebugInfo& info);

  void emitVerilog(const std::string& name,
                   STG& graph,
                   HardwareConstraints& hcs,
                   VerilogDebugInfo& info);

  void emitVerilog(const std::string& name,
                   STG& graph,
                   HardwareConstraints& hcs);

  void emitVerilog(MicroArchitecture& arch,
                   const VerilogDebugInfo& debugInfo);

  void emitVerilog(const STG& stg,
                   std::map<std::string, int>& memoryMap);

  void emitVerilog(const STG& stg,
                   std::map<llvm::Value*, int>& memoryMap);

  void emitVerilog(const STG& stg,
                   std::map<llvm::Value*, int>& memoryMap,
                   const VerilogDebugInfo& debugInfo);

  void emitVerilog(const STG& stg,
                   std::map<llvm::Value*, int>& memoryMap,
                   const VerilogDebugInfo& debugInfo);

  void emitVerilog(const STG& stg,
                   std::map<std::string, int>& memoryMap,
                   const VerilogDebugInfo& debufInfo);

  void synthesizeVerilog(llvm::Function* f,
                         HardwareConstraints& hdc,
                         std::map<std::string, int>& memoryMap);

  void synthesizeVerilog(llvm::Function* f,
                         HardwareConstraints& hdc,
                         std::map<llvm::Value*, int>& memoryMap);

  void noPhiOutputsXWhenUsed(MicroArchitecture& arch,
                             VerilogDebugInfo& debugInfo);

  void noAddsTakeXInputs(MicroArchitecture& arch,
                         VerilogDebugInfo& debugInfo);

  void noMulsTakeXInputs(MicroArchitecture& arch,
                         VerilogDebugInfo& debugInfo);

  void noLoadedValuesXWhenUsed(MicroArchitecture& arch,
                               VerilogDebugInfo& debugInfo);

  void noLoadAddressesXWhenUsed(MicroArchitecture& arch,
                                VerilogDebugInfo& debugInfo);

  void noStoredValuesXWhenUsed(MicroArchitecture& arch,
                               VerilogDebugInfo& debugInfo);

  void addNoXChecks(MicroArchitecture& arch,
                    VerilogDebugInfo& debugInfo);

  void emitVerilog(const std::string& fn,
                   MicroArchitecture& arch,
                   const VerilogDebugInfo& debugInfo);

  MicroArchitecture synthesizeVerilog(llvm::Function* f,
                                      InterfaceFunctions& interfaces,
                                      HardwareConstraints& hcs);

  void addControlSanityChecks(MicroArchitecture& arch,
                              VerilogDebugInfo& info);

  void addSetStencilChecks(MicroArchitecture& arch,
                           VerilogDebugInfo& info);

  void printInstrAtState(llvm::Instruction* instr,
                         StateId st,
                         MicroArchitecture& arch,
                         VerilogDebugInfo& debugInfo);

  void printAllInstructions(MicroArchitecture& arch,
                            VerilogDebugInfo& debugInfo);

  void addDisplay(const std::string& cond,
                  const std::string& condStr,
                  const std::vector<std::string>& vars,
                  VerilogDebugInfo& debugInfo);

  void checkRAM(TestBenchSpec& tb,
                int checkMemCycle,
                const std::string name,
                std::map<string, vector<int> >& memoryExpected,
                std::map<string, int>& testLayout);

  void setRAM(TestBenchSpec& tb,
              int startSetMemCycle,
              const std::string name,
              std::map<string, vector<int> >& memoryInit,
              std::map<string, int>& testLayout);

  void checkSignal(TestBenchSpec& tb,
                   const std::string& signalName,
                   const std::map<int, int>& valuesAtCycles);

  void checkRAMContents(TestBenchSpec& tb,
                        int checkMemCycle,
                        const std::string ramName,
                        const std::vector<int>& memoryExpected);

  // Set
  void setRAMContents(TestBenchSpec& tb,
                      int setMemCycle,
                      const std::string ramName,
                      const vector<int>& memoryValues);

  void setRVChannel(TestBenchSpec& tb,
                    const std::string fifoName,
                    const vector<pair<int, int> >& writeTimesAndValues);

  void checkRVChannel(TestBenchSpec& tb,
                      const std::string& fifoName,
                      const std::vector<std::pair<int, std::string> >& readTimesAndValues);

  void setRVFifo(TestBenchSpec& tb,
                 const std::string fifoName,
                 const vector<pair<int, int> >& writeTimesAndValues);

  void setRVFifo(TestBenchSpec& tb,
                 const std::string fifoName,
                 const vector<pair<int, std::string> >& writeTimesAndValues);

  void
  checkRVFifo(TestBenchSpec& tb,
              const std::string& fifoName,
              const std::vector<std::pair<int, std::string> >& readTimesAndValues);

  void noPortXWrites(const std::string& portName,
                     MicroArchitecture& arch,
                     VerilogDebugInfo& info);

}

void printActiveBlocks(MicroArchitecture& arch, VerilogDebugInfo& info);


#pragma once

#include <cassert>

namespace ahaHLS {

  template<typename T, int depth>
  class Fifo {
    T buffer[depth];
    int raddr;
    int waddr;
    bool empty;

  public:

    Fifo() {
      raddr = 0;
      waddr = 0;
      empty = 1;
    }

    T read() {
      assert(!empty);

      raddr = (raddr + 1) % depth;

      if (raddr == waddr) {
        empty = 1;
      }
      return value;
    }

    void write(T value) {

      buffer[waddr] = value;
      waddr = (waddr + 1) % depth;
      empty = 0;

    }
  };

}


void custom_if(bit_32& val, bit_1& c, bit_32& a, bit_32& b) {

  if (c) {
    v_tmp = a;
  } else {
    v_tmp = b;
  }

  val = v_tmp;
}


class reg_32 {
  input_32 in;
  input_1 en;

  output_32 out;

  bit_32 read() {
    return read_port(out);
  }

  void write(bit_32 data) {
  set_en: set_port(en, 1);
  set_in: set_port(in, data);

    add_constraint(start(set_en) == start(set_in));
  }
};

void store_to_reg(reg_32& reg) {
  reg.write(15);
}

void instantiate_reg(bit_32& i, bit_32& o) {


#define a 0
#define b 5

class axi_ram {
  int addr;
  int data;

public:
  void write(const int addr, const int data);
  int read(const int addr);
};

void axi_multi_transfer(axi_ram* ram) {

  for (int i = 0; i < 5; i++) {
    int val = ram->read(a + i) + 34;
    ram->write(b + i, val);
  }


#include "gen_classes.h"

void vhls_target(
hls_stream_AxiPackedStencil_uint16_t_1_1__ &arg_0,
hls_stream_AxiPackedStencil_uint16_t_1_1__ &arg_1)
{
#pragma HLS DATAFLOW
#pragma HLS INLINE region
#pragma HLS INTERFACE s_axilite port=return bundle=config
#pragma HLS INTERFACE axis register port=arg_0
#pragma HLS INTERFACE axis register port=arg_1

 // alias the arguments
 hls_stream_AxiPackedStencil_uint16_t_1_1__ &_hw_input_stencil_update_stream = arg_0;
 hls_stream_AxiPackedStencil_uint16_t_1_1__ &_hw_output_stencil_stream = arg_1;

 ram_int32_t_9 _kernela0;
 // produce kernel
 for (int _kernel_s0_y = 0; _kernel_s0_y < 0 + 3; _kernel_s0_y++)
 {
  int32_t _290 = _kernel_s0_y * 3;
  for (int _kernel_s0_x = 0; _kernel_s0_x < 0 + 3; _kernel_s0_x++)
  {
#pragma HLS PIPELINE II=1
   int32_t _291 = _kernel_s0_x + _290;
   _kernela0.ram_write(_291, 0);
  } // for _kernel_s0_x
 } // for _kernel_s0_y
 _kernela0.ram_write(0, 1);
 _kernela0.ram_write(3, 2);
 _kernela0.ram_write(6, 1);
 _kernela0.ram_write(1, 2);
 _kernela0.ram_write(4, 4);
 _kernela0.ram_write(7, 2);
 _kernela0.ram_write(2, 1);
 _kernela0.ram_write(5, 2);
 _kernela0.ram_write(8, 1);
 // consume kernel
// realize stream hw_input.stencil.stream
 hls_stream_PackedStencil_uint16_t_3_3__ _hw_input_stencil_stream;
#pragma HLS STREAM variable=_hw_input_stencil_stream depth=1
#pragma HLS RESOURCE variable=_hw_input_stencil_stream core=FIFO_SRL

// Now outputting a linebuffer
 linebuffer_hls_stream_AxiPackedStencil_uint16_t_1_1___to_hls_stream_PackedStencil_uint16_t_3_3___bnds_8_8 _lb__hw_input_stencil_update_stream_to__hw_input_stencil_stream;
	for (int i = 0; i < 64; i++) {
		_lb__hw_input_stencil_update_stream_to__hw_input_stencil_stream.lb_write(_hw_input_stencil_update_stream.read());
	}
	for (int i = 0; i < 46; i++) {
		_hw_input_stencil_stream.write(_lb__hw_input_stencil_update_stream_to__hw_input_stencil_stream.lb_read());
	}
// Steady state for loop to write and read (void)0;
 // dispatch_stream(_hw_input_stencil_stream, 2, 3, 1, 8, 3, 1, 8, 1, "conv1", 0, 0, 8, 0, 8);
 hls_stream_PackedStencil_uint16_t_3_3__ _hw_input_stencil_stream_to_conv1;
#pragma HLS STREAM variable=_hw_input_stencil_stream_to_conv1 depth=1
#pragma HLS RESOURCE variable=_hw_input_stencil_stream_to_conv1 core=FIFO_SRL

 for (int _dim_1 = 0; _dim_1 <= 5; _dim_1 += 1)
 for (int _dim_0 = 0; _dim_0 <= 5; _dim_0 += 1)
 {
#pragma HLS PIPELINE
  PackedStencil_uint16_t_3_3_ _tmp_stencil = _hw_input_stencil_stream.read();
  if (1)
  {
   _hw_input_stencil_stream_to_conv1.write(_tmp_stencil);
  }
 }
// -- End of dispatch
 (void)0;
// realize stream conv1.stencil_update.stream
 hls_stream_PackedStencil_int32_t_1_1__ _conv1_stencil_update_stream;
#pragma HLS STREAM variable=_conv1_stencil_update_stream depth=1
#pragma HLS RESOURCE variable=_conv1_stencil_update_stream core=FIFO_SRL

 // produce conv1.stencil_update.stream
 for (int _conv1_y___scan_dim_1 = 0; _conv1_y___scan_dim_1 < 0 + 6; _conv1_y___scan_dim_1++)
 {
  for (int _conv1_x___scan_dim_0 = 0; _conv1_x___scan_dim_0 < 0 + 6; _conv1_x___scan_dim_0++)
  {
#pragma HLS PIPELINE II=1
// realize stencil or stencil update hw_input.stencil
   Stencil_uint16_t_3_3_ _hw_input_stencil;
#pragma HLS ARRAY_PARTITION variable=_hw_input_stencil.value complete dim=0

_hw_input_stencil.set(0, 0, 0, 0);
   // produce hw_input.stencil
// reading stream hw_input.stencil.stream
   _hw_input_stencil.copy(_hw_input_stencil_stream_to_conv1.read());
   (void)0;
   // consume hw_input.stencil
// realize stencil or stencil update conv1.stencil
   Stencil_int32_t_1_1_ _conv1_stencil;
#pragma HLS ARRAY_PARTITION variable=_conv1_stencil.value complete dim=0

_conv1_stencil.set(0, 0, 0, 0);
   // produce conv1.stencil
   // Providing stencil or stencil update conv1.stencil
_conv1_stencil.set(0, 0, 0);
   int32_t _292 = _conv1_stencil.get(0, 0);
   int32_t _293 = _kernela0.ram_read(0);
   uint16_t _294 = _hw_input_stencil.get(0, 0);
   int32_t _295 = (int32_t)(_294);
   int32_t _296 = _293 * _295;
   int32_t _297 = _292 + _296;
   // Providing stencil or stencil update conv1.stencil
_conv1_stencil.set(_297, 0, 0);
   int32_t _298 = _conv1_stencil.get(0, 0);
   int32_t _299 = _kernela0.ram_read(1);
   uint16_t _300 = _hw_input_stencil.get(1, 0);
   int32_t _301 = (int32_t)(_300);
   int32_t _302 = _299 * _301;
   int32_t _303 = _298 + _302;
   // Providing stencil or stencil update conv1.stencil
_conv1_stencil.set(_303, 0, 0);
   int32_t _304 = _conv1_stencil.get(0, 0);
   int32_t _305 = _kernela0.ram_read(2);
   uint16_t _306 = _hw_input_stencil.get(2, 0);
   int32_t _307 = (int32_t)(_306);
   int32_t _308 = _305 * _307;
   int32_t _309 = _304 + _308;
   // Providing stencil or stencil update conv1.stencil
_conv1_stencil.set(_309, 0, 0);
   int32_t _310 = _conv1_stencil.get(0, 0);
   int32_t _311 = _kernela0.ram_read(3);
   uint16_t _312 = _hw_input_stencil.get(0, 1);
   int32_t _313 = (int32_t)(_312);
   int32_t _314 = _311 * _313;
   int32_t _315 = _310 + _314;
   // Providing stencil or stencil update conv1.stencil
_conv1_stencil.set(_315, 0, 0);
   int32_t _316 = _conv1_stencil.get(0, 0);
   int32_t _317 = _kernela0.ram_read(4);
   uint16_t _318 = _hw_input_stencil.get(1, 1);
   int32_t _319 = (int32_t)(_318);
   int32_t _320 = _317 * _319;
   int32_t _321 = _316 + _320;
   // Providing stencil or stencil update conv1.stencil
_conv1_stencil.set(_321, 0, 0);
   int32_t _322 = _conv1_stencil.get(0, 0);
   int32_t _323 = _kernela0.ram_read(5);
   uint16_t _324 = _hw_input_stencil.get(2, 1);
   int32_t _325 = (int32_t)(_324);
   int32_t _326 = _323 * _325;
   int32_t _327 = _322 + _326;
   // Providing stencil or stencil update conv1.stencil
_conv1_stencil.set(_327, 0, 0);
   int32_t _328 = _conv1_stencil.get(0, 0);
   int32_t _329 = _kernela0.ram_read(6);
   uint16_t _330 = _hw_input_stencil.get(0, 2);
   int32_t _331 = (int32_t)(_330);
   int32_t _332 = _329 * _331;
   int32_t _333 = _328 + _332;
   // Providing stencil or stencil update conv1.stencil
_conv1_stencil.set(_333, 0, 0);
   int32_t _334 = _conv1_stencil.get(0, 0);
   int32_t _335 = _kernela0.ram_read(7);
   uint16_t _336 = _hw_input_stencil.get(1, 2);
   int32_t _337 = (int32_t)(_336);
   int32_t _338 = _335 * _337;
   int32_t _339 = _334 + _338;
   // Providing stencil or stencil update conv1.stencil
_conv1_stencil.set(_339, 0, 0);
   int32_t _340 = _conv1_stencil.get(0, 0);
   int32_t _341 = _kernela0.ram_read(8);
   uint16_t _342 = _hw_input_stencil.get(2, 2);
   int32_t _343 = (int32_t)(_342);
   int32_t _344 = _341 * _343;
   int32_t _345 = _340 + _344;
   // Providing stencil or stencil update conv1.stencil
_conv1_stencil.set(_345, 0, 0);
   // produce conv1.stencil
   // Normal write stream case
_conv1_stencil_update_stream.write(_conv1_stencil);
   (void)0;
  } // for _conv1_x___scan_dim_0
 } // for _conv1_y___scan_dim_1
 // consume conv1.stencil_update.stream
// realize stream conv1.stencil.stream
 hls_stream_PackedStencil_int32_t_3_3__ _conv1_stencil_stream;
#pragma HLS STREAM variable=_conv1_stencil_stream depth=1
#pragma HLS RESOURCE variable=_conv1_stencil_stream core=FIFO_SRL

// Now outputting a linebuffer
 linebuffer_hls_stream_PackedStencil_int32_t_1_1___to_hls_stream_PackedStencil_int32_t_3_3___bnds_6_6 _lb__conv1_stencil_update_stream_to__conv1_stencil_stream;
	for (int i = 0; i < 36; i++) {
		_lb__conv1_stencil_update_stream_to__conv1_stencil_stream.lb_write(_conv1_stencil_update_stream.read());
	}
	for (int i = 0; i < 22; i++) {
		_conv1_stencil_stream.write(_lb__conv1_stencil_update_stream_to__conv1_stencil_stream.lb_read());
	}
// Steady state for loop to write and read (void)0;
 // dispatch_stream(_conv1_stencil_stream, 2, 3, 1, 6, 3, 1, 6, 1, "conv2", 0, 0, 6, 0, 6);
 hls_stream_PackedStencil_int32_t_3_3__ _conv1_stencil_stream_to_conv2;
#pragma HLS STREAM variable=_conv1_stencil_stream_to_conv2 depth=1
#pragma HLS RESOURCE variable=_conv1_stencil_stream_to_conv2 core=FIFO_SRL

 for (int _dim_1 = 0; _dim_1 <= 3; _dim_1 += 1)
 for (int _dim_0 = 0; _dim_0 <= 3; _dim_0 += 1)
 {
#pragma HLS PIPELINE
  PackedStencil_int32_t_3_3_ _tmp_stencil = _conv1_stencil_stream.read();
  if (1)
  {
   _conv1_stencil_stream_to_conv2.write(_tmp_stencil);
  }
 }
// -- End of dispatch
 (void)0;
// realize stream conv2.stencil.stream
 hls_stream_PackedStencil_int32_t_1_1__ _conv2_stencil_stream;
#pragma HLS STREAM variable=_conv2_stencil_stream depth=1
#pragma HLS RESOURCE variable=_conv2_stencil_stream core=FIFO_SRL

 // produce conv2.stencil.stream
 for (int _conv2_y___scan_dim_1 = 0; _conv2_y___scan_dim_1 < 0 + 4; _conv2_y___scan_dim_1++)
 {
  for (int _conv2_x___scan_dim_0 = 0; _conv2_x___scan_dim_0 < 0 + 4; _conv2_x___scan_dim_0++)
  {
#pragma HLS PIPELINE II=1
// realize stencil or stencil update conv1.stencil
   Stencil_int32_t_3_3_ _conv1_stencil;
#pragma HLS ARRAY_PARTITION variable=_conv1_stencil.value complete dim=0

_conv1_stencil.set(0, 0, 0, 0);
   // produce conv1.stencil
// reading stream conv1.stencil.stream
   _conv1_stencil.copy(_conv1_stencil_stream_to_conv2.read());
   (void)0;
   // consume conv1.stencil
// realize stencil or stencil update conv2.stencil
   Stencil_int32_t_1_1_ _conv2_stencil;
#pragma HLS ARRAY_PARTITION variable=_conv2_stencil.value complete dim=0

_conv2_stencil.set(0, 0, 0, 0);
   // produce conv2.stencil
   // Providing stencil or stencil update conv2.stencil
_conv2_stencil.set(0, 0, 0);
   int32_t _346 = _conv2_stencil.get(0, 0);
   int32_t _347 = _conv1_stencil.get(0, 0);
   int32_t _348 = _kernela0.ram_read(0);
   int32_t _349 = _347 * _348;
   int32_t _350 = _346 + _349;
   // Providing stencil or stencil update conv2.stencil
_conv2_stencil.set(_350, 0, 0);
   int32_t _351 = _conv2_stencil.get(0, 0);
   int32_t _352 = _conv1_stencil.get(1, 0);
   int32_t _353 = _kernela0.ram_read(1);
   int32_t _354 = _352 * _353;
   int32_t _355 = _351 + _354;
   // Providing stencil or stencil update conv2.stencil
_conv2_stencil.set(_355, 0, 0);
   int32_t _356 = _conv2_stencil.get(0, 0);
   int32_t _357 = _conv1_stencil.get(2, 0);
   int32_t _358 = _kernela0.ram_read(2);
   int32_t _359 = _357 * _358;
   int32_t _360 = _356 + _359;
   // Providing stencil or stencil update conv2.stencil
_conv2_stencil.set(_360, 0, 0);

   int32_t _362 = _conv1_stencil.get(0, 1);
   int32_t _363 = _kernela0.ram_read(3);
   int32_t _364 = _362 * _363;
   int32_t _365 = _361 + _364;
   // Providing stencil or stencil update conv2.stencil
_conv2_stencil.set(_365, 0, 0);
   int32_t _366 = _conv2_stencil.get(0, 0);
   int32_t _367 = _conv1_stencil.get(1, 1);
   int32_t _368 = _kernela0.ram_read(4);
   int32_t _369 = _367 * _368;
   int32_t _370 = _366 + _369;
   // Providing stencil or stencil update conv2.stencil
_conv2_stencil.set(_370, 0, 0);
   int32_t _371 = _conv2_stencil.get(0, 0);
   int32_t _372 = _conv1_stencil.get(2, 1);
   int32_t _373 = _kernela0.ram_read(5);
   int32_t _374 = _372 * _373;
   int32_t _375 = _371 + _374;
   // Providing stencil or stencil update conv2.stencil
_conv2_stencil.set(_375, 0, 0);
   int32_t _376 = _conv2_stencil.get(0, 0);
   int32_t _377 = _conv1_stencil.get(0, 2);
   int32_t _378 = _kernela0.ram_read(6);
   int32_t _379 = _377 * _378;
   int32_t _380 = _376 + _379;
   // Providing stencil or stencil update conv2.stencil
_conv2_stencil.set(_380, 0, 0);
   int32_t _381 = _conv2_stencil.get(0, 0);
   int32_t _382 = _conv1_stencil.get(1, 2);
   int32_t _383 = _kernela0.ram_read(7);
   int32_t _384 = _382 * _383;
   int32_t _385 = _381 + _384;
   // Providing stencil or stencil update conv2.stencil
_conv2_stencil.set(_385, 0, 0);
   int32_t _386 = _conv2_stencil.get(0, 0);
   int32_t _387 = _conv1_stencil.get(2, 2);
   int32_t _388 = _kernela0.ram_read(8);
   int32_t _389 = _387 * _388;
   int32_t _390 = _386 + _389;
   // Providing stencil or stencil update conv2.stencil
_conv2_stencil.set(_390, 0, 0);
   // produce conv2.stencil
   // Normal write stream case
_conv2_stencil_stream.write(_conv2_stencil);
   (void)0;
  } // for _conv2_x___scan_dim_0
 } // for _conv2_y___scan_dim_1
 // consume conv2.stencil.stream
 // dispatch_stream(_conv2_stencil_stream, 2, 1, 1, 4, 1, 1, 4, 1, "hw_output", 0, 0, 4, 0, 4);
 hls_stream_PackedStencil_int32_t_1_1__ _conv2_stencil_stream_to_hw_output;
#pragma HLS STREAM variable=_conv2_stencil_stream_to_hw_output depth=1
#pragma HLS RESOURCE variable=_conv2_stencil_stream_to_hw_output core=FIFO_SRL

 for (int _dim_1 = 0; _dim_1 <= 3; _dim_1 += 1)
 for (int _dim_0 = 0; _dim_0 <= 3; _dim_0 += 1)
 {
#pragma HLS PIPELINE
  PackedStencil_int32_t_1_1_ _tmp_stencil = _conv2_stencil_stream.read();
  if (1)
  {
   _conv2_stencil_stream_to_hw_output.write(_tmp_stencil);
  }
 }
// -- End of dispatch
 (void)0;
 // produce hw_output.stencil.stream
 for (int _hw_output_y___scan_dim_1 = 0; _hw_output_y___scan_dim_1 < 0 + 4; _hw_output_y___scan_dim_1++)
 {
  for (int _hw_output_x___scan_dim_0 = 0; _hw_output_x___scan_dim_0 < 0 + 4; _hw_output_x___scan_dim_0++)
  {
#pragma HLS PIPELINE II=1
// realize stencil or stencil update conv2.stencil
   Stencil_int32_t_1_1_ _conv2_stencil;
#pragma HLS ARRAY_PARTITION variable=_conv2_stencil.value complete dim=0

_conv2_stencil.set(0, 0, 0, 0);
   // produce conv2.stencil
// reading stream conv2.stencil.stream
   _conv2_stencil.copy(_conv2_stencil_stream_to_hw_output.read());
   (void)0;
   // consume conv2.stencil
// realize stencil or stencil update hw_output.stencil
   Stencil_uint16_t_1_1_ _hw_output_stencil;
#pragma HLS ARRAY_PARTITION variable=_hw_output_stencil.value complete dim=0

_hw_output_stencil.set(0, 0, 0, 0);
   // produce hw_output.stencil
   int32_t _391 = _conv2_stencil.get(0, 0);
   uint16_t _392 = (uint16_t)(_391);
   // Providing stencil or stencil update hw_output.stencil
_hw_output_stencil.set(_392, 0, 0);
   // consume hw_output.stencil
   // Dag output kernel
AxiPackedStencil_uint16_t_1_1_ _hw_output_stencil_packed = _hw_output_stencil;
   if (_hw_output_x___scan_dim_0 == 3 && _hw_output_y___scan_dim_1 == 3) {
    _hw_output_stencil_packed.set_last(1);
   } else {
    _hw_output_stencil_packed.set_last(0);
   }
   _hw_output_stencil_stream.write(_hw_output_stencil_packed);
   (void)0;
  } // for _hw_output_x___scan_dim_0
 } // for _hw_output_y___scan_dim_1
} // kernel hls_target_vhls_target

int32_t _361 = _conv2_stencil.get(0, 0);


#include "ram.h"

class complex {
public:
  int real;
  int imag;
};

// complex operator+(const complex& a, const complex& b) {
//   return {a.real + b.real, a.imag + b.imag};
// }

void complex_num(RAM<complex, 16>* ram) {

  complex c = {a.real + b.real, a.imag + b.imag};
  ram->write(3, c);
}

  complex a = ram->read(0);
  complex b = ram->read(1);


#include "hls_macros.h"
#include "ram.h"

#define a 0
#define b 10

void loop_add_4_6_iters(RAM_2<int, 16>* ram) {

    AHAHLS_PIPELINE(1);
    ram->write_0(a + i, ram->read_0(b + i) + 4);
  }
}


#ifndef ACCEL_INPUT_CONV_H
#define ACCEL_INPUT_CONV_H

#include "Typedefs.h"
#include "Accel.h"

void run_input_conv_layer(

    const float* w_data,
    const float* k_data,
    const float* h_data,
    const float* data_i,
    Word* data_o,


#ifdef _WIN32
#include <windows.h>
#else
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#endif

#if defined(__cplusplus)
extern "C"
{
#endif

#ifdef _WIN32
int entropy_fun(unsigned char buf[], unsigned int len)
{
    HCRYPTPROV provider;
    unsigned __int64 pentium_tsc[1];
    unsigned int i;

    if (CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
    {
        result = CryptGenRandom(provider, len, buf);
        CryptReleaseContext(provider, 0);
        if (result)
            return len;
    }

    QueryPerformanceCounter((LARGE_INTEGER *)pentium_tsc);

    for(i = 0; i < 8 && i < len; ++i)
        buf[i] = ((unsigned char*)pentium_tsc)[i];

    return i;
}
#else
int entropy_fun(unsigned char buf[], unsigned int len)
{
    int frand = open("/dev/random", O_RDONLY);
    int rlen = 0;
    if (frand != -1)
    {
        rlen = read(frand, buf, len);
        close(frand);
    }
    return rlen;
}
#endif

#if defined(__cplusplus)
}
#endif


/*
 ---------------------------------------------------------------------------
 Copyright (c) 2002, Dr Brian Gladman, Worcester, UK.   All rights reserved.

 LICENSE TERMS

 The free distribution and use of this software in both source and binary
 form is allowed (with or without changes) provided that:

   1. distributions of this source code include the above copyright
      notice, this list of conditions and the following disclaimer;

   2. distributions in binary form include the above copyright
      notice, this list of conditions and the following disclaimer
      in the documentation and/or other associated materials;

   3. the copyright holder's name is not used to endorse products
      built using this software without specific written permission.

 ALTERNATIVELY, provided that this notice is retained in full, this product
 may be distributed under the terms of the GNU General Public License (GPL),
 in which case the provisions of the GPL apply INSTEAD OF those given above.

 DISCLAIMER

 This software is provided 'as is' with no explicit or implied warranties
 in respect of its properties, including, but not limited to, correctness
 and/or fitness for purpose.
 ---------------------------------------------------------------------------
 Issue Date: 26/08/2003

 This is an implementation of RFC2898, which specifies key derivation from
 a password and a salt value.
*/

#include <memory.h>
#include "hmac.h"

#if defined(__cplusplus)
extern "C"
{
#endif

void derive_key(const unsigned char pwd[],  /* the PASSWORD     */
               unsigned int pwd_len,        /* and its length   */
               const unsigned char salt[],  /* the SALT and its */
               unsigned int salt_len,       /* length           */
               unsigned int iter,   /* the number of iterations */
               unsigned char key[], /* space for the output key */
               unsigned int key_len)/* and its required length  */
{
    unsigned int    i, j, k, n_blk;
    unsigned char uu[HASH_OUTPUT_SIZE], ux[HASH_OUTPUT_SIZE];

    /* set HMAC context (c1) for password               */
    hmac_sha_begin(c1);
    hmac_sha_key(pwd, pwd_len, c1);

    /* set HMAC context (c2) for password and salt      */
    memcpy(c2, c1, sizeof(hmac_ctx));
    hmac_sha_data(salt, salt_len, c2);

    /* find the number of SHA blocks in the key         */
    n_blk = 1 + (key_len - 1) / HASH_OUTPUT_SIZE;

    for(i = 0; i < n_blk; ++i) /* for each block in key */
    {
        /* ux[] holds the running xor value             */
        memset(ux, 0, HASH_OUTPUT_SIZE);

        /* set HMAC context (c3) for password and salt  */
        memcpy(c3, c2, sizeof(hmac_ctx));

        /* enter additional data for 1st block into uu  */
        uu[0] = (unsigned char)((i + 1) >> 24);
        uu[1] = (unsigned char)((i + 1) >> 16);
        uu[2] = (unsigned char)((i + 1) >> 8);
        uu[3] = (unsigned char)(i + 1);

        /* this is the key mixing iteration         */
        for(j = 0, k = 4; j < iter; ++j)
        {
            /* add previous round data to HMAC      */
            hmac_sha_data(uu, k, c3);

            /* obtain HMAC for uu[]                 */
            hmac_sha_end(uu, HASH_OUTPUT_SIZE, c3);

            /* xor into the running xor block       */
            for(k = 0; k < HASH_OUTPUT_SIZE; ++k)
                ux[k] ^= uu[k];

            /* set HMAC context (c3) for password   */
            memcpy(c3, c1, sizeof(hmac_ctx));
        }

        /* compile key blocks into the key output   */
        j = 0; k = i * HASH_OUTPUT_SIZE;
        while(j < HASH_OUTPUT_SIZE && k < key_len)
            key[k++] = ux[j++];
    }
}

#ifdef TEST

#include <stdio.h>

struct
{   unsigned int    pwd_len;
    unsigned int    salt_len;
    unsigned int    it_count;
    unsigned char   *pwd;
    unsigned char   salt[32];
    unsigned char   key[32];
} tests[] =
{
    {   8, 4, 5, (unsigned char*)"password",
        {
            0x12, 0x34, 0x56, 0x78
        },
        {
            0x5c, 0x75, 0xce, 0xf0, 0x1a, 0x96, 0x0d, 0xf7,
            0x4c, 0xb6, 0xb4, 0x9b, 0x9e, 0x38, 0xe6, 0xb5
        }
    },
    {   8, 8, 5, (unsigned char*)"password",
        {
            0x12, 0x34, 0x56, 0x78, 0x78, 0x56, 0x34, 0x12
        },
        {
            0xd1, 0xda, 0xa7, 0x86, 0x15, 0xf2, 0x87, 0xe6,
            0xa1, 0xc8, 0xb1, 0x20, 0xd7, 0x06, 0x2a, 0x49
        }
    },
    {   8, 21, 1, (unsigned char*)"password",
        {
            "ATHENA.MIT.EDUraeburn"
        },
        {
            0xcd, 0xed, 0xb5, 0x28, 0x1b, 0xb2, 0xf8, 0x01,
            0x56, 0x5a, 0x11, 0x22, 0xb2, 0x56, 0x35, 0x15
        }
    },
    {   8, 21, 2, (unsigned char*)"password",
        {
            "ATHENA.MIT.EDUraeburn"
        },
        {
            0x01, 0xdb, 0xee, 0x7f, 0x4a, 0x9e, 0x24, 0x3e,
            0x98, 0x8b, 0x62, 0xc7, 0x3c, 0xda, 0x93, 0x5d
        }
    },
    {   8, 21, 1200, (unsigned char*)"password",
        {
            "ATHENA.MIT.EDUraeburn"
        },
        {
            0x5c, 0x08, 0xeb, 0x61, 0xfd, 0xf7, 0x1e, 0x4e,
            0x4e, 0xc3, 0xcf, 0x6b, 0xa1, 0xf5, 0x51, 0x2b
        }
    }
};

int main()
{   unsigned int    i, j, key_len = 256;
    unsigned char   key[256];

    printf("\nTest of RFC2898 Password Based Key Derivation");
    for(i = 0; i < 5; ++i)
    {
        derive_key(tests[i].pwd, tests[i].pwd_len, tests[i].salt,
                    tests[i].salt_len, tests[i].it_count, key, key_len);

        printf("\ntest %i: ", i + 1);
        printf("key %s", memcmp(tests[i].key, key, 16) ? "is bad" : "is good");
        for(j = 0; j < key_len && j < 64; j += 4)
        {
            if(j % 16 == 0)
                printf("\n");
            printf("0x%02x%02x%02x%02x ", key[j], key[j + 1], key[j + 2], key[j + 3]);
        }
        printf(j < key_len ? " ... \n" : "\n");
    }
    printf("\n");
    return 0;
}

#if defined(__cplusplus)
}
#endif

#endif


  #include "../../196data/expected.dat"
};

#endif

const DigitType testing_data[NUM_TEST * DIGIT_WIDTH] = {
  #include "../../196data/test_set.dat"
};


/*===============================================================*/
/*                                                               */
/*                          digitrec.h                           */
/*                                                               */
/*             Hardware function for digit recognition           */
/*                                                               */
/*===============================================================*/

#include "../host/typedefs.h"

#pragma SDS data mem_attribute(global_training_set:PHYSICAL_CONTIGUOUS, global_test_set:PHYSICAL_CONTIGUOUS, global_results:PHYSICAL_CONTIGUOUS)
#pragma SDS data data_mover(global_training_set:AXIDMA_SIMPLE, global_test_set:AXIDMA_SIMPLE, global_results:AXIDMA_SIMPLE)

void DigitRec(WholeDigitType global_training_set[NUM_TRAINING / 2], WholeDigitType global_test_set[NUM_TEST], LabelType global_results[NUM_TEST], int run);