Support `break` and `continue` Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
diff --git a/systemverilog-plugin/UhdmAst.cc b/systemverilog-plugin/UhdmAst.cc index 62f7d67..46a91f8 100644 --- a/systemverilog-plugin/UhdmAst.cc +++ b/systemverilog-plugin/UhdmAst.cc
@@ -1151,6 +1151,125 @@ return nullptr; } +void UhdmAst::transform_breaks_continues(AST::AstNode *loop, AST::AstNode *decl_block) +{ + AST::AstNode *break_wire = nullptr; + AST::AstNode *continue_wire = nullptr; + // Creates a 1-bit wire with the given name + const auto make_cond_var = [this](const std::string &var_name) { + auto cond_var = + make_ast_node(AST::AST_WIRE, {make_ast_node(AST::AST_RANGE, {AST::AstNode::mkconst_int(0, false), AST::AstNode::mkconst_int(0, false)}), + AST::AstNode::mkconst_int(0, false)}); + cond_var->str = var_name; + cond_var->is_reg = true; + return cond_var; + }; + // Creates a conditional like 'if (!casevar) block' + auto make_case = [this](AST::AstNode *block, const std::string &casevar_name) { + auto *case_node = make_ast_node(AST::AST_CASE); + auto *id = make_identifier(casevar_name); + case_node->children.push_back(id); + auto *constant = AST::AstNode::mkconst_int(0, false, 1); + auto *cond_node = make_ast_node(AST::AST_COND); + cond_node->children.push_back(constant); + cond_node->children.push_back(block); + case_node->children.push_back(cond_node); + return case_node; + }; + // Pre-declare this function to be able to call it recursively + std::function<bool(AST::AstNode *)> transform_block; + // Transforms the given block if it has a break or continue; recurses into child blocks; return true if a break/continue was encountered + transform_block = [&](AST::AstNode *block) { + auto wrap_and_transform = [&](decltype(block->children)::iterator it) { + // Move the (it, end()) statements into a new block under 'if (!continue) {...}' + auto *new_block = make_ast_node(AST::AST_BLOCK, {it, block->children.end()}); + block->children.erase(it, block->children.end()); + auto *case_node = make_case(new_block, continue_wire->str); + block->children.push_back(case_node); + transform_block(new_block); + }; + + for (auto it = block->children.begin(); it != block->children.end(); it++) { + auto type = static_cast<int>((*it)->type); + switch (type) { + case AST::AST_BLOCK: { + if (transform_block(*it)) { + // If there was a break/continue, we need to wrap the rest of the block in an if + wrap_and_transform(it + 1); + return true; + } + break; + } + case AST::AST_CASE: { + // Go over each block in a case + bool has_jump = false; + for (auto *node : (*it)->children) { + if (node->type == AST::AST_COND) + has_jump = has_jump || transform_block(node->children.back()); + } + if (has_jump) { + // If there was a break/continue, we need to wrap the rest of the block in an if + wrap_and_transform(it + 1); + return true; + } + break; + } + case AST::AST_BREAK: + case AST::AST_CONTINUE: { + std::for_each(it, block->children.end(), [](auto *node) { delete node; }); + block->children.erase(it, block->children.end()); + if (!continue_wire) + continue_wire = make_cond_var("$continue"); + auto *continue_id = make_identifier(continue_wire->str); + block->children.push_back(make_ast_node(AST::AST_ASSIGN_EQ, {continue_id, AST::AstNode::mkconst_int(1, false)})); + if (type == AST::AST_BREAK) { + if (!break_wire) + break_wire = make_cond_var("$break"); + auto *break_id = make_identifier(break_wire->str); + block->children.push_back(make_ast_node(AST::AST_ASSIGN_EQ, {break_id, AST::AstNode::mkconst_int(1, false)})); + } + return true; + } + } + } + return false; + }; + + // Actual transformation starts here + transform_block(loop->children.back()); + if (continue_wire) { + auto *continue_id = make_identifier(continue_wire->str); + // Reset $continue each iteration + auto *continue_assign = make_ast_node(AST::AST_ASSIGN_EQ, {continue_id, AST::AstNode::mkconst_int(0, false)}); + decl_block->children.insert(decl_block->children.begin(), continue_wire); + loop->children.back()->children.insert(loop->children.back()->children.begin(), continue_assign); + } + if (break_wire) { + auto *break_id = make_identifier(break_wire->str); + // Reset $break before the loop + auto *break_assign = make_ast_node(AST::AST_ASSIGN_EQ, {break_id, AST::AstNode::mkconst_int(0, false)}); + decl_block->children.insert(decl_block->children.begin(), break_assign); + decl_block->children.insert(decl_block->children.begin(), break_wire); + if (loop->type == AST::AST_REPEAT || loop->type == AST::AST_FOR) { + // Wrap loop body in 'if (!break) {...}' + // Changing the for loop condition won't work here, + // as then simplify fails with error "2nd expression of procedural for-loop is not constant!" + auto *case_node = make_case(loop->children.back(), break_wire->str); + auto *new_block = make_ast_node(AST::AST_BLOCK); + new_block->children.push_back(case_node); + new_block->str = loop->children.back()->str; + loop->children.back() = new_block; + } else if (loop->type == AST::AST_WHILE) { + // Add the break var to the loop condition + auto *break_id = make_identifier(break_wire->str); + AST::AstNode *&loop_cond = loop->children[0]; + loop_cond = make_ast_node(AST::AST_LOGIC_AND, {make_ast_node(AST::AST_LOGIC_NOT, {break_id}), loop_cond}); + } else { + log_error("break unsupported for this loop type"); + } + } +} + AST::AstNode *UhdmAst::make_ast_node(AST::AstNodeType type, std::vector<AST::AstNode *> children, bool prefer_full_name) { auto node = new AST::AstNode(type); @@ -1168,6 +1287,13 @@ return node; } +AST::AstNode *UhdmAst::make_identifier(const std::string &name) +{ + auto *node = make_ast_node(AST::AST_IDENTIFIER); + node->str = name; + return node; +} + void UhdmAst::process_packed_array_typespec() { std::vector<AST::AstNode *> packed_ranges; @@ -2998,24 +3124,23 @@ void UhdmAst::process_for() { - current_node = make_ast_node(AST::AST_FOR); - auto loop = current_node; + current_node = make_ast_node(AST::AST_BLOCK); auto loop_id = shared.next_loop_id(); - current_node->str = "$loop" + std::to_string(loop_id); + current_node->str = "$fordecl_block" + std::to_string(loop_id); + auto loop = make_ast_node(AST::AST_FOR); + loop->str = "$loop" + std::to_string(loop_id); + current_node->children.push_back(loop); visit_one_to_many({vpiForInitStmt}, obj_h, [&](AST::AstNode *node) { if (node->type == AST::AST_ASSIGN_LE) node->type = AST::AST_ASSIGN_EQ; auto lhs = node->children[0]; if (lhs->type == AST::AST_WIRE) { - current_node = make_ast_node(AST::AST_BLOCK); - current_node->str = "$fordecl_block" + std::to_string(loop_id); auto *wire = lhs->clone(); wire->is_reg = true; current_node->children.push_back(wire); lhs->type = AST::AST_IDENTIFIER; lhs->is_signed = false; lhs->delete_children(); - current_node->children.push_back(loop); } loop->children.push_back(node); }); @@ -3038,6 +3163,7 @@ loop->children.push_back(node); } }); + transform_breaks_continues(loop, current_node); } void UhdmAst::process_gen_scope() @@ -3511,19 +3637,23 @@ void UhdmAst::process_repeat() { - current_node = make_ast_node(AST::AST_REPEAT); - visit_one_to_one({vpiCondition}, obj_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); + auto loop_id = shared.next_loop_id(); + current_node = make_ast_node(AST::AST_BLOCK); + current_node->str = "$repeatdecl_block" + std::to_string(loop_id); + auto *loop = make_ast_node(AST::AST_REPEAT); + loop->str = "$loop" + std::to_string(loop_id); + current_node->children.push_back(loop); + visit_one_to_one({vpiCondition}, obj_h, [&](AST::AstNode *node) { loop->children.push_back(node); }); visit_one_to_one({vpiStmt}, obj_h, [&](AST::AstNode *node) { - if (node) { - AST::AstNode *block = nullptr; - if (node->type != AST::AST_BLOCK) { - block = new AST::AstNode(AST::AST_BLOCK, node); - } else { - block = node; - } - current_node->children.push_back(block); + if (node->type != AST::AST_BLOCK) { + node = new AST::AstNode(AST::AST_BLOCK, node); } + if (node->str == "") { + node->str = loop->str; // Needed in simplify step + } + loop->children.push_back(node); }); + transform_breaks_continues(loop, current_node); } void UhdmAst::process_var_select() @@ -3838,21 +3968,23 @@ void UhdmAst::process_while() { - current_node = make_ast_node(AST::AST_WHILE); - visit_one_to_one({vpiCondition}, obj_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); + auto loop_id = shared.next_loop_id(); + current_node = make_ast_node(AST::AST_BLOCK); + current_node->str = "$whiledecl_block" + std::to_string(loop_id); + auto *loop = make_ast_node(AST::AST_WHILE); + loop->str = "$loop" + std::to_string(loop_id); + current_node->children.push_back(loop); + visit_one_to_one({vpiCondition}, obj_h, [&](AST::AstNode *node) { loop->children.push_back(node); }); visit_one_to_one({vpiStmt}, obj_h, [&](AST::AstNode *node) { if (node->type != AST::AST_BLOCK) { - auto *statements = make_ast_node(AST::AST_BLOCK); - statements->str = current_node->str; // Needed in simplify step - statements->children.push_back(node); - current_node->children.push_back(statements); - } else { - if (node->str == "") { - node->str = current_node->str; - current_node->children.push_back(node); - } + node = make_ast_node(AST::AST_BLOCK, {node}); } + if (node->str.empty()) { + node->str = loop->str; // Needed in simplify step + } + loop->children.push_back(node); }); + transform_breaks_continues(loop, current_node); } void UhdmAst::process_gate() @@ -4045,6 +4177,14 @@ case vpiFor: process_for(); break; + case vpiBreak: + // Will be resolved later by loop processor + current_node = make_ast_node(static_cast<AST::AstNodeType>(AST::AST_BREAK)); + break; + case vpiContinue: + // Will be resolved later by loop processor + current_node = make_ast_node(static_cast<AST::AstNodeType>(AST::AST_CONTINUE)); + break; case vpiGenScopeArray: process_gen_scope_array(); break;
diff --git a/systemverilog-plugin/UhdmAst.h b/systemverilog-plugin/UhdmAst.h index af0b808..8f6a368 100644 --- a/systemverilog-plugin/UhdmAst.h +++ b/systemverilog-plugin/UhdmAst.h
@@ -33,6 +33,9 @@ // the given vpiHandle. AST::AstNode *make_ast_node(AST::AstNodeType type, std::vector<AST::AstNode *> children = {}, bool prefer_full_name = false); + // Create an identifier AstNode + AST::AstNode *make_identifier(const std::string &name); + // Makes the passed node a cell node of the specified type void make_cell(vpiHandle obj_h, AST::AstNode *node, AST::AstNode *type); @@ -48,6 +51,9 @@ // Processes the value connected to the specified node AST::AstNode *process_value(vpiHandle obj_h); + // Transforms break and continue nodes into structures accepted by the AST frontend + void transform_breaks_continues(AST::AstNode *loop, AST::AstNode *decl_block); + // The parent UhdmAst UhdmAst *parent;
diff --git a/systemverilog-plugin/UhdmAstUpstream.cc b/systemverilog-plugin/UhdmAstUpstream.cc index d6d9be8..921acf1 100644 --- a/systemverilog-plugin/UhdmAstUpstream.cc +++ b/systemverilog-plugin/UhdmAstUpstream.cc
@@ -1,7 +1,9 @@ namespace AST { enum AstNodeTypeExtended { - AST_DOT = AST::AST_BIND + 1 // here we always want to point to the last element of yosys' AstNodeType + AST_DOT = AST::AST_BIND + 1, // here we always want to point to the last element of yosys' AstNodeType + AST_BREAK, + AST_CONTINUE }; }
diff --git a/systemverilog-plugin/tests/Makefile b/systemverilog-plugin/tests/Makefile index 14a1915..378788f 100644 --- a/systemverilog-plugin/tests/Makefile +++ b/systemverilog-plugin/tests/Makefile
@@ -14,9 +14,9 @@ # # SPDX-License-Identifier: Apache-2.0 -TESTS = counter +TESTS = counter break_continue include $(shell pwd)/../../Makefile_test.common counter_verify = true - +break_continue_verify = $(call diff_test,break_continue,out)
diff --git a/systemverilog-plugin/tests/break_continue/break_continue.golden.out b/systemverilog-plugin/tests/break_continue/break_continue.golden.out new file mode 100644 index 0000000..74a27c2 --- /dev/null +++ b/systemverilog-plugin/tests/break_continue/break_continue.golden.out
@@ -0,0 +1,2 @@ +top a - - po 110 +top b - - po 15
diff --git a/systemverilog-plugin/tests/break_continue/break_continue.tcl b/systemverilog-plugin/tests/break_continue/break_continue.tcl new file mode 100644 index 0000000..166c38e --- /dev/null +++ b/systemverilog-plugin/tests/break_continue/break_continue.tcl
@@ -0,0 +1,13 @@ +yosys -import +if { [info procs read_uhdm] == {} } { plugin -i systemverilog } +yosys -import ;# ingest plugin commands + +set TMP_DIR /tmp +if { [info exists ::env(TMPDIR) ] } { + set TMP_DIR $::env(TMPDIR) +} + +# Testing simple round-trip +read_systemverilog -o $TMP_DIR/break-continue-test $::env(DESIGN_TOP).v +prep +write_table $::env(DESIGN_TOP).out
diff --git a/systemverilog-plugin/tests/break_continue/break_continue.v b/systemverilog-plugin/tests/break_continue/break_continue.v new file mode 100644 index 0000000..d06d60b --- /dev/null +++ b/systemverilog-plugin/tests/break_continue/break_continue.v
@@ -0,0 +1,30 @@ +// Copyright 2020-2022 F4PGA Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +module top(output int a, output int b); + initial begin + a = 0; + b = 0; + repeat(15) begin + if(a > 100) begin + if (b > 10) + break; + b = b + 5; + continue; + end + a = a + 10; + end + end +endmodule