Skip to content

Commit

Permalink
[Calyx] Switch sequential memories to be true single port memories (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewb1999 authored Feb 29, 2024
1 parent c403838 commit 2d58838
Show file tree
Hide file tree
Showing 10 changed files with 420 additions and 252 deletions.
14 changes: 7 additions & 7 deletions include/circt/Dialect/Calyx/CalyxLoweringUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,12 @@ void buildAssignmentsForRegisterWrite(OpBuilder &builder,
// external memories.
struct MemoryPortsImpl {
std::optional<Value> readData;
std::optional<Value> readEn;
std::optional<Value> readDone;
std::optional<Value> readOrContentEn;
std::optional<Value> writeData;
std::optional<Value> writeEn;
std::optional<Value> writeDone;
std::optional<Value> done;
SmallVector<Value> addrPorts;
std::optional<bool> isContentEn;
};

// Represents the interface of memory in Calyx. The various lowering passes
Expand All @@ -105,16 +105,16 @@ struct MemoryInterface {
// Getter methods for each memory interface port.
Value readData();
Value readEn();
Value readDone();
Value contentEn();
Value writeData();
Value writeEn();
Value writeDone();
Value done();
std::optional<Value> readDataOpt();
std::optional<Value> readEnOpt();
std::optional<Value> readDoneOpt();
std::optional<Value> contentEnOpt();
std::optional<Value> writeDataOpt();
std::optional<Value> writeEnOpt();
std::optional<Value> writeDoneOpt();
std::optional<Value> doneOpt();
ValueRange addrPorts();

private:
Expand Down
16 changes: 8 additions & 8 deletions include/circt/Dialect/Calyx/CalyxPrimitives.td
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def SeqMemoryOp : CalyxPrimitive<"seq_mem", []> {
```mlir
// A 1-dimensional, 32-bit memory with size dimension 1. Equivalent representation in the native compiler:
// `m1 = seq_mem_d1(32, 1, 1)`
%m1.addr0, %m1.write_data, %m1.write_en, %m1.write_done, %m1.clk, %m1.read_data, %m1.read_en, %m1.read_done = calyx.memory @m1 <[1] x 32> [1] : i1, i32, i1, i1, i32, i1
%m1.addr0, %m1.write_data, %m1.write_en, %m1.clk, %m1.read_data, %m1.read_en, %m1.done = calyx.memory @m1 <[1] x 32> [1] : i1, i32, i1, i1, i32, i1

// A 2-dimensional, 8-bit memory with size dimensions 64 x 64. Equivalent representation in the native compiler:
// `m2 = seq_mem_d2(8, 64, 64, 6, 6)`
Expand Down Expand Up @@ -210,13 +210,13 @@ def SeqMemoryOp : CalyxPrimitive<"seq_mem", []> {
assert(i < getNumDimensions() && "index greater than number of memory address ports.");
return getResults()[i];
}
Value writeData() { return getResult(getNumDimensions()); }
Value writeEn() { return getResult(getNumDimensions() + 1); }
Value writeDone() { return getResult(getNumDimensions() + 2); }
Value clk() { return getResult(getNumDimensions() + 3); }
Value readData() { return getResult(getNumDimensions() + 4); }
Value readEn() { return getResult(getNumDimensions() + 5); }
Value readDone() { return getResult(getNumDimensions() + 6); }
Value clk() { return getResult(getNumDimensions()); }
Value reset() { return getResult(getNumDimensions() + 1); }
Value contentEn() { return getResult(getNumDimensions() + 2); }
Value writeEn() { return getResult(getNumDimensions() + 3); }
Value writeData() { return getResult(getNumDimensions() + 4); }
Value readData() { return getResult(getNumDimensions() + 5); }
Value done() { return getResult(getNumDimensions() + 6); }
}];

let extraClassDefinition = [{
Expand Down
11 changes: 8 additions & 3 deletions lib/Conversion/LoopScheduleToCalyx/LoopScheduleToCalyx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -475,8 +475,13 @@ LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
rewriter.create<calyx::AssignOp>(
storeOp.getLoc(), memoryInterface.writeEn(),
createConstant(storeOp.getLoc(), rewriter, getComponent(), 1, 1));
rewriter.create<calyx::GroupDoneOp>(storeOp.getLoc(),
memoryInterface.writeDone());
if (memoryInterface.contentEnOpt().has_value()) {
// If memory has content enable, it must be asserted when writing
rewriter.create<calyx::AssignOp>(
storeOp.getLoc(), memoryInterface.contentEn(),
createConstant(storeOp.getLoc(), rewriter, getComponent(), 1, 1));
}
rewriter.create<calyx::GroupDoneOp>(storeOp.getLoc(), memoryInterface.done());

getState<ComponentLoweringState>().registerNonPipelineOperations(storeOp,
group);
Expand Down Expand Up @@ -828,7 +833,7 @@ struct FuncOpConversion : public calyx::FuncOpPartialLoweringPattern {
unsigned outPortsIt = extMemPortIndices.getSecond().second +
compOp.getInputPortInfo().size();
extMemPorts.readData = compOp.getArgument(inPortsIt++);
extMemPorts.writeDone = compOp.getArgument(inPortsIt);
extMemPorts.done = compOp.getArgument(inPortsIt);
extMemPorts.writeData = compOp.getArgument(outPortsIt++);
unsigned nAddresses = extMemPortIndices.getFirst()
.getType()
Expand Down
44 changes: 39 additions & 5 deletions lib/Conversion/SCFToCalyx/SCFToCalyx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,15 +428,44 @@ LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
calyx::createConstant(loadOp.getLoc(), rewriter, getComponent(), 1, 1);
rewriter.create<calyx::AssignOp>(loadOp.getLoc(), memoryInterface.readEn(),
oneI1);
regWriteEn = memoryInterface.readDone();
regWriteEn = memoryInterface.done();
if (calyx::noStoresToMemory(memref) &&
calyx::singleLoadFromMemory(memref)) {
// Single load from memory; we do not need to write the output to a
// register. The readData value will be held until readEn is asserted
// again
needReg = false;
rewriter.create<calyx::GroupDoneOp>(loadOp.getLoc(),
memoryInterface.readDone());
memoryInterface.done());
// We refrain from replacing the loadOp result with
// memoryInterface.readData, since multiple loadOp's need to be converted
// to a single memory's ReadData. If this replacement is done now, we lose
// the link between which SSA memref::LoadOp values map to which groups
// for loading a value from the Calyx memory. At this point of lowering,
// we keep the memref::LoadOp SSA value, and do value replacement _after_
// control has been generated (see LateSSAReplacement). This is *vital*
// for things such as calyx::InlineCombGroups to be able to properly track
// which memory assignment groups belong to which accesses.
res = loadOp.getResult();
}
} else if (memoryInterface.contentEnOpt().has_value()) {
auto oneI1 =
calyx::createConstant(loadOp.getLoc(), rewriter, getComponent(), 1, 1);
auto zeroI1 =
calyx::createConstant(loadOp.getLoc(), rewriter, getComponent(), 1, 0);
rewriter.create<calyx::AssignOp>(loadOp.getLoc(),
memoryInterface.contentEn(), oneI1);
rewriter.create<calyx::AssignOp>(loadOp.getLoc(), memoryInterface.writeEn(),
zeroI1);
regWriteEn = memoryInterface.done();
if (calyx::noStoresToMemory(memref) &&
calyx::singleLoadFromMemory(memref)) {
// Single load from memory; we do not need to write the output to a
// register. The readData value will be held until contentEn is asserted
// again
needReg = false;
rewriter.create<calyx::GroupDoneOp>(loadOp.getLoc(),
memoryInterface.done());
// We refrain from replacing the loadOp result with
// memoryInterface.readData, since multiple loadOp's need to be converted
// to a single memory's ReadData. If this replacement is done now, we lose
Expand Down Expand Up @@ -496,8 +525,13 @@ LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
rewriter.create<calyx::AssignOp>(
storeOp.getLoc(), memoryInterface.writeEn(),
createConstant(storeOp.getLoc(), rewriter, getComponent(), 1, 1));
rewriter.create<calyx::GroupDoneOp>(storeOp.getLoc(),
memoryInterface.writeDone());
if (memoryInterface.contentEnOpt().has_value()) {
// If memory has content enable, it must be asserted when writing
rewriter.create<calyx::AssignOp>(
storeOp.getLoc(), memoryInterface.contentEn(),
createConstant(storeOp.getLoc(), rewriter, getComponent(), 1, 1));
}
rewriter.create<calyx::GroupDoneOp>(storeOp.getLoc(), memoryInterface.done());

return success();
}
Expand Down Expand Up @@ -1083,7 +1117,7 @@ struct FuncOpConversion : public calyx::FuncOpPartialLoweringPattern {
unsigned outPortsIt = extMemPortIndices.getSecond().second +
compOp.getInputPortInfo().size();
extMemPorts.readData = compOp.getArgument(inPortsIt++);
extMemPorts.writeDone = compOp.getArgument(inPortsIt);
extMemPorts.done = compOp.getArgument(inPortsIt);
extMemPorts.writeData = compOp.getArgument(outPortsIt++);
unsigned nAddresses = extMemPortIndices.getFirst()
.getType()
Expand Down
42 changes: 21 additions & 21 deletions lib/Dialect/Calyx/CalyxOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "circt/Dialect/HW/HWTypes.h"
#include "mlir/IR/AsmState.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/DialectImplementation.h"
Expand Down Expand Up @@ -2092,16 +2093,16 @@ SmallVector<StringRef> SeqMemoryOp::portNames() {
StringAttr::get(this->getContext(), "addr" + std::to_string(i));
portNames.push_back(nameAttr.getValue());
}
portNames.append({"write_data", "write_en", "write_done", clkPort,
"read_data", "read_en", "read_done"});
portNames.append({clkPort, "reset", "content_en", "write_en", "write_data",
"read_data", "done"});
return portNames;
}

SmallVector<Direction> SeqMemoryOp::portDirections() {
SmallVector<Direction> portDirections;
for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
portDirections.push_back(Input);
portDirections.append({Input, Input, Output, Input, Output, Input, Output});
portDirections.append({Input, Input, Input, Input, Input, Output, Output});
return portDirections;
}

Expand All @@ -2115,19 +2116,18 @@ SmallVector<DictionaryAttr> SeqMemoryOp::portAttributes() {
// Use a boolean to indicate this attribute is used.
IntegerAttr isSet = IntegerAttr::get(builder.getIndexType(), 1);
IntegerAttr isTwo = IntegerAttr::get(builder.getIndexType(), 2);
NamedAttrList writeEn, writeDone, clk, reset, readEn, readDone;
writeEn.append(goPort, isSet);
writeDone.append(donePort, isSet);
NamedAttrList done, clk, reset, contentEn;
done.append(donePort, isSet);
clk.append(clkPort, isSet);
readEn.append(goPort, isTwo);
readDone.append(donePort, isTwo);
portAttributes.append({DictionaryAttr::get(context), // Write Data
writeEn.getDictionary(context), // Write enable
writeDone.getDictionary(context), // Write done
clk.getDictionary(context), // Clk
DictionaryAttr::get(context), // Out
readEn.getDictionary(context), // Read enable
readDone.getDictionary(context)} // Read done
clk.append(resetPort, isSet);
contentEn.append(goPort, isTwo);
portAttributes.append({clk.getDictionary(context), // Clk
reset.getDictionary(context), // Reset
contentEn.getDictionary(context), // Content enable
DictionaryAttr::get(context), // Write enable
DictionaryAttr::get(context), // Write data
DictionaryAttr::get(context), // Read data
done.getDictionary(context)} // Done
);
return portAttributes;
}
Expand All @@ -2143,13 +2143,13 @@ void SeqMemoryOp::build(OpBuilder &builder, OperationState &state,
SmallVector<Type> types;
for (int64_t size : addrSizes)
types.push_back(builder.getIntegerType(size)); // Addresses
types.push_back(builder.getIntegerType(width)); // Write data
types.push_back(builder.getI1Type()); // Write enable
types.push_back(builder.getI1Type()); // Write done
types.push_back(builder.getI1Type()); // Clk
types.push_back(builder.getI1Type()); // Reset
types.push_back(builder.getI1Type()); // Content enable
types.push_back(builder.getI1Type()); // Write enable
types.push_back(builder.getIntegerType(width)); // Write data
types.push_back(builder.getIntegerType(width)); // Read data
types.push_back(builder.getI1Type()); // Read enable
types.push_back(builder.getI1Type()); // Read done
types.push_back(builder.getI1Type()); // Done
state.addTypes(types);
}

Expand All @@ -2163,7 +2163,7 @@ LogicalResult SeqMemoryOp::verify() {
<< numDims << ") and address sizes (" << numAddrs << ")";

size_t numExtraPorts =
7; // write data/enable/done, clk, and read data/enable/done.
7; // write data/enable, clk, reset, read data, content enable, and done.
if (getNumResults() != numAddrs + numExtraPorts)
return emitOpError("incorrect number of address ports, expected ")
<< numAddrs;
Expand Down
50 changes: 33 additions & 17 deletions lib/Dialect/Calyx/Transforms/CalyxLoweringUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ void buildAssignmentsForRegisterWrite(OpBuilder &builder,
//===----------------------------------------------------------------------===//

MemoryInterface::MemoryInterface() = default;
MemoryInterface::MemoryInterface(const MemoryPortsImpl &ports) : impl(ports) {}
MemoryInterface::MemoryInterface(const MemoryPortsImpl &ports) : impl(ports) {
if (ports.writeEn.has_value() && ports.readOrContentEn.has_value()) {
assert(ports.isContentEn.value());
}
}
MemoryInterface::MemoryInterface(calyx::MemoryOp memOp) : impl(memOp) {}
MemoryInterface::MemoryInterface(calyx::SeqMemoryOp memOp) : impl(memOp) {}

Expand All @@ -177,10 +181,10 @@ Value MemoryInterface::readEn() {
return readEn.value();
}

Value MemoryInterface::readDone() {
auto readDone = readDoneOpt();
assert(readDone.has_value() && "Memory does not have readDone");
return readDone.value();
Value MemoryInterface::contentEn() {
auto contentEn = contentEnOpt();
assert(contentEn.has_value() && "Memory does not have readEn");
return contentEn.value();
}

Value MemoryInterface::writeData() {
Expand All @@ -195,10 +199,10 @@ Value MemoryInterface::writeEn() {
return writeEn.value();
}

Value MemoryInterface::writeDone() {
auto writeDone = writeDoneOpt();
assert(writeDone.has_value() && "Memory doe snot have writeDone");
return writeDone.value();
Value MemoryInterface::done() {
auto done = doneOpt();
assert(done.has_value() && "Memory does not have done");
return done.value();
}

std::optional<Value> MemoryInterface::readDataOpt() {
Expand All @@ -218,21 +222,33 @@ std::optional<Value> MemoryInterface::readEnOpt() {
}

if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
return memOp->readEn();
return std::nullopt;
}

if (std::get<MemoryPortsImpl>(impl).readOrContentEn.has_value()) {
assert(std::get<MemoryPortsImpl>(impl).isContentEn.has_value());
assert(!std::get<MemoryPortsImpl>(impl).isContentEn.value());
}
return std::get<MemoryPortsImpl>(impl).readEn;
return std::get<MemoryPortsImpl>(impl).readOrContentEn;
}

std::optional<Value> MemoryInterface::readDoneOpt() {
std::optional<Value> MemoryInterface::contentEnOpt() {
if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
return std::nullopt;
}

if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
return memOp->readDone();
return memOp->contentEn();
}
return std::get<MemoryPortsImpl>(impl).readDone;

if (std::get<MemoryPortsImpl>(impl).readOrContentEn.has_value()) {
assert(std::get<MemoryPortsImpl>(impl).writeEn.has_value());
assert(std::get<MemoryPortsImpl>(impl).isContentEn.has_value());
assert(std::get<MemoryPortsImpl>(impl).isContentEn.value());
}
return std::get<MemoryPortsImpl>(impl).readOrContentEn;
}

std::optional<Value> MemoryInterface::writeDataOpt() {
if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
return memOp->writeData();
Expand All @@ -255,15 +271,15 @@ std::optional<Value> MemoryInterface::writeEnOpt() {
return std::get<MemoryPortsImpl>(impl).writeEn;
}

std::optional<Value> MemoryInterface::writeDoneOpt() {
std::optional<Value> MemoryInterface::doneOpt() {
if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
return memOp->done();
}

if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
return memOp->writeDone();
return memOp->done();
}
return std::get<MemoryPortsImpl>(impl).writeDone;
return std::get<MemoryPortsImpl>(impl).done;
}

ValueRange MemoryInterface::addrPorts() {
Expand Down
Loading

0 comments on commit 2d58838

Please sign in to comment.