-
Notifications
You must be signed in to change notification settings - Fork 298
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
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) { | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I noticed that each |
||
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; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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}>> | ||
|
@@ -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}}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 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 | ||
|
There was a problem hiding this comment.
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 awithExpr
andconstantWithWidth
. 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.