Esempio n. 1
0
 def run_syn_to_par_with_output(self, config_path: str, syn_rundir: str,
                                par_rundir: str, syn_out_path: str,
                                syn_to_par_out_path: str) -> None:
     # Check that running the CLIDriver executes successfully (code 0).
     with self.assertRaises(SystemExit) as cm:  # type: ignore
         CLIDriver().main(args=[
             "syn",  # action
             "-p",
             config_path,
             "--output",
             syn_out_path,
             "--syn_rundir",
             syn_rundir,
             "--par_rundir",
             par_rundir
         ])
     self.assertEqual(cm.exception.code, 0)
     # Now run syn-to-par with the main config as well as the outputs.
     with self.assertRaises(SystemExit) as cm:  # type: ignore
         CLIDriver().main(args=[
             "syn-to-par",  # action
             "-p",
             config_path,
             "-p",
             syn_out_path,
             "--output",
             syn_to_par_out_path,
             "--syn_rundir",
             syn_rundir,
             "--par_rundir",
             par_rundir
         ])
     self.assertEqual(cm.exception.code, 0)
Esempio n. 2
0
    def test_syn_to_par_full(self) -> None:
        """
        Test that syn-to-par works with the full JSON.
        """

        # Set up some temporary folders for the unit test.
        syn_rundir = tempfile.mkdtemp()
        par_rundir = tempfile.mkdtemp()

        # Generate a config for testing.
        top_module = "dummy"
        config_path = os.path.join(syn_rundir, "run_config.json")
        syn_out_full_path = os.path.join(syn_rundir, "syn-output-full.json")
        syn_to_par_out_path = os.path.join(syn_rundir, "syn_par_out.json")
        self.generate_dummy_config(syn_rundir, config_path, top_module)

        # Check that running the CLIDriver executes successfully (code 0).
        with self.assertRaises(SystemExit) as cm:  # type: ignore
            CLIDriver().main(args=[
                "syn",  # action
                "-p",
                config_path,
                "--syn_rundir",
                syn_rundir,
                "--par_rundir",
                par_rundir
            ])
        self.assertEqual(cm.exception.code, 0)

        # Now run syn-to-par with the main config as well as the outputs.
        with self.assertRaises(SystemExit) as cm:  # type: ignore
            CLIDriver().main(args=[
                "syn-to-par",  # action
                "-p",
                syn_out_full_path,
                "--output",
                syn_to_par_out_path,
                "--syn_rundir",
                syn_rundir,
                "--par_rundir",
                par_rundir
            ])
        self.assertEqual(cm.exception.code, 0)

        # synthesis full output should keep other settings
        with open(syn_out_full_path, "r") as f:
            syn_output = json.loads(f.read())
            self.assertEqual(syn_output["synthesis.outputs.output_files"],
                             ["/dev/null"])
            self.assertEqual(syn_output["vlsi.core.technology"], "nop")
        # Generated par input should have other settings
        with open(syn_to_par_out_path, "r") as f:
            par_input = json.loads(f.read())
            self.assertEqual(par_input["par.inputs.top_module"], top_module)
            # par-input should preserve other settings
            self.assertEqual(par_input["vlsi.core.technology"], "nop")

        # Cleanup
        shutil.rmtree(syn_rundir)
        shutil.rmtree(par_rundir)
Esempio n. 3
0
    def test_flat_makefile(self) -> None:
        """
        Test that a Makefile for a flat design is generated correctly.
        """
        tmpdir = tempfile.mkdtemp()
        proj_config = os.path.join(tmpdir, "config.json")

        settings = {
            "vlsi.core.technology": "nop",
            "vlsi.core.build_system": "make",
            "synthesis.inputs.top_module": "TopMod"
        }
        with open(proj_config, "w") as f:
            f.write(json.dumps(settings, cls=HammerJSONEncoder, indent=4))

        options = HammerDriverOptions(environment_configs=[],
                                      project_configs=[proj_config],
                                      log_file=os.path.join(tmpdir, "log.txt"),
                                      obj_dir=tmpdir)

        self.assertTrue(
            HammerVLSISettings.set_hammer_vlsi_path_from_environment(),
            "hammer_vlsi_path must exist")
        driver = HammerDriver(options)

        CLIDriver.generate_build_inputs(driver, lambda x: None)

        d_file = os.path.join(driver.obj_dir, "hammer.d")
        self.assertTrue(os.path.exists(d_file))

        with open(d_file, "r") as f:
            contents = f.readlines()

        targets = self._read_targets_from_makefile(contents)

        tasks = {"pcb", "syn", "par", "drc", "lvs"}
        expected_targets = tasks.copy()
        expected_targets.update({"redo-" + x for x in tasks if x is not "pcb"})
        expected_targets.update({
            os.path.join(tmpdir, x + "-rundir", x + "-output-full.json")
            for x in tasks
        })
        expected_targets.update({
            os.path.join(tmpdir, x + "-input.json")
            for x in tasks if x not in {"syn", "pcb"}
        })

        self.assertEqual(set(targets.keys()), set(expected_targets))

        # TODO at some point we should add more tests

        # Cleanup
        shutil.rmtree(tmpdir)
Esempio n. 4
0
    def test_syn_to_par_same_as_syn_par(self) -> None:
        """
        Test that syn-par generates the same par input as calling syn,
        syn-to-par.
        """

        # Set up some temporary folders for the unit test.
        syn_rundir = tempfile.mkdtemp()
        par_rundir = tempfile.mkdtemp()

        # Generate a config for testing.
        top_module = "dummy"
        config_path = os.path.join(syn_rundir, "run_config.json")
        syn_out_path = os.path.join(syn_rundir, "syn_out.json")
        syn_to_par_out_path = os.path.join(syn_rundir, "syn_par_out.json")
        self.generate_dummy_config(syn_rundir, config_path, top_module)

        # Run syn-to-par
        self.run_syn_to_par_with_output(config_path, syn_rundir, par_rundir,
                                        syn_out_path, syn_to_par_out_path)

        # Run syn-par
        with self.assertRaises(SystemExit) as cm:  # type: ignore
            CLIDriver().main(args=[
                "syn-par",  # action
                "-p",
                config_path,
                "--syn_rundir",
                syn_rundir,
                "--par_rundir",
                par_rundir
            ])
        self.assertEqual(cm.exception.code, 0)

        # Check that the syn-to-par generated par input and the syn-par
        # generated par input are the same, modulo any synthesis.outputs.*
        # settings since they don't matter for par input.
        with open(syn_to_par_out_path, "r") as f:
            with open(os.path.join(syn_rundir, "par-input.json"), "r") as f2:
                syn_to_par_output = json.loads(f.read())
                syn_to_par_output = dict(
                    filter(lambda i: "synthesis.outputs." not in i[0],
                           syn_to_par_output.items()))
                syn_par_output = json.loads(f2.read())
                syn_par_output = dict(
                    filter(lambda i: "synthesis.outputs." not in i[0],
                           syn_par_output.items()))
                self.assertEqual(syn_to_par_output, syn_par_output)

        # Cleanup
        shutil.rmtree(syn_rundir)
        shutil.rmtree(par_rundir)
Esempio n. 5
0
    def test_dump_macrosizes(self) -> None:
        """
        Test that dump-macrosizes works properly.
        """
        # Set up some temporary folders for the unit test.
        syn_rundir = tempfile.mkdtemp()

        # Generate a config for testing.
        top_module = "dummy"
        output_path = os.path.join(syn_rundir, "output.json")
        config_path = os.path.join(syn_rundir, "run_config.json")

        my_size = MacroSize(library='my_lib',
                            name='my_cell',
                            width=100.0,
                            height=100.0)

        def add_macro_sizes(d: Dict[str, Any]) -> Dict[str, Any]:
            output = deepdict(d)
            output["vlsi.technology.extra_macro_sizes"] = [
                my_size.to_setting()
            ]
            return output

        self.generate_dummy_config(syn_rundir,
                                   config_path,
                                   top_module,
                                   postprocessing_func=add_macro_sizes)

        # Check that running the CLIDriver executes successfully (code 0).
        with self.assertRaises(SystemExit) as cm:  # type: ignore
            CLIDriver().main(args=[
                "dump-macrosizes",  # action
                "-p",
                config_path,
                "--output",
                output_path,
                "--syn_rundir",
                syn_rundir,
                "--par_rundir",
                syn_rundir
            ])
        self.assertEqual(cm.exception.code, 0)

        # Check that dumped output should be same as what we read in.
        with open(output_path, "r") as f:
            dumped_output = json.loads(f.read())
            self.assertEqual(dumped_output, [my_size.to_setting()])

        # Cleanup
        shutil.rmtree(syn_rundir)
Esempio n. 6
0
    def test_syn_par_config_dumping(self) -> None:
        """
        Test that the syn_par step (running both synthesis and place-and-route) dumps the intermediate config files,
        namely synthesis output config and par input config.
        """

        # Set up some temporary folders for the unit test.
        syn_rundir = tempfile.mkdtemp()
        par_rundir = tempfile.mkdtemp()

        # Generate a config for testing.
        top_module = "dummy"
        config_path = os.path.join(syn_rundir, "run_config.json")
        self.generate_dummy_config(syn_rundir, config_path, top_module)

        # Check that running the CLIDriver executes successfully (code 0).
        with self.assertRaises(SystemExit) as cm:  # type: ignore
            CLIDriver().main(args=[
                "syn-par",  # action
                "-p",
                config_path,
                "--syn_rundir",
                syn_rundir,
                "--par_rundir",
                par_rundir
            ])
        self.assertEqual(cm.exception.code, 0)

        # Check that the synthesis output and par input configs got dumped.
        with open(os.path.join(syn_rundir, "syn-output.json"), "r") as f:
            syn_output = json.loads(f.read())
            self.assertEqual(syn_output["synthesis.outputs.output_files"],
                             ["/dev/null"])
            # syn-output should NOT keep other settings
            self.assertFalse("vlsi.core.technology" in syn_output)
        with open(os.path.join(syn_rundir, "syn-output-full.json"), "r") as f:
            syn_output_full = json.loads(f.read())
            self.assertEqual(syn_output_full["synthesis.outputs.output_files"],
                             ["/dev/null"])
            # syn-output-full should preserve other settings
            self.assertEqual(syn_output_full["vlsi.core.technology"], "nop")
        with open(os.path.join(syn_rundir, "par-input.json"), "r") as f:
            par_input = json.loads(f.read())
            self.assertEqual(par_input["par.inputs.top_module"], top_module)
            # par-input should preserve other settings
            self.assertEqual(par_input["vlsi.core.technology"], "nop")

        # Cleanup
        shutil.rmtree(syn_rundir)
        shutil.rmtree(par_rundir)
Esempio n. 7
0
    def test_syn_par_config_dumping(self) -> None:
        """
        Test that the syn_par step (running both synthesis and place-and-route) dumps the intermediate config files,
        namely synthesis output config and par input config.
        """

        # Set up some temporary folders for the unit test.
        syn_rundir = tempfile.mkdtemp()
        par_rundir = tempfile.mkdtemp()

        top_module = "dummy"
        config = {
            "vlsi.core.technology": "nop",
            "vlsi.core.synthesis_tool": "mocksynth",
            "vlsi.core.par_tool": "nop",
            "synthesis.inputs.top_module": top_module,
            "synthesis.inputs.input_files": ("/dev/null", ),
            "synthesis.mocksynth.temp_folder": syn_rundir
        }
        config_path = os.path.join(syn_rundir, "run_config.json")
        with open(config_path, "w") as f:
            f.write(json.dumps(config, indent=4))

        # Check that running the CLIDriver executes successfully (code 0).
        with self.assertRaises(SystemExit) as cm:  # type: ignore
            CLIDriver().main(args=[
                "syn-par",  # action
                "-p",
                config_path,
                "--syn_rundir",
                syn_rundir,
                "--par_rundir",
                par_rundir
            ])
        self.assertEqual(cm.exception.code, 0)

        # Check that the synthesis output and par input configs got dumped.
        with open(os.path.join(syn_rundir, "syn-output.json"), "r") as f:
            syn_output = json.loads(f.read())
            self.assertEqual(syn_output["synthesis.outputs.output_files"], [])
        with open(os.path.join(syn_rundir, "par-input.json"), "r") as f:
            par_input = json.loads(f.read())
            self.assertEqual(par_input["par.inputs.top_module"], top_module)

        # Cleanup
        shutil.rmtree(syn_rundir)
        shutil.rmtree(par_rundir)
Esempio n. 8
0
    def test_hier_dump_empty_constraints(self) -> None:
        """
        Test that hierarchical settings work properly even when no constraints
        are given.
        """
        # Set up some temporary folders for the unit test.
        syn_rundir = tempfile.mkdtemp()

        # Generate a config for testing.
        top_module = "dummy"
        config_path = os.path.join(syn_rundir, "run_config.json")

        def add_hier(d: Dict[str, Any]) -> Dict[str, Any]:
            output = deepdict(d)
            output["vlsi.inputs.default_output_load"] = 1
            output["vlsi.inputs.hierarchical.top_module"] = top_module
            output["vlsi.inputs.hierarchical.flat"] = "hierarchical"
            output["vlsi.inputs.hierarchical.config_source"] = "manual"
            output["vlsi.inputs.hierarchical.manual_modules"] = [{
                "mod1": ["m1s1", "m1s2"],
                "mod2": ["m2s1"],
                top_module: ["mod1", "mod2"]
            }]
            output[
                "vlsi.inputs.hierarchical.manual_placement_constraints"] = []
            output["vlsi.inputs.hierarchical.constraints"] = []
            return output

        self.generate_dummy_config(syn_rundir,
                                   config_path,
                                   top_module,
                                   postprocessing_func=add_hier)

        # Check that running the CLIDriver executes successfully (code 0).
        with self.assertRaises(SystemExit) as cm:  # type: ignore
            CLIDriver().main(args=[
                "auto",  # action
                "-p",
                config_path,
                "--obj_dir",
                syn_rundir
            ])
        self.assertEqual(cm.exception.code, 0)

        # Cleanup
        shutil.rmtree(syn_rundir)
Esempio n. 9
0
    def test_dump(self) -> None:
        """
        Test that dump works properly.
        """
        # Set up some temporary folders for the unit test.
        syn_rundir = tempfile.mkdtemp()

        # Generate a config for testing.
        top_module = "dummy"
        output_path = os.path.join(syn_rundir, "output.json")
        config_packed_path = os.path.join(syn_rundir, "run_config_packed.json")
        config_path = os.path.join(syn_rundir, "run_config.json")
        self.generate_dummy_config(syn_rundir, config_packed_path, top_module)
        # Equivalent config to above but not unpacked
        with open(config_packed_path, "r") as f:
            unpacked_config = hammer_config.reverse_unpack(json.loads(
                f.read()))
        with open(config_path, "w") as f:
            f.write(
                json.dumps(unpacked_config, cls=HammerJSONEncoder, indent=4))

        # Check that running the CLIDriver executes successfully (code 0).
        with self.assertRaises(SystemExit) as cm:  # type: ignore
            CLIDriver().main(args=[
                "dump",  # action
                "-p",
                config_path,
                "--output",
                output_path,
                "--syn_rundir",
                syn_rundir,
                "--par_rundir",
                syn_rundir
            ])
        self.assertEqual(cm.exception.code, 0)

        # Check that dumped output should be same as what we read in.
        with open(output_path, "r") as f:
            dumped_output = json.loads(f.read())
        with open(config_packed_path, "r") as f:
            packed_output = json.loads(f.read())
        self.assertEqual(packed_output, dumped_output)

        # Cleanup
        shutil.rmtree(syn_rundir)
Esempio n. 10
0
    def test_syn_to_par_improper(self) -> None:
        """
        Test that appropriate error messages are raised when syn-to-par
        is used on a config that does not have outputs.
        """

        # Set up some temporary folders for the unit test.
        syn_rundir = tempfile.mkdtemp()
        par_rundir = tempfile.mkdtemp()

        # Generate a config for testing.
        top_module = "dummy"
        config_path = os.path.join(syn_rundir, "run_config.json")
        log_path = os.path.join(syn_rundir, "log.txt")
        self.generate_dummy_config(syn_rundir, config_path, top_module)

        with HammerLoggingCaptureContext() as c:
            # Running syn-to-par on a not-output config should fail.
            with self.assertRaises(SystemExit) as cm:  # type: ignore
                CLIDriver().main(args=[
                    "syn-to-par",  # action
                    "-p",
                    config_path,
                    "--log",
                    log_path,
                    "--syn_rundir",
                    syn_rundir,
                    "--par_rundir",
                    par_rundir
                ])
            self.assertEqual(cm.exception.code, 1)
        self.assertTrue(
            c.log_contains(
                "Input config does not appear to contain valid synthesis outputs"
            ))

        # Cleanup
        shutil.rmtree(syn_rundir)
        shutil.rmtree(par_rundir)
Esempio n. 11
0
    def test_hier_dump(self) -> None:
        """
        Test that hierarchical settings work properly.
        """
        # Set up some temporary folders for the unit test.
        syn_rundir = tempfile.mkdtemp()

        # Generate a config for testing.
        top_module = "dummy"
        config_path = os.path.join(syn_rundir, "run_config.json")

        def add_hier(d: Dict[str, Any]) -> Dict[str, Any]:
            output = deepdict(d)
            dummy_placement = PlacementConstraint(
                path="dummy",
                type=PlacementConstraintType.Dummy,
                x=0.0,
                y=0.0,
                width=10.0,
                height=10.0,
                orientation=None,
                margins=None,
                layers=None,
                obs_types=None).to_dict()
            output["vlsi.inputs.default_output_load"] = 1
            output["vlsi.inputs.hierarchical.top_module"] = top_module
            output["vlsi.inputs.hierarchical.flat"] = "hierarchical"
            output["vlsi.inputs.hierarchical.config_source"] = "manual"
            output["vlsi.inputs.hierarchical.manual_modules"] = [{
                "mod1": ["m1s1", "m1s2"],
                "mod2": ["m2s1"],
                top_module: ["mod1", "mod2"]
            }]
            manual_constraints = [{
                "mod1": [dummy_placement]
            }, {
                "mod2": [dummy_placement]
            }, {
                "m1s1": [dummy_placement]
            }, {
                "m1s2": [dummy_placement]
            }, {
                "m2s1": [dummy_placement]
            }, {
                top_module: [dummy_placement]
            }]
            output[
                "vlsi.inputs.hierarchical.manual_placement_constraints"] = manual_constraints
            output["vlsi.inputs.hierarchical.constraints"] = [{
                "mod1": [{
                    "vlsi.inputs.default_output_load": 2
                }]
            }, {
                "m2s1": [{
                    "vlsi.inputs.default_output_load": 3
                }]
            }]
            return output

        self.generate_dummy_config(syn_rundir,
                                   config_path,
                                   top_module,
                                   postprocessing_func=add_hier)

        # Check that running the CLIDriver executes successfully (code 0).
        with self.assertRaises(SystemExit) as cm:  # type: ignore
            CLIDriver().main(args=[
                "auto",  # action
                "-p",
                config_path,
                "--obj_dir",
                syn_rundir
            ])
        self.assertEqual(cm.exception.code, 0)

        # Check that dumped output should be same as what we read in.
        with open(os.path.join(syn_rundir, "syn-m2s1/full_config.json"),
                  "r") as f:
            dumped_output = json.loads(f.read())
        self.assertEqual(dumped_output['vlsi.inputs.default_output_load'], 3)
        with open(os.path.join(syn_rundir, "syn-m1s1/full_config.json"),
                  "r") as f:
            dumped_output = json.loads(f.read())
        self.assertEqual(dumped_output['vlsi.inputs.default_output_load'], 1)
        with open(os.path.join(syn_rundir, "syn-mod1/full_config.json"),
                  "r") as f:
            dumped_output = json.loads(f.read())
        self.assertEqual(dumped_output['vlsi.inputs.default_output_load'], 2)

        # Cleanup
        shutil.rmtree(syn_rundir)
Esempio n. 12
0
    def test_hier_makefile(self) -> None:
        """
        Test that a Makefile for a hierarchical design is generated correctly.
        """
        tmpdir = tempfile.mkdtemp()
        proj_config = os.path.join(tmpdir, "config.json")

        settings = {
            "vlsi.core.technology":
            "nop",
            "vlsi.core.build_system":
            "make",
            "vlsi.inputs.hierarchical.mode":
            "hierarchical",
            "vlsi.inputs.hierarchical.top_module":
            "TopMod",
            "vlsi.inputs.hierarchical.config_source":
            "manual",
            "vlsi.inputs.hierarchical.manual_modules": [{
                "TopMod": ["SubModA", "SubModB"]
            }],
            "vlsi.inputs.hierarchical.manual_placement_constraints": [{
                "TopMod": [{
                    "path": "top",
                    "type": "toplevel",
                    "x": 0,
                    "y": 0,
                    "width": 1234,
                    "height": 7890,
                    "margins": {
                        "left": 1,
                        "top": 2,
                        "right": 3,
                        "bottom": 4
                    }
                }, {
                    "path": "top/C",
                    "type": "placement",
                    "x": 2,
                    "y": 102,
                    "width": 30,
                    "height": 40
                }, {
                    "path": "top/B",
                    "type": "hierarchical",
                    "x": 10,
                    "y": 30,
                    "master": "SubModB"
                }, {
                    "path": "top/A",
                    "type": "hierarchical",
                    "x": 200,
                    "y": 120,
                    "master": "SubModA"
                }]
            }, {
                "SubModA": [{
                    "path": "a",
                    "type": "toplevel",
                    "x": 0,
                    "y": 0,
                    "width": 100,
                    "height": 200,
                    "margins": {
                        "left": 0,
                        "top": 0,
                        "right": 0,
                        "bottom": 0
                    }
                }]
            }, {
                "SubModB": [{
                    "path": "b",
                    "type": "toplevel",
                    "x": 0,
                    "y": 0,
                    "width": 340,
                    "height": 160,
                    "margins": {
                        "left": 0,
                        "top": 0,
                        "right": 0,
                        "bottom": 0
                    }
                }]
            }]
        }
        with open(proj_config, "w") as f:
            f.write(json.dumps(settings, cls=HammerJSONEncoder, indent=4))

        options = HammerDriverOptions(environment_configs=[],
                                      project_configs=[proj_config],
                                      log_file=os.path.join(tmpdir, "log.txt"),
                                      obj_dir=tmpdir)

        self.assertTrue(
            HammerVLSISettings.set_hammer_vlsi_path_from_environment(),
            "hammer_vlsi_path must exist")
        driver = HammerDriver(options)

        CLIDriver.generate_build_inputs(driver, lambda x: None)

        d_file = os.path.join(driver.obj_dir, "hammer.d")
        self.assertTrue(os.path.exists(d_file))

        with open(d_file, "r") as f:
            contents = f.readlines()

        targets = self._read_targets_from_makefile(contents)

        mods = {"TopMod", "SubModA", "SubModB"}
        expected_targets = {
            "pcb",
            os.path.join(tmpdir, "pcb-rundir", "pcb-output-full.json")
        }
        expected_targets.update({"sim-rtl-" + x for x in mods})
        expected_targets.update({"syn-" + x for x in mods})
        expected_targets.update({"sim-syn-" + x for x in mods})
        expected_targets.update({"par-" + x for x in mods})
        expected_targets.update({"sim-par-" + x for x in mods})
        expected_targets.update({"power-par-" + x for x in mods})
        expected_targets.update({"lvs-" + x for x in mods})
        expected_targets.update({"drc-" + x for x in mods})
        expected_targets.update({"redo-sim-rtl-" + x for x in mods})
        expected_targets.update({"redo-syn-" + x for x in mods})
        expected_targets.update({"redo-sim-syn-" + x for x in mods})
        expected_targets.update({"redo-par-" + x for x in mods})
        expected_targets.update({"redo-sim-par-" + x for x in mods})
        expected_targets.update({"redo-power-par-" + x for x in mods})
        expected_targets.update({"redo-lvs-" + x for x in mods})
        expected_targets.update({"redo-drc-" + x for x in mods})
        expected_targets.update({
            os.path.join(tmpdir, "sim-rtl-" + x, "sim-output-full.json")
            for x in mods
        })
        expected_targets.update({
            os.path.join(tmpdir, "syn-" + x, "syn-output-full.json")
            for x in mods
        })
        expected_targets.update({
            os.path.join(tmpdir, "sim-syn-" + x, "sim-output-full.json")
            for x in mods
        })
        expected_targets.update({
            os.path.join(tmpdir, "par-" + x, "par-output-full.json")
            for x in mods
        })
        expected_targets.update({
            os.path.join(tmpdir, "sim-par-" + x, "sim-output-full.json")
            for x in mods
        })
        expected_targets.update({
            os.path.join(tmpdir, "power-par-" + x, "power-output-full.json")
            for x in mods
        })
        expected_targets.update({
            os.path.join(tmpdir, "lvs-" + x, "lvs-output-full.json")
            for x in mods
        })
        expected_targets.update({
            os.path.join(tmpdir, "drc-" + x, "drc-output-full.json")
            for x in mods
        })
        # Only non-leafs get a syn-*-input.json target
        expected_targets.update({
            os.path.join(tmpdir, "syn-" + x + "-input.json")
            for x in mods if x in {"TopMod"}
        })
        expected_targets.update({
            os.path.join(tmpdir, "sim-syn-" + x + "-input.json")
            for x in mods
        })
        expected_targets.update(
            {os.path.join(tmpdir, "par-" + x + "-input.json")
             for x in mods})
        expected_targets.update({
            os.path.join(tmpdir, "sim-par-" + x + "-input.json")
            for x in mods
        })
        expected_targets.update({
            os.path.join(tmpdir, "power-par-" + x + "-input.json")
            for x in mods
        })
        expected_targets.update({
            os.path.join(tmpdir, "power-sim-par-" + x + "-input.json")
            for x in mods
        })
        expected_targets.update(
            {os.path.join(tmpdir, "lvs-" + x + "-input.json")
             for x in mods})
        expected_targets.update(
            {os.path.join(tmpdir, "drc-" + x + "-input.json")
             for x in mods})

        self.assertEqual(set(targets.keys()), expected_targets)

        # TODO at some point we should add more tests

        # Cleanup
        shutil.rmtree(tmpdir)