Repository navigation
Expand file tree
/
Copy pathDockerfile
More file actions
1736 lines (1630 loc) · 104 KB
/
Copy pathDockerfile
File metadata and controls
1736 lines (1630 loc) · 104 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# adk-tools: single distributable image for the heterogeneous integration flow.
#
# Stages, in two parts. Above the thin-layer line, the tools: everything this
# repository does not write, built only when one of them changes.
# deps shared apt layer (build deps + runtime libs, one layer for all stages)
# kicad-builder compiles the KiCad fork (wxPython scripting ON -> headless pcbnew)
# kicad-libs official v9 symbol/footprint libraries (pinned tag)
# sg13g2-pdk SG13G2 base-PDK KLayout slice (PCell library, pinned refs)
# tools FROM deps: runtime apt, KiCad, libraries, PDK slices and
# models, the Nix engines and viewers, the venv's third-party
# packages, and the locks naming every package in it
# Below it, our code, on the tools image:
# studio-builder builds KLayout in-tree + Chiplet Studio at its final path
# runtime thin: Studio, the submodules, the staged trees installed
# offline, wrappers, manifest, die views, tool identity
# verify FROM runtime: regenerates the demo headless, runs studio ctest,
# plugin pytest and adk-smoke. Default target, so a plain
# `docker build .` never produces an untested image.
#
# Build through ./build.sh (generates the version manifest, builds the tools
# image only when ci/tools_key.py gives a key no image carries yet, and tags
# the lean runtime image after verify passes).
# Parallel compile jobs for the heavy stages (kicad-builder and studio-builder
# can overlap; 2 x nproc jobs on big machines exhausts RAM). Override per
# machine: --build-arg JOBS=N (build.sh: JOBS=N ./build.sh).
ARG JOBS=8
# The base image every ubuntu stage starts from, pinned by digest: the
# ubuntu:24.04 manifest index whose amd64 image (config 8bf6fbc9) the image
# has been built on. The tag alone moves upstream, and with it every file the
# base carries (coreutils' stdbuf among them) and the apt layers above it.
ARG UBUNTU_IMAGE=ubuntu:24.04@sha256:786a8b558f7be160c6c8c4a54f9a57274f3b4fb1491cf65146521ae77ff1dc54
# The Ubuntu archive every apt step installs from: the snapshot service's
# view of it at this time (docker/apt-snapshot/apt-snapshot.sh), so a rebuild
# on another day installs the same versions. The tools stage writes what it
# installed to /opt/adk-tools/tools/apt.lock. Move it in its own commit; every
# apt layer, and with them the tools image, is rebuilt.
ARG APT_SNAPSHOT=20261012T090000Z
# The image the part below the thin-layer line builds on. The default is the
# tools stage of this file, so a plain `docker build` builds both parts;
# build.sh passes adk-tools:tools-<key>, the image built for this key, so a
# change below the line leaves the tools image as it is.
ARG TOOLS_IMAGE=tools
############################################################################
FROM ${UBUNTU_IMAGE} AS deps
ENV DEBIAN_FRONTEND=noninteractive
# Union of: KiCad fork build deps (see tools/kicad/Dockerfile), Chiplet Studio /
# KLayout build deps (see tools/chiplet-studio/docker/Dockerfile.build) and
# runtime utilities. One fat layer shared by every stage: zero missing-lib
# hunts, maximum cache reuse.
#
# First, every package the base image carries moves to its version in the
# snapshot (--only-upgrade over all of them, which is apt-get upgrade through
# the snapshot helper): the base image is pinned by digest and lags the
# archive's security updates, and nothing else here would bring them in for a
# package no install below depends on. The snapshot time keeps the versions
# fixed, and apt.lock records them.
ARG APT_SNAPSHOT
RUN --mount=type=bind,source=docker/apt-snapshot,target=/run/apt-snapshot \
--mount=type=cache,id=adk-tools-apt-snapshot,target=/var/cache/apt-snapshot,sharing=locked \
sh /run/apt-snapshot/apt-snapshot.sh "$APT_SNAPSHOT" --only-upgrade \
$(dpkg-query -W -f='${binary:Package}\n') \
&& sh /run/apt-snapshot/apt-snapshot.sh "$APT_SNAPSHOT" \
build-essential \
cmake \
ninja-build \
gettext \
git \
curl \
wget \
file \
ca-certificates \
pkg-config \
\
libglm-dev \
zlib1g-dev \
libzstd-dev \
libcurl4-openssl-dev \
libspnav-dev \
libcairo2-dev \
libpixman-1-dev \
libgit2-dev \
libboost-all-dev \
libfreetype-dev \
libharfbuzz-dev \
libfontconfig1-dev \
libngspice0-dev \
libocct-data-exchange-dev \
libocct-draw-dev \
libocct-foundation-dev \
libocct-modeling-algorithms-dev \
libocct-modeling-data-dev \
libocct-ocaf-dev \
libocct-visualization-dev \
protobuf-compiler \
libprotobuf-dev \
swig \
python3 \
python3-dev \
python3-venv \
python3-pip \
python3-wxgtk4.0 \
libwxgtk3.2-dev \
libwxgtk-webview3.2-dev \
libglew-dev \
libgl1-mesa-dev \
libglu1-mesa-dev \
libnng-dev \
unixodbc-dev \
libgtk-3-dev \
libsecret-1-dev \
libpoppler-dev \
libpoppler-glib-dev \
libpoppler-cpp-dev \
\
qt6-base-dev \
qt6-base-dev-tools \
libqt6opengl6-dev \
libqt6core5compat6-dev \
libyaml-cpp-dev \
ruby \
ruby-dev \
\
xvfb \
xauth \
libxkbcommon-x11-0 \
libxcb-cursor0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-randr0 \
libxcb-render-util0 \
libxcb-xinerama0 \
libxcb-xfixes0
# KLayout's qmake-based build expects plain `qmake`
RUN ln -sf /usr/bin/qmake6 /usr/bin/qmake
############################################################################
FROM deps AS kicad-builder
ARG JOBS
COPY tools/kicad /src/kicad
RUN cmake -G Ninja -S /src/kicad -B /build/kicad \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DKICAD_SCRIPTING_WXPYTHON=ON \
-DKICAD_USE_OCC=ON \
-DKICAD_SPICE=ON \
-DKICAD_BUILD_QA_TESTS=OFF \
&& ninja -C /build/kicad -j"${JOBS}" \
&& DESTDIR=/install ninja -C /build/kicad -j"${JOBS}" install
############################################################################
# Official KiCad symbol/footprint libraries, pinned release tags. The fork
# is 9.99 (v10 dev line) but predates the .kicad_symdir format, so the v9
# libraries are the compatible set (.kicad_sym + ${KICAD9_*_DIR} tables).
# 3D models (multi-GB) are deliberately not shipped.
FROM ${UBUNTU_IMAGE} AS kicad-libs
ARG APT_SNAPSHOT
ARG KICAD_LIBS_TAG=9.0.9.1
RUN --mount=type=bind,source=docker/apt-snapshot,target=/run/apt-snapshot \
--mount=type=cache,id=adk-tools-apt-snapshot,target=/var/cache/apt-snapshot,sharing=locked \
sh /run/apt-snapshot/apt-snapshot.sh "$APT_SNAPSHOT" --no-install-recommends git ca-certificates \
&& git clone --depth 1 --branch "${KICAD_LIBS_TAG}" \
https://gitlab.com/kicad/libraries/kicad-symbols.git /libs/symbols \
&& git clone --depth 1 --branch "${KICAD_LIBS_TAG}" \
https://gitlab.com/kicad/libraries/kicad-footprints.git /libs/footprints \
&& rm -rf /libs/symbols/.git /libs/footprints/.git
############################################################################
# SG13G2 base-PDK KLayout slice: the SG13_dev PCell library (hyp_to_gds
# instantiates its via_stack for vias) + tech files + the DRC deck tree
# (tech/drc without its docs/, images/ and testing/), which packaging-flow's
# chiplet.drc reads below $PDK_ROOT; verify step 13 holds that the deck still
# finds every file it reads in what is left. Plus sg13cmos5l's layer sheet,
# the one ihp-sg13cmos5l file packaging-flow's technology table names (verify
# step check_tech_files reads every sheet that table names), and the technology
# file beside it, which packaging-flow's tests hold to sit next to each sheet. A few MB, not the multi-GB PDK; the sg13g2-models
# stage adds the ngspice files a mixed co-simulation case reaches. pycell4klayout-api and pypreprocessor are
# submodules of IHP-Open-PDK. pypreprocessor is cloned here at SG13G2_REF's gitlink.
# pycell4klayout-api is pinned on its own at PYCELL_API_REF (99f469aa),
# behind that PDK's gitlink (2ffdf79f), until a PDK pin change aligns the
# two with an A/B. The PDK's Apache-2.0 LICENSE ships with the slice.
FROM ${UBUNTU_IMAGE} AS sg13g2-pdk
ARG APT_SNAPSHOT
# IHP-Open-PDK dev 2026-09-07 + its two submodules, pinned as said above.
#
# Was efe8364 (dev 2026-05-22). Bumped because the image's PDK has to be the
# demo's PDK: the DRC decks, the sg13g2_io LEF/GDS and the stdcells all come
# from here, so a run in this image would not be reproducing that demo no
# matter how well every tool matched. 586 commits and about four months apart,
# a clean fast-forward (efe8364 is an ancestor).
#
# A sha, deliberately, and not the tip of a branch. The reproduction target is
# one lineage, and "latest remote" is not a pin: it names something different
# every day and silently makes two images that claim to be the same one.
# Verified reachable from the remote fetched below (on origin/dev), because a
# shallow fetch of an unreachable commit fails an hour into the build.
#
# This is a big bump and it is not yet known to be safe: the verify stage runs
# the ADK suite, the demo regen and the assembly DRC against these decks. If
# something moved in those 586 commits, the build is where it shows.
ARG SG13G2_REF=22f43352dd8219f9007eb659e422e0d5fe28c5fb
ARG PYCELL_API_REF=99f469aa348201536b2f3b55c69e58969bd4847b
ARG PYPREPROCESSOR_REF=cf1ff9bad0fb5338cf1c5b990b2b816b1ea01a64
RUN --mount=type=bind,source=docker/apt-snapshot,target=/run/apt-snapshot \
--mount=type=cache,id=adk-tools-apt-snapshot,target=/var/cache/apt-snapshot,sharing=locked \
sh /run/apt-snapshot/apt-snapshot.sh "$APT_SNAPSHOT" --no-install-recommends git ca-certificates \
&& git init -q /pdk \
&& git -C /pdk remote add origin https://github.com/IHP-GmbH/IHP-Open-PDK.git \
&& git -C /pdk fetch -q --depth 1 origin "${SG13G2_REF}" \
&& git -C /pdk checkout -q FETCH_HEAD -- \
LICENSE \
ihp-sg13g2/libs.tech/klayout/python/sg13g2_pycell_lib \
ihp-sg13g2/libs.tech/klayout/tech/sg13g2.lyt \
ihp-sg13g2/libs.tech/klayout/tech/sg13g2.lyp \
ihp-sg13g2/libs.tech/klayout/tech/sg13g2.map \
ihp-sg13cmos5l/libs.tech/klayout/tech/sg13cmos5l.lyp \
ihp-sg13cmos5l/libs.tech/klayout/tech/sg13cmos5l.lyt \
ihp-sg13g2/libs.tech/klayout/tech/drc \
&& rm -r /pdk/ihp-sg13g2/libs.tech/klayout/tech/drc/docs \
/pdk/ihp-sg13g2/libs.tech/klayout/tech/drc/images \
/pdk/ihp-sg13g2/libs.tech/klayout/tech/drc/testing \
&& git clone -q https://github.com/IHP-GmbH/pycell4klayout-api.git \
/pdk/ihp-sg13g2/libs.tech/klayout/python/pycell4klayout-api \
&& git -C /pdk/ihp-sg13g2/libs.tech/klayout/python/pycell4klayout-api \
checkout -q "${PYCELL_API_REF}" \
&& git clone -q https://github.com/IHP-GmbH/pypreprocessor.git \
/pdk/ihp-sg13g2/libs.tech/klayout/python/pypreprocessor \
&& git -C /pdk/ihp-sg13g2/libs.tech/klayout/python/pypreprocessor \
checkout -q "${PYPREPROCESSOR_REF}" \
# The .git directories go, so record what actually landed before they do, read
# back from each tree rather than echoed from the ARG. build.sh's manifest
# cannot see this PDK at all: it is fetched here, not a submodule, so without
# these files the image cannot say which PDK it carries and `adk-tools` prints
# a dash. Writing the ARG instead would read the same whether the fetch
# resolved to it or not.
&& git -C /pdk rev-parse FETCH_HEAD > /pdk/.shipped-ref \
&& git -C /pdk/ihp-sg13g2/libs.tech/klayout/python/pycell4klayout-api \
rev-parse HEAD \
> /pdk/ihp-sg13g2/libs.tech/klayout/python/pycell4klayout-api/.shipped-ref \
&& git -C /pdk/ihp-sg13g2/libs.tech/klayout/python/pypreprocessor \
rev-parse HEAD \
> /pdk/ihp-sg13g2/libs.tech/klayout/python/pypreprocessor/.shipped-ref \
&& test "$(cat /pdk/.shipped-ref)" = "${SG13G2_REF}" \
&& rm -rf /pdk/.git \
/pdk/ihp-sg13g2/libs.tech/klayout/python/pycell4klayout-api/.git \
/pdk/ihp-sg13g2/libs.tech/klayout/python/pypreprocessor/.git
############################################################################
# SG13G2 ngspice models: the files a mixed co-simulation case reaches for a
# transistor-level ihp-sg13g2 die, listed with their digests in
# sg13g2-models.sha256 (every file the named corner libraries include, in any
# section and at any depth, since ngspice reads a library whole; and the
# stdcell spice; ci/check_sg13g2_models.py recomputes that closure at
# SG13G2_REF). Fetched at the ref the slice stage shipped, so SG13G2_REF
# stays the one pin; exactly the listed files are fetched, and each is checked.
# A stage of their own for the GF180 models' reason: the tools stage takes them in
# a late layer.
FROM sg13g2-pdk AS sg13g2-models
COPY sg13g2-models.sha256 /sg13g2-models.sha256
RUN ref="$(cat /pdk/.shipped-ref)" \
&& git init -q /models \
&& git -C /models remote add origin https://github.com/IHP-GmbH/IHP-Open-PDK.git \
&& git -C /models fetch -q --depth 1 --filter=blob:none origin "${ref}" \
&& test "$(git -C /models rev-parse FETCH_HEAD)" = "${ref}" \
&& grep -v '^#' /sg13g2-models.sha256 | awk '{print $2}' \
| xargs -r git -C /models checkout -q FETCH_HEAD -- \
&& grep -v '^#' /sg13g2-models.sha256 | (cd /models && sha256sum -c --quiet) \
&& rm -rf /models/.git
############################################################################
# GF180MCU layer sheet, so a technology entry can name it and Chiplet Studio
# colours a GF180 die's layers. Not the PDK: no PCells or libraries, and no
# KLayout technology is registered from it; besides the sheet the image carries
# only the two ngspice files and the DRC and LVS decks the next stages add.
# Kept in the PDK's own
# layout (gf180mcuD/libs.tech/...) so GF180MCU_PDK_ROOT can point at a full
# checkout on a host instead.
FROM ${UBUNTU_IMAGE} AS gf180mcu-sheet
ARG APT_SNAPSHOT
# wafer-space/gf180mcu at the commit the example's GF180 die was built against
# (the pdk_version its build record names), reachable from main. A blobless
# fetch: the checkout pulls the two blobs it names, not the PDK. The sheet's
# digest is checked as well as the ref, since the ref alone would also accept a
# rewritten history.
ARG GF180MCU_REF=77cb86086e6bcded39e5e3f2d25ab234b6cdee65
ARG GF180MCU_LYP_SHA256=4d0e42a66b16f598bc4c401c193e60b8aeabc45bba7ff96d2f93e84cf6acb31f
RUN --mount=type=bind,source=docker/apt-snapshot,target=/run/apt-snapshot \
--mount=type=cache,id=adk-tools-apt-snapshot,target=/var/cache/apt-snapshot,sharing=locked \
sh /run/apt-snapshot/apt-snapshot.sh "$APT_SNAPSHOT" --no-install-recommends git ca-certificates \
&& git init -q /gf180mcu \
&& git -C /gf180mcu remote add origin https://github.com/wafer-space/gf180mcu.git \
&& git -C /gf180mcu fetch -q --depth 1 --filter=blob:none origin "${GF180MCU_REF}" \
&& git -C /gf180mcu checkout -q FETCH_HEAD -- \
LICENSE \
gf180mcuD/libs.tech/klayout/tech/gf180mcu.lyp \
&& git -C /gf180mcu rev-parse FETCH_HEAD > /gf180mcu/.shipped-ref \
&& test "$(cat /gf180mcu/.shipped-ref)" = "${GF180MCU_REF}" \
&& echo "${GF180MCU_LYP_SHA256} /gf180mcu/gf180mcuD/libs.tech/klayout/tech/gf180mcu.lyp" \
| sha256sum -c --quiet \
&& rm -rf /gf180mcu/.git
############################################################################
# GF180MCU ngspice models: the two files a mixed co-simulation case loads for a
# gf180mcuD die, design.ngspice and sm141064.ngspice (its typical section
# reaches nothing outside the file). Fetched at the ref the sheet stage
# shipped, so GF180MCU_REF stays the one pin, and checked by digest like the
# sheet. A stage of their own, so the tools stage takes them in a late layer and
# the layers it builds before that stay cached. No .git is kept: chiplet-cosim
# then records the models entry's revision as declared, unverified, and binds
# the files by sha256.
FROM gf180mcu-sheet AS gf180mcu-models
ARG GF180MCU_DESIGN_NGSPICE_SHA256=091cb530bf85160a1f07878fb81f789ca367d018991c8ab41a584cd1a85c6692
ARG GF180MCU_SM141064_NGSPICE_SHA256=6edba54d23b38a2c60f8ffdbaa3eb75a7568c615e4820808969bd1121b77b1aa
RUN ref="$(cat /gf180mcu/.shipped-ref)" \
&& git init -q /gf180mcu \
&& git -C /gf180mcu remote add origin https://github.com/wafer-space/gf180mcu.git \
&& git -C /gf180mcu fetch -q --depth 1 --filter=blob:none origin "${ref}" \
&& test "$(git -C /gf180mcu rev-parse FETCH_HEAD)" = "${ref}" \
&& git -C /gf180mcu checkout -q FETCH_HEAD -- \
gf180mcuD/libs.tech/ngspice/design.ngspice \
gf180mcuD/libs.tech/ngspice/sm141064.ngspice \
&& printf '%s %s\n' \
"${GF180MCU_DESIGN_NGSPICE_SHA256}" /gf180mcu/gf180mcuD/libs.tech/ngspice/design.ngspice \
"${GF180MCU_SM141064_NGSPICE_SHA256}" /gf180mcu/gf180mcuD/libs.tech/ngspice/sm141064.ngspice \
| sha256sum -c --quiet \
&& rm -rf /gf180mcu/.git
############################################################################
# GF180MCU DRC and LVS decks: gf180mcuD/libs.tech/klayout/tech/drc and
# tech/lvs at the ref the sheet stage shipped, so GF180MCU_REF stays the one
# pin. Each folder's testing/ is left out, as the sg13g2 deck's is (it is
# 251M of regression layouts the decks never read), so the shipped LVS tree is
# not the full tree a die's build record digests. Only the listed blobs are
# fetched. Each folder is checked by the digest of its shipped tree: the
# sha256 of `sha256sum` over its files, sorted by path, as written below. No
# file in either folder is a link, since a deck kind refuses one below its tree.
FROM gf180mcu-sheet AS gf180mcu-decks
ARG GF180MCU_DRC_TREE_SHA256=23044fc3905f9eca7f5e25d51b0deab231ef653265c6be79d9e78d7952288d4f
ARG GF180MCU_LVS_TREE_SHA256=56f55fda31bc915102d5d463ae8d6ceb79110d5abee0b3afd379e81835ee838d
RUN ref="$(cat /gf180mcu/.shipped-ref)" \
&& tech=gf180mcuD/libs.tech/klayout/tech \
&& git init -q /gf180mcu \
&& git -C /gf180mcu remote add origin https://github.com/wafer-space/gf180mcu.git \
&& git -C /gf180mcu fetch -q --depth 1 --filter=blob:none origin "${ref}" \
&& test "$(git -C /gf180mcu rev-parse FETCH_HEAD)" = "${ref}" \
&& git -C /gf180mcu ls-tree -r --name-only FETCH_HEAD -- "$tech/drc" "$tech/lvs" \
| grep -v -e "^$tech/drc/testing/" -e "^$tech/lvs/testing/" \
| xargs -r -d '\n' git -C /gf180mcu checkout -q FETCH_HEAD -- \
&& rm -rf /gf180mcu/.git \
&& test -z "$(find /gf180mcu/$tech/drc /gf180mcu/$tech/lvs ! -type d ! -type f)" \
&& for pair in "drc ${GF180MCU_DRC_TREE_SHA256}" "lvs ${GF180MCU_LVS_TREE_SHA256}"; do \
set -- $pair; \
got="$(cd "/gf180mcu/$tech/$1" && find . -type f -print0 | LC_ALL=C sort -z \
| xargs -0 sha256sum | sha256sum | cut -d' ' -f1)"; \
[ "$got" = "$2" ] || { echo "gf180mcu-decks: tech/$1 digests to $got, not $2" >&2; exit 1; }; \
done
############################################################################
# The gf180mcuD pieces a LibreLane build of a gf180mcuD die reads: the
# gf180mcu_fd_sc_mcu7t5v0 views and its three 3v30 liberty corners, the
# gf180mcu_fd_io views the PDK's librelane config globs (a glob with no match
# stops the config load, so they ship though no PDK pad is placed), the
# librelane, magic and netgen tech folders, the KLayout lyt and map, and
# SOURCES. Not the whole gf180mcuD: no 9t5v0, SRAM macros, 5 V liberty or
# deck test data. gf180mcu-pdk.sha256 lists every file by its sha256 at the
# ref the sheet stage shipped; only those blobs are fetched, and the stage
# fails on a missing file, other bytes or one more file. Read-only, so a run
# cannot write a file the list lacks into the tree. A fresh folder, so no
# file of the sheet's lands in it.
FROM gf180mcu-sheet AS gf180mcu-pdk
COPY gf180mcu-pdk.sha256 /gf180mcu-pdk.sha256
RUN ref="$(cat /gf180mcu/.shipped-ref)" \
&& git init -q /gf180mcu-pdk \
&& git -C /gf180mcu-pdk remote add origin https://github.com/wafer-space/gf180mcu.git \
&& git -C /gf180mcu-pdk fetch -q --depth 1 --filter=blob:none origin "${ref}" \
&& test "$(git -C /gf180mcu-pdk rev-parse FETCH_HEAD)" = "${ref}" \
&& cut -c67- /gf180mcu-pdk.sha256 \
| xargs -r -d '\n' git -C /gf180mcu-pdk checkout -q FETCH_HEAD -- \
&& rm -rf /gf180mcu-pdk/.git \
&& cd /gf180mcu-pdk \
&& sha256sum -c --strict --quiet /gf180mcu-pdk.sha256 \
&& [ "$(find gf180mcuD ! -type d | LC_ALL=C sort)" = "$(cut -c67- /gf180mcu-pdk.sha256)" ] \
&& test -z "$(find gf180mcuD ! -type d ! -type f)" \
&& chmod -R a-w gf180mcuD
############################################################################
# The tools: everything below /opt/adk-tools and /usr this repository does not
# write. build.sh tags it adk-tools:tools-<key>, the key ci/tools_key.py
# computes from the text above the thin-layer line and every file it reads,
# and builds it only when no image carries that key.
FROM deps AS tools
# Runtime-only apt, kept out of `deps` so the heavy builder caches survive:
# python3-tk the SG13G2 cni PCell API imports tkinter
# vim terminal editor for quick edits inside the container
# featherpad lightweight GUI text editor (Qt) for editing files under /work
# Editors are a deliberate runtime convenience: /work is the host-shared area, so
# users edit project files in-container without a second terminal on the host.
ARG APT_SNAPSHOT
RUN --mount=type=bind,source=docker/apt-snapshot,target=/run/apt-snapshot \
--mount=type=cache,id=adk-tools-apt-snapshot,target=/var/cache/apt-snapshot,sharing=locked \
sh /run/apt-snapshot/apt-snapshot.sh "$APT_SNAPSHOT" --no-install-recommends \
python3-tk vim featherpad
# Ecosystem discovery roots (env is the first link of every tool's discovery
# chain: env -> textvar -> sibling walk -> loud). PDK dirs carry their IHP
# repository names. Kept out of `deps` so env changes never invalidate the
# builder caches; the runtime and `verify` inherit them from the tools image.
# The last three are the flows' own: the staged chiplet-spec (the one the
# installed reader comes from), the interpreter packaging-flow runs the
# interposer_pnr engine legs under, and the klayout.db interpreter chiplet-flow's
# die-view observation runs under. Named rather than probed, as the Nix CLI shell
# does, so a run needs no -e for them.
ENV ADK_TOOLS=/opt/adk-tools \
ADK_ROOT=/opt/adk-tools/adk \
INTERPOSER_PDK_ROOT=/opt/adk-tools/OpenIntM4TM2 \
INTERCONNECT_PDK_ROOT=/opt/adk-tools/IHP-Interconnect-IntM4TM2 \
GDS_TO_KICAD_ROOT=/opt/adk-tools/gds_to_kicad \
CHIPLET_KICAD_PLUGIN_ROOT=/opt/adk-tools/chiplet_kicad_plugin \
PDK_ROOT=/opt/adk-tools/IHP-Open-PDK \
KICAD_CHIPLET_PYTHON=/opt/adk-tools/venv/bin/python3 \
CHIPLET_SPEC_ROOT=/opt/adk-tools/staging/chiplet-spec \
CHIPLET_FLOW_ENGINE_PYTHON=/opt/adk-tools/venv/bin/python3 \
CHIPLET_FLOW_PADMAP_PYTHON=/opt/adk-tools/venv/bin/python3
# Register the interposer KLayout technology (intm4tm2.lyt + .lyp under
# tech/) so `klayout` opens generated interposer/assembly GDS with named,
# colored layers. First path component = writable config home (run.sh sets
# HOME=/tmp, so this matches KLayout's default location). The interposer
# tree carries two macros: the TopMetal filler (autorun=false) and
# tech/pymacros/autorun.lym (autorun=true), which imports the intm4tm2 pycell
# library. Any run without -rx loads it; -b implies -rx and skips it. The
# sg13g2 PDK slice deliberately stays OFF this path -- its pycell autorun would need
# system-level psutil/tkinter and pops error dialogs in the GUI.
ENV KLAYOUT_PATH=/tmp/.klayout:/opt/adk-tools/OpenIntM4TM2/libs.tech/klayout
# Force Mesa's software renderer (llvmpipe) for every GUI. The container is run
# without a GPU device (run.sh does not pass /dev/dri), and remote X servers
# such as ThinLinc advertise GLX but cannot provide a DRI3 device. KiCad's GAL
# then probes the hardware path, storms pixman with invalid rectangles and
# segfaults the instant a GL canvas is built (the symbol-chooser preview is the
# usual trigger). llvmpipe is always available here (libgl1-mesa-dri) and gives
# every tool -- KiCad, Chiplet Studio, gds-to-kicad, KLayout -- a stable context.
# Override with `-e LIBGL_ALWAYS_SOFTWARE=0` if you pass a real GPU. NO_AT_BRIDGE
# silences the harmless at-spi accessibility-bus warning in headless sessions.
ENV LIBGL_ALWAYS_SOFTWARE=1 \
GALLIUM_DRIVER=llvmpipe \
NO_AT_BRIDGE=1
# KiCad fork (kicad, pcbnew, kicad-cli in /usr/bin; pcbnew python module in
# /usr/lib/python3/dist-packages -> headless plugin pipeline works)
COPY --from=kicad-builder /install/ /
# Standard libraries + their default global tables (the repos ship them);
# the env pins resolution regardless of KiCad's built-in defaults.
COPY --from=kicad-libs /libs/symbols/ /usr/share/kicad/symbols/
COPY --from=kicad-libs /libs/footprints/ /usr/share/kicad/footprints/
ENV KICAD9_SYMBOL_DIR=/usr/share/kicad/symbols \
KICAD9_FOOTPRINT_DIR=/usr/share/kicad/footprints
# Keep the full SYMBOL table; seed a small CURATED global FOOTPRINT table. The
# startup stall was footprint enumeration: the stock 155 libraries hold ~15k
# .kicad_mod re-read on every launch (HOME=/tmp keeps no fp-info-cache between
# runs). Symbols are cheap (223 files, ~40ms) so all stock symbol libraries
# (power: GND/VCC/VDD/..., Device, Connector, etc.) stay available. Footprints
# get only a useful basic set (mechanical + SMD discretes + a common header,
# ~1.5k footprints) so startup stays fast; the chiplet flow resolves interposer
# footprints from each project's own ${KIPRJMOD} library, and the full footprint
# libraries remain on disk under /usr/share/kicad/footprints (re-addable from
# Preferences > Manage Footprint Libraries).
RUN mkdir -p /usr/share/kicad/template \
&& cp /usr/share/kicad/symbols/sym-lib-table /usr/share/kicad/template/ \
&& { echo '(fp_lib_table'; echo ' (version 7)'; \
for lib in MountingHole TestPoint Fiducial Resistor_SMD Capacitor_SMD Inductor_SMD LED_SMD Diode_SMD Connector_PinHeader_2.54mm; do \
printf ' (lib (name "%s")(type "KiCad")(uri "${KICAD9_FOOTPRINT_DIR}/%s.pretty")(options "")(descr ""))\n' "$lib" "$lib"; \
done; \
echo ')'; } > /usr/share/kicad/template/fp-lib-table
# Also seed KiCad's common config so the "starting for the first time" setup
# wizard never appears. HOME=/tmp keeps no config between --rm runs. The fork's
# start wizard (common/startwizard) fires if ANY of three providers needs input:
# settings -> satisfied once kicad_common.json exists;
# libraries -> needs the sym, fp AND design-block global tables to be valid;
# privacy -> needs update_check_prompt + data_collection_prompt marked "seen".
# kicad-cli writes a portable kicad_common.json + kicad.json; flip the two privacy
# flags and add the (empty) design-block table, then bake all of it as templates
# for the entrypoint to copy into the runtime HOME alongside the lib tables.
RUN HOME=/tmp QT_QPA_PLATFORM=offscreen kicad-cli version >/dev/null 2>&1 \
&& sed -i 's/"update_check_prompt": false/"update_check_prompt": true/; \
s/"data_collection_prompt": false/"data_collection_prompt": true/' \
/tmp/.config/kicad/9.99/kicad_common.json \
&& cp /tmp/.config/kicad/9.99/kicad_common.json \
/tmp/.config/kicad/9.99/kicad.json \
/usr/share/kicad/template/ \
&& printf '(design_block_lib_table\n (version 7)\n)\n' \
> /usr/share/kicad/template/design-block-lib-table \
&& rm -rf /tmp/.config /tmp/.local /tmp/.cache
# SG13G2 base-PDK slice (PDK_ROOT): hyp_to_gds self-registers the SG13_dev
# PCell library from here, so vias are real via_stack PCells instead of the
# rectangle fallback.
COPY --from=sg13g2-pdk /pdk /opt/adk-tools/IHP-Open-PDK
# Layer sheets and stackups a technology entry names as ${VAR} paths. The
# SG13G2, interposer and interconnect sheets are in the PDK trees above, under
# the roots set earlier; these two roots are for what lives elsewhere: the GF180
# sheet, and the stackups Chiplet Studio ships under configs/stackups. Set here,
# not with the roots above, so the tools layers before this point stay cached.
COPY --from=gf180mcu-sheet /gf180mcu /opt/adk-tools/gf180mcu
ENV GF180MCU_PDK_ROOT=/opt/adk-tools/gf180mcu \
CHIPLET_STUDIO_ROOT=/opt/adk-tools/chiplet-studio
# Worker venv. --system-site-packages on purpose: the venv python then also
# sees pcbnew (kicad install) and wx (python3-wxgtk4.0), so one interpreter
# can drive the whole pipeline.
# The direct deps are exact-pinned to the known-good versions resolved by the
# prior green build, and docker/venv-constraints.txt holds what they pull in.
# Pinning only klayout left the behaviour-defining packages free to drift against
# live PyPI on a cache-cold rebuild, which could silently flip a verify-stage
# suite; bump a pin deliberately, in its own commit.
#
# KLAYOUT_PIP is the ecosystem's one KLayout version, not just this venv's.
# Testing a deck against a different KLayout than the one the image runs proves
# the deck works somewhere other than where it ships, so these agree with it and
# have to be bumped in the same change:
# chiplet-studio extern/klayout submodule tag (the in-tree build)
# IHP-Open-ADK .github/workflows/tests.yml (env KLAYOUT_VERSION)
# IHP-Open-ADK-docs docs/requirements.txt (klayout==)
# IHP-Open-ADK-docs docs/install/02_host.rst (the worker venv block)
# Chiplets-KiCad-Plugin plugins/chiplet_export/requirements.txt (the floor)
# assembly-lvs pyproject.toml at the staged ref (klayout>=0.30,<0.31)
ARG KLAYOUT_PIP=0.30.5
ARG PYYAML_PIP=6.0.3
ARG PYQT6_PIP=6.11.0
ARG JINJA2_PIP=3.1.6
ARG JSONSCHEMA_PIP=4.26.0
ARG PSUTIL_PIP=7.2.2
ARG PYTEST_PIP=9.1.0
# pytest's plugin manager, pinned with it: left free, a rebuild of this layer
# took a newer pluggy than the image before it ran.
ARG PLUGGY_PIP=1.6.0
# pytest -n N for the suites that run in this image; execnet, its one
# requirement, is in docker/venv-constraints.txt.
ARG PYTEST_XDIST_PIP=3.8.0
# assembly-lvs's one third-party requirement the pins above leave out. The
# staged trees install below the thin-layer line with no index, so every
# package they need has to be here already.
ARG Z3_SOLVER_PIP=4.15.8.0
# packaging-flow's console extra: with rich installed its run console draws
# progress and tables on stderr, and its console tests import it. The staged
# trees install below with no index, so it has to be here already.
ARG RICH_PIP=14.3.4
# docker/venv-constraints.txt pins the rest: every package the ones above
# pull in, at the version the image resolved before this file pinned them.
RUN --mount=type=bind,source=docker/venv-constraints.txt,target=/run/venv-constraints.txt \
python3 -m venv --system-site-packages /opt/adk-tools/venv \
&& /opt/adk-tools/venv/bin/pip install --no-cache-dir \
-c /run/venv-constraints.txt \
"klayout==${KLAYOUT_PIP}" \
"PyYAML==${PYYAML_PIP}" \
"PyQt6==${PYQT6_PIP}" \
"jinja2==${JINJA2_PIP}" \
"jsonschema==${JSONSCHEMA_PIP}" \
"psutil==${PSUTIL_PIP}" \
"pytest==${PYTEST_PIP}" \
"pluggy==${PLUGGY_PIP}" \
"pytest-xdist==${PYTEST_XDIST_PIP}" \
"z3-solver==${Z3_SOLVER_PIP}" \
"rich==${RICH_PIP}"
# The build backend the staged trees install with, below the thin-layer line,
# where nothing reaches the network: a wheelhouse pip finds it in, outside the
# venv, so the venv's own setuptools stays the system's. One version for
# every staged tree (assembly-lvs asks for >=77, the system has 68.1.2),
# checked by digest.
ARG BUILD_SETUPTOOLS=84.0.0
ARG BUILD_SETUPTOOLS_SHA256=51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670
RUN mkdir -p /opt/adk-tools/tools/wheels \
&& /opt/adk-tools/venv/bin/pip download --no-cache-dir --no-deps --only-binary=:all: \
--dest /opt/adk-tools/tools/wheels "setuptools==${BUILD_SETUPTOOLS}" \
&& echo "${BUILD_SETUPTOOLS_SHA256} /opt/adk-tools/tools/wheels/setuptools-${BUILD_SETUPTOOLS}-py3-none-any.whl" \
| sha256sum -c --quiet \
&& test "$(ls /opt/adk-tools/tools/wheels)" = "setuptools-${BUILD_SETUPTOOLS}-py3-none-any.whl"
# The co-simulation engine chiplet-cosim runs (staging.pin, "Co-simulation
# engine"): Icarus, cocotb 2.0.0 and a python3.13 with cocotb in its own
# site-packages, the Nix store paths its capability entry was shown on.
# build.sh checks nix/cosim-closure.txt against the host store, packs the
# listed paths into cosim-closure.tar and names the four pins as build
# arguments; ADD unpacks the archive under /nix/store. Nothing in the image
# runs Nix. COSIM_DEVSHELL is where chiplet-cosim finds the engine: the
# devshell's own store path (packages.cosim-devshell), whose bin/ links
# python3, iverilog and vvp into the env and Icarus, so a lock that pins the
# devshell by path replays here as on the host. /opt/adk-tools/cosim-devshell
# is a link to it; chiplet-cosim takes the realpath. None of them is on PATH;
# the venv stays the image's Python, and chiplet-cosim starts this one itself.
# A run.sh launch that mounts the host's store for OpenROAD mounts it over this
# /nix/store, and the engine then has to be in the host's store too (run.sh
# says when it is not). The verify stage's last leg holds the devshell to the
# entry.
ARG NIX_COSIM_IVERILOG_OUTPATH
ARG NIX_COSIM_COCOTB_OUTPATH
ARG NIX_COSIM_PYTHON_OUTPATH
ARG NIX_COSIM_DEVSHELL_OUTPATH
ADD cosim-closure.tar /
COPY nix/cosim-closure.txt /opt/adk-tools/nix/cosim-closure.txt
RUN for pin in "$NIX_COSIM_IVERILOG_OUTPATH" "$NIX_COSIM_COCOTB_OUTPATH" "$NIX_COSIM_PYTHON_OUTPATH" \
"$NIX_COSIM_DEVSHELL_OUTPATH"; do \
grep -qxF -- "$pin" /opt/adk-tools/nix/cosim-closure.txt \
|| { echo "co-simulation engine: build argument '$pin' is not a path nix/cosim-closure.txt lists (build with build.sh)" >&2; exit 1; }; \
done \
&& ln -sT "$NIX_COSIM_DEVSHELL_OUTPATH" /opt/adk-tools/cosim-devshell
ENV COSIM_DEVSHELL=/opt/adk-tools/cosim-devshell
# The mixed-signal engine (staging.pin, "Mixed-signal engine"): ngspice 45, an
# Icarus built with libvvp, and the six sg13g2 OSDI models compiled for generic
# x86-64. build.sh checks nix/mixsim-closure.txt against the host store (the
# engine's closure less what cosim-closure.tar already holds), checks the OSDI
# files' bytes against nix/sg13g2-osdi.sha256, packs the listed paths into
# mixsim-closure.tar and names the three pins as build arguments. MIXSIM_DEVSHELL
# is where a run finds the engine: bin/ngspice, bin/iverilog, bin/vvp,
# lib/libvvp.so, lib/ngspice (ngspice's own: the code models, and the ivlng shim
# and VPI module d_cosim loads) and osdi/, linked into those paths. None of it is
# on PATH. A run gives d_cosim libvvp.so and ivlng by these absolute paths, so no
# loader search is involved. The verify stage's mixed-signal leg runs both.
ARG NIX_MIXSIM_NGSPICE_OUTPATH
ARG NIX_MIXSIM_IVERILOG_OUTPATH
ARG NIX_SG13G2_OSDI_OUTPATH
ADD mixsim-closure.tar /
COPY nix/mixsim-closure.txt /opt/adk-tools/nix/mixsim-closure.txt
COPY nix/sg13g2-osdi.sha256 /opt/adk-tools/nix/sg13g2-osdi.sha256
RUN for pin in "$NIX_MIXSIM_NGSPICE_OUTPATH" "$NIX_MIXSIM_IVERILOG_OUTPATH" "$NIX_SG13G2_OSDI_OUTPATH"; do \
grep -qxF -- "$pin" /opt/adk-tools/nix/mixsim-closure.txt \
|| { echo "mixed-signal engine: build argument '$pin' is not a path nix/mixsim-closure.txt lists (build with build.sh)" >&2; exit 1; }; \
done \
&& mkdir -p /opt/adk-tools/mixsim-devshell/bin /opt/adk-tools/mixsim-devshell/lib \
&& ln -s "$NIX_MIXSIM_NGSPICE_OUTPATH/bin/ngspice" /opt/adk-tools/mixsim-devshell/bin/ngspice \
&& ln -s "$NIX_MIXSIM_IVERILOG_OUTPATH/bin/iverilog" /opt/adk-tools/mixsim-devshell/bin/iverilog \
&& ln -s "$NIX_MIXSIM_IVERILOG_OUTPATH/bin/vvp" /opt/adk-tools/mixsim-devshell/bin/vvp \
&& ln -s "$NIX_MIXSIM_IVERILOG_OUTPATH/lib/libvvp.so" /opt/adk-tools/mixsim-devshell/lib/libvvp.so \
&& ln -s "$NIX_MIXSIM_NGSPICE_OUTPATH/lib/ngspice" /opt/adk-tools/mixsim-devshell/lib/ngspice \
&& ln -s "$NIX_SG13G2_OSDI_OUTPATH/osdi" /opt/adk-tools/mixsim-devshell/osdi
ENV MIXSIM_DEVSHELL=/opt/adk-tools/mixsim-devshell
# The gf180mcuD models a mixed case loads (the gf180mcu-models stage), beside
# the GF180 sheet in the PDK's own layout. chiplet-cosim reads a gf180mcuD
# models entry's root from COSIM_GF180_ROOT and from nothing else; unset, such
# an entry reads NOT_RUN tool_missing. Copied here, not with the sheet, so the
# tools layers before this point stay cached.
COPY --from=gf180mcu-models /gf180mcu/gf180mcuD/libs.tech/ngspice /opt/adk-tools/gf180mcu/gf180mcuD/libs.tech/ngspice
ENV COSIM_GF180_ROOT=/opt/adk-tools/gf180mcu/gf180mcuD
# The ihp-sg13g2 models a mixed case reaches (the sg13g2-models stage), into
# the slice, since chiplet-cosim reads an ihp-sg13g2 models entry's root as
# $PDK_ROOT/ihp-sg13g2. Such an entry names its OSDI files under that root too
# (libs.tech/ngspice/osdi), and the model walk refuses one that resolves
# outside the PDK installation, so a link into the mixed-signal engine's store
# path would not do: the four the case loads are copied from there and checked
# against nix/sg13g2-osdi.sha256 again. Late, for the GF180 models' reason.
COPY --from=sg13g2-models /models/ihp-sg13g2 /opt/adk-tools/IHP-Open-PDK/ihp-sg13g2
RUN cd /opt/adk-tools/IHP-Open-PDK/ihp-sg13g2/libs.tech/ngspice \
&& mkdir osdi \
&& grep -E ' (psp103|psp103_nqs|r3_cmc|mosvar)\.osdi$' /opt/adk-tools/nix/sg13g2-osdi.sha256 \
| sed 's| | osdi/|' > /tmp/pdk-osdi.sha256 \
&& test "$(wc -l < /tmp/pdk-osdi.sha256)" -eq 4 \
&& for name in psp103 psp103_nqs r3_cmc mosvar; do \
cp -L "/opt/adk-tools/mixsim-devshell/osdi/${name}.osdi" osdi/ || exit 1; \
done \
&& sha256sum -c --quiet /tmp/pdk-osdi.sha256 \
&& rm /tmp/pdk-osdi.sha256
# The waveform viewers (staging.pin, "Waveform viewers"): GTKWave 3.3.121 for VCD
# and FST, GAW for ngspice's raw files, and Xschem, which draws a mixed run's
# analog waves from a sheet that loads its raw file. build.sh checks
# nix/viewer-closure.txt against the host store (the viewers' closure less what
# the engines' archives already hold), packs the listed paths into
# viewer-closure.tar and names the three pins as build arguments. Unlike the
# engines they are on PATH: /usr/local/bin/gtkwave, gaw and xschem link into
# the pins. Xschem reads its rcfile and symbol library from XSCHEM_SHAREDIR,
# its own share/xschem through /opt/adk-tools/xschem. A run.sh launch that mounts the host's store for OpenROAD hides
# this copy, and run.sh says when the host's store lacks them. The verify
# stage's viewer leg opens a waveform in each, headless.
ARG NIX_GTKWAVE_OUTPATH
ARG NIX_GAW_OUTPATH
ARG NIX_XSCHEM_OUTPATH
ADD viewer-closure.tar /
COPY nix/viewer-closure.txt /opt/adk-tools/nix/viewer-closure.txt
RUN for pin in "$NIX_GTKWAVE_OUTPATH" "$NIX_GAW_OUTPATH" "$NIX_XSCHEM_OUTPATH"; do \
grep -qxF -- "$pin" /opt/adk-tools/nix/viewer-closure.txt \
|| { echo "viewers: build argument '$pin' is not a path nix/viewer-closure.txt lists (build with build.sh)" >&2; exit 1; }; \
done \
&& ln -s "$NIX_GTKWAVE_OUTPATH/bin/gtkwave" /usr/local/bin/gtkwave \
&& ln -s "$NIX_GAW_OUTPATH/bin/gaw" /usr/local/bin/gaw \
&& ln -s "$NIX_XSCHEM_OUTPATH/bin/xschem" /usr/local/bin/xschem \
&& ln -sT "$NIX_XSCHEM_OUTPATH" /opt/adk-tools/xschem
ENV XSCHEM_SHAREDIR=/opt/adk-tools/xschem/share/xschem
# The Verilog-AMS engine (staging.pin, "Verilog-AMS engine"): gnucap and
# modelgen-verilog in one prefix, and the stdenv's GCC that builds a run's
# modelgen output into a plugin. build.sh checks nix/vams-closure.txt against
# the host store (the devshell's closure less what the other archives already
# hold), packs the listed paths into vams-closure.tar and names the two pins as
# build arguments. VAMS_DEVSHELL is where a run finds the engine: the
# devshell's own store path (packages.vams-devshell), whose bin/ links gnucap,
# gnucap-conf, gnucap-mg-vams, gnucap-modelgen, g++ and c++, so a lock that
# pins the devshell by path replays here as on the host.
# /opt/adk-tools/vams-devshell is a link to it. None of it is on PATH. gnucap
# finds libgnucap.so by its rpath and its plugins on the path it was built with,
# so a run sets neither LD_LIBRARY_PATH nor GNUCAP_PLUGPATH. The verify stage's
# Verilog-AMS leg builds a model with that g++ and solves a deck with it.
ARG NIX_VAMS_GNUCAP_OUTPATH
ARG NIX_VAMS_DEVSHELL_OUTPATH
ADD vams-closure.tar /
COPY nix/vams-closure.txt /opt/adk-tools/nix/vams-closure.txt
RUN for pin in "$NIX_VAMS_GNUCAP_OUTPATH" "$NIX_VAMS_DEVSHELL_OUTPATH"; do \
grep -qxF -- "$pin" /opt/adk-tools/nix/vams-closure.txt \
|| { echo "Verilog-AMS engine: build argument '$pin' is not a path nix/vams-closure.txt lists (build with build.sh)" >&2; exit 1; }; \
done \
&& ln -sT "$NIX_VAMS_DEVSHELL_OUTPATH" /opt/adk-tools/vams-devshell
ENV VAMS_DEVSHELL=/opt/adk-tools/vams-devshell
# The gf180mcuD DRC and LVS decks (the gf180mcu-decks stage), beside the sheet
# and the models in the one GF180 tree. A deck kind finds a die's deck at
# $PDK_ROOT/<pdk>/libs.tech/klayout, so $PDK_ROOT/gf180mcuD is a link to that
# tree: no root moves, and the kind refuses links only below the deck's tree.
# The verify stage's GF180 deck leg holds that. Late in the stage, so the tools
# layers before this point stay cached.
COPY --from=gf180mcu-decks /gf180mcu/gf180mcuD/libs.tech/klayout/tech/drc /opt/adk-tools/gf180mcu/gf180mcuD/libs.tech/klayout/tech/drc
COPY --from=gf180mcu-decks /gf180mcu/gf180mcuD/libs.tech/klayout/tech/lvs /opt/adk-tools/gf180mcu/gf180mcuD/libs.tech/klayout/tech/lvs
# The gf180mcuD pieces a LibreLane build reads (the gf180mcu-pdk stage), in the
# same tree, so GF180MCU_PDK_ROOT is the root a gf180mcuD die build passes with
# --manual-pdk. One COPY per folder or file of its own: none writes a path the
# sheet, the models or the decks ship. The verify stage's GF180 PDK leg holds
# the tree to gf180mcu-pdk.sha256.
COPY --from=gf180mcu-pdk /gf180mcu-pdk/gf180mcuD/SOURCES /opt/adk-tools/gf180mcu/gf180mcuD/SOURCES
COPY --from=gf180mcu-pdk /gf180mcu-pdk/gf180mcuD/libs.ref /opt/adk-tools/gf180mcu/gf180mcuD/libs.ref
COPY --from=gf180mcu-pdk /gf180mcu-pdk/gf180mcuD/libs.tech/librelane /opt/adk-tools/gf180mcu/gf180mcuD/libs.tech/librelane
COPY --from=gf180mcu-pdk /gf180mcu-pdk/gf180mcuD/libs.tech/magic /opt/adk-tools/gf180mcu/gf180mcuD/libs.tech/magic
COPY --from=gf180mcu-pdk /gf180mcu-pdk/gf180mcuD/libs.tech/netgen /opt/adk-tools/gf180mcu/gf180mcuD/libs.tech/netgen
COPY --from=gf180mcu-pdk /gf180mcu-pdk/gf180mcuD/libs.tech/klayout/tech/gf180mcu.lyt /opt/adk-tools/gf180mcu/gf180mcuD/libs.tech/klayout/tech/gf180mcu.lyt
COPY --from=gf180mcu-pdk /gf180mcu-pdk/gf180mcuD/libs.tech/klayout/tech/gf180mcu.map /opt/adk-tools/gf180mcu/gf180mcuD/libs.tech/klayout/tech/gf180mcu.map
RUN ln -sT /opt/adk-tools/gf180mcu/gf180mcuD "$PDK_ROOT/gf180mcuD"
# What the tools image holds, by version: every dpkg package and every Python
# distribution the venv sees. Last in the stage, so they name what shipped.
RUN mkdir -p /opt/adk-tools/tools \
&& dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort \
> /opt/adk-tools/tools/apt.lock \
&& /opt/adk-tools/venv/bin/python3 -m pip freeze --all --disable-pip-version-check \
| LC_ALL=C sort > /opt/adk-tools/tools/pip.lock
#### thin layer: below this line, our code on the tools image (ci/check_thin_layer.py) ####
# Nothing below reaches the network but studio-builder (its CMake fetches
# its pinned dependencies) and nothing installs from apt; every RUN of the
# runtime stage carries --network=none. ci/check_thin_layer.py holds both.
############################################################################
# On the tools image, which carries deps' build packages, so a build below the
# line needs nothing but that image. A new tools image rebuilds it.
FROM ${TOOLS_IMAGE} AS studio-builder
ARG JOBS
# Build at the final runtime path: CMake bakes CONFIGS_DIR (absolute) into the
# binary, and ctest metadata in build/ keeps working in the verify stage.
#
# KLayout first, from its own tree alone, so a chiplet-studio commit that leaves
# extern/klayout as it was reuses this layer instead of rebuilding it. The full
# tree is copied over it below; .dockerignore keeps bin-release/ and
# build-release/ out of the context, so that copy leaves the build in place.
COPY tools/chiplet-studio/extern/klayout /opt/adk-tools/chiplet-studio/extern/klayout
WORKDIR /opt/adk-tools/chiplet-studio
# gtest_discover_tests executes the test binary at build time; make the
# in-tree KLayout libs resolvable for it.
ENV LD_LIBRARY_PATH=/opt/adk-tools/chiplet-studio/extern/klayout/bin-release
# -rpath names the folder the binary and its libraries ship in, so the klayout
# binary loads its own libraries with no LD_LIBRARY_PATH: the tool identity
# table names that binary, and packaging-flow runs it from there in an
# environment it builds from nothing. build.sh's own default (-bin) is unset
# when it picks RPATH, so without the flag the binaries carry none.
#
# The same sources give the same bytes. KLayout's version.sh stamps the build
# day into the binaries (`date "+%Y-%m-%d"`, its prg_date), and the GNU build
# id hashes the output, so a rebuild on another day moved both and with them
# the sha256 the tool identity table keys on. KLAYOUT_SOURCE_DATE_EPOCH is the
# KLayout commit's time, which build.sh reads from the submodule. A `date`
# placed first on PATH for this RUN answers exactly the call version.sh makes
# from that time and refuses any other call, so a build step that starts
# calling date differently fails here instead of getting a frozen value. rcc
# takes the same time in place of the files' mtimes. Not named
# SOURCE_DATE_EPOCH as a build argument: BuildKit would also stamp the image's
# created time with it.
ARG KLAYOUT_SOURCE_DATE_EPOCH
RUN case "$KLAYOUT_SOURCE_DATE_EPOCH" in \
''|*[!0-9]*) echo "KLAYOUT_SOURCE_DATE_EPOCH is '$KLAYOUT_SOURCE_DATE_EPOCH'; build.sh passes the KLayout commit's time" >&2; exit 1 ;; \
esac \
&& mkdir /tmp/klayout-date \
&& printf '%s\n' '#!/bin/sh' \
'if [ "$#" -ne 1 ] || [ "$1" != "+%Y-%m-%d" ]; then' \
' echo "date: the KLayout build answers only version.sh (date +%Y-%m-%d), not: date $*" >&2' \
' exit 1' \
'fi' \
'exec /bin/date -u -d "@$SOURCE_DATE_EPOCH" "+%Y-%m-%d"' > /tmp/klayout-date/date \
&& chmod 0755 /tmp/klayout-date/date \
&& cd extern/klayout \
&& PATH=/tmp/klayout-date:$PATH \
SOURCE_DATE_EPOCH="$KLAYOUT_SOURCE_DATE_EPOCH" \
QT_RCC_SOURCE_DATE_OVERRIDE="$KLAYOUT_SOURCE_DATE_EPOCH" \
./build.sh -j"${JOBS}" -without-qtbinding \
-rpath /opt/adk-tools/chiplet-studio/extern/klayout/bin-release \
&& rm -r /tmp/klayout-date
COPY tools/chiplet-studio /opt/adk-tools/chiplet-studio
RUN mkdir -p build && cd build \
&& cmake .. -DKLAYOUT_BUILD_DIR=/opt/adk-tools/chiplet-studio/extern/klayout/bin-release \
&& make -j"${JOBS}"
############################################################################
FROM ${TOOLS_IMAGE} AS runtime
# Point the built image at its source repo (org.opencontainers.image.source) so
# a locally-built image links back to the code it was built from.
LABEL org.opencontainers.image.source=https://github.com/IHP-GmbH/ADK-Tools \
org.opencontainers.image.description="Heterogeneous integration flow: KiCad fork, Chiplet Studio, PDKs, assembly DRC -- pre-wired"
# The tools image is the one docker/apt.lock and docker/pip.lock record: its
# own locks (the tools stage's last step) against the tracked ones, first, so
# a tools image built on another day, or for another key, stops here. A new
# tools image moves both files in the commit that moves its key.
RUN --network=none \
--mount=type=bind,source=docker/apt.lock,target=/run/locks/apt.lock \
--mount=type=bind,source=docker/pip.lock,target=/run/locks/pip.lock \
diff -u /run/locks/apt.lock /opt/adk-tools/tools/apt.lock \
&& diff -u /run/locks/pip.lock /opt/adk-tools/tools/pip.lock
# Chiplet Studio: binary + configs + embedded-python module (PYTHON_MODULE_DIR
# is baked as build/python) + the in-tree KLayout (libs + klayout CLI)
COPY --from=studio-builder /opt/adk-tools/chiplet-studio/build/chiplet-studio /opt/adk-tools/chiplet-studio/build/chiplet-studio
COPY --from=studio-builder /opt/adk-tools/chiplet-studio/build/python /opt/adk-tools/chiplet-studio/build/python
COPY --from=studio-builder /opt/adk-tools/chiplet-studio/configs /opt/adk-tools/chiplet-studio/configs
COPY --from=studio-builder /opt/adk-tools/chiplet-studio/extern/klayout/bin-release /opt/adk-tools/chiplet-studio/extern/klayout/bin-release
# Python tools + PDKs (data) as siblings under /opt/adk-tools, mirroring the
# development layout the discovery walk expects.
COPY tools/chiplet_kicad_plugin /opt/adk-tools/chiplet_kicad_plugin
COPY tools/gds_to_kicad /opt/adk-tools/gds_to_kicad
COPY tools/adk /opt/adk-tools/adk
COPY tools/OpenIntM4TM2 /opt/adk-tools/OpenIntM4TM2
COPY tools/IHP-Interconnect-IntM4TM2 /opt/adk-tools/IHP-Interconnect-IntM4TM2
# The interposer PDK's label commit: stamped from the gitlink and read back by
# the PDK's own reader (ci/check_interposer_label.py says why the ARG is the
# copied tree's commit). No default: a build that does not pass it fails.
ARG INTERPOSER_PIN
RUN --network=none \
--mount=type=bind,source=ci/check_interposer_label.py,target=/run/check_interposer_label.py \
/opt/adk-tools/venv/bin/python3 -B /run/check_interposer_label.py \
--tree /opt/adk-tools/OpenIntM4TM2 --pin "${INTERPOSER_PIN}"
# No examples here: the runtime image carries no demo. The person copies it
# from the adk-tools clone they ran run.sh from (adk-example says how), and the
# verify stage COPYs examples/ for its own checks.
# Sources that cannot be submodules, staged by commit into staging/ by
# stage-sources.sh. Their shas are in manifest.json under "staged", kept apart
# from "tools" because a submodule entry is reproducible from this repository
# alone and one of these is not.
#
# The .git directories come along deliberately; .dockerignore carries the
# exception that keeps them, with the reason.
#
# They arrive owned by root, and a container run as the host user
# (--user uid:gid, which is how a package acceptance runs) then has git refuse
# every staged checkout as "dubious ownership". packaging-flow's identity probe
# reads that as the package's commit being unavailable, and `packaging-flow
# verify` fails its full tier on it. So each tree is declared safe system-wide.
# One entry per tree, not a glob: the git in this image (2.43) does not honour a
# trailing `/opt/adk-tools/staging/*` (measured in this recipe's image). Written
# ahead of the COPY lines so a staging bump never rebuilds it; a tree added
# there needs its entry here, and the verify stage refuses a staged tree that
# does not read back as a non-owner.
RUN --network=none for tree in assembly-lvs interposer-pnr packaging-flow chiplet-flow chiplet-cosim chiplet-spec; do \
git config --system --add safe.directory "/opt/adk-tools/staging/$tree" || exit 1; \
done
COPY staging/assembly-lvs /opt/adk-tools/staging/assembly-lvs
COPY staging/interposer-pnr /opt/adk-tools/staging/interposer-pnr
COPY staging/packaging-flow /opt/adk-tools/staging/packaging-flow
COPY staging/chiplet-flow /opt/adk-tools/staging/chiplet-flow
COPY staging/chiplet-cosim /opt/adk-tools/staging/chiplet-cosim
COPY staging/chiplet-spec /opt/adk-tools/staging/chiplet-spec
# EDITABLE, and that is a requirement rather than a preference. packaging-flow
# records the identity of every package it dispatches by running
# `git rev-parse HEAD` on the directory find_spec resolves to. A regular install
# resolves to site-packages, where there is no repository, and the identity is
# then recorded as "unavailable: not a git checkout" -- which `packaging-flow
# verify` refuses at its full tier. The image would reproduce a run and be
# unable to demonstrate it with the flow's own verifier.
#
# --no-deps on the three that have already-satisfied requirements would be the
# wrong economy: their pins are what the venv has to agree with, and a silent
# disagreement is exactly what pip refuses to install for. Let it resolve, and
# let it fail here if a pin above stops satisfying one of them.
#
# With no index: every requirement has to be in the tools image's venv
# already, and the build backend comes from its wheelhouse. A staged tree that
# starts needing a package the tools do not carry fails here, by name; the
# package goes into the tools stage, pinned.
#
# chiplet-spec installs from reference/python, not from its root, and the whole
# repository is staged anyway because the package needs what is ABOVE that
# directory: the canonical schemas/ at the repository root. setup.py copies them
# into the package while building a wheel, and in a checkout the reader reads
# them from that same root directory instead. Editable keeps the checkout, so
# the root copy is the one that answers -- which is another reason the tree
# staged for this entry cannot be trimmed to the Python subdirectory. The
# system description is a second project in the same checkout, installed from
# description/.
RUN --network=none /opt/adk-tools/venv/bin/pip install --no-cache-dir --no-index \
--find-links /opt/adk-tools/tools/wheels --disable-pip-version-check \
-e /opt/adk-tools/staging/chiplet-spec/description \
-e /opt/adk-tools/staging/chiplet-flow \
-e /opt/adk-tools/staging/packaging-flow \
-e /opt/adk-tools/staging/chiplet-cosim \
-e /opt/adk-tools/staging/interposer-pnr \
-e /opt/adk-tools/staging/assembly-lvs \
-e "/opt/adk-tools/staging/chiplet-spec/reference/python[strict]" \
&& /opt/adk-tools/venv/bin/pip check
# Plugins visible to the KiCad GUI for every user. The repository is a monorepo
# of plugins, so its root is not a plugin package: KiCad scans this folder for
# packages that register an ActionPlugin, and linking the root would give it a
# directory with no __init__.py and no plugin at all. Link each package, which
# is also what the plugin README prescribes ("the specific plugin directory,
# not the whole repository").
RUN --network=none mkdir -p /usr/share/kicad/scripting/plugins \
&& ln -s /opt/adk-tools/chiplet_kicad_plugin/plugins/chiplet_export \
/usr/share/kicad/scripting/plugins/chiplet_export \
&& ln -s /opt/adk-tools/chiplet_kicad_plugin/plugins/resizer_passive_elements \
/usr/share/kicad/scripting/plugins/resizer_passive_elements
# Command wrappers + interactive-shell banner
COPY bin/ /usr/local/bin/
RUN --network=none chmod +x /usr/local/bin/* \
&& echo '[ -n "$PS1" ] && echo "adk-tools: heterogeneous integration flow image. Run \`adk-tools\` for the tool list."' >> /etc/bash.bashrc
# Version manifest (generated by build.sh, passed base64 to survive quoting)
ARG MANIFEST_B64=e30K
RUN --network=none echo "${MANIFEST_B64}" | base64 -d > /opt/adk-tools/manifest.json
# The die views (staging.pin, "Die views"): the records of the built
# dies, which stage-die-views.sh copied into die-views/ and held to
# die-views.sha256, whose sha256 build.sh passes as DIE_VIEWS_SHA256. Checked
# here, where users get them: exactly the listed files with the listed bytes and
# no symlink, each record bound to its directory's layout sha256, and the
# manifest's die_views entry naming this listing and these directories. Outside
# staging/ and every example's inputs, so no staged tree's identity moves. A
# run finds a die's views at $ADK_DIE_VIEWS/<layout sha256>/ and copies them;
# a die with no directory there has none.
ARG DIE_VIEWS_SHA256
COPY die-views.sha256 /opt/adk-tools/die-views.sha256
COPY ci/check_die_views.py /opt/adk-tools/ci/check_die_views.py
COPY die-views/ /opt/adk-tools/die-views/
RUN --network=none /opt/adk-tools/venv/bin/python3 -B /opt/adk-tools/ci/check_die_views.py \
--listing /opt/adk-tools/die-views.sha256 --root /opt/adk-tools/die-views \
--expect-sha256 "$DIE_VIEWS_SHA256" --manifest /opt/adk-tools/manifest.json
ENV ADK_DIE_VIEWS=/opt/adk-tools/die-views
# The die flow whose plans a system.chiplet builds entry reads is the staged
# chiplet-flow, so an entry that names ${CHIPLET_FLOW_ROOT} for them needs no
# host path; the entry's run folder is the project's own, under /work. Here,
# below the thin layer, so the tools image's key stays.
ENV CHIPLET_FLOW_ROOT=/opt/adk-tools/staging/chiplet-flow
# The pin set this image is built from, where a staged tree's own reader looks
# for it: under the umbrella directory its checkout sits in, as
# adk-tools/ci/pin-set.json. interposer-pnr's tools manifest reads its pinned
# versions there, and packaging-flow's die flow door its die flow commit.
# Read-only, and the bytes build.sh labels as org.ihp.adk-tools.pin-set; the
# verify stage holds the file to that digest. Not a git checkout, so it takes no
# safe.directory entry and no manifest "staged" row.
COPY --chmod=0444 ci/pin-set.json /opt/adk-tools/staging/adk-tools/ci/pin-set.json
# The same pin set, and the resource guard, under the image's own root: a
# reader given ADK_TOOLS_ROOT=/opt/adk-tools finds adk-tools' ci/ there.
# packaging-flow's conftest reads ci/pin-set.json when it is imported and runs
# ci/resource_guard.py, so without them its suite stops at collection. The
# guard is stdlib Python and is run, never imported; leg 0a holds both files.
COPY --chmod=0444 ci/pin-set.json ci/resource_guard.py /opt/adk-tools/ci/
# The die build's tools: OpenROAD, LibreLane, its interpreter and the sg13g2