Version: 2022-05-26

See also:

This is a preliminary information. The tool Java2Vhdl translator is in progress.

1. Approach

This document contains details of implementation of the translator.

2. How to translate in VHDL

Of course, an approach as "evalating the source code with regular expression" in any proper language is not sufficient for translation of this Java code into VHDL. Only a complete parsing of the Java code with the known Java language syntax and the synthesis of the VHDL code from the parsed data is sufficient.

For parsing Java the ZBNF parser is used. The 'Z' means semantic as reversed 'S' because the parse result is presented in the output immediately semantically prepared. This simplifies the generation of VHDL. The ZBNF parser was developed by the author himself in ~2007, frequently used for different approaches, see also ../../../Java/html/RWTrans/ZbnfParser.html.

3. How does the parser work

The parser uses a textual script which is firstly prepared for internal use. Due to this script the parser checks all paths which are given by the syntax. This is a repeated recursively operation, which needs a longer time than a formally separation of syntax elements, but the semantic is immediately clarified while parsing.

Generally, also another parser may be sufficient to use. But the disadvantage of the longer parsing time is less important for this application. It may be a further development in future.

You can look to the working of the parser with some options of the Java2Vhdl conversion, see java cmd line of the execution:

-parseData ... optional, if given, writes the parser java data tree
-pd ... optional, same as -parseData
-parseResult ... optional, if given, writes the parser result
-pr ... optional, same as -parseResult
-parseLog ... optional only with -parseResult, writes an elaborately parser log file
-pl ... optional, same as -parseLog
-tmp:path/to/dirTmp for log and result
  • The -parseData option shows the prepared Java data which are used as input for the VHDL generation. It prepares a html file which shows a tree of all data. Any complex instance is presented with its toString() output which may contain sometimes a "TODO". Any basic instance, String and primitive variables, is shown with the data content. Any other parser may produce similar data, then the generator is usable without adaption.

  • The -parseResult option presents the original parse result tree from the syntax file before post preparation

  • The -parseLog shows an elaborately log how the parser is working.

Generally most all constructs in Java are well parsed, but on the other hand the kind of writing sources can be seen as limited for the hardware FPGA description. It means the parser is not intent to parse all Java syntax with all newest Java features.

You can have a look to the syntax file. It is contained as textual file in the jar archive in the package org.vishia.java2Vhdl.parseJava.JavaSyntax.zbnf.

4. Output preparation

For some outputs (TODO more) the org.vishia.util.OutTextPreparer is used. This class reads a textual file with the text to output with place holder. They can be used to coordinate the appearance of the output in details.

The textual file for the output appearance is stored in jar file in the package path org.vishia.java2Vhdl.VhdlTemplate.txt

5. Converting of an expression

The expression parsed from Java follows the approach described in Java/html/RWTrans/Calculator_RPN.html. It means the parsed expression is supplied as Reverse Polish Notation in prepared execute order. This allows to build the expression in VHDL to the VHDL precedence rules of the operators, independent of the Java ones. General the back conversion to a infix notation from RPN is described also in the link above.

The problematic for VHDL source code generation is the necessary distinguish between boolean and the SIGNAL types. See Java2Vhdl, chapter boolean expression and SIGNAL types with view to VHDL.

5.1. General algorithm to prepare the VHDL expression

The genExpression(…​) is called whenever an expression is expected in the translated code. That is as statement (can be an assignment) or also as condition in if.

//In source: main/java/srcJava_vishiaBase/org/vishia/fpga/VhdlConv.java
  /**Generates a VHDL expression from the parsed Java expression.
   * The Java expression is given or prepared here as Revers Polish Notation by post preparing, 
   * calling {@link JavaSrc.Expression#prep(org.vishia.parseJava.JavaSrc.Expression, Appendable)}. 
   * It means it is the natural execution order, not the writing order of operations.
   * The precedence of operations in Java are not relevant. 
   * But the operands are given in writing order.
   * The result is a valid VHDL source code with the specific VHDL precedence. It is not the same as in Java. 
   * @param out to write out, may be a StringBuffer. For simple expressions the same is written as contained in the return expression. 
   *   But for assignments or truefalseValues more lines are created.
   * @param exprArg
   * @param genBool true if the expression is necessary in a VHDL boolean environment (IF)
   * @param indent
   * @param assignTerm null or the left assign expression if called in an assign context. Then this expression is the right side expression to assign.
   * @return The built expression with type information. 
   * @throws Exception 
   */
  public VhdlExprTerm genExpression ( Appendable out, JavaSrc.Expression exprRpn, boolean genBool, boolean bInsideProcess
      , J2Vhdl_ModuleInstance mdl, String nameInnerClassVariable, CharSequence indent, CharSequence assignTerm) throws Exception {
    //
    VhdlExprTerm exprLeft = null;                          // left side expression segment from stack popped.
    try {
      boolean bStopExprPart = false;
      if(this.dbgStop) { 
        int[] lineColumn = new int[2];
        String file = exprRpn.getSrcInfo(lineColumn);  // TxSpe BlinkingLedCt ClockDivider BlinkingLed_Fpga
        if(file.contains("SpiMaster") && lineColumn[0] >= 286 && lineColumn[0] <= 288) {
          Debugutil.stop();
          bStopExprPart = true;
      } }
      if( ! exprRpn.isPrepared()) { exprRpn.prep(null); }
      Deque<VhdlExprTerm> uStack = new ArrayDeque<VhdlExprTerm>(); 
      int nrAllOperands = 0;
      boolean bLastWasAssignment = false;
      JavaSrc.ExprPart lastPart = null;                      // the last part can contain a trueFalseExpression, check afterwards 
      JavaSrc.ExprPartTrueFalse partTrueFalse = null;
      boolean bUseTrueFalse = false;
      for(JavaSrc.ExprPart part : exprRpn.get_ExprPart()) {

With the last shown for the ExprPart from the expression are processed in order of the RPN. The order of operands is the order in the original Java source. But the operations are sorted to stack operations and active operations.

//In source: main/java/srcJava_vishiaBase/org/vishia/fpga/VhdlConv.java
        lastPart = part;
        //partTrueFalse = part instanceof JavaSrc.ExprPartTrueFalse ? (JavaSrc.ExprPartTrueFalse) part : null;
        if(part instanceof JavaSrc.ExprPartTrueFalse) {
          partTrueFalse = (JavaSrc.ExprPartTrueFalse) part;  // store till assignment or end
        }
        J2Vhdl_Operator opPreced = getOperator(part, exprLeft, genBool);
        //
        //pop
        final VhdlExprTerm exprRight;                      // right side expression segment from left for operation in stack.
        if(part.get_value() ==null) {                      // "@"  // use the accu as operand, pop leftExpr from stack:
          exprRight = exprLeft;                            // then the current expression is the right part, sOperand
          exprLeft = uStack.pop();                         // pop: continue with pushed expression as left part
          bUseTrueFalse = partTrueFalse !=null;            // after pop a last partTrueFalse should be evaluated
        } else { 
          exprRight = null;                                // no pop, then exprRight is empty.
          //push
          if(opPreced.sJava.equals("@")) {                 // start of a new expression segment. @a
            if(exprLeft !=null) {
              uStack.push(exprLeft);                       // push the current expression in stack, use later
            }
            exprLeft = new VhdlExprTerm(this);             // new empty exprLeft
          }
        }
        // Write subordinate expression terms in parenthesis if they have a lower precedence
        // or also on equal precedence in VHDL. 
        // Writing equal precedence in parenthesis is then necessary if they come from own terms
        // of for a special VHDL problem: Because som VHDL Translators tests (...AND  ...)
        // (parenthesis surround AND terms though there are unnecessary in VHDL).
        // Example: b1 OR b2 AND b3 is clarified and correct in VHDL: Execute it in order, OR first, then AND.
        //          But the tools tests whether it is written (b1 OR b2) AND b3 because supposing of an writing error
        if(exprRight !=null                                
          && ( opPreced.precedVhdl > exprRight.precedSegm.precedVhdl   
            || opPreced.precedVhdl == exprRight.precedSegm.precedVhdl && opPreced != exprRight.precedSegm //&& opPreced.bAnd
          )  ) {                                            
          exprRight.b.insert(0, " (").append(") ");        // This part in parenthesis 
        }
        if(exprLeft !=null && exprLeft.b.length() >0 
          && ( opPreced.precedVhdl > exprLeft.precedSegm.precedVhdl
            || opPreced.precedVhdl == exprLeft.precedSegm.precedVhdl && opPreced != exprLeft.precedSegm //&& opPreced.bAnd 
          )  ) { // if the precedence is greater or also equal
          exprLeft.b.insert(0, " (").append(") ");         // then set both expressions in parenthesis to clarify lesser precedence
        }
        //
        if(opPreced.opBool.bAssign) {                      // assignment
          bLastWasAssignment = true;
          //
          //>>>>>>>
          if(bStopExprPart)
            Debugutil.stop();
          genAssignment(out, exprLeft, opPreced, exprRight, part, partTrueFalse, mdl, nameInnerClassVariable, indent, bInsideProcess);
          partTrueFalse = null;
          bUseTrueFalse = false;
        }
        else {
          if(bUseTrueFalse) {
            Debugutil.stop();     //what TODO
          }
          bLastWasAssignment = false;                        // add operator operand
          //
          //>>>>>>>
          if(bStopExprPart)
            Debugutil.stop();
          boolean bOk = exprLeft.exprLeftAddOperand(exprRight, opPreced, part, genBool, mdl, nameInnerClassVariable); 
          if(!bOk) {
            if(nrAllOperands == 0) {break; }                 // first variable unknown, not necessary statement (time assignment etc.).
          }
          nrAllOperands +=1;
        }
      } //-------------------------------------------------- //for parts.
  • If one ExprPart is a start of a new term, then the current content is pushed into a Stack for further usage. Now firstly this term is prepared straight forward.

  • If the accumulator should be used as following argument, then this is the currently prepared term, used now right side. The left side term is popped from stack. The part does not contain an operand, because even, it is the accumulator, the till now prepared term used as operand.

  • Either the exprRight or the operand in the part is used for the operation which is added to exprLeft.

  • If the operator is an assignment, this needs special handling. An assignment can also be a part in the expression, in form y = a & ( x = c | d) ^ e;. Then first the inner assignment is generated: x = c | d; as extra statement. The assign-variable x remains in the exprLeft, so the outer assignment is executed furthermore with y = a & x ^ e.

The following example should demonstrate the stack push and pop.

The original term is:

y = a | b & c;

The RPN form is:

@y; @a; @b; &c; |@; =@;

It means

  • y is taken and stored.

  • a is taken and stored.

  • b is taken, combined with with & c which produces b AND c.

  • |@ means, the stored term should be combined with the current term (accu).

    • It means b AND c is the accu, used as right part of the term,

    • it is wrapped with ( ) because it is prior in execution and has not a higher precedence in VHDL.

    • a is popped from stack, it is now the leftExpr

    • The expression is built with the operator and the rightExpr as a OR (b AND c)

  • =@ means, the stored term should be combined with the current term (accu).

    • The accu contains a OR (b AND c), used as right part of the term.

    • Because it is an assignment, the genAssignment(…​) is called.

    • This produces as new line y ⇐ a OR (b AND c).

The necessary parenthesis arround the AND term are produced for VHDL because AND has not a higher precedence as OR in VHDL, but it is produced as unit before (accu), hence should be wrapped. In Java & has a higher precedence in comparison to |. That’s why the stack operation is used.