From e03688ae93705876d60f64cc2e553756cad91a3a Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Wed, 18 Sep 2024 19:10:22 +0900 Subject: [PATCH] [Seq] Add a pass to implement firreg randomization --- include/circt/Dialect/Seq/SeqPasses.h | 4 +- include/circt/Dialect/Seq/SeqPasses.td | 20 ++ lib/Dialect/Seq/Transforms/CMakeLists.txt | 2 + .../Seq/Transforms/FirregRandomization.cpp | 183 ++++++++++++++++++ test/Dialect/Seq/randomized.mlir | 31 +++ 5 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 lib/Dialect/Seq/Transforms/FirregRandomization.cpp create mode 100644 test/Dialect/Seq/randomized.mlir diff --git a/include/circt/Dialect/Seq/SeqPasses.h b/include/circt/Dialect/Seq/SeqPasses.h index e14efd68ac48..af3e8a84b396 100644 --- a/include/circt/Dialect/Seq/SeqPasses.h +++ b/include/circt/Dialect/Seq/SeqPasses.h @@ -22,6 +22,7 @@ namespace seq { #define GEN_PASS_DECL_EXTERNALIZECLOCKGATE #define GEN_PASS_DECL_HWMEMSIMIMPL +#define GEN_PASS_DECL_FIRREGRANDOMIZATION #include "circt/Dialect/Seq/SeqPasses.h.inc" std::unique_ptr createLowerSeqHLMemPass(); @@ -31,7 +32,8 @@ std::unique_ptr createLowerSeqFIFOPass(); std::unique_ptr createHWMemSimImplPass(const HWMemSimImplOptions &options = {}); std::unique_ptr createLowerSeqShiftRegPass(); - +std::unique_ptr +createFirregRandomizationPass(const FirregRandomizationOptions &options = {}); /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION #include "circt/Dialect/Seq/SeqPasses.h.inc" diff --git a/include/circt/Dialect/Seq/SeqPasses.td b/include/circt/Dialect/Seq/SeqPasses.td index 1790ebbc1208..6567476e2312 100644 --- a/include/circt/Dialect/Seq/SeqPasses.td +++ b/include/circt/Dialect/Seq/SeqPasses.td @@ -107,4 +107,24 @@ def LowerSeqShiftReg : Pass<"lower-seq-shiftreg", "hw::HWModuleOp"> { let dependentDialects = ["circt::hw::HWDialect"]; } +def FirregRandomization : Pass<"seq-firreg-randomization", "ModuleOp"> { + let summary = "Implement randomized initialization for FIRRTL registers"; + let description = [{ + This pass performs firreg random initialization for seq.compreg. + + When `emitSV` is true, the pass generates a random function call as a + macro in the SV dialect. It supports configurable initialization of + registers through macros like `RANDOM` and `INIT_RANDOM_PROLOG`. + + Otherwise, it produces simulatable IR using MLIR core dialects. + }]; + let constructor = "circt::seq::createFirregRandomizationPass()"; + let dependentDialects = ["circt::hw::HWDialect", "circt::sv::SVDialect", + "mlir::func::FuncDialect"]; + let options = [ + Option<"emitSV", "emit-sv", "bool", "false", + [{If true, generate SV ops for register randomization}]> + ]; +} + #endif // CIRCT_DIALECT_SEQ_SEQPASSES diff --git a/lib/Dialect/Seq/Transforms/CMakeLists.txt b/lib/Dialect/Seq/Transforms/CMakeLists.txt index 63f574d5b57b..0dc3fd4e4aba 100644 --- a/lib/Dialect/Seq/Transforms/CMakeLists.txt +++ b/lib/Dialect/Seq/Transforms/CMakeLists.txt @@ -1,5 +1,6 @@ add_circt_dialect_library(CIRCTSeqTransforms ExternalizeClockGate.cpp + FirregRandomization.cpp HWMemSimImpl.cpp LowerSeqHLMem.cpp LowerSeqFIFO.cpp @@ -16,6 +17,7 @@ add_circt_dialect_library(CIRCTSeqTransforms CIRCTSupport CIRCTSV MLIRIR + MLIRFuncDialect MLIRPass MLIRTransformUtils ) diff --git a/lib/Dialect/Seq/Transforms/FirregRandomization.cpp b/lib/Dialect/Seq/Transforms/FirregRandomization.cpp new file mode 100644 index 000000000000..f0a6b83dfb38 --- /dev/null +++ b/lib/Dialect/Seq/Transforms/FirregRandomization.cpp @@ -0,0 +1,183 @@ +//===- FirregRandomization.cpp - Randomize initial values of registers --===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/SV/SVOps.h" +#include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Dialect/Seq/SeqPasses.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/ImplicitLocOpBuilder.h" +#include "mlir/Pass/Pass.h" + +namespace circt { +namespace seq { +#define GEN_PASS_DEF_FIRREGRANDOMIZATION +#include "circt/Dialect/Seq/SeqPasses.h.inc" +} // namespace seq +} // namespace circt + +using namespace circt; +using namespace seq; +using namespace hw; +using namespace mlir; +using namespace func; + +namespace { +struct FirregRandomizationPass + : public circt::seq::impl::FirregRandomizationBase< + FirregRandomizationPass> { + using FirregRandomizationBase< + FirregRandomizationPass>::FirregRandomizationBase; + void runOnOperation() override; + void runOnModule(hw::HWModuleOp module, Operation *randomDecl); + using FirregRandomizationBase::emitSV; +}; +} // anonymous namespace + +struct RegLowerInfo { + CompRegOp compReg; + int64_t randStart; + size_t width; +}; + +static Value initialize(OpBuilder &builder, RegLowerInfo reg, + ArrayRef rands) { + + auto loc = reg.compReg.getLoc(); + SmallVector nibbles; + if (reg.width == 0) + return builder.create(loc, APInt(reg.width, 0)); + + uint64_t width = reg.width; + uint64_t offset = reg.randStart; + while (width) { + auto index = offset / 32; + auto start = offset % 32; + auto nwidth = std::min(32 - start, width); + auto elemVal = rands[index]; + auto elem = + builder.createOrFold(loc, elemVal, start, nwidth); + nibbles.push_back(elem); + offset += nwidth; + width -= nwidth; + } + auto concat = builder.createOrFold(loc, nibbles); + auto bitcast = builder.createOrFold( + loc, reg.compReg.getResult().getType(), concat); + + // Initialize register elements. + return bitcast; +} + +void FirregRandomizationPass::runOnOperation() { + auto module = getOperation(); + OpBuilder builder(module); + + builder.setInsertionPointToStart(module.getBody()); + Operation *randomDecl; + if (emitSV) { + randomDecl = + builder.create(builder.getUnknownLoc(), "RANDOM"); + } else { + auto funcType = builder.getFunctionType({}, {builder.getIntegerType(32)}); + auto randomFunc = builder.create(builder.getUnknownLoc(), + "random", funcType); + randomFunc.setPrivate(); + randomDecl = randomFunc; + } + + for (auto hwModule : module.getBody()->getOps()) + runOnModule(hwModule, randomDecl); +} + +void FirregRandomizationPass::runOnModule(hw::HWModuleOp module, + Operation *randomDecl) { + SmallVector regs; + for (auto reg : module.getOps()) { + // If it has an initial value, we don't randomize it. + if (reg.getInitialValue()) + continue; + + RegLowerInfo info; + info.compReg = reg; + info.width = hw::getBitWidth(reg.getType()); + + // If it has a random init start attribute, we randomize it. + if (auto attr = reg->getAttrOfType("firrtl.random_init_start")) + info.randStart = attr.getInt(); + else + info.randStart = -1; + + regs.push_back(info); + } + + // Compute total width of random space. Place non-chisel registers at the end + // of the space. The Random space is unique to the initial block, due to + // verilog thread rules, so we can drop trailing random calls if they are + // unused. + uint64_t maxBit = 0; + for (auto reg : regs) + if (reg.randStart >= 0) + maxBit = std::max(maxBit, (uint64_t)reg.randStart + reg.width); + + for (auto ® : regs) { + if (reg.randStart == -1) { + reg.randStart = maxBit; + maxBit += reg.width; + } + } + + auto builder = ImplicitLocOpBuilder::atBlockTerminator(module.getLoc(), + module.getBodyBlock()); + + SmallVector resultTypes; + for (auto reg : regs) + resultTypes.push_back(reg.compReg.getResult().getType()); + + auto loc = module.getLoc(); + + auto init = builder.create(resultTypes, [&] { + SmallVector initValues; + + // Create randomization vector + SmallVector randValues; + auto numRandomCalls = (maxBit + 31) / 32; + if (emitSV) { + if (!regs.empty()) { + builder.create("`INIT_RANDOM_PROLOG_"); + for (uint64_t x = 0; x < numRandomCalls; ++x) { + auto rand = builder.create( + loc, builder.getIntegerType(32), "RANDOM"); + randValues.push_back(rand); + } + }; + } else { + // Create a function call for `random`. + for (uint64_t x = 0; x < numRandomCalls; ++x) { + randValues.push_back( + builder + .create(loc, cast(randomDecl)) + .getResult(0)); + } + } + // Create initialisers for all registers. + for (auto &svReg : regs) + initValues.push_back(::initialize(builder, svReg, randValues)); + builder.create(initValues); + }); + + for (auto [reg, init] : llvm::zip(regs, init.getResults())) { + reg.compReg.getInitialValueMutable().assign(init); + } +} + +std::unique_ptr circt::seq::createFirregRandomizationPass( + const FirregRandomizationOptions &options) { + return std::make_unique(options); +} diff --git a/test/Dialect/Seq/randomized.mlir b/test/Dialect/Seq/randomized.mlir new file mode 100644 index 000000000000..6b09c926937b --- /dev/null +++ b/test/Dialect/Seq/randomized.mlir @@ -0,0 +1,31 @@ +// RUN: circt-opt %s -verify-diagnostics -seq-firreg-randomization | FileCheck %s --check-prefixes=COMMON,CHECK +// RUN: circt-opt %s -verify-diagnostics --pass-pipeline='builtin.module(seq-firreg-randomization{emit-sv=true})' | FileCheck %s --check-prefixes=COMMON,SV +sv.macro.decl @INIT_RANDOM_PROLOG_ +hw.module @top(in %clk: !seq.clock, in %rst: i1, in %i: i18, out o: i18, out j: i18) { + %c0_i18 = hw.constant 0 : i18 + %r0 = seq.compreg %i, %clk reset %rst, %c0_i18 : i18 + %r1 = seq.compreg %i, %clk : i18 + // COMMON: %r0 = seq.compreg %i, %clk reset %rst, %c0_i18 initial %0#0 : i18 + // COMMON-NEXT: %r1 = seq.compreg %i, %clk initial %0#1 : i18 + // COMMON-NEXT: %0:2 = seq.initial { + // CHECK-NEXT: %[[RAND1:.+]] = func.call @random() : () -> i32 + // CHECK-NEXT: %[[RAND2:.+]] = func.call @random() : () -> i32 + // CHECK-NEXT: %[[EXTRACT1:.+]] = comb.extract %[[RAND1]] from 0 : (i32) -> i18 + // CHECK-NEXT: %[[EXTRACT2:.+]] = comb.extract %[[RAND1]] from 18 : (i32) -> i14 + // CHECK-NEXT: %[[EXTRACT3:.+]] = comb.extract %[[RAND2]] from 0 : (i32) -> i4 + // CHECK-NEXT: %[[CONCAT:.+]] = comb.concat %[[EXTRACT2]], %[[EXTRACT3]] : i14, i4 + // CHECK-NEXT: seq.yield %[[EXTRACT1]], %[[CONCAT]] : i18, i18 + + // SV-NEXT: sv.verbatim "`INIT_RANDOM_PROLOG_" + // SV-NEXT: %RANDOM = sv.macro.ref.se @RANDOM() : () -> i32 + // SV-NEXT: %RANDOM_0 = sv.macro.ref.se @RANDOM() : () -> i32 + // SV-NEXT: %[[EXTRACT1:.+]] = comb.extract %RANDOM from 0 : (i32) -> i18 + // SV-NEXT: %[[EXTRACT2:.+]] = comb.extract %RANDOM from 18 : (i32) -> i14 + // SV-NEXT: %[[EXTRACT3:.+]] = comb.extract %RANDOM_0 from 0 : (i32) -> i4 + // SV-NEXT: %[[CONCAT:.+]] = comb.concat %[[EXTRACT2]], %[[EXTRACT3]] : i14, i4 + // SV-NEXT: seq.yield %[[EXTRACT1]], %[[CONCAT]] : i18, i18 + + // COMMON: } : !seq.immutable, !seq.immutable + + hw.output %r0, %r1: i18, i18 +}