From f482c9c0168a6857383e7d9360c8ca1df36ba2bc Mon Sep 17 00:00:00 2001 From: Peter Crozier Date: Tue, 12 May 2020 14:25:33 +0100 Subject: [PATCH] Generalise structs and add support for packed unions. --- README.md | 2 +- frontends/ast/ast.cc | 1 + frontends/ast/ast.h | 1 + frontends/ast/genrtlil.cc | 1 + frontends/ast/simplify.cc | 146 +++++++++++++++++++++-------- frontends/verilog/verilog_lexer.l | 6 +- frontends/verilog/verilog_parser.y | 50 ++++++---- tests/svtypes/union_simple.sv | 61 ++++++++++++ 8 files changed, 209 insertions(+), 59 deletions(-) create mode 100644 tests/svtypes/union_simple.sv diff --git a/README.md b/README.md index 3058220e7..770c62459 100644 --- 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. diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index 2c16de0a4..03fd272da 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -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: diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index 29bd1b84d..6d556fae2 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -158,6 +158,7 @@ namespace AST AST_WIRETYPE, AST_TYPEDEF, AST_STRUCT, + AST_UNION, AST_STRUCT_ITEM }; diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index a57333314..83f34c9e1 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -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' diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index ce435c26b..e8ac4b2f2 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -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; diff --git a/frontends/verilog/verilog_lexer.l b/frontends/verilog/verilog_lexer.l index 19e54816d..6217b5728 100644 --- a/frontends/verilog/verilog_lexer.l +++ b/frontends/verilog/verilog_lexer.l @@ -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); diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y index dbd18bb53..fff02f33a 100644 --- a/frontends/verilog/verilog_parser.y +++ b/frontends/verilog/verilog_parser.y @@ -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 *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 range range_or_multirange non_opt_range non_opt_multirange range_or_signed_int %type wire_type expr basic_expr concat_list rvalue lvalue lvalue_concat_list @@ -278,6 +279,7 @@ static void rewriteAsMemoryNode(AstNode *node, AstNode *rangeNode) %type opt_enum_init enum_type struct_type non_wire_data_type %type opt_signed opt_property unique_case_attr always_comb_or_latch always_or_always_ff %type attr case_attr +%type struct_union %type specify_target %type 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 index 000000000..fc23fe6e8 --- /dev/null +++ b/tests/svtypes/union_simple.sv @@ -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 -- 2.30.2