Skip to content

Commit 6cabe3c

Browse files
authored
[ESI] FIFO with ESI channels (#8004)
Adds an op which provides a FIFO with ESI channels (FIFO signaling semantics only for now) on both ends.
1 parent 44ea9a8 commit 6cabe3c

File tree

8 files changed

+286
-4
lines changed

8 files changed

+286
-4
lines changed

include/circt/Dialect/ESI/ESIChannels.td

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,27 @@ def PipelineStageOp : ESI_Physical_Op<"stage", [
476476
// Misc operations
477477
//===----------------------------------------------------------------------===//
478478

479+
def FIFOOp : ESI_Physical_Op<"fifo", []> {
480+
let summary = "A FIFO with ESI channel connections";
481+
let description = [{
482+
A FIFO is a first-in-first-out buffer. This operation is a simple FIFO
483+
which can be used to connect two ESI channels. The ESI channels MUST have
484+
FIFO signaling semantics.
485+
}];
486+
487+
let arguments = (ins
488+
ClockType:$clk, I1:$rst, ChannelType:$input,
489+
ConfinedAttr<I64Attr, [IntMinValue<1>]>:$depth
490+
);
491+
let results = (outs ChannelType:$output);
492+
493+
let assemblyFormat = [{
494+
`in` $input `clk` $clk `rst` $rst `depth` $depth attr-dict
495+
`:` type($input) `->` type($output)
496+
}];
497+
let hasVerifier = 1;
498+
}
499+
479500
def CosimToHostEndpointOp : ESI_Physical_Op<"cosim.to_host", []> {
480501
let summary = "Co-simulation endpoint sending data to the host.";
481502
let description = [{
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// REQUIRES: iverilog,cocotb
2+
3+
// Test the original HandshakeToHW flow.
4+
5+
// RUN: circt-opt %s --lower-esi-to-physical --lower-esi-ports --lower-esi-to-hw --lower-seq-fifo --lower-seq-hlmem --lower-seq-to-sv --lower-verif-to-sv --sv-trace-iverilog --prettify-verilog --export-verilog -o %t.hw.mlir > %t.sv
6+
// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=fifo1 --pythonModule=esi_widgets --pythonFolder="%S" %t.sv %esi_prims
7+
8+
module attributes {circt.loweringOptions = "disallowLocalVariables"} {
9+
hw.module @fifo1(in %clk: !seq.clock, in %rst: i1, in %in: !esi.channel<i32, FIFO>, out out: !esi.channel<i32, FIFO(2)>) {
10+
%fifo = esi.fifo in %in clk %clk rst %rst depth 12 : !esi.channel<i32, FIFO> -> !esi.channel<i32, FIFO(2)>
11+
hw.output %fifo : !esi.channel<i32, FIFO(2)>
12+
}
13+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
from typing import List, Optional
2+
import cocotb
3+
from cocotb.clock import Clock
4+
from cocotb.triggers import RisingEdge
5+
6+
7+
async def init(dut, timeout: Optional[int] = None):
8+
# Create a 10ns period (100MHz) clock on port clock
9+
clk = Clock(dut.clk, 10, units="ns")
10+
cocotb.start_soon(clk.start()) # Start the clock
11+
12+
# Reset
13+
dut.rst.value = 1
14+
for i in range(10):
15+
await RisingEdge(dut.clk)
16+
dut.rst.value = 0
17+
await RisingEdge(dut.clk)
18+
19+
if timeout is None:
20+
return
21+
22+
async def timeout_fn():
23+
for i in range(timeout):
24+
await RisingEdge(dut.clk)
25+
assert False, "Timeout"
26+
27+
cocotb.start_soon(timeout_fn())
28+
29+
30+
class ESIInputPort:
31+
32+
async def write(self, data):
33+
raise RuntimeError("Must be implemented by subclass")
34+
35+
36+
class ESIFifoInputPort(ESIInputPort):
37+
38+
def __init__(self, dut, name):
39+
self.dut = dut
40+
self.name = name
41+
self.data = getattr(dut, name)
42+
self.rden = getattr(dut, f"{name}_rden")
43+
self.empty = getattr(dut, f"{name}_empty")
44+
# Configure initial state
45+
self.empty.value = 1
46+
47+
async def write(self, data: int):
48+
self.data.value = data
49+
self.empty.value = 0
50+
await RisingEdge(self.dut.clk)
51+
while self.rden.value == 0:
52+
await RisingEdge(self.dut.clk)
53+
self.empty.value = 1
54+
55+
56+
class ESIOutputPort:
57+
58+
async def read(self) -> Optional[int]:
59+
raise RuntimeError("Must be implemented by subclass")
60+
61+
async def cmd_read(self):
62+
raise RuntimeError("Must be implemented by subclass")
63+
64+
65+
class ESIFifoOutputPort(ESIOutputPort):
66+
67+
def __init__(self, dut, name: str, latency: int = 0):
68+
self.dut = dut
69+
self.name = name
70+
self.data = getattr(dut, name)
71+
self.rden = getattr(dut, f"{name}_rden")
72+
self.empty = getattr(dut, f"{name}_empty")
73+
self.latency = latency
74+
# Configure initial state
75+
self.rden.value = 0
76+
self.running = 0
77+
self.q: List[int] = []
78+
79+
async def init_read(self):
80+
81+
async def read_after_latency():
82+
for i in range(self.latency):
83+
await RisingEdge(self.dut.clk)
84+
self.q.append(self.data.value)
85+
86+
self.running = 1
87+
self.empty.value = 0
88+
while await RisingEdge(self.dut.clk):
89+
if self.rden.value == 1:
90+
cocotb.start_soon(read_after_latency())
91+
92+
async def read(self) -> Optional[int]:
93+
if len(self.q) == 0:
94+
await RisingEdge(self.dut.clk)
95+
return self.q.pop(0)
96+
97+
async def cmd_read(self):
98+
if self.running == 0:
99+
cocotb.start_soon(self.init_read())
100+
101+
while self.empty.value == 1:
102+
await RisingEdge(self.dut.clk)
103+
self.rden.value = 1
104+
await RisingEdge(self.dut.clk)
105+
self.rden.value = 0
106+
107+
108+
@cocotb.test()
109+
async def fillAndDrain(dut):
110+
in_port = ESIFifoInputPort(dut, "in")
111+
out_port = ESIFifoOutputPort(dut, "out", 2)
112+
await init(dut, timeout=10000)
113+
114+
for i in range(10):
115+
for i in range(12):
116+
await in_port.write(i)
117+
for i in range(12):
118+
await out_port.cmd_read()
119+
for i in range(12):
120+
data = await out_port.read()
121+
# print(f"{i:2}: 0x{int(data):016x}")
122+
assert data == i
123+
124+
for i in range(4):
125+
await RisingEdge(dut.clk)
126+
127+
128+
@cocotb.test()
129+
async def backToBack(dut):
130+
in_port = ESIFifoInputPort(dut, "in")
131+
out_port = ESIFifoOutputPort(dut, "out", 2)
132+
await init(dut)
133+
134+
NUM_ITERS = 500
135+
136+
async def write():
137+
for i in range(NUM_ITERS):
138+
await in_port.write(i)
139+
140+
cocotb.start_soon(write())
141+
142+
for i in range(NUM_ITERS):
143+
await out_port.cmd_read()
144+
145+
for i in range(NUM_ITERS):
146+
data = await out_port.read()
147+
# print(f"{i:2}: 0x{int(data):016x}")
148+
assert data == i
149+
150+
for i in range(4):
151+
await RisingEdge(dut.clk)

lib/Dialect/ESI/ESIOps.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,22 @@ LogicalResult ChannelBufferOp::verify() {
7272
return success();
7373
}
7474

75+
//===----------------------------------------------------------------------===//
76+
// FIFO functions.
77+
//===----------------------------------------------------------------------===//
78+
79+
LogicalResult FIFOOp::verify() {
80+
ChannelType inputType = getInput().getType();
81+
if (inputType.getSignaling() != ChannelSignaling::FIFO)
82+
return emitOpError("only supports FIFO signaling on input");
83+
ChannelType outputType = getOutput().getType();
84+
if (outputType.getSignaling() != ChannelSignaling::FIFO)
85+
return emitOpError("only supports FIFO signaling on output");
86+
if (outputType.getInner() != inputType.getInner())
87+
return emitOpError("input and output types must match");
88+
return success();
89+
}
90+
7591
//===----------------------------------------------------------------------===//
7692
// PipelineStageOp functions.
7793
//===----------------------------------------------------------------------===//

lib/Dialect/ESI/Passes/ESILowerPhysical.cpp

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212

1313
#include "../PassDetails.h"
1414

15+
#include "circt/Dialect/Comb/CombOps.h"
1516
#include "circt/Dialect/ESI/ESIOps.h"
1617
#include "circt/Dialect/HW/HWOps.h"
18+
#include "circt/Dialect/Seq/SeqOps.h"
1719
#include "circt/Support/BackedgeBuilder.h"
1820
#include "circt/Support/LLVM.h"
1921

@@ -78,6 +80,64 @@ LogicalResult ChannelBufferLowering::matchAndRewrite(
7880
return success();
7981
}
8082

83+
namespace {
84+
/// Lower `ChannelBufferOp`s, breaking out the various options. For now, just
85+
/// replace with the specified number of pipeline stages (since that's the only
86+
/// option).
87+
struct FIFOLowering : public OpConversionPattern<FIFOOp> {
88+
public:
89+
using OpConversionPattern::OpConversionPattern;
90+
91+
LogicalResult
92+
matchAndRewrite(FIFOOp, OpAdaptor adaptor,
93+
ConversionPatternRewriter &rewriter) const final;
94+
};
95+
} // anonymous namespace
96+
97+
LogicalResult
98+
FIFOLowering::matchAndRewrite(FIFOOp op, OpAdaptor adaptor,
99+
ConversionPatternRewriter &rewriter) const {
100+
auto loc = op.getLoc();
101+
auto outputType = op.getType();
102+
BackedgeBuilder bb(rewriter, loc);
103+
auto i1 = rewriter.getI1Type();
104+
auto c1 = rewriter.create<hw::ConstantOp>(loc, rewriter.getI1Type(),
105+
rewriter.getBoolAttr(true));
106+
if (op.getInput().getType().getDataDelay() != 0)
107+
return op.emitOpError(
108+
"currently only supports input channels with zero data delay");
109+
110+
Backedge inputEn = bb.get(i1);
111+
auto unwrapPull = rewriter.create<UnwrapFIFOOp>(loc, op.getInput(), inputEn);
112+
113+
Backedge outputRdEn = bb.get(i1);
114+
auto seqFifo = rewriter.create<seq::FIFOOp>(
115+
loc, outputType.getInner(), i1, i1, Type(), Type(), unwrapPull.getData(),
116+
outputRdEn, inputEn, op.getClk(), op.getRst(), op.getDepthAttr(),
117+
rewriter.getI64IntegerAttr(outputType.getDataDelay()), IntegerAttr(),
118+
IntegerAttr());
119+
auto inputNotEmpty =
120+
rewriter.create<comb::XorOp>(loc, unwrapPull.getEmpty(), c1);
121+
inputNotEmpty->setAttr("sv.namehint",
122+
rewriter.getStringAttr("inputNotEmpty"));
123+
auto seqFifoNotFull =
124+
rewriter.create<comb::XorOp>(loc, seqFifo.getFull(), c1);
125+
seqFifoNotFull->setAttr("sv.namehint",
126+
rewriter.getStringAttr("seqFifoNotFull"));
127+
inputEn.setValue(
128+
rewriter.create<comb::AndOp>(loc, inputNotEmpty, seqFifoNotFull));
129+
static_cast<Value>(inputEn).getDefiningOp()->setAttr(
130+
"sv.namehint", rewriter.getStringAttr("inputEn"));
131+
132+
auto output =
133+
rewriter.create<WrapFIFOOp>(loc, mlir::TypeRange{outputType, i1},
134+
seqFifo.getOutput(), seqFifo.getEmpty());
135+
outputRdEn.setValue(output.getRden());
136+
137+
rewriter.replaceOp(op, output.getChanOutput());
138+
return success();
139+
}
140+
81141
namespace {
82142
/// Lower pure modules into hw.modules.
83143
struct PureModuleLowering : public OpConversionPattern<ESIPureModuleOp> {
@@ -186,13 +246,12 @@ void ESIToPhysicalPass::runOnOperation() {
186246
// Set up a conversion and give it a set of laws.
187247
ConversionTarget target(getContext());
188248
target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
189-
target.addIllegalOp<ChannelBufferOp>();
190-
target.addIllegalOp<ESIPureModuleOp>();
249+
target.addIllegalOp<ChannelBufferOp, ESIPureModuleOp, FIFOOp>();
191250

192251
// Add all the conversion patterns.
193252
RewritePatternSet patterns(&getContext());
194-
patterns.insert<ChannelBufferLowering>(&getContext());
195-
patterns.insert<PureModuleLowering>(&getContext());
253+
patterns.insert<ChannelBufferLowering, PureModuleLowering, FIFOLowering>(
254+
&getContext());
196255

197256
// Run the conversion.
198257
if (failed(

test/Dialect/ESI/errors.mlir

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ hw.module @test(in %m : !sv.modport<@IData::@Noexist>) {
4646

4747
// -----
4848

49+
hw.module @testFifoTypes(in %clk: !seq.clock, in %rst: i1, in %a: !esi.channel<i32, FIFO>, in %b: !esi.channel<i16, FIFO(2)>) {
50+
// expected-error @+1 {{input and output types must match}}
51+
%fifo = esi.fifo in %a clk %clk rst %rst depth 12 : !esi.channel<i32, FIFO> -> !esi.channel<i16, FIFO(2)>
52+
hw.output %fifo : !esi.channel<i16, FIFO(2)>
53+
}
54+
55+
// -----
56+
4957
esi.service.decl @HostComms {
5058
esi.service.port @Send : !esi.bundle<[!esi.channel<i16> from "send"]>
5159
}

test/Dialect/ESI/lowering.mlir

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,17 @@ hw.module @i3LoopbackOddNames(in %in: !esi.channel<i3>, out out: !esi.channel<i3
197197
esi.portReadySuffix="_letErRip", esi.portOutSuffix="_out"} {
198198
hw.output %in : !esi.channel<i3>
199199
}
200+
201+
// CHECK-LABEL: hw.module @fifo1(in %clk : !seq.clock, in %rst : i1, in %in : !esi.channel<i32, FIFO>, out out : !esi.channel<i32, FIFO(2)>) {
202+
// CHECK-NEXT: %true = hw.constant true
203+
// CHECK-NEXT: %data, %empty = esi.unwrap.fifo %in, [[R2:%.+]] : !esi.channel<i32, FIFO>
204+
// CHECK-NEXT: %out, %full, %empty_0 = seq.fifo depth 12 rd_latency 2 in %data rdEn %rden wrEn [[R2]] clk %clk rst %rst : i32
205+
// CHECK-NEXT: [[R0:%.+]] = comb.xor %empty, %true {sv.namehint = "inputNotEmpty"} : i1
206+
// CHECK-NEXT: [[R1:%.+]] = comb.xor %full, %true {sv.namehint = "seqFifoNotFull"} : i1
207+
// CHECK-NEXT: [[R2]] = comb.and [[R0]], [[R1]] {sv.namehint = "inputEn"} : i1
208+
// CHECK-NEXT: %chanOutput, %rden = esi.wrap.fifo %out, %empty_0 : !esi.channel<i32, FIFO(2)>
209+
// CHECK-NEXT: hw.output %chanOutput : !esi.channel<i32, FIFO(2)>
210+
hw.module @fifo1(in %clk: !seq.clock, in %rst: i1, in %in: !esi.channel<i32, FIFO>, out out: !esi.channel<i32, FIFO(2)>) {
211+
%fifo = esi.fifo in %in clk %clk rst %rst depth 12 : !esi.channel<i32, FIFO> -> !esi.channel<i32, FIFO(2)>
212+
hw.output %fifo : !esi.channel<i32, FIFO(2)>
213+
}

0 commit comments

Comments
 (0)