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 9 commits into
base: main
Choose a base branch
from
116 changes: 116 additions & 0 deletions lib/Conversion/ImportVerilog/Expressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,65 @@ struct RvalueExprVisitor {
return visitAssignmentPattern(expr, *count);
}

Value visit(const slang::ast::StreamingConcatenationExpression &expr) {
SmallVector<Value> operands;
for (auto stream : expr.streams()) {
auto operandLoc = context.convertLocation(stream.operand->sourceRange);
if (stream.constantWithWidth.has_value() || stream.withExpr) {
mlir::emitError(operandLoc) << "Moore only support streaming "
"concatenation without 'with operation'";
return {};
}
Copy link
Member

Choose a reason for hiding this comment

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

I think we can invoke context.convertRvalueExpression() to handle withExpr if it's existing. And then ignore the same level variable.
For your case: (Only handle withExpr)

vec_1 = {<<byte{vec_0, arr with [7:0]}};

It's AST looks like

{
                              "operand": {
                                "kind": "NamedValue",
                                "type": "logic$[63:0]",
                                "symbol": "2199025903840 arr"
                              },
                              "withExpr": {
                                "kind": "RangeSelect",
                                "type": "logic$[7:0]",
                                "selectionKind": "Simple",
                                "value": {
                                  "kind": "NamedValue",
                                  "type": "logic$[63:0]",
                                  "symbol": "2199025903840 arr"
                                },
   ......

Copy link
Author

Choose a reason for hiding this comment

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

However, when the stream contains multiple variable-size data packets, or each data packet contains more than one variable-size data item, or the size of the data to be unpacked is stored in the middle of the stream, this mechanism can become cumbersome and error-prone. To overcome these problems, the unpack operation allows a with expression to explicitly specify the extent of a variable-size field within the unpack operation.

with expression is dedicated for dynamic stream operands, and it is only used for unpacked operands. however concat operation only supports packed sized operands in systemverilog, so currently we have no method to implement with expression.

By the way, I think to support full stream concat operation, the key is to create a conversion from any type to packed type, and then we can use packed value to do concat, extract and finally implement stream concat.

Copy link
Author

Choose a reason for hiding this comment

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

I noticed that we have a powerful op moore.conversion can convert any type to any type, so I make a demo in 0e7f6bd, but currently I don't know how it works

    // CHECK: %[[TMP1:.*]] = moore.read %vec_3 : <l16>
    // CHECK: %[[TMP2:.*]] = moore.read %arr_1 : <uarray<64 x l1>>
    // CHECK: %[[TMP3:.*]] = moore.extract %[[TMP2]] from 0 : uarray<64 x l1> -> uarray<16 x l1>
    // CHECK: %[[TMP4:.*]] = moore.conversion %[[TMP3]] : !moore.uarray<16 x l1> -> !moore.l16
    // CHECK: %[[TMP5:.*]] = moore.concat %[[TMP1]], %[[TMP4]] : (!moore.l16, !moore.l16) -> l32
    // CHECK: %[[TMP6:.*]] = moore.extract %[[TMP5]] from 0 : l32 -> l8
    // CHECK: %[[TMP7:.*]] = moore.extract %[[TMP5]] from 8 : l32 -> l8
    // CHECK: %[[TMP8:.*]] = moore.extract %[[TMP5]] from 16 : l32 -> l8
    // CHECK: %[[TMP9:.*]] = moore.extract %[[TMP5]] from 24 : l32 -> l8
    // CHECK: %[[TMP10:.*]] = moore.concat %[[TMP6]], %[[TMP7]], %[[TMP8]], %[[TMP9]] : (!moore.l8, !moore.l8, !moore.l8, !moore.l8) -> l32
    // CHECK: moore.blocking_assign %vec_1, %[[TMP10]] : l32
    vec_1 = {<<byte{vec_3, arr_1 with [15:0]}};
    // CHECK: %[[TMP1:.*]] = moore.extract_ref %arr_1 from 0 : <uarray<64 x l1>> -> <uarray<16 x l1>>
    // CHECK: %[[TMP2:.*]] = moore.conversion %[[TMP1]] : !moore.ref<uarray<16 x l1>> -> !moore.ref<l16>
    // CHECK: %[[TMP3:.*]] = moore.concat_ref %vec_3, %[[TMP2]] : (!moore.ref<l16>, !moore.ref<l16>) -> <l32>
    // CHECK: %[[TMP4:.*]] = moore.extract_ref %[[TMP3]] from 0 : <l32> -> <l8>
    // CHECK: %[[TMP5:.*]] = moore.extract_ref %[[TMP3]] from 8 : <l32> -> <l8>
    // CHECK: %[[TMP6:.*]] = moore.extract_ref %[[TMP3]] from 16 : <l32> -> <l8>
    // CHECK: %[[TMP7:.*]] = moore.extract_ref %[[TMP3]] from 24 : <l32> -> <l8>
    // CHECK: %[[TMP8:.*]] = moore.concat_ref %[[TMP4]], %[[TMP5]], %[[TMP6]], %[[TMP7]] : (!moore.ref<l8>, !moore.ref<l8>, !moore.ref<l8>, !moore.ref<l8>) -> <l32>
    // CHECK: %[[TMP9:.*]] = moore.read %vec_1 : <l32>
    // CHECK: moore.blocking_assign %[[TMP8]], %[[TMP9]] : l32
    {<<byte{vec_3, arr_1 with [15:0]}} = vec_1;

Copy link
Contributor

Choose a reason for hiding this comment

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

At the moment, the moore.conversion operation is more or less a placeholder for more detailed conversion operations in the future. Ideally we would have more precise operations, such as moore.pack_sbv and moore.unpack_sbv, moore.int_to_logic and moore.logic_to_int, and other operations to convert between compatible unpacked types. I have started doing this for the sign/zero extension that is needed for resizing, but more work is needed. For now I think it's fine to just rely on moore.conversion to express whatever type conversion you need, and we'll fill in the details of how those conversions work later.


auto value = context.convertRvalueExpression(*stream.operand);
if (!value)
return {};
value = context.convertToSimpleBitVector(value);
if (!value) {
return {};
}
operands.push_back(value);
}
Value value;

if (operands.size() == 1) {
// There must be at least one element, otherwise slang will report an
// error
value = operands.front();
} else {
value = builder.create<moore::ConcatOp>(loc, operands).getResult();
}

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

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

for (size_t i = 0; i < iterMax; i++) {
auto extractResultType = moore::IntType::get(
context.getContext(), expr.sliceSize, type.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, type.getDomain());

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

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 @@ -925,6 +984,63 @@ struct LvalueExprVisitor {
dynLowBit);
}

Value visit(const slang::ast::StreamingConcatenationExpression &expr) {
SmallVector<Value> operands;
for (auto stream : expr.streams()) {
auto operandLoc = context.convertLocation(stream.operand->sourceRange);
if (stream.constantWithWidth.has_value() || stream.withExpr) {
mlir::emitError(operandLoc) << "Moore only support streaming "
"concatenation without 'with operation'";
return {};
}

auto value = context.convertLvalueExpression(*stream.operand);
if (!value)
return {};
operands.push_back(value);
}
Value value;
if (operands.size() == 1) {
// There must be at least one element, otherwise slang will report an
// error
value = operands.front();
} else {
value = builder.create<moore::ConcatRefOp>(loc, operands).getResult();
}

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

auto type = cast<moore::IntType>(
cast<moore::RefType>(value.getType()).getNestedType());
SmallVector<Value> slicedOperands;
auto widthSum = type.getWidth();
auto domain = type.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);
}

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
107 changes: 107 additions & 0 deletions test/Conversion/ImportVerilog/basic.sv
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,19 @@ module Expressions;
// CHECK: %b = moore.variable : <i32>
// CHECK: %c = moore.variable : <i32>
int a, b, c;
// CHECK: %j = moore.variable : <i32>
int j;
// CHECK: %up = moore.variable : <uarray<4 x l11>>
logic [10:0] up [3:0];
// CHECK: %p1 = moore.variable : <l11>
// CHECK: %p2 = moore.variable : <l11>
// CHECK: %p3 = moore.variable : <l11>
// CHECK: %p4 = moore.variable : <l11>
logic [11:1] p1, p2, p3, p4;
// CHECK: %yy = moore.variable : <i96>
bit [96:1] yy;
// CHECK: %dd = moore.variable : <i100>
bit [99:0] dd;
// CHECK: %u = moore.variable : <i32>
// CHECK: %w = moore.variable : <i32>
int unsigned u, w;
Expand All @@ -622,6 +635,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 +709,94 @@ module Expressions;
{a, b, c} = a;
// CHECK: moore.concat_ref %d, %e : (!moore.ref<l32>, !moore.ref<l32>) -> <l64>
{d, e} = d;
// CHECK: [[TMP1:%.+]] = moore.read %j : <i32>
// CHECK: moore.blocking_assign %a, [[TMP1]] : i32
a = { >> {j}};
// CHECK: [[TMP1:%.+]] = moore.read %j : <i32>
// CHECK: [[TMP2:%.+]] = moore.extract [[TMP1]] from 0 : i32 -> i8
// CHECK: [[TMP3:%.+]] = moore.extract [[TMP1]] from 8 : i32 -> i8
// CHECK: [[TMP4:%.+]] = moore.extract [[TMP1]] from 16 : i32 -> i8
// CHECK: [[TMP5:%.+]] = moore.extract [[TMP1]] from 24 : i32 -> i8
// CHECK: [[TMP6:%.+]] = moore.concat [[TMP2]], [[TMP3]], [[TMP4]], [[TMP5]] : (!moore.i8, !moore.i8, !moore.i8, !moore.i8) -> i32
// CHECK: moore.blocking_assign %a, [[TMP6]] : i32
a = { << byte {j}};
// CHECK: [[TMP1:%.+]] = moore.read %j : <i32>
// CHECK: [[TMP2:%.+]] = moore.extract [[TMP1]] from 0 : i32 -> i16
// CHECK: [[TMP3:%.+]] = moore.extract [[TMP1]] from 16 : i32 -> i16
// CHECK: [[TMP4:%.+]] = moore.concat [[TMP2]], [[TMP3]] : (!moore.i16, !moore.i16) -> i32
// CHECK: moore.blocking_assign %a, [[TMP4]] : i32
a = { << 16 {j}};
// CHECK: [[TMP1:%.+]] = moore.constant 53 : i8
// CHECK: [[TMP2:%.+]] = moore.extract [[TMP1]] from 0 : i8 -> i1
// CHECK: [[TMP3:%.+]] = moore.extract [[TMP1]] from 1 : i8 -> i1
// CHECK: [[TMP4:%.+]] = moore.extract [[TMP1]] from 2 : i8 -> i1
// CHECK: [[TMP5:%.+]] = moore.extract [[TMP1]] from 3 : i8 -> i1
// CHECK: [[TMP6:%.+]] = moore.extract [[TMP1]] from 4 : i8 -> i1
// CHECK: [[TMP7:%.+]] = moore.extract [[TMP1]] from 5 : i8 -> i1
// CHECK: [[TMP8:%.+]] = moore.extract [[TMP1]] from 6 : i8 -> i1
// CHECK: [[TMP9:%.+]] = moore.extract [[TMP1]] from 7 : i8 -> i1
// CHECK: [[TMP10:%.+]] = moore.concat [[TMP2]], [[TMP3]], [[TMP4]], [[TMP5]], [[TMP6]], [[TMP7]], [[TMP8]], [[TMP9]] : (!moore.i1, !moore.i1, !moore.i1, !moore.i1, !moore.i1, !moore.i1, !moore.i1, !moore.i1) -> i8
// CHECK: [[TMP11:%.+]] = moore.zext [[TMP10]] : i8 -> i32
// CHECK: moore.blocking_assign %a, [[TMP11]] : i32
a = { << { 8'b0011_0101 }};
// CHECK: [[TMP1:%.+]] = moore.constant -11 : i6
// CHECK: [[TMP2:%.+]] = moore.extract [[TMP1]] from 0 : i6 -> i4
// CHECK: [[TMP3:%.+]] = moore.extract [[TMP1]] from 4 : i6 -> i2
// CHECK: [[TMP4:%.+]] = moore.concat [[TMP2]], [[TMP3]] : (!moore.i4, !moore.i2) -> i6
// CHECK: [[TMP5:%.+]] = moore.zext [[TMP4]] : i6 -> i32
// CHECK: moore.blocking_assign %a, [[TMP5]] : i32
a = { << 4 { 6'b11_0101 }};
// CHECK: [[TMP1:%.+]] = moore.constant -11 : i6
// CHECK: [[TMP2:%.+]] = moore.zext [[TMP1]] : i6 -> i32
// CHECK: moore.blocking_assign %a, [[TMP2]] : i32
a = { >> 4 { 6'b11_0101 }};
// CHECK: [[TMP1:%.+]] = moore.constant -3 : i4
// CHECK: [[TMP2:%.+]] = moore.extract [[TMP1]] from 0 : i4 -> i1
// CHECK: [[TMP3:%.+]] = moore.extract [[TMP1]] from 1 : i4 -> i1
// CHECK: [[TMP4:%.+]] = moore.extract [[TMP1]] from 2 : i4 -> i1
// CHECK: [[TMP5:%.+]] = moore.extract [[TMP1]] from 3 : i4 -> i1
// CHECK: [[TMP6:%.+]] = moore.concat [[TMP2]], [[TMP3]], [[TMP4]], [[TMP5]] : (!moore.i1, !moore.i1, !moore.i1, !moore.i1) -> i4
// CHECK: [[TMP7:%.+]] = moore.extract [[TMP6]] from 0 : i4 -> i2
// CHECK: [[TMP8:%.+]] = moore.extract [[TMP6]] from 2 : i4 -> i2
// CHECK: [[TMP9:%.+]] = moore.concat [[TMP7]], [[TMP8]] : (!moore.i2, !moore.i2) -> i4
// CHECK: [[TMP10:%.+]] = moore.zext [[TMP9]] : i4 -> i32
// CHECK: moore.blocking_assign %a, [[TMP10]] : i32
a = { << 2 { { << { 4'b1101 }} }};
// CHECK: [[TMP1:%.+]] = moore.read %a : <i32>
// CHECK: [[TMP2:%.+]] = moore.read %b : <i32>
// CHECK: [[TMP3:%.+]] = moore.read %c : <i32>
// CHECK: [[TMP4:%.+]] = moore.concat [[TMP1]], [[TMP2]], [[TMP3]] : (!moore.i32, !moore.i32, !moore.i32) -> i96
// CHECK: moore.blocking_assign %yy, [[TMP4]] : i96
yy = {>>{ a, b, c }};
// CHECK: [[TMP1:%.+]] = moore.read %a : <i32>
// CHECK: [[TMP2:%.+]] = moore.read %b : <i32>
// CHECK: [[TMP3:%.+]] = moore.read %c : <i32>
// CHECK: [[TMP4:%.+]] = moore.concat [[TMP1]], [[TMP2]], [[TMP3]] : (!moore.i32, !moore.i32, !moore.i32) -> i96
// CHECK: [[TMP5:%.+]] = moore.zext [[TMP4]] : i96 -> i100
// CHECK: moore.blocking_assign %dd, [[TMP5]] : i100
dd = {>>{ a, b, c }};
// CHECK: [[TMP1:%.+]] = moore.concat_ref %a, %b, %c : (!moore.ref<i32>, !moore.ref<i32>, !moore.ref<i32>) -> <i96>
// CHECK: [[TMP2:%.+]] = moore.constant 1 : i96
// CHECK: moore.blocking_assign [[TMP1]], [[TMP2]] : i96
{>>{ a, b, c }} = 96'b1;
// CHECK: [[TMP1:%.+]] = moore.concat_ref %a, %b, %c : (!moore.ref<i32>, !moore.ref<i32>, !moore.ref<i32>) -> <i96>
// CHECK: [[TMP2:%.+]] = moore.constant 31 : i100
// CHECK: [[TMP3:%.+]] = moore.trunc [[TMP2]] : i100 -> i96
// CHECK: moore.blocking_assign [[TMP1]], [[TMP3]] : i96
{>>{ a, b, c }} = 100'b11111;
// CHECK: [[TMP1:%.+]] = moore.concat_ref %p1, %p2, %p3, %p4 : (!moore.ref<l11>, !moore.ref<l11>, !moore.ref<l11>, !moore.ref<l11>) -> <l44>
// CHECK: [[TMP2:%.+]] = moore.read %up : <uarray<4 x l11>>
// CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.uarray<4 x l11> -> !moore.l44
// CHECK: moore.blocking_assign [[TMP1]], [[TMP3]] : l44
{ >> {p1, p2, p3, p4}} = up;
// CHECK: [[TMP1:%.+]] = moore.extract_ref %a from 0 : <i32> -> <i8>
// CHECK: [[TMP2:%.+]] = moore.extract_ref %a from 8 : <i32> -> <i8>
// CHECK: [[TMP3:%.+]] = moore.extract_ref %a from 16 : <i32> -> <i8>
// CHECK: [[TMP4:%.+]] = moore.extract_ref %a from 24 : <i32> -> <i8>
// CHECK: [[TMP5:%.+]] = moore.concat_ref [[TMP1]], [[TMP2]], [[TMP3]], [[TMP4]] : (!moore.ref<i8>, !moore.ref<i8>, !moore.ref<i8>, !moore.ref<i8>) -> <i32>
// CHECK: [[TMP6:%.+]] = moore.constant 1 : i32
// CHECK: moore.blocking_assign [[TMP5]], [[TMP6]] : i32
{<< byte {a}} = 32'b1;
// CHECK: [[TMP1:%.+]] = moore.constant 0 : i1
// CHECK: [[TMP2:%.+]] = moore.concat [[TMP1]] : (!moore.i1) -> i1
// CHECK: moore.replicate [[TMP2]] : i1 -> i32
Expand Down
42 changes: 42 additions & 0 deletions test/Conversion/ImportVerilog/errors.sv
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,48 @@ module Foo;
int c = a inside { b };
endmodule

// -----
module Foo;
int a, b, c;
int j;
initial begin
// expected-error @below {{streaming operator target size 32 does not fit source size 96}}
j = {>>{ a, b, c }}; // error: j is 32 bits < 96 bits
end
endmodule


// -----
module Foo;
int a, b, c;
int j;
initial begin
// expected-error @below {{streaming operator target size 96 does not fit source size 23}}
{>>{ a, b, c }} = 23'b1;
end
endmodule

// -----
module Foo;
initial begin
logic [15:0] vec_0;
logic [47:0] vec_1;
logic arr [63:0];
// expected-error @below {{Moore only support streaming concatenation without 'with operation'}}
vec_1 = {<<byte{vec_0, arr with [7:0]}};
end
endmodule

// -----
module Foo;
initial begin
int my_queue[];
logic [31:0] vec_0;
// expected-error @below {{expression of type '!moore.open_uarray<i32>' cannot be cast to a simple bit vector}}
vec_0 = {<<byte{my_queue}};
end
endmodule

// -----
module Foo;
// expected-remark @below {{hello}}
Expand Down
Loading