diff --git a/frontends/verilog/verilog_frontend.cc b/frontends/verilog/verilog_frontend.cc
index ed6ce2ecb..8202ab9d7 100644
--- a/frontends/verilog/verilog_frontend.cc
+++ b/frontends/verilog/verilog_frontend.cc
@@ -158,6 +158,9 @@ struct VerilogFrontend : public Frontend {
 		log("        delete (* whitebox *) and (* lib_whitebox *) attributes from\n");
 		log("        all modules.\n");
 		log("\n");
+		log("    -specify\n");
+		log("        parse and import specify blocks\n");
+		log("\n");
 		log("    -noopt\n");
 		log("        don't perform basic optimizations (such as const folding) in the\n");
 		log("        high-level front-end.\n");
@@ -228,6 +231,8 @@ struct VerilogFrontend : public Frontend {
 		bool flag_nooverwrite = false;
 		bool flag_overwrite = false;
 		bool flag_defer = false;
+		bool flag_noblackbox = false;
+		bool flag_nowb = false;
 		std::map<std::string, std::string> defines_map;
 		std::list<std::string> include_dirs;
 		std::list<std::string> attributes;
@@ -237,9 +242,8 @@ struct VerilogFrontend : public Frontend {
 		formal_mode = false;
 		norestrict_mode = false;
 		assume_asserts_mode = false;
-		noblackbox_mode = false;
 		lib_mode = false;
-		nowb_mode = false;
+		specify_mode = false;
 		default_nettype_wire = true;
 
 		log_header(design, "Executing Verilog-2005 frontend.\n");
@@ -342,7 +346,7 @@ struct VerilogFrontend : public Frontend {
 				continue;
 			}
 			if (arg == "-noblackbox") {
-				noblackbox_mode = true;
+				flag_noblackbox = true;
 				continue;
 			}
 			if (arg == "-lib") {
@@ -351,7 +355,11 @@ struct VerilogFrontend : public Frontend {
 				continue;
 			}
 			if (arg == "-nowb") {
-				nowb_mode = true;
+				flag_nowb = true;
+				continue;
+			}
+			if (arg == "-specify") {
+				specify_mode = true;
 				continue;
 			}
 			if (arg == "-noopt") {
@@ -450,7 +458,7 @@ struct VerilogFrontend : public Frontend {
 			error_on_dpi_function(current_ast);
 
 		AST::process(design, current_ast, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches,
-				flag_nomeminit, flag_nomem2reg, flag_mem2reg, noblackbox_mode, lib_mode, nowb_mode, flag_noopt, flag_icells, flag_nooverwrite, flag_overwrite, flag_defer, default_nettype_wire);
+				flag_nomeminit, flag_nomem2reg, flag_mem2reg, flag_noblackbox, lib_mode, flag_nowb, flag_noopt, flag_icells, flag_nooverwrite, flag_overwrite, flag_defer, default_nettype_wire);
 
 		if (!flag_nopp)
 			delete lexin;
diff --git a/frontends/verilog/verilog_frontend.h b/frontends/verilog/verilog_frontend.h
index ca40946cb..a7c9b2fe6 100644
--- a/frontends/verilog/verilog_frontend.h
+++ b/frontends/verilog/verilog_frontend.h
@@ -69,14 +69,11 @@ namespace VERILOG_FRONTEND
 	// running in -assert-assumes mode
 	extern bool assert_assumes_mode;
 
-	// running in -noblackbox mode
-	extern bool noblackbox_mode;
-
 	// running in -lib mode
 	extern bool lib_mode;
 
-	// running in -nowb mode
-	extern bool nowb_mode;
+	// running in -specify mode
+	extern bool specify_mode;
 
 	// lexer input stream
 	extern std::istream *lexin;
diff --git a/frontends/verilog/verilog_lexer.l b/frontends/verilog/verilog_lexer.l
index 6ef38252a..b73ccf5c1 100644
--- a/frontends/verilog/verilog_lexer.l
+++ b/frontends/verilog/verilog_lexer.l
@@ -148,7 +148,7 @@ YOSYS_NAMESPACE_END
 "endfunction"  { return TOK_ENDFUNCTION; }
 "task"         { return TOK_TASK; }
 "endtask"      { return TOK_ENDTASK; }
-"specify"      { return TOK_SPECIFY; }
+"specify"      { return specify_mode ? TOK_SPECIFY : TOK_IGNORED_SPECIFY; }
 "endspecify"   { return TOK_ENDSPECIFY; }
 "specparam"    { return TOK_SPECPARAM; }
 "package"      { SV_KEYWORD(TOK_PACKAGE); }
@@ -411,6 +411,11 @@ import[ \t\r\n]+\"(DPI|DPI-C)\"[ \t\r\n]+function[ \t\r\n]+ {
 "+:" { return TOK_POS_INDEXED; }
 "-:" { return TOK_NEG_INDEXED; }
 
+[-+]?[=*]> {
+	frontend_verilog_yylval.string = new std::string(yytext);
+	return TOK_SPECIFY_OPER;
+}
+
 "/*" { BEGIN(COMMENT); }
 <COMMENT>.    /* ignore comment body */
 <COMMENT>\n   /* ignore comment body */
diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y
index 40968d17a..2e6863302 100644
--- a/frontends/verilog/verilog_parser.y
+++ b/frontends/verilog/verilog_parser.y
@@ -59,7 +59,7 @@ namespace VERILOG_FRONTEND {
 	std::vector<char> case_type_stack;
 	bool do_not_require_port_stubs;
 	bool default_nettype_wire;
-	bool sv_mode, formal_mode, noblackbox_mode, lib_mode, nowb_mode;
+	bool sv_mode, formal_mode, lib_mode, specify_mode;
 	bool noassert_mode, noassume_mode, norestrict_mode;
 	bool assume_asserts_mode, assert_assumes_mode;
 	bool current_wire_rand, current_wire_const;
@@ -94,6 +94,20 @@ static void free_attr(std::map<std::string, AstNode*> *al)
 	delete al;
 }
 
+struct specify_target {
+	char polarity_op;
+	AstNode *dst, *dat;
+};
+
+struct specify_triple {
+	AstNode *t_min, *t_avg, *t_max;
+};
+
+struct specify_rise_fall {
+	specify_triple rise;
+	specify_triple fall;
+};
+
 %}
 
 %name-prefix "frontend_verilog_yy"
@@ -102,10 +116,15 @@ static void free_attr(std::map<std::string, AstNode*> *al)
 	std::string *string;
 	struct YOSYS_NAMESPACE_PREFIX AST::AstNode *ast;
 	std::map<std::string, YOSYS_NAMESPACE_PREFIX AST::AstNode*> *al;
+	struct specify_target *specify_target_ptr;
+	struct specify_triple *specify_triple_ptr;
+	struct specify_rise_fall *specify_rise_fall_ptr;
 	bool boolean;
+	char ch;
 }
 
-%token <string> TOK_STRING TOK_ID TOK_CONSTVAL TOK_REALVAL TOK_PRIMITIVE TOK_SVA_LABEL
+%token <string> TOK_STRING TOK_ID TOK_CONSTVAL TOK_REALVAL TOK_PRIMITIVE
+%token <string> TOK_SVA_LABEL TOK_SPECIFY_OPER
 %token TOK_ASSERT TOK_ASSUME TOK_RESTRICT TOK_COVER
 %token ATTR_BEGIN ATTR_END DEFATTR_BEGIN DEFATTR_END
 %token TOK_MODULE TOK_ENDMODULE TOK_PARAMETER TOK_LOCALPARAM TOK_DEFPARAM
@@ -116,7 +135,8 @@ static void free_attr(std::map<std::string, AstNode*> *al)
 %token TOK_BEGIN TOK_END TOK_IF TOK_ELSE TOK_FOR TOK_WHILE TOK_REPEAT
 %token TOK_DPI_FUNCTION TOK_POSEDGE TOK_NEGEDGE TOK_OR TOK_AUTOMATIC
 %token TOK_CASE TOK_CASEX TOK_CASEZ TOK_ENDCASE TOK_DEFAULT
-%token TOK_FUNCTION TOK_ENDFUNCTION TOK_TASK TOK_ENDTASK TOK_SPECIFY TOK_ENDSPECIFY TOK_SPECPARAM
+%token TOK_FUNCTION TOK_ENDFUNCTION TOK_TASK TOK_ENDTASK TOK_SPECIFY
+%token TOK_IGNORED_SPECIFY TOK_ENDSPECIFY TOK_SPECPARAM
 %token TOK_GENERATE TOK_ENDGENERATE TOK_GENVAR TOK_REAL
 %token TOK_SYNOPSYS_FULL_CASE TOK_SYNOPSYS_PARALLEL_CASE
 %token TOK_SUPPLY0 TOK_SUPPLY1 TOK_TO_SIGNED TOK_TO_UNSIGNED
@@ -130,6 +150,12 @@ static void free_attr(std::map<std::string, AstNode*> *al)
 %type <boolean> opt_signed opt_property unique_case_attr
 %type <al> attr case_attr
 
+%type <specify_target_ptr> specify_target
+%type <specify_triple_ptr> specify_triple
+%type <specify_rise_fall_ptr> specify_rise_fall
+%type <ast> specify_if
+%type <ch> specify_edge
+
 // operator precedence from low to high
 %left OP_LOR
 %left OP_LAND
@@ -539,7 +565,7 @@ module_body:
 
 module_body_stmt:
 	task_func_decl | specify_block |param_decl | localparam_decl | defparam_decl | specparam_declaration | wire_decl | assign_stmt | cell_stmt |
-	always_stmt | TOK_GENERATE module_gen_body TOK_ENDGENERATE | defattr | assert_property | checker_decl;
+	always_stmt | TOK_GENERATE module_gen_body TOK_ENDGENERATE | defattr | assert_property | checker_decl | ignored_specify_block;
 
 checker_decl:
 	TOK_CHECKER TOK_ID ';' {
@@ -697,15 +723,181 @@ task_func_body:
 	task_func_body behavioral_stmt |
 	/* empty */;
 
-specify_block:
-	TOK_SPECIFY specify_item_opt TOK_ENDSPECIFY |
-	TOK_SPECIFY TOK_ENDSPECIFY ;
+/*************************** specify parser ***************************/
 
-specify_item_opt:
-	specify_item_opt specify_item |
-	specify_item ;
+specify_block:
+	TOK_SPECIFY specify_item_list TOK_ENDSPECIFY;
+
+specify_item_list:
+	specify_item specify_item_list |
+	/* empty */;
 
 specify_item:
+	specify_if '(' specify_edge expr TOK_SPECIFY_OPER specify_target ')' '=' specify_rise_fall ';' {
+		AstNode *en_expr = $1;
+		char specify_edge = $3;
+		AstNode *src_expr = $4;
+		string *oper = $5;
+		specify_target *target = $6;
+		specify_rise_fall *timing = $9;
+
+		if (specify_edge != 0 && target->dat == nullptr)
+			frontend_verilog_yyerror("Found specify edge but no data spec.\n");
+
+		AstNode *cell = new AstNode(AST_CELL);
+		ast_stack.back()->children.push_back(cell);
+		cell->str = stringf("$specify$%d", autoidx++);
+		cell->children.push_back(new AstNode(AST_CELLTYPE));
+		cell->children.back()->str = target->dat ? "$specify3" : "$specify2";
+
+		char oper_polarity = 0;
+		char oper_type = oper->at(0);
+
+		if (oper->size() == 3) {
+			oper_polarity = oper->at(0);
+			oper_type = oper->at(1);
+		}
+
+		cell->children.push_back(new AstNode(AST_PARASET, AstNode::mkconst_int(oper_type == '*', false, 1)));
+		cell->children.back()->str = "\\FULL";
+
+		cell->children.push_back(new AstNode(AST_PARASET, AstNode::mkconst_int(oper_polarity != 0, false, 1)));
+		cell->children.back()->str = "\\SRC_DST_PEN";
+
+		cell->children.push_back(new AstNode(AST_PARASET, AstNode::mkconst_int(oper_polarity == '+', false, 1)));
+		cell->children.back()->str = "\\SRC_DST_POL";
+
+		cell->children.push_back(new AstNode(AST_PARASET, timing->rise.t_min));
+		cell->children.back()->str = "\\T_RISE_MIN";
+
+		cell->children.push_back(new AstNode(AST_PARASET, timing->rise.t_avg));
+		cell->children.back()->str = "\\T_RISE_AVG";
+
+		cell->children.push_back(new AstNode(AST_PARASET, timing->rise.t_max));
+		cell->children.back()->str = "\\T_RISE_MAX";
+
+		cell->children.push_back(new AstNode(AST_PARASET, timing->fall.t_min));
+		cell->children.back()->str = "\\T_FALL_MIN";
+
+		cell->children.push_back(new AstNode(AST_PARASET, timing->fall.t_avg));
+		cell->children.back()->str = "\\T_FALL_AVG";
+
+		cell->children.push_back(new AstNode(AST_PARASET, timing->fall.t_max));
+		cell->children.back()->str = "\\T_FALL_MAX";
+
+		cell->children.push_back(new AstNode(AST_ARGUMENT, en_expr ? en_expr : AstNode::mkconst_int(1, false, 1)));
+		cell->children.back()->str = "\\EN";
+
+		cell->children.push_back(new AstNode(AST_ARGUMENT, src_expr));
+		cell->children.back()->str = "\\SRC";
+
+		cell->children.push_back(new AstNode(AST_ARGUMENT, target->dst));
+		cell->children.back()->str = "\\DST";
+
+		if (target->dat)
+		{
+			cell->children.push_back(new AstNode(AST_PARASET, AstNode::mkconst_int(specify_edge != 0, false, 1)));
+			cell->children.back()->str = "\\EDGE_EN";
+
+			cell->children.push_back(new AstNode(AST_PARASET, AstNode::mkconst_int(specify_edge == 'p', false, 1)));
+			cell->children.back()->str = "\\EDGE_POL";
+
+			cell->children.push_back(new AstNode(AST_PARASET, AstNode::mkconst_int(target->polarity_op != 0, false, 1)));
+			cell->children.back()->str = "\\DAT_DST_PEN";
+
+			cell->children.push_back(new AstNode(AST_PARASET, AstNode::mkconst_int(target->polarity_op == '+', false, 1)));
+			cell->children.back()->str = "\\DAT_DST_POL";
+
+			cell->children.push_back(new AstNode(AST_ARGUMENT, target->dat));
+			cell->children.back()->str = "\\DAT";
+		}
+
+		delete oper;
+		delete target;
+		delete timing;
+	};
+
+specify_if:
+	TOK_IF '(' expr ')' {
+		$$ = $3;
+	} |
+	/* empty */ {
+		$$ = nullptr;
+	};
+
+specify_target:
+	expr {
+		$$ = new specify_target;
+		$$->polarity_op = 0;
+		$$->dst = $1;
+		$$->dat = nullptr;
+	} |
+	'(' expr ':' expr ')'{
+		$$ = new specify_target;
+		$$->polarity_op = 0;
+		$$->dst = $2;
+		$$->dat = $4;
+	} |
+	'(' expr TOK_NEG_INDEXED expr ')'{
+		$$ = new specify_target;
+		$$->polarity_op = '-';
+		$$->dst = $2;
+		$$->dat = $4;
+	} |
+	'(' expr TOK_POS_INDEXED expr ')'{
+		$$ = new specify_target;
+		$$->polarity_op = '+';
+		$$->dst = $2;
+		$$->dat = $4;
+	};
+
+specify_edge:
+	TOK_POSEDGE { $$ = 'p'; } |
+	TOK_NEGEDGE { $$ = 'n'; } |
+	{ $$ = 0; };
+
+specify_rise_fall:
+	specify_triple {
+		$$ = new specify_rise_fall;
+		$$->rise = *$1;
+		$$->fall.t_min = $1->t_min->clone();
+		$$->fall.t_avg = $1->t_avg->clone();
+		$$->fall.t_max = $1->t_max->clone();
+		delete $1;
+	} |
+	'(' specify_triple ',' specify_triple ')' {
+		$$ = new specify_rise_fall;
+		$$->rise = *$2;
+		$$->fall = *$4;
+		delete $2;
+		delete $4;
+	};
+
+specify_triple:
+	expr {
+		$$ = new specify_triple;
+		$$->t_min = $1;
+		$$->t_avg = $1->clone();
+		$$->t_max = $1->clone();
+	} |
+	expr ':' expr ':' expr {
+		$$ = new specify_triple;
+		$$->t_min = $1;
+		$$->t_avg = $3;
+		$$->t_max = $5;
+	};
+
+/******************** ignored specify parser **************************/
+
+ignored_specify_block:
+	TOK_IGNORED_SPECIFY ignored_specify_item_opt TOK_ENDSPECIFY |
+	TOK_IGNORED_SPECIFY TOK_ENDSPECIFY ;
+
+ignored_specify_item_opt:
+	ignored_specify_item_opt ignored_specify_item |
+	ignored_specify_item ;
+
+ignored_specify_item:
 	specparam_declaration
 	// | pulsestyle_declaration
 	// | showcancelled_declaration
@@ -721,13 +913,13 @@ specparam_declaration:
 // and the 'non_opt_range' rule allows index ranges not allowed by 1364-2005
 // exxxxtending this for SV specparam would change this anyhow
 specparam_range:
-	'[' constant_expression ':' constant_expression ']' ;
+	'[' ignspec_constant_expression ':' ignspec_constant_expression ']' ;
 
 list_of_specparam_assignments:
 	specparam_assignment | list_of_specparam_assignments ',' specparam_assignment;
 
 specparam_assignment:
-	TOK_ID '=' constant_mintypmax_expression ;
+	ignspec_id '=' constant_mintypmax_expression ;
 
 /*
 pulsestyle_declaration :
@@ -802,19 +994,19 @@ opt_polarity_operator :
 
 // Good enough for the time being
 specify_input_terminal_descriptor :
-	TOK_ID ;
+	ignspec_id ;
 
 // Good enough for the time being
 specify_output_terminal_descriptor :
-	TOK_ID ;
+	ignspec_id ;
 
 system_timing_declaration :
-	TOK_ID '(' system_timing_args ')' ';' ;
+	ignspec_id '(' system_timing_args ')' ';' ;
 
 system_timing_arg :
-	TOK_POSEDGE TOK_ID |
-	TOK_NEGEDGE TOK_ID |
-	expr ;
+	TOK_POSEDGE ignspec_id |
+	TOK_NEGEDGE ignspec_id |
+	ignspec_expr ;
 
 system_timing_args :
 	system_timing_arg |
@@ -871,19 +1063,27 @@ tzx_path_delay_expression :
 */
 
 path_delay_expression :
-	constant_expression;
+	ignspec_constant_expression;
 
 constant_mintypmax_expression :
-	constant_expression
-	| constant_expression ':' constant_expression ':' constant_expression
+	ignspec_constant_expression
+	| ignspec_constant_expression ':' ignspec_constant_expression ':' ignspec_constant_expression
 	;
 
 // for the time being this is OK, but we may write our own expr here.
 // as I'm not sure it is legal to use a full expr here (probably not)
 // On the other hand, other rules requiring constant expressions also use 'expr'
 // (such as param assignment), so we may leave this as-is, perhaps adding runtime checks for constant-ness
-constant_expression:
-	expr ;
+ignspec_constant_expression:
+	expr { delete $1; };
+
+ignspec_expr:
+	expr { delete $1; };
+
+ignspec_id:
+	TOK_ID { delete $1; };
+
+/**********************************************************************/
 
 param_signed:
 	TOK_SIGNED {
diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc
index 7e1159cac..7d5334eb1 100644
--- a/kernel/rtlil.cc
+++ b/kernel/rtlil.cc
@@ -1194,6 +1194,16 @@ namespace {
 				return;
 			}
 
+			if (cell->type == "$specify2") {
+				// FIXME
+				return;
+			}
+
+			if (cell->type == "$specify3") {
+				// FIXME
+				return;
+			}
+
 			if (cell->type == "$_BUF_")    { check_gate("AY"); return; }
 			if (cell->type == "$_NOT_")    { check_gate("AY"); return; }
 			if (cell->type == "$_AND_")    { check_gate("ABY"); return; }