Skip to content

Commit 214fa85

Browse files
dev-minjaeclaude
andcommitted
gpl: per-region GPU density field for multi-region placement
gpl builds one NesterovBase per placement region (top-level core plus one per power-domain / odb group) but a single shared NesterovBaseCommon with one GPU DeviceState. The four FFT field Views (d_bin_density / d_bin_phi / d_bin_elec_*) and the bin-grid metadata lived in that shared DeviceState and were overwritten per region by DeviceState::initBinViews; the Nesterov loop also solves all regions and only then gathers all regions, so with more than one region a region's density solve read another region's (or a wrong-sized) bin buffer. That is an out-of-bounds device access when the regions' bin counts differ, or a silently wrong placement when they match (measured +119% HPWL on the dual voltage-domain region01 design). Single-region placement was unaffected, so the existing tests passed. Reported by a Codex review. Move the per-region state into a new RegionDensityField (PIMPL, one per NesterovBase, declared before fft_/density_grad_backend_ so it outlives the backends that borrow it); it holds only the four field Views + bin metadata. The shared DeviceState keeps the netlist/HPWL pool and the per-inst density params + gather output, which are indexed by the global gCellStor instance id and identical across regions; their allocation moves out of the old initBinViews into the DeviceState constructor. Both GPU density paths are repointed at the region field: the device-resident pipeline (NesterovDeviceContext) and the host-staged FFT path (GpuFftBackend / GpuDensityGradientBackend), the latter used in timing-driven / routability mode where the device context is disabled -- which is why the field is owned by NesterovBase rather than the device context. Harden two previously-silent seams: the bin-count check in densitySolveIteration now aborts in release builds (was a debug-only assert), and NesterovBase errors out if a GPU region's bin grid is empty instead of silently using CPU density. Add two GPU-only regression tests (CMake-only, like the other GPU tests): region01_gpu (two same-size domains; the silent-interleaving case) and region01_gpu_asym (different-size domains; the out-of-bounds case), each checking the GPU total HPWL within 3% of the CPU reference and pinned to ENABLE_GPU=1 so they cannot pass on the CPU backend. Verified: CPU goldens unchanged (including multi-region CPU region01); single-region GPU within 0.001% of CPU; multi-region GPU now within 0.7% / 0.28% of CPU (was +119%). Also trimmed redundant comments across the GPU path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Minjae Kim <develop.minjae@gmail.com>
1 parent 8deea6f commit 214fa85

31 files changed

Lines changed: 779 additions & 254 deletions

src/gpl/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ cc_library(
5050
"src/fftsg2d.cpp",
5151
"src/gpu/deviceState.h",
5252
"src/gpu/nesterovDeviceContext.h",
53+
"src/gpu/regionDensityField.h",
5354
"src/graphicsNone.cpp",
5455
"src/hpwl.cpp",
5556
"src/hpwlBackend.h",

src/gpl/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ if(ENABLE_GPU)
6363
src/gpu/poissonSolver.cpp
6464
src/gpu/dct.cpp
6565
src/gpu/deviceState.cpp
66+
src/gpu/regionDensityField.cpp
6667
src/gpu/gpuWirelengthGradientBackend.cpp
6768
src/gpu/wirelengthOp.cpp
6869
src/gpu/gpuDensityGradientBackend.cpp
@@ -89,6 +90,7 @@ if(ENABLE_GPU)
8990
set_source_files_properties(
9091
src/gpu/gpuHpwlBackend.cpp src/gpu/gpuRuntime.cpp src/gpu/gpuFftBackend.cpp
9192
src/gpu/poissonSolver.cpp src/gpu/dct.cpp src/gpu/deviceState.cpp
93+
src/gpu/regionDensityField.cpp
9294
src/gpu/gpuWirelengthGradientBackend.cpp src/gpu/wirelengthOp.cpp
9395
src/gpu/gpuDensityGradientBackend.cpp src/gpu/densityOp.cpp
9496
src/gpu/nesterovOp.cpp src/gpu/nesterovDeviceContext.cpp
@@ -98,6 +100,7 @@ if(ENABLE_GPU)
98100
set_source_files_properties(
99101
src/gpu/gpuHpwlBackend.cpp src/gpu/gpuRuntime.cpp src/gpu/gpuFftBackend.cpp
100102
src/gpu/poissonSolver.cpp src/gpu/dct.cpp src/gpu/deviceState.cpp
103+
src/gpu/regionDensityField.cpp
101104
src/gpu/gpuWirelengthGradientBackend.cpp src/gpu/wirelengthOp.cpp
102105
src/gpu/gpuDensityGradientBackend.cpp src/gpu/densityOp.cpp
103106
src/gpu/nesterovOp.cpp src/gpu/nesterovDeviceContext.cpp

src/gpl/src/backendContext.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
// Each factory consumes the subset of fields it needs and ignores the rest;
99
// callers build one context per construction site and reuse it across the
1010
// four factory calls. Plain C++ — Kokkos types are forward-declared elsewhere
11-
// and pointers (DeviceState*, NesterovBase*, NesterovBaseCommon*) are only
12-
// dereferenced inside backend translation units.
11+
// and pointers (DeviceState*, NesterovBase*, NesterovBaseCommon*,
12+
// RegionDensityField*) are only dereferenced inside backend translation units.
1313

1414
#pragma once
1515

@@ -18,15 +18,21 @@ namespace gpl {
1818
class DeviceState;
1919
class NesterovBase;
2020
class NesterovBaseCommon;
21+
class RegionDensityField;
2122

2223
struct BackendContext
2324
{
2425
// Owning / context pointers. nbc is required by the wirelength gradient
2526
// backend; nb is required by the density gradient backend; device_state is
26-
// borrowed by every GPU backend and ignored by the CPU backends.
27+
// borrowed by the GPU HPWL, wirelength-gradient, and density-gradient
28+
// backends (ignored by the CPU backends and by the GPU FFT backend, which
29+
// uses region_field instead). region_field carries the per-region FFT field
30+
// Views; borrowed by the GPU FFT and density-gradient backends (one per
31+
// placement region), ignored by the CPU and wirelength/HPWL backends.
2732
NesterovBaseCommon* nbc = nullptr;
2833
NesterovBase* nb = nullptr;
2934
DeviceState* device_state = nullptr;
35+
RegionDensityField* region_field = nullptr;
3036

3137
// OpenMP fan-out for the CPU backends.
3238
int num_threads = 1;

src/gpl/src/densityGradient.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "gpu/deviceState.h"
1717
#include "gpu/gpuDensityGradientBackend.h"
1818
#include "gpu/gpuRuntime.h"
19+
#include "gpu/regionDensityField.h"
1920
#endif
2021

2122
namespace gpl {
@@ -55,9 +56,10 @@ std::unique_ptr<DensityGradientBackend> makeDensityGradientBackend(
5556
const BackendContext& ctx)
5657
{
5758
#ifdef ENABLE_GPU
58-
if (gpuEnabled() && ctx.device_state && ctx.device_state->numBins() > 0) {
59-
return std::make_unique<GpuDensityGradientBackend>(ctx.nb,
60-
ctx.device_state);
59+
if (gpuEnabled() && ctx.device_state && ctx.region_field
60+
&& ctx.region_field->numBins() > 0) {
61+
return std::make_unique<GpuDensityGradientBackend>(
62+
ctx.nb, ctx.device_state, ctx.region_field);
6163
}
6264
#endif
6365
return std::make_unique<CpuDensityGradientBackend>(ctx.nb);

src/gpl/src/fft.cpp

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22
// Copyright (c) 2018-2025, The OpenROAD Authors
33

44
// FFT — the density-grid context — and CpuFftBackend, the Ooura DCT solver.
5-
//
6-
// FFT owns the staging grids and the backend-agnostic accessors; doFFT()
7-
// delegates to the FftBackend chosen at construction. CpuFftBackend (always
8-
// compiled) is the Ooura DCT. makeFftBackend() is the single place the runtime
9-
// backend choice is made: on an ENABLE_GPU build with the GPU path selected
10-
// (gpl::gpuEnabled()) it returns the Kokkos GpuFftBackend.
5+
// doFFT() delegates to the FftBackend chosen at construction. makeFftBackend()
6+
// makes that choice: GpuFftBackend on an ENABLE_GPU build with gpuEnabled(),
7+
// else the always-compiled CpuFftBackend (Ooura DCT).
118

129
#include "fft.h"
1310

@@ -205,7 +202,7 @@ std::unique_ptr<FftBackend> makeFftBackend(const BackendContext& ctx)
205202
ctx.bin_cnt_y,
206203
ctx.bin_size_x,
207204
ctx.bin_size_y,
208-
ctx.device_state);
205+
ctx.region_field);
209206
}
210207
#endif
211208
return std::make_unique<CpuFftBackend>(
@@ -217,14 +214,14 @@ BackendContext makeFftCtx(int bin_cnt_x,
217214
int bin_cnt_y,
218215
float bin_size_x,
219216
float bin_size_y,
220-
DeviceState* device_state)
217+
RegionDensityField* region_field)
221218
{
222219
BackendContext ctx;
223220
ctx.bin_cnt_x = bin_cnt_x;
224221
ctx.bin_cnt_y = bin_cnt_y;
225222
ctx.bin_size_x = bin_size_x;
226223
ctx.bin_size_y = bin_size_y;
227-
ctx.device_state = device_state;
224+
ctx.region_field = region_field;
228225
return ctx;
229226
}
230227
} // namespace
@@ -233,7 +230,7 @@ FFT::FFT(int bin_cnt_x,
233230
int bin_cnt_y,
234231
float bin_size_x,
235232
float bin_size_y,
236-
DeviceState* device_state)
233+
RegionDensityField* region_field)
237234
: bin_density_(static_cast<std::size_t>(bin_cnt_x) * bin_cnt_y, 0.0f),
238235
electro_phi_(static_cast<std::size_t>(bin_cnt_x) * bin_cnt_y, 0.0f),
239236
electro_field_x_(static_cast<std::size_t>(bin_cnt_x) * bin_cnt_y, 0.0f),
@@ -244,7 +241,7 @@ FFT::FFT(int bin_cnt_x,
244241
bin_cnt_y,
245242
bin_size_x,
246243
bin_size_y,
247-
device_state)))
244+
region_field)))
248245
{
249246
}
250247

src/gpl/src/fft.h

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,19 @@
1111

1212
namespace gpl {
1313

14-
class DeviceState;
14+
class RegionDensityField;
1515

16-
// FFT — the density-grid context for the Poisson solve. It owns the staging
17-
// grids and the backend-agnostic accessors; the solve itself is delegated to
18-
// an FftBackend (the CPU Ooura DCT or the GPU Kokkos solver) selected at
19-
// construction by makeFftBackend(). Callers see one concrete class regardless
20-
// of backend.
16+
// FFT — the density-grid context for the Poisson solve. Owns the staging grids
17+
// and backend-agnostic accessors; the solve is delegated to an FftBackend (CPU
18+
// Ooura DCT or GPU Kokkos) chosen by makeFftBackend() at construction.
2119
class FFT
2220
{
2321
public:
2422
FFT(int bin_cnt_x,
2523
int bin_cnt_y,
2624
float bin_size_x,
2725
float bin_size_y,
28-
DeviceState* device_state = nullptr);
26+
RegionDensityField* region_field = nullptr);
2927
~FFT();
3028

3129
// input func
@@ -53,8 +51,7 @@ class FFT
5351
int bin_cnt_x_ = 0;
5452
int bin_cnt_y_ = 0;
5553

56-
// The Poisson solve backend (CPU Ooura or GPU Kokkos), selected at run time
57-
// in the constructor. doFFT() delegates to it.
54+
// Poisson solve backend (CPU Ooura or GPU Kokkos), chosen in the ctor.
5855
std::unique_ptr<FftBackend> backend_;
5956
};
6057

src/gpl/src/fftBackend.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ struct BackendContext;
6363

6464
// Factory: returns GpuFftBackend on an ENABLE_GPU build with the GPU path
6565
// selected at run time, otherwise CpuFftBackend. Consumes ctx.bin_cnt_x /
66-
// bin_cnt_y / bin_size_x / bin_size_y (grid geometry) and ctx.device_state
67-
// (GPU path; may be null for CPU path — GpuFftBackend borrows its bin Views
68-
// when available, falling back to self-owned Views).
66+
// bin_cnt_y / bin_size_x / bin_size_y (grid geometry) and ctx.region_field
67+
// (GPU path; may be null — GpuFftBackend borrows the region's bin Views when
68+
// available, falling back to self-owned Views).
6969
std::unique_ptr<FftBackend> makeFftBackend(const BackendContext& ctx);
7070

7171
static_assert(!std::is_copy_constructible_v<FftBackend>);

src/gpl/src/gpu/dct.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,6 @@ void dct_2d_fft(const int M,
202202
});
203203
}
204204

205-
////////////////////////////////////////////////////////////////////////////////////
206-
207205
void idct_2d_fft(
208206
const int M,
209207
const int N,

src/gpl/src/gpu/densityOp.cpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "deviceState_kokkos.h"
1818
#include "nesterovDeviceState.h"
1919
#include "poissonSolver.h"
20+
#include "regionDensityField_kokkos.h"
2021

2122
namespace gpl {
2223
namespace densop {
@@ -49,6 +50,7 @@ CoordPair getSlpCoords(const KokkosNesterovState& ns, SlpSlot slot)
4950
} // namespace
5051

5152
void launchDensityGather(KokkosDeviceState& ds,
53+
const KokkosRegionDensityField& rdf,
5254
int n_insts,
5355
int bin_cnt_x,
5456
int bin_cnt_y,
@@ -66,8 +68,8 @@ void launchDensityGather(KokkosDeviceState& ds,
6668
auto d_inst_density_half_dx = ds.d_inst_density_half_dx;
6769
auto d_inst_density_half_dy = ds.d_inst_density_half_dy;
6870
auto d_inst_density_scale = ds.d_inst_density_scale;
69-
auto d_bin_elec_x = ds.d_bin_elec_x;
70-
auto d_bin_elec_y = ds.d_bin_elec_y;
71+
auto d_bin_elec_x = rdf.d_bin_elec_x;
72+
auto d_bin_elec_y = rdf.d_bin_elec_y;
7173
auto d_inst_density_grad_x = ds.d_inst_density_grad_x;
7274
auto d_inst_density_grad_y = ds.d_inst_density_grad_y;
7375

@@ -121,7 +123,6 @@ void launchDensityGather(KokkosDeviceState& ds,
121123

122124
for (int bxi = min_bx; bxi < max_bx; ++bxi) {
123125
for (int byi = min_by; byi < max_by; ++byi) {
124-
// Bin bounds.
125126
const int b_lx = glx + static_cast<int>(bxi * bsx);
126127
const int b_ly = gly + static_cast<int>(byi * bsy);
127128
const int b_ux = glx + static_cast<int>((bxi + 1) * bsx);
@@ -156,7 +157,7 @@ void launchDensityGather(KokkosDeviceState& ds,
156157
}
157158

158159
void launchNbDensityScatter(KokkosNesterovState& ns,
159-
KokkosDeviceState& ds,
160+
KokkosRegionDensityField& rdf,
160161
int n_cells,
161162
SlpSlot src,
162163
float& overflow_area,
@@ -258,7 +259,7 @@ void launchNbDensityScatter(KokkosNesterovState& ns,
258259

259260
// Pass 2 — per-bin density write + overflow reductions. Mirrors the
260261
// tail loop of BinGrid::updateBinsGCellDensityArea.
261-
auto d_bin_density = ds.d_bin_density;
262+
auto d_bin_density = rdf.d_bin_density;
262263
auto d_bin_scaled_area = ns.d_bin_scaled_area;
263264
auto d_bin_nonplace = ns.d_bin_nonplace;
264265
auto d_bin_nonplace_unscaled = ns.d_bin_nonplace_unscaled;
@@ -295,7 +296,7 @@ void launchNbDensityScatter(KokkosNesterovState& ns,
295296
}
296297

297298
void launchNbDensityGather(KokkosNesterovState& ns,
298-
const KokkosDeviceState& ds,
299+
const KokkosRegionDensityField& rdf,
299300
int n_cells,
300301
SlpSlot src)
301302
{
@@ -313,8 +314,8 @@ void launchNbDensityGather(KokkosNesterovState& ns,
313314
auto d_bin_ly = ns.d_bin_ly;
314315
auto d_bin_ux = ns.d_bin_ux;
315316
auto d_bin_uy = ns.d_bin_uy;
316-
auto d_bin_elec_x = ds.d_bin_elec_x;
317-
auto d_bin_elec_y = ds.d_bin_elec_y;
317+
auto d_bin_elec_x = rdf.d_bin_elec_x;
318+
auto d_bin_elec_y = rdf.d_bin_elec_y;
318319
auto d_grad_x = ns.d_density_grad_x;
319320
auto d_grad_y = ns.d_density_grad_y;
320321

@@ -371,13 +372,14 @@ void launchNbDensityGather(KokkosNesterovState& ns,
371372
});
372373
}
373374

374-
float launchNbSumPhi(const KokkosNesterovState& ns, const KokkosDeviceState& ds)
375+
float launchNbSumPhi(const KokkosNesterovState& ns,
376+
const KokkosRegionDensityField& rdf)
375377
{
376378
const int nbins = ns.bin_cnt_x * ns.bin_cnt_y;
377379
if (nbins == 0) {
378380
return 0.0f;
379381
}
380-
auto d_bin_phi = ds.d_bin_phi;
382+
auto d_bin_phi = rdf.d_bin_phi;
381383
auto d_bin_inst_area = ns.d_bin_inst_area;
382384
auto d_bin_filler_area = ns.d_bin_filler_area;
383385
auto d_bin_nonplace = ns.d_bin_nonplace;

src/gpl/src/gpu/densityOp.h

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ namespace gpl {
1616

1717
struct KokkosDeviceState;
1818
struct KokkosNesterovState;
19+
struct KokkosRegionDensityField;
1920

2021
namespace densop {
2122

22-
// Per-inst density gradient gather: reads d_bin_elec_x/y (solver convention),
23-
// applies axis swap + 0.5× scale, accumulates overlap × field per overlapping
24-
// bin. Writes d_inst_density_grad_x/y.
23+
// Per-inst density gradient gather: reads the region's d_bin_elec_x/y (solver
24+
// convention) from `rdf`, applies axis swap + 0.5× scale, accumulates
25+
// overlap × field per overlapping bin using the per-inst params in `ds`.
26+
// Writes ds.d_inst_density_grad_x/y.
2527
void launchDensityGather(KokkosDeviceState& ds,
28+
const KokkosRegionDensityField& rdf,
2629
int n_insts,
2730
int bin_cnt_x,
2831
int bin_cnt_y,
@@ -36,28 +39,28 @@ void launchDensityGather(KokkosDeviceState& ds,
3639
// Reads the float coords of `src`, truncates to int density centers (same
3740
// conversion as GCell::setDensityCenterLocation), accumulates per-bin areas
3841
// with int64 per-contribution truncation (bit-equal to the serial CPU sums),
39-
// writes ds.d_bin_density (FFT layout), and reduces the two overflow sums.
42+
// writes rdf.d_bin_density (FFT layout), and reduces the two overflow sums.
4043
void launchNbDensityScatter(KokkosNesterovState& ns,
41-
KokkosDeviceState& ds,
44+
KokkosRegionDensityField& rdf,
4245
int n_cells,
4346
SlpSlot src,
4447
float& overflow_area,
4548
float& overflow_area_unscaled);
4649

4750
// NB-level density gradient gather for all nb cells (fillers included):
48-
// overlap-weighted sum of the bin electric field at the `src` coords.
51+
// overlap-weighted sum of the region bin electric field at the `src` coords.
4952
// Writes ns.d_density_grad_x/y directly (no host round-trip). Bin bounds
5053
// come from the exact per-bin ints (ns.d_bin_lx etc.), matching the CPU's
5154
// lround()-derived Bin geometry.
5255
void launchNbDensityGather(KokkosNesterovState& ns,
53-
const KokkosDeviceState& ds,
56+
const KokkosRegionDensityField& rdf,
5457
int n_cells,
5558
SlpSlot src);
5659

5760
// Σ phi[bin] × float(nonPlace + instPlaced + filler) over all bins — the
5861
// sumPhi_ debug metric. Only call after launchNbDensityScatter + solve.
5962
float launchNbSumPhi(const KokkosNesterovState& ns,
60-
const KokkosDeviceState& ds);
63+
const KokkosRegionDensityField& rdf);
6164

6265
} // namespace densop
6366
} // namespace gpl

0 commit comments

Comments
 (0)