{"id":912036,"date":"2026-07-27T00:17:34","date_gmt":"2026-07-27T00:17:34","guid":{"rendered":"https:\/\/cri-one.com\/blog\/?p=912036"},"modified":"2026-07-27T08:23:25","modified_gmt":"2026-07-27T08:23:25","slug":"six-layers-1536-balls-why-the-escape-router-ran-out-of-room-at-ring-3","status":"publish","type":"post","link":"https:\/\/cri-one.com\/blog\/2026\/07\/27\/six-layers-1536-balls-why-the-escape-router-ran-out-of-room-at-ring-3\/","title":{"rendered":"Six Layers, 1,536 Balls: Why the Escape Router Ran Out of Room at Ring 3"},"content":{"rendered":"<p style=\"font-size:18px;font-weight:bold;color:#1a1a2e;background:#f0f0ff;padding:16px 20px;border-left:4px solid #302b63;border-radius:4px;margin:0 0 1.5em\">A 1,536-ball interposer, six signal layers, and a router that ran out of room after four rings. The fix was not more layers. It was noticing that five sixths of every layer was empty.<\/p>\n<p>The AutoPhi V19 interposer has one job: take a die with 1,536 pads on a 0.800&nbsp;mm grid and fan it out to a BGA with 1,536 pads on a 2.000&nbsp;mm grid. Same 48&nbsp;&times;&nbsp;32 array on both sides, 2.5&times; bigger on the way out. Nothing clever. Just 1,536 connections that have to physically exist.<\/p>\n<p>It took three rewrites of the router to get there, and every rewrite started with a wrong assumption that DRC eventually corrected. This is the whole sequence, including the parts that did not work.<\/p>\n<h2>The Board<\/h2>\n<table style=\"width:100%;border-collapse:collapse;margin:1em 0\">\n<tbody>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Die side (U1)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">1,536 pads, 48 &times; 32, 0.800 mm pitch, 0.35 mm round pads, 37.60 &times; 24.80 mm<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Board side (BGA1)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">1,536 pads, 48 &times; 32, 2.000 mm pitch, 0.9 mm pads, 94.00 &times; 62.00 mm<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Stackup<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">12 copper: TOP, GND1, SIG1, SIG2, PWR1, SIG3, SIG4, PWR2, SIG5, SIG6, GND2, BOTTOM<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Signal layers<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">six &mdash; SIG1 through SIG6<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Nets<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">960 signal balls, 576 on rails (GND, APH_VDD_CORE, 1V0) which drop straight to a plane<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Geometry<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0.075 mm track, 0.20 mm microvia pad on 0.10 mm drill, 0.40 mm blind via pad on 0.20 mm drill<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Index the array from the outside in and you get sixteen concentric rings. Ring&nbsp;0 is the outer edge, ring&nbsp;15 is the four innermost columns. The outer rings are easy &mdash; a ball on the edge has open board on three sides. The inner rings are the whole problem, because a ball in the middle has to cross everything outside it to get anywhere.<\/p>\n<h2>Attempt One: One Ring, One Layer<\/h2>\n<p>The first router gave every ball a private drop lane and every ring its own layer. Ball leaves its die pad, jogs off the pad row, runs horizontally out past the edge of the die pin field, drops down a lane nobody else is using, runs back in to its board pad. Clean, easy to reason about, and provably free of crossings because no two balls ever share a segment.<\/p>\n<p>It got through four rings and stopped:<\/p>\n<table style=\"width:100%;border-collapse:collapse;margin:1em 0\">\n<tbody>\n<tr style=\"background:#f0f0ff\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Ring<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Signal balls<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Layers consumed<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">90<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">SIG1<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">1<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">86<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">SIG2<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">2<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">108<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">SIG3 + SIG4<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">3<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">100<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">SIG5 + SIG6<\/td>\n<\/tr>\n<tr style=\"background:#ffebee\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">4&ndash;15<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">576<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">none left<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>384 balls routed, all six signal layers gone, 576 balls with nowhere to go. Rings 2 and 3 needed two layers each because their left\/right traffic and their up\/down traffic both want the same corner quadrants, so they had to be split.<\/p>\n<p>An autorouter was the obvious next move. Freerouting ran for hours on the same board and ended its log like this:<\/p>\n<pre style=\"background:#1a1a2e;color:#e0e0f0;padding:14px 18px;border-radius:4px;overflow-x:auto;font-size:13px\">java.lang.NullPointerException\r\n2026-07-26 07:26:09.288 ERROR  Error during routing passes\r\njava.lang.NullPointerException\r\n2026-07-26 07:35:33.143 INFO   Saving 'autophi-1536-interposer.ses'...<\/pre>\n<p>The file it saved was zero bytes.<\/p>\n<h2>The Thing I Had Wrong<\/h2>\n<p>The private-lane design was not just conservative. It was answering a question the board does not ask.<\/p>\n<p>The die pads live on TOP. They are 0.35&nbsp;mm circles on a 0.800&nbsp;mm grid, and they are dense &mdash; that is what makes the die field look impassable. But a signal never routes on TOP. It drops through a microvia to a signal layer, and on that signal layer the die pads <em>do not exist<\/em>. The only copper there is the 0.20&nbsp;mm microvia landing belonging to whichever balls were assigned to that particular layer.<\/p>\n<p>Six signal layers, 960 signal balls. Spread evenly, any one signal layer carries about 160 microvia landings across an area of roughly 6,800&nbsp;mm&sup2;. The rest is empty. The first router was routing around an obstacle that was on a different layer.<\/p>\n<h2>Attempt Two: Measure First<\/h2>\n<p>So: model the real obstacles and let a router use the whole plane. A uniform 0.05&nbsp;mm grid over the board gives 2,000&nbsp;&times;&nbsp;1,360 cells per layer. Mark what a trace centre cannot occupy, then run A* from die pad to board pad, with the direction of travel carried in the search state and a penalty on turns so the paths come out as long rectilinear runs instead of maze scribble.<\/p>\n<p>The first measurement said SIG1 was 64.1% blocked. That did not match the argument above at all, which was the useful part &mdash; a number that disagrees with your reasoning is worth more than one that confirms it.<\/p>\n<p>The cause was in my own occupancy code. I was blocking each existing trace by filling its <em>bounding box<\/em>. That is exact for an axis-aligned segment and catastrophically wrong for a diagonal one. And ring&nbsp;0 was laid by an even earlier router as single straight diagonals:<\/p>\n<table style=\"width:100%;border-collapse:collapse;margin:1em 0\">\n<tbody>\n<tr style=\"background:#f0f0ff\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Layer<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Tracks<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Total length<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Average<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">SIG1<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">90<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">2,369.9 mm<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">26.3 mm<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">SIG3<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">280<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">1,993.6 mm<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">7.1 mm<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Ninety diagonals averaging 26&nbsp;mm each, every one of them blocking a square roughly 26&nbsp;mm on a side. Rasterising the actual swept area instead &mdash; a vectorised point-to-segment distance over the bounding box &mdash; dropped SIG1 from 64.1% to 13.4%. All six layers landed between 9% and 14%.<\/p>\n<h2>Four Things DRC Had To Teach Me<\/h2>\n<p>The router now produced clean-looking paths that failed DRC. Each round of failures was a real constraint I had not modelled, and each one is the kind of thing that is obvious in hindsight and invisible in advance.<\/p>\n<h3>1. A microvia is not a point on its landing layer<\/h3>\n<p>Setting a via&#8217;s layer pair to TOP&ndash;SIG5 does not put copper on SIG5. It puts a <em>barrel<\/em> through SIG1, SIG2, PWR1, SIG3 and SIG4 on the way down. I had stamped only the landing layer, so inner-ring balls that landed deep punched their barrels straight through the traces of rings 0&ndash;3:<\/p>\n<pre style=\"background:#1a1a2e;color:#e0e0f0;padding:14px 18px;border-radius:4px;overflow-x:auto;font-size:13px\">[shorting_items]: Items shorting two nets (APH_HSIO_PAIR_238_N and APH_HSIO_PAIR_239_P)\r\n  @(2.8000 mm, -0.4000 mm): Track [APH_HSIO_PAIR_238_N] on SIG3\r\n  @(3.6000 mm, -0.4000 mm): Micro via [APH_HSIO_PAIR_239_P] on TOP - SIG5<\/pre>\n<p>This also inverts the cost of a layer choice. A ball landing on SIG1 blocks its die-pad location on one layer. A ball landing on SIG6 blocks it on all six. The die field is the scarce resource, so the router now tries shallow layers first and only goes deep when it has to.<\/p>\n<h3>2. The clearance that applies is not the one in the board rules<\/h3>\n<p>Board setup says <code>min_clearance: 0.075<\/code>. The netclass says <code>clearance: 0.2<\/code>. The netclass wins, and every channel I had sized off 0.075 was too tight by more than a factor of two:<\/p>\n<pre style=\"background:#1a1a2e;color:#e0e0f0;padding:14px 18px;border-radius:4px;overflow-x:auto;font-size:13px\">[clearance]: Clearance violation ( clearance 0.2000 mm; actual 0.0625 mm)<\/pre>\n<p>The board minimum is a floor for what the fab can build. It is not what your nets are actually being held to.<\/p>\n<h3>3. Vias collide on layers the router never draws on<\/h3>\n<p>The die grid is 0.800&nbsp;mm and the board grid is 2.000&nbsp;mm, both centred on the same origin. Those two grids drift in and out of phase, and in places a die pad ends up 0.283&nbsp;mm from a board pad. Their barrels then meet on PWR1 &mdash; a plane layer, where this router never places a single trace:<\/p>\n<pre style=\"background:#1a1a2e;color:#e0e0f0;padding:14px 18px;border-radius:4px;overflow-x:auto;font-size:13px\">[shorting_items]: Items shorting two nets (APH_VDD_CORE and APH_MGMT_GPIO_149)\r\n  @(-5.0000 mm, 11.0000 mm): Blind via [APH_VDD_CORE] on PWR1 - BOTTOM\r\n  @(-5.2000 mm, 10.8000 mm): Micro via [APH_MGMT_GPIO_149] on TOP - SIG4<\/pre>\n<p>Via feasibility is therefore checked across all twelve copper layers, not the six the router cares about.<\/p>\n<h3>4. Moving a via invalidates the antipad that made it legal<\/h3>\n<p>Every barrel that crosses a power plane needs the pour to clear a hole around it. Re-point the via and that hole is in the wrong place. Twenty-one <code>hole_clearance<\/code> violations, all reading <code>actual 0.0000 mm<\/code> against a zone, disappeared the moment the fills were regenerated before saving.<\/p>\n<h2>The Bug That Only a Counter Can Fix<\/h2>\n<p>One more, and it is the most interesting of them.<\/p>\n<p>Every ball needs a via at its die pad. Until a ball is routed, nobody knows which layers its barrel will cross &mdash; so the router reserves the worst case, TOP down to the deepest signal layer, for every ball still waiting. Otherwise an early ball routes straight across a later ball&#8217;s pad and strands it permanently.<\/p>\n<p>But when a ball&#8217;s own turn comes, it has to hand that reservation back, or it blocks itself. With a boolean occupancy grid there is no way to do that: if two owners both marked a cell, clearing it for one clears it for both.<\/p>\n<p>So occupancy is a count, not a flag. A ball decrements its worst-case reservation, asks what is reachable, and increments the barrel it actually committed to. Conservative early, exact by the end.<\/p>\n<p>My first attempt at this was a shortcut &mdash; a set of cells near each ball&#8217;s pads that the search was allowed to treat as passable, on the grounds that a ball must be allowed to sit on its own keepout. It also let the ball sit on everyone else&#8217;s copper. Two traces 0.200&nbsp;mm apart where 0.275&nbsp;mm was required, for exactly that reason. A &#8220;just this once&#8221; exception in a constraint checker is a constraint checker with a hole in it.<\/p>\n<h2>Where It Stands<\/h2>\n<p>All sixteen rings re-routed from scratch: <strong>506 of 960 signal<br \/>\nballs escaped<\/strong>, and DRC came back <em>cleaner than the board had<br \/>\never been<\/em>. Clearance 40 to 0. Shorting 2 to 0. Those forty clearance<br \/>\nviolations were the original router&#8217;s, and re-laying its work removed<br \/>\nthem.<\/p>\n<table style=\"width:100%;border-collapse:collapse;margin:1em 0\">\n<tbody>\n<tr style=\"background:#f0f0ff\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">DRC category<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Before<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">After<\/td>\n<\/tr>\n<tr style=\"background:#e8f5e9\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">clearance<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">40<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">0<\/td>\n<\/tr>\n<tr style=\"background:#e8f5e9\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">shorting_items<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">2<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">0<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">tracks_crossing<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">hole_clearance<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Getting there took two more mistakes worth naming. Clearing the traces<br \/>\ndoes not clear the <em>via layer pairs<\/em>, so 451 unrouted balls kept<br \/>\nmicrovias parked as deep as TOP&ndash;SIG6 while the occupancy model had<br \/>\nonly reserved their guaranteed TOP&ndash;SIG1: five layers of copper the<br \/>\nmodel believed were empty, and 199 shorts. And reserving each waiting<br \/>\nball&#8217;s <em>worst-case<\/em> barrel rather than its <em>guaranteed<\/em> one<br \/>\nblocked all six layers at all 1,536 die pads at once, which on a 0.8&nbsp;mm<br \/>\ngrid leaves single-cell channels and stalls the router outright.<\/p>\n<h2>Then I Stopped Tuning And Derived The Bound<\/h2>\n<p>Three rounds of tuning all landed near 510. When effort stops moving a<br \/>\nnumber, the number is probably not effort-limited.<\/p>\n<p>A ball landing on signal layer <em>k<\/em> has a microvia spanning<br \/>\nTOP&ndash;<em>k<\/em> and a blind via spanning <em>k<\/em>&ndash;BOTTOM.<br \/>\nThose cross <em>k<\/em> and (N&nbsp;&minus;&nbsp;<em>k<\/em>&nbsp;+&nbsp;1)<br \/>\nsignal layers respectively. Add them:<\/p>\n<pre style=\"background:#1a1a2e;color:#e0e0f0;padding:14px 18px;border-radius:4px;overflow-x:auto;font-size:13px\">k + (N - k + 1) = N + 1     for every k<\/pre>\n<p><strong>The layer choice cancels.<\/strong> Every ball costs seven<br \/>\nbarrel-layer crossings on a six-signal-layer board regardless of where it<br \/>\nlands. 960 balls &times; 7 = 6,720, over six layers = <strong>1,120 via<br \/>\nkeepouts per signal layer, invariant.<\/strong> No assignment strategy<br \/>\nchanges it. Load balancing helped &mdash; it took failures from 56 to 37<br \/>\non a 250-ball sample &mdash; but only by balancing <em>trace<\/em><br \/>\ncongestion. It cannot touch the via load, and the via load is what binds.<\/p>\n<p>That prediction was testable, so I tested it. If traces were the<br \/>\nconstraint, halving the clearance should help:<\/p>\n<table style=\"width:100%;border-collapse:collapse;margin:1em 0\">\n<tbody>\n<tr style=\"background:#f0f0ff\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Netclass clearance<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Trace pitch<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Routed \/ 250<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0.200 mm<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0.275 mm<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">207<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0.100 mm<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0.150 mm<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">207<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Identical. I had expected a clear gain and written the conclusion<br \/>\nbefore running it. Trace pitch is not the constraint.<\/p>\n<h2>What The Constraint Actually Is<\/h2>\n<p>The die grid is 0.8&nbsp;mm and the board grid is 2.0&nbsp;mm, both on<br \/>\nthe same origin. 2.5 is not an integer, so the grids beat against each<br \/>\nother, and 240 board pads end up within 0.50&nbsp;mm of a die pad. The<br \/>\nclosest pair is <strong>0.283&nbsp;mm<\/strong> &mdash; at which a<br \/>\n0.20&nbsp;mm microvia pad and a 0.40&nbsp;mm blind via pad physically<br \/>\noverlap. No clearance rule reaches that. I tested 36 translations of the<br \/>\nBGA grid; the design as drawn is already the best of them.<\/p>\n<p>The fix is not a rule and not a router. It is <strong>dog-boning<\/strong><br \/>\n&mdash; putting the blind via beside its pad instead of in it. Sweeping<br \/>\ncandidate sites around each colliding pad:<\/p>\n<pre style=\"background:#1a1a2e;color:#e0e0f0;padding:14px 18px;border-radius:4px;overflow-x:auto;font-size:13px\">dog-bone reach 0.35 mm: 240\/240 colliding pads get a legal via site, 0 still stuck<\/pre>\n<p>0.35&nbsp;mm of offset clears every one of them, and a 0.9&nbsp;mm pad<br \/>\non a 2.0&nbsp;mm grid has roughly 0.55&nbsp;mm of room to give. The ceiling<br \/>\nis removable; it just is not removable by anything the router controls.<\/p>\n<h2>What I Would Tell Myself At The Start<\/h2>\n<p><strong>Measure the obstacle before designing around it.<\/strong> Four<br \/>\nrings of routing were spent avoiding pads that were on a different layer.<\/p>\n<p><strong>A number that contradicts your reasoning is a gift.<\/strong><br \/>\n&#8220;SIG1 is 64% blocked&#8221; was wrong, and chasing why found a bounding-box bug<br \/>\nthrowing away four fifths of the board.<\/p>\n<p><strong>Read the DRC entries, do not just count them.<\/strong> Every<br \/>\ncount moved in the right direction at least once while the design<br \/>\nunderneath was still broken.<\/p>\n<p><strong>Constraint checkers do not get exceptions.<\/strong> Every<br \/>\nspecial case letting one object ignore the rules is a defect waiting for a<br \/>\nplausible reason to fire.<\/p>\n<p><strong>When tuning stops paying, derive the bound.<\/strong> Three<br \/>\nrounds of increasingly clever heuristics all landed within a few balls of<br \/>\neach other, because they were all optimising a quantity that provably does<br \/>\nnot vary.<\/p>\n<h2>Update: Doubling the Layers, Confirming the Theory<\/h2>\n<p>The invariant said trace surface is where the gain lives, not via load. That prediction was testable, so it got tested. The interposer&#8217;s stackup was rebuilt with twelve signal layers instead of six &mdash; from twelve total copper to eighteen &mdash; and the router ran again against the fresh grid.<\/p>\n<table style=\"width:100%;border-collapse:collapse;margin:1em 0\">\n<tbody>\n<tr style=\"background:#f0f0ff\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Metric<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">6 signal layers (v1)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">12 signal layers (v2)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">Total copper layers<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">12<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">18<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">Signal balls per layer, invariant<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">146.2<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">73.1<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">Signal balls per layer, measured<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">92 \/ 87 \/ 86 \/ 83 \/ 78 \/ 80<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\"><strong>73 \/ 73 \/ 73 \/ 73 \/ 73 \/ 73 \/ 73 \/ 73 \/ 73 \/ 73 \/ 73 \/ 72<\/strong><\/td>\n<\/tr>\n<tr style=\"background:#e8f5e9\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Escape routed<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">506 of 960 (52.7%)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">720 of 960 (75.0%)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">DRC routing-quality violations<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong>The measured layer load matched the invariant prediction to a single ball.<\/strong> 877 signal balls that needed routing, twelve layers to spread them across, and A* placed 73 or 72 on each layer with no manual tuning of the load-balancing heuristic. The theorem does not care what the router thinks it is doing.<\/p>\n<p>Escape coverage jumped by 214 balls &mdash; a 42% improvement in what actually reaches the BGA. DRC came back clean the first time the re-route finished, once 274 stray traces from the old routing had been swept off physical layer IDs that got renamed to plane layers during the stackup change. Those are the standard hazards of a stackup rewrite, not routing errors.<\/p>\n<h3>What the invariant does and does not fix<\/h3>\n<p>Layer choice cancels for via congestion (the derivation earlier in this post). Moving from six to twelve signal layers dropped per-layer via keepouts from 1,120 to 1,040 &mdash; a 7% improvement, exactly what the algebra predicted, and irrelevant next to the real constraint. The real gain was that <em>trace surface per layer halved<\/em>. Same 960 signal balls, twelve layers instead of six, means 80 balls of trace traffic per layer instead of 160. Half the trace congestion, same via congestion. The 214 balls of extra coverage came out of that trace headroom.<\/p>\n<p>Cost of the extra layers on a small (94 &times; 62 mm) HDI substrate: around $300 per piece at typical per-square-inch HDI rates. Not a factor at the substrate&#8217;s list price.<\/p>\n<h3>The general lesson<\/h3>\n<p>The point of deriving the bound was to know which knob to turn. The invariant answered &#8220;not layer assignment, not netclass clearance, not routing effort.&#8221; That left &#8220;more signal layers&#8221; as the last remaining knob, and turning it produced exactly the improvement the algebra said it would.<\/p>\n<p>The interposer on the store now ships with the 12-signal-layer stackup and 720\/960 routed. The ESCAPE-THEORY.md document that goes with the source bundle is the same document as last week: the invariant does not change &mdash; only the choice of <em>N<\/em> does.<\/p>\n<h2>Update Two: 24 Layers, 815 of 960<\/h2>\n<p>The N=12 result begged the same question a second time. If doubling signal layers from six to twelve added 214 balls of coverage, what does going from twelve to eighteen do? The invariant already predicted the answer: less. Trace load per layer scales as 1\/N, so each doubling helps by a smaller fraction than the last. Twelve to eighteen is not a doubling &mdash; it is a 1.5&times; &mdash; so the gain should be smaller still.<\/p>\n<p>Ran the experiment: 24-layer stackup, 18 signal layers between four planes, same router, same lessons.<\/p>\n<table style=\"width:100%;border-collapse:collapse;margin:1em 0\">\n<tbody>\n<tr style=\"background:#f0f0ff\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Metric<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">6 signal (v1)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">12 signal (v2)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">18 signal (v3)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">Total copper layers<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">12<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">18<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">24<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">Balls per layer, invariant<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">146.2<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">73.1<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">53.1<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">Balls per layer, measured<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">92\/87\/86\/83\/78\/80<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">73\/73\/73\/73\/73\/73\/73\/73\/73\/73\/73\/72<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\"><strong>54\/54\/53&times;16<\/strong><\/td>\n<\/tr>\n<tr style=\"background:#e8f5e9\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Escape routed<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">506 (52.7%)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">720 (75.0%)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">815 (84.9%)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">Delta from previous<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">&mdash;<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">+214 balls (+42%)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">+95 balls (+13%)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">DRC routing violations<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">0<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Third time the layer balance matched the invariant to a single ball. Two of the eighteen layers took 54; the other sixteen took 53. 956&nbsp;&divide;&nbsp;18 = 53.11. The two extra balls are the round-off.<\/p>\n<p>Coverage improvement decelerated exactly as predicted. 6&nbsp;&rarr;&nbsp;12 signal layers bought +42%. 12&nbsp;&rarr;&nbsp;18 bought +13% on top. Extrapolate the same shape and 24 signal layers would buy another few percent and then flatten as the geometric limit (the 240 pads at the die-BGA grid collision distance) becomes binding rather than trace surface.<\/p>\n<h2>Same Design, Three Tiers<\/h2>\n<p>Because each stackup change is a fabrication cost knob rather than a design change &mdash; same pin map, same die, same escape routing algorithm &mdash; the three versions ship as three product tiers on the store. Same substrate design, different HDI process class, different price.<\/p>\n<table style=\"width:100%;border-collapse:collapse;margin:1em 0\">\n<tbody>\n<tr style=\"background:#f0f0ff\">\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Tier<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Stackup<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Escape coverage<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Fab class<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd;font-weight:bold\">Price<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\"><a href=\"https:\/\/cri-one.com\/store\/autophi-1536-escape-interposer-access-12-layer-506-960.html\"><b>Access<\/b><\/a><\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">12 layers, 6 signal<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">506 of 960 (52.7%)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">Standard HDI<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">$400,000<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\"><a href=\"https:\/\/cri-one.com\/store\/autophi-1536-escape-interposer-editable-kicad-design-source.html\"><b>Standard<\/b><\/a><\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">18 layers, 12 signal<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">720 of 960 (75.0%)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">Advanced HDI<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">$760,000<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:6px 12px;border:1px solid #ddd\"><a href=\"https:\/\/cri-one.com\/store\/autophi-1536-escape-interposer-premium-24-layer-815-960.html\"><b>Premium<\/b><\/a><\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">24 layers, 18 signal<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">815 of 960 (84.9%)<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">Premium HDI<\/td>\n<td style=\"padding:6px 12px;border:1px solid #ddd\">$1,200,000<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Every tier ships the same package structure: editable KiCad source with libraries and 3D models, plus a fabrication package with gerbers, drill, IPC-2581, BOM and pick-and-place (CPL). Every tier also ships the same ESCAPE-THEORY.md document, because the invariant does not change &mdash; only the choice of <em>N<\/em> does.<\/p>\n<p>The Access tier makes sense for buyers who can live with 506 signals routed on a lower-cost HDI process. The Standard tier is the middle: current mainstream HDI, 720 signals, the invariant analysis published a week ago. Premium runs at the process class where a single die substrate approaches full-density fan-out on this pad geometry.<\/p>\n<p>The AutoPhi V19 blueprints &mdash; including this interposer, with its<br \/>\nescape coverage and every DRC entry disclosed on the product page &mdash;<br \/>\nare at <a href=\"https:\/\/cri-one.com\/store\/\">the store<\/a>. We ship the<br \/>\ndesign. You build the board.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A 1,536-ball interposer, six signal layers, and a router that ran out of room after four rings. The fix was not more layers. It was noticing that five sixths of every layer was empty. The AutoPhi V19 interposer has one job: take a die with 1,536 pads on a 0.800&nbsp;mm grid and fan it out [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[593,580],"tags":[],"class_list":["post-912036","post","type-post","status-publish","format-standard","hentry","category-about-the-work","category-autophi"],"_links":{"self":[{"href":"https:\/\/cri-one.com\/blog\/wp-json\/wp\/v2\/posts\/912036","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cri-one.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cri-one.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/cri-one.com\/blog\/wp-json\/wp\/v2\/comments?post=912036"}],"version-history":[{"count":4,"href":"https:\/\/cri-one.com\/blog\/wp-json\/wp\/v2\/posts\/912036\/revisions"}],"predecessor-version":[{"id":912047,"href":"https:\/\/cri-one.com\/blog\/wp-json\/wp\/v2\/posts\/912036\/revisions\/912047"}],"wp:attachment":[{"href":"https:\/\/cri-one.com\/blog\/wp-json\/wp\/v2\/media?parent=912036"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cri-one.com\/blog\/wp-json\/wp\/v2\/categories?post=912036"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cri-one.com\/blog\/wp-json\/wp\/v2\/tags?post=912036"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}