Designing the input FIFO for the router.

Five-port router in the 2D mesh

I wanted to tackle the FIFO first for two reasons. First, I was confident I could write one. Second, it is the first block the data hits when it enters the router, so it made sense to start there.

I was thinking I didn’t want this to be a simple FIFO. Everything from virtual channels to contention eventually rides on these buffers, and these little simple shits are expensive. SRAM is expensive, so I wanted to make sure I was getting as much use out of it as I could.

A simple FIFO is pretty straightforward. You have a memory, a read pointer, a write pointer, and some logic to tell you when the FIFO is full or empty. That would have worked, but I wanted to see if there was anything useful I could squeeze out of it before calling it done.

I already knew about FWFT FIFOs, and I liked the idea behind them. If the data is already available, why make the consumer wait another cycle just because the FIFO wants to be formal about the procedure?

I didn’t need a full FWFT implementation for the router, but I could take the idea and apply it where it actually helped. So I added simultaneous read/write handling and an empty-FIFO bypass.

If the FIFO is empty and a read and write happen in the same cycle, the incoming data can go straight to the output instead of taking a trip through the memory just to immediately come back out.

That felt like a useful little optimisation rather than adding complexity for the sake of it.

Then there was the other side of the FIFO, the part that deals with the router’s flow control.

The router uses a credit-based system, so a write should only happen when there is space available in the buffer. I kept the credit logic outside the FIFO itself, but added a protocol error at the FIFO level when an illegal operation is attempted. A write while full and a read while empty both raise err. It’s mostly there as a debugging signal, but I like having the FIFO complain when something that should never happen actually happens.

There is another case I wanted to make explicit too. If the FIFO is full and both read and write are asserted, the read still happens but the write is rejected. The FIFO was full when the write was presented, so I don’t want the behavior to depend on treating the simultaneous read as some kind of retroactive permission.

I liked the design I ended up with. I think I hit a decent sweet spot where I didn’t over-engineer the FIFO, but I also didn’t just grab an off-the-shelf FIFO and call it a day.

Then, while writing this post, I came across something.

Wait. How do I handle credits for the bypass condition?

The flit never actually enters the FIFO in that case. And there is the full-FIFO case too, where the read happens but the write is rejected. The FIFO’s occupancy and the interface signals aren’t quite telling the same story anymore, so the credit controller needs to account for what actually happened.

I think I can handle both cases using the full/empty conditions, the read and write pointers, and some combinational logic to track these states.

Hmm.

The blog is useful after all.

Now there is another voice calling me, and this one is asking for numbers.

The RTL tells me what the FIFO does. Simulation can tell me what it actually does under traffic.

I can put some instrumentation behind an `ifdef SIM and start collecting things like buffer occupancy, how often the bypass is actually hit, and how long a flit spends waiting in the buffer.

Now that is gold.

I want to see those numbers.