CIRCT 22.0.0git
Loading...
Searching...
No Matches
ESIPasses.cpp
Go to the documentation of this file.
1//===- ESIPasses.cpp - Common code for ESI passes ---------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "PassDetails.h"
10
14#include "circt/Support/LLVM.h"
15
16#include "mlir/IR/SymbolTable.h"
17#include "mlir/Pass/Pass.h"
18#include "mlir/Transforms/DialectConversion.h"
19
20using namespace circt;
21using namespace circt::esi;
22using namespace circt::esi::detail;
23using namespace circt::hw;
24using namespace circt::sv;
25
27 std::string typeID;
28 llvm::raw_string_ostream(typeID) << t;
29 return StringAttr::get(t.getContext(), typeID);
30}
31
33 if (auto ch = dyn_cast<ChannelType>(t))
34 t = ch.getInner();
35 if (auto win = dyn_cast<WindowType>(t))
36 t = win.getLoweredType();
37 return hw::getBitWidth(t);
38}
39
40//===----------------------------------------------------------------------===//
41// ESI custom op builder.
42//===----------------------------------------------------------------------===//
43
44// C++ requires this for showing it what object file it should store these
45// symbols in. They should be inline but that feature wasn't added until C++17.
49
51 : ImplicitLocOpBuilder(UnknownLoc::get(top->getContext()), top),
52 a(StringAttr::get(getContext(), "a")),
53 aValid(StringAttr::get(getContext(), "a_valid")),
54 aReady(StringAttr::get(getContext(), "a_ready")),
55 x(StringAttr::get(getContext(), "x")),
56 xValid(StringAttr::get(getContext(), "x_valid")),
57 xReady(StringAttr::get(getContext(), "x_ready")),
58 dataOutValid(StringAttr::get(getContext(), "DataOutValid")),
59 dataOutReady(StringAttr::get(getContext(), "DataOutReady")),
60 dataOut(StringAttr::get(getContext(), "DataOut")),
61 dataInValid(StringAttr::get(getContext(), "DataInValid")),
62 dataInReady(StringAttr::get(getContext(), "DataInReady")),
63 dataIn(StringAttr::get(getContext(), "DataIn")),
64 clk(StringAttr::get(getContext(), "clk")),
65 rst(StringAttr::get(getContext(), "rst")),
66 width(StringAttr::get(getContext(), "WIDTH")) {
67
68 auto regions = top->getRegions();
69 if (regions.empty()) {
70 top->emitError("ESI HW Builder needs a region to insert HW.");
71 }
72 auto &region = regions.front();
73 if (!region.empty())
74 setInsertionPoint(&region.front(), region.front().begin());
75}
76
77static StringAttr constructUniqueSymbol(Operation *tableOp,
78 StringRef proposedNameRef) {
79 SmallString<64> proposedName = proposedNameRef;
80
81 // Normalize the type name.
82 for (char &ch : proposedName) {
83 if (isalpha(ch) || isdigit(ch) || ch == '_')
84 continue;
85 ch = '_';
86 }
87
88 // Make sure that this symbol isn't taken. If it is, append a number and try
89 // again.
90 size_t baseLength = proposedName.size();
91 size_t tries = 0;
92 while (SymbolTable::lookupSymbolIn(tableOp, proposedName)) {
93 proposedName.resize(baseLength);
94 proposedName.append(llvm::utostr(++tries));
95 }
96
97 return StringAttr::get(tableOp->getContext(), proposedName);
98}
99
100StringAttr ESIHWBuilder::constructInterfaceName(ChannelType port) {
101 Operation *tableOp =
102 getInsertionPoint()->getParentWithTrait<mlir::OpTrait::SymbolTable>();
103
104 // Get a name based on the type.
105 std::string portTypeName;
106 llvm::raw_string_ostream nameOS(portTypeName);
107 TypeSwitch<Type>(port.getInner())
108 .Case([&](hw::ArrayType arr) {
109 nameOS << "ArrayOf" << arr.getNumElements() << 'x'
110 << arr.getElementType();
111 })
112 .Case([&](hw::StructType t) { nameOS << "Struct"; })
113 .Default([&](Type t) { nameOS << port.getInner(); });
114
115 // Don't allow the name to end with '_'.
116 ssize_t i = portTypeName.size() - 1;
117 while (i >= 0 && portTypeName[i] == '_') {
118 --i;
119 }
120 portTypeName = portTypeName.substr(0, i + 1);
121
122 // All stage names start with this.
123 SmallString<64> proposedName("IValidReady_");
124 proposedName.append(portTypeName);
125 return constructUniqueSymbol(tableOp, proposedName);
126}
127
128/// Return a parameter list for the stage module with the specified value.
129ArrayAttr ESIHWBuilder::getStageParameterList(Attribute value) {
130 auto type = IntegerType::get(width.getContext(), 32, IntegerType::Unsigned);
131 auto widthParam = ParamDeclAttr::get(width.getContext(), width, type, value);
132 return ArrayAttr::get(width.getContext(), widthParam);
133}
134
135/// Write an 'ExternModuleOp' to use a hand-coded SystemVerilog module. Said
136/// module implements pipeline stage, adding 1 cycle latency. This particular
137/// implementation is double-buffered and fully pipelines the reverse-flow ready
138/// signal.
139HWModuleExternOp ESIHWBuilder::declareStage(Operation *symTable,
140 PipelineStageOp stage) {
141 Type dataType = stage.innerType();
142 HWModuleExternOp &stageMod = declaredStage[dataType];
143 if (stageMod)
144 return stageMod;
145
146 // Since this module has parameterized widths on the a input and x output,
147 // give the extern declation a None type since nothing else makes sense.
148 // Will be refining this when we decide how to better handle parameterized
149 // types and ops.
150 size_t argn = 0;
151 size_t resn = 0;
152 llvm::SmallVector<PortInfo> ports = {
153 {{clk, getClockType(), ModulePort::Direction::Input}, argn++},
154 {{rst, getI1Type(), ModulePort::Direction::Input}, argn++}};
155
156 ports.push_back({{a, dataType, ModulePort::Direction::Input}, argn++});
157 ports.push_back(
158 {{aValid, getI1Type(), ModulePort::Direction::Input}, argn++});
159 ports.push_back(
160 {{aReady, getI1Type(), ModulePort::Direction::Output}, resn++});
161 ports.push_back({{x, dataType, ModulePort::Direction::Output}, resn++});
162
163 ports.push_back(
164 {{xValid, getI1Type(), ModulePort::Direction::Output}, resn++});
165 ports.push_back(
166 {{xReady, getI1Type(), ModulePort::Direction::Input}, argn++});
167
168 stageMod = HWModuleExternOp::create(
169 *this, constructUniqueSymbol(symTable, "ESI_PipelineStage"), ports,
170 "ESI_PipelineStage", getStageParameterList({}));
171 return stageMod;
172}
173
174/// Write an 'ExternModuleOp' to use a hand-coded SystemVerilog module. Said
175/// module contains a bi-directional Cosimulation DPI interface with valid/ready
176/// semantics.
177HWModuleExternOp
181
182 SmallVector<Attribute, 8> params;
183 params.push_back(
184 ParamDeclAttr::get("ENDPOINT_ID", NoneType::get(getContext())));
185 params.push_back(
186 ParamDeclAttr::get("TO_HOST_TYPE_ID", NoneType::get(getContext())));
187 params.push_back(ParamDeclAttr::get("TO_HOST_SIZE_BITS", getI32Type()));
188
189 auto dataInType = hw::IntType::get(hw::ParamDeclRefAttr::get(
190 StringAttr::get(getContext(), "TO_HOST_SIZE_BITS"),
191 getIntegerType(32, false)));
192 PortInfo ports[] = {
193 {{clk, getClockType(), ModulePort::Direction::Input}, 0},
194 {{rst, getI1Type(), ModulePort::Direction::Input}, 1},
195 {{dataInValid, getI1Type(), ModulePort::Direction::Input}, 2},
196 {{dataInReady, getI1Type(), ModulePort::Direction::Output}, 3},
197 {{dataIn, dataInType, ModulePort::Direction::Input}, 4}};
198
199 declaredCosimEndpointToHostModule = HWModuleExternOp::create(
200 *this, constructUniqueSymbol(symTable, "Cosim_Endpoint_ToHost"), ports,
201 "Cosim_Endpoint_ToHost", ArrayAttr::get(getContext(), params));
203}
204
205HWModuleExternOp
209
210 SmallVector<Attribute, 8> params;
211 params.push_back(
212 ParamDeclAttr::get("ENDPOINT_ID", NoneType::get(getContext())));
213 params.push_back(
214 ParamDeclAttr::get("FROM_HOST_TYPE_ID", NoneType::get(getContext())));
215 params.push_back(ParamDeclAttr::get("FROM_HOST_SIZE_BITS", getI32Type()));
216
217 auto dataInType = hw::IntType::get(hw::ParamDeclRefAttr::get(
218 StringAttr::get(getContext(), "FROM_HOST_SIZE_BITS"),
219 getIntegerType(32, false)));
220 PortInfo ports[] = {
221 {{clk, getClockType(), ModulePort::Direction::Input}, 0},
222 {{rst, getI1Type(), ModulePort::Direction::Input}, 1},
223 {{dataOutValid, getI1Type(), ModulePort::Direction::Output}, 2},
224 {{dataOutReady, getI1Type(), ModulePort::Direction::Input}, 3},
225 {{dataOut, dataInType, ModulePort::Direction::Output}, 4}};
226
227 declaredCosimEndpointFromHostModule = HWModuleExternOp::create(
228 *this, constructUniqueSymbol(symTable, "Cosim_Endpoint_FromHost"), ports,
229 "Cosim_Endpoint_FromHost", ArrayAttr::get(getContext(), params));
231}
232
233/// Return the InterfaceType which corresponds to an ESI port type. If it
234/// doesn't exist in the cache, build the InterfaceOp and the corresponding
235/// type.
236InterfaceOp ESIHWBuilder::getOrConstructInterface(ChannelType t) {
237 auto ifaceIter = portTypeLookup.find(t);
238 if (ifaceIter != portTypeLookup.end())
239 return ifaceIter->second;
240 auto iface = constructInterface(t);
241 portTypeLookup[t] = iface;
242 return iface;
243}
244
245InterfaceOp ESIHWBuilder::constructInterface(ChannelType chan) {
246 return InterfaceOp::create(
247 *this, constructInterfaceName(chan).getValue(), [&]() {
248 InterfaceSignalOp::create(*this, validStr, getI1Type());
249 InterfaceSignalOp::create(*this, readyStr, getI1Type());
250 InterfaceSignalOp::create(*this, dataStr, chan.getInner());
251 llvm::SmallVector<StringRef> validDataStrs;
252 validDataStrs.push_back(validStr);
253 validDataStrs.push_back(dataStr);
254 InterfaceModportOp::create(*this, sinkStr,
255 /*inputs=*/ArrayRef<StringRef>{readyStr},
256 /*outputs=*/validDataStrs);
257 InterfaceModportOp::create(*this, sourceStr,
258 /*inputs=*/validDataStrs,
259 /*outputs=*/ArrayRef<StringRef>{readyStr});
260 });
261}
262
263Type ESIHWBuilder::getClockType() { return seq::ClockType::get(getContext()); }
264
static void registerPasses()
static StringAttr constructUniqueSymbol(Operation *tableOp, StringRef proposedNameRef)
Definition ESIPasses.cpp:77
#define isalpha(x)
Definition FIRLexer.cpp:27
#define isdigit(x)
Definition FIRLexer.cpp:26
hw::HWModuleExternOp declareCosimEndpointFromHostModule(Operation *symTable)
static constexpr char validStr[]
Definition PassDetails.h:76
static constexpr char sinkStr[]
Definition PassDetails.h:78
std::optional< hw::HWModuleExternOp > declaredCosimEndpointToHostModule
Definition PassDetails.h:87
llvm::DenseMap< Type, sv::InterfaceOp > portTypeLookup
Definition PassDetails.h:90
static constexpr char readyStr[]
Definition PassDetails.h:77
hw::HWModuleExternOp declareCosimEndpointToHostModule(Operation *symTable)
Write an 'ExternModuleOp' to use a hand-coded SystemVerilog module.
static constexpr char dataStr[]
Definition PassDetails.h:76
StringAttr constructInterfaceName(ChannelType)
Construct a type-appropriate name for the interface, making sure it's not taken in the symbol table.
sv::InterfaceOp constructInterface(ChannelType)
sv::InterfaceOp getOrConstructInterface(ChannelType)
Return the InterfaceType which corresponds to an ESI port type.
std::optional< hw::HWModuleExternOp > declaredCosimEndpointFromHostModule
Definition PassDetails.h:88
llvm::DenseMap< Type, hw::HWModuleExternOp > declaredStage
Definition PassDetails.h:89
static constexpr char sourceStr[]
Definition PassDetails.h:77
hw::HWModuleExternOp declareStage(Operation *symTable, PipelineStageOp)
Write an 'ExternModuleOp' to use a hand-coded SystemVerilog module.
ArrayAttr getStageParameterList(Attribute value)
Return a parameter list for the stage module with the specified value.
uint64_t getWidth(Type t)
Definition ESIPasses.cpp:32
StringAttr getTypeID(Type t)
Definition ESIPasses.cpp:26
void registerESIPasses()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
This holds the name, type, direction of a module's ports.