mirror of https://github.com/llvm/circt.git
[Sim] Add printing operations and transformation from non-procedural to procedural flavor (#7292)
This commit is contained in:
parent
de80eb4381
commit
458717bc24
|
@ -12,3 +12,8 @@
|
|||
add_circt_dialect(Sim sim)
|
||||
add_circt_dialect_doc(Sim sim)
|
||||
add_dependencies(circt-headers MLIRSimIncGen)
|
||||
|
||||
set(LLVM_TARGET_DEFINITIONS SimPasses.td)
|
||||
mlir_tablegen(SimPasses.h.inc -gen-pass-decls)
|
||||
add_public_tablegen_target(CIRCTSimTransformsIncGen)
|
||||
add_dependencies(circt-headers CIRCTSimTransformsIncGen)
|
||||
|
|
|
@ -29,4 +29,24 @@
|
|||
#define GET_OP_CLASSES
|
||||
#include "circt/Dialect/Sim/Sim.h.inc"
|
||||
|
||||
namespace circt {
|
||||
namespace sim {
|
||||
|
||||
/// Returns the value operand of a value formatting operation.
|
||||
/// Returns a null value for all other operations.
|
||||
static inline mlir::Value getFormattedValue(mlir::Operation *fmtOp) {
|
||||
if (auto fmt = llvm::dyn_cast_or_null<circt::sim::FormatBinOp>(fmtOp))
|
||||
return fmt.getValue();
|
||||
if (auto fmt = llvm::dyn_cast_or_null<circt::sim::FormatDecOp>(fmtOp))
|
||||
return fmt.getValue();
|
||||
if (auto fmt = llvm::dyn_cast_or_null<circt::sim::FormatHexOp>(fmtOp))
|
||||
return fmt.getValue();
|
||||
if (auto fmt = llvm::dyn_cast_or_null<circt::sim::FormatCharOp>(fmtOp))
|
||||
return fmt.getValue();
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace sim
|
||||
} // namespace circt
|
||||
|
||||
#endif // CIRCT_DIALECT_SIM_SIMOPS_H
|
||||
|
|
|
@ -327,4 +327,42 @@ def FormatStringConcatOp : SimOp<"fmt.concat", [Pure]> {
|
|||
];
|
||||
}
|
||||
|
||||
def PrintFormattedOp : SimOp<"print"> {
|
||||
let summary = "Print a formatted string on a given clock and condition";
|
||||
|
||||
let description = [{
|
||||
Evaluate a format string and print it to the simulation console on the
|
||||
rising edge of the given clock, if, and only if, the condition argument
|
||||
is 'true'.
|
||||
|
||||
Multiple print operations in the same module and on the same clock edge
|
||||
are performed according to their order of occurence in the IR. The order
|
||||
of printing for operations in different modules, instances or on different
|
||||
clocks is undefined.
|
||||
}];
|
||||
|
||||
|
||||
let arguments = (ins FormatStringType:$input, ClockType:$clock, I1:$condition);
|
||||
|
||||
let hasCanonicalizeMethod = true;
|
||||
let assemblyFormat = "$input `on` $clock `if` $condition attr-dict";
|
||||
}
|
||||
|
||||
def PrintFormattedProcOp : SimOp<"proc.print"> {
|
||||
let summary = "Print a formatted string within a procedural region";
|
||||
|
||||
let description = [{
|
||||
Evaluate a format string and print it to the simulation console.
|
||||
|
||||
This operation must be within a procedural region.
|
||||
}];
|
||||
|
||||
let arguments = (ins FormatStringType:$input);
|
||||
|
||||
let hasVerifier = true;
|
||||
let hasCanonicalizeMethod = true;
|
||||
|
||||
let assemblyFormat = "$input attr-dict";
|
||||
}
|
||||
|
||||
#endif // CIRCT_DIALECT_SIM_SIMOPS_TD
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
//===- SimPasses.h - Sim pass entry points ----------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This header file defines prototypes that expose pass constructors.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef CIRCT_DIALECT_SIM_SIMPASSES_H
|
||||
#define CIRCT_DIALECT_SIM_SIMPASSES_H
|
||||
|
||||
#include "mlir/Pass/Pass.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
|
||||
namespace circt {
|
||||
namespace sim {
|
||||
|
||||
#define GEN_PASS_DECL
|
||||
#include "circt/Dialect/Sim/SimPasses.h.inc"
|
||||
|
||||
/// Generate the code for registering passes.
|
||||
#define GEN_PASS_REGISTRATION
|
||||
#include "circt/Dialect/Sim/SimPasses.h.inc"
|
||||
} // namespace sim
|
||||
} // namespace circt
|
||||
|
||||
#endif // CIRCT_DIALECT_SIM_SIMPASSES_H
|
|
@ -0,0 +1,26 @@
|
|||
//===-- SimPasses.td - Sim pass definition file ------------*- tablegen -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file contains definitions for passes that work on the Sim dialect.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef CIRCT_DIALECT_SIM_SEQPASSES
|
||||
#define CIRCT_DIALECT_SIM_SEQPASSES
|
||||
|
||||
include "mlir/Pass/PassBase.td"
|
||||
|
||||
def ProceduralizeSim : Pass<"sim-proceduralize", "hw::HWModuleOp"> {
|
||||
let summary = "Transform non-procedural to procedural operations.";
|
||||
let description = [{Transform non-procedural simulation operations with clock
|
||||
and enable to procedural operations wrapped in
|
||||
a procedural region.}];
|
||||
let dependentDialects = ["circt::hw::HWDialect, circt::seq::SeqDialect, mlir::scf::SCFDialect"];
|
||||
}
|
||||
|
||||
#endif // CIRCT_DIALECT_SIM_SEQPASSES
|
|
@ -34,6 +34,7 @@
|
|||
#include "circt/Dialect/SSP/SSPPasses.h"
|
||||
#include "circt/Dialect/SV/SVPasses.h"
|
||||
#include "circt/Dialect/Seq/SeqPasses.h"
|
||||
#include "circt/Dialect/Sim/SimPasses.h"
|
||||
#include "circt/Dialect/SystemC/SystemCPasses.h"
|
||||
#include "circt/Dialect/Verif/VerifPasses.h"
|
||||
#include "circt/Tools/circt-bmc/Passes.h"
|
||||
|
@ -72,6 +73,7 @@ inline void registerAllPasses() {
|
|||
ibis::registerPasses();
|
||||
hw::registerPasses();
|
||||
pipeline::registerPasses();
|
||||
sim::registerPasses();
|
||||
ssp::registerPasses();
|
||||
systemc::registerPasses();
|
||||
verif::registerPasses();
|
||||
|
|
|
@ -33,3 +33,5 @@ add_circt_dialect_library(CIRCTSim
|
|||
MLIRPass
|
||||
MLIRTransforms
|
||||
)
|
||||
|
||||
add_subdirectory(Transforms)
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "circt/Dialect/Sim/SimOps.h"
|
||||
#include "circt/Dialect/HW/ModuleImplementation.h"
|
||||
#include "circt/Dialect/SV/SVOps.h"
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||
#include "mlir/IR/PatternMatch.h"
|
||||
#include "mlir/Interfaces/FunctionImplementation.h"
|
||||
|
||||
|
@ -346,6 +345,53 @@ LogicalResult FormatStringConcatOp::canonicalize(FormatStringConcatOp op,
|
|||
return success();
|
||||
}
|
||||
|
||||
LogicalResult PrintFormattedOp::canonicalize(PrintFormattedOp op,
|
||||
PatternRewriter &rewriter) {
|
||||
// Remove ops with constant false condition.
|
||||
if (auto cstCond = op.getCondition().getDefiningOp<hw::ConstantOp>()) {
|
||||
if (cstCond.getValue().isZero()) {
|
||||
rewriter.eraseOp(op);
|
||||
return success();
|
||||
}
|
||||
}
|
||||
return failure();
|
||||
}
|
||||
|
||||
LogicalResult PrintFormattedProcOp::verify() {
|
||||
// Check if we know for sure that the parent is not procedural.
|
||||
auto *parentOp = getOperation()->getParentOp();
|
||||
|
||||
if (!parentOp)
|
||||
return emitOpError("must be within a procedural region.");
|
||||
|
||||
if (isa<hw::HWDialect>(parentOp->getDialect())) {
|
||||
if (!isa<hw::TriggeredOp>(parentOp))
|
||||
return emitOpError("must be within a procedural region.");
|
||||
return success();
|
||||
}
|
||||
|
||||
if (isa<sv::SVDialect>(parentOp->getDialect())) {
|
||||
if (!parentOp->hasTrait<sv::ProceduralRegion>())
|
||||
return emitOpError("must be within a procedural region.");
|
||||
return success();
|
||||
}
|
||||
|
||||
// Don't fail for dialects that are not explicitly handled.
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult PrintFormattedProcOp::canonicalize(PrintFormattedProcOp op,
|
||||
PatternRewriter &rewriter) {
|
||||
// Remove empty prints.
|
||||
if (auto litInput = op.getInput().getDefiningOp<FormatLitOp>()) {
|
||||
if (litInput.getLiteral().empty()) {
|
||||
rewriter.eraseOp(op);
|
||||
return success();
|
||||
}
|
||||
}
|
||||
return failure();
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// TableGen generated logic.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
add_circt_dialect_library(CIRCTSimTransforms
|
||||
ProceduralizeSim.cpp
|
||||
|
||||
|
||||
DEPENDS
|
||||
CIRCTSimTransformsIncGen
|
||||
|
||||
LINK_LIBS PUBLIC
|
||||
CIRCTHW
|
||||
CIRCTSim
|
||||
CIRCTSeq
|
||||
CIRCTSV
|
||||
CIRCTComb
|
||||
CIRCTSupport
|
||||
MLIRIR
|
||||
MLIRPass
|
||||
MLIRSCFDialect
|
||||
MLIRTransformUtils
|
||||
)
|
|
@ -0,0 +1,267 @@
|
|||
//===- ProceduralizeSim.cpp - Conversion to procedural operations ---------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Transform non-procedural simulation operations with clock and enable to
|
||||
// procedural operations wrapped in a procedural region.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "circt/Dialect/HW/HWOps.h"
|
||||
#include "circt/Dialect/Seq/SeqOps.h"
|
||||
#include "circt/Dialect/Sim/SimOps.h"
|
||||
#include "circt/Dialect/Sim/SimTypes.h"
|
||||
#include "circt/Support/Debug.h"
|
||||
|
||||
#include "llvm/ADT/IndexedMap.h"
|
||||
#include "llvm/ADT/MapVector.h"
|
||||
#include "llvm/ADT/SetVector.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "mlir/Pass/Pass.h"
|
||||
|
||||
#define DEBUG_TYPE "proceduralize-sim"
|
||||
|
||||
namespace circt {
|
||||
namespace sim {
|
||||
#define GEN_PASS_DEF_PROCEDURALIZESIM
|
||||
#include "circt/Dialect/Sim/SimPasses.h.inc"
|
||||
} // namespace sim
|
||||
} // namespace circt
|
||||
|
||||
using namespace llvm;
|
||||
using namespace circt;
|
||||
using namespace sim;
|
||||
|
||||
namespace {
|
||||
struct ProceduralizeSimPass : impl::ProceduralizeSimBase<ProceduralizeSimPass> {
|
||||
public:
|
||||
void runOnOperation() override;
|
||||
|
||||
private:
|
||||
LogicalResult proceduralizePrintOps(Value clock,
|
||||
ArrayRef<PrintFormattedOp> printOps);
|
||||
SmallVector<Operation *> getPrintTokens(PrintFormattedOp op);
|
||||
void cleanup();
|
||||
|
||||
// Mapping Clock -> List of printf ops
|
||||
SmallMapVector<Value, SmallVector<PrintFormattedOp>, 2> printfOpMap;
|
||||
|
||||
// List of formatting ops to be pruned after proceduralization.
|
||||
SmallVector<Operation *> cleanupList;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
LogicalResult ProceduralizeSimPass::proceduralizePrintOps(
|
||||
Value clock, ArrayRef<PrintFormattedOp> printOps) {
|
||||
|
||||
// List of uniqued values to become arguments of the TriggeredOp.
|
||||
SmallSetVector<Value, 4> arguments;
|
||||
// Map printf ops -> flattened list of tokens
|
||||
SmallDenseMap<PrintFormattedOp, SmallVector<Operation *>, 4> tokenMap;
|
||||
SmallVector<Location> locs;
|
||||
SmallDenseSet<Value, 1> alwaysEnabledConditions;
|
||||
|
||||
locs.reserve(printOps.size());
|
||||
|
||||
for (auto printOp : printOps) {
|
||||
// Handle the print condition value. If it is not constant, it has to become
|
||||
// a region argument. If it is constant false, skip the operation.
|
||||
if (auto cstCond = printOp.getCondition().getDefiningOp<hw::ConstantOp>()) {
|
||||
if (cstCond.getValue().isAllOnes())
|
||||
alwaysEnabledConditions.insert(printOp.getCondition());
|
||||
else
|
||||
continue;
|
||||
} else {
|
||||
arguments.insert(printOp.getCondition());
|
||||
}
|
||||
|
||||
// Accumulate locations
|
||||
locs.push_back(printOp.getLoc());
|
||||
|
||||
// Get the flat list of formatting tokens and collect leaf tokens
|
||||
SmallVector<Value> flatString;
|
||||
if (auto concatInput =
|
||||
printOp.getInput().getDefiningOp<FormatStringConcatOp>()) {
|
||||
|
||||
auto isAcyclic = concatInput.getFlattenedInputs(flatString);
|
||||
if (failed(isAcyclic)) {
|
||||
printOp.emitError("Cyclic format string cannot be proceduralized.");
|
||||
return failure();
|
||||
}
|
||||
} else {
|
||||
flatString.push_back(printOp.getInput());
|
||||
}
|
||||
|
||||
auto &tokenList = tokenMap[printOp];
|
||||
assert(tokenList.empty() && "printf operation visited twice.");
|
||||
|
||||
for (auto &token : flatString) {
|
||||
auto *fmtOp = token.getDefiningOp();
|
||||
if (!fmtOp) {
|
||||
printOp.emitError("Proceduralization of format strings passed as block "
|
||||
"argument is unsupported.");
|
||||
return failure();
|
||||
}
|
||||
tokenList.push_back(fmtOp);
|
||||
// For non-literal tokens, the value to be formatted has to become an
|
||||
// argument.
|
||||
if (!llvm::isa<FormatLitOp>(fmtOp)) {
|
||||
auto fmtVal = getFormattedValue(fmtOp);
|
||||
assert(!!fmtVal && "Unexpected foramtting token op.");
|
||||
arguments.insert(fmtVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the hw::TriggeredOp
|
||||
OpBuilder builder(printOps.back());
|
||||
auto fusedLoc = builder.getFusedLoc(locs);
|
||||
|
||||
SmallVector<Value> argVec = arguments.takeVector();
|
||||
|
||||
auto clockConv = builder.createOrFold<seq::FromClockOp>(fusedLoc, clock);
|
||||
auto trigOp = builder.create<hw::TriggeredOp>(
|
||||
fusedLoc,
|
||||
hw::EventControlAttr::get(builder.getContext(),
|
||||
hw::EventControl::AtPosEdge),
|
||||
clockConv, argVec);
|
||||
|
||||
// Map the collected arguments to the newly created block arguments.
|
||||
IRMapping argumentMapper;
|
||||
unsigned idx = 0;
|
||||
for (auto arg : argVec) {
|
||||
argumentMapper.map(arg, trigOp.getBodyBlock()->getArgument(idx));
|
||||
idx++;
|
||||
}
|
||||
|
||||
// Materialize and map a 'true' constant within the TriggeredOp if required.
|
||||
builder.setInsertionPointToStart(trigOp.getBodyBlock());
|
||||
if (!alwaysEnabledConditions.empty()) {
|
||||
auto cstTrue = builder.createOrFold<hw::ConstantOp>(
|
||||
fusedLoc, IntegerAttr::get(builder.getI1Type(), 1));
|
||||
for (auto cstCond : alwaysEnabledConditions)
|
||||
argumentMapper.map(cstCond, cstTrue);
|
||||
}
|
||||
|
||||
SmallDenseMap<Operation *, Operation *> cloneMap;
|
||||
Value prevConditionValue;
|
||||
Block *prevConditionBlock;
|
||||
|
||||
for (auto printOp : printOps) {
|
||||
|
||||
// Throw away disabled prints
|
||||
if (auto cstCond = printOp.getCondition().getDefiningOp<hw::ConstantOp>()) {
|
||||
if (cstCond.getValue().isZero()) {
|
||||
printOp.erase();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a copy of the required token operations within the TriggeredOp's
|
||||
// body.
|
||||
auto tokens = tokenMap[printOp];
|
||||
SmallVector<Value> clonedOperands;
|
||||
builder.setInsertionPointToStart(trigOp.getBodyBlock());
|
||||
for (auto *token : tokens) {
|
||||
auto &fmtCloned = cloneMap[token];
|
||||
if (!fmtCloned)
|
||||
fmtCloned = builder.clone(*token, argumentMapper);
|
||||
clonedOperands.push_back(fmtCloned->getResult(0));
|
||||
}
|
||||
// Concatenate tokens to a single value if necessary.
|
||||
Value procPrintInput;
|
||||
if (clonedOperands.size() != 1)
|
||||
procPrintInput = builder.createOrFold<FormatStringConcatOp>(
|
||||
printOp.getLoc(), clonedOperands);
|
||||
else
|
||||
procPrintInput = clonedOperands.front();
|
||||
|
||||
// Check if we can reuse the previous conditional block.
|
||||
auto condArg = argumentMapper.lookup(printOp.getCondition());
|
||||
if (condArg != prevConditionValue)
|
||||
prevConditionBlock = nullptr;
|
||||
auto *condBlock = prevConditionBlock;
|
||||
|
||||
// If not, create a new scf::IfOp for the condition.
|
||||
if (!condBlock) {
|
||||
builder.setInsertionPointToEnd(trigOp.getBodyBlock());
|
||||
auto ifOp = builder.create<mlir::scf::IfOp>(printOp.getLoc(), TypeRange{},
|
||||
condArg, true, false);
|
||||
builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
|
||||
builder.create<mlir::scf::YieldOp>(printOp.getLoc());
|
||||
condBlock = builder.getBlock();
|
||||
prevConditionValue = condArg;
|
||||
prevConditionBlock = condBlock;
|
||||
}
|
||||
|
||||
// Create the procedural print operation and prune the operations outside of
|
||||
// the TriggeredOp.
|
||||
builder.setInsertionPoint(condBlock->getTerminator());
|
||||
builder.create<PrintFormattedProcOp>(printOp.getLoc(), procPrintInput);
|
||||
cleanupList.push_back(printOp.getInput().getDefiningOp());
|
||||
printOp.erase();
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
// Prune the DAGs of formatting tokens left outside of the newly created
|
||||
// TriggeredOps.
|
||||
void ProceduralizeSimPass::cleanup() {
|
||||
SmallVector<Operation *> cleanupNextList;
|
||||
SmallDenseSet<Operation *> erasedOps;
|
||||
|
||||
bool noChange = true;
|
||||
while (!cleanupList.empty() || !cleanupNextList.empty()) {
|
||||
|
||||
if (cleanupList.empty()) {
|
||||
if (noChange)
|
||||
break;
|
||||
cleanupList = std::move(cleanupNextList);
|
||||
cleanupNextList = {};
|
||||
noChange = true;
|
||||
}
|
||||
|
||||
auto *opToErase = cleanupList.pop_back_val();
|
||||
if (erasedOps.contains(opToErase))
|
||||
continue;
|
||||
|
||||
if (opToErase->getUses().empty()) {
|
||||
// Remove a dead op. If it is a concat remove its operands, too.
|
||||
if (auto concat = dyn_cast<FormatStringConcatOp>(opToErase))
|
||||
for (auto operand : concat.getInputs())
|
||||
cleanupNextList.push_back(operand.getDefiningOp());
|
||||
opToErase->erase();
|
||||
erasedOps.insert(opToErase);
|
||||
noChange = false;
|
||||
} else {
|
||||
// Op still has uses, revisit later.
|
||||
cleanupNextList.push_back(opToErase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProceduralizeSimPass::runOnOperation() {
|
||||
LLVM_DEBUG(debugPassHeader(this) << "\n");
|
||||
printfOpMap.clear();
|
||||
cleanupList.clear();
|
||||
|
||||
auto theModule = getOperation();
|
||||
// Collect printf operations grouped by their clock.
|
||||
theModule.walk<mlir::WalkOrder::PreOrder>(
|
||||
[&](PrintFormattedOp op) { printfOpMap[op.getClock()].push_back(op); });
|
||||
|
||||
// Create a hw::TriggeredOp for each clock
|
||||
for (auto &[clock, printOps] : printfOpMap)
|
||||
if (failed(proceduralizePrintOps(clock, printOps))) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
cleanup();
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// RUN: circt-opt --canonicalize %s | FileCheck %s
|
||||
|
||||
// CHECK-LABEL: hw.module @always_disabled
|
||||
// CHECK-NOT: sim.print
|
||||
hw.module @always_disabled(in %clock: !seq.clock) {
|
||||
%false = hw.constant false
|
||||
%lit = sim.fmt.lit "Foo"
|
||||
sim.print %lit on %clock if %false
|
||||
}
|
||||
|
||||
// CHECK-LABEL: hw.module @emtpy_proc_print
|
||||
// CHECK-NOT: sim.proc.print
|
||||
hw.module @emtpy_proc_print(in %trigger: i1) {
|
||||
hw.triggered posedge %trigger {
|
||||
%epsilon = sim.fmt.lit ""
|
||||
sim.proc.print %epsilon
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// RUN: circt-opt --sim-proceduralize --split-input-file --verify-diagnostics %s
|
||||
|
||||
hw.module @cyclic_concat(in %clk : !seq.clock) {
|
||||
%true = hw.constant true
|
||||
%ping = sim.fmt.concat (%pong)
|
||||
%pong = sim.fmt.concat (%ping)
|
||||
// expected-error @below {{Cyclic format string cannot be proceduralized.}}
|
||||
sim.print %ping on %clk if %true
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
// RUN: circt-opt --sim-proceduralize --canonicalize %s | FileCheck %s
|
||||
|
||||
// CHECK-LABEL: @basic_print1
|
||||
// CHECK-NEXT: %[[TRG:.*]] = seq.from_clock %clk
|
||||
// CHECK-NEXT: hw.triggered posedge %[[TRG]] {
|
||||
// CHECK-NEXT: %[[LIT:.*]] = sim.fmt.lit "Test"
|
||||
// CHECK-NEXT: sim.proc.print %[[LIT]]
|
||||
// CHECK-NEXT: }
|
||||
|
||||
hw.module @basic_print1(in %clk : !seq.clock) {
|
||||
%true = hw.constant true
|
||||
%test = sim.fmt.lit "Test"
|
||||
sim.print %test on %clk if %true
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @basic_print2
|
||||
// CHECK-NEXT: %[[TRG:.*]] = seq.from_clock %clk
|
||||
// CHECK-NEXT: hw.triggered posedge %[[TRG]](%cond) : i1 {
|
||||
// CHECK-NEXT: ^bb0(%[[ARG:.*]]: i1):
|
||||
// CHECK-DAG: %[[LIT1:.*]] = sim.fmt.lit "Not with a bang but a \00"
|
||||
// CHECK-DAG: %[[LIT0:.*]] = sim.fmt.lit "This is the way the world ends\0A"
|
||||
// CHECK: scf.if %[[ARG]] {
|
||||
// CHECK-NEXT: sim.proc.print %[[LIT0]]
|
||||
// CHECK-NEXT: sim.proc.print %[[LIT0]]
|
||||
// CHECK-NEXT: sim.proc.print %[[LIT0]]
|
||||
// CHECK-NEXT: sim.proc.print %[[LIT1]]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: }
|
||||
|
||||
hw.module @basic_print2(in %clk : !seq.clock, in %cond : i1) {
|
||||
%0 = sim.fmt.lit "Not with a bang but a \00"
|
||||
%1 = sim.fmt.lit "This is the way the world ends\0A"
|
||||
|
||||
sim.print %1 on %clk if %cond
|
||||
sim.print %1 on %clk if %cond
|
||||
sim.print %1 on %clk if %cond
|
||||
sim.print %0 on %clk if %cond
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @basic_print3
|
||||
// CHECK-NEXT: %[[TRG:.*]] = seq.from_clock %clk
|
||||
// CHECK-NEXT: hw.triggered posedge %[[TRG]](%val) : i32 {
|
||||
// CHECK-NEXT: ^bb0(%[[ARG:.*]]: i32):
|
||||
// CHECK-DAG: %[[LB:.*]] = sim.fmt.lit "Bin: "
|
||||
// CHECK-DAG: %[[LD:.*]] = sim.fmt.lit ", Dec: "
|
||||
// CHECK-DAG: %[[LH:.*]] = sim.fmt.lit ", Hex: "
|
||||
// CHECK-DAG: %[[FB:.*]] = sim.fmt.bin %[[ARG]] : i32
|
||||
// CHECK-DAG: %[[FD:.*]] = sim.fmt.dec %[[ARG]] : i32
|
||||
// CHECK-DAG: %[[FH:.*]] = sim.fmt.hex %[[ARG]] : i32
|
||||
// CHECK-DAG: %[[CAT:.*]] = sim.fmt.concat (%[[LB]], %[[FB]], %[[LD]], %[[FD]], %[[LH]], %[[FH]])
|
||||
// CHECK: sim.proc.print %[[CAT]]
|
||||
// CHECK-NEXT: }
|
||||
|
||||
hw.module @basic_print3(in %clk : !seq.clock, in %val: i32) {
|
||||
%true = hw.constant true
|
||||
%comma = sim.fmt.lit ", "
|
||||
|
||||
%bin_lit = sim.fmt.lit "Bin: "
|
||||
%bin_val = sim.fmt.bin %val : i32
|
||||
%bin_cat = sim.fmt.concat (%bin_lit, %bin_val)
|
||||
|
||||
%dec_lit = sim.fmt.lit "Dec: "
|
||||
%dec_val = sim.fmt.dec %val : i32
|
||||
%dec_cat = sim.fmt.concat (%dec_lit, %dec_val)
|
||||
|
||||
%hex_lit = sim.fmt.lit "Hex: "
|
||||
%hex_val = sim.fmt.hex %val : i32
|
||||
%hex_cat = sim.fmt.concat (%hex_lit, %hex_val)
|
||||
|
||||
%str = sim.fmt.concat (%bin_cat, %comma, %dec_cat, %comma, %hex_cat)
|
||||
|
||||
sim.print %str on %clk if %true
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @multi_args
|
||||
// CHECK-NEXT: %[[TRG:.*]] = seq.from_clock %clk
|
||||
// CHECK-NEXT: hw.triggered posedge %0(%a, %b, %c) : i8, i8, i8 {
|
||||
// CHECK-NEXT: ^bb0(%[[ARG0:.*]]: i8, %[[ARG1:.*]]: i8, %[[ARG2:.*]]: i8):
|
||||
// CHECK-DAG: %[[COM:.*]] = sim.fmt.lit ", "
|
||||
// CHECK-DAG: %[[B0:.*]] = sim.fmt.bin %[[ARG0]] : i8
|
||||
// CHECK-DAG: %[[H0:.*]] = sim.fmt.hex %[[ARG0]] : i8
|
||||
// CHECK-DAG: %[[B1:.*]] = sim.fmt.bin %[[ARG1]] : i8
|
||||
// CHECK-DAG: %[[H1:.*]] = sim.fmt.hex %[[ARG1]] : i8
|
||||
// CHECK-DAG: %[[B2:.*]] = sim.fmt.bin %[[ARG2]] : i8
|
||||
// CHECK-DAG: %[[H2:.*]] = sim.fmt.hex %[[ARG2]] : i8
|
||||
// CHECK-DAG: %[[CAT:.*]] = sim.fmt.concat (%[[B0]], %[[B1]], %[[B2]], %[[COM]], %[[H0]], %[[H1]], %[[H2]])
|
||||
// CHECK: sim.proc.print %[[CAT]]
|
||||
// CHECK-NEXT: }
|
||||
|
||||
hw.module @multi_args(in %clk : !seq.clock, in %a: i8, in %b: i8, in %c: i8) {
|
||||
%true = hw.constant true
|
||||
%comma = sim.fmt.lit ", "
|
||||
|
||||
%bina = sim.fmt.bin %a : i8
|
||||
%binb = sim.fmt.bin %b : i8
|
||||
%binc = sim.fmt.bin %c : i8
|
||||
|
||||
%hexa = sim.fmt.hex %a : i8
|
||||
%hexb = sim.fmt.hex %b : i8
|
||||
%hexc = sim.fmt.hex %c : i8
|
||||
|
||||
%cat = sim.fmt.concat (%bina, %binb, %binc, %comma, %hexa, %hexb, %hexc)
|
||||
|
||||
sim.print %cat on %clk if %true
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @multi_clock
|
||||
// CHECK-NEXT: %[[TRGA:.*]] = seq.from_clock %clka
|
||||
// CHECK-NEXT: hw.triggered posedge %[[TRGA]](%val) : i32 {
|
||||
// CHECK-NEXT: ^bb0(%[[ARGA:.*]]: i32):
|
||||
// CHECK-DAG: %[[LA0:.*]] = sim.fmt.lit "Val is 0x"
|
||||
// CHECK-DAG: %[[LA1:.*]] = sim.fmt.lit " on A."
|
||||
// CHECK-DAG: %[[FA:.*]] = sim.fmt.hex %[[ARGA]] : i32
|
||||
// CHECK-DAG: %[[CATA:.*]] = sim.fmt.concat (%[[LA0]], %[[FA]], %[[LA1]])
|
||||
// CHECK: sim.proc.print %[[CATA]]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: %[[TRGB:.*]] = seq.from_clock %clkb
|
||||
// CHECK-NEXT: hw.triggered posedge %[[TRGB]](%val) : i32 {
|
||||
// CHECK-NEXT: ^bb0(%[[ARGB:.*]]: i32):
|
||||
// CHECK-DAG: %[[LB0:.*]] = sim.fmt.lit "Val is 0x"
|
||||
// CHECK-DAG: %[[LB1:.*]] = sim.fmt.lit " on B."
|
||||
// CHECK-DAG: %[[FB:.*]] = sim.fmt.hex %[[ARGB]] : i32
|
||||
// CHECK-DAG: %[[CATB:.*]] = sim.fmt.concat (%[[LB0]], %[[FB]], %[[LB1]])
|
||||
// CHECK: sim.proc.print %[[CATB]]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: %[[TRGC:.*]] = seq.from_clock %clkc
|
||||
// CHECK-NEXT: hw.triggered posedge %[[TRGC]](%val) : i32 {
|
||||
// CHECK-NEXT: ^bb0(%[[ARGC:.*]]: i32):
|
||||
// CHECK-DAG: %[[LC0:.*]] = sim.fmt.lit "Val is 0x"
|
||||
// CHECK-DAG: %[[LC1:.*]] = sim.fmt.lit " on C."
|
||||
// CHECK-DAG: %[[FC:.*]] = sim.fmt.hex %[[ARGC]] : i32
|
||||
// CHECK-DAG: %[[CATC:.*]] = sim.fmt.concat (%[[LC0]], %[[FC]], %[[LC1]])
|
||||
// CHECK: sim.proc.print %[[CATC]]
|
||||
// CHECK-NEXT: }
|
||||
|
||||
hw.module @multi_clock(in %clka : !seq.clock, in %clkb : !seq.clock, in %clkc : !seq.clock, in %val: i32) {
|
||||
%true = hw.constant true
|
||||
%pre = sim.fmt.lit "Val is 0x"
|
||||
%hex_val = sim.fmt.hex %val : i32
|
||||
|
||||
%onA = sim.fmt.lit " on A."
|
||||
%onB = sim.fmt.lit " on B."
|
||||
%onC = sim.fmt.lit " on C."
|
||||
|
||||
%catA = sim.fmt.concat (%pre, %hex_val, %onA)
|
||||
sim.print %catA on %clka if %true
|
||||
|
||||
%catB = sim.fmt.concat (%pre, %hex_val, %onB)
|
||||
sim.print %catB on %clkb if %true
|
||||
|
||||
%catC = sim.fmt.concat (%pre, %hex_val, %onC)
|
||||
sim.print %catC on %clkc if %true
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @sequence
|
||||
// CHECK-NEXT: %[[TRG:.*]] = seq.from_clock %clk
|
||||
// CHECK-NEXT: hw.triggered posedge %[[TRG]](%conda, %condb, %val) : i1, i1, i32 {
|
||||
// CHECK-NEXT: ^bb0(%[[ARG0:.*]]: i1, %[[ARG1:.*]]: i1, %[[ARG2:.*]]: i32):
|
||||
// CHECK-DAG: %[[L1:.*]] = sim.fmt.lit "#1"
|
||||
// CHECK-DAG: %[[L2:.*]] = sim.fmt.lit "#2"
|
||||
// CHECK-DAG: %[[L3:.*]] = sim.fmt.lit "#3"
|
||||
// CHECK-DAG: %[[L4:.*]] = sim.fmt.lit "#4"
|
||||
// CHECK-DAG: %[[L5:.*]] = sim.fmt.lit "#5"
|
||||
// CHECK-DAG: %[[L6:.*]] = sim.fmt.lit "#6"
|
||||
// CHECK-DAG: %[[BIN:.*]] = sim.fmt.bin %[[ARG2]] : i32
|
||||
// CHECK: scf.if %[[ARG0]] {
|
||||
// CHECK-NEXT: sim.proc.print %[[L1]]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: scf.if %[[ARG1]] {
|
||||
// CHECK-NEXT: sim.proc.print %[[L2]]
|
||||
// CHECK-NEXT: sim.proc.print %[[L3]]
|
||||
// CHECK-NEXT: sim.proc.print %[[L4]]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: scf.if %[[ARG0]] {
|
||||
// CHECK-NEXT: sim.proc.print %[[L5]]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: sim.proc.print %[[BIN]]
|
||||
// CHECK-NEXT: scf.if %[[ARG0]] {
|
||||
// CHECK-NEXT: sim.proc.print %[[L6]]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: }
|
||||
|
||||
hw.module @sequence(in %clk: !seq.clock, in %conda: i1, in %condb: i1, in %val : i32) {
|
||||
%true = hw.constant true
|
||||
%false = hw.constant false
|
||||
|
||||
%1 = sim.fmt.lit "#1"
|
||||
sim.print %1 on %clk if %conda
|
||||
%2 = sim.fmt.lit "#2"
|
||||
sim.print %2 on %clk if %condb
|
||||
%3 = sim.fmt.lit "#3"
|
||||
sim.print %3 on %clk if %condb
|
||||
%cdis = sim.fmt.lit "--"
|
||||
sim.print %cdis on %clk if %false
|
||||
%4 = sim.fmt.lit "#4"
|
||||
sim.print %4 on %clk if %condb
|
||||
%5 = sim.fmt.lit "#5"
|
||||
sim.print %5 on %clk if %conda
|
||||
%cen = sim.fmt.bin %val : i32
|
||||
sim.print %cen on %clk if %true
|
||||
%6 = sim.fmt.lit "#6"
|
||||
sim.print %6 on %clk if %conda
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @condition_as_val
|
||||
// CHECK-NEXT: %[[TRG:.*]] = seq.from_clock %clk
|
||||
// CHECK-NEXT: hw.triggered posedge %[[TRG]](%condval) : i1 {
|
||||
// CHECK-NEXT: ^bb0(%[[ARG:.*]]: i1):
|
||||
// CHECK-NEXT: %[[BIN:.*]] = sim.fmt.bin %[[ARG]] : i1
|
||||
// CHECK-NEXT: scf.if %[[ARG]] {
|
||||
// CHECK-NEXT: sim.proc.print %[[BIN]]
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: }
|
||||
|
||||
hw.module @condition_as_val(in %clk: !seq.clock, in %condval: i1) {
|
||||
%bin = sim.fmt.bin %condval : i1
|
||||
sim.print %bin on %clk if %condval
|
||||
}
|
|
@ -23,3 +23,22 @@ hw.module @fmt_infinite_concat_canonicalize(in %val : i8, out res: !sim.fstring)
|
|||
%cat = sim.fmt.concat (%4, %c, %5)
|
||||
hw.output %cat : !sim.fstring
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
hw.module @proc_print_hw() {
|
||||
%lit = sim.fmt.lit "Nope"
|
||||
// expected-error @below {{must be within a procedural region.}}
|
||||
sim.proc.print %lit
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
sv.macro.decl @SOMEMACRO
|
||||
hw.module @proc_print_sv() {
|
||||
%lit = sim.fmt.lit "Nope"
|
||||
sv.ifdef @SOMEMACRO {
|
||||
// expected-error @below {{must be within a procedural region.}}
|
||||
sim.proc.print %lit
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@ target_link_libraries(circt-opt
|
|||
CIRCTSeqToSV
|
||||
CIRCTSeqTransforms
|
||||
CIRCTSimToSV
|
||||
CIRCTSimTransforms
|
||||
CIRCTSSP
|
||||
CIRCTSSPTransforms
|
||||
CIRCTCFToHandshake
|
||||
|
|
Loading…
Reference in New Issue