Skip to content

Commit

Permalink
[Seq] Add a pass to implement firreg randomization
Browse files Browse the repository at this point in the history
  • Loading branch information
uenoku committed Sep 18, 2024
1 parent 6176d13 commit dc0fa2e
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 1 deletion.
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
1 change: 1 addition & 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 Down
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 {
// Native function. Create func.call
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
}

0 comments on commit dc0fa2e

Please sign in to comment.