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

[ImportVerilog] add stream concat operation #7784

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions lib/Conversion/ImportVerilog/Expressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,52 @@ struct RvalueExprVisitor {
return visitAssignmentPattern(expr, *count);
}

Value visit(const slang::ast::StreamingConcatenationExpression &expr) {
SmallVector<Value> operands;
for (auto stream : expr.streams()) {
auto value = context.convertRvalueExpression(*stream.operand);
if (!value)
Comment on lines +796 to +798
Copy link
Contributor

Choose a reason for hiding this comment

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

I noticed that each stream can also have a withExpr and constantWithWidth. Could you add error messages if either of these are set, and tell the user that we don't support those cases? It's important that we only succeed if we were able to process everything the user has type, and not silently ignore parts of the input.

continue;
chenbo-again marked this conversation as resolved.
Show resolved Hide resolved
value = context.convertToSimpleBitVector(value);
if (!value) {
mlir::emitError(loc) << "Streaming concatentation do only support "
"SimpleBitVector for now";
return {};
}
operands.push_back(value);
}
auto value = builder.create<moore::ConcatOp>(loc, operands).getResult();

if (expr.sliceSize == 0) {
return value;
}

SmallVector<Value> slicedOperands;
auto iterMax = value.getType().getWidth() / expr.sliceSize;
auto remainSize = value.getType().getWidth() % expr.sliceSize;

for (size_t i = 0; i < iterMax; i++) {
auto extractResultType = moore::IntType::get(
context.getContext(), expr.sliceSize, value.getType().getDomain());

auto extracted = builder.create<moore::ExtractOp>(
loc, extractResultType, value, i * expr.sliceSize);
slicedOperands.push_back(extracted);
}
// handle other wire
if (remainSize) {
auto extractResultType = moore::IntType::get(
context.getContext(), remainSize, value.getType().getDomain());

auto extracted = builder.create<moore::ExtractOp>(
loc, extractResultType, value, iterMax * expr.sliceSize);
slicedOperands.push_back(extracted);
}

std::reverse(slicedOperands.begin(), slicedOperands.end());
return builder.create<moore::ConcatOp>(loc, slicedOperands);
}

/// Emit an error for all other expressions.
template <typename T>
Value visit(T &&node) {
Expand Down Expand Up @@ -929,6 +975,50 @@ struct LvalueExprVisitor {
dynLowBit);
}

Value visit(const slang::ast::StreamingConcatenationExpression &expr) {
SmallVector<Value> operands;
for (auto stream : expr.streams()) {
auto value = context.convertLvalueExpression(*stream.operand);
if (!value)
return {};
operands.push_back(value);
}
Comment on lines +980 to +985
Copy link
Contributor

Choose a reason for hiding this comment

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

I noticed that each stream can also have a withExpr and constantWithWidth. Could you add error messages if either of these are set, and tell the user that we don't support those cases? It's important that we only succeed if we were able to process everything the user has type, and not silently ignore parts of the input.

auto value = builder.create<moore::ConcatRefOp>(loc, operands).getResult();

if (expr.sliceSize == 0) {
return value;
}

SmallVector<Value> slicedOperands;
auto widthSum =
cast<moore::IntType>(value.getType().getNestedType()).getWidth();
auto domain =
cast<moore::IntType>(value.getType().getNestedType()).getDomain();
auto iterMax = widthSum / expr.sliceSize;
auto remainSize = widthSum % expr.sliceSize;

for (size_t i = 0; i < iterMax; i++) {
auto extractResultType = moore::RefType::get(
moore::IntType::get(context.getContext(), expr.sliceSize, domain));

auto extracted = builder.create<moore::ExtractRefOp>(
loc, extractResultType, value, i * expr.sliceSize);
slicedOperands.push_back(extracted);
}
// handle other wire
if (remainSize) {
auto extractResultType = moore::RefType::get(
moore::IntType::get(context.getContext(), remainSize, domain));

auto extracted = builder.create<moore::ExtractRefOp>(
loc, extractResultType, value, iterMax * expr.sliceSize);
slicedOperands.push_back(extracted);
}

std::reverse(slicedOperands.begin(), slicedOperands.end());
return builder.create<moore::ConcatRefOp>(loc, slicedOperands);
}

Value visit(const slang::ast::MemberAccessExpression &expr) {
auto type = context.convertType(*expr.type);
auto valueType = expr.value().type;
Expand Down
24 changes: 24 additions & 0 deletions test/Conversion/ImportVerilog/basic.sv
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,12 @@ module Expressions;
logic [31:0] vec_1;
// CHECK: %vec_2 = moore.variable : <l32>
logic [0:31] vec_2;
// CHECK: %vec_3 = moore.variable : <l16>
logic [15:0] vec_3;
// CHECK: %vec_4 = moore.variable : <l32>
logic [31:0] vec_4;
// CHECK: %vec_5 = moore.variable : <l48>
logic [47:0] vec_5;
// CHECK: %arr = moore.variable : <uarray<3 x uarray<6 x i4>>>
bit [4:1] arr [1:3][2:7];
// CHECK: %struct0 = moore.variable : <struct<{a: i32, b: i32}>>
Expand Down Expand Up @@ -690,6 +696,24 @@ module Expressions;
{a, b, c} = a;
// CHECK: moore.concat_ref %d, %e : (!moore.ref<l32>, !moore.ref<l32>) -> <l64>
{d, e} = d;
// CHECK: [[CONCAT:%.+]] = moore.concat [[SOURCE0:%.+]], [[SOURCE1:%.+]] : (!moore.l16, !moore.l32) -> l48
// CHECK: [[BYTE0:%.+]] = moore.extract [[CONCAT]] from 0 : l48 -> l8
// CHECK: [[BYTE1:%.+]] = moore.extract [[CONCAT]] from 8 : l48 -> l8
// CHECK: [[BYTE2:%.+]] = moore.extract [[CONCAT]] from 16 : l48 -> l8
// CHECK: [[BYTE3:%.+]] = moore.extract [[CONCAT]] from 24 : l48 -> l8
// CHECK: [[BYTE4:%.+]] = moore.extract [[CONCAT]] from 32 : l48 -> l8
// CHECK: [[BYTE5:%.+]] = moore.extract [[CONCAT]] from 40 : l48 -> l8
// CHECK: [[RESULT:%.+]] = moore.concat [[BYTE5]], [[BYTE4]], [[BYTE3]], [[BYTE2]], [[BYTE1]], [[BYTE0]] : (!moore.l8, !moore.l8, !moore.l8, !moore.l8, !moore.l8, !moore.l8) -> l48
vec_5 = {<<byte{vec_3, vec_4}};
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, thanks for checking different-sized variables here!

// CHECK: [[CONCAT_REF:%.+]] = moore.concat_ref [[SOURCE0:%.+]], [[SOURCE1:%.+]] : (!moore.ref<l16>, !moore.ref<l32>) -> <l48>
// CHECK: [[BYTE0_REF:%.+]] = moore.extract_ref [[CONCAT_REF]] from 0 : <l48> -> <l8>
// CHECK: [[BYTE1_REF:%.+]] = moore.extract_ref [[CONCAT_REF]] from 8 : <l48> -> <l8>
// CHECK: [[BYTE2_REF:%.+]] = moore.extract_ref [[CONCAT_REF]] from 16 : <l48> -> <l8>
// CHECK: [[BYTE3_REF:%.+]] = moore.extract_ref [[CONCAT_REF]] from 24 : <l48> -> <l8>
// CHECK: [[BYTE4_REF:%.+]] = moore.extract_ref [[CONCAT_REF]] from 32 : <l48> -> <l8>
// CHECK: [[BYTE5_REF:%.+]] = moore.extract_ref [[CONCAT_REF]] from 40 : <l48> -> <l8>
// CHECK: [[RESULT_REF:%.+]] = moore.concat_ref [[BYTE5_REF]], [[BYTE4_REF]], [[BYTE3_REF]], [[BYTE2_REF]], [[BYTE1_REF]], [[BYTE0_REF]] : (!moore.ref<l8>, !moore.ref<l8>, !moore.ref<l8>, !moore.ref<l8>, !moore.ref<l8>, !moore.ref<l8>) -> <l48>
{<<byte{vec_3, vec_4}} = vec_5;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a few more test cases that check other ways of how streams can be described? For example, I think your implementation already supports nested streams. And there's also the possibility of having a stream go in the other direction >>. I also noticed that the spec allows for streams to specify a slice size instead of a type, so something like {<<16{...}}.

The SystemVerilog specification contains a few examples for streams which might be great to include here, or use for inspiration for the different kinds of streams that we should test. For example, in IEEE 1800-2017 section 11.4.14.2 "Re-ordering of the generic stream" I see the following test:

int j = { "A", "B", "C", "D" };
{ >> {j}} // generates stream "A" "B" "C" "D"
{ << byte {j}} // generates stream "D" "C" "B" "A" (little endian)
{ << 16 {j}} // generates stream "C" "D" "A" "B"
{ << { 8'b0011_0101 }} // generates stream 'b1010_1100 (bit reverse)
{ << 4 { 6'b11_0101 }} // generates stream 'b0101_11
{ >> 4 { 6'b11_0101 }} // generates stream 'b1101_01 (same)
{ << 2 { { << { 4'b1101 }} }} // generates stream 'b1110

Pretty sure you could remove the string constants "A", "B", "C", "D" and then just check whether you get the correct slices of the variable j with your implementation. Or replace the string with a easily-recognizable constant, like 32'h87654321.

In IEEE 1800-2017 section 11.4.14.3 "Streaming concatenation as an assignment target (unpack)" I see the following test:

int a, b, c;
logic [10:0] up [3:0];
logic [11:1] p1, p2, p3, p4;
bit [96:1] y = {>>{ a, b, c }}; // OK: pack a, b, c
int j = {>>{ a, b, c }}; // error: j is 32 bits < 96 bits
bit [99:0] d = {>>{ a, b, c }}; // OK: d is padded with 4 bits
{>>{ a, b, c }} = 23'b1; // error: too few bits in stream
{>>{ a, b, c }} = 96'b1; // OK: unpack a = 0, b = 0, c = 1
{>>{ a, b, c }} = 100'b11111; // OK: unpack a = 0, b = 0, c = 1; 96 MSBs unpacked, 4 LSBs truncated
{ >> {p1, p2, p3, p4}} = up; // OK: unpack p1 = up[3], p2 = up[2], p3 = up[1], p4 = up[0]

Variations of these tests might be good to have. You don't need to check for error reporting if the error is reported by Slang and not your code; Slang already contains tests for this and we don't need to re-check Slang. But for any error message you produce in your code, and every branch/early exit in your code, you'd want to have a test that checks whether things work as expected there 😃

// CHECK: [[TMP1:%.+]] = moore.constant 0 : i1
// CHECK: [[TMP2:%.+]] = moore.concat [[TMP1]] : (!moore.i1) -> i1
// CHECK: moore.replicate [[TMP2]] : i1 -> i32
Expand Down
Loading