Graphs

Understanding how all the pieces in this project fit together might be daunting for newcomers. Fortunately, there is a map for helping maintainers and contributors traveling through the ecosystem. Subdir doc/graph/ contains the sources of directed graphs, where the relations between workflows, dockerfiles, images and tests are shown.

(Graphviz)’s digraph format is used, hence, graphs can be rendered to multiple image formats. The output shown in Fig. 2 describes which images are created in each map. See the details in the figure corresponding to the name of the subgraph: Base (Fig. 3), Sim (Fig. 4), Synth (Fig. 5), Impl (Fig. 6), Formal (Fig. 7), ASIC (Fig. 8), and F4PGA (Fig. 9). Multiple colours and arrow types are used for describing different dependency types. All of those are explained in the legend: Fig. 10.

Important

These graphs represent a single collection of images (the virtual aggregation of others). In practice, some tools might be missing in some collections. For instance, a tool might be available in Debian Buster based containers, but not in CentOS 7. That info is not tracked in the graphs yet. Please, see whether a dockerfile exists in the corresponding subdir.

# Authors:
#   Unai Martinez-Corral
#     <umartinezcorral@antmicro.com>
#     <unai.martinezcorral@ehu.eus>
#
# Copyright Unai Martinez-Corral
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

digraph G {

  #splines=polyline; #curved
  newrank=true;

  # Maps

  { node [shape=point]
    m_base
    m_synth
    m_sim
    m_impl
    m_formal
    m_asic
    m_f4pga
  }

  # Images

  { node [shape=cylinder]
    "build/base"
    "build/build"
    "build/dev"
    { node [color=limegreen, fontcolor=limegreen]
      "apicula"
      "arachne-pnr"
      "ghdl"
      "ghdl/llvm"
      "gtkwave"
      "icestorm"
      "irsim"
      "iverilog"
      "klayout"
      "magic"
      "netgen"
      "nextpnr/generic"
      "nextpnr/ice40"
      "nextpnr/nexus"
      "nextpnr/ecp5"
      "nextpnr"
      "nvc"
      "openfpgaloader"
      "openroad"
      "openroad/gui"
      "prjoxide"
      "prjtrellis"
      "verible"
      "verilator"
      "vtr"
      "xschem"
      "xyce"
      "yosys"
    }
    { node [color=mediumblue, fontcolor=mediumblue]
      "pkg/apicula"
      "pkg/arachne-pnr"
      "pkg/boolector"
      "pkg/cvc"
      "pkg/ghdl"
      "pkg/ghdl/llvm"
      "pkg/ghdl-yosys-plugin"
      "pkg/gtkwave"
      "pkg/icestorm"
      "pkg/irsim"
      "pkg/iverilog"
      "pkg/klayout"
      "pkg/magic"
      "pkg/netgen"
      "pkg/nextpnr/ice40"
      "pkg/nextpnr/nexus"
      "pkg/nextpnr/ecp5"
      "pkg/nextpnr/generic"
      "pkg/nvc"
      "pkg/openfpgaloader"
      "pkg/openroad"
      "pkg/openroad/gui"
      "pkg/osvb"
      "pkg/pono"
      "pkg/prjoxide"
      "pkg/prjtrellis"
      "pkg/superprove"
      "pkg/sby"
      "pkg/yices2"
      "pkg/yosys"
      "pkg/verible"
      "pkg/verilator"
      "pkg/vtr"
      "pkg/xschem"
      "pkg/xyce"
      "pkg/z3"
    }
    { node [color=brown, fontcolor=brown]
      "ghdl/yosys"
      "formal/min"
      "formal"
      "formal/all"
      "nextpnr/icestorm"
      "nextpnr/prjoxide"
      "nextpnr/prjtrellis"
      "impl"
      "impl/ice40"
      "impl/nexus"
      "impl/ecp5"
      "impl/icestorm"
      "impl/prjoxide"
      "impl/prjtrellis"
      "impl/generic"
      "impl/pnr"
      "prog"
      "sim"
      "sim/osvb"
      "sim/scipy-slim"
      "sim/scipy"
      "sim/octave-slim"
      "sim/octave"
      "sim/octave/gnuplot"
      "conda"
      "conda/f4pga/xc7/toolchain"
      "conda/f4pga/xc7/a50t"
      "conda/f4pga/xc7/a100t"
      "conda/f4pga/xc7/a200t"
      "conda/f4pga/xc7/z010"
      "conda/f4pga/xc7/z020"
      "conda/f4pga/xc7"
      "conda/f4pga/eos-s3"
      "magic/irsim"
    }
  }

  # External images

  { node [shape=cylinder, color=orange, fontcolor=orange] rank=same
    "BASE IMAGE"
    "scratch"
    "ghdl/pkg:bullseye-mcode"
    "ghdl/pkg:bullseye-llvm-9"
  }

  # Dependencies

  "BASE IMAGE" -> m_base;

  {
    "scratch"
    "ghdl/pkg:bullseye-mcode"
    "ghdl/pkg:bullseye-llvm-9"
    "build/base"
    "build/build"
  } -> m_synth;

  {
    "build/build"
    "build/base"
    "scratch"
    "ghdl/llvm"
  } -> m_sim;

  {
    "scratch"
    "build/build"
    "build/base"
    "build/dev"
    "ghdl/yosys"
  } -> m_impl;

  {
    "scratch"
    "build/base"
    "build/build"
    "ghdl/yosys"
   } -> m_formal;

  {
    "scratch"
    "build/dev"
    "build/build"
    "build/base"
  } -> m_asic;

  {
    "build/base"
  } -> m_f4pga;

  # Generated images

  subgraph cluster_base {
    label = "Base";
    m_base -> {
      "build/base"
      "build/build"
      "build/dev"
    } [style=dotted];
  }

  subgraph cluster_synth {
    label = "Synth";
    m_synth -> {
      "ghdl"
      "ghdl/llvm"
      "pkg/ghdl"
      "pkg/ghdl/llvm"
      "pkg/ghdl-yosys-plugin"
      "ghdl/yosys"
      "pkg/yosys"
      "yosys"
    }
  }

  subgraph cluster_sim {
    label = "Sim";
    m_sim -> {
      "gtkwave"
      "iverilog"
      "nvc"
      "verilator"
      "xschem"
      "xyce"
      "sim"
      "sim/osvb"
      "sim/scipy-slim"
      "sim/scipy"
      "sim/octave-slim"
      "sim/octave"
      "sim/octave/gnuplot"
      "pkg/osvb"
      "pkg/gtkwave"
      "pkg/iverilog"
      "pkg/nvc"
      "pkg/verilator"
      "pkg/xschem"
      "pkg/xyce"
    };
  }

  subgraph cluster_impl {
    label = "Impl";
    m_impl -> {
      "apicula"
      "pkg/apicula"
      "arachne-pnr"
      "pkg/arachne-pnr"
      "icestorm"
      "pkg/icestorm"
      "prog"
      "nextpnr/generic"
      "nextpnr/ice40"
      "nextpnr/icestorm"
      "nextpnr/nexus"
      "nextpnr/prjoxide"
      "nextpnr/ecp5"
      "nextpnr/prjtrellis"
      "nextpnr"
      "impl/ice40"
      "impl/nexus"
      "impl/ecp5"
      "impl/icestorm"
      "impl/prjoxide"
      "impl/prjtrellis"
      "impl/generic"
      "impl/pnr"
      "impl"
      "pkg/nextpnr/generic"
      "pkg/nextpnr/ice40"
      "pkg/nextpnr/nexus"
      "pkg/nextpnr/ecp5"
      "openfpgaloader"
      "pkg/openfpgaloader"
      "prjoxide"
      "pkg/prjoxide"
      "prjtrellis"
      "pkg/prjtrellis"
      "vtr"
      "pkg/vtr"
    }
  }

  subgraph cluster_formal {
    label = "Formal";
    m_formal -> {
      "pkg/sby"
      "pkg/boolector"
      "pkg/cvc"
      "pkg/pono"
      "pkg/superprove"
      "pkg/yices2"
      "pkg/z3"
      "formal/min"
      "formal"
      "formal/all"
    }
  }

  subgraph cluster_asic {
    label = "ASIC";
    m_asic -> {
      "irsim"
      "klayout"
      "magic"
      "magic/irsim"
      "netgen"
      "openroad"
      "openroad/gui"
      "pkg/irsim"
      "pkg/klayout"
      "pkg/magic"
      "pkg/netgen"
      "pkg/openroad"
      "pkg/openroad/gui"
    };
  }

  subgraph cluster_f4pga {
    label = "F4PGA";
    m_f4pga -> {
      "conda"
      "conda/f4pga/xc7/toolchain"
      "conda/f4pga/xc7/a50t"
      "conda/f4pga/xc7/a100t"
      "conda/f4pga/xc7/a200t"
      "conda/f4pga/xc7/z010"
      "conda/f4pga/xc7/z020"
      "conda/f4pga/xc7"
      "conda/f4pga/eos-s3"
      "verible"
      "pkg/verible"
    }
  }

}

Fig. 2 Subgraphs and images

# Authors:
#   Unai Martinez-Corral
#     <umartinezcorral@antmicro.com>
#     <unai.martinezcorral@ehu.eus>
#
# Copyright Unai Martinez-Corral
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

digraph G {

  #splines=polyline; #curved
  newrank=true;

  # Dockerfiles

  { node [shape=note, color=dodgerblue, fontcolor=dodgerblue]
    d_base  [label="base"];
  }

  # Images

  { node [shape=cylinder]
    "build/base"
    "build/build"
    "build/dev"
  }

  # External images

  { node [shape=cylinder, color=orange, fontcolor=orange]
    "BASE IMAGE"
  }

  # Workflows

  subgraph cluster_base {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_base_debian" [label="BASE IMAGE"]
    }

    d_base -> {
      "build/base",
      "build/build",
      "build/dev"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red];
      "t_build/base" [label="build--base"];
      "t_build/build" [label="build--build"];
      "t_build/dev" [label="build--dev"];
    }

    "build/base" -> "t_build/base";
    "build/build" -> "t_build/build";
    "build/dev" -> "t_build/dev";
  }

  # Dockerfile dependencies

  "BASE IMAGE" -> "p_base_debian" -> d_base;

  # Image dependencies

  { edge [style=dashed]
    "p_base_debian" -> "build/base" -> "build/build" -> "build/dev";
  }

}

Fig. 3 Base: workflows, dockerfiles, images and tests.

# Authors:
#   Unai Martinez-Corral
#     <umartinezcorral@antmicro.com>
#     <unai.martinezcorral@ehu.eus>
#
# Copyright Unai Martinez-Corral
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

digraph G {

  #splines=polyline; #curved
  newrank=true;

  # Dockerfiles

  { node [shape=note, color=dodgerblue, fontcolor=dodgerblue]
    d_gtkwave   [label="gtkwave"];
    d_iverilog  [label="iverilog"];
    d_nvc       [label="nvc"];
    d_verilator [label="verilator"];
    d_sim       [label="sim"];
    d_octave    [label="octave"];
    d_gnuplot   [label="gnuplot"];
    d_scipy     [label="scipy"];
    d_osvb      [label="osvb"];
    d_xschem    [label="xschem"];
    d_xyce      [label="xyce"];
  }

  # Images

  { node [shape=cylinder]
    "build/build"
    "build/base"
    { node [color=limegreen, fontcolor=limegreen]
      "ghdl/llvm"
      "gtkwave"
      "iverilog"
      "nvc"
      "verilator"
      "xschem"
      "xyce"
    }
    { node [color=mediumblue, fontcolor=mediumblue]
      "pkg/gtkwave"
      "pkg/iverilog"
      "pkg/nvc"
      "pkg/verilator"
      "pkg/osvb"
      "pkg/xschem"
      "pkg/xyce"
    }
    { node [color=brown, fontcolor=brown]
      "sim"
      "sim/scipy-slim"
      "sim/scipy"
      "sim/octave-slim"
      "sim/octave"
      "sim/octave/gnuplot"
      "sim/osvb"
    }
  }

  # External images

  { node [shape=cylinder, color=orange, fontcolor=orange]
    "scratch"
  }

  { rank=same
    "build/build"
    "build/base"
    "scratch"
    "ghdl/llvm"
  }

  # Workflows

  subgraph cluster_gtkwave {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_gtkwave_build/build" [label="build/build"]
      "p_gtkwave_build/base" [label="build/base"]
      "p_gtkwave_scratch" [label="scratch"]
    }

    d_gtkwave -> {
      "gtkwave"
      "pkg/gtkwave"
     } [style=dotted];

    "t_gtkwave" [shape=folder, color=red, fontcolor=red, label="gtkwave"];
    "t_pkg/gtkwave" [shape=folder, color=red, fontcolor=red, label="gtkwave.pkg"];

    "gtkwave" -> "t_gtkwave";
    "pkg/gtkwave" -> "t_pkg/gtkwave";
  }

  subgraph cluster_iverilog {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_iverilog_build/build" [label="build/build"]
      "p_iverilog_build/base" [label="build/base"]
      "p_iverilog_scratch" [label="scratch"]
    }

    d_iverilog -> {
      "iverilog"
      "pkg/iverilog"
    } [style=dotted];

    {
      node [shape=folder, color=red, fontcolor=red]
      "t_iverilog" [label="iverilog"];
      "t_pkg/iverilog" [label="iverilog.pkg"];
    }

    "iverilog" -> "t_iverilog";
    "pkg/iverilog" -> "t_pkg/iverilog";
  }

  subgraph cluster_nvc {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_nvc_build/build" [label="build/build"]
      "p_nvc_build/base" [label="build/base"]
      "p_nvc_scratch" [label="scratch"]
    }

    d_nvc -> {
      "nvc"
      "pkg/nvc"
     } [style=dotted];

    "t_nvc" [shape=folder, color=red, fontcolor=red, label="nvc"];
    "t_pkg/nvc" [shape=folder, color=red, fontcolor=red, label="nvc.pkg"];

    "nvc" -> "t_nvc";
    "pkg/nvc" -> "t_pkg/nvc";
  }

  subgraph cluster_verilator {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_verilator_build/build" [label="build/build"]
      "p_verilator_build/base" [label="build/base"]
      "p_verilator_scratch" [label="scratch"]
    }

    d_verilator -> {
      "verilator"
      "pkg/verilator"
    } [style=dotted];

    {
      node [shape=folder, color=red, fontcolor=red]
      "t_verilator" [label="verilator"];
      "t_pkg/verilator" [label="verilator.pkg"];
    }

    "verilator" -> "t_verilator";
    "pkg/verilator" -> "t_pkg/verilator";
  }

  subgraph cluster_sim {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_sim_ghdl/llvm" [label="ghdl/llvm"]
      "p_sim_pkg/nvc" [label="pkg/nvc"]
      "p_sim_pkg/verilator" [label="pkg/verilator"]
      "p_sim_pkg/iverilog" [label="pkg/iverilog"]
    }

    d_sim -> "sim" [style=dotted];

    "sim" -> {
      d_osvb;
      d_scipy;
      d_octave;
    };

    d_osvb -> {
      "pkg/osvb"
      "sim/osvb"
      "sim/scipy"
      "sim/octave"
    } [style=dotted];

    d_scipy -> "sim/scipy-slim" [style=dotted];
    d_octave -> "sim/octave-slim" [style=dotted];
    d_gnuplot -> "sim/octave/gnuplot" [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red]
      "t_sim"                [label="sim"];
      "t_pkg/osvb"           [label="osvb.pkg"];
      "t_sim/osvb"           [label="sim--osvb"];
      "t_sim/scipy-slim"     [label="sim--scipy-slim"];
      "t_sim/scipy"          [label="sim--scipy"];
      "t_sim/octave-slim"    [label="sim--octave-slim"];
      "t_sim/octave"         [label="sim--octave"];
      "t_sim/octave/gnuplot" [label="sim--octave--gnuplot"];
    }

    "sim" -> "t_sim";
    "pkg/osvb" -> "t_pkg/osvb";
    "sim/osvb" -> "t_sim/osvb";
    "sim/scipy-slim" -> "t_sim/scipy-slim";
    "sim/scipy" -> "t_sim/scipy";
    "sim/octave-slim" -> "t_sim/octave-slim";
    "sim/octave" -> "t_sim/octave";
    "sim/octave/gnuplot" -> "t_sim/octave/gnuplot";
  }

  subgraph cluster_xschem {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_xschem_build/build" [label="build/build"]
      "p_xschem_build/base" [label="build/base"]
      "p_xschem_scratch" [label="scratch"]
    }

    d_xschem -> {
      "xschem"
      "pkg/xschem"
    } [style=dotted];

    {
      node [shape=folder, color=red, fontcolor=red]
      "t_xschem" [label="xschem"];
      "t_pkg/xschem" [label="xschem.pkg"];
    }

    "xschem" -> "t_xschem";
    "pkg/xschem" -> "t_pkg/xschem";
  }

  subgraph cluster_xyce {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_xyce_build/build" [label="build/build"]
      "p_xyce_build/base" [label="build/base"]
      "p_xyce_scratch" [label="scratch"]
    }

    d_xyce -> {
      "xyce"
      "pkg/xyce"
    } [style=dotted];

    {
      node [shape=folder, color=red, fontcolor=red]
      "t_xyce" [label="xyce"];
      "t_pkg/xyce" [label="xyce.pkg"];
    }

    "xyce" -> "t_xyce";
    "pkg/xyce" -> "t_pkg/xyce";
  }

  # Dockerfile dependencies

  "build/build" -> "p_gtkwave_build/build" -> d_gtkwave;
  "build/base" -> "p_gtkwave_build/base" -> d_gtkwave;
  "scratch" -> "p_gtkwave_scratch" -> d_gtkwave;

  "build/build" -> "p_iverilog_build/build" -> d_iverilog;
  "build/base" -> "p_iverilog_build/base" -> d_iverilog;
  "scratch" -> "p_iverilog_scratch" -> d_iverilog;

  "build/build" -> "p_nvc_build/build" -> d_nvc;
  "build/base" -> "p_nvc_build/base" -> d_nvc;
  "scratch" -> "p_nvc_scratch" -> d_nvc;

  "build/build" -> "p_verilator_build/build" -> d_verilator;
  "build/base" -> "p_verilator_build/base" -> d_verilator;
  "scratch" -> "p_verilator_scratch" -> d_verilator;

  "build/build" -> "p_xschem_build/build" -> d_xschem;
  "build/base" -> "p_xschem_build/base" -> d_xschem;
  "scratch" -> "p_xschem_scratch" -> d_xschem;

  "build/build" -> "p_xyce_build/build" -> d_xyce;
  "build/base" -> "p_xyce_build/base" -> d_xyce;
  "scratch" -> "p_xyce_scratch" -> d_xyce;

  "ghdl/llvm" -> "p_sim_ghdl/llvm" -> d_sim;
  "pkg/nvc" -> "p_sim_pkg/nvc" -> d_sim;
  "pkg/verilator" -> "p_sim_pkg/verilator" -> d_sim;
  "pkg/iverilog" -> "p_sim_pkg/iverilog" -> d_sim;

   {
     "sim/scipy-slim"
     "sim/octave-slim"
   } -> d_osvb;

   "sim/octave" -> d_gnuplot;

  # Image dependencies

  { edge [style=dashed]
    "p_gtkwave_build/base" -> "gtkwave";
    "p_gtkwave_scratch" -> "pkg/gtkwave";

    "p_iverilog_build/base" -> "iverilog";
    "p_iverilog_scratch" -> "pkg/iverilog";

    "p_nvc_build/base" -> "nvc";
    "p_nvc_scratch" -> "pkg/nvc";

    "p_verilator_build/base" -> "verilator";
    "p_verilator_scratch" -> "pkg/verilator";

    "p_xschem_build/base" -> "xschem";
    "p_xschem_scratch" -> "pkg/xschem";

    "p_xyce_build/base" -> "xyce";
    "p_xyce_scratch" -> "pkg/xyce";

    "p_sim_ghdl/llvm" -> "sim" -> {
      "sim/osvb"
      "sim/scipy-slim"
      "sim/octave-slim"
    };

    "sim/scipy-slim" -> "sim/scipy";
    "sim/octave-slim" -> "sim/octave" -> "sim/octave/gnuplot";
  }

  { edge [style=dashed, color=grey]
    "p_sim_pkg/nvc" -> "sim";
    "p_sim_pkg/verilator" -> "sim";
    "p_sim_pkg/iverilog" -> "sim";
  }

}

Fig. 4 Sim: workflows, dockerfiles, images and tests.

# Authors:
#   Unai Martinez-Corral
#     <umartinezcorral@antmicro.com>
#     <unai.martinezcorral@ehu.eus>
#
# Copyright Unai Martinez-Corral
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

digraph G {

  #splines=polyline; #curved
  newrank=true;

  # Dockerfiles

  { node [shape=note, color=dodgerblue, fontcolor=dodgerblue]
    d_ghdl             [label="ghdl"];
    d_ghdlYosysPlugin  [label="ghdl-yosys-plugin"];
    d_yosys            [label="yosys"];
  }

  # Images

  { node [shape=cylinder]
    "build/base"
    "build/build"
    { node [color=limegreen, fontcolor=limegreen]
      "ghdl"
      "ghdl/llvm"
      "ghdl/yosys"
      "yosys"
    }
    { node [color=mediumblue, fontcolor=mediumblue]
      "pkg/ghdl"
      "pkg/ghdl/llvm"
      "pkg/ghdl-yosys-plugin"
      "pkg/yosys"
    }
  }

  # External images

  { node [shape=cylinder, color=orange, fontcolor=orange]
    "scratch"
    "ghdl/pkg:bullseye-mcode"
    "ghdl/pkg:bullseye-llvm-9";
  }

  { rank=same
    "build/base"
    "build/build"
    "scratch"
    "ghdl/pkg:bullseye-mcode"
    "ghdl/pkg:bullseye-llvm-9"
  }

  # Workflows

  subgraph cluster_ghdl {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_ghdl_build/base" [label="build/base"]
      "p_ghdl_scratch" [label="scratch"]
      "p_ghdl_bullseye" [label="ghdl/pkg:bullseye-mcode"]
      "p_ghdl_bullseye-llvm" [label="ghdl/pkg:bullseye-llvm-9"]
    }

    d_ghdl -> { rank=same
      "ghdl",
      "ghdl/llvm",
      "pkg/ghdl",
      "pkg/ghdl/llvm"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red];
      "t_ghdl" [label="ghdl"];
      "t_ghdl/llvm" [label="ghdl--llvm"];
      "t_pkg/ghdl" [label="ghdl.pkg"];
      "t_pkg/ghdl/llvm" [label="ghdl--llvm.pkg"];
    }

    "ghdl" -> "t_ghdl";
    "ghdl/llvm" -> "t_ghdl/llvm";
    "pkg/ghdl" -> "t_pkg/ghdl";
    "pkg/ghdl/llvm" -> "t_pkg/ghdl/llvm";
  }

  subgraph cluster_ghdlYosysPlugin {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_ghdl-yosys-plugin_yosys" [label="yosys"]
      "p_ghdl-yosys-plugin_pkg/ghdl" [label="pkg/ghdl"]
    }

    d_ghdlYosysPlugin -> { rank=same
      "pkg/ghdl-yosys-plugin",
      "ghdl/yosys"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red];
      "t_pkg/ghdl-yosys-plugin" [label="ghdl-yosys-plugin.pkg"];
      "t_ghdl/yosys" [label="ghdl--yosys"];
    }

    "pkg/ghdl-yosys-plugin" -> "t_pkg/ghdl-yosys-plugin";
    "ghdl/yosys" -> "t_ghdl/yosys";
  }

  subgraph cluster_yosys {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_yosys_build/build" [label="build/build"]
      "p_yosys_scratch" [label="scratch"]
    }

    d_yosys -> { rank=same
      "pkg/yosys",
      "yosys"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red];
      "t_yosys" [label="yosys"];
      "t_pkg/yosys" [label="yosys.pkg"];
    }

    "yosys" -> "t_yosys";
    "pkg/yosys" -> "t_pkg/yosys";
  }

  { rank=same
    d_ghdl
    d_yosys
  }

  # Dockerfile dependencies

  "scratch" -> "p_ghdl_scratch" -> d_ghdl;
  "ghdl/pkg:bullseye-mcode" -> "p_ghdl_bullseye" -> d_ghdl;
  "ghdl/pkg:bullseye-llvm-9" -> "p_ghdl_bullseye-llvm" -> d_ghdl;
  "build/base" -> "p_ghdl_build/base" -> d_ghdl;

  "pkg/ghdl" -> "p_ghdl-yosys-plugin_pkg/ghdl" -> d_ghdlYosysPlugin;
  "yosys" -> "p_ghdl-yosys-plugin_yosys" -> d_ghdlYosysPlugin;

  "build/build" -> "p_yosys_build/build" -> d_yosys;
  "scratch" -> "p_yosys_scratch" -> d_yosys;

  # Image dependencies

  { edge [style=dashed]
    "p_ghdl_scratch" -> { "pkg/ghdl", "pkg/ghdl/llvm" };
    "p_ghdl_build/base" -> { "ghdl", "ghdl/llvm" };

    "p_yosys_scratch" -> "pkg/yosys";
    "p_yosys_build/build" -> "yosys";

    "p_ghdl-yosys-plugin_yosys" -> "ghdl/yosys";
    "p_ghdl-yosys-plugin_pkg/ghdl" -> "pkg/ghdl-yosys-plugin";
  }

  { edge [style=dashed, color=grey]
    "p_ghdl_bullseye" -> {
      "ghdl",
      "pkg/ghdl"
    };
    "p_ghdl_bullseye-llvm" -> {
      "ghdl/llvm",
      "pkg/ghdl/llvm"
    };
  }

}

Fig. 5 Synth: workflows, dockerfiles, images and tests.

# Authors:
#   Unai Martinez-Corral
#     <umartinezcorral@antmicro.com>
#     <unai.martinezcorral@ehu.eus>
#
# Copyright Unai Martinez-Corral
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

digraph G {

  #splines=polyline; #curved
  newrank=true;

  # Dockerfiles

  { node [shape=note, color=dodgerblue, fontcolor=dodgerblue]
    d_apicula        [label="apicula"];
    d_arachnepnr     [label="arachne-pnr"];
    d_icestorm       [label="icestorm"];
    d_impl           [label="impl"];
    d_nextpnr        [label="nextpnr"];
    d_openfpgaloader [label="openfpgaloader"];
    d_prjoxide       [label="prjoxide"];
    d_prjtrellis     [label="prjtrellis"];
    d_prog           [label="prog"];
    d_vtr            [label="vtr"];
  }

  # Images

  { node [shape=cylinder]
    "build/base"
    "build/build"
    "build/dev"
    { node [color=limegreen, fontcolor=limegreen]
      "apicula"
      "arachne-pnr"
      "ghdl/yosys"
      "icestorm"
      "nextpnr/ice40"
      "nextpnr/nexus"
      "nextpnr/ecp5"
      "nextpnr/generic"
      "nextpnr"
      "openfpgaloader"
      "prjoxide"
      "prjtrellis"
      "vtr"
    }
    { node [color=mediumblue, fontcolor=mediumblue]
      "pkg/apicula"
      "pkg/arachne-pnr"
      "pkg/icestorm"
      "pkg/nextpnr/generic"
      "pkg/nextpnr/ice40"
      "pkg/nextpnr/nexus"
      "pkg/nextpnr/ecp5"
      "pkg/openfpgaloader"
      "pkg/prjoxide"
      "pkg/prjtrellis"
      "pkg/vtr"
    }
    { node [color=brown, fontcolor=brown]
      "nextpnr/icestorm"
      "nextpnr/prjoxide"
      "nextpnr/prjtrellis"
      "impl/generic"
      "impl/ice40"
      "impl/nexus"
      "impl/ecp5"
      "impl/icestorm"
      "impl/prjoxide"
      "impl/prjtrellis"
      "impl/pnr"
      "impl"
      "prog"
    }
  }

  # External images

  { node [shape=cylinder, color=orange, fontcolor=orange]
    "scratch"
  }

  { rank=same
    "build/base"
    "build/build"
    "build/dev"
    "ghdl/yosys"
    "scratch"
  }

  # Workflows

  subgraph cluster_apicula {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_apicula_build/build"  [label="build/build"]
      "p_apicula_build/base"   [label="build/base"]
      "p_apicula_scratch"      [label="scratch"]
    }

    d_apicula -> {
      "apicula"
      "pkg/apicula"
     } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red]
      "t_apicula"     [label="apicula"];
      "t_pkg/apicula" [label="apicula.pkg"];
    }

    "apicula" -> "t_apicula";
    "pkg/apicula" -> "t_pkg/apicula";
  }

  subgraph cluster_arachepnr {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_arachnepnr_pkg/icestorm" [label="pkg/icestorm"]
      "p_arachnepnr_build/build"  [label="build/build"]
      "p_arachnepnr_build/base"   [label="build/base"]
      "p_arachnepnr_scratch"      [label="scratch"]
    }

    d_arachnepnr -> {
      "arachne-pnr"
      "pkg/arachne-pnr"
     } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red]
      "t_arachne-pnr"     [label="arachne-pnr"];
      "t_pkg/arachne-pnr" [label="arachne-pnr.pkg"];
    }

    "arachne-pnr" -> "t_arachne-pnr";
    "pkg/arachne-pnr" -> "t_pkg/arachne-pnr";
  }

  subgraph cluster_icestorm {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_icestorm_scratch"     [label="scratch"]
      "p_icestorm_build/base"  [label="build/base"]
      "p_icestorm_build/build" [label="build/build"]
    }

    d_icestorm -> { rank=same
      "pkg/icestorm",
      "icestorm"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red];
      "t_icestorm"     [label="icestorm"];
      "t_pkg/icestorm" [label="icestorm.pkg"];
    }

    "icestorm" -> "t_icestorm";
    "pkg/icestorm" -> "t_pkg/icestorm";
  }

  subgraph cluster_impl {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_impl_ghdl/yosys"           [label="ghdl/yosys"]
      "p_impl_pkg/nextpnr/generic"  [label="pkg/nextpnr/generic"]
      "p_impl_pkg/nextpnr/ice40"    [label="pkg/nextpnr/ice40"]
      "p_impl_pkg/nextpnr/nexus"    [label="pkg/nextpnr/nexus"]
      "p_impl_pkg/nextpnr/ecp5"     [label="pkg/nextpnr/ecp5"]
      "p_impl_pkg/icestorm"         [label="pkg/icestorm"]
      "p_impl_pkg/prjoxide"         [label="pkg/prjoxide"]
      "p_impl_pkg/prjtrellis"       [label="pkg/prjtrellis"]
    }

    { node [shape=cylinder]
      "build/impl"
    }

    d_impl -> {
      "build/impl"
      "impl/generic"
      "impl/ice40"
      "impl/nexus"
      "impl/ecp5"
      "impl/icestorm"
      "impl/prjoxide"
      "impl/prjtrellis"
      "impl/pnr"
      "impl"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red];
      "t_impl/generic"    [label="impl--generic"];
      "t_impl/ice40"      [label="impl--ice40"];
      "t_impl/nexus"      [label="impl--nexus"];
      "t_impl/ecp5"       [label="impl--ecp5"];
      "t_impl/icestorm"   [label="impl--icestorm"];
      "t_impl/prjoxide"   [label="impl--prjoxide"];
      "t_impl/prjtrellis" [label="impl--prjtrellis"];
      "t_impl/pnr"        [label="impl--pnr"];
      "t_impl"            [label="impl"];
    }

    "impl/generic" -> "t_impl/generic";
    "impl/ice40" -> "t_impl/ice40";
    "impl/nexus" -> "t_impl/nexus";
    "impl/ecp5" -> "t_impl/ecp5";
    "impl/icestorm" -> "t_impl/icestorm";
    "impl/prjoxide" -> "t_impl/prjoxide";
    "impl/prjtrellis" -> "t_impl/prjtrellis";
    "impl/pnr" -> "t_impl/pnr";
    "impl" -> "t_impl";
  }

  subgraph cluster_nextpnr {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_nextpnr_scratch"    [label="scratch"]
      "p_nextpnr_build/base" [label="build/base"]
      "p_nextpnr_build/dev"  [label="build/dev"]
      "p_nextpnr_icestorm"   [label="pkg/icestorm"]
      "p_nextpnr_prjoxide"   [label="pkg/prjoxide"]
      "p_nextpnr_prjtrellis" [label="pkg/prjtrellis"]
    }

    { node [shape=cylinder]
      "build/nextpnr/base"
      "build/nextpnr/build"
    }

    d_nextpnr -> { rank=same
      "build/nextpnr/base"
      "build/nextpnr/build"
    } [style=dotted];

    d_nextpnr -> { rank=same
      "nextpnr/generic",
      "nextpnr/ice40",
      "nextpnr/nexus",
      "nextpnr/ecp5",
      "nextpnr"
    } [style=dotted];

    d_nextpnr -> { rank=same
      "nextpnr/icestorm",
      "nextpnr/prjoxide"
      "nextpnr/prjtrellis"
    } [style=dotted];

    d_nextpnr -> { rank=same
      "pkg/nextpnr/generic",
      "pkg/nextpnr/ice40",
      "pkg/nextpnr/nexus",
      "pkg/nextpnr/ecp5"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red];
      "t_nextpnr"             [label="nextpnr"];
      "t_nextpnr/generic"     [label="nextpnr--generic"];
      "t_nextpnr/ice40"       [label="nextpnr--ice40"];
      "t_nextpnr/nexus"       [label="nextpnr--nexus"];
      "t_nextpnr/ecp5"        [label="nextpnr--ecp5"];
      "t_nextpnr/icestorm"    [label="nextpnr--icestorm"];
      "t_nextpnr/prjoxide"    [label="nextpnr--prjoxide"];
      "t_nextpnr/prjtrellis"  [label="nextpnr--prjtrellis"];
      "t_pkg/nextpnr/generic" [label="nextpnr--generic.pkg"];
      "t_pkg/nextpnr/ice40"   [label="nextpnr--ice40.pkg"];
      "t_pkg/nextpnr/nexus"   [label="nextpnr--nexus.pkg"];
      "t_pkg/nextpnr/ecp5"    [label="nextpnr--ecp5.pkg"];
    }

    "nextpnr/generic"     -> "t_nextpnr/generic";
    "nextpnr/ice40"       -> "t_nextpnr/ice40";
    "nextpnr/nexus"       -> "t_nextpnr/nexus";
    "nextpnr/ecp5"        -> "t_nextpnr/ecp5";
    "nextpnr"             -> "t_nextpnr";
    "nextpnr/icestorm"    -> "t_nextpnr/icestorm";
    "nextpnr/prjoxide"    -> "t_nextpnr/prjoxide";
    "nextpnr/prjtrellis"  -> "t_nextpnr/prjtrellis";
    "pkg/nextpnr/generic" -> "t_pkg/nextpnr/generic";
    "pkg/nextpnr/ice40"   -> "t_pkg/nextpnr/ice40";
    "pkg/nextpnr/nexus"   -> "t_pkg/nextpnr/nexus";
    "pkg/nextpnr/ecp5"    -> "t_pkg/nextpnr/ecp5";
  }

  subgraph cluster_openfpgaloader {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_openfpgaloader_build/build" [label="build/build"]
      "p_openfpgaloader_build/base" [label="build/base"]
      "p_openfpgaloader_scratch" [label="scratch"]
    }

    d_openfpgaloader -> {
      "openfpgaloader"
      "pkg/openfpgaloader"
    } [style=dotted];

    {
      node [shape=folder, color=red, fontcolor=red]
      "t_openfpgaloader" [label="openfpgaloader"];
      "t_pkg/openfpgaloader" [label="openfpgaloader.pkg"];
    }

    "openfpgaloader" -> "t_openfpgaloader";
    "pkg/openfpgaloader" -> "t_pkg/openfpgaloader";
  }

  subgraph cluster_prjoxide {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_prjoxide_scratch" [label="scratch"]
      "p_prjoxide_build/base" [label="build/base"]
      "p_prjoxide_build/build" [label="build/build"]
    }

    d_prjoxide -> { rank=same
      "pkg/prjoxide",
      "prjoxide"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red];
      "t_prjoxide" [label="prjoxide"];
      "t_pkg/prjoxide" [label="prjoxide.pkg"];
    }

    "prjoxide" -> "t_prjoxide";
    "pkg/prjoxide" -> "t_pkg/prjoxide";
  }

  subgraph cluster_prjtrellis {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_prjtrellis_scratch" [label="scratch"]
      "p_prjtrellis_build/base" [label="build/base"]
      "p_prjtrellis_build/dev" [label="build/dev"]
    }

    d_prjtrellis -> { rank=same
      "pkg/prjtrellis",
      "prjtrellis"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red];
      "t_prjtrellis" [label="prjtrellis"];
      "t_pkg/prjtrellis" [label="prjtrellis.pkg"];
    }

    "prjtrellis" -> "t_prjtrellis";
    "pkg/prjtrellis" -> "t_pkg/prjtrellis";
  }

  subgraph cluster_prog {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_prog_icestorm" [label="pkg/icestorm"]
      "p_prog_build/base" [label="build/base"]
    }

    d_prog -> "prog" [style=dotted];

    "t_prog" [shape=folder, color=red, fontcolor=red, label="prog"];

    "prog" -> "t_prog";
  }

  subgraph cluster_vtr {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_vtr_build/dev"    [label="build/dev"]
      "p_vtr_build/base"   [label="build/base"]
      "p_vtr_scratch"      [label="scratch"]
    }

    d_vtr -> {
      "vtr"
      "pkg/vtr"
     } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red]
      "t_vtr"     [label="vtr"];
      "t_pkg/vtr" [label="vtr.pkg"];
    }

    "vtr" -> "t_vtr";
    "pkg/vtr" -> "t_pkg/vtr";
  }

  { rank=same
    d_icestorm
    d_prjtrellis
  }

  { rank=same
    d_nextpnr
    d_prog
  }

  # Dockerfile dependencies

  "build/build" -> "p_apicula_build/build" -> d_apicula;
  "build/base" -> "p_apicula_build/base" -> d_apicula;
  "scratch" -> "p_apicula_scratch" -> d_apicula;

  "build/build" -> "p_arachnepnr_build/build" -> d_arachnepnr;
  "build/base" -> "p_arachnepnr_build/base" -> d_arachnepnr;
  "scratch" -> "p_arachnepnr_scratch" -> d_arachnepnr;
  "pkg/icestorm" -> "p_arachnepnr_pkg/icestorm" -> d_arachnepnr;

  "build/build" -> "p_icestorm_build/build" -> d_icestorm;
  "build/base" -> "p_icestorm_build/base" -> d_icestorm;
  "scratch" -> "p_icestorm_scratch" -> d_icestorm;

  "ghdl/yosys" -> "p_impl_ghdl/yosys" -> d_impl;
  "pkg/nextpnr/generic" -> "p_impl_pkg/nextpnr/generic" -> d_impl;
  "pkg/nextpnr/ice40" -> "p_impl_pkg/nextpnr/ice40" -> d_impl;
  "pkg/nextpnr/nexus" -> "p_impl_pkg/nextpnr/nexus" -> d_impl;
  "pkg/nextpnr/ecp5" -> "p_impl_pkg/nextpnr/ecp5" -> d_impl;
  "pkg/icestorm" -> "p_impl_pkg/icestorm" -> d_impl;
  "pkg/prjoxide" -> "p_impl_pkg/prjoxide" -> d_impl;
  "pkg/prjtrellis" -> "p_impl_pkg/prjtrellis" -> d_impl;

  "scratch" -> "p_nextpnr_scratch" -> d_nextpnr;
  "build/dev" -> "p_nextpnr_build/dev" -> d_nextpnr;
  "build/base" -> "p_nextpnr_build/base" -> d_nextpnr;
  "pkg/icestorm" -> "p_nextpnr_icestorm" -> d_nextpnr;
  "pkg/prjoxide" -> "p_nextpnr_prjoxide" -> d_nextpnr;
  "pkg/prjtrellis" -> "p_nextpnr_prjtrellis" -> d_nextpnr;

  "build/build" -> "p_openfpgaloader_build/build" -> d_openfpgaloader;
  "build/base" -> "p_openfpgaloader_build/base" -> d_openfpgaloader;
  "scratch" -> "p_openfpgaloader_scratch" -> d_openfpgaloader;

  "build/build" -> "p_prjoxide_build/build" -> d_prjoxide;
  "build/base" -> "p_prjoxide_build/base" -> d_prjoxide;
  "scratch" -> "p_prjoxide_scratch" -> d_prjoxide;

  "build/dev" -> "p_prjtrellis_build/dev" -> d_prjtrellis;
  "build/base" -> "p_prjtrellis_build/base" -> d_prjtrellis;
  "scratch" -> "p_prjtrellis_scratch" -> d_prjtrellis;

  "build/base" -> "p_prog_build/base" -> d_prog;
  "pkg/icestorm" -> "p_prog_icestorm" -> d_prog;

  "build/dev" -> "p_vtr_build/dev" -> d_vtr;
  "build/base" -> "p_vtr_build/base" -> d_vtr;
  "scratch" -> "p_vtr_scratch" -> d_vtr;

  # Image dependencies

  { edge [style=dashed]
    "p_apicula_build/base" -> "apicula";
    "p_apicula_scratch" -> "pkg/apicula";

    "p_arachnepnr_build/base" -> "arachne-pnr";
    "p_arachnepnr_scratch" -> "pkg/arachne-pnr";

    "p_prog_build/base" -> "prog";

    "p_openfpgaloader_build/base" -> "openfpgaloader";
    "p_openfpgaloader_scratch" -> "pkg/openfpgaloader";

    "p_nextpnr_build/base" -> "build/nextpnr/base" -> {
      "nextpnr",
      "nextpnr/generic",
      "nextpnr/nexus",
      "nextpnr/ice40",
      "nextpnr/ecp5"
    };

    "p_nextpnr_scratch" -> {
      "pkg/nextpnr/generic",
      "pkg/nextpnr/ice40",
      "pkg/nextpnr/nexus",
      "pkg/nextpnr/ecp5"
    };

    "nextpnr/ice40" -> "nextpnr/icestorm";
    "nextpnr/nexus" -> "nextpnr/prjoxide";
    "nextpnr/ecp5" -> "nextpnr/prjtrellis";

    "p_icestorm_build/base" -> "icestorm";
    "p_icestorm_scratch" -> "pkg/icestorm";

    "p_impl_ghdl/yosys" -> "build/impl" -> {
      "impl/generic"
      "impl/ice40"
      "impl/nexus"
      "impl/ecp5"
      "impl/pnr"
    };

    "impl/ice40" -> "impl/icestorm";
    "impl/nexus" -> "impl/prjoxide";
    "impl/ecp5" -> "impl/prjtrellis";
    "impl/pnr" -> "impl";

    "p_prjoxide_scratch" -> "pkg/prjoxide";
    "p_prjoxide_build/base" -> "prjoxide";

    "p_prjtrellis_scratch" -> "pkg/prjtrellis";
    "p_prjtrellis_build/base" -> "prjtrellis";

    "p_vtr_build/base" -> "vtr";
    "p_vtr_scratch" -> "pkg/vtr";
  }

  { edge [style=dashed, color=grey]
    "p_prog_icestorm" -> "prog";

    {
      "pkg/nextpnr/ice40"
      "pkg/nextpnr/nexus"
      "pkg/nextpnr/ecp5"
    } -> "nextpnr";
    "p_nextpnr_icestorm"   -> "nextpnr/icestorm";
    "p_nextpnr_prjoxide"   -> "nextpnr/prjoxide";
    "p_nextpnr_prjtrellis" -> "nextpnr/prjtrellis";

    "p_impl_pkg/icestorm"        -> { "impl/icestorm", "impl" };
    "p_impl_pkg/prjoxide"        -> { "impl/prjoxide", "impl" };
    "p_impl_pkg/prjtrellis"      -> { "impl/prjtrellis", "impl" };
    "p_impl_pkg/nextpnr/generic" -> { "impl/generic", "impl/pnr" };
    "p_impl_pkg/nextpnr/ice40"   -> { "impl/ice40", "impl/pnr" };
    "p_impl_pkg/nextpnr/nexus"   -> { "impl/nexus", "impl/pnr" };
    "p_impl_pkg/nextpnr/ecp5"    -> { "impl/ecp5", "impl/pnr" };
  }

}

Fig. 6 Impl: workflows, dockerfiles, images and tests.

# Authors:
#   Unai Martinez-Corral
#     <umartinezcorral@antmicro.com>
#     <unai.martinezcorral@ehu.eus>
#
# Copyright Unai Martinez-Corral
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

digraph G {

  #splines=polyline; #curved
  newrank=true;

  # Dockerfiles

  { node [shape=note, color=dodgerblue, fontcolor=dodgerblue]
    d_boolector   [label="boolector"];
    d_pono        [label="pono"];
    d_cvc         [label="cvc"];
    d_formal      [label="formal"];
    d_superprove  [label="superprove"];
    d_sby         [label="sby"];
    d_yices2      [label="yices2"];
    d_z3          [label="z3"];
  }

  # Images

  { node [shape=cylinder]
    "build/base"
    "build/build"
    { node [color=limegreen, fontcolor=limegreen]
      "ghdl/yosys"
    }
    { node [color=mediumblue, fontcolor=mediumblue]
      "pkg/boolector"
      "pkg/cvc"
      "pkg/pono"
      "pkg/superprove"
      "pkg/sby"
      "pkg/yices2"
      "pkg/z3"
    }
    { node [color=brown, fontcolor=brown]
      "formal/min"
      "formal"
      "formal/all"
    }
  }

  # External images

  { node [shape=cylinder, color=orange, fontcolor=orange]
    "scratch"
  }

  { rank=same
    "scratch"
    "build/base"
    "build/build"
    "ghdl/yosys"
  }

  # Workflows

  subgraph cluster_boolector {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_boolector_scratch" [label="scratch"]
      "p_boolector_build/build" [label="build/build"]
    }

    d_boolector -> "pkg/boolector" [style=dotted];

    "t_pkg/boolector" [shape=folder, color=red, fontcolor=red, label="boolector.pkg"];

    "pkg/boolector" -> "t_pkg/boolector";
  }

  subgraph cluster_cvc {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_cvc_scratch" [label="scratch"]
      "p_cvc_build/build" [label="build/build"]
    }

    d_cvc -> "pkg/cvc" [style=dotted];

    "t_pkg/cvc" [shape=folder, color=red, fontcolor=red, label="cvc.pkg"];

    "pkg/cvc" -> "t_pkg/cvc";
  }

  subgraph cluster_pono {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_pono_scratch" [label="scratch"]
      "p_pono_build/build" [label="build/build"]
    }

    d_pono -> "pkg/pono" [style=dotted];

    "t_pkg/pono" [shape=folder, color=red, fontcolor=red, label="pono.pkg"];

    "pkg/pono" -> "t_pkg/pono";
  }

  subgraph cluster_superprove {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_superprove_scratch" [label="scratch"]
      "p_superprove_build/build" [label="build/build"]
    }

    d_superprove -> "pkg/superprove" [style=dotted];

    "t_pkg/superprove" [shape=folder, color=red, fontcolor=red, label="superprove.pkg"];

    "pkg/superprove" -> "t_pkg/superprove";
  }

  subgraph cluster_sby {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_sby_scratch" [label="scratch"]
      "p_sby_build/base" [label="build/base"]
    }

    d_sby -> "pkg/sby" [style=dotted];

    "t_pkg/sby" [shape=folder, color=red, fontcolor=red, label="sby.pkg"];

    "pkg/sby" -> "t_pkg/sby";
  }

  subgraph cluster_yices2 {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_yices2_scratch" [label="scratch"]
      "p_yices2_build/build" [label="build/build"]
    }

    d_yices2 -> "pkg/yices2" [style=dotted];

    "t_pkg/yices2" [shape=folder, color=red, fontcolor=red, label="yices2.pkg"];

    "pkg/yices2" -> "t_pkg/yices2";
  }

  subgraph cluster_z3 {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_z3_scratch" [label="scratch"]
      "p_z3_build/build" [label="build/build"]
    }

    d_z3 -> "pkg/z3" [style=dotted];

    "t_pkg/z3" [shape=folder, color=red, fontcolor=red, label="z3.pkg"];

    "pkg/z3" -> "t_pkg/z3";
  }

  subgraph cluster_formal {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_formal_boolector" [label="pkg/boolector"]
      "p_formal_cvc" [label="pkg/cvc"]
      "p_formal_ghdl" [label="ghdl/yosys"]
      "p_formal_pono" [label="pkg/pono"]
      "p_formal_sby" [label="pkg/sby"]
      "p_formal_superprove" [label="pkg/superprove"]
      "p_formal_z3" [label="pkg/z3"]
      "p_formal_yices2" [label="pkg/yices2"]
    }

    d_formal -> {
      "formal/min",
      "formal",
      "formal/all"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red];
      "t_formal/min" [label="formal--min"];
      "t_formal" [label="formal"];
      "t_formal/all" [label="formal--all"];
    }

    "formal/min" -> "t_formal/min";
    "formal" -> "t_formal";
    "formal/all" -> "t_formal/all";
  }

  { rank=same
    d_boolector
    d_cvc
    d_pono
    d_superprove
    d_sby
    d_yices2
    d_z3
  }

  # Dockerfile dependencies

  "build/build" -> "p_boolector_build/build" -> d_boolector;
  "scratch" -> "p_boolector_scratch" -> d_boolector;

  "build/build" -> "p_cvc_build/build" -> d_cvc;
  "scratch" -> "p_cvc_scratch" -> d_cvc;

  "ghdl/yosys" -> "p_formal_ghdl" -> d_formal;
  "pkg/sby" -> "p_formal_sby" -> d_formal;
  "pkg/boolector" -> "p_formal_boolector" -> d_formal;
  "pkg/cvc" -> "p_formal_cvc" -> d_formal;
  "pkg/pono" -> "p_formal_pono" -> d_formal;
  "pkg/yices2" -> "p_formal_yices2" -> d_formal;
  "pkg/superprove" -> "p_formal_superprove" -> d_formal;
  "pkg/z3" -> "p_formal_z3" -> d_formal;

  "build/build" -> "p_pono_build/build" -> d_pono;
  "scratch" -> "p_pono_scratch" -> d_pono;

  "build/base" -> "p_sby_build/base" -> d_sby;
  "scratch" -> "p_sby_scratch" -> d_sby;

  "build/build" -> "p_superprove_build/build" -> d_superprove;
  "scratch" -> "p_superprove_scratch" -> d_superprove;

  "build/build" -> "p_yices2_build/build" -> d_yices2;
  "scratch" -> "p_yices2_scratch" -> d_yices2;

  "build/build" -> "p_z3_build/build" -> d_z3;
  "scratch" -> "p_z3_scratch" -> d_z3;

  # Image dependencies

  { edge [style=dashed]
    "p_boolector_scratch" -> "pkg/boolector";
    "p_cvc_scratch" -> "pkg/cvc";
    "p_formal_ghdl" -> "formal/min" -> "formal" -> "formal/all";
    "p_pono_scratch" -> "pkg/pono";
    "p_superprove_scratch" -> "pkg/superprove";
    "p_sby_scratch" -> "pkg/sby";
    "p_yices2_scratch" -> "pkg/yices2";
    "p_z3_scratch" -> "pkg/z3";
  }

  { edge [style=dashed, color=grey]
    {
      "p_formal_sby",
      "p_formal_z3"
    } -> "formal/min";

    {
      "p_formal_boolector",
      "p_formal_cvc",
      "p_formal_pono",
      "p_formal_yices2"
    } -> "formal";

    {
      "p_formal_superprove"
    } -> "formal/all";
  }

}

Fig. 7 Formal: workflows, dockerfiles, images and tests.

# Authors:
#   Unai Martinez-Corral
#     <umartinezcorral@antmicro.com>
#     <unai.martinezcorral@ehu.eus>
#
# Copyright Unai Martinez-Corral
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

digraph G {

  #splines=polyline; #curved
  newrank=true;

  # Dockerfiles

  { node [shape=note, color=dodgerblue, fontcolor=dodgerblue]
    d_irsim [label="irsim"];
    d_klayout [label="klayout"];
    d_magic [label="magic"];
    d_netgen [label="netgen"];
    d_openroad [label="openroad"];
  }

  # Images

  { node [shape=cylinder]
    "build/dev"
    "build/build"
    "build/base"
    { node [color=limegreen, fontcolor=limegreen]
      "irsim"
      "klayout"
      "magic"
      "netgen"
      "openroad"
      "openroad/gui"
    }
    { node [color=mediumblue, fontcolor=mediumblue]
      "pkg/irsim"
      "pkg/klayout"
      "pkg/magic"
      "pkg/netgen"
      "pkg/openroad"
      "pkg/openroad/gui"
    }
    { node [color=brown, fontcolor=brown]
      "magic/irsim"
    }
  }

  # External images

  { node [shape=cylinder, color=orange, fontcolor=orange]
    "scratch"
  }

  { rank=same
    "build/dev"
    "build/build"
    "build/base"
    "scratch"
  }

  # Workflows

  subgraph cluster_irsim {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_irsim_build/build" [label="build/build"]
      "p_irsim_build/base" [label="build/base"]
      "p_irsim_scratch" [label="scratch"]
    }

    d_irsim -> {
      "irsim"
      "pkg/irsim"
    } [style=dotted];

    {
      node [shape=folder, color=red, fontcolor=red]
      "t_irsim" [label="irsim"];
      "t_pkg/irsim" [label="irsim.pkg"];
    }

    "irsim" -> "t_irsim";
    "pkg/irsim" -> "t_pkg/irsim";
  }

  subgraph cluster_klayout {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_klayout_build/build" [label="build/build"]
      "p_klayout_build/base" [label="build/base"]
      "p_klayout_scratch" [label="scratch"]
    }

    d_klayout -> {
      "klayout"
      "pkg/klayout"
    } [style=dotted];

    {
      node [shape=folder, color=red, fontcolor=red]
      "t_klayout" [label="klayout"];
      "t_pkg/klayout" [label="klayout.pkg"];
    }

    "klayout" -> "t_klayout";
    "pkg/klayout" -> "t_pkg/klayout";
  }

  subgraph cluster_magic {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_magic_build/build" [label="build/build"]
      "p_magic_build/base" [label="build/base"]
      "p_magic_scratch" [label="scratch"]
      "p_magic_pkg/irsim" [label="pkg/irsim"]
    }

    d_magic -> {
      "magic"
      "pkg/magic"
      "magic/irsim"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red]
      "t_magic" [label="magic"];
      "t_pkg/magic" [label="magic.pkg"];
      "t_magic/irsim" [label="magic--irsim"];
    }

    "magic" -> "t_magic";
    "pkg/magic" -> "t_pkg/magic";
    "magic/irsim" -> "t_magic/irsim";
  }

  subgraph cluster_netgen {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_netgen_build/build" [label="build/build"]
      "p_netgen_build/base" [label="build/base"]
      "p_netgen_scratch" [label="scratch"]
    }

    d_netgen -> {
      "netgen"
      "pkg/netgen"
    } [style=dotted];

    {
      node [shape=folder, color=red, fontcolor=red]
      "t_netgen" [label="netgen"];
      "t_pkg/netgen" [label="netgen.pkg"];
    }

    "netgen" -> "t_netgen";
    "pkg/netgen" -> "t_pkg/netgen";
  }

  subgraph cluster_openroad {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_openroad_build/dev" [label="build/dev"]
      "p_openroad_build/base" [label="build/base"]
      "p_openroad_scratch" [label="scratch"]
    }

    d_openroad -> {
      "openroad"
      "openroad/gui"
      "pkg/openroad"
      "pkg/openroad/gui"
    } [style=dotted];

    {
      node [shape=folder, color=red, fontcolor=red]
      "t_openroad" [label="openroad"];
      "t_pkg/openroad" [label="openroad.pkg"];
      "t_openroad/gui" [label="openroad--gui"];
      "t_pkg/openroad/gui" [label="openroad--gui.pkg"];
    }

    "openroad" -> "t_openroad";
    "pkg/openroad" -> "t_pkg/openroad";

    "openroad/gui" -> "t_openroad/gui";
    "pkg/openroad/gui" -> "t_pkg/openroad/gui";
  }

  # Dockerfile dependencies

  "build/build" -> "p_irsim_build/build" -> d_irsim;
  "build/base" -> "p_irsim_build/base" -> d_irsim;
  "scratch" -> "p_irsim_scratch" -> d_irsim;

  "build/build" -> "p_klayout_build/build" -> d_klayout;
  "build/base" -> "p_klayout_build/base" -> d_klayout;
  "scratch" -> "p_klayout_scratch" -> d_klayout;

  "pkg/irsim" -> "p_magic_pkg/irsim" -> d_magic;
  "build/build" -> "p_magic_build/build" -> d_magic;
  "build/base" -> "p_magic_build/base" -> d_magic;
  "scratch" -> "p_magic_scratch" -> d_magic;

  "build/build" -> "p_netgen_build/build" -> d_netgen;
  "build/base" -> "p_netgen_build/base" -> d_netgen;
  "scratch" -> "p_netgen_scratch" -> d_netgen;

  "build/dev" -> "p_openroad_build/dev" -> d_openroad;
  "build/base" -> "p_openroad_build/base" -> d_openroad;
  "scratch" -> "p_openroad_scratch" -> d_openroad;

  # Image dependencies

  { edge [style=dashed]
    "p_irsim_build/base" -> "irsim";
    "p_irsim_scratch" -> "pkg/irsim";
    "p_klayout_build/base" -> "klayout";
    "p_klayout_scratch" -> "pkg/klayout";
    "p_magic_build/base" -> "magic";
    "p_magic_scratch" -> "pkg/magic";
    "magic" -> "magic/irsim";
    "p_netgen_build/base" -> "netgen";
    "p_netgen_scratch" -> "pkg/netgen";
    "p_openroad_build/base" -> "openroad";
    "p_openroad_scratch" -> "pkg/openroad";
    "p_openroad_build/base" -> "openroad/gui";
    "p_openroad_scratch" -> "pkg/openroad/gui";
  }

  { edge [style=dashed, color=grey]
    "p_magic_pkg/irsim" -> "magic/irsim";
  }

}

Fig. 8 ASIC: workflows, dockerfiles, images and tests.

# Authors:
#   Unai Martinez-Corral
#     <umartinezcorral@antmicro.com>
#     <unai.martinezcorral@ehu.eus>
#
# Copyright Unai Martinez-Corral
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

digraph G {

  #splines=polyline; #curved
  newrank=true;

  # Dockerfiles

  { node [shape=note, color=dodgerblue, fontcolor=dodgerblue]
    d_conda     [label="conda"];
    d_f4pga [label="conda/f4pga"];
    d_verible [label="verible"];
  }

  # Images

  { node [shape=cylinder]
    "build/base"
    "build/build"
    { node [color=limegreen, fontcolor=limegreen]
      "verible"
    }
    { node [color=mediumblue, fontcolor=mediumblue]
      "pkg/verible"
    }
    { node [color=brown, fontcolor=brown]
      "conda"
      "conda/f4pga/xc7/toolchain"
      "conda/f4pga/xc7/a50t"
      "conda/f4pga/xc7/a100t"
      "conda/f4pga/xc7/a200t"
      "conda/f4pga/xc7/z010"
      "conda/f4pga/xc7/z020"
      "conda/f4pga/xc7"
      "conda/f4pga/eos-s3"
    }
  }

  # External images

  { node [shape=cylinder, color=orange, fontcolor=orange]
    "scratch"
  }

  { rank=same
    "build/base"
    "build/build"
    "scratch"
  }

  # Workflows

  subgraph cluster_conda {
    "p_conda_build/base" [shape=cylinder, color=grey, fontcolor=grey, label="build/base"];
    d_conda -> "conda" [style=dotted];
    "t_conda" [shape=folder, color=red, fontcolor=red, label="conda"];
    "conda" -> "t_conda";
  }

  subgraph cluster_F4PGA {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_F4PGA_conda"  [label="conda"]
    }

    d_f4pga -> {
      "conda/f4pga/xc7/toolchain"
      { rank=same
        "conda/f4pga/xc7/a50t"
        "conda/f4pga/xc7/a100t"
        "conda/f4pga/xc7/a200t"
        "conda/f4pga/xc7/z010"
        "conda/f4pga/xc7/z020"
      }
      "conda/f4pga/xc7"
      "conda/f4pga/eos-s3"
    } [style=dotted];

    { rank=same
      node [shape=folder, color=red, fontcolor=red];
      "t_F4PGA_xc7_toolchain" [label="conda--f4pga--xc7--toolchain"];
      "t_F4PGA_xc7_a50t"      [label="conda--f4pga--xc7--a50t"];
      "t_F4PGA_xc7_a100t"     [label="conda--f4pga--xc7--a100t"];
      "t_F4PGA_xc7_a200t"     [label="conda--f4pga--xc7--a200t"];
      "t_F4PGA_xc7_z010"      [label="conda--f4pga--xc7--z010"];
      "t_F4PGA_xc7_z020"      [label="conda--f4pga--xc7--z020"];
      "t_F4PGA_xc7"           [label="conda--f4pga--xc7"];
      "t_F4PGA_eos-s3"        [label="conda--f4pga--eos-s3"];
    }

    "conda/f4pga/xc7/toolchain"  -> "t_F4PGA_xc7_toolchain";
    "conda/f4pga/xc7/a50t"       -> "t_F4PGA_xc7_a50t";
    "conda/f4pga/xc7/a100t"      -> "t_F4PGA_xc7_a100t";
    "conda/f4pga/xc7/a200t"      -> "t_F4PGA_xc7_a200t";
    "conda/f4pga/xc7/z010"       -> "t_F4PGA_xc7_z010";
    "conda/f4pga/xc7/z020"       -> "t_F4PGA_xc7_z020";
    "conda/f4pga/xc7"            -> "t_F4PGA_xc7";
    "conda/f4pga/eos-s3"         -> "t_F4PGA_eos-s3";
  }

  subgraph cluster_verible {
    { rank=same
      node [shape=cylinder, color=grey, fontcolor=grey]
      "p_verible_build/build" [label="build/build"]
      "p_verible_build/base" [label="build/base"]
      "p_verible_scratch" [label="scratch"]
    }

    d_verible -> {
      "verible"
      "pkg/verible"
    } [style=dotted];

    {
      node [shape=folder, color=red, fontcolor=red]
      "t_verible" [label="verible"];
      "t_pkg/verible" [label="verible.pkg"];
    }

    "verible" -> "t_verible";
    "pkg/verible" -> "t_pkg/verible";
  }

  # Dockerfile dependencies

  "build/base" -> "p_conda_build/base" -> d_conda;

  "build/build" -> "p_verible_build/build" -> d_verible;
  "build/base" -> "p_verible_build/base" -> d_verible;
  "scratch" -> "p_verible_scratch" -> d_verible;

  "conda" -> "p_F4PGA_conda" -> d_f4pga;

  # Image dependencies

  { edge [style=dashed]
    "conda/f4pga/xc7/toolchain" -> "conda/f4pga/xc7/a50t";
    "conda/f4pga/xc7/toolchain" -> "conda/f4pga/xc7/a100t";
    "conda/f4pga/xc7/toolchain" -> "conda/f4pga/xc7/a200t";
    "conda/f4pga/xc7/toolchain" -> "conda/f4pga/xc7/z010";
    "conda/f4pga/xc7/toolchain" -> "conda/f4pga/xc7/z020";
    "conda/f4pga/xc7/toolchain" -> "conda/f4pga/xc7";

    "p_verible_build/base" -> "verible";
    "p_verible_scratch" -> "pkg/verible";
  }

  { edge [style=dashed, color=grey]
    "conda/f4pga/xc7/a50t"  -> "conda/f4pga/xc7";
    "conda/f4pga/xc7/a100t" -> "conda/f4pga/xc7";
    # TODO: This is temporarily disabled because of space limits on GitHub Actions default runners
    #"conda/f4pga/xc7/a200t" -> "conda/f4pga/xc7";
    "conda/f4pga/xc7/z010"  -> "conda/f4pga/xc7";
    "conda/f4pga/xc7/z020"  -> "conda/f4pga/xc7";
  }

}

Fig. 9 F4PGA: workflows, dockerfiles, images and tests.

# Authors:
#   Unai Martinez-Corral
#     <umartinezcorral@antmicro.com>
#     <unai.martinezcorral@ehu.eus>
#
# Copyright Unai Martinez-Corral
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

digraph G {

  #splines=polyline; #curved
  newrank=true;
  node [fontsize=8];
  edge [fontsize=8];

  { rank=same
    image_ext [shape=cylinder, color=orange, fontcolor=orange, label=""];
    image_hdl [shape=cylinder, label=""];
    image_hdlpkg [shape=cylinder, label=""];
    deps [shape=none, label="Publicly available images which the workflow depends on:\l- Black: own, generated in other workflows of this project\l- Orange: external, generated somewhere else\l"]
  }

  subgraph cluster_legend {
    test [shape=folder, color=magenta, fontcolor=magenta, label="Test"];
    image [shape=cylinder, label="Image"];
    image_base [shape=cylinder, color=grey, fontcolor=grey, label=""];
    image_dep [shape=cylinder, color=grey, fontcolor=grey, label=""];
    image_pkg [shape=cylinder, color=grey, fontcolor=grey, label=""];
    dockerfile [shape=note, color=dodgerblue, fontcolor=dodgerblue, label="Dockerfile"];
    {
      image_base,
      image_dep,
      image_pkg
    } -> dockerfile;
    dockerfile -> image [style=dotted, label="Image\ngenerated\nby"];
    image_base -> image [style=dashed, label="Image\nbased\non"];
    image_pkg -> image [style=dashed, color=gray, label="Image\nimports\nfrom"];
    image -> test;
  }
  { rank=same
    image_base
    image_dep
    image_pkg
    shadow [shape=none, label="Shadows of the images the workflow depends on.\lShown for ease of visualisation only.\l"];
  }
  { rank=same
    dockerfile
    workflow [shape=none, label="A cluster represents a GitHub Actions workflow, which\ltypically uses a single dockerfile.\l The workflow and the dockerfile are named after the tools.\l"];
  }
  { rank=same
    image
    desc [shape=none, label="Image(s) generated in the workflow:\l- Green: fine-grained ready-to-use image.\l- Brown: ready-to-use image including several tools.\l- Blue: package image, not executable.\lEach image has, at least and at most, two black input edges, one\ldotted and one dashed. It can optionally have other gray input edges.\l"];
  }
  { rank=same
    test
    test_desc [shape=none, label="Each image has a test script, which is executed in a temporary\lcontainer before pushing the image to DockerHub.\lTests which are not complete yet are shown in Red.\l"];
  }
  image_ext -> image_dep;
  image_hdl -> image_base;
  image_hdlpkg -> image_pkg;

}

Fig. 10 Legend of the directed graph.