CIRCT  18.0.0git
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 
12 #include "circt/Dialect/HW/HWOps.h"
13 #include "circt/Dialect/SV/SVOps.h"
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 
20 using namespace circt;
21 using namespace circt::esi;
22 using namespace circt::esi::detail;
23 using namespace circt::hw;
24 using namespace circt::sv;
25 
26 //===----------------------------------------------------------------------===//
27 // ESI custom op builder.
28 //===----------------------------------------------------------------------===//
29 
30 // C++ requires this for showing it what object file it should store these
31 // symbols in. They should be inline but that feature wasn't added until C++17.
35 
37  : ImplicitLocOpBuilder(UnknownLoc::get(top->getContext()), top),
38  a(StringAttr::get(getContext(), "a")),
39  aValid(StringAttr::get(getContext(), "a_valid")),
40  aReady(StringAttr::get(getContext(), "a_ready")),
41  x(StringAttr::get(getContext(), "x")),
42  xValid(StringAttr::get(getContext(), "x_valid")),
43  xReady(StringAttr::get(getContext(), "x_ready")),
44  dataOutValid(StringAttr::get(getContext(), "DataOutValid")),
45  dataOutReady(StringAttr::get(getContext(), "DataOutReady")),
46  dataOut(StringAttr::get(getContext(), "DataOut")),
47  dataInValid(StringAttr::get(getContext(), "DataInValid")),
48  dataInReady(StringAttr::get(getContext(), "DataInReady")),
49  dataIn(StringAttr::get(getContext(), "DataIn")),
50  clk(StringAttr::get(getContext(), "clk")),
51  rst(StringAttr::get(getContext(), "rst")),
52  width(StringAttr::get(getContext(), "WIDTH")) {
53 
54  auto regions = top->getRegions();
55  if (regions.empty()) {
56  top->emitError("ESI HW Builder needs a region to insert HW.");
57  }
58  auto &region = regions.front();
59  if (!region.empty())
60  setInsertionPoint(&region.front(), region.front().begin());
61 }
62 
63 static StringAttr constructUniqueSymbol(Operation *tableOp,
64  StringRef proposedNameRef) {
65  SmallString<64> proposedName = proposedNameRef;
66 
67  // Normalize the type name.
68  for (char &ch : proposedName) {
69  if (isalpha(ch) || isdigit(ch) || ch == '_')
70  continue;
71  ch = '_';
72  }
73 
74  // Make sure that this symbol isn't taken. If it is, append a number and try
75  // again.
76  size_t baseLength = proposedName.size();
77  size_t tries = 0;
78  while (SymbolTable::lookupSymbolIn(tableOp, proposedName)) {
79  proposedName.resize(baseLength);
80  proposedName.append(llvm::utostr(++tries));
81  }
82 
83  return StringAttr::get(tableOp->getContext(), proposedName);
84 }
85 
86 StringAttr ESIHWBuilder::constructInterfaceName(ChannelType port) {
87  Operation *tableOp =
88  getInsertionPoint()->getParentWithTrait<mlir::OpTrait::SymbolTable>();
89 
90  // Get a name based on the type.
91  std::string portTypeName;
92  llvm::raw_string_ostream nameOS(portTypeName);
93  TypeSwitch<Type>(port.getInner())
94  .Case([&](hw::ArrayType arr) {
95  nameOS << "ArrayOf" << arr.getNumElements() << 'x'
96  << arr.getElementType();
97  })
98  .Case([&](hw::StructType t) { nameOS << "Struct"; })
99  .Default([&](Type t) { nameOS << port.getInner(); });
100 
101  // Don't allow the name to end with '_'.
102  ssize_t i = portTypeName.size() - 1;
103  while (i >= 0 && portTypeName[i] == '_') {
104  --i;
105  }
106  portTypeName = portTypeName.substr(0, i + 1);
107 
108  // All stage names start with this.
109  SmallString<64> proposedName("IValidReady_");
110  proposedName.append(portTypeName);
111  return constructUniqueSymbol(tableOp, proposedName);
112 }
113 
114 /// Return a parameter list for the stage module with the specified value.
115 ArrayAttr ESIHWBuilder::getStageParameterList(Attribute value) {
116  auto type = IntegerType::get(width.getContext(), 32, IntegerType::Unsigned);
117  auto widthParam = ParamDeclAttr::get(width.getContext(), width, type, value);
118  return ArrayAttr::get(width.getContext(), widthParam);
119 }
120 
121 /// Write an 'ExternModuleOp' to use a hand-coded SystemVerilog module. Said
122 /// module implements pipeline stage, adding 1 cycle latency. This particular
123 /// implementation is double-buffered and fully pipelines the reverse-flow ready
124 /// signal.
125 HWModuleExternOp ESIHWBuilder::declareStage(Operation *symTable,
126  PipelineStageOp stage) {
127  Type dataType = stage.innerType();
128  HWModuleExternOp &stageMod = declaredStage[dataType];
129  if (stageMod)
130  return stageMod;
131 
132  // Since this module has parameterized widths on the a input and x output,
133  // give the extern declation a None type since nothing else makes sense.
134  // Will be refining this when we decide how to better handle parameterized
135  // types and ops.
136  size_t argn = 0;
137  size_t resn = 0;
138  llvm::SmallVector<PortInfo> ports = {
140  {{rst, getI1Type(), ModulePort::Direction::Input}, argn++}};
141 
142  ports.push_back({{a, dataType, ModulePort::Direction::Input}, argn++});
143  ports.push_back(
144  {{aValid, getI1Type(), ModulePort::Direction::Input}, argn++});
145  ports.push_back(
146  {{aReady, getI1Type(), ModulePort::Direction::Output}, resn++});
147  ports.push_back({{x, dataType, ModulePort::Direction::Output}, resn++});
148 
149  ports.push_back(
150  {{xValid, getI1Type(), ModulePort::Direction::Output}, resn++});
151  ports.push_back(
152  {{xReady, getI1Type(), ModulePort::Direction::Input}, argn++});
153 
154  stageMod = create<HWModuleExternOp>(
155  constructUniqueSymbol(symTable, "ESI_PipelineStage"), ports,
156  "ESI_PipelineStage", getStageParameterList({}));
157  return stageMod;
158 }
159 
160 /// Write an 'ExternModuleOp' to use a hand-coded SystemVerilog module. Said
161 /// module contains a bi-directional Cosimulation DPI interface with valid/ready
162 /// semantics.
163 HWModuleExternOp ESIHWBuilder::declareCosimEndpointOp(Operation *symTable,
164  Type sendType,
165  Type recvType) {
166  HWModuleExternOp &endpoint =
167  declaredCosimEndpointOp[std::make_pair(sendType, recvType)];
168  if (endpoint)
169  return endpoint;
170  // Since this module has parameterized widths on the a input and x output,
171  // give the extern declation a None type since nothing else makes sense.
172  // Will be refining this when we decide how to better handle parameterized
173  // types and ops.
174  PortInfo ports[] = {
176  {{rst, getI1Type(), ModulePort::Direction::Input}, 1},
177  {{dataOutValid, getI1Type(), ModulePort::Direction::Output}, 0},
178  {{dataOutReady, getI1Type(), ModulePort::Direction::Input}, 2},
179  {{dataOut, recvType, ModulePort::Direction::Output}, 1},
180  {{dataInValid, getI1Type(), ModulePort::Direction::Input}, 3},
181  {{dataInReady, getI1Type(), ModulePort::Direction::Output}, 2},
182  {{dataIn, sendType, ModulePort::Direction::Input}, 4}};
183  SmallVector<Attribute, 8> params;
184  params.push_back(ParamDeclAttr::get("ENDPOINT_ID_EXT", getStringAttr("")));
185  params.push_back(
186  ParamDeclAttr::get("SEND_TYPE_ID", getIntegerType(64, false)));
187  params.push_back(ParamDeclAttr::get("SEND_TYPE_SIZE_BITS", getI32Type()));
188  params.push_back(
189  ParamDeclAttr::get("RECV_TYPE_ID", getIntegerType(64, false)));
190  params.push_back(ParamDeclAttr::get("RECV_TYPE_SIZE_BITS", getI32Type()));
191  endpoint = create<HWModuleExternOp>(
192  constructUniqueSymbol(symTable, "Cosim_Endpoint"), ports,
193  "Cosim_Endpoint", ArrayAttr::get(getContext(), params));
194  return endpoint;
195 }
196 
197 /// Return the InterfaceType which corresponds to an ESI port type. If it
198 /// doesn't exist in the cache, build the InterfaceOp and the corresponding
199 /// type.
200 InterfaceOp ESIHWBuilder::getOrConstructInterface(ChannelType t) {
201  auto ifaceIter = portTypeLookup.find(t);
202  if (ifaceIter != portTypeLookup.end())
203  return ifaceIter->second;
204  auto iface = constructInterface(t);
205  portTypeLookup[t] = iface;
206  return iface;
207 }
208 
209 InterfaceOp ESIHWBuilder::constructInterface(ChannelType chan) {
210  return create<InterfaceOp>(constructInterfaceName(chan).getValue(), [&]() {
211  create<InterfaceSignalOp>(validStr, getI1Type());
212  create<InterfaceSignalOp>(readyStr, getI1Type());
213  create<InterfaceSignalOp>(dataStr, chan.getInner());
214  llvm::SmallVector<StringRef> validDataStrs;
215  validDataStrs.push_back(validStr);
216  validDataStrs.push_back(dataStr);
217  create<InterfaceModportOp>(sinkStr,
218  /*inputs=*/ArrayRef<StringRef>{readyStr},
219  /*outputs=*/validDataStrs);
220  create<InterfaceModportOp>(sourceStr,
221  /*inputs=*/validDataStrs,
222  /*outputs=*/ArrayRef<StringRef>{readyStr});
223  });
224 }
225 
226 Type ESIHWBuilder::getClockType() { return seq::ClockType::get(getContext()); }
227 
static void registerPasses()
Definition: CIRCTModule.cpp:37
static StringAttr constructUniqueSymbol(Operation *tableOp, StringRef proposedNameRef)
Definition: ESIPasses.cpp:63
#define isalpha(x)
Definition: FIRLexer.cpp:27
#define isdigit(x)
Definition: FIRLexer.cpp:26
int32_t width
Definition: FIRRTL.cpp:27
@ Input
Definition: HW.h:32
@ Output
Definition: HW.h:32
hw::HWModuleExternOp declareCosimEndpointOp(Operation *symTable, Type sendType, Type recvType) LLVM_ATTRIBUTE_UNUSED
Write an 'ExternModuleOp' to use a hand-coded SystemVerilog module.
Definition: ESIPasses.cpp:163
static constexpr char validStr[]
Definition: PassDetails.h:69
static constexpr char sinkStr[]
Definition: PassDetails.h:71
llvm::DenseMap< Type, sv::InterfaceOp > portTypeLookup
Definition: PassDetails.h:83
static constexpr char readyStr[]
Definition: PassDetails.h:70
static constexpr char dataStr[]
Definition: PassDetails.h:69
StringAttr constructInterfaceName(ChannelType)
Construct a type-appropriate name for the interface, making sure it's not taken in the symbol table.
Definition: ESIPasses.cpp:86
llvm::DenseMap< std::pair< Type, Type >, hw::HWModuleExternOp > declaredCosimEndpointOp
Definition: PassDetails.h:82
sv::InterfaceOp constructInterface(ChannelType)
Definition: ESIPasses.cpp:209
sv::InterfaceOp getOrConstructInterface(ChannelType)
Return the InterfaceType which corresponds to an ESI port type.
Definition: ESIPasses.cpp:200
llvm::DenseMap< Type, hw::HWModuleExternOp > declaredStage
Definition: PassDetails.h:80
static constexpr char sourceStr[]
Definition: PassDetails.h:70
hw::HWModuleExternOp declareStage(Operation *symTable, PipelineStageOp)
Write an 'ExternModuleOp' to use a hand-coded SystemVerilog module.
Definition: ESIPasses.cpp:125
ArrayAttr getStageParameterList(Attribute value)
Return a parameter list for the stage module with the specified value.
Definition: ESIPasses.cpp:115
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
void registerESIPasses()
Definition: ESIPasses.cpp:228
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
This holds the name, type, direction of a module's ports.