Generalise structs and add support for packed unions.
authorPeter Crozier <peter@crozier.com>
Tue, 12 May 2020 13:25:33 +0000 (14:25 +0100)
committerPeter Crozier <peter@crozier.com>
Tue, 12 May 2020 13:25:33 +0000 (14:25 +0100)
README.md
frontends/ast/ast.cc
frontends/ast/ast.h
frontends/ast/genrtlil.cc
frontends/ast/simplify.cc
frontends/verilog/verilog_lexer.l
frontends/verilog/verilog_parser.y
tests/svtypes/union_simple.sv [new file with mode: 0644]

index 3058220e71786ac9b9234f6ea1b07dac2580891b..770c624591bd045d582f12ed5ca2819c79bfdddc 100644 (file)
--- a/README.md
+++ b/README.md
@@ -556,7 +556,7 @@ from SystemVerilog:
 - enums are supported (including inside packages)
        - but are currently not strongly typed
 
-- structs are supported
+- packed structs and unions are supported.
 
 - SystemVerilog interfaces (SVIs) are supported. Modports for specifying whether
   ports are inputs or outputs are supported.
index 2c16de0a4e2c88623c67256d954be02e1e6d0c12..03fd272dafd43c0af4bf1360353582d4d9cd4bf3 100644 (file)
@@ -172,6 +172,7 @@ std::string AST::type2str(AstNodeType type)
        X(AST_WIRETYPE)
        X(AST_TYPEDEF)
        X(AST_STRUCT)
+       X(AST_UNION)
        X(AST_STRUCT_ITEM)
 #undef X
        default:
index 29bd1b84d618ead0bbac0bc9e5ecb30625f86e96..6d556fae2d3b383721fc817d775abb11ac8a1554 100644 (file)
@@ -158,6 +158,7 @@ namespace AST
                AST_WIRETYPE,
                AST_TYPEDEF,
                AST_STRUCT,
+               AST_UNION,
                AST_STRUCT_ITEM
        };
 
index a57333314c7c75fb88e69b1dbcea9ba384cccbf9..83f34c9e1292ce9f477207381505b0fbe8390056 100644 (file)
@@ -992,6 +992,7 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint)
        case AST_MODPORTMEMBER:
        case AST_TYPEDEF:
        case AST_STRUCT:
+       case AST_UNION:
                break;
        case AST_INTERFACEPORT: {
                // If a port in a module with unknown type is found, mark it with the attribute 'is_interface'
index ce435c26b3a31691b2a2abe496f29778fd335c37..e8ac4b2f26b8fa26e737ea9cb3e2d4d55f8217ca 100644 (file)
@@ -250,6 +250,97 @@ static AstNode *make_range(int left, int right, bool is_signed = false)
        return range;
 }
 
+int size_packed_struct(AstNode *snode, int base_offset)
+{
+       // Struct members will be laid out in the structure contiguously from left to right.
+       // Union members all have zero offset from the start of the union.
+       // Determine total packed size and assign offsets.  Store these in the member node.
+       bool is_union = (snode->type == AST_UNION);
+       int offset = 0;
+       int packed_width = -1;
+       // examine members from last to first
+       for (auto it = snode->children.rbegin(); it != snode->children.rend(); ++it) {
+               auto node = *it;
+               int width;
+               if (node->type == AST_STRUCT || node->type == AST_UNION) {
+                       // embedded struct or union
+                       width = size_packed_struct(node, base_offset + offset);
+               }
+               else {
+                       log_assert(node->type == AST_STRUCT_ITEM);
+                       if (node->children.size() == 1 && node->children[0]->type == AST_RANGE) {
+                               auto rnode = node->children[0];
+                               width = (rnode->range_swapped ? rnode->range_right - rnode->range_left :
+                                                               rnode->range_left - rnode->range_right) + 1;
+                               // range nodes are now redundant
+                               node->children.clear();
+                       }
+                       else if (node->range_left < 0) {
+                               // 1 bit signal: bit, logic or reg
+                               width = 1;
+                       }
+                       else {
+                               // already resolved and compacted
+                               width = node->range_left - node->range_right + 1;
+                       }
+                       if (is_union) {
+                               node->range_right = base_offset;
+                               node->range_left = base_offset + width - 1;
+                       }
+                       else {
+                               node->range_right = base_offset + offset;
+                               node->range_left = base_offset + offset + width - 1;
+                       }
+                       node->range_valid = true;
+               }
+               if (is_union) {
+                       // check that all members have the same size
+                       if (packed_width == -1) {
+                               // first member
+                               packed_width = width;
+                       }
+                       else {
+                               if (packed_width != width) {
+
+                                       log_file_error(node->filename, node->location.first_line, "member %s of a packed union has %d bits, expecting %d\n", node->str.c_str(), width, packed_width);
+                               }
+                       }
+               }
+               else {
+                       offset += width;
+               }
+       }
+       return (is_union ? packed_width : offset);
+}
+
+static void add_members_to_scope(AstNode *snode, std::string name)
+{
+       // add all the members in a struct or union to local scope
+       // in case later referenced in assignments
+       log_assert(snode->type==AST_STRUCT || snode->type==AST_UNION);
+       for (auto *node : snode->children) {
+               if (node->type != AST_STRUCT_ITEM) {
+                       // embedded struct or union
+                       add_members_to_scope(node, name + "." + node->str);
+               }
+               else {
+                       auto member_name = name + "." + node->str;
+                       current_scope[member_name] = node;
+               }
+       }
+}
+
+static int get_max_offset(AstNode *node)
+{
+       // get the width from the MS member in the struct
+       // as members are laid out from left to right in the packed wire
+       log_assert(node->type==AST_STRUCT || node->type==AST_UNION);
+       while (node->type != AST_STRUCT_ITEM) {
+               node = node->children[0];
+       }
+       return node->range_left;
+}
+
 static AstNode *make_packed_struct(AstNode *template_node, std::string &name)
 {
        // create a wire for the packed struct
@@ -257,18 +348,14 @@ static AstNode *make_packed_struct(AstNode *template_node, std::string &name)
        wnode->str = name;
        wnode->is_logic = true;
        wnode->range_valid = true;
-       // get the width from the MS member in the template
-       // as members are laid out from left to right
-       int offset = template_node->children[0]->range_left;
+       wnode->is_signed = template_node->is_signed;
+       int offset = get_max_offset(template_node);
        auto range = make_range(offset, 0);
        wnode->children.push_back(range);
        // make sure this node is the one in scope for this name
        current_scope[name] = wnode;
-       // add members to scope
-       for (auto *node : template_node->children) {
-               auto member_name = name + "." + node->str;
-               current_scope[member_name] = node;
-       }
+       // add all the struct members to scope under the wire's name
+       add_members_to_scope(template_node, name);
        return wnode;
 }
 
@@ -672,46 +759,25 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                break;
 
        case AST_STRUCT:
-               //log("STRUCT %d %d %d\n", stage, basic_prep, in_param);
+       case AST_UNION:
                if (!basic_prep) {
-                       //dumpAst(NULL, "1> ");
                        for (auto *node : children) {
                                // resolve any ranges
                                while (!node->basic_prep && node->simplify(true, false, false, stage, -1, false, false)) {
                                        did_something = true;
                                }
                        }
-                       basic_prep = true;
-                       // The members will be laid out in the structure contiguously from left to right.
-                       // Determine total packed size and assign offsets.  Store these in the member node.
-                       // dumpAst(NULL, "2> ");
-                       int offset = 0;
-                       for (auto it = children.rbegin(); it != children.rend(); ++it) {
-                               auto node = *it;
-                               if (is_signed)
-                                       node->is_signed = true;
-                               int width;
-                               if (node->children.size() == 1 && node->children[0]->type == AST_RANGE) {
-                                       auto rnode = node->children[0];
-                                       width = (rnode->range_swapped ? rnode->range_right - rnode->range_left :
-                                                                       rnode->range_left - rnode->range_right) + 1;
-                                       // range nodes are now redundant
-                                       node->children.clear();
-                               }
-                               else {
-                                       width = 1;
-                               }
-                               node->range_right = offset;
-                               node->range_left = offset + width - 1;
-                               node->range_valid = true;
-                               offset += width;
-                       }
-                       if (!str.empty()) {
-                               // instance rather than just a type in a typedef
-                               // so add a wire for the packed structure
+                       // determine member offsets and widths
+                       size_packed_struct(this, 0);
+
+                       // instance rather than just a type in a typedef or outer struct?
+                       if (!str.empty() && str[0] == '\\') {
+                               // instance so add a wire for the packed structure
                                auto wnode = make_packed_struct(this, str);
+                               log_assert(current_ast_mod);
                                current_ast_mod->children.push_back(wnode);
                        }
+                       basic_prep = true;
                }
                break;
 
@@ -1036,7 +1102,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
        if (type == AST_TYPEDEF) {
                log_assert(children.size() == 1);
                auto type_node = children[0];
-               log_assert(type_node->type == AST_WIRE || type_node->type == AST_MEMORY || type_node->type == AST_STRUCT);
+               log_assert(type_node->type == AST_WIRE || type_node->type == AST_MEMORY || type_node->type == AST_STRUCT || type_node->type == AST_UNION);
                while (type_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {
                        did_something = true;
                }
@@ -1061,7 +1127,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        // Ensure typedef itself is fully simplified
                        while (template_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {};
 
-                       if (template_node->type == AST_STRUCT) {
+                       if (template_node->type == AST_STRUCT || template_node->type == AST_UNION) {
                                // replace with wire representing the packed structure
                                newNode = make_packed_struct(template_node, str);
                                current_scope[str] = this;
index 19e54816d42828950a8bd9170d5081c546fb0cb3..6217b5728a4cba6f71c9a78b1fdbcf21c5b377d5 100644 (file)
@@ -265,6 +265,7 @@ static bool isUserType(std::string &s)
 "bit"        { SV_KEYWORD(TOK_LOGIC); }
 "int"        { SV_KEYWORD(TOK_INT); }
 "byte"       { SV_KEYWORD(TOK_BYTE); }
+"shortint"   { SV_KEYWORD(TOK_SHORTINT); }
 
 "eventually"   { if (formal_mode) return TOK_EVENTUALLY; SV_KEYWORD(TOK_EVENTUALLY); }
 "s_eventually" { if (formal_mode) return TOK_EVENTUALLY; SV_KEYWORD(TOK_EVENTUALLY); }
@@ -284,8 +285,9 @@ static bool isUserType(std::string &s)
 
 "enum"    { SV_KEYWORD(TOK_ENUM); }
 "typedef" { SV_KEYWORD(TOK_TYPEDEF); }
-"struct" { SV_KEYWORD(TOK_STRUCT); }
-"packed" { SV_KEYWORD(TOK_PACKED); }
+"struct"  { SV_KEYWORD(TOK_STRUCT); }
+"union"   { SV_KEYWORD(TOK_UNION); }
+"packed"  { SV_KEYWORD(TOK_PACKED); }
 
 [0-9][0-9_]* {
        yylval->string = new std::string(yytext);
index dbd18bb53e0f914a7cd54f96bc3598a296ec3fb7..fff02f33a3db40fc286e6111c8c7ba242d6474b5 100644 (file)
@@ -238,6 +238,7 @@ static void rewriteAsMemoryNode(AstNode *node, AstNode *rangeNode)
 %union {
        std::string *string;
        struct YOSYS_NAMESPACE_PREFIX AST::AstNode *ast;
+       YOSYS_NAMESPACE_PREFIX AST::AstNodeType type;
        YOSYS_NAMESPACE_PREFIX dict<YOSYS_NAMESPACE_PREFIX RTLIL::IdString, YOSYS_NAMESPACE_PREFIX AST::AstNode*> *al;
        struct specify_target *specify_target_ptr;
        struct specify_triple *specify_triple_ptr;
@@ -269,7 +270,7 @@ static void rewriteAsMemoryNode(AstNode *node, AstNode *rangeNode)
 %token TOK_POS_INDEXED TOK_NEG_INDEXED TOK_PROPERTY TOK_ENUM TOK_TYPEDEF
 %token TOK_RAND TOK_CONST TOK_CHECKER TOK_ENDCHECKER TOK_EVENTUALLY
 %token TOK_INCREMENT TOK_DECREMENT TOK_UNIQUE TOK_PRIORITY
-%token TOK_STRUCT TOK_PACKED TOK_UNSIGNED TOK_INT TOK_BYTE
+%token TOK_STRUCT TOK_PACKED TOK_UNSIGNED TOK_INT TOK_BYTE TOK_SHORTINT TOK_UNION 
 
 %type <ast> range range_or_multirange  non_opt_range non_opt_multirange range_or_signed_int
 %type <ast> wire_type expr basic_expr concat_list rvalue lvalue lvalue_concat_list
@@ -278,6 +279,7 @@ static void rewriteAsMemoryNode(AstNode *node, AstNode *rangeNode)
 %type <ast> opt_enum_init enum_type struct_type non_wire_data_type
 %type <boolean> opt_signed opt_property unique_case_attr always_comb_or_latch always_or_always_ff
 %type <al> attr case_attr
+%type <type> struct_union
 
 %type <specify_target_ptr> specify_target
 %type <specify_triple_ptr> specify_triple specify_opt_triple
@@ -328,7 +330,6 @@ design:
        param_decl design |
        localparam_decl design |
        typedef_decl design |
-       struct_decl design |
        package design |
        interface design |
        /* empty */;
@@ -568,8 +569,7 @@ package_body:
        ;
 
 package_body_stmt:
-       typedef_decl
-       | struct_decl
+         typedef_decl
        | localparam_decl
        | param_decl
        ;
@@ -601,7 +601,7 @@ interface_body:
        interface_body interface_body_stmt |;
 
 interface_body_stmt:
-       param_decl | localparam_decl | typedef_decl | struct_decl | defparam_decl | wire_decl | always_stmt | assign_stmt |
+       param_decl | localparam_decl | typedef_decl | defparam_decl | wire_decl | always_stmt | assign_stmt |
        modport_stmt;
 
 non_opt_delay:
@@ -1442,6 +1442,7 @@ enum_base_type: type_atom type_signing
 
 type_atom: TOK_INTEGER         { astbuf1->is_reg = true; addRange(astbuf1); }          // 4-state signed
        |  TOK_INT              { astbuf1->is_reg = true; addRange(astbuf1); }          // 2-state signed
+       |  TOK_SHORTINT         { astbuf1->is_reg = true; addRange(astbuf1, 15, 0); }   // 2-state signed
        |  TOK_BYTE             { astbuf1->is_reg = true; addRange(astbuf1,  7, 0); }   // 2-state signed
        ;
 
@@ -1467,6 +1468,7 @@ enum_name_decl:
                auto node = astbuf1->clone();
                node->str = *$1;
                delete $1;
+               SET_AST_NODE_LOC(node, @1, @1);
                delete node->children[0];
                node->children[0] = $2 ?: new AstNode(AST_NONE);
                astbuf2->children.push_back(node);
@@ -1490,6 +1492,7 @@ enum_var: TOK_ID {
                ast_stack.back()->children.push_back(node);
                node->str = *$1;
                delete $1;
+               SET_AST_NODE_LOC(node, @1, @1);
                node->is_enum = true;
        }
        ;
@@ -1497,23 +1500,29 @@ enum_var: TOK_ID {
 enum_decl: enum_type enum_var_list ';'         { delete $1; }
        ;
 
-/////////
-// struct
-/////////
+//////////////////
+// struct or union
+//////////////////
 
 struct_decl: struct_type struct_var_list ';'   { delete astbuf2; }
        ;
 
-struct_type: TOK_STRUCT { astbuf2 = new AstNode(AST_STRUCT); } opt_packed '{' struct_member_list '}'   { $$ = astbuf2; }
+struct_type: struct_union { astbuf2 = new AstNode($1); } opt_packed '{' struct_member_list '}'         { $$ = astbuf2; }
        ;
 
+struct_union:
+         TOK_STRUCT            { $$ = AST_STRUCT; }
+       | TOK_UNION             { $$ = AST_UNION; }
+       ;
+
+
 opt_packed: TOK_PACKED opt_signed_struct
-       | { frontend_verilog_yyerror("Only STRUCT PACKED supported at this time"); }
+       | { frontend_verilog_yyerror("Only PACKED supported at this time"); }
        ;
 
 opt_signed_struct:
          TOK_SIGNED            { astbuf2->is_signed = true; }
-       | TOK_UNSIGNED
+       | TOK_UNSIGNED          { astbuf2->is_signed = false; }
        | // default is unsigned
        ;
 
@@ -1532,11 +1541,13 @@ member_name_list:
 member_name: TOK_ID {
                        astbuf1->str = $1->substr(1);
                        delete $1;
-                       astbuf2->children.push_back(astbuf1->clone());
+                       auto member_node = astbuf1->clone();
+                       SET_AST_NODE_LOC(member_node, @1, @1);
+                       astbuf2->children.push_back(member_node);
                }
        ;
 
-struct_member_type: { astbuf1 = new AstNode(AST_STRUCT_ITEM); } member_type_token_list { SET_RULE_LOC(@$, @2, @$); }
+struct_member_type: { astbuf1 = new AstNode(AST_STRUCT_ITEM); } member_type_token_list
        ;
 
 member_type_token_list:
@@ -1544,12 +1555,18 @@ member_type_token_list:
        | hierarchical_type_id {
                        // use a clone of the typedef definition nodes
                        auto template_node = copyTypeDefinition(*$1);
-                       if (template_node->type != AST_WIRE) {
+                       delete $1;
+                       switch (template_node->type) {
+                       case AST_WIRE:
+                               template_node->type = AST_STRUCT_ITEM;
+                               break;
+                       case AST_STRUCT:
+                       case AST_UNION:
+                               break;
+                       default:
                                frontend_verilog_yyerror("Invalid type for struct member: %s", type2str(template_node->type).c_str());
                        }
-                       template_node->type = AST_STRUCT_ITEM;
                        delete astbuf1;
-                       delete $1;
                        astbuf1 = template_node;
                }
        ;
@@ -1565,6 +1582,7 @@ struct_var_list: struct_var
 struct_var: TOK_ID     {       auto *var_node = astbuf2->clone();
                                var_node->str = *$1;
                                delete $1;
+                               SET_AST_NODE_LOC(var_node, @1, @1);
                                ast_stack.back()->children.push_back(var_node);
                        }
        ;
diff --git a/tests/svtypes/union_simple.sv b/tests/svtypes/union_simple.sv
new file mode 100644 (file)
index 0000000..fc23fe6
--- /dev/null
@@ -0,0 +1,61 @@
+module top;
+
+       typedef struct packed {
+               byte a,b,c,d;
+       } byte4_t;
+
+       typedef union packed {
+               int     x;
+               byte4_t y;
+       } w_t;
+
+       w_t w;
+
+       assign w.x = 'h42;
+       always_comb begin
+               assert(w.y.d == 8'h42);
+       end
+
+       typedef logic[4:0] reg_addr_t;
+       typedef logic[6:0] opcode_t;
+
+       typedef struct packed {
+               bit [6:0]  func7;
+               reg_addr_t rs2;
+               reg_addr_t rs1;
+               bit [2:0]  func3;
+               reg_addr_t rd;
+               opcode_t   opcode;
+       } R_t;
+
+       typedef struct packed {
+               bit[11:0]  imm;
+               reg_addr_t rs1;
+               bit[2:0]   func3;
+               reg_addr_t rd;
+               opcode_t   opcode;
+       } I_t;
+
+       typedef struct packed {
+               bit[19:0]  imm;
+               reg_addr_t rd;
+               opcode_t   opcode;
+       } U_t;
+
+       typedef union packed {
+               R_t     r;
+               I_t     i;
+               U_t     u;
+       } instruction_t;
+
+       instruction_t ir1;
+       assign ir1 = 32'h0AA01EB7;          //  lui t4,0xAA01
+       always_comb begin
+               assert(ir1.u.opcode == 'h37);
+               assert(ir1.r.opcode == 'h37);
+               assert(ir1.u.rd == 'd29);
+               assert(ir1.r.rd == 'd29);
+               assert(ir1.u.imm == 'hAA01);
+       end
+
+endmodule