mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 19:52:31 +00:00 
			
		
		
		
	sv: auto add nosync to certain always_comb local vars
If a local variable is always assigned before it is used, then adding nosync prevents latches from being needlessly generated.
This commit is contained in:
		
							parent
							
								
									828e85068f
								
							
						
					
					
						commit
						aa35f24290
					
				
					 10 changed files with 265 additions and 0 deletions
				
			
		|  | @ -20,6 +20,9 @@ Yosys 0.11 .. Yosys 0.12 | |||
|       operations | ||||
|     - Fixed static size casts ignoring expression signedness | ||||
|     - Fixed static size casts not extending unbased unsized literals | ||||
|     - Added automatic `nosync` inference for local variables in `always_comb` | ||||
|       procedures which are always assigned before they are used to avoid errant | ||||
|       latch inference | ||||
| 
 | ||||
|  * New commands and options | ||||
|     - Added "-genlib" option to "abc" pass | ||||
|  |  | |||
|  | @ -673,6 +673,118 @@ void add_wire_for_ref(const RTLIL::Wire *ref, const std::string &str) | |||
| 	current_scope[str] = wire; | ||||
| } | ||||
| 
 | ||||
| enum class IdentUsage { | ||||
| 	NotReferenced, // target variable is neither read or written in the block
 | ||||
| 	Assigned, // target variable is always assigned before use
 | ||||
| 	SyncRequired, // target variable may be used before it has been assigned
 | ||||
| }; | ||||
| 
 | ||||
| // determines whether a local variable a block is always assigned before it is
 | ||||
| // used, meaning the nosync attribute can automatically be added to that
 | ||||
| // variable
 | ||||
| static IdentUsage always_asgn_before_use(const AstNode *node, const std::string &target) | ||||
| { | ||||
| 	// This variable has been referenced before it has necessarily been assigned
 | ||||
| 	// a value in this procedure.
 | ||||
| 	if (node->type == AST_IDENTIFIER && node->str == target) | ||||
| 		return IdentUsage::SyncRequired; | ||||
| 
 | ||||
| 	// For case statements (which are also used for if/else), we check each
 | ||||
| 	// possible branch. If the variable is assigned in all branches, then it is
 | ||||
| 	// assigned, and a sync isn't required. If it used before assignment in any
 | ||||
| 	// branch, then a sync is required.
 | ||||
| 	if (node->type == AST_CASE) { | ||||
| 		bool all_defined = true; | ||||
| 		bool any_used = false; | ||||
| 		bool has_default = false; | ||||
| 		for (const AstNode *child : node->children) { | ||||
| 			if (child->type == AST_COND && child->children.at(0)->type == AST_DEFAULT) | ||||
| 				has_default = true; | ||||
| 			IdentUsage nested = always_asgn_before_use(child, target); | ||||
| 			if (nested != IdentUsage::Assigned && child->type == AST_COND) | ||||
| 				all_defined = false; | ||||
| 			if (nested == IdentUsage::SyncRequired) | ||||
| 				any_used = true; | ||||
| 		} | ||||
| 		if (any_used) | ||||
| 			return IdentUsage::SyncRequired; | ||||
| 		else if (all_defined && has_default) | ||||
| 			return IdentUsage::Assigned; | ||||
| 		else | ||||
| 			return IdentUsage::NotReferenced; | ||||
| 	} | ||||
| 
 | ||||
| 	// Check if this is an assignment to the target variable. For simplicity, we
 | ||||
| 	// don't analyze sub-ranges of the variable.
 | ||||
| 	if (node->type == AST_ASSIGN_EQ) { | ||||
| 		const AstNode *ident = node->children.at(0); | ||||
| 		if (ident->type == AST_IDENTIFIER && ident->str == target) | ||||
| 			return IdentUsage::Assigned; | ||||
| 	} | ||||
| 
 | ||||
| 	for (const AstNode *child : node->children) { | ||||
| 		IdentUsage nested = always_asgn_before_use(child, target); | ||||
| 		if (nested != IdentUsage::NotReferenced) | ||||
| 			return nested; | ||||
| 	} | ||||
| 	return IdentUsage::NotReferenced; | ||||
| } | ||||
| 
 | ||||
| static const std::string auto_nosync_prefix = "\\AutoNosync"; | ||||
| 
 | ||||
| // mark a local variable in an always_comb block for automatic nosync
 | ||||
| // consideration
 | ||||
| static void mark_auto_nosync(AstNode *block, const AstNode *wire) | ||||
| { | ||||
| 	log_assert(block->type == AST_BLOCK); | ||||
| 	log_assert(wire->type == AST_WIRE); | ||||
| 	block->attributes[auto_nosync_prefix + wire->str] = AstNode::mkconst_int(1, | ||||
| 			false); | ||||
| } | ||||
| 
 | ||||
| // check a procedural block for auto-nosync markings, remove them, and add
 | ||||
| // nosync to local variables as necessary
 | ||||
| static void check_auto_nosync(AstNode *node) | ||||
| { | ||||
| 	std::vector<RTLIL::IdString> attrs_to_drop; | ||||
| 	for (const auto& elem : node->attributes) { | ||||
| 		// skip attributes that don't begin with the prefix
 | ||||
| 		if (elem.first.compare(0, auto_nosync_prefix.size(), | ||||
| 					auto_nosync_prefix.c_str())) | ||||
| 			continue; | ||||
| 
 | ||||
| 		// delete and remove the attribute once we're done iterating
 | ||||
| 		attrs_to_drop.push_back(elem.first); | ||||
| 
 | ||||
| 		// find the wire based on the attribute
 | ||||
| 		std::string wire_name = elem.first.substr(auto_nosync_prefix.size()); | ||||
| 		auto it = current_scope.find(wire_name); | ||||
| 		if (it == current_scope.end()) | ||||
| 			continue; | ||||
| 
 | ||||
| 		// analyze the usage of the local variable in this block
 | ||||
| 		IdentUsage ident_usage = always_asgn_before_use(node, wire_name); | ||||
| 		if (ident_usage != IdentUsage::Assigned) | ||||
| 			continue; | ||||
| 
 | ||||
| 		// mark the wire with `nosync`
 | ||||
| 		AstNode *wire = it->second; | ||||
| 		log_assert(wire->type == AST_WIRE); | ||||
| 		wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false); | ||||
| 	} | ||||
| 
 | ||||
| 	// remove the attributes we've "consumed"
 | ||||
| 	for (const RTLIL::IdString &str : attrs_to_drop) { | ||||
| 		auto it = node->attributes.find(str); | ||||
| 		delete it->second; | ||||
| 		node->attributes.erase(it); | ||||
| 	} | ||||
| 
 | ||||
| 	// check local variables in any nested blocks
 | ||||
| 	for (AstNode *child : node->children) | ||||
| 		check_auto_nosync(child); | ||||
| } | ||||
| 
 | ||||
| // convert the AST into a simpler AST that has all parameters substituted by their
 | ||||
| // values, unrolled for-loops, expanded generate blocks, etc. when this function
 | ||||
| // is done with an AST it can be converted into RTLIL using genRTLIL().
 | ||||
|  | @ -980,6 +1092,11 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, | |||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		for (AstNode *child : children) | ||||
| 			if (child->type == AST_ALWAYS && | ||||
| 					child->attributes.count(ID::always_comb)) | ||||
| 				check_auto_nosync(child); | ||||
| 	} | ||||
| 
 | ||||
| 	// create name resolution entries for all objects with names
 | ||||
|  | @ -2224,6 +2341,16 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, | |||
| 	{ | ||||
| 		expand_genblock(str + "."); | ||||
| 
 | ||||
| 		// if this is an autonamed block is in an always_comb
 | ||||
| 		if (current_always && current_always->attributes.count(ID::always_comb) | ||||
| 				&& str.at(0) == '$') | ||||
| 			// track local variables in this block so we can consider adding
 | ||||
| 			// nosync once the block has been fully elaborated
 | ||||
| 			for (AstNode *child : children) | ||||
| 				if (child->type == AST_WIRE && | ||||
| 						!child->attributes.count(ID::nosync)) | ||||
| 					mark_auto_nosync(this, child); | ||||
| 
 | ||||
| 		std::vector<AstNode*> new_children; | ||||
| 		for (size_t i = 0; i < children.size(); i++) | ||||
| 			if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM || children[i]->type == AST_TYPEDEF) { | ||||
|  |  | |||
							
								
								
									
										13
									
								
								tests/verilog/always_comb_latch_1.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								tests/verilog/always_comb_latch_1.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| read_verilog -sv <<EOF | ||||
| module top; | ||||
| logic x; | ||||
| always_comb begin | ||||
|     logic y; | ||||
|     if (x) | ||||
|         y = 1; | ||||
|     x = y; | ||||
| end | ||||
| endmodule | ||||
| EOF | ||||
| logger -expect error "^Latch inferred for signal `\\top\.\$unnamed_block\$1\.y' from always_comb process" 1 | ||||
| proc | ||||
							
								
								
									
										15
									
								
								tests/verilog/always_comb_latch_2.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								tests/verilog/always_comb_latch_2.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| read_verilog -sv <<EOF | ||||
| module top; | ||||
| logic x; | ||||
| always_comb begin | ||||
|     logic y; | ||||
|     if (x) | ||||
|         x = 1; | ||||
|     else | ||||
|         y = 1; | ||||
|     x = y; | ||||
| end | ||||
| endmodule | ||||
| EOF | ||||
| logger -expect error "^Latch inferred for signal `\\top\.\$unnamed_block\$1\.y' from always_comb process" 1 | ||||
| proc | ||||
							
								
								
									
										20
									
								
								tests/verilog/always_comb_latch_3.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								tests/verilog/always_comb_latch_3.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| read_verilog -sv <<EOF | ||||
| module top; | ||||
| logic x; | ||||
| logic z; | ||||
| assign z = 1'b1; | ||||
| always_comb begin | ||||
|     logic y; | ||||
|     case (x) | ||||
|     1'b0: | ||||
|         y = 1; | ||||
|     endcase | ||||
|     if (z) | ||||
|         x = y; | ||||
|     else | ||||
|         x = 1'b0; | ||||
| end | ||||
| endmodule | ||||
| EOF | ||||
| logger -expect error "^Latch inferred for signal `\\top\.\$unnamed_block\$1\.y' from always_comb process" 1 | ||||
| proc | ||||
							
								
								
									
										17
									
								
								tests/verilog/always_comb_latch_4.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								tests/verilog/always_comb_latch_4.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| read_verilog -sv <<EOF | ||||
| module top; | ||||
| parameter AVOID_LATCH = 0; | ||||
| logic x, z; | ||||
| assign z = 1'b1; | ||||
| always_comb begin | ||||
|     logic y; | ||||
|     if (z) | ||||
|         y = 0; | ||||
|     for (int i = 1; i == AVOID_LATCH; i++) | ||||
|         y = 1; | ||||
|     x = z ? y : 1'b0; | ||||
| end | ||||
| endmodule | ||||
| EOF | ||||
| logger -expect error "^Latch inferred for signal `\\top\.\$unnamed_block\$3\.y' from always_comb process" 1 | ||||
| proc | ||||
							
								
								
									
										16
									
								
								tests/verilog/always_comb_nolatch_1.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								tests/verilog/always_comb_nolatch_1.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| read_verilog -sv <<EOF | ||||
| module top; | ||||
| logic [4:0] x; | ||||
| logic z; | ||||
| assign z = 1'b1; | ||||
| always_comb begin | ||||
|     x = '0; | ||||
|     if (z) begin | ||||
|         for (int i = 0; i < 5; i++) begin | ||||
|             x[i] = 1'b1; | ||||
|         end | ||||
|     end | ||||
| end | ||||
| endmodule | ||||
| EOF | ||||
| proc | ||||
							
								
								
									
										17
									
								
								tests/verilog/always_comb_nolatch_2.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								tests/verilog/always_comb_nolatch_2.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| read_verilog -sv <<EOF | ||||
| module top; | ||||
| logic [4:0] x; | ||||
| logic z; | ||||
| assign z = 1'b1; | ||||
| always_comb begin | ||||
|     x = '0; | ||||
|     if (z) begin | ||||
|         int i; | ||||
|         for (i = 0; i < 5; i++) begin | ||||
|             x[i] = 1'b1; | ||||
|         end | ||||
|     end | ||||
| end | ||||
| endmodule | ||||
| EOF | ||||
| proc | ||||
							
								
								
									
										21
									
								
								tests/verilog/always_comb_nolatch_3.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								tests/verilog/always_comb_nolatch_3.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| read_verilog -sv <<EOF | ||||
| module top; | ||||
| logic x; | ||||
| logic z; | ||||
| assign z = 1'b1; | ||||
| always_comb begin | ||||
|     logic y; | ||||
|     case (x) | ||||
|     1'b0: | ||||
|         y = 1; | ||||
|     default: | ||||
|         y = 0; | ||||
|     endcase | ||||
|     if (z) | ||||
|         x = y; | ||||
|     else | ||||
|         x = 1'b0; | ||||
| end | ||||
| endmodule | ||||
| EOF | ||||
| proc | ||||
							
								
								
									
										16
									
								
								tests/verilog/always_comb_nolatch_4.ys
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								tests/verilog/always_comb_nolatch_4.ys
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| read_verilog -sv <<EOF | ||||
| module top; | ||||
| parameter AVOID_LATCH = 1; | ||||
| logic x, z; | ||||
| assign z = 1'b1; | ||||
| always_comb begin | ||||
|     logic y; | ||||
|     if (z) | ||||
|         y = 0; | ||||
|     for (int i = 1; i == AVOID_LATCH; i++) | ||||
|         y = 1; | ||||
|     x = z ? y : 1'b0; | ||||
| end | ||||
| endmodule | ||||
| EOF | ||||
| proc | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue