Skip to content

Commit 8638968

Browse files
committed
[ImportVerilog] Add support for oneshot
This followed a mix of isuknown and regular LTL lowering. This is to ensure that the behavior is correct with unknown values.
1 parent 537b162 commit 8638968

4 files changed

Lines changed: 215 additions & 15 deletions

File tree

docs/Dialects/LTL.md

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,59 @@ where the `logic_to_int` conversion is only necessary if `%cond` is 4-valued.
263263
%isunknown = moore.case_eq %reduced, %x : i1
264264
```
265265

266+
- **`$onehot0(a)`**:
267+
For 2-state input `%a` (where `%a` has type `iN`):
268+
269+
```mlir
270+
%one = hw.constant 1 : iN
271+
%sub = comb.sub %a, %one : iN
272+
%and = comb.and %a, %sub : iN
273+
%zero = hw.constant 0 : iN
274+
%onehot0 = comb.icmp eq %and, %zero : iN
275+
```
276+
277+
For 4-state input `%a` (where `%a` has type `!moore.lN`):
278+
First, `%a` is checked for unknown values (equivalent to `$isunknown(a)`). If
279+
any bits are unknown, the result is `1'b0`. Otherwise, it uses the 2-state
280+
lowering above on the coerced 2-state value:
281+
282+
```mlir
283+
%isunknown = ... // see $isunknown lowering
284+
%coerced_a = moore.logic_to_int %a
285+
%int_a = moore.to_builtin_int %coerced_a
286+
%onehot0_2state = ... // 2-state lowering on %int_a
287+
%zero = hw.constant 0 : i1
288+
%onehot0 = comb.mux %isunknown, %zero, %onehot0_2state : i1
289+
```
290+
291+
- **`$onehot(a)`**:
292+
For 2-state input `%a` (where `%a` has type `iN`):
293+
294+
```mlir
295+
%one = hw.constant 1 : iN
296+
%sub = comb.sub %a, %one : iN
297+
%and = comb.and %a, %sub : iN
298+
%zero = hw.constant 0 : iN
299+
%onehot0 = comb.icmp eq %and, %zero : iN
300+
%notzero = comb.icmp ne %a, %zero : iN
301+
%onehot = comb.and %onehot0, %notzero : i1
302+
```
303+
304+
For 4-state input `%a` (where `%a` has type `!moore.lN`):
305+
First, `%a` is checked for unknown values (equivalent to `$isunknown(a)`). If
306+
any bits are unknown, the result is `1'b0`. Otherwise, it uses the 2-state
307+
lowering above on the coerced 2-state value:
308+
309+
```mlir
310+
%isunknown = ... // see $isunknown lowering
311+
%coerced_a = moore.logic_to_int %a
312+
%int_a = moore.to_builtin_int %coerced_a
313+
%onehot_2state = ... // 2-state lowering on %int_a
314+
%zero = hw.constant 0 : i1
315+
%onehot = comb.mux %isunknown, %zero, %onehot_2state : i1
316+
```
317+
266318
> The following functions are not yet supported by CIRCT:
267-
> - **`$onehot(a)`**
268-
> - **`$onehot0(a)`**
269319
> - **`$countones(a)`**
270320
271321

lib/Conversion/ImportVerilog/AssertionExpr.cpp

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -357,11 +357,48 @@ FailureOr<Value> Context::convertAssertionSystemCallArity1(
357357
case ksn::Past:
358358
return castToMoore(ltl::PastOp::create(builder, loc, value, 1, clockVal));
359359

360+
case ksn::OneHot0: {
361+
auto one = hw::ConstantOp::create(builder, loc, value.getType(), 1);
362+
auto minusOne = builder.create<comb::SubOp>(loc, value, one);
363+
auto anded = builder.create<comb::AndOp>(loc, value, minusOne);
364+
auto zero = hw::ConstantOp::create(builder, loc, value.getType(), 0);
365+
return castToMoore(comb::ICmpOp::create(
366+
builder, loc, comb::ICmpPredicate::eq, anded, zero, false));
367+
}
368+
369+
case ksn::OneHot: {
370+
auto one = hw::ConstantOp::create(builder, loc, value.getType(), 1);
371+
auto minusOne = builder.create<comb::SubOp>(loc, value, one);
372+
auto anded = builder.create<comb::AndOp>(loc, value, minusOne);
373+
auto zero = hw::ConstantOp::create(builder, loc, value.getType(), 0);
374+
auto isOneHot0 = comb::ICmpOp::create(builder, loc, comb::ICmpPredicate::eq,
375+
anded, zero, false);
376+
auto isNotZero = comb::ICmpOp::create(builder, loc, comb::ICmpPredicate::ne,
377+
value, zero, false);
378+
auto result = comb::AndOp::create(builder, loc, isOneHot0, isNotZero);
379+
return castToMoore(result);
380+
}
381+
360382
default:
361383
return Value{};
362384
}
363385
}
364386

387+
static Value getIsUnknown(OpBuilder &builder, Location loc, Value value,
388+
moore::IntType valTy, MLIRContext *ctx) {
389+
Value bitVal = value;
390+
if (valTy.getWidth() > 1) {
391+
auto mooreI1Type = moore::IntType::get(ctx, 1, valTy.getDomain());
392+
bitVal = moore::ReduceXorOp::create(builder, loc, mooreI1Type, value);
393+
}
394+
395+
auto xType = moore::IntType::get(ctx, 1, moore::Domain::FourValued);
396+
auto xValue = FVInt::getAllX(1);
397+
auto xConst = moore::ConstantOp::create(builder, loc, xType, xValue);
398+
399+
return moore::CaseEqOp::create(builder, loc, bitVal, xConst).getResult();
400+
}
401+
365402
Value Context::convertAssertionCallExpression(
366403
const slang::ast::CallExpression &expr,
367404
const slang::ast::CallExpression::SystemCallInfo &info, Location loc) {
@@ -421,19 +458,46 @@ Value Context::convertAssertionCallExpression(
421458
// would have already been converted to an integer type rather than bool
422459
// type as here.
423460
if (subroutine.knownNameId == slang::parsing::KnownSystemName::IsUnknown) {
424-
Value bitVal = value;
425-
if (valTy.getWidth() > 1) {
426-
auto mooreI1Type =
427-
moore::IntType::get(getContext(), 1, valTy.getDomain());
428-
bitVal = moore::ReduceXorOp::create(builder, loc, mooreI1Type, value);
429-
}
430-
431-
auto xType =
432-
moore::IntType::get(getContext(), 1, moore::Domain::FourValued);
433-
auto xValue = FVInt::getAllX(1);
434-
auto xConst = moore::ConstantOp::create(builder, loc, xType, xValue);
461+
return getIsUnknown(builder, loc, value, valTy, getContext());
462+
}
435463

436-
return moore::CaseEqOp::create(builder, loc, bitVal, xConst).getResult();
464+
// OneHot/OneHot0 are handled both here and below.
465+
if (subroutine.knownNameId == slang::parsing::KnownSystemName::OneHot ||
466+
subroutine.knownNameId == slang::parsing::KnownSystemName::OneHot0) {
467+
if (valTy.getDomain() == Domain::FourValued) {
468+
// In SystemVerilog, these system only returns 1b1 if the expression is
469+
// fully known and the condition is met. So if any x or y bits, then
470+
// these must return 1'b0.
471+
472+
// Detect if input contain unknown bits.
473+
Value isUnknownMoore =
474+
getIsUnknown(builder, loc, value, valTy, getContext());
475+
Value isUnknown =
476+
builder.createOrFold<moore::ToBuiltinIntOp>(loc, isUnknownMoore);
477+
478+
Value coerced = builder.createOrFold<moore::LogicToIntOp>(loc, value);
479+
Value state2IntVal =
480+
builder.createOrFold<moore::ToBuiltinIntOp>(loc, coerced);
481+
if (!state2IntVal)
482+
return {};
483+
484+
auto systemResult = this->convertAssertionSystemCallArity1(
485+
subroutine, loc, state2IntVal, originalType, clockVal);
486+
if (failed(systemResult) || !*systemResult)
487+
return {};
488+
Value onehot2state = *systemResult;
489+
490+
// Squash to 0 if unknown bits exist.
491+
Value onehot2stateBuiltin = convertToI1(onehot2state);
492+
Value zeroBuiltin =
493+
hw::ConstantOp::create(builder, loc, builder.getI1Type(), 0);
494+
Value resultBuiltin = builder.create<comb::MuxOp>(
495+
loc, isUnknown, zeroBuiltin, onehot2stateBuiltin);
496+
497+
Value finalResult =
498+
moore::FromBuiltinIntOp::create(builder, loc, resultBuiltin);
499+
return moore::IntToLogicOp::create(builder, loc, finalResult);
500+
}
437501
}
438502

439503
// If the value is four-valued, we need to map it to two-valued before we

lib/Conversion/ImportVerilog/Expressions.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2030,6 +2030,8 @@ struct RvalueExprVisitor : public ExprVisitor {
20302030
case ksn::Past:
20312031
case ksn::Sampled:
20322032
case ksn::IsUnknown:
2033+
case ksn::OneHot:
2034+
case ksn::OneHot0:
20332035
return context.convertAssertionCallExpression(expr, info, loc);
20342036
default:
20352037
break;

test/Conversion/ImportVerilog/builtins.sv

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,10 +409,12 @@ endfunction
409409
// CHECK-SAME: in [[CLK:%.+]] : !moore.l1
410410
module SampleValueBuiltins #() (
411411
input clk_i,
412-
input [7:0] data_i
412+
input [7:0] data_i,
413+
input bit [7:0] data_bit_i
413414
);
414415
// CHECK: [[CLKWIRE:%.+]] = moore.net name "clk_i" wire : <l1>
415416
// CHECK: [[DATAWIRE:%.+]] = moore.net name "data_i" wire : <l8>
417+
// CHECK: [[DATABITWIRE:%.+]] = moore.variable name "data_bit_i" : <i8>
416418
// CHECK: moore.procedure always {
417419
// CHECK-NEXT: [[C:%.+]] = moore.read [[CLKWIRE]] : <l1>
418420
// CHECK-NEXT: [[C_INT:%.+]] = moore.logic_to_int [[C]] : l1
@@ -587,6 +589,88 @@ module SampleValueBuiltins #() (
587589
// CHECK: ltl.clock [[CEQ_I1]]
588590
isunknown_data: assert property (@(posedge clk_i) $isunknown(data_i));
589591

592+
// CHECK: moore.procedure always {
593+
// CHECK: [[D:%.+]] = moore.read [[DATAWIRE]] : <l8>
594+
// CHECK: [[RED:%.+]] = moore.reduce_xor [[D]] : l8 -> l1
595+
// CHECK: [[X:%.+]] = moore.constant bX : l1
596+
// CHECK: [[ISUNKNOWN:%.+]] = moore.case_eq [[RED]], [[X]] : l1
597+
// CHECK: [[ISUNKNOWN_I1:%.+]] = moore.to_builtin_int [[ISUNKNOWN]] : i1
598+
// CHECK: [[D_L2I:%.+]] = moore.logic_to_int [[D]] : l8
599+
// CHECK: [[DB:%.+]] = moore.to_builtin_int [[D_L2I]] : i8
600+
// CHECK: [[ONE:%.+]] = hw.constant 1 : i8
601+
// CHECK: [[SUB:%.+]] = comb.sub [[DB]], [[ONE]] : i8
602+
// CHECK: [[AND:%.+]] = comb.and [[DB]], [[SUB]] : i8
603+
// CHECK: [[ZERO:%.+]] = hw.constant 0 : i8
604+
// CHECK: [[EQ:%.+]] = comb.icmp eq [[AND]], [[ZERO]] : i8
605+
// CHECK: [[EQ_INT:%.+]] = moore.from_builtin_int [[EQ]] : i1
606+
// CHECK: [[EQ_LOGIC:%.+]] = moore.int_to_logic [[EQ_INT]] : i1
607+
// CHECK: [[EQ_L2I:%.+]] = moore.logic_to_int [[EQ_LOGIC]] : l1
608+
// CHECK: [[EQ_BUILTIN:%.+]] = moore.to_builtin_int [[EQ_L2I]] : i1
609+
// CHECK: [[FALSE:%.+]] = hw.constant false
610+
// CHECK: [[MUX:%.+]] = comb.mux [[ISUNKNOWN_I1]], [[FALSE]], [[EQ_BUILTIN]] : i1
611+
// CHECK: [[RES_INT:%.+]] = moore.from_builtin_int [[MUX]] : i1
612+
// CHECK: [[RES_LOGIC:%.+]] = moore.int_to_logic [[RES_INT]] : i1
613+
// CHECK: [[RES_L2I:%.+]] = moore.logic_to_int [[RES_LOGIC]] : l1
614+
// CHECK: [[RES_BUILTIN:%.+]] = moore.to_builtin_int [[RES_L2I]] : i1
615+
// CHECK: ltl.clock [[RES_BUILTIN]]
616+
onehot0_data: assert property (@(posedge clk_i) $onehot0(data_i));
617+
618+
// CHECK: moore.procedure always {
619+
// CHECK: [[D:%.+]] = moore.read [[DATAWIRE]] : <l8>
620+
// CHECK: [[RED:%.+]] = moore.reduce_xor [[D]] : l8 -> l1
621+
// CHECK: [[X:%.+]] = moore.constant bX : l1
622+
// CHECK: [[ISUNKNOWN:%.+]] = moore.case_eq [[RED]], [[X]] : l1
623+
// CHECK: [[ISUNKNOWN_I1:%.+]] = moore.to_builtin_int [[ISUNKNOWN]] : i1
624+
// CHECK: [[D_L2I:%.+]] = moore.logic_to_int [[D]] : l8
625+
// CHECK: [[DB:%.+]] = moore.to_builtin_int [[D_L2I]] : i8
626+
// CHECK: [[ONE:%.+]] = hw.constant 1 : i8
627+
// CHECK: [[SUB:%.+]] = comb.sub [[DB]], [[ONE]] : i8
628+
// CHECK: [[AND:%.+]] = comb.and [[DB]], [[SUB]] : i8
629+
// CHECK: [[ZERO:%.+]] = hw.constant 0 : i8
630+
// CHECK: [[EQ:%.+]] = comb.icmp eq [[AND]], [[ZERO]] : i8
631+
// CHECK: [[NE:%.+]] = comb.icmp ne [[DB]], [[ZERO]] : i8
632+
// CHECK: [[AND2:%.+]] = comb.and [[EQ]], [[NE]] : i1
633+
// CHECK: [[RES_INT_2:%.+]] = moore.from_builtin_int [[AND2]] : i1
634+
// CHECK: [[RES_LOGIC_2:%.+]] = moore.int_to_logic [[RES_INT_2]] : i1
635+
// CHECK: [[RES_L2I_2:%.+]] = moore.logic_to_int [[RES_LOGIC_2]] : l1
636+
// CHECK: [[RES_BUILTIN_2:%.+]] = moore.to_builtin_int [[RES_L2I_2]] : i1
637+
// CHECK: [[FALSE:%.+]] = hw.constant false
638+
// CHECK: [[MUX:%.+]] = comb.mux [[ISUNKNOWN_I1]], [[FALSE]], [[RES_BUILTIN_2]] : i1
639+
// CHECK: [[RES_INT:%.+]] = moore.from_builtin_int [[MUX]] : i1
640+
// CHECK: [[RES_LOGIC:%.+]] = moore.int_to_logic [[RES_INT]] : i1
641+
// CHECK: [[RES_L2I:%.+]] = moore.logic_to_int [[RES_LOGIC]] : l1
642+
// CHECK: [[RES_BUILTIN:%.+]] = moore.to_builtin_int [[RES_L2I]] : i1
643+
// CHECK: ltl.clock [[RES_BUILTIN]]
644+
onehot_data: assert property (@(posedge clk_i) $onehot(data_i));
645+
646+
// CHECK: moore.procedure always {
647+
// CHECK: [[D:%.+]] = moore.read [[DATABITWIRE]] : <i8>
648+
// CHECK: [[DB:%.+]] = moore.to_builtin_int [[D]] : i8
649+
// CHECK: [[ONE:%.+]] = hw.constant 1 : i8
650+
// CHECK: [[SUB:%.+]] = comb.sub [[DB]], [[ONE]] : i8
651+
// CHECK: [[AND:%.+]] = comb.and [[DB]], [[SUB]] : i8
652+
// CHECK: [[ZERO:%.+]] = hw.constant 0 : i8
653+
// CHECK: [[EQ:%.+]] = comb.icmp eq [[AND]], [[ZERO]] : i8
654+
// CHECK: [[EQ_INT:%.+]] = moore.from_builtin_int [[EQ]] : i1
655+
// CHECK: [[EQ_BUILTIN:%.+]] = moore.to_builtin_int [[EQ_INT]] : i1
656+
// CHECK: ltl.clock [[EQ_BUILTIN]]
657+
onehot0_bit_data: assert property (@(posedge clk_i) $onehot0(data_bit_i));
658+
659+
// CHECK: moore.procedure always {
660+
// CHECK: [[D:%.+]] = moore.read [[DATABITWIRE]] : <i8>
661+
// CHECK: [[DB:%.+]] = moore.to_builtin_int [[D]] : i8
662+
// CHECK: [[ONE:%.+]] = hw.constant 1 : i8
663+
// CHECK: [[SUB:%.+]] = comb.sub [[DB]], [[ONE]] : i8
664+
// CHECK: [[AND:%.+]] = comb.and [[DB]], [[SUB]] : i8
665+
// CHECK: [[ZERO:%.+]] = hw.constant 0 : i8
666+
// CHECK: [[EQ:%.+]] = comb.icmp eq [[AND]], [[ZERO]] : i8
667+
// CHECK: [[NE:%.+]] = comb.icmp ne [[DB]], [[ZERO]] : i8
668+
// CHECK: [[AND2:%.+]] = comb.and [[EQ]], [[NE]] : i1
669+
// CHECK: [[RES_INT:%.+]] = moore.from_builtin_int [[AND2]] : i1
670+
// CHECK: [[RES_BUILTIN:%.+]] = moore.to_builtin_int [[RES_INT]] : i1
671+
// CHECK: ltl.clock [[RES_BUILTIN]]
672+
onehot_bit_data: assert property (@(posedge clk_i) $onehot(data_bit_i));
673+
590674
endmodule
591675

592676
// CHECK-LABEL: func.func private @StringBuiltins(

0 commit comments

Comments
 (0)