Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for packed multidimensional arrays #4100

Merged
merged 9 commits into from
Feb 11, 2024

Conversation

daglem
Copy link
Contributor

@daglem daglem commented Jan 1, 2024

  • Generalization of dimensions metadata (also simplifies $size et al.)
  • Parsing and elaboration of multidimensional packed arrays
  • Implementation of $increment, $dimensions and $unpacked_dimensions
  • Resolve multiple dimensions defined in stages with typedef

Fixes #667
Fixes #3776

@daglem daglem requested a review from zachjs as a code owner January 1, 2024 21:35
@@ -44,6 +44,7 @@ input [2:0] bit;
output reg y1, y2;
output y3, y4;

(* nomem2reg *)
reg [7:0] mem1 [3:0];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is adding the attribute related to the other changes here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, but while testing the branch I found that mem1 was automatically converted to registers, while the point here seems to be to test both memory (mem1) and memory converted to registers (mem2).

Something has probably changed along the way here wrt. the heuristics for automatic conversion to registers, possibly with the change from memory_bram to memory_libmap?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a nit, but it's best to separate changes like this into commits of their own.

Something has probably changed along the way here wrt. the heuristics for automatic conversion to registers, possibly with the change from memory_bram to memory_libmap?

FWIW the mem2reg/nomem2reg attribute is understood by the frontend, once we are operating on the netlist they are not picked up by anything. Right now I don't understand the nature of the test here and the frontend heuristics for mem2reg conversion to say if we want the attribute here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a nit, but it's best to separate changes like this into commits of their own.

Sure, I'll split this change out.

Something has probably changed along the way here wrt. the heuristics for automatic conversion to registers, possibly with the change from memory_bram to memory_libmap?

FWIW the mem2reg/nomem2reg attribute is understood by the frontend, once we are operating on the netlist they are not picked up by anything. Right now I don't understand the nature of the test here and the frontend heuristics for mem2reg conversion to say if we want the attribute here.

Right. I checked why mem1 is converted to registers by default - it's because all the (non-initial) assignments to mem1 are made using constant indices (MEM2REG_FL_CONST_LHS is set in simplify.cc).

I'm pretty sure the (* nomem2reg *) attribute on mem1 makes sense there, since otherwise the test of mem1 is superfluous (covered by the test of mem2).

I checked the timeline of this. Since (* mem2reg *) was added to the test before MEM2REG_FL_CONST_LHS was introduced in simplify.cc, it seems clear that adding (* nomem2reg *) to mem1 is necessary to make the test work according to the original intention:

$ git show -s --format="%h %as %s" 19dba2561e tests/simple/memory.v
19dba2561 2013-11-20 Implemented part/bit select on memory read
$ git show -s --format="%h %as %s" 7cfae2c52 frontends/ast/simplify.cc
7cfae2c52 2019-03-01 Use mem2reg on memories that only have constant-index write ports

@daglem daglem force-pushed the multidimensional-arrays branch from 1edc3a2 to 741877c Compare January 10, 2024 21:14
@povik
Copy link
Member

povik commented Jan 22, 2024

I only gave this a superficial drive-by look, I don't understand the part of the frontend this is touching. We will probably need to wait for @zachjs to give this a proper review, if he has the chance.

@daglem daglem force-pushed the multidimensional-arrays branch from 741877c to 7d1a95b Compare January 25, 2024 07:23
Copy link
Collaborator

@zachjs zachjs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great change that both extends the frontend and improves its overall code quality. Thank you!

My most substantive request is that we add a few more tests, especially for the "nested" typedefs logic. I think we find a way to break anything in the frontend that isn't fully tested. If you can't do this (you've already done a lot!), I'd be happy to. In any event, it shouldn't prevent this from getting merged. I think we can merge it right after the next release.

}
}
if (!valid)
frontend_verilog_yyerror("wire/reg/logic packed dimension must be of the form: [<expr>:<expr>], [<expr>+:<expr>], or [<expr>-:<expr>]");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's fix this error message while we're here. Yosys indeed allows +: and -: in packed dimension, but this isn't allowed in the standard (range vs. part_select_range), so I'd prefer not to suggest this to users. We could also mention that we allow the SV shorthand [<expr>].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, fixed. Note that [<expr>] would not be correct here, since checkRange is only used to check packed ranges, and [<expr>] is only allowed for unpacked ranges in the LRM.

I first renamed checkRange to checkPackedRange in order to make this clearer, but decided against it in order to keep the diff small. This could always be changed later.

for (int i = unpacked_dimensions; i < GetSize(dimensions); i++) {
mem_width *= dimensions[i].range_width;
}
*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably going to have to be used at some point (arrays of structs), but I've removed it for now.

@@ -4936,6 +4849,9 @@ void AstNode::mem2reg_as_needed_pass1(dict<AstNode*, pool<std::string>> &mem2reg
{
AstNode *mem = id2ast;

if (integer < (unsigned)mem->unpacked_dimensions)
input_error("Insufficient number of array indices for %s.\n", log_id(str));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test for this error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really a shortcoming in Yosys, which currently treats anything with unpacked ranges as "memory", where the memory word consists of the packed part, and only indexing which ultimately selects the packed part is allowed (contrary to the LRM).

I'd rather not add tests for this shortcoming; I think it would be better to consider what could possibly be done about this in the future. Perhaps automatic mem2reg conversion of "memory" which has unpacked parts selected?

AstNode *id_ast = NULL;

// Is this needed?
//while (buf->simplify(true, false, stage, width_hint, sign_hint, false)) { }
//while (buf->simplify(true, stage, width_hint, sign_hint)) { }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this is needed? If not, let's just delete this comment to avoid future confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea whether it can be needed in some situation, really - I only adapted the commented out code to earlier changes in Yosys.

In any case I've removed the comment now.

}
int dims = GetSize(id_ast->dimensions);
if (dim < 1 || dim > dims)
input_error("Dimension %d out of range in `%s', as it only has %d dimensions!\n", dim, id_ast->str.c_str(), dims);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this behavior is incorrect. It should produce 'x. I know this is inherited from the existing logic, so you can defer fixing it and just add a TODO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually noticed this (old) incorrect behavior as well, however I had to stop somewhere for now.
TODO added.

@@ -3158,6 +3125,8 @@ skip_dynamic_range_lvalue_expansion:;
// found right-hand side identifier for memory -> replace with memory read port
if (stage > 1 && type == AST_IDENTIFIER && id2ast != NULL && id2ast->type == AST_MEMORY && !in_lvalue &&
children.size() == 1 && children[0]->type == AST_RANGE && children[0]->children.size() == 1) {
if (integer < (unsigned)id2ast->unpacked_dimensions)
input_error("Insufficient number of array indices for %s.\n", log_id(str));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this error has no test coverage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't, however it is old behavior, and incorrect at that - see comment above.

type = AST_WIRE;
AstNode *expr = children[0];
children.erase(children.begin());
while (is_custom_type && simplify(const_fold, stage, width_hint, sign_hint)) {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this type substitution pattern proliferates, let's factor it out. You can leave it for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'll keep it in mind.

@daglem
Copy link
Contributor Author

daglem commented Jan 29, 2024

This is a great change that both extends the frontend and improves its overall code quality. Thank you!

My most substantive request is that we add a few more tests, especially for the "nested" typedefs logic. I think we find a way to break anything in the frontend that isn't fully tested. If you can't do this (you've already done a lot!), I'd be happy to. In any event, it shouldn't prevent this from getting merged. I think we can merge it right after the next release.

It would be great if you could add the tests you find relevant, thank you!

I am aware of one construct where nested typedefs will yield incorrect results; arrays of structs/unions. However this is material for another PR.

@daglem daglem force-pushed the multidimensional-arrays branch 3 times, most recently from 9cc7498 to de908ae Compare January 29, 2024 19:45
* Generalization of dimensions metadata (also simplifies $size et al.)
* Parsing and elaboration of multidimensional packed ranges
The purpose of memtest02 in tests/simple/memory.v is to	test bit
select on both memory (mem1) and memory converted to registers (mem2).

After 7cfae2c, mem1 was automatically converted to registers,
and the test no longer worked as intended. This is fixed by
adding (* nomem2reg *) to mem1.
@daglem daglem force-pushed the multidimensional-arrays branch from de908ae to f57abd4 Compare January 29, 2024 19:56
@zachjs zachjs merged commit f09ea16 into YosysHQ:master Feb 11, 2024
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants