[ExportVerilog] Move emitTypeDims/printPackedType into ModuleEmitter.

This makes way for dimensions that can refer to declarations within the
module, like parameters.

Note that this exposes some design problems with TypeAliasType.
I just xfailed a test for now (the expected diagnostics are emitted on
the wrong line now) where ExportVerilog is implementing verification of
these decls instead of the verifier.  I will tackle that as its own
separate workstream, since type parameters needs a sound design as well.
This commit is contained in:
Chris Lattner 2021-10-16 15:33:07 -07:00
parent 107d378d61
commit 4b9a8a328b
4 changed files with 202 additions and 149 deletions

View File

@ -170,7 +170,7 @@ def TypeAliasType : HWType<"TypeAlias"> {
];
let extraClassDeclaration = [{
Optional<TypedeclOp> getDecl(Operation *op);
Optional<TypedeclOp> getTypeDecl(Operation *op);
}];
}

View File

@ -196,29 +196,6 @@ static void getTypeDims(SmallVectorImpl<int64_t> &dims, Type type,
}
}
/// Emit a list of dimensions.
static void emitDims(ArrayRef<int64_t> dims, raw_ostream &os) {
for (int64_t width : dims)
switch (width) {
case -1: // -1 is an invalid type.
os << "<<invalid type>>";
return;
case 0:
os << "/*Zero Width*/";
break;
default:
os << '[' << (width - 1) << ":0]";
break;
}
}
/// Emit a type's packed dimensions, returning whether or not text was emitted.
static void emitTypeDims(Type type, Location loc, raw_ostream &os) {
SmallVector<int64_t, 4> dims;
getTypeDims(dims, type, loc);
emitDims(dims, os);
}
/// True iff 'a' and 'b' have the same wire dims.
static bool haveMatchingDims(Type a, Type b, Location loc) {
SmallVector<int64_t, 4> aDims;
@ -260,99 +237,6 @@ static Type stripUnpackedTypes(Type type) {
.Default([](Type type) { return type; });
}
/// Output the basic type that consists of packed and primitive types. This is
/// those to the left of the name in verilog. implicitIntType controls whether
/// to print a base type for (logic) for inteters or whether the caller will
/// have handled this (with logic, wire, reg, etc).
/// Returns true when anything was printed out.
static bool printPackedTypeImpl(Type type, raw_ostream &os, Operation *op,
SmallVectorImpl<int64_t> &dims,
bool implicitIntType,
bool singleBitDefaultType) {
return TypeSwitch<Type, bool>(type)
.Case<IntegerType>([&](IntegerType integerType) {
if (!implicitIntType)
os << "logic";
if (integerType.getWidth() != 1 || !singleBitDefaultType)
dims.push_back(integerType.getWidth());
if (!dims.empty() && !implicitIntType)
os << ' ';
emitDims(dims, os);
return !dims.empty() || !implicitIntType;
})
.Case<InOutType>([&](InOutType inoutType) {
return printPackedTypeImpl(inoutType.getElementType(), os, op, dims,
implicitIntType, singleBitDefaultType);
})
.Case<StructType>([&](StructType structType) {
os << "struct packed {";
for (auto &element : structType.getElements()) {
SmallVector<int64_t, 8> structDims;
printPackedTypeImpl(stripUnpackedTypes(element.type), os, op,
structDims, /*implicitIntType=*/false,
/*singleBitDefaultType=*/true);
os << ' ' << element.name << "; ";
}
os << '}';
emitDims(dims, os);
return true;
})
.Case<ArrayType>([&](ArrayType arrayType) {
dims.push_back(arrayType.getSize());
return printPackedTypeImpl(arrayType.getElementType(), os, op, dims,
implicitIntType, singleBitDefaultType);
})
.Case<InterfaceType>([](InterfaceType ifaceType) { return false; })
.Case<UnpackedArrayType>([&](UnpackedArrayType arrayType) {
os << "<<unexpected unpacked array>>";
op->emitError("Unexpected unpacked array in packed type ") << arrayType;
return true;
})
.Case<TypeAliasType>([&](TypeAliasType typeRef) {
auto typedecl = typeRef.getDecl(op);
if (!typedecl.hasValue())
return false;
os << typedecl.getValue().getPreferredName();
emitDims(dims, os);
return true;
})
.Default([&](Type type) {
os << "<<invalid type '" << type << "'>>";
op->emitError("value has an unsupported verilog type ") << type;
return true;
});
}
/// Print the specified packed portion of the type to the specified stream. The
/// operation specified is used in the case of an error for its location.
/// * When `implicitIntType` is false, a "logic" is printed. This is used in
/// struct fields and typedefs.
/// * When `singleBitDefaultType` is false, single bit values are printed as
/// `[0:0]`. This is used in parameter lists.
/// This returns true if anything was printed out.
static bool printPackedType(Type type, raw_ostream &os, Operation *op,
bool implicitIntType = true,
bool singleBitDefaultType = true) {
SmallVector<int64_t, 8> packedDimensions;
return printPackedTypeImpl(type, os, op, packedDimensions, implicitIntType,
singleBitDefaultType);
}
/// Output the unpacked array dimensions. This is the part of the type that is
/// to the right of the name.
static void printUnpackedTypePostfix(Type type, raw_ostream &os) {
TypeSwitch<Type, void>(type)
.Case<InOutType>([&](InOutType inoutType) {
printUnpackedTypePostfix(inoutType.getElementType(), os);
})
.Case<UnpackedArrayType>([&](UnpackedArrayType arrayType) {
printUnpackedTypePostfix(arrayType.getElementType(), os);
os << "[0:" << (arrayType.getSize() - 1) << "]";
});
}
/// Return the word (e.g. "reg") in Verilog to declare the specified thing.
static StringRef getVerilogDeclWord(Operation *op,
const LoweringOptions &options) {
@ -620,12 +504,16 @@ namespace {
/// various emitters.
class VerilogEmitterState {
public:
explicit VerilogEmitterState(const LoweringOptions &options,
explicit VerilogEmitterState(ModuleOp designOp,
const LoweringOptions &options,
const SymbolCache &symbolCache,
const GlobalNameTable &globalNames,
raw_ostream &os)
: options(options), symbolCache(symbolCache), globalNames(globalNames),
os(os) {}
: designOp(designOp), options(options), symbolCache(symbolCache),
globalNames(globalNames), os(os) {}
/// This is the root mlir::ModuleOp that holds the whole design being emitted.
ModuleOp designOp;
/// The emitter options which control verilog emission.
const LoweringOptions options;
@ -816,16 +704,43 @@ public:
void emitBind(BindOp op);
void emitBindInterface(BindInterfaceOp op);
//===--------------------------------------------------------------------===//
// Methods for formatting types.
/// Emit a type's packed dimensions.
void emitTypeDims(Type type, Location loc, raw_ostream &os);
/// Print the specified packed portion of the type to the specified stream,
///
/// * When `implicitIntType` is false, a "logic" is printed. This is used in
/// struct fields and typedefs.
/// * When `singleBitDefaultType` is false, single bit values are printed as
/// `[0:0]`. This is used in parameter lists.
///
/// This returns true if anything was printed.
bool printPackedType(Type type, raw_ostream &os, Location loc,
bool implicitIntType = true,
bool singleBitDefaultType = true);
/// Output the unpacked array dimensions. This is the part of the type that
/// is to the right of the name.
void printUnpackedTypePostfix(Type type, raw_ostream &os);
//===--------------------------------------------------------------------===//
// Methods for formatting parameters.
/// Prints a parameter attribute expression in a Verilog compatible way to the
/// specified stream. This returns the precedence of the generated string.
SubExprInfo printParamValue(Attribute value, raw_ostream &os,
function_ref<InFlightDiagnostic()> emitError);
public:
SubExprInfo printParamValue(Attribute value, raw_ostream &os,
VerilogPrecedence parenthesizeIfLooserThan,
function_ref<InFlightDiagnostic()> emitError);
//===--------------------------------------------------------------------===//
// Mutable state while emitting a module body.
/// This is the current module being emitted for a HWModuleOp.
HWModuleOp currentModuleOp;
@ -843,6 +758,133 @@ public:
} // end anonymous namespace
//===----------------------------------------------------------------------===//
// Methods for formatting types.
/// Emit a list of dimensions.
static void emitDims(ArrayRef<int64_t> dims, raw_ostream &os) {
for (int64_t width : dims)
switch (width) {
case -1: // -1 is an invalid type.
os << "<<invalid type>>";
return;
case 0:
os << "/*Zero Width*/";
break;
default:
os << '[' << (width - 1) << ":0]";
break;
}
}
/// Emit a type's packed dimensions.
void ModuleEmitter::emitTypeDims(Type type, Location loc, raw_ostream &os) {
SmallVector<int64_t, 4> dims;
getTypeDims(dims, type, loc);
emitDims(dims, os);
}
/// Output the basic type that consists of packed and primitive types. This is
/// those to the left of the name in verilog. implicitIntType controls whether
/// to print a base type for (logic) for inteters or whether the caller will
/// have handled this (with logic, wire, reg, etc).
///
/// Returns true when anything was printed out.
static bool printPackedTypeImpl(Type type, raw_ostream &os, Location loc,
SmallVectorImpl<int64_t> &dims,
bool implicitIntType, bool singleBitDefaultType,
ModuleEmitter &emitter) {
return TypeSwitch<Type, bool>(type)
.Case<IntegerType>([&](IntegerType integerType) {
if (!implicitIntType)
os << "logic";
if (integerType.getWidth() != 1 || !singleBitDefaultType)
dims.push_back(integerType.getWidth());
if (!dims.empty() && !implicitIntType)
os << ' ';
emitDims(dims, os);
return !dims.empty() || !implicitIntType;
})
.Case<InOutType>([&](InOutType inoutType) {
return printPackedTypeImpl(inoutType.getElementType(), os, loc, dims,
implicitIntType, singleBitDefaultType,
emitter);
})
.Case<StructType>([&](StructType structType) {
os << "struct packed {";
for (auto &element : structType.getElements()) {
SmallVector<int64_t, 8> structDims;
printPackedTypeImpl(stripUnpackedTypes(element.type), os, loc,
structDims, /*implicitIntType=*/false,
/*singleBitDefaultType=*/true, emitter);
os << ' ' << element.name << "; ";
}
os << '}';
emitDims(dims, os);
return true;
})
.Case<ArrayType>([&](ArrayType arrayType) {
dims.push_back(arrayType.getSize());
return printPackedTypeImpl(arrayType.getElementType(), os, loc, dims,
implicitIntType, singleBitDefaultType,
emitter);
})
.Case<InterfaceType>([](InterfaceType ifaceType) { return false; })
.Case<UnpackedArrayType>([&](UnpackedArrayType arrayType) {
os << "<<unexpected unpacked array>>";
mlir::emitError(loc, "Unexpected unpacked array in packed type ")
<< arrayType;
return true;
})
.Case<TypeAliasType>([&](TypeAliasType typeRef) {
auto typedecl = typeRef.getTypeDecl(emitter.state.designOp);
if (!typedecl.hasValue())
return false; // This already emitted an error!?
os << typedecl.getValue().getPreferredName();
emitDims(dims, os);
return true;
})
.Default([&](Type type) {
os << "<<invalid type '" << type << "'>>";
mlir::emitError(loc, "value has an unsupported verilog type ") << type;
return true;
});
}
/// Print the specified packed portion of the type to the specified stream,
///
/// * When `implicitIntType` is false, a "logic" is printed. This is used in
/// struct fields and typedefs.
/// * When `singleBitDefaultType` is false, single bit values are printed as
/// `[0:0]`. This is used in parameter lists.
///
/// This returns true if anything was printed.
bool ModuleEmitter::printPackedType(Type type, raw_ostream &os, Location loc,
bool implicitIntType,
bool singleBitDefaultType) {
SmallVector<int64_t, 8> packedDimensions;
return printPackedTypeImpl(type, os, loc, packedDimensions, implicitIntType,
singleBitDefaultType, *this);
}
/// Output the unpacked array dimensions. This is the part of the type that is
/// to the right of the name.
void ModuleEmitter::printUnpackedTypePostfix(Type type, raw_ostream &os) {
TypeSwitch<Type, void>(type)
.Case<InOutType>([&](InOutType inoutType) {
printUnpackedTypePostfix(inoutType.getElementType(), os);
})
.Case<UnpackedArrayType>([&](UnpackedArrayType arrayType) {
printUnpackedTypePostfix(arrayType.getElementType(), os);
os << "[0:" << (arrayType.getSize() - 1) << "]";
});
}
//===----------------------------------------------------------------------===//
// Methods for formatting parameters.
/// Prints a parameter attribute expression in a Verilog compatible way to the
/// specified stream. This returns the precedence of the generated string.
SubExprInfo
@ -1482,7 +1524,7 @@ SubExprInfo ExprEmitter::visitTypeOp(BitcastOp op) {
Type toType = op.getType();
if (!haveMatchingDims(toType, op.input().getType(), op.getLoc())) {
os << "/*cast(bit";
emitTypeDims(toType, op.getLoc(), os);
emitter.emitTypeDims(toType, op.getLoc(), os);
os << ")*/";
}
return emitSubExpr(op.input(), LowestPrecedence, OOLUnary);
@ -1930,8 +1972,8 @@ void NameCollector::collectNames(Block &block) {
// Convert the port's type to a string and measure it.
{
llvm::raw_svector_ostream stringStream(typeString);
printPackedType(stripUnpackedTypes(result.getType()), stringStream,
&op);
moduleEmitter.printPackedType(stripUnpackedTypes(result.getType()),
stringStream, op.getLoc());
}
maxTypeWidth = std::max(typeString.size(), maxTypeWidth);
}
@ -1970,7 +2012,8 @@ class TypeScopeEmitter
public hw::TypeScopeVisitor<TypeScopeEmitter, LogicalResult> {
public:
/// Create a TypeScopeEmitter for the specified module emitter.
TypeScopeEmitter(VerilogEmitterState &state) : EmitterBase(state) {}
TypeScopeEmitter(ModuleEmitter &emitter)
: EmitterBase(emitter.state), emitter(emitter) {}
void emitTypeScopeBlock(Block &body);
@ -1978,6 +2021,8 @@ private:
friend class TypeScopeVisitor<TypeScopeEmitter, LogicalResult>;
LogicalResult visitTypeScope(TypedeclOp op);
ModuleEmitter &emitter;
};
} // end anonymous namespace
@ -1993,8 +2038,9 @@ void TypeScopeEmitter::emitTypeScopeBlock(Block &body) {
LogicalResult TypeScopeEmitter::visitTypeScope(TypedeclOp op) {
indent() << "typedef ";
printPackedType(stripUnpackedTypes(op.type()), os, op, false);
printUnpackedTypePostfix(op.type(), os);
emitter.printPackedType(stripUnpackedTypes(op.type()), os, op.getLoc(),
false);
emitter.printUnpackedTypePostfix(op.type(), os);
os << ' ' << op.getPreferredName();
os << ";\n";
return success();
@ -3042,9 +3088,10 @@ LogicalResult StmtEmitter::visitSV(InterfaceOp op) {
LogicalResult StmtEmitter::visitSV(InterfaceSignalOp op) {
indent();
printPackedType(stripUnpackedTypes(op.type()), os, op, false);
emitter.printPackedType(stripUnpackedTypes(op.type()), os, op->getLoc(),
false);
os << ' ' << state.globalNames.getInterfaceVerilogName(op);
printUnpackedTypePostfix(op.type(), os);
emitter.printUnpackedTypePostfix(op.type(), os);
os << ";\n";
return success();
}
@ -3177,7 +3224,8 @@ bool StmtEmitter::emitDeclarationForTemporary(Operation *op) {
os << declWord;
if (!declWord.empty())
os << ' ';
if (printPackedType(stripUnpackedTypes(op->getResult(0).getType()), os, op))
if (emitter.printPackedType(stripUnpackedTypes(op->getResult(0).getType()),
os, op->getLoc()))
os << ' ';
os << names.getName(op->getResult(0));
@ -3249,7 +3297,7 @@ void StmtEmitter::collectNamesEmitDecls(Block &block) {
os << "()";
} else {
// Print out any array subscripts.
printUnpackedTypePostfix(type, os);
emitter.printUnpackedTypePostfix(type, os);
}
if (auto localparam = dyn_cast<LocalParamOp>(op)) {
@ -3488,7 +3536,7 @@ void ModuleEmitter::emitHWModule(HWModuleOp module) {
return;
}
printPackedType(type, sstream, module,
printPackedType(type, sstream, module->getLoc(),
/*implicitIntType=*/true,
// Print single-bit values as explicit `[0:0]` type.
/*singleBitDefaultType=*/false);
@ -3555,7 +3603,8 @@ void ModuleEmitter::emitHWModule(HWModuleOp module) {
portTypeStrings.push_back({});
{
llvm::raw_svector_ostream stringStream(portTypeStrings.back());
printPackedType(stripUnpackedTypes(port.type), stringStream, module);
printPackedType(stripUnpackedTypes(port.type), stringStream,
module->getLoc());
}
maxTypeWidth = std::max(portTypeStrings.back().size(), maxTypeWidth);
@ -3731,7 +3780,7 @@ private:
/// then shared across all per-file emissions that happen in parallel.
struct SharedEmitterState {
/// The MLIR module to emit.
ModuleOp rootOp;
ModuleOp designOp;
/// The main file that collects all operations that are neither replicated
/// per-file ops nor specifically assigned to a file.
@ -3765,8 +3814,9 @@ struct SharedEmitterState {
/// Information about renamed global symbols, parameters, etc.
const GlobalNameTable globalNames;
explicit SharedEmitterState(ModuleOp rootOp, GlobalNameTable globalNames)
: rootOp(rootOp), options(rootOp), globalNames(std::move(globalNames)) {}
explicit SharedEmitterState(ModuleOp designOp, GlobalNameTable globalNames)
: designOp(designOp), options(designOp),
globalNames(std::move(globalNames)) {}
void gatherFiles(bool separateModules);
using EmissionList = std::vector<StringOrOpToEmit>;
@ -3800,7 +3850,7 @@ void SharedEmitterState::gatherFiles(bool separateModules) {
};
SmallString<32> outputPath;
for (auto &op : *rootOp.getBody()) {
for (auto &op : *designOp.getBody()) {
auto info = OpFileInfo{&op, replicatedOps.size()};
bool hasFileName = false;
@ -3964,7 +4014,8 @@ static void emitOperation(VerilogEmitterState &state, Operation *op) {
.Case<InterfaceOp, VerbatimOp, IfDefOp>(
[&](auto op) { ModuleEmitter(state).emitStatement(op); })
.Case<TypeScopeOp>([&](auto typedecls) {
TypeScopeEmitter(state).emitTypeScopeBlock(*typedecls.getBodyBlock());
ModuleEmitter emitter(state);
TypeScopeEmitter(emitter).emitTypeScopeBlock(*typedecls.getBodyBlock());
})
.Default([&](auto *op) {
state.encounteredError = true;
@ -3976,7 +4027,7 @@ static void emitOperation(VerilogEmitterState &state, Operation *op) {
/// specified file.
void SharedEmitterState::emitOps(EmissionList &thingsToEmit, raw_ostream &os,
bool parallelize) {
MLIRContext *context = rootOp->getContext();
MLIRContext *context = designOp->getContext();
// Disable parallelization overhead if MLIR threading is disabled.
if (parallelize)
@ -3985,7 +4036,7 @@ void SharedEmitterState::emitOps(EmissionList &thingsToEmit, raw_ostream &os,
// If we aren't parallelizing output, directly output each operation to the
// specified stream.
if (!parallelize) {
VerilogEmitterState state(options, symbolCache, globalNames, os);
VerilogEmitterState state(designOp, options, symbolCache, globalNames, os);
for (auto &entry : thingsToEmit) {
if (auto *op = entry.getOperation())
emitOperation(state, op);
@ -4014,7 +4065,8 @@ void SharedEmitterState::emitOps(EmissionList &thingsToEmit, raw_ostream &os,
SmallString<256> buffer;
llvm::raw_svector_ostream tmpStream(buffer);
VerilogEmitterState state(options, symbolCache, globalNames, tmpStream);
VerilogEmitterState state(designOp, options, symbolCache, globalNames,
tmpStream);
emitOperation(state, op);
stringOrOp.setString(buffer);
});
@ -4030,7 +4082,7 @@ void SharedEmitterState::emitOps(EmissionList &thingsToEmit, raw_ostream &os,
}
// If this wasn't emitted to a string (e.g. it is a bind) do so now.
VerilogEmitterState state(options, symbolCache, globalNames, os);
VerilogEmitterState state(designOp, options, symbolCache, globalNames, os);
emitOperation(state, op);
}
}
@ -4117,9 +4169,8 @@ createOutputFile(StringRef fileName, StringRef dirname,
// Create the output directory if needed.
std::error_code error = llvm::sys::fs::create_directories(outputDir);
if (error) {
mlir::emitError(emitter.rootOp.getLoc(),
"cannot create output directory \"" + outputDir +
"\": " + error.message());
emitter.designOp.emitError("cannot create output directory \"")
<< outputDir << "\": " << error.message();
emitter.encounteredError = true;
return {};
}
@ -4128,7 +4179,7 @@ createOutputFile(StringRef fileName, StringRef dirname,
std::string errorMessage;
auto output = mlir::openOutputFile(outputFilename, &errorMessage);
if (!output) {
mlir::emitError(emitter.rootOp.getLoc(), errorMessage);
emitter.designOp.emitError(errorMessage);
emitter.encounteredError = true;
}
return output;

View File

@ -445,9 +445,11 @@ void TypeAliasType::print(DialectAsmPrinter &p) const {
p << getMnemonic() << "<" << getRef() << ", " << getInnerType() << ">";
}
Optional<TypedeclOp> TypeAliasType::getDecl(Operation *op) {
Optional<TypedeclOp> TypeAliasType::getTypeDecl(Operation *op) {
SymbolRefAttr ref = getRef();
auto moduleOp = op->getParentOfType<ModuleOp>();
auto moduleOp = ::dyn_cast<ModuleOp>(op);
if (!moduleOp)
moduleOp = op->getParentOfType<ModuleOp>();
auto typeScope = moduleOp.lookupSymbol<TypeScopeOp>(ref.getRootReference());
if (!typeScope) {

View File

@ -1,5 +1,5 @@
// RUN: circt-opt %s -export-verilog -verify-diagnostics
// XFAIL: *
hw.type_scope @__hw_typedecls {
hw.typedecl @foo : i1
}