CIRCT 22.0.0git
Loading...
Searching...
No Matches
Structure.cpp
Go to the documentation of this file.
1//===- Structure.cpp - Slang hierarchy conversion -------------------------===//
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
10#include "slang/ast/Compilation.h"
11#include "llvm/ADT/ScopeExit.h"
12
13using namespace circt;
14using namespace ImportVerilog;
15
16//===----------------------------------------------------------------------===//
17// Utilities
18//===----------------------------------------------------------------------===//
19
20static void guessNamespacePrefix(const slang::ast::Symbol &symbol,
21 SmallString<64> &prefix) {
22 if (symbol.kind != slang::ast::SymbolKind::Package)
23 return;
24 guessNamespacePrefix(symbol.getParentScope()->asSymbol(), prefix);
25 if (!symbol.name.empty()) {
26 prefix += symbol.name;
27 prefix += "::";
28 }
29}
30
31//===----------------------------------------------------------------------===//
32// Base Visitor
33//===----------------------------------------------------------------------===//
34
35namespace {
36/// Base visitor which ignores AST nodes that are handled by Slang's name
37/// resolution and type checking.
38struct BaseVisitor {
39 Context &context;
40 Location loc;
41 OpBuilder &builder;
42
43 BaseVisitor(Context &context, Location loc)
44 : context(context), loc(loc), builder(context.builder) {}
45
46 // Skip semicolons.
47 LogicalResult visit(const slang::ast::EmptyMemberSymbol &) {
48 return success();
49 }
50
51 // Skip members that are implicitly imported from some other scope for the
52 // sake of name resolution, such as enum variant names.
53 LogicalResult visit(const slang::ast::TransparentMemberSymbol &) {
54 return success();
55 }
56
57 // Skip typedefs.
58 LogicalResult visit(const slang::ast::TypeAliasType &) { return success(); }
59 LogicalResult visit(const slang::ast::ForwardingTypedefSymbol &) {
60 return success();
61 }
62
63 // Skip imports. The AST already has its names resolved.
64 LogicalResult visit(const slang::ast::ExplicitImportSymbol &) {
65 return success();
66 }
67 LogicalResult visit(const slang::ast::WildcardImportSymbol &) {
68 return success();
69 }
70
71 // Skip type parameters. The Slang AST is already monomorphized.
72 LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
73 return success();
74 }
75
76 // Skip elaboration system tasks. These are reported directly by Slang.
77 LogicalResult visit(const slang::ast::ElabSystemTaskSymbol &) {
78 return success();
79 }
80
81 // Handle parameters.
82 LogicalResult visit(const slang::ast::ParameterSymbol &param) {
83 visitParameter(param);
84 return success();
85 }
86
87 LogicalResult visit(const slang::ast::SpecparamSymbol &param) {
88 visitParameter(param);
89 return success();
90 }
91
92 template <class Node>
93 void visitParameter(const Node &param) {
94 // If debug info is enabled, try to materialize the parameter's constant
95 // value on a best-effort basis and create a `dbg.variable` to track the
96 // value.
97 if (!context.options.debugInfo)
98 return;
99 auto value =
100 context.materializeConstant(param.getValue(), param.getType(), loc);
101 if (!value)
102 return;
103 if (builder.getInsertionBlock()->getParentOp() == context.intoModuleOp)
104 context.orderedRootOps.insert({param.location, value.getDefiningOp()});
105
106 // Prefix the parameter name with the surrounding namespace to create
107 // somewhat sane names in the IR.
108 SmallString<64> paramName;
109 guessNamespacePrefix(param.getParentScope()->asSymbol(), paramName);
110 paramName += param.name;
111
112 debug::VariableOp::create(builder, loc, builder.getStringAttr(paramName),
113 value, Value{});
114 }
115};
116} // namespace
117
118//===----------------------------------------------------------------------===//
119// Top-Level Item Conversion
120//===----------------------------------------------------------------------===//
121
122namespace {
123struct RootVisitor : public BaseVisitor {
124 using BaseVisitor::BaseVisitor;
125 using BaseVisitor::visit;
126
127 // Handle packages.
128 LogicalResult visit(const slang::ast::PackageSymbol &package) {
129 return context.convertPackage(package);
130 }
131
132 // Handle functions and tasks.
133 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
134 return context.convertFunction(subroutine);
135 }
136
137 // Emit an error for all other members.
138 template <typename T>
139 LogicalResult visit(T &&node) {
140 mlir::emitError(loc, "unsupported construct: ")
141 << slang::ast::toString(node.kind);
142 return failure();
143 }
144};
145} // namespace
146
147//===----------------------------------------------------------------------===//
148// Package Conversion
149//===----------------------------------------------------------------------===//
150
151namespace {
152struct PackageVisitor : public BaseVisitor {
153 using BaseVisitor::BaseVisitor;
154 using BaseVisitor::visit;
155
156 // Handle functions and tasks.
157 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
158 return context.convertFunction(subroutine);
159 }
160
161 /// Emit an error for all other members.
162 template <typename T>
163 LogicalResult visit(T &&node) {
164 mlir::emitError(loc, "unsupported package member: ")
165 << slang::ast::toString(node.kind);
166 return failure();
167 }
168};
169} // namespace
170
171//===----------------------------------------------------------------------===//
172// Module Conversion
173//===----------------------------------------------------------------------===//
174
175static moore::ProcedureKind
176convertProcedureKind(slang::ast::ProceduralBlockKind kind) {
177 switch (kind) {
178 case slang::ast::ProceduralBlockKind::Always:
179 return moore::ProcedureKind::Always;
180 case slang::ast::ProceduralBlockKind::AlwaysComb:
181 return moore::ProcedureKind::AlwaysComb;
182 case slang::ast::ProceduralBlockKind::AlwaysLatch:
183 return moore::ProcedureKind::AlwaysLatch;
184 case slang::ast::ProceduralBlockKind::AlwaysFF:
185 return moore::ProcedureKind::AlwaysFF;
186 case slang::ast::ProceduralBlockKind::Initial:
187 return moore::ProcedureKind::Initial;
188 case slang::ast::ProceduralBlockKind::Final:
189 return moore::ProcedureKind::Final;
190 }
191 llvm_unreachable("all procedure kinds handled");
192}
193
194static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind) {
195 switch (kind) {
196 case slang::ast::NetType::Supply0:
197 return moore::NetKind::Supply0;
198 case slang::ast::NetType::Supply1:
199 return moore::NetKind::Supply1;
200 case slang::ast::NetType::Tri:
201 return moore::NetKind::Tri;
202 case slang::ast::NetType::TriAnd:
203 return moore::NetKind::TriAnd;
204 case slang::ast::NetType::TriOr:
205 return moore::NetKind::TriOr;
206 case slang::ast::NetType::TriReg:
207 return moore::NetKind::TriReg;
208 case slang::ast::NetType::Tri0:
209 return moore::NetKind::Tri0;
210 case slang::ast::NetType::Tri1:
211 return moore::NetKind::Tri1;
212 case slang::ast::NetType::UWire:
213 return moore::NetKind::UWire;
214 case slang::ast::NetType::Wire:
215 return moore::NetKind::Wire;
216 case slang::ast::NetType::WAnd:
217 return moore::NetKind::WAnd;
218 case slang::ast::NetType::WOr:
219 return moore::NetKind::WOr;
220 case slang::ast::NetType::Interconnect:
221 return moore::NetKind::Interconnect;
222 case slang::ast::NetType::UserDefined:
223 return moore::NetKind::UserDefined;
224 case slang::ast::NetType::Unknown:
225 return moore::NetKind::Unknown;
226 }
227 llvm_unreachable("all net kinds handled");
228}
229
230namespace {
231struct ModuleVisitor : public BaseVisitor {
232 using BaseVisitor::visit;
233
234 // A prefix of block names such as `foo.bar.` to put in front of variable and
235 // instance names.
236 StringRef blockNamePrefix;
237
238 ModuleVisitor(Context &context, Location loc, StringRef blockNamePrefix = "")
239 : BaseVisitor(context, loc), blockNamePrefix(blockNamePrefix) {}
240
241 // Skip ports which are already handled by the module itself.
242 LogicalResult visit(const slang::ast::PortSymbol &) { return success(); }
243 LogicalResult visit(const slang::ast::MultiPortSymbol &) { return success(); }
244
245 // Skip genvars.
246 LogicalResult visit(const slang::ast::GenvarSymbol &genvarNode) {
247 return success();
248 }
249
250 // Skip defparams which have been handled by slang.
251 LogicalResult visit(const slang::ast::DefParamSymbol &) { return success(); }
252
253 // Ignore type parameters. These have already been handled by Slang's type
254 // checking.
255 LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
256 return success();
257 }
258
259 // Handle instances.
260 LogicalResult visit(const slang::ast::InstanceSymbol &instNode) {
261 using slang::ast::ArgumentDirection;
262 using slang::ast::AssignmentExpression;
263 using slang::ast::MultiPortSymbol;
264 using slang::ast::PortSymbol;
265
266 auto *moduleLowering = context.convertModuleHeader(&instNode.body);
267 if (!moduleLowering)
268 return failure();
269 auto module = moduleLowering->op;
270 auto moduleType = module.getModuleType();
271
272 // Set visibility attribute for instantiated module.
273 SymbolTable::setSymbolVisibility(module, SymbolTable::Visibility::Private);
274
275 // Prepare the values that are involved in port connections. This creates
276 // rvalues for input ports and appropriate lvalues for output, inout, and
277 // ref ports. We also separate multi-ports into the individual underlying
278 // ports with their corresponding connection.
280 portValues.reserve(moduleType.getNumPorts());
281
282 for (const auto *con : instNode.getPortConnections()) {
283 const auto *expr = con->getExpression();
284
285 // Handle unconnected behavior. The expression is null if it have no
286 // connection for the port.
287 if (!expr) {
288 auto *port = con->port.as_if<PortSymbol>();
289 if (auto *existingPort =
290 moduleLowering->portsBySyntaxNode.lookup(port->getSyntax()))
291 port = existingPort;
292
293 switch (port->direction) {
294 case ArgumentDirection::In: {
295 auto refType = moore::RefType::get(
296 cast<moore::UnpackedType>(context.convertType(port->getType())));
297
298 if (const auto *net =
299 port->internalSymbol->as_if<slang::ast::NetSymbol>()) {
300 auto netOp = moore::NetOp::create(
301 builder, loc, refType,
302 StringAttr::get(builder.getContext(), net->name),
303 convertNetKind(net->netType.netKind), nullptr);
304 auto readOp = moore::ReadOp::create(builder, loc, netOp);
305 portValues.insert({port, readOp});
306 } else if (const auto *var =
307 port->internalSymbol
308 ->as_if<slang::ast::VariableSymbol>()) {
309 auto varOp = moore::VariableOp::create(
310 builder, loc, refType,
311 StringAttr::get(builder.getContext(), var->name), nullptr);
312 auto readOp = moore::ReadOp::create(builder, loc, varOp);
313 portValues.insert({port, readOp});
314 } else {
315 return mlir::emitError(loc)
316 << "unsupported internal symbol for unconnected port `"
317 << port->name << "`";
318 }
319 continue;
320 }
321
322 // No need to express unconnected behavior for output port, skip to the
323 // next iteration of the loop.
324 case ArgumentDirection::Out:
325 continue;
326
327 // TODO: Mark Inout port as unsupported and it will be supported later.
328 default:
329 return mlir::emitError(loc)
330 << "unsupported port `" << port->name << "` ("
331 << slang::ast::toString(port->kind) << ")";
332 }
333 }
334
335 // Unpack the `<expr> = EmptyArgument` pattern emitted by Slang for
336 // output and inout ports.
337 if (const auto *assign = expr->as_if<AssignmentExpression>())
338 expr = &assign->left();
339
340 // Regular ports lower the connected expression to an lvalue or rvalue and
341 // either attach it to the instance as an operand (for input, inout, and
342 // ref ports), or assign an instance output to it (for output ports).
343 if (auto *port = con->port.as_if<PortSymbol>()) {
344 // Convert as rvalue for inputs, lvalue for all others.
345 auto value = (port->direction == ArgumentDirection::In)
346 ? context.convertRvalueExpression(*expr)
347 : context.convertLvalueExpression(*expr);
348 if (!value)
349 return failure();
350 if (auto *existingPort =
351 moduleLowering->portsBySyntaxNode.lookup(con->port.getSyntax()))
352 port = existingPort;
353 portValues.insert({port, value});
354 continue;
355 }
356
357 // Multi-ports lower the connected expression to an lvalue and then slice
358 // it up into multiple sub-values, one for each of the ports in the
359 // multi-port.
360 if (const auto *multiPort = con->port.as_if<MultiPortSymbol>()) {
361 // Convert as lvalue.
362 auto value = context.convertLvalueExpression(*expr);
363 if (!value)
364 return failure();
365 unsigned offset = 0;
366 for (const auto *port : llvm::reverse(multiPort->ports)) {
367 if (auto *existingPort = moduleLowering->portsBySyntaxNode.lookup(
368 con->port.getSyntax()))
369 port = existingPort;
370 unsigned width = port->getType().getBitWidth();
371 auto sliceType = context.convertType(port->getType());
372 if (!sliceType)
373 return failure();
374 Value slice = moore::ExtractRefOp::create(
375 builder, loc,
376 moore::RefType::get(cast<moore::UnpackedType>(sliceType)), value,
377 offset);
378 // Create the "ReadOp" for input ports.
379 if (port->direction == ArgumentDirection::In)
380 slice = moore::ReadOp::create(builder, loc, slice);
381 portValues.insert({port, slice});
382 offset += width;
383 }
384 continue;
385 }
386
387 mlir::emitError(loc) << "unsupported instance port `" << con->port.name
388 << "` (" << slang::ast::toString(con->port.kind)
389 << ")";
390 return failure();
391 }
392
393 // Match the module's ports up with the port values determined above.
394 SmallVector<Value> inputValues;
395 SmallVector<Value> outputValues;
396 inputValues.reserve(moduleType.getNumInputs());
397 outputValues.reserve(moduleType.getNumOutputs());
398
399 for (auto &port : moduleLowering->ports) {
400 auto value = portValues.lookup(&port.ast);
401 if (port.ast.direction == ArgumentDirection::Out)
402 outputValues.push_back(value);
403 else
404 inputValues.push_back(value);
405 }
406
407 // Insert conversions for input ports.
408 for (auto [value, type] :
409 llvm::zip(inputValues, moduleType.getInputTypes()))
410 // TODO: This should honor signedness in the conversion.
411 value = context.materializeConversion(type, value, false, value.getLoc());
412
413 // Here we use the hierarchical value recorded in `Context::valueSymbols`.
414 // Then we pass it as the input port with the ref<T> type of the instance.
415 for (const auto &hierPath : context.hierPaths[&instNode.body])
416 if (auto hierValue = context.valueSymbols.lookup(hierPath.valueSym);
417 hierPath.hierName && hierPath.direction == ArgumentDirection::In)
418 inputValues.push_back(hierValue);
419
420 // Create the instance op itself.
421 auto inputNames = builder.getArrayAttr(moduleType.getInputNames());
422 auto outputNames = builder.getArrayAttr(moduleType.getOutputNames());
423 auto inst = moore::InstanceOp::create(
424 builder, loc, moduleType.getOutputTypes(),
425 builder.getStringAttr(Twine(blockNamePrefix) + instNode.name),
426 FlatSymbolRefAttr::get(module.getSymNameAttr()), inputValues,
427 inputNames, outputNames);
428
429 // Record instance's results generated by hierarchical names.
430 for (const auto &hierPath : context.hierPaths[&instNode.body])
431 if (hierPath.idx && hierPath.direction == ArgumentDirection::Out)
432 context.valueSymbols.insert(hierPath.valueSym,
433 inst->getResult(*hierPath.idx));
434
435 // Assign output values from the instance to the connected expression.
436 for (auto [lvalue, output] : llvm::zip(outputValues, inst.getOutputs())) {
437 if (!lvalue)
438 continue;
439 Value rvalue = output;
440 auto dstType = cast<moore::RefType>(lvalue.getType()).getNestedType();
441 // TODO: This should honor signedness in the conversion.
442 rvalue = context.materializeConversion(dstType, rvalue, false, loc);
443 moore::ContinuousAssignOp::create(builder, loc, lvalue, rvalue);
444 }
445
446 return success();
447 }
448
449 // Handle variables.
450 LogicalResult visit(const slang::ast::VariableSymbol &varNode) {
451 auto loweredType = context.convertType(*varNode.getDeclaredType());
452 if (!loweredType)
453 return failure();
454
455 Value initial;
456 if (const auto *init = varNode.getInitializer()) {
457 initial = context.convertRvalueExpression(*init, loweredType);
458 if (!initial)
459 return failure();
460 }
461
462 auto varOp = moore::VariableOp::create(
463 builder, loc,
464 moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
465 builder.getStringAttr(Twine(blockNamePrefix) + varNode.name), initial);
466 context.valueSymbols.insert(&varNode, varOp);
467 return success();
468 }
469
470 // Handle nets.
471 LogicalResult visit(const slang::ast::NetSymbol &netNode) {
472 auto loweredType = context.convertType(*netNode.getDeclaredType());
473 if (!loweredType)
474 return failure();
475
476 Value assignment;
477 if (const auto *init = netNode.getInitializer()) {
478 assignment = context.convertRvalueExpression(*init, loweredType);
479 if (!assignment)
480 return failure();
481 }
482
483 auto netkind = convertNetKind(netNode.netType.netKind);
484 if (netkind == moore::NetKind::Interconnect ||
485 netkind == moore::NetKind::UserDefined ||
486 netkind == moore::NetKind::Unknown)
487 return mlir::emitError(loc, "unsupported net kind `")
488 << netNode.netType.name << "`";
489
490 auto netOp = moore::NetOp::create(
491 builder, loc,
492 moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
493 builder.getStringAttr(Twine(blockNamePrefix) + netNode.name), netkind,
494 assignment);
495 context.valueSymbols.insert(&netNode, netOp);
496 return success();
497 }
498
499 // Handle continuous assignments.
500 LogicalResult visit(const slang::ast::ContinuousAssignSymbol &assignNode) {
501 if (const auto *delay = assignNode.getDelay()) {
502 auto loc = context.convertLocation(delay->sourceRange);
503 return mlir::emitError(loc,
504 "delayed continuous assignments not supported");
505 }
506
507 const auto &expr =
508 assignNode.getAssignment().as<slang::ast::AssignmentExpression>();
509 auto lhs = context.convertLvalueExpression(expr.left());
510 if (!lhs)
511 return failure();
512
513 auto rhs = context.convertRvalueExpression(
514 expr.right(), cast<moore::RefType>(lhs.getType()).getNestedType());
515 if (!rhs)
516 return failure();
517
518 moore::ContinuousAssignOp::create(builder, loc, lhs, rhs);
519 return success();
520 }
521
522 // Handle procedures.
523 LogicalResult convertProcedure(moore::ProcedureKind kind,
524 const slang::ast::Statement &body) {
525 auto procOp = moore::ProcedureOp::create(builder, loc, kind);
526 OpBuilder::InsertionGuard guard(builder);
527 builder.setInsertionPointToEnd(&procOp.getBody().emplaceBlock());
528 Context::ValueSymbolScope scope(context.valueSymbols);
529 if (failed(context.convertStatement(body)))
530 return failure();
531 if (builder.getBlock())
532 moore::ReturnOp::create(builder, loc);
533 return success();
534 }
535
536 LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
537 // Detect `always @(*) <stmt>` and convert to `always_comb <stmt>` if
538 // requested by the user.
539 if (context.options.lowerAlwaysAtStarAsComb) {
540 auto *stmt = procNode.getBody().as_if<slang::ast::TimedStatement>();
541 if (procNode.procedureKind == slang::ast::ProceduralBlockKind::Always &&
542 stmt &&
543 stmt->timing.kind == slang::ast::TimingControlKind::ImplicitEvent)
544 return convertProcedure(moore::ProcedureKind::AlwaysComb, stmt->stmt);
545 }
546
547 return convertProcedure(convertProcedureKind(procNode.procedureKind),
548 procNode.getBody());
549 }
550
551 // Handle generate block.
552 LogicalResult visit(const slang::ast::GenerateBlockSymbol &genNode) {
553 // Ignore uninstantiated blocks.
554 if (genNode.isUninstantiated)
555 return success();
556
557 // If the block has a name, add it to the list of block name prefices.
558 SmallString<64> prefixBuffer;
559 auto prefix = blockNamePrefix;
560 if (!genNode.name.empty()) {
561 prefixBuffer += blockNamePrefix;
562 prefixBuffer += genNode.name;
563 prefixBuffer += '.';
564 prefix = prefixBuffer;
565 }
566
567 // Visit each member of the generate block.
568 for (auto &member : genNode.members())
569 if (failed(member.visit(ModuleVisitor(context, loc, prefix))))
570 return failure();
571 return success();
572 }
573
574 // Handle generate block array.
575 LogicalResult visit(const slang::ast::GenerateBlockArraySymbol &genArrNode) {
576 // If the block has a name, add it to the list of block name prefices and
577 // prepare to append the array index and a `.` in each iteration.
578 SmallString<64> prefixBuffer;
579 if (!genArrNode.name.empty()) {
580 prefixBuffer += blockNamePrefix;
581 prefixBuffer += genArrNode.name;
582 }
583 auto prefixBufferBaseLen = prefixBuffer.size();
584
585 // Visit each iteration entry of the generate block.
586 for (const auto *entry : genArrNode.entries) {
587 // Append the index to the prefix if this block has a name.
588 auto prefix = blockNamePrefix;
589 if (prefixBufferBaseLen > 0) {
590 prefixBuffer.resize(prefixBufferBaseLen);
591 prefixBuffer += '_';
592 if (entry->arrayIndex)
593 prefixBuffer += entry->arrayIndex->toString();
594 else
595 Twine(entry->constructIndex).toVector(prefixBuffer);
596 prefixBuffer += '.';
597 prefix = prefixBuffer;
598 }
599
600 // Visit this iteration entry.
601 if (failed(entry->asSymbol().visit(ModuleVisitor(context, loc, prefix))))
602 return failure();
603 }
604 return success();
605 }
606
607 // Ignore statement block symbols. These get generated by Slang for blocks
608 // with variables and other declarations. For example, having an initial
609 // procedure with a variable declaration, such as `initial begin int x;
610 // end`, will create the procedure with a block and variable declaration as
611 // expected, but will also create a `StatementBlockSymbol` with just the
612 // variable layout _next to_ the initial procedure.
613 LogicalResult visit(const slang::ast::StatementBlockSymbol &) {
614 return success();
615 }
616
617 // Ignore sequence declarations. The declarations are already evaluated by
618 // Slang and are part of an AssertionInstance.
619 LogicalResult visit(const slang::ast::SequenceSymbol &seqNode) {
620 return success();
621 }
622
623 // Ignore property declarations. The declarations are already evaluated by
624 // Slang and are part of an AssertionInstance.
625 LogicalResult visit(const slang::ast::PropertySymbol &propNode) {
626 return success();
627 }
628
629 // Handle functions and tasks.
630 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
631 return context.convertFunction(subroutine);
632 }
633
634 /// Emit an error for all other members.
635 template <typename T>
636 LogicalResult visit(T &&node) {
637 mlir::emitError(loc, "unsupported module member: ")
638 << slang::ast::toString(node.kind);
639 return failure();
640 }
641};
642} // namespace
643
644//===----------------------------------------------------------------------===//
645// Structure and Hierarchy Conversion
646//===----------------------------------------------------------------------===//
647
648/// Convert an entire Slang compilation to MLIR ops. This is the main entry
649/// point for the conversion.
650LogicalResult Context::convertCompilation() {
651 const auto &root = compilation.getRoot();
652
653 // Keep track of the local time scale. `getTimeScale` automatically looks
654 // through parent scopes to find the time scale effective locally.
655 auto prevTimeScale = timeScale;
656 timeScale = root.getTimeScale().value_or(slang::TimeScale());
657 auto timeScaleGuard =
658 llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
659
660 // First only to visit the whole AST to collect the hierarchical names without
661 // any operation creating.
662 for (auto *inst : root.topInstances)
663 if (failed(traverseInstanceBody(inst->body)))
664 return failure();
665
666 // Visit all top-level declarations in all compilation units. This does not
667 // include instantiable constructs like modules, interfaces, and programs,
668 // which are listed separately as top instances.
669 for (auto *unit : root.compilationUnits) {
670 for (const auto &member : unit->members()) {
671 auto loc = convertLocation(member.location);
672 if (failed(member.visit(RootVisitor(*this, loc))))
673 return failure();
674 }
675 }
676
677 // Prime the root definition worklist by adding all the top-level modules.
678 SmallVector<const slang::ast::InstanceSymbol *> topInstances;
679 for (auto *inst : root.topInstances)
680 if (!convertModuleHeader(&inst->body))
681 return failure();
682
683 // Convert all the root module definitions.
684 while (!moduleWorklist.empty()) {
685 auto *module = moduleWorklist.front();
686 moduleWorklist.pop();
687 if (failed(convertModuleBody(module)))
688 return failure();
689 }
690
691 return success();
692}
693
694/// Convert a module and its ports to an empty module op in the IR. Also adds
695/// the op to the worklist of module bodies to be lowered. This acts like a
696/// module "declaration", allowing instances to already refer to a module even
697/// before its body has been lowered.
699Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
700 using slang::ast::ArgumentDirection;
701 using slang::ast::MultiPortSymbol;
702 using slang::ast::ParameterSymbol;
703 using slang::ast::PortSymbol;
704 using slang::ast::TypeParameterSymbol;
705
706 // Keep track of the local time scale. `getTimeScale` automatically looks
707 // through parent scopes to find the time scale effective locally.
708 auto prevTimeScale = timeScale;
709 timeScale = module->getTimeScale().value_or(slang::TimeScale());
710 auto timeScaleGuard =
711 llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
712
713 auto parameters = module->getParameters();
714 bool hasModuleSame = false;
715 // If there is already exist a module that has the same name with this
716 // module ,has the same parent scope and has the same parameters we can
717 // define this module is a duplicate module
718 for (auto const &existingModule : modules) {
719 if (module->getDeclaringDefinition() ==
720 existingModule.getFirst()->getDeclaringDefinition()) {
721 auto moduleParameters = existingModule.getFirst()->getParameters();
722 hasModuleSame = true;
723 for (auto it1 = parameters.begin(), it2 = moduleParameters.begin();
724 it1 != parameters.end() && it2 != moduleParameters.end();
725 it1++, it2++) {
726 // Parameters size different
727 if (it1 == parameters.end() || it2 == moduleParameters.end()) {
728 hasModuleSame = false;
729 break;
730 }
731 const auto *para1 = (*it1)->symbol.as_if<ParameterSymbol>();
732 const auto *para2 = (*it2)->symbol.as_if<ParameterSymbol>();
733 // Parameters kind different
734 if ((para1 == nullptr) ^ (para2 == nullptr)) {
735 hasModuleSame = false;
736 break;
737 }
738 // Compare ParameterSymbol
739 if (para1 != nullptr) {
740 hasModuleSame = para1->getValue() == para2->getValue();
741 }
742 // Compare TypeParameterSymbol
743 if (para1 == nullptr) {
744 auto para1Type = convertType(
745 (*it1)->symbol.as<TypeParameterSymbol>().getTypeAlias());
746 auto para2Type = convertType(
747 (*it2)->symbol.as<TypeParameterSymbol>().getTypeAlias());
748 hasModuleSame = para1Type == para2Type;
749 }
750 if (!hasModuleSame)
751 break;
752 }
753 if (hasModuleSame) {
754 module = existingModule.first;
755 break;
756 }
757 }
758 }
759
760 auto &slot = modules[module];
761 if (slot)
762 return slot.get();
763 slot = std::make_unique<ModuleLowering>();
764 auto &lowering = *slot;
765
766 auto loc = convertLocation(module->location);
767 OpBuilder::InsertionGuard g(builder);
768
769 // We only support modules for now. Extension to interfaces and programs
770 // should be trivial though, since they are essentially the same thing with
771 // only minor differences in semantics.
772 if (module->getDefinition().definitionKind !=
773 slang::ast::DefinitionKind::Module) {
774 mlir::emitError(loc) << "unsupported definition: "
775 << module->getDefinition().getKindString();
776 return {};
777 }
778
779 // Handle the port list.
780 auto block = std::make_unique<Block>();
781 SmallVector<hw::ModulePort> modulePorts;
782
783 // It's used to tag where a hierarchical name is on the port list.
784 unsigned int outputIdx = 0, inputIdx = 0;
785 for (auto *symbol : module->getPortList()) {
786 auto handlePort = [&](const PortSymbol &port) {
787 auto portLoc = convertLocation(port.location);
788 auto type = convertType(port.getType());
789 if (!type)
790 return failure();
791 auto portName = builder.getStringAttr(port.name);
792 BlockArgument arg;
793 if (port.direction == ArgumentDirection::Out) {
794 modulePorts.push_back({portName, type, hw::ModulePort::Output});
795 outputIdx++;
796 } else {
797 // Only the ref type wrapper exists for the time being, the net type
798 // wrapper for inout may be introduced later if necessary.
799 if (port.direction != ArgumentDirection::In)
800 type = moore::RefType::get(cast<moore::UnpackedType>(type));
801 modulePorts.push_back({portName, type, hw::ModulePort::Input});
802 arg = block->addArgument(type, portLoc);
803 inputIdx++;
804 }
805 lowering.ports.push_back({port, portLoc, arg});
806 return success();
807 };
808
809 if (const auto *port = symbol->as_if<PortSymbol>()) {
810 if (failed(handlePort(*port)))
811 return {};
812 } else if (const auto *multiPort = symbol->as_if<MultiPortSymbol>()) {
813 for (auto *port : multiPort->ports)
814 if (failed(handlePort(*port)))
815 return {};
816 } else {
817 mlir::emitError(convertLocation(symbol->location))
818 << "unsupported module port `" << symbol->name << "` ("
819 << slang::ast::toString(symbol->kind) << ")";
820 return {};
821 }
822 }
823
824 // Mapping hierarchical names into the module's ports.
825 for (auto &hierPath : hierPaths[module]) {
826 auto hierType = convertType(hierPath.valueSym->getType());
827 if (!hierType)
828 return {};
829
830 if (auto hierName = hierPath.hierName) {
831 // The type of all hierarchical names are marked as the "RefType".
832 hierType = moore::RefType::get(cast<moore::UnpackedType>(hierType));
833 if (hierPath.direction == ArgumentDirection::Out) {
834 hierPath.idx = outputIdx++;
835 modulePorts.push_back({hierName, hierType, hw::ModulePort::Output});
836 } else {
837 hierPath.idx = inputIdx++;
838 modulePorts.push_back({hierName, hierType, hw::ModulePort::Input});
839 auto hierLoc = convertLocation(hierPath.valueSym->location);
840 block->addArgument(hierType, hierLoc);
841 }
842 }
843 }
844 auto moduleType = hw::ModuleType::get(getContext(), modulePorts);
845
846 // Pick an insertion point for this module according to the source file
847 // location.
848 auto it = orderedRootOps.upper_bound(module->location);
849 if (it == orderedRootOps.end())
850 builder.setInsertionPointToEnd(intoModuleOp.getBody());
851 else
852 builder.setInsertionPoint(it->second);
853
854 // Create an empty module that corresponds to this module.
855 auto moduleOp =
856 moore::SVModuleOp::create(builder, loc, module->name, moduleType);
857 orderedRootOps.insert(it, {module->location, moduleOp});
858 moduleOp.getBodyRegion().push_back(block.release());
859 lowering.op = moduleOp;
860
861 // Add the module to the symbol table of the MLIR module, which uniquifies its
862 // name as we'd expect.
863 symbolTable.insert(moduleOp);
864
865 // Schedule the body to be lowered.
866 moduleWorklist.push(module);
867
868 // Map duplicate port by Syntax
869 for (const auto &port : lowering.ports)
870 lowering.portsBySyntaxNode.insert({port.ast.getSyntax(), &port.ast});
871
872 return &lowering;
873}
874
875/// Convert a module's body to the corresponding IR ops. The module op must have
876/// already been created earlier through a `convertModuleHeader` call.
877LogicalResult
878Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
879 auto &lowering = *modules[module];
880 OpBuilder::InsertionGuard g(builder);
881 builder.setInsertionPointToEnd(lowering.op.getBody());
882
884
885 // Keep track of the local time scale. `getTimeScale` automatically looks
886 // through parent scopes to find the time scale effective locally.
887 auto prevTimeScale = timeScale;
888 timeScale = module->getTimeScale().value_or(slang::TimeScale());
889 auto timeScaleGuard =
890 llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
891
892 // Collect downward hierarchical names. Such as,
893 // module SubA; int x = Top.y; endmodule. The "Top" module is the parent of
894 // the "SubA", so "Top.y" is the downward hierarchical name.
895 for (auto &hierPath : hierPaths[module])
896 if (hierPath.direction == slang::ast::ArgumentDirection::In && hierPath.idx)
897 valueSymbols.insert(hierPath.valueSym,
898 lowering.op.getBody()->getArgument(*hierPath.idx));
899
900 // Convert the body of the module.
901 for (auto &member : module->members()) {
902 auto loc = convertLocation(member.location);
903 if (failed(member.visit(ModuleVisitor(*this, loc))))
904 return failure();
905 }
906
907 // Create additional ops to drive input port values onto the corresponding
908 // internal variables and nets, and to collect output port values for the
909 // terminator.
910 SmallVector<Value> outputs;
911 for (auto &port : lowering.ports) {
912 Value value;
913 if (auto *expr = port.ast.getInternalExpr()) {
914 value = convertLvalueExpression(*expr);
915 } else if (port.ast.internalSymbol) {
916 if (const auto *sym =
917 port.ast.internalSymbol->as_if<slang::ast::ValueSymbol>())
918 value = valueSymbols.lookup(sym);
919 }
920 if (!value)
921 return mlir::emitError(port.loc, "unsupported port: `")
922 << port.ast.name
923 << "` does not map to an internal symbol or expression";
924
925 // Collect output port values to be returned in the terminator.
926 if (port.ast.direction == slang::ast::ArgumentDirection::Out) {
927 if (isa<moore::RefType>(value.getType()))
928 value = moore::ReadOp::create(builder, value.getLoc(), value);
929 outputs.push_back(value);
930 continue;
931 }
932
933 // Assign the value coming in through the port to the internal net or symbol
934 // of that port.
935 Value portArg = port.arg;
936 if (port.ast.direction != slang::ast::ArgumentDirection::In)
937 portArg = moore::ReadOp::create(builder, port.loc, port.arg);
938 moore::ContinuousAssignOp::create(builder, port.loc, value, portArg);
939 }
940
941 // Ensure the number of operands of this module's terminator and the number of
942 // its(the current module) output ports remain consistent.
943 for (auto &hierPath : hierPaths[module])
944 if (auto hierValue = valueSymbols.lookup(hierPath.valueSym))
945 if (hierPath.direction == slang::ast::ArgumentDirection::Out)
946 outputs.push_back(hierValue);
947
948 moore::OutputOp::create(builder, lowering.op.getLoc(), outputs);
949 return success();
950}
951
952/// Convert a package and its contents.
953LogicalResult
954Context::convertPackage(const slang::ast::PackageSymbol &package) {
955 // Keep track of the local time scale. `getTimeScale` automatically looks
956 // through parent scopes to find the time scale effective locally.
957 auto prevTimeScale = timeScale;
958 timeScale = package.getTimeScale().value_or(slang::TimeScale());
959 auto timeScaleGuard =
960 llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
961
962 OpBuilder::InsertionGuard g(builder);
963 builder.setInsertionPointToEnd(intoModuleOp.getBody());
965 for (auto &member : package.members()) {
966 auto loc = convertLocation(member.location);
967 if (failed(member.visit(PackageVisitor(*this, loc))))
968 return failure();
969 }
970 return success();
971}
972
973/// Convert a function and its arguments to a function declaration in the IR.
974/// This does not convert the function body.
976Context::declareFunction(const slang::ast::SubroutineSymbol &subroutine) {
977 using slang::ast::ArgumentDirection;
978
979 // Check if there already is a declaration for this function.
980 auto &lowering = functions[&subroutine];
981 if (lowering) {
982 if (!lowering->op)
983 return {};
984 return lowering.get();
985 }
986 lowering = std::make_unique<FunctionLowering>();
987 auto loc = convertLocation(subroutine.location);
988
989 // Pick an insertion point for this function according to the source file
990 // location.
991 OpBuilder::InsertionGuard g(builder);
992 auto it = orderedRootOps.upper_bound(subroutine.location);
993 if (it == orderedRootOps.end())
994 builder.setInsertionPointToEnd(intoModuleOp.getBody());
995 else
996 builder.setInsertionPoint(it->second);
997
998 // Class methods are currently not supported.
999 if (subroutine.thisVar) {
1000 mlir::emitError(loc) << "unsupported class method";
1001 return {};
1002 }
1003
1004 // Determine the function type.
1005 SmallVector<Type> inputTypes;
1006 SmallVector<Type, 1> outputTypes;
1007
1008 for (const auto *arg : subroutine.getArguments()) {
1009 auto type = convertType(arg->getType());
1010 if (!type)
1011 return {};
1012 if (arg->direction == ArgumentDirection::In) {
1013 inputTypes.push_back(type);
1014 } else {
1015 inputTypes.push_back(
1016 moore::RefType::get(cast<moore::UnpackedType>(type)));
1017 }
1018 }
1019
1020 if (!subroutine.getReturnType().isVoid()) {
1021 auto type = convertType(subroutine.getReturnType());
1022 if (!type)
1023 return {};
1024 outputTypes.push_back(type);
1025 }
1026
1027 auto funcType = FunctionType::get(getContext(), inputTypes, outputTypes);
1028
1029 // Prefix the function name with the surrounding namespace to create somewhat
1030 // sane names in the IR.
1031 SmallString<64> funcName;
1032 guessNamespacePrefix(subroutine.getParentScope()->asSymbol(), funcName);
1033 funcName += subroutine.name;
1034
1035 // Create a function declaration.
1036 auto funcOp = mlir::func::FuncOp::create(builder, loc, funcName, funcType);
1037 SymbolTable::setSymbolVisibility(funcOp, SymbolTable::Visibility::Private);
1038 orderedRootOps.insert(it, {subroutine.location, funcOp});
1039 lowering->op = funcOp;
1040
1041 // Add the function to the symbol table of the MLIR module, which uniquifies
1042 // its name.
1043 symbolTable.insert(funcOp);
1044
1045 return lowering.get();
1046}
1047
1048/// Convert a function.
1049LogicalResult
1050Context::convertFunction(const slang::ast::SubroutineSymbol &subroutine) {
1051 // Keep track of the local time scale. `getTimeScale` automatically looks
1052 // through parent scopes to find the time scale effective locally.
1053 auto prevTimeScale = timeScale;
1054 timeScale = subroutine.getTimeScale().value_or(slang::TimeScale());
1055 auto timeScaleGuard =
1056 llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
1057
1058 // First get or create the function declaration.
1059 auto *lowering = declareFunction(subroutine);
1060 if (!lowering)
1061 return failure();
1063
1064 // Create a function body block and populate it with block arguments.
1065 SmallVector<moore::VariableOp> argVariables;
1066 auto &block = lowering->op.getBody().emplaceBlock();
1067 for (auto [astArg, type] :
1068 llvm::zip(subroutine.getArguments(),
1069 lowering->op.getFunctionType().getInputs())) {
1070 auto loc = convertLocation(astArg->location);
1071 auto blockArg = block.addArgument(type, loc);
1072
1073 if (isa<moore::RefType>(type)) {
1074 valueSymbols.insert(astArg, blockArg);
1075 } else {
1076 // Convert the body of the function.
1077 OpBuilder::InsertionGuard g(builder);
1078 builder.setInsertionPointToEnd(&block);
1079
1080 auto shadowArg = moore::VariableOp::create(
1081 builder, loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
1082 StringAttr{}, blockArg);
1083 valueSymbols.insert(astArg, shadowArg);
1084 argVariables.push_back(shadowArg);
1085 }
1086 }
1087
1088 // Convert the body of the function.
1089 OpBuilder::InsertionGuard g(builder);
1090 builder.setInsertionPointToEnd(&block);
1091
1092 Value returnVar;
1093 if (subroutine.returnValVar) {
1094 auto type = convertType(*subroutine.returnValVar->getDeclaredType());
1095 if (!type)
1096 return failure();
1097 returnVar = moore::VariableOp::create(
1098 builder, lowering->op.getLoc(),
1099 moore::RefType::get(cast<moore::UnpackedType>(type)), StringAttr{},
1100 Value{});
1101 valueSymbols.insert(subroutine.returnValVar, returnVar);
1102 }
1103
1104 if (failed(convertStatement(subroutine.getBody())))
1105 return failure();
1106
1107 // If there was no explicit return statement provided by the user, insert a
1108 // default one.
1109 if (builder.getBlock()) {
1110 if (returnVar && !subroutine.getReturnType().isVoid()) {
1111 Value read =
1112 moore::ReadOp::create(builder, returnVar.getLoc(), returnVar);
1113 mlir::func::ReturnOp::create(builder, lowering->op.getLoc(), read);
1114 } else {
1115 mlir::func::ReturnOp::create(builder, lowering->op.getLoc(),
1116 ValueRange{});
1117 }
1118 }
1119 if (returnVar && returnVar.use_empty())
1120 returnVar.getDefiningOp()->erase();
1121
1122 for (auto var : argVariables) {
1123 if (llvm::all_of(var->getUsers(),
1124 [](auto *user) { return isa<moore::ReadOp>(user); })) {
1125 for (auto *user : llvm::make_early_inc_range(var->getUsers())) {
1126 user->getResult(0).replaceAllUsesWith(var.getInitial());
1127 user->erase();
1128 }
1129 var->erase();
1130 }
1131 }
1132 return success();
1133}
static FIRRTLBaseType convertType(FIRRTLBaseType type)
Returns null type if no conversion is needed.
Definition DropConst.cpp:32
static Location convertLocation(MLIRContext *context, const slang::SourceManager &sourceManager, slang::SourceLocation loc)
Convert a slang SourceLocation to an MLIR Location.
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:216
static moore::ProcedureKind convertProcedureKind(slang::ast::ProceduralBlockKind kind)
static void guessNamespacePrefix(const slang::ast::Symbol &symbol, SmallString< 64 > &prefix)
Definition Structure.cpp:20
static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
bool lowerAlwaysAtStarAsComb
Interpret always @(*) as always_comb.
bool debugInfo
Generate debug information in the form of debug dialect ops in the IR.
A helper class to facilitate the conversion from a Slang AST to MLIR operations.
LogicalResult convertFunction(const slang::ast::SubroutineSymbol &subroutine)
Convert a function.
Value materializeConversion(Type type, Value value, bool isSigned, Location loc)
Helper function to insert the necessary operations to cast a value from one type to another.
ModuleLowering * convertModuleHeader(const slang::ast::InstanceBodySymbol *module)
Convert a module and its ports to an empty module op in the IR.
Value convertLvalueExpression(const slang::ast::Expression &expr)
Value materializeConstant(const slang::ConstantValue &constant, const slang::ast::Type &type, Location loc)
Helper function to materialize a ConstantValue as an SSA value.
LogicalResult convertModuleBody(const slang::ast::InstanceBodySymbol *module)
Convert a module's body to the corresponding IR ops.
slang::ast::Compilation & compilation
OpBuilder builder
The builder used to create IR operations.
std::queue< const slang::ast::InstanceBodySymbol * > moduleWorklist
A list of modules for which the header has been created, but the body has not been converted yet.
std::map< slang::SourceLocation, Operation * > orderedRootOps
The top-level operations ordered by their Slang source location.
Type convertType(const slang::ast::Type &type, LocationAttr loc={})
Convert a slang type into an MLIR type.
Definition Types.cpp:176
DenseMap< const slang::ast::SubroutineSymbol *, std::unique_ptr< FunctionLowering > > functions
Functions that have already been converted.
slang::TimeScale timeScale
The time scale currently in effect.
const ImportVerilogOptions & options
Value convertRvalueExpression(const slang::ast::Expression &expr, Type requiredType={})
LogicalResult traverseInstanceBody(const slang::ast::Symbol &symbol)
SymbolTable symbolTable
A symbol table of the MLIR module we are emitting into.
DenseMap< const slang::ast::InstanceBodySymbol *, SmallVector< HierPathInfo > > hierPaths
Collect all hierarchical names used for the per module/instance.
FunctionLowering * declareFunction(const slang::ast::SubroutineSymbol &subroutine)
Convert a function and its arguments to a function declaration in the IR.
LogicalResult convertPackage(const slang::ast::PackageSymbol &package)
Convert a package and its contents.
MLIRContext * getContext()
Return the MLIR context.
LogicalResult convertStatement(const slang::ast::Statement &stmt)
DenseMap< const slang::ast::InstanceBodySymbol *, std::unique_ptr< ModuleLowering > > modules
How we have lowered modules to MLIR.
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.