Skip to content

Commit 3585069

Browse files
[ImportVerilog] Add delayed assignment support (#9085)
Add the `moore.delayed_assign` and `moore.delayed_nonblocking_assign` ops to represent `assign #1ns a = b` and `a <= #1ns b` in SystemVerilog, respectively. Lower the new delay ops to `llhd.drv` with the appropriate delay value. Add support for blocking assignments with intra-assignment timing control to the ImportVerilog conversion. These are pretty trivial, since the blocking effects of the timing control are simply inserted in between computing the right-hand side and assigning to the left-hand side. Also add support for non-blocking assignments with intra-assignment delays. These require the new `moore.delayed_nonblocking_assign`, since the operation cannot suspend execution of the surrounding process. Instead, the assignment has to be added to the event queue and executed at a later point in time. Theoretically, the user could type wild things here, like `a <= repeat(5) @(posedge b or negedge c) d`, which would require us to spawn a separate "thread" to determine when all the events have occurred and the assignment can take place. We don't support any of that for now, because this is just utterly deranged. Also add support for delayed continuous assignments. This is pretty trivial since SystemVerilog only allows for simple delay values, such as `assign #1ns a = b`. This requires the new `moore.delayed_assign` op.
1 parent 770f2a0 commit 3585069

File tree

10 files changed

+242
-39
lines changed

10 files changed

+242
-39
lines changed

include/circt/Dialect/Moore/MooreOps.td

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,16 @@ class AssignOpBase<string mnemonic, list<Trait> traits = []> :
337337
}];
338338
}
339339

340+
class DelayedAssignOpBase<string mnemonic, list<Trait> traits = []> :
341+
AssignOpBase<mnemonic, traits> {
342+
let arguments = (ins RefType:$dst, UnpackedType:$src, TimeType:$delay);
343+
let assemblyFormat = [{
344+
$dst `,` $src `,` $delay attr-dict `:` type($src)
345+
}];
346+
}
347+
348+
// Continuous assignment
349+
340350
def ContinuousAssignOp : AssignOpBase<"assign", [HasParent<"SVModuleOp">]> {
341351
let summary = "Continuous assignment within a module";
342352
let description = [{
@@ -348,6 +358,19 @@ def ContinuousAssignOp : AssignOpBase<"assign", [HasParent<"SVModuleOp">]> {
348358
}];
349359
}
350360

361+
def DelayedContinuousAssignOp :
362+
DelayedAssignOpBase<"delayed_assign", [HasParent<"SVModuleOp">]> {
363+
let summary = "Delayed continuous assignment within a module";
364+
let description = [{
365+
A continuous assignment with a delay.
366+
367+
See the `moore.assign` op.
368+
See IEEE 1800-2017 § 10.3 "Continuous assignments".
369+
}];
370+
}
371+
372+
// Blocking assignment
373+
351374
def BlockingAssignOp : AssignOpBase<"blocking_assign", [
352375
DeclareOpInterfaceMethods<PromotableMemOpInterface>
353376
]> {
@@ -365,6 +388,8 @@ def BlockingAssignOp : AssignOpBase<"blocking_assign", [
365388
);
366389
}
367390

391+
// Non-blocking assignment
392+
368393
def NonBlockingAssignOp : AssignOpBase<"nonblocking_assign"> {
369394
let summary = "Nonblocking procedural assignment";
370395
let description = [{
@@ -378,6 +403,17 @@ def NonBlockingAssignOp : AssignOpBase<"nonblocking_assign"> {
378403
}];
379404
}
380405

406+
def DelayedNonBlockingAssignOp :
407+
DelayedAssignOpBase<"delayed_nonblocking_assign"> {
408+
let summary = "Delayed nonblocking procedural assignment";
409+
let description = [{
410+
A nonblocking procedural assignment with an intra-assignment delay control.
411+
412+
See the `moore.nonblocking_assign` op.
413+
See IEEE 1800-2017 § 9.4.5 "Intra-assignment timing controls".
414+
}];
415+
}
416+
381417
//===----------------------------------------------------------------------===//
382418
// Statements
383419
//===----------------------------------------------------------------------===//

lib/Conversion/ImportVerilog/Expressions.cpp

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -427,23 +427,46 @@ struct RvalueExprVisitor : public ExprVisitor {
427427
if (!lhs)
428428
return {};
429429

430+
// Determine the right-hand side value of the assignment.
430431
context.lvalueStack.push_back(lhs);
431432
auto rhs = context.convertRvalueExpression(
432433
expr.right(), cast<moore::RefType>(lhs.getType()).getNestedType());
433434
context.lvalueStack.pop_back();
434435
if (!rhs)
435436
return {};
436437

438+
// If this is a blocking assignment, we can insert the delay/wait ops of the
439+
// optional timing control directly in between computing the RHS and
440+
// executing the assignment.
441+
if (!expr.isNonBlocking()) {
442+
if (expr.timingControl)
443+
if (failed(context.convertTimingControl(*expr.timingControl)))
444+
return {};
445+
moore::BlockingAssignOp::create(builder, loc, lhs, rhs);
446+
return rhs;
447+
}
448+
449+
// For non-blocking assignments, we only support time delays for now.
437450
if (expr.timingControl) {
451+
// Handle regular time delays.
452+
if (auto *ctrl = expr.timingControl->as_if<slang::ast::DelayControl>()) {
453+
auto delay = context.convertRvalueExpression(
454+
ctrl->expr, moore::TimeType::get(builder.getContext()));
455+
if (!delay)
456+
return {};
457+
moore::DelayedNonBlockingAssignOp::create(builder, loc, lhs, rhs,
458+
delay);
459+
return rhs;
460+
}
461+
462+
// All other timing controls are not supported.
438463
auto loc = context.convertLocation(expr.timingControl->sourceRange);
439-
mlir::emitError(loc, "delayed assignments not supported");
464+
mlir::emitError(loc)
465+
<< "unsupported non-blocking assignment timing control: "
466+
<< slang::ast::toString(expr.timingControl->kind);
440467
return {};
441468
}
442-
443-
if (expr.isNonBlocking())
444-
moore::NonBlockingAssignOp::create(builder, loc, lhs, rhs);
445-
else
446-
moore::BlockingAssignOp::create(builder, loc, lhs, rhs);
469+
moore::NonBlockingAssignOp::create(builder, loc, lhs, rhs);
447470
return rhs;
448471
}
449472

lib/Conversion/ImportVerilog/ImportVerilogInternals.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,14 @@ struct Context {
134134
const slang::ast::Symbol &outermostModule);
135135
LogicalResult traverseInstanceBody(const slang::ast::Symbol &symbol);
136136

137-
// Convert a slang timing control into an MLIR timing control.
137+
// Convert timing controls into a corresponding set of ops that delay
138+
// execution of the current block. Produces an error if the implicit event
139+
// control `@*` or `@(*)` is used.
140+
LogicalResult convertTimingControl(const slang::ast::TimingControl &ctrl);
141+
// Convert timing controls into a corresponding set of ops that delay
142+
// execution of the current block. Then converts the given statement, taking
143+
// note of the rvalues it reads and adding them to a wait op in case an
144+
// implicit event control `@*` or `@(*)` is used.
138145
LogicalResult convertTimingControl(const slang::ast::TimingControl &ctrl,
139146
const slang::ast::Statement &stmt);
140147

lib/Conversion/ImportVerilog/Structure.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -498,12 +498,6 @@ struct ModuleVisitor : public BaseVisitor {
498498

499499
// Handle continuous assignments.
500500
LogicalResult visit(const slang::ast::ContinuousAssignSymbol &assignNode) {
501-
if (const auto *delay = assignNode.getDelay()) {
502-
auto loc = context.convertLocation(delay->sourceRange);
503-
return mlir::emitError(loc,
504-
"delayed continuous assignments not supported");
505-
}
506-
507501
const auto &expr =
508502
assignNode.getAssignment().as<slang::ast::AssignmentExpression>();
509503
auto lhs = context.convertLvalueExpression(expr.left());
@@ -515,6 +509,19 @@ struct ModuleVisitor : public BaseVisitor {
515509
if (!rhs)
516510
return failure();
517511

512+
// Handle delayed assignments.
513+
if (auto *timingCtrl = assignNode.getDelay()) {
514+
auto *ctrl = timingCtrl->as_if<slang::ast::DelayControl>();
515+
assert(ctrl && "slang guarantees this to be a simple delay");
516+
auto delay = context.convertRvalueExpression(
517+
ctrl->expr, moore::TimeType::get(builder.getContext()));
518+
if (!delay)
519+
return failure();
520+
moore::DelayedContinuousAssignOp::create(builder, loc, lhs, rhs, delay);
521+
return success();
522+
}
523+
524+
// Otherwise this is a regular assignment.
518525
moore::ContinuousAssignOp::create(builder, loc, lhs, rhs);
519526
return success();
520527
}

lib/Conversion/ImportVerilog/TimingControls.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ struct LTLClockControlVisitor {
155155
// be handled by `handleDelayControl`.
156156
static LogicalResult handleRoot(Context &context,
157157
const slang::ast::TimingControl &ctrl,
158-
moore::WaitEventOp &implicitWaitOp) {
158+
moore::WaitEventOp *implicitWaitOp) {
159159
auto &builder = context.builder;
160160
auto loc = context.convertLocation(ctrl.sourceRange);
161161

@@ -178,7 +178,9 @@ static LogicalResult handleRoot(Context &context,
178178
// empty wait op and let `Context::convertTimingControl` populate it once
179179
// the statement has been lowered.
180180
case TimingControlKind::ImplicitEvent:
181-
implicitWaitOp = moore::WaitEventOp::create(builder, loc);
181+
if (!implicitWaitOp)
182+
return mlir::emitError(loc) << "implicit events cannot be used here";
183+
*implicitWaitOp = moore::WaitEventOp::create(builder, loc);
182184
return success();
183185

184186
// Handle event control.
@@ -206,6 +208,11 @@ static LogicalResult handleRoot(Context &context,
206208
}
207209
}
208210

211+
LogicalResult
212+
Context::convertTimingControl(const slang::ast::TimingControl &ctrl) {
213+
return handleRoot(*this, ctrl, nullptr);
214+
}
215+
209216
LogicalResult
210217
Context::convertTimingControl(const slang::ast::TimingControl &ctrl,
211218
const slang::ast::Statement &stmt) {
@@ -221,7 +228,7 @@ Context::convertTimingControl(const slang::ast::TimingControl &ctrl,
221228
// surrounding implicit event control's list of implicitly observed
222229
// variables.
223230
rvalueReadCallback = nullptr;
224-
if (failed(handleRoot(*this, ctrl, implicitWaitOp)))
231+
if (failed(handleRoot(*this, ctrl, &implicitWaitOp)))
225232
return failure();
226233
}
227234

lib/Conversion/MooreToCore/MooreToCore.cpp

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1470,21 +1470,34 @@ struct AssignedVariableOpConversion
14701470
}
14711471
};
14721472

1473-
template <typename OpTy, unsigned DeltaTime, unsigned EpsilonTime>
1473+
template <typename OpTy>
14741474
struct AssignOpConversion : public OpConversionPattern<OpTy> {
14751475
using OpConversionPattern<OpTy>::OpConversionPattern;
14761476
using OpAdaptor = typename OpTy::Adaptor;
14771477

14781478
LogicalResult
14791479
matchAndRewrite(OpTy op, OpAdaptor adaptor,
14801480
ConversionPatternRewriter &rewriter) const override {
1481-
// TODO: When we support delay control in Moore dialect, we need to update
1482-
// this conversion.
1483-
auto timeAttr = llhd::TimeAttr::get(
1484-
op->getContext(), 0U, llvm::StringRef("ns"), DeltaTime, EpsilonTime);
1485-
auto time = llhd::ConstantTimeOp::create(rewriter, op->getLoc(), timeAttr);
1486-
rewriter.replaceOpWithNewOp<llhd::DriveOp>(op, adaptor.getDst(),
1487-
adaptor.getSrc(), time, Value{});
1481+
// Determine the delay for the assignment.
1482+
Value delay;
1483+
if constexpr (std::is_same_v<OpTy, ContinuousAssignOp> ||
1484+
std::is_same_v<OpTy, BlockingAssignOp>) {
1485+
// Blocking and continuous assignments get a 0ns 0d 1e delay.
1486+
delay = llhd::ConstantTimeOp::create(
1487+
rewriter, op->getLoc(),
1488+
llhd::TimeAttr::get(op->getContext(), 0U, "ns", 0, 1));
1489+
} else if constexpr (std::is_same_v<OpTy, NonBlockingAssignOp>) {
1490+
// Non-blocking assignments get a 0ns 1d 0e delay.
1491+
delay = llhd::ConstantTimeOp::create(
1492+
rewriter, op->getLoc(),
1493+
llhd::TimeAttr::get(op->getContext(), 0U, "ns", 1, 0));
1494+
} else {
1495+
// Delayed assignments have a delay operand.
1496+
delay = adaptor.getDelay();
1497+
}
1498+
1499+
rewriter.replaceOpWithNewOp<llhd::DriveOp>(
1500+
op, adaptor.getDst(), adaptor.getSrc(), delay, Value{});
14881501
return success();
14891502
}
14901503
};
@@ -1973,9 +1986,11 @@ static void populateOpConversion(ConversionPatternSet &patterns,
19731986
AShrOpConversion,
19741987

19751988
// Patterns of assignment operations.
1976-
AssignOpConversion<ContinuousAssignOp, 0, 1>,
1977-
AssignOpConversion<BlockingAssignOp, 0, 1>,
1978-
AssignOpConversion<NonBlockingAssignOp, 1, 0>,
1989+
AssignOpConversion<ContinuousAssignOp>,
1990+
AssignOpConversion<DelayedContinuousAssignOp>,
1991+
AssignOpConversion<BlockingAssignOp>,
1992+
AssignOpConversion<NonBlockingAssignOp>,
1993+
AssignOpConversion<DelayedNonBlockingAssignOp>,
19791994
AssignedVariableOpConversion,
19801995

19811996
// Patterns of branch operations.

test/Conversion/ImportVerilog/basic.sv

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,3 +3245,66 @@ function automatic void ConcatSformatf(string testStr, string otherString, ref s
32453245
// CHECK-NEXT: moore.blocking_assign [[LV]], [[CONV]] : l64
32463246
$sformat(logicVector, "%s %s", testStr, otherString);
32473247
endfunction
3248+
3249+
// CHECK-LABEL: moore.module @ContinuousAssignment(
3250+
module ContinuousAssignment;
3251+
// CHECK-NEXT: [[A:%.+]] = moore.variable
3252+
// CHECK-NEXT: [[B:%.+]] = moore.variable
3253+
bit [41:0] a;
3254+
bit [41:0] b;
3255+
3256+
// CHECK-NEXT: [[TMP:%.+]] = moore.read [[B]]
3257+
// CHECK-NEXT: [[NOTB:%.+]] = moore.not [[TMP]]
3258+
// CHECK-NEXT: moore.assign [[A]], [[NOTB]]
3259+
assign a = ~b;
3260+
3261+
// CHECK-NEXT: [[TMP:%.+]] = moore.read [[B]]
3262+
// CHECK-NEXT: [[NOTB:%.+]] = moore.not [[TMP]]
3263+
// CHECK-NEXT: [[TIME:%.+]] = moore.constant_time 1000000 fs
3264+
// CHECK-NEXT: moore.delayed_assign [[A]], [[NOTB]], [[TIME]]
3265+
assign #1ns a = ~b;
3266+
endmodule
3267+
3268+
// CHECK-LABEL: func.func private @BlockingAssignment(
3269+
// CHECK-SAME: [[A:%.+]]: !moore.ref<i42>
3270+
// CHECK-SAME: [[B:%.+]]: !moore.i42
3271+
// CHECK-SAME: [[C:%.+]]: !moore.i1
3272+
task BlockingAssignment(
3273+
output bit [41:0] a,
3274+
input bit [41:0] b,
3275+
input bit c
3276+
);
3277+
// CHECK-NEXT: [[NOTB:%.+]] = moore.not [[B]]
3278+
// CHECK-NEXT: moore.blocking_assign [[A]], [[NOTB]]
3279+
a = ~b;
3280+
3281+
// CHECK-NEXT: [[NOTB:%.+]] = moore.not [[B]]
3282+
// CHECK-NEXT: [[TIME:%.+]] = moore.constant_time 1000000 fs
3283+
// CHECK-NEXT: moore.wait_delay [[TIME]]
3284+
// CHECK-NEXT: moore.blocking_assign [[A]], [[NOTB]]
3285+
a = #1ns ~b;
3286+
3287+
// CHECK-NEXT: [[NOTB:%.+]] = moore.not [[B]]
3288+
// CHECK-NEXT: moore.wait_event {
3289+
// CHECK-NEXT: moore.detect_event posedge [[C]]
3290+
// CHECK-NEXT: }
3291+
// CHECK-NEXT: moore.blocking_assign [[A]], [[NOTB]]
3292+
a = @(posedge c) ~b;
3293+
endtask
3294+
3295+
// CHECK-LABEL: func.func private @NonBlockingAssignment(
3296+
// CHECK-SAME: [[A:%.+]]: !moore.ref<i42>
3297+
// CHECK-SAME: [[B:%.+]]: !moore.i42
3298+
task NonBlockingAssignment(
3299+
output bit [41:0] a,
3300+
input bit [41:0] b
3301+
);
3302+
// CHECK-NEXT: [[NOTB:%.+]] = moore.not [[B]]
3303+
// CHECK-NEXT: moore.nonblocking_assign [[A]], [[NOTB]]
3304+
a <= ~b;
3305+
3306+
// CHECK-NEXT: [[NOTB:%.+]] = moore.not [[B]]
3307+
// CHECK-NEXT: [[TIME:%.+]] = moore.constant_time 1000000 fs
3308+
// CHECK-NEXT: moore.delayed_nonblocking_assign [[A]], [[NOTB]], [[TIME]]
3309+
a <= #1ns ~b;
3310+
endtask

test/Conversion/ImportVerilog/errors.sv

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,16 @@ endmodule
4141
// -----
4242
module Foo;
4343
int x;
44-
// expected-error @below {{delayed assignments not supported}}
45-
initial x <= #1ns x;
44+
bit y;
45+
// expected-error @below {{unsupported non-blocking assignment timing control: SignalEvent}}
46+
initial x <= @y x;
4647
endmodule
4748

4849
// -----
4950
module Foo;
5051
int x;
51-
// expected-error @below {{delayed continuous assignments not supported}}
52-
assign #1ns x = x;
52+
// expected-error @below {{implicit events cannot be used here}}
53+
initial x = @* x;
5354
endmodule
5455

5556
// -----

test/Conversion/MooreToCore/basic.mlir

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,3 +1305,28 @@ moore.module @MultiDimensionalSlice(in %in : !moore.array<2 x array<2 x l2>>, ou
13051305
%0 = moore.extract %in from 0 : array<2 x array<2 x l2>> -> array<2 x l2>
13061306
moore.output %0 : !moore.array<2 x l2>
13071307
}
1308+
1309+
// CHECK-LABEL: hw.module @ContinuousAssignment
1310+
// CHECK-SAME: in %a : !llhd.ref<i42>
1311+
// CHECK-SAME: in %b : i42
1312+
// CHECK-SAME: in %c : !llhd.time
1313+
moore.module @ContinuousAssignment(in %a: !moore.ref<i42>, in %b: !moore.i42, in %c: !moore.time) {
1314+
// CHECK-NEXT: [[DELTA:%.+]] = llhd.constant_time <0ns, 0d, 1e>
1315+
// CHECK-NEXT: llhd.drv %a, %b after [[DELTA]]
1316+
moore.assign %a, %b : i42
1317+
// CHECK-NEXT: llhd.drv %a, %b after %c
1318+
moore.delayed_assign %a, %b, %c : i42
1319+
}
1320+
1321+
// CHECK-LABEL: func.func @NonBlockingAssignment
1322+
// CHECK-SAME: %arg0: !llhd.ref<i42>
1323+
// CHECK-SAME: %arg1: i42
1324+
// CHECK-SAME: %arg2: !llhd.time
1325+
func.func @NonBlockingAssignment(%arg0: !moore.ref<i42>, %arg1: !moore.i42, %arg2: !moore.time) {
1326+
// CHECK-NEXT: [[DELTA:%.+]] = llhd.constant_time <0ns, 1d, 0e>
1327+
// CHECK-NEXT: llhd.drv %arg0, %arg1 after [[DELTA]]
1328+
moore.nonblocking_assign %arg0, %arg1 : i42
1329+
// CHECK-NEXT: llhd.drv %arg0, %arg1 after %arg2
1330+
moore.delayed_nonblocking_assign %arg0, %arg1, %arg2 : i42
1331+
return
1332+
}

0 commit comments

Comments
 (0)