public override bool VisitMethodDecl(AST.Method method) { if (!VisitDeclaration(method)) { return(false); } if (!method.IsOverride) { return(false); } var overridenMethod = GetOverridenBaseMethod(method); if (overridenMethod == null) { return(false); } // Fix-up any types that are not equal to the overriden base. if (!method.ReturnType.Equals(overridenMethod.ReturnType)) { method.ReturnType = overridenMethod.ReturnType; Diagnostics.Debug( "{0} return type is co-variant with overriden base", method.QualifiedOriginalName); } return(false); }
/// <summary> /// Renders a single ForStatement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="s">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.ForStatement s, int indentation) { var edges = s.GetStaticForLoopValues(); var endval = edges.Item2; endval--; var indent = new string(' ', indentation); yield return($"{indent}for {s.LoopIndex.Name} in {edges.Item1} to {endval} loop"); var incr = edges.Item3; if (incr != 1) { throw new Exception($"Expected the for loop to have an increment of 1, it has {incr}"); } foreach (var n in RenderStatement(method, s.LoopBody, indentation + 4)) { yield return(n); } yield return($"{indent}end loop;"); }
/// <summary> /// Renders a single CommentStatement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="s">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.CommentStatement s, int indentation) { var indent = new string(' ', indentation); foreach (var m in s.Message.Split(new[] { Environment.NewLine.ToString() }, StringSplitOptions.None)) { yield return($"{indent}-- {m}"); } }
/// <summary> /// Renders a single CommentStatement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="s">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.CommentStatement s, int indentation) { var indent = new string(' ', indentation); foreach (var c in (s.Message ?? string.Empty).Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) { yield return($"{indent}// {s.Message}"); } }
/// <summary> /// Renders a single ExpressionStatement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="s">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.ExpressionStatement s, int indentation) { var indent = new string(' ', indentation); var value = RenderExpression(s.Expression); if (!string.IsNullOrWhiteSpace(value)) { yield return($"{indent}{value};"); } }
/// <summary> /// Renders a single BlockStatement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="s">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.BlockStatement s, int indentation) { foreach (var n in s.Statements) { foreach (var x in RenderStatement(method, n, indentation)) { yield return(x); } } }
/// <summary> /// Renders all the statements in a state machine method as VHDL /// </summary> /// <returns>The statements in the method.</returns> /// <param name="method">The method to render.</param> public IEnumerable <string> RenderStateMachine(AST.Method method, RenderStateProcess rsp) { var proc = method.GetNearestParent <SME.AST.Process>(); var statesignal = proc.InternalDataElements[proc.InternalDataElements.Length - 2]; var runvariable = method.Variables.Last(); yield return($"{method.Name}: process (RST, FSM_Trigger)"); foreach (var n in method.Variables.Union(proc.SharedVariables).Where(x => x != runvariable)) { yield return($" variable {n.Name}: {Parent.VHDLWrappedTypeName(n)} := reset_{n.Name};"); } yield return($" variable {runvariable.Name}: {Parent.VHDLWrappedTypeName(runvariable)} := {RenderExpression(new PrimitiveExpression("State0", runvariable.CecilType))};"); yield return("begin"); // The first statement is the state reset call foreach (var s in method.Statements.Take(1).SelectMany(x => RenderStatement(method, x, 4))) { yield return(s); } yield return(" if RST = '1' then"); foreach (var bus in Process.OutputBusses.Concat(Process.InternalBusses).Distinct()) { foreach (var signal in rsp.WrittenSignals(bus)) { foreach (var s in RenderStatement(null, Parent.GetResetStatement(signal), 8)) { yield return(s); } } } foreach (var variable in method.AllVariables.Union(proc.SharedVariables).Where(x => x != statesignal)) { yield return($" {variable.Name} := {Naming.ToValidName("reset_" + variable.Name)};"); } yield return(" FIN <= '0';"); yield return(" else"); foreach (var s in method.Statements.Skip(1).SelectMany(x => RenderStatement(method, x, 8))) { yield return(s); } yield return(" FIN <= RDY;"); yield return(" end if;"); yield return("end process;"); }
/// <summary> /// Renders a single ReturnStatement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="s">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.ReturnStatement s, int indentation) { if (!(s.ReturnExpression is EmptyExpression)) { throw new Exception("Expected return expression to be empty"); } var indent = new string(' ', indentation); yield return($"{indent}return {method.ReturnVariable.Name};"); }
/// <summary> /// Renders a single ReturnStatement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="s">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.ReturnStatement s, int indentation) { var indent = new string(' ', indentation); if (s.ReturnExpression is EmptyExpression && method != ((AST.Process)method.Parent).MainMethod) { yield return($"{indent}return {method.ReturnVariable.Name};"); } else { yield return($"{indent}return {RenderExpression(s.ReturnExpression)};"); } }
/// <summary> /// Renders a single ForStatement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="s">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.ForStatement s, int indentation) { var edges = s.GetStaticForLoopValues(); var endval = edges.Item2; var incr = edges.Item3; var indent = new string(' ', indentation); yield return($"{indent}for (size_t {s.LoopIndex.Name} = {edges.Item1}; {s.LoopIndex.Name} < {endval}; {s.LoopIndex.Name} += {incr}) {{"); foreach (var n in RenderStatement(method, s.LoopBody, indentation + 4)) { yield return(n); } yield return($"{indent}}}"); }
/// <summary> /// Renders all the statements in a method as VHDL /// </summary> /// <returns>The statements in the method.</returns> /// <param name="method">The method to render.</param> public IEnumerable <string> RenderMethod(AST.Method method) { if (method == null || method.Ignore) { yield break; } foreach (var n in DeclareVariables(method.Variables, 0)) { yield return(n); } foreach (var n in method.Statements.SelectMany(x => RenderStatement(method, x, 0))) { yield return(n); } }
/// <summary> /// Renders a single SwitchStatement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="s">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.SwitchStatement s, int indentation) { var indent = new string(' ', indentation); indentation += 4; var indent2 = new string(' ', indentation); indentation += 4; yield return($"{indent}switch({RenderExpression(s.SwitchExpression)}) {{"); var hasOthers = false; foreach (var c in s.Cases) { if (c.Item1.Length == 1 && c.Item1.First() is EmptyExpression) { hasOthers = true; yield return($"{indent2}default:"); } else { foreach (var cs in c.Item1.Select(x => RenderExpression(x))) { yield return($"{indent2}case {cs}:"); } } foreach (var ss in c.Item2.SelectMany(x => RenderStatement(method, x, indentation))) { yield return(ss); } yield return($"{indent2}break;"); } if (!hasOthers) { yield return($"{indent2}default:"); } yield return($"{indent}}}"); }
/// <summary> /// Renders a single SwitchStatement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="s">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.SwitchStatement s, int indentation) { var indent = new string(' ', indentation); indentation += 4; var indent2 = new string(' ', indentation); indentation += 4; yield return($"{indent}case {RenderExpression(s.SwitchExpression)} is"); var others = new Statement[0]; foreach (var c in s.Cases) { if (c.Item1.Length == 1 && c.Item1.First() is EmptyExpression) { others = c.Item2; continue; } else { yield return(indent2 + "when " + string.Join(" | ", c.Item1.Select(x => RenderExpression(x))) + " =>"); } foreach (var ss in c.Item2.SelectMany(x => RenderStatement(method, x, indentation))) { yield return(ss); } } yield return($"{indent2}when others =>"); foreach (var ss in others.SelectMany(x => RenderStatement(method, x, indentation))) { yield return(ss); } yield return($"{indent}end case;"); }
/// <summary> /// Renders a single statement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="statement">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.Statement statement, int indentation) { if (statement is AST.ForStatement) { return(RenderStatement(method, statement as AST.ForStatement, indentation)); } else if (statement is AST.ReturnStatement) { return(RenderStatement(method, statement as AST.ReturnStatement, indentation)); } else if (statement is AST.BlockStatement) { return(RenderStatement(method, statement as AST.BlockStatement, indentation)); } else if (statement is AST.SwitchStatement) { return(RenderStatement(method, statement as AST.SwitchStatement, indentation)); } else if (statement is AST.IfElseStatement) { return(RenderStatement(method, statement as AST.IfElseStatement, indentation)); } else if (statement is AST.ExpressionStatement) { return(RenderStatement(method, statement as AST.ExpressionStatement, indentation)); } else if (statement is AST.CommentStatement) { return(RenderStatement(method, statement as AST.CommentStatement, indentation)); } else if (statement is AST.EmptyStatement) { return(new string[0]); } else { throw new Exception($"Unuspported statement type: {statement.GetType().FullName}"); } }
/// <summary> /// Renders a single IfElseStatement with the given indentation /// </summary> /// <returns>The VHDL lines in the statement.</returns> /// <param name="method">The method the statement belongs to.</param> /// <param name="s">The statement to render.</param> /// <param name="indentation">The indentation to use.</param> private IEnumerable <string> RenderStatement(AST.Method method, AST.IfElseStatement s, int indentation) { var indent = new string(' ', indentation); yield return($"{indent}if ({RenderExpression(s.Condition)}) {{"); foreach (var e in RenderStatement(method, s.TrueStatement, indentation + 4)) { yield return(e); } if (s.FalseStatement != null && !(s.FalseStatement is EmptyStatement)) { yield return($"{indent}}} else {{"); foreach (var e in RenderStatement(method, s.FalseStatement, indentation + 4)) { yield return(e); } } yield return($"{indent}}}"); }
/// <summary> /// Renders all the statements in a method as VHDL /// </summary> /// <returns>The statements in the method.</returns> /// <param name="method">The method to render.</param> public IEnumerable <string> RenderMethod(AST.Method method) { if (method == null || method.Ignore) { yield break; } if (method != Process.MainMethod) { var margs = string.Join("; ", from n in method.Parameters let inoutargstr = ((ParameterDefinition)n.Source).GetArgumentInOut().ToString().ToLowerInvariant() select string.Format( "{0}{1}: {2} {3}", string.Equals(inoutargstr, "in", StringComparison.OrdinalIgnoreCase) ? "constant " : "", n.Name, inoutargstr, ((ParameterDefinition)n.Source).GetAttribute <RangeAttribute>() != null ? method.Name + "_" + n.Name + "_type" : Parent.VHDLType(n).ToSafeVHDLName() )); if (method.ReturnVariable == null || method.ReturnVariable.CecilType.IsSameTypeReference(typeof(void))) { yield return($"procedure {method.Name}({margs}) is"); } else { yield return($"pure function {method.Name}({margs}) return {Parent.VHDLWrappedTypeName(method.ReturnVariable)} is"); } foreach (var n in method.AllVariables) { yield return($" variable {n.Name}: {Parent.VHDLWrappedTypeName(n)};"); } foreach (var m in Parent.TemporaryVariables) { if (m.Key == method) { foreach (var n in m.Value.Values) { yield return($" variable {n.Name}: {Parent.VHDLWrappedTypeName(n)};"); } } } yield return("begin"); } foreach (var s in method.Statements.SelectMany(x => RenderStatement(method, x, method == Process.MainMethod ? 0 : 4))) { yield return(s); } if (method != Process.MainMethod) { yield return($"end {method.Name};"); } }
/// <summary> /// Applies the transformation /// </summary> /// <returns>The transformed item.</returns> /// <param name="item">The item to visit.</param> public ASTItem Transform(ASTItem item) { if (!(item is AST.Method) || !((Method)item).IsStateMachine) { return(item); } var method = item as AST.Method; if (!method.All().OfType <AwaitExpression>().Any()) { return(item); } var enumname = "FSM_" + method.Name + "_State"; // Construct an enum type that matches the desired states var enumtype = new Mono.Cecil.TypeDefinition("", enumname, Mono.Cecil.TypeAttributes.Public | Mono.Cecil.TypeAttributes.AutoClass | Mono.Cecil.TypeAttributes.AnsiClass | Mono.Cecil.TypeAttributes.Sealed, method.SourceMethod.Module.ImportReference(typeof(System.Enum))) { IsSealed = true, }; enumtype.DeclaringType = method.SourceMethod.DeclaringType; enumtype.Fields.Add(new Mono.Cecil.FieldDefinition($"value__", Mono.Cecil.FieldAttributes.Public | Mono.Cecil.FieldAttributes.SpecialName | Mono.Cecil.FieldAttributes.RTSpecialName, method.SourceMethod.Module.ImportReference(typeof(int)))); var statenametemplate = $"{enumtype.DeclaringType.FullName}_{enumname}_State"; var fragments = SplitIntoFragments(method.Statements); var statecount = fragments.Count; var enumfields = new Mono.Cecil.FieldDefinition[statecount]; // Add each of the states to the type for (var i = 0; i < statecount; i++) { enumtype.Fields.Add(enumfields[i] = new Mono.Cecil.FieldDefinition($"State{i}", Mono.Cecil.FieldAttributes.Public | Mono.Cecil.FieldAttributes.Static | Mono.Cecil.FieldAttributes.Literal, enumtype) { Constant = i }); } // The variable being updated internally in the method var run_state_var = new AST.Variable("FSM_RunState", enumfields[0]) { CecilType = enumtype, DefaultValue = 0, Source = new Mono.Cecil.ParameterDefinition("FSM_RunState", Mono.Cecil.ParameterAttributes.None, enumtype) }; // The current state used in the state machine process var current_state_signal = new AST.Signal("FSM_CurrentState", enumfields[0]) { CecilType = enumtype, DefaultValue = 0, Source = new Mono.Cecil.ParameterDefinition("FSM_CurrentState", Mono.Cecil.ParameterAttributes.None, enumtype) }; // The next state that is propagated to var next_state_signal = new AST.Signal("FSM_NextState", enumfields[0]) { CecilType = enumtype, DefaultValue = 0, Source = new Mono.Cecil.ParameterDefinition("FSM_NextState", Mono.Cecil.ParameterAttributes.None, enumtype) }; // Construct a state-machine method, that will be rendered as a process var stateMachineProcess = new AST.Method() { Name = "FSM_" + method.Name + "_Method", Parameters = new AST.Parameter[0], ReturnVariable = null, Parent = method.Parent, Variables = method.AllVariables.Concat(new Variable[] { run_state_var }).ToArray(), AllVariables = method.AllVariables.Concat(new Variable[] { }).ToArray(), IsStateMachine = true }; var enumdataitems = enumfields.Select(x => new Constant(x.Name, x) { CecilType = enumtype }).ToArray(); var isSimpleStatePossible = fragments.SelectMany(x => x.SelectMany(y => y.All().OfType <CaseGotoStatement>())).All(x => !x.FallThrough); List <Statement> cases; // If we have no fallthrough states, we build a switch if (isSimpleStatePossible) { cases = new List <Statement>(new[] { new EmptyStatement(), CreateSwitchStatement(fragments, current_state_signal, enumdataitems) }); } // Otherwise, we build an if-based state machine else { cases = WrapFragmentsWithLabels(fragments, run_state_var, enumdataitems); cases.Insert(0, new ExpressionStatement( new AssignmentExpression( new IdentifierExpression(run_state_var), new IdentifierExpression(current_state_signal) ) )); } stateMachineProcess.Statements = cases.ToArray(); foreach (var v in stateMachineProcess.Statements) { v.Parent = stateMachineProcess; v.UpdateParents(); ReplaceGotoWithVariables(v, run_state_var, next_state_signal, enumdataitems); } method.Statements = new Statement[] { new ExpressionStatement( new AssignmentExpression( new IdentifierExpression(current_state_signal), new IdentifierExpression(next_state_signal) ) ) { Parent = method } }; method.IsStateMachine = false; var proc = method.GetNearestParent <Process>(); proc.Methods = proc.Methods.Concat(new[] { stateMachineProcess }).ToArray(); // Move variables into the shared area proc.InternalDataElements = proc.InternalDataElements .Union(new[] { current_state_signal, next_state_signal }) //.Union(method.AllVariables) .ToArray(); proc.SharedVariables = proc.SharedVariables.Union(method.AllVariables).ToArray(); method.Variables = new Variable[0]; method.AllVariables = new Variable[0]; return(stateMachineProcess); }