dan-fritchman / Hdl21

Hardware Description Library
BSD 3-Clause "New" or "Revised" License
69 stars 16 forks source link

Module Import from a Netlist or Schematic File #108

Open curtisma opened 1 year ago

curtisma commented 1 year ago

I'm interested in using Hdl21 for creating a test bench for a design already defined in a traditional schematic. In this case, it is coming from an xschem schematic.

It would also be great to be able to import a spice netlist in general as a module. This would allow some sub-blocks to be defined in a schematic based on user preference.

I was thinking I could start with a netlist of the dut design and import it as a module into Hdl21. I could then set up the testbench using Hdl21.

Are there any existing import options? If not, do you have any recommendations on the best way to implement this functionality?

One other nice feature would be if you could create a module which is a wrapper pointing to an xschem schematic file (ascii) which it can import as a module directly, handling the netlisiting using the xschem CLI.

curtisma commented 1 year ago

I would propose adding another Module class called "SchematicModule" that could wrap a schematic for inclusion as a black box similar to "ExternalModule". It would be given the path to the schematic and an enumerated schematic tool type. Then it could use the external tool CLI to netlist the schematic. For example, xscem. Then the netlist could be included as part of the testbench netlist.

Code to netlist an xschem schematic to an ngspice netlist:

    def netlist(self):
        """netlist an xschem schematic."""
        print(f"netlisting {str(self.schematic_path)}\n to {self.netlist_path}")
        self.netlist_path.parent.rmdir()
        self.netlist_path.parent.mkdir(parents=True,exist_ok=True)
        sch_picture_path = self.netlist_path.parent / \
                           (self.schematic_path.stem + ".svg")
        run_result=subprocess.run(["xschem", "-q",
                        "-n", "-o", str(self.netlist_path.parent),
                        "--svg", "--plotfile", str(sch_picture_path),
                        "-l", str(self.netlist_log_path),
                        "--rcfile", str(self.xschemrc_path),
                        str(self.schematic_path)], 
                       capture_output=True, check=True)

Full Class Code:

class xschem_testbench:
    #default_root_result_path = Path.home() / "sim_results"
    xschemrc_path = Path("/foss/pdks/sky130A/libs.tech/xschem/xschemrc")
    def __init__(self, name: str, schematic_path: Union[Path, str],
                 result_path: optional_path = None) -> None:
        self.name = name
        self.schematic_path = Path(schematic_path)
        if result_path is not None:
            self.result_path = Path(result_path)
        else:
            #self.result_path = self.default_root_result_path / self.name
            self.result_path = self.schematic_path.parent
        netlist_filename = self.schematic_path.stem +".spice"
        self.netlist_path = self.result_path / "netlist" / netlist_filename
        self.netlist_log_path = self.netlist_path.parent / ".netlisting.log"
        soa_log_filename = self.schematic_path.stem + ".soa.log"
        self.soa_log_path = self.result_path.parent / soa_log_filename
        sim_log_filename = self.schematic_path.stem + ".sim.log"
        self.sim_log_path = self.result_path.parent / sim_log_filename

    def netlist(self):
        """netlist an xschem schematic."""
        print(f"netlisting {str(self.schematic_path)}\n to {self.netlist_path}")
        self.netlist_path.parent.rmdir()
        self.netlist_path.parent.mkdir(parents=True,exist_ok=True)
        sch_picture_path = self.netlist_path.parent / \
                           (self.schematic_path.stem + ".svg")
        run_result=subprocess.run(["xschem", "-q",
                        "-n", "-o", str(self.netlist_path.parent),
                        "--svg", "--plotfile", str(sch_picture_path),
                        "-l", str(self.netlist_log_path),
                        "--rcfile", str(self.xschemrc_path),
                        str(self.schematic_path)], 
                       capture_output=True, check=True)
        # if len(run_result.stdout) > 0:
        #     print(run_result.stdout)
        #     

        #os.system(f'xschem -q'
        #          f' -n {self.netlist_path}'
        #          f' {self.schematic_path}')

    def simulate(self) -> ngspice_result:
        """Simulate using ngspice"""
        if not self.netlist_path.is_file():
            raise RuntimeError("Testbench netlist does not exist.")
        print(f'Simulating {self.netlist_path.name} at \n'
              f'  {self.netlist_path}')
        raw_output_path = f'{self.result_path/self.name}.raw'
        output_path = f'{self.result_path/self.name}.out'
        run_result=subprocess.run(
            ["ngspice", "-b",
             "-o", str(output_path),
             "-r", raw_output_path,
             f'--soa-log={self.soa_log_path}',
             str(self.netlist_path),
            ],
            capture_output=True, check=True)
        with open(self.sim_log_path,mode="w") as log:
                 log.write(str(run_result.stdout))
        return ngspice_result(self, output_path, raw_output_path)

    def run_schematic(self) -> ngspice_result:
        self.netlist()
        return self.simulate()

    @classmethod
    def run(cls, name: str, schematic_path: Union[Path, str]):
        "Netlists and then simulates a schematic"
        tb = cls(name, schematic_path)
        result = tb.run_schematic()
        result.print_summary()
curtisma commented 1 year ago

I'm going to try this out as part of evaluating a bandgap we're working on.

curtisma commented 1 year ago

The current version I'm working on will just support xschem schematics but we could consider adding other formats in the future such as:

dan-fritchman commented 1 year ago

Thanks for digging in here!

Some background: Hdl21 really only (directly) knows about one import/ export format, and that is VLSIR. And that's on purpose, so that the code for, for example, translating to industry-standard formats can be shared with other projects. The primary implementations of this are in VlsirTools.

Now, the primary netlist-parsing in this whole wide family of software is here: https://github.com/dan-fritchman/Netlist Which:

One could certainly write a separate netlist parser, and cover a different set of the universe of available netlist content. (Noting there are so many fun cases to discover out there.)

My preference would be to -

The latter should be pretty straightforward, and pretty sane. They each have concepts of, say, Module and Param, mappable between each other.

Alternately netlist-parsing could produce VLSIR directly - but this would require adding a whole buncha stuff to the VLSIR schema. Netlist's parser includes things like math expression trees (as a traditional compiler would have), families of binned models, etc.

On the types this all produces - I don't see why we'd need another specialty "kind of" ExternalModule. Code can just return ExternalModules, whatever their ultimate source.

ThomasPluck commented 1 year ago

Picking up where this left off... because I have a shiny Op-Amp netlist I want to use and I'd rather complain and muse about it than actually implement it... so, I'm seeing there are a few proposals on the table here:

  1. Implement a direct netlist -> HDL21 (maybe even specific to just a PDK module) converter that creates the ExternalModules on the fly.
  2. Add a converter to Netlist which maps its data model to current VLSIR and then convert that into an HDL21 module using from_proto.
  3. Integrate the Netlist data model into VLSIR itself and add it to the VLSIR Tools family in earnest.
  4. Create a Denetlister class in VlsirTools and then have this work with the current data model in VLSIR.

Either way, these will all sit flush with a <LIBRARY>.parse_netlist() function that will map a netlist to an external module, no matter what pipeline is chosen. I feel as if the choice being made here comes down to the boundaries between VLSIR, VlsirTools and HDL21.

The way I imagine the project is as follows:

So, in my head, proposal 3 makes the most sense, as we're expanding VlsirTools into a more general simulator utilities library that allows whatever goes into and comes out of a simulator to be translated into and out of VLSIR. Although this then begs the question if all simulator stuff should then live in VlsirTools, but let's not get ahead of ourselves here.

So yeah, what does VLSIR team make of all this, before we get building a pipeline through 3 repositories?

dan-fritchman commented 1 year ago

Yeah reiterating my prior commentary - I would very much recommend starting with the parsers here:
https://github.com/dan-fritchman/Netlist And turning its data model into VLSIR.

Netlist-parsing is maybe like C parsing, or XML parsing, or some-such. Seems (and is!) really easy to get rolling. Then one notices the edge-cases, one at a time, until they're like 98% of the action. Having written all the corner cases of that parser already, I would not recommend re-discovering them. (I am reminded of this right now by using another project which went the "yet another custom parser" route... and stinks.)

Also agree there's more git repos at play than would be ideal. Pulling that into the Vlsir repo could make sense.

dan-fritchman commented 1 year ago

After a re-read, I realize that's basically your #3. So, agree.