That Time I Decided to Use a Dimension-Sliced Crossbar but Crossed the Idea Because Synthesis Said No
I know what I’m doing with that title. If you got the reference, welcome cultured people.
The idea
I was reading textbooks for the NoC when I came across an interesting combination of words: dimension-sliced xbar (xbar is slang for crossbar).
The more I thought about it, the more I realised that with XY routing, some connections in the crossbar are never touched. A packet finishes its X movement before it starts moving in Y, so some input-to-output combinations can never happen.
A 5×5 crossbar has 25 possible connections, but only 16 of them are legal under XY routing. The other 9 are things like U-turns and reverse-dimension turns that a correctly routed packet should never request.
So naturally I thought: why am I building hardware for connections that can never happen?
That was the idea behind the dimension-sliced xbar. Instead of one full 5×5 crossbar, split the switch fabric into two smaller ones, one for each routing dimension, with a bridge between them. Roughly:
- X crossbar: East, West and Local-side inputs, with outputs for continuing in X, moving toward the Y side, and local ejection.
- Y crossbar: North, South, Local-side input and the bridge input, with outputs for North, South and local ejection.
- The Local input can participate in both dimensions, so the final local ejection paths have to be handled carefully.
The X side handles X-phase traffic, the Y side handles Y-phase traffic, and the bridge lets a packet move from one to the other.
It looked reasonable. The crossbar takes up a substantial amount of space in a NoC, so removing hardware I already knew would never be used felt like a pretty good optimisation.
Then I decided to actually measure how much I was gaining. Well, let’s just say it was not good.
So I decided to measure it
This was also a good chance to understand Yosys and ABC properly, instead of treating synthesis as a command that gives me a number while I pretend I know what happened.
I wrote the logic for both the full 5×5 xbar and the dimension-sliced version (xbar is slang for crossbar), and ran the experiments.
| Tool | Version |
|---|---|
| Yosys | 0.33 |
| ABC | 1.01 |
| Vivado | 2025.2 |
| OS | Kubuntu 24.04.5 LTS |
I started with the generic LUT mapping flow in Yosys, using 4-input LUTs:
| Design | LUT4 |
|---|---|
| Full 5×5 | 1125 |
| Dimension-sliced | 711 |
I looked at that and thought:
I am intelligent.
The dimension-sliced version was significantly smaller. This was going exactly how I imagined it would.
So instead of stopping there like a responsible engineer, I took both designs to Vivado.
Then Vivado happened
For the FPGA experiment I targeted xc7a200t-fbg676-2 with Vivado 2025.2. The result:
| Design | LUT5 | LUT6 | Total LUTs |
|---|---|---|---|
| Full 5×5 | 160 | 160 | 320 |
| Dimension-sliced | 160 | 160 | 320 |
Wait. 320? Both?
I had just seen 1125 versus 711 in Yosys, and now Vivado was telling me the two designs were identical in size. I had no idea what was happening. This was the point where I stopped pretending I understood synthesis.
So I went back to Yosys
This time I didn’t only run the 4-LUT experiment. I swept the LUT input width:
| LUT width | Full 5×5 | Dimension-sliced |
|---|---|---|
| 4 | 1125 | 711 |
| 5 | 645 | 480 |
| 6 | 490 | 352 |
| 7 | 320 | 256 |
| 8 | 320 | 256 |
| 9 | 160 | 256 |
| 10 | 160 | 256 |
| 11 | 160 | 256 |
| 12 | 160 | 256 |
And I sat there for a while.
The LUT input count itself changes the result. It isn’t just design → number of LUTs. It’s design + target LUT architecture → number of LUTs. The dimension-sliced design wins at the smaller LUT widths in this generic experiment, but once the LUTs get wide enough the full crossbar becomes smaller.
That explained why my first LUT4 number wasn’t some universal truth about the two architectures. It was a result for one particular LUT model, and it was far more useful than the number that made me feel clever.
And then I looked at timing
I ran both designs through Vivado timing analysis with a virtual 10 ns clock. Placement was not constrained and neither design met the target, so these are not final NoC timing numbers. I mainly wanted the relative comparison.
| Design | WNS | TNS | Failing endpoints |
|---|---|---|---|
| Full 5×5 | -1.153 ns | -99.740 ns | 158 / 160 |
| Dimension-sliced | -1.426 ns | -132.273 ns | 151 / 160 |
The full crossbar had the better WNS and TNS.
My best guess is the bridge. I added it to make the architecture smarter, but a path from an X-direction input to a Y-direction output now has to go through both sides of the split, which adds a level of logic to the common turning case. So I removed some impossible connections and then added a physical bridge that made the turns that actually happen slower.
That hurt.
At this point I was ready to blame FPGAs. Vendor locked. Corporate. Weird architecture. Yosys, on the other hand, was sitting there being open source and letting me poke around inside the flow, and I love open source.
Maybe the FPGA world just wasn’t ready for my intelligence yet. Maybe I was destined for the ASIC world.
ASIC world, here I come.
Then the ASIC experiment put the final nail in the coffin
I took both designs and mapped them to the Nangate45 standard-cell library. Now I wasn’t thinking in LUTs anymore. The design gets mapped into actual standard cells, and I was suddenly looking at things like AOI21_X1, AOI222_X1, AOI22_X1 and OAI33_X1.
What are these?
AOI and OAI cells combine several Boolean operations into a single cell. Instead of building everything from tiny primitive gates, the mapper can recognise a Boolean structure and map it onto a cell that already implements it. I had spent a lot of time thinking about muxes and LUTs, and this was a completely different way of looking at the same RTL.
Then I got the area report.
| Design | Area |
|---|---|
| Full 5×5 | 759.430 µm² |
| Dimension-sliced, X crossbar | 389.424 µm² |
| Dimension-sliced, Y crossbar | 397.404 µm² |
| Dimension-sliced, total | 820.876 µm² |
The full crossbar was 61.446 µm² smaller, about 7.49%, than the version I had specifically designed to remove unnecessary hardware.
The full crossbar mapped to:
| Cell | Count |
|---|---|
| AND3_X1 | 5 |
| AOI21_X1 | 160 |
| AOI222_X1 | 160 |
| AOI22_X1 | 160 |
| INV_X1 | 15 |
| NOR3_X1 | 20 |
The full crossbar still had all those theoretically illegal connections. I had deliberately removed them in the other design. And the full crossbar still came out smaller.
I was dumbstruck.
The worst part wasn’t that the full crossbar won. It was that I didn’t understand why. I could explain the FPGA result, because I could look at the LUT architecture and see that the target primitive changed the answer. Here I had a standard-cell library, no LUT-width ceiling to blame, and I had still made my supposedly optimised design bigger.
That was honestly more painful than a bad number. It was also the part that made this experiment worth doing.
The thing I realised about ASIC and FPGA synthesis
This slowly made me realise that FPGA and ASIC synthesis really are two different ball games.
In college, we learn an ASIC-esque design flow: write RTL, synthesise, talk about timing and area, and then very often the story ends with run it on an FPGA and call it a day. I don’t mean that as an attack on what we learn. It’s a useful flow and it gives you something physical to work with.
But there is a whole other world behind it. The FPGA flow asks you to map your logic onto a specific set of programmable resources. The ASIC flow hands the tool a standard-cell library and lets it choose from a completely different collection of building blocks, with different drive strengths, cell areas, and timing characterised for that library.
That was probably the most valuable part of the whole experiment.
So was the dimension-sliced crossbar actually bad?
| Generic Yosys | Xilinx Vivado | Nangate45 | |
|---|---|---|---|
| Full 5×5 | 1125 LUT4 | 320 LUTs | 759.430 µm² |
| Dimension-sliced | 711 LUT4 | 320 LUTs | 820.876 µm² |
For the Xilinx target I tested, the dimension-sliced design gave no area advantage and made timing worse. For Nangate45, the full crossbar was about 7.5% smaller. At first glance the conclusion is obvious: dimension slicing was a bad idea.
Except I don’t think that’s quite right.
While writing this up I went back through the results again, and the conclusion I was originally writing towards was slightly wrong. At 4 LUT inputs, the dimension-sliced design was 711 LUTs against 1125 for the full crossbar. That’s not a small gap. If I were targeting a technology where 4-input LUTs are the real primitive, the idea suddenly becomes worth discussing.
That doesn’t mean every 4-LUT FPGA will love my architecture. The Yosys result is still a generic mapping experiment. But it does mean the idea wasn’t stupid. I was asking a technology-dependent question and expecting a universal answer, and synthesis doesn’t work like that.
What I actually learned
I started this because I thought I had found an obvious optimisation. Nine connections that XY routing never uses. Remove them, smaller hardware, done.
Instead I learned that:
- a generic LUT experiment is not the same as mapping onto a real FPGA,
- LUT input width can completely change the result,
- FPGA and ASIC synthesis optimise for very different target architectures,
- an architectural reduction does not automatically mean a physical area reduction,
- and the synthesis result can be more interesting than the architecture idea that started it.
What I haven’t tested yet
Looking back at it, I actually tested two ideas at once and only noticed afterwards.
The first was pruning: remove the 9 connections XY routing never uses. The second was the structure I chose to implement it, which was two smaller crossbars with a bridge between them. Every result I got is for the combination. The bridge is my best explanation for the timing loss, and the full crossbar collapsing to exactly 160 LUTs at wide LUT widths suggests it maps very cleanly, while the two-stage version never gets there.
So I can’t say pruning is a bad idea. I can only say pruning with a bridge lost on the targets I tried.
The experiment I haven’t run yet is the obvious one: keep a single-level crossbar and just delete the 9 illegal inputs from each output mux. No slicing, no bridge. If that one wins, the idea was fine and my implementation of it was the problem. If it loses too, then I’ll finally have a result that points at the idea itself.
And underneath that is the bigger thing this left me with. I want to put FPGA and ASIC synthesis side by side and understand why the same RTL changes shape when the target changes, not just read off a LUT count.
I’ll write it up when I get to it.
Pick your poison.