Add support for parsing the SystemVerilog 'bind' construct
authorRupert Swarbrick <rswarbrick@gmail.com>
Thu, 21 May 2020 16:36:29 +0000 (17:36 +0100)
committerZachary Snow <zachary.j.snow@gmail.com>
Fri, 16 Jul 2021 13:31:39 +0000 (09:31 -0400)
This doesn't do anything useful yet: the patch just adds support for
the syntax to the lexer and parser and adds some tests to check the
syntax parses properly. This generates AST nodes, but doesn't yet
generate RTLIL.

Since our existing hierarchical_identifier parser doesn't allow bit
selects (so you can't do something like foo[1].bar[2].baz), I've also
not added support for a trailing bit select (the "constant_bit_select"
non-terminal in "bind_target_instance" in the spec). If we turn out to
need this in future, we'll want to augment hierarchical_identifier and
its other users too.

Note that you can't easily use the BNF from the spec:

    bind_directive ::=
        "bind" bind_target_scope [ : bind_target_instance_list]
               bind_instantiation ;
      | "bind" bind_target_instance bind_instantiation ;

even if you fix the lookahead problem, because code like this matches
both branches in the BNF:

    bind a b b_i (.*);

The problem is that 'a' could either be a module name or a degenerate
hierarchical reference. This seems to be a genuine syntactic
ambiguity, which the spec resolves (p739) by saying that we have to
wait until resolution time (the hierarchy pass) and take whatever is
defined, treating 'a' as an instance name if it names both an instance
and a module.

To keep the parser simple, it currently accepts this invalid syntax:

    bind a.b : c d e (.*);

This is invalid because we're in the first branch of the BNF above, so
the "a.b" term should match bind_target_scope: a module or interface
identifier, not an arbitrary hierarchical identifier.

This will fail in the hierarchy pass (when it's implemented in a
future patch).

19 files changed:
frontends/ast/ast.cc
frontends/ast/ast.h
frontends/ast/genrtlil.cc
frontends/verilog/verilog_lexer.l
frontends/verilog/verilog_parser.y
tests/bind/.gitignore [new file with mode: 0644]
tests/bind/basic.sv [new file with mode: 0644]
tests/bind/basic.ys [new file with mode: 0644]
tests/bind/cell_list.sv [new file with mode: 0644]
tests/bind/cell_list.ys [new file with mode: 0644]
tests/bind/hier.sv [new file with mode: 0644]
tests/bind/hier.ys [new file with mode: 0644]
tests/bind/inst_list.sv [new file with mode: 0644]
tests/bind/inst_list.ys [new file with mode: 0644]
tests/bind/param.sv [new file with mode: 0644]
tests/bind/param.ys [new file with mode: 0644]
tests/bind/run-test.sh [new file with mode: 0755]
tests/bind/toplevel.sv [new file with mode: 0644]
tests/bind/toplevel.ys [new file with mode: 0644]

index f33b76785603bbc9e38b59d173fda554a76b9e36..e42cf0348a68a406ed5bafdf14801db5bb1e289f 100644 (file)
@@ -177,6 +177,7 @@ std::string AST::type2str(AstNodeType type)
        X(AST_STRUCT)
        X(AST_UNION)
        X(AST_STRUCT_ITEM)
+       X(AST_BIND)
 #undef X
        default:
                log_abort();
index 60c7de32d8f0362d948d327240dbf03b5b379354..57ce5605f4c7ca175ac8bf4ab8c0e2b52504e5d0 100644 (file)
@@ -160,7 +160,8 @@ namespace AST
                AST_TYPEDEF,
                AST_STRUCT,
                AST_UNION,
-               AST_STRUCT_ITEM
+               AST_STRUCT_ITEM,
+               AST_BIND
        };
 
        struct AstSrcLocType {
index 29d0bddefd0e449d5dd68731fc80e70c942b7d75..7fa751e2482c39ec0f70b8edee2056deb38af3a8 100644 (file)
@@ -1963,6 +1963,11 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint)
                        }
                } break;
 
+       case AST_BIND: {
+               // The bind construct. Currently unimplemented: just ignore it.
+               break;
+       }
+
        case AST_FCALL: {
                        if (str == "\\$anyconst" || str == "\\$anyseq" || str == "\\$allconst" || str == "\\$allseq")
                        {
index 55e8b48b9a96799c9ecbf6eabdd56c8bc70de4f3..54fb652403a602ce679ac4fd12821b28ea704041 100644 (file)
@@ -260,6 +260,7 @@ static bool isUserType(std::string &s)
 "const"      { if (formal_mode) return TOK_CONST; SV_KEYWORD(TOK_CONST); }
 "checker"    { if (formal_mode) return TOK_CHECKER; SV_KEYWORD(TOK_CHECKER); }
 "endchecker" { if (formal_mode) return TOK_ENDCHECKER; SV_KEYWORD(TOK_ENDCHECKER); }
+"bind"       { if (formal_mode) return TOK_BIND; SV_KEYWORD(TOK_BIND); }
 "final"      { SV_KEYWORD(TOK_FINAL); }
 "logic"      { SV_KEYWORD(TOK_LOGIC); }
 "var"        { SV_KEYWORD(TOK_VAR); }
index 9558b0e873c2b0bf68d5dc23008c6a4196d2613c..de463b47deb066f2d8250b26162f6579c7a29966 100644 (file)
@@ -299,6 +299,7 @@ static void checkLabelsMatch(const char *element, const std::string *before, con
 %token TOK_BIT_OR_ASSIGN TOK_BIT_AND_ASSIGN TOK_BIT_XOR_ASSIGN TOK_ADD_ASSIGN
 %token TOK_SUB_ASSIGN TOK_DIV_ASSIGN TOK_MOD_ASSIGN TOK_MUL_ASSIGN
 %token TOK_SHL_ASSIGN TOK_SHR_ASSIGN TOK_SSHL_ASSIGN TOK_SSHR_ASSIGN
+%token TOK_BIND
 
 %type <ast> range range_or_multirange non_opt_range non_opt_multirange
 %type <ast> wire_type expr basic_expr concat_list rvalue lvalue lvalue_concat_list non_io_wire_type io_wire_type
@@ -364,6 +365,7 @@ design:
        typedef_decl design |
        package design |
        interface design |
+       bind_directive design |
        %empty;
 
 attr:
@@ -636,7 +638,67 @@ interface_body:
 
 interface_body_stmt:
        param_decl | localparam_decl | typedef_decl | defparam_decl | wire_decl | always_stmt | assign_stmt |
-       modport_stmt;
+       modport_stmt | bind_directive;
+
+bind_directive:
+       TOK_BIND {
+               AstNode *bnode = new AstNode(AST_BIND);
+               ast_stack.back()->children.push_back(bnode);
+               ast_stack.push_back(bnode);
+       }
+       bind_target {
+               // bind_target should have added at least one child
+               log_assert(ast_stack.back()->children.size() >= 1);
+       }
+       TOK_ID {
+               // The single_cell parser in cell_list_no_array uses astbuf1 as
+               // a sort of template for constructing cells.
+               astbuf1 = new AstNode(AST_CELL);
+               astbuf1->children.push_back(new AstNode(AST_CELLTYPE));
+               astbuf1->children[0]->str = *$5;
+               delete $5;
+       }
+       cell_parameter_list_opt cell_list_no_array ';' {
+               // cell_list should have added at least one more child
+               log_assert(ast_stack.back()->children.size() >= 2);
+               delete astbuf1;
+               ast_stack.pop_back();
+       };
+
+// bind_target matches the target of the bind (everything before
+// bind_instantiation in the IEEE 1800 spec).
+//
+// We can't use the BNF from the spec directly because it's ambiguous:
+// something like "bind foo bar_i (.*)" can either be interpreted with "foo" as
+// a module or interface identifier (matching bind_target_scope in the spec) or
+// by considering foo as a degenerate hierarchical identifier with no '.'
+// characters, followed by no bit select (which matches bind_target_instance in
+// the spec).
+//
+// Instead, we resolve everything as an instance name and then deal with the
+// ambiguity when converting to RTLIL / in the hierarchy pass.
+bind_target:
+       bind_target_instance opt_bind_target_instance_list;
+
+// An optional list of target instances for a bind statement, introduced by a
+// colon.
+opt_bind_target_instance_list:
+       ':' bind_target_instance_list |
+       %empty;
+
+bind_target_instance_list:
+       bind_target_instance |
+       bind_target_instance_list ',' bind_target_instance;
+
+// A single target instance for a bind statement. The top of ast_stack will be
+// the bind node where we should add it.
+bind_target_instance:
+       hierarchical_id {
+               auto *node = new AstNode(AST_IDENTIFIER);
+               node->str = *$1;
+               delete $1;
+               ast_stack.back()->children.push_back(node);
+       };
 
 mintypmax_expr:
        expr { delete $1; } |
@@ -813,7 +875,7 @@ module_body:
 
 module_body_stmt:
        task_func_decl | specify_block | param_decl | localparam_decl | typedef_decl | defparam_decl | specparam_declaration | wire_decl | assign_stmt | cell_stmt |
-       enum_decl | struct_decl |
+       enum_decl | struct_decl | bind_directive |
        always_stmt | TOK_GENERATE module_gen_body TOK_ENDGENERATE | defattr | assert_property | checker_decl | ignored_specify_block;
 
 checker_decl:
@@ -1975,6 +2037,9 @@ cell_list:
        cell_list ',' single_cell;
 
 single_cell:
+       single_cell_no_array | single_cell_arraylist;
+
+single_cell_no_array:
        TOK_ID {
                astbuf2 = astbuf1->clone();
                if (astbuf2->type != AST_PRIMITIVE)
@@ -1983,7 +2048,9 @@ single_cell:
                ast_stack.back()->children.push_back(astbuf2);
        } '(' cell_port_list ')' {
                SET_AST_NODE_LOC(astbuf2, @1, @$);
-       } |
+       }
+
+single_cell_arraylist:
        TOK_ID non_opt_range {
                astbuf2 = astbuf1->clone();
                if (astbuf2->type != AST_PRIMITIVE)
@@ -1994,6 +2061,10 @@ single_cell:
                SET_AST_NODE_LOC(astbuf2, @1, @$);
        };
 
+cell_list_no_array:
+       single_cell_no_array |
+       cell_list_no_array ',' single_cell_no_array;
+
 prim_list:
        single_prim |
        prim_list ',' single_prim;
diff --git a/tests/bind/.gitignore b/tests/bind/.gitignore
new file mode 100644 (file)
index 0000000..8355de9
--- /dev/null
@@ -0,0 +1,2 @@
+*.log
+run-test.mk
diff --git a/tests/bind/basic.sv b/tests/bind/basic.sv
new file mode 100644 (file)
index 0000000..ce0d04c
--- /dev/null
@@ -0,0 +1,20 @@
+// A basic example of the bind construct
+
+module foo (input logic a, input logic b, output logic c);
+  // Magic happens here...
+endmodule
+
+module bar (input a, input b, output c);
+  assign c = a ^ b;
+endmodule
+
+module top ();
+  logic u, v, w;
+  foo foo_i (.a (u), .b (v), .c (w));
+
+  bind foo bar bound_i (.*);
+
+  always_comb begin
+    assert(w == u ^ v);
+  end
+endmodule
diff --git a/tests/bind/basic.ys b/tests/bind/basic.ys
new file mode 100644 (file)
index 0000000..266fa4e
--- /dev/null
@@ -0,0 +1 @@
+read_verilog -sv basic.sv
diff --git a/tests/bind/cell_list.sv b/tests/bind/cell_list.sv
new file mode 100644 (file)
index 0000000..c0da13d
--- /dev/null
@@ -0,0 +1,26 @@
+// An example of specifying multiple bind instances in a single directive. This
+// also uses explicit bound names.
+
+module foo (input logic a0, input logic b0, output logic c0,
+            input logic a1, input logic b1, output logic c1);
+  // Magic happens here...
+endmodule
+
+module bar (input a, input b, output c);
+  assign c = a ^ b;
+endmodule
+
+module top ();
+  logic u0, v0, w0;
+  logic u1, v1, w1;
+
+  foo foo0 (.a0 (u0), .b0 (v0), .c0 (w0),
+            .a1 (u1), .b1 (v1), .c1 (w1));
+
+  bind foo bar bar0 (.a(a0), .b(b0), .c(c0)), bar1 (.a(a1), .b(b1), .c(c1));
+
+  always_comb begin
+    assert(w0 == u0 ^ v0);
+    assert(w1 == u1 ^ v1);
+  end
+endmodule
diff --git a/tests/bind/cell_list.ys b/tests/bind/cell_list.ys
new file mode 100644 (file)
index 0000000..9afd9a9
--- /dev/null
@@ -0,0 +1 @@
+read_verilog -sv cell_list.sv
diff --git a/tests/bind/hier.sv b/tests/bind/hier.sv
new file mode 100644 (file)
index 0000000..fd3bc62
--- /dev/null
@@ -0,0 +1,20 @@
+// An example of the bind construct using a hierarchical reference starting with $root
+
+module foo (input logic a, input logic b, output logic c);
+  // Magic happens here...
+endmodule
+
+module bar (input a, input b, output c);
+  assign c = a ^ b;
+endmodule
+
+module top ();
+  logic u, v, w;
+  foo foo_i (.a (u), .b (v), .c (w));
+
+  always_comb begin
+    assert(w == u ^ v);
+  end
+endmodule
+
+bind $root.top.foo_i bar bound_i (.*);
diff --git a/tests/bind/hier.ys b/tests/bind/hier.ys
new file mode 100644 (file)
index 0000000..c19fba1
--- /dev/null
@@ -0,0 +1 @@
+read_verilog -sv hier.sv
diff --git a/tests/bind/inst_list.sv b/tests/bind/inst_list.sv
new file mode 100644 (file)
index 0000000..e0077ca
--- /dev/null
@@ -0,0 +1,24 @@
+// An example of specifying multiple bind targets with an instance list
+
+module foo (input logic a, input logic b, output logic c);
+  // Magic happens here...
+endmodule
+
+module bar (input a, input b, output c);
+  assign c = a ^ b;
+endmodule
+
+module top ();
+  logic u0, v0, w0;
+  logic u1, v1, w1;
+
+  foo foo0 (.a (u0), .b (v0), .c (w0));
+  foo foo1 (.a (u1), .b (v1), .c (w1));
+
+  bind foo : foo0, foo1 bar bound_i (.*);
+
+  always_comb begin
+    assert(w0 == u0 ^ v0);
+    assert(w1 == u1 ^ v1);
+  end
+endmodule
diff --git a/tests/bind/inst_list.ys b/tests/bind/inst_list.ys
new file mode 100644 (file)
index 0000000..ac1385b
--- /dev/null
@@ -0,0 +1 @@
+read_verilog -sv inst_list.sv
diff --git a/tests/bind/param.sv b/tests/bind/param.sv
new file mode 100644 (file)
index 0000000..c779352
--- /dev/null
@@ -0,0 +1,26 @@
+// An example showing how parameters get inferred when binding
+
+module foo (input logic a, input logic b, output logic c);
+  parameter doit = 1;
+
+  // Magic happens here...
+endmodule
+
+module bar (input a, input b, output c);
+  parameter doit = 1;
+
+  assign c = doit ? a ^ b : 0;
+endmodule
+
+module top (input u0, input v0, output w0,
+            input u1, input v1, output w1);
+  foo #(.doit (0)) foo0 (.a (u0), .b (v0), .c (w0));
+  foo #(.doit (1)) foo1 (.a (u1), .b (v1), .c (w1));
+
+  bind foo bar #(.doit (doit)) bound_i (.*);
+
+  always_comb begin
+    assert (w0 == '0);
+    assert (w1 == u1 ^ v1);
+  end
+endmodule
diff --git a/tests/bind/param.ys b/tests/bind/param.ys
new file mode 100644 (file)
index 0000000..a43d057
--- /dev/null
@@ -0,0 +1 @@
+read_verilog -sv param.sv
diff --git a/tests/bind/run-test.sh b/tests/bind/run-test.sh
new file mode 100755 (executable)
index 0000000..ea56b70
--- /dev/null
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+set -e
+{
+echo "all::"
+for x in *.ys; do
+       echo "all:: run-$x"
+       echo "run-$x:"
+       echo "  @echo 'Running $x..'"
+       echo "  @../../yosys -ql ${x%.ys}.log $x"
+done
+for s in *.sh; do
+       if [ "$s" != "run-test.sh" ]; then
+               echo "all:: run-$s"
+               echo "run-$s:"
+               echo "  @echo 'Running $s..'"
+               echo "  @bash $s"
+       fi
+done
+} > run-test.mk
+exec ${MAKE:-make} -f run-test.mk
diff --git a/tests/bind/toplevel.sv b/tests/bind/toplevel.sv
new file mode 100644 (file)
index 0000000..328edcf
--- /dev/null
@@ -0,0 +1,20 @@
+// The bind construct occurring at top-level in the script
+
+module foo (input logic a, input logic b, output logic c);
+  // Magic happens here...
+endmodule
+
+module bar (input a, input b, output c);
+  assign c = a ^ b;
+endmodule
+
+module top ();
+  logic u, v, w;
+  foo foo_i (.a (u), .b (v), .c (w));
+
+  always_comb begin
+    assert(w == u ^ v);
+  end
+endmodule
+
+bind top.foo_i bar bound_i (.*);
diff --git a/tests/bind/toplevel.ys b/tests/bind/toplevel.ys
new file mode 100644 (file)
index 0000000..11c0ada
--- /dev/null
@@ -0,0 +1 @@
+read_verilog -sv toplevel.sv