Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Seq] Add a pass to implement firreg randomization #7606

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion include/circt/Dialect/Seq/SeqPasses.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<mlir::Pass> createLowerSeqHLMemPass();
Expand All @@ -31,7 +32,8 @@ std::unique_ptr<mlir::Pass> createLowerSeqFIFOPass();
std::unique_ptr<mlir::Pass>
createHWMemSimImplPass(const HWMemSimImplOptions &options = {});
std::unique_ptr<mlir::Pass> createLowerSeqShiftRegPass();

std::unique_ptr<mlir::Pass>
createFirregRandomizationPass(const FirregRandomizationOptions &options = {});
/// Generate the code for registering passes.
#define GEN_PASS_REGISTRATION
#include "circt/Dialect/Seq/SeqPasses.h.inc"
Expand Down
20 changes: 20 additions & 0 deletions include/circt/Dialect/Seq/SeqPasses.td
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions lib/Dialect/Seq/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_circt_dialect_library(CIRCTSeqTransforms
ExternalizeClockGate.cpp
FirregRandomization.cpp
HWMemSimImpl.cpp
LowerSeqHLMem.cpp
LowerSeqFIFO.cpp
Expand All @@ -16,6 +17,7 @@ add_circt_dialect_library(CIRCTSeqTransforms
CIRCTSupport
CIRCTSV
MLIRIR
MLIRFuncDialect
MLIRPass
MLIRTransformUtils
)
183 changes: 183 additions & 0 deletions lib/Dialect/Seq/Transforms/FirregRandomization.cpp
Original file line number Diff line number Diff line change
@@ -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<FirregRandomizationPass>::emitSV;
};
} // anonymous namespace

struct RegLowerInfo {
CompRegOp compReg;
int64_t randStart;
size_t width;
};

static Value initialize(OpBuilder &builder, RegLowerInfo reg,
ArrayRef<Value> rands) {

auto loc = reg.compReg.getLoc();
SmallVector<Value> nibbles;
if (reg.width == 0)
return builder.create<hw::ConstantOp>(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<comb::ExtractOp>(loc, elemVal, start, nwidth);
nibbles.push_back(elem);
offset += nwidth;
width -= nwidth;
}
auto concat = builder.createOrFold<comb::ConcatOp>(loc, nibbles);
auto bitcast = builder.createOrFold<hw::BitcastOp>(
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<sv::MacroDeclOp>(builder.getUnknownLoc(), "RANDOM");
} else {
auto funcType = builder.getFunctionType({}, {builder.getIntegerType(32)});
auto randomFunc = builder.create<func::FuncOp>(builder.getUnknownLoc(),
"random", funcType);
randomFunc.setPrivate();
randomDecl = randomFunc;
}

for (auto hwModule : module.getBody()->getOps<hw::HWModuleOp>())
runOnModule(hwModule, randomDecl);
}

void FirregRandomizationPass::runOnModule(hw::HWModuleOp module,
Operation *randomDecl) {
SmallVector<RegLowerInfo> regs;
for (auto reg : module.getOps<seq::CompRegOp>()) {
// 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<IntegerAttr>("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 &reg : regs) {
if (reg.randStart == -1) {
reg.randStart = maxBit;
maxBit += reg.width;
}
}

auto builder = ImplicitLocOpBuilder::atBlockTerminator(module.getLoc(),
module.getBodyBlock());

SmallVector<Type> resultTypes;
for (auto reg : regs)
resultTypes.push_back(reg.compReg.getResult().getType());

auto loc = module.getLoc();

auto init = builder.create<seq::InitialOp>(resultTypes, [&] {
SmallVector<Value> initValues;

// Create randomization vector
SmallVector<Value> randValues;
auto numRandomCalls = (maxBit + 31) / 32;
if (emitSV) {
if (!regs.empty()) {
builder.create<sv::VerbatimOp>("`INIT_RANDOM_PROLOG_");
for (uint64_t x = 0; x < numRandomCalls; ++x) {
auto rand = builder.create<sv::MacroRefExprSEOp>(
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<mlir::func::CallOp>(loc, cast<func::FuncOp>(randomDecl))
.getResult(0));
}
}
// Create initialisers for all registers.
for (auto &svReg : regs)
initValues.push_back(::initialize(builder, svReg, randValues));
builder.create<seq::YieldOp>(initValues);
});

for (auto [reg, init] : llvm::zip(regs, init.getResults())) {
reg.compReg.getInitialValueMutable().assign(init);
}
}

std::unique_ptr<Pass> circt::seq::createFirregRandomizationPass(
const FirregRandomizationOptions &options) {
return std::make_unique<FirregRandomizationPass>(options);
}
31 changes: 31 additions & 0 deletions test/Dialect/Seq/randomized.mlir
Original file line number Diff line number Diff line change
@@ -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<i18>, !seq.immutable<i18>

hw.output %r0, %r1: i18, i18
}
Loading