Skip to content

Commit

Permalink
[DSLX] Add gate! builtin to frontend for manual operand gating.
Browse files Browse the repository at this point in the history
Adds it to the fuzzer generation infrastructure with a flag to toggle emission.

Note #469 -- can't currently fuzz through normal `codegen_main`
fuzzing path because it appears the block generator is required. (Should
understand that better next week.)

Also:

* Prints fuzzer samples as crashers in the run_fuzz infrastructure, that way if
  there's a crash e.g. in one of the smoke tests we still have a reproducer
  available.
* Propagates node name to Gate op in IRParser.
* Doesn't constant fold the gate op (since this prevents convergence to fixed
  point in the optimizer). We'll need to figure out if this is the way we want
  to handle it -- classic difficulty with hints arises, where the hint lives in
  tension with optimiation opportunities that would defy the hint.

PiperOrigin-RevId: 387822924
  • Loading branch information
cdleary authored and Copybara-Service committed Jul 30, 2021
1 parent 093b0e4 commit c09339f
Show file tree
Hide file tree
Showing 26 changed files with 233 additions and 23 deletions.
33 changes: 28 additions & 5 deletions docs_src/dslx_reference.md
Expand Up @@ -2019,12 +2019,12 @@ not insert fatal-error-indicating hardware.

### cover!

NOTE: This section describes work-in-progress functionality. Currently, `cover!`
has no effect. Progress is being tracked in
[#436](https://github.com/google/xls/issues/436).
NOTE: Currently, `cover!` has no effect in RTL simulators supported in XLS open
source (i.e. iverilog). See
[google/xls#436](https://github.com/google/xls/issues/436).

The `cover!` builtin tracks how often some condition is satisfied. Its signature
is:
The `cover!` builtin tracks how often some condition is satisfied. It desugars
into SystemVerilog cover points. Its signature is:

```
cover!(<name>, <condition>);
Expand All @@ -2038,6 +2038,29 @@ what paths of a design are exercised in practice. The name of the coverpoint
must begin with either a letter or underscore, and its remainder must consist of
letters, digits, underscores, or dollar signs.

### gate!

NOTE: Currently, `gate!` is work-in-progress and does not yet support code
generation to Verilog (via `codegen_main`); see:
[google/xls#469](https://github.com/google/xls/issues/469).

The `gate!` builtin is used for operand gating, of the form:

```
let gated_value = gate!(<should_gate>, <value>);
```

This will generally use a special Verilog macro to avoid the underlying
synthesis tool doing boolean optimization, and will turn `gated_value` to `0`
when the predicate `should_gate` is `true`. This can be used in attempts to
manually avoid toggles based on the gating predicate.

It is expected that XLS will grow facilities to inserting gating ops
automatically, but manual user insertion is a practical step in this direction.
Additionally, it is expected that if, in the resulting Verilog, gating occurs on
a value that originates from a flip flop, the operand gating may be promoted to
register-based load-enable gating.

## Testing and Debugging

DSLX allows specifying tests right in the implementation file via the `test` and
Expand Down
1 change: 1 addition & 0 deletions xls/delay_model/models/unit.textproto
Expand Up @@ -29,6 +29,7 @@ op_models { op: "kDecode" estimator { fixed: 1 } }
op_models { op: "kDynamicBitSlice" estimator { fixed: 1 } }
op_models { op: "kEncode" estimator { fixed: 1 } }
op_models { op: "kEq" estimator { fixed: 1 } }
op_models { op: "kGate" estimator { fixed: 1 } }
op_models { op: "kIdentity" estimator { fixed: 1 } }
op_models { op: "kLiteral" estimator { fixed: 1 } }
op_models { op: "kArrayIndex" estimator { fixed: 1 } }
Expand Down
25 changes: 24 additions & 1 deletion xls/dslx/builtins.cc
Expand Up @@ -64,6 +64,16 @@ class ArgChecker {
return *this;
}

ArgChecker& ubits(int64_t argno, int64_t bit_count) {
const InterpValue& arg = args_[argno];
if (!arg.IsUBits() || arg.GetBitCount().value() != bit_count) {
status_.Update(absl::InvalidArgumentError(absl::StrFormat(
"Expect argument %d to %s to be uN[%d]; got: %s", argno, name_,
bit_count, TagToString(args_[argno].tag()))));
}
return *this;
}

const absl::Status& status() const { return status_; }

private:
Expand Down Expand Up @@ -200,10 +210,23 @@ absl::StatusOr<InterpValue> BuiltinFail(
absl::StatusOr<InterpValue> BuiltinCover(
absl::Span<const InterpValue> args, const Span& span, Invocation* expr,
const SymbolicBindings* symbolic_bindings) {
XLS_RETURN_IF_ERROR(ArgChecker("cover!", args).size(2).status());
XLS_RETURN_IF_ERROR(ArgChecker("cover!", args).size(2).array(0).status());
return InterpValue::MakeUnit();
}

absl::StatusOr<InterpValue> BuiltinGate(
absl::Span<const InterpValue> args, const Span& span, Invocation* expr,
const SymbolicBindings* symbolic_bindings) {
XLS_RETURN_IF_ERROR(
ArgChecker("gate!", args).size(2).ubits(0, 1).bits(1).status());
const InterpValue& p = args[0];
const InterpValue& value = args[1];
if (p.IsFalse()) {
return value;
}
return InterpValue::MakeBits(value.tag(), Bits(value.GetBitCount().value()));
}

absl::StatusOr<InterpValue> BuiltinUpdate(
absl::Span<const InterpValue> args, const Span& span, Invocation* expr,
const SymbolicBindings* symbolic_bindings) {
Expand Down
5 changes: 5 additions & 0 deletions xls/dslx/builtins.h
Expand Up @@ -62,6 +62,11 @@ absl::StatusOr<InterpValue> BuiltinCover(
absl::Span<const InterpValue> args, const Span& span, Invocation* expr,
const SymbolicBindings* symbolic_bindings);

// Implements 'gate!' builtin function.
absl::StatusOr<InterpValue> BuiltinGate(
absl::Span<const InterpValue> args, const Span& span, Invocation* expr,
const SymbolicBindings* symbolic_bindings);

// Implements 'update' builtin function.
absl::StatusOr<InterpValue> BuiltinUpdate(
absl::Span<const InterpValue> args, const Span& span, Invocation* expr,
Expand Down
1 change: 1 addition & 0 deletions xls/dslx/deduce.cc
Expand Up @@ -1890,6 +1890,7 @@ static const absl::flat_hash_set<std::string>& GetParametricBuiltinNames() {
"ctz",
"concat",
"fail!",
"gate!",
"map",
"one_hot",
"one_hot_sel",
Expand Down
8 changes: 8 additions & 0 deletions xls/dslx/dslx_builtins.cc
Expand Up @@ -213,6 +213,7 @@ const absl::flat_hash_map<std::string, std::string>& GetParametricBuiltins() {
{"concat", "(uN[M], uN[N]) -> uN[M+N]"},
{"cover!", "(u8[N], u1) -> ()"},
{"fail!", "(T) -> T"},
{"gate!", "(u1, T) -> T"},
{"map", "(T[N], (T) -> U) -> U[N]"},
{"one_hot", "(uN[N], u1) -> uN[N+1]"},
{"one_hot_sel", "(xN[N], xN[M][N]) -> xN[M]"},
Expand Down Expand Up @@ -455,6 +456,13 @@ static void PopulateSignatureToLambdaMap(
return TypeAndBindings{absl::make_unique<FunctionType>(
CloneToUnique(data.arg_types), BitsType::MakeU1())};
};
map["(u1, T) -> T"] = [](const SignatureData& data,
DeduceCtx* ctx) -> absl::StatusOr<TypeAndBindings> {
XLS_RETURN_IF_ERROR(
Checker(data.arg_types, data.name, data.span).Len(2).IsU1(0).status());
return TypeAndBindings{absl::make_unique<FunctionType>(
CloneToUnique(data.arg_types), data.arg_types[1]->CloneToUnique())};
};
map["(u1, T, T) -> T"] =
[](const SignatureData& data,
DeduceCtx* ctx) -> absl::StatusOr<TypeAndBindings> {
Expand Down
1 change: 1 addition & 0 deletions xls/dslx/interp_value.h
Expand Up @@ -30,6 +30,7 @@ namespace xls::dslx {
X("bit_slice_update", kBitSliceUpdate) \
X("clz", kClz) \
X("cover!", kCover) \
X("gate!", kGate) \
X("ctz", kCtz) \
X("enumerate", kEnumerate) \
X("fail!", kFail) \
Expand Down
1 change: 1 addition & 0 deletions xls/dslx/interpreter.cc
Expand Up @@ -262,6 +262,7 @@ absl::StatusOr<InterpValue> Interpreter::RunBuiltin(
CASE(Ctz);
CASE(Enumerate);
CASE(Fail);
CASE(Gate);
CASE(OneHot);
CASE(OneHotSel);
CASE(Range);
Expand Down
16 changes: 16 additions & 0 deletions xls/dslx/ir_converter.cc
Expand Up @@ -347,6 +347,10 @@ class FunctionConverter {
// Handles the cover!() builtin invocation.
absl::Status HandleCoverBuiltin(Invocation* node, BValue condition);

// Handles the gate!() builtin invocation.
absl::Status HandleGateBuiltin(Invocation* node, BValue condition,
BValue value);

// Handles an arm of a match expression.
absl::StatusOr<BValue> HandleMatcher(NameDefTree* matcher,
absl::Span<const int64_t> index,
Expand Down Expand Up @@ -382,6 +386,7 @@ class FunctionConverter {
absl::Status HandleBuiltinBitSliceUpdate(Invocation* node);
absl::Status HandleBuiltinClz(Invocation* node);
absl::Status HandleBuiltinCtz(Invocation* node);
absl::Status HandleBuiltinGate(Invocation* node);
absl::Status HandleBuiltinOneHot(Invocation* node);
absl::Status HandleBuiltinOneHotSel(Invocation* node);
absl::Status HandleBuiltinOrReduce(Invocation* node);
Expand Down Expand Up @@ -1912,6 +1917,7 @@ absl::Status FunctionConverter::HandleInvocation(Invocation* node) {
map = {
{"clz", &FunctionConverter::HandleBuiltinClz},
{"ctz", &FunctionConverter::HandleBuiltinCtz},
{"gate!", &FunctionConverter::HandleBuiltinGate},
{"signex", &FunctionConverter::HandleBuiltinSignex},
{"one_hot", &FunctionConverter::HandleBuiltinOneHot},
{"one_hot_sel", &FunctionConverter::HandleBuiltinOneHotSel},
Expand Down Expand Up @@ -2523,6 +2529,16 @@ absl::Status FunctionConverter::HandleBuiltinCtz(Invocation* node) {
return absl::OkStatus();
}

absl::Status FunctionConverter::HandleBuiltinGate(Invocation* node) {
XLS_RET_CHECK_EQ(node->args().size(), 2);
XLS_ASSIGN_OR_RETURN(BValue predicate, Use(node->args()[0]));
XLS_ASSIGN_OR_RETURN(BValue value, Use(node->args()[1]));
Def(node, [&](absl::optional<SourceLocation> loc) {
return function_builder_->Gate(predicate, value, loc);
});
return absl::OkStatus();
}

absl::Status FunctionConverter::HandleBuiltinOneHot(Invocation* node) {
XLS_RET_CHECK_EQ(node->args().size(), 2);
XLS_ASSIGN_OR_RETURN(BValue input, Use(node->args()[0]));
Expand Down
12 changes: 12 additions & 0 deletions xls/dslx/ir_converter_test.cc
Expand Up @@ -1378,6 +1378,18 @@ fn main(x: u32, y: u32) {
ExpectIr(converted, TestName());
}

TEST(IrConverterTest, ConvertGateOp) {
const std::string kProgram = R"(
fn main(p: bool, x: u32) -> u32 {
gate!(p, x)
}
)";

XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
ConvertModuleForTest(kProgram, kFailNoPos));
ExpectIr(converted, TestName());
}

TEST(IrConverterTest, PublicFnGetsTokenWrapper) {
const std::string kProgram = R"(
fn callee_callee(x:u32) -> u32 {
Expand Down
1 change: 1 addition & 0 deletions xls/dslx/parser.cc
Expand Up @@ -34,6 +34,7 @@ const std::vector<absl::string_view>& GetParametricBuiltinNames() {
"ctz",
"concat",
"fail!",
"gate!",
"map",
"one_hot",
"one_hot_sel",
Expand Down
5 changes: 5 additions & 0 deletions xls/dslx/testdata/ir_converter_test_ConvertGateOp.ir
@@ -0,0 +1,5 @@
package test_module

fn __test_module__main(p: bits[1], x: bits[32]) -> bits[32] {
ret gate.3: bits[32] = gate(p, x, id=3)
}
12 changes: 12 additions & 0 deletions xls/dslx/tests/BUILD
Expand Up @@ -2253,3 +2253,15 @@ xls_dslx_test(
name = "map_parametric_with_default_dslx_test",
dep = ":map_parametric_with_default_dslx_module",
)

# -- gate

xls_dslx_module_library(
name = "gate_dslx_module",
src = "gate.x",
)

xls_dslx_test(
name = "gate_dslx_test",
dep = ":gate_dslx_module",
)
24 changes: 24 additions & 0 deletions xls/dslx/tests/gate.x
@@ -0,0 +1,24 @@
// Copyright 2021 The XLS Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

fn main(p: bool, x: u32) -> u32 {
gate!(p, x)
}

#![test]
fn test_main() {
let _ = assert_eq(u32:-1, main(false, u32:-1));
let _ = assert_eq(u32:0, main(true, u32:-1));
()
}
19 changes: 19 additions & 0 deletions xls/fuzzer/ast_generator.cc
Expand Up @@ -524,6 +524,16 @@ absl::StatusOr<TypedExpr> AstGenerator::GenerateArrayUpdate(Env* env) {
array.type};
}

absl::StatusOr<TypedExpr> AstGenerator::GenerateGate(Env* env) {
XLS_RET_CHECK(env != nullptr);
XLS_ASSIGN_OR_RETURN(TypedExpr p, GenerateCompare(env));
XLS_ASSIGN_OR_RETURN(TypedExpr value, ChooseEnvValueBits(env));
return TypedExpr{
module_->Make<Invocation>(fake_span_, MakeBuiltinNameRef("gate!"),
std::vector<Expr*>{p.expr, value.expr}),
value.type};
}

absl::StatusOr<TypedExpr> AstGenerator::GenerateConcat(Env* env) {
XLS_RET_CHECK(env != nullptr);
if (EnvContainsArray(*env) && RandomBool()) {
Expand Down Expand Up @@ -1017,6 +1027,7 @@ enum OpChoice {
kConcat,
kCountedFor,
kLogical,
kGate,
kMap,
kNumber,
kOneHotSelectBuiltin,
Expand Down Expand Up @@ -1060,6 +1071,8 @@ int OpProbability(OpChoice op) {
return 5;
case kCountedFor:
return 1;
case kGate:
return 1;
case kLogical:
return 3;
case kMap:
Expand Down Expand Up @@ -1152,6 +1165,12 @@ absl::StatusOr<TypedExpr> AstGenerator::GenerateExpr(int64_t expr_size,
case kLogical:
generated = GenerateLogicalOp(env);
break;
case kGate:
if (!options_.emit_gate) {
continue;
}
generated = GenerateGate(env);
break;
case kMap:
generated = GenerateMap(call_depth, env);
break;
Expand Down
6 changes: 6 additions & 0 deletions xls/fuzzer/ast_generator.h
Expand Up @@ -67,6 +67,9 @@ struct AstGeneratorOptions {
// TODO(https://github.com/google/xls/issues/346): 2021-03-19 Remove this
// option when pipeline generator handles empty tuples properly.
bool generate_empty_tuples = true;

// Whether to emit `gate!()` builtin calls.
bool emit_gate = true;
};

// Type that generates a random module for use in fuzz testing; i.e.
Expand Down Expand Up @@ -280,6 +283,9 @@ class AstGenerator {
// Returns an array update operation using values in "env".
absl::StatusOr<TypedExpr> GenerateArrayUpdate(Env* env);

// Return a `gate!()` invocation using values in "env".
absl::StatusOr<TypedExpr> GenerateGate(Env* env);

// Returns a (potentially vacuous) concatenate operation of values in "env".
absl::StatusOr<TypedExpr> GenerateConcat(Env* env);

Expand Down
30 changes: 30 additions & 0 deletions xls/fuzzer/crashers/crasher_2021-07-29_001.x
@@ -0,0 +1,30 @@
// Copyright 2021 The XLS Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Issue: was causing an optimizer hang -- attempts to constant fold the gate op
// would not converge.
//
// options: {"codegen": false, "codegen_args": null, "convert_to_ir": true, "input_is_dslx": true, "optimize_ir": true, "simulate": false, "simulator": null, "use_jit": true, "use_system_verilog": true}
// args: (); (bits[10]:0x2aa)
// args: (); (bits[10]:0x3ff)
// args: (); (bits[10]:0x2aa)
// args: (); (bits[10]:0x0)
fn main(x0: (), x1: (s10,)) -> (u11, u11, s10, u11, u11, u11) {
let x3: u11 = u11:0x325;
let x4: u11 = gate!((x3) != (x3), x3);
let x5: u11 = clz(x3);
let x6: s10 = (x1)[0];
let x7: u11 = !(x3);
(x3, x4, x6, x7, x4, x3)
}

0 comments on commit c09339f

Please sign in to comment.