2026年华数杯数学建模B题VLSI布图规划设计解题全过程及程序
2026年华数杯全国大学生数学建模
B题 VLSI布图规划设计
原题再现:
超大规模集成电路(VLSI,Very Large Scale Integration)将大量电路单元集成于单一芯片。随着设计复杂度增加,如今开展VLSI设计已离不开电子设计自动化(EDA,Electronic Design Automation)工具的支持。EDA 作为算法密集型产业,需要对数千种情境进行快速设计探索,是国家关键技术领域。其中,布图规划(Floorplanning)是 EDA 研究的核心问题之一。布图规划旨在给定的芯片轮廓内确定各功能模块的位置和方向,为后续的布局和布线奠定基础。芯片轮廓通常为正方形,其尺寸由所有模块的总面积(total_block_area)和预设的死区比例(dead_space_ratio)共同确定,死区比例反映了布线和其他开销所需的额外空间。芯片轮廓的尺寸计算如公式(1)所示,则芯片的左下角坐标和右上角坐标分别为 (0,0) 和 (Wf,ℎf)。

通常情况下,布图规划的目标是功能模块间的总连线长度最小,同时满足模块之间不重叠、不超出芯片轮廓等约束条件。每个模块都为矩形,允许进行90度旋转以获得更好的布局效果。模块间的连接关系通过连线网络描述,每个网络连接若干个模块的引脚,网络的连线长度采用半周长线长(Half PerimeterWirelength, HPWL)[1]进行估算,即连接所有引脚的最小包围矩形的周长的一半。为简化问题,假设所有模块的引脚均位于模块的几何中心位置。图(1)描述了 VLSI 布图规划设计中的相关概念,附件给出了模块和连线网络(包含.blocks,.nets,.pl 三个文件)的具体信息。

问题1 假设芯片轮廓不固定,模块之间也没有连接关系。请问如何摆放模块,使得芯片轮廓面积最小;在轮廓的面积相同的情况下,其长宽比越接近1则结果越好。图2示意了3个模块及其对应的2种布图规划结果。请根据所建立的模型和.blocks文件信息,对附件中的三组芯片(n100、n200、n300)分别完成模块摆放(即确定.blocks文件中类型为block的模块位置,每组芯片独立布图),分别给出三组芯片轮廓的面积、长宽比、模块摆放的可视化结果。

问题2 实际场景中,芯片轮廓通常为正方形,其尺寸由公式(1)确定,模块之间也会有连接关系。请问如何摆放模块,使得模块之间总HPWL最小,同时满足模块之间不重叠、不超出芯片轮廓的约束条件。假设死区比例为0.15,请根据所建立的模型和附件信息,对附件中的三组芯片完成模块摆放,分别给出三组芯片的总连线长度、模块摆放的可视化结果。
问题3 在问题2的基础上,为满足模块之间不重叠、不超出芯片轮廓的约束条件,死区比例最小可以是多少(见公式1,随着死区比例减小,芯片轮廓尺
寸逐渐减小,布图越难以满足约束条件。即求解使得存在一个可行布局方案的最小死区比例)?请分别给出附件中三组芯片的死区比例最小值,并基于问题2所建立的模型和死区比例最小值,更新问题2的总HPWL以及模块摆放的可视化结果。
问题4 假设模块不再全是规则的矩形,而可能是L型和T型,且所有模块均可以进行90°、180°和270°旋转。请讨论问题1所建立的模型如何修正?求解算法可在问题1算法基础上适当修改,不要求重新设计新的优化算法。为验证修正后的模型的有效性,现给出如图3所示的4个待摆放模块,请基于您修正后的模型,求解如何摆放这4个模块使得包围他们的芯片轮廓面积最小,给出最优摆放的示意图及此时的最小面积。

整体求解过程概述(摘要)
针对问题一,为了探究在芯片轮廓不固定且忽略线网连接时的极限紧凑布图,首先对附件 n100、n200、n300 的 .blocks 文件进行结构化解析、字段一致性校验、模块尺度分布和形状比探索。鉴于该问题本质上属于带可旋转矩形的二维装箱/平面布图组合优化,本文建立以包围盒面积为第一目标、长宽比偏离 1 为第二目标的词典序优化模型,并构建“多尺度候选轮廓搜索—MaxRects 自由矩形合法化—多排序多方向扰动”的启发式求解器。结果得到 n100、n200、n300 的当前搜索最优包围盒分别为 435×435、385×480、478×598,对应面积 189225、184800、285844,面积相对模块总面积下界的空隙率分别为 5.42%、5.18% 和 4.64%。其中 n100 达到长宽比 1,n200 与 n300 在面积优先条件下保留约 1.25 的长宽比。通过 NFDH 基线、可行轮廓轨迹和面积—形状比权衡图验证了算法对紧凑度与轮廓均衡性的改善。
针对问题二,在问题一已完成的数据解析与模块几何特征工程基础上不再重复预处理,而进一步引入 .nets 线网和 .pl 固定终端信息。考虑到死区比例为 0.15 时芯片轮廓固定为正方形,首先以线网图构建二次线长松弛,求解图 Laplacian 线性方程组获得全局连续“理想中心”;随后构造带碎片惩罚的目标引导 MaxRects,将连续解合法化为互不重叠、不过界的离散矩形布局;最后基于线网引脚坐标的中位数性质开展保持可行性的坐标下降局部改进。相较仅考虑几何装箱的基线,总 HPWL 在 n100、n200、n300 上分别由 291088、555556、858412.5 降至 257508、484703、715480.5,降幅分别达到 11.54%、12.75% 和 16.65%。同时使用碎片权重敏感性、线网 HPWL 累积分布、不同线网度数分组比较以及硬约束独立校验验证模型的稳定性。
针对问题三,在问题二固定轮廓模型的基础上进一步研究“最小可行死区比例”,避免重新建立一套无关联模型。鉴于正方形边长 S 与死区比例 δ 满足 δ=S²/A−1,且“在边长 S 可行则在更大边长仍可行”形成单调可行域,本文引入整数边长二分搜索与边界多起点复核,逐步压缩可行轮廓,并在最小可行边长下继续复用问题二的 HPWL 局部改进。算法搜索得到 n100、n200、n300 的最小可行边长分别为 435、432、536,对应死区比例约 5.42%、6.22%、5.17%,且对边长减 1 的轮廓均未在多起点检验中获得可行解。压缩轮廓后总 HPWL 分别为 301697.5、549429、829631.5,揭示芯片面积与互连代价之间存在显著的 Pareto 权衡。
针对问题四,为了处理矩形假设失效后的 L 型与 T 型模块,本文不重新设计优化框架,而将问题一的“宽高矩形”表示推广为“基本网格单元集合/正交多边形占据集”,将 0°、90°、180°、270°旋转统一表示为离散旋转算子,非重叠约束转化为占据集合交为空。对题图给定的 1 个 T 型、1 个 L 型和 2 个矩形模块进行穷举旋转与精确覆盖搜索。四个模块总面积为 20,因而任何包围轮廓面积不可能小于 20;算法找到一个 4×5 的无空隙合法拼接,恰好达到该理论下界,因此最小面积严格为 20,并由下界—可行解一致性给出全局最优证明。
总体而言,本文形成了一条由“几何紧凑性”逐步过渡到“互连优化”、再过渡到“极限可制造空间”和“非矩形几何推广”的递进式建模链。数据层面统一解析三类文件并将后续问题建立在同一数据对象上;算法层面结合自由矩形空间编码、图拉普拉斯连续松弛、可行性合法化、局部改进、单调二分和精确覆盖;验证层面从硬约束、下界差距、超参数敏感性、不同规模算例以及文献基准思想多个维度评价合理性、准确性、鲁棒性与实用性。该框架兼顾了竞赛问题的可复现求解与实际 EDA 布图规划中“先可行、后优化、再压缩”的工程逻辑。
模型假设:
假设1:所有 HardBlock 均为刚性模块,问题一至问题三仅允许 0° 与 90° 旋转,旋转不会改变模块面积;问题四按题意允许 0°、90°、180°、270°。
假设2:模块边界可相切但内部不可相交;模块坐标使用附件原始坐标单位,几何运算不额外引入工艺栅格误差。
假设3:Terminal 的坐标固定,仅作为 HPWL 引脚参与问题二与问题三,不占据 HardBlock 可摆放面积。
假设4:每个 HardBlock 的线网引脚位于模块几何中心,因此模块旋转后其中心引脚位置仍由当前矩形中心给出。
假设5:HPWL 作为全局布线前的互连代理指标,不进一步考虑真实 Steiner 树、绕障、线宽、拥塞和时序延迟;该简化与题目给定指标一致。
假设6:问题三中若某一正方形边长存在合法布局,则更大的边长也存在合法布局;这是因为可将原可行布局原样嵌入更大的轮廓。
假设7:启发式算法“未找到可行解”不等价于数学意义上绝对不可行,因此问题三的最小死区结果表述为在当前搜索策略、多排序与多起点复核下的最小可行边界。
问题分析:
问题一分析
针对无互连约束、芯片轮廓自由的可旋转矩形宏模块最小包围盒布图问题,本题属于带词典序目标的二维矩形装箱 NP 难组合优化问题。核心要求优先最小包围盒总面积,面积相同时追求轮廓长宽比趋近 1;难点在于模块尺寸、长宽比异质性强,直接全排列枚举计算量爆炸,单纯货架式 NFDH 算法空间利用率低。建模思路先统一解析三组芯片模块数据,通过面积、长宽比分布挖掘装箱难点,构建 MaxRects 自由矩形空间编码实现无重叠合法摆放,设计多尺度候选轮廓网格搭配多排序、多旋转扰动启发搜索,以模块总面积为理论下界评估布局空隙率,对比传统装箱基线验证紧凑性提升效果,输出兼顾面积最优与轮廓均衡的全局近似最优布局方案。
问题二分析
针对固定正方形轮廓、给定 15% 死区比例、以总 HPWL 最小为目标的互连布图问题,本题属于几何可行约束与超图线长优化耦合的混合优化问题,完整复用问题一模块解析、旋转规则与合法化内核,无需重复数据预处理。难点是 HPWL 非凸分段不可微,直接联合几何约束求解极易陷入局部差解;建模采用分层求解策略,先基于线网与固定终端构建图拉普拉斯方程求解连续理想模块中心,引入碎片惩罚系数构造目标引导 MaxRects 生成合法布局,再以引脚中位数坐标下降开展单模块局部微调持续降低总线长,通过超参数敏感性、线长分布对比、多算例基线差值检验模型鲁棒性,在完全满足不越界、无重叠硬约束前提下实现互连长度大幅缩减。
问题三分析
针对求解最小可行正方形死区比例并优化极限轮廓 HPWL 的问题,本题依托问题二整套线长计算、布局合法化工具开展外层一维搜索。核心特性为正方形边长与布局可行性单调:边长越大越容易存在合法摆放,因此采用二分法压缩最小可行边长区间;难点是启发算法存在假阴性漏判,需对二分临界边长减 1 开展多排序多起点复核保证结果严谨。建模先以模块总面积平方根为下界、15% 死区边长为上界二分遍历,锁定最小可行轮廓后复用问题二的线长优化流程重新布线,对比 15% 死区方案量化芯片面积与总线长的 Pareto 权衡关系,明确极限紧凑布局带来的互连代价增幅。
问题四分析
针对含 L 型、T 型正交非矩形模块最小包围面积拼接问题,本题对前三问矩形建模框架做几何拓展,不更换整体优化逻辑。核心改动是将矩形宽高表示替换为离散旋转正交网格占据集合,非重叠约束转化为多边形内部无交集;因示例仅 4 个模块规模极小,放弃启发搜索改用完整精确覆盖枚举。先计算所有模块总面积确定理论最小面积下界,枚举全部旋转姿态与轮廓尺寸递归回溯摆放,找到恰好填满理论下界的无空隙拼接方案,通过下界与可行解一致严格证明该方案为全局最优,同时给出大规模异形模块场景下的算法拓展改造思路。
模型的建立与求解整体论文缩略图

全部论文请见下方“ 只会建模 QQ名片” 点击QQ名片即可
部分程序代码:
from __future__ import annotations
import argparse
import json
import math
import random
import re
import time
import zipfile
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Tuple, Optional, Iterable
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, Polygon
from scipy.sparse import lil_matrix, csr_matrix
from scipy.sparse.linalg import spsolve
# -----------------------------
# 全局绘图设置
# -----------------------------
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK JP', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['figure.dpi'] = 150
@dataclass
class CaseData:
name: str
rects: List[Tuple[int, int]]
terminals: Dict[str, Tuple[float, float]]
nets: List[List[str]]
block_incident_nets: List[List[int]]
@property
def n_blocks(self) -> int:
return len(self.rects)
@property
def n_terminals(self) -> int:
return len(self.terminals)
@property
def n_nets(self) -> int:
return len(self.nets)
@property
def total_area(self) -> int:
return int(sum(w*h for w, h in self.rects))
# ================================================================
# 1. 数据读取与理解
# ================================================================
def _zip_text_map(zip_path: Path) -> Dict[str, str]:
"""忽略压缩包内部中文目录编码,仅按文件 basename 建立文本映射。"""
out = {}
with zipfile.ZipFile(zip_path, 'r') as zf:
for info in zf.infolist():
if info.is_dir():
continue
base = Path(info.filename).name
if base.endswith(('.blocks', '.nets', '.pl')):
raw = zf.read(info)
# 数据文件为 ASCII;errors=ignore 只用于目录/注释中潜在异常字节
out[base] = raw.decode('utf-8', errors='ignore')
return out
def parse_case(texts: Dict[str, str], name: str) -> CaseData:
blocks_text = texts[f'{name}.blocks']
nets_text = texts[f'{name}.nets']
pl_text = texts[f'{name}.pl']
blocks = []
for line in blocks_text.splitlines():
m = re.match(
r'^(b\d+)\s+block\s+4\s+\(0,\s*0\)\s+\(0,\s*(\d+)\)\s+\((\d+),',
line.strip()
)
if m:
idx = int(m.group(1)[1:])
h = int(m.group(2))
w = int(m.group(3))
blocks.append((idx, w, h))
blocks.sort(key=lambda x: x[0])
rects = [(w, h) for _, w, h in blocks]
terminals: Dict[str, Tuple[float, float]] = {}
for line in pl_text.splitlines():
p = line.split()
if len(p) >= 3 and p[0].startswith('p'):
terminals[p[0]] = (float(p[1]), float(p[2]))
nets: List[List[str]] = []
lines = [x.strip() for x in nets_text.splitlines() if x.strip()]
i = 0
while i < len(lines):
if lines[i].startswith('NetDegree'):
d = int(lines[i].split(':')[1].strip())
pins = []
for _ in range(d):
i += 1
pins.append(lines[i].split()[0])
nets.append(pins)
i += 1
inc = [[] for _ in rects]
for ni, net in enumerate(nets):
for pin in net:
if pin.startswith('b'):
bi = int(pin[1:])
if 0 <= bi < len(rects):
inc[bi].append(ni)
return CaseData(name, rects, terminals, nets, inc)
def load_cases(zip_path: Path) -> Dict[str, CaseData]:
texts = _zip_text_map(zip_path)
return {name: parse_case(texts, name) for name in ['n100', 'n200', 'n300']}
# ================================================================
# 2. 几何与 HPWL 基本计算
# placement 元素:(x, y, width, height, rotated_flag)
# ================================================================
def intersects(a, b) -> bool:
ax, ay, aw, ah = a[:4]
bx, by, bw, bh = b[:4]
return not (ax + aw <= bx or bx + bw <= ax or ay + ah <= by or by + bh <= ay)
def contained(a, b) -> bool:
"""矩形 b 是否完全包含于矩形 a。"""
ax, ay, aw, ah = a[:4]
bx, by, bw, bh = b[:4]
return bx >= ax and by >= ay and bx+bw <= ax+aw and by+bh <= ay+ah
def placement_bbox(place):
W = max(x+w for x, y, w, h, r in place)
H = max(y+h for x, y, w, h, r in place)
return float(W), float(H)
def centers(place):
return [(x+w/2.0, y+h/2.0) for x, y, w, h, r in place]
def net_hpwl(net: List[str], cs, terminals) -> float:
pts = []
for p in net:
if p.startswith('b'):
pts.append(cs[int(p[1:])])
else:
pts.append(terminals[p])
xs = [p[0] for p in pts]
ys = [p[1] for p in pts]
return (max(xs)-min(xs)) + (max(ys)-min(ys))
def all_net_hpwls(place, case: CaseData) -> np.ndarray:
cs = centers(place)
return np.array([net_hpwl(net, cs, case.terminals) for net in case.nets], dtype=float)
def total_hpwl(place, case: CaseData) -> float:
return float(all_net_hpwls(place, case).sum())
def validate_placement(place, outline: Optional[Tuple[float, float]] = None) -> Dict[str, float]:
n = len(place)
overlaps = 0
for i in range(n):
for j in range(i+1, n):
if intersects(place[i], place[j]):
overlaps += 1
W, H = placement_bbox(place)
over_w = 0.0
over_h = 0.0
if outline is not None:
over_w = max(0.0, W-outline[0])
over_h = max(0.0, H-outline[1])
return {
'overlap_pairs': overlaps,
'bbox_w': W,
'bbox_h': H,
'overrun_w': over_w,
'overrun_h': over_h,
}
# ================================================================
# 3. MaxRects 可行性/装箱器
# ================================================================
def maxrects_pack(rects, W: int, H: int, order: Iterable[int], score_mode: int = 0):
free = [(0, 0, int(W), int(H))]
placed = [None] * len(rects)
for b in order:
rw, rh = rects[b]
best = None
orientations = [(rw, rh, 0)] if rw == rh else [(rw, rh, 0), (rh, rw, 1)]
for fx, fy, fw, fh in free:
for w, h, rot in orientations:
if w <= fw and h <= fh:
short = min(fw-w, fh-h)
long = max(fw-w, fh-h)
waste = fw*fh - w*h
if score_mode == 0:
score = (short, long, waste, fy, fx)
else:
score = (waste, short, long, fy, fx)
if best is None or score < best[0]:
best = (score, fx, fy, w, h, rot)
if best is None:
return None
_, px, py, pw, ph, rot = best
used = (px, py, pw, ph)
new_free = []
for fr in free:
if not intersects(fr + (0,), used + (0,)):
new_free.append(fr)
continue
fx, fy, fw, fh = fr
ux, uy, uw, uh = used
if ux > fx:
new_free.append((fx, fy, ux-fx, fh))
if ux+uw < fx+fw:
new_free.append((ux+uw, fy, fx+fw-ux-uw, fh))
if uy > fy:
new_free.append((fx, fy, fw, uy-fy))
if uy+uh < fy+fh:
new_free.append((fx, uy+uh, fw, fy+fh-uy-uh))
# 去除被其他空闲矩形完全包含的冗余区域
pruned = []
for i, r in enumerate(new_free):
if r[2] <= 0 or r[3] <= 0:
continue
is_contained = False
for j, s in enumerate(new_free):
if i != j and contained(s + (0,), r + (0,)):
is_contained = True
break
if not is_contained:
pruned.append(r)
free = pruned
placed[b] = (px, py, pw, ph, rot)
return placed
def order_variants(rects):
ids = list(range(len(rects)))
return [
('max_side_desc', sorted(ids, key=lambda i: -max(rects[i]))),
('min_side_desc', sorted(ids, key=lambda i: -min(rects[i]))),
('area_desc', sorted(ids, key=lambda i: -rects[i][0]*rects[i][1])),
('max_side_area_desc', sorted(ids, key=lambda i: (-max(rects[i]), -rects[i][0]*rects[i][1]))),
('perimeter_desc', sorted(ids, key=lambda i: -(rects[i][0]+rects[i][1]))),
]
def randomized_orders(rects, seed: int, count: int = 8, base_kind: str = 'max'):
rng = random.Random(seed)
variants = order_variants(rects)
if base_kind == 'max':
base = variants[0][1]
else:
base = variants[2][1]
out = []
n = len(rects)
for k in range(count):
o = base.copy()
for _ in range(max(5, n//4)):
i = rng.randrange(n)
j = min(n-1, max(0, i + rng.randint(-10, 10)))
o[i], o[j] = o[j], o[i]
out.append((f'random_{k+1}', o))
return out
# ================================================================
# 4. 问题1:自由轮廓面积优化
# ================================================================
def shelf_baseline(rects):
"""NFDH 风格条带装箱基线,用于算法对照。"""
A = sum(w*h for w, h in rects)
target_w = int(round(math.sqrt(A)*1.05))
ids = sorted(range(len(rects)), key=lambda i: (-max(rects[i]), -rects[i][0]*rects[i][1]))
place = [None]*len(rects)
x = y = row_h = 0
for b in ids:
rw, rh = rects[b]
candidates = [(rw, rh, 0), (rh, rw, 1)] if rw != rh else [(rw, rh, 0)]
# 优先选择能放入当前行且高度较小的方向
feasible = [c for c in candidates if x+c[0] <= target_w]
if not feasible:
y += row_h
x = 0
row_h = 0
feasible = candidates
w, h, r = min(feasible, key=lambda c: (max(row_h, c[1]), c[1], c[0]))
place[b] = (x, y, w, h, r)
x += w
row_h = max(row_h, h)
return place
def solve_q1(case: CaseData):
A = case.total_area
if case.n_blocks >= 280:
variants = order_variants(case.rects)[:2]
ratios = [1.0, 0.90, 0.85, 0.80, 1.10]
gamma_grid = [0.0500, 0.0475, 0.0450, 0.0425]
elif case.n_blocks >= 180:
variants = order_variants(case.rects)[:3]
ratios = [1.0, 0.90, 0.85, 0.80, 1.10]
gamma_grid = [0.0550, 0.0525, 0.0500, 0.0475]
else:
variants = order_variants(case.rects)[:3]
ratios = [1.0, 0.90, 0.85, 0.80, 1.10]
gamma_grid = [0.0550, 0.0525, 0.0500]
# 从较宽松面积开始向下压缩;出现首个低一级不可行后停止,属于离散搜索的工程实现。
best = None
trace = []
seen_feasible = False
fail_streak = 0
t0 = time.perf_counter()
for gamma in gamma_grid:
target_area = A*(1+gamma)
level_best = None
attempts = 0
for ar in ratios:
W = int(math.ceil(math.sqrt(target_area*ar)))
H = int(math.ceil(target_area/W))
for oi, (oname, order) in enumerate(variants):
attempts += 1
p = maxrects_pack(case.rects, W, H, order, score_mode=oi % 2)
if p is not None:
actual_area = W*H
key = (actual_area, abs(math.log(W/H)))
if level_best is None or key < level_best[0]:
level_best = (key, W, H, p, oname, ar)
trace.append({
'target_gamma': gamma,
'feasible': level_best is not None,
'attempts': attempts,
'best_area': None if level_best is None else level_best[0][0],
'best_W': None if level_best is None else level_best[1],
'best_H': None if level_best is None else level_best[2],
})
if level_best is not None:
seen_feasible = True
fail_streak = 0
if best is None or level_best[0] < best[0]:
best = level_best
elif seen_feasible:
fail_streak += 1
# 离散轮廓存在“尺寸共振”,单个搜索档位失败不代表更小档位必然失败;连续两档失败才停止。
if fail_streak >= 2:
break
if best is None:
raise RuntimeError(f'{case.name}: Q1 未找到可行布局')
_, W, H, placement, order_name, ar = best
baseline = shelf_baseline(case.rects)
bW, bH = placement_bbox(baseline)
runtime = time.perf_counter()-t0
return {
'placement': placement,
'W': W, 'H': H,
'area': W*H,
'deadspace': W*H/A - 1,
'aspect_ratio': max(W/H, H/W),
'order_name': order_name,
'candidate_aspect': ar,
'baseline_placement': baseline,
'baseline_area': bW*bH,
'baseline_deadspace': bW*bH/A - 1,
'trace': trace,
'runtime': runtime,
'validation': validate_placement(placement),
}
全部论文请见下方“ 只会建模 QQ名片” 点击QQ名片即可
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)