From 8faaa3ac2098697be2ecf1752170b655e01fb18e Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Mon, 5 Jan 2026 22:33:44 -0700 Subject: [PATCH 01/29] Document some of how the database is laid out --- docs/database.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/database.md diff --git a/docs/database.md b/docs/database.md new file mode 100644 index 0000000..8fcf52b --- /dev/null +++ b/docs/database.md @@ -0,0 +1,54 @@ +# Database + +The chip database for prjoxide is ingested into libprjoxide and used to construct the BBA database used by nextpnr. + +## Per device files + +Each supported device has the following json files to describe it: + +### baseaddr.json + +Some primitives are internally routed on an LMMI bus that is used for configuration. This file describes the addresses of mapped primitives and the length of their address in bits. + +These addresses are mapped with the fuzzer in fuzzers/LIFCL/100-ip-base. This requires, for each device, a mapping between the primitive type in question and it's site. This mapping isn't immediately apparent; but can usually be derived by placing the known number of that primitive in a design and looking at it in the physical designer / routing tool in lattice. + +### iodb.json + +The IO database file contains information on a particular pins function and logical name for each given package. + +Each entry describes the pin in terms of it's physical location. Since all the IO pins are on the outside edge of the chip, you can map the tile as a combination of it's 'offset' and it's 'side' -- a pin on the top for instance with an offset of 58 maps to tile 58, 0. The pio attribute describes which pin in that tile is being described. + +The iodb.json files are constructed by running tools/parse_pins.py against the CSV files lattice makes available in the documentation section for each device. + +### tilegrid.json + +The tile grid file describes where the physical location of the tile is, what it's type is, and where it is encoded into the final bitstream file. + +This file is generated by tools/tilegrid_all.py. A new device will require some custom fields in the devices.json file but also tools/extract_tilegrid.py. + +## Shared database files + +The various .ron files are serialized/deserialized to rust. Each fuzzer deserializes the current database, processes samples, and then reserializes out the updated database. + +### devices.json + +This serves as a master listing of each device and metadata associated with that device: + +- packages: Comes from various lattice documentation, can also be seen by looking at the radiant device selection dialog. +- frame metadata: There are various necessary peices of data here. All are available in the "sysCONFIG Guide for Nexus Platform" document from the lattice website. +- idcode: This is also availble in the above document. +- max_row / max_cols: These are retreivable from the physical designer for a given chip, or from the tilegrid metadata file. + +This file serves as the central index for devices in the libprjoxide library. + +### iptypes + +These files are generated by various fuzzers associated with the given primitive. They map out the relationship between configuration parameters and the bits set in the bitstream. + +### tiletypes + +These files are also generated by fuzzers. While they also map out the relationship between various parameters and bits in the bitstream, they mainly focus on the interconnection between tiles themselves. This gives both a graph on what connections are possible but also the way those connections are configured in the bitstream. + +### timing + +This is a collection of a bunch of cell types and a description of the delay timing between pins as well as the setup and hold time for each pin in the cell. \ No newline at end of file From 6e607320a3a26311278e31fff16e2fa199a8eb82 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Mon, 5 Jan 2026 22:36:27 -0700 Subject: [PATCH 02/29] Update scripting for 2023-2025 radiant --- radiant.sh | 12 ++++++++ tools/parse_pins.py | 48 +++++++++----------------------- util/common/lapie.py | 65 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 78 insertions(+), 47 deletions(-) diff --git a/radiant.sh b/radiant.sh index c1ccf48..ad95177 100755 --- a/radiant.sh +++ b/radiant.sh @@ -27,6 +27,18 @@ case "${PART}" in LSE_ARCH="lifcl" SPEED_GRADE="${SPEED_GRADE:-7_High-Performance_1.0V}" ;; + LIFCL-33) + PACKAGE="${DEV_PACKAGE:-WLCSP84}" + DEVICE="LIFCL-33" + LSE_ARCH="lifcl" + SPEED_GRADE="${SPEED_GRADE:-7_High-Performance_1.0V}" + ;; + LIFCL-33U) + PACKAGE="${DEV_PACKAGE:-FCCSP104}" + DEVICE="LIFCL-33U" + LSE_ARCH="lifcl" + SPEED_GRADE="${SPEED_GRADE:-7_High-Performance_1.0V}" + ;; LIFCL-40) PACKAGE="${DEV_PACKAGE:-CABGA400}" DEVICE="LIFCL-40" diff --git a/tools/parse_pins.py b/tools/parse_pins.py index f3f1f74..894d983 100644 --- a/tools/parse_pins.py +++ b/tools/parse_pins.py @@ -12,44 +12,21 @@ def main(): sl = sl.strip() if len(sl) == 0 or sl.startswith('#'): continue - splitl = sl.split(',') + splitl = [x.strip() for x in sl.split(',')] if len(splitl) == 0 or splitl[0] == '': continue if len(packages) == 0: # Header line - COL_PADN = 0 - COL_FUNC = 1 - COL_CUST_NAME = 2 - COL_BANK = 3 - COL_DF = 4 - COL_LVDS = 5 - COL_HIGHSPEED = 6 - COL_DQS = 7 - COL_PKG_START = 8 + COL_PADN = splitl.index("PADN") + COL_FUNC = splitl.index("Pin/Ball Funcion") + COL_CUST_NAME = splitl.index("CUST_NAME") if "CUST_NAME" in splitl else None + COL_BANK = splitl.index("BANK") + COL_DF = splitl.index("Dual Function") + COL_LVDS = splitl.index("LVDS") + COL_HIGHSPEED = splitl.index("HIGHSPEED") + COL_DQS = splitl.index("DQS") if "DQS" in splitl else None + COL_PKG_START = 1 + max(x for x in [COL_PADN, COL_FUNC, COL_CUST_NAME, COL_BANK, COL_DF, COL_LVDS, COL_HIGHSPEED, COL_DQS] if x is not None) - if splitl[0] == "index": - # new style pinout - COL_PADN = 1 - COL_FUNC = 2 - COL_CUST_NAME = None - COL_BANK = 3 - COL_DF = 5 - COL_LVDS = 6 - COL_HIGHSPEED = 7 - COL_DQS = 4 - COL_PKG_START = 8 - elif splitl[2] == "BANK": - # LIFCL-17 style pinout - COL_PADN = 0 - COL_FUNC = 1 - COL_CUST_NAME = None - COL_BANK = 2 - COL_DF = 4 - COL_LVDS = 5 - COL_HIGHSPEED = 6 - COL_DQS = 3 - COL_PKG_START = 7 - assert splitl[COL_PADN] == "PADN" packages = splitl[COL_PKG_START:] continue func = splitl[COL_FUNC] @@ -59,7 +36,7 @@ def main(): io_pio = -1 io_dqs = [] io_vref = -1 - if len(func) >= 4 and func[0] == 'P' and func[1] in ('T', 'L', 'R', 'B') and func[-1] in ('A', 'B', 'C', 'D'): + if len(func) >= 4 and func[0] == 'P' and func[1] in ('T', 'L', 'R', 'B') and func[-1] in ('A', 'B', 'C', 'D') and "_" not in func: # Regular PIO io_offset = int(func[2:-1]) io_side = func[1] @@ -67,7 +44,7 @@ def main(): io_pio = "ABCD".index(func[-1]) if io_spfunc == ['-']: io_spfunc = [] - io_dqs = splitl[COL_DQS] + io_dqs = splitl[COL_DQS] if COL_DQS is not None else "" if io_dqs == "" or io_dqs == "-": io_dqs = [] elif io_dqs.find("DQSN") == 1: @@ -77,6 +54,7 @@ def main(): elif io_dqs.find("DQ") == 1: io_dqs = [0, int(io_dqs[3:])] else: + print(f"Bad DQS type {io_dqs}") assert False, "bad DQS type" for spf in io_spfunc: diff --git a/util/common/lapie.py b/util/common/lapie.py index 76b8db2..116be1b 100644 --- a/util/common/lapie.py +++ b/util/common/lapie.py @@ -8,6 +8,38 @@ import tempfile import re + +# `lapie` seems to be renamed every version or so. Map that out here. Most installations will have +# the version name at the end of their path, so we just look at the radiant dir for a hint. The user +# can override this setting with a RADIANTVERSION env variable +known_versions = [ "2.2", "3.1", "2023", "2024", "2025" ] +RADIANT_DIR = os.environ.get("RADIANTDIR") +radiant_version= os.environ.get("RADIANTVERSION", None) + +if radiant_version is None: + for version in known_versions: + if RADIANT_DIR.find(version) > -1: + radiant_version = version + +if radiant_version is None: + radiant_version = "3.1" + +if radiant_version == "2023": + tcltool = "lark" + tcltool_log = "radiantc.log" + dev_enable_name = "RAT_DEV_ENABLE" +elif radiant_version == "2025" or radiant_version == "2024": + # For whatever reason; these versions of the tool have a dependency on libqt 3 so finding a way to run it + # might be challenging; even in a container environment. Included here for completeness; recommend running 2023 + # for tasks requiring this instead. + tcltool = "labrus" + tcltool_log = "radiantc.log" + dev_enable_name = "RAT_DEV_ENABLE" +else: + tcltool = "lapie" + tcltool_log = "lapie.log" + dev_enable_name = "LATCL_DEV_ENABLE" + def run(commands, workdir=None): """Run a list of Tcl commands, returning the output as a string""" rcmd_path = path.join(database.get_oxide_root(), "radiant_cmd.sh") @@ -18,11 +50,11 @@ def run(commands, workdir=None): for c in commands: f.write(c + '\n') env = os.environ.copy() - env["LATCL_DEV_ENABLE"] = "1" - result = subprocess.run(["bash", rcmd_path, "lapie", scriptfile], cwd=workdir, env=env).returncode + env[dev_enable_name] = "1" + result = subprocess.run(["bash", rcmd_path, tcltool, scriptfile], cwd=workdir, env=env).returncode # meh, fails sometimes # assert result == 0, "lapie returned non-zero status code {}".format(result) - outfile = path.join(workdir, 'lapie.log') + outfile = path.join(workdir, tcltool_log) output = "" with open(outfile, 'r') as f: for line in f: @@ -63,8 +95,10 @@ def __init__(self, name): self.downhill_pips = [] self.pins = [] -node_re = re.compile(r'^\[\s*(\d+)\]\s*([A-Z0-9a-z_]+)') -pip_re = re.compile(r'^([A-Z0-9a-z_]+) (<--|<->|-->) ([A-Z0-9a-z_]+) \(Flags: ...., (\d+)\) \(Buffer: ([A-Z0-9a-z_]+)\)') +node_re = re.compile(r'^\[\s*\d+\]\s*([A-Z0-9a-z_]+)') +alias_node_re = re.compile(r'^\s*Alias name = ([A-Z0-9a-z_]+)') +pip_re = re.compile(r'^([A-Z0-9a-z_]+) (<--|<->|-->) ([A-Z0-9a-z_]+) \(Flags: .+, (\d+)\) \(Buffer: ([A-Z0-9a-z_]+)\)') +#R1C77_JLFTRMFAB7_OSC_CORE <-- R1C75_JCIBMUXOUTA7 (Flags: ----j, 0) (Buffer: b_ciboutbuf) pin_re = re.compile(r'^Pin : ([A-Z0-9a-z_]+)/([A-Z0-9a-z_]+) \(([A-Z0-9a-z_]+)\)') def parse_node_report(rpt): @@ -72,15 +106,20 @@ def parse_node_report(rpt): nodes = [] for line in rpt.split('\n'): sl = line.strip() - nm = node_re.match(sl) - if nm: - curr_node = NodeInfo(nm.group(2)) - nodes.append(curr_node) - continue + print("Node line", sl) + for re in [node_re, alias_node_re]: + nm = re.match(sl) + if nm: + curr_node = NodeInfo(nm.group(1)) + print(f"Curr node {nm.group(1)}") + nodes.append(curr_node) + continue + pm = pip_re.match(sl) if pm: flg = int(pm.group(4)) btyp = pm.group(5) + print(f"Found connection {pm}") if pm.group(2) == "<--": curr_node.uphill_pips.append( PipInfo(pm.group(3), pm.group(1), False, flg, btyp) @@ -100,10 +139,12 @@ def parse_node_report(rpt): assert False continue qm = pin_re.match(sl) - if qm: + print("Match", qm, curr_node) + if qm and curr_node: curr_node.pins.append( PinInfo(qm.group(1), qm.group(2), curr_node.name, qm.group(3)) ) + print([x.name for x in nodes]) return nodes @@ -115,7 +156,7 @@ def get_node_data(udb, nodes, regex=False): nodelist = nodes[0] elif len(nodes) > 1: nodelist = "[list {}]".format(" ".join(nodes)) - run_with_udb(udb, ['dev_report_node -file {} [get_nodes {}{}]'. + run_with_udb(udb, ['dev_report_node -file {} [dev_get_nodes {}{}]'. format(nodefile, "-re " if regex else "", nodelist)]) with open(nodefile, 'r') as nf: return parse_node_report(nf.read()) From 4e3472d056f7e3f11d76673aaaaebecb3c92de0b Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Mon, 5 Jan 2026 23:44:17 -0700 Subject: [PATCH 03/29] Add bel overview --- docs/bels/overview.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 docs/bels/overview.md diff --git a/docs/bels/overview.md b/docs/bels/overview.md new file mode 100644 index 0000000..3517c56 --- /dev/null +++ b/docs/bels/overview.md @@ -0,0 +1,14 @@ +# Overview + +The tiletype of a tile dictates which BEL's are available on a given tile and which options they might have exposed. This is mapped out fully in prjoxide/libprjoxide/prjoxide/src/bels.rs, namely in `get_tile_bels`. + +A given bel might span over multiple logical tiles. It's anchor tile is the one with the appropriate tiletype but for routing information on where the related tile is there is rel_x and rel_y; which encode the relative tile offset for the related tile. This data can be varied based on the family, device and actual tile in question. + +For bels with these offsets, the offset information is used in fuzzing the routing to map the interconnect. The offset themselves can be devined from the output of the dev_get_nodes command and report for nearby CIB tiles. For instance, related tiles to LRAM will have LRAM_CORE wires in their tile. + +Bel information is encoded in the bba file which is produced by prjoxide and ingested in the build process of nextpnr. + +# References + +- [Terminology](https://fpga-interchange-schema.readthedocs.io/device_resources.html) + From 54656f38de2f111352644c6bf229e24bb97138c1 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Mon, 19 Jan 2026 08:52:17 -0700 Subject: [PATCH 04/29] Add docs --- docs/general/bitstream.md | 6 + docs/general/fuzzing.md | 46 + docs/general/structure.md | 72 + examples/spinex_minimal/Makefile | 0 .../spinex_minimal/SpinexMinimal_references.v | 1614 +++++++++++++++++ examples/spinex_minimal/tinyclunx.pdc | 2 + examples/spinex_minimal/tinyclunx33.pdc | 2 + 7 files changed, 1742 insertions(+) create mode 100644 docs/general/fuzzing.md create mode 100644 docs/general/structure.md create mode 100644 examples/spinex_minimal/Makefile create mode 100644 examples/spinex_minimal/SpinexMinimal_references.v create mode 100644 examples/spinex_minimal/tinyclunx.pdc create mode 100644 examples/spinex_minimal/tinyclunx33.pdc diff --git a/docs/general/bitstream.md b/docs/general/bitstream.md index b810849..23ca0bd 100644 --- a/docs/general/bitstream.md +++ b/docs/general/bitstream.md @@ -28,8 +28,14 @@ All commands (except the `0xFF` dummy command) are followed by three "parameter" - **ISC_PROGRAM_DONE** (`0x5E`): ends configuration and starts FPGA fabric running - **LSC_POWER_CTRL** (`0x56`): third param byte configures internal power switches (detail unknown) +Fuller table available [here](https://www.latticesemi.com/-/media/LatticeSemi/Documents/ApplicationNotes/PT3/FPGA-TN-02099-3-5-sysCONFIG-User-Guide-for-Nexus-Platform.ashx?document_id=52790). + ## Config Frames +Configuration bits are two dimensional. For a given device, there is a grid of frame_cnt by bits_per_frame size. Each tile configuration exists as a sub rectangle inside of the overall device grid. Each tile in the tilegrid.json file specifies the coordinates for that tiles configuration data. + +An important detail is that the relative bits in each tile sharing a tiletype mean the same thing for that tile. This means that when we have the configuration mapping for a single tile, we have it for all tiles of that type as well. + Config frames are written in three chunks (numbers for LIFCL): - 32 frames at address 0x8000 set up left and right side IO/IP diff --git a/docs/general/fuzzing.md b/docs/general/fuzzing.md new file mode 100644 index 0000000..d16a584 --- /dev/null +++ b/docs/general/fuzzing.md @@ -0,0 +1,46 @@ +# Overview + +The primary thing to solve for to be able to run placement and routing on a given device is the relationship between BELs +that nextpnr understands to sites on the device, as well as the routing pips available, and what bits need to be flipped +in the bitstream frames to achieve a given route / BEL configuration. + +The main process is to create a minimal verilog file with the features that need to be isolated, generate a bitstream +from that file, and compare it to a baseline bitstream without that feature. This generates a bitstream delta which is +represented as a list of (tile, frame, bit) tuples which changed between the two. + +The main difficulty presented is that generating a bitstream takes a few seconds, and given the scale of number of things +to test, some care has to be taken to minimize the state space to attempt. To completely map a single device requires +thousands to tens of thousands of bitstream generations. + +The results of all this fuzzing ends up in the database. + +## BEL Fuzzing + +BEL fuzzing tends to be straightforward, although it does rely on knowing how to configure the primitive in question, in +terms of what options it supports and how to specify those options. Most configuration options can be fuzzed in isolation +with the others which keeps down the number of bitstreams to generate. + +### Enum Fuzzing + +Many primitives have documented series of enumerated values. One bitstream is typically generated per enum value, and the +mapping is relatively simple. The main work required here is to identify which options are valid and when they are +operative. + +### Word Fuzzing + +Word fuzzing is conceptually the same as enum fuzzing, but is used against parameters that exist as integers. There is +an assumption here that a word of `N` bits long will require only `N` evaluations to map; ie that any bit in the value +maps to only one bit in the config block. Otherwise something like the initialization values of a LUT would be intractable. + +## PIP Fuzzing + +PIP fuzzing is the most difficult aspect to constrain to a limited number of trials. We can test these by placing a single +ARC (with a SLICE so it is not optimized away), and then looking at which tiles were impacted. This is usually done by +passing in a set of nodes to pull the PIPs from, and then creating a design with each of those pips placed in isolation. + +While BEL fuzzing tends to operate against only one or two tiles at a time, a given node is hard to constrain to a given +tile. Some PIPs are also predicated on a related site being active. + +Some edges between nodes are always active. These are detected by placing the ARC and observing no change in relevant +tiles. These are refered to as 'connections' in the database. For fuzzing purposes, it is important to pass the correct +ignore list to the solver since if an irrelevant tile has a change in it, it will not mark it as a pure connection. \ No newline at end of file diff --git a/docs/general/structure.md b/docs/general/structure.md new file mode 100644 index 0000000..23d001a --- /dev/null +++ b/docs/general/structure.md @@ -0,0 +1,72 @@ +# Overview + +At it's most basic level, the nexus (and most other FPGAs) is composed of basic elements (BELs), pins, wires and +Programmable Interconnect Points (PIPs). The bitstream configures the BELs and which PIPs are active. + +Loosely speaking, on the lattice components each BEL corresponds to a site. The internal tooling for lattice refers to +wires as nodes and the terms are used interchangeably. + +The lattice parts overlay a tile grid over this structure. Largely speaking the tile grid informs on where the component +might be on the chip, but also where the configuration data can be found / specified for any given chip. + +## Sites (BELs) + +Every site has a type, and the type dictates both it's pin capabilities and what programmable options and modes exist for +that given site. Sites correspond most closely to the primitives found in lattice documentation, but sometimes a site +isn't directly translated as a primitive, and instead has multiple modes which map the same site to multiple primitives +as defined in the manual. + +Nearly every site name contains a prefix indicating the row and column it is most aligned to, and that tile is used to +configure that site. A tile can have multiple sites in it, or the same site type can occur in multiple tile types where +it's configuration bits occur at different offsets. + +Many exceptions do exist where a site is named for one row-column pair but it's configuration lives in another tile, and +that tile has the appropriate tile type. For instance, LRAM's typically are like this. Part of what the fuzzers are configured +for is to represent the mapping between the site tile location and the config tile location. + +## Nodes (Wires) and PIPs + +Nodes represent physical wires with gates connecting it to other nodes. Nodes can have pins tied directly onto them. + +Lattice has a TCL library exposed in a tool -- lapie / lark depending on version -- which can be used to query the node +graph. This tooling gives you which PIPs and pins are associated with the node, as well as what aliases are associated +with it. + +In terms of scale, there are about 1.7 million nodes on the LIFCL-40 part. + +Nodes also have aliases. The typical reason for this is that nodes can span multiple tiles, and so each tile has a local +name for that node. Only the primary name associated with the node is directly queryable, so there is no robust way in +general to determine every node that is associated with a given tile. + +### Node Naming + +Nodes have a semantically meaningful structure to their naming. They are all prefixed with `RC_` which gives a hint +to it's location; although nodes can span multiple tiles. + +After that there are the following naming conventions: + +## Tile types + +Tiles of a given tile type will always have the same set of: + +- Sites +- Nodes +- PIPs + +Often they will also dictate the relationship between neighboring tiles in a rigid way. For instance, LRAM instances +have an associated `CIB_LR` tiletype at an offset determined by it's tiletype. + +Tile types also are the fundamental building block to configuring the chip since it rigidly maps the bits in it's +configuration bits to the sites and pips associated with it. + +Tile types are also standard across devices -- the way you configure a PLC tile is identical in LIFCL-17 as it is in LIFCL-40, +for instance. It should be noted though that lattice is inconsistent with this principal, and so some tile types are +flagged and changed when the tilegrid is imported from lattice's interchange format. + +## Global Routes + +PLC -> Branch Tap -> Branch Node -> Spine -> HROW + +There is a global distribution network on the LIFCL devices which is laid out as follows: + +- \ No newline at end of file diff --git a/examples/spinex_minimal/Makefile b/examples/spinex_minimal/Makefile new file mode 100644 index 0000000..e69de29 diff --git a/examples/spinex_minimal/SpinexMinimal_references.v b/examples/spinex_minimal/SpinexMinimal_references.v new file mode 100644 index 0000000..e3eb973 --- /dev/null +++ b/examples/spinex_minimal/SpinexMinimal_references.v @@ -0,0 +1,1614 @@ +`timescale 1ns/1ps + +module Ram_1w_1rs #( + parameter wordCount = 640, + parameter wordWidth = 128, + parameter clockCrossing = 1'b0, + parameter technology = "auto", + parameter readUnderWrite = "dontCare", + parameter wrAddressWidth = 10, + parameter wrDataWidth = 128, + parameter wrMaskWidth = 1, + parameter wrMaskEnable = 1'b0, + parameter rdAddressWidth = 10, + parameter rdDataWidth = 128, + parameter rdLatency = 1 +)( + input wr_clk, + input wr_en, + input wr_mask, + input [wrAddressWidth-1:0] wr_addr, + input [wrDataWidth-1:0] wr_data, + input rd_clk, + input rd_en, + input [rdAddressWidth-1:0] rd_addr, + input rd_dataEn, + output [rdDataWidth-1:0] rd_data +); + +if (technology == "distributedLut") begin + //assert(wrMaskEnable == 1'b0) + + lscc_distributed_dpram # ( + .WADDR_DEPTH(wordCount), + .WADDR_WIDTH(wrAddressWidth), + .WDATA_WIDTH(wrDataWidth), + .RADDR_DEPTH(wordCount), + .RADDR_WIDTH(rdAddressWidth), + .RDATA_WIDTH(rdDataWidth), + .REGMODE("reg"), + .MODULE_TYPE("lscc_distributed_dpram"), + .BYTE_SIZE(8), + .ECC_ENABLE("") + ) dpram_instance( + .wr_clk_i(wr_clk), + .rd_clk_i(rd_clk), + .rst_i(1'b0), + .wr_clk_en_i(1'b1), + .rd_clk_en_i(1'b1), + + .wr_en_i(wr_en), + .wr_data_i(wr_data), + .wr_addr_i(wr_addr), + .rd_en_i(rd_en), + .rd_addr_i(rd_addr), + + .rd_data_o(rd_data) + ); + +end else begin + lscc_ram_dp_true # ( + .FAMILY("LIFCL"), + .ADDR_DEPTH_A(wordCount), + .ADDR_WIDTH_A(wrAddressWidth), + .DATA_WIDTH_A(wrDataWidth), + .ADDR_DEPTH_B(wordCount), + .ADDR_WIDTH_B(rdAddressWidth), + .DATA_WIDTH_B(rdDataWidth), + .GSR("enable"), + .MODULE_TYPE("ram_dp_true"), + .BYTE_ENABLE_A(wrMaskEnable), + .BYTE_SIZE_A(wrMaskWidth), + .BYTE_EN_POL_A("active-high"), + .WRITE_MODE_A("normal"), + .BYTE_ENABLE_B(wrMaskEnable), + .BYTE_SIZE_B(wrMaskWidth), + .MEM_ID("MEM0") + ) RAM_instance( + .addr_a_i(wr_addr), + .addr_b_i(rd_addr), + .wr_data_a_i(wr_data), + .wr_data_b_i(0), + .clk_a_i(wr_clk), + .clk_b_i(rd_clk), + .clk_en_a_i(wr_en), + .clk_en_b_i(rd_en), + .wr_en_a_i(wr_en), + .wr_en_b_i(0), + .rst_a_i(1'b0), + .rst_b_i(1'b0), + .ben_a_i(wr_mask), + .ben_b_i(0), + .rd_data_a_o(), + .rd_data_b_o(rd_data), + .ecc_one_err_a_o(), + .ecc_two_err_a_o(), + .ecc_one_err_b_o(), + .ecc_two_err_b_o() + ); +end + + +endmodule + +module Ram_2wrs #( + parameter wordCount = 256, + parameter wordWidth = 16, + parameter clockCrossing = 1'b0, + parameter technology = "auto", + parameter portA_readUnderWrite = "dontCare", + parameter portA_duringWrite = "dontCare", + parameter portA_addressWidth = 8, + parameter portA_dataWidth = 16, + parameter portA_maskWidth = 1, + parameter portA_maskEnable = 1'b0, + parameter portB_readUnderWrite = "dontCare", + parameter portB_duringWrite = "dontCare", + parameter portB_addressWidth = 8, + parameter portB_dataWidth = 16, + parameter portB_maskWidth = 1, + parameter portB_maskEnable = 1'b0 +)( + input portA_clk, + input portA_en, + input portA_wr, + input portA_mask, + input [portA_addressWidth-1:0] portA_addr, + input [portA_dataWidth-1:0] portA_wrData, + output [portA_dataWidth-1:0] portA_rdData, + input portB_clk, + input portB_en, + input portB_wr, + input portB_mask, + input [portB_addressWidth-1:0] portB_addr, + input [portB_dataWidth-1:0] portB_wrData, + output [portB_dataWidth-1:0] portB_rdData +); + + +lscc_ram_dp_true # ( + .FAMILY("LIFCL"), + .ADDR_DEPTH_A(wordCount), + .ADDR_WIDTH_A(portA_addressWidth), + .DATA_WIDTH_A(portA_dataWidth), + .ADDR_DEPTH_B(wordCount), + .ADDR_WIDTH_B(portB_addressWidth), + .DATA_WIDTH_B(portB_dataWidth), + .GSR("enable"), + .MODULE_TYPE("ram_dp_true"), + .BYTE_ENABLE_A(portA_maskEnable), + .BYTE_SIZE_A(portA_maskWidth), + .BYTE_EN_POL_A("active-high"), + .WRITE_MODE_A("normal"), + .BYTE_ENABLE_B(portB_maskEnable), + .BYTE_SIZE_B(portB_maskWidth), + .MEM_ID("MEM0") +) RAM_instance( + .addr_a_i(portA_addr), + .addr_b_i(portB_addr), + .wr_data_a_i(portA_wrData), + .wr_data_b_i(portB_wrData), + .clk_a_i(portA_clk), + .clk_b_i(portB_clk), + .clk_en_a_i(portA_en), + .clk_en_b_i(portB_en), + .wr_en_a_i(portA_wr), + .wr_en_b_i(portB_wr), + .rst_a_i(1'b0), + .rst_b_i(1'b0), + .ben_a_i(portA_mask), + .ben_b_i(portB_mask), + .rd_data_a_o(portA_rdData), + .rd_data_b_o(portB_rdData), + .ecc_one_err_a_o(), + .ecc_two_err_a_o(), + .ecc_one_err_b_o(), + .ecc_two_err_b_o() +); +endmodule + +module Ram_1wrs #( + parameter wordCount = 128, + parameter wordWidth = 64, + parameter readUnderWrite = "", + parameter duringWrite = "", + parameter technology = "", + parameter maskWidth = 8, + parameter maskEnable = 1 +)( + input clk, + input en, + input wr, + input [$clog2(wordCount)-1:0] addr, + input [(wordWidth/8-1):0] mask, + input [(wordWidth-1):0] wrData, + output [(wordWidth-1):0] rdData +); + + +if (technology == "LRAM") begin + +LRAM #( + // Parameters. + .ECC_BYTE_SEL ("BYTE_EN") +) LRAM_instance ( + .CEA (1'd1), + .CEB (1'd1), + .CSA (1'd1), + .CSB (1'd1), + .OCEA (1'd1), + .OCEB (1'd1), + .RSTA (1'd0), + .RSTB (1'd0), + .CLK (clk), + + .DPS (1'd0), + // Inputs. + .ADA (addr << 1), + .DIA (wrData[wordWidth/2-1:0]), + .WEA (wr), + .BENA_N (~mask[maskWidth/2-1:0]), + .DOA (rdData[wordWidth/2-1:0]), + + .ADB ((addr << 1) | 1'd1), + .DIB (wrData[wordWidth-1:wordWidth/2]), + .WEB (wr), + .BENB_N (~mask[maskWidth-1:maskWidth/2]), + .DOB (rdData[wordWidth-1:wordWidth/2]) +); + +/* + lscc_lram_sp # ( + .FAMILY("LIFCL"), + .MEM_ID("MEM0"), + .MEM_SIZE(wordWidth + "," + wordCount), + .ADDR_DEPTH(wordCount), + .DATA_WIDTH(wordWidth), + .REGMODE("noreg"), + .RESETMODE("sync"), + .RESET_RELEASE("sync"), + .BYTE_ENABLE(maskEnable), + .ECC_ENABLE(0), + .WRITE_MODE("normal"), + .UNALIGNED_READ(0), + .PRESERVE_ARRAY(0), + .ADDR_WIDTH($clog2(wordCount)), + .BYTE_WIDTH(wordWidth/8), + .GSR_EN(0) + ) LRAM_instance( + .clk_i(clk), + .dps_i(0), + .rst_i(1'b0), + + .errdet_o(), + .lramready_o(), + + .clk_en_i(en), + .rdout_clken_i(1'b1), + .wr_en_i(wr), + .wr_data_i(wrData), + .addr_i(addr), + .ben_i(mask), + + .rd_data_o(rdData), + .rd_datavalid_o(), + .ecc_errdet_o() + ); + + lscc_lram_dp_true #( + .MEM_ID("MEM0"), + //.MEM_SIZE(wordWidth + "," + wordCount), + .FAMILY("LIFCL"), + .ADDR_DEPTH_A(wordCount), + .ADDR_WIDTH_A($clog2(wordCount) + 1), + .DATA_WIDTH_A(wordWidth / 2), + .ADDR_DEPTH_B(wordCount), + .ADDR_WIDTH_B($clog2(wordCount) + 1), + .DATA_WIDTH_B(wordWidth / 2), + .REGMODE_A("noreg"), + .REGMODE_B("noreg"), + .GSR_EN(0), + .RESETMODE("sync"), + .RESET_RELEASE("sync"), + .BYTE_ENABLE_A(1), + .BYTE_ENABLE_B(1), + .ECC_ENABLE(0) + ) LRAM_instance( + + .clk_i(clk), + .dps_i(0), + + .errdet_o(), + .lramready_o(), + // -------------------------- + // ----- Port A signals ----- + // -------------------------- + .rst_a_i(1'b0), + .clk_en_a_i(1'b1), + .rdout_clken_a_i(1'b1), + .wr_en_a_i(wr), + .wr_data_a_i(wrData[wordWidth/2-1:0]), + .addr_a_i(addr << 1), + .ben_a_i(mask[maskWidth/2-1:0]), + + .rd_data_a_o(rdData[wordWidth/2-1:0]), + .rd_datavalid_a_o(), + .ecc_errdet_a_o(), + + // -------------------------- + // ----- Port B signals ----- + // -------------------------- + + .rst_b_i(1'b0), + .clk_en_b_i(1'b1), + .rdout_clken_b_i(1'b1), + .wr_en_b_i(wr), + .wr_data_b_i(wrData[wordWidth-1:wordWidth/2]), + .addr_b_i((addr << 1) | 1), + .ben_b_i(mask[maskWidth-1:maskWidth/2]), + + .rd_data_b_o(rdData[wordWidth-1:wordWidth/2]), + .rd_datavalid_b_o(), + .ecc_errdet_b_o() + ); + */ +end else begin + wire wr_clk_i = clk; + wire rd_clk_i = clk; + wire rst_i = 1'b0; + wire wr_en_i = en & wr; + wire rd_en_i = en & ~wr; + + lscc_ram_dp #( + .MEM_ID("MEM0"), + .MEM_SIZE(wordWidth + "," + wordCount), + .FAMILY("LIFCL"), + .WADDR_DEPTH(wordCount), + .WADDR_WIDTH($clog2(wordCount)), + .WDATA_WIDTH(wordWidth), + .RADDR_DEPTH(wordCount), + .RADDR_WIDTH($clog2(wordCount)), + .RDATA_WIDTH(wordWidth), + .REGMODE("noreg"), + .GSR("enable"), + .RESETMODE("sync"), + .RESET_RELEASE("sync"), + .MODULE_TYPE("ram_dp"), + .INIT_MODE("none"), + .BYTE_ENABLE(1), + .BYTE_SIZE(8), + .BYTE_WIDTH(wordWidth/8), + .PIPELINES(0), + .ECC_ENABLE(0), + .OUTPUT_CLK_EN(0), + .BYTE_ENABLE_POL("active-high") + ) RAM_instance( + .wr_clk_i(wr_clk_i), + .rd_clk_i(rd_clk_i), + .rst_i(rst_i), + .wr_clk_en_i(1'b1), + .rd_clk_en_i(1'b1), + .rd_out_clk_en_i(1'b1), + .wr_en_i(wr_en_i), + .wr_data_i(wrData), + .wr_addr_i(addr), + .rd_en_i(rd_en_i), + .rd_addr_i(addr), + .ben_i(mask), + .rd_data_o(rdData), + .one_err_det_o(), + .two_err_det_o() + ); +end + +endmodule +///////////////////////////////////////////////////////////////////// +//// //// +//// WISHBONE rev.B2 compliant I2C Master bit-controller //// +//// //// +//// //// +//// Author: Richard Herveille //// +//// richard@asics.ws //// +//// www.asics.ws //// +//// //// +//// Downloaded from: http://www.opencores.org/projects/i2c/ //// +//// //// +///////////////////////////////////////////////////////////////////// +//// //// +//// Copyright (C) 2001 Richard Herveille //// +//// richard@asics.ws //// +//// //// +//// This source file may be used and distributed without //// +//// restriction provided that this copyright statement is not //// +//// removed from the file and that any derivative work contains //// +//// the original copyright notice and the associated disclaimer.//// +//// //// +//// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY //// +//// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED //// +//// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS //// +//// FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL THE AUTHOR //// +//// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, //// +//// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES //// +//// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE //// +//// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR //// +//// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF //// +//// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT //// +//// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT //// +//// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE //// +//// POSSIBILITY OF SUCH DAMAGE. //// +//// //// +///////////////////////////////////////////////////////////////////// + +// CVS Log +// +// $Id: i2c_master_bit_ctrl.v,v 1.14 2009-01-20 10:25:29 rherveille Exp $ +// +// $Date: 2009-01-20 10:25:29 $ +// $Revision: 1.14 $ +// $Author: rherveille $ +// $Locker: $ +// $State: Exp $ +// +// Change History: +// $Log: $ +// Revision 1.14 2009/01/20 10:25:29 rherveille +// Added clock synchronization logic +// Fixed slave_wait signal +// +// Revision 1.13 2009/01/19 20:29:26 rherveille +// Fixed synopsys miss spell (synopsis) +// Fixed cr[0] register width +// Fixed ! usage instead of ~ +// Fixed bit controller parameter width to 18bits +// +// Revision 1.12 2006/09/04 09:08:13 rherveille +// fixed short scl high pulse after clock stretch +// fixed slave model not returning correct '(n)ack' signal +// +// Revision 1.11 2004/05/07 11:02:26 rherveille +// Fixed a bug where the core would signal an arbitration lost (AL bit set), when another master controls the bus and the other master generates a STOP bit. +// +// Revision 1.10 2003/08/09 07:01:33 rherveille +// Fixed a bug in the Arbitration Lost generation caused by delay on the (external) sda line. +// Fixed a potential bug in the byte controller's host-acknowledge generation. +// +// Revision 1.9 2003/03/10 14:26:37 rherveille +// Fixed cmd_ack generation item (no bug). +// +// Revision 1.8 2003/02/05 00:06:10 rherveille +// Fixed a bug where the core would trigger an erroneous 'arbitration lost' interrupt after being reset, when the reset pulse width < 3 clk cycles. +// +// Revision 1.7 2002/12/26 16:05:12 rherveille +// Small code simplifications +// +// Revision 1.6 2002/12/26 15:02:32 rherveille +// Core is now a Multimaster I2C controller +// +// Revision 1.5 2002/11/30 22:24:40 rherveille +// Cleaned up code +// +// Revision 1.4 2002/10/30 18:10:07 rherveille +// Fixed some reported minor start/stop generation timing issuess. +// +// Revision 1.3 2002/06/15 07:37:03 rherveille +// Fixed a small timing bug in the bit controller.\nAdded verilog simulation environment. +// +// Revision 1.2 2001/11/05 11:59:25 rherveille +// Fixed wb_ack_o generation bug. +// Fixed bug in the byte_controller statemachine. +// Added headers. +// + +// +///////////////////////////////////// +// Bit controller section +///////////////////////////////////// +// +// Translate simple commands into SCL/SDA transitions +// Each command has 5 states, A/B/C/D/idle +// +// start: SCL ~~~~~~~~~~\____ +// SDA ~~~~~~~~\______ +// x | A | B | C | D | i +// +// repstart SCL ____/~~~~\___ +// SDA __/~~~\______ +// x | A | B | C | D | i +// +// stop SCL ____/~~~~~~~~ +// SDA ==\____/~~~~~ +// x | A | B | C | D | i +// +//- write SCL ____/~~~~\____ +// SDA ==X=========X= +// x | A | B | C | D | i +// +//- read SCL ____/~~~~\____ +// SDA XXXX=====XXXX +// x | A | B | C | D | i +// + +// Timing: Normal mode Fast mode +/////////////////////////////////////////////////////////////////////// +// Fscl 100KHz 400KHz +// Th_scl 4.0us 0.6us High period of SCL +// Tl_scl 4.7us 1.3us Low period of SCL +// Tsu:sta 4.7us 0.6us setup time for a repeated start condition +// Tsu:sto 4.0us 0.6us setup time for a stop conditon +// Tbuf 4.7us 1.3us Bus free time between a stop and start condition +// + +// synopsys translate_off +`timescale 1ns / 10ps +// synopsys translate_on + +`define I2C_CMD_NOP 4'b0000 +`define I2C_CMD_START 4'b0001 +`define I2C_CMD_STOP 4'b0010 +`define I2C_CMD_WRITE 4'b0100 +`define I2C_CMD_READ 4'b1000 + +module i2c_master_bit_ctrl ( + input clk, // system clock + input rst, // synchronous active high reset + input nReset, // asynchronous active low reset + input ena, // core enable signal + + input [15:0] clk_cnt, // clock prescale value + + input [ 3:0] cmd, // command (from byte controller) + output reg cmd_ack, // command complete acknowledge + output reg busy, // i2c bus busy + output reg al, // i2c bus arbitration lost + + input din, + output reg dout, + + input scl_i, // i2c clock line input + output scl_o, // i2c clock line output + output reg scl_oen, // i2c clock line output enable (active low) + input sda_i, // i2c data line input + output sda_o, // i2c data line output + output reg sda_oen // i2c data line output enable (active low) +); + + + // + // variable declarations + // + + reg [ 1:0] cSCL, cSDA; // capture SCL and SDA + reg [ 2:0] fSCL, fSDA; // SCL and SDA filter inputs + reg sSCL, sSDA; // filtered and synchronized SCL and SDA inputs + reg dSCL, dSDA; // delayed versions of sSCL and sSDA + reg dscl_oen; // delayed scl_oen + reg sda_chk; // check SDA output (Multi-master arbitration) + reg clk_en; // clock generation signals + reg slave_wait; // slave inserts wait states + reg [15:0] cnt; // clock divider counter (synthesis) + reg [13:0] filter_cnt; // clock divider for filter + + + // state machine variable + reg [17:0] c_state; // synopsys enum_state + + // + // module body + // + + // whenever the slave is not ready it can delay the cycle by pulling SCL low + // delay scl_oen + always @(posedge clk) + dscl_oen <= #1 scl_oen; + + // slave_wait is asserted when master wants to drive SCL high, but the slave pulls it low + // slave_wait remains asserted until the slave releases SCL + always @(posedge clk or negedge nReset) + if (!nReset) slave_wait <= 1'b0; + else slave_wait <= (scl_oen & ~dscl_oen & ~sSCL) | (slave_wait & ~sSCL); + + // master drives SCL high, but another master pulls it low + // master start counting down its low cycle now (clock synchronization) + wire scl_sync = dSCL & ~sSCL & scl_oen; + + + // generate clk enable signal + always @(posedge clk or negedge nReset) + if (~nReset) + begin + cnt <= #1 16'h0; + clk_en <= #1 1'b1; + end + else if (rst || ~|cnt || !ena || scl_sync) + begin + cnt <= #1 clk_cnt; + clk_en <= #1 1'b1; + end + else if (slave_wait) + begin + cnt <= #1 cnt; + clk_en <= #1 1'b0; + end + else + begin + cnt <= #1 cnt - 16'h1; + clk_en <= #1 1'b0; + end + + + // generate bus status controller + + // capture SDA and SCL + // reduce metastability risk + always @(posedge clk or negedge nReset) + if (!nReset) + begin + cSCL <= #1 2'b00; + cSDA <= #1 2'b00; + end + else if (rst) + begin + cSCL <= #1 2'b00; + cSDA <= #1 2'b00; + end + else + begin + cSCL <= {cSCL[0],scl_i}; + cSDA <= {cSDA[0],sda_i}; + end + + + // filter SCL and SDA signals; (attempt to) remove glitches + always @(posedge clk or negedge nReset) + if (!nReset ) filter_cnt <= 14'h0; + else if (rst || !ena ) filter_cnt <= 14'h0; + else if (~|filter_cnt) filter_cnt <= clk_cnt >> 2; //16x I2C bus frequency + else filter_cnt <= filter_cnt -1; + + + always @(posedge clk or negedge nReset) + if (!nReset) + begin + fSCL <= 3'b111; + fSDA <= 3'b111; + end + else if (rst) + begin + fSCL <= 3'b111; + fSDA <= 3'b111; + end + else if (~|filter_cnt) + begin + fSCL <= {fSCL[1:0],cSCL[1]}; + fSDA <= {fSDA[1:0],cSDA[1]}; + end + + + // generate filtered SCL and SDA signals + always @(posedge clk or negedge nReset) + if (~nReset) + begin + sSCL <= #1 1'b1; + sSDA <= #1 1'b1; + + dSCL <= #1 1'b1; + dSDA <= #1 1'b1; + end + else if (rst) + begin + sSCL <= #1 1'b1; + sSDA <= #1 1'b1; + + dSCL <= #1 1'b1; + dSDA <= #1 1'b1; + end + else + begin + sSCL <= #1 &fSCL[2:1] | &fSCL[1:0] | (fSCL[2] & fSCL[0]); + sSDA <= #1 &fSDA[2:1] | &fSDA[1:0] | (fSDA[2] & fSDA[0]); + + dSCL <= #1 sSCL; + dSDA <= #1 sSDA; + end + + // detect start condition => detect falling edge on SDA while SCL is high + // detect stop condition => detect rising edge on SDA while SCL is high + reg sta_condition; + reg sto_condition; + always @(posedge clk or negedge nReset) + if (~nReset) + begin + sta_condition <= #1 1'b0; + sto_condition <= #1 1'b0; + end + else if (rst) + begin + sta_condition <= #1 1'b0; + sto_condition <= #1 1'b0; + end + else + begin + sta_condition <= #1 ~sSDA & dSDA & sSCL; + sto_condition <= #1 sSDA & ~dSDA & sSCL; + end + + + // generate i2c bus busy signal + always @(posedge clk or negedge nReset) + if (!nReset) busy <= #1 1'b0; + else if (rst ) busy <= #1 1'b0; + else busy <= #1 (sta_condition | busy) & ~sto_condition; + + + // generate arbitration lost signal + // aribitration lost when: + // 1) master drives SDA high, but the i2c bus is low + // 2) stop detected while not requested + reg cmd_stop; + always @(posedge clk or negedge nReset) + if (~nReset) + cmd_stop <= #1 1'b0; + else if (rst) + cmd_stop <= #1 1'b0; + else if (clk_en) + cmd_stop <= #1 cmd == `I2C_CMD_STOP; + + always @(posedge clk or negedge nReset) + if (~nReset) + al <= #1 1'b0; + else if (rst) + al <= #1 1'b0; + else + al <= #1 (sda_chk & ~sSDA & sda_oen) | (|c_state & sto_condition & ~cmd_stop); + + + initial begin + dout = 0; + end + // generate dout signal (store SDA on rising edge of SCL) + always @(posedge clk) + if (sSCL & ~dSCL) dout <= #1 sSDA; + + + // generate statemachine + + // nxt_state decoder + parameter [17:0] idle = 18'b0_0000_0000_0000_0000; + parameter [17:0] start_a = 18'b0_0000_0000_0000_0001; + parameter [17:0] start_b = 18'b0_0000_0000_0000_0010; + parameter [17:0] start_c = 18'b0_0000_0000_0000_0100; + parameter [17:0] start_d = 18'b0_0000_0000_0000_1000; + parameter [17:0] start_e = 18'b0_0000_0000_0001_0000; + parameter [17:0] stop_a = 18'b0_0000_0000_0010_0000; + parameter [17:0] stop_b = 18'b0_0000_0000_0100_0000; + parameter [17:0] stop_c = 18'b0_0000_0000_1000_0000; + parameter [17:0] stop_d = 18'b0_0000_0001_0000_0000; + parameter [17:0] rd_a = 18'b0_0000_0010_0000_0000; + parameter [17:0] rd_b = 18'b0_0000_0100_0000_0000; + parameter [17:0] rd_c = 18'b0_0000_1000_0000_0000; + parameter [17:0] rd_d = 18'b0_0001_0000_0000_0000; + parameter [17:0] wr_a = 18'b0_0010_0000_0000_0000; + parameter [17:0] wr_b = 18'b0_0100_0000_0000_0000; + parameter [17:0] wr_c = 18'b0_1000_0000_0000_0000; + parameter [17:0] wr_d = 18'b1_0000_0000_0000_0000; + + always @(posedge clk or negedge nReset) + if (!nReset) + begin + c_state <= #1 idle; + cmd_ack <= #1 1'b0; + scl_oen <= #1 1'b1; + sda_oen <= #1 1'b1; + sda_chk <= #1 1'b0; + end + else if (rst | al) + begin + c_state <= #1 idle; + cmd_ack <= #1 1'b0; + scl_oen <= #1 1'b1; + sda_oen <= #1 1'b1; + sda_chk <= #1 1'b0; + end + else + begin + cmd_ack <= #1 1'b0; // default no command acknowledge + assert cmd_ack only 1clk cycle + + if (clk_en) + case (c_state) // synopsys full_case parallel_case + // idle state + idle: + begin + case (cmd) // synopsys full_case parallel_case + `I2C_CMD_START: c_state <= #1 start_a; + `I2C_CMD_STOP: c_state <= #1 stop_a; + `I2C_CMD_WRITE: c_state <= #1 wr_a; + `I2C_CMD_READ: c_state <= #1 rd_a; + default: c_state <= #1 idle; + endcase + + scl_oen <= #1 scl_oen; // keep SCL in same state + sda_oen <= #1 sda_oen; // keep SDA in same state + sda_chk <= #1 1'b0; // don't check SDA output + end + + // start + start_a: + begin + c_state <= #1 start_b; + scl_oen <= #1 scl_oen; // keep SCL in same state + sda_oen <= #1 1'b1; // set SDA high + sda_chk <= #1 1'b0; // don't check SDA output + end + + start_b: + begin + c_state <= #1 start_c; + scl_oen <= #1 1'b1; // set SCL high + sda_oen <= #1 1'b1; // keep SDA high + sda_chk <= #1 1'b0; // don't check SDA output + end + + start_c: + begin + c_state <= #1 start_d; + scl_oen <= #1 1'b1; // keep SCL high + sda_oen <= #1 1'b0; // set SDA low + sda_chk <= #1 1'b0; // don't check SDA output + end + + start_d: + begin + c_state <= #1 start_e; + scl_oen <= #1 1'b1; // keep SCL high + sda_oen <= #1 1'b0; // keep SDA low + sda_chk <= #1 1'b0; // don't check SDA output + end + + start_e: + begin + c_state <= #1 idle; + cmd_ack <= #1 1'b1; + scl_oen <= #1 1'b0; // set SCL low + sda_oen <= #1 1'b0; // keep SDA low + sda_chk <= #1 1'b0; // don't check SDA output + end + + // stop + stop_a: + begin + c_state <= #1 stop_b; + scl_oen <= #1 1'b0; // keep SCL low + sda_oen <= #1 1'b0; // set SDA low + sda_chk <= #1 1'b0; // don't check SDA output + end + + stop_b: + begin + c_state <= #1 stop_c; + scl_oen <= #1 1'b1; // set SCL high + sda_oen <= #1 1'b0; // keep SDA low + sda_chk <= #1 1'b0; // don't check SDA output + end + + stop_c: + begin + c_state <= #1 stop_d; + scl_oen <= #1 1'b1; // keep SCL high + sda_oen <= #1 1'b0; // keep SDA low + sda_chk <= #1 1'b0; // don't check SDA output + end + + stop_d: + begin + c_state <= #1 idle; + cmd_ack <= #1 1'b1; + scl_oen <= #1 1'b1; // keep SCL high + sda_oen <= #1 1'b1; // set SDA high + sda_chk <= #1 1'b0; // don't check SDA output + end + + // read + rd_a: + begin + c_state <= #1 rd_b; + scl_oen <= #1 1'b0; // keep SCL low + sda_oen <= #1 1'b1; // tri-state SDA + sda_chk <= #1 1'b0; // don't check SDA output + end + + rd_b: + begin + c_state <= #1 rd_c; + scl_oen <= #1 1'b1; // set SCL high + sda_oen <= #1 1'b1; // keep SDA tri-stated + sda_chk <= #1 1'b0; // don't check SDA output + end + + rd_c: + begin + c_state <= #1 rd_d; + scl_oen <= #1 1'b1; // keep SCL high + sda_oen <= #1 1'b1; // keep SDA tri-stated + sda_chk <= #1 1'b0; // don't check SDA output + end + + rd_d: + begin + c_state <= #1 idle; + cmd_ack <= #1 1'b1; + scl_oen <= #1 1'b0; // set SCL low + sda_oen <= #1 1'b1; // keep SDA tri-stated + sda_chk <= #1 1'b0; // don't check SDA output + end + + // write + wr_a: + begin + c_state <= #1 wr_b; + scl_oen <= #1 1'b0; // keep SCL low + sda_oen <= #1 din; // set SDA + sda_chk <= #1 1'b0; // don't check SDA output (SCL low) + end + + wr_b: + begin + c_state <= #1 wr_c; + scl_oen <= #1 1'b1; // set SCL high + sda_oen <= #1 din; // keep SDA + sda_chk <= #1 1'b0; // don't check SDA output yet + // allow some time for SDA and SCL to settle + end + + wr_c: + begin + c_state <= #1 wr_d; + scl_oen <= #1 1'b1; // keep SCL high + sda_oen <= #1 din; + sda_chk <= #1 1'b1; // check SDA output + end + + wr_d: + begin + c_state <= #1 idle; + cmd_ack <= #1 1'b1; + scl_oen <= #1 1'b0; // set SCL low + sda_oen <= #1 din; + sda_chk <= #1 1'b0; // don't check SDA output (SCL low) + end + + default: ; + endcase + end + + + // assign scl and sda output (always gnd) + assign scl_o = 1'b0; + assign sda_o = 1'b0; + +endmodule +///////////////////////////////////////////////////////////////////// +//// //// +//// WISHBONE rev.B2 compliant I2C Master byte-controller //// +//// //// +//// //// +//// Author: Richard Herveille //// +//// richard@asics.ws //// +//// www.asics.ws //// +//// //// +//// Downloaded from: http://www.opencores.org/projects/i2c/ //// +//// //// +///////////////////////////////////////////////////////////////////// +//// //// +//// Copyright (C) 2001 Richard Herveille //// +//// richard@asics.ws //// +//// //// +//// This source file may be used and distributed without //// +//// restriction provided that this copyright statement is not //// +//// removed from the file and that any derivative work contains //// +//// the original copyright notice and the associated disclaimer.//// +//// //// +//// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY //// +//// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED //// +//// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS //// +//// FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL THE AUTHOR //// +//// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, //// +//// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES //// +//// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE //// +//// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR //// +//// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF //// +//// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT //// +//// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT //// +//// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE //// +//// POSSIBILITY OF SUCH DAMAGE. //// +//// //// +///////////////////////////////////////////////////////////////////// + +// CVS Log +// +// $Id: i2c_master_byte_ctrl.v,v 1.8 2009-01-19 20:29:26 rherveille Exp $ +// +// $Date: 2009-01-19 20:29:26 $ +// $Revision: 1.8 $ +// $Author: rherveille $ +// $Locker: $ +// $State: Exp $ +// +// Change History: +// $Log: not supported by cvs2svn $ +// Revision 1.7 2004/02/18 11:40:46 rherveille +// Fixed a potential bug in the statemachine. During a 'stop' 2 cmd_ack signals were generated. Possibly canceling a new start command. +// +// Revision 1.6 2003/08/09 07:01:33 rherveille +// Fixed a bug in the Arbitration Lost generation caused by delay on the (external) sda line. +// Fixed a potential bug in the byte controller's host-acknowledge generation. +// +// Revision 1.5 2002/12/26 15:02:32 rherveille +// Core is now a Multimaster I2C controller +// +// Revision 1.4 2002/11/30 22:24:40 rherveille +// Cleaned up code +// +// Revision 1.3 2001/11/05 11:59:25 rherveille +// Fixed wb_ack_o generation bug. +// Fixed bug in the byte_controller statemachine. +// Added headers. +// + +// synopsys translate_off +`timescale 1ns / 10ps +// synopsys translate_on + +`define I2C_CMD_NOP 4'b0000 +`define I2C_CMD_START 4'b0001 +`define I2C_CMD_STOP 4'b0010 +`define I2C_CMD_WRITE 4'b0100 +`define I2C_CMD_READ 4'b1000 + +module i2c_master_byte_ctrl ( + clk, rst, nReset, ena, clk_cnt, start, stop, read, write, ack_in, din, + cmd_ack, ack_out, dout, i2c_busy, i2c_al, scl_i, scl_o, scl_oen, sda_i, sda_o, sda_oen ); + + // + // inputs & outputs + // + input clk; // master clock + input rst; // synchronous active high reset + input nReset; // asynchronous active low reset + input ena; // core enable signal + + input [15:0] clk_cnt; // 4x SCL + + // control inputs + input start; + input stop; + input read; + input write; + input ack_in; + input [7:0] din; + + // status outputs + output cmd_ack; + reg cmd_ack; + output ack_out; + reg ack_out; + output i2c_busy; + output i2c_al; + output [7:0] dout; + + // I2C signals + input scl_i; + output scl_o; + output scl_oen; + input sda_i; + output sda_o; + output sda_oen; + + + // + // Variable declarations + // + + // statemachine + parameter [4:0] ST_IDLE = 5'b0_0000; + parameter [4:0] ST_START = 5'b0_0001; + parameter [4:0] ST_READ = 5'b0_0010; + parameter [4:0] ST_WRITE = 5'b0_0100; + parameter [4:0] ST_ACK = 5'b0_1000; + parameter [4:0] ST_STOP = 5'b1_0000; + + // signals for bit_controller + reg [3:0] core_cmd; + reg core_txd; + wire core_ack, core_rxd; + + // signals for shift register + reg [7:0] sr; //8bit shift register + reg shift, ld; + + // signals for state machine + wire go; + reg [2:0] dcnt; + wire cnt_done; + + // + // Module body + // + + // hookup bit_controller + i2c_master_bit_ctrl bit_controller ( + .clk ( clk ), + .rst ( rst ), + .nReset ( nReset ), + .ena ( ena ), + .clk_cnt ( clk_cnt ), + .cmd ( core_cmd ), + .cmd_ack ( core_ack ), + .busy ( i2c_busy ), + .al ( i2c_al ), + .din ( core_txd ), + .dout ( core_rxd ), + .scl_i ( scl_i ), + .scl_o ( scl_o ), + .scl_oen ( scl_oen ), + .sda_i ( sda_i ), + .sda_o ( sda_o ), + .sda_oen ( sda_oen ) + ); + + // generate go-signal + assign go = (read | write | stop) & ~cmd_ack; + + // assign dout output to shift-register + assign dout = sr; + + // generate shift register + always @(posedge clk or negedge nReset) + if (!nReset) + sr <= #1 8'h0; + else if (rst) + sr <= #1 8'h0; + else if (ld) + sr <= #1 din; + else if (shift) + sr <= #1 {sr[6:0], core_rxd}; + + // generate counter + always @(posedge clk or negedge nReset) + if (!nReset) + dcnt <= #1 3'h0; + else if (rst) + dcnt <= #1 3'h0; + else if (ld) + dcnt <= #1 3'h7; + else if (shift) + dcnt <= #1 dcnt - 3'h1; + + assign cnt_done = ~(|dcnt); + + // + // state machine + // + reg [4:0] c_state; // synopsys enum_state + + always @(posedge clk or negedge nReset) + if (!nReset) + begin + core_cmd <= #1 `I2C_CMD_NOP; + core_txd <= #1 1'b0; + shift <= #1 1'b0; + ld <= #1 1'b0; + cmd_ack <= #1 1'b0; + c_state <= #1 ST_IDLE; + ack_out <= #1 1'b0; + end + else if (rst | i2c_al) + begin + core_cmd <= #1 `I2C_CMD_NOP; + core_txd <= #1 1'b0; + shift <= #1 1'b0; + ld <= #1 1'b0; + cmd_ack <= #1 1'b0; + c_state <= #1 ST_IDLE; + ack_out <= #1 1'b0; + end + else + begin + // initially reset all signals + core_txd <= #1 sr[7]; + shift <= #1 1'b0; + ld <= #1 1'b0; + cmd_ack <= #1 1'b0; + + case (c_state) // synopsys full_case parallel_case + ST_IDLE: + if (go) + begin + if (start) + begin + c_state <= #1 ST_START; + core_cmd <= #1 `I2C_CMD_START; + end + else if (read) + begin + c_state <= #1 ST_READ; + core_cmd <= #1 `I2C_CMD_READ; + end + else if (write) + begin + c_state <= #1 ST_WRITE; + core_cmd <= #1 `I2C_CMD_WRITE; + end + else // stop + begin + c_state <= #1 ST_STOP; + core_cmd <= #1 `I2C_CMD_STOP; + end + + ld <= #1 1'b1; + end + + ST_START: + if (core_ack) + begin + if (read) + begin + c_state <= #1 ST_READ; + core_cmd <= #1 `I2C_CMD_READ; + end + else + begin + c_state <= #1 ST_WRITE; + core_cmd <= #1 `I2C_CMD_WRITE; + end + + ld <= #1 1'b1; + end + + ST_WRITE: + if (core_ack) + if (cnt_done) + begin + c_state <= #1 ST_ACK; + core_cmd <= #1 `I2C_CMD_READ; + end + else + begin + c_state <= #1 ST_WRITE; // stay in same state + core_cmd <= #1 `I2C_CMD_WRITE; // write next bit + shift <= #1 1'b1; + end + + ST_READ: + if (core_ack) + begin + if (cnt_done) + begin + c_state <= #1 ST_ACK; + core_cmd <= #1 `I2C_CMD_WRITE; + end + else + begin + c_state <= #1 ST_READ; // stay in same state + core_cmd <= #1 `I2C_CMD_READ; // read next bit + end + + shift <= #1 1'b1; + core_txd <= #1 ack_in; + end + + ST_ACK: + if (core_ack) + begin + if (stop) + begin + c_state <= #1 ST_STOP; + core_cmd <= #1 `I2C_CMD_STOP; + end + else + begin + c_state <= #1 ST_IDLE; + core_cmd <= #1 `I2C_CMD_NOP; + + // generate command acknowledge signal + cmd_ack <= #1 1'b1; + end + + // assign ack_out output to bit_controller_rxd (contains last received bit) + ack_out <= #1 core_rxd; + + core_txd <= #1 1'b1; + end + else + core_txd <= #1 ack_in; + + ST_STOP: + if (core_ack) + begin + c_state <= #1 ST_IDLE; + core_cmd <= #1 `I2C_CMD_NOP; + + // generate command acknowledge signal + cmd_ack <= #1 1'b1; + end + default: ; + + endcase + end +endmodule +///////////////////////////////////////////////////////////////////// +//// //// +//// WISHBONE revB.2 compliant I2C Master controller Top-level //// +//// //// +//// //// +//// Author: Richard Herveille //// +//// richard@asics.ws //// +//// www.asics.ws //// +//// //// +//// Downloaded from: http://www.opencores.org/projects/i2c/ //// +//// //// +///////////////////////////////////////////////////////////////////// +//// //// +//// Copyright (C) 2001 Richard Herveille //// +//// richard@asics.ws //// +//// //// +//// This source file may be used and distributed without //// +//// restriction provided that this copyright statement is not //// +//// removed from the file and that any derivative work contains //// +//// the original copyright notice and the associated disclaimer.//// +//// //// +//// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY //// +//// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED //// +//// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS //// +//// FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL THE AUTHOR //// +//// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, //// +//// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES //// +//// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE //// +//// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR //// +//// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF //// +//// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT //// +//// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT //// +//// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE //// +//// POSSIBILITY OF SUCH DAMAGE. //// +//// //// +///////////////////////////////////////////////////////////////////// + +// CVS Log +// +// $Id: i2c_master_top.v,v 1.12 2009-01-19 20:29:26 rherveille Exp $ +// +// $Date: 2009-01-19 20:29:26 $ +// $Revision: 1.12 $ +// $Author: rherveille $ +// $Locker: $ +// $State: Exp $ +// +// Change History: +// Revision 1.11 2005/02/27 09:26:24 rherveille +// Fixed register overwrite issue. +// Removed full_case pragma, replaced it by a default statement. +// +// Revision 1.10 2003/09/01 10:34:38 rherveille +// Fix a blocking vs. non-blocking error in the wb_dat output mux. +// +// Revision 1.9 2003/01/09 16:44:45 rherveille +// Fixed a bug in the Command Register declaration. +// +// Revision 1.8 2002/12/26 16:05:12 rherveille +// Small code simplifications +// +// Revision 1.7 2002/12/26 15:02:32 rherveille +// Core is now a Multimaster I2C controller +// +// Revision 1.6 2002/11/30 22:24:40 rherveille +// Cleaned up code +// +// Revision 1.5 2001/11/10 10:52:55 rherveille +// Changed PRER reset value from 0x0000 to 0xffff, conform specs. +// + +// synopsys translate_off +`timescale 1ns / 10ps +// synopsys translate_on + +`define I2C_CMD_NOP 4'b0000 +`define I2C_CMD_START 4'b0001 +`define I2C_CMD_STOP 4'b0010 +`define I2C_CMD_WRITE 4'b0100 +`define I2C_CMD_READ 4'b1000 + +module i2c_master_top( + wb_clk_i, wb_rst_i, arst_i, wb_adr_i, wb_dat_i, wb_dat_o, + wb_we_i, wb_stb_i, wb_cyc_i, wb_ack_o, wb_inta_o, + scl_pad_i, scl_pad_o, scl_padoen_o, sda_pad_i, sda_pad_o, sda_padoen_o ); + + // parameters + parameter ARST_LVL = 1'b0; // asynchronous reset level + + // + // inputs & outputs + // + + // wishbone signals + input wb_clk_i; // master clock input + input wb_rst_i; // synchronous active high reset + input arst_i; // asynchronous reset + input [2:0] wb_adr_i; // lower address bits + input [7:0] wb_dat_i; // databus input + output [7:0] wb_dat_o; // databus output + input wb_we_i; // write enable input + input wb_stb_i; // stobe/core select signal + input wb_cyc_i; // valid bus cycle input + output wb_ack_o; // bus cycle acknowledge output + output wb_inta_o; // interrupt request signal output + + reg [7:0] wb_dat_o; + reg wb_ack_o; + reg wb_inta_o; + + // I2C signals + // i2c clock line + input scl_pad_i; // SCL-line input + output scl_pad_o; // SCL-line output (always 1'b0) + output scl_padoen_o; // SCL-line output enable (active low) + + // i2c data line + input sda_pad_i; // SDA-line input + output sda_pad_o; // SDA-line output (always 1'b0) + output sda_padoen_o; // SDA-line output enable (active low) + + + // + // variable declarations + // + + // registers + reg [15:0] prer; // clock prescale register + reg [ 7:0] ctr; // control register + reg [ 7:0] txr; // transmit register + wire [ 7:0] rxr; // receive register + reg [ 7:0] cr; // command register + wire [ 7:0] sr; // status register + + // done signal: command completed, clear command register + wire done; + + // core enable signal + wire core_en; + wire ien; + + // status register signals + wire irxack; + reg rxack; // received aknowledge from slave + reg tip; // transfer in progress + reg irq_flag; // interrupt pending flag + wire i2c_busy; // bus busy (start signal detected) + wire i2c_al; // i2c bus arbitration lost + reg al; // status register arbitration lost bit + + // + // module body + // + + // generate internal reset + wire rst_i = arst_i ^ ARST_LVL; + + // generate wishbone signals + wire wb_wacc = wb_we_i & wb_ack_o; + + // assign DAT_O + always @(posedge wb_clk_i) + begin + case (wb_adr_i) // synopsys parallel_case + 3'b000: wb_dat_o <= #1 prer[ 7:0]; + 3'b001: wb_dat_o <= #1 prer[15:8]; + 3'b010: wb_dat_o <= #1 ctr; + 3'b011: wb_dat_o <= #1 rxr; // write is transmit register (txr) + 3'b100: wb_dat_o <= #1 sr; // write is command register (cr) + 3'b101: wb_dat_o <= #1 txr; + 3'b110: wb_dat_o <= #1 cr; + 3'b111: wb_dat_o <= #1 0; // reserved + endcase + end + + // generate registers + always @(posedge wb_clk_i or negedge rst_i) + if (!rst_i) + begin + prer <= #1 16'hffff; + ctr <= #1 8'h0; + txr <= #1 8'h0; + wb_ack_o <= 1'b0; + end + else if (wb_rst_i) + begin + prer <= #1 16'hffff; + ctr <= #1 8'h0; + txr <= #1 8'h0; + wb_ack_o <= 1'b0; + end + else + begin + // generate acknowledge output signal + wb_ack_o <= #1 wb_cyc_i & wb_stb_i & ~wb_ack_o; // because timing is always honored + + if (wb_wacc) + case (wb_adr_i) // synopsys parallel_case + 3'b000 : prer [ 7:0] <= #1 wb_dat_i; + 3'b001 : prer [15:8] <= #1 wb_dat_i; + 3'b010 : ctr <= #1 wb_dat_i; + 3'b011 : txr <= #1 wb_dat_i; + default: ; + endcase + end + + // generate command register (special case) + always @(posedge wb_clk_i or negedge rst_i) + if (!rst_i) + cr <= #1 8'h0; + else if (wb_rst_i) + cr <= #1 8'h0; + else if (wb_wacc) + begin + if (core_en & (wb_adr_i == 3'b100) ) + cr <= #1 wb_dat_i; + end + else + begin + if (done | i2c_al) + cr[7:4] <= #1 4'h0; // clear command bits when done + // or when aribitration lost + cr[2:1] <= #1 2'b0; // reserved bits + cr[0] <= #1 1'b0; // clear IRQ_ACK bit + end + + + // decode command register + wire sta = cr[7]; + wire sto = cr[6]; + wire rd = cr[5]; + wire wr = cr[4]; + wire ack = cr[3]; + wire iack = cr[0]; + + // decode control register + assign core_en = ctr[7]; + assign ien = ctr[6]; + + // hookup byte controller block + i2c_master_byte_ctrl byte_controller ( + .clk ( wb_clk_i ), + .rst ( wb_rst_i ), + .nReset ( rst_i ), + .ena ( core_en ), + .clk_cnt ( prer ), + .start ( sta ), + .stop ( sto ), + .read ( rd ), + .write ( wr ), + .ack_in ( ack ), + .din ( txr ), + .cmd_ack ( done ), + .ack_out ( irxack ), + .dout ( rxr ), + .i2c_busy ( i2c_busy ), + .i2c_al ( i2c_al ), + .scl_i ( scl_pad_i ), + .scl_o ( scl_pad_o ), + .scl_oen ( scl_padoen_o ), + .sda_i ( sda_pad_i ), + .sda_o ( sda_pad_o ), + .sda_oen ( sda_padoen_o ) + ); + + // status register block + interrupt request signal + always @(posedge wb_clk_i or negedge rst_i) + if (!rst_i) + begin + al <= #1 1'b0; + rxack <= #1 1'b0; + tip <= #1 1'b0; + irq_flag <= #1 1'b0; + end + else if (wb_rst_i) + begin + al <= #1 1'b0; + rxack <= #1 1'b0; + tip <= #1 1'b0; + irq_flag <= #1 1'b0; + end + else + begin + al <= #1 i2c_al | (al & ~sta); + rxack <= #1 irxack; + tip <= #1 (rd | wr); + irq_flag <= #1 (done | i2c_al | irq_flag) & ~iack; // interrupt request flag is always generated + end + + // generate interrupt request signals + always @(posedge wb_clk_i or negedge rst_i) + if (!rst_i) + wb_inta_o <= #1 1'b0; + else if (wb_rst_i) + wb_inta_o <= #1 1'b0; + else + wb_inta_o <= #1 irq_flag && ien; // interrupt signal is only generated when IEN (interrupt enable bit is set) + + // assign status register bits + assign sr[7] = rxack; + assign sr[6] = i2c_busy; + assign sr[5] = al; + assign sr[4:2] = 3'h0; // reserved + assign sr[1] = tip; + assign sr[0] = irq_flag; + +endmodule diff --git a/examples/spinex_minimal/tinyclunx.pdc b/examples/spinex_minimal/tinyclunx.pdc new file mode 100644 index 0000000..504c27e --- /dev/null +++ b/examples/spinex_minimal/tinyclunx.pdc @@ -0,0 +1,2 @@ +ldc_set_location -site {A3} [get_ports led] +ldc_set_location -site {B1} [get_ports gsrn] diff --git a/examples/spinex_minimal/tinyclunx33.pdc b/examples/spinex_minimal/tinyclunx33.pdc new file mode 100644 index 0000000..7ac7272 --- /dev/null +++ b/examples/spinex_minimal/tinyclunx33.pdc @@ -0,0 +1,2 @@ +ldc_set_location -site {B6} [get_ports led] +ldc_set_location -site {D3} [get_ports gsrn] From dddc797a2a006f49fff91aa6d41b157d50cfd159 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Mon, 19 Jan 2026 09:17:07 -0700 Subject: [PATCH 05/29] Changes to prjoxide to support lifcl-33 devices. - Added support for related BELs - Added support to parse compressed bitstreams - Support new device attributes - Support serializing deltas for checkpoint / debugging --- .gitignore | 9 + examples/common.mk | 3 +- fuzzers/LIFCL/001-plc-routing/fuzzer.py | 24 +- libprjoxide/prjoxide/Cargo.toml | 4 +- libprjoxide/prjoxide/src/bba/tileloc.rs | 70 ++++-- libprjoxide/prjoxide/src/bels.rs | 72 ++++-- libprjoxide/prjoxide/src/bitstream.rs | 82 +++++-- libprjoxide/prjoxide/src/chip.rs | 57 ++++- libprjoxide/prjoxide/src/database.rs | 83 +++++-- libprjoxide/prjoxide/src/fuzz.rs | 309 +++++++++++++++--------- libprjoxide/prjoxide/src/ipfuzz.rs | 26 +- libprjoxide/pyprjoxide/Cargo.toml | 3 + libprjoxide/pyprjoxide/src/lib.rs | 30 ++- 13 files changed, 557 insertions(+), 215 deletions(-) diff --git a/.gitignore b/.gitignore index 721db2b..df70e8d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,12 @@ __pycache__ libprjoxide/target Cargo.lock .bitstreamcache/ +radiantc.tcl* +radiantc.log* +*~ +\#* +*.log.gz +.deltas +work/ +.\#* +*.fasm diff --git a/examples/common.mk b/examples/common.mk index 5bdfbc7..110726f 100644 --- a/examples/common.mk +++ b/examples/common.mk @@ -6,11 +6,12 @@ YOSYS?=yosys NEXTPNR?=nextpnr-nexus PRJOXIDE?=prjoxide ECPPROG?=ecpprog +TOP?=topy all: $(PROJ).bit $(PROJ).json: $(PROJ).v $(EXTRA_VERILOG) $(MEM_INIT_FILES) - $(YOSYS) -ql $(PROJ)_syn.log -p "synth_nexus $(SYNTH_ARGS) -top top -json $(PROJ).json" $(PROJ).v $(EXTRA_VERILOG) + $(YOSYS) -ql $(PROJ)_syn.log -p "synth_nexus $(SYNTH_ARGS) -top $(TOP) -json $(PROJ).json" $(PROJ).v $(EXTRA_VERILOG) $(PROJ).fasm: $(PROJ).json $(PDC) $(NEXTPNR) --device $(DEVICE) --pdc $(PDC) --json $(PROJ).json --fasm $(PROJ).fasm diff --git a/fuzzers/LIFCL/001-plc-routing/fuzzer.py b/fuzzers/LIFCL/001-plc-routing/fuzzer.py index c71838e..c387f45 100644 --- a/fuzzers/LIFCL/001-plc-routing/fuzzer.py +++ b/fuzzers/LIFCL/001-plc-routing/fuzzer.py @@ -1,14 +1,17 @@ from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect import re +import tiles -cfg = FuzzConfig(job="PLCROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["R16C22:PLC"]) +def run_cfg(device): + tile = list(tiles.get_tiles_by_tiletype(device, "PLC").keys())[0] + (r,c) = tiles.get_rc_from_name(device, tile) -def main(): + cfg = FuzzConfig(job=f"PLCROUTE-{device}-{tile}", device=device, sv=f"../shared/route_{device.split('-')[-1]}.v", tiles=[tile]) + cfg.setup() - r = 16 - c = 22 - nodes = ["R{}C{}_J*".format(r, c)] + + nodes = ["R{}C{}_J.*".format(r, c)] extra_sources = [] extra_sources += ["R{}C{}_H02E{:02}01".format(r, c+1, i) for i in range(8)] extra_sources += ["R{}C{}_H06E{:02}03".format(r, c+3, i) for i in range(4)] @@ -18,8 +21,15 @@ def main(): extra_sources += ["R{}C{}_V06S{:02}03".format(r+3, c, i) for i in range(4)] extra_sources += ["R{}C{}_H02W{:02}01".format(r, c-1, i) for i in range(8)] extra_sources += ["R{}C{}_H06W{:02}03".format(r, c-3, i) for i in range(4)] - fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=True, ignore_tiles=set(["TAP_PLC_R16C14:TAP_PLC"])) - fuzz_interconnect(config=cfg, nodenames=extra_sources, regex=False, bidir=False, ignore_tiles=set(["TAP_PLC_R16C14:TAP_PLC"])) + #nodes = [n.name for n in tiles.get_nodes_for_tile(cfg.device, tile)] + #fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=True)#, ignore_tiles=set(["TAP_PLC_R16C14:TAP_PLC"])) + cfg.job = cfg.job + "-extra-srcs" + fuzz_interconnect(config=cfg, nodenames=extra_sources, regex=True, bidir=False)#, ignore_tiles=set(["TAP_PLC_R16C14:TAP_PLC"])) + +def main(): + for device in ["LIFCL-33"]: + run_cfg(device) + if __name__ == "__main__": main() diff --git a/libprjoxide/prjoxide/Cargo.toml b/libprjoxide/prjoxide/Cargo.toml index 95b52aa..7d5c54e 100644 --- a/libprjoxide/prjoxide/Cargo.toml +++ b/libprjoxide/prjoxide/Cargo.toml @@ -7,7 +7,7 @@ build = "build.rs" [features] default = [] -interchange = ["capnp", "flate2", "capnpc"] +interchange = ["capnp", "capnpc"] [dependencies] regex = "1" @@ -23,7 +23,7 @@ log = "0.4.11" clap = { version = "3.1", features = ["derive"] } include_dir = "0.6.0" capnp = {version = "0.14", optional = true } -flate2 = {version = "1.0", optional = true } +flate2 = {version = "1.0" } [build-dependencies] capnpc = {version = "0.14", optional = true } diff --git a/libprjoxide/prjoxide/src/bba/tileloc.rs b/libprjoxide/prjoxide/src/bba/tileloc.rs index 2ff9bc5..a9c10d9 100644 --- a/libprjoxide/prjoxide/src/bba/tileloc.rs +++ b/libprjoxide/prjoxide/src/bba/tileloc.rs @@ -13,6 +13,7 @@ use std::collections::{BTreeSet, HashMap}; use std::convert::TryInto; use std::iter::FromIterator; use regex::Regex; +use crate::bba::tiletype::Neighbour::RelXY; lazy_static! { static ref LUT_INPUT_RE: Regex = Regex::new(r"^J([ABCD])([01])_SLICE([ABCD])$").unwrap(); @@ -38,7 +39,13 @@ impl TileLocation { let mut tiletypes: Vec = tiles .iter() .map(|t| t.tiletype.to_string()) - .filter(|tt| tts.get(tt).unwrap().has_routing()) + .filter(|tt| { + let has_routing = tts.get(tt).unwrap().has_routing(); + if !has_routing { + println!("Warning: {tt} has no routing and has been omitted. Please check it's PIPs and connections."); + } + has_routing + }) .collect(); // (0, 0) is a special case as we keep all the global signals here, // but don't want to pollute other null tiles @@ -75,7 +82,8 @@ pub struct LocationGrid { pub height: usize, tiles: Vec, glb: DeviceGlobalsData, - iodb: DeviceIOData + iodb: DeviceIOData, + device: String } impl LocationGrid { @@ -94,6 +102,7 @@ impl LocationGrid { tiles: locs, glb: globals.clone(), iodb: iodb, + device: ch.device.to_string() } } pub fn get(&self, x: usize, y: usize) -> Option<&TileLocation> { @@ -137,22 +146,39 @@ impl LocationGrid { } } Neighbour::Branch => { - let branch_col = self.glb.branch_sink_to_origin(x).unwrap(); - Some((branch_col, y)) + self.glb.branch_sink_to_origin(x).map(|x| (x, y)) } Neighbour::BranchDriver { side } => { - let offset: i32 = match side { - BranchSide::Right => 2, - BranchSide::Left => -2, + let tap_side = match side { + BranchSide::Right => "R", + BranchSide::Left => "L", }; - let branch_col = self - .glb - .branch_sink_to_origin((x as i32 + offset) as usize) - .unwrap(); - Some((branch_col, y)) + self.glb.branches + .iter() + .find(|b| x == b.tap_driver_col && tap_side == b.tap_side) + .map(|b| (b.branch_col, y)) + // + // self + // .glb + // .branch_sink_to_origin((x as i32 + offset) as usize) + // .map(|x| (x, y)) } - Neighbour::Spine => return Some(self.glb.spine_sink_to_origin(x, y).unwrap()), - Neighbour::HRow => return Some(self.glb.hrow_sink_to_origin(x, y).unwrap()), + Neighbour::Spine => { + let origin = self.glb.spine_sink_to_origin(x, y); + if origin.is_none() { + println!("WARNING: Branch at {x} {y} can not resolve the spine origin") + } + origin + }, + Neighbour::HRow => { + let origin = self.glb.hrow_sink_to_origin(x, y); + if origin.is_none() { + println!("WARNING: Spine at {x} {y} can not resolve the HRow origin"); + } else { + println!("INFO: Spine at {x} {y} resolved to {origin:?}"); + } + origin + }, _ => None, } } @@ -160,15 +186,23 @@ impl LocationGrid { pub fn stamp_neighbours(&mut self) { for y in 0..self.height { for x in 0..self.width { - let neighbours: Vec = self - .get(x, y) - .unwrap() + let tile = self.get(x, y).unwrap(); + let tiletypes = tile.tiletypes.clone(); + let neighbours: Vec = tile .neighbours .iter() .map(|x| x.0.clone()) .collect(); + for n in neighbours { let nt = self.neighbour_tile(x, y, &n); + match (n.clone(), nt) { + (RelXY {rel_x: _, rel_y: _}, None) => {} + (n, None) => { + println!("Could not resolve the neighbor {n:?} at {x} {y} for {tiletypes:?}") + } + _ => {} + } if let Some((nx, ny)) = nt { let other = self.get_mut(nx as usize, ny as usize).unwrap(); other.neighbours.insert(( @@ -336,7 +370,7 @@ impl LocationGrid { out.list_begin(&format!("d{}_packages", device_idx))?; for package in self.iodb.packages.iter() { - out.package_info(package, &Chip::get_package_short_name(package))?; + out.package_info(package, &Chip::get_package_short_name(package, self.device.as_str()))?; } Ok(()) diff --git a/libprjoxide/prjoxide/src/bels.rs b/libprjoxide/prjoxide/src/bels.rs index 3c33b67..0362612 100644 --- a/libprjoxide/prjoxide/src/bels.rs +++ b/libprjoxide/prjoxide/src/bels.rs @@ -1,6 +1,7 @@ -use std::collections::BTreeSet; use crate::chip::*; use crate::database::TileBitsDatabase; +use itertools::Itertools; +use std::collections::BTreeSet; use std::convert::TryInto; // A reference to a wire in a relatively located tile @@ -405,7 +406,7 @@ impl Bel { } } - pub fn make_seio18(z: usize) -> Bel { + pub fn make_seio18_relative(z: usize, rel: (i32, i32)) -> Bel { let ch = Z_TO_CHAR[z]; let postfix = if z == 1 { format!("SEIO18_CORE_IO{}", ch) @@ -439,12 +440,16 @@ impl Bel { output!(&postfix, "INLP", "DPHY LP mode input buffer output"), output!(&postfix, "INADC", "analog signal out to ADC"), ], - rel_x: 0, - rel_y: 0, + rel_x: rel.0, + rel_y: rel.1, z: z as u32, } } + pub fn make_seio18(z: usize) -> Bel { + Self::make_seio18_relative(z, (0, 0)) + } + pub fn make_diffio18() -> Bel { let postfix = format!("DIFFIO18_CORE_IOA"); Bel { @@ -670,18 +675,19 @@ pub fn get_tile_bels(tiletype: &str, tiledata: &TileBitsDatabase) -> Vec { }) .flatten() .collect(), - "SYSIO_B0_0" | "SYSIO_B1_0" | "SYSIO_B1_0_C" | "SYSIO_B2_0" | "SYSIO_B2_0_C" - | "SYSIO_B6_0" | "SYSIO_B6_0_C" | "SYSIO_B7_0" | "SYSIO_B7_0_C" - | "SYSIO_B0_0_15K" | "SYSIO_B1_0_15K" => { - vec![Bel::make_seio33(0), Bel::make_seio33(1), Bel::make_iol(tiledata, true, 0), Bel::make_iol(tiledata, true, 1)] - }, - "SYSIO_B1_DED" | "SYSIO_B1_DED_15K" => vec![Bel::make_seio33(1)], - "SYSIO_B3_0" | "SYSIO_B3_0_DLY30_V18" | "SYSIO_B3_0_DQS1" | "SYSIO_B3_0_DQS3" - | "SYSIO_B4_0" | "SYSIO_B4_0_DQS1" | "SYSIO_B4_0_DQS3" | "SYSIO_B4_0_DLY50" | "SYSIO_B4_0_DLY42" - | "SYSIO_B5_0" | "SYSIO_B5_0_15K_DQS52" | "SYSIO_B4_0_15K_DQS42" - | "SYSIO_B4_0_15K_BK4_V42" | "SYSIO_B4_0_15K_V31" | "SYSIO_B3_0_15K_DQS32" => vec![Bel::make_seio18(0), Bel::make_seio18(1), Bel::make_diffio18(), - Bel::make_iol(tiledata, false, 0), Bel::make_iol(tiledata, false, 1)], - "EFB_1_OSC" | "OSC_15K" => vec![Bel::make_osc_core()], + // "SYSIO_B0_0" | "SYSIO_B1_0" | "SYSIO_B1_0_C" | "SYSIO_B2_0" | "SYSIO_B2_0_C" + // | "SYSIO_B6_0" | "SYSIO_B6_0_C" | "SYSIO_B7_0" | "SYSIO_B7_0_C" + // | "SYSIO_B0_0_15K" | "SYSIO_B1_0_15K" => { + // vec![Bel::make_seio33(0), Bel::make_seio33(1), Bel::make_iol(tiledata, true, 0), Bel::make_iol(tiledata, true, 1)] + // }, + // "SYSIO_B1_DED" | "SYSIO_B1_DED_15K" => vec![Bel::make_seio33(1)], + // "SYSIO_B3_0" | "SYSIO_B3_0_DLY30_V18" | "SYSIO_B3_0_DQS1" | "SYSIO_B3_0_DQS3" + // | "SYSIO_B4_0" | "SYSIO_B4_0_DQS1" | "SYSIO_B4_0_DQS3" | "SYSIO_B4_0_DLY50" | "SYSIO_B4_0_DLY42" + // | "SYSIO_B5_0" | "SYSIO_B5_0_15K_DQS52" | "SYSIO_B4_0_15K_DQS42" + // | "SYSIO_B4_0_15K_BK4_V42" | "SYSIO_B4_0_15K_V31" | "SYSIO_B3_0_15K_DQS32" => + // vec![Bel::make_seio18(0), Bel::make_seio18(1), Bel::make_diffio18(), Bel::make_iol(tiledata, false, 0), Bel::make_iol(tiledata, false, 1)], + + "EFB_1_OSC" | "OSC_15K" | "OSC" => vec![Bel::make_osc_core()], "EBR_1" => vec![Bel::make_ebr(&tiledata, 0)], "EBR_4" => vec![Bel::make_ebr(&tiledata, 1)], "EBR_7" => vec![Bel::make_ebr(&tiledata, 2)], @@ -738,7 +744,7 @@ pub fn get_tile_bels(tiletype: &str, tiledata: &TileBitsDatabase) -> Vec { "RMID_DLY20" | "RMID_PICB_DLY10" => (0..12).map(|x| Bel::make_dcc("R", x)).collect(), "TMID_0" => (0..16).map(|x| Bel::make_dcc("T", x)).collect(), "BMID_0_ECLK_1" => (0..18).map(|x| Bel::make_dcc("B", x)).collect(), - "CMUX_0" => { + "CMUX_0" | "CMUX_0_TL" | "CMUX_1_GSR_TR" | "CMUX_2" | "CMUX_3" => { let mut bels = (0..4).map(|x| Bel::make_dcc("C", x)).collect::>(); bels.push(Bel::make_dcs()); bels @@ -756,10 +762,40 @@ pub fn get_tile_bels(tiletype: &str, tiledata: &TileBitsDatabase) -> Vec { "LRAM_2_15K" => vec![Bel::make_lram_core("LRAM2", &tiledata, 0, -1)], "LRAM_3_15K" => vec![Bel::make_lram_core("LRAM3", &tiledata, 0, -1)], "LRAM_4_15K" => vec![Bel::make_lram_core("LRAM4", &tiledata, 0, -1)], + + "LRAM_0_33K" => vec![Bel::make_lram_core("LRAM0", &tiledata, -1, 0)], + "LRAM_1_33K" => vec![Bel::make_lram_core("LRAM1", &tiledata, -1, 0)], + "LRAM_2_33K" => vec![Bel::make_lram_core("LRAM2", &tiledata, -1, 0)], + "LRAM_3_33K" => vec![Bel::make_lram_core("LRAM3", &tiledata, -1, 0)], + "LRAM_4_33K" => vec![Bel::make_lram_core("LRAM4", &tiledata, -1, 0)], "MIPI_DPHY_0" => vec![Bel::make_dphy_core("TDPHY_CORE2", &tiledata, -2, 0)], "MIPI_DPHY_1" => vec![Bel::make_dphy_core("TDPHY_CORE26", &tiledata, -2, 0)], - _ => vec![], + _ => { + let bel_relative_location = tiledata.bel_relative_location.unwrap_or((0, 0)); + if bel_relative_location == (0, 0) { + let enum_bels = tiledata.enums.iter().flat_map(|(key, _value)|match key.as_str() { + "PIOA.BASE_TYPE" => vec![Bel::make_seio33(0)], + "PIOB.BASE_TYPE" => vec![Bel::make_seio33(1)], + "PIOA.SEIO18.BASE_TYPE" => vec![Bel::make_seio18_relative(0, bel_relative_location)], + "PIOB.SEIO18.BASE_TYPE" => vec![Bel::make_seio18_relative(1, bel_relative_location)], + "PIOA.DIFFIO18.BASE_TYPE" => vec![Bel::make_diffio18()], + "PIOB.DIFFIO18.BASE_TYPE" => vec![Bel::make_diffio18()], + "SIOLOGICA.GSR" => vec![Bel::make_iol(tiledata, true, 0)], + "SIOLOGICB.GSR" => vec![Bel::make_iol(tiledata, true, 1)], + "IOLOGICA.GSR" => vec![Bel::make_iol(tiledata, false, 0)], + "IOLOGICB.GSR" => vec![Bel::make_iol(tiledata, false, 1)], + _ => vec![] + }); + let inferred_bels = enum_bels.collect_vec(); + if inferred_bels.is_empty() { + println!("No BEL for tile type {}", &stt); + } + inferred_bels + } else { + vec![] + } + }, } } diff --git a/libprjoxide/prjoxide/src/bitstream.rs b/libprjoxide/prjoxide/src/bitstream.rs index 4462fa3..6f45ed6 100644 --- a/libprjoxide/prjoxide/src/bitstream.rs +++ b/libprjoxide/prjoxide/src/bitstream.rs @@ -3,7 +3,9 @@ use crate::database::*; use std::convert::TryInto; use std::fs::File; -use std::io::Read; +use std::io::{BufReader, Read}; +use flate2::read::GzDecoder; +use log::info; pub struct BitstreamParser { data: Vec, @@ -19,6 +21,7 @@ const COMMENT_START: [u8; 2] = [0xFF, 0x00]; const COMMENT_END: [u8; 2] = [0x00, 0xFF]; const COMMENT_END_RDBK: [u8; 2] = [0x00, 0xFE]; const PREAMBLE: [u8; 4] = [0xFF, 0xFF, 0xBD, 0xB3]; +const PREAMBLE_IP_EVAL: [u8; 4] = [0xFF, 0xFF, 0xBE, 0xB3]; // Commands @@ -33,6 +36,7 @@ const VERIFY_ID: u8 = 0b11100010; #[allow(dead_code)] const LSC_WRITE_COMP_DIC: u8 = 0b00000010; +const LSC_READ_CNTRL0: u8 = 0b00100000; const LSC_PROG_CNTRL0: u8 = 0b00100010; const LSC_INIT_ADDRESS: u8 = 0b01000110; const LSC_WRITE_ADDRESS: u8 = 0b10110100; @@ -56,6 +60,8 @@ const DUMMY: u8 = 0b11111111; // Signing related (we just ignore) const LSC_AUTH_CTRL: u8 = 0b01011000; +// Read the dry-run User Electronic Signature shadow register. +const LSC_READ_DR_UES: u8 = 0b01011101; // CRC16 constants const CRC16_POLY: u16 = 0x8005; @@ -84,10 +90,17 @@ impl BitstreamParser { pub fn parse_file(db: &mut Database, filename: &str) -> Result { let mut f = File::open(filename).map_err(|_x| "failed to open file")?; + let mut buffer = Vec::new(); - // read the whole file - f.read_to_end(&mut buffer) - .map_err(|_x| "failed to read file")?; + + if filename.ends_with(".gz") { + let reader = BufReader::new(f); + let mut gz = GzDecoder::new(reader); + gz.read_to_end(&mut buffer) + } else { + f.read_to_end(&mut buffer) + }.map_err(|_x| "failed to read file")?; + let mut parser = BitstreamParser::new(&buffer); let mut c = parser.parse(db)?; c.cram_to_tiles(); @@ -429,9 +442,13 @@ impl BitstreamParser { let mut curr_meta = String::new(); while !self.done() { if self.check_preamble(&PREAMBLE) { - println!("bitstream start at {}", self.index); + info!("bitstream start at {}", self.index); return Ok(BitstreamType::NORMAL); } + if self.check_preamble(&PREAMBLE_IP_EVAL) { + info!("bitstream (ip eval) start at {}", self.index); + return Ok(BitstreamType::NORMAL); + } if !in_metadata && self.check_preamble(&COMMENT_START) { in_metadata = true; continue; @@ -439,6 +456,11 @@ impl BitstreamParser { if in_metadata && self.check_preamble(&COMMENT_END) { if curr_meta.len() > 0 { self.metadata.push(curr_meta.to_string()); + if curr_meta.is_ascii() { + info!("Metadata: {}", &curr_meta); + } else { + info!("Warning: Metadata of len {} contains non ascii data", curr_meta.len()); + } curr_meta.clear(); } in_metadata = false; @@ -455,7 +477,11 @@ impl BitstreamParser { let ch = self.get_byte(); if ch == 0x00 { if curr_meta.len() > 0 { - println!("Metadata: {}", &curr_meta); + if curr_meta.is_ascii() { + info!("Metadata: {}", &curr_meta); + } else { + info!("Warning: Metadata of len {} contains non ascii data", curr_meta.len()); + } } self.metadata.push(curr_meta.to_string()); curr_meta.clear(); @@ -478,14 +504,14 @@ impl BitstreamParser { let cmd = self.get_opcode_byte(); match cmd { LSC_RESET_CRC => { - println!("reset CRC"); + info!("reset CRC"); self.skip_bytes(3); self.crc16 = CRC16_INIT; } LSC_PROG_CNTRL0 => { self.skip_bytes(3); let ctrl0 = self.get_u32(); - println!("set CTRL0 to 0x{:08X}", ctrl0); + info!("set CTRL0 to 0x{:08X}", ctrl0); } VERIFY_ID => { self.skip_bytes(3); @@ -493,22 +519,22 @@ impl BitstreamParser { let mut chip = Chip::from_idcode(db, idcode); chip.metadata = self.metadata.clone(); curr_chip = Some(chip); - println!("check IDCODE is 0x{:08X}", idcode); + info!("check IDCODE is 0x{:08X}", idcode); } LSC_INIT_ADDRESS => { self.skip_bytes(3); - println!("reset frame address"); + info!("reset frame address"); curr_frame = 0; } LSC_WRITE_ADDRESS => { self.skip_bytes(3); curr_frame = self.get_u32(); - println!("set frame address to 0x{:08X}", curr_frame); + info!("set frame address to 0x{:08X}", curr_frame); } LSC_AUTH_CTRL => { self.skip_bytes(3); self.skip_bytes(64); - println!("LSC_AUTH_CTRL (bitstream is probably signed!)"); + info!("LSC_AUTH_CTRL (bitstream is probably signed!)"); } LSC_PROG_INCR_RTI => { let cfg = self.get_byte(); @@ -526,24 +552,28 @@ impl BitstreamParser { return Err("got bitstream before idcode"); } } - println!("write {} frames at 0x{:08x}", count, curr_frame); + info!("write {} frames at 0x{:08x}", count, curr_frame); let mut frame_bytes = vec![0 as u8; (bits_per_frame + 14 + 7) / 8]; assert_eq!(cfg, 0x91); + for _ in 0..count { self.copy_bytes(&mut frame_bytes); self.ecc14 = ECC_INIT; + + let decoded_frame = chip.frame_addr_to_idx(curr_frame); for j in (0..bits_per_frame).rev() { let ofs = (j + pad_bits) as usize; if ((frame_bytes[(frame_bytes.len() - 1) - (ofs / 8)] >> (ofs % 8)) & 0x01) == 0x01 { - let decoded_frame = chip.frame_addr_to_idx(curr_frame); if decoded_frame < chip.cram.frames { chip.cram.set(decoded_frame, j, true); + } else { + info!("Decoded frame {} exceeds frame size {}", decoded_frame, chip.cram.frames); } if self.verbose { - println!("F0x{:08x}B{:04}", curr_frame, j); + info!("F0x{:08x}B{:04}", curr_frame, j); } self.update_ecc(true); } else { @@ -554,13 +584,12 @@ impl BitstreamParser { | (frame_bytes[frame_bytes.len() - 1] as u16)) & 0x3FFF; let exp_parity = self.finalise_ecc(); - // ECC calculation here is actually occasionally unsound, // as LUT RAM initialisation is masked from ECC calculation // as it changes at runtime. But it is too early to check this here. if self.verbose { - println!("F0x{:08x}P{:014b}E{:014b}", curr_frame, parity, exp_parity); + info!("F0x{:08x}P{:014b}E{:014b}", curr_frame, parity, exp_parity); } self.check_crc16(); let d = self.get_byte(); @@ -571,13 +600,13 @@ impl BitstreamParser { LSC_POWER_CTRL => { self.skip_bytes(2); let pwr = self.get_byte(); - println!("power control: {}", pwr); + info!("power control: {}", pwr); } ISC_PROGRAM_USERCODE => { let cmp_crc = self.get_byte() & 0x80 == 0x80; self.skip_bytes(2); let usercode = self.get_u32(); - println!("set usercode to 0x{:08X}", usercode); + info!("set usercode to 0x{:08X}", usercode); if cmp_crc { self.check_crc16(); } @@ -604,12 +633,21 @@ impl BitstreamParser { } ISC_PROGRAM_DONE => { self.skip_bytes(3); - println!("done"); + info!("done"); } + LSC_READ_DR_UES => { + self.skip_bytes(3); + info!("read DR_UES"); + } + LSC_READ_CNTRL0 => { + self.skip_bytes(3); + info!("read CNTRL0"); + } DUMMY => {} _ => { - println!("unknown command 0x{:02X} at {}", cmd, self.index); - return Err("unknown bitstream command"); + info!("unknown command 0x{:02X} at {}", cmd, self.index); + //self.skip_bytes(3); + //return Err("unknown bitstream command"); } } } diff --git a/libprjoxide/prjoxide/src/chip.rs b/libprjoxide/prjoxide/src/chip.rs index cc0a314..68aaf56 100644 --- a/libprjoxide/prjoxide/src/chip.rs +++ b/libprjoxide/prjoxide/src/chip.rs @@ -142,10 +142,7 @@ impl Chip { tilegroups: HashMap::new(), metadata: Vec::new(), settings: BTreeMap::new(), - tap_frame_count: match device { - "LFCPNX-100" => 42, - _ => 24, - } + tap_frame_count: if data.tap_frame_count == 0 { 24 } else { data.tap_frame_count } }; c.tiles_by_name = c .tiles @@ -222,8 +219,14 @@ impl Chip { chip.configure_ip(ip_name, db, ft); } else if chip.tilegroups.contains_key(tn) { chip.apply_tilegroup(tn, db, ft); + } else if let Ok(tile) = chip.tile_by_name_mut(tn) { + tile.from_fasm(db, ft); } else { - chip.tile_by_name_mut(tn).unwrap().from_fasm(db, ft); + error!("Unknown tile {}", tn); + error!("Tile groups: "); + chip.tilegroups.iter().for_each(|(k, _)| { + error!("- {}", k); + }); } } chip.tiles_to_cram(); @@ -236,6 +239,12 @@ impl Chip { .copy_from_window(&self.cram, t.start_frame, t.start_bit); } } + pub fn tile_by_frame_and_bit(&mut self, frame : usize, bit : usize)-> Result<&Tile, &'static str> { + self.tiles.iter().find(|x| x.start_frame <= frame && x.start_frame + x.cram.frames > frame && + x.start_bit <= bit && x.start_bit + x.cram.bits > bit).ok_or("No tile for query") + //self.tiles.iter().find(|x| x.start_frame == frame && x.start_bit >= bit).ok_or("No tile for query") + } + // Copy the per-tile CRAM windows to the whole chip CRAM pub fn tiles_to_cram(&mut self) { for t in self.tiles.iter() { @@ -321,12 +330,30 @@ impl Chip { } } // Convert frame address to flat frame index + // The tiles are set indexed in one way, and then the addressing is done in a different way + // addressing: + // 0x0000 -> 0x7fff -> cram.frames -> R_SIDE_IO_END + // 0x8000 -> 0x800f -> R_SIDE_IO_END -> R_SIDE_IO_START + // 0x8010 -> 0x801f -> L_SIDE_IO_END -> L_SIDE_IO_START + // 0x8020 -> 0x81ff -> TAP_END -> TAP_START + // + // The tile stack ups typically are: + // 0: L_SIDE_IO_START + // : L_SIDE_IO_END + // 16: TAP_START + // : TAP_END + // XX: R_SIDE_IO_START + // XX: R_SIDE_IO_END + // XX: ... + // cram.frames + // + // Most chips seem to have the same number of IO frames -- 16 but the tap frames varies. pub fn frame_addr_to_idx(&self, addr: u32) -> usize { match addr { - 0x0000..=0x7FFF => (self.cram.frames - 1) - (addr as usize), - 0x8000..=0x800F => (15 - ((addr - 0x8000) as usize)) + (16 + self.tap_frame_count), // right side IO - 0x8010..=0x801F => (15 - ((addr - 0x8010) as usize)) + 0, // left side IO - 0x8020..=0x81FF => ((self.tap_frame_count - 1) - ((addr - 0x8020) as usize)) + 16, // TAPs (row-segment clocking) + 0x0000..=0x7FFF => (self.cram.frames - 1) - (addr as usize), // 56 -> ??? + 0x8000..=0x800F => (15 - ((addr - 0x8000) as usize)) + (16 + self.tap_frame_count), // right side IO 40->55 + 0x8010..=0x801F => (15 - ((addr - 0x8010) as usize)) + 0, // left side IO // 0 -> 15 + 0x8020..=0x81FF => ((self.tap_frame_count - 1) - ((addr - 0x8020) as usize)) + 16, // TAPs (row-segment clocking) 16 -> 39 _ => panic!("unable to process frame address 0x{:08x}", addr), } } @@ -343,7 +370,7 @@ impl Chip { } } // Convert a long package name to a short one - pub fn get_package_short_name(long_name: &str) -> String { + pub fn get_package_short_name(long_name: &str, device: &str) -> String { if long_name.starts_with("CABGA") { format!("BG{}", &long_name[5..]) } else if long_name.starts_with("CSBGA") { @@ -352,8 +379,12 @@ impl Chip { format!("MG{}", &long_name[6..]) } else if long_name.starts_with("QFN") { format!("SG{}", &long_name[3..]) - } else if long_name.starts_with("WLCSP") { + } else if long_name.starts_with("WLCSP") && !device.starts_with("LIFCL-33") { format!("UWG{}", &long_name[5..]) + } else if long_name.starts_with("WLCSP") { + format!("USG{}", &long_name[5..]) + } else if long_name.starts_with("FCC") { + format!("CTG{}", &long_name[5..]) } else { panic!("unknown package name {}", &long_name); } @@ -427,6 +458,10 @@ Please make sure Oxide and nextpnr are up to date and input source code is meani } } if !found { + println!("Tilegroup {group} tiles:"); + tg.iter().for_each(|tile| { + println!(" - {tile}"); + }); panic!("No enum named {} in tilegroup {}.\n\ Please make sure Oxide and nextpnr are up to date. If they are, consider reporting this as an issue.", k, group); } diff --git a/libprjoxide/prjoxide/src/database.rs b/libprjoxide/prjoxide/src/database.rs index 114108d..cbda12e 100644 --- a/libprjoxide/prjoxide/src/database.rs +++ b/libprjoxide/prjoxide/src/database.rs @@ -1,12 +1,27 @@ +use itertools::Itertools; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet, HashMap}; -use std::fmt; +use std::{env, fmt}; use std::fs::File; use std::io::prelude::*; use std::path::Path; +use log::info; // Deserialization of 'devices.json' +macro_rules! emit_bit_change_error { + // Expands to either `$crate::panic::panic_2015` or `$crate::panic::panic_2021` + // depending on the edition of the caller. + ($($arg:tt)*) => { + /* compiler built-in */ + if env::var("PRJOXIDE_ALLOW_BIT_CHANGE").is_ok() { + println!($($arg)*); + } else { + panic!($($arg)*); + } + }; +} + #[derive(Deserialize)] pub struct DevicesDatabase { pub families: BTreeMap, @@ -35,6 +50,7 @@ pub struct DeviceData { pub col_bias: u32, pub fuzz: bool, pub variants: BTreeMap, + pub tap_frame_count: usize } // Deserialization of 'tilegrid.json' @@ -44,7 +60,7 @@ pub struct DeviceTilegrid { pub tiles: BTreeMap, } -#[derive(Deserialize)] +#[derive(Serialize, Deserialize)] pub struct TileData { pub tiletype: String, pub x: u32, @@ -117,21 +133,21 @@ impl DeviceGlobalsData { && self.spines.iter().any(|s| s.spine_row == y) } pub fn spine_sink_to_origin(&self, x: usize, y: usize) -> Option<(usize, usize)> { - match self - .hrows - .iter() - .map(|h| h.spine_cols.iter()) - .flatten() - .find(|c| ((x as i32) - (**c as i32)).abs() < 3) - { - None => None, - Some(spine_col) => self - .spines - .iter() - .find(|s| y >= s.from_row && y <= s.to_row) - .map(|s| (*spine_col, s.spine_row)), - } + let spine_column = self.hrows.iter() + .flat_map(|x|x.spine_cols.clone()) + .map(|c| (x.abs_diff(c), c)) + .sorted() + .map(|x| x.1) + .next(); + + let spine_data = + self.spines.iter() + .find(|s| y >= s.from_row && y <= s.to_row); + + spine_data.zip(spine_column) + .map(|(spine, spine_col)| (spine_col, spine.spine_row)) } + pub fn is_hrow_loc(&self, x: usize, y: usize) -> bool { self.hrows.iter().any(|h| h.hrow_col == x) && self.spines.iter().any(|s| s.spine_row == y) } @@ -284,6 +300,9 @@ pub struct TileBitsDatabase { #[serde(default)] #[serde(skip_serializing_if = "BTreeSet::is_empty")] pub always_on: BTreeSet, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub bel_relative_location : Option<(i32, i32)> } impl TileBitsDatabase { @@ -324,23 +343,29 @@ impl TileBitsData { dirty: false, } } + + pub fn add_pip(&mut self, from: &str, to: &str, bits: BTreeSet) { if !self.db.pips.contains_key(to) { + info!("Inserting new pip destination {to}"); self.db.pips.insert(to.to_string(), Vec::new()); } let ac = self.db.pips.get_mut(to).unwrap(); for ad in ac.iter() { if ad.from_wire == from { if bits != ad.bits { - panic!( + emit_bit_change_error!( "Bit conflict for {}.{}<-{} existing: {:?} new: {:?}", self.tiletype, from, to, ad.bits, bits ); } + + info!("Pip {from} -> {to} already exists for {}", self.tiletype); return; } } self.dirty = true; + info!("Inserting new pip {from} -> {to}"); ac.push(ConfigPipData { from_wire: from.to_string(), bits: bits.clone(), @@ -363,7 +388,7 @@ impl TileBitsData { word.desc = desc.to_string(); } if bits.len() != word.bits.len() { - panic!( + emit_bit_change_error!( "Width conflict {}.{} existing: {:?} new: {:?}", self.tiletype, name, @@ -373,7 +398,7 @@ impl TileBitsData { } for (bit, (e, n)) in word.bits.iter().zip(bits.iter()).enumerate() { if e != n { - panic!( + emit_bit_change_error!( "Bit conflict for {}.{}[{}] existing: {:?} new: {:?}", self.tiletype, name, bit, e, n ); @@ -382,12 +407,24 @@ impl TileBitsData { } } } + + pub fn set_bel_offset(&mut self, bel_relative_location : Option<(i32, i32)>) { + if self.db.bel_relative_location.is_some() && self.db.bel_relative_location != bel_relative_location { + emit_bit_change_error!( + "Bel offset conflict for {}. existing: {:?} new: {:?}", + self.tiletype, self.db.bel_relative_location, bel_relative_location + ); + } + info!("Setting bel offset {} {:?}", self.tiletype, bel_relative_location); + self.db.bel_relative_location = bel_relative_location; + self.dirty = true; + } pub fn add_enum_option( &mut self, name: &str, option: &str, desc: &str, - bits: BTreeSet, + bits: BTreeSet ) { if !self.db.enums.contains_key(name) { self.db.enums.insert( @@ -406,7 +443,7 @@ impl TileBitsData { match ec.options.get(option) { Some(old_bits) => { if bits != *old_bits { - panic!( + emit_bit_change_error!( "Bit conflict for {}.{}={} existing: {:?} new: {:?}", self.tiletype, name, option, old_bits, bits ); @@ -425,7 +462,9 @@ impl TileBitsData { let pc = self.db.conns.get_mut(to).unwrap(); if pc.iter().any(|fc| fc.from_wire == from) { // Connection already exists + info!("Connection {from} already exists {}", self.tiletype); } else { + info!("Connection {from} added {}", self.tiletype); self.dirty = true; pc.push(FixedConnectionData { from_wire: from.to_string(), @@ -616,6 +655,7 @@ impl Database { enums: BTreeMap::new(), conns: BTreeMap::new(), always_on: BTreeSet::new(), + bel_relative_location : None } }; self.tilebits @@ -639,6 +679,7 @@ impl Database { enums: BTreeMap::new(), conns: BTreeMap::new(), always_on: BTreeSet::new(), + bel_relative_location : None } }; self.ipbits diff --git a/libprjoxide/prjoxide/src/fuzz.rs b/libprjoxide/prjoxide/src/fuzz.rs index 7d76cbb..aec3e03 100644 --- a/libprjoxide/prjoxide/src/fuzz.rs +++ b/libprjoxide/prjoxide/src/fuzz.rs @@ -5,6 +5,13 @@ use crate::wires; use std::collections::{BTreeMap, BTreeSet}; use std::iter::FromIterator; +use ron::ser::PrettyConfig; +use serde::Serialize; +use std::fs::File; +use std::io::prelude::*; +use log::{info, warn}; + +#[derive(Clone, Debug)] pub enum FuzzMode { Pip { to_wire: String, @@ -22,12 +29,13 @@ pub enum FuzzMode { include_zeros: bool, // if true, explicit 0s instead of base will be created for unset bits for a setting disambiguate: bool, // add explicit 0s to disambiguate settings only assume_zero_base: bool, - }, + mark_relative_to: Option, // Track relative to this tile + } } -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] +#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] enum FuzzKey { - PipKey { from_wire: String }, + PipKey { from_wire: String, allow_partial_deltas: bool }, WordKey { bit: usize }, EnumKey { option: String }, } @@ -91,13 +99,15 @@ impl Fuzzer { desc: &str, include_zeros: bool, assume_zero_base: bool, + mark_relative_to: Option ) -> Fuzzer { Fuzzer { mode: FuzzMode::Enum { name: name.to_string(), - include_zeros: include_zeros, + include_zeros, disambiguate: false, // fixme - assume_zero_base: assume_zero_base, + assume_zero_base, + mark_relative_to }, tiles: fuzz_tiles.clone(), base: base_bit.clone(), @@ -131,6 +141,17 @@ impl Fuzzer { db, FuzzKey::PipKey { from_wire: from_wire.to_string(), + allow_partial_deltas : false + }, + bitfile, + ); + } + pub fn add_pip_sample_with_partial_delta(&mut self, db: &mut Database, from_wire: &str, bitfile: &str) { + self.add_sample( + db, + FuzzKey::PipKey { + from_wire: from_wire.to_string(), + allow_partial_deltas : true }, bitfile, ); @@ -147,126 +168,174 @@ impl Fuzzer { bitfile, ); } + + pub fn serialize_deltas(&mut self, filename: &str) { + let pretty = PrettyConfig { + depth_limit: 5, + new_line: "\n".to_string(), + indentor: " ".to_string(), + enumerate_arrays: false, + separate_tuple_members: false, + }; + + let buf = ron::ser::to_string_pretty(&self.deltas, pretty).unwrap(); + File::create(format!("{}.ron", filename)) + .unwrap() + .write_all(buf.as_bytes()) + .unwrap(); + } + + fn solve_pip(&mut self, db: &mut Database, + changed_tiles: &BTreeSet, + to_wire: &String, + full_mux: bool, // if true, explicit 0s instead of base will be created for unset bits for a setting + skip_fixed: bool, // if true, skip pips that have no bits associated with them (rather than created fixed conns) + fixed_conn_tile: &String, + ignore_tiles: &BTreeSet, // changes in these tiles don't cause pips to be rejected + ) -> usize { + let mut findings : usize = 0; + + // In full mux mode; we need the coverage sets of the changes + let mut coverage: BTreeMap> = BTreeMap::new(); + if full_mux { + for tile in self.tiles.iter() { + coverage.insert( + tile.to_string(), + self.deltas + .iter() + .filter_map(|(_k, v)| v.get(tile)) + .flatten() + .map(|(f, b, _v)| (*f, *b)) + .collect(), + ); + } + } + + for (key, value) in self.deltas.iter() { + if let FuzzKey::PipKey { from_wire, allow_partial_deltas } = key { + let relevant_deltas : BTreeMap> = + value.into_iter().filter(|(k, _v)| self.tiles.contains(*k) || !allow_partial_deltas) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + + if relevant_deltas + .iter() + .any(|(k, _v)| !self.tiles.contains(k) && !ignore_tiles.contains(k)) + { + warn!("Pip {} -> {} ({:?}) is not in watched set of {:?}", from_wire, to_wire, relevant_deltas.keys(), self.tiles); + // If this pip affects tiles outside of the fuzz region, skip it + continue; + } + if changed_tiles.len() == 0 { + info!("No changed tiles for {from_wire} -> {to_wire}"); + // No changes; it is a fixed connection + if skip_fixed { + continue; + } + let db_tile = self.base.tile_by_name(fixed_conn_tile).unwrap(); + let tile_db = db.tile_bitdb(&self.base.family, &db_tile.tiletype); + tile_db.add_conn( + &wires::normalize_wire(&self.base, db_tile, from_wire), + &wires::normalize_wire(&self.base, db_tile, to_wire), + ); + } else { + for tile in changed_tiles.iter() { + // Get the set of bits for this config + let bits: BTreeSet = if full_mux { + // In full mux mode, we add a value for all bits even if they didn't change + let value_bits = relevant_deltas.get(tile); + coverage + .get(tile) + .iter() + .map(|&x| x) + .flatten() + .map(|(f, b)| ConfigBit { + frame: *f, + bit: *b, + invert: value_bits.iter().any(|x| { + x.contains(&( + *f, + *b, + !self + .base + .tile_by_name(tile) + .unwrap() + .cram + .get(*f, *b), + )) + }) == self + .base + .tile_by_name(tile) + .unwrap() + .cram + .get(*f, *b), + }) + .collect() + } else { + // Get the changed bits in this tile as ConfigBits; or the base set if the tile didn't change + relevant_deltas + .get(tile) + .iter() + .map(|&x| x) + .flatten() + .map(|(f, b, v)| ConfigBit { + frame: *f, + bit: *b, + invert: !(*v), + }) + .collect() + }; + if bits.is_empty() && skip_fixed { + info!("Skipping {from_wire}->{to_wire} while solving"); + continue; + } + // Add the pip to the tile data + let tile_data = self.base.tile_by_name(tile).unwrap(); + let tile_db = db.tile_bitdb(&self.base.family, &tile_data.tiletype); + tile_db.add_pip( + &wires::normalize_wire(&self.base, tile_data, from_wire), + &wires::normalize_wire(&self.base, tile_data, to_wire), + bits, + ); + findings += 1; + } + } + } + } + + info!("Found {} pips for {} deltas", findings, self.deltas.len()); + + findings + } + pub fn solve(&mut self, db: &mut Database) { // Get a set of tiles that have been changed let changed_tiles: BTreeSet = self - .deltas + .deltas.clone() .iter() .flat_map(|(_k, v)| v.keys()) .filter(|t| self.tiles.contains(*t)) .map(String::to_string) .collect(); - match &self.mode { + // + // for (key, delta) in self.deltas.iter() { + // info!("Delta: {:?} {:?}", key, delta); + // } + + // Clone so we can call out to individual functions later. Does a copy but this should be + // okay since solve isn't called in a hot loop + match self.mode.clone() { FuzzMode::Pip { to_wire, full_mux, skip_fixed, fixed_conn_tile, ignore_tiles, - } => { - // In full mux mode; we need the coverage sets of the changes - let mut coverage: BTreeMap> = BTreeMap::new(); - if *full_mux { - for tile in self.tiles.iter() { - coverage.insert( - tile.to_string(), - self.deltas - .iter() - .filter_map(|(_k, v)| v.get(tile)) - .flatten() - .map(|(f, b, _v)| (*f, *b)) - .collect(), - ); - } - } - - for (key, value) in self.deltas.iter() { - if let FuzzKey::PipKey { from_wire } = key { - if value - .iter() - .any(|(k, _v)| !self.tiles.contains(k) && !ignore_tiles.contains(k)) - { - // If this pip affects tiles outside of the fuzz region, skip it - continue; - } - if changed_tiles.len() == 0 { - // No changes; it is a fixed connection - if *skip_fixed { - continue; - } - let db_tile = self.base.tile_by_name(fixed_conn_tile).unwrap(); - let tile_db = db.tile_bitdb(&self.base.family, &db_tile.tiletype); - tile_db.add_conn( - &wires::normalize_wire(&self.base, db_tile, from_wire), - &wires::normalize_wire(&self.base, db_tile, to_wire), - ); - } else { - for tile in changed_tiles.iter() { - // Get the set of bits for this config - let bits: BTreeSet = if *full_mux { - // In full mux mode, we add a value for all bits even if they didn't change - let value_bits = value.get(tile); - coverage - .get(tile) - .iter() - .map(|&x| x) - .flatten() - .map(|(f, b)| ConfigBit { - frame: *f, - bit: *b, - invert: value_bits.iter().any(|x| { - x.contains(&( - *f, - *b, - !self - .base - .tile_by_name(tile) - .unwrap() - .cram - .get(*f, *b), - )) - }) == self - .base - .tile_by_name(tile) - .unwrap() - .cram - .get(*f, *b), - }) - .collect() - } else { - // Get the changed bits in this tile as ConfigBits; or the base set if the tile didn't change - value - .get(tile) - .iter() - .map(|&x| x) - .flatten() - .map(|(f, b, v)| ConfigBit { - frame: *f, - bit: *b, - invert: !(*v), - }) - .collect() - }; - if bits.is_empty() && *skip_fixed { - continue; - } - // Add the pip to the tile data - let tile_data = self.base.tile_by_name(tile).unwrap(); - let tile_db = db.tile_bitdb(&self.base.family, &tile_data.tiletype); - tile_db.add_pip( - &wires::normalize_wire(&self.base, tile_data, from_wire), - &wires::normalize_wire(&self.base, tile_data, to_wire), - bits, - ); - } - } - } - } - } + } => { self.solve_pip(db, &changed_tiles, &to_wire, full_mux, skip_fixed, &fixed_conn_tile, &ignore_tiles); } FuzzMode::Word { name, width } => { for tile in changed_tiles.iter() { let mut cbits = Vec::new(); - for i in 0..*width { + for i in 0..width { let key = FuzzKey::WordKey { bit: i }; let b = match self.deltas.get(&key) { None => BTreeSet::new(), @@ -295,8 +364,10 @@ impl Fuzzer { include_zeros, disambiguate: _, assume_zero_base, + mark_relative_to } => { if self.deltas.len() < 2 { + warn!("Need at least two deltas got {}", self.deltas.len()); return; } for tile in changed_tiles { @@ -326,7 +397,7 @@ impl Fuzzer { if let FuzzKey::EnumKey { option } = key { let b = match delta.get(&tile) { None => { - if *include_zeros { + if include_zeros { // All bits as default changed_bits .iter() @@ -336,7 +407,7 @@ impl Fuzzer { invert: *v, }) .collect() - } else if *assume_zero_base { + } else if assume_zero_base { changed_bits .iter() .filter(|(_f, _b, v)| !(*v)) @@ -353,12 +424,12 @@ impl Fuzzer { Some(td) => changed_bits .iter() .filter(|(f, b, v)| { - *include_zeros + include_zeros || !(*v) || td.contains(&(*f, *b, *v)) }) .filter(|(f, b, v)| { - !(*assume_zero_base) + !(assume_zero_base) || *v || !(*v) && !td.contains(&(*f, *b, *v)) }) @@ -375,9 +446,21 @@ impl Fuzzer { }; // Add the enum to the tile data let tile_data = self.base.tile_by_name(&tile).unwrap(); + info!("Resolved {} {} {:?} {}", name, option, b, &tile_data.tiletype); + let tile_db = db.tile_bitdb(&self.base.family, &tile_data.tiletype); - tile_db.add_enum_option(name, &option, &self.desc, b); + + if let Some(relative_tile) = mark_relative_to.clone() { + let offset = { + let ref_tile = self.base.tile_by_name(&relative_tile).unwrap(); + (ref_tile.x as i32 - tile_data.x as i32, + ref_tile.y as i32 - tile_data.y as i32) + }; + tile_db.set_bel_offset(Some(offset)); + } + + tile_db.add_enum_option(&name, &option, &self.desc, b); } } } diff --git a/libprjoxide/prjoxide/src/ipfuzz.rs b/libprjoxide/prjoxide/src/ipfuzz.rs index d9cac04..cd360c1 100644 --- a/libprjoxide/prjoxide/src/ipfuzz.rs +++ b/libprjoxide/prjoxide/src/ipfuzz.rs @@ -4,12 +4,17 @@ use crate::database::*; use std::collections::{BTreeMap, BTreeSet}; use std::iter::FromIterator; +use ron::ser::PrettyConfig; +use std::fs::File; +use std::io::prelude::*; +use serde::Serialize; + pub enum IPFuzzMode { Word { name: String, width: usize, inverted_mode: bool }, Enum { name: String }, } -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] +#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] enum IPFuzzKey { WordKey { bits: Vec }, EnumKey { option: String }, @@ -177,4 +182,23 @@ impl IPFuzzer { } db.flush(); } + + pub fn serialize_deltas(&mut self, filename: &str) { + let pretty = PrettyConfig { + depth_limit: 5, + new_line: "\n".to_string(), + indentor: " ".to_string(), + enumerate_arrays: false, + separate_tuple_members: false, + }; + + let buf = ron::ser::to_string_pretty(&self.deltas, pretty).unwrap(); + File::create(format!( + "{}.ron", + filename + )) + .unwrap() + .write_all(buf.as_bytes()) + .unwrap(); + } } diff --git a/libprjoxide/pyprjoxide/Cargo.toml b/libprjoxide/pyprjoxide/Cargo.toml index 55fb0ee..3bc8c3c 100644 --- a/libprjoxide/pyprjoxide/Cargo.toml +++ b/libprjoxide/pyprjoxide/Cargo.toml @@ -10,6 +10,9 @@ prjoxide = { path = "../prjoxide" } version = "0.13.1" features = ["extension-module"] +[dependencies.pythonize] +version = "0.13" + [lib] name = "pyprjoxide" crate-type = ["cdylib"] diff --git a/libprjoxide/pyprjoxide/src/lib.rs b/libprjoxide/pyprjoxide/src/lib.rs index 3f0cb1d..ebd9883 100644 --- a/libprjoxide/pyprjoxide/src/lib.rs +++ b/libprjoxide/pyprjoxide/src/lib.rs @@ -13,9 +13,10 @@ use prjoxide::docs; use prjoxide::fuzz; use prjoxide::ipfuzz; use prjoxide::nodecheck; -use prjoxide::wires; use prjoxide::pip_classes; use prjoxide::sites; +use prjoxide::wires; + #[pyclass] struct Database { @@ -35,6 +36,7 @@ impl Database { #[pyclass] struct Fuzzer { fz: fuzz::Fuzzer, + name: String, } #[pymethods] @@ -64,6 +66,7 @@ impl Fuzzer { width, zero_bitfile, ), + name: name.to_string() } } @@ -96,6 +99,7 @@ impl Fuzzer { full_mux, skip_fixed, ), + name: to_wire.to_string() } } @@ -108,6 +112,7 @@ impl Fuzzer { desc: &str, include_zeros: bool, assume_zero_base: bool, + mark_relative_to: Option ) -> Fuzzer { let base_chip = bitstream::BitstreamParser::parse_file(&mut db.db, base_bitfile).unwrap(); @@ -122,7 +127,9 @@ impl Fuzzer { desc, include_zeros, assume_zero_base, + mark_relative_to ), + name: name.to_string() } } @@ -134,6 +141,10 @@ impl Fuzzer { self.fz.add_pip_sample(&mut db.db, from_wire, base_bitfile); } + fn add_pip_sample_with_partial_delta(&mut self, db: &mut Database, from_wire: &str, base_bitfile: &str) { + self.fz.add_pip_sample_with_partial_delta(&mut db.db, from_wire, base_bitfile); + } + fn add_enum_sample(&mut self, db: &mut Database, option: &str, base_bitfile: &str) { self.fz.add_enum_sample(&mut db.db, option, base_bitfile); } @@ -141,6 +152,14 @@ impl Fuzzer { fn solve(&mut self, db: &mut Database) { self.fz.solve(&mut db.db); } + + fn serialize_deltas(&mut self, filename: &str) { + self.fz.serialize_deltas(filename); + } + + fn get_name(&self) -> String { + self.name.clone() + } } #[pyclass] @@ -214,6 +233,10 @@ impl IPFuzzer { fn solve(&mut self, db: &mut Database) { self.fz.solve(&mut db.db); } + + fn serialize_deltas(&mut self, filename: &str) { + self.fz.serialize_deltas(filename); + } } #[pyfunction] @@ -272,6 +295,11 @@ impl Chip { fn get_ip_values(&mut self) -> Vec<(u32, u8)> { self.c.ipconfig.iter().map(|(a, d)| (*a, *d)).collect() } + + fn delta(&self, db: &mut Database, new_bitstream: &str) -> PyResult { + let parsed_bitstream = bitstream::BitstreamParser::parse_file(&mut db.db, new_bitstream).unwrap(); + Ok(parsed_bitstream.delta(&self.c)) + } } #[pyfunction] From b8de947fc6c3dd301d45c985cdf9c6047dff875b Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Mon, 19 Jan 2026 09:51:33 -0700 Subject: [PATCH 06/29] Changes related to bringing support for lifcl-33 devices --- fuzzers/LFCPNX/shared/empty_40.v | 9 + fuzzers/LIFCL/021-cmux/fuzzer.py | 29 +- fuzzers/LIFCL/023-trunk-spine/fuzzer.py | 17 +- fuzzers/LIFCL/024-dcc-dcs/dcc.v | 2 +- fuzzers/LIFCL/024-dcc-dcs/dcs.v | 2 +- fuzzers/LIFCL/024-dcc-dcs/fuzzer.py | 21 ++ fuzzers/LIFCL/030-io_route/fuzzer.py | 61 +++- fuzzers/LIFCL/031-io_mode/iob.v | 11 + fuzzers/LIFCL/031-io_mode/iob_17.v | 5 +- fuzzers/LIFCL/032-hsio_mode/iob.v | 13 + fuzzers/LIFCL/032-hsio_mode/iob_40.v | 7 +- fuzzers/LIFCL/039-copy-io/fuzzer.py | 10 +- fuzzers/LIFCL/062-lram-config/fuzzer.py | 109 ++++---- fuzzers/LIFCL/062-lram-config/lram.v | 2 +- fuzzers/LIFCL/063-lram-routing/fuzzer.py | 32 ++- fuzzers/LIFCL/071-iodelay/iodelay.v | 2 +- fuzzers/LIFCL/091-osc/osc_33.v | 12 + fuzzers/LIFCL/091-osc/osc_pins.v | 16 ++ fuzzers/LIFCL/100-ip-base/ip33.v | 11 + fuzzers/LIFCL/100-ip-base/ip_33u.v | 12 + fuzzers/LIFCL/110-global-structure/fuzzer.py | 41 ++- fuzzers/LIFCL/900-always-on/fuzzer.py | 1 + fuzzers/LIFCL/shared/empty_33.v | 8 + fuzzers/LIFCL/shared/empty_33u.v | 9 + fuzzers/LIFCL/shared/route_33.v | 10 + generate_database.sh | 17 ++ radiant.sh | 6 +- tools/bitstreamcache.py | 26 +- tools/extract_tilegrid.py | 21 +- util/common/database.py | 124 ++++++++- util/common/lapie.py | 224 +++++++++++++-- util/common/radiant.py | 34 ++- util/common/tiles.py | 275 ++++++++++++++++++- util/fuzz/fuzzconfig.py | 79 +++++- util/fuzz/fuzzloops.py | 32 ++- util/fuzz/interconnect.py | 251 ++++++++++++++--- util/fuzz/nonrouting.py | 54 +++- util/fuzz/primitives.py | 95 +++++++ 38 files changed, 1487 insertions(+), 203 deletions(-) create mode 100644 fuzzers/LFCPNX/shared/empty_40.v create mode 100644 fuzzers/LIFCL/031-io_mode/iob.v create mode 100644 fuzzers/LIFCL/032-hsio_mode/iob.v create mode 100644 fuzzers/LIFCL/091-osc/osc_33.v create mode 100644 fuzzers/LIFCL/091-osc/osc_pins.v create mode 100644 fuzzers/LIFCL/100-ip-base/ip33.v create mode 100644 fuzzers/LIFCL/100-ip-base/ip_33u.v create mode 100644 fuzzers/LIFCL/shared/empty_33.v create mode 100644 fuzzers/LIFCL/shared/empty_33u.v create mode 100644 fuzzers/LIFCL/shared/route_33.v create mode 100755 generate_database.sh create mode 100644 util/fuzz/primitives.py diff --git a/fuzzers/LFCPNX/shared/empty_40.v b/fuzzers/LFCPNX/shared/empty_40.v new file mode 100644 index 0000000..8647858 --- /dev/null +++ b/fuzzers/LFCPNX/shared/empty_40.v @@ -0,0 +1,9 @@ + +(* \db:architecture ="LFCPNX", \db:device ="LFCPNX-40", \db:package ="LFG672", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule diff --git a/fuzzers/LIFCL/021-cmux/fuzzer.py b/fuzzers/LIFCL/021-cmux/fuzzer.py index 9835539..0f01db2 100644 --- a/fuzzers/LIFCL/021-cmux/fuzzer.py +++ b/fuzzers/LIFCL/021-cmux/fuzzer.py @@ -1,10 +1,27 @@ from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect import re +import tiles +import database -cfg = FuzzConfig(job="CMUXROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R29C49:CMUX_0", "CIB_R29C50:CMUX_1", "CIB_R38C49:CMUX_2", "CIB_R38C50:CMUX_3"]) +def fuzz_33(): + device = "LIFCL-33" + tilegrid = database.get_tilegrid(device)['tiles'] + ts = [t for t,tinfo in tilegrid.items() if tinfo["tiletype"].startswith("CMUX")] + cfg = FuzzConfig(job="CMUXROUTE-33", device=device, sv="../shared/route_33.v", tiles=ts) + + cfg.setup() + + (r,c) = (37, 25) + tile_prefix = f"R{r}C{c}" + + nodes = [f"{tile_prefix}_J.*CMUX.*", f"{tile_prefix}_J.*DCSIP", f"{tile_prefix}_J.*PCLKDIV.*"] + + fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=False, full_mux_style=False) + +def fuzz_40(): + cfg = FuzzConfig(job="CMUXROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R29C49:CMUX_0", "CIB_R29C50:CMUX_1", "CIB_R38C49:CMUX_2", "CIB_R38C50:CMUX_3"]) -def main(): cfg.setup() nodes = ["R28C49_JHPRX{}_CMUX_CORE_CMUX1".format(i) for i in range(16)] + \ ["R28C49_JHPRX{}_CMUX_CORE_CMUX0".format(i) for i in range(16)] + \ @@ -42,5 +59,13 @@ def main(): misc_nodes.append("R28C49_JGSR_N_GSR_CORE_GSR_CENTER") misc_nodes.append("R28C49_JCLK_GSR_CORE_GSR_CENTER") fuzz_interconnect(config=cfg, nodenames=misc_nodes, regex=False, bidir=False, full_mux_style=False) + + +def main(): + fuzz_33() + fuzz_40() + + if __name__ == "__main__": main() + diff --git a/fuzzers/LIFCL/023-trunk-spine/fuzzer.py b/fuzzers/LIFCL/023-trunk-spine/fuzzer.py index 53b1495..4a32533 100644 --- a/fuzzers/LIFCL/023-trunk-spine/fuzzer.py +++ b/fuzzers/LIFCL/023-trunk-spine/fuzzer.py @@ -1,17 +1,25 @@ from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect import re +import database +import tiles spine_cfgs = { ("CIB_R29C13:SPINE_L1", "R28C13"), ("CIB_R29C37:SPINE_L0", "R28C37"), ("CIB_R29C62:SPINE_R0", "R28C61"), ("CIB_R29C74:SPINE_R1", "R28C73"), + + ("CIB_R38C13:SPINE_L0_33K", "R41C13"), + ("CIB_R38C38:SPINE_R0_33K", "R41C37"), } hrow_cfgs = { ("CIB_R29C37:SPINE_L0", "R28C31"), ("CIB_R29C62:SPINE_R0", "R28C61"), + + ("CIB_R38C13:SPINE_L0_33K", "R37C19"), + ("CIB_R38C38:SPINE_R0_33K", "R37C31"), } trunk_cfgs = { @@ -21,17 +29,20 @@ def main(): for tile, rc in spine_cfgs: - cfg = FuzzConfig(job="TAPROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=[tile]) + suffix = "33" if "33K" in tile else "40" + cfg = FuzzConfig(job=f"TAPROUTE-{tile}", device=f"LIFCL-{suffix}", sv=f"../shared/route_{suffix}.v", tiles=[tile]) cfg.setup() nodes = ["{}_VPSX{:02}00".format(rc, i) for i in range(16)] fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=False) for tile, rc in hrow_cfgs: - cfg = FuzzConfig(job="ROWROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=[tile]) + suffix = "33" if "33K" in tile else "40" + cfg = FuzzConfig(job=f"ROWROUTE-{tile}", device=f"LIFCL-{suffix}", sv=f"../shared/route_{suffix}.v", tiles=[tile]) cfg.setup() nodes = ["{}_HPRX{:02}00".format(rc, i) for i in range(16)] fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=False) for tile, rcs in trunk_cfgs: - cfg = FuzzConfig(job="TRUNKROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=[tile]) + suffix = "33" if "33K" in tile else "40" + cfg = FuzzConfig(job="TRUNKROUTE", device=f"LIFCL-{suffix}", sv=f"../shared/route_{suffix}.v", tiles=[tile]) cfg.setup() nodes = ["{}HPRX{}".format(rcs, i) for i in range(16)] fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=False) diff --git a/fuzzers/LIFCL/024-dcc-dcs/dcc.v b/fuzzers/LIFCL/024-dcc-dcs/dcc.v index f709b17..da10614 100644 --- a/fuzzers/LIFCL/024-dcc-dcs/dcc.v +++ b/fuzzers/LIFCL/024-dcc-dcs/dcc.v @@ -1,5 +1,5 @@ -(* \db:architecture ="LIFCL", \db:device ="${dev}", \db:package ="QFN72", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +(* \db:architecture ="LIFCL", \db:device ="${dev}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) module top ( ); diff --git a/fuzzers/LIFCL/024-dcc-dcs/dcs.v b/fuzzers/LIFCL/024-dcc-dcs/dcs.v index 29aa8ad..e02aa3b 100644 --- a/fuzzers/LIFCL/024-dcc-dcs/dcs.v +++ b/fuzzers/LIFCL/024-dcc-dcs/dcs.v @@ -1,5 +1,5 @@ -(* \db:architecture ="LIFCL", \db:device ="${dev}", \db:package ="QFN72", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +(* \db:architecture ="LIFCL", \db:device ="${dev}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) module top ( ); diff --git a/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py b/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py index 1067bae..ed620bd 100644 --- a/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py +++ b/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py @@ -2,6 +2,8 @@ import nonrouting import fuzzloops import re +import lapie +import database def main(): # 40k @@ -51,5 +53,24 @@ def get_substs(dcsmode): dcc_tiles = ["CIB_R10C0:LMID_RBB_5_15K", "CIB_R10C75:RMID_PICB_DLY10", "CIB_R0C37:TMID_0", "CIB_R0C38:TMID_1_15K", "CIB_R0C39:CLKBUF_T_15K"] fuzzloops.parallel_foreach(dcc_prims, per_site) + dev = "LIFCL-33" + sv = "../shared/empty_33.v" + cfg = FuzzConfig(job="udbcheck", device=dev, sv=sv, tiles=[]) + cfg.setup() + + # Tile names pulled from physical view + dcc_prims = ["DCC_B{}".format(i) for i in range(18)] + \ + ["DCC_C{}".format(i) for i in range(4)] + \ + ["DCC_T{}".format(i) for i in range(16)] + + + dcc_tiles = [x for x in database.get_tilegrid("LIFCL-33")['tiles'] if "MID" in x ] + dcs_tiles = [x for x in database.get_tilegrid("LIFCL-33")['tiles'] if "CMUX" in x ] + + print(dcc_tiles, dcs_tiles) + fuzzloops.parallel_foreach(dcc_prims + dcs_prims, per_site) + + + if __name__ == '__main__': main() diff --git a/fuzzers/LIFCL/030-io_route/fuzzer.py b/fuzzers/LIFCL/030-io_route/fuzzer.py index 8c378a4..1e01f37 100644 --- a/fuzzers/LIFCL/030-io_route/fuzzer.py +++ b/fuzzers/LIFCL/030-io_route/fuzzer.py @@ -1,8 +1,37 @@ from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect import re +import tiles -configs = [ +tiles_33 = [ + [(0, 30)],# "B0"), + [(0, 2)],# "B5"), + [(0, 38)],# "B1_DED"), + [(0, 40)],# "B1"), + [(83, 12), (83, 11)],# B3_0, B3_1 + [(83, 14), (83, 15)],# B3_0_ECLK_L, B3_1 + [(83, 18), (83, 19)], # B3_0, B3_1_V18_32 + [(83, 32), (83, 33)], # B2_0, B2_1_V18_21 + [(83, 34), (83, 35)], # B2_0, B2_1 + [(83, 46), (83, 47)], # B2_0, B2_1_1_V18_22 + [(83, 4), (83, 5)],# B4_0 B4_1_V18_41 + [(83, 6), (83, 7)],# B4_0 B4_1_V18_42 + [(83, 8), (83, 9)],# B3_0, B3_1_V18_31 + [(0, 10)], # SYSIO_b5 + ] + +def create_io_config(device, rcs): + ts = [ tile for rc in rcs for tile in tiles.get_tiles_by_rc(device, rc) ] + job_name = "IOROUTE_" + "_".join([f"R{rc[0]}C{rc[1]}" for rc in rcs]) + return { + "cfg": FuzzConfig(job=job_name, device="LIFCL-33", sv="../shared/route_33.v", + tiles=ts), + "rcs": rcs + } + +configs_33 = [create_io_config("LIFCL-33", x) for x in tiles_33] + +configs = configs_33 + [ { "cfg": FuzzConfig(job="IOROUTE0_17K", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R0C59:SYSIO_B0_0_15K"]), "rc": (0, 59), @@ -73,7 +102,7 @@ }, ] -ignore_tiles = set([ +ignore_tiles_40k = set([ "CIB_R55C8:CIB", "CIB_R55C9:CIB", "CIB_R55C16:CIB", @@ -127,17 +156,37 @@ def main(): for config in configs: cfg = config["cfg"] cfg.setup() - r, c = config["rc"] - nodes = ["R{}C{}_*".format(r, c)] + + rcs = set([]) + if "rc" in config: + rcs = set([ config["rc"] ]) + else: + rcs = set(config["rcs"]) + + nodes = [f"R{r}C{c}_.*" for (r,c) in rcs] def nodename_filter(x, nodes): - return ("R{}C{}_".format(r, c) in x) and ("_GEARING_PIC_TOP_" in x or "SEIO18_CORE" in x or "DIFFIO18_CORE" in x or "I217" in x or "I218" in x or "SEIO33_CORE" in x or "SIOLOGIC_CORE" in x) + node_in_tiles = tiles.get_rc_from_name(cfg.device, x) in rcs + return ("_GEARING_PIC_TOP_" in x or "SEIO18_CORE" in x or "DIFFIO18_CORE" in x or "I217" in x or "I218" in x or "SEIO33_CORE" in x or "SIOLOGIC_CORE" in x) def pip_filter(pip, nodes): from_wire, to_wire = pip return not ("ADC_CORE" in to_wire or "ECLKBANK_CORE" in to_wire or "MID_CORE" in to_wire or "REFMUX_CORE" in to_wire or "CONFIG_JTAG_CORE" in to_wire or "CONFIG_JTAG_CORE" in from_wire or "REFCLOCK_MUX_CORE" in to_wire) + + ignore_tiles = [tile + for (r,c) in rcs + for ro in [r+1,r-1,r] + for co in [c+1,c-1,c] + for tile in tiles.get_tiles_by_rc(cfg.device, (ro,co)) + ] + if cfg.device == "LIFCL-17": + ignore_tiles = ignore_tiles_17k + elif cfg.device == "LIFCL-40": + ignore_tiles = ignore_tiles_40k + + print("Ignore tiles: ", ignore_tiles) fuzz_interconnect(config=cfg, nodenames=nodes, nodename_predicate=nodename_filter, pip_predicate=pip_filter, regex=True, bidir=True, - ignore_tiles=ignore_tiles_17k if cfg.device == "LIFCL-17" else ignore_tiles) + ignore_tiles=ignore_tiles) if __name__ == "__main__": main() diff --git a/fuzzers/LIFCL/031-io_mode/iob.v b/fuzzers/LIFCL/031-io_mode/iob.v new file mode 100644 index 0000000..f4ced0b --- /dev/null +++ b/fuzzers/LIFCL/031-io_mode/iob.v @@ -0,0 +1,11 @@ +(* \db:architecture ="${arch}", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + ${cmt} ${pintype} q +); + + VHI vhi_i(); + + ${cmt}(* \xref:LOG ="${primtype}=q_pad.bb_inst@0@8", \dm:cellmodel_primitives ="${primtype}=q_pad.bb_inst", \dm:primitive ="${primtype}", \dm:programming ="MODE:${primtype} ${primtype}:::IO_TYPE=${iotype},BANK_VCCIO=${vcc}${extra_config}:T=${t}", \dm:site ="${site}" *) + ${cmt}${primtype} \q_pad.bb_inst (.PADDO(q)); + +endmodule diff --git a/fuzzers/LIFCL/031-io_mode/iob_17.v b/fuzzers/LIFCL/031-io_mode/iob_17.v index 629a1f3..4d1a236 100644 --- a/fuzzers/LIFCL/031-io_mode/iob_17.v +++ b/fuzzers/LIFCL/031-io_mode/iob_17.v @@ -7,10 +7,7 @@ module top ( (* \xref:LOG ="q_c@0@9" *) VHI vhi_i(); - (* \xref:LOG ="q_c@0@9" *) - wire q_c; - ${cmt}(* \xref:LOG ="${primtype}=q_pad.bb_inst@0@8", \dm:cellmodel_primitives ="${primtype}=q_pad.bb_inst", \dm:primitive ="${primtype}", \dm:programming ="MODE:${primtype} ${primtype}:::IO_TYPE=${iotype},BANK_VCCIO=${vcc}${extra_config}:T=${t}", \dm:site ="${site}" *) - ${cmt}${primtype} \q_pad.bb_inst (.PADDO(q_c)); + ${cmt}${primtype} \q_pad.bb_inst (.PADDO(q)); endmodule diff --git a/fuzzers/LIFCL/032-hsio_mode/iob.v b/fuzzers/LIFCL/032-hsio_mode/iob.v new file mode 100644 index 0000000..164da3c --- /dev/null +++ b/fuzzers/LIFCL/032-hsio_mode/iob.v @@ -0,0 +1,13 @@ +(* \db:architecture ="LIFCL", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + ${cmt} ${pintype} q +); + + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); + + ${cmt}(* \dm:primitive ="${primtype}", \dm:programming ="MODE:${primtype} ${primtype}:::IO_TYPE=${iotype},BANK_VCCIO=${vcc}${extra_config}:T=${t}", \dm:site ="${site}" *) + ${cmt}${primtype} INST (.PADDO(q)); + +endmodule diff --git a/fuzzers/LIFCL/032-hsio_mode/iob_40.v b/fuzzers/LIFCL/032-hsio_mode/iob_40.v index 15cfbbd..61a9880 100644 --- a/fuzzers/LIFCL/032-hsio_mode/iob_40.v +++ b/fuzzers/LIFCL/032-hsio_mode/iob_40.v @@ -7,10 +7,7 @@ module top ( (* \xref:LOG ="q_c@0@9" *) VHI vhi_i(); - (* \xref:LOG ="q_c@0@9" *) - wire q_c; - ${cmt}(* \xref:LOG ="${primtype}=q_pad.bb_inst@0@8", \dm:cellmodel_primitives ="${primtype}=q_pad.bb_inst", \dm:primitive ="${primtype}", \dm:programming ="MODE:${primtype} ${primtype}:::IO_TYPE=${iotype},BANK_VCCIO=${vcc}${extra_config}:T=${t}", \dm:site ="${site}" *) - ${cmt}${primtype} \q_pad.bb_inst (.PADDO(q_c)); + ${cmt}${primtype} \q_pad.bb_inst (.PADDO(q)); -endmodule \ No newline at end of file +endmodule diff --git a/fuzzers/LIFCL/039-copy-io/fuzzer.py b/fuzzers/LIFCL/039-copy-io/fuzzer.py index 7f3098f..3ed226d 100644 --- a/fuzzers/LIFCL/039-copy-io/fuzzer.py +++ b/fuzzers/LIFCL/039-copy-io/fuzzer.py @@ -1,14 +1,20 @@ import database import libpyprjoxide +import database def main(): + + lilfcl_tile_types = database.get_tiletypes("LIFCL") + def get_tiletypes_with_prefix(prefix): + return [k for k in lilfcl_tile_types if k.startswith(prefix)] + db = libpyprjoxide.Database(database.get_db_root()) libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B5_1", ["SYSIO_B5_1_V18", "SYSIO_B5_1_15K_DQS51", "SYSIO_B5_1_15K_DQS50", "SYSIO_B5_1_15K_ECLK_L_V52"], "PEWC", "") libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B5_0", ["SYSIO_B5_0_15K_DQS52"], "PEWC", "") libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B4_0", ["SYSIO_B4_0_DQS1", "SYSIO_B4_0_DQS3", "SYSIO_B4_0_DLY50", "SYSIO_B4_0_DLY42", "SYSIO_B4_0_15K_DQS42", "SYSIO_B4_0_15K_BK4_V42", "SYSIO_B4_0_15K_V31"], "PEWC", "") libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B4_1", ["SYSIO_B4_1_DQS0", "SYSIO_B4_1_DQS2", "SYSIO_B4_1_DQS4", "SYSIO_B4_1_DLY52", "SYSIO_B4_1_15K_DQS41"], "PEWC", "") - libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B3_0", ["SYSIO_B3_0_DLY30_V18", "SYSIO_B3_0_DQS1", "SYSIO_B3_0_DQS3", "SYSIO_B3_0_15K_DQS32"], "PEWC", "") - libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B3_1", ["SYSIO_B3_1_DLY32", "SYSIO_B3_1_DQS0", "SYSIO_B3_1_DQS2", "SYSIO_B3_1_DQS4", "SYSIO_B3_1_ECLK_R", "SYSIO_B3_1_V18", "SYSIO_B3_1_15K_DQS30", "SYSIO_B3_1_15K_ECLK_R_DQS31"], "PEWC", "") + libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B3_0", get_tiletypes_with_prefix("SYSIO_B3_0_"), "PEWC", "") + libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B3_1", get_tiletypes_with_prefix("SYSIO_B3_1_"), "PEWC", "") libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B1_0_ODD", ["SYSIO_B1_0_C"], "C", "") libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B2_0_ODD", ["SYSIO_B2_0_C"], "C", "") diff --git a/fuzzers/LIFCL/062-lram-config/fuzzer.py b/fuzzers/LIFCL/062-lram-config/fuzzer.py index c183360..1cf1fb8 100644 --- a/fuzzers/LIFCL/062-lram-config/fuzzer.py +++ b/fuzzers/LIFCL/062-lram-config/fuzzer.py @@ -2,79 +2,64 @@ import nonrouting import fuzzloops import re +import database +from primitives import lram_core +import tiles +from tqdm.asyncio import tqdm +import asyncio -configs = [ - ("LRAM_CORE_R18C86", "LRAM0", FuzzConfig(job="LRAM0", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R23C87:LRAM_0" ])), - ("LRAM_CORE_R40C86", "LRAM1", FuzzConfig(job="LRAM1", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R41C87:LRAM_1", ])), +def create_device_lram_configs(device): + # Find all the tiles where the LRAM_CORE primitive lives + bel_tiles = sorted(tiles.get_tiles_by_primitive(device, "LRAM_CORE").keys(), key = lambda x: tiles.get_rc_from_name(device, x[0])) + print("bel", device, bel_tiles, list(enumerate(bel_tiles))) - ("LRAM_CORE_R15C74", "LRAM0", FuzzConfig(job="LRAM0_17", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R15C75:LRAM_0_15K"])), - ("LRAM_CORE_R16C74", "LRAM1", FuzzConfig(job="LRAM1_17", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R16C75:LRAM_1_15K"])), - ("LRAM_CORE_R2C1", "LRAM2", FuzzConfig(job="LRAM2_17", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R3C0:LRAM_2_15K"])), - ("LRAM_CORE_R11C1", "LRAM3", FuzzConfig(job="LRAM3_17", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R12C0:LRAM_3_15K"])), - ("LRAM_CORE_R20C1", "LRAM4", FuzzConfig(job="LRAM4_17", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R21C0:LRAM_4_15K"])), -] + # All the LRAM's have different tiles which do the configuration. These are the LRAM_* tile types + lram_config_tiles = list(tiles.get_tiles_by_filter(device, lambda k,v: v["tiletype"].startswith("LRAM_")).keys()) + + return [(bel_site, lram, FuzzConfig(job=bel_site, device=device, tiles=[bel_tile] + lram_config_tiles)) + for (lram,(bel_site,bel_tile)) in enumerate(bel_tiles) + ] + + +configs = [cfg + for device in database.get_device_list() if device.startswith("LIFCL") + for cfg in create_device_lram_configs(device)] def main(): def per_config(x): - site, lram, cfg = x + site, lram_idx, cfg = x cfg.setup() empty = cfg.build_design(cfg.sv, {}) cfg.sv = "lram.v" - def get_substs(mode="NONE", kv=None, mux=False): + lram = f"LRAM{lram_idx}" + def get_substs(mode="NONE", kv=None): if kv is None: config = "" - elif mux: - val = "#SIG" - if kv[1] in ("0", "1"): - val = kv[1] - if kv[1] == "INV": - val = "#INV" - config = "{}::::{}={}".format(mode, kv[0], val) else: - config = "{}:::{}={}".format(mode, kv[0], kv[1]) - return dict(cmt="//" if mode == "NONE" else "", config=config, site=site) - modes = ["NONE", "LRAM_CORE"] - nonrouting.fuzz_enum_setting(cfg, empty, "{}.MODE".format(lram), modes, - lambda x: get_substs(mode=x), False, - desc="{} primitive mode".format(lram)) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.ASYNC_RST_RELEASE".format(lram), ["SYNC", "ASYNC"], - lambda x: get_substs(mode="LRAM_CORE", kv=("ASYNC_RST_RELEASE", x)), False, - desc="LRAM reset release configuration") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DATA_PRESERVE".format(lram), ["DISABLE", "ENABLE"], - lambda x: get_substs(mode="LRAM_CORE", kv=("DATA_PRESERVE", x)), False, - desc="LRAM data preservation across resets") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.EBR_SP_EN".format(lram), ["DISABLE", "ENABLE"], - lambda x: get_substs(mode="LRAM_CORE", kv=("EBR_SP_EN", x)), False, - desc="EBR single port mode") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.ECC_BYTE_SEL".format(lram), ["ECC_EN", "BYTE_EN"], - lambda x: get_substs(mode="LRAM_CORE", kv=("ECC_BYTE_SEL", x)), False, - desc="") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.GSR".format(lram), ["ENABLED", "DISABLED"], - lambda x: get_substs(mode="LRAM_CORE", kv=("GSR", x)), False, - desc="LRAM global set/reset mask") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.OUT_REGMODE_A".format(lram), ["NO_REG", "OUT_REG"], - lambda x: get_substs(mode="LRAM_CORE", kv=("OUT_REGMODE_A", x)), False, - desc="LRAM output pipeline register A enable") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.OUT_REGMODE_B".format(lram), ["NO_REG", "OUT_REG"], - lambda x: get_substs(mode="LRAM_CORE", kv=("OUT_REGMODE_B", x)), False, - desc="LRAM output pipeline register B enable") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.RESETMODE".format(lram), ["SYNC", "ASYNC"], - lambda x: get_substs(mode="LRAM_CORE", kv=("RESETMODE", x)), False, - desc="LRAM sync/async reset select") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.RST_AB_EN".format(lram), ["RESET_AB_DISABLE", "RESET_AB_ENABLE"], - lambda x: get_substs(mode="LRAM_CORE", kv=("RST_AB_EN", x)), False, - desc="LRAM reset A/B enable") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.SP_EN".format(lram), ["DISABLE", "ENABLE"], - lambda x: get_substs(mode="LRAM_CORE", kv=("SP_EN", x)), False, - desc="LRAM single port mode") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.UNALIGNED_READ".format(lram), ["DISABLE", "ENABLE"], - lambda x: get_substs(mode="LRAM_CORE", kv=("UNALIGNED_READ", x)), False, - desc="LRAM unaligned read support") - for port in ("CLK", "CSA", "CSB", "CEA", "CEB", "RSTA", "RSTB", "OCEA", "OCEB", "WEA", "WEB"): - nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}MUX".format(lram, port), [port, "INV"], - lambda x: get_substs(mode="LRAM_CORE", kv=(port, x), mux=True), False, - desc="LRAM {} inversion control".format(port)) + key = kv[0] + if key.endswith("MUX"): + key = ":" + key[:-3] + config = f"{mode}:::{key}={kv[1]}" + return dict(cmt="//" if mode == "NONE" else "", + config=config, + site=site) + + for setting in lram_core.settings: + subs_fn = lambda x,name=setting.name: get_substs(mode="LRAM_CORE", kv=(name, x)) + if setting.name == "MODE": + subs_fn = lambda x: get_substs(mode=x) + + mark_relative_to = None + if cfg.tiles[0] != cfg.tiles[-1]: + mark_relative_to = cfg.tiles[0] + nonrouting.fuzz_enum_setting(cfg, empty, f"{lram}.{setting.name}", setting.values, + subs_fn, + False, + desc=setting.desc, mark_relative_to=mark_relative_to) + + fuzzloops.parallel_foreach(configs, per_config) + if __name__ == "__main__": - main() + asyncio.run(main()) diff --git a/fuzzers/LIFCL/062-lram-config/lram.v b/fuzzers/LIFCL/062-lram-config/lram.v index d943424..fa80efc 100644 --- a/fuzzers/LIFCL/062-lram-config/lram.v +++ b/fuzzers/LIFCL/062-lram-config/lram.v @@ -1,5 +1,5 @@ -(* \db:architecture ="LIFCL", \db:device ="${device}", \db:package ="QFN72", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +(* \db:architecture ="LIFCL", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) module top ( ); diff --git a/fuzzers/LIFCL/063-lram-routing/fuzzer.py b/fuzzers/LIFCL/063-lram-routing/fuzzer.py index 6c480aa..84ca3d7 100644 --- a/fuzzers/LIFCL/063-lram-routing/fuzzer.py +++ b/fuzzers/LIFCL/063-lram-routing/fuzzer.py @@ -1,8 +1,26 @@ from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect + +import database import re -configs = [ +lifcl33_sites = { + 'R4C50':["CIB_R4C51:LRAM_0_33K", "CIB_R4C1:CIB_LR"], + 'R20C50':["CIB_R20C51:LRAM_1_33K", "CIB_R20C1:CIB_LR"], + 'R34C50':["CIB_R34C51:LRAM_2_33K", "CIB_R34C1:CIB_LR"] , + 'R47C50':["CIB_R47C51:LRAM_3_33K", "CIB_R47C1:CIB_LR"], + 'R65C50':["CIB_R65C51:LRAM_4_33K", "CIB_R65C1:CIB_LR"] +} + + +def create_config(site, tiles, device): + lram = tiles[0].split(":")[1].replace("_", "") + rc = site[1:].split("C") + return { "cfg": FuzzConfig(job=f"{lram}_{device}", device=f"LIFCL-{device}", sv=f"../shared/route_{device}.v", tiles=tiles), "rc": rc } + + +configs = [ create_config(site, tiles, "33") for (site, tiles) in lifcl33_sites.items() ] +\ +[ { "cfg": FuzzConfig(job="LRAMROUTE0", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R23C87:LRAM_0"]), "rc": (18, 86), @@ -34,7 +52,7 @@ }, ] -ignore_tiles = set([ +ignore_tiles_40 = set([ "CIB_R{}C86:CIB_LR".format(c) for c in range(2, 55) ] + [ "CIB_R19C86:CIB_LR_A", @@ -64,7 +82,15 @@ def main(): nodes = ["R{}C{}_*".format(r, c)] def nodename_filter(x, nodes): return ("R{}C{}_".format(r, c) in x) and ("LRAM_CORE" in x) - fuzz_interconnect(config=cfg, nodenames=nodes, nodename_predicate=nodename_filter, regex=True, bidir=True, ignore_tiles=ignore_tiles_17 if cfg.device == "LIFCL-17" else ignore_tiles) + + tg = database.get_tilegrid(cfg.device)["tiles"] + ignore_tiles = [tile for tile in tg if "CIB_LR" in tile or "CIB_T" in tile] + if cfg.device == "LIFCL-17": + ignore_tiles = ignore_tiles_17 + elif cfg.device == "LIFCL-40": + ignore_tiles = ignore_tiles_40 + + fuzz_interconnect(config=cfg, nodenames=nodes, nodename_predicate=nodename_filter, regex=True, bidir=True, ignore_tiles=ignore_tiles) if __name__ == "__main__": main() diff --git a/fuzzers/LIFCL/071-iodelay/iodelay.v b/fuzzers/LIFCL/071-iodelay/iodelay.v index 3e65137..eefcd74 100644 --- a/fuzzers/LIFCL/071-iodelay/iodelay.v +++ b/fuzzers/LIFCL/071-iodelay/iodelay.v @@ -1,5 +1,5 @@ -(* \db:architecture ="LIFCL", \db:device ="LIFCL-40", \db:package ="QFN72", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +(* \db:architecture ="LIFCL", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) module top ( ); diff --git a/fuzzers/LIFCL/091-osc/osc_33.v b/fuzzers/LIFCL/091-osc/osc_33.v new file mode 100644 index 0000000..9e701ff --- /dev/null +++ b/fuzzers/LIFCL/091-osc/osc_33.v @@ -0,0 +1,12 @@ + +(* \db:architecture ="LIFCL", \db:device ="LIFCL-33", \db:package ="WLCSP84", \db:speed ="8_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + ${cmt} (* \dm:primitive ="OSC_CORE", \dm:programming ="MODE:OSC_CORE ${config}", \dm:site ="OSC_CORE_R1C29" *) + ${cmt} OSC_CORE OSC_I ( ); + + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule diff --git a/fuzzers/LIFCL/091-osc/osc_pins.v b/fuzzers/LIFCL/091-osc/osc_pins.v new file mode 100644 index 0000000..29362a0 --- /dev/null +++ b/fuzzers/LIFCL/091-osc/osc_pins.v @@ -0,0 +1,16 @@ +(* \db:architecture ="LIFCL", \db:device ="LIFCL-33", \db:package ="WLCSP84", \db:speed ="8_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + + (* \xref:LOG ="q_c@0@9"${arcs_attr} *) + wire q; + + (* \dm:primitive ="OSC_CORE", \dm:programming ="MODE:OSC_CORE ${config}", \dm:site ="OSC_CORE_R1C29" *) + OSC_CORE OSC_I ( + .${pin_name}( q ) + ); + + (* \dm:cellmodel_primitives ="REG0=reg", \dm:primitive ="SLICE", \dm:programming ="MODE:LOGIC Q0:Q0 ", \dm:site ="R2C2A" *) + SLICE SLICE_I ( ${target} ); +endmodule diff --git a/fuzzers/LIFCL/100-ip-base/ip33.v b/fuzzers/LIFCL/100-ip-base/ip33.v new file mode 100644 index 0000000..3de2361 --- /dev/null +++ b/fuzzers/LIFCL/100-ip-base/ip33.v @@ -0,0 +1,11 @@ +(* \db:architecture ="LIFCL", \db:device ="${device}", \db:package ="WLCSP84", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + ${cmt} (* \dm:primitive ="${prim}", \dm:programming ="MODE:${prim} ${config}", \dm:site ="${site}" *) + ${cmt} ${prim} IP_I ( ); + + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule diff --git a/fuzzers/LIFCL/100-ip-base/ip_33u.v b/fuzzers/LIFCL/100-ip-base/ip_33u.v new file mode 100644 index 0000000..183c6af --- /dev/null +++ b/fuzzers/LIFCL/100-ip-base/ip_33u.v @@ -0,0 +1,12 @@ + +(* \db:architecture ="LIFCL", \db:device ="${device}", \db:package ="WLCSP84", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + ${cmt} (* \dm:primitive ="${prim}", \dm:programming ="MODE:${prim} ${config}", \dm:site ="${site}" *) + ${cmt} ${prim} IP_I ( ); + + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule diff --git a/fuzzers/LIFCL/110-global-structure/fuzzer.py b/fuzzers/LIFCL/110-global-structure/fuzzer.py index a93ec01..1fd0c40 100644 --- a/fuzzers/LIFCL/110-global-structure/fuzzer.py +++ b/fuzzers/LIFCL/110-global-structure/fuzzer.py @@ -1,18 +1,27 @@ import database import lapie import json -from fuzzconfig import FuzzConfig +from fuzzconfig import FuzzConfig, should_fuzz_platform from tiles import pos_from_name from os import path +import database +from collections import defaultdict +import tiles # name max_row max_col + configs = [ - ("LIFCL-40", 56, 87, "../shared/empty_40.v"), - ("LIFCL-17", 29, 75, "../shared/empty_17.v"), + ("LIFCL-33", "../shared/empty_33.v"), + ("LIFCL-33U", "../shared/empty_33u.v"), + ("LIFCL-40", "../shared/empty_40.v"), + ("LIFCL-17", "../shared/empty_17.v"), ] def main(): - for name, max_row, max_col, sv in configs: + for name, sv in configs: + if not should_fuzz_platform(name): + continue + cfg = FuzzConfig(job="GLOBAL_{}".format(name), device=name, sv=sv, tiles=[]) cfg.setup() db_path = path.join(database.get_db_root(), "LIFCL", name, "globals.json") @@ -26,11 +35,19 @@ def save_db(): with open(db_path, "w") as dbf: print(json.dumps(gdb, sort_keys=True, indent=4), file=dbf) gdb = load_db() + + devices = database.get_devices() + device_info = devices["families"][name.split("-")[0]]["devices"][name] + max_row = device_info["max_row"] + max_col = device_info["max_col"] + + tap_plcs = set([v['x'] for k, v in tg.items() if v["tiletype"].startswith("TAP_PLC")]) + # Determine branch driver locations test_row = 4 clock_wires = ["R{}C{}_JCLK0".format(test_row, c) for c in range(1, max_col)] clock_info = lapie.get_node_data(cfg.udb, clock_wires) - branch_to_col = {} + branch_to_col = defaultdict(list) for n in clock_info: r, c = pos_from_name(n.name) hpbx_c = None @@ -40,12 +57,11 @@ def save_db(): assert hpbx_r == r break assert hpbx_c is not None - if hpbx_c not in branch_to_col: - branch_to_col[hpbx_c] = [] branch_to_col[hpbx_c].append(c) branches = [] - branch_wires = ["R{}C{}_HPBX0000".format(test_row, bc) for bc in sorted(branch_to_col.keys())] + # Trace back the nodes which connect the tap to the spine + branch_wires = [f"R{test_row}C{bc}_HPBX0000" for bc in sorted(branch_to_col.keys())] if name == "LIFCL-17": branch_wires.append("R{}C13_RHPBX0000".format(test_row)) branch_wire_info = lapie.get_node_data(cfg.udb, branch_wires) @@ -69,10 +85,13 @@ def save_db(): for uh in bw.uphill_pips: if "HPBX0" in uh.from_wire: hpbx_r, hpbx_c = pos_from_name(uh.from_wire) - branch_driver_col[c] = branch_driver_col[hpbx_c] + branch_driver_col[c] = branch_driver_col[hpbx_c] for bc, scs in sorted(branch_to_col.items()): - tap_drv_col = branch_driver_col[bc] + 1 - side = "R" if tap_drv_col < bc else "L" + tap_drv_distances = [(abs(x - branch_driver_col[bc]), x) for x in tap_plcs] + tap_drv_col = min(tap_drv_distances)[1] + side = "R" if branch_driver_col[bc] < bc else "L" + if tap_drv_col in tap_plcs: + print("Tap drv col", tap_drv_col, bc, sorted(scs)) branches.append(dict(branch_col=bc, tap_driver_col=tap_drv_col, tap_side=side, from_col=min(scs), to_col=max(scs))) gdb["branches"] = branches save_db() diff --git a/fuzzers/LIFCL/900-always-on/fuzzer.py b/fuzzers/LIFCL/900-always-on/fuzzer.py index b2c997e..9046c73 100644 --- a/fuzzers/LIFCL/900-always-on/fuzzer.py +++ b/fuzzers/LIFCL/900-always-on/fuzzer.py @@ -8,6 +8,7 @@ cfgs = [ FuzzConfig(job="EMPTY", device="LIFCL-40", sv="../shared/empty_40.v", tiles=[]), FuzzConfig(job="EMPTY", device="LIFCL-17", sv="../shared/empty_17.v", tiles=[]), + FuzzConfig(job="EMPTY", device="LIFCL-33", sv="../shared/empty_33.v", tiles=[]), ] def main(): diff --git a/fuzzers/LIFCL/shared/empty_33.v b/fuzzers/LIFCL/shared/empty_33.v new file mode 100644 index 0000000..7527001 --- /dev/null +++ b/fuzzers/LIFCL/shared/empty_33.v @@ -0,0 +1,8 @@ +(* \db:architecture ="LIFCL", \db:device ="LIFCL-33", \db:package ="WLCSP84", \db:speed ="8_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule diff --git a/fuzzers/LIFCL/shared/empty_33u.v b/fuzzers/LIFCL/shared/empty_33u.v new file mode 100644 index 0000000..0820f98 --- /dev/null +++ b/fuzzers/LIFCL/shared/empty_33u.v @@ -0,0 +1,9 @@ + +(* \db:architecture ="LIFCL", \db:device ="LIFCL-33U", \db:package ="FCCSP104", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule diff --git a/fuzzers/LIFCL/shared/route_33.v b/fuzzers/LIFCL/shared/route_33.v new file mode 100644 index 0000000..121c458 --- /dev/null +++ b/fuzzers/LIFCL/shared/route_33.v @@ -0,0 +1,10 @@ +(* \db:architecture ="LIFCL", \db:device ="LIFCL-33", \db:package ="WLCSP84", \db:speed ="8_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + (* \xref:LOG ="q_c@0@9"${arcs_attr} *) + wire q; + + (* \dm:cellmodel_primitives ="REG0=reg", \dm:primitive ="SLICE", \dm:programming ="MODE:LOGIC Q0:Q0 ", \dm:site ="R2C2A" *) + SLICE SLICE_I ( .A0(q), .Q0(q) ); +endmodule diff --git a/generate_database.sh b/generate_database.sh new file mode 100755 index 0000000..ddcc43a --- /dev/null +++ b/generate_database.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +. user_environment.sh + +pushd tools +#python3 tilegrid_all.py +popd + +pushd fuzzers +for dir in LIFCL/* ; do + if [ -d "$dir" ]; then + echo "=================== Entering $dir ===================" + pushd $dir + python3 fuzzer.py 2>&1 | tee >(gzip --stdout > fuzzer.log.gz) || true + popd + fi +done \ No newline at end of file diff --git a/radiant.sh b/radiant.sh index ad95177..415b310 100755 --- a/radiant.sh +++ b/radiant.sh @@ -12,7 +12,7 @@ ld_lib_path_orig=$LD_LIBRARY_PATH export LD_LIBRARY_PATH="${bindir}:${fpgabindir}" export LM_LICENSE_FILE="${radiantdir}/license/license.dat" -set -ex +#set -ex V_SUB=${2%.v} PART=$1 @@ -37,6 +37,7 @@ case "${PART}" in PACKAGE="${DEV_PACKAGE:-FCCSP104}" DEVICE="LIFCL-33U" LSE_ARCH="lifcl" + EXTRA_BIT_ARGS="-ipeval" SPEED_GRADE="${SPEED_GRADE:-7_High-Performance_1.0V}" ;; LIFCL-40) @@ -116,7 +117,8 @@ fi if [ -n "$GEN_RBF" ]; then cp "$2.tmp"/par.rbt "$2.rbt" else -cp "$2.tmp"/par.bit "$2.bit" +cp -P "$2.tmp"/par.bit "$2.bit" 2> /dev/null || : +cp -P "$2.tmp"/par.bit.gz "$2.bit.gz" 2> /dev/null || : fi if [ -n "$DO_UNPACK" ]; then diff --git a/tools/bitstreamcache.py b/tools/bitstreamcache.py index 7dd7a88..e75bd20 100755 --- a/tools/bitstreamcache.py +++ b/tools/bitstreamcache.py @@ -22,6 +22,8 @@ """ import sys, os, shutil, hashlib, gzip +from logging import exception +from pathlib import Path root_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") cache_dir = os.path.join(root_dir, ".bitstreamcache") @@ -57,15 +59,24 @@ def get_hash(device, input_files): h = get_hash(sys.argv[2], sys.argv[4:]) print(h) cache_entry = os.path.join(cache_dir, h) - if not os.path.exists(cache_entry) or len(os.listdir(cache_entry)) == 0: + if not os.path.exists(cache_entry) or len(os.listdir(cache_entry)) < 2: sys.exit(1) + + # Touch the directory and it's contents + Path(cache_entry).touch() for outprod in os.listdir(cache_entry): bn = outprod assert bn.endswith(".gz") bn = bn[:-3] - with gzip.open(os.path.join(cache_entry, outprod), 'rb') as gzf: - with open(os.path.join(sys.argv[3], bn), 'wb') as outf: - outf.write(gzf.read()) + gz_path = os.path.join(cache_entry, outprod) + Path(gz_path).touch() + + if gz_path.endswith(".bit.gz"): + os.symlink(gz_path, os.path.join(sys.argv[3], outprod)) + else: + with gzip.open(gz_path, 'rb') as gzf: + with open(os.path.join(sys.argv[3], bn), 'wb') as outf: + outf.write(gzf.read()) sys.exit(0) if cmd == "commit": if not os.path.exists(cache_dir): @@ -81,6 +92,13 @@ def get_hash(device, input_files): for outprod in sys.argv[idx+1:]: bn = os.path.basename(outprod) cn = os.path.join(cache_entry, bn + ".gz") + + if not os.path.exists(outprod): + raise Exception(f"Output product does not exist") + + if os.path.getsize(outprod) == 0: + raise Exception(f"Output product has zero length; refusing to gzip {outprod}") + with gzip.open(cn, 'wb') as gzf: with open(outprod, 'rb') as inf: gzf.write(inf.read()) diff --git a/tools/extract_tilegrid.py b/tools/extract_tilegrid.py index ae7208c..6c95e07 100644 --- a/tools/extract_tilegrid.py +++ b/tools/extract_tilegrid.py @@ -63,11 +63,16 @@ def get_tf2c(dev): if dev == "LFCPNX-100": return tap_frame_to_col_100 - elif dev == "LIFCL-40" or dev == "LFDN2X-40": + elif dev == "LIFCL-40" or dev == "LFDN2X-40" or dev == "LFD2NX-40": return tap_frame_to_col_40 elif dev == "LIFCL-17": return tap_frame_to_col_17 + elif dev == "LIFCL-33": + return tap_frame_to_col_17 + elif dev == "LIFCL-33U": + return tap_frame_to_col_17 else: + print(f"Could not find dev {dev}") assert False def main(argv): @@ -75,12 +80,22 @@ def main(argv): tiles = {} current_tile = None tap_frame_to_col = get_tf2c(args.device) + + def fixup(tiletype): + if (args.device.find("-33") > 0): + tiletypes_with_variants = ["LRAM_", "SYSIO_B1_DED", "SPINE_"] + for v in tiletypes_with_variants: + if tiletype.startswith(v): + return tiletype + "_33K" + return tiletype + for line in args.infile: tile_m = tile_re.match(line) if tile_m: name = tile_m.group(6) + tiletype = fixup(tile_m.group(1)), current_tile = { - "tiletype": tile_m.group(1), + "tiletype": tiletype, "start_bit": int(tile_m.group(4)), "start_frame": int(tile_m.group(5)), "bits": int(tile_m.group(2)), @@ -99,7 +114,7 @@ def main(argv): else: current_tile["y"] = int(s.group(1)) current_tile["x"] = int(s.group(2)) - identifier = name + ":" + tile_m.group(1) + identifier = name + ":" + tiletype assert identifier not in tiles tiles[identifier] = current_tile json.dump({"tiles": tiles}, args.outfile, sort_keys=True, indent=4) diff --git a/util/common/database.py b/util/common/database.py index 749ab76..504374b 100644 --- a/util/common/database.py +++ b/util/common/database.py @@ -5,7 +5,12 @@ from os import path import json import subprocess +from pathlib import Path +import pyron as ron +import gzip +import sqlite3 +import lapie def get_oxide_root(): """Return the absolute path to the Project Oxide repo root""" @@ -40,12 +45,32 @@ def get_db_subdir(family = None, device = None, package = None): os.mkdir(subdir) return subdir - -def get_tilegrid(family, device): +_tilegrids = {} +def get_tilegrid(family, device = None): """ Return the deserialised tilegrid for a family, device """ - tgjson = path.join(get_db_subdir(family, device), "tilegrid.json") + if device is None: + device = family + family = device.split('-')[0] + + if device not in _tilegrids: + tgjson = path.join(get_db_subdir(family, device), "tilegrid.json") + if path.exists(tgjson): + with open(tgjson, "r") as f: + _tilegrids[device] = json.load(f) + else: + _tilegrids[device] = {"tiles":{}} + return _tilegrids[device] + +def get_iodb(family, device = None): + """ + Return the deserialised iodb for a family, device + """ + if device is None: + device = family + family = device.split('-')[0] + tgjson = path.join(get_db_subdir(family, device), "iodb.json") with open(tgjson, "r") as f: return json.load(f) @@ -58,6 +83,99 @@ def get_devices(): with open(djson, "r") as f: return json.load(f) +def get_tiletypes(family): + family = family.split("-")[0] + p = path.join(get_db_root(), family, "tiletypes") + + tiletypes = {} + + if path.exists(p): + for entry in Path(p).iterdir(): + if entry.name.endswith(".ron"): + with open(entry.absolute(), "r") as f: + tiletypes[entry.name.split(".")[0]] = ron.loads(f.read().replace("\\'", "'")) + + return tiletypes + def get_db_commit(): return subprocess.getoutput('git -C "{}" rev-parse HEAD'.format(get_db_root())) + +_sites = {} +def get_sites(family, device = None): + if device is None: + device = family + family = device.split('-')[0] + + site_file = path.join(get_db_subdir(family, device), "sites.json.gz") + if site_file not in _sites: + if not path.exists(site_file): + sites = lapie.get_sites_with_pin(device) + with gzip.open(site_file, 'wb') as f: + f.write(json.dumps(sites).encode('utf-8')) + + with gzip.open(site_file, 'r') as f: + _sites[site_file] = json.loads(f.read().decode('utf-8')) + return _sites[site_file] + + +def check_tiletype(tiletype, tiletype_info): + pips = tiletype_info["pips"] + enums = tiletype_info["enums"] + words = tiletype_info["words"] + + for to_pin in pips: + for from_pin in pips[to_pin]: + if "bits" not in from_pin: + wire = from_pin["from_wire"] + print(f"Warning: Unmapped pip {wire} -> {to_pin}") + + for enum in enums: + for option in enums[enum]["options"]: + if len(enums[enum]["options"][option]) == 0: + print(f"Warning unmapped option {option} in {enum}") + + for word in words: + idx = 0 + for bit in words[word]["bits"]: + if len(bit): + print(f"Warning word entry for value {idx} in {word}") + idx = idx + 1 + + + +def check_device(device): + tiletypes = get_tiletypes(device) + tg = get_tilegrid(device)["tiles"] + + warned = set() + + for tile, tile_info in tg.items(): + tiletype = tile_info["tiletype"] + + if tiletype not in tiletypes and tiletype not in warned: + warned.add(tiletype) + print(f"Warning: Could not find tile type definition for tiletype {tiletype} tile {tile} in {device}") + +def get_device_list(): + devices = get_devices() + + for family in devices["families"]: + for device in devices["families"][family]["devices"]: + yield device + + + +def check_consistency(): + devices = get_devices() + + for family in devices["families"]: + + tiletypes = get_tiletypes(family) + + for tiletype in tiletypes: + check_tiletype(tiletype, tiletypes[tiletype]) + + for device in devices["families"][family]["devices"]: + check_device(device) + diff --git a/util/common/lapie.py b/util/common/lapie.py index 116be1b..6100060 100644 --- a/util/common/lapie.py +++ b/util/common/lapie.py @@ -1,13 +1,17 @@ """ Python wrapper for `lapie` """ +import logging +import sys from os import path import os import subprocess import database import tempfile import re - +import hashlib +import shutil +import fuzzconfig # `lapie` seems to be renamed every version or so. Map that out here. Most installations will have # the version name at the end of their path, so we just look at the radiant dir for a hint. The user @@ -15,7 +19,8 @@ known_versions = [ "2.2", "3.1", "2023", "2024", "2025" ] RADIANT_DIR = os.environ.get("RADIANTDIR") radiant_version= os.environ.get("RADIANTVERSION", None) - +get_nodes = "dev_get_nodes" + if radiant_version is None: for version in known_versions: if RADIANT_DIR.find(version) > -1: @@ -39,8 +44,11 @@ tcltool = "lapie" tcltool_log = "lapie.log" dev_enable_name = "LATCL_DEV_ENABLE" + get_nodes = "get_nodes" + +def run(commands, workdir=None, stdout=None): + from radiant import run_bash_script -def run(commands, workdir=None): """Run a list of Tcl commands, returning the output as a string""" rcmd_path = path.join(database.get_oxide_root(), "radiant_cmd.sh") if workdir is None: @@ -51,9 +59,11 @@ def run(commands, workdir=None): f.write(c + '\n') env = os.environ.copy() env[dev_enable_name] = "1" - result = subprocess.run(["bash", rcmd_path, tcltool, scriptfile], cwd=workdir, env=env).returncode - # meh, fails sometimes - # assert result == 0, "lapie returned non-zero status code {}".format(result) + + result_struct = run_bash_script(env, rcmd_path, tcltool, scriptfile, cwd=workdir) + + result = result_struct.returncode + outfile = path.join(workdir, tcltool_log) output = "" with open(outfile, 'r') as f: @@ -69,8 +79,8 @@ def run(commands, workdir=None): output = output[:output.find(pleasantry)].strip() return output -def run_with_udb(udb, commands): - return run(['des_read_udb "{}"'.format(path.abspath(udb))] + commands) +def run_with_udb(udb, commands, stdout = None): + return run(['des_read_udb "{}"'.format(path.abspath(udb))] + commands, stdout = stdout) class PipInfo: def __init__(self, from_wire, to_wire, is_bidi = False, flags = 0, buffertype = ""): @@ -79,6 +89,9 @@ def __init__(self, from_wire, to_wire, is_bidi = False, flags = 0, buffertype = self.flags = flags self.buffertype = buffertype self.is_bidi = is_bidi + + def __repr__(self): + return str((self.from_wire, self.to_wire, self.flags, self.buffertype, self.is_bidi)) class PinInfo: def __init__(self, site, pin, wire, pindir): @@ -90,36 +103,66 @@ def __init__(self, site, pin, wire, pindir): class NodeInfo: def __init__(self, name): self.name = name + self.aliases = [] self.nodetype = None self.uphill_pips = [] self.downhill_pips = [] self.pins = [] - + + def pips(self): + return self.uphill_pips + self.downhill_pips + node_re = re.compile(r'^\[\s*\d+\]\s*([A-Z0-9a-z_]+)') alias_node_re = re.compile(r'^\s*Alias name = ([A-Z0-9a-z_]+)') pip_re = re.compile(r'^([A-Z0-9a-z_]+) (<--|<->|-->) ([A-Z0-9a-z_]+) \(Flags: .+, (\d+)\) \(Buffer: ([A-Z0-9a-z_]+)\)') #R1C77_JLFTRMFAB7_OSC_CORE <-- R1C75_JCIBMUXOUTA7 (Flags: ----j, 0) (Buffer: b_ciboutbuf) pin_re = re.compile(r'^Pin : ([A-Z0-9a-z_]+)/([A-Z0-9a-z_]+) \(([A-Z0-9a-z_]+)\)') -def parse_node_report(rpt): +# Parsing is weird here since the format of the report can vary somewhat. +# Pre 2023; there were no aliases listed and the nodes returned were numbered. Post 2023, each node +# can have a lot of aliases and the only clear indication of which name is normative is its the one +# used in the connections. + +def parse_node_report(rpt): curr_node = None + nodes_dict = {} nodes = [] + reset_curr_node = True + + def get_node(name): + if name in nodes_dict: + n = nodes_dict[name] + n.name = name + return n + + nodes_dict[name] = NodeInfo(name) + nodes.append(nodes_dict[name]) + return nodes_dict[name] + for line in rpt.split('\n'): sl = line.strip() - print("Node line", sl) - for re in [node_re, alias_node_re]: - nm = re.match(sl) - if nm: - curr_node = NodeInfo(nm.group(1)) - print(f"Curr node {nm.group(1)}") - nodes.append(curr_node) - continue - + + name_match = [nm.group(1) for nm in [re.match(sl) for re in [node_re, alias_node_re]] if nm is not None] + + if len(name_match): + new_name = name_match[0] + if reset_curr_node: + curr_node = get_node(new_name) + reset_curr_node = False + curr_node.aliases.append(new_name) + continue + + # If we get back into an alias section, we are onto a new node + reset_curr_node = True + pm = pip_re.match(sl) if pm: + # Name the node according to what things call it + curr_node.name = pm.group(1) + flg = int(pm.group(4)) btyp = pm.group(5) - print(f"Found connection {pm}") + #print(f"Found connection {pm}") if pm.group(2) == "<--": curr_node.uphill_pips.append( PipInfo(pm.group(3), pm.group(1), False, flg, btyp) @@ -139,28 +182,159 @@ def parse_node_report(rpt): assert False continue qm = pin_re.match(sl) - print("Match", qm, curr_node) + #print("Match", qm, curr_node) if qm and curr_node: curr_node.pins.append( PinInfo(qm.group(1), qm.group(2), curr_node.name, qm.group(3)) ) - print([x.name for x in nodes]) + #print([x.name for x in nodes]) return nodes +def parse_sites(rpt): + past_preamble = False + sites = [] + for line in rpt.split('\n'): + sl = line.strip() + + if not past_preamble: + past_preamble = "Successfully loading udb" in sl + continue + + if "--------------------" in sl: + break + + if len(sl): + sites.append(sl) + + return sites + +def get_full_node_list(udb): + workdir = f"/tmp/prjoxide_node_data/{udb}" + nodefile = path.join(workdir, "full_nodes.txt") + os.makedirs(workdir, exist_ok=True) + + if not os.path.exists(nodefile): + if not udb.endswith(".udb"): + config = fuzzconfig.FuzzConfig(udb, "extract-site-info", []) + config.setup() + udb = config.udb + run_with_udb(udb, [f'dev_list_node_by_name -file {nodefile}']) + with open(nodefile, 'r') as nf: + return [line.split(":")[-1].strip() for line in nf.read().split("\n")] def get_node_data(udb, nodes, regex=False): workdir = tempfile.mkdtemp() nodefile = path.join(workdir, "nodes.txt") nodelist = "" - if len(nodes) == 1: + + if not isinstance(nodes, list): + nodelist = nodes + nodes = [nodes] + elif len(nodes) == 1: nodelist = nodes[0] elif len(nodes) > 1: + nodes = sorted(set(nodes)) nodelist = "[list {}]".format(" ".join(nodes)) - run_with_udb(udb, ['dev_report_node -file {} [dev_get_nodes {}{}]'. - format(nodefile, "-re " if regex else "", nodelist)]) + + logging.info(f"Querying for {len(nodes)} nodes {nodes[:10]}") + key_input = "\n".join([radiant_version, udb, f"regex: {regex}", ''] + nodes) + key = hashlib.md5(key_input.encode('utf-8')).hexdigest() + key_path = f"/tmp/prjoxide_node_data/{key}" + os.makedirs("/tmp/prjoxide_node_data", exist_ok=True) + + if os.path.exists(key_path): + #print(f"Nodefile found at {key_path}") + shutil.copyfile(key_path, nodefile) + else: + if not udb.endswith(".udb"): + device = udb + udb = f"/tmp/prjoxide_node_data/{device}.udb" + if not os.path.exists(udb): + config = fuzzconfig.FuzzConfig(device, f"extract-site-info-{device}", []) + config.setup() + shutil.copyfile(config.udb, udb) + + re_slug = "-re " if regex else "" + run_with_udb(udb, [f'dev_report_node -file {nodefile} [{get_nodes} {re_slug}{nodelist}]'], stdout = subprocess.DEVNULL) + shutil.copyfile(nodefile, key_path) + with open(key_path + ".input", 'w') as f: + f.write(key_input) + #print(f"Nodefile cached at {key_path}") + with open(nodefile, 'r') as nf: return parse_node_report(nf.read()) +def get_sites(udb, rc = None): + if not udb.endswith(".udb"): + config = fuzzconfig.FuzzConfig(udb, "extract-site-info", []) + config.setup() + udb = config.udb + + rc_slug = "" + if rc is not None: + rc_slug = f"-row {rc[0]} -column {rc[1]}" + rpt = run_with_udb(udb, [f'dev_list_site {rc_slug}'], stdout = subprocess.DEVNULL) + + return parse_sites(rpt) + +def parse_report_site(rpt): + site_re = re.compile( + r'^Site=(?P\S+)\s+' + r'id=(?P\d+)\s+' + r'type=(?P\S+)\s+' + r'X=(?P-?\d+)\s+' + r'Y=(?P-?\d+)$' + ) + + pin_re = re.compile( + r'^\s*Pin\s+id\s*=\s*(?P\d+)\s+' + r'pin\s+name\s*=\s*(?P\S+)\s+' + r'pin\s+node\s+name\s*=\s*(?P\S+)$' + ) + + past_preamble = False + sites = {} + current_site = None + + for line in rpt.split('\n'): + sl = line.strip() + + if not past_preamble: + past_preamble = "Successfully loading udb" in sl + continue + + if "--------------------" in sl: + break + + m = site_re.match(line) + if m: + current_site = m.groupdict() + current_site["pins"] = [] + sites[current_site["site_name"]] = current_site + del current_site["site_name"] + + m = pin_re.match(line) + if m: + pins = m.groupdict() + del pins["pin_id"] + current_site["pins"].append(pins) + + return sites + +def get_sites_with_pin(udb, rc = None): + if not udb.endswith(".udb"): + config = fuzzconfig.FuzzConfig(udb, "extract-site-info", []) + config.setup() + udb = config.udb + + rc_slug = "" + if rc is not None: + rc_slug = f"-row {rc[0]} -column {rc[1]}" + rpt = run_with_udb(udb, [f'dev_report_site {rc_slug}']) + + return parse_report_site(rpt) + + def list_nets(udb): # des_list_net no longer works? output = run_with_udb(udb, ['des_report_instance']) diff --git a/util/common/radiant.py b/util/common/radiant.py index 42a5e82..2a94cc3 100644 --- a/util/common/radiant.py +++ b/util/common/radiant.py @@ -1,16 +1,46 @@ """ Python wrapper for `radiant.sh` """ +import asyncio +import logging from os import path import os import subprocess import database +import sys +def run_bash_script(env, *args, cwd = None): + slug = " ".join(args[1:]) + logging.info("Running script: %s", slug) + + proc = subprocess.run( + args=["bash", *args], + env=env, + cwd=cwd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + + stdout, stderr = proc.stdout, proc.stderr + + returncode = proc.returncode + show_output = returncode != 0 + + if show_output or True: + for stream in [("", stdout, sys.stdout), ("ERR:", stderr, sys.stdout)]: + for l in stream[1].decode().splitlines(): + print(f"[{stream[0]} {slug}] {l}", file=stream[2]) + + if returncode != 0: + raise Exception(f"Error encountered running radiant: {slug}") + + return proc def run(device, source, struct_ver=True, raw_bit=False, pdcfile=None, rbk_mode=False): """ Run radiant.sh with a given device name and source Verilog file """ + env = os.environ.copy() if struct_ver: env["STRUCT_VER"] = "1" @@ -18,5 +48,7 @@ def run(device, source, struct_ver=True, raw_bit=False, pdcfile=None, rbk_mode=F env["GEN_RBT"] = "1" if rbk_mode: env["RBK_MODE"] = "1" + dsh_path = path.join(database.get_oxide_root(), "radiant.sh") - return subprocess.run(["bash",dsh_path,device,source], env=env) + + return run_bash_script(env, dsh_path, device, source) diff --git a/util/common/tiles.py b/util/common/tiles.py index 2d8890a..df4a572 100644 --- a/util/common/tiles.py +++ b/util/common/tiles.py @@ -1,4 +1,7 @@ import re +import database +from collections import defaultdict +import lapie pos_re = re.compile(r'R(\d+)C(\d+)') @@ -11,9 +14,279 @@ def pos_from_name(tile): assert s return int(s.group(1)), int(s.group(2)) - def type_from_fullname(tile): """ Extract the type from a full tile name (in name:type) format """ return tile.split(":")[1] + +def get_rc_from_edge(device, side, offset): + devices = database.get_devices() + device_info = devices["families"][device.split("-")[0]]["devices"][device] + + max_row = device_info["max_row"] + max_col = device_info["max_col"] + + if side == "T": + return (0, int(offset)) + elif side == "B": + return (int(max_row), int(offset)) + elif side == "R": + return (int(offset), int(max_col)) + elif side == "L": + return (int(offset), 0) + + assert False, f"Could not match IO with side as side {side} offset {offset}" + +def get_tiles_from_edge(device, side, offset = -1): + (r, c) = get_rc_from_edge(device, side, offset) + tg = database.get_tilegrid(device)["tiles"] + + return [t for t, tinfo in tg.items() if (c == -1 or tinfo["x"] == c) and (r == -1 or tinfo["y"] == r)] + +def get_sites_from_primitive(device, primitive): + sites = database.get_sites(device) + return {k:s for (k,s) in sites.items() if s['type'] == primitive} + + +def get_tiletypes(device): + tilegrid = database.get_tilegrid(device)['tiles'] + tiletypes = defaultdict(list) + for (k,v) in tilegrid.items(): + tiletypes[k.split(":")[-1]].append(k) + return tiletypes + +def get_tiles_by_filter(device, fn): + tilegrid = database.get_tilegrid(device)['tiles'] + + return {k:v for k,v in tilegrid.items() if fn(k, v)} + + +def get_tiles_by_tiletype(device, tiletype): + tilegrid = database.get_tilegrid(device)['tiles'] + + return {k:v for k,v in tilegrid.items() if k.split(":")[-1] == tiletype} + +def get_tiles_by_primitive(device, primitive): + tilegrid = database.get_tilegrid(device)['tiles'] + + rc_regex = re.compile("R([0-9]*)C([0-9]*)") + edge_regex = re.compile("IOL_(.)([0-9]*)") + sites = get_sites_from_primitive(device, primitive) + + tg_by_rc = { (t['y'], t['x']):(k, t) for (k, t) in tilegrid.items() } + + rcs = {} + for (a,v) in sites.items(): + rc = get_rc_from_name(device, a) + + (name, t) = tg_by_rc[rc] + rcs[(a,name)] = t + + return rcs + +def get_tiletypes_by_primitive(device, primitive): + tiles = get_tiles_by_primitive(device, primitive) + + rtn = defaultdict(list) + for ((site,tilename),v) in tiles.items(): + tiletype = tilename.split(":")[1] + rtn[tiletype].append((site, tilename, v)) + return rtn + +def get_sites_for_tile(device, tile): + tilegrid = database.get_tilegrid(device)['tiles'] + tile = [v for (k,v) in tilegrid.items() if k.startswith(tile) ][0] + + sites = database.get_sites(device) + + RC = (tile["y"], tile["x"]) + + return {k:v for k,v in sites.items() if RC == get_rc_from_name(device, k)} + +_node_list_lookup = {} +_node_owned_lookup = {} + +_spine_regex = re.compile("(.)([0-9][0-9])(.)([0-9][0-9])([0-9][0-9])") + +_full_node_set = {} +def get_full_node_set(device): + if device not in _full_node_set: + all_nodes = lapie.get_full_node_list(device) + _full_node_set[device] = set(all_nodes) + return _full_node_set[device] + + +def get_nodes_for_tile(device, tile, owned = False): + if device not in _node_list_lookup: + all_nodes = lapie.get_full_node_list(device) + _node_list_lookup[device] = defaultdict(list) + _node_owned_lookup[device] = defaultdict(list) + for name in all_nodes: + rc = get_rc_from_name(device, name) + + if rc is None: + continue + elif rc[0] < 0 or rc[1] < 0: + print(f"Nodename {name} has negative rc: {rc}") + name_no_rc = "_".join(name.split("_")[1:]) + m = _spine_regex.match(name_no_rc) + if m is not None: + (r,c) = rc + orientation = m.group(1) + size = int(m.group(2)) + direction = m.group(3) + track = int(m.group(4)) + segment = int(m.group(5)) + + if size == 0: + continue + + assert(orientation in ["H", "V"]) + assert(direction in ["N","E","W","S"]) + + (dir_x, dir_y) = (0, 0) + if direction == "N": + dir_y = -1 + elif direction == "S": + dir_y = 1 + elif direction == "E": + dir_x = 1 + else: + dir_x = -1 + + rs = r - dir_y * segment + cs = c - dir_x * segment + + for i in range(0, size + 1): + ro = rs + dir_y * i + co = cs + dir_x * i + alias_name = f"R{ro}C{co}{orientation}{size:02}{direction}{track:02}{i:02}" + #_node_list_lookup[device][ro, co].append(name) + if i == 0: + _node_owned_lookup[device][rc].append(name) + else: + _node_list_lookup[device][rc].append(name) + _node_owned_lookup[device][rc].append(name) + + def get_node_list_for_tile(t): + return (_node_owned_lookup if owned else _node_list_lookup)[device].get(get_rc_from_name(device, t), []) + + if isinstance(tile, list): + nodes2tile = {n:t for t in tile for n in get_node_list_for_tile(t)} + node_info = {n.name:n for n in lapie.get_node_data(device, list(nodes2tile.keys()), False)} + + tile_nodes = defaultdict(dict) + for n, nifo in node_info.items(): + tile_nodes[nodes2tile[n]][n.name] = ninfo + + return tile_nodes + else: + tile_nodes = get_node_list_for_tile(tile) + if len(tile_nodes) == 0: + return {} + + return {n.name:n for n in lapie.get_node_data(device, tile_nodes, False)} + + +def get_tiles_by_rc(device, rc): + if isinstance(rc, str): + rc = get_rc_from_name(device, rc) + + tilegrid = database.get_tilegrid(device)['tiles'] + return {k:v for k,v in tilegrid.items() if (v['y'], v['x']) == rc} + + + +def get_tile_routes(device, tilename, owned = False): + node_data = get_nodes_for_tile(device, tilename, owned = owned) + + return node_data + +rc_regex = re.compile("R([0-9]+)C([0-9]+)") +edge_regex = re.compile("IOL_(.)([0-9]+)") +def get_rc_from_name(device, name): + if isinstance(name, tuple): + return name + + m = rc_regex.search(name) + if m: + return (int(m.group(1)), int(m.group(2))) + + m = edge_regex.search(name) + if m: + return get_rc_from_edge(device, m.group(1), m.group(2)) + + return None + +def get_tile_from_node(device, node): + rc = get_rc_from_name(device, node) + tilegrid = database.get_tilegrid(device)['tiles'] + + for k,v in tilegrid.items(): + if (v['y'], v['x']) == rc: + return k + +def get_connected_nodes(device, tilename): + routes = get_tile_routes(device, tilename) + + def tile_route(route): + return list(set([ + wire + for (n,r) in route.items() + for p in r.pips() + for wire in [p.from_wire, p.to_wire] + ])) + + + if isinstance(tilename, list): + return {t:tile_route(route) for t,route in routes.items()} + + print(routes) + return tile_route(routes) + + +def get_pins_for_site(device, site): + sites = database.get_sites(device) + site_info = sites[site] + + nodes = {n.name:n for n in lapie.get_node_data(device, [p['pin_node'] for p in site_info['pins']])} + + return [(p, nodes[p['pin_node']]) for p in site_info['pins']] + +def get_pips_for_tile(device, tilename, owned = False, dir = None): + assert(dir is None or dir == "uphill" or dir == "downhill") + + def pips(r): + if dir is None: + return r.pips() + elif dir == "uphill": + return r.uphill_pips + elif dir == "downhill": + return r.downhill_pips + + routes = get_tile_routes(device, tilename, owned = owned) + return list(set([ + (p.from_wire, + p.to_wire) + for (n,r) in routes.items() + for p in pips() + ])) + +def get_connected_tiles(device, tilename): + connected_nodes = get_connected_nodes(device, tilename) + + tilegrid = database.get_tilegrid(device)['tiles'] + + rcs = set([get_rc_from_name(device, n) for n in connected_nodes]) + + return { k:v for k,v in tilegrid.items() if (v['y'], v['x']) in rcs } + +def draw_rc(rcs): + rcs = set(rcs) + for y in range(0, 55): + for x in range(0, 83): + print("■" if (x,y) in rcs else " " , end='') + print() + + diff --git a/util/fuzz/fuzzconfig.py b/util/fuzz/fuzzconfig.py index f294ba5..7421912 100644 --- a/util/fuzz/fuzzconfig.py +++ b/util/fuzz/fuzzconfig.py @@ -1,6 +1,7 @@ """ This module provides a structure to define the fuzz environment """ +import logging import os from os import path from string import Template @@ -10,8 +11,26 @@ db = None +def get_db(): + global db + if db is None: + db = libpyprjoxide.Database(database.get_db_root()) + return db + +PLATFORM_FILTER = os.environ.get("FUZZER_PLATFORM", None) + +_platform_skip_warnings = set() +def should_fuzz_platform(device): + if PLATFORM_FILTER is not None and PLATFORM_FILTER != device: + if device not in _platform_skip_warnings: + print(f"FUZZER_PLATFORM set to {PLATFORM_FILTER}, skipping {device}") + _platform_skip_warnings.add(device) + return False + return True + + class FuzzConfig: - def __init__(self, device, job, tiles, sv): + def __init__(self, device, job, tiles, sv = None): """ :param job: user-friendly job name, used for folder naming etc :param device: Target device name @@ -21,6 +40,10 @@ def __init__(self, device, job, tiles, sv): self.device = device self.job = job self.tiles = tiles + if sv is None: + family = device.split("-")[0] + suffix = device.split("-")[1] + sv = database.get_db_root() + f"/../fuzzers/{family}/shared/empty_{suffix}.v" self.sv = sv self.rbk_mode = True if self.device == "LFCPNX-100" else False self.struct_mode = True @@ -34,6 +57,24 @@ def make_workdir(self): """Create the working directory for this job, if it doesn't exist already""" os.makedirs(self.workdir, exist_ok=True) + def serialize_deltas(self, fz, prefix = ""): + os.makedirs(".deltas", exist_ok=True) + fz.serialize_deltas(f".deltas/{prefix}{self.job}_{self.device}") + + def check_deltas(self, name): + if os.path.exists(f"./.deltas/{name}{self.job}_{self.device}.ron"): + print(f"Delta exists for {name} {self.job} {self.device}; skipping") + return True + print(f"./.deltas/{name}{self.job}_{self.device}.ron miss") + return False + + def solve(self, fz): + try: + fz.solve(db) + self.serialize_deltas(fz, fz.get_name()) + except: + self.serialize_deltas(fz, f"FAILED-{fz.get_name()}") + def setup(self, skip_specimen=False): """ Create a working directory, and run Radiant on a minimal Verilog file to create a udb for Tcl usage etc @@ -48,7 +89,16 @@ def setup(self, skip_specimen=False): if not skip_specimen: self.build_design(self.sv, {}) - def build_design(self, des_template, substitutions, prefix="", substitute=True): + def subst_defaults(self): + return { + "arch": "LIFCL", + "arcs_attr": "", + "device": self.device, + "package": "WLCSP84" if self.device.startswith("LIFCL-33") else "QFN72", + "speed_grade": "8" if self.device == "LIFCL-33" else "7" + } + + def build_design(self, des_template, substitutions = {}, prefix="", substitute=True): """ Run Radiant on a given design template, applying a map of substitutions, plus some standard substitutions if not overriden. @@ -60,12 +110,21 @@ def build_design(self, des_template, substitutions, prefix="", substitute=True): Returns the path to the output bitstream """ subst = dict(substitutions) - if "arcs_attr" not in subst: - subst["arcs_attr"] = "" - if "device" not in subst: - subst["device"] = self.device + + subst_defaults = { + "arch": self.device.split("-")[0], + "arcs_attr": "", + "device": self.device, + "package": "WLCSP84" if self.device.startswith("LIFCL-33") else "QFN72", + "speed_grade": "8" if self.device == "LIFCL-33" else "7" + } + + subst = subst_defaults | subst + + os.makedirs(path.join(self.workdir, prefix), exist_ok=True) desfile = path.join(self.workdir, prefix + "design.v") bitfile = path.join(self.workdir, prefix + "design.bit") + bitfile_gz = path.join(self.workdir, prefix + "design.bit.gz") if "sysconfig" in subst: pdcfile = path.join(self.workdir, prefix + "design.pdc") @@ -83,13 +142,19 @@ def build_design(self, des_template, substitutions, prefix="", substitute=True): radiant.run(self.device, desfile, struct_ver=self.struct_mode, raw_bit=False, rbk_mode=self.rbk_mode) if self.struct_mode and self.udb_specimen is None: self.udb_specimen = path.join(self.workdir, prefix + "design.tmp", "par.udb") - return bitfile + if path.exists(bitfile): + return bitfile + if path.exists(bitfile_gz): + return bitfile_gz + raise Exception(f"Could not generate bitstream file {bitfile} {bitfile_gz}") @property def udb(self): """ A udb file specimen for Tcl """ + if self.udb_specimen is None: + self.setup() assert self.udb_specimen is not None return self.udb_specimen diff --git a/util/fuzz/fuzzloops.py b/util/fuzz/fuzzloops.py index dfbd99e..6e4d172 100644 --- a/util/fuzz/fuzzloops.py +++ b/util/fuzz/fuzzloops.py @@ -1,9 +1,10 @@ """ General Utilities for Fuzzing """ - +import asyncio import os from threading import Thread, RLock +import traceback def parallel_foreach(items, func): """ @@ -18,17 +19,34 @@ def parallel_foreach(items, func): items_queue = list(items) items_lock = RLock() + exception = None + print(f"Starting loop with {exception} jobs") + def runner(): - while True: + nonlocal exception + + try: + while True: + with items_lock: + if len(items_queue) == 0: + return + item = items_queue[0] + items_queue.pop(0) + print(f"{len(items_queue)} jobs remaining") + + func(item) + except Exception as e: + print(f"Error: {e}") + traceback.print_exc() + + exception = e with items_lock: - if len(items_queue) == 0: - return - item = items_queue[0] - items_queue.pop(0) - func(item) + items_queue.clear() threads = [Thread(target=runner) for i in range(jobs)] for t in threads: t.start() for t in threads: t.join() + if exception is not None: + raise exception diff --git a/util/fuzz/interconnect.py b/util/fuzz/interconnect.py index 5646e3d..0192e93 100644 --- a/util/fuzz/interconnect.py +++ b/util/fuzz/interconnect.py @@ -8,7 +8,160 @@ import fuzzconfig import fuzzloops import lapie +import database +import os +import math +import tempfile +from os import path +import heapq +import bisect +import re +from collections import defaultdict + +workdir = tempfile.mkdtemp() + +def create_wires_file(config, wires, prefix = "", empty_version = False): + if empty_version: + prefix = prefix + "_empty" + + if isinstance(wires, list): + + touched_tiles = set([tiles.get_rc_from_name(config.device, n) for w in wires for n in w]) + + slice_sites = tiles.get_tiles_by_tiletype(config.device, "PLC") + slice_iter = iter([x for x in slice_sites if tiles.get_rc_from_name(config.device, x) not in touched_tiles]) + + + if empty_version: + wires = "\n".join([f""" +wire q_{idx}; +(* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) +SLICE SLICE_I_{idx} ( .A0(q_{idx}), .Q0(q_{idx}) ); + """ for idx, (frm, to) in enumerate(sorted(wires))]) + else: + wires = "\n".join([f""" +(* keep = "true", dont_touch = "true", keep, dont_touch,\\xref:LOG ="q_c@0@0", \\dm:arcs ="{to}.{frm}" *) +wire q_{idx}; + +(* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) +SLICE SLICE_I_{idx} ( .A0(q_{idx}), .Q0(q_{idx}) ); + + """ for idx, (frm, to) in enumerate(sorted(wires))]) + + subst = config.subst_defaults() + arch = config.device.split("-")[0] + device = config.device + package = subst["package"] + speed_grade = subst["speed_grade"] + + source = f"""\ +(* \\db:architecture ="{arch}", \\db:device ="{device}", \\db:package ="{package}", \\db:speed ="{speed_grade}_High-Performance_1.0V", \\db:timestamp = 0, \\db:view ="physical" *) +module top ( +); +{wires} + (* \\xref:LOG ="q_c@0@0" *) + VHI vhi_i(); +endmodule + """ + + vfile = path.join(workdir, f"{prefix}.v") + with open(vfile, 'w') as f: + f.write(source) + return config.build_design(vfile, prefix = prefix) + +def pips_to_sinks(pips): + sinks = {} + + for from_wire, to_wire in pips: + if to_wire not in sinks: + sinks[to_wire] = [] + sinks[to_wire].append(from_wire) + + for k in sinks: + sinks[k] = sorted(sinks[k]) + + return sinks + +def collect_sinks(config, nodenames, regex = False, + nodename_predicate=lambda x, nets: True, + pip_predicate=lambda x, nets: True, + bidir=False, + nodename_filter_union=False, + ): + if regex: + all_nodes = lapie.get_full_node_list(config.device) + regex = [re.compile(n) for n in nodenames] + print(regex, nodenames) + nodenames = [n for n in all_nodes if any([r for r in regex if r.search(n) is not None])] + regex = False + + nodes = lapie.get_node_data(config.udb, nodenames, regex) + + print(regex, {n.name:n.aliases for n in nodes}) + + all_wirenames = set([n.name for n in nodes]) + all_pips = set() + for node in nodes: + for p in node.uphill_pips: + all_pips.add((p.from_wire, p.to_wire)) + if bidir: + for p in node.downhill_pips: + all_pips.add((p.from_wire, p.to_wire)) + per_sink = list(sorted(all_pips)) + + # First filter using netname predicate + if nodename_filter_union: + all_pips = filter(lambda x: nodename_predicate(x[0], all_wirenames) and nodename_predicate(x[1], all_wirenames), + all_pips) + else: + all_pips = filter(lambda x: nodename_predicate(x[0], all_wirenames) or nodename_predicate(x[1], all_wirenames), + all_pips) + # Then filter using the pip predicate + fuzz_pips = list(filter(lambda x: pip_predicate(x, all_wirenames), all_pips)) + if len(fuzz_pips) == 0: + print(f"No fuzz_pips defined for job {config}. Nodes: {nodes} {all_pips}") + return + + print(fuzz_pips) + return pips_to_sinks(fuzz_pips) + +def fuzz_interconnect_sinks( + config, + sinks, + full_mux_style=False, + ignore_tiles=set(), + extra_substs={}, + fc_filter=lambda x: True + ): + if sinks is None: + return + + if not isinstance(sinks, dict): + sinks = pips_to_sinks(sinks) + + base_bitf = config.build_design(config.sv, extra_substs, "base_") + + def per_sink(to_wire): + if config.check_deltas(to_wire): + return + + # Get a unique prefix from the thread ID + prefix = "thread{}_{}_{}_{}_".format(threading.get_ident(), config.job, config.device, to_wire) + print(config.tiles) + fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.db, base_bitf, set(config.tiles), to_wire, config.tiles[0], set(ignore_tiles), full_mux_style, not (fc_filter(to_wire))) + for from_wire in sinks[to_wire]: + arcs_attr = r', \dm:arcs ="{}.{}"'.format(to_wire, from_wire) + substs = extra_substs.copy() + substs["arcs_attr"] = arcs_attr + print(f"Building design for ({config.job} {config.device}) {to_wire} to {from_wire}") + arc_bit = config.build_design(config.sv, substs, prefix) + fz.add_pip_sample(fuzzconfig.db, from_wire, arc_bit) + + config.solve(fz) + + fuzzloops.parallel_foreach(list(sorted(sinks.keys())), per_sink) + def fuzz_interconnect( config, nodenames, @@ -42,43 +195,65 @@ def fuzz_interconnect( :param extra_substs: extra SV substitutions :param fc_filter: skip fixed connections if this returns false for a sink wire name """ - nodes = lapie.get_node_data(config.udb, nodenames, regex) - base_bitf = config.build_design(config.sv, extra_substs, "base_") - - all_wirenames = set([n.name for n in nodes]) - all_pips = set() - for node in nodes: - for p in node.uphill_pips: - all_pips.add((p.from_wire, p.to_wire)) - if bidir: - for p in node.downhill_pips: - all_pips.add((p.from_wire, p.to_wire)) - per_sink = list(sorted(all_pips)) - # First filter using netname predicate - if nodename_filter_union: - all_pips = filter(lambda x: nodename_predicate(x[0], all_wirenames) and nodename_predicate(x[1], all_wirenames), - all_pips) - else: - all_pips = filter(lambda x: nodename_predicate(x[0], all_wirenames) or nodename_predicate(x[1], all_wirenames), - all_pips) - # Then filter using the pip predicate - fuzz_pips = list(filter(lambda x: pip_predicate(x, all_wirenames), all_pips)) - if len(fuzz_pips) == 0: + if not fuzzconfig.should_fuzz_platform(config.device): return - sinks = {} - for from_wire, to_wire in fuzz_pips: - if to_wire not in sinks: - sinks[to_wire] = [] - sinks[to_wire].append(from_wire) - def per_sink(to_wire): + + sinks = collect_sinks(config, nodenames, regex = regex, + nodename_predicate = nodename_predicate, + pip_predicate = pip_predicate, + bidir=bidir, + nodename_filter_union=False) + + fuzz_interconnect_sinks(config, sinks, full_mux_style, ignore_tiles, extra_substs, fc_filter) + +def fuzz_interconnect_for_tiletype(device, tiletype): + prototype = list(tiles.get_tiles_by_tiletype(device, tiletype).keys())[0] + + nodes = tiles.get_connected_nodes(device, prototype) + + connected_tiles = tiles.get_connected_tiles(device, prototype) + + cfg = fuzzconfig.FuzzConfig(job=f"interconnect_{tiletype}", device=device, tiles=[prototype]) + #fuzz_interconnect(config=cfg, nodenames=nodes, bidir=True) + return collect_sinks(cfg, nodes, bidir=True) + +def fuzz_interconnect_pins(config, site_name, extra_substs = {}, full_mux_style = False, fc_filter=lambda x: True): + pins = tiles.get_pins_for_site(config.device, site_name) + + family = config.device.split("-")[0] + suffix = config.device.split("-")[1] + empty_sv = database.get_db_root() + f"/../fuzzers/{family}/shared/empty_{suffix}.v" + base_bitf = config.build_design(empty_sv, extra_substs, "base_") + + def per_pip(pin_info, pin_pip): # Get a unique prefix from the thread ID - prefix = "thread{}_".format(threading.get_ident()) - fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.db, base_bitf, set(config.tiles), to_wire, config.tiles[0], ignore_tiles, full_mux_style, not (fc_filter(to_wire))) - for from_wire in sinks[to_wire]: - arcs_attr = r', \dm:arcs ="{}.{}"'.format(to_wire, from_wire) - substs = extra_substs.copy() - substs["arcs_attr"] = arcs_attr - arc_bit = config.build_design(config.sv, substs, prefix) - fz.add_pip_sample(fuzzconfig.db, from_wire, arc_bit) - fz.solve(fuzzconfig.db) - fuzzloops.parallel_foreach(list(sorted(sinks.keys())), per_sink) + + print(pin_info, pin_pip) + pin_name = pin_info['pin_name'] + to_wire = pin_pip.to_wire + from_wire = pin_pip.from_wire + is_output = pin_info['pin_node'] == pin_pip.from_wire + + prefix = "{}_{}_{}_".format(config.job, config.device, to_wire) + fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.db, base_bitf, + set(config.tiles), + to_wire, + config.tiles[0], set(), full_mux_style, not (fc_filter(to_wire))) + + arcs_attr = r', \dm:arcs ="{}.{}"'.format(to_wire, from_wire) + substs = extra_substs.copy() + substs["pin_name"] = pin_name + substs["target"] = ".A0(q)" if is_output else ".Q0(q),.A0(q)" + substs["arcs_attr"] = arcs_attr + + print(f"Building design for ({config.job} {config.device}) {to_wire} to {from_wire}") + arc_bit = config.build_design(config.sv, substs, prefix) + fz.add_pip_sample(fuzzconfig.db, from_wire, arc_bit) + + config.solve(fz) + + for p, pnode in pins: + assert(len(pnode.pips()) == 1) + per_pip(p, pnode.pips()[0]) + + diff --git a/util/fuzz/nonrouting.py b/util/fuzz/nonrouting.py index f7400e4..5947353 100644 --- a/util/fuzz/nonrouting.py +++ b/util/fuzz/nonrouting.py @@ -1,11 +1,13 @@ """ Utilities for fuzzing non-routing configuration. This is the counterpart to interconnect.py """ - import threading import tiles import libpyprjoxide + import fuzzconfig +import fuzzloops +import os def fuzz_word_setting(config, name, length, get_sv_substs, desc=""): """ @@ -16,15 +18,20 @@ def fuzz_word_setting(config, name, length, get_sv_substs, desc=""): :param length: number of bits in the setting :param get_sv_substs: a callback function, that is called with an array of bits to create a design with that setting """ + if not fuzzconfig.should_fuzz_platform(config.device): + return + prefix = "thread{}_".format(threading.get_ident()) baseline = config.build_design(config.sv, get_sv_substs([False for _ in range(length)]), prefix) fz = libpyprjoxide.Fuzzer.word_fuzzer(fuzzconfig.db, baseline, set(config.tiles), name, desc, length, baseline) for i in range(length): i_bit = config.build_design(config.sv, get_sv_substs([(_ == i) for _ in range(length)]), prefix) fz.add_word_sample(fuzzconfig.db, i, i_bit) - fz.solve(fuzzconfig.db) -def fuzz_enum_setting(config, empty_bitfile, name, values, get_sv_substs, include_zeros=True, assume_zero_base=False, min_cover={}, desc=""): + config.solve(fz) + +def fuzz_enum_setting(config, empty_bitfile, name, values, get_sv_substs, include_zeros=True, + assume_zero_base=False, min_cover={}, desc="", mark_relative_to=None): """ Fuzz a setting with multiple possible values @@ -39,17 +46,35 @@ def fuzz_enum_setting(config, empty_bitfile, name, values, get_sv_substs, includ :param min_cover: for each setting in this, run with each value in the array that setting points to, to get a minimal bit set """ - prefix = "thread{}_".format(threading.get_ident()) - fz = libpyprjoxide.Fuzzer.enum_fuzzer(fuzzconfig.db, empty_bitfile, set(config.tiles), name, desc, include_zeros, assume_zero_base) + if not fuzzconfig.should_fuzz_platform(config.device): + return + + if config.check_deltas(name): + return + + prefix = "thread{}_{}_{}_{}_".format(threading.get_ident(), config.job, config.device, name) + try: + fz = libpyprjoxide.Fuzzer.enum_fuzzer(fuzzconfig.db, empty_bitfile, set(config.tiles), name, desc, include_zeros, assume_zero_base, mark_relative_to = mark_relative_to) + except: + print(f"ERROR: from {empty_bitfile}") + raise + for opt in values: + opt_name = opt + if opt == "#SIG" and name.endswith("MUX"): + opt_name = name[:-3].split(".")[1] + if opt == "#INV": + opt_name = "INV" + if opt in min_cover: for c in min_cover[opt]: opt_bit = config.build_design(config.sv, get_sv_substs((opt, c)), prefix) - fz.add_enum_sample(fuzzconfig.db, opt, opt_bit) + fz.add_enum_sample(fuzzconfig.db, opt_name, opt_bit) else: opt_bit = config.build_design(config.sv, get_sv_substs(opt), "{}{}_".format(prefix, opt)) - fz.add_enum_sample(fuzzconfig.db, opt, opt_bit) - fz.solve(fuzzconfig.db) + fz.add_enum_sample(fuzzconfig.db, opt_name, opt_bit) + + config.solve(fz) def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=None): """ @@ -60,6 +85,9 @@ def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=N :param length: number of bits in the setting :param get_sv_substs: a callback function, that is called with an array of bits to create a design with that setting """ + if not fuzzconfig.should_fuzz_platform(config.device): + return + prefix = "thread{}_".format(threading.get_ident()) inverted_mode = False @@ -77,8 +105,8 @@ def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=N bits = [(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)] i_bit = config.build_design(config.sv, get_sv_substs(bits), prefix) fz.add_word_sample(fuzzconfig.db, bits, i_bit) - fz.solve(fuzzconfig.db) + config.solve(fz) def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, desc=""): """ @@ -90,10 +118,16 @@ def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, des :param values: list of values taken by the enum :param get_sv_substs: a callback function, """ + if not fuzzconfig.should_fuzz_platform(config.device): + return + prefix = "thread{}_".format(threading.get_ident()) ipcore, iptype = config.tiles[0].split(":") fz = libpyprjoxide.IPFuzzer.enum_fuzzer(fuzzconfig.db, empty_bitfile, ipcore, iptype, name, desc) for opt in values: opt_bit = config.build_design(config.sv, get_sv_substs(opt), prefix) fz.add_enum_sample(fuzzconfig.db, opt, opt_bit) - fz.solve(fuzzconfig.db) + + config.solve(fz) + + diff --git a/util/fuzz/primitives.py b/util/fuzz/primitives.py new file mode 100644 index 0000000..294a799 --- /dev/null +++ b/util/fuzz/primitives.py @@ -0,0 +1,95 @@ + + +class PinSetting: + def __init__(self, name, dir): + self.name = name + +class WordSetting: + def __init__(self, name, states, desc=""): + self.name = name + self.states = states + self.desc = desc + +class EnumSetting: + def __init__(self, name, values, mux = False, desc=""): + self.name = name + self.values = values + self.desc = desc + self.mux = mux + +class PrimitiveDefinition: + def __init__(self, name, settings, pins = []): + self.name = name + self.settings = settings + self.pins = pins + + def build(self, ctx, site, programming, **kwargs): + return f""" + (* \dm:primitive ="{self.name}", \dm:programming ="MODE:OSC_CORE ${config}", \dm:site ="{site}" *) + {self.name} {self.name}_{idx} ( ); + """ + +lram_core = PrimitiveDefinition( + "LRAM_CORE", + [ + EnumSetting("MODE", ["NONE", "LRAM_CORE"], desc="LRAM primitive mode"), + EnumSetting("ASYNC_RST_RELEASE", ["SYNC", "ASYNC"], + desc="LRAM reset release configuration"), + EnumSetting("DATA_PRESERVE", ["DISABLE", "ENABLE"], + desc="LRAM data preservation across resets"), + EnumSetting("EBR_SP_EN", ["DISABLE", "ENABLE"], + desc="EBR single port mode"), + EnumSetting("ECC_BYTE_SEL", ["ECC_EN", "BYTE_EN"]), + EnumSetting("GSR", ["ENABLED", "DISABLED"], + desc="LRAM global set/reset mask"), + EnumSetting("OUT_REGMODE_A", ["NO_REG", "OUT_REG"], + desc="LRAM output pipeline register A enable"), + EnumSetting("OUT_REGMODE_B", ["NO_REG", "OUT_REG"], + desc="LRAM output pipeline register B enable"), + EnumSetting("RESETMODE", ["SYNC", "ASYNC"], + desc="LRAM sync/async reset select"), + EnumSetting("RST_AB_EN", ["RESET_AB_DISABLE", "RESET_AB_ENABLE"], + desc="LRAM reset A/B enable"), + EnumSetting("SP_EN", ["DISABLE", "ENABLE"], + desc="LRAM single port mode"), + EnumSetting("UNALIGNED_READ", ["DISABLE", "ENABLE"], + desc="LRAM unaligned read support"), + EnumSetting("CLKMUX", ["#SIG", "#INV"], desc="LRAM CLK inversion control"), + EnumSetting("CSAMUX", ["#SIG", "#INV"], desc="LRAM CSA inversion control"), + EnumSetting("CSBMUX", ["#SIG", "#INV"], desc="LRAM CSB inversion control"), + EnumSetting("RSTAMUX", ["#SIG", "#INV"], desc="LRAM RSTA inversion control"), + EnumSetting("RSTBMUX", ["#SIG", "#INV"], desc="LRAM RSTB inversion control"), + EnumSetting("WEAMUX", ["#SIG", "#INV"], desc="LRAM WEA inversion control"), + EnumSetting("WEBMUX", ["#SIG", "#INV"], desc="LRAM WEB inversion control"), + ] +) + +iologic_core = PrimitiveDefinition( + "IOLOGIC_CORE", + [ + WordSetting("DELAY.DEL_VALUE", 7), + EnumSetting("DELAY.COARSE_DELAY", ["0NS", "0P8NS", "1P6NS"]), + EnumSetting("DELAY.COARSE_DELAY_MODE", ["DYNAMIC", "STATIC"]), + EnumSetting("DELAY.EDGE_MONITOR", ["ENABLED", "DISABLED"]), + EnumSetting("DELAY.WAIT_FOR_EDGE", ["ENABLED", "DISABLED"]), + ] +) + +siologic_core = PrimitiveDefinition( + "SIOLOGIC_CORE", + iologic_core.settings + + [ + EnumSetting(f":{n}", ["#SIG", "#OFF"]) + for n in + ["CIBCRS0", "CIBCRS1", "RANKSELECT", "RANKENABLE", "RANK0UPDATE", "RANK1UPDATE"] + ] +) + +osc_core = PrimitiveDefinition( + "OSC_CORE", + [], + [ + PinSetting("HFCLKOUT", "out"), + PinSetting("HFSDSCEN", "in") + ] +) From 5e3c9c55a11ce83340dd9e85382c85bea3467bde Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Mon, 19 Jan 2026 09:52:36 -0700 Subject: [PATCH 07/29] Changes around IO for lifcl-33 --- fuzzers/LIFCL/031-io_mode/fuzzer.py | 251 +++++++++++++++++++------- fuzzers/LIFCL/032-hsio_mode/fuzzer.py | 175 +++++++++++++----- fuzzers/LIFCL/071-iodelay/fuzzer.py | 32 +++- fuzzers/LIFCL/091-osc/fuzzer.py | 38 ++-- fuzzers/LIFCL/100-ip-base/fuzzer.py | 55 ++++++ 5 files changed, 413 insertions(+), 138 deletions(-) diff --git a/fuzzers/LIFCL/031-io_mode/fuzzer.py b/fuzzers/LIFCL/031-io_mode/fuzzer.py index 5e07c12..c31b27a 100644 --- a/fuzzers/LIFCL/031-io_mode/fuzzer.py +++ b/fuzzers/LIFCL/031-io_mode/fuzzer.py @@ -1,10 +1,70 @@ -from fuzzconfig import FuzzConfig +import logging +import signal +import traceback + +from fuzzconfig import FuzzConfig, should_fuzz_platform import nonrouting import fuzzloops import re +import database +import tiles +import lapie +import sys +import asyncio + +from tqdm.asyncio import tqdm +from tqdm.contrib.logging import logging_redirect_tqdm + +pio_names = ["A", "B"] + + +def handle_ctrl_c(loop): + print("\nReceived Ctrl+C. Printing task stacks and shutting down gracefully...") + + # Get all running tasks + try: + # In Python 3.7+, use asyncio.all_tasks(loop) + # For compatibility with older versions (pre-3.7), you might need asyncio.Task.all_tasks() + all_tasks = asyncio.all_tasks(loop) + except AttributeError: + # Fallback for older Python versions if needed, though asyncio.run is 3.7+ + all_tasks = asyncio.Task.all_tasks() -configs = [ - ("B", "E11", # PR3A, + # Print stack for each task + for task in all_tasks: + if not task.done(): + print(f"Task: {task.get_name()}") + # print_stack directly prints to stderr, but get_stack returns the frame objects + #traceback.print_stack(task.get_stack()) # Can use if you need more control + task.print_stack(file=sys.stderr, limit=100) # Prints to standard error + print("-" * 20) + + # Cancel all tasks to allow the program to exit cleanly + for task in all_tasks: + task.cancel() + + +def create_config_from_pad(pad, device): + pin = pad["pins"][0] + pio = pad["pio"] + ts = [t for t in tiles.get_tiles_from_edge(device, pad["side"], pad["offset"]) if "SYSIO" in t] + all_sysio = [t for t in tiles.get_tiles_from_edge(device, pad["side"]) if "SYSIO" in t] + tiletype = ts[0].split(":")[1] + print(ts, tiletype, pad) + return ( + f"{tiletype}-{pio}", + (pio_names[pad["pio"]], pin, + FuzzConfig(job=f"IO{pin}_{device}_{tiletype}", device=device, sv="../shared/empty_33.v", + tiles=ts + all_sysio)) + ) + +pads33 = [x for x in database.get_iodb("LIFCL-33")["pads"]] +configs_33 = dict([ + create_config_from_pad(x, "LIFCL-33") for x in pads33 if x["offset"] >= 0 +]) + +configs = list(configs_33.values()) + [ + ("B", "E11", # PR3B, FuzzConfig(job="IO1D_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R3C75:SYSIO_B1_DED_15K"])), ("A","F16", # PR13A FuzzConfig(job="IO1A_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R13C75:SYSIO_B1_0_15K", "CIB_R14C75:SYSIO_B1_1_15K"])), @@ -80,11 +140,23 @@ def per_config(config): pio, site, cfg = config cfg.setup() empty = cfg.build_design(cfg.sv, {}) - if cfg.device == "LIFCL-17": - cfg.sv = "iob_17.v" - else: - cfg.sv = "iob_40.v" + cfg.sv = "iob.v" + + # if cfg.device == "LIFCL-17": + # cfg.sv = "iob_17.v" + # elif cfg.device == "LIFCL-40": + # cfg.sv = "iob_40.v" + + (r,c) = tiles.get_rc_from_name(cfg.device, cfg.tiles[0]) + + if f"R{r}C{c}_JPADDO_SEIO33_CORE_IO{pio}" not in tiles.get_full_node_set(cfg.device): + print(f"Skipping {site} {cfg.tiles}; no SEIO33 tile") + return + primtype = "SEIO33_CORE" + + suffix = "" + def get_bank_vccio(iotype): if iotype == "": return "3.3" @@ -116,73 +188,112 @@ def get_substs(iotype="BIDIR_LVCMOS33", kv=None, vcc=None, tmux="T"): pintype=pintype, primtype=primtype, site=site, iotype=iostd, t=t, extra_config=extra_config, vcc=vcc) seio_types = [ "NONE", - "INPUT_LVCMOS10", - "INPUT_LVCMOS12", "OUTPUT_LVCMOS12", "BIDIR_LVCMOS12", + ] + + pullmodes = ["NONE", "UP", "DOWN", "KEEPER"] + + pullmodes += [ "I3C" ] + + seio_types += [ + "INPUT_LVCMOS12","OUTPUT_LVCMOS12","BIDIR_LVCMOS12", "INPUT_LVCMOS15", "OUTPUT_LVCMOS15", "BIDIR_LVCMOS15", - "INPUT_LVCMOS18", "OUTPUT_LVCMOS18", "BIDIR_LVCMOS18", "INPUT_LVCMOS25", "OUTPUT_LVCMOS25", "BIDIR_LVCMOS25", + "OUTPUT_LVCMOS25D", + "INPUT_LVCMOS33", "OUTPUT_LVCMOS33", "BIDIR_LVCMOS33", - "OUTPUT_LVCMOS25D", "OUTPUT_LVCMOS33D" + "INPUT_LVCMOS18", "OUTPUT_LVCMOS18", "BIDIR_LVCMOS18", + "OUTPUT_LVCMOS33D" ] - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.BASE_TYPE".format(pio), seio_types, - lambda x: get_substs(iotype=x), False, assume_zero_base=True) - - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DRIVE_3V3".format(pio), ["2", "4", "8", "12", "50RS"], - lambda x: get_substs(iotype="OUTPUT_LVCMOS33", kv=("DRIVE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DRIVE_2V5".format(pio), ["2", "4", "8", "10", "50RS"], - lambda x: get_substs(iotype="OUTPUT_LVCMOS25", kv=("DRIVE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DRIVE_1V8".format(pio), ["2", "4", "8", "50RS"], - lambda x: get_substs(iotype="OUTPUT_LVCMOS18", kv=("DRIVE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DRIVE_1V5".format(pio), ["2", "4", "8", "12"], - lambda x: get_substs(iotype="OUTPUT_LVCMOS15", kv=("DRIVE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DRIVE_1V2".format(pio), ["2", "4", "8", "12"], - lambda x: get_substs(iotype="OUTPUT_LVCMOS12", kv=("DRIVE", x)), True) - - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.PULLMODE".format(pio), ["NONE", "UP", "DOWN", "KEEPER", "I3C"], - lambda x: get_substs(iotype="INPUT_LVCMOS33", kv=("PULLMODE", x)), True) - - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.HYSTERESIS_3V3".format(pio), ["ON", "OFF"], - lambda x: get_substs(iotype="INPUT_LVCMOS33", kv=("HYSTERESIS", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.HYSTERESIS_2V5".format(pio), ["ON", "OFF"], - lambda x: get_substs(iotype="INPUT_LVCMOS25", kv=("HYSTERESIS", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.HYSTERESIS_1V8".format(pio), ["ON", "OFF"], - lambda x: get_substs(iotype="INPUT_LVCMOS18", kv=("HYSTERESIS", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.HYSTERESIS_1V5".format(pio), ["ON", "OFF"], - lambda x: get_substs(iotype="INPUT_LVCMOS15", kv=("HYSTERESIS", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.HYSTERESIS_1V2".format(pio), ["ON", "OFF"], - lambda x: get_substs(iotype="INPUT_LVCMOS12", kv=("HYSTERESIS", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.UNDERDRIVE_3V3".format(pio), ["ON", "OFF"], - lambda x: get_substs(iotype="INPUT_LVCMOS33" if x=="OFF" else "INPUT_LVCMOS25", vcc="3.3"), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.UNDERDRIVE_1V8".format(pio), ["ON", "OFF"], - lambda x: get_substs(iotype="INPUT_LVCMOS18" if x=="OFF" else "INPUT_LVCMOS15", vcc="1.8"), True) - - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.CLAMP".format(pio), ["OFF", "ON"], - lambda x: get_substs(iotype="INPUT_LVCMOS33", kv=("CLAMP", x)), True) - - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DFTDO2DI".format(pio), ["DISABLED", "ENABLED"], - lambda x: get_substs(iotype="INPUT_LVCMOS33", kv=("DFTDO2DI", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.GLITCHFILTER".format(pio), ["OFF", "ON"], - lambda x: get_substs(iotype="INPUT_LVCMOS33", kv=("GLITCHFILTER", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.LOOPBKCD2AB".format(pio), ["DISABLED", "ENABLED"], - lambda x: get_substs(iotype="INPUT_LVCMOS33", kv=("LOOPBKCD2AB", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.OPENDRAIN".format(pio), ["OFF", "ON"], - lambda x: get_substs(iotype="OUTPUT_LVCMOS33", kv=("OPENDRAIN", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SLEEPHIGHLEAKAGE".format(pio), ["DISABLED", "ENABLED"], - lambda x: get_substs(iotype="INPUT_LVCMOS33", kv=("SLEEPHIGHLEAKAGE", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SLEWRATE".format(pio), ["FAST", "MED", "SLOW"], - lambda x: get_substs(iotype="OUTPUT_LVCMOS33", kv=("SLEWRATE", x)), False) - - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.TERMINATION_1V8".format(pio), ["OFF", "40", "50", "60", "75", "150"], - lambda x: get_substs(iotype="INPUT_LVCMOS18", kv=("TERMINATION", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.TERMINATION_1V5".format(pio), ["OFF", "40", "50", "60", "75"], - lambda x: get_substs(iotype="INPUT_LVCMOS15", kv=("TERMINATION", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.TERMINATION_1V2".format(pio), ["OFF", "40", "50", "60", "75"], - lambda x: get_substs(iotype="INPUT_LVCMOS12", kv=("TERMINATION", x)), False) - - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.TMUX".format(pio), ["T", "INV"], - lambda x: get_substs(iotype="BIDIR_LVCMOS33", tmux=x), False) - - fuzzloops.parallel_foreach(configs, per_config) + coroutines = [] + def fuzz_enum_setting(*args, **kwargs): + nonrouting.fuzz_enum_setting(cfg, empty, *args, **kwargs) + + fuzz_enum_setting(f"PIO{pio}.BASE_TYPE", seio_types, + lambda x: get_substs(iotype=x), False, assume_zero_base=True, mark_relative_to = cfg.tiles[0]) + + input_mode = "INPUT_LVCMOS33" + def iotype(v, out = False): + return ("OUTPUT_" if out else "INPUT_") + "LVCMOS" + str(v).replace(".", "") + suffix + + if primtype == "SEIO33_CORE": + fuzz_enum_setting(f"PIO{pio}.DRIVE_3V3", ["2", "4", "8", "12", "50RS"], + lambda x: get_substs(iotype="OUTPUT_LVCMOS33", kv=("DRIVE", x)), True) + + fuzz_enum_setting("PIO{}.GLITCHFILTER".format(pio), ["OFF", "ON"], + lambda x: get_substs(iotype=input_mode, kv=("GLITCHFILTER", x)), False) + fuzz_enum_setting("PIO{}.DRIVE_2V5".format(pio), ["2", "4", "8", "10", "50RS"], + lambda x: get_substs(iotype=iotype(2.5, True), kv=("DRIVE", x)), True) + + fuzz_enum_setting("PIO{}.HYSTERESIS_3V3".format(pio), ["ON", "OFF"], + lambda x: get_substs(iotype=iotype(3.3), kv=("HYSTERESIS", x)), True) + + fuzz_enum_setting("PIO{}.HYSTERESIS_2V5".format(pio), ["ON", "OFF"], + lambda x: get_substs(iotype=iotype(2.5), kv=("HYSTERESIS", x)), True) + + fuzz_enum_setting("PIO{}.UNDERDRIVE_3V3".format(pio), ["ON", "OFF"], + lambda x: get_substs(iotype=iotype(3.3) if x=="OFF" else iotype(2.5), vcc="3.3"), True) + + fuzz_enum_setting("PIO{}.DRIVE_1V8".format(pio), ["2", "4", "8", "50RS"], + lambda x: get_substs(iotype=iotype(1.8, True), kv=("DRIVE", x)), True) + fuzz_enum_setting("PIO{}.DRIVE_1V5".format(pio), ["2", "4", "8", "12"], + lambda x: get_substs(iotype=iotype(1.5, True), kv=("DRIVE", x)), True) + fuzz_enum_setting("PIO{}.DRIVE_1V2".format(pio), ["2", "4", "8", "12"], + lambda x: get_substs(iotype=iotype(1.2, True), kv=("DRIVE", x)), True) + + fuzz_enum_setting("PIO{}.PULLMODE".format(pio), pullmodes, + lambda x: get_substs(iotype=input_mode, kv=("PULLMODE", x)), True) + + fuzz_enum_setting("PIO{}.HYSTERESIS_1V8".format(pio), ["ON", "OFF"], + lambda x: get_substs(iotype=iotype(1.8), kv=("HYSTERESIS", x)), True) + fuzz_enum_setting("PIO{}.HYSTERESIS_1V5".format(pio), ["ON", "OFF"], + lambda x: get_substs(iotype=iotype(1.5), kv=("HYSTERESIS", x)), True) + fuzz_enum_setting("PIO{}.HYSTERESIS_1V2".format(pio), ["ON", "OFF"], + lambda x: get_substs(iotype=iotype(1.2), kv=("HYSTERESIS", x)), True) + fuzz_enum_setting("PIO{}.UNDERDRIVE_1V8".format(pio), ["ON", "OFF"], + lambda x: get_substs(iotype=iotype(1.8) if x=="OFF" else iotype(1.5), vcc="1.8"), True) + + fuzz_enum_setting("PIO{}.CLAMP".format(pio), ["OFF", "ON"], + lambda x: get_substs(iotype=input_mode, kv=("CLAMP", x)), True) + + fuzz_enum_setting("PIO{}.DFTDO2DI".format(pio), ["DISABLED", "ENABLED"], + lambda x: get_substs(iotype=iotype(1.8), kv=("DFTDO2DI", x)), False) + fuzz_enum_setting("PIO{}.LOOPBKCD2AB".format(pio), ["DISABLED", "ENABLED"], + lambda x: get_substs(iotype=iotype(1.8), kv=("LOOPBKCD2AB", x)), False) + fuzz_enum_setting("PIO{}.OPENDRAIN".format(pio), ["OFF", "ON"], + lambda x: get_substs(iotype=iotype(1.8, True), kv=("OPENDRAIN", x)), False) + fuzz_enum_setting("PIO{}.SLEEPHIGHLEAKAGE".format(pio), ["DISABLED", "ENABLED"], + lambda x: get_substs(iotype=iotype(1.8), kv=("SLEEPHIGHLEAKAGE", x)), False) + fuzz_enum_setting("PIO{}.SLEWRATE".format(pio), ["FAST", "MED", "SLOW"], + lambda x: get_substs(iotype=iotype(1.8, True), kv=("SLEWRATE", x)), False) + + fuzz_enum_setting("PIO{}.TERMINATION_1V8".format(pio), ["OFF", "40", "50", "60", "75", "150"], + lambda x: get_substs(iotype=iotype(1.8), kv=("TERMINATION", x)), False) + fuzz_enum_setting("PIO{}.TERMINATION_1V5".format(pio), ["OFF", "40", "50", "60", "75"], + lambda x: get_substs(iotype=iotype(1.5), kv=("TERMINATION", x)), False) + fuzz_enum_setting("PIO{}.TERMINATION_1V2".format(pio), ["OFF", "40", "50", "60", "75"], + lambda x: get_substs(iotype=iotype(1.2), kv=("TERMINATION", x)), False) + + fuzz_enum_setting("PIO{}.TMUX".format(pio), ["T", "INV"], + lambda x: get_substs(iotype=f"BIDIR_LVCMOS18{suffix}", tmux=x), False) + + return coroutines + + def cfg_filter(config): + pio, site, cfg = config + if not should_fuzz_platform(cfg.device): + return False + + if len(sys.argv) > 1 and sys.argv[1] not in cfg.tiles[0]: + return False + + if len(sys.argv) > 2 and sys.argv[2] != pio: + return False + + return True + + fuzzloops.parallel_foreach(filter(cfg_filter, configs), per_config) + if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) main() diff --git a/fuzzers/LIFCL/032-hsio_mode/fuzzer.py b/fuzzers/LIFCL/032-hsio_mode/fuzzer.py index 440ea59..2704812 100644 --- a/fuzzers/LIFCL/032-hsio_mode/fuzzer.py +++ b/fuzzers/LIFCL/032-hsio_mode/fuzzer.py @@ -1,24 +1,60 @@ -from fuzzconfig import FuzzConfig +from fuzzconfig import FuzzConfig, should_fuzz_platform import nonrouting import fuzzloops import re +import database +import tiles +import lapie +import sys -configs = [ - ("A","V1", # PB6A - FuzzConfig(job="IO5A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C6:SYSIO_B5_0", "CIB_R56C7:SYSIO_B5_1"])), - ("B","W1", # PB6B - FuzzConfig(job="IO5B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C6:SYSIO_B5_0", "CIB_R56C7:SYSIO_B5_1"])), - ("A","Y7", # PB30A - FuzzConfig(job="IO4A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C30:SYSIO_B4_0", "CIB_R56C31:SYSIO_B4_1"])), - ("B","Y8", # PB30B - FuzzConfig(job="IO4B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C30:SYSIO_B4_0", "CIB_R56C31:SYSIO_B4_1"])), - ("A","R12", # PB64A - FuzzConfig(job="IO3A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C64:SYSIO_B3_0", "CIB_R56C65:SYSIO_B3_1"])), - ("B","P12", # PB64A - FuzzConfig(job="IO3B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C64:SYSIO_B3_0", "CIB_R56C65:SYSIO_B3_1"])), +pio_names = ["A", "B"] +def create_config_from_pad(pad, device): + pin = pad["pins"][0] + ts = [t for t in tiles.get_tiles_from_edge(device, pad["side"], pad["offset"]) if "SYSIO" in t] + all_sysio = [t for t in tiles.get_tiles_from_edge(device, pad["side"]) if "SYSIO" in t] + tiletype = ts[0].split(":")[1] -] + (r,c) = tiles.get_rc_from_name(device,ts[0]) + + # Make sure we get every combination of SYSIO tile types that are next to eachother + neighbor_tile_types = sorted(list({ + tile.split(":")[1] + for x in [-1,0,1] + for y in [-1,0,1] + for tile in tiles.get_tiles_by_rc(device, ((r+x), (c+y)) ) if "SYSIO" in tile + })) + pio = pio_names[pad["pio"]] + return ( + "|".join(neighbor_tile_types) + "-" + pio, + (pio_names[pad["pio"]], + pin, + FuzzConfig(job=f"IO{pin}_{tiletype}", device=device, + tiles=ts + all_sysio)) + ) + +def create_configs_for_device(device): + pads = [x for x in database.get_iodb(device)["pads"]] + configs = dict([ + create_config_from_pad(x, device) for x in pads if x["offset"] >= 0 + ]) + return configs + +configs = (create_configs_for_device("LIFCL-33") | create_configs_for_device("LIFCL-40")).values() +# + [ +# ("A","V1", # PB6A +# FuzzConfig(job="IO5A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C6:SYSIO_B5_0", "CIB_R56C7:SYSIO_B5_1"])), +# ("B","W1", # PB6B +# FuzzConfig(job="IO5B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C6:SYSIO_B5_0", "CIB_R56C7:SYSIO_B5_1"])), +# ("A","Y7", # PB30A +# FuzzConfig(job="IO4A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C30:SYSIO_B4_0", "CIB_R56C31:SYSIO_B4_1"])), +# ("B","Y8", # PB30B +# FuzzConfig(job="IO4B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C30:SYSIO_B4_0", "CIB_R56C31:SYSIO_B4_1"])), +# ("A","R12", # PB64A +# FuzzConfig(job="IO3A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C64:SYSIO_B3_0", "CIB_R56C65:SYSIO_B3_1"])), +# ("B","P12", # PB64A +# FuzzConfig(job="IO3B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C64:SYSIO_B3_0", "CIB_R56C65:SYSIO_B3_1"])), +# ] seio_types = [ ("LVCMOS18H", 1.8, None), @@ -50,12 +86,29 @@ ("HSTL15D_I", 1.5, None), ("HSUL12D", 1.2, None), ] + +device_empty_bitfile = {} + def main(): def per_config(config): pio, site, cfg = config + + (r,c) = tiles.get_rc_from_name(cfg.device, cfg.tiles[0]) + + if f"R{r}C{c}_JPADDO_SEIO18_CORE_IO{pio}" not in tiles.get_full_node_set(cfg.device) and \ + f"R{r}C{c}_JPADDO_DIFFIO18_CORE_IO{pio}" not in tiles.get_full_node_set(cfg.device): + print(f"Skipping {site}") + return + cfg.setup() - empty = cfg.build_design(cfg.sv, {}) - cfg.sv = "iob_40.v" + if cfg.device not in device_empty_bitfile: + device_empty_bitfile[cfg.device] = cfg.build_design(cfg.sv, {}) + empty = device_empty_bitfile[cfg.device] + + cfg.sv = "iob.v" + if cfg.device == "LIFCL-40": + cfg.sv = "iob_40.v" + def get_bank_vccio(iotype): if iotype == "NONE": return "1.8" @@ -107,75 +160,99 @@ def get_substs(iotype="BIDIR_LVCMOS18H", kv=None, vcc=None, tmux="T"): else: all_di_types += ["{}_{}".format(di, t) for di in d] - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.BASE_TYPE".format(pio), all_se_types, - lambda x: get_substs(iotype=x), False, assume_zero_base=True) + coroutines = [] + def fuzz_enum_setting(*args, **kwargs): + coroutines.append( + nonrouting.fuzz_enum_setting(cfg, empty, *args, **kwargs) + ) + + fuzz_enum_setting("PIO{}.SEIO18.DRIVE_1V0".format(pio), ["2", "4"], + lambda x: get_substs(iotype="OUTPUT_LVCMOS10H", kv=("DRIVE", x)), True) + + fuzz_enum_setting("PIO{}.SEIO18.BASE_TYPE".format(pio), all_se_types, + lambda x: get_substs(iotype=x), False, assume_zero_base=True, mark_relative_to = cfg.tiles[0]) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.DRIVE_1V8".format(pio), ["2", "4", "8", "12", "50RS"], + fuzz_enum_setting("PIO{}.SEIO18.DRIVE_1V8".format(pio), ["2", "4", "8", "12", "50RS"], lambda x: get_substs(iotype="OUTPUT_LVCMOS18H", kv=("DRIVE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.DRIVE_1V5".format(pio), ["2", "4", "8"], + fuzz_enum_setting("PIO{}.SEIO18.DRIVE_1V5".format(pio), ["2", "4", "8"], lambda x: get_substs(iotype="OUTPUT_LVCMOS15H", kv=("DRIVE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.DRIVE_1V2".format(pio), ["2", "4", "8"], + fuzz_enum_setting("PIO{}.SEIO18.DRIVE_1V2".format(pio), ["2", "4", "8"], lambda x: get_substs(iotype="OUTPUT_LVCMOS12H", kv=("DRIVE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.DRIVE_1V0".format(pio), ["2", "4"], - lambda x: get_substs(iotype="OUTPUT_LVCMOS10H", kv=("DRIVE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.DRIVE_HSUL12".format(pio), ["4", "6", "8"], + fuzz_enum_setting("PIO{}.SEIO18.DRIVE_HSUL12".format(pio), ["4", "6", "8"], lambda x: get_substs(iotype="OUTPUT_HSUL12", kv=("DRIVE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.PULLMODE".format(pio), ["NONE", "UP", "DOWN", "KEEPER"], + fuzz_enum_setting("PIO{}.SEIO18.PULLMODE".format(pio), ["NONE", "UP", "DOWN", "KEEPER"], lambda x: get_substs(iotype="INPUT_LVCMOS18H", kv=("PULLMODE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.UNDERDRIVE_1V8".format(pio), ["ON", "OFF"], + fuzz_enum_setting("PIO{}.SEIO18.UNDERDRIVE_1V8".format(pio), ["ON", "OFF"], lambda x: get_substs(iotype="INPUT_LVCMOS18H" if x=="OFF" else "INPUT_LVCMOS15H", vcc="1.8"), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.SLEWRATE".format(pio), ["SLOW", "MED", "FAST"], + fuzz_enum_setting("PIO{}.SEIO18.SLEWRATE".format(pio), ["SLOW", "MED", "FAST"], lambda x: get_substs(iotype="OUTPUT_LVCMOS18H", kv=("SLEWRATE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.TERMINATION_1V8".format(pio), ["OFF", "40", "50", "60", "75", "150"], + fuzz_enum_setting("PIO{}.SEIO18.TERMINATION_1V8".format(pio), ["OFF", "40", "50", "60", "75", "150"], lambda x: get_substs(iotype="INPUT_LVCMOS18H", kv=("TERMINATION", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.TERMINATION_1V5".format(pio), ["OFF", "40", "50", "60", "75"], + fuzz_enum_setting("PIO{}.SEIO18.TERMINATION_1V5".format(pio), ["OFF", "40", "50", "60", "75"], lambda x: get_substs(iotype="INPUT_LVCMOS15H", kv=("TERMINATION", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.TERMINATION_1V35".format(pio), ["OFF", "40", "50", "60", "75"], + fuzz_enum_setting("PIO{}.SEIO18.TERMINATION_1V35".format(pio), ["OFF", "40", "50", "60", "75"], lambda x: get_substs(iotype="INPUT_SSTL135_I", kv=("TERMINATION", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.TERMINATION_1V2".format(pio), ["OFF", "40", "50", "60", "75"], + fuzz_enum_setting("PIO{}.SEIO18.TERMINATION_1V2".format(pio), ["OFF", "40", "50", "60", "75"], lambda x: get_substs(iotype="INPUT_LVCMOS12H", kv=("TERMINATION", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.DFTDO2DI".format(pio), ["DISABLED", "ENABLED"], + fuzz_enum_setting("PIO{}.SEIO18.DFTDO2DI".format(pio), ["DISABLED", "ENABLED"], lambda x: get_substs(iotype="INPUT_LVCMOS18H", kv=("DFTDO2DI", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.LOOPBKCD2AB".format(pio), ["DISABLED", "ENABLED"], + fuzz_enum_setting("PIO{}.SEIO18.LOOPBKCD2AB".format(pio), ["DISABLED", "ENABLED"], lambda x: get_substs(iotype="INPUT_LVCMOS18H", kv=("LOOPBKCD2AB", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.OPENDRAIN".format(pio), ["OFF", "ON"], + fuzz_enum_setting("PIO{}.SEIO18.OPENDRAIN".format(pio), ["OFF", "ON"], lambda x: get_substs(iotype="OUTPUT_LVCMOS18H", kv=("OPENDRAIN", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.SLEEPHIGHLEAKAGE".format(pio), ["DISABLED", "ENABLED"], + fuzz_enum_setting("PIO{}.SEIO18.SLEEPHIGHLEAKAGE".format(pio), ["DISABLED", "ENABLED"], lambda x: get_substs(iotype="INPUT_LVCMOS18H", kv=("SLEEPHIGHLEAKAGE", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.ENADC_IN".format(pio), ["DISABLED", "ENABLED"], + fuzz_enum_setting("PIO{}.SEIO18.ENADC_IN".format(pio), ["DISABLED", "ENABLED"], lambda x: get_substs(iotype="INPUT_LVCMOS18H", kv=("ENADC_IN", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.INT_LPBK".format(pio), ["DISABLED", "ENABLED"], + fuzz_enum_setting("PIO{}.SEIO18.INT_LPBK".format(pio), ["DISABLED", "ENABLED"], lambda x: get_substs(iotype="INPUT_LVCMOS18H", kv=("INT_LPBK", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.SEIO18.VREF".format(pio), ["OFF", "VREF1_LOAD", "VREF2_LOAD"], + fuzz_enum_setting("PIO{}.SEIO18.VREF".format(pio), ["OFF", "VREF1_LOAD", "VREF2_LOAD"], lambda x: get_substs(iotype="INPUT_SSTL135_I", kv=("VREF", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.TMUX".format(pio), ["T", "INV"], + fuzz_enum_setting("PIO{}.TMUX".format(pio), ["T", "INV"], lambda x: get_substs(iotype="BIDIR_LVCMOS18H", tmux=x), False) if pio == "A": - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DIFFIO18.BASE_TYPE".format(pio), all_di_types, + fuzz_enum_setting("PIO{}.DIFFIO18.BASE_TYPE".format(pio), all_di_types, lambda x: get_substs(iotype=x), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DIFFIO18.PULLMODE".format(pio), ["NONE", "FAILSAFE"], + fuzz_enum_setting("PIO{}.DIFFIO18.PULLMODE".format(pio), ["NONE", "FAILSAFE"], lambda x: get_substs(iotype="INPUT_LVDS", kv=("PULLMODE", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DIFFIO18.DIFFRESISTOR".format(pio), ["OFF", "100"], + fuzz_enum_setting("PIO{}.DIFFIO18.DIFFRESISTOR".format(pio), ["OFF", "100"], lambda x: get_substs(iotype="INPUT_LVDS", kv=("DIFFRESISTOR", x)), True) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DIFFIO18.DIFFDRIVE_MIPI_DPHY".format(pio), ["NA", "2P0"], + fuzz_enum_setting("PIO{}.DIFFIO18.DIFFDRIVE_MIPI_DPHY".format(pio), ["NA", "2P0"], lambda x: get_substs(iotype="OUTPUT_MIPI_DPHY", kv=("DIFFDRIVE", x.replace("P", "."))), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DIFFIO18.DIFFDRIVE_SLVS".format(pio), ["NA", "2P0"], + fuzz_enum_setting("PIO{}.DIFFIO18.DIFFDRIVE_SLVS".format(pio), ["NA", "2P0"], lambda x: get_substs(iotype="OUTPUT_SLVS", kv=("DIFFDRIVE", x.replace("P", "."))), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DIFFIO18.DIFFDRIVE_LVDS".format(pio), ["NA", "3P5"], + fuzz_enum_setting("PIO{}.DIFFIO18.DIFFDRIVE_LVDS".format(pio), ["NA", "3P5"], lambda x: get_substs(iotype="OUTPUT_LVDS", kv=("DIFFDRIVE", x.replace("P", "."))), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DIFFIO18.DIFFRX_INV".format(pio), ["NORMAL", "INVERT"], + fuzz_enum_setting("PIO{}.DIFFIO18.DIFFRX_INV".format(pio), ["NORMAL", "INVERT"], lambda x: get_substs(iotype="INPUT_LVDS", kv=("DIFFRX_INV", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "PIO{}.DIFFIO18.DIFFTX_INV".format(pio), ["NORMAL", "INVERT"], + fuzz_enum_setting("PIO{}.DIFFIO18.DIFFTX_INV".format(pio), ["NORMAL", "INVERT"], lambda x: get_substs(iotype="OUTPUT_LVDS", kv=("DIFFTX_INV", x)), False) - fuzzloops.parallel_foreach(configs, per_config) + + return coroutines + + def cfg_filter(config): + pio, site, cfg = config + if not should_fuzz_platform(cfg.device): + return False + + if len(sys.argv) > 1 and sys.argv[1] not in cfg.tiles[0]: + return False + + if len(sys.argv) > 2 and sys.argv[2] != pio: + return False + + return True + + fuzzloops.parallel_foreach(filter(cfg_filter, configs), per_config) + if __name__ == "__main__": main() diff --git a/fuzzers/LIFCL/071-iodelay/fuzzer.py b/fuzzers/LIFCL/071-iodelay/fuzzer.py index 45a5911..47fa41a 100644 --- a/fuzzers/LIFCL/071-iodelay/fuzzer.py +++ b/fuzzers/LIFCL/071-iodelay/fuzzer.py @@ -3,7 +3,23 @@ import fuzzloops import re -configs = [ +import tiles + + +def create_cfgs(device): + cfgs = [] + for primitive in ["IOLOGIC_CORE", "SIOLOGIC_CORE"]: + for (tiletype, infos) in tiles.get_tiletypes_by_primitive(device, "IOLOGIC_CORE").items(): + if tiletype.startswith("SYSIO"): + print(f"Adding {device} {infos[0]}") + cfgs.append( + (infos[0][0], primitive, FuzzConfig(job=f"{device}_{infos[0][0]}_{infos[0][1]}", device=device, tiles=infos[0][1])) + ) + return cfgs + + + +configs = create_cfgs("LIFCL-33") + [ ("IOL_B8A", "IOLOGICA", FuzzConfig(job="IOL5AMODE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C8:SYSIO_B5_0", "CIB_R56C9:SYSIO_B5_1"])), ("IOL_B8B", "IOLOGICB", FuzzConfig(job="IOL5BMODE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C8:SYSIO_B5_0", "CIB_R56C9:SYSIO_B5_1"])), ("IOL_B18A", "IOLOGICA", FuzzConfig(job="IOL4AMODE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C18:SYSIO_B4_0", "CIB_R56C19:SYSIO_B4_1"])), @@ -91,18 +107,20 @@ def intval(vec): nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.COARSE_DELAY".format(prim), ["0NS", "0P8NS", "1P6NS"], lambda x: get_substs(kv=("COARSE_DELAY", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.COARSE_DELAY_MODE".format(prim), ["DYNAMIC", "STATIC"], - lambda x: get_substs(kv=("COARSE_DELAY_MODE", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.EDGE_MONITOR".format(prim), ["ENABLED", "DISABLED"], - lambda x: get_substs(kv=("EDGE_MONITOR", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.WAIT_FOR_EDGE".format(prim), ["ENABLED", "DISABLED"], - lambda x: get_substs(kv=("WAIT_FOR_EDGE", x)), False) if not s: for pin in ["CIBCRS0", "CIBCRS1", "RANKSELECT", "RANKENABLE", "RANK0UPDATE", "RANK1UPDATE"]: nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}MUX".format(prim, pin), ["OFF", pin], lambda x: get_substs(kv=(pin, x), mux=True), False) + nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.COARSE_DELAY_MODE".format(prim), ["DYNAMIC", "STATIC"], + lambda x: get_substs(kv=("COARSE_DELAY_MODE", x)), False) + nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.EDGE_MONITOR".format(prim), ["ENABLED", "DISABLED"], + lambda x: get_substs(kv=("EDGE_MONITOR", x)), False) + + nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.WAIT_FOR_EDGE".format(prim), ["ENABLED", "DISABLED"], + lambda x: get_substs(kv=("WAIT_FOR_EDGE", x)), False) + fuzzloops.parallel_foreach(configs, per_config) if __name__ == "__main__": diff --git a/fuzzers/LIFCL/091-osc/fuzzer.py b/fuzzers/LIFCL/091-osc/fuzzer.py index 0c3722a..befad0d 100644 --- a/fuzzers/LIFCL/091-osc/fuzzer.py +++ b/fuzzers/LIFCL/091-osc/fuzzer.py @@ -2,21 +2,25 @@ import nonrouting import fuzzloops import re -from interconnect import fuzz_interconnect +from interconnect import fuzz_interconnect, fuzz_interconnect_pins +import tiles cfgs = [ FuzzConfig(job="OSCMODE17", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R0C71:OSC_15K"]), - FuzzConfig(job="OSCMODE40", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C77:EFB_1_OSC"]), + FuzzConfig(job="OSCMODE33", device="LIFCL-33", sv="../shared/empty_33.v", tiles=["CIB_R0C29:OSC"]), +# FuzzConfig(job="OSCMODE40", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C77:EFB_1_OSC"]), ] def main(): for cfg in cfgs: cfg.setup() empty = cfg.build_design(cfg.sv, {}) - if cfg.device == "LIFCL-17": - cfg.sv = "osc_17.v" - else: + if cfg.device == "LIFCL-40": cfg.sv = "osc.v" + elif cfg.device.startswith("LIFCL-33"): + cfg.sv = "osc_33.v" + else: + cfg.sv = "osc_17.v" def bin_to_int(x): val = 0 @@ -70,15 +74,24 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): nonrouting.fuzz_enum_setting(cfg, empty, "OSC_CORE.DEBUG_N", ["ENABLED", "DISABLED"], lambda x: get_substs(mode="OSC_CORE", kv=("DEBUG_N", x)), False, desc="enable debug mode") + + rc = tiles.get_rc_from_name(cfg.device, cfg.tiles[0]) # Fuzz oscillator routing + regex = False + full_mux = False if cfg.device == "LIFCL-17": cfg.sv = "../shared/route_17.v" - nodes = ["R1C71_JLFCLKOUT_OSC_CORE", "R1C71_JHFCLKOUT_OSC_CORE", - "R1C71_JHFSDCOUT_OSC_CORE", "R1C71_JHFCLKCFG_OSC_CORE", - "R1C71_JHFOUTEN_OSC_CORE", "R1C71_JHFSDSCEN_OSC_CORE"] - for i in range(9): - nodes.append("R1C71_JHFTRMFAB{}_OSC_CORE".format(i)) - nodes.append("R1C71_JLFTRMFAB{}_OSC_CORE".format(i)) + regex = True + nodes = [".*_OSC_CORE"] + elif cfg.device.startswith("LIFCL-33"): + #cfg.sv = "osc_pins.v" + cfg.sv = "../shared/route_33.v" + regex = True + nodes = [".*_OSC_CORE" ] + full_mux = True +# DTR_EN:#ON HF_CLK_DIV:::HF_CLK_DIV=+1 HF_SED_SEC_DIV:::HF_SED_SEC_DIV=+1 HF_FABRIC_EN:#ON HF_OSC_EN:#ON HFDIV_FABRIC_EN:#ON LF_FABRIC_EN:#ON LF_OUTPUT_EN:#ON DEBUG_N:#ON +# OSC_CORE:::LF_FABRIC_EN=ENABLED OSC_CORE:::HF_FABRIC_EN=ENABLED OSC_CORE:::DTR_EN=ENABLED OSC_CORE:::HF_OSC_EN=ENABLED OSC_CORE:::HFDIV_FABRIC_EN=ENABLED +#fuzz_interconnect_pins(cfg, "OSC_CORE_R1C29", {"config": "OSC_CORE:::LF_FABRIC_EN=ENABLED OSC_CORE:::HF_FABRIC_EN=ENABLED OSC_CORE:::DTR_EN=ENABLED OSC_CORE:::HF_OSC_EN=ENABLED OSC_CORE:::HFDIV_FABRIC_EN=ENABLED OSC_CORE:::DEBUG_N=DISABLED OSC_CORE:::HF_CLK_DIV=1 OSC_CORE:::HF_SED_SEC_DIV=1"}) else: cfg.sv = "../shared/route_40.v" nodes = ["R1C77_JLFCLKOUT_OSC_CORE", "R1C77_JHFCLKOUT_OSC_CORE", @@ -87,7 +100,8 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): for i in range(9): nodes.append("R1C77_JHFTRMFAB{}_OSC_CORE".format(i)) nodes.append("R1C77_JLFTRMFAB{}_OSC_CORE".format(i)) - fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=True, full_mux_style=False) + fuzz_interconnect(config=cfg, nodenames=nodes, regex=regex, bidir=True, full_mux_style=full_mux) + if __name__ == '__main__': main() diff --git a/fuzzers/LIFCL/100-ip-base/fuzzer.py b/fuzzers/LIFCL/100-ip-base/fuzzer.py index 2411af0..df5c21c 100644 --- a/fuzzers/LIFCL/100-ip-base/fuzzer.py +++ b/fuzzers/LIFCL/100-ip-base/fuzzer.py @@ -26,6 +26,61 @@ # ("LRAM_CORE_R40C86", "LRAM_CORE"), # ] # ), + (fuzzconfig.FuzzConfig(job="IPADDR33", device="LIFCL-33", sv="ip33.v", tiles=[]), + [ + ("PLL_LLC", "PLL_CORE"), +# ("PLL_LRC", "PLL_CORE"), +# ("PMU_CORE_R1C70", "PMU_CORE"), +# ("SGMIICDR_CORE_R28C5", "SGMIICDR_CORE"), +# ("SGMIICDR_CORE_R28C4", "SGMIICDR_CORE"), + ("I2CFIFO_CORE_R1C37", "I2CFIFO_CORE"), + ("EBR_CORE_R10C5", "EBR_CORE_WID0"), + ("EBR_CORE_R10C5", "EBR_CORE_WID1"), + ("EBR_CORE_R10C5", "EBR_CORE_WID2047"), + ("LRAM_CORE_R2C1", "LRAM_CORE"), + ("LRAM_CORE_R11C1", "LRAM_CORE"), + ("LRAM_CORE_R20C1", "LRAM_CORE"), + ("LRAM_CORE_R15C74", "LRAM_CORE"), + ("LRAM_CORE_R16C74", "LRAM_CORE"), + ] + ), + (fuzzconfig.FuzzConfig(job="IPADDR33U", device="LIFCL-33U", sv="ip_33u.v", tiles=[]), + [ + ("PLL_LLC", "PLL_CORE"), +# ("PLL_LRC", "PLL_CORE"), +# ("PMU_CORE_R1C70", "PMU_CORE"), +# ("SGMIICDR_CORE_R28C5", "SGMIICDR_CORE"), +# ("SGMIICDR_CORE_R28C4", "SGMIICDR_CORE"), + ("I2CFIFO_CORE_R1C37", "I2CFIFO_CORE"), + ("EBR_CORE_R10C5", "EBR_CORE_WID0"), + ("EBR_CORE_R10C5", "EBR_CORE_WID1"), + ("EBR_CORE_R10C5", "EBR_CORE_WID2047"), + ("LRAM_CORE_R2C1", "LRAM_CORE"), + ("LRAM_CORE_R11C1", "LRAM_CORE"), + ("LRAM_CORE_R20C1", "LRAM_CORE"), + ("LRAM_CORE_R15C74", "LRAM_CORE"), + ("LRAM_CORE_R16C74", "LRAM_CORE"), + ] + ), + + # (fuzzconfig.FuzzConfig(job="IPADDR33U", device="LIFCL-33U", sv="ip_33u.v", tiles=[]), + # [ + # ("PLL_LLC", "PLL_CORE"), + # # ("PLL_LRC", "PLL_CORE"), + # # ("PMU_CORE_R1C70", "PMU_CORE"), + # # ("SGMIICDR_CORE_R28C5", "SGMIICDR_CORE"), + # # ("SGMIICDR_CORE_R28C4", "SGMIICDR_CORE"), + # # ("I2CFIFO_CORE_R1C72", "I2CFIFO_CORE"), + # # ("EBR_CORE_R10C5", "EBR_CORE_WID0"), + # # ("EBR_CORE_R10C5", "EBR_CORE_WID1"), + # # ("EBR_CORE_R10C5", "EBR_CORE_WID2047"), + # # ("LRAM_CORE_R2C1", "LRAM_CORE"), + # # ("LRAM_CORE_R11C1", "LRAM_CORE"), + # # ("LRAM_CORE_R20C1", "LRAM_CORE"), + # # ("LRAM_CORE_R15C74", "LRAM_CORE"), + # # ("LRAM_CORE_R16C74", "LRAM_CORE"), + # ] + # ), (fuzzconfig.FuzzConfig(job="IPADDR17", device="LIFCL-17", sv="ip.v", tiles=[]), [ ("DPHY0", "DPHY_CORE"), From daae9380b984ad2d398fc0ee1b18f431c87baf23 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Mon, 19 Jan 2026 10:15:19 -0700 Subject: [PATCH 08/29] Fix undriven signal warning --- fuzzers/LIFCL/010-lut-init/lut.v | 2 +- fuzzers/LIFCL/011-reg-config/ff.v | 10 +++++++--- fuzzers/LIFCL/011-reg-config/fuzzer.py | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/fuzzers/LIFCL/010-lut-init/lut.v b/fuzzers/LIFCL/010-lut-init/lut.v index 429232e..e42947e 100644 --- a/fuzzers/LIFCL/010-lut-init/lut.v +++ b/fuzzers/LIFCL/010-lut-init/lut.v @@ -3,6 +3,6 @@ module top ( ); - (* \dm:cellmodel_primitives ="K${k}=i48_3_lut", \dm:primitive ="SLICE", \dm:programming ="MODE:LOGIC F${k}:F${k} K${k}::Z=${func} ", \dm:site ="R2C2${z}" *) + (* \dm:cellmodel_primitives ="K${k}=i48_3_lut", \dm:primitive ="SLICE", \dm:programming ="MODE:LOGIC F${k}:F${k} K${k}::Z=${func} ", \dm:site ="R2C2${z}" *) SLICE SLICE_I ( ); endmodule diff --git a/fuzzers/LIFCL/011-reg-config/ff.v b/fuzzers/LIFCL/011-reg-config/ff.v index d25f5b1..9e81844 100644 --- a/fuzzers/LIFCL/011-reg-config/ff.v +++ b/fuzzers/LIFCL/011-reg-config/ff.v @@ -3,9 +3,13 @@ module top ( ); + VHI vhi_i(); + (* \xref:LOG ="q_c@0@9", \dm:arcs ="${arc}" *) - wire q; + ${q_used_comment} wire q; - (* \dm:cellmodel_primitives ="REG${k}=i48_3_lut", \dm:primitive ="SLICE", \dm:programming ="MODE:LOGIC ${mux} REG${k}:::REGSET=${regset},SEL=${sel},LSRMODE=${lsrmode} GSR:${gsr} SRMODE:${srmode} Q0:Q0 Q1:Q1 ", \dm:site ="R2C2${z}" *) - SLICE SLICE_I ( .A0(q)${used} ); + (* \dm:cellmodel_primitives ="REG${k}=i48_3_lut", \dm:primitive ="SLICE", \dm:programming ="MODE:LOGIC ${mux} REG${k}:::REGSET=${regset},SEL=${sel},LSRMODE=${lsrmode} GSR:${gsr} SRMODE:${srmode} Q0:Q0 Q1:Q1 ", \dm:site ="R2C2${z}" *) + SLICE SLICE_I ( + ${q_used_comment} .A0(q) + ${used} ); endmodule diff --git a/fuzzers/LIFCL/011-reg-config/fuzzer.py b/fuzzers/LIFCL/011-reg-config/fuzzer.py index cc856b9..be4ef90 100644 --- a/fuzzers/LIFCL/011-reg-config/fuzzer.py +++ b/fuzzers/LIFCL/011-reg-config/fuzzer.py @@ -14,7 +14,7 @@ def per_slice(slicen): for r in range(2): def get_substs(regset="SET", sel="DL", lsrmode="LSR", srmode="LSR_OVER_CE", gsr="DISABLED", mux="", used="", arc=""): return dict(z=slicen, k=str(r), mux=mux, regset=regset, - sel=sel, lsrmode=lsrmode, srmode=srmode, gsr=gsr, used=used, arc=arc) + sel=sel, lsrmode=lsrmode, srmode=srmode, gsr=gsr, used=used, arc=arc, q_used_comment="//" if used == "" else "" ) def get_used_substs(used): u = "" arc = "" From 9393828423dc5a5190a922b6753400ed454b57a6 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Mon, 19 Jan 2026 10:16:56 -0700 Subject: [PATCH 09/29] Remove debug code --- fuzzers/LIFCL/031-io_mode/fuzzer.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/fuzzers/LIFCL/031-io_mode/fuzzer.py b/fuzzers/LIFCL/031-io_mode/fuzzer.py index c31b27a..61c3e86 100644 --- a/fuzzers/LIFCL/031-io_mode/fuzzer.py +++ b/fuzzers/LIFCL/031-io_mode/fuzzer.py @@ -18,32 +18,6 @@ pio_names = ["A", "B"] -def handle_ctrl_c(loop): - print("\nReceived Ctrl+C. Printing task stacks and shutting down gracefully...") - - # Get all running tasks - try: - # In Python 3.7+, use asyncio.all_tasks(loop) - # For compatibility with older versions (pre-3.7), you might need asyncio.Task.all_tasks() - all_tasks = asyncio.all_tasks(loop) - except AttributeError: - # Fallback for older Python versions if needed, though asyncio.run is 3.7+ - all_tasks = asyncio.Task.all_tasks() - - # Print stack for each task - for task in all_tasks: - if not task.done(): - print(f"Task: {task.get_name()}") - # print_stack directly prints to stderr, but get_stack returns the frame objects - #traceback.print_stack(task.get_stack()) # Can use if you need more control - task.print_stack(file=sys.stderr, limit=100) # Prints to standard error - print("-" * 20) - - # Cancel all tasks to allow the program to exit cleanly - for task in all_tasks: - task.cancel() - - def create_config_from_pad(pad, device): pin = pad["pins"][0] pio = pad["pio"] From 80e9a27f71afef646785c9939461d64915b21679 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Mon, 19 Jan 2026 14:38:23 -0700 Subject: [PATCH 10/29] Add support for some 33u --- docs/general/structure.md | 9 ++- fuzzers/LIFCL/001-plc-routing/fuzzer.py | 3 +- fuzzers/LIFCL/091-osc/fuzzer.py | 18 +++--- fuzzers/LIFCL/091-osc/osc.v | 5 +- fuzzers/LIFCL/093-oscd/fuzzer.py | 57 +++++++++++++++++ fuzzers/LIFCL/093-oscd/oscd.v | 11 ++++ fuzzers/LIFCL/shared/route.v | 10 +++ generate_database.sh | 8 +-- libprjoxide/pyprjoxide/Cargo.toml | 2 + libprjoxide/pyprjoxide/src/lib.rs | 2 + radiant.sh | 2 +- tools/extract_tilegrid.py | 9 ++- util/common/database.py | 6 +- util/common/lapie.py | 2 +- util/common/radiant.py | 11 ++-- util/common/tiles.py | 2 +- util/fuzz/fuzzconfig.py | 10 +-- util/fuzz/nonrouting.py | 33 +++++++++- util/fuzz/primitives.py | 81 +++++++++++++++++++++++-- 19 files changed, 238 insertions(+), 43 deletions(-) create mode 100644 fuzzers/LIFCL/093-oscd/fuzzer.py create mode 100644 fuzzers/LIFCL/093-oscd/oscd.v create mode 100644 fuzzers/LIFCL/shared/route.v diff --git a/docs/general/structure.md b/docs/general/structure.md index 23d001a..420c905 100644 --- a/docs/general/structure.md +++ b/docs/general/structure.md @@ -65,8 +65,11 @@ flagged and changed when the tilegrid is imported from lattice's interchange for ## Global Routes -PLC -> Branch Tap -> Branch Node -> Spine -> HROW +There is a global distribution network on the LIFCL devices for clocks and resets to limit skew to any given logic cell: -There is a global distribution network on the LIFCL devices which is laid out as follows: +- Starts at CMUX +- Distributed along HROW's to SPINEs +- SPINEs push to branch nodes +- Branch nodes push out left or right into PLCs -- \ No newline at end of file +See global.json for a listing of those cells for each device. \ No newline at end of file diff --git a/fuzzers/LIFCL/001-plc-routing/fuzzer.py b/fuzzers/LIFCL/001-plc-routing/fuzzer.py index c387f45..cafa99b 100644 --- a/fuzzers/LIFCL/001-plc-routing/fuzzer.py +++ b/fuzzers/LIFCL/001-plc-routing/fuzzer.py @@ -28,8 +28,7 @@ def run_cfg(device): fuzz_interconnect(config=cfg, nodenames=extra_sources, regex=True, bidir=False)#, ignore_tiles=set(["TAP_PLC_R16C14:TAP_PLC"])) def main(): - for device in ["LIFCL-33"]: - run_cfg(device) + run_cfg("LIFCL-40") if __name__ == "__main__": main() diff --git a/fuzzers/LIFCL/091-osc/fuzzer.py b/fuzzers/LIFCL/091-osc/fuzzer.py index befad0d..0a58d1e 100644 --- a/fuzzers/LIFCL/091-osc/fuzzer.py +++ b/fuzzers/LIFCL/091-osc/fuzzer.py @@ -7,20 +7,16 @@ cfgs = [ FuzzConfig(job="OSCMODE17", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R0C71:OSC_15K"]), - FuzzConfig(job="OSCMODE33", device="LIFCL-33", sv="../shared/empty_33.v", tiles=["CIB_R0C29:OSC"]), -# FuzzConfig(job="OSCMODE40", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C77:EFB_1_OSC"]), + FuzzConfig(job="OSCMODE33", device="LIFCL-33", sv="../shared/empty_33.v", tiles=["CIB_R0C29:OSC"]), + FuzzConfig(job="OSCMODE33U", device="LIFCL-33U", sv="../shared/empty_33.v", tiles=["CIB_R0C29:OSC"]), + FuzzConfig(job="OSCMODE40", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C77:EFB_1_OSC"]), ] def main(): for cfg in cfgs: cfg.setup() empty = cfg.build_design(cfg.sv, {}) - if cfg.device == "LIFCL-40": - cfg.sv = "osc.v" - elif cfg.device.startswith("LIFCL-33"): - cfg.sv = "osc_33.v" - else: - cfg.sv = "osc_17.v" + cfg.sv = "osc.v" def bin_to_int(x): val = 0 @@ -31,6 +27,9 @@ def bin_to_int(x): mul *= 2 return val + sites = tiles.get_sites_from_primitive(cfg.device, "OSC_CORE") + site = list(sites.keys())[0] + def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): if kv is None: config = "" @@ -43,7 +42,7 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): config = "{}::::{}={}".format(mode, kv[0], val) else: config = "{}:::{}={}".format(mode, kv[0], kv[1]) - return dict(mode=mode, cmt="//" if mode == "NONE" else "", config=config) + return dict(mode=mode, cmt="//" if mode == "NONE" else "", config=config, site=site) nonrouting.fuzz_enum_setting(cfg, empty, "OSC_CORE.MODE", ["NONE", "OSC_CORE"], lambda x: get_substs(mode=x), False, desc="OSC_CORE primitive mode") @@ -53,6 +52,7 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): nonrouting.fuzz_word_setting(cfg, "OSC_CORE.HF_SED_SEC_DIV", 8, lambda x: get_substs(mode="OSC_CORE", kv=("HF_SED_SEC_DIV", str(bin_to_int(x)))), desc="high frequency oscillator output divider") + nonrouting.fuzz_enum_setting(cfg, empty, "OSC_CORE.DTR_EN", ["ENABLED", "DISABLED"], lambda x: get_substs(mode="OSC_CORE", kv=("DTR_EN", x)), False, desc="") diff --git a/fuzzers/LIFCL/091-osc/osc.v b/fuzzers/LIFCL/091-osc/osc.v index e67e872..fb41bf0 100644 --- a/fuzzers/LIFCL/091-osc/osc.v +++ b/fuzzers/LIFCL/091-osc/osc.v @@ -1,9 +1,8 @@ - -(* \db:architecture ="LIFCL", \db:device ="LIFCL-40", \db:package ="QFN72", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +(* \db:architecture ="${arch}", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) module top ( ); - ${cmt} (* \dm:primitive ="OSC_CORE", \dm:programming ="MODE:OSC_CORE ${config}", \dm:site ="OSC_CORE_R1C77" *) + ${cmt} (* \dm:primitive ="OSC_CORE", \dm:programming ="MODE:OSC_CORE ${config}", \dm:site ="${site}" *) ${cmt} OSC_CORE OSC_I ( ); // A primitive is needed, but VHI should be harmless diff --git a/fuzzers/LIFCL/093-oscd/fuzzer.py b/fuzzers/LIFCL/093-oscd/fuzzer.py new file mode 100644 index 0000000..44a0ced --- /dev/null +++ b/fuzzers/LIFCL/093-oscd/fuzzer.py @@ -0,0 +1,57 @@ +from fuzzconfig import FuzzConfig +import nonrouting +import fuzzloops +import re +from interconnect import fuzz_interconnect, fuzz_interconnect_pins +import tiles + +from primitives import oscd_core + +cfgs = [ + FuzzConfig(job="OSCD", device="LIFCL-33U", tiles=["CIB_R0C29:OSCD"]), +] + +def main(): + for cfg in cfgs: + cfg.setup() + empty = cfg.build_design(cfg.sv, {}) + cfg.sv = "oscd.v" + + def bin_to_int(x): + val = 0 + mul = 1 + for bit in x: + if bit: + val |= mul + mul *= 2 + return val + + sites = tiles.get_sites_from_primitive(cfg.device, "OSCD_CORE") + site = list(sites.keys())[0] + + def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): + if kv is None: + config = "" + elif mux: + val = "#SIG" + if kv[1] in ("0", "1"): + val = kv[1] + if kv[1] == "INV": + val = "#INV" + config = "{}::::{}={}".format(mode, kv[0], val) + else: + config = "{}:::{}={}".format(mode, kv[0], kv[1]) + return dict(mode=mode, cmt="//" if mode == "NONE" else "", config=config, site=site) + + nonrouting.fuzz_primitive_definition(cfg, empty, site, oscd_core) + + cfg.sv = "../shared/route.v" + regex = True + nodes = [".*_OSCD_CORE" ] + full_mux = True + + fuzz_interconnect(config=cfg, nodenames=nodes, regex=regex, bidir=True, full_mux_style=full_mux) + + +if __name__ == '__main__': + main() diff --git a/fuzzers/LIFCL/093-oscd/oscd.v b/fuzzers/LIFCL/093-oscd/oscd.v new file mode 100644 index 0000000..27a68fc --- /dev/null +++ b/fuzzers/LIFCL/093-oscd/oscd.v @@ -0,0 +1,11 @@ +(* \db:architecture ="${arch}", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + ${cmt} (* \dm:primitive ="OSCD_CORE", \dm:programming ="MODE:OSCD_CORE ${config}", \dm:site ="${site}" *) + ${cmt} OSCD_CORE OSC_I ( ); + + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule diff --git a/fuzzers/LIFCL/shared/route.v b/fuzzers/LIFCL/shared/route.v new file mode 100644 index 0000000..3c48e56 --- /dev/null +++ b/fuzzers/LIFCL/shared/route.v @@ -0,0 +1,10 @@ +(* \db:architecture ="${arch}", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + (* \xref:LOG ="q_c@0@9"${arcs_attr} *) + wire q; + + (* \dm:cellmodel_primitives ="REG0=reg", \dm:primitive ="SLICE", \dm:programming ="MODE:LOGIC Q0:Q0 ", \dm:site ="R2C2A" *) + SLICE SLICE_I ( .A0(q), .Q0(q) ); +endmodule diff --git a/generate_database.sh b/generate_database.sh index ddcc43a..aded20a 100755 --- a/generate_database.sh +++ b/generate_database.sh @@ -3,15 +3,15 @@ . user_environment.sh pushd tools -#python3 tilegrid_all.py +python3 tilegrid_all.py popd pushd fuzzers for dir in LIFCL/* ; do - if [ -d "$dir" ]; then + if [ -f "$dir/fuzzer.py" ]; then echo "=================== Entering $dir ===================" pushd $dir - python3 fuzzer.py 2>&1 | tee >(gzip --stdout > fuzzer.log.gz) || true + python3 fuzzer.py 2>&1 | tee >(gzip --stdout > fuzzer.log.gz) popd fi -done \ No newline at end of file +done diff --git a/libprjoxide/pyprjoxide/Cargo.toml b/libprjoxide/pyprjoxide/Cargo.toml index 3bc8c3c..8814cab 100644 --- a/libprjoxide/pyprjoxide/Cargo.toml +++ b/libprjoxide/pyprjoxide/Cargo.toml @@ -5,6 +5,8 @@ edition = "2018" [dependencies] prjoxide = { path = "../prjoxide" } +log = "0.4.29" +env_logger = "0.11.8" [dependencies.pyo3] version = "0.13.1" diff --git a/libprjoxide/pyprjoxide/src/lib.rs b/libprjoxide/pyprjoxide/src/lib.rs index ebd9883..91e7b76 100644 --- a/libprjoxide/pyprjoxide/src/lib.rs +++ b/libprjoxide/pyprjoxide/src/lib.rs @@ -375,6 +375,8 @@ fn classify_pip(src_x: i32, src_y: i32, src_name: &str, dst_x: i32, dst_y: i32, #[pymodule] fn libpyprjoxide(_py: Python, m: &PyModule) -> PyResult<()> { + env_logger::init(); + m.add_wrapped(wrap_pyfunction!(parse_bitstream))?; m.add_wrapped(wrap_pyfunction!(write_tilegrid_html))?; m.add_wrapped(wrap_pyfunction!(write_region_html))?; diff --git a/radiant.sh b/radiant.sh index 415b310..e79de1e 100755 --- a/radiant.sh +++ b/radiant.sh @@ -31,7 +31,7 @@ case "${PART}" in PACKAGE="${DEV_PACKAGE:-WLCSP84}" DEVICE="LIFCL-33" LSE_ARCH="lifcl" - SPEED_GRADE="${SPEED_GRADE:-7_High-Performance_1.0V}" + SPEED_GRADE="${SPEED_GRADE:-8_High-Performance_1.0V}" ;; LIFCL-33U) PACKAGE="${DEV_PACKAGE:-FCCSP104}" diff --git a/tools/extract_tilegrid.py b/tools/extract_tilegrid.py index 6c95e07..bb8b2af 100644 --- a/tools/extract_tilegrid.py +++ b/tools/extract_tilegrid.py @@ -82,18 +82,22 @@ def main(argv): tap_frame_to_col = get_tf2c(args.device) def fixup(tiletype): - if (args.device.find("-33") > 0): + if args.device.find("-33") > 0: tiletypes_with_variants = ["LRAM_", "SYSIO_B1_DED", "SPINE_"] for v in tiletypes_with_variants: if tiletype.startswith(v): return tiletype + "_33K" + elif args.device.find("-33U") > 0: + if tiletype == "OSC": + return "OSCD" + return tiletype for line in args.infile: tile_m = tile_re.match(line) if tile_m: name = tile_m.group(6) - tiletype = fixup(tile_m.group(1)), + tiletype = fixup(tile_m.group(1)) current_tile = { "tiletype": tiletype, "start_bit": int(tile_m.group(4)), @@ -114,6 +118,7 @@ def fixup(tiletype): else: current_tile["y"] = int(s.group(1)) current_tile["x"] = int(s.group(2)) + identifier = name + ":" + tiletype assert identifier not in tiles tiles[identifier] = current_tile diff --git a/util/common/database.py b/util/common/database.py index 504374b..42c8ddb 100644 --- a/util/common/database.py +++ b/util/common/database.py @@ -58,7 +58,11 @@ def get_tilegrid(family, device = None): tgjson = path.join(get_db_subdir(family, device), "tilegrid.json") if path.exists(tgjson): with open(tgjson, "r") as f: - _tilegrids[device] = json.load(f) + try: + _tilegrids[device] = json.load(f) + except: + print(f"Exception encountered reading {tgjson}") + raise else: _tilegrids[device] = {"tiles":{}} return _tilegrids[device] diff --git a/util/common/lapie.py b/util/common/lapie.py index 6100060..6a2d640 100644 --- a/util/common/lapie.py +++ b/util/common/lapie.py @@ -60,7 +60,7 @@ def run(commands, workdir=None, stdout=None): env = os.environ.copy() env[dev_enable_name] = "1" - result_struct = run_bash_script(env, rcmd_path, tcltool, scriptfile, cwd=workdir) + result_struct = run_bash_script(env, rcmd_path, tcltool, scriptfile, cwd=workdir, stdout=stdout) result = result_struct.returncode diff --git a/util/common/radiant.py b/util/common/radiant.py index 2a94cc3..fafda43 100644 --- a/util/common/radiant.py +++ b/util/common/radiant.py @@ -9,7 +9,7 @@ import database import sys -def run_bash_script(env, *args, cwd = None): +def run_bash_script(env, *args, cwd = None, stdout = subprocess.PIPE, stderr = subprocess.PIPE): slug = " ".join(args[1:]) logging.info("Running script: %s", slug) @@ -17,8 +17,8 @@ def run_bash_script(env, *args, cwd = None): args=["bash", *args], env=env, cwd=cwd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, + stdout=stdout, + stderr=subprocess.PIPE, ) stdout, stderr = proc.stdout, proc.stderr @@ -28,8 +28,9 @@ def run_bash_script(env, *args, cwd = None): if show_output or True: for stream in [("", stdout, sys.stdout), ("ERR:", stderr, sys.stdout)]: - for l in stream[1].decode().splitlines(): - print(f"[{stream[0]} {slug}] {l}", file=stream[2]) + if stream[1] is not None: + for l in stream[1].decode().splitlines(): + print(f"[{stream[0]} {slug}] {l}", file=stream[2]) if returncode != 0: raise Exception(f"Error encountered running radiant: {slug}") diff --git a/util/common/tiles.py b/util/common/tiles.py index df4a572..e020695 100644 --- a/util/common/tiles.py +++ b/util/common/tiles.py @@ -270,7 +270,7 @@ def pips(r): (p.from_wire, p.to_wire) for (n,r) in routes.items() - for p in pips() + for p in pips(r) ])) def get_connected_tiles(device, tilename): diff --git a/util/fuzz/fuzzconfig.py b/util/fuzz/fuzzconfig.py index 7421912..c61aa40 100644 --- a/util/fuzz/fuzzconfig.py +++ b/util/fuzz/fuzzconfig.py @@ -21,7 +21,7 @@ def get_db(): _platform_skip_warnings = set() def should_fuzz_platform(device): - if PLATFORM_FILTER is not None and PLATFORM_FILTER != device: + if PLATFORM_FILTER is not None and PLATFORM_FILTER not in device: if device not in _platform_skip_warnings: print(f"FUZZER_PLATFORM set to {PLATFORM_FILTER}, skipping {device}") _platform_skip_warnings.add(device) @@ -43,9 +43,9 @@ def __init__(self, device, job, tiles, sv = None): if sv is None: family = device.split("-")[0] suffix = device.split("-")[1] - sv = database.get_db_root() + f"/../fuzzers/{family}/shared/empty_{suffix}.v" + sv = database.get_db_root() + f"/../fuzzers/{family}/shared/empty.v" self.sv = sv - self.rbk_mode = True if self.device == "LFCPNX-100" else False + self.rbk_mode = True if self.device == "LFCPNX-100" or self.device == "LIFCL-33U" else False self.struct_mode = True self.udb_specimen = None @@ -63,9 +63,9 @@ def serialize_deltas(self, fz, prefix = ""): def check_deltas(self, name): if os.path.exists(f"./.deltas/{name}{self.job}_{self.device}.ron"): - print(f"Delta exists for {name} {self.job} {self.device}; skipping") + logging.info(f"Delta exists for {name} {self.job} {self.device}; skipping") return True - print(f"./.deltas/{name}{self.job}_{self.device}.ron miss") + logging.debug(f"./.deltas/{name}{self.job}_{self.device}.ron miss") return False def solve(self, fz): diff --git a/util/fuzz/nonrouting.py b/util/fuzz/nonrouting.py index 5947353..6ebe551 100644 --- a/util/fuzz/nonrouting.py +++ b/util/fuzz/nonrouting.py @@ -9,6 +9,9 @@ import fuzzloops import os +from primitives import EnumSetting + + def fuzz_word_setting(config, name, length, get_sv_substs, desc=""): """ Fuzz a multi-bit setting, such as LUT initialisation @@ -130,4 +133,32 @@ def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, des config.solve(fz) - + +def fuzz_primitive_definition(cfg, empty, site, primitive, mark_relative_to = None, mode_name = None, get_substs=None): + def default_get_substs(mode="NONE", kv=None): + if kv is None: + config = "" + else: + key = kv[0] + if key.endswith("MUX"): + key = ":" + key[:-3] + config = f"{mode}:::{key}={kv[1]}" + return dict(cmt="//" if mode == "NONE" else "", + config=config, + site=site) + if get_substs is None: + get_substs = default_get_substs + + if mode_name is None: + mode_name = primitive.name + + for setting in primitive.settings: + subs_fn = lambda x, name=setting.name: get_substs(mode=mode_name, kv=(name, x)) + if setting.name == "MODE": + subs_fn = lambda x: get_substs(mode=x) + + if isinstance(setting, EnumSetting): + fuzz_enum_setting(cfg, empty, f"{mode_name}.{setting.name}", setting.values, subs_fn,False, + desc=setting.desc, mark_relative_to=mark_relative_to) + + diff --git a/util/fuzz/primitives.py b/util/fuzz/primitives.py index 294a799..2bc5aee 100644 --- a/util/fuzz/primitives.py +++ b/util/fuzz/primitives.py @@ -1,13 +1,15 @@ class PinSetting: - def __init__(self, name, dir): + def __init__(self, name, dir, desc=""): self.name = name + self.dir = dir + self.desc = desc class WordSetting: - def __init__(self, name, states, desc=""): + def __init__(self, name, bits, desc=""): self.name = name - self.states = states + self.bits = bits self.desc = desc class EnumSetting: @@ -25,7 +27,7 @@ def __init__(self, name, settings, pins = []): def build(self, ctx, site, programming, **kwargs): return f""" - (* \dm:primitive ="{self.name}", \dm:programming ="MODE:OSC_CORE ${config}", \dm:site ="{site}" *) + (* \\dm:primitive ="{self.name}", \\dm:programming ="MODE:OSC_CORE ${config}", \\dm:site ="{site}" *) {self.name} {self.name}_{idx} ( ); """ @@ -87,9 +89,78 @@ def build(self, ctx, site, programming, **kwargs): osc_core = PrimitiveDefinition( "OSC_CORE", - [], + [ + EnumSetting("MODE", ["NONE", "OSC_CORE"], + desc="OSC_CORE primitive mode"), + WordSetting("HF_CLK_DIV", 8, + desc="high frequency oscillator output divider"), + WordSetting("HF_SED_SEC_DIV", 8, + desc="high frequency oscillator output divider"), + EnumSetting("DTR_EN", ["ENABLED", "DISABLED"]), + EnumSetting("HF_FABRIC_EN", ["ENABLED", "DISABLED"], + desc="enable HF oscillator trimming from input pins"), + EnumSetting("HF_OSC_EN", ["ENABLED", "DISABLED"], + desc="enable HF oscillator"), + EnumSetting("HFDIV_FABRIC_EN", ["ENABLED", "DISABLED"], + desc="enable HF divider from parameter"), + EnumSetting("LF_FABRIC_EN", ["ENABLED", "DISABLED"], + desc="enable LF oscillator trimming from input pins"), + EnumSetting("LF_OUTPUT_EN", ["ENABLED", "DISABLED"], + desc="enable LF oscillator output"), + EnumSetting("DEBUG_N", ["ENABLED", "DISABLED"], + desc="enable debug mode"), + ], [ PinSetting("HFCLKOUT", "out"), PinSetting("HFSDSCEN", "in") ] ) + +oscd_core = PrimitiveDefinition( + name="OSCD_CORE", + settings = [ + EnumSetting("MODE", ["NONE", "OSCD_CORE"], desc="OSC_CORED primitive mode"), + EnumSetting("DTR_EN", ["ENABLED", "DISABLED"], + desc="DTR block enable from MIB"), + + WordSetting("HF_CLK_DIV", 8, + desc="HF oscillator user output divider (div2–div256)"), + + WordSetting("HF_SED_SEC_DIV", 8, + desc="HF oscillator SED/secondary divider (div2–div256)"), + + EnumSetting("HF_FABRIC_EN", ["ENABLED", "DISABLED"], + desc="HF oscillator trim source mux select"), + + EnumSetting("HF_OSC_EN", ["ENABLED", "DISABLED"], + desc="HF oscillator enable"), + + EnumSetting("LF_FABRIC_EN", ["ENABLED", "DISABLED"], + desc="LF oscillator trim source mux select"), + + EnumSetting("LF_OUTPUT_EN", ["ENABLED", "DISABLED"], + desc="LF clock output enable"), + + EnumSetting("DEBUG_N", ["ENABLED", "DISABLED"], + desc="Ignore SLEEP/STOP during USER mode when disabled"), + ], + pins=[ + PinSetting("HFOUTEN", dir="in", + desc="HF clock (225MHz) output enable (test only)"), + PinSetting("HFSDSCEN", dir="in", + desc="HF user clock output enable"), + PinSetting("HFOUTCIBEN", dir="in", + desc="CIB control to enable/disable HF oscillator during user mode"), + PinSetting("REBOOT", dir="in", + desc="CIB control to enable/disable hf_clk_config output"), + PinSetting("HFCLKOUT", dir="out", + desc="450MHz with programmable divider (2–256) to user"), + PinSetting("LFCLKOUT", dir="out", + desc="Low frequency clock output after div4 (32kHz)"), + PinSetting("HFCLKCFG", dir="out", + desc="450MHz clock to configuration block"), + PinSetting("HFSDCOUT", dir="out", + desc="450MHz with programmable divider (2–256) to configuration"), + ], + +) \ No newline at end of file From 853b29313d501aa228798854f3789085ad949c5d Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Mon, 19 Jan 2026 14:56:56 -0700 Subject: [PATCH 11/29] Fix 110 --- fuzzers/LIFCL/110-global-structure/fuzzer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fuzzers/LIFCL/110-global-structure/fuzzer.py b/fuzzers/LIFCL/110-global-structure/fuzzer.py index 1fd0c40..0d57a3a 100644 --- a/fuzzers/LIFCL/110-global-structure/fuzzer.py +++ b/fuzzers/LIFCL/110-global-structure/fuzzer.py @@ -41,6 +41,7 @@ def save_db(): max_row = device_info["max_row"] max_col = device_info["max_col"] + tg = database.get_tilegrid(cfg.device)["tiles"] tap_plcs = set([v['x'] for k, v in tg.items() if v["tiletype"].startswith("TAP_PLC")]) # Determine branch driver locations From d6b924f0185d101d391c9a12cab248107d461d86 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Tue, 27 Jan 2026 10:13:59 -0700 Subject: [PATCH 12/29] Incorporate env logger and py logger. Add more methods for adding deltas; refactor logic for tiletypes which configure adjacent tiles --- fuzzers/LIFCL/004-tile-routing/fuzzer.py | 315 +++++++++++++++++++++ fuzzers/LIFCL/005-site-routing/fuzzer.py | 0 fuzzers/LIFCL/005-site-routing/primitive.v | 0 libprjoxide/prjoxide/Cargo.toml | 1 + libprjoxide/prjoxide/src/bels.rs | 84 +++--- libprjoxide/prjoxide/src/bin/prjoxide.rs | 2 + libprjoxide/prjoxide/src/bitstream.rs | 52 ++-- libprjoxide/prjoxide/src/chip.rs | 32 ++- libprjoxide/prjoxide/src/database.rs | 76 +++-- libprjoxide/prjoxide/src/fuzz.rs | 42 ++- libprjoxide/prjoxide/src/ipfuzz.rs | 19 +- libprjoxide/prjoxide/src/wires.rs | 8 +- libprjoxide/pyprjoxide/Cargo.toml | 1 + libprjoxide/pyprjoxide/src/lib.rs | 26 +- tools/parse_webdoc.py | 0 util/common/cachecontrol.py | 0 16 files changed, 547 insertions(+), 111 deletions(-) create mode 100644 fuzzers/LIFCL/004-tile-routing/fuzzer.py create mode 100644 fuzzers/LIFCL/005-site-routing/fuzzer.py create mode 100644 fuzzers/LIFCL/005-site-routing/primitive.v create mode 100644 tools/parse_webdoc.py create mode 100644 util/common/cachecontrol.py diff --git a/fuzzers/LIFCL/004-tile-routing/fuzzer.py b/fuzzers/LIFCL/004-tile-routing/fuzzer.py new file mode 100644 index 0000000..a3e23a1 --- /dev/null +++ b/fuzzers/LIFCL/004-tile-routing/fuzzer.py @@ -0,0 +1,315 @@ +import logging +import shutil +import sys +import time +import traceback +from collections import defaultdict +from concurrent.futures import Future + +import fuzzconfig +import fuzzloops +import interconnect +import libpyprjoxide +import nonrouting +import primitives +import radiant +import tiles +from cachier import cachier +from fuzzconfig import FuzzConfig, get_db +from interconnect import fuzz_interconnect_sinks + +import database + +tiletypes = set() + +overlapping_tile_types = set(["CIB", "MIB_B_TAP"] + + [f"BANKREF{i}" for i in range(16)] + + [f"BK{i}_15K" for i in range(16)] + ) + +def get_site_tiles(device, site): + site_tiles = [tile for tile in tiles.get_tiles_by_rc(device, site) if + tile.split(":")[1] not in overlapping_tile_types] + + return site_tiles + +def site_differences(device, bitfile, baseline = None): + if baseline is None: + baseline = FuzzConfig.standard_empty(device) + + deltas = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, baseline).delta(fuzzconfig.db, bitfile) + + ip_values = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, bitfile).get_ip_values() + ip_values = [(a,v) for a,v in ip_values if v != 0] + + power_tile_types = set(["PMU"] + [f"BANKREF{i}" for i in range(16)]) + pmu_tiles = [x for x in list(deltas.keys()) if x.split(":")[-1] in power_tile_types] + driving_tiles = [x for x in list(deltas.keys()) if x.split(":")[-1] not in power_tile_types] + + tile = driving_tiles[0] + + return (driving_tiles + pmu_tiles), ip_values + + +@cachier(separate_files=True, cache_dir='.cachier') +def find_relevant_tiles(device, primitive_config, site, site_type): + cfg = FuzzConfig(job=f"{site}", device=device, tiles=[]) + + empty_file = FuzzConfig.standard_empty(device) + + primitive_type = cfg.build_design("./primitive.v", { + "config": primitive_config, + "site": site, + "site_type": site_type, + "extra": "", + "signals": "" + }, prefix=site + "/") + + deltas = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, empty_file).delta(fuzzconfig.db, primitive_type) + + ip_values = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, primitive_type).get_ip_values() + ip_values = [(a,v) for a,v in ip_values if v != 0] + + power_tile_types = set(["PMU"] + [f"BANKREF{i}" for i in range(16)]) + pmu_tiles = [x for x in list(deltas.keys()) if x.split(":")[-1] in power_tile_types] + + delta_sorted = [x[0] for x in sorted(deltas.items(), key=lambda x: -len(x[1]))] + driving_tiles = [x for x in delta_sorted if x.split(":")[-1] not in power_tile_types] + print(delta_sorted) + + # single_driving_type_check = site_type in ["PCLKDIV", "ECLKDIV_CORE", "DIFFIO18_CORE"] or len(driving_tiles) == 1 + # if not single_driving_type_check: + # raise Exception(f"{site_type} should have single driving tile but it has {driving_tiles}. {deltas}") + + tile = driving_tiles[0] + + site_tiles = [tile for tile in tiles.get_tiles_by_rc(device, site) if + tile.split(":")[1] not in overlapping_tile_types] + + # This happens for DCC, DCS + if len(site_tiles) == 0: + site_tiles = driving_tiles + + return (driving_tiles + pmu_tiles), site_tiles, ip_values + +def map_local_pips(name, device, ts, pips, local_graph, executor = None): + cfg = FuzzConfig(job=name, sv="../shared/route.v", device=device, tiles=ts) + + external_nodes = [wire for pip in pips for wire in pip if wire not in local_graph] + + # CIB is routed separately + cfg.tiles.extend( + [tile for n in external_nodes for tile in tiles.get_tiles_by_rc(device, n) if tile.split(":")[-1] != "CIB"]) + + return fuzz_interconnect_sinks(cfg, pips, False, executor = executor) + +def map_primitive_settings(device, ts, site, site_tiles, site_type, ip_values, executor = None): + if site_type not in primitives.primitives: + return [] + + empty_file = FuzzConfig.standard_empty(device) + + base_addrs = database.get_base_addrs(device) + + if site not in base_addrs: + ip_values = [] + + is_ip_config = len(ip_values) > 0 + if len(ip_values): + fuzz_enum_setting = nonrouting.fuzz_ip_enum_setting + fuzz_word_setting = nonrouting.fuzz_ip_word_setting + else: + fuzz_enum_setting = nonrouting.fuzz_enum_setting + fuzz_word_setting = nonrouting.fuzz_word_setting + + def map_mode(mode): + logging.info(f"====== {mode.mode} : {site_type} IP: {len(ip_values)} ==========") + related_tiles = (ts + site_tiles) + cfg = FuzzConfig(job=f"config/{site}/{mode.mode}", device=device, sv="primitive.v", tiles= related_tiles if len(ip_values) == 0 else [f"{site}:{site_type}"]) + + slice_sites = tiles.get_tiles_by_tiletype(device, "PLC") + slice_iter = iter([x for x in slice_sites if tiles.get_rc_from_name(device, x) not in related_tiles]) + + extra_lines = [] + signals = [] + + avail_in_pins = [] + for p in mode.pins: + if p.dir == "in" or p.dir == "inout": + for r in range(0, p.bits if p.bits is not None else 1): + suffix = str(r) if p.bits != None else "" + avail_in_pins.append(f"{p.name}{suffix}") + q_driver = None + def get_sink_pin(): + if len(avail_in_pins): + in_pin = avail_in_pins.pop() + extra_lines.append(f"wire q_{in_pin};") + signals.append(f".{in_pin}(q_{in_pin})") + return f"q_{in_pin}" + + idx = len(extra_lines) + extra_lines.append(f""" + wire q_{idx}; + (* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) + SLICE SLICE_I_{idx} ( .A0(q_{idx}) ); + """) + return f"q_{idx}" + + for p in mode.pins: + for r in range(0, p.bits if p.bits is not None else 1): + suffix = str(r) if p.bits != None else "" + if p.dir == "out": + q = get_sink_pin() + q_driver = q + signals.append(f".{p.name}{suffix}({q})") + + if len(avail_in_pins) and q_driver is None: + extra_lines.append(f""" + wire q_driver; + (* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) + SLICE SLICE_I_driver ( .A0(q_driver), .Q0(q_driver) ); + """) + q_driver = "q_driver" + + for undriven_pin in avail_in_pins: + signals.append(f".{undriven_pin}({q_driver})") + + subs = { + "site": site, + "site_type": site_type, + "extra": "\n".join(extra_lines), + "signals": ", ".join(signals) + } + + def map_mode_setting(setting): + mark_relative_to = None + if site_tiles[0] != ts[0]: + mark_relative_to = site_tiles[0] + + args = { + "config": cfg, + "name": f"{mode.mode}.{setting.name}", + "desc": setting.desc, + "executor": executor + } + + if isinstance(setting, primitives.EnumSetting): + def subs_fn(val): + return subs | {"config": mode.configuration([(setting, val)])} + + if len(ip_values) == 0: + args["mark_relative_to"] = mark_relative_to + + if isinstance(setting, primitives.ProgrammablePin) and not is_ip_config: + args["include_zeros"] = True + + return fuzz_enum_setting(empty_bitfile = empty_file, values = setting.values, get_sv_substs = subs_fn, **args) + elif isinstance(setting, primitives.WordSetting): + def subs_fn(val): + return subs | {"config": mode.configuration([(setting, nonrouting.fuzz_intval(val))])} + + return fuzz_word_setting(length=setting.bits, get_sv_substs=subs_fn, **args) + else: + raise Exception(f"Unknown setting type: {setting}") + + return [map_mode_setting(s) for s in mode.settings] + + return [f for mode in primitives.primitives[site_type] for f in map_mode(mode)] + +def run_for_device(device, executor = None): + if not fuzzconfig.should_fuzz_platform(device): + return + + sites = database.get_sites(device) + + def find_relevant_tiles_for_site(site, site_info): + site_type = site_info["type"] + + primitive = primitives.primitives[site_type][0] + + return find_relevant_tiles(device, primitive.fill_config(), site, site_type) + + def find_relevant_tiles_for_site_without_primitive(site, site_info): + + pips, local_graph = tiles.get_local_pips_for_site(device, site) + cfg = FuzzConfig(job=f"{site}", device=device, tiles=[]) + + all_wires_bit = interconnect.create_wires_file(cfg, pips, prefix = site) + + (driving_tiles, ip_delta) = site_differences(device, all_wires_bit) + site_tiles = get_site_tiles(device, site) + + return (driving_tiles, site_tiles, ip_delta) + + def per_site(site, site_info, relevant_tile_info): + site_type = site_info["type"] + + (driving_tiles, site_tiles, ip_values) = relevant_tile_info + + tiletype = driving_tiles[0].split(":")[1] + if tiletype in tiletypes: + return [] + + tiletypes.add(tiletype) + + logging.info(f"====== {site} : {tiletype} ==========") + pips, local_graph = tiles.get_local_pips_for_site(device, site) + pips_future = map_local_pips(site, device, driving_tiles + site_tiles, pips, local_graph, executor=executor) + + # Map primitive parameter settings + settings_future = map_primitive_settings(device, driving_tiles + site_tiles, site, site_tiles, site_type, ip_values, executor = executor) + + return [pips_future, settings_future] + + device_futures = [] + for site, site_info in sorted(sites.items()): + site_type = site_info["type"] + + if len(sys.argv) > 1 and sys.argv[1] != site_type: + continue + + if site_type in ["PLL_CORE"] and device in ["LIFCL-33U"]: + logging.warning(f"Can't map out IP core f{site_type} with device {device} which is in readback mode") + continue + + f = None + if site_type not in primitives.primitives: + continue + f = executor.submit(find_relevant_tiles_for_site_without_primitive, site, site_info) + + else: + f = executor.submit(find_relevant_tiles_for_site, site, site_info) + + f.name = "Find tiles" + site_future = fuzzloops.chain(f, lambda fut, site=site, site_info=site_info: per_site(site, site_info, fut), "Map site") + + device_futures.extend([f, site_future]) + + return device_futures + +def main(): + get_db() + + families = database.get_devices()["families"] + devices = sorted([ + device + for family in families + for device in families[family]["devices"] + ]) + + all_sites = set([site_info["type"] + for device in devices + for site,site_info in database.get_sites(device).items() + ]) + + if len(sys.argv) > 1 and sys.argv[1] not in all_sites: + logging.warning(f"Site filter doesn't match any known sites") + print(sorted(all_sites)) + + return + + fuzzloops.FuzzerMain(lambda executor: [ run_for_device(device, executor) for device in devices ]) + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + main() diff --git a/fuzzers/LIFCL/005-site-routing/fuzzer.py b/fuzzers/LIFCL/005-site-routing/fuzzer.py new file mode 100644 index 0000000..e69de29 diff --git a/fuzzers/LIFCL/005-site-routing/primitive.v b/fuzzers/LIFCL/005-site-routing/primitive.v new file mode 100644 index 0000000..e69de29 diff --git a/libprjoxide/prjoxide/Cargo.toml b/libprjoxide/prjoxide/Cargo.toml index 7d5c54e..1486ec9 100644 --- a/libprjoxide/prjoxide/Cargo.toml +++ b/libprjoxide/prjoxide/Cargo.toml @@ -24,6 +24,7 @@ clap = { version = "3.1", features = ["derive"] } include_dir = "0.6.0" capnp = {version = "0.14", optional = true } flate2 = {version = "1.0" } +env_logger = "0.11.8" [build-dependencies] capnpc = {version = "0.14", optional = true } diff --git a/libprjoxide/prjoxide/src/bels.rs b/libprjoxide/prjoxide/src/bels.rs index 0362612..39128a0 100644 --- a/libprjoxide/prjoxide/src/bels.rs +++ b/libprjoxide/prjoxide/src/bels.rs @@ -3,6 +3,7 @@ use crate::database::TileBitsDatabase; use itertools::Itertools; use std::collections::BTreeSet; use std::convert::TryInto; +use log::{debug, warn}; // A reference to a wire in a relatively located tile #[derive(Clone)] @@ -406,7 +407,7 @@ impl Bel { } } - pub fn make_seio18_relative(z: usize, rel: (i32, i32)) -> Bel { + pub fn make_seio18(z: usize) -> Bel { let ch = Z_TO_CHAR[z]; let postfix = if z == 1 { format!("SEIO18_CORE_IO{}", ch) @@ -440,15 +441,12 @@ impl Bel { output!(&postfix, "INLP", "DPHY LP mode input buffer output"), output!(&postfix, "INADC", "analog signal out to ADC"), ], - rel_x: rel.0, - rel_y: rel.1, + rel_x: 0, + rel_y: 0, z: z as u32, } } - pub fn make_seio18(z: usize) -> Bel { - Self::make_seio18_relative(z, (0, 0)) - } pub fn make_diffio18() -> Bel { let postfix = format!("DIFFIO18_CORE_IOA"); @@ -652,6 +650,14 @@ impl Bel { z: 0, } } + + pub fn with_rel(self, rel: (i32, i32)) -> Bel { + Bel { + rel_x: rel.0, + rel_y: rel.1, + ..self + } + } } pub fn get_tile_bels(tiletype: &str, tiledata: &TileBitsDatabase) -> Vec { @@ -771,59 +777,69 @@ pub fn get_tile_bels(tiletype: &str, tiledata: &TileBitsDatabase) -> Vec { "MIPI_DPHY_0" => vec![Bel::make_dphy_core("TDPHY_CORE2", &tiledata, -2, 0)], "MIPI_DPHY_1" => vec![Bel::make_dphy_core("TDPHY_CORE26", &tiledata, -2, 0)], + "MIB_T_TAP" | "TAP_CIBT" | "TAP_CIB" | "TAP_PLC" | "CIB" | "MIB_LR" => vec![], // Silence warnings _ => { - let bel_relative_location = tiledata.bel_relative_location.unwrap_or((0, 0)); - if bel_relative_location == (0, 0) { - let enum_bels = tiledata.enums.iter().flat_map(|(key, _value)|match key.as_str() { - "PIOA.BASE_TYPE" => vec![Bel::make_seio33(0)], - "PIOB.BASE_TYPE" => vec![Bel::make_seio33(1)], - "PIOA.SEIO18.BASE_TYPE" => vec![Bel::make_seio18_relative(0, bel_relative_location)], - "PIOB.SEIO18.BASE_TYPE" => vec![Bel::make_seio18_relative(1, bel_relative_location)], - "PIOA.DIFFIO18.BASE_TYPE" => vec![Bel::make_diffio18()], - "PIOB.DIFFIO18.BASE_TYPE" => vec![Bel::make_diffio18()], - "SIOLOGICA.GSR" => vec![Bel::make_iol(tiledata, true, 0)], - "SIOLOGICB.GSR" => vec![Bel::make_iol(tiledata, true, 1)], - "IOLOGICA.GSR" => vec![Bel::make_iol(tiledata, false, 0)], - "IOLOGICB.GSR" => vec![Bel::make_iol(tiledata, false, 1)], - _ => vec![] - }); - let inferred_bels = enum_bels.collect_vec(); - if inferred_bels.is_empty() { - println!("No BEL for tile type {}", &stt); - } - inferred_bels - } else { - vec![] + let bel_relative_location = tiledata.tile_configures_external_tiles.iter().next().cloned().unwrap_or(("".to_string(), 0, 0)); + + let enum_bels = tiledata.enums.iter().flat_map(|(key, _value)|match key.as_str() { + "PIOA.BASE_TYPE" => vec![Bel::make_seio33(0)], + "PIOB.BASE_TYPE" => vec![Bel::make_seio33(1)], + "PIOA.SEIO18.BASE_TYPE" => vec![Bel::make_seio18(0)], + "PIOB.SEIO18.BASE_TYPE" => vec![Bel::make_seio18(1)], + "PIOA.DIFFIO18.BASE_TYPE" => vec![Bel::make_diffio18()], + "PIOB.DIFFIO18.BASE_TYPE" => vec![Bel::make_diffio18()], + "SIOLOGICA.GSR" => vec![Bel::make_iol(tiledata, true, 0)], + "SIOLOGICB.GSR" => vec![Bel::make_iol(tiledata, true, 1)], + "IOLOGICA.GSR" => vec![Bel::make_iol(tiledata, false, 0)], + "IOLOGICB.GSR" => vec![Bel::make_iol(tiledata, false, 1)], + _ => vec![] + }).map(|x| { + x.with_rel((bel_relative_location.1, bel_relative_location.2)) + }); + let inferred_bels = enum_bels.collect_vec(); + if inferred_bels.is_empty() { + debug!("No BEL for tile type {}", &stt); } + inferred_bels }, } } // Get the tiles that a bel's configuration might be split across -pub fn get_bel_tiles(chip: &Chip, tile: &Tile, bel: &Bel) -> Vec { +pub fn get_bel_tiles(chip: &Chip, tile: &Tile, bel: &Bel, rel: &Option<(String, i32, i32)>) -> Vec { let tn = tile.name.to_string(); let rel_tile = |dx: i32, dy: i32, tt: &str| { chip.tile_by_xy_type((tile.x as i32 + dx).try_into().unwrap(), (tile.y as i32 + dy).try_into().unwrap(), tt).unwrap().name.to_string() }; - let rel_tile_prefix = |dx, dy, tt_prefix| { - for tile in chip.tiles_by_xy(tile.x + dx, tile.y + dy).iter() { + let rel_tile_prefix = |dx:i32, dy:i32, tt_prefix| { + let nx = tile.x.checked_add_signed(dx).unwrap(); + let ny = tile.y.checked_add_signed(dy).unwrap(); + for tile in chip.tiles_by_xy(nx, ny).iter() { if tile.tiletype.starts_with(tt_prefix) { return tile.name.to_string(); } } - panic!("no tile matched prefix ({}, {}, {})", tile.x + dx, tile.y + dy, tt_prefix); + warn!("no tile matched prefix ({}, {}, {}) for {}", nx, ny, tt_prefix, tile.name); + "".to_string() }; let rel_tile_suffix = |dx, dy, tt_suffix| { - for tile in chip.tiles_by_xy(tile.x + dx, tile.y + dy).iter() { + let nx = tile.x.checked_add_signed(dx).unwrap(); + let ny = tile.y.checked_add_signed(dy).unwrap(); + for tile in chip.tiles_by_xy(nx, ny).iter() { if tile.tiletype.ends_with(tt_suffix) { return tile.name.to_string(); } } - panic!("no tile matched suffix ({}, {}, {})", tile.x + dx, tile.y + dy, tt_suffix); + warn!("no tile matched suffix ({}, {}, {}) for {}", nx, ny, tt_suffix, tile.name); + "".to_string() }; + if let Some(rel_offset) = rel { + return vec![rel_tile_suffix(rel_offset.1, rel_offset.2, "")]; + } + let tt = &tile.tiletype[..]; match &bel.beltype[..] { "SEIO33_CORE" | "SIOLOGIC" => match tt { diff --git a/libprjoxide/prjoxide/src/bin/prjoxide.rs b/libprjoxide/prjoxide/src/bin/prjoxide.rs index 5c3b849..ce66834 100644 --- a/libprjoxide/prjoxide/src/bin/prjoxide.rs +++ b/libprjoxide/prjoxide/src/bin/prjoxide.rs @@ -205,6 +205,8 @@ impl InterchangeExport { } fn main() -> Result<()> { + env_logger::init(); + let opts: Opts = Opts::parse(); match opts.subcmd { SubCommand::Pack(t) => { diff --git a/libprjoxide/prjoxide/src/bitstream.rs b/libprjoxide/prjoxide/src/bitstream.rs index 6f45ed6..49dd057 100644 --- a/libprjoxide/prjoxide/src/bitstream.rs +++ b/libprjoxide/prjoxide/src/bitstream.rs @@ -5,7 +5,7 @@ use std::convert::TryInto; use std::fs::File; use std::io::{BufReader, Read}; use flate2::read::GzDecoder; -use log::info; +use log::{debug, warn}; pub struct BitstreamParser { data: Vec, @@ -88,8 +88,8 @@ impl BitstreamParser { } } - pub fn parse_file(db: &mut Database, filename: &str) -> Result { - let mut f = File::open(filename).map_err(|_x| "failed to open file")?; + pub fn parse_file(db: &mut Database, filename: &str) -> Result { + let mut f = File::open(filename).map_err(|x| format!("failed to open file {}: {:?}", filename, x) )?; let mut buffer = Vec::new(); @@ -99,7 +99,7 @@ impl BitstreamParser { gz.read_to_end(&mut buffer) } else { f.read_to_end(&mut buffer) - }.map_err(|_x| "failed to read file")?; + }.map_err(|x| format!("failed to read file {filename}: {x:?}"))?; let mut parser = BitstreamParser::new(&buffer); let mut c = parser.parse(db)?; @@ -442,11 +442,11 @@ impl BitstreamParser { let mut curr_meta = String::new(); while !self.done() { if self.check_preamble(&PREAMBLE) { - info!("bitstream start at {}", self.index); + debug!("bitstream start at {}", self.index); return Ok(BitstreamType::NORMAL); } if self.check_preamble(&PREAMBLE_IP_EVAL) { - info!("bitstream (ip eval) start at {}", self.index); + debug!("bitstream (ip eval) start at {}", self.index); return Ok(BitstreamType::NORMAL); } if !in_metadata && self.check_preamble(&COMMENT_START) { @@ -457,9 +457,9 @@ impl BitstreamParser { if curr_meta.len() > 0 { self.metadata.push(curr_meta.to_string()); if curr_meta.is_ascii() { - info!("Metadata: {}", &curr_meta); + debug!("Metadata: {}", &curr_meta); } else { - info!("Warning: Metadata of len {} contains non ascii data", curr_meta.len()); + warn!("Warning: Metadata of len {} contains non ascii data", curr_meta.len()); } curr_meta.clear(); } @@ -478,9 +478,9 @@ impl BitstreamParser { if ch == 0x00 { if curr_meta.len() > 0 { if curr_meta.is_ascii() { - info!("Metadata: {}", &curr_meta); + debug!("Metadata: {}", &curr_meta); } else { - info!("Warning: Metadata of len {} contains non ascii data", curr_meta.len()); + warn!("Warning: Metadata of len {} contains non ascii data", curr_meta.len()); } } self.metadata.push(curr_meta.to_string()); @@ -504,14 +504,14 @@ impl BitstreamParser { let cmd = self.get_opcode_byte(); match cmd { LSC_RESET_CRC => { - info!("reset CRC"); + debug!("reset CRC"); self.skip_bytes(3); self.crc16 = CRC16_INIT; } LSC_PROG_CNTRL0 => { self.skip_bytes(3); let ctrl0 = self.get_u32(); - info!("set CTRL0 to 0x{:08X}", ctrl0); + debug!("set CTRL0 to 0x{:08X}", ctrl0); } VERIFY_ID => { self.skip_bytes(3); @@ -519,22 +519,22 @@ impl BitstreamParser { let mut chip = Chip::from_idcode(db, idcode); chip.metadata = self.metadata.clone(); curr_chip = Some(chip); - info!("check IDCODE is 0x{:08X}", idcode); + debug!("check IDCODE is 0x{:08X}", idcode); } LSC_INIT_ADDRESS => { self.skip_bytes(3); - info!("reset frame address"); + debug!("reset frame address"); curr_frame = 0; } LSC_WRITE_ADDRESS => { self.skip_bytes(3); curr_frame = self.get_u32(); - info!("set frame address to 0x{:08X}", curr_frame); + debug!("set frame address to 0x{:08X}", curr_frame); } LSC_AUTH_CTRL => { self.skip_bytes(3); self.skip_bytes(64); - info!("LSC_AUTH_CTRL (bitstream is probably signed!)"); + debug!("LSC_AUTH_CTRL (bitstream is probably signed!)"); } LSC_PROG_INCR_RTI => { let cfg = self.get_byte(); @@ -552,7 +552,7 @@ impl BitstreamParser { return Err("got bitstream before idcode"); } } - info!("write {} frames at 0x{:08x}", count, curr_frame); + debug!("write {} frames at 0x{:08x}", count, curr_frame); let mut frame_bytes = vec![0 as u8; (bits_per_frame + 14 + 7) / 8]; assert_eq!(cfg, 0x91); @@ -570,10 +570,10 @@ impl BitstreamParser { if decoded_frame < chip.cram.frames { chip.cram.set(decoded_frame, j, true); } else { - info!("Decoded frame {} exceeds frame size {}", decoded_frame, chip.cram.frames); + debug!("Decoded frame {} exceeds frame size {}", decoded_frame, chip.cram.frames); } if self.verbose { - info!("F0x{:08x}B{:04}", curr_frame, j); + debug!("F0x{:08x}B{:04}", curr_frame, j); } self.update_ecc(true); } else { @@ -589,7 +589,7 @@ impl BitstreamParser { // as it changes at runtime. But it is too early to check this here. if self.verbose { - info!("F0x{:08x}P{:014b}E{:014b}", curr_frame, parity, exp_parity); + debug!("F0x{:08x}P{:014b}E{:014b}", curr_frame, parity, exp_parity); } self.check_crc16(); let d = self.get_byte(); @@ -600,13 +600,13 @@ impl BitstreamParser { LSC_POWER_CTRL => { self.skip_bytes(2); let pwr = self.get_byte(); - info!("power control: {}", pwr); + debug!("power control: {}", pwr); } ISC_PROGRAM_USERCODE => { let cmp_crc = self.get_byte() & 0x80 == 0x80; self.skip_bytes(2); let usercode = self.get_u32(); - info!("set usercode to 0x{:08X}", usercode); + debug!("set usercode to 0x{:08X}", usercode); if cmp_crc { self.check_crc16(); } @@ -633,19 +633,19 @@ impl BitstreamParser { } ISC_PROGRAM_DONE => { self.skip_bytes(3); - info!("done"); + debug!("done"); } LSC_READ_DR_UES => { self.skip_bytes(3); - info!("read DR_UES"); + debug!("read DR_UES"); } LSC_READ_CNTRL0 => { self.skip_bytes(3); - info!("read CNTRL0"); + debug!("read CNTRL0"); } DUMMY => {} _ => { - info!("unknown command 0x{:02X} at {}", cmd, self.index); + warn!("unknown command 0x{:02X} at {}", cmd, self.index); //self.skip_bytes(3); //return Err("unknown bitstream command"); } diff --git a/libprjoxide/prjoxide/src/chip.rs b/libprjoxide/prjoxide/src/chip.rs index 68aaf56..f23295a 100644 --- a/libprjoxide/prjoxide/src/chip.rs +++ b/libprjoxide/prjoxide/src/chip.rs @@ -223,10 +223,6 @@ impl Chip { tile.from_fasm(db, ft); } else { error!("Unknown tile {}", tn); - error!("Tile groups: "); - chip.tilegroups.iter().for_each(|(k, _)| { - error!("- {}", k); - }); } } chip.tiles_to_cram(); @@ -415,11 +411,31 @@ impl Chip { pub fn create_tilegroups(&mut self, db: &mut Database) { // Create tilegroups for all bels for t in self.tiles.iter() { - let bels = get_tile_bels(&t.tiletype, &db.tile_bitdb(&self.family, &t.tiletype).db); + let tile_bit_db = &db.tile_bitdb(&self.family, &t.tiletype).db; + let bels = get_tile_bels(&t.tiletype, tile_bit_db); for bel in bels { let bel_name = format!("R{}C{}_{}", (t.y as i32) + bel.rel_y, (t.x as i32) + bel.rel_x, bel.name); - let bel_tiles = get_bel_tiles(&self, t, &bel); - self.tilegroups.insert(bel_name, bel_tiles); + + let bel_tiles = { + if tile_bit_db.tile_configures_external_tiles.is_empty() { + get_bel_tiles(&self, t, &bel, &tile_bit_db.tile_configures_external_tiles.iter().cloned().next()) + } else { + vec![t.name.clone()] + } + }; + + match self.tilegroups.get_mut(&bel_name) { + Some(tiles) => { + info!("Appending tilegroup {bel_name} with {bel_tiles:?}"); + tiles.append(&mut bel_tiles.clone()); + } + None => { + if !bel_name.contains("SLICE") { + info!("Creating tilegroup {bel_name} with {bel_tiles:?}"); + } + self.tilegroups.insert(bel_name, bel_tiles); + } + } } } // Create a tilegroup for chipwide settings @@ -431,7 +447,7 @@ impl Chip { // This sets applicable words and enums to all tiles that match inside the tilegroup pub fn apply_tilegroup(&mut self, group: &str, db: &mut Database, ft: &FasmTile) { let tg = self.tilegroups.get(group).unwrap_or_else(|| panic!("No tilegroup named {}", group)).clone(); - let tdbs : Vec = tg.iter().map(|x| db.tile_bitdb(&self.family, &self.tile_by_name(x).unwrap().tiletype).db.clone()).collect(); + let tdbs : Vec = tg.iter().map(|x| db.tile_bitdb(&self.family, &self.tile_by_name(x).expect(format!("Could not find tile named '{x}' from {group} (tiles: {tg:?})").as_str()).tiletype).db.clone()).collect(); for i in 0..2 { // Process "BASE_" enums first for (k, v) in ft diff --git a/libprjoxide/prjoxide/src/database.rs b/libprjoxide/prjoxide/src/database.rs index cbda12e..7ae3a25 100644 --- a/libprjoxide/prjoxide/src/database.rs +++ b/libprjoxide/prjoxide/src/database.rs @@ -6,7 +6,7 @@ use std::{env, fmt}; use std::fs::File; use std::io::prelude::*; use std::path::Path; -use log::info; +use log::{debug, info, warn}; // Deserialization of 'devices.json' macro_rules! emit_bit_change_error { @@ -15,7 +15,7 @@ macro_rules! emit_bit_change_error { ($($arg:tt)*) => { /* compiler built-in */ if env::var("PRJOXIDE_ALLOW_BIT_CHANGE").is_ok() { - println!($($arg)*); + warn!($($arg)*); } else { panic!($($arg)*); } @@ -301,8 +301,11 @@ pub struct TileBitsDatabase { #[serde(skip_serializing_if = "BTreeSet::is_empty")] pub always_on: BTreeSet, #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - pub bel_relative_location : Option<(i32, i32)> + + // Tiletype and relative offset for the tiles that this tiletype configures -- that is, changes in + // this tiles bits reflect a change in either pips or primitives in the other tile. + #[serde(skip_serializing_if = "BTreeSet::is_empty")] + pub tile_configures_external_tiles : BTreeSet<(String, i32, i32)>, } impl TileBitsDatabase { @@ -344,6 +347,34 @@ impl TileBitsData { } } + pub fn merge(&mut self, other_db: TileBitsDatabase) { + for (to, pip_data) in other_db.pips.iter() { + for from in pip_data.iter() { + self.add_pip(&from.from_wire, to, from.bits.clone()); + } + } + for (word, word_config) in other_db.words.iter() { + self.add_word(word, &*word_config.desc, word_config.bits.clone()); + }; + for (enm, enum_config) in other_db.enums.iter() { + for (option, option_bits) in enum_config.options.iter() { + self.add_enum_option(enm, option, &*enum_config.desc, option_bits.clone()); + } + } + + for (to, from_wires) in other_db.conns { + for from in from_wires { + self.add_conn(&*from.from_wire, &*to); + if from.bidir { + self.add_conn(&*to, &*from.from_wire); + } + } + } + + for external_tile_configs in other_db.tile_configures_external_tiles { + self.set_bel_offset(Some(external_tile_configs)); + } + } pub fn add_pip(&mut self, from: &str, to: &str, bits: BTreeSet) { if !self.db.pips.contains_key(to) { @@ -351,21 +382,25 @@ impl TileBitsData { self.db.pips.insert(to.to_string(), Vec::new()); } let ac = self.db.pips.get_mut(to).unwrap(); - for ad in ac.iter() { + for ad in ac.iter_mut() { if ad.from_wire == from { if bits != ad.bits { emit_bit_change_error!( - "Bit conflict for {}.{}<-{} existing: {:?} new: {:?}", + "Bit conflict for {}. {}<-{} existing: {:?} new: {:?}", self.tiletype, from, to, ad.bits, bits ); + + ad.bits = bits; + self.dirty = true; + return; } - info!("Pip {from} -> {to} already exists for {}", self.tiletype); + debug!("Pip {from} -> {to} already exists for {}", self.tiletype); return; } } self.dirty = true; - info!("Inserting new pip {from} -> {to}"); + info!("Inserting new pip {from} -> {to} for {}", self.tiletype); ac.push(ConfigPipData { from_wire: from.to_string(), bits: bits.clone(), @@ -408,15 +443,19 @@ impl TileBitsData { } } - pub fn set_bel_offset(&mut self, bel_relative_location : Option<(i32, i32)>) { - if self.db.bel_relative_location.is_some() && self.db.bel_relative_location != bel_relative_location { + pub fn set_bel_offset(&mut self, bel_relative_location : Option<(String, i32, i32)>) { + if !self.db.tile_configures_external_tiles.is_empty() && + self.db.tile_configures_external_tiles.iter().next() != bel_relative_location.as_ref() { emit_bit_change_error!( "Bel offset conflict for {}. existing: {:?} new: {:?}", - self.tiletype, self.db.bel_relative_location, bel_relative_location + self.tiletype, self.db.tile_configures_external_tiles, bel_relative_location ); } info!("Setting bel offset {} {:?}", self.tiletype, bel_relative_location); - self.db.bel_relative_location = bel_relative_location; + + bel_relative_location.iter().for_each( + |loc| { self.db.tile_configures_external_tiles.insert(loc.clone()); } + ); self.dirty = true; } pub fn add_enum_option( @@ -440,13 +479,16 @@ impl TileBitsData { ec.desc = desc.to_string(); self.dirty = true; } - match ec.options.get(option) { + match ec.options.get_mut(option) { Some(old_bits) => { if bits != *old_bits { emit_bit_change_error!( "Bit conflict for {}.{}={} existing: {:?} new: {:?}", self.tiletype, name, option, old_bits, bits ); + + ec.options.insert(option.to_string(), bits); + self.dirty = true; } } None => { @@ -462,9 +504,9 @@ impl TileBitsData { let pc = self.db.conns.get_mut(to).unwrap(); if pc.iter().any(|fc| fc.from_wire == from) { // Connection already exists - info!("Connection {from} already exists {}", self.tiletype); + debug!("Connection {from} -> {to} already exists {}", self.tiletype); } else { - info!("Connection {from} added {}", self.tiletype); + info!("Connection {from} -> {to} added {}", self.tiletype); self.dirty = true; pc.push(FixedConnectionData { from_wire: from.to_string(), @@ -655,7 +697,7 @@ impl Database { enums: BTreeMap::new(), conns: BTreeMap::new(), always_on: BTreeSet::new(), - bel_relative_location : None + tile_configures_external_tiles : BTreeSet::new(), } }; self.tilebits @@ -679,7 +721,7 @@ impl Database { enums: BTreeMap::new(), conns: BTreeMap::new(), always_on: BTreeSet::new(), - bel_relative_location : None + tile_configures_external_tiles : BTreeSet::new(), } }; self.ipbits diff --git a/libprjoxide/prjoxide/src/fuzz.rs b/libprjoxide/prjoxide/src/fuzz.rs index aec3e03..6e18f44 100644 --- a/libprjoxide/prjoxide/src/fuzz.rs +++ b/libprjoxide/prjoxide/src/fuzz.rs @@ -9,7 +9,8 @@ use ron::ser::PrettyConfig; use serde::Serialize; use std::fs::File; use std::io::prelude::*; -use log::{info, warn}; + +use log::{debug, info, trace, warn}; #[derive(Clone, Debug)] pub enum FuzzMode { @@ -115,9 +116,7 @@ impl Fuzzer { desc: desc.to_string(), } } - fn add_sample(&mut self, db: &mut Database, key: FuzzKey, bitfile: &str) { - let parsed_bitstream = BitstreamParser::parse_file(db, bitfile).unwrap(); - let delta: ChipDelta = parsed_bitstream.delta(&self.base); + fn add_sample_delta(&mut self, key: FuzzKey, delta: ChipDelta) { if let Some(d) = self.deltas.get_mut(&key) { // If key already in delta, take the intersection of the two let intersect: ChipDelta = d @@ -136,6 +135,13 @@ impl Fuzzer { self.deltas.insert(key, delta); } } + fn add_sample(&mut self, db: &mut Database, key: FuzzKey, bitfile: &str) { + let parsed_bitstream = BitstreamParser::parse_file(db, bitfile).unwrap(); + let delta: ChipDelta = parsed_bitstream.delta(&self.base); + trace!("Sample delta {bitfile} {key:?} {delta:?}"); + self.add_sample_delta(key, delta); + } + pub fn add_pip_sample(&mut self, db: &mut Database, from_wire: &str, bitfile: &str) { self.add_sample( db, @@ -146,6 +152,15 @@ impl Fuzzer { bitfile, ); } + pub fn add_pip_sample_delta(&mut self, from_wire: &str, delta: ChipDelta) { + self.add_sample_delta( + FuzzKey::PipKey { + from_wire: from_wire.to_string(), + allow_partial_deltas : false + }, + delta, + ); + } pub fn add_pip_sample_with_partial_delta(&mut self, db: &mut Database, from_wire: &str, bitfile: &str) { self.add_sample( db, @@ -227,7 +242,7 @@ impl Fuzzer { continue; } if changed_tiles.len() == 0 { - info!("No changed tiles for {from_wire} -> {to_wire}"); + debug!("No changed tiles for {from_wire} -> {to_wire}"); // No changes; it is a fixed connection if skip_fixed { continue; @@ -303,8 +318,6 @@ impl Fuzzer { } } - info!("Found {} pips for {} deltas", findings, self.deltas.len()); - findings } @@ -367,7 +380,7 @@ impl Fuzzer { mark_relative_to } => { if self.deltas.len() < 2 { - warn!("Need at least two deltas got {}", self.deltas.len()); + warn!("Need at least two deltas got {} for fuzzmode {name}", self.deltas.len()); return; } for tile in changed_tiles { @@ -451,16 +464,19 @@ impl Fuzzer { let tile_db = db.tile_bitdb(&self.base.family, &tile_data.tiletype); + tile_db.add_enum_option(&name, &option, &self.desc, b); + if let Some(relative_tile) = mark_relative_to.clone() { + let ref_tile = self.base.tile_by_name(&relative_tile).unwrap(); let offset = { - let ref_tile = self.base.tile_by_name(&relative_tile).unwrap(); - (ref_tile.x as i32 - tile_data.x as i32, + (ref_tile.tiletype.clone(), + ref_tile.x as i32 - tile_data.x as i32, ref_tile.y as i32 - tile_data.y as i32) }; - tile_db.set_bel_offset(Some(offset)); - } - tile_db.add_enum_option(&name, &option, &self.desc, b); + tile_db.set_bel_offset(Some(offset.clone())); + }; + } } } diff --git a/libprjoxide/prjoxide/src/ipfuzz.rs b/libprjoxide/prjoxide/src/ipfuzz.rs index cd360c1..e1de77f 100644 --- a/libprjoxide/prjoxide/src/ipfuzz.rs +++ b/libprjoxide/prjoxide/src/ipfuzz.rs @@ -7,6 +7,7 @@ use std::iter::FromIterator; use ron::ser::PrettyConfig; use std::fs::File; use std::io::prelude::*; +use log::error; use serde::Serialize; pub enum IPFuzzMode { @@ -73,11 +74,16 @@ impl IPFuzzer { } fn add_sample(&mut self, db: &mut Database, key: IPFuzzKey, bitfile: &str) { let parsed_bitstream = BitstreamParser::parse_file(db, bitfile).unwrap(); - let addr = db + let addr_opt = db .device_baseaddrs(&parsed_bitstream.family, &parsed_bitstream.device) .regions - .get(&self.ipcore) - .unwrap(); + .get(&self.ipcore); + + if addr_opt.is_none() { + error!("Sample added for {} {} for ip core {} but base address is not known for it.", parsed_bitstream.family, parsed_bitstream.device, self.ipcore); + } + + let addr = addr_opt.unwrap(); let delta: IPDelta = parsed_bitstream.ip_delta(&self.base, addr.addr, addr.addr + (1 << addr.abits)); self.deltas.insert(key, delta); @@ -191,12 +197,9 @@ impl IPFuzzer { enumerate_arrays: false, separate_tuple_members: false, }; - + let buf = ron::ser::to_string_pretty(&self.deltas, pretty).unwrap(); - File::create(format!( - "{}.ron", - filename - )) + File::create(format!("{}.ron", filename)) .unwrap() .write_all(buf.as_bytes()) .unwrap(); diff --git a/libprjoxide/prjoxide/src/wires.rs b/libprjoxide/prjoxide/src/wires.rs index 4941938..79ccab9 100644 --- a/libprjoxide/prjoxide/src/wires.rs +++ b/libprjoxide/prjoxide/src/wires.rs @@ -1,3 +1,4 @@ +use log::warn; // Wire normalisation for Nexus use crate::chip::*; use regex::Regex; @@ -77,6 +78,9 @@ pub fn handle_edge_name( "01" => { // H01xyy00 --> x+1, H01xyy01 if tx == max_x - 1 { + if hm[4].to_string() != "00" { + warn!("Invalid edge name {wn} - {hm:?}. hm[4] == '00'") + } assert_eq!(hm[4].to_string(), "00"); return (format!("H01{}{}01", &hm[2], &hm[3]), wx + 1, wy); } @@ -270,7 +274,9 @@ pub fn normalize_wire(chip: &Chip, tile: &Tile, wire: &str) -> String { } let tx = tile.x as i32; let ty = tile.y as i32; - if tile.name.contains("TAP") && wn.starts_with("H") { + + + if tile.name.contains("TAP") && wn.starts_with("H") && !wn.starts_with("HFIE") { if wx < tx { return format!("BRANCH_L:{}", wn); } else if wx > tx { diff --git a/libprjoxide/pyprjoxide/Cargo.toml b/libprjoxide/pyprjoxide/Cargo.toml index 8814cab..e395c73 100644 --- a/libprjoxide/pyprjoxide/Cargo.toml +++ b/libprjoxide/pyprjoxide/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" prjoxide = { path = "../prjoxide" } log = "0.4.29" env_logger = "0.11.8" +pyo3-log = "0.13.2" [dependencies.pyo3] version = "0.13.1" diff --git a/libprjoxide/pyprjoxide/src/lib.rs b/libprjoxide/pyprjoxide/src/lib.rs index 91e7b76..e50d189 100644 --- a/libprjoxide/pyprjoxide/src/lib.rs +++ b/libprjoxide/pyprjoxide/src/lib.rs @@ -4,7 +4,7 @@ use pyo3::wrap_pyfunction; use std::fs::File; use std::io::*; - +use pyo3_log::{Caching, Logger}; use prjoxide::bitstream; use prjoxide::chip; use prjoxide::database; @@ -31,6 +31,13 @@ impl Database { db: database::Database::new(root), } } + pub fn add_conn(&mut self, family: &str, tiletype: &str, from: &str, to: &str) { + self.db.tile_bitdb(family, tiletype).add_conn(from, to); + } + + pub fn flush(&mut self) { + self.db.flush(); + } } #[pyclass] @@ -141,6 +148,10 @@ impl Fuzzer { self.fz.add_pip_sample(&mut db.db, from_wire, base_bitfile); } + fn add_pip_sample_delta(&mut self, from_wire: &str, delta: chip::ChipDelta) { + self.fz.add_pip_sample_delta(from_wire, delta); + } + fn add_pip_sample_with_partial_delta(&mut self, db: &mut Database, from_wire: &str, base_bitfile: &str) { self.fz.add_pip_sample_with_partial_delta(&mut db.db, from_wire, base_bitfile); } @@ -165,6 +176,7 @@ impl Fuzzer { #[pyclass] struct IPFuzzer { fz: ipfuzz::IPFuzzer, + name: String } #[pymethods] @@ -193,6 +205,7 @@ impl IPFuzzer { width, inverted_mode, ), + name: name.to_string() } } @@ -215,6 +228,7 @@ impl IPFuzzer { name, desc, ), + name: name.to_string() } } @@ -237,6 +251,10 @@ impl IPFuzzer { fn serialize_deltas(&mut self, filename: &str) { self.fz.serialize_deltas(filename); } + + fn get_name(&self) -> String { + self.name.clone() + } } #[pyfunction] @@ -374,9 +392,9 @@ fn classify_pip(src_x: i32, src_y: i32, src_name: &str, dst_x: i32, dst_y: i32, } #[pymodule] -fn libpyprjoxide(_py: Python, m: &PyModule) -> PyResult<()> { - env_logger::init(); - +fn libpyprjoxide(py: Python, m: &PyModule) -> PyResult<()> { + pyo3_log::init(); + m.add_wrapped(wrap_pyfunction!(parse_bitstream))?; m.add_wrapped(wrap_pyfunction!(write_tilegrid_html))?; m.add_wrapped(wrap_pyfunction!(write_region_html))?; diff --git a/tools/parse_webdoc.py b/tools/parse_webdoc.py new file mode 100644 index 0000000..e69de29 diff --git a/util/common/cachecontrol.py b/util/common/cachecontrol.py new file mode 100644 index 0000000..e69de29 From 39d03988ad62522f16a0fa63a929320678cd28f2 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Tue, 27 Jan 2026 10:16:13 -0700 Subject: [PATCH 13/29] Gitignore / docs --- .gitignore | 2 ++ docs/general/structure.md | 24 ++++++++++++++++++++---- link-db-root.sh | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100755 link-db-root.sh diff --git a/.gitignore b/.gitignore index df70e8d..224a1f7 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ radiantc.log* work/ .\#* *.fasm +.cache +fuzzers/*/*/db \ No newline at end of file diff --git a/docs/general/structure.md b/docs/general/structure.md index 420c905..f0a538c 100644 --- a/docs/general/structure.md +++ b/docs/general/structure.md @@ -68,8 +68,24 @@ flagged and changed when the tilegrid is imported from lattice's interchange for There is a global distribution network on the LIFCL devices for clocks and resets to limit skew to any given logic cell: - Starts at CMUX -- Distributed along HROW's to SPINEs -- SPINEs push to branch nodes -- Branch nodes push out left or right into PLCs +- Branch out left or right -- LHPRX or RHPRX +- Distributed along HROW's to SPINEs - HPRX1000 -> VPSX1000 +- SPINEs push to branch nodes VPSX1000 -> R..C44_HPBX..00 +- PLCs local to the SPINE can be fed from here. An additional branch jump HPBX0 can reach the rest. + +See global.json for a listing of those cells for each device. + +### Example routing: + +To get from R82C25_JCLKO_DCC_DCC0 -> R4C35_JCLK_SLICED on LIFCL-33 + +- R37C25_JCLKO_DCC_DCC0 feeds [R37C25_JJCLKUL_CMUX_CORE_CMUX0, R37C25_JJCLKUL_CMUX_CORE_CMUX1] +- These feed out to [R37C25_JHPRX{LANE}_CMUX_CORE_CMUX0, R37C25_JHPRX{LANE}_CMUX_CORE_CMUX1] respectively +- These feed out to [R37C25_LHPRX{LANE}, R37C25_RHPRX{LANE}]. LHPRX branches out to C0 to C25. RHPRX branches out from C25 to C50 +- Following R37C25_LHPRX{LANE}, it drives R37C31_HPRX{LANE}00 +- This drives R41C37_VPSX{LANE}00 +- R41C37_VPSX{LANE}00 drives R{ROW}C44_HPBX{INST}00 +- R4C32_HPBX{INST}00 provides local access to tiles R38 to R50. It also provides access to a branch R4C32_HPBX{INST}00 node. +- R4C32_HPBX{INST}00 provides local access to tiles R25 to R37, namely R4C35_JCLK1 +- R4C35_JCLK1 drives R4C35_JCLK_SLICED -See global.json for a listing of those cells for each device. \ No newline at end of file diff --git a/link-db-root.sh b/link-db-root.sh new file mode 100755 index 0000000..53f2d24 --- /dev/null +++ b/link-db-root.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +new_dir=${1:-db} +mkdir ./${new_dir} +pushd ${new_dir} + +set -euo pipefail + +root=`git rev-parse --show-toplevel` +SRC=$root/database +DST=$(pwd) + +find "$SRC" -type d | while read -r dir; do + rel="${dir#$SRC/}" + [[ "$rel" == "$dir" ]] && continue # skip root itself + mkdir -p "$DST/$rel" +done + +# Find all json files and recreate directory structure with symlinks +find "$SRC" -type f -name '*.json' | while read -r file; do + # Path relative to source root + rel="${file#$SRC/}" + + # Destination path + dest="$DST/$rel" + + # Create destination directory + mkdir -p "$(dirname "$dest")" + + # Create symlink (overwrite if exists) + ln -sf "$file" "$dest" +done From 08f9b43c3f85323c21394a3e8c63ef3b2bf13bae Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Tue, 27 Jan 2026 10:19:08 -0700 Subject: [PATCH 14/29] Add node database, add futures / async support, misc util functions and improved UI --- environment.sh | 2 +- tools/bitstreamcache.py | 130 ++++++----- tools/parse_webdoc.py | 103 ++++++++ util/common/cachecontrol.py | 28 +++ util/common/database.py | 82 ++++--- util/common/lapie.py | 206 +++++++++++----- util/common/nodes_database.py | 421 +++++++++++++++++++++++++++++++++ util/common/radiant.py | 60 +++-- util/common/tiles.py | 245 +++++++++++++++++-- util/fuzz/fuzzconfig.py | 116 +++++++-- util/fuzz/fuzzloops.py | 309 +++++++++++++++++++++++- util/fuzz/interconnect.py | 106 ++++++--- util/fuzz/nonrouting.py | 173 +++++++++----- util/fuzz/primitives.py | 426 +++++++++++++++++++++++++++------- 14 files changed, 2021 insertions(+), 386 deletions(-) create mode 100644 util/common/nodes_database.py diff --git a/environment.sh b/environment.sh index 656527c..163a561 100644 --- a/environment.sh +++ b/environment.sh @@ -12,7 +12,7 @@ fi SCRIPT_PATH=$(readlink -f "${BASH_SOURCE:-$0}") SCRIPT_DIR=$(dirname "$SCRIPT_PATH") -PYTHONLIBS_DIR="${SCRIPT_DIR}/util:${SCRIPT_DIR}/util/common:${SCRIPT_DIR}/util/fuzz:${SCRIPT_DIR}/timing/util:${SCRIPT_DIR}/libprjoxide/target/${TARGET:-release}" +PYTHONLIBS_DIR="${SCRIPT_DIR}/tools:${SCRIPT_DIR}/util:${SCRIPT_DIR}/util/common:${SCRIPT_DIR}/util/fuzz:${SCRIPT_DIR}/timing/util:${SCRIPT_DIR}/libprjoxide/target/${TARGET:-release}" export PYTHONPATH="${PYTHONLIBS_DIR}:${PYTHONPATH}" export RADIANTDIR=$HOME/lscc/radiant/3.1 USER_ENV="${SCRIPT_DIR}/user_environment.sh" diff --git a/tools/bitstreamcache.py b/tools/bitstreamcache.py index e75bd20..7c15f15 100755 --- a/tools/bitstreamcache.py +++ b/tools/bitstreamcache.py @@ -20,7 +20,7 @@ gzip and gunzip must be on your path for it to work """ - +import logging import sys, os, shutil, hashlib, gzip from logging import exception from pathlib import Path @@ -28,14 +28,17 @@ root_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") cache_dir = os.path.join(root_dir, ".bitstreamcache") -def get_hash(device, input_files): +def get_hash(device, input_files, env = None): + if env is None: + env = os.environ + hasher = hashlib.sha1() hasher.update(b"DEVICE") hasher.update(device.encode('utf-8')) for envkey in ("GEN_RBF", "DEV_PACKAGE", "SPEED_GRADE", "STRUCT_VER", "RBK_MODE"): - if envkey in os.environ: + if envkey in env: hasher.update(envkey.encode('utf-8')) - hasher.update(os.environ[envkey].encode('utf-8')) + hasher.update(env[envkey].encode('utf-8')) for fname in input_files: ext = os.path.splitext(fname)[1] hasher.update("input{}".format(ext).encode('utf-8')) @@ -43,63 +46,86 @@ def get_hash(device, input_files): hasher.update(f.read()) return hasher.hexdigest() -if len(sys.argv) < 2: - print("Expected command (init|fetch|commit)") - sys.exit(1) -cmd = sys.argv[1] -if cmd == "init": - if not os.path.exists(cache_dir): - os.mkdir(cache_dir) -if cmd == "fetch": +def fetch(device, input_files, env = None): if not os.path.exists(cache_dir): - sys.exit(1) - if len(sys.argv) < 5: - print("Usage: tools/bitstreamcache.py fetch ...") - sys.exit(1) - h = get_hash(sys.argv[2], sys.argv[4:]) - print(h) + return + + h = get_hash(device, input_files, env=env) + #print(h) + cache_entry = os.path.join(cache_dir, h) if not os.path.exists(cache_entry) or len(os.listdir(cache_entry)) < 2: - sys.exit(1) + return # Touch the directory and it's contents Path(cache_entry).touch() for outprod in os.listdir(cache_entry): - bn = outprod - assert bn.endswith(".gz") - bn = bn[:-3] gz_path = os.path.join(cache_entry, outprod) Path(gz_path).touch() - if gz_path.endswith(".bit.gz"): - os.symlink(gz_path, os.path.join(sys.argv[3], outprod)) + yield (outprod, gz_path) + +def main(): + if len(sys.argv) < 2: + print("Expected command (init|fetch|commit)") + sys.exit(1) + cmd = sys.argv[1] + if cmd == "init": + if not os.path.exists(cache_dir): + os.mkdir(cache_dir) + if cmd == "fetch": + + if not os.path.exists(cache_dir): + sys.exit(1) + if len(sys.argv) < 5: + print("Usage: tools/bitstreamcache.py fetch ...") + sys.exit(1) + + cache_entries = fetch(sys.argv[2], sys.argv[4:]) + + for (outprod, gz_path) in cache_entries: + assert gz_path.endswith(".gz") + + Path(gz_path).touch() + if gz_path.endswith(".bit.gz"): + print(f"Linking {os.path.join(sys.argv[3], outprod)}") + os.symlink(gz_path, os.path.join(sys.argv[3], outprod)) + else: + bn = Path(gz_path[:-3]).name + with gzip.open(gz_path, 'rb') as gzf: + print(f"Writing {os.path.join(sys.argv[3], bn)}") + with open(os.path.join(sys.argv[3], bn), 'wb') as outf: + outf.write(gzf.read()) else: - with gzip.open(gz_path, 'rb') as gzf: - with open(os.path.join(sys.argv[3], bn), 'wb') as outf: - outf.write(gzf.read()) - sys.exit(0) -if cmd == "commit": - if not os.path.exists(cache_dir): + sys.exit(1) + sys.exit(0) - idx = sys.argv.index("output") - if len(sys.argv) < 6 or idx == -1: - print("Usage: tools/bitstreamcache.py commit output ..") - sys.exit(1) - h = get_hash(sys.argv[2], sys.argv[3:idx]) - cache_entry = os.path.join(cache_dir, h) - if not os.path.exists(cache_entry): - os.mkdir(cache_entry) - for outprod in sys.argv[idx+1:]: - bn = os.path.basename(outprod) - cn = os.path.join(cache_entry, bn + ".gz") - - if not os.path.exists(outprod): - raise Exception(f"Output product does not exist") - - if os.path.getsize(outprod) == 0: - raise Exception(f"Output product has zero length; refusing to gzip {outprod}") - - with gzip.open(cn, 'wb') as gzf: - with open(outprod, 'rb') as inf: - gzf.write(inf.read()) - sys.exit(0) + + if cmd == "commit": + if not os.path.exists(cache_dir): + sys.exit(0) + idx = sys.argv.index("output") + if len(sys.argv) < 6 or idx == -1: + print("Usage: tools/bitstreamcache.py commit output ..") + sys.exit(1) + h = get_hash(sys.argv[2], sys.argv[3:idx]) + cache_entry = os.path.join(cache_dir, h) + if not os.path.exists(cache_entry): + os.mkdir(cache_entry) + for outprod in sys.argv[idx+1:]: + bn = os.path.basename(outprod) + cn = os.path.join(cache_entry, bn + ".gz") + + if not os.path.exists(outprod): + raise Exception(f"Output product does not exist") + + if os.path.getsize(outprod) == 0: + raise Exception(f"Output product has zero length; refusing to gzip {outprod}") + + with gzip.open(cn, 'wb') as gzf: + with open(outprod, 'rb') as inf: + gzf.write(inf.read()) + sys.exit(0) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tools/parse_webdoc.py b/tools/parse_webdoc.py index e69de29..6011d5a 100644 --- a/tools/parse_webdoc.py +++ b/tools/parse_webdoc.py @@ -0,0 +1,103 @@ +import json +import os +import re + +from bs4 import BeautifulSoup +from pathlib import Path + + +def normalize_text(s: str) -> str: + return " ".join(s.encode('ascii', errors='ignore').decode("ascii").replace("(default)", "").split()) + + +def extract_cell_value(td): + text = normalize_text(td.get_text()) + matches = re.findall(r'"([^"]*)"', text) + if len(matches): + return matches + + divs = td.find_all("div", class_="CellBody", recursive=False) + if not divs: + return text + + values = [normalize_text(d.get_text()) for d in divs if normalize_text(d.get_text())] + if len(values) == 0: + return "" + + if len(values) == 1: + return values[0] + return values + + +def parse_table(table): + rows = table.find_all("tr") + if not rows: + return [] + + # Extract headers + header_cells = rows[0].find_all(["th", "td"]) + headers = [normalize_text(h.get_text()) or f"col_{i}" for i, h in enumerate(header_cells)] + + data = [] + for row in rows[1:]: + cells = row.find_all("td") + if not cells: + continue + + row_obj = {} + for header, cell in zip(headers, cells): + if header == "col_1": + header = "Values" + cell_value = extract_cell_value(cell) + if header == "Values" and not isinstance(cell_value, list): + cell_value = [cell_value] + row_obj[header] = cell_value + + if row_obj.get("Name", None) == "": + data[-1]["Values"].extend(row_obj["Values"]) + #data[-1]["Description"] += (row_obj["Description"]) + else: + data.append(row_obj) + + return data + + +def scrape_html(html_path: Path): + with open(html_path, "r", encoding="utf-8") as f: + soup = BeautifulSoup(f, "lxml") + + title = soup.title.get_text(strip=True) if soup.title else html_path.stem + output = {} + + output["description"] = "".join([div.get_text() for div in soup.select(".BodyAfterHead")]) + output["platforms"] = [ supported_platforms.get_text() for supported_platforms in soup.select(".Bulleted")] + + + for title_div in soup.select("div.TableTitle"): + table_title = normalize_text(title_div.get_text()) + if not table_title: + continue + + table = title_div.find_parent("table") + if not table: + continue + + output[table_title] = parse_table(table) + + output_path = f"primitives/{title}.json" + with open(output_path, "w", encoding="utf-8") as f: + json.dump(output, f, indent=2) + + return output_path + + +if __name__ == "__main__": + import sys + + if len(sys.argv) != 2: + print("Usage: python scrape.py ") + sys.exit(1) + + html_file = Path(sys.argv[1]) + out = scrape_html(html_file) + print(f"Wrote {out}") diff --git a/util/common/cachecontrol.py b/util/common/cachecontrol.py index e69de29..7d6124a 100644 --- a/util/common/cachecontrol.py +++ b/util/common/cachecontrol.py @@ -0,0 +1,28 @@ +import hashlib +import os +import pickle + +import cachier +from sqlalchemy import create_engine + +from database import get_cache_dir + +radiant_version = os.environ.get("RADIANTVERSION", None) +engine = create_engine(f'sqlite:///{get_cache_dir()}/cache.db') +#@cachier.cachier(backend="sql", sql_engine=engine, cache_dir=database.get_cache_dir(), pickle_reload=False,separate_files=True) + +def cache_fn(): + RADIANT_DIR = os.environ.get("RADIANTDIR") + + def hashfunc(args, kwds): + # Sort the kwargs to ensure consistent ordering + kwds["RADIANT_DIR"] = RADIANT_DIR + kwds["RADIANT_VERSION"] = radiant_version + + sorted_kwargs = sorted(kwds.items()) + # Serialize args and sorted_kwargs using pickle or similar + serialized = pickle.dumps((args, sorted_kwargs)) + # Create a hash of the serialized data + return hashlib.sha256(serialized).hexdigest() + + return cachier.cachier(hash_func=hashfunc, backend="sql", sql_engine=engine, pickle_reload=False,separate_files=True, wait_for_calc_timeout=5) \ No newline at end of file diff --git a/util/common/database.py b/util/common/database.py index 42c8ddb..e64076b 100644 --- a/util/common/database.py +++ b/util/common/database.py @@ -2,20 +2,39 @@ Database and Database Path Management """ import os -from os import path +from functools import lru_cache, cache +from os import path, makedirs import json import subprocess from pathlib import Path import pyron as ron import gzip -import sqlite3 -import lapie - def get_oxide_root(): """Return the absolute path to the Project Oxide repo root""" return path.abspath(path.join(__file__, "../../../")) +def get_radiant_version(): + # `lapie` seems to be renamed every version or so. Map that out here. Most installations will have + # the version name at the end of their path, so we just look at the radiant dir for a hint. The user + # can override this setting with a RADIANTVERSION env variable + known_versions = [ "2.2", "3.1", "2023", "2024", "2025" ] + RADIANT_DIR = os.environ.get("RADIANTDIR") + radiant_version= os.environ.get("RADIANTVERSION", None) + + if radiant_version is None: + for version in known_versions: + if RADIANT_DIR.find(version) > -1: + radiant_version = version + + if radiant_version is None: + radiant_version = "3.1" + return radiant_version + +def get_cache_dir(): + path = get_oxide_root() + "/.cache/" + get_radiant_version() + makedirs(path, exist_ok=True) + return path def get_db_root(): """ @@ -45,7 +64,22 @@ def get_db_subdir(family = None, device = None, package = None): os.mkdir(subdir) return subdir -_tilegrids = {} +def get_base_addrs(family, device = None): + if device is None: + device = family + family = device.split('-')[0] + + tgjson = path.join(get_db_subdir(family, device), "baseaddr.json") + if path.exists(tgjson): + with open(tgjson, "r") as f: + try: + return json.load(f)["regions"] + except: + print(f"Exception encountered reading {tgjson}") + raise + return {} + +@cache def get_tilegrid(family, device = None): """ Return the deserialised tilegrid for a family, device @@ -54,18 +88,16 @@ def get_tilegrid(family, device = None): device = family family = device.split('-')[0] - if device not in _tilegrids: - tgjson = path.join(get_db_subdir(family, device), "tilegrid.json") - if path.exists(tgjson): - with open(tgjson, "r") as f: - try: - _tilegrids[device] = json.load(f) - except: - print(f"Exception encountered reading {tgjson}") - raise - else: - _tilegrids[device] = {"tiles":{}} - return _tilegrids[device] + tgjson = path.join(get_db_subdir(family, device), "tilegrid.json") + if path.exists(tgjson): + with open(tgjson, "r") as f: + try: + return json.load(f) + except: + print(f"Exception encountered reading {tgjson}") + raise + else: + return {"tiles":{}} def get_iodb(family, device = None): """ @@ -105,23 +137,15 @@ def get_tiletypes(family): def get_db_commit(): return subprocess.getoutput('git -C "{}" rev-parse HEAD'.format(get_db_root())) -_sites = {} +@cache def get_sites(family, device = None): + import lapie + if device is None: device = family family = device.split('-')[0] - site_file = path.join(get_db_subdir(family, device), "sites.json.gz") - if site_file not in _sites: - if not path.exists(site_file): - sites = lapie.get_sites_with_pin(device) - with gzip.open(site_file, 'wb') as f: - f.write(json.dumps(sites).encode('utf-8')) - - with gzip.open(site_file, 'r') as f: - _sites[site_file] = json.loads(f.read().decode('utf-8')) - return _sites[site_file] - + return lapie.get_sites_with_pin(device) def check_tiletype(tiletype, tiletype_info): pips = tiletype_info["pips"] diff --git a/util/common/lapie.py b/util/common/lapie.py index 6a2d640..4c2af7f 100644 --- a/util/common/lapie.py +++ b/util/common/lapie.py @@ -1,34 +1,27 @@ """ Python wrapper for `lapie` """ +import hashlib import logging -import sys -from os import path import os -import subprocess -import database -import tempfile import re -import hashlib import shutil +import subprocess +import tempfile +import time +from collections import defaultdict +from functools import cache +from os import path + +import cachier import fuzzconfig -# `lapie` seems to be renamed every version or so. Map that out here. Most installations will have -# the version name at the end of their path, so we just look at the radiant dir for a hint. The user -# can override this setting with a RADIANTVERSION env variable -known_versions = [ "2.2", "3.1", "2023", "2024", "2025" ] -RADIANT_DIR = os.environ.get("RADIANTDIR") -radiant_version= os.environ.get("RADIANTVERSION", None) -get_nodes = "dev_get_nodes" - -if radiant_version is None: - for version in known_versions: - if RADIANT_DIR.find(version) > -1: - radiant_version = version +import cachecontrol +import database -if radiant_version is None: - radiant_version = "3.1" +radiant_version = database.get_radiant_version() +get_nodes = "dev_get_nodes" if radiant_version == "2023": tcltool = "lark" tcltool_log = "radiantc.log" @@ -43,7 +36,7 @@ else: tcltool = "lapie" tcltool_log = "lapie.log" - dev_enable_name = "LATCL_DEV_ENABLE" + dev_enable_name = "LATCL_DEV_ENABLE" get_nodes = "get_nodes" def run(commands, workdir=None, stdout=None): @@ -79,7 +72,18 @@ def run(commands, workdir=None, stdout=None): output = output[:output.find(pleasantry)].strip() return output +run_with_udb_cnt = 0 def run_with_udb(udb, commands, stdout = None): + global run_with_udb_cnt + run_with_udb_cnt = run_with_udb_cnt + 1 + if not udb.endswith(".udb"): + device = udb + udb = f"/tmp/prjoxide_node_data/{device}.udb" + if not os.path.exists(udb): + config = fuzzconfig.FuzzConfig(device, f"extract-site-info-{device}", []) + config.setup() + shutil.copyfile(config.udb, udb) + return run(['des_read_udb "{}"'.format(path.abspath(udb))] + commands, stdout = stdout) class PipInfo: @@ -115,7 +119,6 @@ def pips(self): node_re = re.compile(r'^\[\s*\d+\]\s*([A-Z0-9a-z_]+)') alias_node_re = re.compile(r'^\s*Alias name = ([A-Z0-9a-z_]+)') pip_re = re.compile(r'^([A-Z0-9a-z_]+) (<--|<->|-->) ([A-Z0-9a-z_]+) \(Flags: .+, (\d+)\) \(Buffer: ([A-Z0-9a-z_]+)\)') -#R1C77_JLFTRMFAB7_OSC_CORE <-- R1C75_JCIBMUXOUTA7 (Flags: ----j, 0) (Buffer: b_ciboutbuf) pin_re = re.compile(r'^Pin : ([A-Z0-9a-z_]+)/([A-Z0-9a-z_]+) \(([A-Z0-9a-z_]+)\)') # Parsing is weird here since the format of the report can vary somewhat. @@ -123,7 +126,7 @@ def pips(self): # can have a lot of aliases and the only clear indication of which name is normative is its the one # used in the connections. -def parse_node_report(rpt): +def parse_node_report(rpt, node_keys): curr_node = None nodes_dict = {} nodes = [] @@ -150,6 +153,9 @@ def get_node(name): curr_node = get_node(new_name) reset_curr_node = False curr_node.aliases.append(new_name) + + if new_name in node_keys: + curr_node.name = new_name continue # If we get back into an alias section, we are onto a new node @@ -208,11 +214,12 @@ def parse_sites(rpt): return sites +@cachecontrol.cache_fn() def get_full_node_list(udb): workdir = f"/tmp/prjoxide_node_data/{udb}" nodefile = path.join(workdir, "full_nodes.txt") os.makedirs(workdir, exist_ok=True) - + if not os.path.exists(nodefile): if not udb.endswith(".udb"): config = fuzzconfig.FuzzConfig(udb, "extract-site-info", []) @@ -222,29 +229,64 @@ def get_full_node_list(udb): with open(nodefile, 'r') as nf: return [line.split(":")[-1].strip() for line in nf.read().split("\n")] -def get_node_data(udb, nodes, regex=False): +@cache +def _get_list_arc(device): + nodefile = f"/tmp/prjoxide_node_data/{device}/arclist" + if not os.path.exists(nodefile): + run_with_udb(device, [f'dev_list_arc -file {nodefile} -jumpwire'], stdout=subprocess.DEVNULL) + + with open(nodefile, 'r') as nf: + nodes = {} + arcs = set() + def get_node(n): + if n not in nodes: + nodes[n] = NodeInfo(n) + return nodes[n] + + logging.info(f"Reading arc file {nodefile}") + for line in nf.readlines(): + parts = line.split(" ") + if parts[2] != "-->": + print(line, parts) + assert parts[2] == "-->" + + pip = PipInfo(parts[1], parts[3]) + get_node(parts[1]).downhill_pips.append(pip) + get_node(parts[3]).uphill_pips.append(pip) + + for n,info in nodes.items(): + for t in info.pips(): + arcs.add((t.from_wire, t.to_wire)) + + return arcs + +@cache +def get_list_arc(device): + from nodes_database import NodesDatabase + node_db = NodesDatabase.get(device) + jmp = set(node_db.get_jumpwires()) + if len(jmp) == 0: + jmp = _get_list_arc(device) + node_db.insert_jumpwires(jmp) + + return jmp + +def _get_node_data(udb, nodes): + regex = False + workdir = tempfile.mkdtemp() nodefile = path.join(workdir, "nodes.txt") - nodelist = "" - - if not isinstance(nodes, list): - nodelist = nodes - nodes = [nodes] - elif len(nodes) == 1: - nodelist = nodes[0] - elif len(nodes) > 1: - nodes = sorted(set(nodes)) - nodelist = "[list {}]".format(" ".join(nodes)) + nodelist = "[list {}]".format(" ".join(nodes)) logging.info(f"Querying for {len(nodes)} nodes {nodes[:10]}") key_input = "\n".join([radiant_version, udb, f"regex: {regex}", ''] + nodes) key = hashlib.md5(key_input.encode('utf-8')).hexdigest() key_path = f"/tmp/prjoxide_node_data/{key}" os.makedirs("/tmp/prjoxide_node_data", exist_ok=True) - + if os.path.exists(key_path): - #print(f"Nodefile found at {key_path}") - shutil.copyfile(key_path, nodefile) + logging.debug(f"Nodefile found at {key_path}") + shutil.copyfile(key_path, nodefile) else: if not udb.endswith(".udb"): device = udb @@ -253,23 +295,62 @@ def get_node_data(udb, nodes, regex=False): config = fuzzconfig.FuzzConfig(device, f"extract-site-info-{device}", []) config.setup() shutil.copyfile(config.udb, udb) - + re_slug = "-re " if regex else "" run_with_udb(udb, [f'dev_report_node -file {nodefile} [{get_nodes} {re_slug}{nodelist}]'], stdout = subprocess.DEVNULL) shutil.copyfile(nodefile, key_path) with open(key_path + ".input", 'w') as f: f.write(key_input) - #print(f"Nodefile cached at {key_path}") - + logging.debug(f"Nodefile cached at {key_path}") + with open(nodefile, 'r') as nf: - return parse_node_report(nf.read()) + return parse_node_report(nf.read(), nodes) -def get_sites(udb, rc = None): - if not udb.endswith(".udb"): - config = fuzzconfig.FuzzConfig(udb, "extract-site-info", []) - config.setup() - udb = config.udb +def get_node_data(udb, nodes, regex=False, executor = None): + from nodes_database import NodesDatabase + import fuzzloops + + if not isinstance(nodes, (list, set)): + nodes = [nodes] + else: + nodes = sorted(set(nodes)) + + if regex: + all_nodes = get_full_node_list(udb) + regex = [re.compile(n) for n in nodes] + nodes = sorted(set([n for n in all_nodes if any([r for r in regex if r.search(n) is not None])])) + + db = NodesDatabase.get(udb) + nis = db.get_node_data(nodes) + missing = sorted({k for k in nodes if k not in nis}) + futures = [] + + if len(missing): + cnt = 5000 + logging.info(f"Getting from lapie: {missing[:10]}...") + + with fuzzloops.Executor(executor) as local_executor: + def lapie_get_node_data(query): + s = time.time() + nodes = _get_node_data(udb, missing[:cnt]) + logging.debug(f"{len(query)} N {len(query) / (time.time() - s)} N/sec ({(time.time() - s)} deltas)") + return nodes + + def integrate_nodes(nodes): + db.insert_nodeinfos(nodes) + for n in nodes: + nis[n.name] = n + + while len(missing): + f = local_executor.submit(lapie_get_node_data, missing[:cnt]) + missing = missing[cnt:] + futures.append(fuzzloops.chain(f, integrate_nodes)) + if executor is not None: + return fuzzloops.chain(futures, lambda _: list(nis.values())) + else: + return list(nis.values()) +def _get_sites(udb, rc = None): rc_slug = "" if rc is not None: rc_slug = f"-row {rc[0]} -column {rc[1]}" @@ -277,6 +358,13 @@ def get_sites(udb, rc = None): return parse_sites(rpt) +def get_sites(device, rc = None): + if rc is None: + return _get_sites(device, rc) + + sites = get_sites_with_pin(device, rc) + return list(sites.keys()) + def parse_report_site(rpt): site_re = re.compile( r'^Site=(?P\S+)\s+' @@ -321,18 +409,20 @@ def parse_report_site(rpt): return sites -def get_sites_with_pin(udb, rc = None): - if not udb.endswith(".udb"): - config = fuzzconfig.FuzzConfig(udb, "extract-site-info", []) - config.setup() - udb = config.udb - - rc_slug = "" - if rc is not None: - rc_slug = f"-row {rc[0]} -column {rc[1]}" - rpt = run_with_udb(udb, [f'dev_report_site {rc_slug}']) +def get_sites_with_pin(device): + from nodes_database import NodesDatabase + + node_db = NodesDatabase.get(device) + + sites = node_db.get_sites() - return parse_report_site(rpt) + if len(sites) == 0: + rpt = run_with_udb(device, [f'dev_report_site'], stdout = subprocess.DEVNULL) + sites = parse_report_site(rpt) + + node_db.insert_sites(sites) + + return sites def list_nets(udb): diff --git a/util/common/nodes_database.py b/util/common/nodes_database.py new file mode 100644 index 0000000..31c2c07 --- /dev/null +++ b/util/common/nodes_database.py @@ -0,0 +1,421 @@ +import logging +import sqlite3 +from threading import RLock + + +class NodesDatabase: + _dbs = {} + _lock = RLock() + + @staticmethod + def get(device): + with NodesDatabase._lock: + if device not in NodesDatabase._dbs: + NodesDatabase._dbs[device] = NodesDatabase(device) + return NodesDatabase._dbs[device] + + def __init__(self, device): + import database + + self.db_path = f"{database.get_cache_dir()}/{device}-nodes.sqlite" + logging.info(f"Opening node database at {self.db_path}") + + self.device = device + self.conn = sqlite3.connect(self.db_path, check_same_thread=False) + self.lock = RLock() + self.init_db() + + def init_db(self): + with self.lock: + conn = self.conn + conn.execute("PRAGMA foreign_keys = ON") + cur = conn.cursor() + + cur.execute(""" + CREATE TABLE IF NOT EXISTS nodes ( + id INTEGER PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + has_full_data INTEGER NOT NULL DEFAULT 0 CHECK (has_full_data IN (0, 1)) + ); + """) + + # PIPs table: + # from_wire and to_wire are node IDs + # bidir = 0 (unidirectional) or 1 (bidirectional) + cur.execute(""" + CREATE TABLE IF NOT EXISTS pips ( + from_id INTEGER NOT NULL, + to_id INTEGER NOT NULL, + bidir INTEGER NOT NULL CHECK (bidir IN (0,1)), + jumpwire INTEGER NOT NULL CHECK (jumpwire IN (0,1)) DEFAULT 0, + flags INTEGER NOT NULL DEFAULT 0, + buffertype TEXT NOT NULL DEFAULT "", + PRIMARY KEY (from_id, to_id), + FOREIGN KEY (from_id) REFERENCES nodes(id), + FOREIGN KEY (to_id) REFERENCES nodes(id) + ) WITHOUT ROWID; + """) + + try: + cur.execute("ALTER TABLE pips ADD COLUMN jumpwire INTEGER") + except sqlite3.OperationalError as e: + pass + + cur.execute(""" + CREATE TEMP TABLE IF NOT EXISTS tmp_node_names ( + name TEXT PRIMARY KEY + ); + """) + + cur.execute(""" + CREATE TEMP TABLE IF NOT EXISTS tmp_node_ids ( + id INTEGER PRIMARY KEY + ); + """) + + cur.execute(""" + CREATE TABLE IF NOT EXISTS sites ( + id INTEGER PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + type TEXT NOT NULL, + x INTEGER NOT NULL, + y INTEGER NOT NULL + ); + """) + + cur.execute(""" +CREATE TABLE IF NOT EXISTS site_pins ( + site_id INTEGER NOT NULL, + pin_name TEXT NOT NULL, + node_id INTEGER NOT NULL, + + PRIMARY KEY (site_id, pin_name), + + FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE, + FOREIGN KEY (node_id) REFERENCES nodes(id) +) WITHOUT ROWID; + """) + + conn.commit() + + def _populate_tmp(self, cur, type, values): + cur.execute(f"DELETE FROM tmp_node_{type}s") + + cur.executemany( + f"INSERT INTO tmp_node_{type}s ({type}) VALUES (?)", + ((n,) for n in values) + ) + + def get_node_ids(self, names): + conn = self.conn + cur = conn.cursor() + self._populate_tmp(cur, "name", names) + + cur.executemany( + "INSERT OR IGNORE INTO nodes (name) VALUES (?)", + ((ni,) for ni in names) + ) + + cur.execute( + f"SELECT id, name FROM nodes where name IN (SELECT name from tmp_node_names)", + ) + + id_to_name = dict(cur.fetchall()) + name_to_id = {v: k for k, v in id_to_name.items()} + return name_to_id + + def get_jumpwires(self): + conn = self.conn + cur = conn.cursor() + + cur.execute(f""" + SELECT n1.name, n2.name, p.bidir, p.flags, p.buffertype + FROM pips p + JOIN nodes n1 ON n1.id = p.from_id + JOIN nodes n2 ON n2.id = p.to_id + WHERE jumpwire = 1 + """) + + for from_name, to_name, bidir, flags, bt in cur.fetchall(): + yield from_name, to_name + + def insert_jumpwires(self, jumpwires): + conn = self.conn + cur = conn.cursor() + + touched_names = set([w for ni in jumpwires for w in ni]) + + cur.executemany( + "INSERT OR IGNORE INTO nodes (name) VALUES (?)", + ((ni,) for ni in touched_names) + ) + + self._populate_tmp(cur, "name", touched_names) + + cur.execute( + f"SELECT id, name FROM nodes WHERE name IN (SELECT name from tmp_node_names)" + ) + id_to_name = dict(cur.fetchall()) + name_to_id = {v: k for k, v in id_to_name.items()} + + cur.executemany( + """ + INSERT OR IGNORE INTO pips (from_id, to_id, bidir, flags, buffertype) VALUES (?, ?, ?, ?, ?) + """, + [(name_to_id[j[0]], name_to_id[j[1]], 0, -1, "") for j in jumpwires] + ) + + cur.executemany( + """ + UPDATE pips + SET jumpwire = 1 + WHERE from_id = ? AND to_id = ? + """, + [(name_to_id[j[0]], name_to_id[j[1]]) for j in jumpwires] + ) + print("jmp", len(jumpwires)) + + conn.commit() + + def get_node_data(self, names): + from lapie import NodeInfo, PipInfo + + with self.lock: + conn = self.conn + cur = conn.cursor() + + self._populate_tmp(cur, "name", names) + + cur.execute( + f"SELECT id, name FROM nodes WHERE has_full_data = 1 and name IN (SELECT name from tmp_node_names)", + ) + id_to_name = dict(cur.fetchall()) + name_to_id = {v:k for k,v in id_to_name.items()} + + # Prepare result dict + result = {name: NodeInfo(name) for name in name_to_id} + + self._populate_tmp(cur, "id", list(id_to_name.keys())) + # ---- Downhill PIPs ---- + cur.execute(f""" + SELECT p.from_id, n2.name, p.bidir, p.flags, p.buffertype + FROM pips p + JOIN nodes n2 ON n2.id = p.to_id + WHERE p.from_id IN (SELECT id from tmp_node_ids) + """) + + for from_id, to_name, bidir, flags, bt in cur.fetchall(): + from_name = id_to_name[from_id] + result[from_name].downhill_pips.append( + PipInfo(from_name, to_name, + is_bidi=bool(bidir), + flags=flags, + buffertype=bt) + ) + + # ---- Uphill PIPs ---- + cur.execute(f""" + SELECT p.to_id, n1.name, p.bidir, p.flags, p.buffertype + FROM pips p + JOIN nodes n1 ON n1.id = p.from_id + WHERE p.to_id IN (SELECT id from tmp_node_ids) + """) + + for to_id, from_name, bidir, flags, bt in cur.fetchall(): + to_name = id_to_name[to_id] + result[to_name].uphill_pips.append( + PipInfo(from_name, to_name, + is_bidi=bool(bidir), + flags=flags, + buffertype=bt) + ) + + return result + + def insert_nodeinfos(self, nodeinfos): + with self.lock: + conn = self.conn + cur = conn.cursor() + + touched_names = set([w for ni in nodeinfos for p in ni.pips() for w in [p.to_wire, p.from_wire]]) | set([n.name for n in nodeinfos]) + + # 1. Insert all nodes + cur.executemany( + "INSERT OR IGNORE INTO nodes (name) VALUES (?)", + ((ni,) for ni in touched_names) + ) + + self._populate_tmp(cur, "name", {n.name for n in nodeinfos}) + + cur.execute( + f""" + UPDATE nodes + SET has_full_data = 1 + WHERE name IN (SELECT name from tmp_node_names) + """ + ) + # 2. Resolve node ids + names = [ni.name for ni in nodeinfos] + + self._populate_tmp(cur, "name", touched_names) + + cur.execute( + f"SELECT id, name FROM nodes WHERE name IN (SELECT name from tmp_node_names)" + ) + id_to_name = dict(cur.fetchall()) + name_to_id = {v:k for k,v in id_to_name.items()} + + pip_rows = [] + + for ni in nodeinfos: + for p in ni.pips(): + from_id = name_to_id.get(p.from_wire) + to_id = name_to_id.get(p.to_wire) + + pip_rows.append( + (from_id, to_id, + 1 if p.is_bidi else 0, + p.flags, + p.buffertype) + ) + + cur.executemany( + """ + INSERT OR IGNORE INTO pips + (from_id, to_id, bidir, flags, buffertype) + VALUES (?, ?, ?, ?, ?) + """, + pip_rows + ) + + conn.commit() + + def insert_sites_and_fetch_ids(self, sites): + if not sites: + return {} + + with self.conn: + cur = self.conn.cursor() + + self._populate_tmp(cur, "name", {s for s in sites}) + + cur.execute(""" + INSERT INTO sites (name) + SELECT t.name + FROM tmp_node_names t + LEFT JOIN sites s ON s.name = t.name + WHERE s.name IS NULL + """) + + rows = cur.execute(""" + SELECT s.name, s.id + FROM sites s + JOIN tmp_names t ON t.name = s.name + """).fetchall() + + return dict(rows) + + def insert_sites(self, sites): + conn = self.conn + cur = conn.cursor() + + # ---- Insert sites ---- + site_rows = [ + (name, + data["type"], + int(data["x"]), + int(data["y"])) + for name, data in sites.items() + ] + + cur.executemany( + """ + INSERT OR IGNORE INTO sites (name, type, x, y) + VALUES (?, ?, ?, ?) + """, + site_rows + ) + + site2id = dict(cur.execute(""" + SELECT s.name, s.id + FROM sites s + """).fetchall()) + + # ---- Resolve node IDs (from pin_node) ---- + node_names = { + pin["pin_node"] + for data in sites.values() + for pin in data["pins"] + } + + node2id = self.get_node_ids(node_names) + + # ---- Insert pins ---- + pin_rows = [] + + for site_name, data in sites.items(): + sid = site2id[site_name] + + for pin in data["pins"]: + nid = node2id.get(pin["pin_node"]) + if nid is None: + continue # or raise if missing nodes are an error + + pin_rows.append( + (sid, pin["pin_name"], nid) + ) + + cur.executemany( + """ + INSERT OR IGNORE INTO site_pins + (site_id, pin_name, node_id) + VALUES (?, ?, ?) + """, + pin_rows + ) + + conn.commit() + + def get_sites(self): + conn = self.conn + cur = conn.cursor() + result = {} + + # ---- Fetch sites ---- + cur.execute( + f""" + SELECT id, name, type, x, y + FROM sites + """, + ) + + site_rows = cur.fetchall() + if not site_rows: + return result + + site_id = {} + for sid, name, typ, x, y in site_rows: + site_id[sid] = name + result[name] = { + "type": typ, + "x": x, + "y": y, + "pins": [] + } + + # ---- Fetch pins ---- + cur.execute( + f""" + SELECT sp.site_id, sp.pin_name, n.name + FROM site_pins sp + JOIN nodes n ON n.id = sp.node_id + """, + ) + + for sid, pin_name, node_name in cur.fetchall(): + result[site_id[sid]]["pins"].append({ + "pin_name": pin_name, + "pin_node": node_name + }) + + return result diff --git a/util/common/radiant.py b/util/common/radiant.py index fafda43..89c0230 100644 --- a/util/common/radiant.py +++ b/util/common/radiant.py @@ -3,6 +3,7 @@ """ import asyncio import logging +import time from os import path import os import subprocess @@ -11,37 +12,56 @@ def run_bash_script(env, *args, cwd = None, stdout = subprocess.PIPE, stderr = subprocess.PIPE): slug = " ".join(args[1:]) - logging.info("Running script: %s", slug) + logging.debug("Running script: %s", slug) - proc = subprocess.run( - args=["bash", *args], - env=env, - cwd=cwd, - stdout=stdout, - stderr=subprocess.PIPE, - ) + subprocess_args = { + "args": ["bash", *args], + "env": env, + "cwd": cwd, + "stdout": stdout, + "stderr": stderr + } - stdout, stderr = proc.stdout, proc.stderr + def process_subprocess_result(stdout, stderr, returncode): + show_output = returncode != 0 or len(stderr.strip()) > 0 - returncode = proc.returncode - show_output = returncode != 0 + if show_output or logging.DEBUG >= logging.root.level: + for stream in [("", stdout, sys.stdout), ("ERR:", stderr, sys.stdout)]: + if stream[1] is not None: + for l in stream[1].decode().splitlines(): + logging.info(f"[{stream[0]} {slug}] {l}") - if show_output or True: - for stream in [("", stdout, sys.stdout), ("ERR:", stderr, sys.stdout)]: - if stream[1] is not None: - for l in stream[1].decode().splitlines(): - print(f"[{stream[0]} {slug}] {l}", file=stream[2]) + if returncode != 0: + raise Exception(f"Error encountered running radiant: {slug} {returncode}") - if returncode != 0: - raise Exception(f"Error encountered running radiant: {slug}") + # try: + # loop = asyncio.get_running_loop() + # + # async def async_function(): + # proc = await asyncio.create_subprocess_exec(**subprocess_args) + # + # stdout, stderr = await proc.communicate() + # + # process_subprocess_result(stdout, stderr, await proc.wait()) + # + # return proc + # + # return asyncio.run_coroutine_threadsafe(async_function(), loop).result() + # except RuntimeError: + # pass + + + proc = subprocess.run(**subprocess_args) + + process_subprocess_result(proc.stdout, proc.stderr, proc.returncode) return proc + def run(device, source, struct_ver=True, raw_bit=False, pdcfile=None, rbk_mode=False): """ Run radiant.sh with a given device name and source Verilog file """ - env = os.environ.copy() if struct_ver: env["STRUCT_VER"] = "1" @@ -51,5 +71,5 @@ def run(device, source, struct_ver=True, raw_bit=False, pdcfile=None, rbk_mode=F env["RBK_MODE"] = "1" dsh_path = path.join(database.get_oxide_root(), "radiant.sh") - + logging.info(f"Building [{device}] {source}") return run_bash_script(env, dsh_path, device, source) diff --git a/util/common/tiles.py b/util/common/tiles.py index e020695..0861b32 100644 --- a/util/common/tiles.py +++ b/util/common/tiles.py @@ -1,8 +1,14 @@ +import itertools +import random import re +from collections.abc import Iterable + import database from collections import defaultdict import lapie +import cachecontrol + pos_re = re.compile(r'R(\d+)C(\d+)') @@ -113,26 +119,26 @@ def get_sites_for_tile(device, tile): def get_full_node_set(device): if device not in _full_node_set: all_nodes = lapie.get_full_node_list(device) - _full_node_set[device] = set(all_nodes) + _full_node_set[device] = sorted(list(set([n for n in all_nodes if len(n)]))) return _full_node_set[device] -def get_nodes_for_tile(device, tile, owned = False): +def get_node_list_for_tile(device, tile, owned = False): if device not in _node_list_lookup: all_nodes = lapie.get_full_node_list(device) _node_list_lookup[device] = defaultdict(list) - _node_owned_lookup[device] = defaultdict(list) + _node_owned_lookup[device] = defaultdict(list) for name in all_nodes: rc = get_rc_from_name(device, name) if rc is None: continue elif rc[0] < 0 or rc[1] < 0: - print(f"Nodename {name} has negative rc: {rc}") + print(f"Nodename {name} has negative rc: {rc}") name_no_rc = "_".join(name.split("_")[1:]) m = _spine_regex.match(name_no_rc) if m is not None: - (r,c) = rc + (r, c) = rc orientation = m.group(1) size = int(m.group(2)) direction = m.group(3) @@ -141,9 +147,9 @@ def get_nodes_for_tile(device, tile, owned = False): if size == 0: continue - - assert(orientation in ["H", "V"]) - assert(direction in ["N","E","W","S"]) + + assert (orientation in ["H", "V"]) + assert (direction in ["N", "E", "W", "S"]) (dir_x, dir_y) = (0, 0) if direction == "N": @@ -157,44 +163,56 @@ def get_nodes_for_tile(device, tile, owned = False): rs = r - dir_y * segment cs = c - dir_x * segment - + for i in range(0, size + 1): ro = rs + dir_y * i co = cs + dir_x * i alias_name = f"R{ro}C{co}{orientation}{size:02}{direction}{track:02}{i:02}" - #_node_list_lookup[device][ro, co].append(name) + # _node_list_lookup[device][ro, co].append(name) if i == 0: - _node_owned_lookup[device][rc].append(name) + _node_owned_lookup[device][rc].append(name) else: _node_list_lookup[device][rc].append(name) _node_owned_lookup[device][rc].append(name) - def get_node_list_for_tile(t): + def _get_node_list_for_tile(t): return (_node_owned_lookup if owned else _node_list_lookup)[device].get(get_rc_from_name(device, t), []) - + + if isinstance(tile, list): + return {n:t for t in tile for n in _get_node_list_for_tile(t)} + else: + return _get_node_list_for_tile(tile) + +def get_nodes_for_tile(device, tile, owned = False): if isinstance(tile, list): - nodes2tile = {n:t for t in tile for n in get_node_list_for_tile(t)} + nodes2tile = {n:t for t in tile for n in get_node_list_for_tile(device, t, owned)} node_info = {n.name:n for n in lapie.get_node_data(device, list(nodes2tile.keys()), False)} tile_nodes = defaultdict(dict) - for n, nifo in node_info.items(): + for n, ninfo in node_info.items(): tile_nodes[nodes2tile[n]][n.name] = ninfo return tile_nodes else: - tile_nodes = get_node_list_for_tile(tile) + tile_nodes = get_node_list_for_tile(device, tile, owned) if len(tile_nodes) == 0: return {} return {n.name:n for n in lapie.get_node_data(device, tile_nodes, False)} - -def get_tiles_by_rc(device, rc): +_get_tiles_by_rc = {} +def get_tiles_by_rc(device, rc = None): if isinstance(rc, str): rc = get_rc_from_name(device, rc) - tilegrid = database.get_tilegrid(device)['tiles'] - return {k:v for k,v in tilegrid.items() if (v['y'], v['x']) == rc} + if device not in _get_tiles_by_rc: + tilegrid = database.get_tilegrid(device)['tiles'] + _get_tiles_by_rc[device] = defaultdict(dict) + for k,v in tilegrid.items(): + nrc = (v['y'], v['x']) + _get_tiles_by_rc[device][nrc][k] = v + + return _get_tiles_by_rc[device][rc] @@ -289,4 +307,191 @@ def draw_rc(rcs): print("■" if (x,y) in rcs else " " , end='') print() +def get_wires_for_tiles(device): + anon_nodes = defaultdict(lambda : defaultdict(list)) + for n in get_full_node_set(device): + wire_name = "_".join(n.split("_")[1:]) + rc = get_rc_from_name(device, n) + for tile in sorted(get_tiles_by_rc(device, rc)): + tiletype = tile.split(":")[-1] + anon_nodes[tiletype][wire_name].append(tile) + + return anon_nodes + +def get_wires_for_sites(device): + anon_nodes = defaultdict(lambda : defaultdict(list)) + sites = database.get_sites(device) + + for site, site_info in sites.items(): + pins = site_info['pins'] + pin_nodes = [p["pin_node"] for p in pins] + + for n in pin_nodes: + wire_name = "_".join(n.split("_")[1:]) + rc = get_rc_from_name(device, n) + + anon_nodes[site_info["type"]][wire_name].append(site) + return anon_nodes + +def get_representative_nodes_data(device, seed = 42, exclude_set = []): + rep_nodes = get_wires_for_tiles(device) + nodes = [] + random.seed(42) + + lookup = {} + for tiletype, wire_dict in sorted(rep_nodes.items()): + if tiletype not in exclude_set: + for wire, tiles in sorted(wire_dict.items()): + tile = random.choice(tiles) + (r,c) = get_rc_from_name(device, tile) + wire_name = f"R{r}C{c}_{wire}" + nodes.append(f"R{r}C{c}_{wire}") + lookup[wire_name] = (tiletype, wire, tile) + + nodes = sorted(nodes) + + batches = list(itertools.batched(nodes, 5000)) + batch_returns = [None] * len(batches) + + def f(idx_batch): + (idx, batch) = idx_batch + batch_returns[idx] = lapie.get_node_data(device, list(batch)) + + import fuzzloops + fuzzloops.parallel_foreach(enumerate(batches), f, jobs=len(batches)) + + node_data = {a:v + for d in batch_returns + for v in d + for a in v.aliases} + + rtn = defaultdict(list) + for wire_name, lu in lookup.items(): + rtn[lu[0]].append((lu[2], node_data[wire_name])) + + return rtn + +def get_node_data_local_graph(device, node, should_expand = None): + if isinstance(node, Iterable): + node = list(node) + + if not isinstance(node, list): + node = [node] + + rc = get_rc_from_name(device, node[0]) + def def_should_expand(node): + return rc == get_rc_from_name(device, node) + + if should_expand is None: + should_expand = def_should_expand + + query_list = node + + graph = {} + while len(query_list) > 0: + new_nodes = lapie.get_node_data(device, query_list) + #new_nodes = [k for k in lapie.get_list_arc(device) + query_list = [] + + for n in new_nodes: + graph[n.name] = n + + for p in n.pips(): + for wire in [p.to_wire, p.from_wire]: + if wire not in graph and should_expand(wire): + query_list.append(wire) + return graph + +def get_local_pips_for_site(device, site, include_interface_pips = True): + if isinstance(site, str): + sites = database.get_sites(device) + site = sites[site] + + site_nodes = [p["pin_node"] for p in site["pins"]] + + return get_local_pips_for_nodes(device, site_nodes, + include_interface_pips = include_interface_pips, + should_expand = lambda x: site["type"] in x) + +def get_local_pips_for_nodes(device, nodes, should_expand = None, include_interface_pips = True, executor = None): + if executor is not None: + return executor.submit(get_local_pips_for_nodes, device, nodes, should_expand = should_expand ,include_interface_pips = include_interface_pips) + + local_graph = get_node_data_local_graph(device, nodes, should_expand = should_expand) + + def should_include(p): + if include_interface_pips: + return p.from_wire in local_graph or p.to_wire in local_graph + else: + return p.from_wire in local_graph and p.to_wire in local_graph + + pips = [(p.from_wire, p.to_wire) for n, info, in local_graph.items() for p in info.pips() if + should_include(p)] + + return sorted(set(pips)), local_graph + +def get_representative_nodes_for_tiletype(device, tiletype): + node_set = None + + for tile in get_tiles_by_tiletype(device, tiletype): + nodes = set(["_".join(n.split("_")[1:]) for n in get_node_list_for_tile(device, tile)]) + if node_set is None: + node_set = nodes + else: + node_set = node_set & nodes + if node_set is None: + return set() + + return node_set + +def get_outlier_nodes_for_tiletype(device, tiletype): + repr_nodes = get_representative_nodes_for_tiletype(device, tiletype) + + outliers = {} + for tile in get_tiles_by_tiletype(device, tiletype): + nodes = set(["_".join(n.split("_")[1:]) for n in get_node_list_for_tile(device, tile)]) + + node_outliers = nodes - repr_nodes + + if len(node_outliers) > 0: + outliers[tile] = node_outliers + + return outliers + +@cachecontrol.cache_fn() +def get_connections_for_device(device): + arcs = lapie.get_list_arc(device) + + connections = {} + + for arc_node, arc_node_info in arcs.items(): + connections[arc_node] = set([p.to_wire for p in arc_node_info.downhill_pips]) + + return connections + +def find_path(device, frm, to): + nodes = lapie.get_node_data(device, [frm]) + + edges = {} + visited = set() + found = False + while not found: + query = set() + for n in nodes: + for p in n.uphill_pips: + if p.to_wire == to: + found = True + break + + if p.to_wire not in visited: + edges[p.to_wire] = n + visited.add(p.to_wire) + query.add(p.to_wire) + nodes = lapie.get_node_data(device, query) + path = [] + c = to + while c != frm: + path.append(c) + c = edges[c] + return path \ No newline at end of file diff --git a/util/fuzz/fuzzconfig.py b/util/fuzz/fuzzconfig.py index c61aa40..2c4aa43 100644 --- a/util/fuzz/fuzzconfig.py +++ b/util/fuzz/fuzzconfig.py @@ -1,9 +1,12 @@ """ This module provides a structure to define the fuzz environment """ +import gzip import logging import os +import threading from os import path +from pathlib import Path from string import Template import radiant import database @@ -23,14 +26,19 @@ def get_db(): def should_fuzz_platform(device): if PLATFORM_FILTER is not None and PLATFORM_FILTER not in device: if device not in _platform_skip_warnings: - print(f"FUZZER_PLATFORM set to {PLATFORM_FILTER}, skipping {device}") + logging.warning(f"FUZZER_PLATFORM set to {PLATFORM_FILTER}, skipping {device}") _platform_skip_warnings.add(device) return False return True class FuzzConfig: - def __init__(self, device, job, tiles, sv = None): + _standard_empty_bitfile = {} + radiant_cache_hits = 0 + radiant_builds = 0 + delta_skips = 0 + + def __init__(self, device, job, tiles=[], sv = None): """ :param job: user-friendly job name, used for folder naming etc :param device: Target device name @@ -43,29 +51,47 @@ def __init__(self, device, job, tiles, sv = None): if sv is None: family = device.split("-")[0] suffix = device.split("-")[1] - sv = database.get_db_root() + f"/../fuzzers/{family}/shared/empty.v" + sv = database.get_oxide_root() + f"/fuzzers/{family}/shared/empty.v" self.sv = sv self.rbk_mode = True if self.device == "LFCPNX-100" or self.device == "LIFCL-33U" else False self.struct_mode = True self.udb_specimen = None + @staticmethod + def standard_empty(device): + if device not in FuzzConfig._standard_empty_bitfile: + cfg = FuzzConfig(job=f"standard-empty-file", device=device, tiles=[]) + FuzzConfig._standard_empty_bitfile[device] = cfg.build_design(cfg.sv, {}, prefix="baseline/") + pass + return FuzzConfig._standard_empty_bitfile[device] + @property def workdir(self): - return path.join(".", "work", self.job) + return path.join(".", "work", self.device, self.job) def make_workdir(self): """Create the working directory for this job, if it doesn't exist already""" os.makedirs(self.workdir, exist_ok=True) + def delta_dir(self): + db_dir = os.environ.get("PRJOXIDE_DB", None) + if db_dir is not None: + db_name = Path(db_dir).name + return f".deltas/{db_name}/{self.device}" + + return f".deltas/{self.device}" + def serialize_deltas(self, fz, prefix = ""): - os.makedirs(".deltas", exist_ok=True) - fz.serialize_deltas(f".deltas/{prefix}{self.job}_{self.device}") + name = f"{self.delta_dir()}/{self.job}/{prefix}" + os.makedirs(Path(name).parent, exist_ok=True) + fz.serialize_deltas(name) def check_deltas(self, name): - if os.path.exists(f"./.deltas/{name}{self.job}_{self.device}.ron"): - logging.info(f"Delta exists for {name} {self.job} {self.device}; skipping") + if os.path.exists(f"{self.delta_dir()}/{self.job}/{name}.ron"): + logging.debug(f"Delta exists for {name} {self.job} {self.device}; skipping") + FuzzConfig.delta_skips = FuzzConfig.delta_skips + 1 return True - logging.debug(f"./.deltas/{name}{self.job}_{self.device}.ron miss") + logging.debug(f"{self.delta_dir()}/{self.job}/{name}.ron miss") return False def solve(self, fz): @@ -73,7 +99,8 @@ def solve(self, fz): fz.solve(db) self.serialize_deltas(fz, fz.get_name()) except: - self.serialize_deltas(fz, f"FAILED-{fz.get_name()}") + self.serialize_deltas(fz, f"{fz.get_name()}/FAILED") + raise def setup(self, skip_specimen=False): """ @@ -90,14 +117,26 @@ def setup(self, skip_specimen=False): self.build_design(self.sv, {}) def subst_defaults(self): + packages = { + "LIFCL-33": "WLCSP84", + "LIFCL-33U": "WLCSP84", + "LFCPNX-40": "LFG672", + "LFCPNX-100": "LFG672" + } + return { - "arch": "LIFCL", + "arch": self.device.split("-")[0], "arcs_attr": "", "device": self.device, - "package": "WLCSP84" if self.device.startswith("LIFCL-33") else "QFN72", + "package": packages.get(self.device, "QFN72"), "speed_grade": "8" if self.device == "LIFCL-33" else "7" } - + + def build_design_future(self, executor, *args, **kwargs): + future = executor.submit(self.build_design, *args, **kwargs) + future.name = f"Build {self.device}" + return future + def build_design(self, des_template, substitutions = {}, prefix="", substitute=True): """ Run Radiant on a given design template, applying a map of substitutions, plus some standard substitutions @@ -111,18 +150,15 @@ def build_design(self, des_template, substitutions = {}, prefix="", substitute=T """ subst = dict(substitutions) - subst_defaults = { - "arch": self.device.split("-")[0], - "arcs_attr": "", - "device": self.device, - "package": "WLCSP84" if self.device.startswith("LIFCL-33") else "QFN72", - "speed_grade": "8" if self.device == "LIFCL-33" else "7" - } + prefix = f"{threading.get_ident()}/{prefix}" + + subst_defaults = self.subst_defaults() subst = subst_defaults | subst os.makedirs(path.join(self.workdir, prefix), exist_ok=True) desfile = path.join(self.workdir, prefix + "design.v") + bitfile = path.join(self.workdir, prefix + "design.bit") bitfile_gz = path.join(self.workdir, prefix + "design.bit.gz") @@ -131,15 +167,49 @@ def build_design(self, des_template, substitutions = {}, prefix="", substitute=T with open(pdcfile, "w") as pdcf: pdcf.write("ldc_set_sysconfig {{{}}}\n".format(subst["sysconfig"])) - if path.exists(bitfile): - os.remove(bitfile) + for bf in [bitfile, bitfile_gz]: + if path.exists(bf): + os.remove(bf) + with open(des_template, "r") as inf: with open(desfile, "w") as ouf: if substitute: ouf.write(Template(inf.read()).substitute(**subst)) else: ouf.write(inf.read()) - radiant.run(self.device, desfile, struct_ver=self.struct_mode, raw_bit=False, rbk_mode=self.rbk_mode) + + env = os.environ.copy() + if self.struct_mode: + env["STRUCT_VER"] = "1" + if self.rbk_mode: + env["RBK_MODE"] = "1" + + needs_udb = self.struct_mode and self.udb_specimen is None + + import bitstreamcache + cached_result = bitstreamcache.fetch(self.device, [desfile], env=env) + foundFile = None + for (outprod, gzfile) in cached_result: + if gzfile.endswith(".bit.gz"): + FuzzConfig.radiant_cache_hits = FuzzConfig.radiant_cache_hits + 1 + foundFile = gzfile + elif needs_udb and gzfile.endswith(".udb.gz"): + with gzip.open(gzfile, 'rb') as gzf: + self.udb_specimen = path.join(self.workdir, prefix, "par.udb") + Path(self.udb_specimen).parent.mkdir(parents=True, exist_ok=True) + with open(self.udb_specimen, 'wb') as outf: + outf.write(gzf.read()) + + if foundFile is not None: + return foundFile + + FuzzConfig.radiant_builds = FuzzConfig.radiant_builds + 1 + process_results = radiant.run(self.device, desfile, struct_ver=self.struct_mode, raw_bit=False, rbk_mode=self.rbk_mode) + + error_output = process_results.stderr.decode().strip() + if "ERROR <" in error_output: + raise Exception(f"Error found during bitstream build: {error_output}") + if self.struct_mode and self.udb_specimen is None: self.udb_specimen = path.join(self.workdir, prefix + "design.tmp", "par.udb") if path.exists(bitfile): diff --git a/util/fuzz/fuzzloops.py b/util/fuzz/fuzzloops.py index 6e4d172..064d00a 100644 --- a/util/fuzz/fuzzloops.py +++ b/util/fuzz/fuzzloops.py @@ -2,26 +2,67 @@ General Utilities for Fuzzing """ import asyncio +import concurrent +import logging import os +import shutil +import signal +import threading +import time +from asyncio import CancelledError +from pathlib import Path +from signal import SIGINT, SIGTERM + +import lapie + +from collections import defaultdict + +from concurrent.futures import ThreadPoolExecutor, Future +from contextlib import contextmanager from threading import Thread, RLock import traceback -def parallel_foreach(items, func): +is_in_loop = False + +def jobs(): + if "OXIDE_JOBS" in os.environ: + jobs = int(os.environ["OXIDE_JOBS"]) + else: + jobs = 4 + return jobs + +@contextmanager +def Executor(executor=None): + cleanup = executor is None + if executor is None: + executor = ThreadPoolExecutor(jobs()) + try: + yield executor + finally: + if cleanup: + executor.shutdown(wait=True) + + +def parallel_foreach(items, func, jobs = None): """ Run a function over a list of values, running a number of jobs in parallel. OXIDE_JOBS should be set to the number of jobs to run, defaulting to 4. """ - if "OXIDE_JOBS" in os.environ: - jobs = int(os.environ["OXIDE_JOBS"]) - else: - jobs = 4 + if jobs is None: + if "OXIDE_JOBS" in os.environ: + jobs = int(os.environ["OXIDE_JOBS"]) + else: + jobs = 4 + items_queue = list(items) items_lock = RLock() exception = None print(f"Starting loop with {exception} jobs") - + + global is_in_loop + is_in_loop = True def runner(): nonlocal exception @@ -50,3 +91,259 @@ def runner(): t.join() if exception is not None: raise exception + is_in_loop = False + +def gather_futures(futures, name = None): + """ + Returns a Future that completes when all input futures complete. + Result is a list of results in the same order. + """ + out = Future() + n = len(futures) + results = [None] * n + remaining = n + lock = threading.Lock() + + def _done(i, fut): + nonlocal remaining + try: + if hasattr(fut, "result"): + res = fut.result() + else: + res = fut + except Exception as e: + # fail fast: propagate first exception + with lock: + if not out.done(): + out.set_exception(e) + return + + with lock: + if out.done(): + return + results[i] = res + remaining -= 1 + if remaining == 0: + out.set_result(results) + + executor = None + for i, fut in enumerate(futures): + if hasattr(fut, 'executor'): + executor = fut.executor + if hasattr(fut, "result"): + fut.add_done_callback(lambda f, i=i: _done(i, f)) + else: + _done(i, fut) + + if executor is not None: + executor.register_future(out) + + if name is not None: + out.name = name + + if n == 0: + out.set_result([]) + + return out + +def chain(future, func, name = None, *args, **kwargs): + if isinstance(future, list): + future = gather_futures(future) + + fut = Future() + + def _done(f): + r = None + try: + r = f.result() + fut.set_result(func(r, *args, **kwargs)) + except BaseException as e: + logging.error(f"Encountered exception while calling {func} with {r} {args} {kwargs}") + traceback.print_exception(e) + try: + raise RuntimeError(f"Encountered exception while calling {func} with {r} {args} {kwargs}") from e + except BaseException as f: + fut.set_exception(f) + + except: + fut.set_exception(Exception("Unknown exception in future")) + + future.add_done_callback(_done) + if hasattr(future, 'executor'): + future.executor.register_future(fut) + + if name is not None: + fut.name = name + + return fut + +class AsyncExecutor: + def __init__(self): + self.futures = [] + self.lock = RLock() + self.loop = asyncio.get_running_loop() + + def submit(self, f, *args, **kwargs): + future = self.loop.run_in_executor(None, lambda args=args,kwargs=kwargs: f(*args, **kwargs)) + future.name = f.__name__ + self.register_future(future) + return future + + def register_future(self, future): + future.executor = self + with self.lock: + self.futures.append(future) + + def iterate_futures(self): + with self.lock: + local_futures = self.futures + self.futures = [] + + new_futures = [] + for f in local_futures: + yield f + if not f.done(): + new_futures.append(f) + + with self.lock: + self.futures.extend(new_futures) + + def busy(self): + return len(self.futures) > 0 + + def task_count(self): + return len(self.futures) + +def FuzzerAsyncMain(f): + from fuzzconfig import FuzzConfig + + import rich + import rich.console + from rich.live import Live + from rich.panel import Panel + from rich.text import Text + + console = rich.console.Console() + import sys + sys.stdout = console.file + + from rich.logging import RichHandler + + LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() + + logging.basicConfig( + level=LOGLEVEL, + handlers=[RichHandler(console=console, show_time=False, show_path=False)], + ) + + + start_time = time.time() + + async def start(f): + async_executor = None + + main_task = asyncio.current_task() + int_count = 0 + def sighandler(sig, frame): + nonlocal int_count + print(f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! SIG HANDLER !!!!!!!!!!!!!!!!!!!!!!! {int_count}") + if int_count > 1: + sys.exit(-1) + + main_task.cancel() + int_count = int_count + 1 + + for sig in [SIGINT, SIGTERM]: + signal.signal(sig, sighandler) + + try: + def status_panel(status: str) -> Panel: + return Panel( + f"[bold cyan]{status}[/bold cyan]", + title="Status", + border_style="blue", + height=3, + ) + + async def ui(async_executor): + with Live(status_panel(""), refresh_per_second=10, console=console) as live: + finished_tasks = 0 + + while async_executor.busy() or not task.done(): + histogram = defaultdict(int) + + def process_future(fut): + nonlocal finished_tasks + + name = "anon" + if hasattr(fut, "name"): + name = fut.name + elif hasattr(fut, "get_stack"): + fn = fut.get_stack()[-1].f_code.co_name + if fn != "ui" and fn != "start": + ln = fut.get_stack()[-1].f_lineno + name = f"{fn}:{ln}" + else: + name = None + + if name is not None: + histogram[name] = histogram[name] + 1 + + if fut.done(): + if fut.exception() is not None: + all_exceptions.append(fut.exception()) + else: + finished_tasks = finished_tasks + 1 + fut.result() + + for fut in async_executor.iterate_futures(): process_future(fut) + for fut in asyncio.all_tasks(): process_future(fut) + + width = shutil.get_terminal_size().columns + text = f"{list(histogram.items())} {async_executor.task_count()} {finished_tasks} finished {len(all_exceptions)} errors, built/cached {FuzzConfig.radiant_builds}/{FuzzConfig.radiant_cache_hits} tool queries {lapie.run_with_udb_cnt} {int(time.time() - start_time)}s" + # print("{text:>{width}}".format(text=text, width=width), end="\r") + live.update(status_panel(text)) + await asyncio.sleep(.1) + + + with Executor() as executor: + try: + asyncio.get_running_loop().set_default_executor(executor) + + async_executor = AsyncExecutor() + + all_exceptions = [] + + ui_task = asyncio.create_task(ui(async_executor)) + task = asyncio.create_task(f(async_executor)) + + await asyncio.gather(task, ui_task) + except CancelledError: + executor.shutdown(wait=False, cancel_futures=True) + raise + + + except KeyboardInterrupt: + logging.warning("Keyboard interrupt") + except CancelledError: + if int_count > 1: + sys.exit(-1) + int_count = int_count + 1 + + + if len(all_exceptions): + logging.error(f"Encountered the following {len(all_exceptions)} errors:") + for e in all_exceptions: + traceback.print_exception(e) + + logging.info(f"Processed {FuzzConfig.radiant_builds}/{FuzzConfig.radiant_cache_hits} bitfiles in {time.time() - start_time} seconds. Skipped {FuzzConfig.delta_skips} solves due to existing .delta files") + + asyncio.run(start(f)) + + + +def FuzzerMain(f): + async def async_main(executor): + return f(executor) + + FuzzerAsyncMain(async_main) \ No newline at end of file diff --git a/util/fuzz/interconnect.py b/util/fuzz/interconnect.py index 0192e93..5112a83 100644 --- a/util/fuzz/interconnect.py +++ b/util/fuzz/interconnect.py @@ -1,8 +1,11 @@ """ Utilities for fuzzing interconect """ - +import logging import threading +from concurrent.futures.thread import ThreadPoolExecutor +from pathlib import Path + import tiles import libpyprjoxide import fuzzconfig @@ -21,10 +24,12 @@ workdir = tempfile.mkdtemp() -def create_wires_file(config, wires, prefix = "", empty_version = False): +def create_wires_file(config, wires, prefix = "", empty_version = False, executor = None): if empty_version: prefix = prefix + "_empty" - + if isinstance(wires, set): + wires = sorted(wires) + if isinstance(wires, list): touched_tiles = set([tiles.get_rc_from_name(config.device, n) for w in wires for n in w]) @@ -65,10 +70,15 @@ def create_wires_file(config, wires, prefix = "", empty_version = False): endmodule """ - vfile = path.join(workdir, f"{prefix}.v") + vfile = path.join(workdir, f"{prefix}{config.job}.v") + Path(vfile).parent.mkdir(parents=True, exist_ok=True) + with open(vfile, 'w') as f: f.write(source) - return config.build_design(vfile, prefix = prefix) + + if executor is not None: + return config.build_design_future(executor, vfile, prefix=prefix) + return config.build_design(vfile, prefix=prefix) def pips_to_sinks(pips): sinks = {} @@ -92,14 +102,11 @@ def collect_sinks(config, nodenames, regex = False, if regex: all_nodes = lapie.get_full_node_list(config.device) regex = [re.compile(n) for n in nodenames] - print(regex, nodenames) nodenames = [n for n in all_nodes if any([r for r in regex if r.search(n) is not None])] regex = False - - nodes = lapie.get_node_data(config.udb, nodenames, regex) - print(regex, {n.name:n.aliases for n in nodes}) - + nodes = lapie.get_node_data(config.device, nodenames, regex) + all_wirenames = set([n.name for n in nodes]) all_pips = set() for node in nodes: @@ -120,10 +127,10 @@ def collect_sinks(config, nodenames, regex = False, # Then filter using the pip predicate fuzz_pips = list(filter(lambda x: pip_predicate(x, all_wirenames), all_pips)) if len(fuzz_pips) == 0: - print(f"No fuzz_pips defined for job {config}. Nodes: {nodes} {all_pips}") - return + logging.warning(f"No fuzz_pips defined for job {config}. Nodes: {nodes} {all_pips}") + return {} + logging.debug(f"Fuzz pips {len(fuzz_pips)}") - print(fuzz_pips) return pips_to_sinks(fuzz_pips) def fuzz_interconnect_sinks( @@ -132,36 +139,60 @@ def fuzz_interconnect_sinks( full_mux_style=False, ignore_tiles=set(), extra_substs={}, - fc_filter=lambda x: True + fc_filter=lambda x: True, + executor = None ): if sinks is None: return + if not isinstance(sinks, dict): sinks = pips_to_sinks(sinks) - base_bitf = config.build_design(config.sv, extra_substs, "base_") - - def per_sink(to_wire): - if config.check_deltas(to_wire): - return + base_bitf_future = config.build_design_future(executor, config.sv, extra_substs, "base_") - # Get a unique prefix from the thread ID - prefix = "thread{}_{}_{}_{}_".format(threading.get_ident(), config.job, config.device, to_wire) - print(config.tiles) - fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.db, base_bitf, set(config.tiles), to_wire, config.tiles[0], set(ignore_tiles), full_mux_style, not (fc_filter(to_wire))) - for from_wire in sinks[to_wire]: - arcs_attr = r', \dm:arcs ="{}.{}"'.format(to_wire, from_wire) - substs = extra_substs.copy() - substs["arcs_attr"] = arcs_attr - print(f"Building design for ({config.job} {config.device}) {to_wire} to {from_wire}") - arc_bit = config.build_design(config.sv, substs, prefix) - fz.add_pip_sample(fuzzconfig.db, from_wire, arc_bit) + logging.info(f"Processing {len(sinks)} sinks for {sum([len(v) for k,v in sinks.items()])} designs for {config.job} {config.device}") + assert(len(config.tiles) > 0) + + def process_bits(bitstreams, from_wires, to_wire): + base_bitf = bitstreams[0] + bitstreams = bitstreams[1:] + fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.get_db(), base_bitf, set(config.tiles), to_wire, + config.tiles[0], + set(ignore_tiles), full_mux_style, not (fc_filter(to_wire))) + + for (from_wire, arc_bit) in zip(from_wires, bitstreams): + fz.add_pip_sample(fuzzconfig.get_db(), from_wire, arc_bit if arc_bit is not None else base_bitf) + + logging.debug(f"Solving for {to_wire}") config.solve(fz) - fuzzloops.parallel_foreach(list(sorted(sinks.keys())), per_sink) - + conns = tiles.get_connections_for_device(config.device) + + with fuzzloops.Executor(executor) as executor: + for to_wire in sinks: + if config.check_deltas(to_wire): + continue + + bitstream_futures = [base_bitf_future] + for from_wire in sinks[to_wire]: + arcs_attr = r', \dm:arcs ="{}.{}"'.format(to_wire, from_wire) + substs = extra_substs.copy() + substs["arcs_attr"] = arcs_attr + + arc_bit = None + if to_wire in conns.get(from_wire, {}): + logging.debug(f"{from_wire} -> {to_wire} is in arc list; not building file") + else: + logging.debug(f"Building design for ({config.job} {config.device}) {to_wire} to {from_wire}") + arc_bit = config.build_design_future(executor, config.sv, substs, f"{from_wire}/{to_wire}/") + yield arc_bit + + bitstream_futures.append(arc_bit) + + yield fuzzloops.chain(bitstream_futures, process_bits, "Interconnect sink", sinks[to_wire], to_wire) + def fuzz_interconnect( config, nodenames, @@ -173,7 +204,8 @@ def fuzz_interconnect( full_mux_style=False, ignore_tiles=set(), extra_substs={}, - fc_filter=lambda x: True + fc_filter=lambda x: True, + executor = None ): """ Fuzz interconnect given a list of nodenames to analyse. Pips associated these nodenames will be found using the Tcl @@ -197,14 +229,14 @@ def fuzz_interconnect( """ if not fuzzconfig.should_fuzz_platform(config.device): return - + sinks = collect_sinks(config, nodenames, regex = regex, nodename_predicate = nodename_predicate, pip_predicate = pip_predicate, bidir=bidir, nodename_filter_union=False) - fuzz_interconnect_sinks(config, sinks, full_mux_style, ignore_tiles, extra_substs, fc_filter) + yield from fuzz_interconnect_sinks(config, sinks, full_mux_style, ignore_tiles, extra_substs, fc_filter, executor=executor) def fuzz_interconnect_for_tiletype(device, tiletype): prototype = list(tiles.get_tiles_by_tiletype(device, tiletype).keys())[0] @@ -235,7 +267,7 @@ def per_pip(pin_info, pin_pip): is_output = pin_info['pin_node'] == pin_pip.from_wire prefix = "{}_{}_{}_".format(config.job, config.device, to_wire) - fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.db, base_bitf, + fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.get_db(), base_bitf, set(config.tiles), to_wire, config.tiles[0], set(), full_mux_style, not (fc_filter(to_wire))) @@ -248,7 +280,7 @@ def per_pip(pin_info, pin_pip): print(f"Building design for ({config.job} {config.device}) {to_wire} to {from_wire}") arc_bit = config.build_design(config.sv, substs, prefix) - fz.add_pip_sample(fuzzconfig.db, from_wire, arc_bit) + fz.add_pip_sample(fuzzconfig.get_db(), from_wire, arc_bit) config.solve(fz) diff --git a/util/fuzz/nonrouting.py b/util/fuzz/nonrouting.py index 6ebe551..939abfa 100644 --- a/util/fuzz/nonrouting.py +++ b/util/fuzz/nonrouting.py @@ -11,8 +11,14 @@ from primitives import EnumSetting - -def fuzz_word_setting(config, name, length, get_sv_substs, desc=""): +def fuzz_intval(vec): + x = 0 + for i, b in enumerate(vec): + if b: + x |= (1 << i) + return x + +def fuzz_word_setting(config, name, length, get_sv_substs, desc="", executor = None): """ Fuzz a multi-bit setting, such as LUT initialisation @@ -22,19 +28,34 @@ def fuzz_word_setting(config, name, length, get_sv_substs, desc=""): :param get_sv_substs: a callback function, that is called with an array of bits to create a design with that setting """ if not fuzzconfig.should_fuzz_platform(config.device): - return - - prefix = "thread{}_".format(threading.get_ident()) - baseline = config.build_design(config.sv, get_sv_substs([False for _ in range(length)]), prefix) - fz = libpyprjoxide.Fuzzer.word_fuzzer(fuzzconfig.db, baseline, set(config.tiles), name, desc, length, baseline) - for i in range(length): - i_bit = config.build_design(config.sv, get_sv_substs([(_ == i) for _ in range(length)]), prefix) - fz.add_word_sample(fuzzconfig.db, i, i_bit) - - config.solve(fz) - -def fuzz_enum_setting(config, empty_bitfile, name, values, get_sv_substs, include_zeros=True, - assume_zero_base=False, min_cover={}, desc="", mark_relative_to=None): + return [] + + with fuzzloops.Executor(executor) as executor: + prefix = f"{name}/" + + baseline = config.build_design_future(executor, config.sv, get_sv_substs([False for _ in range(length)]), prefix + "baseline/") + + bitstream_futures = [ + config.build_design_future(executor, config.sv, get_sv_substs([(_ == i) for _ in range(length)]), + prefix + f"{i}/") + for i in range(length) + ] + + def integrate_bitstreams(bitstreams): + baseline = bitstreams[0] + bitstreams = bitstreams[1:] + + fz = libpyprjoxide.Fuzzer.word_fuzzer(fuzzconfig.db, baseline, set(config.tiles), name, desc, length, + baseline) + for i in range(length): + fz.add_word_sample(fuzzconfig.db, i, bitstreams[i]) + + config.solve(fz) + + return [*bitstream_futures, fuzzloops.chain([baseline, *bitstream_futures], integrate_bitstreams, name = "Solve word")] + +def fuzz_enum_setting(config, empty_bitfile, name, values, get_sv_substs, include_zeros=False, + assume_zero_base=False, min_cover={}, desc="", mark_relative_to=None, executor = None): """ Fuzz a setting with multiple possible values @@ -49,37 +70,47 @@ def fuzz_enum_setting(config, empty_bitfile, name, values, get_sv_substs, includ :param min_cover: for each setting in this, run with each value in the array that setting points to, to get a minimal bit set """ - if not fuzzconfig.should_fuzz_platform(config.device): - return - if config.check_deltas(name): - return + assert len(values) > 1, f"Enum setting {name} requires more than one option (given {values})" - prefix = "thread{}_{}_{}_{}_".format(threading.get_ident(), config.job, config.device, name) - try: - fz = libpyprjoxide.Fuzzer.enum_fuzzer(fuzzconfig.db, empty_bitfile, set(config.tiles), name, desc, include_zeros, assume_zero_base, mark_relative_to = mark_relative_to) - except: - print(f"ERROR: from {empty_bitfile}") - raise - - for opt in values: - opt_name = opt - if opt == "#SIG" and name.endswith("MUX"): - opt_name = name[:-3].split(".")[1] - if opt == "#INV": - opt_name = "INV" - - if opt in min_cover: - for c in min_cover[opt]: - opt_bit = config.build_design(config.sv, get_sv_substs((opt, c)), prefix) - fz.add_enum_sample(fuzzconfig.db, opt_name, opt_bit) - else: - opt_bit = config.build_design(config.sv, get_sv_substs(opt), "{}{}_".format(prefix, opt)) - fz.add_enum_sample(fuzzconfig.db, opt_name, opt_bit) - - config.solve(fz) + if not fuzzconfig.should_fuzz_platform(config.device): + return [] -def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=None): + if config.check_deltas(name): + return [] + + with fuzzloops.Executor(executor) as executor: + futures = [] + + def integrate_build(subs, prefix, opt_name): + bitstream = config.build_design(config.sv, subs, prefix) + return (opt_name, bitstream) + + for opt in values: + opt_name = opt + if opt == "#SIG" and name.endswith("MUX"): + opt_name = name[:-3].split(".")[1] + if opt == "#INV": + opt_name = "INV" + + if opt in min_cover: + for c in min_cover[opt]: + futures.append(executor.submit(integrate_build, get_sv_substs((opt, c)), f"cover/{name}/{opt}/{c}", opt_name)) + else: + futures.append(executor.submit(integrate_build, get_sv_substs(opt), f"{name}/{opt}/", opt_name)) + for future in futures: + future.name = "Build design" + + def integrate_bitstreams(bitstreams): + fz = libpyprjoxide.Fuzzer.enum_fuzzer(fuzzconfig.db, empty_bitfile, set(config.tiles), name, desc, + include_zeros, assume_zero_base, mark_relative_to=mark_relative_to) + for (opt, bitstream) in bitstreams: + fz.add_enum_sample(fuzzconfig.db, opt, bitstream) + config.solve(fz) + + return fuzzloops.chain(futures, integrate_bitstreams, "Enum Setting") + +def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=None, executor = None): """ Fuzz a multi-bit IP setting with an optimum number of bitstreams @@ -89,9 +120,12 @@ def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=N :param get_sv_substs: a callback function, that is called with an array of bits to create a design with that setting """ if not fuzzconfig.should_fuzz_platform(config.device): - return + return [] - prefix = "thread{}_".format(threading.get_ident()) + if config.check_deltas(name): + return [] + + prefix = f"{name}/" inverted_mode = False if default is not None: @@ -101,17 +135,25 @@ def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=N inverted_mode = True break - baseline = config.build_design(config.sv, get_sv_substs([inverted_mode for _ in range(length)]), prefix) - ipcore, iptype = config.tiles[0].split(":") - fz = libpyprjoxide.IPFuzzer.word_fuzzer(fuzzconfig.db, baseline, ipcore, iptype, name, desc, length, inverted_mode) - for i in range(0, length.bit_length()): - bits = [(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)] - i_bit = config.build_design(config.sv, get_sv_substs(bits), prefix) - fz.add_word_sample(fuzzconfig.db, bits, i_bit) + baseline_future = config.build_design_future(executor, config.sv, get_sv_substs([inverted_mode for _ in range(length)]), prefix) - config.solve(fz) + bitstream_futures = [ + config.build_design_future(executor, config.sv, get_sv_substs([(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)]), f"{prefix}/{i}/") + for i in range(0, length.bit_length()) + ] -def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, desc=""): + def integrate_bitstreams(bitstreams): + baseline = bitstreams[0] + ipcore, iptype = config.tiles[0].split(":") + fz = libpyprjoxide.IPFuzzer.word_fuzzer(fuzzconfig.db, baseline, ipcore, iptype, name, desc, length, inverted_mode) + for (i, bitfile) in enumerate(bitstreams[1:]): + bits = [(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)] + fz.add_word_sample(fuzzconfig.db, bits, bitfile) + config.solve(fz) + + return [*bitstream_futures, fuzzloops.chain([baseline_future, *bitstream_futures], integrate_bitstreams, name = "Solve IP word")] + +def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, desc="", executor = None): """ Fuzz a multi-bit IP enum with an optimum number of bitstreams @@ -123,16 +165,25 @@ def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, des """ if not fuzzconfig.should_fuzz_platform(config.device): return - - prefix = "thread{}_".format(threading.get_ident()) + + if config.check_deltas(name): + return + ipcore, iptype = config.tiles[0].split(":") - fz = libpyprjoxide.IPFuzzer.enum_fuzzer(fuzzconfig.db, empty_bitfile, ipcore, iptype, name, desc) - for opt in values: - opt_bit = config.build_design(config.sv, get_sv_substs(opt), prefix) - fz.add_enum_sample(fuzzconfig.db, opt, opt_bit) + prefix = f"{ipcore}/{name}" + + bitstream_futures = [ + config.build_design_future(executor, config.sv, get_sv_substs(opt), f"{prefix}/{opt}/") + for opt in values + ] - config.solve(fz) + def integrate_bitstreams(bitstreams): + fz = libpyprjoxide.IPFuzzer.enum_fuzzer(fuzzconfig.db, empty_bitfile, ipcore, iptype, name, desc) + for (opt, bitfile) in zip(values, bitstreams): + fz.add_enum_sample(fuzzconfig.db, opt, bitfile) + config.solve(fz) + return [*bitstream_futures, fuzzloops.chain(bitstream_futures, integrate_bitstreams, name = "Solve IP enum")] def fuzz_primitive_definition(cfg, empty, site, primitive, mark_relative_to = None, mode_name = None, get_substs=None): def default_get_substs(mode="NONE", kv=None): diff --git a/util/fuzz/primitives.py b/util/fuzz/primitives.py index 2bc5aee..2579f71 100644 --- a/util/fuzz/primitives.py +++ b/util/fuzz/primitives.py @@ -1,97 +1,267 @@ +import json +from collections import defaultdict +from pathlib import Path +from cffi.model import PrimitiveType -class PinSetting: - def __init__(self, name, dir, desc=""): + +class PrimitiveSetting: + def __init__(self, name, desc, depth=3, enable_value=None): self.name = name - self.dir = dir self.desc = desc + self.depth = depth + self.enable_value = enable_value -class WordSetting: - def __init__(self, name, bits, desc=""): - self.name = name + def format(self, prim, value): + if self.depth == -1: + return f"{self.name}:{value}" + + k = self.name + seperator = ":" * self.depth + if "." not in k: + k = prim.primitive + seperator + k + else: + k = k.replace(".", seperator) + return f"{k}={value}" + + +class PinSetting(PrimitiveSetting): + def __init__(self, name, dir, desc="", bits=None): + super().__init__(name, desc) + self.dir = dir self.bits = bits - self.desc = desc -class EnumSetting: - def __init__(self, name, values, mux = False, desc=""): - self.name = name + def __repr__(self): + return f'PinSetting(name = "{self.name}", dir = "{self.dir}", desc = "{self.desc}", bits = {self.bits})' + + +class WordSetting(PrimitiveSetting): + def __init__(self, name, bits, default=None, desc="", number_formatter=None, enable_value=None): + super().__init__(name, desc, enable_value=enable_value) + self.bits = bits + self.default = default + self.number_formatter = number_formatter + if self.number_formatter is None: + self.number_formatter = lambda _, x: x + + def binary_formatter(self, v): + return f"0b{v:0{self.bits}b}" + + def signed_formatter(self, v): + return (-1 if (1 << self.bits) else 1) * (~(1 << self.bits) & v) + + def format(self, prim, value): + return super().format(prim, self.number_formatter(self, value)) + + def fill_value(self): + return 1 + +class EnumSetting(PrimitiveSetting): + def __init__(self, name, values, default=None, desc="", enable_value=None, depth=3): + super().__init__(name, desc, enable_value=enable_value) self.values = values + self.default = default + + def fill_value(self): + return self.values[-1] + +class ProgrammablePin(EnumSetting): + def __init__(self, name, values, desc="", primitive = None): + super().__init__(name, values, desc=desc, depth=4) + if primitive is None: + primitive = name + "MUX" + self.primitive = primitive + + def format(self, prim, value): + if value == "#OFF": + return f"{self.primitive}:#OFF" + elif value[0] == "#": + #return f"{self.primitive}:{self.name}:::{self.name}={value}" + return f"{self.primitive}::::{self.name}={value}" + else: + return f"{self.primitive}:CONST:::CONST={value}" + raise Exception(f"Unknown value {value}") + + +primitives = defaultdict(list) + + +class PrimitiveDefinition(object): + def __init__(self, site_type, settings=[], pins=[], mode=None, desc=None, primitive=None): + self.site_type = site_type + self.mode = mode self.desc = desc - self.mux = mux - -class PrimitiveDefinition: - def __init__(self, name, settings, pins = []): - self.name = name + self.primitive = primitive + if self.mode is None: + self.mode = self.site_type + if self.primitive is None: + self.primitive = self.mode + + primitives[self.site_type].append(self) + self.settings = settings self.pins = pins - def build(self, ctx, site, programming, **kwargs): - return f""" - (* \\dm:primitive ="{self.name}", \\dm:programming ="MODE:OSC_CORE ${config}", \\dm:site ="{site}" *) - {self.name} {self.name}_{idx} ( ); - """ - + def get_setting(self, name): + settings = {s.name: s for s in self.settings} + return settings[name] + + def configuration(self, values): + enable_values = {s.name: s.enable_value for s in self.settings if s.enable_value is not None} + settings = {s.name: s for s in self.settings} + if isinstance(values, dict): + values = list(values.items()) + + def find_setting(x): + k, v = x + if isinstance(k, str): + return (settings.get(k), v) + return x + + values = list(map(find_setting, values)) + for k, v in values: + enable_values.pop(k.name, None) + + for x in enable_values.items(): + values.append(find_setting(x)) + + return f"MODE:{self.mode} " + " ".join([s.format(self, v) for (s, v) in values]) + + def default_config(self): + return self.configuration({s: s.enable_value for s in self.settings if s.enable_value is not None}) + + def fill_config(self): + return self.configuration({s: s.fill_value() for s in self.settings}) + + @staticmethod + def parse_primitive_json(primitive, site_type=None, core_suffix=True, mode = None, value_sizes={}): + import database + + fn = database.get_oxide_root() + f"/tools/primitives/{primitive}.json" + with open(fn) as f: + parsed = json.load(f) + + if core_suffix and site_type is None: + primitive = primitive + "_CORE" + + if mode is None: + mode = primitive + + def create_setting(s): + values = s.get("Value", s.get("Values")) + name = s.get("Name", s.get("Attribute")) + + if len(values) == 1 and name in value_sizes: + return WordSetting(name, value_sizes[name], desc=name, default=int(values[0])) + + if values[0].replace("`", "").startswith("0b"): + bit_cnt = len(values[0].replace("`", "").split(" ")[0].split("0b")[-1]) + return WordSetting(name, bit_cnt, desc=name, number_formatter=WordSetting.binary_formatter) + + return EnumSetting(name, values, desc=str(s.get("Description")), default=values[0]) + + parameters_key = "Parameters" + if parameters_key not in parsed: + parameters_key = [k for k in parsed.keys() if "parameters" in k.lower()][0] + + def create_pin(pin_def, dir): + range = pin_def.get("Range", "") + name = pin_def["Name"] + if '[' in name: + range = name.split("[")[1].replace("]", "") + name = name.split("[")[0] + desc = pin_def["Description"] + + if ":" in range: + range = range.split(":") + assert range[1] == "0" + range = int(range[0]) + else: + range = None + + return PinSetting(name, dir, desc, bits=range) + + pins = [create_pin(p, dir) for (name, dir) in {"Output Ports": "out", "Input Ports": "in"}.items() for p in + parsed[name]] + + return PrimitiveDefinition( + site_type=site_type if site_type else mode, + settings=[create_setting(s) for s in parsed[parameters_key]], + pins=pins, + desc=parsed["description"], + mode=mode, + primitive=primitive + ) + lram_core = PrimitiveDefinition( "LRAM_CORE", - [ - EnumSetting("MODE", ["NONE", "LRAM_CORE"], desc="LRAM primitive mode"), - EnumSetting("ASYNC_RST_RELEASE", ["SYNC", "ASYNC"], - desc="LRAM reset release configuration"), - EnumSetting("DATA_PRESERVE", ["DISABLE", "ENABLE"], - desc="LRAM data preservation across resets"), - EnumSetting("EBR_SP_EN", ["DISABLE", "ENABLE"], - desc="EBR single port mode"), - EnumSetting("ECC_BYTE_SEL", ["ECC_EN", "BYTE_EN"]), - EnumSetting("GSR", ["ENABLED", "DISABLED"], - desc="LRAM global set/reset mask"), - EnumSetting("OUT_REGMODE_A", ["NO_REG", "OUT_REG"], - desc="LRAM output pipeline register A enable"), - EnumSetting("OUT_REGMODE_B", ["NO_REG", "OUT_REG"], - desc="LRAM output pipeline register B enable"), - EnumSetting("RESETMODE", ["SYNC", "ASYNC"], - desc="LRAM sync/async reset select"), - EnumSetting("RST_AB_EN", ["RESET_AB_DISABLE", "RESET_AB_ENABLE"], - desc="LRAM reset A/B enable"), - EnumSetting("SP_EN", ["DISABLE", "ENABLE"], - desc="LRAM single port mode"), - EnumSetting("UNALIGNED_READ", ["DISABLE", "ENABLE"], - desc="LRAM unaligned read support"), - EnumSetting("CLKMUX", ["#SIG", "#INV"], desc="LRAM CLK inversion control"), - EnumSetting("CSAMUX", ["#SIG", "#INV"], desc="LRAM CSA inversion control"), - EnumSetting("CSBMUX", ["#SIG", "#INV"], desc="LRAM CSB inversion control"), - EnumSetting("RSTAMUX", ["#SIG", "#INV"], desc="LRAM RSTA inversion control"), - EnumSetting("RSTBMUX", ["#SIG", "#INV"], desc="LRAM RSTB inversion control"), - EnumSetting("WEAMUX", ["#SIG", "#INV"], desc="LRAM WEA inversion control"), - EnumSetting("WEBMUX", ["#SIG", "#INV"], desc="LRAM WEB inversion control"), + [ + EnumSetting("ASYNC_RST_RELEASE", ["SYNC", "ASYNC"], + desc="LRAM reset release configuration"), + EnumSetting("DATA_PRESERVE", ["DISABLE", "ENABLE"], + desc="LRAM data preservation across resets"), + EnumSetting("EBR_SP_EN", ["DISABLE", "ENABLE"], + desc="EBR single port mode"), + EnumSetting("ECC_BYTE_SEL", ["ECC_EN", "BYTE_EN"]), + EnumSetting("GSR", ["ENABLED", "DISABLED"], + desc="LRAM global set/reset mask"), + EnumSetting("OUT_REGMODE_A", ["NO_REG", "OUT_REG"], + desc="LRAM output pipeline register A enable"), + EnumSetting("OUT_REGMODE_B", ["NO_REG", "OUT_REG"], + desc="LRAM output pipeline register B enable"), + EnumSetting("RESETMODE", ["SYNC", "ASYNC"], + desc="LRAM sync/async reset select"), + EnumSetting("RST_AB_EN", ["RESET_AB_DISABLE", "RESET_AB_ENABLE"], + desc="LRAM reset A/B enable"), + EnumSetting("SP_EN", ["DISABLE", "ENABLE"], + desc="LRAM single port mode"), + EnumSetting("UNALIGNED_READ", ["DISABLE", "ENABLE"], + desc="LRAM unaligned read support"), + ProgrammablePin("CLK", ["#SIG", "#INV"], desc="LRAM CLK inversion control", primitive="LRAM_CORE"), + ProgrammablePin("CSA", ["#SIG", "#INV"], desc="LRAM CSA inversion control", primitive="LRAM_CORE"), + ProgrammablePin("CSB", ["#SIG", "#INV"], desc="LRAM CSB inversion control", primitive="LRAM_CORE"), + ProgrammablePin("RSTA", ["#SIG", "#INV"], desc="LRAM RSTA inversion control", primitive="LRAM_CORE"), + ProgrammablePin("RSTB", ["#SIG", "#INV"], desc="LRAM RSTB inversion control", primitive="LRAM_CORE"), + ProgrammablePin("WEA", ["#SIG", "#INV"], desc="LRAM WEA inversion control", primitive="LRAM_CORE"), + ProgrammablePin("WEB", ["#SIG", "#INV"], desc="LRAM WEB inversion control", primitive="LRAM_CORE"), ] ) iologic_core = PrimitiveDefinition( "IOLOGIC_CORE", [ - WordSetting("DELAY.DEL_VALUE", 7), - EnumSetting("DELAY.COARSE_DELAY", ["0NS", "0P8NS", "1P6NS"]), - EnumSetting("DELAY.COARSE_DELAY_MODE", ["DYNAMIC", "STATIC"]), - EnumSetting("DELAY.EDGE_MONITOR", ["ENABLED", "DISABLED"]), - EnumSetting("DELAY.WAIT_FOR_EDGE", ["ENABLED", "DISABLED"]), - ] + WordSetting("DELAYA.DEL_VALUE", 7, enable_value=1), + EnumSetting("DELAYA.COARSE_DELAY", ["0NS", "0P8NS", "1P6NS"]), + EnumSetting("DELAYA.COARSE_DELAY_MODE", ["DYNAMIC", "STATIC"]), + EnumSetting("DELAYA.EDGE_MONITOR", ["ENABLED", "DISABLED"]), + EnumSetting("DELAYA.WAIT_FOR_EDGE", ["ENABLED", "DISABLED"]), + ]+ [ProgrammablePin(n, ["#SIG", "#OFF"]) + for n in + ["CIBCRS0", "CIBCRS1", "RANKSELECT", "RANKENABLE", "RANK0UPDATE", "RANK1UPDATE"] + ], + mode="IREG_OREG" ) -siologic_core = PrimitiveDefinition( - "SIOLOGIC_CORE", - iologic_core.settings + - [ - EnumSetting(f":{n}", ["#SIG", "#OFF"]) - for n in - ["CIBCRS0", "CIBCRS1", "RANKSELECT", "RANKENABLE", "RANK0UPDATE", "RANK1UPDATE"] - ] -) +delayb = PrimitiveDefinition.parse_primitive_json("DELAYB", site_type="SIOLOGIC_CORE", mode="IREG_OREG", value_sizes={"DEL_VALUE": 7}) +# siologic_core = PrimitiveDefinition( +# "SIOLOGIC_CORE", +# [ +# WordSetting("DELAYB.DEL_VALUE", 7, enable_value=1), +# EnumSetting("DELAYB.COARSE_DELAY", ["0NS", "0P8NS", "1P6NS"]), +# EnumSetting("DELAYB.COARSE_DELAY_MODE", ["DYNAMIC", "STATIC"]), +# EnumSetting("DELAYB.EDGE_MONITOR", ["ENABLED", "DISABLED"]), +# EnumSetting("DELAYB.WAIT_FOR_EDGE", ["ENABLED", "DISABLED"]), +# ] , +# mode="IREG_OREG" +# ) + +delayb.get_setting("DEL_VALUE").enable_value = 1 +delayb.get_setting("GSR").depth = -1 +delayb.pins = [] osc_core = PrimitiveDefinition( "OSC_CORE", [ - EnumSetting("MODE", ["NONE", "OSC_CORE"], - desc="OSC_CORE primitive mode"), WordSetting("HF_CLK_DIV", 8, desc="high frequency oscillator output divider"), WordSetting("HF_SED_SEC_DIV", 8, @@ -113,17 +283,16 @@ def build(self, ctx, site, programming, **kwargs): [ PinSetting("HFCLKOUT", "out"), PinSetting("HFSDSCEN", "in") - ] + ], ) oscd_core = PrimitiveDefinition( - name="OSCD_CORE", - settings = [ - EnumSetting("MODE", ["NONE", "OSCD_CORE"], desc="OSC_CORED primitive mode"), + "OSCD_CORE", + settings=[ EnumSetting("DTR_EN", ["ENABLED", "DISABLED"], desc="DTR block enable from MIB"), - WordSetting("HF_CLK_DIV", 8, + WordSetting("HF_CLK_DIV", 8, default=1, desc="HF oscillator user output divider (div2–div256)"), WordSetting("HF_SED_SEC_DIV", 8, @@ -133,7 +302,7 @@ def build(self, ctx, site, programming, **kwargs): desc="HF oscillator trim source mux select"), EnumSetting("HF_OSC_EN", ["ENABLED", "DISABLED"], - desc="HF oscillator enable"), + desc="HF oscillator enable", default="ENABLED", enable_value="ENABLED"), EnumSetting("LF_FABRIC_EN", ["ENABLED", "DISABLED"], desc="LF oscillator trim source mux select"), @@ -146,13 +315,13 @@ def build(self, ctx, site, programming, **kwargs): ], pins=[ PinSetting("HFOUTEN", dir="in", - desc="HF clock (225MHz) output enable (test only)"), + desc="HF clock (225MHz) output enable (test only)"), PinSetting("HFSDSCEN", dir="in", - desc="HF user clock output enable"), + desc="HF user clock output enable"), PinSetting("HFOUTCIBEN", dir="in", - desc="CIB control to enable/disable HF oscillator during user mode"), + desc="CIB control to enable/disable HF oscillator during user mode"), PinSetting("REBOOT", dir="in", - desc="CIB control to enable/disable hf_clk_config output"), + desc="CIB control to enable/disable hf_clk_config output"), PinSetting("HFCLKOUT", dir="out", desc="450MHz with programmable divider (2–256) to user"), PinSetting("LFCLKOUT", dir="out", @@ -162,5 +331,104 @@ def build(self, ctx, site, programming, **kwargs): PinSetting("HFSDCOUT", dir="out", desc="450MHz with programmable divider (2–256) to configuration"), ], +) + +dcc = PrimitiveDefinition.parse_primitive_json("DCC", core_suffix=False) +dcc.get_setting("DCCEN").enable_value = "1" + +PrimitiveDefinition( + "DCS", + settings=[ + EnumSetting("DCSMODE", + ["VCC", "GND", "DCS", "DCS_1", "BUFGCECLK0", "BUFGCECLK0_1", "BUFGCECLK1", "BUFGCECLK1_1", "BUF0", + "BUF1"], desc="clock selector mode", enable_value="DCS"), + ] +) + +pll_core = PrimitiveDefinition.parse_primitive_json("PLL", core_suffix=True) +for s in pll_core.settings: + if s.name.startswith("ENCLK_"): + s.enable_value = "ENABLED" +pll_core.pins = [ + PinSetting(name="INTFBK0", dir="out", desc="", bits=None), + PinSetting(name="INTFBK1", dir="out", desc="", bits=None), + PinSetting(name="INTFBK2", dir="out", desc="", bits=None), + PinSetting(name="INTFBK3", dir="out", desc="", bits=None), + PinSetting(name="INTFBK4", dir="out", desc="", bits=None), + PinSetting(name="INTFBK5", dir="out", desc="", bits=None), + PinSetting(name="LMMIRDATA", dir="out", desc="LMMI read data to fabric.", bits=7), + PinSetting(name="LMMIRDATAVALID", dir="out", desc="LMMI read data valid to fabric.", bits=None), + PinSetting(name="LMMIREADY", dir="out", desc="LMMI ready signal to fabric.", bits=None), + PinSetting(name="CLKOP", dir="out", desc="Primary (A) output clock.", bits=None), + PinSetting(name="CLKOS", dir="out", desc="Secondary (B) output clock.", bits=None), + PinSetting(name="CLKOS2", dir="out", desc="Secondary (C) output clock.", bits=None), + PinSetting(name="CLKOS3", dir="out", desc="Secondary (D) output clock.", bits=None), + PinSetting(name="CLKOS4", dir="out", desc="Secondary (E) output clock.", bits=None), + PinSetting(name="CLKOS5", dir="out", desc="Secondary (F) output clock.", bits=None), + PinSetting(name="INTLOCK", dir="out", desc="PLL internal lock indicator. PLL CIB output.", bits=None), + PinSetting(name="LEGRDYN", dir="out", desc="PLL lock indicator. PLL CIB output.", bits=None), + PinSetting(name="LOCK", dir="out", desc="", bits=None), + PinSetting(name="PFDDN", dir="out", desc="PFD DN output signal to PLL CIB port.", bits=None), + PinSetting(name="PFDUP", dir="out", desc="PFD UP output signal to PLL CIB port.", bits=None), + PinSetting(name="REFMUXCK", dir="out", desc="Reference CLK mux output. PLL CIB output.", bits=None), + PinSetting(name="REGQA", dir="out", desc="", bits=None), PinSetting(name="REGQB", dir="out", desc="", bits=None), + PinSetting(name="REGQB1", dir="out", desc="", bits=None), + PinSetting(name="CLKOUTDL", dir="out", desc="", bits=None), + + #PinSetting(name="LOADREG", dir="in",desc="Valid if MC1_DYN_SOURCE = 1. Initiates a divider output phase shift on negative edge of CIB_LOAD_REG.",bits=None), + #PinSetting(name="DYNROTATE", dir="in", desc="Valid if MC1_DYN_SOURCE = 1. Initiates a change from current VCO clock phase to an earlier or later phase on the negative edge of CIB_ROTATE.", bits=None), + PinSetting(name="LMMICLK", dir="in", desc="LMMI clock from fabric.", bits=None), + PinSetting(name="LMMIRESETN", dir="in", desc="LMMI reset signal to reset the state machine if the IP gets locked.", + bits=None), + PinSetting(name="LMMIREQUEST", dir="in", desc="LMMI request signal from fabric.", bits=None), + PinSetting(name="LMMIWRRDN", dir="in", desc="LMMI write-high/read-low from fabric.", bits=None), + PinSetting(name="LMMIOFFSET", dir="in", + desc="LMMI offset address from fabric. Not all bits are required for an IP.", bits=6), + PinSetting(name="LMMIWDATA", dir="in", desc="LMMI write data from fabric. Not all bits are required for an IP.", + bits=7), + + PinSetting(name="REFCK", dir="in", desc="", bits=None), + PinSetting(name="ENCLKOP", dir="in", desc="Enable A output (CLKOP). Active high. PLL CIB input.", bits=None), + PinSetting(name="ENCLKOS", dir="in", desc="Enable B output (CLKOS). Active high. PLL CIB input.", bits=None), + PinSetting(name="ENCLKOS2", dir="in", desc="Enable C output (CLKOS2). Active high. PLL CIB input.", bits=None), + PinSetting(name="ENCLKOS3", dir="in", desc="Enable D output (CLKOS3). Active high. PLL CIB input.", bits=None), + PinSetting(name="ENCLKOS4", dir="in", desc="Enable E output (CLKOS4). Active high. PLL CIB input.", bits=None), + PinSetting(name="ENCLKOS5", dir="in", desc="Enable F output (CLKOS5). Active high. PLL CIB input.", bits=None), + PinSetting(name="FBKCK", dir="in", desc="", bits=None), + PinSetting(name="LEGACY", dir="in", desc="PLL legacy mode signal. Active high to enter the mode. Enabled by lmmi_legacy fuse. PLL CIB input.", bits=None), + PinSetting(name="PLLRESET", dir="in", desc="Active high to reset PLL. Enabled by MC1_PLLRESET. PLL CIB input.", + bits=None), + PinSetting(name="STDBY", dir="in", desc="PLL standby signal. Active high to put PLL clocks in low. Not used.", + bits=None), + PinSetting(name="ROTDEL", dir="in", desc="", bits=None), + PinSetting(name="DIRDEL", dir="in", desc="", bits=None), + PinSetting(name="ROTDELP1", dir="in", desc="", bits=None), + + PinSetting(name="BINTEST", dir="in", desc="", bits=1), + PinSetting(name="DIRDELP1", dir="in", desc="", bits=None), + PinSetting(name="GRAYACT", dir="in", desc="", bits=4), + PinSetting(name="BINACT", dir="in", desc="", bits=1) +] + +# The documentation has this but its for DIFFIO +def remove_failsafe_enum(definition): + for setting in definition.settings: + if hasattr(setting, "values") and "FAILSAFE" in setting.values: + setting.values.remove("FAILSAFE") + return definition + +seio33 = remove_failsafe_enum(PrimitiveDefinition.parse_primitive_json("SEIO33")) +seio18 = remove_failsafe_enum(PrimitiveDefinition.parse_primitive_json("SEIO18")) + +PrimitiveDefinition.parse_primitive_json("DIFFIO18") +eclkdiv = PrimitiveDefinition.parse_primitive_json("ECLKDIV") +eclkdiv.get_setting("ECLK_DIV").enable_value = "2" + +PrimitiveDefinition.parse_primitive_json("PCLKDIV", core_suffix=False) -) \ No newline at end of file +dlldel = PrimitiveDefinition.parse_primitive_json("DLLDEL", value_sizes={"ADJUST": 9}) +dlldel.get_setting("ENABLE").enable_value = "ENABLED" +# Doesn't work right now -- seems optimimized out? +# i2cfifo = PrimitiveDefinition.parse_primitive_json("I2CFIFO") +# i2cfifo.get_setting("CR1GCEN").enable_value = "EN" +# i2cfifo.get_setting("CR1I2CEN").enable_value = "EN" From 387f83592afc25b85956f791616d8b668bcb72ab Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Tue, 27 Jan 2026 10:20:10 -0700 Subject: [PATCH 15/29] Make sure to print error and exit with error code if an error exists --- radiant.sh | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/radiant.sh b/radiant.sh index e79de1e..f142b7a 100755 --- a/radiant.sh +++ b/radiant.sh @@ -80,6 +80,7 @@ else # Cache miss cd "$2.tmp" if [ -n "$STRUCT_VER" ]; then + rm -f par.udb "$fpgabindir"/sv2udb -o par.udb input.v else "$fpgabindir"/synthesis -a "$LSE_ARCH" -p "$DEVICE" -t "$PACKAGE" \ @@ -99,15 +100,26 @@ else fi if [ -n "$GEN_RBF" ]; then - "$fpgabindir"/bitgen $EXTRA_BIT_ARGS -b -d -w par.udb + OUTPUT=$("$fpgabindir"/bitgen $EXTRA_BIT_ARGS -b -d -w par.udb 2>&1) + if [[ $OUTPUT == *"ERROR <"* ]]; then + echo "Exiting due to error found during bitgen" + exit -1 + fi + LD_LIBRARY_PATH=$ld_lib_path_orig $bscache commit $PART "input.v" $MAP_PDC output "par.udb" "par.rbt" else if [ -n "$RBK_MODE" ]; then - "$fpgabindir"/bitgen $EXTRA_BIT_ARGS -d -w -m 1 par.udb + OUTPUT=$("$fpgabindir"/bitgen $EXTRA_BIT_ARGS -d -w -m 1 par.udb 2>&1) mv par.rbk par.bit else - "$fpgabindir"/bitgen $EXTRA_BIT_ARGS -d -w par.udb + OUTPUT=$("$fpgabindir"/bitgen $EXTRA_BIT_ARGS -d -w par.udb 2>&1) fi + + if [[ $OUTPUT == *"ERROR <"* ]]; then + echo "Exiting due to error found during bitgen" + exit -1 + fi + LD_LIBRARY_PATH=$ld_lib_path_orig $bscache commit $PART "input.v" $MAP_PDC output "par.udb" "par.bit" fi export LD_LIBRARY_PATH="" From 75cf8b6e6ed9b24c4f2ccb1c282919b816d3af5a Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Tue, 27 Jan 2026 10:24:35 -0700 Subject: [PATCH 16/29] Updated fuzzers; add support for 33u OSCD, improved general tile/site routing --- fuzzers/LFCPNX/shared/empty.v | 8 + fuzzers/LFCPNX/shared/route.v | 10 + fuzzers/LIFCL/004-tile-routing/fuzzer.py | 486 ++++++++++----------- fuzzers/LIFCL/005-site-routing/fuzzer.py | 390 +++++++++++++++++ fuzzers/LIFCL/005-site-routing/primitive.v | 13 + fuzzers/LIFCL/010-lut-init/fuzzer.py | 2 +- fuzzers/LIFCL/020-plc_tap/fuzzer.py | 53 ++- fuzzers/LIFCL/021-cmux/fuzzer.py | 2 +- fuzzers/LIFCL/022-midmux/fuzzer.py | 8 +- fuzzers/LIFCL/024-dcc-dcs/fuzzer.py | 80 ++-- fuzzers/LIFCL/031-io_mode/fuzzer.py | 182 ++++---- fuzzers/LIFCL/032-hsio_mode/fuzzer.py | 29 +- fuzzers/LIFCL/062-lram-config/fuzzer.py | 2 +- fuzzers/LIFCL/093-oscd/osc_pins.v | 16 + fuzzers/LIFCL/shared/empty.v | 8 + generate_database.sh | 3 +- 16 files changed, 893 insertions(+), 399 deletions(-) create mode 100644 fuzzers/LFCPNX/shared/empty.v create mode 100644 fuzzers/LFCPNX/shared/route.v create mode 100644 fuzzers/LIFCL/093-oscd/osc_pins.v create mode 100644 fuzzers/LIFCL/shared/empty.v diff --git a/fuzzers/LFCPNX/shared/empty.v b/fuzzers/LFCPNX/shared/empty.v new file mode 100644 index 0000000..7942d25 --- /dev/null +++ b/fuzzers/LFCPNX/shared/empty.v @@ -0,0 +1,8 @@ +(* \db:architecture ="${arch}", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule diff --git a/fuzzers/LFCPNX/shared/route.v b/fuzzers/LFCPNX/shared/route.v new file mode 100644 index 0000000..3c48e56 --- /dev/null +++ b/fuzzers/LFCPNX/shared/route.v @@ -0,0 +1,10 @@ +(* \db:architecture ="${arch}", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + (* \xref:LOG ="q_c@0@9"${arcs_attr} *) + wire q; + + (* \dm:cellmodel_primitives ="REG0=reg", \dm:primitive ="SLICE", \dm:programming ="MODE:LOGIC Q0:Q0 ", \dm:site ="R2C2A" *) + SLICE SLICE_I ( .A0(q), .Q0(q) ); +endmodule diff --git a/fuzzers/LIFCL/004-tile-routing/fuzzer.py b/fuzzers/LIFCL/004-tile-routing/fuzzer.py index a3e23a1..fcde923 100644 --- a/fuzzers/LIFCL/004-tile-routing/fuzzer.py +++ b/fuzzers/LIFCL/004-tile-routing/fuzzer.py @@ -1,315 +1,303 @@ +import asyncio import logging -import shutil +import re import sys -import time -import traceback from collections import defaultdict -from concurrent.futures import Future +import cachecontrol import fuzzconfig import fuzzloops import interconnect +import lapie import libpyprjoxide import nonrouting import primitives import radiant import tiles -from cachier import cachier from fuzzconfig import FuzzConfig, get_db from interconnect import fuzz_interconnect_sinks import database -tiletypes = set() - -overlapping_tile_types = set(["CIB", "MIB_B_TAP"] + - [f"BANKREF{i}" for i in range(16)] + - [f"BK{i}_15K" for i in range(16)] - ) - -def get_site_tiles(device, site): - site_tiles = [tile for tile in tiles.get_tiles_by_rc(device, site) if - tile.split(":")[1] not in overlapping_tile_types] - - return site_tiles - -def site_differences(device, bitfile, baseline = None): - if baseline is None: - baseline = FuzzConfig.standard_empty(device) - - deltas = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, baseline).delta(fuzzconfig.db, bitfile) - - ip_values = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, bitfile).get_ip_values() - ip_values = [(a,v) for a,v in ip_values if v != 0] - - power_tile_types = set(["PMU"] + [f"BANKREF{i}" for i in range(16)]) - pmu_tiles = [x for x in list(deltas.keys()) if x.split(":")[-1] in power_tile_types] - driving_tiles = [x for x in list(deltas.keys()) if x.split(":")[-1] not in power_tile_types] - - tile = driving_tiles[0] - - return (driving_tiles + pmu_tiles), ip_values +processed_tiletypes = set("PLC") + +exclusion_list = { + # I think this particular pip needs other things in SYSIO_B3 to trigger, but when SYSIO_B3_1 is + # driving SYSIO_B3_0_ECLK_L, the pip seems active. Just blacklist this one and accept the bit flip. + ("SYSIO_B3_0", "JECLKIN1_I218", "JECLKOUT_I218") +} + +# Cache this so we only do it once. Could also probably read the ron file and check it +@cachecontrol.cache_fn() +def register_tile_connections(device, tiletype, tile, conn_pips): + connection_sinks = defaultdict(list) + for (frm, to) in conn_pips: + connection_sinks[to].append(frm) + + db = libpyprjoxide.Database(database.get_db_root()) + family = device.split("-")[0] + for (to_wire, froms) in connection_sinks.items(): + for from_wire in froms: + db.add_conn(family, tiletype, to_wire, from_wire) + db.flush() + +async def get_tiletype_pips(device, tiletype, executor = None): + wires = tiles.get_representative_nodes_for_tiletype(device, tiletype) + + if len(wires) == 0: + logging.debug(f"{tiletype} has no consistent internal wires") + return tiletype, [], [] + + arcs = lapie.get_list_arc(device) + + ts = sorted(list(tiles.get_tiles_by_tiletype(device, tiletype).keys())) + (r, c) = tiles.get_rc_from_name(device, ts[0]) + nodes = set([f"R{r}C{c}_{w}" for w in wires]) + + internal_and_external_pips, tiletype_graph = await asyncio.wrap_future(tiles.get_local_pips_for_nodes(device, nodes, include_interface_pips=True, + should_expand=lambda p: p[0] in nodes and p[1] in nodes, + executor = executor)) + pips = {p for p in internal_and_external_pips if p[0] in tiletype_graph and p[1] in tiletype_graph} + + connected_arcs = set([ + (frm_wire, to_wire) + for (frm_wire, to_wire) in arcs + if frm_wire in nodes + ]) + conn_pips = set(pips) & connected_arcs + actual_pips = set(pips) - conn_pips + pips = sorted(actual_pips) -@cachier(separate_files=True, cache_dir='.cachier') -def find_relevant_tiles(device, primitive_config, site, site_type): - cfg = FuzzConfig(job=f"{site}", device=device, tiles=[]) + register_tile_connections(device, tiletype, ts[0], sorted(conn_pips)) - empty_file = FuzzConfig.standard_empty(device) + anon_pips = sorted(set([tuple(["_".join(w.split("_")[1:]) for w in p]) for p in pips])) - primitive_type = cfg.build_design("./primitive.v", { - "config": primitive_config, - "site": site, - "site_type": site_type, - "extra": "", - "signals": "" - }, prefix=site + "/") + baseline = FuzzConfig.standard_empty(device) + cfg = FuzzConfig(job=f"find-tile-set-{device}-{tiletype}", device=device) + baseline_pips = [] + baseline_nodes = set() + for p in pips: + if not p[0] in baseline_nodes and not p[1] in baseline_nodes: + for w in p: + baseline_nodes.add(w) + baseline_pips.append(p) - deltas = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, empty_file).delta(fuzzconfig.db, primitive_type) + bitstream = await asyncio.wrap_future(interconnect.create_wires_file(cfg, pips, executor=executor)) + deltas = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, baseline).delta(fuzzconfig.db, bitstream) + filtered_deltas = {k:v for k,v in deltas.items() if k.split(":")[1] != "PLC"} - ip_values = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, primitive_type).get_ip_values() - ip_values = [(a,v) for a,v in ip_values if v != 0] + modified_tiles_rcs = set([(tiles.get_rc_from_name(device, n),n.split(":")[-1]) for n in filtered_deltas.keys()]) + modified_tiles_rcs_anon = [((r0-r),(c0-c),tt) for ((r0,c0),tt) in modified_tiles_rcs] - power_tile_types = set(["PMU"] + [f"BANKREF{i}" for i in range(16)]) - pmu_tiles = [x for x in list(deltas.keys()) if x.split(":")[-1] in power_tile_types] + logging.info(f"{tiletype} has {len(anon_pips)} PIPs for {len(ts)} tiles {len(conn_pips)} connections and {len(nodes)} nodes with {modified_tiles_rcs_anon} modified tiles") - delta_sorted = [x[0] for x in sorted(deltas.items(), key=lambda x: -len(x[1]))] - driving_tiles = [x for x in delta_sorted if x.split(":")[-1] not in power_tile_types] - print(delta_sorted) + logging.debug(f"{tiletype} Connections:") + for c in conn_pips: + logging.debug(f" - {c}") - # single_driving_type_check = site_type in ["PCLKDIV", "ECLKDIV_CORE", "DIFFIO18_CORE"] or len(driving_tiles) == 1 - # if not single_driving_type_check: - # raise Exception(f"{site_type} should have single driving tile but it has {driving_tiles}. {deltas}") + logging.debug(f"{tiletype} pips:") + for c in anon_pips: + logging.debug(f" - {c}") - tile = driving_tiles[0] + # Either there are no pips or a primitive enables them + if len(modified_tiles_rcs_anon) == 0: + return tiletype, [], [] - site_tiles = [tile for tile in tiles.get_tiles_by_rc(device, site) if - tile.split(":")[1] not in overlapping_tile_types] + # TAP_PLC's are weird and need to be mapped separately. + if "TAP_PLC" in [tt for (_,_,tt) in modified_tiles_rcs_anon]: + logging.warning(f"Ignoring {tiletype}; {modified_tiles_rcs_anon}") + return tiletype, [], [] - # This happens for DCC, DCS - if len(site_tiles) == 0: - site_tiles = driving_tiles + design_sets = [] + rcs = sorted([(tile,tiles.get_rc_from_name(device, tile)) for tile in ts]) - return (driving_tiles + pmu_tiles), site_tiles, ip_values + extra_rcs = set([((r+rd), (c+cd), tt) + for (_, (r,c)) in rcs + for (rd,cd,tt) in modified_tiles_rcs_anon]) -def map_local_pips(name, device, ts, pips, local_graph, executor = None): - cfg = FuzzConfig(job=name, sv="../shared/route.v", device=device, tiles=ts) + while len(anon_pips): + design_set = {} + for rc in extra_rcs: + for tile in tiles.get_tiles_by_rc(device, rc): + design_set[tile] = None + for (tile, (r,c)) in rcs: + pip = anon_pips.pop() + pip = [f"R{r}C{c}_{w}" for w in pip] + design_set[tile] = pip + # + # design_sets.append(design_set) + # design_set = {} - external_nodes = [wire for pip in pips for wire in pip if wire not in local_graph] + if len(anon_pips) == 0: + break - # CIB is routed separately - cfg.tiles.extend( - [tile for n in external_nodes for tile in tiles.get_tiles_by_rc(device, n) if tile.split(":")[-1] != "CIB"]) + if len(design_set): + design_sets.append(design_set) - return fuzz_interconnect_sinks(cfg, pips, False, executor = executor) + return tiletype, design_sets, modified_tiles_rcs_anon -def map_primitive_settings(device, ts, site, site_tiles, site_type, ip_values, executor = None): - if site_type not in primitives.primitives: - return [] - - empty_file = FuzzConfig.standard_empty(device) +def diff_designs(bitstream, baseline): + deltas = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, baseline).delta(fuzzconfig.db, bitstream) + deltas = {k:v for (k,v) in deltas.items() if k.split(":")[1] != "PLC"} + return deltas - base_addrs = database.get_base_addrs(device) - - if site not in base_addrs: - ip_values = [] - - is_ip_config = len(ip_values) > 0 - if len(ip_values): - fuzz_enum_setting = nonrouting.fuzz_ip_enum_setting - fuzz_word_setting = nonrouting.fuzz_ip_word_setting - else: - fuzz_enum_setting = nonrouting.fuzz_enum_setting - fuzz_word_setting = nonrouting.fuzz_word_setting - - def map_mode(mode): - logging.info(f"====== {mode.mode} : {site_type} IP: {len(ip_values)} ==========") - related_tiles = (ts + site_tiles) - cfg = FuzzConfig(job=f"config/{site}/{mode.mode}", device=device, sv="primitive.v", tiles= related_tiles if len(ip_values) == 0 else [f"{site}:{site_type}"]) - - slice_sites = tiles.get_tiles_by_tiletype(device, "PLC") - slice_iter = iter([x for x in slice_sites if tiles.get_rc_from_name(device, x) not in related_tiles]) - - extra_lines = [] - signals = [] - - avail_in_pins = [] - for p in mode.pins: - if p.dir == "in" or p.dir == "inout": - for r in range(0, p.bits if p.bits is not None else 1): - suffix = str(r) if p.bits != None else "" - avail_in_pins.append(f"{p.name}{suffix}") - q_driver = None - def get_sink_pin(): - if len(avail_in_pins): - in_pin = avail_in_pins.pop() - extra_lines.append(f"wire q_{in_pin};") - signals.append(f".{in_pin}(q_{in_pin})") - return f"q_{in_pin}" - - idx = len(extra_lines) - extra_lines.append(f""" - wire q_{idx}; - (* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) - SLICE SLICE_I_{idx} ( .A0(q_{idx}) ); - """) - return f"q_{idx}" - - for p in mode.pins: - for r in range(0, p.bits if p.bits is not None else 1): - suffix = str(r) if p.bits != None else "" - if p.dir == "out": - q = get_sink_pin() - q_driver = q - signals.append(f".{p.name}{suffix}({q})") - - if len(avail_in_pins) and q_driver is None: - extra_lines.append(f""" - wire q_driver; - (* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) - SLICE SLICE_I_driver ( .A0(q_driver), .Q0(q_driver) ); - """) - q_driver = "q_driver" - - for undriven_pin in avail_in_pins: - signals.append(f".{undriven_pin}({q_driver})") - - subs = { - "site": site, - "site_type": site_type, - "extra": "\n".join(extra_lines), - "signals": ", ".join(signals) - } - - def map_mode_setting(setting): - mark_relative_to = None - if site_tiles[0] != ts[0]: - mark_relative_to = site_tiles[0] - - args = { - "config": cfg, - "name": f"{mode.mode}.{setting.name}", - "desc": setting.desc, - "executor": executor - } - - if isinstance(setting, primitives.EnumSetting): - def subs_fn(val): - return subs | {"config": mode.configuration([(setting, val)])} - - if len(ip_values) == 0: - args["mark_relative_to"] = mark_relative_to - - if isinstance(setting, primitives.ProgrammablePin) and not is_ip_config: - args["include_zeros"] = True - - return fuzz_enum_setting(empty_bitfile = empty_file, values = setting.values, get_sv_substs = subs_fn, **args) - elif isinstance(setting, primitives.WordSetting): - def subs_fn(val): - return subs | {"config": mode.configuration([(setting, nonrouting.fuzz_intval(val))])} - - return fuzz_word_setting(length=setting.bits, get_sv_substs=subs_fn, **args) - else: - raise Exception(f"Unknown setting type: {setting}") - - return [map_mode_setting(s) for s in mode.settings] - - return [f for mode in primitives.primitives[site_type] for f in map_mode(mode)] - -def run_for_device(device, executor = None): +async def run_for_device(device, executor = None): if not fuzzconfig.should_fuzz_platform(device): - return - - sites = database.get_sites(device) - - def find_relevant_tiles_for_site(site, site_info): - site_type = site_info["type"] - - primitive = primitives.primitives[site_type][0] - - return find_relevant_tiles(device, primitive.fill_config(), site, site_type) - - def find_relevant_tiles_for_site_without_primitive(site, site_info): - - pips, local_graph = tiles.get_local_pips_for_site(device, site) - cfg = FuzzConfig(job=f"{site}", device=device, tiles=[]) - - all_wires_bit = interconnect.create_wires_file(cfg, pips, prefix = site) - - (driving_tiles, ip_delta) = site_differences(device, all_wires_bit) - site_tiles = get_site_tiles(device, site) - - return (driving_tiles, site_tiles, ip_delta) - - def per_site(site, site_info, relevant_tile_info): - site_type = site_info["type"] - - (driving_tiles, site_tiles, ip_values) = relevant_tile_info - - tiletype = driving_tiles[0].split(":")[1] - if tiletype in tiletypes: - return [] - - tiletypes.add(tiletype) + logging.warning(f"Ignoring device {device}") + return [] - logging.info(f"====== {site} : {tiletype} ==========") - pips, local_graph = tiles.get_local_pips_for_site(device, site) - pips_future = map_local_pips(site, device, driving_tiles + site_tiles, pips, local_graph, executor=executor) + logging.info("Fuzzing device: " + device) - # Map primitive parameter settings - settings_future = map_primitive_settings(device, driving_tiles + site_tiles, site, site_tiles, site_type, ip_values, executor = executor) + lapie.get_list_arc(device) - return [pips_future, settings_future] + tiletypes = tiles.get_tiletypes(device) device_futures = [] - for site, site_info in sorted(sites.items()): - site_type = site_info["type"] - if len(sys.argv) > 1 and sys.argv[1] != site_type: + for tiletype, ts in sorted(tiletypes.items()): + + if tiletype in ["PLC", "TAP_PLC"]: continue - if site_type in ["PLL_CORE"] and device in ["LIFCL-33U"]: - logging.warning(f"Can't map out IP core f{site_type} with device {device} which is in readback mode") + if len(sys.argv) > 1 and re.compile(sys.argv[1]).search(tiletype) is None: continue - f = None - if site_type not in primitives.primitives: + if tiletype in processed_tiletypes: continue - f = executor.submit(find_relevant_tiles_for_site_without_primitive, site, site_info) + processed_tiletypes.add(tiletype) - else: - f = executor.submit(find_relevant_tiles_for_site, site, site_info) + device_futures.append(get_tiletype_pips(device, tiletype, executor=executor)) - f.name = "Find tiles" - site_future = fuzzloops.chain(f, lambda fut, site=site, site_info=site_info: per_site(site, site_info, fut), "Map site") + # list of list of dicts + logging.info(f"Gathering {len(device_futures)} tiletypes") + all_design_sets = await asyncio.gather(*device_futures) - device_futures.extend([f, site_future]) + owned_rcs = {tt:e for (tt,d,e) in all_design_sets} - return device_futures + design_sets = [] + while True: + design_set = {} + owners = defaultdict(list) + for (tiletype, designs, extra_rcs) in all_design_sets: + if len(designs) and len(designs[-1].keys() & design_set.keys()) == 0: + owners[tiletype].extend(designs[-1].keys()) + design_set.update(designs.pop()) -def main(): - get_db() + # The original idea here was that the tile types could be combined. However, this + # does seem to trigger some bit changes + break + if len(design_set) == 0: + break + design_sets.append((owners, design_set)) + + logging.info(f"Building {len(design_sets)} designs") + cfg = FuzzConfig(job="all-routing", device=device, tiles=[]) + diff_designs_futures = [] + empty_file = FuzzConfig.standard_empty(device) + for idx, (owners, design_set) in enumerate(design_sets): + pips = [pip for tile, pip in design_set.items() if pip is not None] + create_bitstream_future = interconnect.create_wires_file(cfg, pips, executor=executor, prefix=f"{idx}/") + diff_designs_futures.append(fuzzloops.chain(create_bitstream_future, diff_designs, "solve_design", empty_file)) + + all_design_diffs = await asyncio.gather(*[asyncio.wrap_future(f) for f in diff_designs_futures]) + + def anon_pip(p): + return ["_".join(w.split("_")[1:]) for w in p] + + # for (i, (deltas, (owners, design_set))) in enumerate(zip(all_design_diffs, design_sets)): + # with open(f"delta_raw{i}.json", "w") as f: + # json.dump(deltas, f, indent=4) + # with open(f"design{i}.json", "w") as f: + # json.dump(design_set, f, indent=4) + + pip_deltas = defaultdict(list) + for (deltas, (owners, design_set)) in zip(all_design_diffs, design_sets): + rc_deltas = {(*tiles.get_rc_from_name(device, k), k.split(":")[-1]):v for k,v in deltas.items()} + + owned_by = {} + for k,ts in owners.items(): + for tile in ts: + owned_by[tile] = k + for tile, pip in design_set.items(): + tiletype = tile.split(":")[1] + if pip is not None: + rc = tiles.get_rc_from_name(device, pip[0]) + tile_owned_rcs = set([ + (orc[0]+rc[0], orc[1]+rc[1], orc[2]) + for orc in owned_rcs[tiletype] + ]) + + owned_tiles_for_tiletype = { + k:rc_deltas.get(k, []) + for k in tile_owned_rcs + } + + rc_tiles_for_tiletype = {(r-rc[0], c-rc[1], tiletype):d for (r,c,tiletype),d in owned_tiles_for_tiletype.items()} + + apip = anon_pip(pip) + pip_deltas[tiletype].append((apip, rc_tiles_for_tiletype)) + + for tiletype, pips_with_deltas in pip_deltas.items(): + sinks = defaultdict(list) + for (pip, deltas) in pips_with_deltas: + sinks[pip[1]].append((pip[0], deltas)) + + all_ts = sorted(list(tiles.get_tiles_by_tiletype(device, tiletype).keys())) + tile = all_ts[0] + ts = [tile] + + logging.debug(f"Solving for {len(sinks)} sinks on {tiletype}; ref tile {tile}") + for to_wire, full_deltas in sinks.items(): + + rc = tiles.get_rc_from_name(device, tile) + rc_prefix = f"R{rc[0]}C{rc[1]}_" + tile_lookup = {} + for from_wire, deltas in full_deltas: + for (r1, c1, tiletype), d in deltas.items(): + for t in tiles.get_tiles_by_rc(device, (r1+rc[0],c1+rc[1])): + if t.split(":")[-1] == tiletype: + tile_lookup[(r1,c1,tiletype)] = t + ts.append(t) + + cfg = FuzzConfig(job=f"{tiletype}/{to_wire}", device=device, tiles=ts) + fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.db, empty_file, set(ts), rc_prefix + to_wire, tile, + set(), "MUX" in to_wire, False) + for from_wire, deltas in full_deltas: + if (tiletype, from_wire, to_wire) in exclusion_list: + continue + + concrete_deltas = {tile_lookup[k]: v for k, v in deltas.items() if len(v)} + logging.debug(f"{tiletype} {from_wire} -> {to_wire} has {len(concrete_deltas)} delta tiles") + fz.add_pip_sample_delta(rc_prefix + from_wire, concrete_deltas) + cfg.solve(fz) + + return [] + +async def run_for_devices(executor): + get_db() families = database.get_devices()["families"] devices = sorted([ device for family in families for device in families[family]["devices"] + if fuzzconfig.should_fuzz_platform(device) ]) - all_sites = set([site_info["type"] - for device in devices - for site,site_info in database.get_sites(device).items() - ]) + all_tiletypes = sorted(set([tile.split(":")[-1] + for device in devices + for tile in database.get_tilegrid(device)["tiles"] + ])) + + if len(sys.argv) > 1 and not any(map(lambda tt: re.compile(sys.argv[1]).search(tt), all_tiletypes)): + logging.warning(f"Tiletype filter doesn't match any known tiles") + logging.warning(sorted(all_tiletypes)) + return [] - if len(sys.argv) > 1 and sys.argv[1] not in all_sites: - logging.warning(f"Site filter doesn't match any known sites") - print(sorted(all_sites)) + return await asyncio.gather(*[run_for_device(device, executor) for device in devices]) - return - fuzzloops.FuzzerMain(lambda executor: [ run_for_device(device, executor) for device in devices ]) if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - main() + fuzzloops.FuzzerAsyncMain(run_for_devices) diff --git a/fuzzers/LIFCL/005-site-routing/fuzzer.py b/fuzzers/LIFCL/005-site-routing/fuzzer.py index e69de29..fece0fc 100644 --- a/fuzzers/LIFCL/005-site-routing/fuzzer.py +++ b/fuzzers/LIFCL/005-site-routing/fuzzer.py @@ -0,0 +1,390 @@ +import asyncio +import logging +import re +import shutil +import sys +import time +import traceback +from collections import defaultdict +from concurrent.futures import Future +from multiprocessing.synchronize import RLock + +import fuzzconfig +import fuzzloops +import interconnect +import libpyprjoxide +import nonrouting +import primitives +import radiant +import tiles +from cachier import cachier +from fuzzconfig import FuzzConfig, get_db +from interconnect import fuzz_interconnect_sinks + +import database +import cachecontrol + +mapped_sites = set() + +overlapping_tile_types = set(["CIB", "MIB_B_TAP", "TAP_CIB"] + + [f"BANKREF{i}" for i in range(16)] + + [f"BK{i}_15K" for i in range(16)] + ) + +def get_site_tiles(device, site): + site_tiles = [tile for tile in tiles.get_tiles_by_rc(device, site) if + tile.split(":")[1] not in overlapping_tile_types] + + return site_tiles + +@cachecontrol.cache_fn() +def find_baseline_differences(device, active_bitstream): + baseline = FuzzConfig.standard_empty(device) + + deltas = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, baseline).delta(fuzzconfig.db, active_bitstream) + + ip_values = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, active_bitstream).get_ip_values() + ip_values = [(a,v) for a,v in ip_values if v != 0] + + return deltas, ip_values + +def find_relevant_tiles_from_bitstream(device, site, active_bitstream): + deltas, ip_values = find_baseline_differences(device, active_bitstream) + + power_tile_types = set(["PMU"] + [f"BANKREF{i}" for i in range(16)]) + pmu_tiles = [x for x in list(deltas.keys()) if x.split(":")[-1] in power_tile_types] + + delta_sorted = [x[0] for x in sorted(deltas.items(), key=lambda x: -len(x[1]))] + driving_tiles = [x for x in delta_sorted if x.split(":")[-1] not in power_tile_types] + + # single_driving_type_check = site_type in ["PCLKDIV", "ECLKDIV_CORE", "DIFFIO18_CORE"] or len(driving_tiles) == 1 + # if not single_driving_type_check: + # raise Exception(f"{site_type} should have single driving tile but it has {driving_tiles}. {deltas}") + + #tile = driving_tiles[0] + + site_tiles = [tile for tile in tiles.get_tiles_by_rc(device, site) if + tile.split(":")[1] not in overlapping_tile_types] + + # This happens for DCC, DCS + if len(site_tiles) == 0: + site_tiles = driving_tiles + + return (driving_tiles + pmu_tiles), site_tiles, ip_values + +#@cachier(separate_files=True, cache_dir='.cachier') +async def find_relevant_tiles_from_primitive(device, primitive, site, site_info, executor): + site_type = site_info["type"] + + cfg = FuzzConfig(job=f"{site}:{site_type}", device=device, tiles=[]) + + primitive_bitstream = await asyncio.wrap_future(cfg.build_design_future(executor, "./primitive.v", { + "config": primitive.fill_config(), + "site": site, + "site_type": site_type, + "extra": "", + "signals": "" + }, prefix=f"find-relevant-tiles/{primitive.mode}/")) + logging.info(f"Getting relevant tiles for {device} {site}:{site_type} for {primitive.mode}") + + driving_tiles, site_tiles, ip_values = find_relevant_tiles_from_bitstream(device, site, primitive_bitstream) + + pin_driving_tiles, pin_site_tiles, pin_ip_values = await find_relevant_tiles(device, site, site_type, site_info, executor = executor) + def uniq(x): + return list(dict.fromkeys(x)) + + return uniq(driving_tiles + pin_driving_tiles), uniq(site_tiles + pin_site_tiles), uniq(ip_values + pin_ip_values) + +async def find_relevant_tiles(device, site, site_type, site_info, executor): + cfg = FuzzConfig(job=f"{site}:{site_type}", device=device, tiles=[]) + + nodes = [p["pin_node"] for p in site_info["pins"]] + logging.info(f"Getting relevant wire tiles for {device} {site}:{site_type}") + pips, _ = await asyncio.wrap_future( + tiles.get_local_pips_for_nodes(device, nodes, include_interface_pips=True, + should_expand=lambda p: p[0] in nodes or p[1] in nodes, + executor = executor) + ) + + wires_bitstream = await asyncio.wrap_future(interconnect.create_wires_file(cfg, pips, prefix=f"find-relevant-tiles/", executor = executor)) + + driving_tiles, site_tiles, ip_values = find_relevant_tiles_from_bitstream(device, site, wires_bitstream) + + return ([t for t in driving_tiles if t.split(":")[-1] != "PLC"], + [t for t in site_tiles if t.split(":")[-1] != "PLC"], + ip_values + ) + +mux_re = re.compile("MUX[0-9]*$") +def map_local_pips(site, site_type, device, ts, pips, local_graph, executor=None): + cfg = FuzzConfig(job=f"{site}:{site_type}", sv="../shared/route.v", device=device, tiles=ts) + + logging.debug(f"PIPs for {site}:") + for p in pips: + logging.debug(f" - {p[0]} -> {p[1]}") + + external_nodes = [wire for pip in pips for wire in pip if wire not in local_graph] + + # CIB is routed separately + cfg.tiles.extend( + [tile for n in external_nodes for tile in tiles.get_tiles_by_rc(device, n) if tile.split(":")[-1] != "CIB"]) + cfg.tiles = [t for t in cfg.tiles if not t.split(":")[-1].startswith("CIB")] + + if len(cfg.tiles) == 0: + logging.warning(f"Local pips for {site} only corresponded to CIB tiles") + return + + mux_pips = [p for p in pips if mux_re.search(p[0]) and mux_re.search(p[1])] + non_mux_pips = [p for p in pips if not p in mux_pips] + if len(non_mux_pips): + yield from fuzz_interconnect_sinks(cfg, non_mux_pips, False, executor = executor) + + if len(mux_pips): + mux_cfg = FuzzConfig(job=f"{site}:{site_type}-MUX", sv="../shared/route.v", device=device, tiles=cfg.tiles) + yield from fuzz_interconnect_sinks(mux_cfg, mux_pips, True, executor = executor) + +def map_primitive_settings(device, ts, site, site_tiles, site_type, ip_values, executor = None): + if site_type not in primitives.primitives: + return [] + + empty_file = FuzzConfig.standard_empty(device) + + base_addrs = database.get_base_addrs(device) + + if site not in base_addrs: + ip_values = [] + + is_ip_config = len(ip_values) > 0 + if len(ip_values): + fuzz_enum_setting = nonrouting.fuzz_ip_enum_setting + fuzz_word_setting = nonrouting.fuzz_ip_word_setting + else: + fuzz_enum_setting = nonrouting.fuzz_enum_setting + fuzz_word_setting = nonrouting.fuzz_word_setting + + def map_mode(mode): + logging.info(f"====== {mode.mode} : {site_type} IP: {len(ip_values)} ==========") + related_tiles = (ts + site_tiles) + cfg = FuzzConfig(job=f"config/{site_type}/{site}/{mode.mode}", device=device, sv="primitive.v", tiles= related_tiles if len(ip_values) == 0 else [f"{site}:{site_type}"]) + + slice_sites = tiles.get_tiles_by_tiletype(device, "PLC") + slice_iter = iter([x for x in slice_sites if tiles.get_rc_from_name(device, x) not in related_tiles]) + + extra_lines = [] + signals = [] + + avail_in_pins = [] + for p in mode.pins: + if p.dir == "in" or p.dir == "inout": + for r in range(0, p.bits if p.bits is not None else 1): + suffix = str(r) if p.bits != None else "" + avail_in_pins.append(f"{p.name}{suffix}") + q_driver = None + def get_sink_pin(): + if len(avail_in_pins): + in_pin = avail_in_pins.pop() + extra_lines.append(f"wire q_{in_pin};") + signals.append(f".{in_pin}(q_{in_pin})") + return f"q_{in_pin}" + + idx = len(extra_lines) + extra_lines.append(f""" + wire q_{idx}; + (* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) + SLICE SLICE_I_{idx} ( .A0(q_{idx}) ); + """) + return f"q_{idx}" + + for p in mode.pins: + for r in range(0, p.bits if p.bits is not None else 1): + suffix = str(r) if p.bits != None else "" + if p.dir == "out": + q = get_sink_pin() + q_driver = q + signals.append(f".{p.name}{suffix}({q})") + + if len(avail_in_pins) and q_driver is None: + extra_lines.append(f""" + wire q_driver; + (* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) + SLICE SLICE_I_driver ( .A0(q_driver), .Q0(q_driver) ); + """) + q_driver = "q_driver" + + for undriven_pin in avail_in_pins: + signals.append(f".{undriven_pin}({q_driver})") + + subs = { + "site": site, + "site_type": site_type, + "extra": "\n".join(extra_lines), + "signals": ", ".join(signals) + } + + def map_mode_setting(setting): + mark_relative_to = None + if site_tiles[0] != ts[0]: + mark_relative_to = site_tiles[0] + + args = { + "config": cfg, + "name": f"{mode.mode}.{setting.name}", + "desc": setting.desc, + "executor": executor + } + + if isinstance(setting, primitives.EnumSetting): + def subs_fn(val): + return subs | {"config": mode.configuration([(setting, val)])} + + if len(ip_values) == 0: + args["mark_relative_to"] = mark_relative_to + + if isinstance(setting, primitives.ProgrammablePin) and not is_ip_config: + args["include_zeros"] = True + + return fuzz_enum_setting(empty_bitfile = empty_file, values = setting.values, get_sv_substs = subs_fn, **args) + elif isinstance(setting, primitives.WordSetting): + def subs_fn(val): + return subs | {"config": mode.configuration([(setting, nonrouting.fuzz_intval(val))])} + + return fuzz_word_setting(length=setting.bits, get_sv_substs=subs_fn, **args) + else: + raise Exception(f"Unknown setting type: {setting}") + + return [map_mode_setting(s) for s in mode.settings] + + return [f for mode in primitives.primitives[site_type] for f in map_mode(mode)] + +async def run_for_device(device, executor = None): + if not fuzzconfig.should_fuzz_platform(device): + return + + async def find_relevant_tiles_for_site(site, site_info, executor): + if should_skip_site(site, site_info): + return None + + site_type = site_info["type"] + + if site_type in primitives.primitives: + primitive = primitives.primitives[site_type][0] + + return await find_relevant_tiles_from_primitive(device, primitive, site, site_info, executor=executor) + + return await find_relevant_tiles(device, site, site_type, site_info, executor=executor) + + def should_skip_site(site, site_info): + site_type = site_info["type"] + if len(sys.argv) > 1 and sys.argv[1] != site_type: + return True + + if site_type in ["PLL_CORE"] and device in ["LIFCL-33U"]: + logging.warning(f"Can't map out IP core {site_type} with device {device} which is in readback mode") + return True + + if site_type in ["CIBTEST", "SLICE"]: + return True + + return False + + async def per_site(site, site_info, driving_tiles, executor): + (driving_tiles, site_tiles, ip_values) = driving_tiles + + site_type = site_info["type"] + + logging.info(f"====== {site} : {driving_tiles} ==========") + tiletype = driving_tiles[0].split(":")[1] + + logging.info(f"====== {site} : {tiletype} ==========") + pips, local_graph = await asyncio.wrap_future( + executor.submit(tiles.get_local_pips_for_site, device, site) + ) + pips_future = list(map_local_pips(site, site_type, device, driving_tiles + site_tiles, pips, local_graph, executor=executor)) + + # Map primitive parameter settings + settings_future = map_primitive_settings(device, driving_tiles + site_tiles, site, site_tiles, site_type, ip_values, executor = executor) + + return [pips_future, settings_future] + + sites = database.get_sites(device) + sites_items = [(k,v) for k,v in sorted(sites.items()) if v["type"] not in ["CIBTEST", "SLICE"]] + + import lapie + sitetypes = {v["type"] for s,v in sites_items} + + # repr_sites = {v["type"]:(k,v) for (k,v) in sites_items} + # sites_items = sorted([v for k,v in repr_sites.items()]) + + #await asyncio.gather(*[executor.submit(lapie.get_node_data, device, sitetype, True) for sitetype in sitetypes]) + await asyncio.wrap_future(lapie.get_node_data(device, sitetypes, True, executor)) + logging.info("-----------------------------------------") + + driving_tiles_futures = [] + async with asyncio.TaskGroup() as tg: + for site, site_info in sites_items: + driving_tiles_futures.append(find_relevant_tiles_for_site(site, site_info, executor=executor)) + + all_driving_tiles = await asyncio.gather(*driving_tiles_futures) + + async with (asyncio.TaskGroup() as tg): + for (site, site_info), driving_tiles_rtn in zip(sites_items, all_driving_tiles): + if driving_tiles_rtn is None: continue + + driving_tiles, site_tiles, ip_values = driving_tiles_rtn + + driving_tiles = [t for t in driving_tiles if t.split(":")[1] not in ["PLC", "TAB_CIB", "CIB"]] + + if len(driving_tiles) == 0: + continue + + logging.debug(f"Driving sites for {site}:") + for t in set(driving_tiles + site_tiles): + logging.debug(f" - {t}") + + # Certain sites present different even with the same site_type and tile_type surrounding it. Specifically + # IO types have A and B suffixes. The IP and configuration is the same, but the pins map to differently + # named wires and it is the wire name that matters for the DB. So we key on the wire names too + site_key = ( + site_info["type"], + tuple(sorted(t.split(":")[-1] for t in driving_tiles)), + tuple(sorted(f"{p["pin_name"]}:{"_".join(p["pin_node"].split("_")[1:])}" for p in site_info["pins"])) + ) + + logging.debug(f"Site key: {site_key}") + if mapped_sites in site_key: + continue + + mapped_sites.add(site_key) + tg.create_task(per_site(site, site_info, (driving_tiles, site_tiles, ip_values), executor)) + + +def main(): + async def run_for_devices(executor): + get_db() + + families = database.get_devices()["families"] + devices = sorted([ + device + for family in families + for device in families[family]["devices"] + if fuzzconfig.should_fuzz_platform(device) + ]) + + all_sites = set([site_info["type"] + for device in devices + if device.startswith("LIFCL") + for site, site_info in database.get_sites(device).items() + ]) + + if len(sys.argv) > 1 and sys.argv[1] not in all_sites: + logging.warning(f"Site filter doesn't match any known sites") + logging.info(sorted(all_sites)) + + return [] + + return await asyncio.gather(*[ run_for_device(device, executor) for device in devices ]) + + fuzzloops.FuzzerAsyncMain(run_for_devices) + +if __name__ == "__main__": + main() diff --git a/fuzzers/LIFCL/005-site-routing/primitive.v b/fuzzers/LIFCL/005-site-routing/primitive.v index e69de29..2241682 100644 --- a/fuzzers/LIFCL/005-site-routing/primitive.v +++ b/fuzzers/LIFCL/005-site-routing/primitive.v @@ -0,0 +1,13 @@ +(* \db:architecture ="${arch}", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + ${extra}; + + (* \dm:primitive ="${site_type}", \dm:programming ="${config}", \dm:site ="${site}" *) + ${site_type} inst ( ${signals} ); + + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule \ No newline at end of file diff --git a/fuzzers/LIFCL/010-lut-init/fuzzer.py b/fuzzers/LIFCL/010-lut-init/fuzzer.py index 232d41d..bcbc80a 100644 --- a/fuzzers/LIFCL/010-lut-init/fuzzer.py +++ b/fuzzers/LIFCL/010-lut-init/fuzzer.py @@ -3,7 +3,7 @@ import fuzzloops import re -cfg = FuzzConfig(job="PLCINIT", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["R2C2:PLC"]) +cfg = FuzzConfig(job="PLCINIT", device="LIFCL-40", tiles=["R2C2:PLC"]) def get_lut_function(init_bits): sop_terms = [] diff --git a/fuzzers/LIFCL/020-plc_tap/fuzzer.py b/fuzzers/LIFCL/020-plc_tap/fuzzer.py index ead72f2..5e83864 100644 --- a/fuzzers/LIFCL/020-plc_tap/fuzzer.py +++ b/fuzzers/LIFCL/020-plc_tap/fuzzer.py @@ -1,11 +1,19 @@ +import asyncio +import logging + from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect import re +import tiles +import fuzzloops +import database +import lapie configs = [ ([(11, 7), (11, 19)], [], FuzzConfig(job="TAPROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_PLC_R11C14:TAP_PLC"])), ([(10, 7), (10, 19)], [], FuzzConfig(job="TAPROUTECIB", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_CIB_R10C14:TAP_CIB"])), ([(1, 7), (1, 19)], [], FuzzConfig(job="TAPROUTECIBT", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_CIBT_R1C14:TAP_CIBT"])), + ([(11, 80)], [], FuzzConfig(job="TAPROUTE_1S", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_PLC_1S_R11C74:TAP_PLC_1S"])), ([(10, 80)], [], FuzzConfig(job="TAPROUTECIB_1S", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_CIB_1S_R10C74:TAP_CIB_1S"])), ([(1, 80)], [], FuzzConfig(job="TAPROUTECIBT_1S", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_CIBT_1S_R1C74:TAP_CIBT_1S"])), @@ -15,7 +23,35 @@ ([(1, 7), ], [(1, 13), ], FuzzConfig(job="TAPROUTECIBT_1SL", device="LIFCL-17", sv="../shared/route_17.v", tiles=["TAP_CIBT_1S_L_R1C14:TAP_CIBT_1S_L"])), ] -def main(): +async def resolve_all_tiles_for_device(device,executor): + tg = database.get_tilegrid(device)["tiles"] + + tap_tiletypes = {i["tiletype"] for k,i in tg.items() if i['tiletype'].startswith("TAP_")} + + for tiletype in tap_tiletypes: + ts = {k for k,i in tg.items() if tiletype == i['tiletype']} + + sorted_tiles = sorted(ts, key=lambda t: tuple(reversed(tiles.get_rc_from_name(device, t)))) + tile = sorted_tiles[0] + + r = tiles.get_rc_from_name(device, tile)[0] + + tile_columns = sorted({tiles.get_rc_from_name(device, n)[1] for n in ts}) + + nodenames = [f"R{r}C..?_R?HPBX..00"] + nodes = lapie.get_node_data(device, nodenames, True) + + node_columns = sorted({tiles.get_rc_from_name(device, n.name)[1] for n in nodes}) + nodes_per_tile = len(node_columns) // len(tile_columns) + relevant_nodes = [n.name for n in nodes if tiles.get_rc_from_name(device, n.name)[1] in node_columns[:nodes_per_tile]] + + print(relevant_nodes, tile) + cfg = FuzzConfig(job=f"TAPROUTE-{tile}", device=device, sv="../shared/route.v", tiles=[tile]) + for f in fuzz_interconnect(config=cfg, nodenames=relevant_nodes, regex=False, bidir=False, full_mux_style=True, + executor=executor): + yield f + +async def main(executor): for locs, rlocs, cfg in configs: cfg.setup() nodes = [] @@ -23,7 +59,18 @@ def main(): nodes += ["R{}C{}_HPBX{:02}00".format(r, c, i) for i in range(8)] for r, c in rlocs: nodes += ["R{}C{}_RHPBX{:02}00".format(r, c, i) for i in range(8)] - fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=True) + + for f in fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=True, executor=executor): + yield f + + for device in ["LIFCL-33", "LIFCL-33U"]: + async for f in resolve_all_tiles_for_device(device, executor=executor): + yield f if __name__ == "__main__": - main() + async def async_main(executor): + await asyncio.gather(*[asyncio.wrap_future(f) async for f in main(executor)]) + + fuzzloops.FuzzerAsyncMain(async_main) + + diff --git a/fuzzers/LIFCL/021-cmux/fuzzer.py b/fuzzers/LIFCL/021-cmux/fuzzer.py index 0f01db2..661d848 100644 --- a/fuzzers/LIFCL/021-cmux/fuzzer.py +++ b/fuzzers/LIFCL/021-cmux/fuzzer.py @@ -15,7 +15,7 @@ def fuzz_33(): (r,c) = (37, 25) tile_prefix = f"R{r}C{c}" - nodes = [f"{tile_prefix}_J.*CMUX.*", f"{tile_prefix}_J.*DCSIP", f"{tile_prefix}_J.*PCLKDIV.*"] + nodes = [f"{tile_prefix}_J.*MUX.*", f"{tile_prefix}_J.*DCSIP", f"{tile_prefix}_J.*PCLKDIV.*"] fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=False, full_mux_style=False) diff --git a/fuzzers/LIFCL/022-midmux/fuzzer.py b/fuzzers/LIFCL/022-midmux/fuzzer.py index d9f2f21..a8746a0 100644 --- a/fuzzers/LIFCL/022-midmux/fuzzer.py +++ b/fuzzers/LIFCL/022-midmux/fuzzer.py @@ -17,6 +17,10 @@ FuzzConfig(job="RMIDROUTE", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R10C75:RMID_PICB_DLY10"])), ("VPFS", (0, 37), 16, "T", FuzzConfig(job="TMIDROUTE", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R0C37:TMID_0", "CIB_R0C38:TMID_1_15K", "CIB_R0C39:CLKBUF_T_15K"])), + + ("VPFN", (83, 25), 16, "B", + FuzzConfig(job="BMIDROUTE-33U", device="LIFCL-33U", sv="../shared/route.v", + tiles=["CIB_R83C25:BMID_0_ECLK_1", "CIB_R83C26:BMID_1_ECLK_2"])), ] def main(): @@ -24,8 +28,10 @@ def main(): cfg.setup() if cfg.device == "LIFCL-40": cr, cc = (28, 49) - else: + elif cfg.device == "LIFCL-17": cr, cc = (10, 37) + else: + cr, cc = (37, 25) r, c = rc nodes = [] mux_nodes = [] diff --git a/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py b/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py index ed620bd..6c6549d 100644 --- a/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py +++ b/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py @@ -5,6 +5,35 @@ import lapie import database + +def per_site(dev, site, dcc_tiles, dcs_tiles): + if site.startswith("DCC"): + cfg = FuzzConfig(job=site, device=dev, tiles=dcc_tiles) + cfg.setup() + empty = cfg.build_design(cfg.sv, {}) + cfg.sv = "dcc.v" + + def get_substs(dccen): + return dict(dev=dev, site=site, dccen=dccen) + + nonrouting.fuzz_enum_setting(cfg, empty, "{}.DCCEN".format(site), ["0", "1"], + lambda x: get_substs(x), False, + desc="DCC bypassed (0) or used as gate (1)") + else: + assert site.startswith("DCS") + cfg = FuzzConfig(job=site, device=dev, tiles=dcs_tiles) + cfg.setup() + empty = cfg.build_design(cfg.sv, {}) + cfg.sv = "dcs.v" + + def get_substs(dcsmode): + return dict(dev=dev, site=site, dcsmode=dcsmode) + + nonrouting.fuzz_enum_setting(cfg, empty, "{}.DCSMODE".format(site), + ["GND", "DCS", "DCS_1", "BUFGCECLK0", "BUFGCECLK0_1", "BUFGCECLK1", "BUFGCECLK1_1", + "BUF0", "BUF1", "VCC"], + lambda x: get_substs(x), False, + desc="clock selector mode") def main(): # 40k dev = "LIFCL-40" @@ -19,30 +48,7 @@ def main(): ["DCC_C{}".format(i) for i in range(4)] dcs_prims = ["DCS0", ] - def per_site(site): - if site.startswith("DCC"): - cfg = FuzzConfig(job=site, device=dev, sv=sv, tiles=dcc_tiles) - cfg.setup() - empty = cfg.build_design(cfg.sv, {}) - cfg.sv = "dcc.v" - def get_substs(dccen): - return dict(dev=dev, site=site, dccen=dccen) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DCCEN".format(site), ["0", "1"], - lambda x: get_substs(x), False, - desc="DCC bypassed (0) or used as gate (1)") - else: - assert site.startswith("DCS") - cfg = FuzzConfig(job=site, device=dev, sv=sv, tiles=dcs_tiles) - cfg.setup() - empty = cfg.build_design(cfg.sv, {}) - cfg.sv = "dcs.v" - def get_substs(dcsmode): - return dict(dev=dev, site=site, dcsmode=dcsmode) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DCSMODE".format(site), - ["GND", "DCS", "DCS_1", "BUFGCECLK0", "BUFGCECLK0_1", "BUFGCECLK1", "BUFGCECLK1_1", "BUF0", "BUF1", "VCC"], - lambda x: get_substs(x), False, - desc="clock selector mode") - fuzzloops.parallel_foreach(dcc_prims + dcs_prims, per_site) + fuzzloops.parallel_foreach(dcc_prims + dcs_prims, lambda site: per_site("LIFCL-40", site, dcc_tiles, dcs_tiles)) #17k dev = "LIFCL-17" @@ -51,26 +57,18 @@ def get_substs(dcsmode): ["DCC_R{}".format(i) for i in range(12)] + \ ["DCC_T{}".format(i) for i in range(16)] dcc_tiles = ["CIB_R10C0:LMID_RBB_5_15K", "CIB_R10C75:RMID_PICB_DLY10", "CIB_R0C37:TMID_0", "CIB_R0C38:TMID_1_15K", "CIB_R0C39:CLKBUF_T_15K"] - fuzzloops.parallel_foreach(dcc_prims, per_site) + fuzzloops.parallel_foreach(dcc_prims, lambda site: per_site("LIFCL-17", site, dcc_tiles, dcs_tiles)) - dev = "LIFCL-33" - sv = "../shared/empty_33.v" - cfg = FuzzConfig(job="udbcheck", device=dev, sv=sv, tiles=[]) - cfg.setup() + for dev in ["LIFCL-33", "LIFCL-33U"]: - # Tile names pulled from physical view - dcc_prims = ["DCC_B{}".format(i) for i in range(18)] + \ - ["DCC_C{}".format(i) for i in range(4)] + \ - ["DCC_T{}".format(i) for i in range(16)] + dcc_prims = [s for s in database.get_sites(dev) if "DCC_" in s] + + dcc_tiles = [x for x in database.get_tilegrid(dev)['tiles'] if "MID" in x] + dcs_tiles = [x for x in database.get_tilegrid(dev)['tiles'] if "CMUX" in x] + + print(dcc_tiles, dcs_tiles) - - dcc_tiles = [x for x in database.get_tilegrid("LIFCL-33")['tiles'] if "MID" in x ] - dcs_tiles = [x for x in database.get_tilegrid("LIFCL-33")['tiles'] if "CMUX" in x ] - - print(dcc_tiles, dcs_tiles) - fuzzloops.parallel_foreach(dcc_prims + dcs_prims, per_site) + fuzzloops.parallel_foreach(dcc_prims + dcs_prims, lambda site: per_site(dev, site, dcc_tiles, dcs_tiles)) - - if __name__ == '__main__': main() diff --git a/fuzzers/LIFCL/031-io_mode/fuzzer.py b/fuzzers/LIFCL/031-io_mode/fuzzer.py index 61c3e86..ad06eb0 100644 --- a/fuzzers/LIFCL/031-io_mode/fuzzer.py +++ b/fuzzers/LIFCL/031-io_mode/fuzzer.py @@ -23,93 +23,101 @@ def create_config_from_pad(pad, device): pio = pad["pio"] ts = [t for t in tiles.get_tiles_from_edge(device, pad["side"], pad["offset"]) if "SYSIO" in t] all_sysio = [t for t in tiles.get_tiles_from_edge(device, pad["side"]) if "SYSIO" in t] + if len(ts) == 0: + logging.warning(f"Could not find tile for {pad} for {device}") + return + tiletype = ts[0].split(":")[1] - print(ts, tiletype, pad) + return ( f"{tiletype}-{pio}", (pio_names[pad["pio"]], pin, - FuzzConfig(job=f"IO{pin}_{device}_{tiletype}", device=device, sv="../shared/empty_33.v", + FuzzConfig(job=f"IO{pin}_{device}_{tiletype}", device=device, tiles=ts + all_sysio)) ) -pads33 = [x for x in database.get_iodb("LIFCL-33")["pads"]] -configs_33 = dict([ - create_config_from_pad(x, "LIFCL-33") for x in pads33 if x["offset"] >= 0 -]) - -configs = list(configs_33.values()) + [ - ("B", "E11", # PR3B, - FuzzConfig(job="IO1D_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R3C75:SYSIO_B1_DED_15K"])), - ("A","F16", # PR13A - FuzzConfig(job="IO1A_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R13C75:SYSIO_B1_0_15K", "CIB_R14C75:SYSIO_B1_1_15K"])), - ("B","G15", # PR13B - FuzzConfig(job="IO1B_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R13C75:SYSIO_B1_0_15K", "CIB_R14C75:SYSIO_B1_1_15K"])), - ("A","E15", # PT67A - FuzzConfig(job="IO0A_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R0C67:SYSIO_B0_0_15K"])), - ("B","E16", # PT67B - FuzzConfig(job="IO0B_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R0C67:SYSIO_B0_0_15K"])), - - ("A","F16", # PR8A - FuzzConfig(job="IO1AO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R8C87:SYSIO_B1_0_ODD"])), - ("B","F17", # PR8B - FuzzConfig(job="IO1BO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R8C87:SYSIO_B1_0_ODD"])), - ("A","F14", # PR6A - FuzzConfig(job="IO1AE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R6C87:SYSIO_B1_0_EVEN"])), - ("B","F15", # PR6B - FuzzConfig(job="IO1BE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R6C87:SYSIO_B1_0_EVEN"])), - ("A","F18", # PR10A - FuzzConfig(job="IC1A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R10C87:SYSIO_B1_0_C", "CIB_R11C87:SYSIO_B1_0_REM"])), - ("B","F19", # PR10B - FuzzConfig(job="IC1B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R10C87:SYSIO_B1_0_C", "CIB_R11C87:SYSIO_B1_0_REM"])), - ("A","N14", # PR24A - FuzzConfig(job="IO2AE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R24C87:SYSIO_B2_0_EVEN"])), - ("B","M14", # PR24B - FuzzConfig(job="IO2BE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R24C87:SYSIO_B2_0_EVEN"])), - ("A","M17", # PR30A - FuzzConfig(job="IO2AO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R30C87:SYSIO_B2_0_ODD"])), - ("B","M18", # PR30B - FuzzConfig(job="IO2BO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R30C87:SYSIO_B2_0_ODD"])), - ("A","T18", # PR46A - FuzzConfig(job="IC2A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R46C87:SYSIO_B2_0_C", "CIB_R47C87:SYSIO_B2_0_REM"])), - ("B","U18", # PR46B - FuzzConfig(job="IC2B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R46C87:SYSIO_B2_0_C", "CIB_R47C87:SYSIO_B2_0_REM"])), - ("A","R3", # PL49A - FuzzConfig(job="IO6AO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R49C0:SYSIO_B6_0_ODD"])), - ("B","R4", # PL49B - FuzzConfig(job="IO6BO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R49C0:SYSIO_B6_0_ODD"])), - ("A","L1", # PL27A - FuzzConfig(job="IO6AE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R27C0:SYSIO_B6_0_EVEN"])), - ("B","L2", # PL27B - FuzzConfig(job="IO6BE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R27C0:SYSIO_B6_0_EVEN"])), - ("A","P5", # PL46A - FuzzConfig(job="IC6A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R46C0:SYSIO_B6_0_C", "CIB_R47C0:SYSIO_B6_0_REM"])), - ("B","P6", # PL46B - FuzzConfig(job="IC6B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R46C0:SYSIO_B6_0_C", "CIB_R47C0:SYSIO_B6_0_REM"])), - ("A","E2", # PL15A - FuzzConfig(job="IO7A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R15C0:SYSIO_B7_0_ODD"])), - ("B","F1", # PL15B - FuzzConfig(job="IO7B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R15C0:SYSIO_B7_0_ODD"])), - ("A","D6", # PL6A - FuzzConfig(job="IO7AE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R6C0:SYSIO_B7_0_EVEN"])), - ("B","D5", # PL6B - FuzzConfig(job="IO7BE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R6C0:SYSIO_B7_0_EVEN"])), - ("A","K2", # PL19A - FuzzConfig(job="IC7A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R19C0:SYSIO_B7_0_C", "CIB_R20C0:SYSIO_B7_0_REM"])), - ("B","K1", # PL19B - FuzzConfig(job="IC7B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R19C0:SYSIO_B7_0_C", "CIB_R20C0:SYSIO_B7_0_REM"])), - ("A","E18", # PT84A - FuzzConfig(job="IO0A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C84:SYSIO_B0_0_ODD"])), - ("B","D17", # PT84B - FuzzConfig(job="IO0B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C84:SYSIO_B0_0_ODD"])), - ("A","E13", # PT78A - FuzzConfig(job="IO0AE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C78:SYSIO_B0_0_EVEN"])), - ("B","D13", # PT78B - FuzzConfig(job="IO0BE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C78:SYSIO_B0_0_EVEN"])), - ("B","E17", # PR3A - FuzzConfig(job="IO1D", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R3C87:SYSIO_B1_DED"])), -] - -def main(): + +def create_device_configs(device): + pads = [x for x in database.get_iodb(device)["pads"]] + configs = dict(filter(None, [ + create_config_from_pad(x, device) for x in pads if x["offset"] >= 0 + ])) + return list(configs.values()) + +configs = create_device_configs("LIFCL-33") + create_device_configs("LIFCL-33U") + create_device_configs("LIFCL-17") + create_device_configs("LIFCL-40") +# [ +# ("B", "E11", # PR3B, +# FuzzConfig(job="IO1D_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R3C75:SYSIO_B1_DED_15K"])), +# ("A","F16", # PR13A +# FuzzConfig(job="IO1A_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R13C75:SYSIO_B1_0_15K", "CIB_R14C75:SYSIO_B1_1_15K"])), +# ("B","G15", # PR13B +# FuzzConfig(job="IO1B_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R13C75:SYSIO_B1_0_15K", "CIB_R14C75:SYSIO_B1_1_15K"])), +# ("A","E15", # PT67A +# FuzzConfig(job="IO0A_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R0C67:SYSIO_B0_0_15K"])), +# ("B","E16", # PT67B +# FuzzConfig(job="IO0B_17K", device="LIFCL-17", sv="../shared/empty_17.v", tiles=["CIB_R0C67:SYSIO_B0_0_15K"])), +# +# ("A","F16", # PR8A +# FuzzConfig(job="IO1AO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R8C87:SYSIO_B1_0_ODD"])), +# ("B","F17", # PR8B +# FuzzConfig(job="IO1BO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R8C87:SYSIO_B1_0_ODD"])), +# ("A","F14", # PR6A +# FuzzConfig(job="IO1AE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R6C87:SYSIO_B1_0_EVEN"])), +# ("B","F15", # PR6B +# FuzzConfig(job="IO1BE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R6C87:SYSIO_B1_0_EVEN"])), +# ("A","F18", # PR10A +# FuzzConfig(job="IC1A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R10C87:SYSIO_B1_0_C", "CIB_R11C87:SYSIO_B1_0_REM"])), +# ("B","F19", # PR10B +# FuzzConfig(job="IC1B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R10C87:SYSIO_B1_0_C", "CIB_R11C87:SYSIO_B1_0_REM"])), +# ("A","N14", # PR24A +# FuzzConfig(job="IO2AE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R24C87:SYSIO_B2_0_EVEN"])), +# ("B","M14", # PR24B +# FuzzConfig(job="IO2BE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R24C87:SYSIO_B2_0_EVEN"])), +# ("A","M17", # PR30A +# FuzzConfig(job="IO2AO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R30C87:SYSIO_B2_0_ODD"])), +# ("B","M18", # PR30B +# FuzzConfig(job="IO2BO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R30C87:SYSIO_B2_0_ODD"])), +# ("A","T18", # PR46A +# FuzzConfig(job="IC2A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R46C87:SYSIO_B2_0_C", "CIB_R47C87:SYSIO_B2_0_REM"])), +# ("B","U18", # PR46B +# FuzzConfig(job="IC2B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R46C87:SYSIO_B2_0_C", "CIB_R47C87:SYSIO_B2_0_REM"])), +# ("A","R3", # PL49A +# FuzzConfig(job="IO6AO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R49C0:SYSIO_B6_0_ODD"])), +# ("B","R4", # PL49B +# FuzzConfig(job="IO6BO", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R49C0:SYSIO_B6_0_ODD"])), +# ("A","L1", # PL27A +# FuzzConfig(job="IO6AE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R27C0:SYSIO_B6_0_EVEN"])), +# ("B","L2", # PL27B +# FuzzConfig(job="IO6BE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R27C0:SYSIO_B6_0_EVEN"])), +# ("A","P5", # PL46A +# FuzzConfig(job="IC6A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R46C0:SYSIO_B6_0_C", "CIB_R47C0:SYSIO_B6_0_REM"])), +# ("B","P6", # PL46B +# FuzzConfig(job="IC6B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R46C0:SYSIO_B6_0_C", "CIB_R47C0:SYSIO_B6_0_REM"])), +# ("A","E2", # PL15A +# FuzzConfig(job="IO7A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R15C0:SYSIO_B7_0_ODD"])), +# ("B","F1", # PL15B +# FuzzConfig(job="IO7B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R15C0:SYSIO_B7_0_ODD"])), +# ("A","D6", # PL6A +# FuzzConfig(job="IO7AE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R6C0:SYSIO_B7_0_EVEN"])), +# ("B","D5", # PL6B +# FuzzConfig(job="IO7BE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R6C0:SYSIO_B7_0_EVEN"])), +# ("A","K2", # PL19A +# FuzzConfig(job="IC7A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R19C0:SYSIO_B7_0_C", "CIB_R20C0:SYSIO_B7_0_REM"])), +# ("B","K1", # PL19B +# FuzzConfig(job="IC7B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R19C0:SYSIO_B7_0_C", "CIB_R20C0:SYSIO_B7_0_REM"])), +# ("A","E18", # PT84A +# FuzzConfig(job="IO0A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C84:SYSIO_B0_0_ODD"])), +# ("B","D17", # PT84B +# FuzzConfig(job="IO0B", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C84:SYSIO_B0_0_ODD"])), +# ("A","E13", # PT78A +# FuzzConfig(job="IO0AE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C78:SYSIO_B0_0_EVEN"])), +# ("B","D13", # PT78B +# FuzzConfig(job="IO0BE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C78:SYSIO_B0_0_EVEN"])), +# ("B","E17", # PR3A +# FuzzConfig(job="IO1D", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R3C87:SYSIO_B1_DED"])), +# ] + +def main(executor): def per_config(config): pio, site, cfg = config cfg.setup() @@ -124,7 +132,7 @@ def per_config(config): (r,c) = tiles.get_rc_from_name(cfg.device, cfg.tiles[0]) if f"R{r}C{c}_JPADDO_SEIO33_CORE_IO{pio}" not in tiles.get_full_node_set(cfg.device): - print(f"Skipping {site} {cfg.tiles}; no SEIO33 tile") + logging.info(f"Skipping {site} {cfg.tiles[:3]}; no SEIO33 tile") return primtype = "SEIO33_CORE" @@ -179,9 +187,8 @@ def get_substs(iotype="BIDIR_LVCMOS33", kv=None, vcc=None, tmux="T"): "OUTPUT_LVCMOS33D" ] - coroutines = [] def fuzz_enum_setting(*args, **kwargs): - nonrouting.fuzz_enum_setting(cfg, empty, *args, **kwargs) + nonrouting.fuzz_enum_setting(cfg, empty, executor = executor, *args, **kwargs) fuzz_enum_setting(f"PIO{pio}.BASE_TYPE", seio_types, lambda x: get_substs(iotype=x), False, assume_zero_base=True, mark_relative_to = cfg.tiles[0]) @@ -251,8 +258,6 @@ def iotype(v, out = False): fuzz_enum_setting("PIO{}.TMUX".format(pio), ["T", "INV"], lambda x: get_substs(iotype=f"BIDIR_LVCMOS18{suffix}", tmux=x), False) - return coroutines - def cfg_filter(config): pio, site, cfg = config if not should_fuzz_platform(cfg.device): @@ -266,8 +271,9 @@ def cfg_filter(config): return True - fuzzloops.parallel_foreach(filter(cfg_filter, configs), per_config) + for config in filter(cfg_filter, configs): + per_config(config) if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - main() + fuzzloops.FuzzerMain(main) + diff --git a/fuzzers/LIFCL/032-hsio_mode/fuzzer.py b/fuzzers/LIFCL/032-hsio_mode/fuzzer.py index 2704812..16d77c2 100644 --- a/fuzzers/LIFCL/032-hsio_mode/fuzzer.py +++ b/fuzzers/LIFCL/032-hsio_mode/fuzzer.py @@ -1,3 +1,5 @@ +import logging + from fuzzconfig import FuzzConfig, should_fuzz_platform import nonrouting import fuzzloops @@ -12,6 +14,11 @@ def create_config_from_pad(pad, device): pin = pad["pins"][0] ts = [t for t in tiles.get_tiles_from_edge(device, pad["side"], pad["offset"]) if "SYSIO" in t] + + if len(ts) == 0: + logging.warning(f"Could not find tile for {pad} for {device}") + return + all_sysio = [t for t in tiles.get_tiles_from_edge(device, pad["side"]) if "SYSIO" in t] tiletype = ts[0].split(":")[1] @@ -35,12 +42,12 @@ def create_config_from_pad(pad, device): def create_configs_for_device(device): pads = [x for x in database.get_iodb(device)["pads"]] - configs = dict([ + configs = dict(filter(None, [ create_config_from_pad(x, device) for x in pads if x["offset"] >= 0 - ]) + ])) return configs -configs = (create_configs_for_device("LIFCL-33") | create_configs_for_device("LIFCL-40")).values() +configs = (create_configs_for_device("LIFCL-33") | create_configs_for_device("LIFCL-40") ).values() # + [ # ("A","V1", # PB6A # FuzzConfig(job="IO5A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C6:SYSIO_B5_0", "CIB_R56C7:SYSIO_B5_1"])), @@ -89,7 +96,7 @@ def create_configs_for_device(device): device_empty_bitfile = {} -def main(): +def main(executor): def per_config(config): pio, site, cfg = config @@ -97,7 +104,7 @@ def per_config(config): if f"R{r}C{c}_JPADDO_SEIO18_CORE_IO{pio}" not in tiles.get_full_node_set(cfg.device) and \ f"R{r}C{c}_JPADDO_DIFFIO18_CORE_IO{pio}" not in tiles.get_full_node_set(cfg.device): - print(f"Skipping {site}") + print(f"Skipping {site}; it's an SEIO33 site") return cfg.setup() @@ -160,11 +167,8 @@ def get_substs(iotype="BIDIR_LVCMOS18H", kv=None, vcc=None, tmux="T"): else: all_di_types += ["{}_{}".format(di, t) for di in d] - coroutines = [] def fuzz_enum_setting(*args, **kwargs): - coroutines.append( - nonrouting.fuzz_enum_setting(cfg, empty, *args, **kwargs) - ) + nonrouting.fuzz_enum_setting(cfg, empty, executor=executor,*args, **kwargs) fuzz_enum_setting("PIO{}.SEIO18.DRIVE_1V0".format(pio), ["2", "4"], lambda x: get_substs(iotype="OUTPUT_LVCMOS10H", kv=("DRIVE", x)), True) @@ -237,8 +241,6 @@ def fuzz_enum_setting(*args, **kwargs): fuzz_enum_setting("PIO{}.DIFFIO18.DIFFTX_INV".format(pio), ["NORMAL", "INVERT"], lambda x: get_substs(iotype="OUTPUT_LVDS", kv=("DIFFTX_INV", x)), False) - return coroutines - def cfg_filter(config): pio, site, cfg = config if not should_fuzz_platform(cfg.device): @@ -252,7 +254,8 @@ def cfg_filter(config): return True - fuzzloops.parallel_foreach(filter(cfg_filter, configs), per_config) + for config in filter(cfg_filter, configs): + per_config(config) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/062-lram-config/fuzzer.py b/fuzzers/LIFCL/062-lram-config/fuzzer.py index 1cf1fb8..144fb12 100644 --- a/fuzzers/LIFCL/062-lram-config/fuzzer.py +++ b/fuzzers/LIFCL/062-lram-config/fuzzer.py @@ -62,4 +62,4 @@ def get_substs(mode="NONE", kv=None): fuzzloops.parallel_foreach(configs, per_config) if __name__ == "__main__": - asyncio.run(main()) + main() diff --git a/fuzzers/LIFCL/093-oscd/osc_pins.v b/fuzzers/LIFCL/093-oscd/osc_pins.v new file mode 100644 index 0000000..29362a0 --- /dev/null +++ b/fuzzers/LIFCL/093-oscd/osc_pins.v @@ -0,0 +1,16 @@ +(* \db:architecture ="LIFCL", \db:device ="LIFCL-33", \db:package ="WLCSP84", \db:speed ="8_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + + (* \xref:LOG ="q_c@0@9"${arcs_attr} *) + wire q; + + (* \dm:primitive ="OSC_CORE", \dm:programming ="MODE:OSC_CORE ${config}", \dm:site ="OSC_CORE_R1C29" *) + OSC_CORE OSC_I ( + .${pin_name}( q ) + ); + + (* \dm:cellmodel_primitives ="REG0=reg", \dm:primitive ="SLICE", \dm:programming ="MODE:LOGIC Q0:Q0 ", \dm:site ="R2C2A" *) + SLICE SLICE_I ( ${target} ); +endmodule diff --git a/fuzzers/LIFCL/shared/empty.v b/fuzzers/LIFCL/shared/empty.v new file mode 100644 index 0000000..7942d25 --- /dev/null +++ b/fuzzers/LIFCL/shared/empty.v @@ -0,0 +1,8 @@ +(* \db:architecture ="${arch}", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule diff --git a/generate_database.sh b/generate_database.sh index aded20a..d8767a0 100755 --- a/generate_database.sh +++ b/generate_database.sh @@ -11,7 +11,8 @@ for dir in LIFCL/* ; do if [ -f "$dir/fuzzer.py" ]; then echo "=================== Entering $dir ===================" pushd $dir - python3 fuzzer.py 2>&1 | tee >(gzip --stdout > fuzzer.log.gz) + ../../../link-db-root.sh + PRJOXIDE_DB=`pwd`/db python3 fuzzer.py 2>&1 | tee >(gzip --stdout > fuzzer.log.gz) popd fi done From ace51a1a2a126686929f048126ed2c5944a59747 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Tue, 27 Jan 2026 13:31:54 -0700 Subject: [PATCH 17/29] Fix borrow issues with db; docs --- fuzzers/LIFCL/002-cib-routing/fuzzer.py | 80 +++++++------ .../fuzzer.py | 112 +++++++++--------- .../fuzzer.py | 93 ++++++--------- .../primitive.v | 0 fuzzers/LIFCL/900-always-on/fuzzer.py | 1 + util/common/lapie.py | 9 +- util/common/nodes_database.py | 2 +- util/common/tiles.py | 9 +- util/fuzz/fuzzconfig.py | 69 +++++++---- util/fuzz/fuzzloops.py | 67 +++++++---- util/fuzz/interconnect.py | 24 ++-- util/fuzz/nonrouting.py | 29 +++-- 12 files changed, 280 insertions(+), 215 deletions(-) rename fuzzers/LIFCL/{004-tile-routing => 015-local-routes}/fuzzer.py (76%) rename fuzzers/LIFCL/{005-site-routing => 016-site-mappings}/fuzzer.py (91%) rename fuzzers/LIFCL/{005-site-routing => 016-site-mappings}/primitive.v (100%) diff --git a/fuzzers/LIFCL/002-cib-routing/fuzzer.py b/fuzzers/LIFCL/002-cib-routing/fuzzer.py index 734d403..3d3fffb 100644 --- a/fuzzers/LIFCL/002-cib-routing/fuzzer.py +++ b/fuzzers/LIFCL/002-cib-routing/fuzzer.py @@ -1,45 +1,57 @@ +import asyncio + from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect +from fuzzloops import FuzzerAsyncMain import re configs = [ - ((1, 18), FuzzConfig(job="CIBTROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R1C18:CIB_T"]), set(["TAP_CIBT_R1C14:TAP_CIBT"])), - ((18, 1), FuzzConfig(job="CIBLRROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R18C1:CIB_LR"]), set(["TAP_PLC_R18C14:TAP_PLC"])), - ((28, 17), FuzzConfig(job="CIBROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R28C17:CIB"]), set(["TAP_CIB_R28C14:TAP_CIB"])), - ((28, 1), FuzzConfig(job="CIBLRAROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R28C1:CIB_LR_A"]), set(["TAP_CIB_R28C14:TAP_CIB"])), + ((1, 18), FuzzConfig(job="CIBTROUTE", device="LIFCL-40", tiles=["CIB_R1C18:CIB_T"]), set(["TAP_CIBT_R1C14:TAP_CIBT"])), + ((18, 1), FuzzConfig(job="CIBLRROUTE", device="LIFCL-40", tiles=["CIB_R18C1:CIB_LR"]), set(["TAP_PLC_R18C14:TAP_PLC"])), + ((28, 17), FuzzConfig(job="CIBROUTE", device="LIFCL-40", tiles=["CIB_R28C17:CIB"]), set(["TAP_CIB_R28C14:TAP_CIB"])), + ((28, 1), FuzzConfig(job="CIBLRAROUTE", device="LIFCL-40", tiles=["CIB_R28C1:CIB_LR_A"]), set(["TAP_CIB_R28C14:TAP_CIB"])), ] -def main(): - for rc, cfg, ignore in configs: - cfg.setup() - r, c = rc - nodes = ["R{}C{}_J*".format(r, c)] - extra_sources = [] - extra_sources += ["R{}C{}_H02E{:02}01".format(r, c+1, i) for i in range(8)] - extra_sources += ["R{}C{}_H06E{:02}03".format(r, c+3, i) for i in range(4)] - if r != 1: - extra_sources += ["R{}C{}_V02N{:02}01".format(r-1, c, i) for i in range(8)] - extra_sources += ["R{}C{}_V06N{:02}03".format(r-3, c, i) for i in range(4)] - else: - extra_sources += ["R{}C{}_V02N{:02}00".format(r, c, i) for i in range(8)] - extra_sources += ["R{}C{}_V06N{:02}00".format(r, c, i) for i in range(4)] - extra_sources += ["R{}C{}_V02S{:02}01".format(r+1, c, i) for i in range(8)] - extra_sources += ["R{}C{}_V06S{:02}03".format(r+3, c, i) for i in range(4)] - if c != 1: - extra_sources += ["R{}C{}_H02W{:02}01".format(r, c-1, i) for i in range(8)] - extra_sources += ["R{}C{}_H06W{:02}03".format(r, c-3, i) for i in range(4)] - else: - extra_sources += ["R{}C{}_H02W{:02}00".format(r, c, i) for i in range(8)] - extra_sources += ["R{}C{}_H06W{:02}00".format(r, c, i) for i in range(4)] - def pip_filter(pip, nodes): - from_wire, to_wire = pip - return not ("_CORE" in from_wire or "_CORE" in to_wire or "JCIBMUXOUT" in to_wire) - def fc_filter(to_wire): - return "CIBMUX" in to_wire or "CIBTEST" in to_wire or to_wire.startswith("R{}C{}_J".format(r, c)) - fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=True, ignore_tiles=ignore, - pip_predicate=pip_filter, fc_filter=fc_filter) +async def per_config(rc, cfg, ignore, executor): + cfg.setup() + r, c = rc + nodes = ["R{}C{}_J*".format(r, c)] + extra_sources = [] + extra_sources += ["R{}C{}_H02E{:02}01".format(r, c+1, i) for i in range(8)] + extra_sources += ["R{}C{}_H06E{:02}03".format(r, c+3, i) for i in range(4)] + if r != 1: + extra_sources += ["R{}C{}_V02N{:02}01".format(r-1, c, i) for i in range(8)] + extra_sources += ["R{}C{}_V06N{:02}03".format(r-3, c, i) for i in range(4)] + else: + extra_sources += ["R{}C{}_V02N{:02}00".format(r, c, i) for i in range(8)] + extra_sources += ["R{}C{}_V06N{:02}00".format(r, c, i) for i in range(4)] + extra_sources += ["R{}C{}_V02S{:02}01".format(r+1, c, i) for i in range(8)] + extra_sources += ["R{}C{}_V06S{:02}03".format(r+3, c, i) for i in range(4)] + if c != 1: + extra_sources += ["R{}C{}_H02W{:02}01".format(r, c-1, i) for i in range(8)] + extra_sources += ["R{}C{}_H06W{:02}03".format(r, c-3, i) for i in range(4)] + else: + extra_sources += ["R{}C{}_H02W{:02}00".format(r, c, i) for i in range(8)] + extra_sources += ["R{}C{}_H06W{:02}00".format(r, c, i) for i in range(4)] + def pip_filter(pip, nodes): + from_wire, to_wire = pip + return not ("_CORE" in from_wire or "_CORE" in to_wire or "JCIBMUXOUT" in to_wire) + def fc_filter(to_wire): + return "CIBMUX" in to_wire or "CIBTEST" in to_wire or to_wire.startswith("R{}C{}_J".format(r, c)) + + futures = fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=True, ignore_tiles=ignore, + pip_predicate=pip_filter, fc_filter=fc_filter, executor=executor) + \ fuzz_interconnect(config=cfg, nodenames=extra_sources, regex=False, bidir=False, ignore_tiles=ignore, - pip_predicate=pip_filter, fc_filter=fc_filter) + pip_predicate=pip_filter, fc_filter=fc_filter, executor=executor) + + await asyncio.gather(*[asyncio.wrap_future(f) for f in futures]) + +async def run(executor): + await asyncio.gather(*[(per_config(*cfg, executor=executor))for cfg in configs]) + + +def main(): + FuzzerAsyncMain(run) if __name__ == "__main__": main() diff --git a/fuzzers/LIFCL/004-tile-routing/fuzzer.py b/fuzzers/LIFCL/015-local-routes/fuzzer.py similarity index 76% rename from fuzzers/LIFCL/004-tile-routing/fuzzer.py rename to fuzzers/LIFCL/015-local-routes/fuzzer.py index fcde923..68895a6 100644 --- a/fuzzers/LIFCL/004-tile-routing/fuzzer.py +++ b/fuzzers/LIFCL/015-local-routes/fuzzer.py @@ -19,6 +19,15 @@ import database +### +# The idea for this fuzzer is that we can map out the internal pips and connections for a given tiletype in relatively +# short order without knowing much about them by using the introspection from the radiant tools. The basic process is: +# - Generate a list of wires and pips a given tile type has +# - Figure out which tiles configure these pips +# - Use all the tiles of that tiletype on the board to test. So if there are 16 tiles with that tiletype, we can solve +# for 16 PIP configurations at a time. +### + processed_tiletypes = set("PLC") exclusion_list = { @@ -27,7 +36,7 @@ ("SYSIO_B3_0", "JECLKIN1_I218", "JECLKOUT_I218") } -# Cache this so we only do it once. Could also probably read the ron file and check it +# Cache this so we only do it once. Could also probably read the ron file and check it. @cachecontrol.cache_fn() def register_tile_connections(device, tiletype, tile, conn_pips): connection_sinks = defaultdict(list) @@ -41,52 +50,52 @@ def register_tile_connections(device, tiletype, tile, conn_pips): db.add_conn(family, tiletype, to_wire, from_wire) db.flush() -async def get_tiletype_pips(device, tiletype, executor = None): +### Gather up all the consistent internal pips for a tiletype, then use however many tiles of that tiletype exist +### to create design sets for each PIP. This also tracks and manages tiles that configure the tiletype but are positioned +### relative to it and have different tiles types +async def get_tiletype_design_sets(device, tiletype, executor = None): + + # representative nodes is all wires that are common to all instances of that tiletype for the device wires = tiles.get_representative_nodes_for_tiletype(device, tiletype) if len(wires) == 0: logging.debug(f"{tiletype} has no consistent internal wires") return tiletype, [], [] - arcs = lapie.get_list_arc(device) - ts = sorted(list(tiles.get_tiles_by_tiletype(device, tiletype).keys())) + + # Treat this as an exemplar node to gather the pips from (r, c) = tiles.get_rc_from_name(device, ts[0]) nodes = set([f"R{r}C{c}_{w}" for w in wires]) - - internal_and_external_pips, tiletype_graph = await asyncio.wrap_future(tiles.get_local_pips_for_nodes(device, nodes, include_interface_pips=True, + pips, tiletype_graph = await asyncio.wrap_future(tiles.get_local_pips_for_nodes(device, nodes, include_interface_pips=False, should_expand=lambda p: p[0] in nodes and p[1] in nodes, executor = executor)) - pips = {p for p in internal_and_external_pips if p[0] in tiletype_graph and p[1] in tiletype_graph} - connected_arcs = set([ - (frm_wire, to_wire) - for (frm_wire, to_wire) in arcs - if frm_wire in nodes - ]) + # Jump wires are what lattice tools refer to as connections -- basically PIPs that are always on + connected_arcs = lapie.get_jump_wires_by_nodes(device, nodes) + # Remove the jump wires -- no point in including them in our designs, we already know they are connections conn_pips = set(pips) & connected_arcs actual_pips = set(pips) - conn_pips pips = sorted(actual_pips) + # While we have the list, we might as well mark down that they are connections register_tile_connections(device, tiletype, ts[0], sorted(conn_pips)) anon_pips = sorted(set([tuple(["_".join(w.split("_")[1:]) for w in p]) for p in pips])) baseline = FuzzConfig.standard_empty(device) cfg = FuzzConfig(job=f"find-tile-set-{device}-{tiletype}", device=device) - baseline_pips = [] - baseline_nodes = set() - for p in pips: - if not p[0] in baseline_nodes and not p[1] in baseline_nodes: - for w in p: - baseline_nodes.add(w) - baseline_pips.append(p) bitstream = await asyncio.wrap_future(interconnect.create_wires_file(cfg, pips, executor=executor)) - deltas = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, baseline).delta(fuzzconfig.db, bitstream) + + deltas, _ =fuzzconfig.find_baseline_differences(device, bitstream) + + # PLC is used to affix the wires, strip those from the delta filtered_deltas = {k:v for k,v in deltas.items() if k.split(":")[1] != "PLC"} + # PIPs are often controlled by nearby tiles. Convert those to relative positioned tiles. Since sometimes two tiles + # will share an RC, we grab the tiletype too. modified_tiles_rcs = set([(tiles.get_rc_from_name(device, n),n.split(":")[-1]) for n in filtered_deltas.keys()]) modified_tiles_rcs_anon = [((r0-r),(c0-c),tt) for ((r0,c0),tt) in modified_tiles_rcs] @@ -110,24 +119,28 @@ async def get_tiletype_pips(device, tiletype, executor = None): return tiletype, [], [] design_sets = [] - rcs = sorted([(tile,tiles.get_rc_from_name(device, tile)) for tile in ts]) + rcs_for_tiles_of_tiletype = sorted([(tile,tiles.get_rc_from_name(device, tile)) for tile in ts]) - extra_rcs = set([((r+rd), (c+cd), tt) - for (_, (r,c)) in rcs - for (rd,cd,tt) in modified_tiles_rcs_anon]) + extra_rcs = [((r+rd), (c+cd), tt) + for (_, (r,c)) in rcs_for_tiles_of_tiletype + for (rd,cd,tt) in modified_tiles_rcs_anon] + # Make sure there isn't overlap between modified tiles for all the tiles of the type. + assert(len(extra_rcs) == len(set(extra_rcs))) + extra_rcs = set(extra_rcs) + + # Generate design sets by continuously iterating through tile locations and putting a random PIP there. while len(anon_pips): design_set = {} + + # Just place all the extra tiles. We dont have pips for these tiles but this marks it as used. for rc in extra_rcs: for tile in tiles.get_tiles_by_rc(device, rc): design_set[tile] = None - for (tile, (r,c)) in rcs: + for (tile, (r,c)) in rcs_for_tiles_of_tiletype: pip = anon_pips.pop() pip = [f"R{r}C{c}_{w}" for w in pip] design_set[tile] = pip - # - # design_sets.append(design_set) - # design_set = {} if len(anon_pips) == 0: break @@ -137,24 +150,8 @@ async def get_tiletype_pips(device, tiletype, executor = None): return tiletype, design_sets, modified_tiles_rcs_anon -def diff_designs(bitstream, baseline): - deltas = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, baseline).delta(fuzzconfig.db, bitstream) - deltas = {k:v for (k,v) in deltas.items() if k.split(":")[1] != "PLC"} - return deltas - -async def run_for_device(device, executor = None): - if not fuzzconfig.should_fuzz_platform(device): - logging.warning(f"Ignoring device {device}") - return [] - - logging.info("Fuzzing device: " + device) - - lapie.get_list_arc(device) - +def get_filtered_typetypes(device): tiletypes = tiles.get_tiletypes(device) - - device_futures = [] - for tiletype, ts in sorted(tiletypes.items()): if tiletype in ["PLC", "TAP_PLC"]: @@ -166,11 +163,22 @@ async def run_for_device(device, executor = None): if tiletype in processed_tiletypes: continue processed_tiletypes.add(tiletype) + yield tiletype - device_futures.append(get_tiletype_pips(device, tiletype, executor=executor)) +async def run_for_device(device, executor = None): + if not fuzzconfig.should_fuzz_platform(device): + logging.warning(f"Ignoring device {device}") + return [] + + logging.info("Fuzzing device: " + device) + + device_futures = [ + get_tiletype_design_sets(device, tiletype, executor=executor) + for tiletype in get_filtered_typetypes(device) + ] # list of list of dicts - logging.info(f"Gathering {len(device_futures)} tiletypes") + logging.info(f"Gathering {len(device_futures)} tile type pips") all_design_sets = await asyncio.gather(*device_futures) owned_rcs = {tt:e for (tt,d,e) in all_design_sets} @@ -199,19 +207,13 @@ async def run_for_device(device, executor = None): for idx, (owners, design_set) in enumerate(design_sets): pips = [pip for tile, pip in design_set.items() if pip is not None] create_bitstream_future = interconnect.create_wires_file(cfg, pips, executor=executor, prefix=f"{idx}/") - diff_designs_futures.append(fuzzloops.chain(create_bitstream_future, diff_designs, "solve_design", empty_file)) + diff_designs_futures.append(fuzzloops.chain(create_bitstream_future, "solve_design", lambda x,device=device: fuzzconfig.find_baseline_differences(device, x)[0])) all_design_diffs = await asyncio.gather(*[asyncio.wrap_future(f) for f in diff_designs_futures]) def anon_pip(p): return ["_".join(w.split("_")[1:]) for w in p] - # for (i, (deltas, (owners, design_set))) in enumerate(zip(all_design_diffs, design_sets)): - # with open(f"delta_raw{i}.json", "w") as f: - # json.dump(deltas, f, indent=4) - # with open(f"design{i}.json", "w") as f: - # json.dump(design_set, f, indent=4) - pip_deltas = defaultdict(list) for (deltas, (owners, design_set)) in zip(all_design_diffs, design_sets): rc_deltas = {(*tiles.get_rc_from_name(device, k), k.split(":")[-1]):v for k,v in deltas.items()} @@ -297,7 +299,5 @@ async def run_for_devices(executor): return await asyncio.gather(*[run_for_device(device, executor) for device in devices]) - - if __name__ == "__main__": fuzzloops.FuzzerAsyncMain(run_for_devices) diff --git a/fuzzers/LIFCL/005-site-routing/fuzzer.py b/fuzzers/LIFCL/016-site-mappings/fuzzer.py similarity index 91% rename from fuzzers/LIFCL/005-site-routing/fuzzer.py rename to fuzzers/LIFCL/016-site-mappings/fuzzer.py index fece0fc..d379bc9 100644 --- a/fuzzers/LIFCL/005-site-routing/fuzzer.py +++ b/fuzzers/LIFCL/016-site-mappings/fuzzer.py @@ -1,14 +1,10 @@ import asyncio import logging import re -import shutil import sys -import time -import traceback -from collections import defaultdict -from concurrent.futures import Future -from multiprocessing.synchronize import RLock +import lapie +import cachecontrol import fuzzconfig import fuzzloops import interconnect @@ -17,15 +13,19 @@ import primitives import radiant import tiles -from cachier import cachier from fuzzconfig import FuzzConfig, get_db from interconnect import fuzz_interconnect_sinks import database -import cachecontrol + +### +# This fuzzer pulls up each site, figures out its relationship to tiletypes, and then find the routeing and primitive +# mappings for those representative tile(s). +### mapped_sites = set() +# These tiles overlap many sites and are not the main site tiles overlapping_tile_types = set(["CIB", "MIB_B_TAP", "TAP_CIB"] + [f"BANKREF{i}" for i in range(16)] + [f"BK{i}_15K" for i in range(16)] @@ -37,32 +37,15 @@ def get_site_tiles(device, site): return site_tiles -@cachecontrol.cache_fn() -def find_baseline_differences(device, active_bitstream): - baseline = FuzzConfig.standard_empty(device) - - deltas = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, baseline).delta(fuzzconfig.db, active_bitstream) - - ip_values = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, active_bitstream).get_ip_values() - ip_values = [(a,v) for a,v in ip_values if v != 0] - - return deltas, ip_values - +# Pull from a bitstream baseline delta the main tile and IP changes def find_relevant_tiles_from_bitstream(device, site, active_bitstream): - deltas, ip_values = find_baseline_differences(device, active_bitstream) + deltas, ip_values = fuzzconfig.find_baseline_differences(device, active_bitstream) power_tile_types = set(["PMU"] + [f"BANKREF{i}" for i in range(16)]) pmu_tiles = [x for x in list(deltas.keys()) if x.split(":")[-1] in power_tile_types] delta_sorted = [x[0] for x in sorted(deltas.items(), key=lambda x: -len(x[1]))] driving_tiles = [x for x in delta_sorted if x.split(":")[-1] not in power_tile_types] - - # single_driving_type_check = site_type in ["PCLKDIV", "ECLKDIV_CORE", "DIFFIO18_CORE"] or len(driving_tiles) == 1 - # if not single_driving_type_check: - # raise Exception(f"{site_type} should have single driving tile but it has {driving_tiles}. {deltas}") - - #tile = driving_tiles[0] - site_tiles = [tile for tile in tiles.get_tiles_by_rc(device, site) if tile.split(":")[1] not in overlapping_tile_types] @@ -72,7 +55,29 @@ def find_relevant_tiles_from_bitstream(device, site, active_bitstream): return (driving_tiles + pmu_tiles), site_tiles, ip_values -#@cachier(separate_files=True, cache_dir='.cachier') +# Look at the site pins and map out the nodes on those pins. Find the deltas that enable those pips. +async def find_relevant_tiles(device, site, site_type, site_info, executor): + cfg = FuzzConfig(job=f"{site}:{site_type}", device=device, tiles=[]) + + nodes = [p["pin_node"] for p in site_info["pins"]] + logging.info(f"Getting relevant wire tiles for {device} {site}:{site_type}") + pips, _ = await asyncio.wrap_future( + tiles.get_local_pips_for_nodes(device, nodes, include_interface_pips=True, + should_expand=lambda p: p[0] in nodes or p[1] in nodes, + executor = executor) + ) + + wires_bitstream = await asyncio.wrap_future(interconnect.create_wires_file(cfg, pips, prefix=f"find-relevant-tiles/", executor = executor)) + + driving_tiles, site_tiles, ip_values = find_relevant_tiles_from_bitstream(device, site, wires_bitstream) + + return ([t for t in driving_tiles if t.split(":")[-1] != "PLC"], + [t for t in site_tiles if t.split(":")[-1] != "PLC"], + ip_values + ) + +# If we have a primitive definition, use it to generate a bitstream and compare it to baseline. This delta shows which +# tiles the site belongs to. async def find_relevant_tiles_from_primitive(device, primitive, site, site_info, executor): site_type = site_info["type"] @@ -89,33 +94,18 @@ async def find_relevant_tiles_from_primitive(device, primitive, site, site_info, driving_tiles, site_tiles, ip_values = find_relevant_tiles_from_bitstream(device, site, primitive_bitstream) + # Also get the tiling from just the wiring pin_driving_tiles, pin_site_tiles, pin_ip_values = await find_relevant_tiles(device, site, site_type, site_info, executor = executor) + + # Note: We do this to keep ordering but removing dups def uniq(x): return list(dict.fromkeys(x)) return uniq(driving_tiles + pin_driving_tiles), uniq(site_tiles + pin_site_tiles), uniq(ip_values + pin_ip_values) -async def find_relevant_tiles(device, site, site_type, site_info, executor): - cfg = FuzzConfig(job=f"{site}:{site_type}", device=device, tiles=[]) - - nodes = [p["pin_node"] for p in site_info["pins"]] - logging.info(f"Getting relevant wire tiles for {device} {site}:{site_type}") - pips, _ = await asyncio.wrap_future( - tiles.get_local_pips_for_nodes(device, nodes, include_interface_pips=True, - should_expand=lambda p: p[0] in nodes or p[1] in nodes, - executor = executor) - ) - - wires_bitstream = await asyncio.wrap_future(interconnect.create_wires_file(cfg, pips, prefix=f"find-relevant-tiles/", executor = executor)) - - driving_tiles, site_tiles, ip_values = find_relevant_tiles_from_bitstream(device, site, wires_bitstream) - - return ([t for t in driving_tiles if t.split(":")[-1] != "PLC"], - [t for t in site_tiles if t.split(":")[-1] != "PLC"], - ip_values - ) - mux_re = re.compile("MUX[0-9]*$") + +# Take all a given sites local graph and pips, and solve for all of it def map_local_pips(site, site_type, device, ts, pips, local_graph, executor=None): cfg = FuzzConfig(job=f"{site}:{site_type}", sv="../shared/route.v", device=device, tiles=ts) @@ -143,6 +133,7 @@ def map_local_pips(site, site_type, device, ts, pips, local_graph, executor=None mux_cfg = FuzzConfig(job=f"{site}:{site_type}-MUX", sv="../shared/route.v", device=device, tiles=cfg.tiles) yield from fuzz_interconnect_sinks(mux_cfg, mux_pips, True, executor = executor) +# Use the primitive definitions to map out each mode's options. Works for IP and non IP settings def map_primitive_settings(device, ts, site, site_tiles, site_type, ip_values, executor = None): if site_type not in primitives.primitives: return [] @@ -309,15 +300,9 @@ async def per_site(site, site_info, driving_tiles, executor): sites = database.get_sites(device) sites_items = [(k,v) for k,v in sorted(sites.items()) if v["type"] not in ["CIBTEST", "SLICE"]] - import lapie sitetypes = {v["type"] for s,v in sites_items} - # repr_sites = {v["type"]:(k,v) for (k,v) in sites_items} - # sites_items = sorted([v for k,v in repr_sites.items()]) - - #await asyncio.gather(*[executor.submit(lapie.get_node_data, device, sitetype, True) for sitetype in sitetypes]) await asyncio.wrap_future(lapie.get_node_data(device, sitetypes, True, executor)) - logging.info("-----------------------------------------") driving_tiles_futures = [] async with asyncio.TaskGroup() as tg: diff --git a/fuzzers/LIFCL/005-site-routing/primitive.v b/fuzzers/LIFCL/016-site-mappings/primitive.v similarity index 100% rename from fuzzers/LIFCL/005-site-routing/primitive.v rename to fuzzers/LIFCL/016-site-mappings/primitive.v diff --git a/fuzzers/LIFCL/900-always-on/fuzzer.py b/fuzzers/LIFCL/900-always-on/fuzzer.py index 9046c73..c860a48 100644 --- a/fuzzers/LIFCL/900-always-on/fuzzer.py +++ b/fuzzers/LIFCL/900-always-on/fuzzer.py @@ -12,6 +12,7 @@ ] def main(): + db = fuzzconfig.get_db() for cfg in cfgs: cfg.setup() empty = cfg.build_design(cfg.sv, {}) diff --git a/util/common/lapie.py b/util/common/lapie.py index 4c2af7f..8c48682 100644 --- a/util/common/lapie.py +++ b/util/common/lapie.py @@ -261,7 +261,7 @@ def get_node(n): return arcs @cache -def get_list_arc(device): +def get_jump_wires(device): from nodes_database import NodesDatabase node_db = NodesDatabase.get(device) jmp = set(node_db.get_jumpwires()) @@ -271,6 +271,13 @@ def get_list_arc(device): return jmp +def get_jump_wires_by_nodes(device, nodes): + return set([ + (frm_wire, to_wire) + for (frm_wire, to_wire) in get_jump_wires(device) + if frm_wire in nodes or to_wire in nodes + ]) + def _get_node_data(udb, nodes): regex = False diff --git a/util/common/nodes_database.py b/util/common/nodes_database.py index 31c2c07..502de1c 100644 --- a/util/common/nodes_database.py +++ b/util/common/nodes_database.py @@ -18,7 +18,7 @@ def __init__(self, device): import database self.db_path = f"{database.get_cache_dir()}/{device}-nodes.sqlite" - logging.info(f"Opening node database at {self.db_path}") + logging.debug(f"Opening node database at {self.db_path}") self.device = device self.conn = sqlite3.connect(self.db_path, check_same_thread=False) diff --git a/util/common/tiles.py b/util/common/tiles.py index 0861b32..71b3da8 100644 --- a/util/common/tiles.py +++ b/util/common/tiles.py @@ -460,12 +460,11 @@ def get_outlier_nodes_for_tiletype(device, tiletype): @cachecontrol.cache_fn() def get_connections_for_device(device): - arcs = lapie.get_list_arc(device) + arcs = lapie.get_jump_wires(device) - connections = {} - - for arc_node, arc_node_info in arcs.items(): - connections[arc_node] = set([p.to_wire for p in arc_node_info.downhill_pips]) + connections = defaultdict(set) + for frm, to in arcs: + connections[frm].add(to) return connections diff --git a/util/fuzz/fuzzconfig.py b/util/fuzz/fuzzconfig.py index 2c4aa43..1131ffa 100644 --- a/util/fuzz/fuzzconfig.py +++ b/util/fuzz/fuzzconfig.py @@ -5,20 +5,26 @@ import logging import os import threading +from concurrent.futures import Future from os import path from pathlib import Path from string import Template import radiant import database import libpyprjoxide +import cachecontrol -db = None +#db = None def get_db(): - global db - if db is None: - db = libpyprjoxide.Database(database.get_db_root()) - return db + #global db + l = threading.local() + if "fuzz_db" in l.__dict__: + return l.__dict__["fuzz_db"] + l.__dict__["fuzz_db"] = libpyprjoxide.Database(database.get_db_root()) + #if db is None: + # db = l.__dict__["fuzz_db"] + return l.__dict__["fuzz_db"] PLATFORM_FILTER = os.environ.get("FUZZER_PLATFORM", None) @@ -31,6 +37,16 @@ def should_fuzz_platform(device): return False return True +@cachecontrol.cache_fn() +def find_baseline_differences(device, active_bitstream): + baseline = FuzzConfig.standard_empty(device) + db = get_db() + deltas = libpyprjoxide.Chip.from_bitstream(db, baseline).delta(db, active_bitstream) + + ip_values = libpyprjoxide.Chip.from_bitstream(db, active_bitstream).get_ip_values() + ip_values = [(a, v) for a, v in ip_values if v != 0] + + return deltas, ip_values class FuzzConfig: _standard_empty_bitfile = {} @@ -96,7 +112,7 @@ def check_deltas(self, name): def solve(self, fz): try: - fz.solve(db) + fz.solve(get_db()) self.serialize_deltas(fz, fz.get_name()) except: self.serialize_deltas(fz, f"{fz.get_name()}/FAILED") @@ -108,9 +124,6 @@ def setup(self, skip_specimen=False): """ # Load the global database if it doesn't exist already - global db - if db is None: - db = libpyprjoxide.Database(database.get_db_root()) self.make_workdir() if not skip_specimen: @@ -135,9 +148,10 @@ def subst_defaults(self): def build_design_future(self, executor, *args, **kwargs): future = executor.submit(self.build_design, *args, **kwargs) future.name = f"Build {self.device}" + future.executor = executor return future - def build_design(self, des_template, substitutions = {}, prefix="", substitute=True): + def build_design(self, des_template, substitutions = {}, prefix="", substitute=True, executor = None): """ Run Radiant on a given design template, applying a map of substitutions, plus some standard substitutions if not overriden. @@ -201,23 +215,34 @@ def build_design(self, des_template, substitutions = {}, prefix="", substitute=T outf.write(gzf.read()) if foundFile is not None: + if executor is not None: + f = Future() + f.set_result(foundFile) + return f return foundFile - FuzzConfig.radiant_builds = FuzzConfig.radiant_builds + 1 - process_results = radiant.run(self.device, desfile, struct_ver=self.struct_mode, raw_bit=False, rbk_mode=self.rbk_mode) + def run_radiant_sh(): + FuzzConfig.radiant_builds = FuzzConfig.radiant_builds + 1 + process_results = radiant.run(self.device, desfile, struct_ver=self.struct_mode, raw_bit=False, rbk_mode=self.rbk_mode) + + error_output = process_results.stderr.decode().strip() + if "ERROR <" in error_output: + raise Exception(f"Error found during bitstream build: {error_output}") + + if self.struct_mode and self.udb_specimen is None: + self.udb_specimen = path.join(self.workdir, prefix + "design.tmp", "par.udb") + if path.exists(bitfile): + return bitfile + if path.exists(bitfile_gz): + return bitfile_gz + + raise Exception(f"Could not generate bitstream file {bitfile} {bitfile_gz}") - error_output = process_results.stderr.decode().strip() - if "ERROR <" in error_output: - raise Exception(f"Error found during bitstream build: {error_output}") + if executor is None: + return run_radiant_sh() - if self.struct_mode and self.udb_specimen is None: - self.udb_specimen = path.join(self.workdir, prefix + "design.tmp", "par.udb") - if path.exists(bitfile): - return bitfile - if path.exists(bitfile_gz): - return bitfile_gz + return executor.submit(run_radiant_sh) - raise Exception(f"Could not generate bitstream file {bitfile} {bitfile_gz}") @property def udb(self): diff --git a/util/fuzz/fuzzloops.py b/util/fuzz/fuzzloops.py index 064d00a..41a2ce0 100644 --- a/util/fuzz/fuzzloops.py +++ b/util/fuzz/fuzzloops.py @@ -10,6 +10,7 @@ import threading import time from asyncio import CancelledError +from os import abort from pathlib import Path from signal import SIGINT, SIGTERM @@ -42,7 +43,7 @@ def Executor(executor=None): if cleanup: executor.shutdown(wait=True) - +error_count = 0 def parallel_foreach(items, func, jobs = None): """ Run a function over a list of values, running a number of jobs @@ -77,8 +78,11 @@ def runner(): func(item) except Exception as e: - print(f"Error: {e}") - traceback.print_exc() + global error_count + if error_count < 10: + error_count = error_count + 1 + print(f"Error: {e}") + traceback.print_exc() exception = e with items_lock: @@ -146,7 +150,13 @@ def _done(i, fut): return out -def chain(future, func, name = None, *args, **kwargs): +def chain(future, func, *args, **kwargs): + name = None + if isinstance(func, str): + name = func + func = args[0] + args = args[1:] + if isinstance(future, list): future = gather_futures(future) @@ -156,7 +166,11 @@ def _done(f): r = None try: r = f.result() - fut.set_result(func(r, *args, **kwargs)) + if hasattr(f, 'executor'): + new_f = f.executor.submit(func, r, *args, **kwargs) + new_f.add_done_callback(fut.set_result) + else: + fut.set_result(func(r, *args, **kwargs)) except BaseException as e: logging.error(f"Encountered exception while calling {func} with {r} {args} {kwargs}") traceback.print_exception(e) @@ -178,13 +192,14 @@ def _done(f): return fut class AsyncExecutor: - def __init__(self): + def __init__(self, executor): self.futures = [] self.lock = RLock() self.loop = asyncio.get_running_loop() - + self.executor = executor def submit(self, f, *args, **kwargs): future = self.loop.run_in_executor(None, lambda args=args,kwargs=kwargs: f(*args, **kwargs)) + future.name = f.__name__ self.register_future(future) return future @@ -214,6 +229,9 @@ def busy(self): def task_count(self): return len(self.futures) + def shutdown(self): + self.executor.shutdown(wait=True, cancel_futures=True) + def FuzzerAsyncMain(f): from fuzzconfig import FuzzConfig @@ -242,16 +260,19 @@ def FuzzerAsyncMain(f): async def start(f): async_executor = None - main_task = asyncio.current_task() int_count = 0 def sighandler(sig, frame): nonlocal int_count - print(f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! SIG HANDLER !!!!!!!!!!!!!!!!!!!!!!! {int_count}") - if int_count > 1: - sys.exit(-1) - - main_task.cancel() int_count = int_count + 1 + if int_count > 2: + logging.warning("Forcing exit") + os._exit(-1) + + for t in asyncio.all_tasks(): + t.cancel("Signal interrupt") + + if async_executor is not None: + async_executor.shutdown() for sig in [SIGINT, SIGTERM]: signal.signal(sig, sighandler) @@ -291,6 +312,11 @@ def process_future(fut): if fut.done(): if fut.exception() is not None: + logging.error(f"Encountered exception in future {fut}: {fut.exception()}") + try: + raise fut.exception() + except: + traceback.print_exc() all_exceptions.append(fut.exception()) else: finished_tasks = finished_tasks + 1 @@ -310,26 +336,27 @@ def process_future(fut): try: asyncio.get_running_loop().set_default_executor(executor) - async_executor = AsyncExecutor() + async_executor = AsyncExecutor(executor) all_exceptions = [] - ui_task = asyncio.create_task(ui(async_executor)) task = asyncio.create_task(f(async_executor)) + ui_task = asyncio.create_task(ui(async_executor)) await asyncio.gather(task, ui_task) + + logging.info("UI and main task finished") + except CancelledError: + logging.warning("Cancelling all executor jobs") executor.shutdown(wait=False, cancel_futures=True) raise - except KeyboardInterrupt: logging.warning("Keyboard interrupt") except CancelledError: - if int_count > 1: - sys.exit(-1) - int_count = int_count + 1 - + logging.warning("Cancelled") + raise if len(all_exceptions): logging.error(f"Encountered the following {len(all_exceptions)} errors:") diff --git a/util/fuzz/interconnect.py b/util/fuzz/interconnect.py index 5112a83..33cc57d 100644 --- a/util/fuzz/interconnect.py +++ b/util/fuzz/interconnect.py @@ -143,7 +143,7 @@ def fuzz_interconnect_sinks( executor = None ): if sinks is None: - return + return [] if not isinstance(sinks, dict): @@ -158,18 +158,21 @@ def fuzz_interconnect_sinks( def process_bits(bitstreams, from_wires, to_wire): base_bitf = bitstreams[0] bitstreams = bitstreams[1:] - fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.get_db(), base_bitf, set(config.tiles), to_wire, + db = fuzzconfig.get_db() + + fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf, set(config.tiles), to_wire, config.tiles[0], set(ignore_tiles), full_mux_style, not (fc_filter(to_wire))) for (from_wire, arc_bit) in zip(from_wires, bitstreams): - fz.add_pip_sample(fuzzconfig.get_db(), from_wire, arc_bit if arc_bit is not None else base_bitf) + fz.add_pip_sample(db, from_wire, arc_bit if arc_bit is not None else base_bitf) logging.debug(f"Solving for {to_wire}") config.solve(fz) conns = tiles.get_connections_for_device(config.device) + futures = [] with fuzzloops.Executor(executor) as executor: for to_wire in sinks: if config.check_deltas(to_wire): @@ -187,11 +190,13 @@ def process_bits(bitstreams, from_wires, to_wire): else: logging.debug(f"Building design for ({config.job} {config.device}) {to_wire} to {from_wire}") arc_bit = config.build_design_future(executor, config.sv, substs, f"{from_wire}/{to_wire}/") - yield arc_bit + futures.append(arc_bit) bitstream_futures.append(arc_bit) - yield fuzzloops.chain(bitstream_futures, process_bits, "Interconnect sink", sinks[to_wire], to_wire) + futures.append(fuzzloops.chain(bitstream_futures, "Interconnect sink", process_bits, sinks[to_wire], to_wire)) + + return futures def fuzz_interconnect( config, @@ -228,7 +233,7 @@ def fuzz_interconnect( :param fc_filter: skip fixed connections if this returns false for a sink wire name """ if not fuzzconfig.should_fuzz_platform(config.device): - return + return [] sinks = collect_sinks(config, nodenames, regex = regex, nodename_predicate = nodename_predicate, @@ -236,7 +241,7 @@ def fuzz_interconnect( bidir=bidir, nodename_filter_union=False) - yield from fuzz_interconnect_sinks(config, sinks, full_mux_style, ignore_tiles, extra_substs, fc_filter, executor=executor) + return fuzz_interconnect_sinks(config, sinks, full_mux_style, ignore_tiles, extra_substs, fc_filter, executor=executor) def fuzz_interconnect_for_tiletype(device, tiletype): prototype = list(tiles.get_tiles_by_tiletype(device, tiletype).keys())[0] @@ -267,7 +272,8 @@ def per_pip(pin_info, pin_pip): is_output = pin_info['pin_node'] == pin_pip.from_wire prefix = "{}_{}_{}_".format(config.job, config.device, to_wire) - fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.get_db(), base_bitf, + db = fuzzconfig.get_db() + fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf, set(config.tiles), to_wire, config.tiles[0], set(), full_mux_style, not (fc_filter(to_wire))) @@ -280,7 +286,7 @@ def per_pip(pin_info, pin_pip): print(f"Building design for ({config.job} {config.device}) {to_wire} to {from_wire}") arc_bit = config.build_design(config.sv, substs, prefix) - fz.add_pip_sample(fuzzconfig.get_db(), from_wire, arc_bit) + fz.add_pip_sample(db, from_wire, arc_bit) config.solve(fz) diff --git a/util/fuzz/nonrouting.py b/util/fuzz/nonrouting.py index 939abfa..f95dddb 100644 --- a/util/fuzz/nonrouting.py +++ b/util/fuzz/nonrouting.py @@ -44,15 +44,15 @@ def fuzz_word_setting(config, name, length, get_sv_substs, desc="", executor = N def integrate_bitstreams(bitstreams): baseline = bitstreams[0] bitstreams = bitstreams[1:] - - fz = libpyprjoxide.Fuzzer.word_fuzzer(fuzzconfig.db, baseline, set(config.tiles), name, desc, length, + db = fuzzconfig.get_db() + fz = libpyprjoxide.Fuzzer.word_fuzzer(db, baseline, set(config.tiles), name, desc, length, baseline) for i in range(length): - fz.add_word_sample(fuzzconfig.db, i, bitstreams[i]) + fz.add_word_sample(db, i, bitstreams[i]) config.solve(fz) - return [*bitstream_futures, fuzzloops.chain([baseline, *bitstream_futures], integrate_bitstreams, name = "Solve word")] + return [*bitstream_futures, fuzzloops.chain([baseline, *bitstream_futures], "Solve word", integrate_bitstreams)] def fuzz_enum_setting(config, empty_bitfile, name, values, get_sv_substs, include_zeros=False, assume_zero_base=False, min_cover={}, desc="", mark_relative_to=None, executor = None): @@ -102,13 +102,14 @@ def integrate_build(subs, prefix, opt_name): future.name = "Build design" def integrate_bitstreams(bitstreams): - fz = libpyprjoxide.Fuzzer.enum_fuzzer(fuzzconfig.db, empty_bitfile, set(config.tiles), name, desc, + db = fuzzconfig.get_db() + fz = libpyprjoxide.Fuzzer.enum_fuzzer(db, empty_bitfile, set(config.tiles), name, desc, include_zeros, assume_zero_base, mark_relative_to=mark_relative_to) for (opt, bitstream) in bitstreams: - fz.add_enum_sample(fuzzconfig.db, opt, bitstream) + fz.add_enum_sample(db, opt, bitstream) config.solve(fz) - return fuzzloops.chain(futures, integrate_bitstreams, "Enum Setting") + return fuzzloops.chain(futures, "Enum Setting", integrate_bitstreams ) def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=None, executor = None): """ @@ -145,13 +146,14 @@ def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=N def integrate_bitstreams(bitstreams): baseline = bitstreams[0] ipcore, iptype = config.tiles[0].split(":") - fz = libpyprjoxide.IPFuzzer.word_fuzzer(fuzzconfig.db, baseline, ipcore, iptype, name, desc, length, inverted_mode) + db = fuzzconfig.get_db() + fz = libpyprjoxide.IPFuzzer.word_fuzzer(db, baseline, ipcore, iptype, name, desc, length, inverted_mode) for (i, bitfile) in enumerate(bitstreams[1:]): bits = [(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)] - fz.add_word_sample(fuzzconfig.db, bits, bitfile) + fz.add_word_sample(db, bits, bitfile) config.solve(fz) - return [*bitstream_futures, fuzzloops.chain([baseline_future, *bitstream_futures], integrate_bitstreams, name = "Solve IP word")] + return [*bitstream_futures, fuzzloops.chain([baseline_future, *bitstream_futures], "Solve IP word", integrate_bitstreams)] def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, desc="", executor = None): """ @@ -178,12 +180,13 @@ def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, des ] def integrate_bitstreams(bitstreams): - fz = libpyprjoxide.IPFuzzer.enum_fuzzer(fuzzconfig.db, empty_bitfile, ipcore, iptype, name, desc) + db = fuzzconfig.get_db() + fz = libpyprjoxide.IPFuzzer.enum_fuzzer(db, empty_bitfile, ipcore, iptype, name, desc) for (opt, bitfile) in zip(values, bitstreams): - fz.add_enum_sample(fuzzconfig.db, opt, bitfile) + fz.add_enum_sample(db, opt, bitfile) config.solve(fz) - return [*bitstream_futures, fuzzloops.chain(bitstream_futures, integrate_bitstreams, name = "Solve IP enum")] + return [*bitstream_futures, fuzzloops.chain(bitstream_futures, "Solve IP enum", integrate_bitstreams)] def fuzz_primitive_definition(cfg, empty, site, primitive, mark_relative_to = None, mode_name = None, get_substs=None): def default_get_substs(mode="NONE", kv=None): From 1926dd03354afdc6b93b2a2c9eefdb6240fbcabf Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Tue, 27 Jan 2026 13:57:32 -0700 Subject: [PATCH 18/29] Docs; examples --- docs/general/cacheing.md | 36 +++++++++++++++++++++++++++ examples/common.mk | 6 ++--- examples/spinex_minimal/Makefile | 8 ++++++ examples/spinex_minimal/tinyclunx.pdc | 4 +-- 4 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 docs/general/cacheing.md diff --git a/docs/general/cacheing.md b/docs/general/cacheing.md new file mode 100644 index 0000000..5b50518 --- /dev/null +++ b/docs/general/cacheing.md @@ -0,0 +1,36 @@ +# Cacheing + +The fuzzers all require a lot of runtime in building bitfiles and pulling data from radiant tools. + +## Bitstream cacheing + +The main cacheing mechanism is the bitstreamcache -- tools/bitstreamcache.py. This stores checksums for input files and +the bitstream in `.bitstreamcache`. + +The bitstreams are stored compressed and libprjoxide can read them compressed. Instead of copying the files around, the +cache fetch generates symbolic links. + +This folder should be cleared very rarely. + +## Stored deltas + +Each solve generates serialized delta files in `.deltas` of the given fuzzer. This is useful to see what changed for each +solver run, but is also used as a marker -- if that file exists, the solver assumes it has been applied already and skips +generating / fetching bitstreams and calling into the fuzzer solvers. + +Delete these folders when making heavy changes to fuzz.rs or other portions of the rust library. + +## Lapie cache database + +Lapie / Lark is a radiant tool used to query the internal chip lattice database. Each run of this program has around +10 seconds of overhead and it only returns around 60 results per second after that. + +To speed these accesses up, a sqlite database is generated at `.cache//-nodes.sqlite` to cache +the results of these queries. Specifically, it caches node data and site data per device as well as the jumpwires list. + +Queries, once cached, return nearly instantaneously in comparison, but these files do end up being around 100M in size. + +## Cachier + +Other methods are annotated with cachier -- a decoration that caches calls into a function by its arguments. These +entries are stored in `.cache//cache.db`. \ No newline at end of file diff --git a/examples/common.mk b/examples/common.mk index 110726f..f31f45d 100644 --- a/examples/common.mk +++ b/examples/common.mk @@ -6,15 +6,15 @@ YOSYS?=yosys NEXTPNR?=nextpnr-nexus PRJOXIDE?=prjoxide ECPPROG?=ecpprog -TOP?=topy +TOP?=top all: $(PROJ).bit $(PROJ).json: $(PROJ).v $(EXTRA_VERILOG) $(MEM_INIT_FILES) - $(YOSYS) -ql $(PROJ)_syn.log -p "synth_nexus $(SYNTH_ARGS) -top $(TOP) -json $(PROJ).json" $(PROJ).v $(EXTRA_VERILOG) + $(YOSYS) -ql $(PROJ)_syn.log -p "synth_nexus $(SYNTH_ARGS) -top $(TOP) -json $(PROJ).json -v -debug" $(PROJ).v $(EXTRA_VERILOG) $(PROJ).fasm: $(PROJ).json $(PDC) - $(NEXTPNR) --device $(DEVICE) --pdc $(PDC) --json $(PROJ).json --fasm $(PROJ).fasm + $(NEXTPNR) --device $(DEVICE) --pdc $(PDC) --json $(PROJ).json --fasm $(PROJ).fasm --debug -v $(PROJ).bit: $(PROJ).fasm $(PRJOXIDE) pack $(PROJ).fasm $(PROJ).bit diff --git a/examples/spinex_minimal/Makefile b/examples/spinex_minimal/Makefile index e69de29..2344768 100644 --- a/examples/spinex_minimal/Makefile +++ b/examples/spinex_minimal/Makefile @@ -0,0 +1,8 @@ +PROJ=SpinexMinimal +#DEVICE=LIFCL-33-8CTG104C +DEVICE=LIFCL-33U-8CTG104C +PDC=tinyclunx.pdc +TOP=SpinexMinimal +EXTRA_VERILOG=SpinexMinimal_references.v + +include ../common.mk diff --git a/examples/spinex_minimal/tinyclunx.pdc b/examples/spinex_minimal/tinyclunx.pdc index 504c27e..9e2ba59 100644 --- a/examples/spinex_minimal/tinyclunx.pdc +++ b/examples/spinex_minimal/tinyclunx.pdc @@ -1,2 +1,2 @@ -ldc_set_location -site {A3} [get_ports led] -ldc_set_location -site {B1} [get_ports gsrn] +ldc_set_location -site {A1} [get_ports clk] +ldc_set_location -site {B1} [get_ports reset] From 32449e36744d35b72cc8f89d3b45623d478f53e5 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Fri, 13 Feb 2026 11:02:02 -0700 Subject: [PATCH 19/29] Add overlays, reformat, and 33 handling to libprjoxide --- fuzzers/shared/empty.v | 8 + fuzzers/shared/route.v | 10 + libprjoxide/prjoxide/src/bba/tileloc.rs | 3 +- libprjoxide/prjoxide/src/database.rs | 361 ++++++++++++++++++++---- libprjoxide/prjoxide/src/fuzz.rs | 14 +- libprjoxide/prjoxide/src/ipfuzz.rs | 4 +- libprjoxide/prjoxide/src/wires.rs | 42 ++- libprjoxide/pyprjoxide/src/lib.rs | 177 ++++++++---- requirements.txt | 0 tools/reformat_database.py | 0 util/fuzz/DesignFileBuilder.py | 0 11 files changed, 498 insertions(+), 121 deletions(-) create mode 100644 fuzzers/shared/empty.v create mode 100644 fuzzers/shared/route.v create mode 100644 requirements.txt create mode 100644 tools/reformat_database.py create mode 100644 util/fuzz/DesignFileBuilder.py diff --git a/fuzzers/shared/empty.v b/fuzzers/shared/empty.v new file mode 100644 index 0000000..7942d25 --- /dev/null +++ b/fuzzers/shared/empty.v @@ -0,0 +1,8 @@ +(* \db:architecture ="${arch}", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule diff --git a/fuzzers/shared/route.v b/fuzzers/shared/route.v new file mode 100644 index 0000000..3c48e56 --- /dev/null +++ b/fuzzers/shared/route.v @@ -0,0 +1,10 @@ +(* \db:architecture ="${arch}", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + (* \xref:LOG ="q_c@0@9"${arcs_attr} *) + wire q; + + (* \dm:cellmodel_primitives ="REG0=reg", \dm:primitive ="SLICE", \dm:programming ="MODE:LOGIC Q0:Q0 ", \dm:site ="R2C2A" *) + SLICE SLICE_I ( .A0(q), .Q0(q) ); +endmodule diff --git a/libprjoxide/prjoxide/src/bba/tileloc.rs b/libprjoxide/prjoxide/src/bba/tileloc.rs index a9c10d9..7fe8c80 100644 --- a/libprjoxide/prjoxide/src/bba/tileloc.rs +++ b/libprjoxide/prjoxide/src/bba/tileloc.rs @@ -12,6 +12,7 @@ use itertools::Itertools; use std::collections::{BTreeSet, HashMap}; use std::convert::TryInto; use std::iter::FromIterator; +use log::warn; use regex::Regex; use crate::bba::tiletype::Neighbour::RelXY; @@ -199,7 +200,7 @@ impl LocationGrid { match (n.clone(), nt) { (RelXY {rel_x: _, rel_y: _}, None) => {} (n, None) => { - println!("Could not resolve the neighbor {n:?} at {x} {y} for {tiletypes:?}") + warn!("Could not resolve the neighbor {n:?} at {x} {y} for {tiletypes:?}") } _ => {} } diff --git a/libprjoxide/prjoxide/src/database.rs b/libprjoxide/prjoxide/src/database.rs index 7ae3a25..f64835a 100644 --- a/libprjoxide/prjoxide/src/database.rs +++ b/libprjoxide/prjoxide/src/database.rs @@ -1,12 +1,14 @@ use itertools::Itertools; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::{env, fmt}; + use std::fs::File; use std::io::prelude::*; use std::path::Path; use log::{debug, info, warn}; + // Deserialization of 'devices.json' macro_rules! emit_bit_change_error { @@ -14,10 +16,12 @@ macro_rules! emit_bit_change_error { // depending on the edition of the caller. ($($arg:tt)*) => { /* compiler built-in */ + + warn!($($arg)*); if env::var("PRJOXIDE_ALLOW_BIT_CHANGE").is_ok() { - warn!($($arg)*); + } else { - panic!($($arg)*); + return Err(format!($($arg)*)); } }; } @@ -60,6 +64,12 @@ pub struct DeviceTilegrid { pub tiles: BTreeMap, } +#[derive(Deserialize, Clone)] +pub struct OverlayTiletype { + // All of the overlays that combine to make this tiletype + pub overlays: BTreeSet +} + #[derive(Serialize, Deserialize)] pub struct TileData { pub tiletype: String, @@ -257,7 +267,7 @@ impl fmt::Debug for ConfigBit { } } -#[derive(Deserialize, Serialize, Clone)] +#[derive(Deserialize, Serialize, Clone, Ord, PartialOrd, Eq, PartialEq)] pub struct ConfigPipData { pub from_wire: String, pub bits: BTreeSet, @@ -283,7 +293,7 @@ fn is_false(x: &bool) -> bool { !(*x) } -#[derive(Deserialize, Serialize, Clone)] +#[derive(Deserialize, Serialize, Clone, Ord, PartialOrd, Eq, PartialEq)] pub struct FixedConnectionData { pub from_wire: String, #[serde(default)] @@ -339,6 +349,14 @@ pub struct TileBitsData { } impl TileBitsData { + pub fn sort(&mut self) { + debug!("Sorting {}", self.tiletype); + + self.db.conns.iter_mut().for_each(|(_,conn)| conn.sort()); + self.db.pips.iter_mut().for_each(|(_,pip)| pip.sort()); + self.db.words.iter_mut().for_each(|(_,word)| word.bits.sort()); + } + pub fn new(tiletype: &str, db: TileBitsDatabase) -> TileBitsData { TileBitsData { tiletype: tiletype.to_string(), @@ -347,22 +365,33 @@ impl TileBitsData { } } - pub fn merge(&mut self, other_db: TileBitsDatabase) { - for (to, pip_data) in other_db.pips.iter() { - for from in pip_data.iter() { - self.add_pip(&from.from_wire, to, from.bits.clone()); - } - } + pub fn merge_configs(&mut self, other_db: &TileBitsDatabase) -> Result<(), String> { for (word, word_config) in other_db.words.iter() { - self.add_word(word, &*word_config.desc, word_config.bits.clone()); + self.add_word(word, &*word_config.desc, word_config.bits.clone())?; }; for (enm, enum_config) in other_db.enums.iter() { for (option, option_bits) in enum_config.options.iter() { - self.add_enum_option(enm, option, &*enum_config.desc, option_bits.clone()); + self.add_enum_option(enm, option, &*enum_config.desc, option_bits.clone())?; } } - for (to, from_wires) in other_db.conns { + for external_tile_configs in other_db.tile_configures_external_tiles.iter() { + self.set_bel_offset(Some(external_tile_configs.clone()))?; + } + + Ok(()) + } + + pub fn merge(&mut self, other_db: &TileBitsDatabase) -> Result<(), String> { + self.merge_configs(other_db)?; + + for (to, pip_data) in other_db.pips.iter() { + for from in pip_data.iter() { + self.add_pip(&from.from_wire, to, from.bits.clone())?; + } + } + + for (to, from_wires) in other_db.conns.iter() { for from in from_wires { self.add_conn(&*from.from_wire, &*to); if from.bidir { @@ -371,14 +400,21 @@ impl TileBitsData { } } - for external_tile_configs in other_db.tile_configures_external_tiles { - self.set_bel_offset(Some(external_tile_configs)); + Ok(()) + } + + pub fn find_pip_data(&self,from: &str, to: &str) -> Option<&ConfigPipData> { + for pip_config in self.db.pips.get(to)? { + if pip_config.from_wire == from { + return Some(pip_config); + } } + None } - pub fn add_pip(&mut self, from: &str, to: &str, bits: BTreeSet) { + pub fn add_pip(&mut self, from: &str, to: &str, bits: BTreeSet) -> Result<(), String> { if !self.db.pips.contains_key(to) { - info!("Inserting new pip destination {to}"); + debug!("Inserting new pip destination {to}"); self.db.pips.insert(to.to_string(), Vec::new()); } let ac = self.db.pips.get_mut(to).unwrap(); @@ -392,21 +428,23 @@ impl TileBitsData { ad.bits = bits; self.dirty = true; - return; + return Ok(()); } debug!("Pip {from} -> {to} already exists for {}", self.tiletype); - return; + return Ok(()); } } self.dirty = true; - info!("Inserting new pip {from} -> {to} for {}", self.tiletype); + debug!("Inserting new pip {from} -> {to} for {}", self.tiletype); ac.push(ConfigPipData { from_wire: from.to_string(), bits: bits.clone(), }); + + Ok(()) } - pub fn add_word(&mut self, name: &str, desc: &str, bits: Vec>) { + pub fn add_word(&mut self, name: &str, desc: &str, bits: Vec>) -> Result<(), String> { self.dirty = true; match self.db.words.get_mut(name) { None => { @@ -441,9 +479,11 @@ impl TileBitsData { } } } + + Ok(()) } - pub fn set_bel_offset(&mut self, bel_relative_location : Option<(String, i32, i32)>) { + pub fn set_bel_offset(&mut self, bel_relative_location : Option<(String, i32, i32)>) -> Result<(), String> { if !self.db.tile_configures_external_tiles.is_empty() && self.db.tile_configures_external_tiles.iter().next() != bel_relative_location.as_ref() { emit_bit_change_error!( @@ -457,6 +497,8 @@ impl TileBitsData { |loc| { self.db.tile_configures_external_tiles.insert(loc.clone()); } ); self.dirty = true; + + Ok(()) } pub fn add_enum_option( &mut self, @@ -464,7 +506,7 @@ impl TileBitsData { option: &str, desc: &str, bits: BTreeSet - ) { + ) -> Result<(), String> { if !self.db.enums.contains_key(name) { self.db.enums.insert( name.to_string(), @@ -496,6 +538,8 @@ impl TileBitsData { self.dirty = true; } } + + Ok(()) } pub fn add_conn(&mut self, from: &str, to: &str) { if !self.db.conns.contains_key(to) { @@ -522,18 +566,30 @@ impl TileBitsData { } } +type FamilyName = String; +type DeviceName = String; +type DeviceSpecifier = (FamilyName, DeviceName); +type TileName = String; + +type TileTypeName = String; + pub struct Database { root: Option, builtin: Option>, devices: DevicesDatabase, - tilegrids: HashMap<(String, String), DeviceTilegrid>, - baseaddrs: HashMap<(String, String), DeviceBaseAddrs>, - globals: HashMap<(String, String), DeviceGlobalsData>, - iodbs: HashMap<(String, String), DeviceIOData>, - interconn_tmg: HashMap<(String, String), InterconnectTimingData>, - cell_tmg: HashMap<(String, String), CellTimingData>, - tilebits: HashMap<(String, String), TileBitsData>, - ipbits: HashMap<(String, String), TileBitsData>, + tilegrids: HashMap, + baseaddrs: HashMap, + globals: HashMap, + iodbs: HashMap, + interconn_tmg: HashMap, + cell_tmg: HashMap, + + tilebits: HashMap<(FamilyName, TileTypeName), TileBitsData>, + ipbits: HashMap<(FamilyName, TileTypeName), TileBitsData>, + + overlay_based_devices: HashSet, + _overlays: Option>>, + overlay_tiletypes: HashMap>, } impl Database { @@ -544,10 +600,24 @@ impl Database { .unwrap() .read_to_string(&mut devices_json_buf) .unwrap(); + + let devices : DevicesDatabase = serde_json::from_str(&devices_json_buf).unwrap(); + let mut overlay_based_devices = HashSet::new(); + + if !env::var("PRJOXIDE_DISABLE_OVERLAYS").is_ok() { + for (family, family_data) in devices.families.iter() { + for (device, _) in family_data.devices.iter() { + if Path::new(format!("{root}/{family}/{device}/overlays.json").as_str()).exists() { + overlay_based_devices.insert((family.clone(), device.clone())); + } + } + } + } + Database { root: Some(root.to_string()), builtin: None, - devices: serde_json::from_str(&devices_json_buf).unwrap(), + devices: devices, tilegrids: HashMap::new(), baseaddrs: HashMap::new(), globals: HashMap::new(), @@ -556,14 +626,28 @@ impl Database { cell_tmg: HashMap::new(), tilebits: HashMap::new(), ipbits: HashMap::new(), + overlay_based_devices, + _overlays: None, + overlay_tiletypes: HashMap::new(), } } pub fn new_builtin(data: include_dir::Dir<'static>) -> Database { let devices_json_buf = data.get_file("devices.json").unwrap().contents_utf8().unwrap(); + + let devices : DevicesDatabase = serde_json::from_str(&devices_json_buf).unwrap(); + let mut overlay_based_devices = HashSet::new(); + for (family, family_data) in devices.families.iter() { + for (device, _) in family_data.devices.iter() { + if data.get_file(format!("{family}/{device}/overlays.json").as_str()).is_some() { + overlay_based_devices.insert((family.clone(), device.clone())); + } + } + } + Database { root: None, builtin: Some(data), - devices: serde_json::from_str(&devices_json_buf).unwrap(), + devices: devices, tilegrids: HashMap::new(), baseaddrs: HashMap::new(), globals: HashMap::new(), @@ -572,6 +656,9 @@ impl Database { cell_tmg: HashMap::new(), tilebits: HashMap::new(), ipbits: HashMap::new(), + overlay_based_devices, + _overlays: None, + overlay_tiletypes: HashMap::new(), } } // Check if a file exists @@ -621,12 +708,89 @@ impl Database { } None } + + + pub fn device_overlay_tiletypes(&mut self, family: &str, device: &str) -> Result<&BTreeMap, String> { + let key = (family.to_string(), device.to_string()); + if !self.overlay_tiletypes.contains_key(&key) { + let json_buf = self.read_file(&format!("{}/{}/overlays.json", family, device)); + + let root: BTreeMap>> = + serde_json::from_str(&json_buf) + .map_err(|e| format!("Failed to parse overlays.json: {}", e))?; + + let tiletypes = root.get("tiletypes") + .ok_or("missing tiletypes")?; + + let tiletype_lookup = tiletypes.iter() + .flat_map(|(k, set)| set.iter().map(move |v| (v.clone(), k.clone()))) + .try_fold(BTreeMap::new(), |mut acc, (v, k)| { + match acc.insert(v.clone(), k.clone()) { + None => Ok(acc), + Some(prev) => Err(format!( + "Collision: '{}' belongs to both '{}' and '{}'", + v, prev, k + )), + } + })?; + + self.overlay_tiletypes.insert(key.clone(), tiletype_lookup); + } + self.overlay_tiletypes.get(&key).ok_or(format!("Could not find overlay tile types for {family} {device}")) + } + pub fn overlays(&mut self) -> &HashMap> { + if self._overlays.is_none() { + let mut overlays = HashMap::new(); + + for (family, family_data) in self.devices.families.iter() { + for (device, _) in family_data.devices.iter() { + + if self.file_exists(&format!("{}/{}/overlays.json", family, device)) { + let json_buf = self.read_file(&format!("{}/{}/overlays.json", family, device)); + + let root: BTreeMap>> = + serde_json::from_str(&json_buf) + .map_err(|e| format!("Failed to parse overlays.json: {}", e)).unwrap(); + + let overlay_tiletypes = root.get("overlays") + .ok_or(format!("missing overlays in {device}")).unwrap(); + + let device_overlays = overlay_tiletypes.iter().map(|(name, contents)| { + (name.clone(), OverlayTiletype { + overlays: contents.clone() + }) + }).collect(); + + overlays.insert((family.clone(), device.clone()), device_overlays); + } + } + } + + self._overlays = Some(overlays); + } + + &self._overlays.as_ref().unwrap() + } + // Tilegrid for a device by family and name pub fn device_tilegrid(&mut self, family: &str, device: &str) -> &DeviceTilegrid { let key = (family.to_string(), device.to_string()); if !self.tilegrids.contains_key(&key) { let tg_json_buf = self.read_file(&format!("{}/{}/tilegrid.json", family, device)); - let tg = serde_json::from_str(&tg_json_buf).unwrap(); + let mut tg : DeviceTilegrid = serde_json::from_str(&tg_json_buf).unwrap(); + + if self.overlay_based_devices.contains(&key) { + + let device_overlay = self.device_overlay_tiletypes(family, device).unwrap(); + for (tile, tile_data) in tg.tiles.iter_mut() { + if let Some(tile_type_name) = device_overlay.get(tile) { + tile_data.tiletype = tile_type_name.clone(); + } else { + warn!("Could not find {tile} in overlays listing {device}"); + } + } + } + self.tilegrids.insert(key.clone(), tg); } self.tilegrids.get(&key).unwrap() @@ -681,28 +845,95 @@ impl Database { } self.cell_tmg.get(&key).unwrap() } + pub fn tile_bitdb_from_overlays(&mut self, family: &str, tiletype: &str, overlay: &OverlayTiletype) -> Result { + let tile_bits_db = TileBitsDatabase { + pips: BTreeMap::new(), + words: BTreeMap::new(), + enums: BTreeMap::new(), + conns: BTreeMap::new(), + always_on: BTreeSet::new(), + tile_configures_external_tiles : BTreeSet::new(), + }; + let mut tile_bits = TileBitsData::new(tiletype, tile_bits_db); + info!("Merge {tiletype} {:?}", overlay.overlays); + + let overlay_members : Vec = overlay.overlays.clone().into_iter() + .sorted_by(|x, y| { + (y.starts_with("overlay"), y).cmp(&(x.starts_with("overlay"), x)) + }).collect(); + + for layer in overlay_members { + info!("Merging {layer} into {tiletype}"); + + let overlay_tiletypes = self.overlay_tiletypes.clone(); + let overlay_bits = self.tile_bitdb(family, layer.as_str()); + + if !layer.starts_with("overlay") { + for (to, from_wires) in &overlay_bits.db.pips { + for from_data in from_wires { + let from_wire = &from_data.from_wire; + + if tile_bits.find_pip_data(from_wire, to).is_none() { + let indicated_tiles : Vec<_> = overlay_tiletypes.iter().flat_map(|(_, tilemaps)| { + tilemaps.iter().filter(move |(_, tiletypename)| { + tiletypename == &tiletype + }).map(|x| x.0.clone()) + }).collect(); + warn!("Ignoring PIP data {from_wire} -> {to} from {layer} for {tiletype}. Used in {indicated_tiles:?}") + } + } + } + tile_bits.merge_configs(&overlay_bits.db)?; + } else { + tile_bits.merge(&overlay_bits.db)?; + } + } + Ok(tile_bits) + } // Bit database for a tile by family and tile type pub fn tile_bitdb(&mut self, family: &str, tiletype: &str) -> &mut TileBitsData { let key = (family.to_string(), tiletype.to_string()); if !self.tilebits.contains_key(&key) { - // read the whole file - let filename = format!("{}/tiletypes/{}.ron", family, tiletype); - let tb = if self.file_exists(&filename) { - let tt_ron_buf = self.read_file(&filename); - ron::de::from_str(&tt_ron_buf).unwrap() - } else { - TileBitsDatabase { - pips: BTreeMap::new(), - words: BTreeMap::new(), - enums: BTreeMap::new(), - conns: BTreeMap::new(), - always_on: BTreeSet::new(), - tile_configures_external_tiles : BTreeSet::new(), + let overlay = self.overlays().iter() + .find(|((overlay_family, _), overlay)| { + family == overlay_family && overlay.contains_key(tiletype) + }) + .map(|(_, overlay)| overlay.get(tiletype).unwrap().clone()).map(|overlay| overlay.clone()); + + let tile_bits = match overlay { + Some(overlay) => { + self.tile_bitdb_from_overlays(family, tiletype, &overlay).unwrap() + } + None => { + let is_overlay = tiletype.starts_with("overlay/"); + let filename = if is_overlay { + format!("{}/overlays/{}.ron", family, tiletype.replace("overlay/", "")) + } else { + format!("{}/tiletypes/{}.ron", family, tiletype) + }; + let tb = if self.file_exists(&filename) { + // read the whole file + let tt_ron_buf = self.read_file(&filename); + ron::de::from_str(&tt_ron_buf).unwrap() + } else { + debug!("No tile database found for {tiletype} at {filename} -- using empty db."); + + TileBitsDatabase { + pips: BTreeMap::new(), + words: BTreeMap::new(), + enums: BTreeMap::new(), + conns: BTreeMap::new(), + always_on: BTreeSet::new(), + tile_configures_external_tiles : BTreeSet::new(), + } + }; + TileBitsData::new(tiletype, tb) } }; - self.tilebits - .insert(key.clone(), TileBitsData::new(tiletype, tb)); + + self.tilebits.insert(key.clone(), tile_bits); } + self.tilebits.get_mut(&key).unwrap() } // Bit database for a tile by family and tile type @@ -729,6 +960,17 @@ impl Database { } self.ipbits.get_mut(&key).unwrap() } + + pub fn reformat(&mut self) { + debug!("Reformatting {:?}", self.tilebits.len()); + + for (_, tilebits) in self.tilebits.iter_mut() { + tilebits.dirty = true; + tilebits.sort(); + } + + self.flush(); + } // Flush tile bit database changes to disk pub fn flush(&mut self) { for kv in self.tilebits.iter_mut() { @@ -744,10 +986,23 @@ impl Database { enumerate_arrays: false, separate_tuple_members: false, }; + + tilebits.sort(); + let is_overlay = tiletype.starts_with("overlay"); + + let (dir_name, file_name) = if is_overlay { + ("overlays", tiletype.replace("overlay", "")) + } else { + ("tiletypes", tiletype.clone()) + }; + + debug!("Writing {}/{}/{}/{}.ron", + self.root.as_ref().unwrap(), family, dir_name, file_name); + let tt_ron_buf = ron::ser::to_string_pretty(&tilebits.db, pretty).unwrap(); File::create(format!( - "{}/{}/tiletypes/{}.ron", - self.root.as_ref().unwrap(), family, tiletype + "{}/{}/{}/{}.ron", + self.root.as_ref().unwrap(), family, dir_name, file_name )) .unwrap() .write_all(tt_ron_buf.as_bytes()) @@ -764,6 +1019,8 @@ impl Database { assert!(ipbits.db.pips.is_empty()); assert!(ipbits.db.conns.is_empty()); + ipbits.sort(); + let pretty = PrettyConfig { depth_limit: 5, new_line: "\n".to_string(), diff --git a/libprjoxide/prjoxide/src/fuzz.rs b/libprjoxide/prjoxide/src/fuzz.rs index 6e18f44..2698f1d 100644 --- a/libprjoxide/prjoxide/src/fuzz.rs +++ b/libprjoxide/prjoxide/src/fuzz.rs @@ -311,7 +311,7 @@ impl Fuzzer { &wires::normalize_wire(&self.base, tile_data, from_wire), &wires::normalize_wire(&self.base, tile_data, to_wire), bits, - ); + ).unwrap(); findings += 1; } } @@ -369,7 +369,7 @@ impl Fuzzer { // Add the word to the tile data let tile_data = self.base.tile_by_name(tile).unwrap(); let tile_db = db.tile_bitdb(&self.base.family, &tile_data.tiletype); - tile_db.add_word(&name, &self.desc, cbits); + tile_db.add_word(&name, &self.desc, cbits).unwrap(); } } FuzzMode::Enum { @@ -464,7 +464,7 @@ impl Fuzzer { let tile_db = db.tile_bitdb(&self.base.family, &tile_data.tiletype); - tile_db.add_enum_option(&name, &option, &self.desc, b); + tile_db.add_enum_option(&name, &option, &self.desc, b).unwrap(); if let Some(relative_tile) = mark_relative_to.clone() { let ref_tile = self.base.tile_by_name(&relative_tile).unwrap(); @@ -474,7 +474,7 @@ impl Fuzzer { ref_tile.y as i32 - tile_data.y as i32) }; - tile_db.set_bel_offset(Some(offset.clone())); + tile_db.set_bel_offset(Some(offset.clone())).unwrap(); }; } @@ -504,7 +504,7 @@ pub fn copy_db( for (to_wire, pips) in origin_data.pips.iter() { for p in pips.iter() { if pattern == "" || to_wire.contains(pattern) || p.from_wire.contains(pattern) { - dest_data.add_pip(&p.from_wire, to_wire, p.bits.clone()); + dest_data.add_pip(&p.from_wire, to_wire, p.bits.clone()).unwrap(); } } } @@ -513,7 +513,7 @@ pub fn copy_db( for (name, opts) in origin_data.enums.iter() { if pattern == "" || name.contains(pattern) { for (opt, bits) in opts.options.iter() { - dest_data.add_enum_option(name, opt, &opts.desc, bits.clone()); + dest_data.add_enum_option(name, opt, &opts.desc, bits.clone()).unwrap(); } } } @@ -521,7 +521,7 @@ pub fn copy_db( if mode.contains('W') { for (name, data) in origin_data.words.iter() { if pattern == "" || name.contains(pattern) { - dest_data.add_word(name, &data.desc, data.bits.clone()); + dest_data.add_word(name, &data.desc, data.bits.clone()).unwrap(); } } } diff --git a/libprjoxide/prjoxide/src/ipfuzz.rs b/libprjoxide/prjoxide/src/ipfuzz.rs index e1de77f..c3fbe84 100644 --- a/libprjoxide/prjoxide/src/ipfuzz.rs +++ b/libprjoxide/prjoxide/src/ipfuzz.rs @@ -143,7 +143,7 @@ impl IPFuzzer { .collect(); // Add the enum to the tile data let iptype_db = db.ip_bitdb(&self.base.family, &self.iptype); - iptype_db.add_enum_option(name, &option, &self.desc, b); + iptype_db.add_enum_option(name, &option, &self.desc, b).unwrap(); } } } @@ -183,7 +183,7 @@ impl IPFuzzer { used_bits.append(&mut is.clone()); } let iptype_db = db.ip_bitdb(&self.base.family, &self.iptype); - iptype_db.add_word(&name, &self.desc, cbits); + iptype_db.add_word(&name, &self.desc, cbits).unwrap(); } } db.flush(); diff --git a/libprjoxide/prjoxide/src/wires.rs b/libprjoxide/prjoxide/src/wires.rs index 79ccab9..7aa60fa 100644 --- a/libprjoxide/prjoxide/src/wires.rs +++ b/libprjoxide/prjoxide/src/wires.rs @@ -269,22 +269,46 @@ pub fn normalize_wire(chip: &Chip, tile: &Tile, wire: &str) -> String { spw[2].parse::().unwrap(), &spw[3], ); - if wn.ends_with("VCCHPRX") || wn.ends_with("VCCHPBX") || wn.ends_with("VCC") { + if wn.ends_with("VCCHPRX") || wn.ends_with("VCCHPBX") || wn.ends_with("VCC") + // LIFCL-33 has VCCSPINEs which connect to VCCHPRX + || wn.ends_with("VCCVSPINE") { return "G:VCC".to_string(); } let tx = tile.x as i32; let ty = tile.y as i32; + if tile.name.contains("TAP") && (wn.starts_with("HPRX") || wn.starts_with("HPBX")) && !wn.starts_with("HFIE") { + let branch_dir = match chip.device.as_str() { + "LIFCL-40" | "LIFCL-17" | "LFD2NX-40" => + if wx < tx { + Some("L") + } else if wx > tx { + Some("R") + } else { + None + } - if tile.name.contains("TAP") && wn.starts_with("H") && !wn.starts_with("HFIE") { - if wx < tx { - return format!("BRANCH_L:{}", wn); - } else if wx > tx { - return format!("BRANCH_R:{}", wn); - } else { - panic!("unable to determine TAP side of {} in {}", wire, tile.name); - } + // On every device except the 33's, the column the tap is on is the first column of the R side. + // Probably the real fix is to pass the globals database in and have it sort it with that data. + "LIFCL-33U" | "LIFCL-33" => { + let first_r_col = match tx { + // Column tap is on -> First column of the R side of the tap + 14 => 14, + 26 => 38, + _ => panic!("Invalid tap column given: {} {}", wx, tx) + }; + if wx >= first_r_col { + Some("R") + } else { + Some("L") + } + } + _ => None, + }.expect(format!("unable to determine TAP side of {} in {}", wire, tile.name).as_str()); + + return format!("BRANCH_{}:{}", branch_dir, wn); } + if GLB_HBRANCH_RE.is_match(wn) { return format!("BRANCH:{}", wn); } else if GLB_SPINE_RE.is_match(wn) { diff --git a/libprjoxide/pyprjoxide/src/lib.rs b/libprjoxide/pyprjoxide/src/lib.rs index e50d189..925aa9c 100644 --- a/libprjoxide/pyprjoxide/src/lib.rs +++ b/libprjoxide/pyprjoxide/src/lib.rs @@ -1,13 +1,7 @@ -use pyo3::prelude::*; -use pyo3::types::{PyList, PySet}; -use pyo3::wrap_pyfunction; - -use std::fs::File; -use std::io::*; -use pyo3_log::{Caching, Logger}; use prjoxide::bitstream; use prjoxide::chip; use prjoxide::database; +use prjoxide::database::ConfigBit; use prjoxide::database_html; use prjoxide::docs; use prjoxide::fuzz; @@ -16,27 +10,83 @@ use prjoxide::nodecheck; use prjoxide::pip_classes; use prjoxide::sites; use prjoxide::wires; - +use pyo3::exceptions::PyException; +use pyo3::prelude::*; +use pyo3::types::{PyList, PySet}; +use pyo3::wrap_pyfunction; +use std::collections::BTreeSet; +use std::fs::File; +use std::io::*; #[pyclass] struct Database { - db: database::Database, + db: database::Database } #[pymethods] impl Database { #[new] - pub fn __new__(root: &str) -> Self { - Database { - db: database::Database::new(root), - } + pub fn __new__(root: &str, py: Python) -> Self { + py.allow_threads(|| { + Database { + db: database::Database::new(root) + } + }) } pub fn add_conn(&mut self, family: &str, tiletype: &str, from: &str, to: &str) { self.db.tile_bitdb(family, tiletype).add_conn(from, to); } + pub fn add_conns(&mut self, family: &str, tiletype: &str, conns: Vec<(String, String)>, py: Python) { + py.allow_threads(|| { + let db = self.db.tile_bitdb(family, tiletype); + conns.iter().for_each(|(frm, to)| { + db.add_conn(frm, to); + }); + }); + } - pub fn flush(&mut self) { - self.db.flush(); + pub fn load_tiletype(&mut self, family: &str, tiletype: &str) { + self.db.tile_bitdb(family, tiletype); + } + pub fn flush(&mut self, py: Python) { + py.allow_threads(|| { + self.db.flush(); + }); + } + + pub fn add_pip(&mut self, base: &Chip, tile: &str, from_wire: &str, to_wire: &str, bits : BTreeSet<(usize, usize, bool)>, py: Python) -> PyResult<()> { + py.allow_threads(|| { + let tile_spec : Vec<&str> = tile.split(",").collect(); + let tile_name = tile_spec[0]; + let tile_data = base.c.tile_by_name(tile_name).unwrap(); + let tile_type_or_overlay = if tile_spec.len() == 1 { + &tile_data.tiletype + } else { + tile_spec[1] + }; + let norm_from_wire = wires::normalize_wire(&base.c, tile_data, from_wire); + let norm_to_wire = wires::normalize_wire(&base.c, tile_data, to_wire); + + let tile_db = self.db.tile_bitdb(base.c.family.as_str(), tile_type_or_overlay); + + tile_db.add_pip( + &norm_from_wire, + &norm_to_wire, + bits.iter().map(|x| ConfigBit { + frame: x.0, + bit: x.1, + invert: !x.2 + }).collect(), + ).map_err(|e| { + PyException::new_err(e) + })?; + + Ok(()) + }) + } + + pub fn reformat(&mut self) { + self.db.reformat(); } } @@ -87,27 +137,33 @@ impl Fuzzer { ignore_tiles: &PySet, full_mux: bool, skip_fixed: bool, + py: Python ) -> Fuzzer { - let base_chip = bitstream::BitstreamParser::parse_file(&mut db.db, base_bitfile).unwrap(); - - Fuzzer { - fz: fuzz::Fuzzer::init_pip_fuzzer( - &base_chip, - &fuzz_tiles - .iter() - .map(|x| x.extract::().unwrap()) - .collect(), - to_wire, - fixed_conn_tile, - &ignore_tiles - .iter() - .map(|x| x.extract::().unwrap()) - .collect(), - full_mux, - skip_fixed, - ), - name: to_wire.to_string() - } + let rust_tiles = &fuzz_tiles + .iter() + .map(|x| x.extract::().unwrap()) + .collect(); + let rust_ignore_tiles = &ignore_tiles + .iter() + .map(|x| x.extract::().unwrap()) + .collect(); + + py.allow_threads(|| { + let base_chip = bitstream::BitstreamParser::parse_file(&mut db.db, base_bitfile).unwrap(); + + Fuzzer { + fz: fuzz::Fuzzer::init_pip_fuzzer( + &base_chip, + rust_tiles, + to_wire, + fixed_conn_tile, + rust_ignore_tiles, + full_mux, + skip_fixed, + ), + name: to_wire.to_string() + } + }) } #[staticmethod] @@ -143,11 +199,18 @@ impl Fuzzer { fn add_word_sample(&mut self, db: &mut Database, index: usize, base_bitfile: &str) { self.fz.add_word_sample(&mut db.db, index, base_bitfile); } - fn add_pip_sample(&mut self, db: &mut Database, from_wire: &str, base_bitfile: &str) { self.fz.add_pip_sample(&mut db.db, from_wire, base_bitfile); } + fn add_pip_samples(&mut self, db: &mut Database, samples: Vec<(String, String)>, py: Python) { + py.allow_threads(|| { + samples.iter().for_each(|(from_wire, base_bitfile)| { + self.fz.add_pip_sample(&mut db.db, from_wire, base_bitfile); + }); + }); + } + fn add_pip_sample_delta(&mut self, from_wire: &str, delta: chip::ChipDelta) { self.fz.add_pip_sample_delta(from_wire, delta); } @@ -160,8 +223,10 @@ impl Fuzzer { self.fz.add_enum_sample(&mut db.db, option, base_bitfile); } - fn solve(&mut self, db: &mut Database) { - self.fz.solve(&mut db.db); + fn solve(&mut self, db: &mut Database, py: Python) { + py.allow_threads(|| { + self.fz.solve(&mut db.db); + }); } fn serialize_deltas(&mut self, filename: &str) { @@ -294,16 +359,20 @@ struct Chip { #[pymethods] impl Chip { #[new] - pub fn __new__(db: &mut Database, name: &str) -> Self { - Chip { - c: chip::Chip::from_name(&mut db.db, name), - } + pub fn __new__(db: &mut Database, name: &str, py: Python) -> Self { + py.allow_threads(|| { + Chip { + c: chip::Chip::from_name(&mut db.db, name), + } + }) } #[staticmethod] - pub fn from_bitstream(db: &mut Database, filename: &str) -> Chip { - let chip = bitstream::BitstreamParser::parse_file(&mut db.db, filename).unwrap(); - Chip { c: chip } + pub fn from_bitstream(db: &mut Database, filename: &str, py: Python) -> Chip { + py.allow_threads(|| { + let chip = bitstream::BitstreamParser::parse_file(&mut db.db, filename).unwrap(); + Chip { c: chip } + }) } fn normalize_wire(&mut self, tile: &str, wire: &str) -> String { @@ -314,9 +383,17 @@ impl Chip { self.c.ipconfig.iter().map(|(a, d)| (*a, *d)).collect() } - fn delta(&self, db: &mut Database, new_bitstream: &str) -> PyResult { - let parsed_bitstream = bitstream::BitstreamParser::parse_file(&mut db.db, new_bitstream).unwrap(); - Ok(parsed_bitstream.delta(&self.c)) + fn delta_with_ipvalues(&self, db: &mut Database, new_bitstream: &str, py: Python) -> PyResult<(chip::ChipDelta, Vec<(u32, u8)>)> { + py.allow_threads(|| { + let parsed_bitstream = bitstream::BitstreamParser::parse_file(&mut db.db, new_bitstream).unwrap(); + Ok((parsed_bitstream.delta(&self.c), parsed_bitstream.ipconfig.iter().map(|(a, d)| (*a, *d)).collect())) + }) + } + fn delta(&self, db: &mut Database, new_bitstream: &str, py: Python) -> PyResult { + py.allow_threads(|| { + let parsed_bitstream = bitstream::BitstreamParser::parse_file(&mut db.db, new_bitstream).unwrap(); + Ok(parsed_bitstream.delta(&self.c)) + }) } } @@ -392,9 +469,9 @@ fn classify_pip(src_x: i32, src_y: i32, src_name: &str, dst_x: i32, dst_y: i32, } #[pymodule] -fn libpyprjoxide(py: Python, m: &PyModule) -> PyResult<()> { +fn libpyprjoxide(_py: Python, m: &PyModule) -> PyResult<()> { pyo3_log::init(); - + m.add_wrapped(wrap_pyfunction!(parse_bitstream))?; m.add_wrapped(wrap_pyfunction!(write_tilegrid_html))?; m.add_wrapped(wrap_pyfunction!(write_region_html))?; diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/tools/reformat_database.py b/tools/reformat_database.py new file mode 100644 index 0000000..e69de29 diff --git a/util/fuzz/DesignFileBuilder.py b/util/fuzz/DesignFileBuilder.py new file mode 100644 index 0000000..e69de29 From 555f78bb467ae53495da42d9116ed0244adb310b Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Sat, 14 Feb 2026 07:16:29 -0700 Subject: [PATCH 20/29] Enabling chnages to node db and tiles, other places. General build out of introspective features using the full node db --- docs/general/structure.md | 25 +- util/common/cachecontrol.py | 21 +- util/common/database.py | 5 +- util/common/lapie.py | 115 ++++--- util/common/nodes_database.py | 344 +++++++++++++------- util/common/radiant.py | 53 ++- util/common/tiles.py | 533 +++++++++++++++++++++++++----- util/fuzz/DesignFileBuilder.py | 269 +++++++++++++++ util/fuzz/fuzzconfig.py | 144 ++++++-- util/fuzz/fuzzloops.py | 140 ++++---- util/fuzz/interconnect.py | 577 +++++++++++++++++++++++++++------ util/fuzz/nonrouting.py | 46 +-- util/fuzz/primitives.py | 3 + 13 files changed, 1803 insertions(+), 472 deletions(-) diff --git a/docs/general/structure.md b/docs/general/structure.md index f0a538c..efbc025 100644 --- a/docs/general/structure.md +++ b/docs/general/structure.md @@ -38,12 +38,35 @@ Nodes also have aliases. The typical reason for this is that nodes can span mult name for that node. Only the primary name associated with the node is directly queryable, so there is no robust way in general to determine every node that is associated with a given tile. +Generally -- although not universally -- a pip's config is located at the destination node's tile of the PIP. + ### Node Naming Nodes have a semantically meaningful structure to their naming. They are all prefixed with `RC_` which gives a hint to it's location; although nodes can span multiple tiles. -After that there are the following naming conventions: +#### J.* + +These describe nodes local to the tile and often tie in to pins + +#### [HV]0(D)[NEWS]0[C]0[S] + +These describe horizontal or vertical wires that cross (D+1) tiles in N/E/W/S direction starting from where S is 0. There +can be multiple channels of these nodes per a given wire denoted with C. + +Special names and configuration is given when these nodes run into the edge of the chip. These nodes all have aliases +that show what they branch across. + +The 'real' name for these nodes correspond to the middle position. The tile that typically configures them is the one in +the zero slot. + +For LIFCL-33U: + +- R4C14:PLC configures R2C14_V06N0002: ['R0C14_A06N0003', 'R1C14_V06N0003', 'R2C14_V06N0002', 'R3C14_V06N0001', 'R4C14_V06N0000'] +- R5C14:PLC configures 'R8C14_V06S0003': ['R10C14_V06S0005', 'R11C14_V06S0006', 'R5C14_V06S0000', 'R6C14_V06S0001', 'R7C14_V06S0002', 'R8C14_V06S0003', 'R9C14_V06S0004'] +- R11C10:PLC configures R11C13_H06E0203: ['R11C10_H06E0200', 'R11C11_H06E0201', 'R11C12_H06E0202', 'R11C13_H06E0203', 'R11C14_H06E0204', 'R11C15_H06E0205', 'R11C16_H06E0206'] +- R11C16:PLC configures R11C13_H06W0203: ['R11C10_H06W0206', 'R11C11_H06W0205', 'R11C12_H06W0204', 'R11C13_H06W0203', 'R11C14_H06W0202', 'R11C15_H06W0201', 'R11C16_H06W0200'] +- CIB_R1C14:CIB_T configures 'R4C14_V06S0003': ['R1C14_V06S0000', 'R2C14_V06S0001', 'R3C14_V06S0002', 'R4C14_V06S0003', 'R5C14_V06S0004', 'R6C14_V06S0005', 'R7C14_V06S0006'] ## Tile types diff --git a/util/common/cachecontrol.py b/util/common/cachecontrol.py index 7d6124a..f5c1425 100644 --- a/util/common/cachecontrol.py +++ b/util/common/cachecontrol.py @@ -11,18 +11,21 @@ engine = create_engine(f'sqlite:///{get_cache_dir()}/cache.db') #@cachier.cachier(backend="sql", sql_engine=engine, cache_dir=database.get_cache_dir(), pickle_reload=False,separate_files=True) -def cache_fn(): +def cache_fn(hashfunc = None): RADIANT_DIR = os.environ.get("RADIANTDIR") - def hashfunc(args, kwds): - # Sort the kwargs to ensure consistent ordering + def default_hashfunc(args, kwds): kwds["RADIANT_DIR"] = RADIANT_DIR kwds["RADIANT_VERSION"] = radiant_version - sorted_kwargs = sorted(kwds.items()) - # Serialize args and sorted_kwargs using pickle or similar - serialized = pickle.dumps((args, sorted_kwargs)) - # Create a hash of the serialized data - return hashlib.sha256(serialized).hexdigest() + if hashfunc is None: + # Sort the kwargs to ensure consistent ordering + sorted_kwargs = sorted(kwds.items()) + # Serialize args and sorted_kwargs using pickle or similar + serialized = pickle.dumps((args, sorted_kwargs)) + # Create a hash of the serialized data + return hashlib.sha256(serialized).hexdigest() + else: + return hashfunc(args, kwds) - return cachier.cachier(hash_func=hashfunc, backend="sql", sql_engine=engine, pickle_reload=False,separate_files=True, wait_for_calc_timeout=5) \ No newline at end of file + return cachier.cachier(hash_func=default_hashfunc, backend="sql", sql_engine=engine, pickle_reload=False,separate_files=True, wait_for_calc_timeout=5) \ No newline at end of file diff --git a/util/common/database.py b/util/common/database.py index e64076b..5f544f9 100644 --- a/util/common/database.py +++ b/util/common/database.py @@ -55,6 +55,9 @@ def get_db_subdir(family = None, device = None, package = None): exist. """ subdir = get_db_root() + if family is None and device is not None: + family = device.split('-')[0] + dparts = [family, device, package] for dpart in dparts: if dpart is None: @@ -110,7 +113,7 @@ def get_iodb(family, device = None): with open(tgjson, "r") as f: return json.load(f) - +@cache def get_devices(): """ Return the deserialised content of devices.json diff --git a/util/common/lapie.py b/util/common/lapie.py index 8c48682..5053ac1 100644 --- a/util/common/lapie.py +++ b/util/common/lapie.py @@ -1,11 +1,14 @@ """ Python wrapper for `lapie` """ +import asyncio import hashlib +import itertools import logging import os import re import shutil +import sqlite3 import subprocess import tempfile import time @@ -52,6 +55,7 @@ def run(commands, workdir=None, stdout=None): f.write(c + '\n') env = os.environ.copy() env[dev_enable_name] = "1" + env["LSC_SHOW_INTERNAL_ERROR"] = "1" result_struct = run_bash_script(env, rcmd_path, tcltool, scriptfile, cwd=workdir, stdout=stdout) @@ -95,7 +99,7 @@ def __init__(self, from_wire, to_wire, is_bidi = False, flags = 0, buffertype = self.is_bidi = is_bidi def __repr__(self): - return str((self.from_wire, self.to_wire, self.flags, self.buffertype, self.is_bidi)) + return str((self.from_wire, self.to_wire)) class PinInfo: def __init__(self, site, pin, wire, pindir): @@ -214,7 +218,7 @@ def parse_sites(rpt): return sites -@cachecontrol.cache_fn() +@cache def get_full_node_list(udb): workdir = f"/tmp/prjoxide_node_data/{udb}" nodefile = path.join(workdir, "full_nodes.txt") @@ -227,7 +231,8 @@ def get_full_node_list(udb): udb = config.udb run_with_udb(udb, [f'dev_list_node_by_name -file {nodefile}']) with open(nodefile, 'r') as nf: - return [line.split(":")[-1].strip() for line in nf.read().split("\n")] + return {res for line in nf.read().split("\n") + if len(res:=line.split(":")[-1].strip()) != 0 } @cache def _get_list_arc(device): @@ -271,12 +276,32 @@ def get_jump_wires(device): return jmp +@cache +def get_jump_wires_lookup(device): + rtn = defaultdict(set) + for jmp in get_jump_wires(device): + rtn[jmp[0]].add(jmp) + rtn[jmp[1]].add(jmp) + return rtn + def get_jump_wires_by_nodes(device, nodes): - return set([ - (frm_wire, to_wire) - for (frm_wire, to_wire) in get_jump_wires(device) - if frm_wire in nodes or to_wire in nodes - ]) + nodes = set(nodes) + lu = get_jump_wires_lookup(device) + + raw_set = set() + for n in nodes: + raw_set = raw_set | lu[n] + + # Most of the things are connections; but sometimes there are multi-source connections. Filter those out. + raw_dict = defaultdict(list) + for (from_wire, to_wire) in raw_set: + raw_dict[to_wire].append(from_wire) + + return { + (from_wires[0], to_wire) + for (to_wire, from_wires) in raw_dict.items() + if len(from_wires) == 1 + } def _get_node_data(udb, nodes): regex = False @@ -286,34 +311,30 @@ def _get_node_data(udb, nodes): nodelist = "[list {}]".format(" ".join(nodes)) logging.info(f"Querying for {len(nodes)} nodes {nodes[:10]}") - key_input = "\n".join([radiant_version, udb, f"regex: {regex}", ''] + nodes) - key = hashlib.md5(key_input.encode('utf-8')).hexdigest() - key_path = f"/tmp/prjoxide_node_data/{key}" - os.makedirs("/tmp/prjoxide_node_data", exist_ok=True) - - if os.path.exists(key_path): - logging.debug(f"Nodefile found at {key_path}") - shutil.copyfile(key_path, nodefile) - else: - if not udb.endswith(".udb"): - device = udb - udb = f"/tmp/prjoxide_node_data/{device}.udb" - if not os.path.exists(udb): - config = fuzzconfig.FuzzConfig(device, f"extract-site-info-{device}", []) - config.setup() - shutil.copyfile(config.udb, udb) - - re_slug = "-re " if regex else "" - run_with_udb(udb, [f'dev_report_node -file {nodefile} [{get_nodes} {re_slug}{nodelist}]'], stdout = subprocess.DEVNULL) - shutil.copyfile(nodefile, key_path) - with open(key_path + ".input", 'w') as f: - f.write(key_input) - logging.debug(f"Nodefile cached at {key_path}") + + if not udb.endswith(".udb"): + device = udb + udb = f"/tmp/prjoxide_node_data/{device}.udb" + if not os.path.exists(udb): + config = fuzzconfig.FuzzConfig(device, f"extract-site-info-{device}", []) + config.setup() + shutil.copyfile(config.udb, udb) + + re_slug = "-re " if regex else "" + run_with_udb(udb, [f'dev_report_node -file {nodefile} [{get_nodes} {re_slug}{nodelist}]'], stdout = subprocess.DEVNULL) with open(nodefile, 'r') as nf: return parse_node_report(nf.read(), nodes) -def get_node_data(udb, nodes, regex=False, executor = None): +async def get_pip_data(device, nodes, filter_type = None): + from nodes_database import NodesDatabase + # Make sure we have full db for these entries + await asyncio.to_thread(get_node_data, device, nodes, skip_pips=True) + + db = NodesDatabase.get(device) + return db.get_pips(nodes, filter_type = filter_type) + +def get_node_data(udb, nodes, regex=False, executor = None, filter_by_name=True, skip_missing = False, skip_pips=False): from nodes_database import NodesDatabase import fuzzloops @@ -326,32 +347,45 @@ def get_node_data(udb, nodes, regex=False, executor = None): all_nodes = get_full_node_list(udb) regex = [re.compile(n) for n in nodes] nodes = sorted(set([n for n in all_nodes if any([r for r in regex if r.search(n) is not None])])) + elif filter_by_name: + all_nodes = get_full_node_list(udb) + nodes = sorted(set(nodes) & all_nodes) + + if len(nodes) == 0: + return [] db = NodesDatabase.get(udb) - nis = db.get_node_data(nodes) + t = time.time() + nis = db.get_node_data(nodes, skip_pips=skip_pips) + logging.debug(f"Looked up {len(nis)} records in {time.time() - t} sec") missing = sorted({k for k in nodes if k not in nis}) futures = [] - if len(missing): + if not skip_missing and len(missing): cnt = 5000 - logging.info(f"Getting from lapie: {missing[:10]}...") + logging.info(f"Getting from lapie: {len(missing)} nodes {missing[:10]}...") with fuzzloops.Executor(executor) as local_executor: def lapie_get_node_data(query): s = time.time() - nodes = _get_node_data(udb, missing[:cnt]) + nodes = _get_node_data(udb, query) logging.debug(f"{len(query)} N {len(query) / (time.time() - s)} N/sec ({(time.time() - s)} deltas)") return nodes def integrate_nodes(nodes): - db.insert_nodeinfos(nodes) + db = NodesDatabase.get(udb) + try: + db.insert_nodeinfos(nodes) + except sqlite3.OperationalError as e: + logging.warning(f"Could not populate node db: {e}") + for n in nodes: nis[n.name] = n - while len(missing): - f = local_executor.submit(lapie_get_node_data, missing[:cnt]) - missing = missing[cnt:] + for grp in itertools.batched(missing, cnt): + f = local_executor.submit(lapie_get_node_data, list(grp)) futures.append(fuzzloops.chain(f, integrate_nodes)) + if executor is not None: return fuzzloops.chain(futures, lambda _: list(nis.values())) else: @@ -416,6 +450,7 @@ def parse_report_site(rpt): return sites +@cachecontrol.cache_fn() def get_sites_with_pin(device): from nodes_database import NodesDatabase diff --git a/util/common/nodes_database.py b/util/common/nodes_database.py index 502de1c..30ab402 100644 --- a/util/common/nodes_database.py +++ b/util/common/nodes_database.py @@ -1,102 +1,143 @@ import logging import sqlite3 +import threading +import time +from collections import defaultdict from threading import RLock +_thread_local = threading.local() class NodesDatabase: - _dbs = {} _lock = RLock() + _write_locks = {} + _last_checkpoint = {} + current_version = 3 @staticmethod def get(device): with NodesDatabase._lock: - if device not in NodesDatabase._dbs: - NodesDatabase._dbs[device] = NodesDatabase(device) - return NodesDatabase._dbs[device] + global _thread_local + dbs = getattr(_thread_local, 'dbs', None) + if dbs is None: + logging.warning(f"Creating dbs {_thread_local} {threading.get_ident()}") + setattr(_thread_local, 'dbs', {}) + dbs = _thread_local.dbs + if device not in NodesDatabase._write_locks: + NodesDatabase._write_locks[device] = threading.Lock() + if device not in NodesDatabase._last_checkpoint: + NodesDatabase._last_checkpoint[device] = time.time() + if device not in dbs: + dbs[device] = NodesDatabase(device) + return dbs[device] def __init__(self, device): import database - + self.write_lock = NodesDatabase._write_locks[device] self.db_path = f"{database.get_cache_dir()}/{device}-nodes.sqlite" - logging.debug(f"Opening node database at {self.db_path}") + logging.debug(f"Opening node database at {self.db_path} thread: {threading.get_ident()}") self.device = device - self.conn = sqlite3.connect(self.db_path, check_same_thread=False) - self.lock = RLock() + self.conn = sqlite3.connect(self.db_path) self.init_db() def init_db(self): - with self.lock: - conn = self.conn - conn.execute("PRAGMA foreign_keys = ON") - cur = conn.cursor() + conn = self.conn + cur = conn.cursor() + cur.execute("PRAGMA user_version;") - cur.execute(""" - CREATE TABLE IF NOT EXISTS nodes ( - id INTEGER PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - has_full_data INTEGER NOT NULL DEFAULT 0 CHECK (has_full_data IN (0, 1)) - ); - """) + version = cur.fetchone() - # PIPs table: - # from_wire and to_wire are node IDs - # bidir = 0 (unidirectional) or 1 (bidirectional) + with conn: cur.execute(""" - CREATE TABLE IF NOT EXISTS pips ( - from_id INTEGER NOT NULL, - to_id INTEGER NOT NULL, - bidir INTEGER NOT NULL CHECK (bidir IN (0,1)), - jumpwire INTEGER NOT NULL CHECK (jumpwire IN (0,1)) DEFAULT 0, - flags INTEGER NOT NULL DEFAULT 0, - buffertype TEXT NOT NULL DEFAULT "", - PRIMARY KEY (from_id, to_id), - FOREIGN KEY (from_id) REFERENCES nodes(id), - FOREIGN KEY (to_id) REFERENCES nodes(id) - ) WITHOUT ROWID; + CREATE TEMP TABLE IF NOT EXISTS tmp_node_ids ( + id INTEGER PRIMARY KEY + ); """) - - try: - cur.execute("ALTER TABLE pips ADD COLUMN jumpwire INTEGER") - except sqlite3.OperationalError as e: - pass - cur.execute(""" - CREATE TEMP TABLE IF NOT EXISTS tmp_node_names ( - name TEXT PRIMARY KEY - ); + CREATE + TEMP TABLE IF NOT EXISTS tmp_node_names ( + name TEXT PRIMARY KEY + ); """) - cur.execute(""" - CREATE TEMP TABLE IF NOT EXISTS tmp_node_ids ( - id INTEGER PRIMARY KEY - ); - """) + if len(version) == 0 or version[0] != NodesDatabase.current_version: + with conn: + cur.execute(f"PRAGMA user_version = {NodesDatabase.current_version};") + conn.execute("PRAGMA foreign_keys = ON") + conn.execute('PRAGMA journal_mode=WAL;') + conn.execute('PRAGMA synchronous=NORMAL') - cur.execute(""" - CREATE TABLE IF NOT EXISTS sites ( + cur.execute(""" + CREATE TABLE IF NOT EXISTS nodes ( id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL, - type TEXT NOT NULL, - x INTEGER NOT NULL, - y INTEGER NOT NULL + has_full_data INTEGER NOT NULL DEFAULT 0 CHECK (has_full_data IN (0, 1)) ); - """) - - cur.execute(""" -CREATE TABLE IF NOT EXISTS site_pins ( - site_id INTEGER NOT NULL, - pin_name TEXT NOT NULL, - node_id INTEGER NOT NULL, - - PRIMARY KEY (site_id, pin_name), - - FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE, - FOREIGN KEY (node_id) REFERENCES nodes(id) -) WITHOUT ROWID; - """) - - conn.commit() + """) + + try: + cur.execute("ALTER TABLE pips ADD COLUMN jumpwire INTEGER") + except sqlite3.OperationalError as e: + pass + + # PIPs table: + # from_wire and to_wire are node IDs + # bidir = 0 (unidirectional) or 1 (bidirectional) + cur.execute(""" + CREATE TABLE IF NOT EXISTS pips ( + from_id INTEGER NOT NULL, + to_id INTEGER NOT NULL, + bidir INTEGER NOT NULL CHECK (bidir IN (0,1)), + jumpwire INTEGER NOT NULL CHECK (jumpwire IN (0,1)) DEFAULT 0, + flags INTEGER NOT NULL DEFAULT 0, + buffertype TEXT NOT NULL DEFAULT "", + PRIMARY KEY (from_id, to_id), + FOREIGN KEY (from_id) REFERENCES nodes(id), + FOREIGN KEY (to_id) REFERENCES nodes(id) + ) WITHOUT ROWID; + """) + + try: + cur.execute("""CREATE INDEX from_id_index ON pips (from_id);""") + cur.execute("""CREATE INDEX to_id_index ON pips (to_id);""") + except sqlite3.OperationalError as e: + pass + + + cur.execute(""" + CREATE TABLE IF NOT EXISTS sites ( + id INTEGER PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + type TEXT NOT NULL, + x INTEGER NOT NULL, + y INTEGER NOT NULL + ); + """) + + cur.execute(""" + CREATE TABLE IF NOT EXISTS site_pins ( + site_id INTEGER NOT NULL, + pin_name TEXT NOT NULL, + node_id INTEGER NOT NULL, + + PRIMARY KEY (site_id, pin_name), + + FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE, + FOREIGN KEY (node_id) REFERENCES nodes(id) + ) WITHOUT ROWID; + """) + + + cur.execute(""" + CREATE TABLE IF NOT EXISTS node_aliases ( + node_id INTEGER NOT NULL, + alias TEXT UNIQUE NOT NULL, + + PRIMARY KEY (alias), + + FOREIGN KEY (node_id) REFERENCES nodes(id) + ) WITHOUT ROWID; + """) def _populate_tmp(self, cur, type, values): cur.execute(f"DELETE FROM tmp_node_{type}s") @@ -124,6 +165,44 @@ def get_node_ids(self, names): name_to_id = {v: k for k, v in id_to_name.items()} return name_to_id + def get_pips(self, filter = None, filter_type = None): + conn = self.conn + cur = conn.cursor() + + t = time.time() + if filter is None: + cur.execute(f""" + SELECT n1.name, n2.name + FROM pips p + JOIN nodes n1 ON n1.id = p.from_id + JOIN nodes n2 ON n2.id = p.to_id + """) + elif filter_type is None: + self._populate_tmp(cur, "name", filter) + cur.execute(f""" + SELECT n1.name, n2.name + FROM pips p + JOIN nodes n1 ON n1.id = p.from_id + JOIN nodes n2 ON n2.id = p.to_id + WHERE n1.name IN (SELECT name from tmp_node_names) OR n2.name IN (SELECT name from tmp_node_names) + """) + else: + self._populate_tmp(cur, "name", filter) + + cur.execute(f""" + SELECT n1.name, n2.name + FROM pips p + JOIN nodes n1 ON n1.id = p.from_id + JOIN nodes n2 ON n2.id = p.to_id + WHERE {"n1" if filter_type == "from" else "n2"}.name IN (SELECT name from tmp_node_names) + """) + + cnt = 0 + for from_id, to_id in cur.fetchall(): + yield from_id, to_id + cnt = cnt + 1 + logging.debug(f"Returned {cnt} pips in {time.time()-t} seconds {cnt / (time.time()-t)} hz") + def get_jumpwires(self): conn = self.conn cur = conn.cursor() @@ -177,68 +256,98 @@ def insert_jumpwires(self, jumpwires): conn.commit() - def get_node_data(self, names): + def get_node_data(self, names, skip_pips=False): from lapie import NodeInfo, PipInfo - with self.lock: - conn = self.conn - cur = conn.cursor() + conn = self.conn + cur = conn.cursor() + cur.arraysize = 100000 - self._populate_tmp(cur, "name", names) + self._populate_tmp(cur, "name", names) - cur.execute( - f"SELECT id, name FROM nodes WHERE has_full_data = 1 and name IN (SELECT name from tmp_node_names)", - ) - id_to_name = dict(cur.fetchall()) - name_to_id = {v:k for k,v in id_to_name.items()} + cur.execute( + f"SELECT id, name FROM nodes WHERE has_full_data = 1 and name IN (SELECT name from tmp_node_names)", + ) + id_to_name = dict(cur.fetchall()) + name_to_id = {v:k for k,v in id_to_name.items()} - # Prepare result dict - result = {name: NodeInfo(name) for name in name_to_id} + # Prepare result dict + result = {name: NodeInfo(name) for name in name_to_id} + if skip_pips: + return result - self._populate_tmp(cur, "id", list(id_to_name.keys())) - # ---- Downhill PIPs ---- - cur.execute(f""" - SELECT p.from_id, n2.name, p.bidir, p.flags, p.buffertype - FROM pips p - JOIN nodes n2 ON n2.id = p.to_id - WHERE p.from_id IN (SELECT id from tmp_node_ids) - """) + for k,v in result.items(): + v.aliases.append(k) - for from_id, to_name, bidir, flags, bt in cur.fetchall(): - from_name = id_to_name[from_id] - result[from_name].downhill_pips.append( - PipInfo(from_name, to_name, - is_bidi=bool(bidir), - flags=flags, - buffertype=bt) - ) + self._populate_tmp(cur, "id", list(id_to_name.keys())) - # ---- Uphill PIPs ---- - cur.execute(f""" - SELECT p.to_id, n1.name, p.bidir, p.flags, p.buffertype - FROM pips p - JOIN nodes n1 ON n1.id = p.from_id - WHERE p.to_id IN (SELECT id from tmp_node_ids) - """) + cur.execute(f""" + SELECT n1.name, n2.name, p.bidir, p.flags, p.buffertype + FROM pips p + JOIN nodes n1 ON n1.id = p.from_id + JOIN nodes n2 ON n2.id = p.to_id + WHERE p.from_id IN (SELECT id from tmp_node_ids) or + p.to_id IN (SELECT id from tmp_node_ids) + """) - for to_id, from_name, bidir, flags, bt in cur.fetchall(): - to_name = id_to_name[to_id] - result[to_name].uphill_pips.append( - PipInfo(from_name, to_name, + t = time.time() + cnt = 0 + while True: + results = cur.fetchmany(cur.arraysize) + if not results: + break + cnt = cnt + len(results) + for from_name, to_name, bidir, flags, bt in results: + pip = PipInfo(from_name, to_name, is_bidi=bool(bidir), flags=flags, buffertype=bt) - ) + if from_name in result: + result[from_name].downhill_pips.append(pip) + if to_name in result: + result[to_name].uphill_pips.append(pip) + + logging.debug(f"Looked up {cnt} pips in {time.time() - t} sec") + + cur.execute(f""" + SELECT n.node_id, n.alias + FROM node_aliases n + WHERE n.node_id IN (SELECT id from tmp_node_ids) + """) + + for node_id, alias in cur.fetchall(): + result[id_to_name[node_id]].aliases.append(alias) return result + def insert_nodeinfos(self, nodeinfos): - with self.lock: - conn = self.conn + with self.write_lock: + exception = None + for i in range(3): + try: + self._insert_nodeinfos(nodeinfos) + + now = time.time() + if now - NodesDatabase._last_checkpoint[self.device] > 5 * 60: + NodesDatabase._last_checkpoint[self.device] = now + cur = self.conn.cursor() + logging.debug(f"Running wal checkpoint {threading.get_ident()}") + cur.execute("PRAGMA wal_checkpoint(FULL);") + + return + except sqlite3.OperationalError as e: + exception = e + logging.warning(f"Could not insert nodeinfos after 3 tries: {exception}") + + def _insert_nodeinfos(self, nodeinfos): + touched_names = set([w for ni in nodeinfos for p in ni.pips() for w in [p.to_wire, p.from_wire]]) | set( + [n.name for n in nodeinfos]) + + + with self.conn as conn: cur = conn.cursor() - touched_names = set([w for ni in nodeinfos for p in ni.pips() for w in [p.to_wire, p.from_wire]]) | set([n.name for n in nodeinfos]) - # 1. Insert all nodes cur.executemany( "INSERT OR IGNORE INTO nodes (name) VALUES (?)", @@ -254,8 +363,6 @@ def insert_nodeinfos(self, nodeinfos): WHERE name IN (SELECT name from tmp_node_names) """ ) - # 2. Resolve node ids - names = [ni.name for ni in nodeinfos] self._populate_tmp(cur, "name", touched_names) @@ -288,7 +395,14 @@ def insert_nodeinfos(self, nodeinfos): pip_rows ) - conn.commit() + cur.executemany( + """ + INSERT OR IGNORE INTO node_aliases + (node_id, alias) + VALUES (?, ?) + """, + [(name_to_id[n.name], alias) for n in nodeinfos for alias in n.aliases if alias != n.name] + ) def insert_sites_and_fetch_ids(self, sites): if not sites: diff --git a/util/common/radiant.py b/util/common/radiant.py index 89c0230..da89c61 100644 --- a/util/common/radiant.py +++ b/util/common/radiant.py @@ -10,6 +10,13 @@ import database import sys +class RadiantRunError(Exception): + def __init__(self, message, error_lines): + self.message = message + self.error_lines = error_lines + super().__init__(self.message) + + def run_bash_script(env, *args, cwd = None, stdout = subprocess.PIPE, stderr = subprocess.PIPE): slug = " ".join(args[1:]) logging.debug("Running script: %s", slug) @@ -25,14 +32,19 @@ def run_bash_script(env, *args, cwd = None, stdout = subprocess.PIPE, stderr = s def process_subprocess_result(stdout, stderr, returncode): show_output = returncode != 0 or len(stderr.strip()) > 0 + error_lines = [] + if show_output or logging.DEBUG >= logging.root.level: for stream in [("", stdout, sys.stdout), ("ERR:", stderr, sys.stdout)]: if stream[1] is not None: for l in stream[1].decode().splitlines(): + if l.startswith("ERROR - "): + error_lines.append(l) logging.info(f"[{stream[0]} {slug}] {l}") + if returncode != 0: - raise Exception(f"Error encountered running radiant: {slug} {returncode}") + raise RadiantRunError(f"Error encountered running radiant: {slug} {returncode}", error_lines) # try: # loop = asyncio.get_running_loop() @@ -73,3 +85,42 @@ def run(device, source, struct_ver=True, raw_bit=False, pdcfile=None, rbk_mode=F dsh_path = path.join(database.get_oxide_root(), "radiant.sh") logging.info(f"Building [{device}] {source}") return run_bash_script(env, dsh_path, device, source) + +async def partition_wire_list(cfg, wires, prefix=""): + import interconnect + wires = sorted(set(wires)) + try: + t = time.time() + await asyncio.to_thread(interconnect.create_wires_file, cfg, wires, prefix=prefix) + # interconnect.create_wires_file(cfg, wires, prefix = f"{idx}/{len(wires)}/") + print(f"Built file with {len(wires)} {time.time() - t} seconds {len(wires) / (time.time() - t)} wires.") + + return wires, [], [] + except RadiantRunError as e: + for l in e.error_lines: + if "No arc found for" in l: + (to_wire, from_wire) = l.split(" ")[-1].split(".") + idx = wires.index((from_wire, to_wire)) + return wires[:idx], [ wires[idx] ], wires[(idx+1):] + raise e + +async def validate_wire_list(cfg, wires, prefix="", max_wires=100000): + def chunkify(lst, n): + return [lst[i::n] for i in range(n)] + + bad_arcs = [] + while len(wires): + chunks = 10 + if len(wires) // chunks > max_wires: + chunks = len(wires) // (max_wires - 1) + + partitions = await asyncio.gather( + *[asyncio.create_task(partition_wire_list(cfg, grp, f"{(len(wires) // chunks * i)}/")) for i, grp in enumerate(chunkify(wires, chunks))]) + + wires = [] + for (good, bad, unknown) in partitions: + bad_arcs.extend(bad) + wires.extend(unknown) + + return bad_arcs + diff --git a/util/common/tiles.py b/util/common/tiles.py index 71b3da8..46ea7ac 100644 --- a/util/common/tiles.py +++ b/util/common/tiles.py @@ -1,13 +1,21 @@ +import asyncio import itertools +import logging import random import re +import time +import traceback from collections.abc import Iterable +from functools import cache + +from six import reraise import database from collections import defaultdict import lapie import cachecontrol +from radiant import validate_wire_list pos_re = re.compile(r'R(\d+)C(\d+)') @@ -71,7 +79,22 @@ def get_tiles_by_filter(device, fn): def get_tiles_by_tiletype(device, tiletype): tilegrid = database.get_tilegrid(device)['tiles'] - return {k:v for k,v in tilegrid.items() if k.split(":")[-1] == tiletype} + return {k: v for k, v in tilegrid.items() if k.split(":")[-1] == tiletype} + +def get_coincidental_tiletypes_for_tiletype(device, tiletype): + tt_t = get_tiles_by_tiletype(device, tiletype) + rcs = [get_rc_from_name(device, t) for t in tt_t] + tiles_at_rc = [{t.split(":")[-1] for t in get_tiles_by_rc(device, rc)} for rc in rcs] + if len(tiles_at_rc) == 0: + return {} + + s = tiles_at_rc[0] + for tiletypes in tiles_at_rc: + s = s & tiletypes + s.remove(tiletype) + + return s + def get_tiles_by_primitive(device, primitive): tilegrid = database.get_tilegrid(device)['tiles'] @@ -110,73 +133,102 @@ def get_sites_for_tile(device, tile): return {k:v for k,v in sites.items() if RC == get_rc_from_name(device, k)} -_node_list_lookup = {} -_node_owned_lookup = {} +@cache +def get_full_node_set(device): + all_nodes = lapie.get_full_node_list(device) + return set([n for n in all_nodes if len(n)]) -_spine_regex = re.compile("(.)([0-9][0-9])(.)([0-9][0-9])([0-9][0-9])") +@cache +def get_node_list_lookups(device): + _spine_regex = re.compile("(.)([0-9][0-9])[NEWS]([0-9][0-9])([0-9][0-9])") + _hpbx_regex = re.compile("HPBX[0-9]*") -_full_node_set = {} -def get_full_node_set(device): - if device not in _full_node_set: - all_nodes = lapie.get_full_node_list(device) - _full_node_set[device] = sorted(list(set([n for n in all_nodes if len(n)]))) - return _full_node_set[device] + node_list_lookup = defaultdict(list) + node_owned_lookup = defaultdict(list) + tile_owned_lookup = defaultdict(list) + + for name in lapie.get_full_node_list(device): + if len(name) == 0: continue + rc = get_rc_from_name(device, name) + name_no_rc = "_".join(name.split("_")[1:]) -def get_node_list_for_tile(device, tile, owned = False): - if device not in _node_list_lookup: - all_nodes = lapie.get_full_node_list(device) - _node_list_lookup[device] = defaultdict(list) - _node_owned_lookup[device] = defaultdict(list) - for name in all_nodes: - rc = get_rc_from_name(device, name) - - if rc is None: - continue - elif rc[0] < 0 or rc[1] < 0: - print(f"Nodename {name} has negative rc: {rc}") - name_no_rc = "_".join(name.split("_")[1:]) - m = _spine_regex.match(name_no_rc) - if m is not None: - (r, c) = rc - orientation = m.group(1) - size = int(m.group(2)) - direction = m.group(3) - track = int(m.group(4)) - segment = int(m.group(5)) - - if size == 0: - continue - - assert (orientation in ["H", "V"]) - assert (direction in ["N", "E", "W", "S"]) - - (dir_x, dir_y) = (0, 0) - if direction == "N": - dir_y = -1 - elif direction == "S": - dir_y = 1 - elif direction == "E": - dir_x = 1 - else: - dir_x = -1 - - rs = r - dir_y * segment - cs = c - dir_x * segment - - for i in range(0, size + 1): - ro = rs + dir_y * i - co = cs + dir_x * i - alias_name = f"R{ro}C{co}{orientation}{size:02}{direction}{track:02}{i:02}" - # _node_list_lookup[device][ro, co].append(name) - if i == 0: - _node_owned_lookup[device][rc].append(name) + def get_owning_tiles_for_rc(rc): + tiles = sorted([t for t in get_tiles_by_rc(device, rc)]) + + for t in tiles: + if ":PLC" in t: + return [t] + + if name_no_rc.startswith("HPBX"): + for t in tiles: + if ":TAP" in t: + return [t] + + if len(tiles) > 1: + tiles = [t for t in tiles if "TAP_" not in t and "EBR" not in t] + + return tiles + + tiles_at_rc = get_owning_tiles_for_rc(rc) + + if len(tiles_at_rc) == 0: + logging.warning(f"Could not find tiles for {device} {rc} {name} {[t for t in get_tiles_by_rc(device, rc)]}") + + if name == "R37C52_H01E0100" and len(tiles_at_rc) == 0: + # LIFCL-33 weirdness + continue + + assert len(tiles_at_rc) > 0 + + if rc is None: + continue + + elif rc[0] < 0 or rc[1] < 0: + logging.warning(f"Nodename {name} has negative rc: {rc}") + + for tile in tiles_at_rc: node_list_lookup[tile].append(name) + + m = _spine_regex.match(name_no_rc) + + if m is not None: + owners = list(resolve_possible_names(device, name)) + for owner in owners: + tiles_at_rc = get_owning_tiles_for_rc(get_rc_from_name(device, owner)) + if len(tiles_at_rc) > 0: + break + + if len(tiles_at_rc) != 1: + tiles_at_rc = sorted(tiles_at_rc, key=lambda t: len(t)) + + node_owned_lookup[tiles_at_rc[0]].append(name) + tile_owned_lookup[name].extend(tiles_at_rc) + elif _hpbx_regex.search(name_no_rc) is not None: + tap_tiles_on_r = sorted([(abs(rc[1] - get_rc_from_name(device, x)[1]), x) + for x in get_tiles_by_filter(device, lambda _, info: info["y"] == rc[0]) if ":TAP" in x]) + owner = next(iter([tile for tile in tap_tiles_on_r]), None)[1] + + if owner is not None: + node_owned_lookup[owner].append(name) + tile_owned_lookup[name].append(owner) else: - _node_list_lookup[device][rc].append(name) - _node_owned_lookup[device][rc].append(name) + logging.warning(f"Could not find owner for {name}: {lapie.get_node_data(device, name)[0].aliases} {tap_tiles_on_r}") + else: + node_owned_lookup[tiles_at_rc[0]].append(name) + tile_owned_lookup[name].extend(tiles_at_rc) + + return node_list_lookup, node_owned_lookup, tile_owned_lookup + +def get_tile_list_for_node(device, node): + _, _, tile_owned_lookup = get_node_list_lookups(device) + + return tile_owned_lookup[node] + +def get_node_list_for_tile(device, tile, owned = False): + node_list_lookup, node_owned_lookup, _ = get_node_list_lookups(device) def _get_node_list_for_tile(t): - return (_node_owned_lookup if owned else _node_list_lookup)[device].get(get_rc_from_name(device, t), []) + return (node_owned_lookup if owned else node_list_lookup).get(t, []) if isinstance(tile, list): return {n:t for t in tile for n in _get_node_list_for_tile(t)} @@ -223,18 +275,27 @@ def get_tile_routes(device, tilename, owned = False): rc_regex = re.compile("R([0-9]+)C([0-9]+)") edge_regex = re.compile("IOL_(.)([0-9]+)") +_get_rc_from_name_lookup = {} def get_rc_from_name(device, name): if isinstance(name, tuple): return name - + + if name[:6] in _get_rc_from_name_lookup: + return _get_rc_from_name_lookup[name[:6]] + m = rc_regex.search(name) if m: - return (int(m.group(1)), int(m.group(2))) + rc = (int(m.group(1)), int(m.group(2))) + if m.start() == 0: + _get_rc_from_name_lookup[name[:6]] = rc + return rc - m = edge_regex.search(name) + m = edge_regex.match(name) if m: return get_rc_from_edge(device, m.group(1), m.group(2)) - + + if name not in ["R", "L"]: + logging.warning(f"Could not derive RC from {name}") return None def get_tile_from_node(device, node): @@ -300,11 +361,17 @@ def get_connected_tiles(device, tilename): return { k:v for k,v in tilegrid.items() if (v['y'], v['x']) in rcs } -def draw_rc(rcs): +def draw_rc(device, rcs): + devices = database.get_devices() + device_info = devices["families"][device.split("-")[0]]["devices"][device] + + max_row = device_info["max_row"] + max_col = device_info["max_col"] + rcs = set(rcs) - for y in range(0, 55): - for x in range(0, 83): - print("■" if (x,y) in rcs else " " , end='') + for y in range(0, max_col): + for x in range(0, max_row): + print("■" if (x,y) in rcs else "☐" , end='') print() def get_wires_for_tiles(device): @@ -390,16 +457,13 @@ def def_should_expand(node): graph = {} while len(query_list) > 0: new_nodes = lapie.get_node_data(device, query_list) - #new_nodes = [k for k in lapie.get_list_arc(device) - query_list = [] + graph.update({n.name:n for n in new_nodes}) - for n in new_nodes: - graph[n.name] = n + query_list = [wire for n in new_nodes + for p in n.pips() + for wire in [p.to_wire, p.from_wire] + if wire not in graph and should_expand(wire)] - for p in n.pips(): - for wire in [p.to_wire, p.from_wire]: - if wire not in graph and should_expand(wire): - query_list.append(wire) return graph def get_local_pips_for_site(device, site, include_interface_pips = True): @@ -425,20 +489,109 @@ def should_include(p): else: return p.from_wire in local_graph and p.to_wire in local_graph - pips = [(p.from_wire, p.to_wire) for n, info, in local_graph.items() for p in info.pips() if + pips = [(p.from_wire, p.to_wire) + for n, info, in local_graph.items() + for p in info.pips() if should_include(p)] return sorted(set(pips)), local_graph -def get_representative_nodes_for_tiletype(device, tiletype): +async def get_tiles_with_pip(device, pip, tiles = None, pips_by_node = None): + if tiles is None: + tilegrid = database.get_tilegrid(device)['tiles'] + tiles = {k:get_rc_from_name(device, k) for k, v in tilegrid.items()} + else: + tiles = {k:get_rc_from_name(device, k) for k in tiles} + + def has_pip_nodes(rc): + nodes = tuple([resolve_actual_node(device, w, rc) for w in pip]) + if any([n is None for n in nodes]): + return None + return nodes + + rtn = {actual_pip:k for k,rc in tiles.items() if (actual_pip := has_pip_nodes(rc)) is not None} + + if pips_by_node is None: + pips_by_node = await lapie.get_pip_data(device, [n[0] for n in rtn]) + + return {v for k,v in rtn.items() if k in pips_by_node} + +async def get_pip_tile_groupings_for_tiletype(device, tiletype, owned=True): + ts = sorted(list(get_tiles_by_tiletype(device, tiletype).keys())) + + return await get_pip_tile_groupings(device, ts) + +@cachecontrol.cache_fn() +async def get_pip_tile_groupings(device, tiles): + import interconnect + import fuzzconfig + import fuzzloops + + ts = tiles + + all_nodes = { + node:tile + for tile in ts + for node in get_node_list_for_tile(device, tile, owned=True) + } + + all_pips = set(await lapie.get_pip_data(device, list(all_nodes.keys()), filter_type="to")) + + pips_by_tile = defaultdict(set) + for p in all_pips: + w = p[1] + if w in all_nodes: + pips_by_tile[all_nodes[w]].add(p) + + tiles_with_rel_pips = defaultdict(set) + + def relative_node(n, rc): + rel_node = resolve_relative_node(device, n, rc) + return rel_node + + for t, absolute_pips in pips_by_tile.items(): + # Yield + await asyncio.sleep(0) + + rc = get_rc_from_name(device, t) + rc_pips = set([ + tuple([relative_node(n, rc) for n in p]) + for p in absolute_pips + ]) + + for anon_pip in rc_pips: + tiles_with_rel_pips[anon_pip].add(t) + + rel_pip_groups = defaultdict(set) + for anon_pip, tiles in tiles_with_rel_pips.items(): + rel_pip_groups[tuple(sorted(tiles))].add(anon_pip) + + return rel_pip_groups + +@cachecontrol.cache_fn() +def get_representative_nodes_for_tiletype(device, tiletype, owned = True): + node_set = get_representative_nodes_for_tiles(device, get_tiles_by_tiletype(device, tiletype), owned = owned) + + coincidental_tiletypes = get_coincidental_tiletypes_for_tiletype(device, tiletype) + for ctt in coincidental_tiletypes: + ctt_nodes = get_representative_nodes_for_tiletype(device, ctt, owned = owned) + node_set = node_set - ctt_nodes + + return node_set + +def get_representative_nodes_for_tiles(device, tiles, owned = True, union = False): node_set = None - for tile in get_tiles_by_tiletype(device, tiletype): - nodes = set(["_".join(n.split("_")[1:]) for n in get_node_list_for_tile(device, tile)]) + for tile in tiles: + tile_rc = get_rc_from_name(device, tile) + nodes = set(resolve_relative_node(device, n, tile_rc) for n in get_node_list_for_tile(device, tile, owned)) if node_set is None: node_set = nodes else: - node_set = node_set & nodes + if union: + node_set = node_set | nodes + else: + node_set = node_set & nodes if node_set is None: return set() @@ -493,4 +646,218 @@ def find_path(device, frm, to): while c != frm: path.append(c) c = edges[c] - return path \ No newline at end of file + return path + +_resolve_relative_node_regex = re.compile(r"([HV])0(\d)([NEWS])0([0-9])0([0-9])") +def resolve_relative_node(device, n, rel_to = (0,0)): + if isinstance(rel_to, str): + rel_to = get_rc_from_name(device, rel_to) + (rr, cc) = rel_to + + rc = get_rc_from_name(device, n) + if rc is None: + logging.warning(f"Can not resolve relative node for {n}") + return None + + (r,c) = rc + + wire = "_".join(n.split("_")[1:]) + + global_prefixes = ["VCC", "VPSX", "LHPRX", "RHPRX"] + + if any([wire.startswith(prefix) for prefix in global_prefixes]): + return (f"G:{wire}", (r, c)) + + fixed_column_prefixes = ["HPBX", "HPRX"] + + if any([wire.startswith(prefix) for prefix in fixed_column_prefixes]): + return (f"C:{wire}", (r-rr, c)) + + # if wire.startswith("HPBX"): + # return ("B:", wire.split("HPBX")[-1]) + + match = _resolve_relative_node_regex.search(n) + if match: + (orientation, length, direction, slot, tap) = match.groups() + (length, slot, tap) = (int(length), int(slot), int(tap)) + + canon_tap = 0 + offset = (tap - canon_tap) + if direction in "SE": + offset = -offset + + d = (offset + r-rr, c-cc) + if orientation == 'H': + d = (r-rr, offset + c-cc) + return (direction, d, length, slot) + + return (wire, (r-rr, c-cc)) + +def resolve_node_rcs(device, n): + if isinstance(n, str): + orig_n = n + n = resolve_relative_node(device, n) + + if n is None: + return [] + + (wire_type, rc, *args) = n + + if wire_type in "NEWS": + def resolve_possible_names(n): + (direction, rc, length, slot) = n + (r, c) = (rc[0], rc[1] ) + + for i in range(length + 1): + diri = i if direction in "SE" else -i + nc = c + diri if direction in "EW" else c + nr = r + diri if direction in "NS" else r + + yield (nr, nc) + return [n for n in resolve_possible_names(n)] + return [rc] + +def resolve_possible_names(device, n, rel_to=(0,0)): + if isinstance(n, str): + orig_n = n + n = resolve_relative_node(device, n) + + (direction, rc, length, slot) = n + orientation = "H" if direction in "EW" else "V" + for i, (rr,cc) in enumerate(resolve_node_rcs(device, n)): + nr = rr + rel_to[0] + nc = cc + rel_to[1] + if nr >= 0 and nc >= 0: + yield f"R{nr}C{nc}_{orientation}0{length}{direction}0{slot}0{i}" + +def is_edge_node(device, n): + rcs = resolve_node_rcs(device, n) + devices = database.get_devices() + device_info = devices["families"][device.split("-")[0]]["devices"][device] + + max_row = device_info["max_row"] + max_col = device_info["max_col"] + + return any([(r >= max_row or r <= 0 or c >= max_col or c <= 0) for (r,c) in rcs]) + +def resolve_actual_node(device, n, rel_to = (0,0)): + if isinstance(rel_to, str): + rel_to = get_rc_from_name(device, rel_to) + + (wire_type, rc, *args) = n + + if wire_type in "NEWS": + def resolve_possible_names(n): + (direction, rc, length, slot) = n + orientation = "H" if direction in "EW" else "V" + for i, (rr,cc) in enumerate(resolve_node_rcs(device, n)): + nr = rr + rel_to[0] + nc = cc + rel_to[1] + yield f"R{nr}C{nc}_{orientation}0{length}{direction}0{slot}0{i}" + + fullnodes = get_full_node_set(device) + existing_nodes = [n for n in resolve_possible_names(n) if n in fullnodes] + + assert(len(existing_nodes) < 2) + if len(existing_nodes) == 0: + logging.debug(f"No nodes found for {n} {rel_to}") + return next(iter(existing_nodes), None) + + if wire_type.startswith("G:"): + (r,c) = rc + return f"R{r}C{c}_{wire_type.split(":")[1]}" + + if wire_type.startswith("B:"): + print(f"{n} relative to {rel_to}") + assert (False) + + if wire_type.startswith("C:"): + (r,c) = (rc[0] + rel_to[0], rc[1]) + return f"R{r}C{c}_{wire_type.split(":")[1]}" + + (r,c) = (rc[0] + rel_to[0], rc[1] + rel_to[1]) + if (r < 0) or c < 0: + return None + return f"R{r}C{c}_{wire_type}" + + +class TilesHelper: + def __init__(self, device): + self.device = device + + def rc_sub(self, a, b): + device = self.device + if isinstance(a, str): a = get_rc_from_name(device, a) + if isinstance(b, str): b = get_rc_from_name(device, b) + return (a[0] - b[0], a[1] - b[1]) + + def rc_add(self, a, b): + device = self.device + if isinstance(a, str): a = get_rc_from_name(device, a) + if isinstance(b, str): b = get_rc_from_name(device, b) + return (a[0] + b[0], a[1] + b[1]) + + @cache + def chip(self): + import fuzzconfig + return fuzzconfig.FuzzConfig.standard_chip(self.device) + + def make_tile_unanon(self, anon_tile, rel_to): + return sorted({get_rc_from_name(self.device, t) for t in self.resolve_anon_tile(anon_tile, rel_to)}) + + def get_related_tiles(self, anon_tile, rel_to): + tiletype = anon_tile[0] + + unique_prefixes = ["PCLK_DLY", "DDR_OSC", "IO_", "SYSIO_", "TMID_", "BMID_", "GPLL_"] + for unique_prefix in unique_prefixes: + if tiletype.startswith(unique_prefix): + + def match_by_r_and_tiletype(_, tile_info): + return tile_info['tiletype'].startswith(unique_prefix) # and rel_to[0] == tile_info['y'] + + return get_tiles_by_filter(self.device, match_by_r_and_tiletype) + + return self.resolve_anon_tile(anon_tile, rel_to) + + def make_tile_anon(self, tile, rel_to): + device = self.device + + if isinstance(rel_to, str): + rel_to = get_rc_from_name(device, rel_to) + + tiletype = tile.split(":")[-1] + rc = get_rc_from_name(device, tile) + if "TAP" in tiletype: + return f"C:{tiletype}", rc[1] + # + # unique_prefixes = ["PCLK_DLY", "DDR_OSC", "IO_", "SYSIO_", "TMID_", "BMID_"] + # for unique_prefix in unique_prefixes: + # if tiletype.startswith(unique_prefix): + # def unanon_fn(rel_to): + # def match_by_r_and_tiletype(_, tile_info): + # return tile_info['tiletype'].startswith(unique_prefix) # and rel_to[0] == tile_info['y'] + # + # return get_tiles_by_filter(device, match_by_r_and_tiletype) + # + # return unanon_fn + + return tiletype, self.rc_sub(rc, rel_to) + + def resolve_anon_tile(self, anon_tile, rel_to): + device = self.device + if isinstance(rel_to, str): + rel_to = get_rc_from_name(device, rel_to) + + if callable(anon_tile): + return anon_tile(rel_to) + + (type, x) = anon_tile + + if type.startswith("C:"): + match_rc = (rel_to[0], x) + match_type = type.split(":")[-1] + else: + match_rc = self.rc_add(rel_to, x) + match_type = type + + return [t for t in get_tiles_by_rc(device, match_rc) if t.split(":")[-1] == match_type] diff --git a/util/fuzz/DesignFileBuilder.py b/util/fuzz/DesignFileBuilder.py index e69de29..d080e43 100644 --- a/util/fuzz/DesignFileBuilder.py +++ b/util/fuzz/DesignFileBuilder.py @@ -0,0 +1,269 @@ +import hashlib +import json +import logging +import os +import tempfile +import threading +import asyncio +from asyncio import CancelledError + +import fuzzconfig +import tiles +from pathlib import Path +from os import path + +workdir = tempfile.mkdtemp() + +def create_wires_file(config, wires, prefix = "", executor = None): + if executor is not None: + future = executor.submit(create_wires_file, config, wires, prefix) + future.name = f"Build {config.device}" + future.executor = executor + return future + + wires = sorted(wires) + + wires_txt = "\n".join([f""" +(* keep = "true", dont_touch = "true", keep, dont_touch,\\xref:LOG ="q_c@0@0", \\dm:arcs ="{to}.{frm}" *) +wire q_{idx}; +VHI vhi_i_{idx}(.Z(q_{idx})); + """ for idx, (frm, to) in enumerate(sorted(wires))]) + + outputs = ", ".join([f"output wire q_{idx}" for idx in range(len(wires))]) + subst = config.subst_defaults() + arch = config.device.split("-")[0] + device = config.device + package = subst["package"] + speed_grade = subst["speed_grade"] + + source = f"""\ +(* \\db:architecture ="{arch}", \\db:device ="{device}", \\db:package ="{package}", \\db:speed ="{speed_grade}_High-Performance_1.0V", \\db:timestamp = 0, \\db:view ="physical" *) +module top ({outputs}); +{wires_txt} + (* \\xref:LOG ="q_c@0@0" *) + VHI vhi_i(); +endmodule + """ + + h = abs(hash(source)) + vfile = path.join(workdir, f"{config.device}/{prefix}{config.job}-{h}.v") + Path(vfile).parent.mkdir(parents=True, exist_ok=True) + + with open(vfile, 'w') as f: + f.write(source) + + return config.build_design(vfile, prefix=prefix) + +def get_wires_delta(device, wires, prefix = "", executor = None, with_bitstream_info=False, job_name = None): + if executor is not None: + f = executor.submit(get_wires_delta, device, wires, prefix, with_bitstream_info=with_bitstream_info) + if job_name is not None: + f.name = job_name + return f + + config = fuzzconfig.FuzzConfig(job=f"wires-delta", device=device) + bitstream = create_wires_file(config, wires, prefix) + if with_bitstream_info: + return *fuzzconfig.find_baseline_differences(device, bitstream), bitstream + return fuzzconfig.find_baseline_differences(device, bitstream) + +def set_default(obj): + if isinstance(obj, set): + return sorted(obj) + raise TypeError + +async def DesignsForPips(device_tiles, anon_pips, shuffled_rcs_for_tiles_of_tiletype, modified_tiles_rcs_anon): + + device = device_tiles.device + anon_pips = list(anon_pips) + anon_pips_sig = hashlib.sha256(json.dumps((sorted(anon_pips), sorted(shuffled_rcs_for_tiles_of_tiletype)), + default=set_default).encode()).hexdigest() + + last_anon_pips_remaining = len(anon_pips) + 1 + + idx = 0 + while len(anon_pips): + idx = idx + 1 + if last_anon_pips_remaining <= len(anon_pips): + logging.error(f"Could not place {len(anon_pips)} {anon_pips}") + + logging.debug(f"Processing anon pips {len(anon_pips)}...") + assert last_anon_pips_remaining > len(anon_pips) + last_anon_pips_remaining = len(anon_pips) + design_set = {} + + # Just place all the extra tiles. We don't have pips for these tiles but this marks it as used. + for (tile, (r, c)) in shuffled_rcs_for_tiles_of_tiletype: + if len(anon_pips) == 0: + break + + anon_pip = anon_pips[-1] + + other_modified_tiles = {tiles.get_rc_from_name(device, t) for anon_tile in modified_tiles_rcs_anon + for t in device_tiles.get_related_tiles(anon_tile, (r, c))} + + + pip = [tiles.resolve_actual_node(device, n, (r, c)) for n in anon_pip] + pip_coords = {rrcc for n in pip for rrcc in tiles.resolve_node_rcs(device, n)} + pip_owner_tiles = {tiles.get_rc_from_name(device, tile) + for w in pip + for tile in tiles.get_tile_list_for_node(device, w)} + + all_touched_coords = pip_coords | other_modified_tiles | {(r, c)} | pip_owner_tiles + all_touched_tiles = {tile for rc in all_touched_coords for tile in tiles.get_tiles_by_rc(device, rc)} + if len(all_touched_tiles & design_set.keys()) > 0: + continue + + anon_pips.pop() + + for t in all_touched_tiles: design_set[t] = None + design_set[tile] = (pip, all_touched_coords) + + if len(design_set): + sig = hashlib.sha256(json.dumps(sorted(design_set.items()), default=set_default).encode()).hexdigest() + os.makedirs(f"/tmp/prjoxide/{device}/{anon_pips_sig}/{idx}", exist_ok=True) + fn = f"/tmp/prjoxide/{device}/{anon_pips_sig}/{idx}/{sig}" + if not path.exists(fn): + with open(fn, "w") as f: + logging.warning(f"New signature {fn}") + json.dump(sorted(design_set.items()), f, default=set_default, indent=4) + + yield design_set + +class BitConflictException(Exception): + def __init__(self, device, nfrom_wire, nto_wire, tile, internal_exception): + self.device = device + self.from_wire = nfrom_wire + self.to_wire = nto_wire + self.tile = tile + self.internal_exception = internal_exception + + async def solve_standalone(self): + device = self.device + gt_delta, _ = get_wires_delta(device, [(self.from_wire, self.to_wire)]) + logging.error(f"Encountered {self.internal_exception} adding pip {self.tile} {self.from_wire} -> {self.to_wire}. Isolated test delta: {gt_delta}. Args: {self.to_wire}") + + +# Exception for when a bitstream returns unexpected tile configs in DesignFileBuilder +class UnexpectedDeltaException(Exception): + def __init__(self, device, unexpected_deltas, designs, bitstream_info, name = ""): + super().__init__(f"Got unexpected deltas from {bitstream_info.vfiles}: {unexpected_deltas}") + self.device = device + self.designs = designs + self.unexpected_deltas = unexpected_deltas + self.name = name + + async def find_bad_design(self, executor = None): + all_deltas = await asyncio.gather(*[get_wires_delta(self.device, [v for k, v in d.items() if v is not None], + prefix=f"unexpected_delta_{self.name}_{idx}", + executor=executor, with_bitstream_info=True) for + idx, d in enumerate(self.designs)]) + for design, (deltas, *_) in zip(self.designs, all_deltas): + delta_match = self.unexpected_deltas & set(deltas.keys()) + if len(delta_match) > 0: + logging.error(f"Due to design: unexpected {delta_match} from {design}") + return design + return None + +# Helper class that allows stacking designs to test so multiple things can be tested in one bitstream. +# Relies on each design submitted to fully annotate which tiles could potentially be modified +class DesignFileBuilder: + def __init__(self, device, executor): + self.device = device + self.active_designs_lock = threading.Lock() + self.active_designs_event = asyncio.Event() + self.active_designs = [] + + self._executor = executor + self.runs = 0 + self.hasher = hashlib.sha1() + + self.wait_count = 0 + + self.running = False + self._emitted_sig_warning = False + + # Indicate that at some point in the future n new designs will be added. Nothing is built until all reserved slots + # have been filled with designs + def reserve(self, n = 1): + with self.active_designs_lock: + self.wait_count = self.wait_count + n + + def unreserve(self, n = 1): + with self.active_designs_lock: + self.wait_count = self.wait_count - n + + async def _run_design(self, designset, future, designset_list): + wires = [v for k, v in designset.items() if v is not None] + self.runs = self.runs + 1 + result = (deltas, ipdeltas, bitstream_info) = await get_wires_delta(self.device, wires, + prefix=f"design-file-builder_{self.runs}_{len(wires)}_{len(designset)}", + executor=self._executor, with_bitstream_info=True) + unexpected_deltas = set(deltas.keys()) - set(designset.keys()) + if (len(unexpected_deltas) > 0): + raise UnexpectedDeltaException(self.device, unexpected_deltas, designset_list, bitstream_info, name=f"{self.runs}_{len(wires)}_{len(designset)}") + + future.set_result(result) + return result + + async def build_task(self): + try: + while self.wait_count == 0: + await asyncio.sleep(1) + self.running = True + + while self.wait_count > 0: + await asyncio.sleep(1) + + with self.active_designs_lock: + self.running = False + logging.info(f"No more reservations, finishing design run with {len(self.active_designs)} designs for {sum([len([1 if v is not None else 0 for v in d[0].values()]) for d in self.active_designs])} pips") + + await asyncio.gather( + *[asyncio.create_task(self._run_design(*design_tuple), name=f"design-file-builder/build-{idx}") + for idx, design_tuple in enumerate(self.active_designs)] + ) + + except CancelledError: + logging.info("Joining design file builder") + + async def _get_viable_design(self, designset): + design_tuple = None + + prior = self.hasher.hexdigest() + self.hasher.update(json.dumps(sorted(designset.items())).encode()) + + fn = f"/tmp/prjoxide/viable-design-entry/{prior}/{self.hasher.hexdigest()}" + os.makedirs(os.path.dirname(fn), exist_ok=True) + + if not path.exists(fn): + with open(fn, "w") as f: + if not self._emitted_sig_warning: + logging.warning(f"New signature {fn} at {len(self.active_designs)} designs") + + self._emitted_sig_warning = True + json.dump(sorted(designset), f, default=set_default, indent=4) + + with self.active_designs_lock: + for (d, full_design_future, design_list) in self.active_designs: + if len(d.keys() & designset.keys()) == 0: + d.update(designset) + design_list.append(designset) + + design_tuple = (d, full_design_future, design_list) + break + else: + design_tuple = (dict(designset), asyncio.get_running_loop().create_future(), [designset]) + self.active_designs.append(design_tuple) + + self.wait_count = self.wait_count - 1 + + self.active_designs_event.set() + + return await design_tuple[1] + + # Submit the given design to the builder. Only returns when that design has been built. + async def build_design(self, designset): + assert self.running + (full_delta, ipdeltas, bitstream_info) = await self._get_viable_design(designset) + return {k:v for k,v in full_delta.items() if k in designset}, bitstream_info diff --git a/util/fuzz/fuzzconfig.py b/util/fuzz/fuzzconfig.py index 1131ffa..9be342d 100644 --- a/util/fuzz/fuzzconfig.py +++ b/util/fuzz/fuzzconfig.py @@ -2,10 +2,14 @@ This module provides a structure to define the fuzz environment """ import gzip +import hashlib import logging import os +import pickle import threading from concurrent.futures import Future +from functools import cache +from multiprocessing.synchronize import RLock from os import path from pathlib import Path from string import Template @@ -16,15 +20,29 @@ #db = None -def get_db(): - #global db - l = threading.local() - if "fuzz_db" in l.__dict__: - return l.__dict__["fuzz_db"] - l.__dict__["fuzz_db"] = libpyprjoxide.Database(database.get_db_root()) - #if db is None: - # db = l.__dict__["fuzz_db"] - return l.__dict__["fuzz_db"] +class LockedObject: + def __init__(self, obj): + self.obj = obj + self.lock = threading.Lock() + + def __enter__(self): + self.lock.acquire() + return self.obj + + def __exit__(self, exc_type, exc_val, exc_tb): + self.lock.release() + +_db_lock = None + +def db_lock(): + global _db_lock + if _db_lock is None: + db = libpyprjoxide.Database(database.get_db_root()) + _db_lock = LockedObject(db) + + return _db_lock + + PLATFORM_FILTER = os.environ.get("FUZZER_PLATFORM", None) @@ -37,16 +55,67 @@ def should_fuzz_platform(device): return False return True -@cachecontrol.cache_fn() -def find_baseline_differences(device, active_bitstream): - baseline = FuzzConfig.standard_empty(device) - db = get_db() - deltas = libpyprjoxide.Chip.from_bitstream(db, baseline).delta(db, active_bitstream) +def devices_to_fuzz(): + families = database.get_devices()["families"] + + return sorted([ + device + for family in families + for device in families[family]["devices"] + if should_fuzz_platform(device) + ]) + + +@cache +def get_baseline_chip(baseline): + with db_lock() as db: + logging.info(f"Loading {baseline}") + return libpyprjoxide.Chip.from_bitstream(db, baseline) + +def find_baseline_differences_hash_fn(args, kwds): + device = kwds["device"] + baseline = kwds.get("baseline", None) + if baseline is None: + baseline = FuzzConfig.standard_empty(device) + + kwds["active_bitstream"] = hashlib.sha1(open(kwds["active_bitstream"].bitstream, 'br').read()).hexdigest() + kwds["baseline"] = hashlib.sha1(open(baseline.bitstream, 'br').read()).hexdigest() - ip_values = libpyprjoxide.Chip.from_bitstream(db, active_bitstream).get_ip_values() - ip_values = [(a, v) for a, v in ip_values if v != 0] + sorted_kwargs = sorted(kwds.items()) + + serialized = pickle.dumps(sorted_kwargs) + return hashlib.sha256(serialized).hexdigest() + +@cachecontrol.cache_fn(find_baseline_differences_hash_fn) +def find_baseline_differences(device, active_bitstream, ignore_tiles=set(), baseline = None): + import tiles + + if baseline is None: + baseline = FuzzConfig.standard_empty(device) + + baseline_chip = get_baseline_chip(baseline.bitstream) + with db_lock() as db: + deltas, ip_values = baseline_chip.delta_with_ipvalues(db, active_bitstream.bitstream) + ip_values = [(a, v) for a, v in ip_values if v != 0] + + filtered_deltas = {k: v for k, v in deltas.items() if k not in ignore_tiles} + + return filtered_deltas, ip_values + +@cache +def read_design_template(des_template): + with open(des_template, "r") as inf: + return inf.read() + +class BitstreamInfo: + def __init__(self, config, bitstream_file, vfiles): + self.config = config + self.bitstream = bitstream_file + self.vfiles = vfiles + + def __str__(self): + return self.bitstream - return deltas, ip_values class FuzzConfig: _standard_empty_bitfile = {} @@ -74,6 +143,7 @@ def __init__(self, device, job, tiles=[], sv = None): self.udb_specimen = None @staticmethod + @cache def standard_empty(device): if device not in FuzzConfig._standard_empty_bitfile: cfg = FuzzConfig(job=f"standard-empty-file", device=device, tiles=[]) @@ -81,9 +151,16 @@ def standard_empty(device): pass return FuzzConfig._standard_empty_bitfile[device] + @staticmethod + @cache + def standard_chip(device): + with db_lock() as db: + baseline = FuzzConfig.standard_empty(device) + return libpyprjoxide.Chip.from_bitstream(db, baseline.bitstream) + @property def workdir(self): - return path.join(".", "work", self.device, self.job) + return path.join(database.get_oxide_root(), "work", Path(os.getcwd()).name, self.device, self.job) def make_workdir(self): """Create the working directory for this job, if it doesn't exist already""" @@ -110,12 +187,12 @@ def check_deltas(self, name): logging.debug(f"{self.delta_dir()}/{self.job}/{name}.ron miss") return False - def solve(self, fz): + def solve(self, fz, db): try: - fz.solve(get_db()) + fz.solve(db) self.serialize_deltas(fz, fz.get_name()) except: - self.serialize_deltas(fz, f"{fz.get_name()}/FAILED") + self.serialize_deltas(fz, f"{fz.get_name()}-FAILED") raise def setup(self, skip_specimen=False): @@ -162,9 +239,10 @@ def build_design(self, des_template, substitutions = {}, prefix="", substitute=T Returns the path to the output bitstream """ + logging.debug(f"Building {des_template} with subs {substitutions}") subst = dict(substitutions) - prefix = f"{threading.get_ident()}/{prefix}" + prefix = f"{threading.get_ident()}/{prefix}/" subst_defaults = self.subst_defaults() @@ -185,12 +263,13 @@ def build_design(self, des_template, substitutions = {}, prefix="", substitute=T if path.exists(bf): os.remove(bf) - with open(des_template, "r") as inf: - with open(desfile, "w") as ouf: - if substitute: - ouf.write(Template(inf.read()).substitute(**subst)) - else: - ouf.write(inf.read()) + template_contents = read_design_template(des_template) + + with open(desfile, "w") as ouf: + if substitute: + ouf.write(Template(template_contents).substitute(**subst)) + else: + ouf.write(template_contents) env = os.environ.copy() if self.struct_mode: @@ -215,11 +294,12 @@ def build_design(self, des_template, substitutions = {}, prefix="", substitute=T outf.write(gzf.read()) if foundFile is not None: + rtn = BitstreamInfo(self, foundFile, desfile) if executor is not None: f = Future() - f.set_result(foundFile) + f.set_result(rtn) return f - return foundFile + return rtn def run_radiant_sh(): FuzzConfig.radiant_builds = FuzzConfig.radiant_builds + 1 @@ -232,9 +312,9 @@ def run_radiant_sh(): if self.struct_mode and self.udb_specimen is None: self.udb_specimen = path.join(self.workdir, prefix + "design.tmp", "par.udb") if path.exists(bitfile): - return bitfile + return BitstreamInfo(self, bitfile, desfile) if path.exists(bitfile_gz): - return bitfile_gz + return BitstreamInfo(self, bitfile_gz, desfile) raise Exception(f"Could not generate bitstream file {bitfile} {bitfile_gz}") diff --git a/util/fuzz/fuzzloops.py b/util/fuzz/fuzzloops.py index 41a2ce0..be4a476 100644 --- a/util/fuzz/fuzzloops.py +++ b/util/fuzz/fuzzloops.py @@ -2,26 +2,20 @@ General Utilities for Fuzzing """ import asyncio -import concurrent import logging import os -import shutil import signal import threading import time +import traceback from asyncio import CancelledError -from os import abort -from pathlib import Path -from signal import SIGINT, SIGTERM - -import lapie - from collections import defaultdict - from concurrent.futures import ThreadPoolExecutor, Future from contextlib import contextmanager +from signal import SIGINT, SIGTERM from threading import Thread, RLock -import traceback + +import lapie is_in_loop = False @@ -168,7 +162,8 @@ def _done(f): r = f.result() if hasattr(f, 'executor'): new_f = f.executor.submit(func, r, *args, **kwargs) - new_f.add_done_callback(fut.set_result) + new_f.add_done_callback(lambda f, fut=fut: fut.set_result(f.result())) + new_f.name = name else: fut.set_result(func(r, *args, **kwargs)) except BaseException as e: @@ -232,29 +227,27 @@ def task_count(self): def shutdown(self): self.executor.shutdown(wait=True, cancel_futures=True) -def FuzzerAsyncMain(f): +def FuzzerAsyncMain(f, *args, **kwargs): from fuzzconfig import FuzzConfig - import rich import rich.console from rich.live import Live from rich.panel import Panel - from rich.text import Text console = rich.console.Console() import sys + orignal_stdout = sys.stdout sys.stdout = console.file from rich.logging import RichHandler LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() - logging.basicConfig( level=LOGLEVEL, - handlers=[RichHandler(console=console, show_time=False, show_path=False)], + handlers=[RichHandler(console=console, show_time=False, show_path=False, rich_tracebacks=True)], + force=True ) - start_time = time.time() async def start(f): @@ -286,50 +279,56 @@ def status_panel(status: str) -> Panel: height=3, ) - async def ui(async_executor): - with Live(status_panel(""), refresh_per_second=10, console=console) as live: - finished_tasks = 0 - - while async_executor.busy() or not task.done(): - histogram = defaultdict(int) - - def process_future(fut): - nonlocal finished_tasks - - name = "anon" - if hasattr(fut, "name"): - name = fut.name - elif hasattr(fut, "get_stack"): - fn = fut.get_stack()[-1].f_code.co_name - if fn != "ui" and fn != "start": - ln = fut.get_stack()[-1].f_lineno - name = f"{fn}:{ln}" - else: - name = None - - if name is not None: - histogram[name] = histogram[name] + 1 - - if fut.done(): - if fut.exception() is not None: - logging.error(f"Encountered exception in future {fut}: {fut.exception()}") + async def ui(async_executor, task): + try: + with Live(status_panel(""), refresh_per_second=10, console=console) as live: + finished_tasks = 0 + + while async_executor.busy() or not task.done(): + histogram = defaultdict(int) + + def process_future(fut): + nonlocal finished_tasks + + name = "anon" + if hasattr(fut, "name"): + name = fut.name + elif hasattr(fut, "get_stack"): + fn = fut.get_stack()[-1].f_code.co_name + if fn != "ui" and fn != "start": + ln = fut.get_stack()[-1].f_lineno + name = f"{fn}:{ln}" + else: + name = None + + if name is not None: + histogram[name] = histogram[name] + 1 + + if fut.done(): try: - raise fut.exception() - except: - traceback.print_exc() - all_exceptions.append(fut.exception()) - else: - finished_tasks = finished_tasks + 1 - fut.result() - - for fut in async_executor.iterate_futures(): process_future(fut) - for fut in asyncio.all_tasks(): process_future(fut) - - width = shutil.get_terminal_size().columns - text = f"{list(histogram.items())} {async_executor.task_count()} {finished_tasks} finished {len(all_exceptions)} errors, built/cached {FuzzConfig.radiant_builds}/{FuzzConfig.radiant_cache_hits} tool queries {lapie.run_with_udb_cnt} {int(time.time() - start_time)}s" - # print("{text:>{width}}".format(text=text, width=width), end="\r") - live.update(status_panel(text)) - await asyncio.sleep(.1) + if fut.exception() is not None: + logging.error(f"Encountered exception in future {fut}: {fut.exception()}") + traceback.print_exception(fut.excecption()) + all_exceptions.append(fut.exception()) + else: + finished_tasks = finished_tasks + 1 + fut.result() + except BaseException as e: + all_exceptions.append(e) + + for fut in async_executor.iterate_futures(): process_future(fut) + for fut in asyncio.all_tasks(): process_future(fut) + + text = f"{list(histogram.items())} {async_executor.task_count()} {finished_tasks} finished {len(all_exceptions)} errors, built/cached {FuzzConfig.radiant_builds}/{FuzzConfig.radiant_cache_hits} tool queries {lapie.run_with_udb_cnt} {int(time.time() - start_time)}s" + + live.update(status_panel(text)) + await asyncio.sleep(1) + except BaseException as e: + logging.warning(f"Shutting down UI due to exception {e}") + traceback.print_exception(e) + raise + finally: + print("Exit ui thread") with Executor() as executor: @@ -340,17 +339,26 @@ def process_future(fut): all_exceptions = [] - task = asyncio.create_task(f(async_executor)) - ui_task = asyncio.create_task(ui(async_executor)) + task = asyncio.create_task(f(async_executor, *args, **kwargs)) + ui_task = ui(async_executor, task) - await asyncio.gather(task, ui_task) + (_, task_result) = await asyncio.gather(ui_task, task, return_exceptions=False) - logging.info("UI and main task finished") + logging.info(f"UI and main task finished {task_result}") - except CancelledError: + except CancelledError as e: logging.warning("Cancelling all executor jobs") + traceback.print_exception(e) executor.shutdown(wait=False, cancel_futures=True) raise + except BaseException as e: + logging.warning(f"Shutting down executor due to exception {e}") + traceback.print_exception(e) + executor.shutdown(wait=True, cancel_futures=True) + raise + finally: + logging.info("Shutting down threads") + logging.info("Shut down threads") except KeyboardInterrupt: logging.warning("Keyboard interrupt") @@ -373,4 +381,4 @@ def FuzzerMain(f): async def async_main(executor): return f(executor) - FuzzerAsyncMain(async_main) \ No newline at end of file + FuzzerAsyncMain(async_main) diff --git a/util/fuzz/interconnect.py b/util/fuzz/interconnect.py index 33cc57d..c1769d1 100644 --- a/util/fuzz/interconnect.py +++ b/util/fuzz/interconnect.py @@ -1,84 +1,49 @@ """ Utilities for fuzzing interconect """ +import asyncio import logging -import threading -from concurrent.futures.thread import ThreadPoolExecutor -from pathlib import Path +import os +import random +import re +from collections import defaultdict +from functools import cache +from logging.handlers import RotatingFileHandler -import tiles +import cachecontrol +import lapie import libpyprjoxide +import tiles + +import database import fuzzconfig import fuzzloops -import lapie -import database -import os -import math -import tempfile -from os import path -import heapq -import bisect -import re +from DesignFileBuilder import get_wires_delta, DesignsForPips, BitConflictException -from collections import defaultdict -workdir = tempfile.mkdtemp() - -def create_wires_file(config, wires, prefix = "", empty_version = False, executor = None): - if empty_version: - prefix = prefix + "_empty" - if isinstance(wires, set): - wires = sorted(wires) - - if isinstance(wires, list): - - touched_tiles = set([tiles.get_rc_from_name(config.device, n) for w in wires for n in w]) - - slice_sites = tiles.get_tiles_by_tiletype(config.device, "PLC") - slice_iter = iter([x for x in slice_sites if tiles.get_rc_from_name(config.device, x) not in touched_tiles]) - - - if empty_version: - wires = "\n".join([f""" -wire q_{idx}; -(* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) -SLICE SLICE_I_{idx} ( .A0(q_{idx}), .Q0(q_{idx}) ); - """ for idx, (frm, to) in enumerate(sorted(wires))]) - else: - wires = "\n".join([f""" -(* keep = "true", dont_touch = "true", keep, dont_touch,\\xref:LOG ="q_c@0@0", \\dm:arcs ="{to}.{frm}" *) -wire q_{idx}; - -(* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) -SLICE SLICE_I_{idx} ( .A0(q_{idx}), .Q0(q_{idx}) ); - - """ for idx, (frm, to) in enumerate(sorted(wires))]) - - subst = config.subst_defaults() - arch = config.device.split("-")[0] - device = config.device - package = subst["package"] - speed_grade = subst["speed_grade"] - - source = f"""\ -(* \\db:architecture ="{arch}", \\db:device ="{device}", \\db:package ="{package}", \\db:speed ="{speed_grade}_High-Performance_1.0V", \\db:timestamp = 0, \\db:view ="physical" *) -module top ( -); -{wires} - (* \\xref:LOG ="q_c@0@0" *) - VHI vhi_i(); -endmodule - """ - - vfile = path.join(workdir, f"{prefix}{config.job}.v") - Path(vfile).parent.mkdir(parents=True, exist_ok=True) +def make_dict_of_lists(lst, key = None): + if key is None: + key = lambda x: x[0] - with open(vfile, 'w') as f: - f.write(source) + rtn = defaultdict(list) + for item in lst: + rtn[key(item)].append(item) + return rtn - if executor is not None: - return config.build_design_future(executor, vfile, prefix=prefix) - return config.build_design(vfile, prefix=prefix) +def setup_size_rotating_log(log_file): + logger = logging.getLogger("SizeRotatingLog") + + # Rotate when file reaches 1MB (1024*1024 bytes), keep 5 backups + logger.addHandler(RotatingFileHandler( + log_file, + maxBytes=1024 * 1024 * 64, + backupCount=1 + )) + logger.propagate = False + logger.setLevel("INFO") + return logger + +transaction_log = setup_size_rotating_log(database.get_db_root() + "/db_transaction.log") def pips_to_sinks(pips): sinks = {} @@ -129,7 +94,6 @@ def collect_sinks(config, nodenames, regex = False, if len(fuzz_pips) == 0: logging.warning(f"No fuzz_pips defined for job {config}. Nodes: {nodes} {all_pips}") return {} - logging.debug(f"Fuzz pips {len(fuzz_pips)}") return pips_to_sinks(fuzz_pips) @@ -151,29 +115,37 @@ def fuzz_interconnect_sinks( base_bitf_future = config.build_design_future(executor, config.sv, extra_substs, "base_") - logging.info(f"Processing {len(sinks)} sinks for {sum([len(v) for k,v in sinks.items()])} designs for {config.job} {config.device}") - assert(len(config.tiles) > 0) def process_bits(bitstreams, from_wires, to_wire): base_bitf = bitstreams[0] bitstreams = bitstreams[1:] - db = fuzzconfig.get_db() - fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf, set(config.tiles), to_wire, - config.tiles[0], - set(ignore_tiles), full_mux_style, not (fc_filter(to_wire))) + with fuzzconfig.db_lock() as db: + fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf, set(config.tiles), to_wire, + config.tiles[0], + set(ignore_tiles), full_mux_style, not (fc_filter(to_wire))) - for (from_wire, arc_bit) in zip(from_wires, bitstreams): - fz.add_pip_sample(db, from_wire, arc_bit if arc_bit is not None else base_bitf) + pip_samples = [(from_wire, arc_bit if arc_bit is not None else base_bitf) for (from_wire, arc_bit) in zip(from_wires, bitstreams)] + fz.add_pip_samples(db, pip_samples) - logging.debug(f"Solving for {to_wire}") - config.solve(fz) + logging.debug(f"Solving for {to_wire}") + config.solve(fz, db) conns = tiles.get_connections_for_device(config.device) - futures = [] + connection_pips = [] + for to_wire in sinks: + connection_pips.extend([(frm, to_wire) for frm in sinks[to_wire] if (frm, to_wire) in connection_pips]) + sinks[to_wire] = [frm for frm in sinks[to_wire] if (frm, to_wire) not in connection_pips] + if len(sinks[to_wire]) == 0: + sinks.pop(to_wire) + + logging.info(f"Processing {len(sinks)} sinks for {sum([len(v) for k,v in sinks.items()])} designs for {config.job} {config.device}") + with fuzzloops.Executor(executor) as executor: + futures = [] + for to_wire in sinks: if config.check_deltas(to_wire): continue @@ -196,6 +168,8 @@ def process_bits(bitstreams, from_wires, to_wire): futures.append(fuzzloops.chain(bitstream_futures, "Interconnect sink", process_bits, sinks[to_wire], to_wire)) + futures.append(executor.submit(register_tile_connections, config.device, config.tiles[0].split(":")[-1], config.tiles[0], connection_pips)) + return futures def fuzz_interconnect( @@ -249,8 +223,8 @@ def fuzz_interconnect_for_tiletype(device, tiletype): nodes = tiles.get_connected_nodes(device, prototype) connected_tiles = tiles.get_connected_tiles(device, prototype) - - cfg = fuzzconfig.FuzzConfig(job=f"interconnect_{tiletype}", device=device, tiles=[prototype]) + + cfg = fuzzconfig.FuzzConfig(job=f"interconnect_{tiletype}", device=device, tiles=[prototype]) #fuzz_interconnect(config=cfg, nodenames=nodes, bidir=True) return collect_sinks(cfg, nodes, bidir=True) @@ -272,26 +246,427 @@ def per_pip(pin_info, pin_pip): is_output = pin_info['pin_node'] == pin_pip.from_wire prefix = "{}_{}_{}_".format(config.job, config.device, to_wire) - db = fuzzconfig.get_db() - fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf, - set(config.tiles), - to_wire, - config.tiles[0], set(), full_mux_style, not (fc_filter(to_wire))) - - arcs_attr = r', \dm:arcs ="{}.{}"'.format(to_wire, from_wire) - substs = extra_substs.copy() - substs["pin_name"] = pin_name - substs["target"] = ".A0(q)" if is_output else ".Q0(q),.A0(q)" - substs["arcs_attr"] = arcs_attr - - print(f"Building design for ({config.job} {config.device}) {to_wire} to {from_wire}") - arc_bit = config.build_design(config.sv, substs, prefix) - fz.add_pip_sample(db, from_wire, arc_bit) - - config.solve(fz) + + with fuzzconfig.db_lock() as db: + fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf, + set(config.tiles), + to_wire, + config.tiles[0], set(), full_mux_style, not (fc_filter(to_wire))) + + arcs_attr = r', \dm:arcs ="{}.{}"'.format(to_wire, from_wire) + substs = extra_substs.copy() + substs["pin_name"] = pin_name + substs["target"] = ".A0(q)" if is_output else ".Q0(q),.A0(q)" + substs["arcs_attr"] = arcs_attr + + print(f"Building design for ({config.job} {config.device}) {to_wire} to {from_wire}") + arc_bit = config.build_design(config.sv, substs, prefix) + fz.add_pip_sample(db, from_wire, arc_bit) + + config.solve(fz, db) for p, pnode in pins: assert(len(pnode.pips()) == 1) per_pip(p, pnode.pips()[0]) - +# Cache this so we only do it once. Could also probably read the ron file and check it. +@cachecontrol.cache_fn() +def register_tile_connections(device, tiletype, tile, conn_pips): + with fuzzconfig.db_lock() as db: + db = libpyprjoxide.Database(database.get_db_root()) + family = device.split("-")[0] + + chip = libpyprjoxide.Chip(db, device) + + normalized_connections = [(chip.normalize_wire(tile, p[0]), chip.normalize_wire(tile, p[1])) for p in + set(conn_pips)] + db.add_conns(family, tiletype, normalized_connections) + db.flush() + +async def fuzz_interconnect_across_span( + config, + tile_span, + nodenames, + regex=False, + nodename_predicate=lambda x, nets: True, + pip_predicate=lambda x, nets: True, + bidir=False, + nodename_filter_union=False, + full_mux_style=False, + max_per_design = None, + exclusion_list = [], + executor = None): + + sinks = collect_sinks(config, nodenames, regex = regex, + nodename_predicate = nodename_predicate, + pip_predicate = pip_predicate, + bidir=bidir, + nodename_filter_union=nodename_filter_union) + + pips = [(frm, to) for to, froms in sinks.items() for frm in froms] + + await fuzz_interconnect_sinks_across_span( + config, tile_span, pips, + max_per_design=max_per_design, + full_mux_style=full_mux_style, + exclusion_list=exclusion_list, + executor=executor + ) + +def generate_mux_deltas(device_tiles, to_wire, from_wire_deltas, remove_constants = False): + if isinstance(device_tiles, str): + device_tiles = tiles.TilesHelper(device_tiles) + + device = device_tiles.device + chip = device_tiles.chip() + + consistent_delta = None + base_delta = None + for (from_wire, (rel_tile, delta)) in from_wire_deltas.items(): + from_deltas = { + (device_tiles.make_tile_anon(tile, rel_tile), frame_diff) + for tile, frame_diffs in delta.items() + for frame_diff in frame_diffs + } + if consistent_delta is None: + consistent_delta = from_deltas + base_delta = from_deltas + else: + consistent_delta = consistent_delta & from_deltas + base_delta = base_delta | from_deltas + + if not remove_constants: + consistent_delta = set() + + logging.debug(f"Generate mux base {base_delta} consistent {consistent_delta}") + base_delta = base_delta - consistent_delta + + if len(from_wire_deltas) == 0: + from_wire, (rel_tile, delta) = next(from_wire_deltas.items()) + + nfrom_wire = tiles.resolve_actual_node(device, from_wire, rel_tile) + nto_wire = tiles.resolve_actual_node(device, to_wire, rel_tile) + + yield rel_tile, nfrom_wire, nto_wire, set() + + + for (from_wire, (rel_tile, delta)) in from_wire_deltas.items(): + from_deltas = { + (device_tiles.make_tile_anon(tile, rel_tile), frame_diff) + for (tile, frame_diffs) in delta.items() + for frame_diff in frame_diffs + } - consistent_delta + + coverage_delta = from_deltas & base_delta + inverted_delta = {(tile, (f, b, not s)) for (tile, (f, b, s)) in (base_delta - from_deltas)} + new_deltas = defaultdict(list) + + for (tile, delta) in (coverage_delta | inverted_delta): + new_deltas[tile].append(delta) + + for (tile, delta) in new_deltas.items(): + nfrom_wire = tiles.resolve_actual_node(device, from_wire, rel_tile) + nto_wire = tiles.resolve_actual_node(device, to_wire, rel_tile) + + norm_from_wire, norm_to_wire = chip.normalize_wire(rel_tile.split(",")[0], nfrom_wire), \ + chip.normalize_wire(rel_tile.split(",")[0], nto_wire) + + logging.debug(f"Adding mux pip {tile} {nfrom_wire} -> {nto_wire} {delta}") + transaction_log.info( + f"add_pip {device} {tile}: {nfrom_wire} -> {nto_wire} {norm_from_wire} -> {norm_to_wire} Bits: {delta}") + concrete_tile = next(iter(device_tiles.resolve_anon_tile(tile, rel_tile)), None) + + yield concrete_tile, nfrom_wire, nto_wire, set(delta) + +async def fuzz_interconnect_sinks_across_span( + config, + tile_span, + pips, + full_mux_style=False, + max_per_design=None, + exclusion_list=[], + executor=None, + overlay = None, + check_pip_placement = True, + builder = None +): + + nodes = set([w for p in pips for w in p]) + + nodeinfos = {n.name:n for n in lapie.get_node_data(config.device, nodes)} + if builder is not None: builder.reserve(1) + + device = config.device + device_tiles = tiles.TilesHelper(device) + + @cache + def get_node_info(x): + if x in nodeinfos: + return nodeinfos[x] + else: + db_node_info = lapie.get_node_data(config.device, x, skip_missing = True) + # Entry appears normally in database + if len(db_node_info) > 0 and db_node_info[0] is not None: + return db_node_info[0] + + # Entry doesn't match as an existing node from the full node list + if len(db_node_info) == 0: + return None + + if x.split("_")[-1][0] in "VH": + # Precache + logging.info(f"Get info {x}") + lapie.get_node_data(config.device, x.split("_")[0] + "_[VH].*", True) + + db_node_info = lapie.get_node_data(config.device, x) + if len(db_node_info) > 0: + return db_node_info[0] + logging.warning("No node info found for {}".format(x)) + return None + + def make_anon_node(rc, p): + return tiles.resolve_relative_node(device, p, rc) + + def fix_name(w): + (wire_type, rc, *args) = tiles.resolve_relative_node(device, w) + + if wire_type in "NEWS": + names = list(tiles.resolve_possible_names(device, w)) + #logging.info(f"Names {names} {w} {rc}") + return names[(len(names) - 1) //2] + + return w + + local_rng = random.Random(os.environ.get("OXIDE_RANDOM_SEED", 42)) + representative_tile = local_rng.choice(config.tiles) + + tiletype_or_overlay = representative_tile.split(":")[-1] if overlay is None else overlay + representative_tile_rc = rc = (r, c) = tiles.get_rc_from_name(device, representative_tile) + + is_anon_pips = not isinstance(next(iter(pips))[0], str) + + def make_tile_anon(tile, rel_to): + return device_tiles.make_tile_anon(tile, rel_to) + + def make_anon_pip(rc, p): + return tuple([make_anon_node(rc, w) for w in p]) + + if is_anon_pips: + anon_pips = sorted(set([tuple([w for w in p]) for p in pips])) + pips = [tuple(tiles.resolve_actual_node(device, n, (r, c)) for n in pip) for pip in anon_pips] + else: + anon_pips = sorted(set([make_anon_pip(rc, p) for p in pips])) + + modified_tiles_rcs_anon = [] + for tt, tt_tiles in sorted(make_dict_of_lists(config.tiles, lambda t: t.split(":")[-1]).items()): + exemplar_tile = local_rng.choice(sorted(tt_tiles)) + exemplar_tile_rc = tiles.get_rc_from_name(device, exemplar_tile) + exemplar_pips = [tuple(tiles.resolve_actual_node(device, n, exemplar_tile_rc) for n in pip) for pip in anon_pips] + sinks = {k:list(v) for k,v in sorted(make_dict_of_lists(exemplar_pips, lambda p: p[1]).items())} + exemplar_designs = [] + while len(sinks): + design_pips = [] + for to_wire, pips_for_to in sorted(sinks.items()): + design_pips.append(pips_for_to.pop()) + exemplar_designs.append(design_pips) + sinks = {k: v for k, v in sinks.items() if len(v) > 0} + + exemplar_bitstream_infos = [] + filtered_deltas = defaultdict(list) + + for f in asyncio.as_completed([ + asyncio.wrap_future( + get_wires_delta(config.device, exemplar_design, prefix=f"{exemplar_tile}/{tiletype_or_overlay}/{idx+1}_of_{len(exemplar_designs)}",executor=executor,with_bitstream_info=True,job_name=f"build-eval-design {device}") + ) + for idx, exemplar_design in enumerate(exemplar_designs) + ]): + design_deltas, _, exemplar_bitstream_info = await f + + exemplar_bitstream_infos.append(exemplar_bitstream_info) + for k,v in design_deltas.items(): + filtered_deltas[k].extend(v) + + modified_tiles_rcs_anon.extend([make_tile_anon(tile, exemplar_tile_rc) for tile in filtered_deltas.keys()]) + + # PIPs are often controlled by nearby tiles. Convert those to relative positioned tiles. Since sometimes two tiles + # will share an RC, we grab the tiletype too. + + connected_arcs = lapie.get_jump_wires_by_nodes(config.device, nodes) + + pips = set(pips) - set(connected_arcs) + + connected_arcs = [p for p in connected_arcs + if any([tiles.get_rc_from_name(device, w) == rc for w in p])] + + register_tile_connections(config.device, tiletype_or_overlay, representative_tile, sorted(connected_arcs)) + + chip = fuzzconfig.FuzzConfig.standard_chip(device) + + tile_suffix = "" if overlay is None else ",overlay/" + overlay + + if len(modified_tiles_rcs_anon) == 0: + if len(pips) > 0: + for (from_wire, to_wire) in pips: + nfrom_wire = fix_name(from_wire) + nto_wire = fix_name(to_wire) + + with fuzzconfig.db_lock() as db: + logging.debug(f"Adding pip {representative_tile} {nfrom_wire} -> {nto_wire} for empty set") + db.add_pip(chip, representative_tile + tile_suffix, nfrom_wire, nto_wire, set()) + else: + logging.warning(f"No modified tiles for {representative_tile} running no designs for interconnect.") + + if builder is not None: builder.unreserve(1) + return + + if any(map(lambda x: callable(x) or x[1] != (0, 0), modified_tiles_rcs_anon)): + logging.info(f"Modified tiles {modified_tiles_rcs_anon} {anon_pips[:5]}") + + rcs_for_tiles_of_tiletype = sorted([(tile, tiles.get_rc_from_name(device, tile)) for tile in tile_span]) + + design_sets = [] + + orig_anon_pips = [p for p in anon_pips] + + # Shuffle based on RNG seed. If several seeds do not demonstrate a bit conflict it indicates less chance of bugs + shuffled_rcs_for_tiles_of_tiletype = [x for x in rcs_for_tiles_of_tiletype] + local_rng.shuffle(shuffled_rcs_for_tiles_of_tiletype) + + design_sets = [d async for d in DesignsForPips(device_tiles, + anon_pips, + shuffled_rcs_for_tiles_of_tiletype, + modified_tiles_rcs_anon)] + + logging.info(f"Found {len(orig_anon_pips)} standard pips total; results in {len(design_sets)} designs over {len(rcs_for_tiles_of_tiletype)} tiles with {representative_tile} ({tiletype_or_overlay}) as prototype") + solve_tiletype_or_overlay = tiletype_or_overlay + + mux_deltas = defaultdict(dict) + empty_deltas = dict() + anon_pip_delta_tiles = defaultdict(list) + + async def process_design(idx, design_set): + design_set_no_nulls = {k:v for k,v in design_set.items() if v is not None} + pips = [tuple(pip_rcs[0]) for tile, pip_rcs in design_set_no_nulls.items()] + assert(len(pips) == len(set(pips))) + + prefix = f"{solve_tiletype_or_overlay}/{idx+1}_of_{len(design_sets)}_{len(design_set_no_nulls)}_pips/" + + if builder is None: + deltas, _, bitstream = await asyncio.wrap_future(get_wires_delta(config.device, pips, prefix=prefix, executor=executor, with_bitstream_info=True, job_name=f'build-compare-design {device}')) + else: + deltas, bitstream = await builder.build_design({k:v[0] if v is not None else None for k,v in design_set.items()}) + + # We should never have deltas appearing where design_set doesnt have entries + unexpected_deltas = set(deltas.keys()) - set(design_set.keys()) + if (len(unexpected_deltas) > 0): + logging.error(f"Got unexpected deltas from {bitstream.vfiles}: Deltas: {unexpected_deltas}. Design: {design_set} Exemplar: {[i.vfiles for i in exemplar_bitstream_infos]} {filtered_deltas} modified_tiles {modified_tiles_rcs_anon}") + assert(len(unexpected_deltas) == 0) + + rc_deltas = defaultdict(list) + for k, v in deltas.items(): + rc_deltas[tiles.get_rc_from_name(device, k)].append((k,v)) + + for tile, (pip, all_touched_coords) in design_set_no_nulls.items(): + tiletype_or_overlay = tile.split(":")[1] + + if pip is not None: + owned_tiles_for_tiletype = { + tile: delta + for (r,c) in all_touched_coords + for tile,delta in rc_deltas.get((r, c), []) + } + + (from_wire, to_wire) = pip + + wire_is_mux = \ + ("MUXOUT" in to_wire or \ + "CMUX_CORE_CMUX" in to_wire or \ + to_wire.endswith("MIDMUX") or \ + ("HPBX" in to_wire and "VPSX" in from_wire)) + + anon_pip = make_anon_pip(tile, pip) + + if (tiletype_or_overlay, from_wire, to_wire) not in exclusion_list: + nfrom_wire = fix_name(from_wire) + nto_wire = fix_name(to_wire) + + if not wire_is_mux: + try: + with (fuzzconfig.db_lock() as db): + def add_pip(changed_tile, delta): + norm_from_wire, norm_to_wire = chip.normalize_wire(changed_tile.split(",")[0], nfrom_wire), \ + chip.normalize_wire(changed_tile.split(",")[0], nto_wire) + + logging.debug(f"Adding pip {changed_tile}({tile}) {nfrom_wire} -> {nto_wire} {delta} from {prefix}") + transaction_log.info(f"{bitstream.vfiles} add_pip {device} {changed_tile}({tile}): {nfrom_wire} -> {nto_wire} {norm_from_wire} -> {norm_to_wire} Bits: {delta}") + if changed_tile == tile: + changed_tile = changed_tile + tile_suffix + db.add_pip(chip, changed_tile, nfrom_wire, nto_wire, set(delta)) + + if tile not in owned_tiles_for_tiletype and len(owned_tiles_for_tiletype) > 0: + logging.warning(f"Primary tile {tile} for {pip} {anon_pip} not in mod list: {owned_tiles_for_tiletype}") + + for delta_tile, delta in owned_tiles_for_tiletype.items(): + anon_pip_delta_tiles[anon_pip].append((delta_tile, delta)) + add_pip(delta_tile, delta) + + if len(owned_tiles_for_tiletype) == 0: + empty_deltas[anon_pip] = (pip, tile) + + except BaseException as e: + raise BitConflictException(device, nfrom_wire, nto_wire, tile, e) + + else: + # Very niche carve out. When you enable a MUXINA wire to something without enabling any other + # wires in that mux group, radiant enables all of the MUXIND's by default on the unused arcs. So + # to correct for this, we just incorporate the knowledge that really those MUXINA connections are + # otherwise defaults (no bit changes) + if "MUXINA" in from_wire: + owned_tiles_for_tiletype = {k:[] for k in owned_tiles_for_tiletype} + mux_deltas[make_anon_node(tile, to_wire)][make_anon_node(tile, from_wire)] = (tile, owned_tiles_for_tiletype) + + if builder is not None: builder.reserve(len(design_sets) - 1) + + await asyncio.gather(*[ + asyncio.create_task(process_design(idx, design_set), name=f"{asyncio.current_task().get_name()}/process_design_{idx}") + for idx, (design_set) in enumerate(design_sets) + ]) + + with (fuzzconfig.db_lock() as db): + for anon_pip, ((from_wire, to_wire), tile) in empty_deltas.items(): + if anon_pip in anon_pip_delta_tiles: + nfrom_wire = fix_name(from_wire) + nto_wire = fix_name(to_wire) + (delta_tile, delta) = anon_pip_delta_tiles[0] + if delta_tile == tile: + delta_tile = delta_tile + tile_suffix + db.add_pip(chip, delta_tile, nfrom_wire, nto_wire, set()) + + for to_wire, from_wire_deltas in mux_deltas.items(): + (rel_tile, delta) = list(from_wire_deltas.values())[0] + for concrete_tile, nfrom_wire, nto_wire, delta in generate_mux_deltas(device_tiles, to_wire, from_wire_deltas): + norm_from_wire, norm_to_wire = chip.normalize_wire(rel_tile.split(",")[0], nfrom_wire), \ + chip.normalize_wire(rel_tile.split(",")[0], nto_wire) + + logging.debug(f"Adding mux pip {rel_tile} {nfrom_wire} -> {nto_wire} {delta}") + transaction_log.info( + f"add_pip {device} {rel_tile}: {nfrom_wire} -> {nto_wire} {norm_from_wire} -> {norm_to_wire} Bits: {delta}") + + if concrete_tile is None: + logging.error(f"Could not resolve concrete tile for {rel_tile}") + assert (concrete_tile is not None) + + if concrete_tile == rel_tile: + concrete_tile = concrete_tile + tile_suffix + + try: + db.add_pip(chip, concrete_tile, nfrom_wire, nto_wire, set(delta)) + + except BaseException as e: + raise BitConflictException(device, nfrom_wire, nto_wire, concrete_tile, e) + + + + with fuzzconfig.db_lock() as db: + db.flush() + diff --git a/util/fuzz/nonrouting.py b/util/fuzz/nonrouting.py index f95dddb..a647241 100644 --- a/util/fuzz/nonrouting.py +++ b/util/fuzz/nonrouting.py @@ -44,13 +44,13 @@ def fuzz_word_setting(config, name, length, get_sv_substs, desc="", executor = N def integrate_bitstreams(bitstreams): baseline = bitstreams[0] bitstreams = bitstreams[1:] - db = fuzzconfig.get_db() - fz = libpyprjoxide.Fuzzer.word_fuzzer(db, baseline, set(config.tiles), name, desc, length, - baseline) - for i in range(length): - fz.add_word_sample(db, i, bitstreams[i]) + with fuzzconfig.db_lock() as db: + fz = libpyprjoxide.Fuzzer.word_fuzzer(db, baseline, set(config.tiles), name, desc, length, + baseline) + for i in range(length): + fz.add_word_sample(db, i, bitstreams[i]) - config.solve(fz) + config.solve(fz, db) return [*bitstream_futures, fuzzloops.chain([baseline, *bitstream_futures], "Solve word", integrate_bitstreams)] @@ -102,12 +102,12 @@ def integrate_build(subs, prefix, opt_name): future.name = "Build design" def integrate_bitstreams(bitstreams): - db = fuzzconfig.get_db() - fz = libpyprjoxide.Fuzzer.enum_fuzzer(db, empty_bitfile, set(config.tiles), name, desc, - include_zeros, assume_zero_base, mark_relative_to=mark_relative_to) - for (opt, bitstream) in bitstreams: - fz.add_enum_sample(db, opt, bitstream) - config.solve(fz) + with fuzzconfig.db_lock() as db: + fz = libpyprjoxide.Fuzzer.enum_fuzzer(db, empty_bitfile, set(config.tiles), name, desc, + include_zeros, assume_zero_base, mark_relative_to=mark_relative_to) + for (opt, bitstream) in bitstreams: + fz.add_enum_sample(db, opt, bitstream) + config.solve(fz, db) return fuzzloops.chain(futures, "Enum Setting", integrate_bitstreams ) @@ -146,12 +146,12 @@ def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=N def integrate_bitstreams(bitstreams): baseline = bitstreams[0] ipcore, iptype = config.tiles[0].split(":") - db = fuzzconfig.get_db() - fz = libpyprjoxide.IPFuzzer.word_fuzzer(db, baseline, ipcore, iptype, name, desc, length, inverted_mode) - for (i, bitfile) in enumerate(bitstreams[1:]): - bits = [(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)] - fz.add_word_sample(db, bits, bitfile) - config.solve(fz) + with fuzzconfig.db_lock() as db: + fz = libpyprjoxide.IPFuzzer.word_fuzzer(db, baseline, ipcore, iptype, name, desc, length, inverted_mode) + for (i, bitfile) in enumerate(bitstreams[1:]): + bits = [(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)] + fz.add_word_sample(db, bits, bitfile) + config.solve(fz, db) return [*bitstream_futures, fuzzloops.chain([baseline_future, *bitstream_futures], "Solve IP word", integrate_bitstreams)] @@ -180,11 +180,11 @@ def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, des ] def integrate_bitstreams(bitstreams): - db = fuzzconfig.get_db() - fz = libpyprjoxide.IPFuzzer.enum_fuzzer(db, empty_bitfile, ipcore, iptype, name, desc) - for (opt, bitfile) in zip(values, bitstreams): - fz.add_enum_sample(db, opt, bitfile) - config.solve(fz) + with fuzzconfig.db_lock() as db: + fz = libpyprjoxide.IPFuzzer.enum_fuzzer(db, empty_bitfile, ipcore, iptype, name, desc) + for (opt, bitfile) in zip(values, bitstreams): + fz.add_enum_sample(db, opt, bitfile) + config.solve(fz, db) return [*bitstream_futures, fuzzloops.chain(bitstream_futures, "Solve IP enum", integrate_bitstreams)] diff --git a/util/fuzz/primitives.py b/util/fuzz/primitives.py index 2579f71..cfe898c 100644 --- a/util/fuzz/primitives.py +++ b/util/fuzz/primitives.py @@ -10,6 +10,9 @@ def __init__(self, name, desc, depth=3, enable_value=None): self.name = name self.desc = desc self.depth = depth + + # The enable value for a setting is the one that is required or sufficient to turn the primitive on in the bit + # stream. None means the values of this setting have no enable effect on the primitive mode. self.enable_value = enable_value def format(self, prim, value): From 51627cea674a7d1a1ce4d209396719e1344d3931 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Sat, 14 Feb 2026 07:22:51 -0700 Subject: [PATCH 21/29] Reorg to use the standard pip/overlay generator --- fuzzers/000-build-pip-overlays/fuzzer.py | 180 +++++++++++++ fuzzers/LIFCL/001-plc-routing/fuzzer.py | 34 --- fuzzers/LIFCL/002-cib-routing/fuzzer.py | 57 ---- fuzzers/LIFCL/015-local-routes/fuzzer.py | 303 ---------------------- fuzzers/LIFCL/016-site-mappings/fuzzer.py | 54 ++-- fuzzers/LIFCL/900-always-on/fuzzer.py | 10 +- 6 files changed, 207 insertions(+), 431 deletions(-) create mode 100644 fuzzers/000-build-pip-overlays/fuzzer.py delete mode 100644 fuzzers/LIFCL/001-plc-routing/fuzzer.py delete mode 100644 fuzzers/LIFCL/002-cib-routing/fuzzer.py delete mode 100644 fuzzers/LIFCL/015-local-routes/fuzzer.py diff --git a/fuzzers/000-build-pip-overlays/fuzzer.py b/fuzzers/000-build-pip-overlays/fuzzer.py new file mode 100644 index 0000000..160fe46 --- /dev/null +++ b/fuzzers/000-build-pip-overlays/fuzzer.py @@ -0,0 +1,180 @@ +import hashlib +import itertools +import json +import logging +import pprint +import traceback +from functools import cache + +from fuzzconfig import FuzzConfig +import interconnect +import re +import database +import tiles +import random +import fuzzloops +import fuzzconfig +import lapie +import libpyprjoxide +import asyncio +from collections import defaultdict + +from DesignFileBuilder import UnexpectedDeltaException, DesignFileBuilder, BitConflictException + +def stablehash(x): + def set_default(obj): + if isinstance(obj, set): + return sorted(obj) + raise TypeError + + bytes_data = json.dumps(x, sort_keys=True, default=set_default).encode('utf-8') + + hasher = hashlib.new("sha1") + hasher.update(bytes_data) + + return hasher.hexdigest() + +def make_dict_of_lists(lst, key): + rtn = defaultdict(list) + for item in lst: + rtn[key(item)].append(item) + return rtn + + +async def FuzzAsync(executor): + families = database.get_devices()["families"] + devices = [ + device + for family in families + for device in families[family]["devices"] + if fuzzconfig.should_fuzz_platform(device) + ] + + for device in devices: + logging.info(device) + + tiletype = "PLC" + + tilegrid = database.get_tilegrid(device)['tiles'] + + all_tiles = sorted({k for k in tilegrid}) + + # Map of tiles group -> pip grouping + rel_pip_groups = await tiles.get_pip_tile_groupings(device, all_tiles) + + pips_to_tiles = defaultdict(list) + for ts, pips in rel_pip_groups.items(): + for pip in pips: + for t in ts: + pips_to_tiles[pip].append(t) + + rel_pip_groups_by_tiletype = defaultdict(set) + + for pip,ts in pips_to_tiles.items(): + for tile_type, tt_ts in make_dict_of_lists(ts, lambda x: x.split(":")[-1]).items(): + + rel_pip_groups_by_tiletype[tuple(sorted(tt_ts))].add(pip) + + + sorted_groups = sorted(rel_pip_groups.items(), key=lambda x: len(x[0]), reverse=True) + + json_groups = [ + (k, sorted([[", ".join(map(str, w)) for w in p] for p in v])) + for k, v in sorted_groups + ] + def set_default(obj): + if isinstance(obj, set): + return sorted(obj) + raise TypeError + + def pip_is_tiletype_dependent(p): + return True + return any([(w[0].split(":")[0] in ["C"] or w[0].startswith("G:VCC")) + for w in p]) + + dll_core_wire = re.compile(r"^J(CODEI(\d+)_I_DQS_TOP_DLL_CODE_ROUTING_MUX|D[01]_I4_\d)$") + + overlays = {} + for ts, anon_pips in rel_pip_groups_by_tiletype.items(): + assert (len(ts) == len(set(ts))) + assert (len(anon_pips) == len(set(anon_pips))) + for needs_tt, split_anon_pips in make_dict_of_lists(sorted(set(anon_pips)), pip_is_tiletype_dependent).items(): + split_anon_pips = sorted(split_anon_pips) + + if needs_tt: + for tt, grp in make_dict_of_lists(ts, lambda x: x.split(":")[-1]).items(): + grp = sorted(grp) + + overlay_args = [tt] + if tt == "TAP_CIB": + overlay_args.append(device) + + overlays[(tuple(sorted(split_anon_pips)), *overlay_args)] = grp + else: + overlays[(tuple(sorted(split_anon_pips)), )] = sorted(ts) + + + def make_overlay_name(k): + (anon_pips, *args) = k + return "-".join([*args, stablehash(anon_pips)]) + + tiles_to_overlays = {} + for k,lst in overlays.items(): + for item in lst: + if item not in tiles_to_overlays: + tiles_to_overlays[item] = {item.split(":")[-1]} + tiles_to_overlays[item].add("overlay/" + make_overlay_name(k)) + + overlays_to_tiles = defaultdict(set) + for tile,tile_overlays in tiles_to_overlays.items(): + overlays_to_tiles[tuple(sorted(tile_overlays))].add(tile) + + db_sub_dir = database.get_db_subdir(device = device) + with open(f"{db_sub_dir}/overlays.json", "w") as f: + overlay_doc = { + #"overlay_membership": {make_overlay_name(k):sorted(v) for k,v in overlays.items()}, + "overlays": { + stablehash(k): sorted(k) for k in overlays_to_tiles + }, + "tiletypes": { + stablehash(k): sorted(v) for k,v in overlays_to_tiles.items() + } + } + + json.dump(overlay_doc, f, default=set_default, indent=4, sort_keys=True) + + builder = DesignFileBuilder(device, executor) + + async def interconnect_group(overlay_key, ts): + (anon_pips, *args) = overlay_key + overlay = make_overlay_name(overlay_key) + + config = FuzzConfig(job=f"{tiletype}-routes", device=device, tiles=ts) + return await interconnect.fuzz_interconnect_sinks_across_span(config, ts, anon_pips, executor=executor, overlay=overlay, check_pip_placement=False, builder=builder) + + logging.info(f"Overlay count: {len(overlays)}") + + try: + async with asyncio.TaskGroup() as tg: + for k, v in sorted(overlays.items()): + tg.create_task(interconnect_group(k, v), name=f"interconnect_group_{make_overlay_name(k)}") + tg.create_task(builder.build_task()) + except* UnexpectedDeltaException as egrp: + logging.error(f"Caught an exception group for unexpected deltas: {egrp} {egrp.exceptions}") + for e in egrp.exceptions: + await e.find_bad_design(executor) + raise + except* BitConflictException as egrp: + logging.error(f"Caught an exception group for bit conflicts: {egrp} {egrp.exceptions}") + for e in egrp.exceptions: + await e.solve_standalone() + raise + except* BaseException as eg: + logging.error(f"Caught an exception group for base: {eg} {eg.exceptions}") + for e in eg.exceptions: + traceback.print_exception(e) + raise + +if __name__ == "__main__": + fuzzloops.FuzzerAsyncMain(FuzzAsync) + diff --git a/fuzzers/LIFCL/001-plc-routing/fuzzer.py b/fuzzers/LIFCL/001-plc-routing/fuzzer.py deleted file mode 100644 index cafa99b..0000000 --- a/fuzzers/LIFCL/001-plc-routing/fuzzer.py +++ /dev/null @@ -1,34 +0,0 @@ -from fuzzconfig import FuzzConfig -from interconnect import fuzz_interconnect -import re -import tiles - -def run_cfg(device): - tile = list(tiles.get_tiles_by_tiletype(device, "PLC").keys())[0] - (r,c) = tiles.get_rc_from_name(device, tile) - - cfg = FuzzConfig(job=f"PLCROUTE-{device}-{tile}", device=device, sv=f"../shared/route_{device.split('-')[-1]}.v", tiles=[tile]) - - cfg.setup() - - nodes = ["R{}C{}_J.*".format(r, c)] - extra_sources = [] - extra_sources += ["R{}C{}_H02E{:02}01".format(r, c+1, i) for i in range(8)] - extra_sources += ["R{}C{}_H06E{:02}03".format(r, c+3, i) for i in range(4)] - extra_sources += ["R{}C{}_V02N{:02}01".format(r-1, c, i) for i in range(8)] - extra_sources += ["R{}C{}_V06N{:02}03".format(r-3, c, i) for i in range(4)] - extra_sources += ["R{}C{}_V02S{:02}01".format(r+1, c, i) for i in range(8)] - extra_sources += ["R{}C{}_V06S{:02}03".format(r+3, c, i) for i in range(4)] - extra_sources += ["R{}C{}_H02W{:02}01".format(r, c-1, i) for i in range(8)] - extra_sources += ["R{}C{}_H06W{:02}03".format(r, c-3, i) for i in range(4)] - #nodes = [n.name for n in tiles.get_nodes_for_tile(cfg.device, tile)] - #fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=True)#, ignore_tiles=set(["TAP_PLC_R16C14:TAP_PLC"])) - - cfg.job = cfg.job + "-extra-srcs" - fuzz_interconnect(config=cfg, nodenames=extra_sources, regex=True, bidir=False)#, ignore_tiles=set(["TAP_PLC_R16C14:TAP_PLC"])) - -def main(): - run_cfg("LIFCL-40") - -if __name__ == "__main__": - main() diff --git a/fuzzers/LIFCL/002-cib-routing/fuzzer.py b/fuzzers/LIFCL/002-cib-routing/fuzzer.py deleted file mode 100644 index 3d3fffb..0000000 --- a/fuzzers/LIFCL/002-cib-routing/fuzzer.py +++ /dev/null @@ -1,57 +0,0 @@ -import asyncio - -from fuzzconfig import FuzzConfig -from interconnect import fuzz_interconnect -from fuzzloops import FuzzerAsyncMain -import re - -configs = [ - ((1, 18), FuzzConfig(job="CIBTROUTE", device="LIFCL-40", tiles=["CIB_R1C18:CIB_T"]), set(["TAP_CIBT_R1C14:TAP_CIBT"])), - ((18, 1), FuzzConfig(job="CIBLRROUTE", device="LIFCL-40", tiles=["CIB_R18C1:CIB_LR"]), set(["TAP_PLC_R18C14:TAP_PLC"])), - ((28, 17), FuzzConfig(job="CIBROUTE", device="LIFCL-40", tiles=["CIB_R28C17:CIB"]), set(["TAP_CIB_R28C14:TAP_CIB"])), - ((28, 1), FuzzConfig(job="CIBLRAROUTE", device="LIFCL-40", tiles=["CIB_R28C1:CIB_LR_A"]), set(["TAP_CIB_R28C14:TAP_CIB"])), -] - -async def per_config(rc, cfg, ignore, executor): - cfg.setup() - r, c = rc - nodes = ["R{}C{}_J*".format(r, c)] - extra_sources = [] - extra_sources += ["R{}C{}_H02E{:02}01".format(r, c+1, i) for i in range(8)] - extra_sources += ["R{}C{}_H06E{:02}03".format(r, c+3, i) for i in range(4)] - if r != 1: - extra_sources += ["R{}C{}_V02N{:02}01".format(r-1, c, i) for i in range(8)] - extra_sources += ["R{}C{}_V06N{:02}03".format(r-3, c, i) for i in range(4)] - else: - extra_sources += ["R{}C{}_V02N{:02}00".format(r, c, i) for i in range(8)] - extra_sources += ["R{}C{}_V06N{:02}00".format(r, c, i) for i in range(4)] - extra_sources += ["R{}C{}_V02S{:02}01".format(r+1, c, i) for i in range(8)] - extra_sources += ["R{}C{}_V06S{:02}03".format(r+3, c, i) for i in range(4)] - if c != 1: - extra_sources += ["R{}C{}_H02W{:02}01".format(r, c-1, i) for i in range(8)] - extra_sources += ["R{}C{}_H06W{:02}03".format(r, c-3, i) for i in range(4)] - else: - extra_sources += ["R{}C{}_H02W{:02}00".format(r, c, i) for i in range(8)] - extra_sources += ["R{}C{}_H06W{:02}00".format(r, c, i) for i in range(4)] - def pip_filter(pip, nodes): - from_wire, to_wire = pip - return not ("_CORE" in from_wire or "_CORE" in to_wire or "JCIBMUXOUT" in to_wire) - def fc_filter(to_wire): - return "CIBMUX" in to_wire or "CIBTEST" in to_wire or to_wire.startswith("R{}C{}_J".format(r, c)) - - futures = fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=True, ignore_tiles=ignore, - pip_predicate=pip_filter, fc_filter=fc_filter, executor=executor) + \ - fuzz_interconnect(config=cfg, nodenames=extra_sources, regex=False, bidir=False, ignore_tiles=ignore, - pip_predicate=pip_filter, fc_filter=fc_filter, executor=executor) - - await asyncio.gather(*[asyncio.wrap_future(f) for f in futures]) - -async def run(executor): - await asyncio.gather(*[(per_config(*cfg, executor=executor))for cfg in configs]) - - -def main(): - FuzzerAsyncMain(run) - -if __name__ == "__main__": - main() diff --git a/fuzzers/LIFCL/015-local-routes/fuzzer.py b/fuzzers/LIFCL/015-local-routes/fuzzer.py deleted file mode 100644 index 68895a6..0000000 --- a/fuzzers/LIFCL/015-local-routes/fuzzer.py +++ /dev/null @@ -1,303 +0,0 @@ -import asyncio -import logging -import re -import sys -from collections import defaultdict - -import cachecontrol -import fuzzconfig -import fuzzloops -import interconnect -import lapie -import libpyprjoxide -import nonrouting -import primitives -import radiant -import tiles -from fuzzconfig import FuzzConfig, get_db -from interconnect import fuzz_interconnect_sinks - -import database - -### -# The idea for this fuzzer is that we can map out the internal pips and connections for a given tiletype in relatively -# short order without knowing much about them by using the introspection from the radiant tools. The basic process is: -# - Generate a list of wires and pips a given tile type has -# - Figure out which tiles configure these pips -# - Use all the tiles of that tiletype on the board to test. So if there are 16 tiles with that tiletype, we can solve -# for 16 PIP configurations at a time. -### - -processed_tiletypes = set("PLC") - -exclusion_list = { - # I think this particular pip needs other things in SYSIO_B3 to trigger, but when SYSIO_B3_1 is - # driving SYSIO_B3_0_ECLK_L, the pip seems active. Just blacklist this one and accept the bit flip. - ("SYSIO_B3_0", "JECLKIN1_I218", "JECLKOUT_I218") -} - -# Cache this so we only do it once. Could also probably read the ron file and check it. -@cachecontrol.cache_fn() -def register_tile_connections(device, tiletype, tile, conn_pips): - connection_sinks = defaultdict(list) - for (frm, to) in conn_pips: - connection_sinks[to].append(frm) - - db = libpyprjoxide.Database(database.get_db_root()) - family = device.split("-")[0] - for (to_wire, froms) in connection_sinks.items(): - for from_wire in froms: - db.add_conn(family, tiletype, to_wire, from_wire) - db.flush() - -### Gather up all the consistent internal pips for a tiletype, then use however many tiles of that tiletype exist -### to create design sets for each PIP. This also tracks and manages tiles that configure the tiletype but are positioned -### relative to it and have different tiles types -async def get_tiletype_design_sets(device, tiletype, executor = None): - - # representative nodes is all wires that are common to all instances of that tiletype for the device - wires = tiles.get_representative_nodes_for_tiletype(device, tiletype) - - if len(wires) == 0: - logging.debug(f"{tiletype} has no consistent internal wires") - return tiletype, [], [] - - ts = sorted(list(tiles.get_tiles_by_tiletype(device, tiletype).keys())) - - # Treat this as an exemplar node to gather the pips from - (r, c) = tiles.get_rc_from_name(device, ts[0]) - nodes = set([f"R{r}C{c}_{w}" for w in wires]) - pips, tiletype_graph = await asyncio.wrap_future(tiles.get_local_pips_for_nodes(device, nodes, include_interface_pips=False, - should_expand=lambda p: p[0] in nodes and p[1] in nodes, - executor = executor)) - - # Jump wires are what lattice tools refer to as connections -- basically PIPs that are always on - connected_arcs = lapie.get_jump_wires_by_nodes(device, nodes) - - # Remove the jump wires -- no point in including them in our designs, we already know they are connections - conn_pips = set(pips) & connected_arcs - actual_pips = set(pips) - conn_pips - pips = sorted(actual_pips) - - # While we have the list, we might as well mark down that they are connections - register_tile_connections(device, tiletype, ts[0], sorted(conn_pips)) - - anon_pips = sorted(set([tuple(["_".join(w.split("_")[1:]) for w in p]) for p in pips])) - - baseline = FuzzConfig.standard_empty(device) - cfg = FuzzConfig(job=f"find-tile-set-{device}-{tiletype}", device=device) - - bitstream = await asyncio.wrap_future(interconnect.create_wires_file(cfg, pips, executor=executor)) - - deltas, _ =fuzzconfig.find_baseline_differences(device, bitstream) - - # PLC is used to affix the wires, strip those from the delta - filtered_deltas = {k:v for k,v in deltas.items() if k.split(":")[1] != "PLC"} - - # PIPs are often controlled by nearby tiles. Convert those to relative positioned tiles. Since sometimes two tiles - # will share an RC, we grab the tiletype too. - modified_tiles_rcs = set([(tiles.get_rc_from_name(device, n),n.split(":")[-1]) for n in filtered_deltas.keys()]) - modified_tiles_rcs_anon = [((r0-r),(c0-c),tt) for ((r0,c0),tt) in modified_tiles_rcs] - - logging.info(f"{tiletype} has {len(anon_pips)} PIPs for {len(ts)} tiles {len(conn_pips)} connections and {len(nodes)} nodes with {modified_tiles_rcs_anon} modified tiles") - - logging.debug(f"{tiletype} Connections:") - for c in conn_pips: - logging.debug(f" - {c}") - - logging.debug(f"{tiletype} pips:") - for c in anon_pips: - logging.debug(f" - {c}") - - # Either there are no pips or a primitive enables them - if len(modified_tiles_rcs_anon) == 0: - return tiletype, [], [] - - # TAP_PLC's are weird and need to be mapped separately. - if "TAP_PLC" in [tt for (_,_,tt) in modified_tiles_rcs_anon]: - logging.warning(f"Ignoring {tiletype}; {modified_tiles_rcs_anon}") - return tiletype, [], [] - - design_sets = [] - rcs_for_tiles_of_tiletype = sorted([(tile,tiles.get_rc_from_name(device, tile)) for tile in ts]) - - extra_rcs = [((r+rd), (c+cd), tt) - for (_, (r,c)) in rcs_for_tiles_of_tiletype - for (rd,cd,tt) in modified_tiles_rcs_anon] - - # Make sure there isn't overlap between modified tiles for all the tiles of the type. - assert(len(extra_rcs) == len(set(extra_rcs))) - extra_rcs = set(extra_rcs) - - # Generate design sets by continuously iterating through tile locations and putting a random PIP there. - while len(anon_pips): - design_set = {} - - # Just place all the extra tiles. We dont have pips for these tiles but this marks it as used. - for rc in extra_rcs: - for tile in tiles.get_tiles_by_rc(device, rc): - design_set[tile] = None - for (tile, (r,c)) in rcs_for_tiles_of_tiletype: - pip = anon_pips.pop() - pip = [f"R{r}C{c}_{w}" for w in pip] - design_set[tile] = pip - - if len(anon_pips) == 0: - break - - if len(design_set): - design_sets.append(design_set) - - return tiletype, design_sets, modified_tiles_rcs_anon - -def get_filtered_typetypes(device): - tiletypes = tiles.get_tiletypes(device) - for tiletype, ts in sorted(tiletypes.items()): - - if tiletype in ["PLC", "TAP_PLC"]: - continue - - if len(sys.argv) > 1 and re.compile(sys.argv[1]).search(tiletype) is None: - continue - - if tiletype in processed_tiletypes: - continue - processed_tiletypes.add(tiletype) - yield tiletype - -async def run_for_device(device, executor = None): - if not fuzzconfig.should_fuzz_platform(device): - logging.warning(f"Ignoring device {device}") - return [] - - logging.info("Fuzzing device: " + device) - - device_futures = [ - get_tiletype_design_sets(device, tiletype, executor=executor) - for tiletype in get_filtered_typetypes(device) - ] - - # list of list of dicts - logging.info(f"Gathering {len(device_futures)} tile type pips") - all_design_sets = await asyncio.gather(*device_futures) - - owned_rcs = {tt:e for (tt,d,e) in all_design_sets} - - design_sets = [] - while True: - design_set = {} - owners = defaultdict(list) - for (tiletype, designs, extra_rcs) in all_design_sets: - if len(designs) and len(designs[-1].keys() & design_set.keys()) == 0: - owners[tiletype].extend(designs[-1].keys()) - design_set.update(designs.pop()) - - # The original idea here was that the tile types could be combined. However, this - # does seem to trigger some bit changes - break - if len(design_set) == 0: - break - design_sets.append((owners, design_set)) - - logging.info(f"Building {len(design_sets)} designs") - cfg = FuzzConfig(job="all-routing", device=device, tiles=[]) - - diff_designs_futures = [] - empty_file = FuzzConfig.standard_empty(device) - for idx, (owners, design_set) in enumerate(design_sets): - pips = [pip for tile, pip in design_set.items() if pip is not None] - create_bitstream_future = interconnect.create_wires_file(cfg, pips, executor=executor, prefix=f"{idx}/") - diff_designs_futures.append(fuzzloops.chain(create_bitstream_future, "solve_design", lambda x,device=device: fuzzconfig.find_baseline_differences(device, x)[0])) - - all_design_diffs = await asyncio.gather(*[asyncio.wrap_future(f) for f in diff_designs_futures]) - - def anon_pip(p): - return ["_".join(w.split("_")[1:]) for w in p] - - pip_deltas = defaultdict(list) - for (deltas, (owners, design_set)) in zip(all_design_diffs, design_sets): - rc_deltas = {(*tiles.get_rc_from_name(device, k), k.split(":")[-1]):v for k,v in deltas.items()} - - owned_by = {} - for k,ts in owners.items(): - for tile in ts: - owned_by[tile] = k - for tile, pip in design_set.items(): - tiletype = tile.split(":")[1] - if pip is not None: - rc = tiles.get_rc_from_name(device, pip[0]) - tile_owned_rcs = set([ - (orc[0]+rc[0], orc[1]+rc[1], orc[2]) - for orc in owned_rcs[tiletype] - ]) - - owned_tiles_for_tiletype = { - k:rc_deltas.get(k, []) - for k in tile_owned_rcs - } - - rc_tiles_for_tiletype = {(r-rc[0], c-rc[1], tiletype):d for (r,c,tiletype),d in owned_tiles_for_tiletype.items()} - - apip = anon_pip(pip) - pip_deltas[tiletype].append((apip, rc_tiles_for_tiletype)) - - for tiletype, pips_with_deltas in pip_deltas.items(): - sinks = defaultdict(list) - for (pip, deltas) in pips_with_deltas: - sinks[pip[1]].append((pip[0], deltas)) - - all_ts = sorted(list(tiles.get_tiles_by_tiletype(device, tiletype).keys())) - tile = all_ts[0] - ts = [tile] - - logging.debug(f"Solving for {len(sinks)} sinks on {tiletype}; ref tile {tile}") - for to_wire, full_deltas in sinks.items(): - - rc = tiles.get_rc_from_name(device, tile) - rc_prefix = f"R{rc[0]}C{rc[1]}_" - tile_lookup = {} - for from_wire, deltas in full_deltas: - for (r1, c1, tiletype), d in deltas.items(): - for t in tiles.get_tiles_by_rc(device, (r1+rc[0],c1+rc[1])): - if t.split(":")[-1] == tiletype: - tile_lookup[(r1,c1,tiletype)] = t - ts.append(t) - - cfg = FuzzConfig(job=f"{tiletype}/{to_wire}", device=device, tiles=ts) - fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.db, empty_file, set(ts), rc_prefix + to_wire, tile, - set(), "MUX" in to_wire, False) - for from_wire, deltas in full_deltas: - if (tiletype, from_wire, to_wire) in exclusion_list: - continue - - concrete_deltas = {tile_lookup[k]: v for k, v in deltas.items() if len(v)} - logging.debug(f"{tiletype} {from_wire} -> {to_wire} has {len(concrete_deltas)} delta tiles") - fz.add_pip_sample_delta(rc_prefix + from_wire, concrete_deltas) - cfg.solve(fz) - - return [] - -async def run_for_devices(executor): - get_db() - families = database.get_devices()["families"] - devices = sorted([ - device - for family in families - for device in families[family]["devices"] - if fuzzconfig.should_fuzz_platform(device) - ]) - - all_tiletypes = sorted(set([tile.split(":")[-1] - for device in devices - for tile in database.get_tilegrid(device)["tiles"] - ])) - - if len(sys.argv) > 1 and not any(map(lambda tt: re.compile(sys.argv[1]).search(tt), all_tiletypes)): - logging.warning(f"Tiletype filter doesn't match any known tiles") - logging.warning(sorted(all_tiletypes)) - return [] - - return await asyncio.gather(*[run_for_device(device, executor) for device in devices]) - -if __name__ == "__main__": - fuzzloops.FuzzerAsyncMain(run_for_devices) diff --git a/fuzzers/LIFCL/016-site-mappings/fuzzer.py b/fuzzers/LIFCL/016-site-mappings/fuzzer.py index d379bc9..0c07df3 100644 --- a/fuzzers/LIFCL/016-site-mappings/fuzzer.py +++ b/fuzzers/LIFCL/016-site-mappings/fuzzer.py @@ -13,7 +13,7 @@ import primitives import radiant import tiles -from fuzzconfig import FuzzConfig, get_db +from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect_sinks import database @@ -300,10 +300,6 @@ async def per_site(site, site_info, driving_tiles, executor): sites = database.get_sites(device) sites_items = [(k,v) for k,v in sorted(sites.items()) if v["type"] not in ["CIBTEST", "SLICE"]] - sitetypes = {v["type"] for s,v in sites_items} - - await asyncio.wrap_future(lapie.get_node_data(device, sitetypes, True, executor)) - driving_tiles_futures = [] async with asyncio.TaskGroup() as tg: for site, site_info in sites_items: @@ -342,34 +338,28 @@ async def per_site(site, site_info, driving_tiles, executor): mapped_sites.add(site_key) tg.create_task(per_site(site, site_info, (driving_tiles, site_tiles, ip_values), executor)) +async def FuzzAsync(executor): + families = database.get_devices()["families"] + devices = sorted([ + device + for family in families + for device in families[family]["devices"] + if fuzzconfig.should_fuzz_platform(device) + ]) + + all_sites = set([site_info["type"] + for device in devices + if device.startswith("LIFCL") + for site, site_info in database.get_sites(device).items() + ]) + + if len(sys.argv) > 1 and sys.argv[1] not in all_sites: + logging.warning(f"Site filter doesn't match any known sites") + logging.info(sorted(all_sites)) -def main(): - async def run_for_devices(executor): - get_db() - - families = database.get_devices()["families"] - devices = sorted([ - device - for family in families - for device in families[family]["devices"] - if fuzzconfig.should_fuzz_platform(device) - ]) - - all_sites = set([site_info["type"] - for device in devices - if device.startswith("LIFCL") - for site, site_info in database.get_sites(device).items() - ]) - - if len(sys.argv) > 1 and sys.argv[1] not in all_sites: - logging.warning(f"Site filter doesn't match any known sites") - logging.info(sorted(all_sites)) - - return [] - - return await asyncio.gather(*[ run_for_device(device, executor) for device in devices ]) + return [] - fuzzloops.FuzzerAsyncMain(run_for_devices) + return await asyncio.gather(*[ run_for_device(device, executor) for device in devices ]) if __name__ == "__main__": - main() + fuzzloops.FuzzerAsyncMain(FuzzAsync) diff --git a/fuzzers/LIFCL/900-always-on/fuzzer.py b/fuzzers/LIFCL/900-always-on/fuzzer.py index c860a48..a69b5b1 100644 --- a/fuzzers/LIFCL/900-always-on/fuzzer.py +++ b/fuzzers/LIFCL/900-always-on/fuzzer.py @@ -12,11 +12,11 @@ ] def main(): - db = fuzzconfig.get_db() - for cfg in cfgs: - cfg.setup() - empty = cfg.build_design(cfg.sv, {}) - libpyprjoxide.add_always_on_bits(fuzzconfig.db, empty) + with fuzzconfig.db_lock() as db: + for cfg in cfgs: + cfg.setup() + empty = cfg.build_design(cfg.sv, {}) + libpyprjoxide.add_always_on_bits(db, empty) if __name__ == "__main__": main() From dd4f81b1830aece1845019686075f5ffcf518947 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Sun, 15 Feb 2026 07:16:06 -0700 Subject: [PATCH 22/29] Commit before refactor --- fuzzers/LIFCL/016-site-mappings/fuzzer.py | 12 +-- .../LIFCL/{ => unused}/020-plc_tap/fuzzer.py | 0 fuzzers/LIFCL/{ => unused}/021-cmux/fuzzer.py | 0 .../{ => unused}/023-trunk-spine/fuzzer.py | 0 .../LIFCL/{ => unused}/030-io_route/fuzzer.py | 0 .../{ => unused}/061-ebr-routing/fuzzer.py | 0 .../{ => unused}/063-lram-routing/fuzzer.py | 0 .../{ => unused}/081-dsp-routing/fuzzer.py | 0 .../{ => unused}/120-pll-routing/fuzzer.py | 0 .../130-config-ip-routing/fuzzer.py | 0 .../{ => unused}/150-eclkroute/fuzzer.py | 0 .../LIFCL/{ => unused}/152-dqsroute/fuzzer.py | 0 .../160-hard-ip-routing/fuzzer.py | 0 radiant_cmd.sh | 1 + requirements.txt | 1 + tools/bitstreamcache.py | 78 ++++++++++++++----- tools/extract_tilegrid.py | 3 +- tools/reformat_database.py | 20 +++++ 18 files changed, 87 insertions(+), 28 deletions(-) rename fuzzers/LIFCL/{ => unused}/020-plc_tap/fuzzer.py (100%) rename fuzzers/LIFCL/{ => unused}/021-cmux/fuzzer.py (100%) rename fuzzers/LIFCL/{ => unused}/023-trunk-spine/fuzzer.py (100%) rename fuzzers/LIFCL/{ => unused}/030-io_route/fuzzer.py (100%) rename fuzzers/LIFCL/{ => unused}/061-ebr-routing/fuzzer.py (100%) rename fuzzers/LIFCL/{ => unused}/063-lram-routing/fuzzer.py (100%) rename fuzzers/LIFCL/{ => unused}/081-dsp-routing/fuzzer.py (100%) rename fuzzers/LIFCL/{ => unused}/120-pll-routing/fuzzer.py (100%) rename fuzzers/LIFCL/{ => unused}/130-config-ip-routing/fuzzer.py (100%) rename fuzzers/LIFCL/{ => unused}/150-eclkroute/fuzzer.py (100%) rename fuzzers/LIFCL/{ => unused}/152-dqsroute/fuzzer.py (100%) rename fuzzers/LIFCL/{ => unused}/160-hard-ip-routing/fuzzer.py (100%) diff --git a/fuzzers/LIFCL/016-site-mappings/fuzzer.py b/fuzzers/LIFCL/016-site-mappings/fuzzer.py index 0c07df3..769a98e 100644 --- a/fuzzers/LIFCL/016-site-mappings/fuzzer.py +++ b/fuzzers/LIFCL/016-site-mappings/fuzzer.py @@ -61,11 +61,8 @@ async def find_relevant_tiles(device, site, site_type, site_info, executor): nodes = [p["pin_node"] for p in site_info["pins"]] logging.info(f"Getting relevant wire tiles for {device} {site}:{site_type}") - pips, _ = await asyncio.wrap_future( - tiles.get_local_pips_for_nodes(device, nodes, include_interface_pips=True, - should_expand=lambda p: p[0] in nodes or p[1] in nodes, - executor = executor) - ) + pips, _ = tiles.get_local_pips_for_nodes(device, nodes, include_interface_pips=True, + should_expand=lambda p: p[0] in nodes or p[1] in nodes) wires_bitstream = await asyncio.wrap_future(interconnect.create_wires_file(cfg, pips, prefix=f"find-relevant-tiles/", executor = executor)) @@ -287,9 +284,8 @@ async def per_site(site, site_info, driving_tiles, executor): tiletype = driving_tiles[0].split(":")[1] logging.info(f"====== {site} : {tiletype} ==========") - pips, local_graph = await asyncio.wrap_future( - executor.submit(tiles.get_local_pips_for_site, device, site) - ) + pips, local_graph = tiles.get_local_pips_for_site(device, site) + pips_future = list(map_local_pips(site, site_type, device, driving_tiles + site_tiles, pips, local_graph, executor=executor)) # Map primitive parameter settings diff --git a/fuzzers/LIFCL/020-plc_tap/fuzzer.py b/fuzzers/LIFCL/unused/020-plc_tap/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/020-plc_tap/fuzzer.py rename to fuzzers/LIFCL/unused/020-plc_tap/fuzzer.py diff --git a/fuzzers/LIFCL/021-cmux/fuzzer.py b/fuzzers/LIFCL/unused/021-cmux/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/021-cmux/fuzzer.py rename to fuzzers/LIFCL/unused/021-cmux/fuzzer.py diff --git a/fuzzers/LIFCL/023-trunk-spine/fuzzer.py b/fuzzers/LIFCL/unused/023-trunk-spine/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/023-trunk-spine/fuzzer.py rename to fuzzers/LIFCL/unused/023-trunk-spine/fuzzer.py diff --git a/fuzzers/LIFCL/030-io_route/fuzzer.py b/fuzzers/LIFCL/unused/030-io_route/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/030-io_route/fuzzer.py rename to fuzzers/LIFCL/unused/030-io_route/fuzzer.py diff --git a/fuzzers/LIFCL/061-ebr-routing/fuzzer.py b/fuzzers/LIFCL/unused/061-ebr-routing/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/061-ebr-routing/fuzzer.py rename to fuzzers/LIFCL/unused/061-ebr-routing/fuzzer.py diff --git a/fuzzers/LIFCL/063-lram-routing/fuzzer.py b/fuzzers/LIFCL/unused/063-lram-routing/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/063-lram-routing/fuzzer.py rename to fuzzers/LIFCL/unused/063-lram-routing/fuzzer.py diff --git a/fuzzers/LIFCL/081-dsp-routing/fuzzer.py b/fuzzers/LIFCL/unused/081-dsp-routing/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/081-dsp-routing/fuzzer.py rename to fuzzers/LIFCL/unused/081-dsp-routing/fuzzer.py diff --git a/fuzzers/LIFCL/120-pll-routing/fuzzer.py b/fuzzers/LIFCL/unused/120-pll-routing/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/120-pll-routing/fuzzer.py rename to fuzzers/LIFCL/unused/120-pll-routing/fuzzer.py diff --git a/fuzzers/LIFCL/130-config-ip-routing/fuzzer.py b/fuzzers/LIFCL/unused/130-config-ip-routing/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/130-config-ip-routing/fuzzer.py rename to fuzzers/LIFCL/unused/130-config-ip-routing/fuzzer.py diff --git a/fuzzers/LIFCL/150-eclkroute/fuzzer.py b/fuzzers/LIFCL/unused/150-eclkroute/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/150-eclkroute/fuzzer.py rename to fuzzers/LIFCL/unused/150-eclkroute/fuzzer.py diff --git a/fuzzers/LIFCL/152-dqsroute/fuzzer.py b/fuzzers/LIFCL/unused/152-dqsroute/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/152-dqsroute/fuzzer.py rename to fuzzers/LIFCL/unused/152-dqsroute/fuzzer.py diff --git a/fuzzers/LIFCL/160-hard-ip-routing/fuzzer.py b/fuzzers/LIFCL/unused/160-hard-ip-routing/fuzzer.py similarity index 100% rename from fuzzers/LIFCL/160-hard-ip-routing/fuzzer.py rename to fuzzers/LIFCL/unused/160-hard-ip-routing/fuzzer.py diff --git a/radiant_cmd.sh b/radiant_cmd.sh index 6423698..e26ca05 100755 --- a/radiant_cmd.sh +++ b/radiant_cmd.sh @@ -14,4 +14,5 @@ export TCL_LIBRARY="${radiantdir}/tcltk/linux/lib/tcl8.6" export fpgabindir=${FOUNDRY}/bin/lin64 export LD_LIBRARY_PATH="${bindir}:${fpgabindir}" export LM_LICENSE_FILE="${radiantdir}/license/license.dat" +export LSC_SHOW_INTERNAL_ERROR=1 PATH=$FOUNDRY/bin/lin64:$bindir:$PATH exec $* diff --git a/requirements.txt b/requirements.txt index e69de29..a2b6349 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1 @@ +cachier @ git+https://github.com/python-cachier/cachier/@66bd714172b8a31817620881fe706fcaa539a628 \ No newline at end of file diff --git a/tools/bitstreamcache.py b/tools/bitstreamcache.py index 7c15f15..e83a5ac 100755 --- a/tools/bitstreamcache.py +++ b/tools/bitstreamcache.py @@ -22,13 +22,20 @@ """ import logging import sys, os, shutil, hashlib, gzip +import time from logging import exception from pathlib import Path root_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") cache_dir = os.path.join(root_dir, ".bitstreamcache") -def get_hash(device, input_files, env = None): +def get_version_directory(): + radiantdir = os.environ.get("RADIANTDIR", "UNKNOWN") + return Path(radiantdir).name + "-" + hashlib.md5(radiantdir.encode("UTF-8")).hexdigest() + +version_directory = get_version_directory() + +def get_hash_by_contents(device, input_file_contents, env = None): if env is None: env = os.environ @@ -39,31 +46,63 @@ def get_hash(device, input_files, env = None): if envkey in env: hasher.update(envkey.encode('utf-8')) hasher.update(env[envkey].encode('utf-8')) - for fname in input_files: - ext = os.path.splitext(fname)[1] + for fn,contents in input_file_contents.items(): + ext = os.path.splitext(fn)[1] hasher.update("input{}".format(ext).encode('utf-8')) - with open(fname, "rb") as f: - hasher.update(f.read()) - return hasher.hexdigest() + hasher.update(contents) -def fetch(device, input_files, env = None): + # Split into chunks since some file systems don't scale well with giant flat dirs + h = hasher.hexdigest() + h_prefix = h[:2] + h_remaining = h[2:] + logging.debug(f"Hash lookup gave {h}") + return (h_prefix, h_remaining) + +def get_hash(device, input_files, env = None): + input_file_contents = { + fname: open(fname,"rb").read() + for fname in input_files + } + + return get_hash_by_contents(device, input_file_contents, env=env) + +def fetch_by_contents(device, input_file_contents, env = None): if not os.path.exists(cache_dir): return - h = get_hash(device, input_files, env=env) - #print(h) + h = get_hash_by_contents(device, input_file_contents, env=env) + + check_dirs = [os.path.join(cache_dir, version_directory, *h), + os.path.join(cache_dir, "".join(h))] - cache_entry = os.path.join(cache_dir, h) - if not os.path.exists(cache_entry) or len(os.listdir(cache_entry)) < 2: + for cache_entry in check_dirs: + if not os.path.exists(cache_entry) or len(os.listdir(cache_entry)) < 2: + continue + + # Touch the directory and it's contents + now = time.time() + os.utime(cache_entry, (now, now)) + products = os.listdir(cache_entry) + for outprod in products: + gz_path = os.path.join(cache_entry, outprod) + os.utime(gz_path, (now, now)) + + yield (outprod, gz_path) + + if len(products): + return + + +def fetch(device, input_files, env = None): + if not os.path.exists(cache_dir): return - # Touch the directory and it's contents - Path(cache_entry).touch() - for outprod in os.listdir(cache_entry): - gz_path = os.path.join(cache_entry, outprod) - Path(gz_path).touch() + input_file_contents = { + fname:open(fname, "rb").read() + for fname in input_files + } - yield (outprod, gz_path) + return fetch_by_contents(device, input_file_contents, env) def main(): if len(sys.argv) < 2: @@ -109,9 +148,10 @@ def main(): print("Usage: tools/bitstreamcache.py commit output ..") sys.exit(1) h = get_hash(sys.argv[2], sys.argv[3:idx]) - cache_entry = os.path.join(cache_dir, h) + + cache_entry = os.path.join(cache_dir, version_directory, *h) if not os.path.exists(cache_entry): - os.mkdir(cache_entry) + os.makedirs(cache_entry, exist_ok=True) for outprod in sys.argv[idx+1:]: bn = os.path.basename(outprod) cn = os.path.join(cache_entry, bn + ".gz") diff --git a/tools/extract_tilegrid.py b/tools/extract_tilegrid.py index bb8b2af..8d5cb07 100644 --- a/tools/extract_tilegrid.py +++ b/tools/extract_tilegrid.py @@ -83,7 +83,8 @@ def main(argv): def fixup(tiletype): if args.device.find("-33") > 0: - tiletypes_with_variants = ["LRAM_", "SYSIO_B1_DED", "SPINE_"] + # These definitions were found to have conflicting PIPs against LIFCL-40/17 + tiletypes_with_variants = ["LRAM_", "SYSIO_B1_DED", "SPINE_", "TAP_CIB", "TMID_1", "CIB_LR"] for v in tiletypes_with_variants: if tiletype.startswith(v): return tiletype + "_33K" diff --git a/tools/reformat_database.py b/tools/reformat_database.py index e69de29..1b74799 100644 --- a/tools/reformat_database.py +++ b/tools/reformat_database.py @@ -0,0 +1,20 @@ +import database +import sys +import fuzzconfig +import tiles + +def main(): + devices = database.get_devices() + + for family in sorted(devices["families"].keys()): + for device in sorted(devices["families"][family]["devices"].keys()): + + with fuzzconfig.db_lock() as db: + for tiletype in tiles.get_tiletypes(device): + db.load_tiletype(family, tiletype) + db.reformat() + + +if __name__ == "__main__": + main() + From 27de3200752762deff50e2a53e8c932f101b04fc Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Tue, 17 Feb 2026 14:24:09 -0700 Subject: [PATCH 23/29] Update to use status line, fix some incorrectly bound lambdas, update to different api calls --- fuzzers/LIFCL/010-lut-init/fuzzer.py | 18 ++-- fuzzers/LIFCL/016-site-mappings/fuzzer.py | 100 ++++++------------- fuzzers/LIFCL/022-midmux/fuzzer.py | 59 ----------- fuzzers/LIFCL/031-io_mode/iob_40.v | 7 +- fuzzers/LIFCL/032-hsio_mode/fuzzer.py | 25 +++-- fuzzers/LIFCL/035-bankref/fuzzer.py | 3 +- fuzzers/LIFCL/050-cib-special/fuzzer.py | 35 +++---- fuzzers/LIFCL/060-ebr-config/fuzzer.py | 91 +++++++++-------- fuzzers/LIFCL/062-lram-config/fuzzer.py | 2 +- fuzzers/LIFCL/070-iologic_mode/fuzzer.py | 76 ++++++++------ fuzzers/LIFCL/071-iodelay/fuzzer.py | 33 ++++-- fuzzers/LIFCL/080-dsp-config/fuzzer.py | 51 ++++++---- fuzzers/LIFCL/090-sysconfig/fuzzer.py | 20 ++-- fuzzers/LIFCL/091-osc/fuzzer.py | 10 +- fuzzers/LIFCL/092-gsr/fuzzer.py | 3 +- fuzzers/LIFCL/093-oscd/fuzzer.py | 3 +- fuzzers/LIFCL/100-ip-base/fuzzer.py | 16 ++- fuzzers/LIFCL/110-global-structure/fuzzer.py | 11 +- fuzzers/LIFCL/121-pll-ipconfig/fuzzer.py | 6 +- fuzzers/LIFCL/122-pll-config/fuzzer.py | 14 +-- fuzzers/LIFCL/131-config-ip/fuzzer.py | 8 +- fuzzers/LIFCL/140-bram-init/fuzzer.py | 2 +- fuzzers/LIFCL/141-lram-init/fuzzer.py | 13 ++- fuzzers/LIFCL/151-eclkprim/fuzzer.py | 3 +- fuzzers/LIFCL/153-dqsbuf/fuzzer.py | 2 +- fuzzers/LIFCL/154-ddrll/fuzzer.py | 4 +- fuzzers/LIFCL/155-dlldel/fuzzer.py | 3 +- fuzzers/LIFCL/161-dphy-ipconfig/fuzzer.py | 3 +- fuzzers/LIFCL/162-pcie-ipconfig/fuzzer.py | 3 +- fuzzers/LIFCL/900-always-on/fuzzer.py | 5 +- 30 files changed, 308 insertions(+), 321 deletions(-) delete mode 100644 fuzzers/LIFCL/022-midmux/fuzzer.py diff --git a/fuzzers/LIFCL/010-lut-init/fuzzer.py b/fuzzers/LIFCL/010-lut-init/fuzzer.py index bcbc80a..6313eb0 100644 --- a/fuzzers/LIFCL/010-lut-init/fuzzer.py +++ b/fuzzers/LIFCL/010-lut-init/fuzzer.py @@ -1,3 +1,5 @@ +import asyncio + from fuzzconfig import FuzzConfig import nonrouting import fuzzloops @@ -24,19 +26,17 @@ def get_lut_function(init_bits): return lut_func -def main(): +async def main(executor): cfg.setup() cfg.sv = "lut.v" - def per_slice(slicen): + async def per_slice(slicen): for k in range(2): - def get_substs(bits): - return dict(z=slicen, k=str(k), func=get_lut_function(bits)) - nonrouting.fuzz_word_setting(cfg, "SLICE{}.K{}.INIT".format(slicen, k), 16, get_substs, - desc="SLICE {} LUT{} init value".format(slicen, k)) - - fuzzloops.parallel_foreach(["A", "B", "C", "D"], per_slice) + await asyncio.wrap_future(executor.submit(nonrouting.fuzz_word_setting, cfg, "SLICE{}.K{}.INIT".format(slicen, k), 16, + lambda bits, k=k,slicen=slicen: dict(z=slicen, k=str(k), func=get_lut_function(bits)), + desc="SLICE {} LUT{} init value".format(slicen, k), executor=executor)) + await asyncio.gather(*[per_slice(s) for s in ["A", "B", "C", "D"]]) if __name__ == "__main__": - main() + fuzzloops.FuzzerAsyncMain(main) diff --git a/fuzzers/LIFCL/016-site-mappings/fuzzer.py b/fuzzers/LIFCL/016-site-mappings/fuzzer.py index 769a98e..16c90ef 100644 --- a/fuzzers/LIFCL/016-site-mappings/fuzzer.py +++ b/fuzzers/LIFCL/016-site-mappings/fuzzer.py @@ -25,6 +25,10 @@ mapped_sites = set() +async def wrap_future(f): + if f is not None: + return await asyncio.wrap_future(f) + # These tiles overlap many sites and are not the main site tiles overlapping_tile_types = set(["CIB", "MIB_B_TAP", "TAP_CIB"] + [f"BANKREF{i}" for i in range(16)] + @@ -86,52 +90,24 @@ async def find_relevant_tiles_from_primitive(device, primitive, site, site_info, "site_type": site_type, "extra": "", "signals": "" - }, prefix=f"find-relevant-tiles/{primitive.mode}/")) + }, prefix=f"find-relevant-tiles/mode-{primitive.mode}/")) logging.info(f"Getting relevant tiles for {device} {site}:{site_type} for {primitive.mode}") - driving_tiles, site_tiles, ip_values = find_relevant_tiles_from_bitstream(device, site, primitive_bitstream) - - # Also get the tiling from just the wiring - pin_driving_tiles, pin_site_tiles, pin_ip_values = await find_relevant_tiles(device, site, site_type, site_info, executor = executor) - - # Note: We do this to keep ordering but removing dups - def uniq(x): - return list(dict.fromkeys(x)) + return find_relevant_tiles_from_bitstream(device, site, primitive_bitstream) - return uniq(driving_tiles + pin_driving_tiles), uniq(site_tiles + pin_site_tiles), uniq(ip_values + pin_ip_values) + # # Also get the tiling from just the wiring + # pin_driving_tiles, pin_site_tiles, pin_ip_values = await find_relevant_tiles(device, site, site_type, site_info, executor = executor) + # + # # Note: We do this to keep ordering but removing dups + # def uniq(x): + # return list(dict.fromkeys(x)) + # + # return uniq(driving_tiles + pin_driving_tiles), uniq(site_tiles + pin_site_tiles), uniq(ip_values + pin_ip_values) mux_re = re.compile("MUX[0-9]*$") -# Take all a given sites local graph and pips, and solve for all of it -def map_local_pips(site, site_type, device, ts, pips, local_graph, executor=None): - cfg = FuzzConfig(job=f"{site}:{site_type}", sv="../shared/route.v", device=device, tiles=ts) - - logging.debug(f"PIPs for {site}:") - for p in pips: - logging.debug(f" - {p[0]} -> {p[1]}") - - external_nodes = [wire for pip in pips for wire in pip if wire not in local_graph] - - # CIB is routed separately - cfg.tiles.extend( - [tile for n in external_nodes for tile in tiles.get_tiles_by_rc(device, n) if tile.split(":")[-1] != "CIB"]) - cfg.tiles = [t for t in cfg.tiles if not t.split(":")[-1].startswith("CIB")] - - if len(cfg.tiles) == 0: - logging.warning(f"Local pips for {site} only corresponded to CIB tiles") - return - - mux_pips = [p for p in pips if mux_re.search(p[0]) and mux_re.search(p[1])] - non_mux_pips = [p for p in pips if not p in mux_pips] - if len(non_mux_pips): - yield from fuzz_interconnect_sinks(cfg, non_mux_pips, False, executor = executor) - - if len(mux_pips): - mux_cfg = FuzzConfig(job=f"{site}:{site_type}-MUX", sv="../shared/route.v", device=device, tiles=cfg.tiles) - yield from fuzz_interconnect_sinks(mux_cfg, mux_pips, True, executor = executor) - # Use the primitive definitions to map out each mode's options. Works for IP and non IP settings -def map_primitive_settings(device, ts, site, site_tiles, site_type, ip_values, executor = None): +async def map_primitive_settings(device, ts, site, site_tiles, site_type, ip_values, executor = None): if site_type not in primitives.primitives: return [] @@ -150,8 +126,8 @@ def map_primitive_settings(device, ts, site, site_tiles, site_type, ip_values, e fuzz_enum_setting = nonrouting.fuzz_enum_setting fuzz_word_setting = nonrouting.fuzz_word_setting - def map_mode(mode): - logging.info(f"====== {mode.mode} : {site_type} IP: {len(ip_values)} ==========") + async def map_mode(mode): + logging.info(f"====== {mode.mode} : {site}:{site_type} IP: {len(ip_values)} ==========") related_tiles = (ts + site_tiles) cfg = FuzzConfig(job=f"config/{site_type}/{site}/{mode.mode}", device=device, sv="primitive.v", tiles= related_tiles if len(ip_values) == 0 else [f"{site}:{site_type}"]) @@ -209,40 +185,39 @@ def get_sink_pin(): "signals": ", ".join(signals) } - def map_mode_setting(setting): + async def map_mode_setting(setting): mark_relative_to = None if site_tiles[0] != ts[0]: mark_relative_to = site_tiles[0] args = { "config": cfg, - "name": f"{mode.mode}.{setting.name}", + "name": f"{site}.{setting.name}",# "name": f"{mode.mode}.{setting.name}", "desc": setting.desc, "executor": executor } if isinstance(setting, primitives.EnumSetting): - def subs_fn(val): - return subs | {"config": mode.configuration([(setting, val)])} + subs_fn = lambda val, setting=setting, mode=mode: subs | {"config": mode.configuration([(setting, val)])} - if len(ip_values) == 0: - args["mark_relative_to"] = mark_relative_to + # if len(ip_values) == 0: + # args["mark_relative_to"] = mark_relative_to if isinstance(setting, primitives.ProgrammablePin) and not is_ip_config: args["include_zeros"] = True - return fuzz_enum_setting(empty_bitfile = empty_file, values = setting.values, get_sv_substs = subs_fn, **args) + await wrap_future(fuzz_enum_setting(empty_bitfile = empty_file, values = setting.values, get_sv_substs = subs_fn, **args)) elif isinstance(setting, primitives.WordSetting): def subs_fn(val): return subs | {"config": mode.configuration([(setting, nonrouting.fuzz_intval(val))])} - return fuzz_word_setting(length=setting.bits, get_sv_substs=subs_fn, **args) + await wrap_future(fuzz_word_setting(length=setting.bits, get_sv_substs=subs_fn, **args)) else: raise Exception(f"Unknown setting type: {setting}") - return [map_mode_setting(s) for s in mode.settings] + await asyncio.gather(*[map_mode_setting(s) for s in mode.settings]) - return [f for mode in primitives.primitives[site_type] for f in map_mode(mode)] + return await asyncio.gather(*[map_mode(mode) for mode in primitives.primitives[site_type]]) async def run_for_device(device, executor = None): if not fuzzconfig.should_fuzz_platform(device): @@ -259,7 +234,7 @@ async def find_relevant_tiles_for_site(site, site_info, executor): return await find_relevant_tiles_from_primitive(device, primitive, site, site_info, executor=executor) - return await find_relevant_tiles(device, site, site_type, site_info, executor=executor) + return [], [], [] def should_skip_site(site, site_info): site_type = site_info["type"] @@ -280,26 +255,18 @@ async def per_site(site, site_info, driving_tiles, executor): site_type = site_info["type"] - logging.info(f"====== {site} : {driving_tiles} ==========") tiletype = driving_tiles[0].split(":")[1] - - logging.info(f"====== {site} : {tiletype} ==========") - pips, local_graph = tiles.get_local_pips_for_site(device, site) - - pips_future = list(map_local_pips(site, site_type, device, driving_tiles + site_tiles, pips, local_graph, executor=executor)) + logging.info(f"====== {site} : {tiletype} {driving_tiles} ==========") # Map primitive parameter settings - settings_future = map_primitive_settings(device, driving_tiles + site_tiles, site, site_tiles, site_type, ip_values, executor = executor) - - return [pips_future, settings_future] + await map_primitive_settings(device, driving_tiles + site_tiles, site, site_tiles, site_type, ip_values, executor = executor) sites = database.get_sites(device) sites_items = [(k,v) for k,v in sorted(sites.items()) if v["type"] not in ["CIBTEST", "SLICE"]] driving_tiles_futures = [] - async with asyncio.TaskGroup() as tg: - for site, site_info in sites_items: - driving_tiles_futures.append(find_relevant_tiles_for_site(site, site_info, executor=executor)) + for site, site_info in sites_items: + driving_tiles_futures.append(find_relevant_tiles_for_site(site, site_info, executor=executor)) all_driving_tiles = await asyncio.gather(*driving_tiles_futures) @@ -352,10 +319,9 @@ async def FuzzAsync(executor): if len(sys.argv) > 1 and sys.argv[1] not in all_sites: logging.warning(f"Site filter doesn't match any known sites") logging.info(sorted(all_sites)) + return - return [] - - return await asyncio.gather(*[ run_for_device(device, executor) for device in devices ]) + await asyncio.gather(*[ run_for_device(device, executor) for device in devices ]) if __name__ == "__main__": fuzzloops.FuzzerAsyncMain(FuzzAsync) diff --git a/fuzzers/LIFCL/022-midmux/fuzzer.py b/fuzzers/LIFCL/022-midmux/fuzzer.py deleted file mode 100644 index a8746a0..0000000 --- a/fuzzers/LIFCL/022-midmux/fuzzer.py +++ /dev/null @@ -1,59 +0,0 @@ -from fuzzconfig import FuzzConfig -from interconnect import fuzz_interconnect - -configs = [ - ("HPFE", (28, 0), 12, "L", - FuzzConfig(job="LMIDROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R28C0:LMID"])), - ("HPFW", (28, 86), 12, "R", - FuzzConfig(job="RMIDROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R28C87:RMID_DLY20"])), - ("VPFN", (56, 49), 18, "B", - FuzzConfig(job="BMIDROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R56C49:BMID_0_ECLK_1", "CIB_R56C50:BMID_1_ECLK_2"])), - ("VPFS", (0, 49), 16, "T", - FuzzConfig(job="TMIDROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R0C49:TMID_0", "CIB_R0C50:TMID_1"])), - - ("HPFE", (10, 0), 12, "L", - FuzzConfig(job="LMIDROUTE", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R10C0:LMID_RBB_5_15K"])), - ("HPFW", (10, 74), 12, "R", - FuzzConfig(job="RMIDROUTE", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R10C75:RMID_PICB_DLY10"])), - ("VPFS", (0, 37), 16, "T", - FuzzConfig(job="TMIDROUTE", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R0C37:TMID_0", "CIB_R0C38:TMID_1_15K", "CIB_R0C39:CLKBUF_T_15K"])), - - ("VPFN", (83, 25), 16, "B", - FuzzConfig(job="BMIDROUTE-33U", device="LIFCL-33U", sv="../shared/route.v", - tiles=["CIB_R83C25:BMID_0_ECLK_1", "CIB_R83C26:BMID_1_ECLK_2"])), -] - -def main(): - for feed, rc, ndcc, side, cfg in configs: - cfg.setup() - if cfg.device == "LIFCL-40": - cr, cc = (28, 49) - elif cfg.device == "LIFCL-17": - cr, cc = (10, 37) - else: - cr, cc = (37, 25) - r, c = rc - nodes = [] - mux_nodes = [] - for i in range(ndcc): - for j in range(2): - nodes.append("R{}C{}_J{}{}_CMUX_CORE_CMUX{}".format(cr, cc, feed, i, j)) - nodes.append("R{}C{}_J{}{}_CMUX_CORE_CMUX{}".format(cr, cc, feed, i, j)) - nodes.append("R{}C{}_J{}{}_DCSMUX_CORE_DCSMUX{}".format(cr, cc, feed, i, j)) - nodes.append("R{}C{}_J{}{}_DCSMUX_CORE_DCSMUX{}".format(cr, cc, feed, i, j)) - nodes.append("R{}C{}_JCLKO_DCC_DCC{}".format(r, c, i)) - nodes.append("R{}C{}_JCE_DCC_DCC{}".format(r, c, i)) - nodes.append("R{}C{}_JCLKI_DCC_DCC{}".format(r, c, i)) - mux_nodes.append("R{}C{}_J{}{}_{}MID_CORE_{}MIDMUX".format(r, c, feed, i, side, side)) - for i in range(4): - nodes.append("R{}C{}_JTESTINP{}_{}MID_CORE_{}MIDMUX".format(r, c, i, side, side)) - fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=False) - fuzz_interconnect(config=cfg, nodenames=mux_nodes, regex=False, bidir=False, full_mux_style=True) - def pip_filter(pip, nodes): - from_wire, to_wire = pip - return "PCLKT" in to_wire or "PCLKCIB" in to_wire - fuzz_interconnect(config=cfg, nodenames=["R{}C{}_J*".format(r, c)], regex=True, bidir=False, full_mux_style=False, - pip_predicate=pip_filter) - -if __name__ == "__main__": - main() diff --git a/fuzzers/LIFCL/031-io_mode/iob_40.v b/fuzzers/LIFCL/031-io_mode/iob_40.v index 15cfbbd..8e009b2 100644 --- a/fuzzers/LIFCL/031-io_mode/iob_40.v +++ b/fuzzers/LIFCL/031-io_mode/iob_40.v @@ -2,13 +2,12 @@ module top ( ${cmt} ${pintype} q ); - - // A primitive is needed, but VHI should be harmless (* \xref:LOG ="q_c@0@9" *) - VHI vhi_i(); + wire q_c; + // A primitive is needed, but VHI should be harmless (* \xref:LOG ="q_c@0@9" *) - wire q_c; + VHI vhi_i( .Z(q_c) ); ${cmt}(* \xref:LOG ="${primtype}=q_pad.bb_inst@0@8", \dm:cellmodel_primitives ="${primtype}=q_pad.bb_inst", \dm:primitive ="${primtype}", \dm:programming ="MODE:${primtype} ${primtype}:::IO_TYPE=${iotype},BANK_VCCIO=${vcc}${extra_config}:T=${t}", \dm:site ="${site}" *) ${cmt}${primtype} \q_pad.bb_inst (.PADDO(q_c)); diff --git a/fuzzers/LIFCL/032-hsio_mode/fuzzer.py b/fuzzers/LIFCL/032-hsio_mode/fuzzer.py index 16d77c2..da1199c 100644 --- a/fuzzers/LIFCL/032-hsio_mode/fuzzer.py +++ b/fuzzers/LIFCL/032-hsio_mode/fuzzer.py @@ -1,3 +1,5 @@ +import asyncio +import hashlib import logging from fuzzconfig import FuzzConfig, should_fuzz_platform @@ -36,7 +38,7 @@ def create_config_from_pad(pad, device): "|".join(neighbor_tile_types) + "-" + pio, (pio_names[pad["pio"]], pin, - FuzzConfig(job=f"IO{pin}_{tiletype}", device=device, + FuzzConfig(job=f"{pin}/{ts[0]}/{tiletype}", device=device, tiles=ts + all_sysio)) ) @@ -47,7 +49,7 @@ def create_configs_for_device(device): ])) return configs -configs = (create_configs_for_device("LIFCL-33") | create_configs_for_device("LIFCL-40") ).values() +configs = (create_configs_for_device("LIFCL-33") | create_configs_for_device("LIFCL-40") ).items() # + [ # ("A","V1", # PB6A # FuzzConfig(job="IO5A", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C6:SYSIO_B5_0", "CIB_R56C7:SYSIO_B5_1"])), @@ -96,10 +98,11 @@ def create_configs_for_device(device): device_empty_bitfile = {} -def main(executor): - def per_config(config): - pio, site, cfg = config +async def main(executor): + async def per_config(config): + overlay, (pio, site, cfg) = config + overlay_suffix = hashlib.sha1(overlay.encode()).hexdigest() (r,c) = tiles.get_rc_from_name(cfg.device, cfg.tiles[0]) if f"R{r}C{c}_JPADDO_SEIO18_CORE_IO{pio}" not in tiles.get_full_node_set(cfg.device) and \ @@ -167,8 +170,9 @@ def get_substs(iotype="BIDIR_LVCMOS18H", kv=None, vcc=None, tmux="T"): else: all_di_types += ["{}_{}".format(di, t) for di in d] + futures = [] def fuzz_enum_setting(*args, **kwargs): - nonrouting.fuzz_enum_setting(cfg, empty, executor=executor,*args, **kwargs) + futures.append(fuzzloops.wrap_future(nonrouting.fuzz_enum_setting(cfg, empty, executor=executor, overlay=overlay_suffix, *args, **kwargs))) fuzz_enum_setting("PIO{}.SEIO18.DRIVE_1V0".format(pio), ["2", "4"], lambda x: get_substs(iotype="OUTPUT_LVCMOS10H", kv=("DRIVE", x)), True) @@ -241,8 +245,10 @@ def fuzz_enum_setting(*args, **kwargs): fuzz_enum_setting("PIO{}.DIFFIO18.DIFFTX_INV".format(pio), ["NORMAL", "INVERT"], lambda x: get_substs(iotype="OUTPUT_LVDS", kv=("DIFFTX_INV", x)), False) + await asyncio.gather(*futures) + def cfg_filter(config): - pio, site, cfg = config + _, (pio, site, cfg) = config if not should_fuzz_platform(cfg.device): return False @@ -254,8 +260,7 @@ def cfg_filter(config): return True - for config in filter(cfg_filter, configs): - per_config(config) + await asyncio.gather(*[per_config(config) for config in filter(cfg_filter, configs)]) if __name__ == "__main__": - fuzzloops.FuzzerMain(main) + fuzzloops.FuzzerAsyncMain(main) diff --git a/fuzzers/LIFCL/035-bankref/fuzzer.py b/fuzzers/LIFCL/035-bankref/fuzzer.py index 1dce35f..d0fc836 100644 --- a/fuzzers/LIFCL/035-bankref/fuzzer.py +++ b/fuzzers/LIFCL/035-bankref/fuzzer.py @@ -106,5 +106,6 @@ def get_substs(iotype="LVCMOS18H", kv=None, vcc=None, diff=False, tmux="#SIG"): assume_zero_base=True, desc="VccIO of bank {}".format(bank)) fuzzloops.parallel_foreach(configs, per_config) + if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/050-cib-special/fuzzer.py b/fuzzers/LIFCL/050-cib-special/fuzzer.py index aaa50e0..b83e0a2 100644 --- a/fuzzers/LIFCL/050-cib-special/fuzzer.py +++ b/fuzzers/LIFCL/050-cib-special/fuzzer.py @@ -12,7 +12,7 @@ ((37, 86), FuzzConfig(job="CIBLRAENABLE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R37C86:CIB_LR_A", "CIB_R38C86:CIB_LR_B"])), ] -def main(): +def main(executor): def per_cib(cib): rc, cfg = cib cfg.setup() @@ -22,7 +22,7 @@ def per_cib(cib): nodes = ["R{}C{}_JF{}".format(r, c, i) for i in range(8)] nodes += ["R{}C{}_JQ{}".format(r, c, i) for i in range(8)] - node_data = lapie.get_node_data(cfg.udb, nodes) + node_data = lapie.get_node_data(cfg.device, nodes) for n in node_data: to_wire = n.name setting_name = to_wire.split("_")[1] + "_USED" @@ -34,21 +34,22 @@ def per_cib(cib): assert from_wire is not None arcs_attr = r', \dm:arcs ="{}.{}"'.format(to_wire, from_wire) nonrouting.fuzz_enum_setting(cfg, empty, "CIB." + setting_name, ["NO", "YES"], - lambda x: dict(arcs_attr=arcs_attr) if x == "YES" else {}, False) + lambda x: dict(arcs_attr=arcs_attr) if x == "YES" else {}, False, executor=executor) - # CIBMUXIN -> CIBMUXOUT - cfg.sv = "cib_iomux_40.v" - for x in ("A", "B", "C", "D"): - # Stop Radiant trying to tie unused outputs; as this causes weird bit patterns - extra_arcs = [] - for i in range(8): - for x2 in ("A", "B", "C", "D"): - if x2 == x: - continue - extra_arcs.append("R{r}C{c}_JCIBMUXOUT{x}{i}.R{r}C{c}_JCIBMUXINA{i}".format(r=r, c=c, x=x2, i=i)) - cibmuxout = ["R{}C{}_JCIBMUXOUT{}{}".format(r, c, x, i) for i in range(8)] - fuzz_interconnect(config=cfg, nodenames=cibmuxout, regex=False, bidir=False, full_mux_style=True, - extra_substs=dict(extra_arc=" ".join(extra_arcs))) + # # CIBMUXIN -> CIBMUXOUT + # cfg.sv = "cib_iomux_40.v" + # for x in ("A", "B", "C", "D"): + # # Stop Radiant trying to tie unused outputs; as this causes weird bit patterns + # extra_arcs = [] + # for i in range(8): + # for x2 in ("A", "B", "C", "D"): + # if x2 == x: + # continue + # extra_arcs.append("R{r}C{c}_JCIBMUXOUT{x}{i}.R{r}C{c}_JCIBMUXINA{i}".format(r=r, c=c, x=x2, i=i)) + # cibmuxout = ["R{}C{}_JCIBMUXOUT{}{}".format(r, c, x, i) for i in range(8)] + # fuzz_interconnect(config=cfg, nodenames=cibmuxout, regex=False, bidir=False, full_mux_style=True, + # extra_substs=dict(extra_arc=" ".join(extra_arcs)), executor=executor) fuzzloops.parallel_foreach(configs, per_cib) + if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/060-ebr-config/fuzzer.py b/fuzzers/LIFCL/060-ebr-config/fuzzer.py index 160488f..3ca2a97 100644 --- a/fuzzers/LIFCL/060-ebr-config/fuzzer.py +++ b/fuzzers/LIFCL/060-ebr-config/fuzzer.py @@ -1,3 +1,5 @@ +import asyncio + from fuzzconfig import FuzzConfig import nonrouting import fuzzloops @@ -20,8 +22,8 @@ "FIFO16K_MODE": "FIFO16K_MODE:::FULLBITS=0b00000000000000,DATA_WIDTH_A=X36,DATA_WIDTH_B=X36 FIFO16K_MODE::::CKA=0,CKB=0" } -def main(): - def per_config(x): +async def main(executor): + async def per_config(x): site, ebr, cfg = x cfg.setup() empty = cfg.build_design(cfg.sv, {}) @@ -43,34 +45,41 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): config = "{}:::{}={}".format(mode, kv[0], kv[1]) return dict(mode=mode, cmt="//" if mode == "NONE" else "", config=config, site=site) modes = ["NONE", "DP16K_MODE", "PDP16K_MODE", "PDPSC16K_MODE", "SP16K_MODE", "FIFO16K_MODE"] - nonrouting.fuzz_enum_setting(cfg, empty, "{}.MODE".format(ebr), modes, + + futures = [] + def fuzz_enum_setting(*args, **kwargs): + futures.append(wrap_future(nonrouting.fuzz_enum_setting(cfg, empty, *args, **kwargs,executor=executor))) + def fuzz_word_setting(*args, **kwargs): + futures.append(wrap_future(nonrouting.fuzz_word_setting(cfg, *args, **kwargs,executor=executor))) + + fuzz_enum_setting("{}.MODE".format(ebr), modes, lambda x: get_substs(x, default_cfg=True), False, desc="{} primitive mode".format(ebr)) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DP16K_MODE.DATA_WIDTH_A".format(ebr), ["X1", "X2", "X4", "X9", "X18"], + fuzz_enum_setting("{}.DP16K_MODE.DATA_WIDTH_A".format(ebr), ["X1", "X2", "X4", "X9", "X18"], lambda x: get_substs(mode="DP16K_MODE", kv=("DATA_WIDTH_A", x)), False, desc="data width of port A in DP16K_MODE") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DP16K_MODE.DATA_WIDTH_B".format(ebr), ["X1", "X2", "X4", "X9", "X18"], + fuzz_enum_setting("{}.DP16K_MODE.DATA_WIDTH_B".format(ebr), ["X1", "X2", "X4", "X9", "X18"], lambda x: get_substs(mode="DP16K_MODE", kv=("DATA_WIDTH_B", x)), False, desc="data width of port B in DP16K_MODE") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.PDP16K_MODE.DATA_WIDTH_W".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], + fuzz_enum_setting("{}.PDP16K_MODE.DATA_WIDTH_W".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], lambda x: get_substs(mode="PDP16K_MODE", kv=("DATA_WIDTH_W", x)), False, desc="data width of write port in PDP16K_MODE") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.PDP16K_MODE.DATA_WIDTH_R".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], + fuzz_enum_setting("{}.PDP16K_MODE.DATA_WIDTH_R".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], lambda x: get_substs(mode="PDP16K_MODE", kv=("DATA_WIDTH_R", x)), False, desc="data width of read port in PDP16K_MODE") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.PDPSC16K_MODE.DATA_WIDTH_W".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], + fuzz_enum_setting("{}.PDPSC16K_MODE.DATA_WIDTH_W".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], lambda x: get_substs(mode="PDPSC16K_MODE", kv=("DATA_WIDTH_W", x)), False, desc="data width of write port in PDPSC16K_MODE") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.PDPSC16K_MODE.DATA_WIDTH_R".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], + fuzz_enum_setting("{}.PDPSC16K_MODE.DATA_WIDTH_R".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], lambda x: get_substs(mode="PDPSC16K_MODE", kv=("DATA_WIDTH_R", x)), False, desc="data width of read port in PDPSC16K_MODE") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.SP16K_MODE.DATA_WIDTH".format(ebr), ["X1", "X2", "X4", "X9", "X18"], + fuzz_enum_setting("{}.SP16K_MODE.DATA_WIDTH".format(ebr), ["X1", "X2", "X4", "X9", "X18"], lambda x: get_substs(mode="SP16K_MODE", kv=("DATA_WIDTH", x)), False, desc="data width of R/W port in SP16K_MODE") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.FIFO16K_MODE.DATA_WIDTH_A".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], + fuzz_enum_setting("{}.FIFO16K_MODE.DATA_WIDTH_A".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], lambda x: get_substs(mode="FIFO16K_MODE", kv=("DATA_WIDTH_A", x)), False, desc="data width of port A in FIFO16K_MODE") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.FIFO16K_MODE.DATA_WIDTH_B".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], + fuzz_enum_setting("{}.FIFO16K_MODE.DATA_WIDTH_B".format(ebr), ["X1", "X2", "X4", "X9", "X18", "X32", "X36"], lambda x: get_substs(mode="FIFO16K_MODE", kv=("DATA_WIDTH_B", x)), False, desc="data width of port B in FIFO16K_MODE") @@ -80,70 +89,74 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): ("SP16K_MODE", ["CLK"]), ("FIFO16K_MODE", ["CKA", "CKB"])]: for sig in clksigs: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}.{}MUX".format(ebr, mode, sig), ["0", sig, "INV"], - lambda x: get_substs(mode=mode, kv=(sig, x), mux=True), False, + fuzz_enum_setting("{}.{}.{}MUX".format(ebr, mode, sig), ["0", sig, "INV"], + lambda x,sig=sig,mode=mode: get_substs(mode=mode, kv=(sig, x), mux=True), False, desc="clock inversion control for {}".format(sig)) for mode, cwesigs in [("DP16K_MODE", ["WEA", "WEB", "CEA", "CEB", "RSTA", "RSTB", "ADA0", "ADA1", "ADA2", "ADA3", "ADB0", "ADB1"]), ("PDP16K_MODE", ["WE", "CER", "CEW", "RST", "ADW0", "ADW1", "ADW2", "ADW3", "ADR0", "ADR1"]), ("PDPSC16K_MODE", ["WE", "CER", "CEW", "RST", "ADW0", "ADW1", "ADW2", "ADW3", "ADR0", "ADR1"]), - ("SP16K_MODE", ["WE", "CE", "RST", "AD0", "AD1", "AD2", "AD3"]), - ("FIFO16K_MODE", ["CEA", "CEB", "RSTA", "RSTB"])]: + ("SP16K_MODE", ["WE", "CE", "RST", "AD0", "AD1", "AD2", "AD3"])]: + #("FIFO16K_MODE", ["CEA", "CEB", "RSTA", "RSTB"])]: for sig in cwesigs: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}.{}MUX".format(ebr, mode, sig), [sig, "INV"], - lambda x: get_substs(mode=mode, kv=(sig, x), mux=True), False) + fuzz_enum_setting("{}.{}.{}MUX".format(ebr, mode, sig), [sig, "INV"], + lambda x,sig=sig,mode=mode: get_substs(mode=mode, kv=(sig, x), mux=True), False) for mode, outregs in [("DP16K_MODE", ["OUTREG_A", "OUTREG_B"]), ("PDP16K_MODE", ["OUTREG"]), ("PDPSC16K_MODE", ["OUTREG"]), - ("SP16K_MODE", ["OUTREG"]), - ("FIFO16K_MODE", ["OUTREG_A", "OUTREG_B"])]: + ("SP16K_MODE", ["OUTREG"])]: + #("FIFO16K_MODE", ["OUTREG_A", "OUTREG_B"])]: for outreg in outregs: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}.{}".format(ebr, mode, outreg), ["USED", "BYPASSED"], - lambda x: get_substs(mode=mode, kv=(outreg, x)), False, + fuzz_enum_setting("{}.{}.{}".format(ebr, mode, outreg), ["USED", "BYPASSED"], + lambda x,outreg=outreg,mode=mode: get_substs(mode=mode, kv=(outreg, x)), False, desc="extra output pipeline register enable/bypass") for mode, ports in [("DP16K_MODE", ["_A", "_B"]), ("PDP16K_MODE", [""]), ("PDPSC16K_MODE", [""]), - ("SP16K_MODE", [""]), - ("FIFO16K_MODE", ["_A", "_B"])]: + ("SP16K_MODE", [""])]: + #("FIFO16K_MODE", ["_A", "_B"])]: for port in ports: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}.RESETMODE{}".format(ebr, mode, port), ["ASYNC", "SYNC"], - lambda x: get_substs(mode=mode, kv=("RESETMODE{}".format(port), x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}.ASYNC_RST_RELEASE{}".format(ebr, mode, port), ["ASYNC", "SYNC"], - lambda x: get_substs(mode=mode, kv=("ASYNC_RST_RELEASE{}".format(port), x)), False) + fuzz_enum_setting("{}.{}.RESETMODE{}".format(ebr, mode, port), ["ASYNC", "SYNC"], + lambda x,port=port: get_substs(mode=mode, kv=("RESETMODE{}".format(port), x)), False) + fuzz_enum_setting("{}.{}.ASYNC_RST_RELEASE{}".format(ebr, mode, port), ["ASYNC", "SYNC"], + lambda x,port=port: get_substs(mode=mode, kv=("ASYNC_RST_RELEASE{}".format(port), x)), False) for mode, csds in [("DP16K_MODE", ["CSDECODE_A", "CSDECODE_B"]), ("PDP16K_MODE", ["CSDECODE_R", "CSDECODE_W"]), ("PDPSC16K_MODE", ["CSDECODE_R", "CSDECODE_W"]), ("SP16K_MODE", ["CSDECODE"]),]: for csd in csds: - nonrouting.fuzz_word_setting(cfg, "{}.{}.{}".format(ebr, mode, csd), 3, - lambda x: get_substs(mode=mode, kv=(csd, "".join(reversed(["1" if b else "0" for b in x])))), + fuzz_word_setting("{}.{}.{}".format(ebr, mode, csd), 3, + lambda x,csd=csd,mode=mode: get_substs(mode=mode, kv=(csd, "".join(reversed(["1" if b else "0" for b in x])))), desc="port is enabled when CS inputs match this value".format(csd)) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.GSR".format(ebr), ["ENABLED", "DISABLED"], + fuzz_enum_setting("{}.GSR".format(ebr), ["ENABLED", "DISABLED"], lambda x: get_substs(mode="DP16K_MODE", kv=("GSR", x)), False, desc="if `ENABLED`, then read ports are reset by user GSR") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.INIT_DATA".format(ebr), ["DYNAMIC", "STATIC", "NO_INIT"], + fuzz_enum_setting("{}.INIT_DATA".format(ebr), ["DYNAMIC", "STATIC", "NO_INIT"], lambda x: get_substs(mode="DP16K_MODE", kv=("INIT_DATA", x)), False, desc="selects initialisation mode") - nonrouting.fuzz_word_setting(cfg, "{}.WID".format(ebr), 11, + fuzz_word_setting("{}.WID".format(ebr), 11, lambda x: get_substs(mode="DP16K_MODE", kv=("WID", "0b" + "".join(reversed(["1" if b else "0" for b in x])))), desc="unique ID for the BRAM, used to initialise it in the bitstream") - nonrouting.fuzz_word_setting(cfg, "{}.FIFO16K_MODE.FULLBITS".format(ebr), 14, + fuzz_word_setting("{}.FIFO16K_MODE.FULLBITS".format(ebr), 14, lambda x: get_substs(mode="FIFO16K_MODE", kv=("FULLBITS", "0b" + "".join(reversed(["1" if b else "0" for b in x])))), desc="FIFO 'full' threshold") - nonrouting.fuzz_word_setting(cfg, "{}.FIFO16K_MODE.ALMOST_FULL".format(ebr), 14, + fuzz_word_setting("{}.FIFO16K_MODE.ALMOST_FULL".format(ebr), 14, lambda x: get_substs(mode="FIFO16K_MODE", kv=("ALMOST_FULL", "0b" + "".join(reversed(["1" if b else "0" for b in x])))), desc="FIFO 'almost full' output threshold") - nonrouting.fuzz_word_setting(cfg, "{}.FIFO16K_MODE.EMPTYBITS".format(ebr), 5, + fuzz_word_setting("{}.FIFO16K_MODE.EMPTYBITS".format(ebr), 5, lambda x: get_substs(mode="EBR_CORE", kv=("EMPTY", "0b" + "".join(reversed(["1" if b else "0" for b in x])))), desc="FIFO 'empty' threshold") - nonrouting.fuzz_word_setting(cfg, "{}.FIFO16K_MODE.ALMOST_EMPTY".format(ebr), 14, + fuzz_word_setting("{}.FIFO16K_MODE.ALMOST_EMPTY".format(ebr), 14, lambda x: get_substs(mode="FIFO16K_MODE", kv=("ALMOST_EMPTY", "0b" + "".join(reversed(["1" if b else "0" for b in x])))), desc="FIFO 'almost empty' output threshold") - fuzzloops.parallel_foreach(configs, per_config) + + await asyncio.gather(*futures) + + await asyncio.gather(*[per_config(c) for c in configs]) + if __name__ == "__main__": - main() + fuzzloops.FuzzerAsyncMain(main) diff --git a/fuzzers/LIFCL/062-lram-config/fuzzer.py b/fuzzers/LIFCL/062-lram-config/fuzzer.py index 144fb12..37ea863 100644 --- a/fuzzers/LIFCL/062-lram-config/fuzzer.py +++ b/fuzzers/LIFCL/062-lram-config/fuzzer.py @@ -62,4 +62,4 @@ def get_substs(mode="NONE", kv=None): fuzzloops.parallel_foreach(configs, per_config) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/070-iologic_mode/fuzzer.py b/fuzzers/LIFCL/070-iologic_mode/fuzzer.py index e6eff7b..1df01a6 100644 --- a/fuzzers/LIFCL/070-iologic_mode/fuzzer.py +++ b/fuzzers/LIFCL/070-iologic_mode/fuzzer.py @@ -1,3 +1,5 @@ +import asyncio + from fuzzconfig import FuzzConfig import nonrouting import fuzzloops @@ -61,8 +63,8 @@ # - SYSIO_B1_1_15K ] -def main(): - def per_config(x): +async def main(executor): + async def per_config(x): site, prim, cfg = x cfg.setup() empty = cfg.build_design(cfg.sv, {}) @@ -101,9 +103,13 @@ def per_config(x): else: assert False, cfg.device + futures = [] + def fuzz_enum_setting(*args, **kwargs): + futures.append(fuzzloops.wrap_future(nonrouting.fuzz_enum_setting(cfg, empty, *args, **kwargs,executor=executor))) + def get_substs(mode="NONE", default_cfg=False, scope=None, kv=None, mux=False, glb=False, dqs=False, pinconn=""): if default_cfg: - config = "SCLKINMUX:#OFF GSR:ENABLED INMUX:#OFF OUTMUX:#OFF DELAYMUX:#OFF SRMODE:#ASYNC LOAD_NMUX:#OFF DIRMUX:#OFF MOVEMUX:#OFF CEOUTMUX:#OFF CEINMUX:#OFF LSRINMUX:#OFF LSROUTMUX:#OFF STOP_EN:DISABLED" + config = "SCLKINMUX:#OFF GSR:ENABLED INMUX:#OFF OUTMUX:#OFF DELAYMUX:#OFF CEOUTMUX:#OFF CEINMUX:#OFF LSRINMUX:#OFF LSROUTMUX:#OFF STOP_EN:DISABLED" elif kv is None: config = "" elif glb: @@ -151,66 +157,70 @@ def get_substs(mode="NONE", default_cfg=False, scope=None, kv=None, mux=False, g modes = ["NONE", "IREG_OREG", "IDDRX1_ODDRX1"] if not s: modes += ["IDDRXN", "ODDRXN", "MIDDRXN_MODDRXN"] - nonrouting.fuzz_enum_setting(cfg, empty, "{}.MODE".format(prim), modes, + fuzz_enum_setting("{}.MODE".format(prim), modes, lambda x: get_substs(x, default_cfg=True), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.GSR".format(prim), ["ENABLED", "DISABLED"], + fuzz_enum_setting("{}.GSR".format(prim), ["ENABLED", "DISABLED"], lambda x: get_substs(mode="IREG_OREG", kv=("GSR", x), glb=True), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.SRMODE".format(prim), ["ASYNC", "LSR_OVER_CE"], + fuzz_enum_setting("{}.SRMODE".format(prim), ["ASYNC", "LSR_OVER_CE"], lambda x: get_substs(mode="IREG_OREG", kv=("SRMODE", x), glb=True), False) if not s: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.IDDRXN.DDRMODE".format(prim), ["NONE", "IDDRX2", "IDDR71", "IDDRX4", "IDDRX5"], + fuzz_enum_setting("{}.IDDRXN.DDRMODE".format(prim), ["NONE", "IDDRX2", "IDDR71", "IDDRX4", "IDDRX5"], lambda x: get_substs(mode="IDDRXN", kv=("DDRMODE", "#OFF" if x == "NONE" else x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.ODDRXN.DDRMODE".format(prim), ["NONE", "ODDRX2", "ODDR71", "ODDRX4", "ODDRX5"], + fuzz_enum_setting("{}.ODDRXN.DDRMODE".format(prim), ["ODDRX2", "ODDR71", "ODDRX4", "ODDRX5"], lambda x: get_substs(mode="ODDRXN", kv=("DDRMODE", "#OFF" if x == "NONE" else x)), False) for sig in ("SCLKIN", "SCLKOUT", "CEIN", "CEOUT", "LSRIN", "LSROUT"): - nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}MUX".format(prim, sig), ["1" if sig[0:2] == "CE" else "0", sig, "INV"], + fuzz_enum_setting("{}.{}MUX".format(prim, sig), ["1" if sig[0:2] == "CE" else "0", sig, "INV"], lambda x: get_substs(mode="IREG_OREG", kv=("{}MUX".format(sig), x), mux=True), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.IDDRX1_ODDRX1.OUTPUT".format(prim), ["DISABLED", "ENABLED"], + fuzz_enum_setting("{}.IDDRX1_ODDRX1.OUTPUT".format(prim), ["DISABLED", "ENABLED"], lambda x: get_substs(mode="IDDRX1_ODDRX1", default_cfg=True, pinconn=(".DOUT(sig), .LSRIN(sig)" if x == "ENABLED" else "")), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.IREG_OREG.OUTPUT".format(prim), ["DISABLED", "ENABLED"], + fuzz_enum_setting("{}.IREG_OREG.OUTPUT".format(prim), ["DISABLED", "ENABLED"], lambda x: get_substs(mode="IREG_OREG", default_cfg=True, pinconn=(".DOUT(sig), .LSRIN(sig)" if x == "ENABLED" else "")), False) if not s: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.IDDRX1_ODDRX1.TRISTATE".format(prim), ["DISABLED", "ENABLED"], + fuzz_enum_setting("{}.IDDRX1_ODDRX1.TRISTATE".format(prim), ["DISABLED", "ENABLED"], lambda x: get_substs(mode="IDDRX1_ODDRX1", kv=("TOUTMUX", "TSREG"), glb=True, pinconn=(".TOUT(sig), .LSRIN(sig)" if x == "ENABLED" else "")), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.IREG_OREG.TRISTATE".format(prim), ["DISABLED", "ENABLED"], + fuzz_enum_setting("{}.IREG_OREG.TRISTATE".format(prim), ["DISABLED", "ENABLED"], lambda x: get_substs(mode="IREG_OREG", kv=("TOUTMUX", "TSREG"), glb=True, pinconn=(".TOUT(sig), .LSRIN(sig)" if x == "ENABLED" else "")), False) else: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.IDDRX1_ODDRX1.TRISTATE".format(prim), ["DISABLED", "ENABLED"], + fuzz_enum_setting("{}.IDDRX1_ODDRX1.TRISTATE".format(prim), ["DISABLED", "ENABLED"], lambda x: get_substs(mode="IDDRX1_ODDRX1", default_cfg=True, pinconn=(".TOUT(sig), .LSRIN(sig)" if x == "ENABLED" else "")), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.IREG_OREG.TRISTATE".format(prim), ["DISABLED", "ENABLED"], + fuzz_enum_setting("{}.IREG_OREG.TRISTATE".format(prim), ["DISABLED", "ENABLED"], lambda x: get_substs(mode="IREG_OREG", default_cfg=True, pinconn=(".TOUT(sig), .LSRIN(sig)" if x == "ENABLED" else "")), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.INMUX".format(prim), ["BYPASS", "DELAY"], + fuzz_enum_setting("{}.INMUX".format(prim), ["BYPASS", "DELAY"], lambda x: get_substs(mode="IREG_OREG", kv=("INMUX", x), glb=True), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.OUTMUX".format(prim), ["BYPASS", "DELAY"], + fuzz_enum_setting("{}.OUTMUX".format(prim), ["BYPASS", "DELAY"], lambda x: get_substs(mode="IREG_OREG", kv=("OUTMUX", x), glb=True), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAYMUX".format(prim), ["OUT_REG", "IN"], + fuzz_enum_setting("{}.DELAYMUX".format(prim), ["OUT_REG", "IN"], lambda x: get_substs(mode="IREG_OREG", kv=("DELAYMUX", x), glb=True), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.MOVEMUX".format(prim), ["0", "MOVE"], - lambda x: get_substs(mode="IREG_OREG", kv=("MOVEMUX", x), glb=True), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DIRMUX".format(prim), ["0", "DIR"], - lambda x: get_substs(mode="IREG_OREG", kv=("DIRMUX", x), glb=True), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.LOAD_NMUX".format(prim), ["1", "LOAD_N"], - lambda x: get_substs(mode="IREG_OREG", kv=("LOAD_NMUX", x), glb=True), False) - - nonrouting.fuzz_enum_setting(cfg, empty, "{}.INREG.REGSET".format(prim), ["SET", "RESET"], + if not s: + fuzz_enum_setting("{}.MOVEMUX".format(prim), ["0", "MOVE"], + lambda x: get_substs(mode="IREG_OREG", kv=("MOVEMUX", x), glb=True), False) + fuzz_enum_setting("{}.DIRMUX".format(prim), ["0", "DIR"], + lambda x: get_substs(mode="IREG_OREG", kv=("DIRMUX", x), glb=True), False) + fuzz_enum_setting("{}.LOAD_NMUX".format(prim), ["1", "LOAD_N"], + lambda x: get_substs(mode="IREG_OREG", kv=("LOAD_NMUX", x), glb=True), False) + + fuzz_enum_setting("{}.INREG.REGSET".format(prim), ["SET", "RESET"], lambda x: get_substs(mode="IREG_OREG", kv=("REGSET", x), scope="INREG"), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.OUTREG.REGSET".format(prim), ["SET", "RESET"], + fuzz_enum_setting("{}.OUTREG.REGSET".format(prim), ["SET", "RESET"], lambda x: get_substs(mode="IREG_OREG", kv=("REGSET", x), scope="OUTREG"), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.TSREG.REGSET".format(prim), ["SET", "RESET"], + fuzz_enum_setting("{}.TSREG.REGSET".format(prim), ["SET", "RESET"], lambda x: get_substs(mode="IREG_OREG", kv=("REGSET", x), scope="TSREG"), False) if not s: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.MIDDRXN.DDRMODE".format(prim), ["NONE", "MIDDRX2", "MIDDRX4"], + fuzz_enum_setting("{}.MIDDRXN.DDRMODE".format(prim), ["MIDDRX2", "MIDDRX4"], lambda x: get_substs(mode="MIDDRXN_MODDRXN", kv=("DDRMODE", "#OFF" if x == "NONE" else x), scope="MIDDRXN"), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.MODDRXN.DDRMODE".format(prim), ["NONE", "MOSHX2", "MOSHX4", "MODDRX2_DQSW", "MODDRX4_DQSW", "MODDRX2_DQSW270", "MODDRX4_DQSW270"], + fuzz_enum_setting("{}.MODDRXN.DDRMODE".format(prim), ["MOSHX2", "MOSHX4", "MODDRX2_DQSW", "MODDRX4_DQSW", "MODDRX2_DQSW270", "MODDRX4_DQSW270"], lambda x: get_substs(mode="MIDDRXN_MODDRXN", kv=("DDRMODE", "#OFF" if x == "NONE" else x), scope="MODDRXN", dqs=True), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.MTDDRXN.DDRMODE".format(prim), ["NONE", "MTSHX2", "MTSHX4"], + fuzz_enum_setting("{}.MTDDRXN.DDRMODE".format(prim), ["MTSHX2", "MTSHX4"], lambda x: get_substs(mode="MIDDRXN_MODDRXN", kv=("DDRMODE", "#OFF" if x == "NONE" else x + " TOUTMUX:MTDDR"), scope="MTDDRXN"), False) - fuzzloops.parallel_foreach(configs, per_config) + + await asyncio.gather(*futures) + + await asyncio.gather(*[per_config(c) for c in configs]) if __name__ == "__main__": - main() + fuzzloops.FuzzerAsyncMain(main) diff --git a/fuzzers/LIFCL/071-iodelay/fuzzer.py b/fuzzers/LIFCL/071-iodelay/fuzzer.py index 23a7858..d5c89f7 100644 --- a/fuzzers/LIFCL/071-iodelay/fuzzer.py +++ b/fuzzers/LIFCL/071-iodelay/fuzzer.py @@ -1,3 +1,5 @@ +import asyncio + from fuzzconfig import FuzzConfig import nonrouting import fuzzloops @@ -64,8 +66,8 @@ def create_cfgs(device): ] -def main(): - def per_config(x): +async def main(executor): + async def per_config(x): site, prim, cfg = x cfg.setup() empty = cfg.build_design(cfg.sv, {}) @@ -101,25 +103,34 @@ def intval(vec): x |= (1 << i) return x - nonrouting.fuzz_word_setting(cfg, "{}.DELAY.DEL_VALUE".format(prim), 7, + futures = [] + def fuzz_enum_setting(*args, **kwargs): + futures.append(fuzzloops.wrap_future(nonrouting.fuzz_enum_setting(cfg, empty, *args, **kwargs,executor=executor))) + + def fuzz_word_setting(*args, **kwargs): + futures.append(fuzzloops.wrap_future(nonrouting.fuzz_word_setting(cfg, *args, **kwargs,executor=executor))) + + fuzz_word_setting("{}.DELAY.DEL_VALUE".format(prim), 7, lambda x : get_substs(kv=("DEL_VALUE", str(intval(x)))), desc="initial fine delay value") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.COARSE_DELAY".format(prim), ["0NS", "0P8NS", "1P6NS"], + fuzz_enum_setting("{}.DELAY.COARSE_DELAY".format(prim), ["0NS", "0P8NS", "1P6NS"], lambda x: get_substs(kv=("COARSE_DELAY", x)), False) if not s: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.COARSE_DELAY_MODE".format(prim), ["DYNAMIC", "STATIC"], + fuzz_enum_setting("{}.DELAY.COARSE_DELAY_MODE".format(prim), ["DYNAMIC", "STATIC"], lambda x: get_substs(kv=("COARSE_DELAY_MODE", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.EDGE_MONITOR".format(prim), ["ENABLED", "DISABLED"], + fuzz_enum_setting("{}.DELAY.EDGE_MONITOR".format(prim), ["ENABLED", "DISABLED"], lambda x: get_substs(kv=("EDGE_MONITOR", x)), False) - nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.WAIT_FOR_EDGE".format(prim), ["ENABLED", "DISABLED"], + fuzz_enum_setting("{}.DELAY.WAIT_FOR_EDGE".format(prim), ["ENABLED", "DISABLED"], lambda x: get_substs(kv=("WAIT_FOR_EDGE", x)), False) for pin in ["CIBCRS0", "CIBCRS1", "RANKSELECT", "RANKENABLE", "RANK0UPDATE", "RANK1UPDATE"]: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}MUX".format(prim, pin), ["OFF", pin], - lambda x: get_substs(kv=(pin, x), mux=True), False) + fuzz_enum_setting("{}.{}MUX".format(prim, pin), ["OFF", pin], + lambda x, pin=pin: get_substs(kv=(pin, x), mux=True), False) + + await asyncio.gather(*futures) - fuzzloops.parallel_foreach(configs, per_config) + await asyncio.gather(*[per_config(c) for c in configs]) if __name__ == "__main__": - main() + fuzzloops.FuzzerAsyncMain(main) diff --git a/fuzzers/LIFCL/080-dsp-config/fuzzer.py b/fuzzers/LIFCL/080-dsp-config/fuzzer.py index 0c1081e..89c8250 100644 --- a/fuzzers/LIFCL/080-dsp-config/fuzzer.py +++ b/fuzzers/LIFCL/080-dsp-config/fuzzer.py @@ -1,3 +1,6 @@ +import asyncio +import logging + from fuzzconfig import FuzzConfig import nonrouting import fuzzloops @@ -138,8 +141,8 @@ ] } -def main(): - def per_config(x): +async def main(executor): + async def per_config(x): rc, cfg = x r, c = rc locs = [ @@ -187,7 +190,7 @@ def per_config(x): cfg.setup() empty = cfg.build_design(cfg.sv, {}) cfg.sv = "dsp.v" - def per_loc(l): + async def per_loc(l): dsp, prim, site = l def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False, extra_sigs=""): if default_cfg: @@ -204,47 +207,57 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False, extra_sigs="" else: config = "{}:::{}={}".format(mode, kv[0], kv[1]) return dict(mode=mode, cmt="//" if mode == "NONE" else "", config=config, prim=prim, site=site) + + futures = [] + def fuzz_enum_setting(*args, **kwargs): + futures.append(asyncio.wrap_future(executor.submit(nonrouting.fuzz_enum_setting, cfg, empty, *args, **kwargs, executor=executor))) + + if prim == "ACC54_CORE": # Use 'cover' to get a minimal bit set - nonrouting.fuzz_enum_setting(cfg, empty, "{}.MODE".format(dsp), ["NONE", prim], + fuzz_enum_setting("{}.MODE".format(dsp), ["NONE", prim], lambda x: get_substs(x[0], default_cfg=True, extra_sigs=x[1]), False, assume_zero_base=True, min_cover={"NONE": [""], "ACC54_CORE": [" ACC54_CORE::::RSTCTRL=0", " ACC54_CORE::::RSTCTRL=#SIG"]}, desc="{} primitive mode".format(dsp)) else: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.MODE".format(dsp), ["NONE", prim], + fuzz_enum_setting("{}.MODE".format(dsp), ["NONE", prim], lambda x: get_substs(x, default_cfg=True), False, assume_zero_base=True, desc="{} primitive mode".format(dsp)) if prim not in ("MULT18_CORE", "MULT18X36_CORE", "MULT36_CORE"): - nonrouting.fuzz_enum_setting(cfg, empty, "{}.GSR".format(dsp), ["ENABLED", "DISABLED"], + fuzz_enum_setting("{}.GSR".format(dsp), ["ENABLED", "DISABLED"], lambda x: get_substs(mode=prim, kv=("GSR", x)), False, desc="if `ENABLED` primitive is reset by user GSR") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.RESET".format(dsp), ["SYNC", "ASYNC"], + fuzz_enum_setting("{}.RESET".format(dsp), ["SYNC", "ASYNC"], lambda x: get_substs(mode=prim, kv=("RESET", x)), False, desc="selects synchronous or asynchronous reset for DSP registers") - nonrouting.fuzz_enum_setting(cfg, empty, "{}.CLKMUX".format(dsp), ["0", "CLK", "INV"], + fuzz_enum_setting("{}.CLKMUX".format(dsp), ["0", "CLK", "INV"], lambda x: get_substs(mode=prim, kv=("CLK", x), mux=True), False, assume_zero_base=True, desc="clock gating and inversion control") for ce in ce_sigs[prim]: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}MUX".format(dsp, ce), ["1", ce, "INV"], - lambda x: get_substs(mode=prim, kv=(ce, x), mux=True, extra_sigs=",CLK=#SIG"), + fuzz_enum_setting("{}.{}MUX".format(dsp, ce), ["1", ce, "INV"], + lambda x,ce=ce: get_substs(mode=prim, kv=(ce, x), mux=True, extra_sigs=",CLK=#SIG"), False, assume_zero_base=True, desc="{} gating and inversion control".format(ce)) for rst in rst_sigs[prim]: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}MUX".format(dsp, rst), ["0", rst, "INV"], - lambda x: get_substs(mode=prim, kv=(rst, x), mux=True, extra_sigs=",CLK=#SIG"), + fuzz_enum_setting("{}.{}MUX".format(dsp, rst), ["0", rst, "INV"], + lambda x,rst=rst: get_substs(mode=prim, kv=(rst, x), mux=True, extra_sigs=",CLK=#SIG"), False, assume_zero_base=True, desc="{} gating and inversion control".format(rst)) for reg in regs[prim]: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.REGBYPS{}".format(dsp, reg), ["REGISTER", "BYPASS"], - lambda x: get_substs(mode=prim, kv=("REGBYPS{}".format(reg), x)), False, assume_zero_base=True, + fuzz_enum_setting("{}.REGBYPS{}".format(dsp, reg), ["REGISTER", "BYPASS"], + lambda x,reg=reg: get_substs(mode=prim, kv=("REGBYPS{}".format(reg), x)), False, assume_zero_base=True, desc="register enable or bypass{}{}".format(" for " if reg != "" else "", reg)) for name, opts, desc in misc_config[prim]: - nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}".format(dsp, name), opts, - lambda x: get_substs(mode=prim, kv=(name, x)), False, assume_zero_base=True, + fuzz_enum_setting("{}.{}".format(dsp, name), opts, + lambda x,desc=desc,name=name: get_substs(mode=prim, kv=(name, x)), False, assume_zero_base=True, desc=desc) - fuzzloops.parallel_foreach(locs, per_loc) - fuzzloops.parallel_foreach(configs, per_config) + + await asyncio.gather(*futures) + await asyncio.gather(*[per_loc(l) for l in locs]) + await asyncio.gather(*[per_config(config) for config in configs]) if __name__ == "__main__": - main() + fuzzloops.FuzzerAsyncMain(main) + + diff --git a/fuzzers/LIFCL/090-sysconfig/fuzzer.py b/fuzzers/LIFCL/090-sysconfig/fuzzer.py index 297becc..5ac0034 100644 --- a/fuzzers/LIFCL/090-sysconfig/fuzzer.py +++ b/fuzzers/LIFCL/090-sysconfig/fuzzer.py @@ -12,7 +12,7 @@ "CIB_R0C72:I2C_15K", "CIB_R0C71:OSC_15K", "CIB_R0C70:PMU_15K", "CIB_R0C66:EFB_15K"]) ] -def main(): +def main(executor): for cfg in cfgs: cfg.setup() empty = cfg.build_design(cfg.sv, {}) @@ -23,34 +23,34 @@ def get_substs(k, v): nonrouting.fuzz_enum_setting(cfg, empty, "SYSCONFIG.MASTER_SPI_PORT", ["DISABLE", "SERIAL", "DUAL", "QUAD"], lambda x: get_substs("MASTER_SPI_PORT", x), False, assume_zero_base=True, - desc="status of master SPI port after configuration") + desc="status of master SPI port after configuration",executor=executor) nonrouting.fuzz_enum_setting(cfg, empty, "SYSCONFIG.SLAVE_SPI_PORT", ["DISABLE", "SERIAL", "DUAL", "QUAD"], lambda x: get_substs("SLAVE_SPI_PORT", x), False, assume_zero_base=True, - desc="status of slave SPI port after configuration") + desc="status of slave SPI port after configuration",executor=executor) nonrouting.fuzz_enum_setting(cfg, empty, "SYSCONFIG.SLAVE_I2C_PORT", ["DISABLE", "ENABLE"], lambda x: get_substs("SLAVE_I2C_PORT", x), False, assume_zero_base=True, - desc="status of slave I2C port after configuration") + desc="status of slave I2C port after configuration",executor=executor) nonrouting.fuzz_enum_setting(cfg, empty, "SYSCONFIG.SLAVE_I3C_PORT", ["DISABLE", "ENABLE"], lambda x: get_substs("SLAVE_I3C_PORT", x), False, assume_zero_base=True, - desc="status of slave I3C port after configuration") + desc="status of slave I3C port after configuration",executor=executor) nonrouting.fuzz_enum_setting(cfg, empty, "SYSCONFIG.JTAG_PORT", ["DISABLE", "ENABLE"], lambda x: get_substs("JTAG_PORT", x), False, assume_zero_base=True, - desc="status of JTAG port after configuration") + desc="status of JTAG port after configuration",executor=executor) nonrouting.fuzz_enum_setting(cfg, empty, "SYSCONFIG.DONE_PORT", ["DISABLE", "ENABLE"], lambda x: get_substs("DONE_PORT", x), False, assume_zero_base=True, - desc="use DONE output after configuration") + desc="use DONE output after configuration",executor=executor) nonrouting.fuzz_enum_setting(cfg, empty, "SYSCONFIG.INITN_PORT", ["DISABLE", "ENABLE"], lambda x: get_substs("INITN_PORT", x), False, assume_zero_base=True, - desc="use INITN input after configuration") + desc="use INITN input after configuration",executor=executor) nonrouting.fuzz_enum_setting(cfg, empty, "SYSCONFIG.PROGRAMN_PORT", ["DISABLE", "ENABLE"], lambda x: get_substs("PROGRAMN_PORT", x), False, assume_zero_base=True, - desc="use PROGRAMN input after configuration") + desc="use PROGRAMN input after configuration",executor=executor) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/091-osc/fuzzer.py b/fuzzers/LIFCL/091-osc/fuzzer.py index 0a58d1e..69d2759 100644 --- a/fuzzers/LIFCL/091-osc/fuzzer.py +++ b/fuzzers/LIFCL/091-osc/fuzzer.py @@ -1,3 +1,5 @@ +import logging + from fuzzconfig import FuzzConfig import nonrouting import fuzzloops @@ -12,7 +14,7 @@ FuzzConfig(job="OSCMODE40", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R0C77:EFB_1_OSC"]), ] -def main(): +def main(executor): for cfg in cfgs: cfg.setup() empty = cfg.build_design(cfg.sv, {}) @@ -28,6 +30,10 @@ def bin_to_int(x): return val sites = tiles.get_sites_from_primitive(cfg.device, "OSC_CORE") + if len(sites) == 0: + logging.error(f"No OSC_CORE's defined for {cfg.device}") + continue + site = list(sites.keys())[0] def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): @@ -104,4 +110,4 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): if __name__ == '__main__': - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/092-gsr/fuzzer.py b/fuzzers/LIFCL/092-gsr/fuzzer.py index eb5a028..fae7b6a 100644 --- a/fuzzers/LIFCL/092-gsr/fuzzer.py +++ b/fuzzers/LIFCL/092-gsr/fuzzer.py @@ -56,4 +56,5 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=True, full_mux_style=False) if __name__ == '__main__': - main() + fuzzloops.FuzzerMain(main) + diff --git a/fuzzers/LIFCL/093-oscd/fuzzer.py b/fuzzers/LIFCL/093-oscd/fuzzer.py index 44a0ced..f78ef08 100644 --- a/fuzzers/LIFCL/093-oscd/fuzzer.py +++ b/fuzzers/LIFCL/093-oscd/fuzzer.py @@ -54,4 +54,5 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): if __name__ == '__main__': - main() + fuzzloops.FuzzerMain(main) + diff --git a/fuzzers/LIFCL/100-ip-base/fuzzer.py b/fuzzers/LIFCL/100-ip-base/fuzzer.py index df5c21c..816b8e5 100644 --- a/fuzzers/LIFCL/100-ip-base/fuzzer.py +++ b/fuzzers/LIFCL/100-ip-base/fuzzer.py @@ -1,3 +1,5 @@ +import logging + import fuzzconfig import nonrouting import fuzzloops @@ -138,9 +140,14 @@ def main(): if wid_idx != -1: prim_type = prim_type[0:wid_idx] bit = cfg.build_design(cfg.sv, dict(cmt="", prim=prim_type, site=site, config=ip_settings[prim])) - chip = libpyprjoxide.Chip.from_bitstream(fuzzconfig.db, bit) - ipv = chip.get_ip_values() - assert len(ipv) > 0 + with fuzzconfig.db_lock() as db: + chip = libpyprjoxide.Chip.from_bitstream(db, bit.bitstream) + ipv = chip.get_ip_values() + + logging.info(f"{bit.bitstream} {cfg.device} {site} {prim} has {len(ipv)} IP deltas") + if len(ipv) == 0: + continue + addr = ipv[0][0] ip_name = site if "EBR_CORE" in ip_name: @@ -155,4 +162,5 @@ def main(): print(json.dumps(dict(regions=ip_base), sort_keys=True, indent=4), file=jf) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) + diff --git a/fuzzers/LIFCL/110-global-structure/fuzzer.py b/fuzzers/LIFCL/110-global-structure/fuzzer.py index 0d57a3a..11fa246 100644 --- a/fuzzers/LIFCL/110-global-structure/fuzzer.py +++ b/fuzzers/LIFCL/110-global-structure/fuzzer.py @@ -8,6 +8,7 @@ from collections import defaultdict import tiles +import fuzzloops # name max_row max_col configs = [ @@ -47,7 +48,7 @@ def save_db(): # Determine branch driver locations test_row = 4 clock_wires = ["R{}C{}_JCLK0".format(test_row, c) for c in range(1, max_col)] - clock_info = lapie.get_node_data(cfg.udb, clock_wires) + clock_info = lapie.get_node_data(cfg.device, clock_wires) branch_to_col = defaultdict(list) for n in clock_info: r, c = pos_from_name(n.name) @@ -65,7 +66,7 @@ def save_db(): branch_wires = [f"R{test_row}C{bc}_HPBX0000" for bc in sorted(branch_to_col.keys())] if name == "LIFCL-17": branch_wires.append("R{}C13_RHPBX0000".format(test_row)) - branch_wire_info = lapie.get_node_data(cfg.udb, branch_wires) + branch_wire_info = lapie.get_node_data(cfg.device, branch_wires) branch_driver_col = {} # Branches directly driven by a VPSX # Also, get a test column for the spine exploration later @@ -99,7 +100,7 @@ def save_db(): # Spines sp_branch_wires = ["R{}C{}_HPBX0000".format(r, sp_test_col) for r in range(1, max_row)] spine_to_branch_row = {} - sp_info = lapie.get_node_data(cfg.udb, sp_branch_wires) + sp_info = lapie.get_node_data(cfg.device, sp_branch_wires) for n in sp_info: r, c = pos_from_name(n.name) vpsx_r = None @@ -122,7 +123,7 @@ def save_db(): # HROWs hrow_to_spine_col = {} spine_wires = ["R{}C{}_VPSX0000".format(sp_test_row, c) for c in sorted(set(branch_driver_col.values()))] - hr_info = lapie.get_node_data(cfg.udb, spine_wires) + hr_info = lapie.get_node_data(cfg.device, spine_wires) for n in hr_info: r, c = pos_from_name(n.name) hrow_c = None @@ -141,5 +142,5 @@ def save_db(): gdb["hrows"] = hrows save_db() if __name__ == '__main__': - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/121-pll-ipconfig/fuzzer.py b/fuzzers/LIFCL/121-pll-ipconfig/fuzzer.py index 5fdc6ab..b20a045 100644 --- a/fuzzers/LIFCL/121-pll-ipconfig/fuzzer.py +++ b/fuzzers/LIFCL/121-pll-ipconfig/fuzzer.py @@ -137,14 +137,14 @@ def main(): ] empty = cfg.build_design(cfg.sv, dict(k="V2I_PP_RES", v="11P3K")) for name, options, desc in enum_settings: - func = lambda x: dict(k=name, v=x) + func = lambda x,name=name: dict(k=name, v=x) if name == "V2I_KVCO_SEL": cfg.sv = "pll_2.v" empty = cfg.build_design(cfg.sv, dict(k="V2I_PP_RES", v="11P3K", ldt="LDT_LOCK_SEL", ldt_val="U_FREQ")) - func = lambda x: dict(k=name, v=x, ldt="LDT_LOCK_SEL", ldt_val="U_FREQ") + func = lambda x,name=name: dict(k=name, v=x, ldt="LDT_LOCK_SEL", ldt_val="U_FREQ") nonrouting.fuzz_ip_enum_setting(cfg, empty, name, options, func, desc) if name == "V2I_KVCO_SEL": cfg.sv = "pll.v" empty = cfg.build_design(cfg.sv, dict(k="V2I_PP_RES", v="11P3K")) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/122-pll-config/fuzzer.py b/fuzzers/LIFCL/122-pll-config/fuzzer.py index 483b870..faf187a 100644 --- a/fuzzers/LIFCL/122-pll-config/fuzzer.py +++ b/fuzzers/LIFCL/122-pll-config/fuzzer.py @@ -22,7 +22,7 @@ ), ] -def main(): +def main(executor): for prim, cfg in cfgs: cfg.setup() empty = cfg.build_design(cfg.sv, {}) @@ -55,15 +55,15 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): return dict(mode=mode, cmt="//" if mode == "NONE" else "", config=config, site=prim) nonrouting.fuzz_enum_setting(cfg, empty, "{}.MODE".format(prim), ["NONE", "PLL_CORE"], lambda x: get_substs(mode=x), False, - desc="PLL_CORE primitive mode") + desc="PLL_CORE primitive mode", executor=executor) nonrouting.fuzz_enum_setting(cfg, empty, "{}.CLKMUX_FB".format(prim), ["CMUX_CLKOP", "CMUX_CLKOS", "CMUX_CLKOS2", "CMUX_CLKOS3", "CMUX_CLKOS4", "CMUX_CLKOS5"], lambda x: get_substs(mode="PLL_CORE", kv=("CLKMUX_FB", x)), False, - desc="internal feedback selection") + desc="internal feedback selection", executor=executor) nonrouting.fuzz_enum_setting(cfg, empty, "{}.LMMICLKMUX".format(prim), ["LMMICLK", "INV"], lambda x: get_substs(mode="PLL_CORE", kv=("LMMICLK", x), mux=True), False, - desc="") + desc="", executor=executor) nonrouting.fuzz_enum_setting(cfg, empty, "{}.LMMIRESETNMUX".format(prim), ["LMMIRESETN", "INV"], lambda x: get_substs(mode="PLL_CORE", kv=("LMMIRESETN", x), mux=True), False, - desc="") -if __name__ == '__main__': - main() + desc="", executor=executor) +if __name__ == "__main__": + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/131-config-ip/fuzzer.py b/fuzzers/LIFCL/131-config-ip/fuzzer.py index 7537985..eb1a885 100644 --- a/fuzzers/LIFCL/131-config-ip/fuzzer.py +++ b/fuzzers/LIFCL/131-config-ip/fuzzer.py @@ -82,7 +82,7 @@ ("TCONFIG_WDT_CORE73", "CONFIG_WDT_CORE"), ("TCONFIG_CLKRST_CORE73", "CONFIG_CLKRST_CORE"), ("TCONFIG_IP_CORE73", "CONFIG_IP_CORE"), - ("TCONFIG_HSE_CORE73", "CONFIG_HSE_CORE"), + #("TCONFIG_HSE_CORE73", "CONFIG_HSE_CORE"), ("TCONFIG_MULTIBOOT_CORE73", "CONFIG_MULTIBOOT_CORE"), ("TCONFIG_LMMI_CORE73", "CONFIG_LMMI_CORE"), ] @@ -125,14 +125,14 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False, extra_sigs="" desc="{} primitive mode".format(prim)) for name, values, desc in settings[prim]: nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}".format(prim, name), values, - lambda x: get_substs(mode=prim, kv=(name, x)), False, + lambda x,name=name,prim=prim: get_substs(mode=prim, kv=(name, x)), False, desc=desc) if prim in words: for name, width, desc in words[prim]: nonrouting.fuzz_word_setting(cfg, "{}.{}".format(prim, name), width, - lambda x: get_substs(mode=prim, kv=(name, "0b" + "".join(reversed(["1" if b else "0" for b in x])))), + lambda x,name=name,prim=prim: get_substs(mode=prim, kv=(name, "0b" + "".join(reversed(["1" if b else "0" for b in x])))), desc=desc) fuzzloops.parallel_foreach(sites, per_site) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/140-bram-init/fuzzer.py b/fuzzers/LIFCL/140-bram-init/fuzzer.py index 2535d17..61eb4d5 100644 --- a/fuzzers/LIFCL/140-bram-init/fuzzer.py +++ b/fuzzers/LIFCL/140-bram-init/fuzzer.py @@ -19,4 +19,4 @@ def per_word(w): nonrouting.fuzz_ip_word_setting(cfg, "INITVAL_{:02X}".format(w), 320, lambda b: dict(a="{:02X}".format(w), v="0x{:080x}".format(bin2dec(b)))) fuzzloops.parallel_foreach(range(0x40), per_word) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/141-lram-init/fuzzer.py b/fuzzers/LIFCL/141-lram-init/fuzzer.py index 70186e3..9bc5e0e 100644 --- a/fuzzers/LIFCL/141-lram-init/fuzzer.py +++ b/fuzzers/LIFCL/141-lram-init/fuzzer.py @@ -1,3 +1,5 @@ +import asyncio + from fuzzconfig import FuzzConfig import nonrouting import fuzzloops @@ -12,12 +14,13 @@ def bin2dec(bits): x |= (1 << i) return x -def main(): +async def main(executor): cfg.setup() cfg.sv = "lram.v" - def per_word(w): - nonrouting.fuzz_ip_word_setting(cfg, "INITVAL_{:02X}".format(w), 5120, lambda b: dict(a="{:02X}".format(w), v="0x{:01280x}".format(bin2dec(b)))) + async def per_word(w): + await fuzzloops.wrap_future(nonrouting.fuzz_ip_word_setting(cfg, "INITVAL_{:02X}".format(w), 5120, + lambda b: dict(a="{:02X}".format(w), v="0x{:01280x}".format(bin2dec(b))), executor=executor)) # Only fuzz a couple of init values to stop the database getting massive - we can derive the rest - fuzzloops.parallel_foreach(range(0x2), per_word) + await asyncio.gather(*[per_word(w) for w in range(0x02)]) if __name__ == "__main__": - main() + fuzzloops.FuzzerAsyncMain(main) diff --git a/fuzzers/LIFCL/151-eclkprim/fuzzer.py b/fuzzers/LIFCL/151-eclkprim/fuzzer.py index 4a419c5..13dcd41 100644 --- a/fuzzers/LIFCL/151-eclkprim/fuzzer.py +++ b/fuzzers/LIFCL/151-eclkprim/fuzzer.py @@ -63,6 +63,7 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): desc="ECLKDIV divide value") def main(): fuzzloops.parallel_foreach(configs, per_loc) + if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/153-dqsbuf/fuzzer.py b/fuzzers/LIFCL/153-dqsbuf/fuzzer.py index 372cb35..3470027 100644 --- a/fuzzers/LIFCL/153-dqsbuf/fuzzer.py +++ b/fuzzers/LIFCL/153-dqsbuf/fuzzer.py @@ -115,5 +115,5 @@ def intval(vec): def main(): fuzzloops.parallel_foreach(configs, per_loc) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) diff --git a/fuzzers/LIFCL/154-ddrll/fuzzer.py b/fuzzers/LIFCL/154-ddrll/fuzzer.py index 7fe4462..d1a1315 100644 --- a/fuzzers/LIFCL/154-ddrll/fuzzer.py +++ b/fuzzers/LIFCL/154-ddrll/fuzzer.py @@ -2,6 +2,7 @@ from interconnect import fuzz_interconnect import nonrouting import re +import fuzzloops configs = [ { @@ -70,4 +71,5 @@ def get_substs(mode="DDRDLL_CORE", kv=None, mux=False): nonrouting.fuzz_enum_setting(cfg, empty, "DDRDLL.RSTMUX", ["RST", "INV"], lambda x: get_substs(kv=("RST", x), mux=True), False) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) + diff --git a/fuzzers/LIFCL/155-dlldel/fuzzer.py b/fuzzers/LIFCL/155-dlldel/fuzzer.py index 5764d1c..b47d2fc 100644 --- a/fuzzers/LIFCL/155-dlldel/fuzzer.py +++ b/fuzzers/LIFCL/155-dlldel/fuzzer.py @@ -137,4 +137,5 @@ def intval(vec): lambda x: get_substs(kv=("CLKIN", x), mux=True), False) fuzzloops.parallel_foreach(configs, per_cfg) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) + diff --git a/fuzzers/LIFCL/161-dphy-ipconfig/fuzzer.py b/fuzzers/LIFCL/161-dphy-ipconfig/fuzzer.py index 97c111e..4771ee9 100644 --- a/fuzzers/LIFCL/161-dphy-ipconfig/fuzzer.py +++ b/fuzzers/LIFCL/161-dphy-ipconfig/fuzzer.py @@ -28,4 +28,5 @@ def per_enum(e): fuzzloops.parallel_foreach(enums, per_enum) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) + diff --git a/fuzzers/LIFCL/162-pcie-ipconfig/fuzzer.py b/fuzzers/LIFCL/162-pcie-ipconfig/fuzzer.py index 376bc0f..4ae67ab 100644 --- a/fuzzers/LIFCL/162-pcie-ipconfig/fuzzer.py +++ b/fuzzers/LIFCL/162-pcie-ipconfig/fuzzer.py @@ -40,4 +40,5 @@ def per_enum(e): fuzzloops.parallel_foreach(enums, per_enum) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) + diff --git a/fuzzers/LIFCL/900-always-on/fuzzer.py b/fuzzers/LIFCL/900-always-on/fuzzer.py index a69b5b1..0c4dd52 100644 --- a/fuzzers/LIFCL/900-always-on/fuzzer.py +++ b/fuzzers/LIFCL/900-always-on/fuzzer.py @@ -16,7 +16,8 @@ def main(): for cfg in cfgs: cfg.setup() empty = cfg.build_design(cfg.sv, {}) - libpyprjoxide.add_always_on_bits(db, empty) + libpyprjoxide.add_always_on_bits(db, empty.bitstream) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) + From 40c96858a1561fb8f3083877de39c0803733bf06 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Tue, 17 Feb 2026 14:26:41 -0700 Subject: [PATCH 24/29] Move interconnect fuzzers to unused dir --- .../LIFCL/unused/001-plc-routing/fuzzer.py | 51 +++++++ .../LIFCL/unused/002-cib-routing/fuzzer.py | 102 +++++++++++++ .../LIFCL/unused/015-local-routes/fuzzer.py | 135 ++++++++++++++++++ fuzzers/LIFCL/unused/022-midmux/fuzzer.py | 63 ++++++++ 4 files changed, 351 insertions(+) create mode 100644 fuzzers/LIFCL/unused/001-plc-routing/fuzzer.py create mode 100644 fuzzers/LIFCL/unused/002-cib-routing/fuzzer.py create mode 100644 fuzzers/LIFCL/unused/015-local-routes/fuzzer.py create mode 100644 fuzzers/LIFCL/unused/022-midmux/fuzzer.py diff --git a/fuzzers/LIFCL/unused/001-plc-routing/fuzzer.py b/fuzzers/LIFCL/unused/001-plc-routing/fuzzer.py new file mode 100644 index 0000000..831fbc1 --- /dev/null +++ b/fuzzers/LIFCL/unused/001-plc-routing/fuzzer.py @@ -0,0 +1,51 @@ +import asyncio +import logging + +from fuzzconfig import FuzzConfig +from interconnect import fuzz_interconnect, fuzz_interconnect_sinks_across_span +import re +import tiles +import database +import fuzzconfig +import fuzzloops + +async def run_cfg(executor, device): + all_of_type = list(tiles.get_tiles_by_tiletype(device, "PLC").keys()) + def not_on_edge(rc): + return rc[0] > 5 and rc[1] > 5 + sorted_tiles = [tile for tile in sorted(all_of_type, key=lambda t: tiles.get_rc_from_name(device, t)) if not_on_edge(tiles.get_rc_from_name(device, tile))] + tile = sorted_tiles[len(sorted_tiles)//2] + + logging.info(f"Total PLC count {len(all_of_type)}") + (r, c) = tiles.get_rc_from_name(device, tile) + + tap_plcs = list(tiles.get_tiles_by_tiletype(device, "TAP_PLC")) + + cfg = FuzzConfig(job=f"PLCROUTE-{device}-{tile}", device=device, sv=f"../shared/route.v", tiles=[tile]) + + nodes = [f"R{r}C{c}_J.*", f"R{r}C{c}_H00.0.00", f"R{r}C{c}_HFIE0000"] + \ + [f"R{r}C{c}_{o}01{d}{i:02}00" for i in range(4) for o in "HV" for d in "SW"] + \ + [f"R{r}C{c}_V00{s}{i:02}00" for i in range(4) for s in "TB"] + + extra_sources = [] + extra_sources += ["R{}C{}_H02E{:02}01".format(r, c+1, i) for i in range(8)] + extra_sources += ["R{}C{}_H06E{:02}03".format(r, c+3, i) for i in range(4)] + extra_sources += ["R{}C{}_V02N{:02}01".format(r-1, c, i) for i in range(8)] + extra_sources += ["R{}C{}_V06N{:02}03".format(r-3, c, i) for i in range(4)] + extra_sources += ["R{}C{}_V02S{:02}01".format(r+1, c, i) for i in range(8)] + extra_sources += ["R{}C{}_V06S{:02}03".format(r+3, c, i) for i in range(4)] + extra_sources += ["R{}C{}_H02W{:02}01".format(r, c-1, i) for i in range(8)] + extra_sources += ["R{}C{}_H06W{:02}03".format(r, c - 3, i) for i in range(4)] + + + extra_cfg = FuzzConfig(job=f"PLCROUTE-{device}-{tile}-extra", device=device, sv=f"../shared/route.v", tiles=[tile]) + await asyncio.gather( + fuzz_interconnect_sinks_across_span(config=cfg, tile_span = all_of_type, nodenames=nodes, regex=True, bidir=True, ignore_tiles=tap_plcs, executor=executor, max_per_design=len(all_of_type) / 2), + fuzz_interconnect_sinks_across_span(config=extra_cfg, tile_span = all_of_type, nodenames=extra_sources, regex=False, bidir=False, ignore_tiles=tap_plcs, executor=executor, max_per_design=len(all_of_type) / 2), + ) + +async def FuzzAsync(executor): + await run_cfg(executor, fuzzconfig.devices_to_fuzz()[0]) + +if __name__ == "__main__": + fuzzloops.FuzzerAsyncMain(FuzzAsync) diff --git a/fuzzers/LIFCL/unused/002-cib-routing/fuzzer.py b/fuzzers/LIFCL/unused/002-cib-routing/fuzzer.py new file mode 100644 index 0000000..96aac54 --- /dev/null +++ b/fuzzers/LIFCL/unused/002-cib-routing/fuzzer.py @@ -0,0 +1,102 @@ +import asyncio +import logging + +from fuzzconfig import FuzzConfig +from interconnect import fuzz_interconnect, fuzz_interconnect_sinks_across_span +from fuzzloops import FuzzerAsyncMain +import fuzzconfig +import tiles +import re + +async def per_config(rc, device, tile, all_of_type, ignore, executor): + tiletype = all_of_type[0].split(":")[-1] + + r, c = rc + nodes = ["R{}C{}_J*".format(r, c)] + extra_sources = [] + extra_sources += ["R{}C{}_H02E{:02}01".format(r, c+1, i) for i in range(8)] + extra_sources += ["R{}C{}_H06E{:02}03".format(r, c+3, i) for i in range(4)] + if r != 1: + extra_sources += ["R{}C{}_V02N{:02}01".format(r-1, c, i) for i in range(8)] + extra_sources += ["R{}C{}_V06N{:02}03".format(r-3, c, i) for i in range(4)] + else: + extra_sources += ["R{}C{}_V02N{:02}00".format(r, c, i) for i in range(8)] + extra_sources += ["R{}C{}_V06N{:02}00".format(r, c, i) for i in range(4)] + extra_sources += ["R{}C{}_V02S{:02}01".format(r+1, c, i) for i in range(8)] + extra_sources += ["R{}C{}_V06S{:02}03".format(r+3, c, i) for i in range(4)] + if c != 1: + extra_sources += ["R{}C{}_H02W{:02}01".format(r, c-1, i) for i in range(8)] + extra_sources += ["R{}C{}_H06W{:02}03".format(r, c-3, i) for i in range(4)] + else: + extra_sources += ["R{}C{}_H02W{:02}00".format(r, c, i) for i in range(8)] + extra_sources += ["R{}C{}_H06W{:02}00".format(r, c, i) for i in range(4)] + def pip_filter(pip, nodes): + from_wire, to_wire = pip + return not ("_CORE" in from_wire or "_CORE" in to_wire or "JCIBMUXOUT" in to_wire) + def fc_filter(to_wire): + return "CIBMUX" in to_wire or "CIBTEST" in to_wire or to_wire.startswith("R{}C{}_J".format(r, c)) + + kwargs = { + "ignore_tiles": ignore, + "pip_predicate": pip_filter, + "fc_filter": fc_filter, + "executor": executor + } + + unspanable_types = []#["CIB_LR_A", "CIB_LR", "CIB_LR_B", "CIB_T"] + + + main_cfg = FuzzConfig(job=f"{tiletype}-ROUTE", device=device, tiles=[tile]) + extra_cfg = FuzzConfig(job=f"{tiletype}-ROUTE-EXTRAS", device=device, tiles=[tile]) + + if tiletype in unspanable_types: + fuzz_jobs = [executor.submit(fuzz_interconnect, config =main_cfg, nodenames=nodes, regex=True, bidir=True, **kwargs), + executor.submit(fuzz_interconnect, config=extra_cfg, nodenames=extra_sources, regex=False, + bidir=False, **kwargs), + ] + await asyncio.gather(*[asyncio.wrap_future(f) for future_lists in await asyncio.gather(*fuzz_jobs) for f in future_lists]) + else: + await asyncio.gather(asyncio.create_task(fuzz_interconnect_sinks_across_span(config=main_cfg, tile_span=all_of_type, nodenames=nodes, regex=True,bidir=True, **kwargs)), + asyncio.create_task(fuzz_interconnect_sinks_across_span(config = extra_cfg, tile_span=all_of_type, nodenames=extra_sources, regex=False, bidir=False, **kwargs))) + + +async def per_tiletype(device, tiletype, executor): + all_of_type = list(tiles.get_tiles_by_tiletype(device, tiletype).keys()) + + all_taps = { + tile + for tile in tiles.get_tiles_by_tiletype(device, tiletype) + for tiletype in tiles.get_tiletypes(device) + if tiletype.startswith("TAP") + } + + #tiles.draw_rc(device, {tiles.get_rc_from_name(device, t) for t in all_of_type}) + + logging.info(f"Total tiles for {tiletype} count {len(all_of_type)}") + def is_not_edge(tile): + (r, c) = tiles.get_rc_from_name(device, tile) + return r > 15 and c > 15 + + sorted_tiles = [tile for tile in sorted(all_of_type, key=lambda t: tiles.get_rc_from_name(device, t))] + tile = sorted_tiles[len(sorted_tiles)//2] + + (r, c) = tiles.get_rc_from_name(device, tile) + + + await per_config((r,c), device, tile, all_of_type, all_taps, executor) + +async def FuzzAsync(executor): + cib_tile_types = {t:device + for device in fuzzconfig.devices_to_fuzz() + for t in tiles.get_tiletypes(device) if t.startswith("CIB")} + + await asyncio.gather(*[ + per_tiletype(device, cib_tile_type, executor) + for cib_tile_type,device in cib_tile_types.items() + ]) + +def main(): + FuzzerAsyncMain(FuzzAsync) + +if __name__ == "__main__": + main() diff --git a/fuzzers/LIFCL/unused/015-local-routes/fuzzer.py b/fuzzers/LIFCL/unused/015-local-routes/fuzzer.py new file mode 100644 index 0000000..54b61b7 --- /dev/null +++ b/fuzzers/LIFCL/unused/015-local-routes/fuzzer.py @@ -0,0 +1,135 @@ +import asyncio +import logging +import re +import sys +from collections import defaultdict + +import cachecontrol +import fuzzconfig +import fuzzloops +import interconnect +import lapie +import libpyprjoxide +import nonrouting +import primitives +import radiant +import tiles +from fuzzconfig import FuzzConfig, db_lock +from interconnect import fuzz_interconnect_sinks + +import database + +### +# The idea for this fuzzer is that we can map out the internal pips and connections for a given tiletype in relatively +# short order without knowing much about them by using the introspection from the radiant tools. The basic process is: +# - Generate a list of wires and pips a given tile type has +# - Figure out which tiles configure these pips +# - Use all the tiles of that tiletype on the board to test. So if there are 16 tiles with that tiletype, we can solve +# for 16 PIP configurations at a time. +### + +processed_tiletypes = set() + +exclusion_list = { + # I think this particular pip needs other things in SYSIO_B3 to trigger, but when SYSIO_B3_1 is + # driving SYSIO_B3_0_ECLK_L, the pip seems active. Just blacklist this one and accept the bit flip. + ("SYSIO_B3_0", "JECLKIN1_I218", "JECLKOUT_I218") +} + +### Gather up all the consistent internal pips for a tiletype, then use however many tiles of that tiletype exist +### to create design sets for each PIP. This also tracks and manages tiles that configure the tiletype but are positioned +### relative to it and have different tiles types +async def tiletype_interconnect_job(device, tiletype, executor = None): + + # representative nodes is all wires that are common to all instances of that tiletype for the device + wires = tiles.get_representative_nodes_for_tiletype(device, tiletype) + + logging.info(f"Tiletype {tiletype} has {len(wires)} representative nodes; coincides with {tiles.get_coincidental_tiletypes_for_tiletype(device, tiletype)}") + + if len(wires) == 0: + logging.debug(f"{tiletype} has no consistent internal wires") + return tiletype, [], [] + + ts = sorted(list(tiles.get_tiles_by_tiletype(device, tiletype).keys())) + + # Treat this as an exemplar node to gather the pips from + (r, c) = tiles.get_rc_from_name(device, ts[0]) + nodes = set([tiles.resolve_actual_node(device, w, (r,c)) for w in wires]) + + if None in nodes: + nodes.remove(None) + + # These are almost 100% consistent EXCEPT for the center of the chip where the lines have to make a longer jump. + special_nodes = ["HFIE", "HFOE"] + + pips = sorted([ + (p.from_wire, p.to_wire) + for n in lapie.get_node_data(device, nodes) + for p in n.uphill_pips + if not any([special_node in n + for special_node in special_nodes + for n in [p.from_wire, p.to_wire] + ]) + ]) + + cfg = FuzzConfig(job=f"{tiletype}-routes", device=device, tiles=[ts[0]]) + + await asyncio.create_task(interconnect.fuzz_interconnect_sinks_across_span( + config = cfg, + tile_span = ts, + pips = pips, + exclusion_list=exclusion_list, + executor = executor + )) + +def get_filtered_typetypes(device): + tiletypes = tiles.get_tiletypes(device) + for tiletype, ts in sorted(tiletypes.items()): + + if tiletype in ["TAP_PLC"]: + continue + + if len(sys.argv) > 1 and re.compile(sys.argv[1]).search(tiletype) is None: + continue + + if tiletype in processed_tiletypes: + continue + processed_tiletypes.add(tiletype) + yield tiletype + +async def run_for_device(device, executor = None): + if not fuzzconfig.should_fuzz_platform(device): + logging.warning(f"Ignoring device {device}") + return [] + + logging.info("Fuzzing device: " + device) + + await asyncio.gather(*[tiletype_interconnect_job(device, tiletype, executor=executor) + for tiletype in get_filtered_typetypes(device)]) + + return [] + +async def FuzzAsync(executor): + + families = database.get_devices()["families"] + devices = sorted([ + device + for family in families + for device in families[family]["devices"] + if fuzzconfig.should_fuzz_platform(device) + ]) + + all_tiletypes = sorted(set([tile.split(":")[-1] + for device in devices + for tile in database.get_tilegrid(device)["tiles"] + ])) + + if len(sys.argv) > 1 and not any(map(lambda tt: re.compile(sys.argv[1]).search(tt), all_tiletypes)): + logging.warning(f"Tiletype filter doesn't match any known tiles") + logging.warning(sorted(all_tiletypes)) + return [] + + return await asyncio.gather(*[run_for_device(device, executor) for device in devices]) + +if __name__ == "__main__": + fuzzloops.FuzzerAsyncMain(FuzzAsync) diff --git a/fuzzers/LIFCL/unused/022-midmux/fuzzer.py b/fuzzers/LIFCL/unused/022-midmux/fuzzer.py new file mode 100644 index 0000000..e486feb --- /dev/null +++ b/fuzzers/LIFCL/unused/022-midmux/fuzzer.py @@ -0,0 +1,63 @@ +import asyncio + +import fuzzloops +from fuzzconfig import FuzzConfig +from interconnect import fuzz_interconnect + +configs = [ + ("HPFE", (28, 0), 12, "L", + FuzzConfig(job="LMIDROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R28C0:LMID"])), + ("HPFW", (28, 86), 12, "R", + FuzzConfig(job="RMIDROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R28C87:RMID_DLY20"])), + ("VPFN", (56, 49), 18, "B", + FuzzConfig(job="BMIDROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R56C49:BMID_0_ECLK_1", "CIB_R56C50:BMID_1_ECLK_2"])), + ("VPFS", (0, 49), 16, "T", + FuzzConfig(job="TMIDROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R0C49:TMID_0", "CIB_R0C50:TMID_1"])), + + ("HPFE", (10, 0), 12, "L", + FuzzConfig(job="LMIDROUTE", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R10C0:LMID_RBB_5_15K"])), + ("HPFW", (10, 74), 12, "R", + FuzzConfig(job="RMIDROUTE", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R10C75:RMID_PICB_DLY10"])), + ("VPFS", (0, 37), 16, "T", + FuzzConfig(job="TMIDROUTE", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R0C37:TMID_0", "CIB_R0C38:TMID_1_15K", "CIB_R0C39:CLKBUF_T_15K"])), + + ("VPFN", (83, 25), 16, "B", + FuzzConfig(job="BMIDROUTE-33U", device="LIFCL-33U", sv="../shared/route.v", + tiles=["CIB_R83C25:BMID_0_ECLK_1", "CIB_R83C26:BMID_1_ECLK_2"])), +] + +async def main(executor): + for feed, rc, ndcc, side, cfg in configs: + cfg.setup() + if cfg.device == "LIFCL-40": + cr, cc = (28, 49) + elif cfg.device == "LIFCL-17": + cr, cc = (10, 37) + else: + cr, cc = (37, 25) + r, c = rc + nodes = [] + mux_nodes = [] + for i in range(ndcc): + for j in range(2): + nodes.append("R{}C{}_J{}{}_CMUX_CORE_CMUX{}".format(cr, cc, feed, i, j)) + nodes.append("R{}C{}_J{}{}_CMUX_CORE_CMUX{}".format(cr, cc, feed, i, j)) + nodes.append("R{}C{}_J{}{}_DCSMUX_CORE_DCSMUX{}".format(cr, cc, feed, i, j)) + nodes.append("R{}C{}_J{}{}_DCSMUX_CORE_DCSMUX{}".format(cr, cc, feed, i, j)) + nodes.append("R{}C{}_JCLKO_DCC_DCC{}".format(r, c, i)) + nodes.append("R{}C{}_JCE_DCC_DCC{}".format(r, c, i)) + nodes.append("R{}C{}_JCLKI_DCC_DCC{}".format(r, c, i)) + mux_nodes.append("R{}C{}_J{}{}_{}MID_CORE_{}MIDMUX".format(r, c, feed, i, side, side)) + for i in range(4): + nodes.append("R{}C{}_JTESTINP{}_{}MID_CORE_{}MIDMUX".format(r, c, i, side, side)) + fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=False,executor=executor) + fuzz_interconnect(config=cfg, nodenames=mux_nodes, regex=False, bidir=False, full_mux_style=True,executor=executor) + def pip_filter(pip, nodes): + from_wire, to_wire = pip + return "PCLKT" in to_wire or "PCLKCIB" in to_wire + fuzz_interconnect(config=cfg, nodenames=["R{}C{}_J*".format(r, c)], regex=True, bidir=False, full_mux_style=False, + pip_predicate=pip_filter, executor=executor) + +if __name__ == "__main__": + fuzzloops.FuzzerAsyncMain(main) + From 1ecd2d80e5b149e69177d075a7f8a61ca759db84 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Tue, 17 Feb 2026 14:30:08 -0700 Subject: [PATCH 25/29] Changes to get fuzzers to work with new api; overlay and rel location changes --- generate_database.sh | 26 ++++-- libprjoxide/prjoxide/src/bels.rs | 18 ++-- libprjoxide/prjoxide/src/bin/prjoxide.rs | 2 +- libprjoxide/prjoxide/src/database.rs | 29 +----- libprjoxide/prjoxide/src/fuzz.rs | 38 ++++++-- libprjoxide/pyprjoxide/src/lib.rs | 10 +- radiant.sh | 2 +- tools/extract_tilegrid.py | 2 +- util/common/database.py | 4 +- util/common/lapie.py | 12 +-- util/common/nodes_database.py | 1 + util/common/radiant.py | 2 +- util/fuzz/DesignFileBuilder.py | 39 ++++++++ util/fuzz/fuzzconfig.py | 8 +- util/fuzz/fuzzloops.py | 19 ++-- util/fuzz/interconnect.py | 17 ++-- util/fuzz/nonrouting.py | 112 ++++++++++++++--------- util/fuzz/primitives.py | 3 +- 18 files changed, 216 insertions(+), 128 deletions(-) diff --git a/generate_database.sh b/generate_database.sh index d8767a0..53ab8b6 100755 --- a/generate_database.sh +++ b/generate_database.sh @@ -1,18 +1,28 @@ -#!/bin/bash +#!/bin/bash -i . user_environment.sh pushd tools -python3 tilegrid_all.py +#python3 tilegrid_all.py popd +PRJOXIDE_ROOT=`pwd` pushd fuzzers -for dir in LIFCL/* ; do + +run_fuzzer() { + dir="$1" if [ -f "$dir/fuzzer.py" ]; then echo "=================== Entering $dir ===================" - pushd $dir - ../../../link-db-root.sh - PRJOXIDE_DB=`pwd`/db python3 fuzzer.py 2>&1 | tee >(gzip --stdout > fuzzer.log.gz) - popd + pushd "$dir" > /dev/null || return + rm -rf db .deltas + $PRJOXIDE_ROOT/link-db-root.sh + FUZZER_TITLE=$dir PRJOXIDE_DB="$(pwd)/db" python3 fuzzer.py 2>&1 | tee >(gzip --stdout > fuzzer.log.gz) + popd > /dev/null || true fi -done +} + +export -f run_fuzzer +export PRJOXIDE_ROOT + +#find . -mindepth 1 -maxdepth 1 -type d ! -name LIFCL -exec bash -c 'run_fuzzer "$0"' {} \; +find LIFCL -mindepth 1 -maxdepth 1 -type d | sort | xargs -I {} bash -c 'run_fuzzer "{}"' diff --git a/libprjoxide/prjoxide/src/bels.rs b/libprjoxide/prjoxide/src/bels.rs index c33fc0a..203c0af 100644 --- a/libprjoxide/prjoxide/src/bels.rs +++ b/libprjoxide/prjoxide/src/bels.rs @@ -804,7 +804,7 @@ pub fn get_tile_bels(tiletype: &str, tiledata: &TileBitsDatabase) -> Vec { "MIPI_DPHY_1" => vec![Bel::make_dphy_core("TDPHY_CORE26", &tiledata, -2, 0)], "MIB_T_TAP" | "TAP_CIBT" | "TAP_CIB" | "TAP_PLC" | "CIB" | "MIB_LR" => vec![], // Silence warnings _ => { - let bel_relative_location = tiledata.tile_configures_external_tiles.iter().next().cloned().unwrap_or(("".to_string(), 0, 0)); + let bel_relative_location = tiledata.tile_configures_external_tiles.iter().next().cloned().unwrap_or((0, 0)); let enum_bels = tiledata.enums.iter().flat_map(|(key, _value)|match key.as_str() { "PIOA.BASE_TYPE" => vec![Bel::make_seio33(0)], @@ -819,7 +819,7 @@ pub fn get_tile_bels(tiletype: &str, tiledata: &TileBitsDatabase) -> Vec { "IOLOGICB.GSR" => vec![Bel::make_iol(tiledata, false, 1)], _ => vec![] }).map(|x| { - x.with_rel((bel_relative_location.1, bel_relative_location.2)) + x.with_rel(bel_relative_location) }); let inferred_bels = enum_bels.collect_vec(); if inferred_bels.is_empty() { @@ -831,7 +831,7 @@ pub fn get_tile_bels(tiletype: &str, tiledata: &TileBitsDatabase) -> Vec { } // Get the tiles that a bel's configuration might be split across -pub fn get_bel_tiles(chip: &Chip, tile: &Tile, bel: &Bel, rel: &Option<(String, i32, i32)>) -> Vec { +pub fn get_bel_tiles(chip: &Chip, tile: &Tile, bel: &Bel, rel: &Option<(i32, i32)>) -> Vec { let tn = tile.name.to_string(); let rel_tile = |dx: i32, dy: i32, tt: &str| { chip.tile_by_xy_type((tile.x as i32 + dx).try_into().unwrap(), @@ -849,20 +849,18 @@ pub fn get_bel_tiles(chip: &Chip, tile: &Tile, bel: &Bel, rel: &Option<(String, warn!("no tile matched prefix ({}, {}, {}) for {}", nx, ny, tt_prefix, tile.name); "".to_string() }; - let rel_tile_suffix = |dx, dy, tt_suffix| { + let rel_tile_suffix = |dx, dy| { let nx = tile.x.checked_add_signed(dx).unwrap(); let ny = tile.y.checked_add_signed(dy).unwrap(); for tile in chip.tiles_by_xy(nx, ny).iter() { - if tile.tiletype.ends_with(tt_suffix) { - return tile.name.to_string(); - } + return tile.name.to_string(); } - warn!("no tile matched suffix ({}, {}, {}) for {}", nx, ny, tt_suffix, tile.name); + warn!("no tile matched suffix ({}, {}) for {}", nx, ny, tile.name); "".to_string() }; if let Some(rel_offset) = rel { - return vec![rel_tile_suffix(rel_offset.1, rel_offset.2, "")]; + return vec![rel_tile_suffix(rel_offset.0, rel_offset.1)]; } let tt = &tile.tiletype[..]; @@ -883,7 +881,7 @@ pub fn get_bel_tiles(chip: &Chip, tile: &Tile, bel: &Bel, rel: &Option<(String, 0 => vec![rel_tile(0, 0, "EBR_1"), rel_tile(1, 0, "EBR_2")], 1 => vec![rel_tile(0, 0, "EBR_4"), rel_tile(1, 0, "EBR_5")], 2 => vec![rel_tile(0, 0, "EBR_7"), rel_tile(1, 0, "EBR_8")], - 3 => vec![rel_tile(0, 0, "EBR_9"), rel_tile_suffix(1, 0, "EBR_10")], + 3 => vec![rel_tile(0, 0, "EBR_9"), rel_tile_suffix(1, 0)], _ => panic!("unknown EBR z-index") } "PREADD9_CORE" | "MULT9_CORE" | "MULT18_CORE" | "REG18_CORE" | diff --git a/libprjoxide/prjoxide/src/bin/prjoxide.rs b/libprjoxide/prjoxide/src/bin/prjoxide.rs index 301eb5b..918f69e 100644 --- a/libprjoxide/prjoxide/src/bin/prjoxide.rs +++ b/libprjoxide/prjoxide/src/bin/prjoxide.rs @@ -135,7 +135,7 @@ impl BBAExport { } let speed_grades = vec!["4", "5", "6", "10", "11", "12", "M"]; - let devices = vec!["LIFCL-40", "LFD2NX-40", "LIFCL-17"]; + let devices = vec!["LIFCL-40", "LFD2NX-40", "LIFCL-17", "LIFCL-33", "LIFCL-33U"]; let mut db = Database::new_builtin(DATABASE_DIR); let tts = TileTypes::new(&mut db, &mut ids, "LIFCL", &devices); diff --git a/libprjoxide/prjoxide/src/database.rs b/libprjoxide/prjoxide/src/database.rs index f64835a..0f10ae0 100644 --- a/libprjoxide/prjoxide/src/database.rs +++ b/libprjoxide/prjoxide/src/database.rs @@ -315,7 +315,7 @@ pub struct TileBitsDatabase { // Tiletype and relative offset for the tiles that this tiletype configures -- that is, changes in // this tiles bits reflect a change in either pips or primitives in the other tile. #[serde(skip_serializing_if = "BTreeSet::is_empty")] - pub tile_configures_external_tiles : BTreeSet<(String, i32, i32)>, + pub tile_configures_external_tiles : BTreeSet<(i32, i32)>, } impl TileBitsDatabase { @@ -354,7 +354,6 @@ impl TileBitsData { self.db.conns.iter_mut().for_each(|(_,conn)| conn.sort()); self.db.pips.iter_mut().for_each(|(_,pip)| pip.sort()); - self.db.words.iter_mut().for_each(|(_,word)| word.bits.sort()); } pub fn new(tiletype: &str, db: TileBitsDatabase) -> TileBitsData { @@ -483,7 +482,7 @@ impl TileBitsData { Ok(()) } - pub fn set_bel_offset(&mut self, bel_relative_location : Option<(String, i32, i32)>) -> Result<(), String> { + pub fn set_bel_offset(&mut self, bel_relative_location : Option<(i32, i32)>) -> Result<(), String> { if !self.db.tile_configures_external_tiles.is_empty() && self.db.tile_configures_external_tiles.iter().next() != bel_relative_location.as_ref() { emit_bit_change_error!( @@ -864,30 +863,10 @@ impl Database { for layer in overlay_members { info!("Merging {layer} into {tiletype}"); - - let overlay_tiletypes = self.overlay_tiletypes.clone(); let overlay_bits = self.tile_bitdb(family, layer.as_str()); - - if !layer.starts_with("overlay") { - for (to, from_wires) in &overlay_bits.db.pips { - for from_data in from_wires { - let from_wire = &from_data.from_wire; - - if tile_bits.find_pip_data(from_wire, to).is_none() { - let indicated_tiles : Vec<_> = overlay_tiletypes.iter().flat_map(|(_, tilemaps)| { - tilemaps.iter().filter(move |(_, tiletypename)| { - tiletypename == &tiletype - }).map(|x| x.0.clone()) - }).collect(); - warn!("Ignoring PIP data {from_wire} -> {to} from {layer} for {tiletype}. Used in {indicated_tiles:?}") - } - } - } - tile_bits.merge_configs(&overlay_bits.db)?; - } else { - tile_bits.merge(&overlay_bits.db)?; - } + tile_bits.merge(&overlay_bits.db)?; } + Ok(tile_bits) } // Bit database for a tile by family and tile type diff --git a/libprjoxide/prjoxide/src/fuzz.rs b/libprjoxide/prjoxide/src/fuzz.rs index 2698f1d..9e1997f 100644 --- a/libprjoxide/prjoxide/src/fuzz.rs +++ b/libprjoxide/prjoxide/src/fuzz.rs @@ -31,6 +31,7 @@ pub enum FuzzMode { disambiguate: bool, // add explicit 0s to disambiguate settings only assume_zero_base: bool, mark_relative_to: Option, // Track relative to this tile + overlay: String } } @@ -100,7 +101,8 @@ impl Fuzzer { desc: &str, include_zeros: bool, assume_zero_base: bool, - mark_relative_to: Option + mark_relative_to: Option, + overlay: &str ) -> Fuzzer { Fuzzer { mode: FuzzMode::Enum { @@ -108,7 +110,8 @@ impl Fuzzer { include_zeros, disambiguate: false, // fixme assume_zero_base, - mark_relative_to + mark_relative_to, + overlay: overlay.to_string(), }, tiles: fuzz_tiles.clone(), base: base_bit.clone(), @@ -174,6 +177,19 @@ impl Fuzzer { pub fn add_word_sample(&mut self, db: &mut Database, index: usize, bitfile: &str) { self.add_sample(db, FuzzKey::WordKey { bit: index }, bitfile); } + pub fn add_word_delta(&mut self, index: usize, delta : ChipDelta) { + self.add_sample_delta( FuzzKey::WordKey { bit: index }, delta); + } + + pub fn add_enum_delta(&mut self, option: &str, delta: ChipDelta) { + self.add_sample_delta( + FuzzKey::EnumKey { + option: option.to_string(), + }, + delta + ); + } + pub fn add_enum_sample(&mut self, db: &mut Database, option: &str, bitfile: &str) { self.add_sample( db, @@ -377,7 +393,8 @@ impl Fuzzer { include_zeros, disambiguate: _, assume_zero_base, - mark_relative_to + mark_relative_to, + overlay } => { if self.deltas.len() < 2 { warn!("Need at least two deltas got {} for fuzzmode {name}", self.deltas.len()); @@ -459,18 +476,25 @@ impl Fuzzer { }; // Add the enum to the tile data let tile_data = self.base.tile_by_name(&tile).unwrap(); - info!("Resolved {} {} {:?} {}", name, option, b, &tile_data.tiletype); + + let tiletype = &tile_data.tiletype; + let tiletype_or_overlay = if overlay.is_empty() { + tiletype.clone() + } else { + format!("overlay/{}-{}", tiletype, overlay) + }; + + info!("Resolved {} {} {:?} {}", name, option, b, tiletype_or_overlay); let tile_db = - db.tile_bitdb(&self.base.family, &tile_data.tiletype); + db.tile_bitdb(&self.base.family, &tiletype_or_overlay); tile_db.add_enum_option(&name, &option, &self.desc, b).unwrap(); if let Some(relative_tile) = mark_relative_to.clone() { let ref_tile = self.base.tile_by_name(&relative_tile).unwrap(); let offset = { - (ref_tile.tiletype.clone(), - ref_tile.x as i32 - tile_data.x as i32, + (ref_tile.x as i32 - tile_data.x as i32, ref_tile.y as i32 - tile_data.y as i32) }; diff --git a/libprjoxide/pyprjoxide/src/lib.rs b/libprjoxide/pyprjoxide/src/lib.rs index 925aa9c..e227a27 100644 --- a/libprjoxide/pyprjoxide/src/lib.rs +++ b/libprjoxide/pyprjoxide/src/lib.rs @@ -17,6 +17,7 @@ use pyo3::wrap_pyfunction; use std::collections::BTreeSet; use std::fs::File; use std::io::*; +use prjoxide::chip::ChipDelta; #[pyclass] struct Database { @@ -175,7 +176,8 @@ impl Fuzzer { desc: &str, include_zeros: bool, assume_zero_base: bool, - mark_relative_to: Option + mark_relative_to: Option, + overlay: &str ) -> Fuzzer { let base_chip = bitstream::BitstreamParser::parse_file(&mut db.db, base_bitfile).unwrap(); @@ -190,7 +192,8 @@ impl Fuzzer { desc, include_zeros, assume_zero_base, - mark_relative_to + mark_relative_to, + overlay ), name: name.to_string() } @@ -222,6 +225,9 @@ impl Fuzzer { fn add_enum_sample(&mut self, db: &mut Database, option: &str, base_bitfile: &str) { self.fz.add_enum_sample(&mut db.db, option, base_bitfile); } + fn add_enum_delta(&mut self, db: &mut Database, option: &str, delta: ChipDelta) { + self.fz.add_enum_delta(option, delta); + } fn solve(&mut self, db: &mut Database, py: Python) { py.allow_threads(|| { diff --git a/radiant.sh b/radiant.sh index f142b7a..7b1582f 100755 --- a/radiant.sh +++ b/radiant.sh @@ -96,7 +96,7 @@ else MAP_PDC="" fi "$fpgabindir"/map -o map.udb synth.udb $MAP_PDC - "$fpgabindir"/par map.udb par.udb + "$fpgabindir"/par map.udb -w par.udb fi if [ -n "$GEN_RBF" ]; then diff --git a/tools/extract_tilegrid.py b/tools/extract_tilegrid.py index 8d5cb07..fd7686d 100644 --- a/tools/extract_tilegrid.py +++ b/tools/extract_tilegrid.py @@ -84,7 +84,7 @@ def main(argv): def fixup(tiletype): if args.device.find("-33") > 0: # These definitions were found to have conflicting PIPs against LIFCL-40/17 - tiletypes_with_variants = ["LRAM_", "SYSIO_B1_DED", "SPINE_", "TAP_CIB", "TMID_1", "CIB_LR"] + tiletypes_with_variants = ["LRAM_", "SYSIO_B1_DED", "SPINE_", "TAP_CIB", "TMID_", "CIB_LR"] for v in tiletypes_with_variants: if tiletype.startswith(v): return tiletype + "_33K" diff --git a/util/common/database.py b/util/common/database.py index 5f544f9..40eeb52 100644 --- a/util/common/database.py +++ b/util/common/database.py @@ -1,6 +1,7 @@ """ Database and Database Path Management """ +import logging import os from functools import lru_cache, cache from os import path, makedirs @@ -36,6 +37,7 @@ def get_cache_dir(): makedirs(path, exist_ok=True) return path +@cache def get_db_root(): """ Return the path containing the Project Oxide database @@ -43,11 +45,11 @@ def get_db_root(): variable is set to another value. """ if "PRJOXIDE_DB" in os.environ and os.environ["PRJOXIDE_DB"] != "": + logging.info(f"Using external database path {os.environ['PRJOXIDE_DB']}") return os.environ["PRJOXIDE_DB"] else: return path.join(get_oxide_root(), "database") - def get_db_subdir(family = None, device = None, package = None): """ Return the DB subdirectory corresponding to a family, device and diff --git a/util/common/lapie.py b/util/common/lapie.py index 5053ac1..301634e 100644 --- a/util/common/lapie.py +++ b/util/common/lapie.py @@ -334,7 +334,7 @@ async def get_pip_data(device, nodes, filter_type = None): db = NodesDatabase.get(device) return db.get_pips(nodes, filter_type = filter_type) -def get_node_data(udb, nodes, regex=False, executor = None, filter_by_name=True, skip_missing = False, skip_pips=False): +def get_node_data(device, nodes, regex=False, executor = None, filter_by_name=True, skip_missing = False, skip_pips=False): from nodes_database import NodesDatabase import fuzzloops @@ -344,17 +344,17 @@ def get_node_data(udb, nodes, regex=False, executor = None, filter_by_name=True, nodes = sorted(set(nodes)) if regex: - all_nodes = get_full_node_list(udb) + all_nodes = get_full_node_list(device) regex = [re.compile(n) for n in nodes] nodes = sorted(set([n for n in all_nodes if any([r for r in regex if r.search(n) is not None])])) elif filter_by_name: - all_nodes = get_full_node_list(udb) + all_nodes = get_full_node_list(device) nodes = sorted(set(nodes) & all_nodes) if len(nodes) == 0: return [] - db = NodesDatabase.get(udb) + db = NodesDatabase.get(device) t = time.time() nis = db.get_node_data(nodes, skip_pips=skip_pips) logging.debug(f"Looked up {len(nis)} records in {time.time() - t} sec") @@ -368,12 +368,12 @@ def get_node_data(udb, nodes, regex=False, executor = None, filter_by_name=True, with fuzzloops.Executor(executor) as local_executor: def lapie_get_node_data(query): s = time.time() - nodes = _get_node_data(udb, query) + nodes = _get_node_data(device, query) logging.debug(f"{len(query)} N {len(query) / (time.time() - s)} N/sec ({(time.time() - s)} deltas)") return nodes def integrate_nodes(nodes): - db = NodesDatabase.get(udb) + db = NodesDatabase.get(device) try: db.insert_nodeinfos(nodes) except sqlite3.OperationalError as e: diff --git a/util/common/nodes_database.py b/util/common/nodes_database.py index 30ab402..895882f 100644 --- a/util/common/nodes_database.py +++ b/util/common/nodes_database.py @@ -32,6 +32,7 @@ def get(device): def __init__(self, device): import database + self.write_lock = NodesDatabase._write_locks[device] self.db_path = f"{database.get_cache_dir()}/{device}-nodes.sqlite" logging.debug(f"Opening node database at {self.db_path} thread: {threading.get_ident()}") diff --git a/util/common/radiant.py b/util/common/radiant.py index da89c61..24dd8cf 100644 --- a/util/common/radiant.py +++ b/util/common/radiant.py @@ -44,7 +44,7 @@ def process_subprocess_result(stdout, stderr, returncode): if returncode != 0: - raise RadiantRunError(f"Error encountered running radiant: {slug} {returncode}", error_lines) + raise RadiantRunError(f"Error encountered running radiant: {slug} {returncode} cwd: {cwd}", error_lines) # try: # loop = asyncio.get_running_loop() diff --git a/util/fuzz/DesignFileBuilder.py b/util/fuzz/DesignFileBuilder.py index d080e43..d1917c9 100644 --- a/util/fuzz/DesignFileBuilder.py +++ b/util/fuzz/DesignFileBuilder.py @@ -14,6 +14,45 @@ workdir = tempfile.mkdtemp() +def create_design_file(config, elements, prefix = "", executor = None): + if executor is not None: + future = executor.submit(create_wires_file, config, elements, prefix) + future.name = f"Build {config.device}" + future.executor = executor + return future + + all_outputs = [o for _, os, _ in elements for o in os] + all_inputs = [i for ins, _, _ in elements for i in ins] + + blurb_text = "\n".join([b for _, _, b in elements]) + + subst = config.subst_defaults() + arch = config.device.split("-")[0] + device = config.device + package = subst["package"] + speed_grade = subst["speed_grade"] + + outputs = ", ".join([f"output wire q_{o}" for o in all_outputs]) + input_ties = "\n".join([f"VHI vhi_i_{i}(.Z(q_{i}));" for i in all_inputs]) + + source = f"""\ + (* \\db:architecture ="{arch}", \\db:device ="{device}", \\db:package ="{package}", \\db:speed ="{speed_grade}_High-Performance_1.0V", \\db:timestamp = 0, \\db:view ="physical" *) + module top ({outputs}); + {input_ties} + {blurb_text} + (* \\xref:LOG ="q_c@0@0" *) + VHI vhi_i(); + endmodule + """ + h = abs(hash(source)) + vfile = path.join(workdir, f"{config.device}/{prefix}{config.job}-{h}.v") + Path(vfile).parent.mkdir(parents=True, exist_ok=True) + + with open(vfile, 'w') as f: + f.write(source) + + return config.build_design(vfile, prefix=prefix) + def create_wires_file(config, wires, prefix = "", executor = None): if executor is not None: future = executor.submit(create_wires_file, config, wires, prefix) diff --git a/util/fuzz/fuzzconfig.py b/util/fuzz/fuzzconfig.py index 9be342d..0111616 100644 --- a/util/fuzz/fuzzconfig.py +++ b/util/fuzz/fuzzconfig.py @@ -110,11 +110,12 @@ def read_design_template(des_template): class BitstreamInfo: def __init__(self, config, bitstream_file, vfiles): self.config = config + assert isinstance(bitstream_file, str) self.bitstream = bitstream_file self.vfiles = vfiles def __str__(self): - return self.bitstream + return f"BitstreamInfo: {self.bitstream}" class FuzzConfig: @@ -229,6 +230,7 @@ def build_design_future(self, executor, *args, **kwargs): return future def build_design(self, des_template, substitutions = {}, prefix="", substitute=True, executor = None): + assert ' ' not in prefix """ Run Radiant on a given design template, applying a map of substitutions, plus some standard substitutions if not overriden. @@ -239,7 +241,6 @@ def build_design(self, des_template, substitutions = {}, prefix="", substitute=T Returns the path to the output bitstream """ - logging.debug(f"Building {des_template} with subs {substitutions}") subst = dict(substitutions) prefix = f"{threading.get_ident()}/{prefix}/" @@ -253,6 +254,7 @@ def build_design(self, des_template, substitutions = {}, prefix="", substitute=T bitfile = path.join(self.workdir, prefix + "design.bit") bitfile_gz = path.join(self.workdir, prefix + "design.bit.gz") + logging.debug(f"Building {des_template} with subs {substitutions} into {desfile}") if "sysconfig" in subst: pdcfile = path.join(self.workdir, prefix + "design.pdc") @@ -307,7 +309,7 @@ def run_radiant_sh(): error_output = process_results.stderr.decode().strip() if "ERROR <" in error_output: - raise Exception(f"Error found during bitstream build: {error_output}") + raise Exception(f"Error found during bitstream build: {error_output} (Args: {self.device} {desfile})") if self.struct_mode and self.udb_specimen is None: self.udb_specimen = path.join(self.workdir, prefix + "design.tmp", "par.udb") diff --git a/util/fuzz/fuzzloops.py b/util/fuzz/fuzzloops.py index be4a476..08f897d 100644 --- a/util/fuzz/fuzzloops.py +++ b/util/fuzz/fuzzloops.py @@ -17,6 +17,10 @@ import lapie +async def wrap_future(f): + if f is not None: + await asyncio.wrap_future(f) + is_in_loop = False def jobs(): @@ -54,7 +58,6 @@ def parallel_foreach(items, func, jobs = None): items_lock = RLock() exception = None - print(f"Starting loop with {exception} jobs") global is_in_loop is_in_loop = True @@ -68,14 +71,13 @@ def runner(): return item = items_queue[0] items_queue.pop(0) - print(f"{len(items_queue)} jobs remaining") func(item) except Exception as e: global error_count if error_count < 10: error_count = error_count + 1 - print(f"Error: {e}") + logging.error(f"Error: {e}") traceback.print_exc() exception = e @@ -133,7 +135,7 @@ def _done(i, fut): else: _done(i, fut) - if executor is not None: + if executor is not None and hasattr(executor, "register_future"): executor.register_future(out) if name is not None: @@ -271,10 +273,11 @@ def sighandler(sig, frame): signal.signal(sig, sighandler) try: + FUZZER_TITLE = os.environ.get("FUZZER_TITLE", "") def status_panel(status: str) -> Panel: return Panel( f"[bold cyan]{status}[/bold cyan]", - title="Status", + title=f"Status - {FUZZER_TITLE}", border_style="blue", height=3, ) @@ -328,7 +331,7 @@ def process_future(fut): traceback.print_exception(e) raise finally: - print("Exit ui thread") + logging.info("Exit ui thread") with Executor() as executor: @@ -379,6 +382,8 @@ def process_future(fut): def FuzzerMain(f): async def async_main(executor): - return f(executor) + if f.__code__.co_argcount > 0: + return f(executor) + return f() FuzzerAsyncMain(async_main) diff --git a/util/fuzz/interconnect.py b/util/fuzz/interconnect.py index c1769d1..1e02135 100644 --- a/util/fuzz/interconnect.py +++ b/util/fuzz/interconnect.py @@ -18,8 +18,7 @@ import database import fuzzconfig import fuzzloops -from DesignFileBuilder import get_wires_delta, DesignsForPips, BitConflictException - +from DesignFileBuilder import get_wires_delta, DesignsForPips, BitConflictException, create_wires_file def make_dict_of_lists(lst, key = None): if key is None: @@ -113,20 +112,18 @@ def fuzz_interconnect_sinks( if not isinstance(sinks, dict): sinks = pips_to_sinks(sinks) - base_bitf_future = config.build_design_future(executor, config.sv, extra_substs, "base_") - assert(len(config.tiles) > 0) def process_bits(bitstreams, from_wires, to_wire): base_bitf = bitstreams[0] - bitstreams = bitstreams[1:] + bitstreams = [b.bitstream if b is not None else None for b in bitstreams[1:]] with fuzzconfig.db_lock() as db: - fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf, set(config.tiles), to_wire, + fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf.bitstream, set(config.tiles), to_wire, config.tiles[0], set(ignore_tiles), full_mux_style, not (fc_filter(to_wire))) - pip_samples = [(from_wire, arc_bit if arc_bit is not None else base_bitf) for (from_wire, arc_bit) in zip(from_wires, bitstreams)] + pip_samples = [(from_wire, arc_bit if arc_bit is not None else base_bitf.bitstream) for (from_wire, arc_bit) in zip(from_wires, bitstreams)] fz.add_pip_samples(db, pip_samples) logging.debug(f"Solving for {to_wire}") @@ -146,6 +143,8 @@ def process_bits(bitstreams, from_wires, to_wire): with fuzzloops.Executor(executor) as executor: futures = [] + base_bitf_future = config.build_design_future(executor, config.sv, extra_substs, "base_") + for to_wire in sinks: if config.check_deltas(to_wire): continue @@ -248,7 +247,7 @@ def per_pip(pin_info, pin_pip): prefix = "{}_{}_{}_".format(config.job, config.device, to_wire) with fuzzconfig.db_lock() as db: - fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf, + fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf.bitstream, set(config.tiles), to_wire, config.tiles[0], set(), full_mux_style, not (fc_filter(to_wire))) @@ -261,7 +260,7 @@ def per_pip(pin_info, pin_pip): print(f"Building design for ({config.job} {config.device}) {to_wire} to {from_wire}") arc_bit = config.build_design(config.sv, substs, prefix) - fz.add_pip_sample(db, from_wire, arc_bit) + fz.add_pip_sample(db, from_wire, arc_bit.bitstream) config.solve(fz, db) diff --git a/util/fuzz/nonrouting.py b/util/fuzz/nonrouting.py index a647241..06e691c 100644 --- a/util/fuzz/nonrouting.py +++ b/util/fuzz/nonrouting.py @@ -1,6 +1,7 @@ """ Utilities for fuzzing non-routing configuration. This is the counterpart to interconnect.py """ +import logging import threading import tiles import libpyprjoxide @@ -8,6 +9,8 @@ import fuzzconfig import fuzzloops import os +from interconnect import transaction_log +from DesignFileBuilder import BitConflictException from primitives import EnumSetting @@ -28,7 +31,7 @@ def fuzz_word_setting(config, name, length, get_sv_substs, desc="", executor = N :param get_sv_substs: a callback function, that is called with an array of bits to create a design with that setting """ if not fuzzconfig.should_fuzz_platform(config.device): - return [] + return with fuzzloops.Executor(executor) as executor: prefix = f"{name}/" @@ -45,17 +48,22 @@ def integrate_bitstreams(bitstreams): baseline = bitstreams[0] bitstreams = bitstreams[1:] with fuzzconfig.db_lock() as db: - fz = libpyprjoxide.Fuzzer.word_fuzzer(db, baseline, set(config.tiles), name, desc, length, - baseline) + fz = libpyprjoxide.Fuzzer.word_fuzzer(db, baseline.bitstream, set(config.tiles), name, desc, length, + baseline.bitstream) for i in range(length): - fz.add_word_sample(db, i, bitstreams[i]) + fz.add_word_sample(db, i, bitstreams[i].bitstream) - config.solve(fz, db) + try: + config.solve(fz, db) + except BaseException as e: + logging.exception( + f"Exception {e} while adding word sample {i} from {[b.vfiles for b in bitstreams]} vs {baseline.vfiles}") + raise - return [*bitstream_futures, fuzzloops.chain([baseline, *bitstream_futures], "Solve word", integrate_bitstreams)] + return fuzzloops.chain([baseline, *bitstream_futures], "Solve word", integrate_bitstreams) def fuzz_enum_setting(config, empty_bitfile, name, values, get_sv_substs, include_zeros=False, - assume_zero_base=False, min_cover={}, desc="", mark_relative_to=None, executor = None): + assume_zero_base=False, min_cover={}, desc="", mark_relative_to=None, executor = None, overlay=""): """ Fuzz a setting with multiple possible values @@ -74,16 +82,16 @@ def fuzz_enum_setting(config, empty_bitfile, name, values, get_sv_substs, includ assert len(values) > 1, f"Enum setting {name} requires more than one option (given {values})" if not fuzzconfig.should_fuzz_platform(config.device): - return [] + return if config.check_deltas(name): - return [] + return with fuzzloops.Executor(executor) as executor: futures = [] def integrate_build(subs, prefix, opt_name): - bitstream = config.build_design(config.sv, subs, prefix) + bitstream = config.build_design(config.sv, subs, prefix.replace(" ", "")) return (opt_name, bitstream) for opt in values: @@ -99,15 +107,27 @@ def integrate_build(subs, prefix, opt_name): else: futures.append(executor.submit(integrate_build, get_sv_substs(opt), f"{name}/{opt}/", opt_name)) for future in futures: - future.name = "Build design" + future.name = f"Build enum design" def integrate_bitstreams(bitstreams): with fuzzconfig.db_lock() as db: - fz = libpyprjoxide.Fuzzer.enum_fuzzer(db, empty_bitfile, set(config.tiles), name, desc, - include_zeros, assume_zero_base, mark_relative_to=mark_relative_to) - for (opt, bitstream) in bitstreams: - fz.add_enum_sample(db, opt, bitstream) - config.solve(fz, db) + fz = libpyprjoxide.Fuzzer.enum_fuzzer(db, empty_bitfile.bitstream, set(config.tiles), name, desc, + include_zeros, assume_zero_base, + mark_relative_to=mark_relative_to, overlay=overlay) + + for idx, (opt, bitstream) in enumerate(bitstreams): + logging.debug(f"Enum sample for {name}={opt} with {bitstream.bitstream} {bitstream.vfiles}") + transaction_log.info(f"add_enum_sample {config.device}: {name} {opt} Files: {bitstream.vfiles}") + + fz.add_enum_sample(db, opt, bitstream.bitstream) + + try: + config.solve(fz, db) + except BaseException as e: + logging.error(f"Enum sample error for {name} with {bitstream.bitstream} {bitstream.vfiles}") + transaction_log.info(f"add_enum_sample error {e}") + raise + return fuzzloops.chain(futures, "Enum Setting", integrate_bitstreams ) @@ -121,10 +141,10 @@ def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=N :param get_sv_substs: a callback function, that is called with an array of bits to create a design with that setting """ if not fuzzconfig.should_fuzz_platform(config.device): - return [] + return if config.check_deltas(name): - return [] + return prefix = f"{name}/" @@ -136,24 +156,25 @@ def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=N inverted_mode = True break - baseline_future = config.build_design_future(executor, config.sv, get_sv_substs([inverted_mode for _ in range(length)]), prefix) + with fuzzloops.Executor(executor) as executor: + baseline_future = config.build_design_future(executor, config.sv, get_sv_substs([inverted_mode for _ in range(length)]), prefix) - bitstream_futures = [ - config.build_design_future(executor, config.sv, get_sv_substs([(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)]), f"{prefix}/{i}/") - for i in range(0, length.bit_length()) - ] + bitstream_futures = [ + config.build_design_future(executor, config.sv, get_sv_substs([(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)]), f"{prefix}/{i}/") + for i in range(0, length.bit_length()) + ] - def integrate_bitstreams(bitstreams): - baseline = bitstreams[0] - ipcore, iptype = config.tiles[0].split(":") - with fuzzconfig.db_lock() as db: - fz = libpyprjoxide.IPFuzzer.word_fuzzer(db, baseline, ipcore, iptype, name, desc, length, inverted_mode) - for (i, bitfile) in enumerate(bitstreams[1:]): - bits = [(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)] - fz.add_word_sample(db, bits, bitfile) - config.solve(fz, db) + def integrate_bitstreams(bitstreams): + baseline = bitstreams[0] + ipcore, iptype = config.tiles[0].split(":") + with fuzzconfig.db_lock() as db: + fz = libpyprjoxide.IPFuzzer.word_fuzzer(db, baseline.bitstream, ipcore, iptype, name, desc, length, inverted_mode) + for (i, bitfile) in enumerate(bitstreams[1:]): + bits = [(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)] + fz.add_word_sample(db, bits, bitfile.bitstream) + config.solve(fz, db) - return [*bitstream_futures, fuzzloops.chain([baseline_future, *bitstream_futures], "Solve IP word", integrate_bitstreams)] + return fuzzloops.chain([baseline_future, *bitstream_futures], "Solve IP word", integrate_bitstreams) def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, desc="", executor = None): """ @@ -174,19 +195,20 @@ def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, des ipcore, iptype = config.tiles[0].split(":") prefix = f"{ipcore}/{name}" - bitstream_futures = [ - config.build_design_future(executor, config.sv, get_sv_substs(opt), f"{prefix}/{opt}/") - for opt in values - ] + with fuzzloops.Executor(executor) as executor: + bitstream_futures = [ + config.build_design_future(executor, config.sv, get_sv_substs(opt), f"{prefix}/{opt}/") + for opt in values + ] - def integrate_bitstreams(bitstreams): - with fuzzconfig.db_lock() as db: - fz = libpyprjoxide.IPFuzzer.enum_fuzzer(db, empty_bitfile, ipcore, iptype, name, desc) - for (opt, bitfile) in zip(values, bitstreams): - fz.add_enum_sample(db, opt, bitfile) - config.solve(fz, db) + def integrate_bitstreams(bitstreams): + with fuzzconfig.db_lock() as db: + fz = libpyprjoxide.IPFuzzer.enum_fuzzer(db, empty_bitfile.bitstream, ipcore, iptype, name, desc) + for (opt, bitfile) in zip(values, bitstreams): + fz.add_enum_sample(db, opt, bitfile.bitstream) + config.solve(fz, db) - return [*bitstream_futures, fuzzloops.chain(bitstream_futures, "Solve IP enum", integrate_bitstreams)] + return fuzzloops.chain(bitstream_futures, "Solve IP enum", integrate_bitstreams) def fuzz_primitive_definition(cfg, empty, site, primitive, mark_relative_to = None, mode_name = None, get_substs=None): def default_get_substs(mode="NONE", kv=None): @@ -204,7 +226,7 @@ def default_get_substs(mode="NONE", kv=None): get_substs = default_get_substs if mode_name is None: - mode_name = primitive.name + mode_name = primitive.mode for setting in primitive.settings: subs_fn = lambda x, name=setting.name: get_substs(mode=mode_name, kv=(name, x)) diff --git a/util/fuzz/primitives.py b/util/fuzz/primitives.py index cfe898c..6530c14 100644 --- a/util/fuzz/primitives.py +++ b/util/fuzz/primitives.py @@ -226,7 +226,7 @@ def create_pin(pin_def, dir): ProgrammablePin("RSTA", ["#SIG", "#INV"], desc="LRAM RSTA inversion control", primitive="LRAM_CORE"), ProgrammablePin("RSTB", ["#SIG", "#INV"], desc="LRAM RSTB inversion control", primitive="LRAM_CORE"), ProgrammablePin("WEA", ["#SIG", "#INV"], desc="LRAM WEA inversion control", primitive="LRAM_CORE"), - ProgrammablePin("WEB", ["#SIG", "#INV"], desc="LRAM WEB inversion control", primitive="LRAM_CORE"), + #ProgrammablePin("WEB", ["#SIG", "#INV"], desc="LRAM WEB inversion control", primitive="LRAM_CORE"), ] ) @@ -352,6 +352,7 @@ def create_pin(pin_def, dir): for s in pll_core.settings: if s.name.startswith("ENCLK_"): s.enable_value = "ENABLED" +pll_core.settings = [s for s in pll_core.settings if s.name != "CONFIG_WAIT_FOR_LOCK"] pll_core.pins = [ PinSetting(name="INTFBK0", dir="out", desc="", bits=None), PinSetting(name="INTFBK1", dir="out", desc="", bits=None), From a9d65f109d55e7860c9ea3b7cf99f5e2d9077265 Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Tue, 17 Feb 2026 23:07:50 -0700 Subject: [PATCH 26/29] Incorporating database changes from $dir --- tools/merge-databases.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/merge-databases.py diff --git a/tools/merge-databases.py b/tools/merge-databases.py new file mode 100644 index 0000000..e69de29 From 66ecd695186bb7fadb4f2b80137c195892e3a96f Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Wed, 18 Feb 2026 23:24:39 -0700 Subject: [PATCH 27/29] Web parsing code; clean up which primitives we import. Add scripting for merge/commit into db --- .gitignore | 2 +- generate_database.sh | 50 +++++-- libprjoxide/prjoxide/src/database.rs | 105 ++++++++++++++- libprjoxide/pyprjoxide/src/lib.rs | 8 +- tools/merge-databases.py | 22 +++ tools/parse_webdoc.py | 17 ++- util/common/database.py | 39 +++++- util/common/tiles.py | 6 +- util/fuzz/fuzzconfig.py | 4 +- util/fuzz/primitives.py | 191 ++++++++++++++------------- 10 files changed, 316 insertions(+), 128 deletions(-) mode change 100644 => 100755 tools/merge-databases.py diff --git a/.gitignore b/.gitignore index 224a1f7..9103205 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ work/ .\#* *.fasm .cache -fuzzers/*/*/db \ No newline at end of file +fuzzers/*/*/db diff --git a/generate_database.sh b/generate_database.sh index 53ab8b6..f6f37a6 100755 --- a/generate_database.sh +++ b/generate_database.sh @@ -1,28 +1,62 @@ #!/bin/bash -i +set -o allexport + . user_environment.sh +fuzz=false +merge=false +git_commit=false + +while getopts 'fgm' flag; do + case "${flag}" in + f) fuzz=true ;; + m) merge=true ;; + g) git_commit=true ;; + *) print_usage + exit 1 ;; + esac +done + pushd tools #python3 tilegrid_all.py popd PRJOXIDE_ROOT=`pwd` -pushd fuzzers run_fuzzer() { dir="$1" if [ -f "$dir/fuzzer.py" ]; then + set -ex + echo "=================== Entering $dir ===================" pushd "$dir" > /dev/null || return - rm -rf db .deltas - $PRJOXIDE_ROOT/link-db-root.sh - FUZZER_TITLE=$dir PRJOXIDE_DB="$(pwd)/db" python3 fuzzer.py 2>&1 | tee >(gzip --stdout > fuzzer.log.gz) + + if [ "$fuzz" = true ] ; then + rm -rf db .deltas + $PRJOXIDE_ROOT/link-db-root.sh + FUZZER_TITLE=$dir PRJOXIDE_DB="$(pwd)/db" python3 fuzzer.py 2>&1 | tee >(gzip --stdout > fuzzer.log.gz) + fi popd > /dev/null || true + + if [ -d "$dir/db" ]; then + if [ "$merge" = true ] ; then + pushd .. + python3 ./tools/merge-databases.py fuzzers/$dir/db database/ + popd + fi + + if [ "$git_commit" = true ] ; then + pushd ../database + git add **.ron + git commit -m "Incorporating database changes from $dir" + popd + fi + fi + fi } - export -f run_fuzzer -export PRJOXIDE_ROOT -#find . -mindepth 1 -maxdepth 1 -type d ! -name LIFCL -exec bash -c 'run_fuzzer "$0"' {} \; -find LIFCL -mindepth 1 -maxdepth 1 -type d | sort | xargs -I {} bash -c 'run_fuzzer "{}"' +pushd fuzzers +find . -mindepth 1 -maxdepth 2 -type d | sort | xargs -I {} bash -c 'run_fuzzer "{}"' diff --git a/libprjoxide/prjoxide/src/database.rs b/libprjoxide/prjoxide/src/database.rs index 0f10ae0..06cff6f 100644 --- a/libprjoxide/prjoxide/src/database.rs +++ b/libprjoxide/prjoxide/src/database.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use std::{env, fmt}; +use std::{env, fmt, fs}; use std::fs::File; use std::io::prelude::*; @@ -346,12 +346,13 @@ pub struct TileBitsData { tiletype: String, pub db: TileBitsDatabase, dirty: bool, + new_pips: u32, + new_enums: u32, + new_words: u32 } impl TileBitsData { pub fn sort(&mut self) { - debug!("Sorting {}", self.tiletype); - self.db.conns.iter_mut().for_each(|(_,conn)| conn.sort()); self.db.pips.iter_mut().for_each(|(_,pip)| pip.sort()); } @@ -361,6 +362,9 @@ impl TileBitsData { tiletype: tiletype.to_string(), db: db.clone(), dirty: false, + new_pips: 0, + new_enums: 0, + new_words : 0 } } @@ -382,6 +386,7 @@ impl TileBitsData { } pub fn merge(&mut self, other_db: &TileBitsDatabase) -> Result<(), String> { + debug!("Merging {}", self.tiletype); self.merge_configs(other_db)?; for (to, pip_data) in other_db.pips.iter() { @@ -398,6 +403,7 @@ impl TileBitsData { } } } + self.dirty = true; Ok(()) } @@ -427,6 +433,8 @@ impl TileBitsData { ad.bits = bits; self.dirty = true; + self.new_pips += 1; + return Ok(()); } @@ -435,6 +443,8 @@ impl TileBitsData { } } self.dirty = true; + self.new_pips += 1; + debug!("Inserting new pip {from} -> {to} for {}", self.tiletype); ac.push(ConfigPipData { from_wire: from.to_string(), @@ -454,6 +464,8 @@ impl TileBitsData { bits: bits.clone(), }, ); + + self.new_words += 1; } Some(word) => { if !desc.is_empty() && desc != &word.desc { @@ -518,6 +530,7 @@ impl TileBitsData { let ec = self.db.enums.get_mut(name).unwrap(); if !desc.is_empty() && desc != &ec.desc { ec.desc = desc.to_string(); + self.new_enums += 1; self.dirty = true; } match ec.options.get_mut(option) { @@ -529,11 +542,13 @@ impl TileBitsData { ); ec.options.insert(option.to_string(), bits); + self.new_enums += 1; self.dirty = true; } } None => { ec.options.insert(option.to_string(), bits); + self.new_enums += 1; self.dirty = true; } } @@ -595,6 +610,8 @@ impl Database { pub fn new(root: &str) -> Database { let mut devices_json_buf = String::new(); // read the whole file + debug!("Opening database at {}", root); + File::open(format!("{}/devices.json", root)) .unwrap() .read_to_string(&mut devices_json_buf) @@ -770,7 +787,43 @@ impl Database { &self._overlays.as_ref().unwrap() } + pub fn device_tiletypes(&mut self, family: &str) -> BTreeSet { + let mut tiletypes = BTreeSet::new(); + let root = self.root.clone().unwrap(); + let tiletypes_dir = format!("{}/{}/tiletypes/", root, family); + + match fs::read_dir(&tiletypes_dir) { + Ok(entries) => { + for entry in entries { + let file = entry.unwrap(); + if file.path().extension().unwrap_or_default() == "ron" { + tiletypes.insert(file.path().file_stem().unwrap().to_str().unwrap().to_string()); + } + } + } + Err(e) => { + debug!("Failed to read tile types for {family} {tiletypes_dir}: {e}"); + } + } + + match fs::read_dir(format!("{}/{}/overlays/", root, family)) { + Ok(entries) => { + for entry in entries { + let file = entry.unwrap(); + if file.path().extension().unwrap_or_default() == "ron" { + let overlay = file.path().file_stem().unwrap().to_str().unwrap().to_string(); + tiletypes.insert(format!("overlays/{}", overlay)); + } + } + } + Err(e) => { + debug!("Failed to read overlay tile types for {family}: {e}"); + } + } + debug!("Reading {} tile types {:?}", family, tiletypes); + tiletypes + } // Tilegrid for a device by family and name pub fn device_tilegrid(&mut self, family: &str, device: &str) -> &DeviceTilegrid { let key = (family.to_string(), device.to_string()); @@ -844,6 +897,31 @@ impl Database { } self.cell_tmg.get(&key).unwrap() } + pub fn merge(&mut self, other: &mut Database) -> Result<(), String>{ + let families : BTreeSet = self.devices.families.iter().map(|(k,_v)| k.to_string()).collect(); + + for family in families { + let family_str = family.as_str(); + + for tiletype in other.device_tiletypes(family_str) { + let other_tiledb = other.tile_bitdb(family_str, tiletype.as_str()); + self.tile_bitdb(family_str, &tiletype).merge(&other_tiledb.db)?; + } + + // let ip_tiledb = other.ip_bitdb(family_str, tiletype.as_str()); + // self.ip_bitdb(family_str, &tiletype).merge(&ip_tiledb.db)?; + + + } + + for (device, name_to_type_map) in other.overlay_tiletypes.iter() { + let new_map = self.overlay_tiletypes.entry(device.clone()).or_insert(BTreeMap::new()); + for (tilename, tiletypename) in name_to_type_map.iter() { + new_map.insert(tilename.clone(), tiletypename.clone()); + } + } + Ok(()) + } pub fn tile_bitdb_from_overlays(&mut self, family: &str, tiletype: &str, overlay: &OverlayTiletype) -> Result { let tile_bits_db = TileBitsDatabase { pips: BTreeMap::new(), @@ -884,9 +962,9 @@ impl Database { self.tile_bitdb_from_overlays(family, tiletype, &overlay).unwrap() } None => { - let is_overlay = tiletype.starts_with("overlay/"); + let is_overlay = tiletype.starts_with("overlays/"); let filename = if is_overlay { - format!("{}/overlays/{}.ron", family, tiletype.replace("overlay/", "")) + format!("{}/overlays/{}.ron", family, tiletype.replace("overlays/", "")) } else { format!("{}/tiletypes/{}.ron", family, tiletype) }; @@ -952,6 +1030,10 @@ impl Database { } // Flush tile bit database changes to disk pub fn flush(&mut self) { + let mut new_pips : u32 = 0; + let mut new_enums : u32 = 0; + let mut new_words : u32 = 0; + for kv in self.tilebits.iter_mut() { let (family, tiletype) = kv.0; let tilebits = kv.1; @@ -967,18 +1049,23 @@ impl Database { }; tilebits.sort(); - let is_overlay = tiletype.starts_with("overlay"); + let is_overlay = tiletype.starts_with("overlays/"); let (dir_name, file_name) = if is_overlay { - ("overlays", tiletype.replace("overlay", "")) + ("overlays", tiletype.replace("overlays", "")) } else { ("tiletypes", tiletype.clone()) }; debug!("Writing {}/{}/{}/{}.ron", self.root.as_ref().unwrap(), family, dir_name, file_name); + new_pips += tilebits.new_pips; + new_enums += tilebits.new_enums; + new_words += tilebits.new_words; let tt_ron_buf = ron::ser::to_string_pretty(&tilebits.db, pretty).unwrap(); + + fs::create_dir_all(format!("{}/{}/{}", self.root.as_ref().unwrap(), family, dir_name)).unwrap(); File::create(format!( "{}/{}/{}/{}.ron", self.root.as_ref().unwrap(), family, dir_name, file_name @@ -1014,5 +1101,9 @@ impl Database { .unwrap(); ipbits.dirty = false; } + + if new_pips > 0 || new_enums > 0 || new_words > 0 { + info!("Flushing with {} new pips, {} new enum settings, {} new words", new_pips, new_enums, new_words); + } } } diff --git a/libprjoxide/pyprjoxide/src/lib.rs b/libprjoxide/pyprjoxide/src/lib.rs index e227a27..8dcd787 100644 --- a/libprjoxide/pyprjoxide/src/lib.rs +++ b/libprjoxide/pyprjoxide/src/lib.rs @@ -89,6 +89,12 @@ impl Database { pub fn reformat(&mut self) { self.db.reformat(); } + pub fn merge(&mut self, other: &mut Database) -> PyResult<()>{ + match self.db.merge(&mut other.db) { + Ok(_) => Ok(()), + Err(e) => Err(PyException::new_err(e)) + } + } } #[pyclass] @@ -225,7 +231,7 @@ impl Fuzzer { fn add_enum_sample(&mut self, db: &mut Database, option: &str, base_bitfile: &str) { self.fz.add_enum_sample(&mut db.db, option, base_bitfile); } - fn add_enum_delta(&mut self, db: &mut Database, option: &str, delta: ChipDelta) { + fn add_enum_delta(&mut self, option: &str, delta: ChipDelta) { self.fz.add_enum_delta(option, delta); } diff --git a/tools/merge-databases.py b/tools/merge-databases.py old mode 100644 new mode 100755 index e69de29..8f08e1b --- a/tools/merge-databases.py +++ b/tools/merge-databases.py @@ -0,0 +1,22 @@ +import sys +import os +import logging +import libpyprjoxide + +def main(): + LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() + logging.basicConfig( + level=LOGLEVEL, + ) + + frm_db_path = sys.argv[1] + to_db_path = sys.argv[2] + + frm_db = libpyprjoxide.Database(frm_db_path) + to_db = libpyprjoxide.Database(to_db_path) + + to_db.merge(frm_db) + to_db.flush() + +if __name__ == "__main__": + main() diff --git a/tools/parse_webdoc.py b/tools/parse_webdoc.py index 6011d5a..4581f24 100644 --- a/tools/parse_webdoc.py +++ b/tools/parse_webdoc.py @@ -5,11 +5,9 @@ from bs4 import BeautifulSoup from pathlib import Path - def normalize_text(s: str) -> str: return " ".join(s.encode('ascii', errors='ignore').decode("ascii").replace("(default)", "").split()) - def extract_cell_value(td): text = normalize_text(td.get_text()) matches = re.findall(r'"([^"]*)"', text) @@ -53,16 +51,21 @@ def parse_table(table): cell_value = [cell_value] row_obj[header] = cell_value + + if row_obj.get("Name", None) == "": - data[-1]["Values"].extend(row_obj["Values"]) - #data[-1]["Description"] += (row_obj["Description"]) + if "Values" not in data[-1]: + data[-1]["Values"] = [] + + data[-1]["Values"].extend(row_obj.get("Values", [])) + #data[-1]["Description"] += (row_obj.get("Description", "")) else: data.append(row_obj) return data -def scrape_html(html_path: Path): +def scrape_html(html_path: Path, out_dir = "./primitives"): with open(html_path, "r", encoding="utf-8") as f: soup = BeautifulSoup(f, "lxml") @@ -72,7 +75,6 @@ def scrape_html(html_path: Path): output["description"] = "".join([div.get_text() for div in soup.select(".BodyAfterHead")]) output["platforms"] = [ supported_platforms.get_text() for supported_platforms in soup.select(".Bulleted")] - for title_div in soup.select("div.TableTitle"): table_title = normalize_text(title_div.get_text()) if not table_title: @@ -84,7 +86,8 @@ def scrape_html(html_path: Path): output[table_title] = parse_table(table) - output_path = f"primitives/{title}.json" + output_path = Path(out_dir) / Path(f"{title.replace('/', '_')}.json") + print(output_path, out_dir, title) with open(output_path, "w", encoding="utf-8") as f: json.dump(output, f, indent=2) diff --git a/util/common/database.py b/util/common/database.py index 40eeb52..58e5fe0 100644 --- a/util/common/database.py +++ b/util/common/database.py @@ -15,6 +15,12 @@ def get_oxide_root(): """Return the absolute path to the Project Oxide repo root""" return path.abspath(path.join(__file__, "../../../")) +def get_family_for_device(device): + family = device.split('-')[0] + if family == "LFD2NX": + return "LIFCL" + return family + def get_radiant_version(): # `lapie` seems to be renamed every version or so. Map that out here. Most installations will have # the version name at the end of their path, so we just look at the radiant dir for a hint. The user @@ -37,6 +43,27 @@ def get_cache_dir(): makedirs(path, exist_ok=True) return path + +def get_primitive_json(primitive): + import parse_webdoc + + fn = get_cache_dir() + f"/primitives/{primitive}.json" + + primitives_dir = get_cache_dir() + f"/primitives/" + if not path.exists(primitives_dir): + os.makedirs(primitives_dir, exist_ok=True) + + RADIANT_DIR = os.environ.get("RADIANTDIR") + html_dir = f"{RADIANT_DIR}/docs/webhelp/eng/Reference Guides/FPGA Libraries Reference Guide/" + + for file_path in Path(html_dir).iterdir(): + if file_path.is_file(): + print(file_path, primitives_dir) + parse_webdoc.scrape_html(str(file_path), primitives_dir) + + with open(fn) as f: + return json.load(f) + @cache def get_db_root(): """ @@ -58,7 +85,7 @@ def get_db_subdir(family = None, device = None, package = None): """ subdir = get_db_root() if family is None and device is not None: - family = device.split('-')[0] + family = get_family_for_device(device) dparts = [family, device, package] for dpart in dparts: @@ -72,7 +99,7 @@ def get_db_subdir(family = None, device = None, package = None): def get_base_addrs(family, device = None): if device is None: device = family - family = device.split('-')[0] + family = get_family_for_device(device) tgjson = path.join(get_db_subdir(family, device), "baseaddr.json") if path.exists(tgjson): @@ -91,7 +118,7 @@ def get_tilegrid(family, device = None): """ if device is None: device = family - family = device.split('-')[0] + family = get_family_for_device(device) tgjson = path.join(get_db_subdir(family, device), "tilegrid.json") if path.exists(tgjson): @@ -110,7 +137,7 @@ def get_iodb(family, device = None): """ if device is None: device = family - family = device.split('-')[0] + family = get_family_for_device(device) tgjson = path.join(get_db_subdir(family, device), "iodb.json") with open(tgjson, "r") as f: return json.load(f) @@ -125,7 +152,7 @@ def get_devices(): return json.load(f) def get_tiletypes(family): - family = family.split("-")[0] + family = get_family_for_device(family) p = path.join(get_db_root(), family, "tiletypes") tiletypes = {} @@ -148,7 +175,7 @@ def get_sites(family, device = None): if device is None: device = family - family = device.split('-')[0] + family = get_family_for_device(family) return lapie.get_sites_with_pin(device) diff --git a/util/common/tiles.py b/util/common/tiles.py index 46ea7ac..241b759 100644 --- a/util/common/tiles.py +++ b/util/common/tiles.py @@ -36,7 +36,7 @@ def type_from_fullname(tile): def get_rc_from_edge(device, side, offset): devices = database.get_devices() - device_info = devices["families"][device.split("-")[0]]["devices"][device] + device_info = devices["families"][database.get_family_for_device(device)]["devices"][device] max_row = device_info["max_row"] max_col = device_info["max_col"] @@ -363,7 +363,7 @@ def get_connected_tiles(device, tilename): def draw_rc(device, rcs): devices = database.get_devices() - device_info = devices["families"][device.split("-")[0]]["devices"][device] + device_info = devices["families"][database.get_family_for_device(device)]["devices"][device] max_row = device_info["max_row"] max_col = device_info["max_col"] @@ -733,7 +733,7 @@ def resolve_possible_names(device, n, rel_to=(0,0)): def is_edge_node(device, n): rcs = resolve_node_rcs(device, n) devices = database.get_devices() - device_info = devices["families"][device.split("-")[0]]["devices"][device] + device_info = devices["families"][database.get_family_for_device(device)]["devices"][device] max_row = device_info["max_row"] max_col = device_info["max_col"] diff --git a/util/fuzz/fuzzconfig.py b/util/fuzz/fuzzconfig.py index 0111616..0e0769c 100644 --- a/util/fuzz/fuzzconfig.py +++ b/util/fuzz/fuzzconfig.py @@ -135,7 +135,7 @@ def __init__(self, device, job, tiles=[], sv = None): self.job = job self.tiles = tiles if sv is None: - family = device.split("-")[0] + family = database.get_family_for_device(device) suffix = device.split("-")[1] sv = database.get_oxide_root() + f"/fuzzers/{family}/shared/empty.v" self.sv = sv @@ -216,7 +216,7 @@ def subst_defaults(self): } return { - "arch": self.device.split("-")[0], + "arch": database.get_family_for_device(self.device), "arcs_attr": "", "device": self.device, "package": packages.get(self.device, "QFN72"), diff --git a/util/fuzz/primitives.py b/util/fuzz/primitives.py index 6530c14..02738c7 100644 --- a/util/fuzz/primitives.py +++ b/util/fuzz/primitives.py @@ -3,7 +3,7 @@ from pathlib import Path from cffi.model import PrimitiveType - +import logging class PrimitiveSetting: def __init__(self, name, desc, depth=3, enable_value=None): @@ -140,9 +140,7 @@ def fill_config(self): def parse_primitive_json(primitive, site_type=None, core_suffix=True, mode = None, value_sizes={}): import database - fn = database.get_oxide_root() + f"/tools/primitives/{primitive}.json" - with open(fn) as f: - parsed = json.load(f) + parsed = database.get_primitive_json(primitive) if core_suffix and site_type is None: primitive = primitive + "_CORE" @@ -154,6 +152,10 @@ def create_setting(s): values = s.get("Value", s.get("Values")) name = s.get("Name", s.get("Attribute")) + if values is None: + logging.warn(f"No values for {primitive}") + return None + if len(values) == 1 and name in value_sizes: return WordSetting(name, value_sizes[name], desc=name, default=int(values[0])) @@ -161,7 +163,7 @@ def create_setting(s): bit_cnt = len(values[0].replace("`", "").split(" ")[0].split("0b")[-1]) return WordSetting(name, bit_cnt, desc=name, number_formatter=WordSetting.binary_formatter) - return EnumSetting(name, values, desc=str(s.get("Description")), default=values[0]) + return EnumSetting(name, values, desc=str(s.get("Description", "")), default=values[0]) parameters_key = "Parameters" if parameters_key not in parsed: @@ -191,7 +193,7 @@ def create_pin(pin_def, dir): site_type=site_type if site_type else mode, settings=[create_setting(s) for s in parsed[parameters_key]], pins=pins, - desc=parsed["description"], + desc=parsed.get("description", ""), mode=mode, primitive=primitive ) @@ -292,52 +294,38 @@ def create_pin(pin_def, dir): oscd_core = PrimitiveDefinition( "OSCD_CORE", settings=[ - EnumSetting("DTR_EN", ["ENABLED", "DISABLED"], - desc="DTR block enable from MIB"), + EnumSetting("DTR_EN", ["ENABLED", "DISABLED"]), - WordSetting("HF_CLK_DIV", 8, default=1, - desc="HF oscillator user output divider (div2–div256)"), + WordSetting("HF_CLK_DIV", 8, default=1), - WordSetting("HF_SED_SEC_DIV", 8, - desc="HF oscillator SED/secondary divider (div2–div256)"), + WordSetting("HF_SED_SEC_DIV", 8), - EnumSetting("HF_FABRIC_EN", ["ENABLED", "DISABLED"], - desc="HF oscillator trim source mux select"), + EnumSetting("HF_FABRIC_EN", ["ENABLED", "DISABLED"]), EnumSetting("HF_OSC_EN", ["ENABLED", "DISABLED"], - desc="HF oscillator enable", default="ENABLED", enable_value="ENABLED"), + default="ENABLED", enable_value="ENABLED"), - EnumSetting("LF_FABRIC_EN", ["ENABLED", "DISABLED"], - desc="LF oscillator trim source mux select"), + EnumSetting("LF_FABRIC_EN", ["ENABLED", "DISABLED"]), - EnumSetting("LF_OUTPUT_EN", ["ENABLED", "DISABLED"], - desc="LF clock output enable"), + EnumSetting("LF_OUTPUT_EN", ["ENABLED", "DISABLED"]), - EnumSetting("DEBUG_N", ["ENABLED", "DISABLED"], - desc="Ignore SLEEP/STOP during USER mode when disabled"), + EnumSetting("DEBUG_N", ["ENABLED", "DISABLED"]), ], pins=[ - PinSetting("HFOUTEN", dir="in", - desc="HF clock (225MHz) output enable (test only)"), - PinSetting("HFSDSCEN", dir="in", - desc="HF user clock output enable"), - PinSetting("HFOUTCIBEN", dir="in", - desc="CIB control to enable/disable HF oscillator during user mode"), - PinSetting("REBOOT", dir="in", - desc="CIB control to enable/disable hf_clk_config output"), - PinSetting("HFCLKOUT", dir="out", - desc="450MHz with programmable divider (2–256) to user"), - PinSetting("LFCLKOUT", dir="out", - desc="Low frequency clock output after div4 (32kHz)"), - PinSetting("HFCLKCFG", dir="out", - desc="450MHz clock to configuration block"), - PinSetting("HFSDCOUT", dir="out", - desc="450MHz with programmable divider (2–256) to configuration"), + PinSetting("HFOUTEN", dir="in"), + PinSetting("HFSDSCEN", dir="in"), + PinSetting("HFOUTCIBEN", dir="in"), + PinSetting("REBOOT", dir="in"), + PinSetting("HFCLKOUT", dir="out"), + PinSetting("LFCLKOUT", dir="out"), + PinSetting("HFCLKCFG", dir="out"), + PinSetting("HFSDCOUT", dir="out"), ], ) dcc = PrimitiveDefinition.parse_primitive_json("DCC", core_suffix=False) dcc.get_setting("DCCEN").enable_value = "1" +dcc.settings[0].desc = "DCC bypassed (0) or used as gate (1)" PrimitiveDefinition( "DCS", @@ -354,64 +342,64 @@ def create_pin(pin_def, dir): s.enable_value = "ENABLED" pll_core.settings = [s for s in pll_core.settings if s.name != "CONFIG_WAIT_FOR_LOCK"] pll_core.pins = [ - PinSetting(name="INTFBK0", dir="out", desc="", bits=None), - PinSetting(name="INTFBK1", dir="out", desc="", bits=None), - PinSetting(name="INTFBK2", dir="out", desc="", bits=None), - PinSetting(name="INTFBK3", dir="out", desc="", bits=None), - PinSetting(name="INTFBK4", dir="out", desc="", bits=None), - PinSetting(name="INTFBK5", dir="out", desc="", bits=None), - PinSetting(name="LMMIRDATA", dir="out", desc="LMMI read data to fabric.", bits=7), - PinSetting(name="LMMIRDATAVALID", dir="out", desc="LMMI read data valid to fabric.", bits=None), - PinSetting(name="LMMIREADY", dir="out", desc="LMMI ready signal to fabric.", bits=None), - PinSetting(name="CLKOP", dir="out", desc="Primary (A) output clock.", bits=None), - PinSetting(name="CLKOS", dir="out", desc="Secondary (B) output clock.", bits=None), - PinSetting(name="CLKOS2", dir="out", desc="Secondary (C) output clock.", bits=None), - PinSetting(name="CLKOS3", dir="out", desc="Secondary (D) output clock.", bits=None), - PinSetting(name="CLKOS4", dir="out", desc="Secondary (E) output clock.", bits=None), - PinSetting(name="CLKOS5", dir="out", desc="Secondary (F) output clock.", bits=None), - PinSetting(name="INTLOCK", dir="out", desc="PLL internal lock indicator. PLL CIB output.", bits=None), - PinSetting(name="LEGRDYN", dir="out", desc="PLL lock indicator. PLL CIB output.", bits=None), - PinSetting(name="LOCK", dir="out", desc="", bits=None), - PinSetting(name="PFDDN", dir="out", desc="PFD DN output signal to PLL CIB port.", bits=None), - PinSetting(name="PFDUP", dir="out", desc="PFD UP output signal to PLL CIB port.", bits=None), - PinSetting(name="REFMUXCK", dir="out", desc="Reference CLK mux output. PLL CIB output.", bits=None), - PinSetting(name="REGQA", dir="out", desc="", bits=None), PinSetting(name="REGQB", dir="out", desc="", bits=None), - PinSetting(name="REGQB1", dir="out", desc="", bits=None), - PinSetting(name="CLKOUTDL", dir="out", desc="", bits=None), - - #PinSetting(name="LOADREG", dir="in",desc="Valid if MC1_DYN_SOURCE = 1. Initiates a divider output phase shift on negative edge of CIB_LOAD_REG.",bits=None), - #PinSetting(name="DYNROTATE", dir="in", desc="Valid if MC1_DYN_SOURCE = 1. Initiates a change from current VCO clock phase to an earlier or later phase on the negative edge of CIB_ROTATE.", bits=None), - PinSetting(name="LMMICLK", dir="in", desc="LMMI clock from fabric.", bits=None), - PinSetting(name="LMMIRESETN", dir="in", desc="LMMI reset signal to reset the state machine if the IP gets locked.", + PinSetting(name="INTFBK0", dir="out", bits=None), + PinSetting(name="INTFBK1", dir="out", bits=None), + PinSetting(name="INTFBK2", dir="out", bits=None), + PinSetting(name="INTFBK3", dir="out", bits=None), + PinSetting(name="INTFBK4", dir="out", bits=None), + PinSetting(name="INTFBK5", dir="out", bits=None), + PinSetting(name="LMMIRDATA", dir="out", bits=7), + PinSetting(name="LMMIRDATAVALID", dir="out", bits=None), + PinSetting(name="LMMIREADY", dir="out", bits=None), + PinSetting(name="CLKOP", dir="out", bits=None), + PinSetting(name="CLKOS", dir="out", bits=None), + PinSetting(name="CLKOS2", dir="out", bits=None), + PinSetting(name="CLKOS3", dir="out", bits=None), + PinSetting(name="CLKOS4", dir="out", bits=None), + PinSetting(name="CLKOS5", dir="out", bits=None), + PinSetting(name="INTLOCK", dir="out", bits=None), + PinSetting(name="LEGRDYN", dir="out", bits=None), + PinSetting(name="LOCK", dir="out", bits=None), + PinSetting(name="PFDDN", dir="out", bits=None), + PinSetting(name="PFDUP", dir="out", bits=None), + PinSetting(name="REFMUXCK", dir="out", bits=None), + PinSetting(name="REGQA", dir="out", bits=None), + PinSetting(name="REGQB1", dir="out", bits=None), + PinSetting(name="CLKOUTDL", dir="out", bits=None), + + #PinSetting(name="LOADREG", dir="in",bits=None), + #PinSetting(name="DYNROTATE", dir="in", bits=None), + PinSetting(name="LMMICLK", dir="in", bits=None), + PinSetting(name="LMMIRESETN", dir="in", bits=None), - PinSetting(name="LMMIREQUEST", dir="in", desc="LMMI request signal from fabric.", bits=None), - PinSetting(name="LMMIWRRDN", dir="in", desc="LMMI write-high/read-low from fabric.", bits=None), + PinSetting(name="LMMIREQUEST", dir="in", bits=None), + PinSetting(name="LMMIWRRDN", dir="in", bits=None), PinSetting(name="LMMIOFFSET", dir="in", - desc="LMMI offset address from fabric. Not all bits are required for an IP.", bits=6), - PinSetting(name="LMMIWDATA", dir="in", desc="LMMI write data from fabric. Not all bits are required for an IP.", + bits=6), + PinSetting(name="LMMIWDATA", dir="in", bits=7), - PinSetting(name="REFCK", dir="in", desc="", bits=None), - PinSetting(name="ENCLKOP", dir="in", desc="Enable A output (CLKOP). Active high. PLL CIB input.", bits=None), - PinSetting(name="ENCLKOS", dir="in", desc="Enable B output (CLKOS). Active high. PLL CIB input.", bits=None), - PinSetting(name="ENCLKOS2", dir="in", desc="Enable C output (CLKOS2). Active high. PLL CIB input.", bits=None), - PinSetting(name="ENCLKOS3", dir="in", desc="Enable D output (CLKOS3). Active high. PLL CIB input.", bits=None), - PinSetting(name="ENCLKOS4", dir="in", desc="Enable E output (CLKOS4). Active high. PLL CIB input.", bits=None), - PinSetting(name="ENCLKOS5", dir="in", desc="Enable F output (CLKOS5). Active high. PLL CIB input.", bits=None), - PinSetting(name="FBKCK", dir="in", desc="", bits=None), - PinSetting(name="LEGACY", dir="in", desc="PLL legacy mode signal. Active high to enter the mode. Enabled by lmmi_legacy fuse. PLL CIB input.", bits=None), - PinSetting(name="PLLRESET", dir="in", desc="Active high to reset PLL. Enabled by MC1_PLLRESET. PLL CIB input.", + PinSetting(name="REFCK", dir="in", bits=None), + PinSetting(name="ENCLKOP", dir="in", bits=None), + PinSetting(name="ENCLKOS", dir="in", bits=None), + PinSetting(name="ENCLKOS2", dir="in", bits=None), + PinSetting(name="ENCLKOS3", dir="in", bits=None), + PinSetting(name="ENCLKOS4", dir="in", bits=None), + PinSetting(name="ENCLKOS5", dir="in", bits=None), + PinSetting(name="FBKCK", dir="in", bits=None), + PinSetting(name="LEGACY", dir="in", bits=None), + PinSetting(name="PLLRESET", dir="in", bits=None), - PinSetting(name="STDBY", dir="in", desc="PLL standby signal. Active high to put PLL clocks in low. Not used.", + PinSetting(name="STDBY", dir="in", bits=None), - PinSetting(name="ROTDEL", dir="in", desc="", bits=None), - PinSetting(name="DIRDEL", dir="in", desc="", bits=None), - PinSetting(name="ROTDELP1", dir="in", desc="", bits=None), - - PinSetting(name="BINTEST", dir="in", desc="", bits=1), - PinSetting(name="DIRDELP1", dir="in", desc="", bits=None), - PinSetting(name="GRAYACT", dir="in", desc="", bits=4), - PinSetting(name="BINACT", dir="in", desc="", bits=1) + PinSetting(name="ROTDEL", dir="in", bits=None), + PinSetting(name="DIRDEL", dir="in", bits=None), + PinSetting(name="ROTDELP1", dir="in", bits=None), + + PinSetting(name="BINTEST", dir="in", bits=1), + PinSetting(name="DIRDELP1", dir="in", bits=None), + PinSetting(name="GRAYACT", dir="in", bits=4), + PinSetting(name="BINACT", dir="in", bits=1) ] # The documentation has this but its for DIFFIO @@ -421,17 +409,34 @@ def remove_failsafe_enum(definition): setting.values.remove("FAILSAFE") return definition -seio33 = remove_failsafe_enum(PrimitiveDefinition.parse_primitive_json("SEIO33")) -seio18 = remove_failsafe_enum(PrimitiveDefinition.parse_primitive_json("SEIO18")) +# These work but are specially handled right now +#seio33 = remove_failsafe_enum(PrimitiveDefinition.parse_primitive_json("SEIO33")) +#seio18 = remove_failsafe_enum(PrimitiveDefinition.parse_primitive_json("SEIO18")) +#PrimitiveDefinition.parse_primitive_json("DIFFIO18") -PrimitiveDefinition.parse_primitive_json("DIFFIO18") eclkdiv = PrimitiveDefinition.parse_primitive_json("ECLKDIV") eclkdiv.get_setting("ECLK_DIV").enable_value = "2" -PrimitiveDefinition.parse_primitive_json("PCLKDIV", core_suffix=False) +# This definition is from 2024 web docs +pclkdiv = PrimitiveDefinition( + "PCLKDIV", + settings=[ + EnumSetting("DIV_PCLKDIV", [ + "X1", + "X2", + "X4", + "X8", + "X16", + "X32", + "X64", + "X128" + ], desc="Divisor applied to clkin"), + ], +) dlldel = PrimitiveDefinition.parse_primitive_json("DLLDEL", value_sizes={"ADJUST": 9}) dlldel.get_setting("ENABLE").enable_value = "ENABLED" + # Doesn't work right now -- seems optimimized out? # i2cfifo = PrimitiveDefinition.parse_primitive_json("I2CFIFO") # i2cfifo.get_setting("CR1GCEN").enable_value = "EN" From 2457dc11be5132391adf219546edce7fe1eb3c0f Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Wed, 18 Feb 2026 23:59:44 -0700 Subject: [PATCH 28/29] Incorporate feedback; add docs; general cleanup --- docs/bels/overview.md | 2 +- docs/database.md | 10 ++++++ fuzzers/000-build-pip-overlays/fuzzer.py | 46 ++++++++++++------------ fuzzers/LFCPNX/shared/empty_40.v | 9 ----- generate_database.sh | 8 +++++ tools/extract_tilegrid.py | 2 +- 6 files changed, 42 insertions(+), 35 deletions(-) delete mode 100644 fuzzers/LFCPNX/shared/empty_40.v diff --git a/docs/bels/overview.md b/docs/bels/overview.md index 3517c56..94451f8 100644 --- a/docs/bels/overview.md +++ b/docs/bels/overview.md @@ -4,7 +4,7 @@ The tiletype of a tile dictates which BEL's are available on a given tile and wh A given bel might span over multiple logical tiles. It's anchor tile is the one with the appropriate tiletype but for routing information on where the related tile is there is rel_x and rel_y; which encode the relative tile offset for the related tile. This data can be varied based on the family, device and actual tile in question. -For bels with these offsets, the offset information is used in fuzzing the routing to map the interconnect. The offset themselves can be devined from the output of the dev_get_nodes command and report for nearby CIB tiles. For instance, related tiles to LRAM will have LRAM_CORE wires in their tile. +For bels with these offsets, the offset information is used in fuzzing the routing to map the interconnect. The offset themselves can be derived from the output of the dev_get_nodes command and report for nearby CIB tiles. For instance, related tiles to LRAM will have LRAM_CORE wires in their tile. Bel information is encoded in the bba file which is produced by prjoxide and ingested in the build process of nextpnr. diff --git a/docs/database.md b/docs/database.md index 8fcf52b..f4cd135 100644 --- a/docs/database.md +++ b/docs/database.md @@ -49,6 +49,16 @@ These files are generated by various fuzzers associated with the given primitive These files are also generated by fuzzers. While they also map out the relationship between various parameters and bits in the bitstream, they mainly focus on the interconnection between tiles themselves. This gives both a graph on what connections are possible but also the way those connections are configured in the bitstream. +### overlays + +Overlays describe snippets of a tile functionality in the same way that tiletypes do but these snippets might only apply to +a subset of tiles. The finding here is that not all tiles for a tile type have identical PIP definitions, and that the +minimum set of shared pips excludes a non trivial amount of pips. + +An overlays.json file per device describes which overlays a given tile participates in. The total pips and settings for a +tile is then the union of the overlays it participates in, as well as the tiletype RON file. Overlays and tiles specified +should not have any bit conflicts and prjoxide will generate an error if they do. + ### timing This is a collection of a bunch of cell types and a description of the delay timing between pins as well as the setup and hold time for each pin in the cell. \ No newline at end of file diff --git a/fuzzers/000-build-pip-overlays/fuzzer.py b/fuzzers/000-build-pip-overlays/fuzzer.py index 160fe46..510dc13 100644 --- a/fuzzers/000-build-pip-overlays/fuzzer.py +++ b/fuzzers/000-build-pip-overlays/fuzzer.py @@ -21,6 +21,22 @@ from DesignFileBuilder import UnexpectedDeltaException, DesignFileBuilder, BitConflictException +### This fuzzer maps all of the pips in each device. It does this by anonymizing the pips for every tile, and generating +### common groupings which share pip definitions. A grouping of common pips is writen to an overlay file and we track +### which tiles containe which overlays. +### +### Currently the overlays are also keyed by tile type and in certain cases by device. +### +### To get the pips for every tile, the first thing this fuzzer does is download the node database from lark/lapie tools. +### This is pretty slow -- expect 2-3 hours per device -- but the results are cached into a sqlite database so this is +### a one time thing. +### +### To minimize the number of bitstreams built, this fuzzer uses DesignFileBuilder. This construct can combine multiple +### PIPs to solve into a single design. This brings the total number of bitfiles needed from around 20k per device to +### 2-3k per device. + + +### The hash in python isn't stable, so we generate our own to name the overlays unqiuely. def stablehash(x): def set_default(obj): if isinstance(obj, set): @@ -40,6 +56,9 @@ def make_dict_of_lists(lst, key): rtn[key(item)].append(item) return rtn +def make_overlay_name(k): + (anon_pips, *args) = k + return "-".join([*args, stablehash(anon_pips)]) async def FuzzAsync(executor): families = database.get_devices()["families"] @@ -51,10 +70,6 @@ async def FuzzAsync(executor): ] for device in devices: - logging.info(device) - - tiletype = "PLC" - tilegrid = database.get_tilegrid(device)['tiles'] all_tiles = sorted({k for k in tilegrid}) @@ -72,27 +87,14 @@ async def FuzzAsync(executor): for pip,ts in pips_to_tiles.items(): for tile_type, tt_ts in make_dict_of_lists(ts, lambda x: x.split(":")[-1]).items(): - rel_pip_groups_by_tiletype[tuple(sorted(tt_ts))].add(pip) sorted_groups = sorted(rel_pip_groups.items(), key=lambda x: len(x[0]), reverse=True) - json_groups = [ - (k, sorted([[", ".join(map(str, w)) for w in p] for p in v])) - for k, v in sorted_groups - ] - def set_default(obj): - if isinstance(obj, set): - return sorted(obj) - raise TypeError - def pip_is_tiletype_dependent(p): + # Currently we seperate everything out by tiletype; but this might be unnecessary in some cases. return True - return any([(w[0].split(":")[0] in ["C"] or w[0].startswith("G:VCC")) - for w in p]) - - dll_core_wire = re.compile(r"^J(CODEI(\d+)_I_DQS_TOP_DLL_CODE_ROUTING_MUX|D[01]_I4_\d)$") overlays = {} for ts, anon_pips in rel_pip_groups_by_tiletype.items(): @@ -106,6 +108,8 @@ def pip_is_tiletype_dependent(p): grp = sorted(grp) overlay_args = [tt] + + # TAP_CIB has conflicts amongst devices; so add device to the overlay key if tt == "TAP_CIB": overlay_args.append(device) @@ -113,11 +117,6 @@ def pip_is_tiletype_dependent(p): else: overlays[(tuple(sorted(split_anon_pips)), )] = sorted(ts) - - def make_overlay_name(k): - (anon_pips, *args) = k - return "-".join([*args, stablehash(anon_pips)]) - tiles_to_overlays = {} for k,lst in overlays.items(): for item in lst: @@ -132,7 +131,6 @@ def make_overlay_name(k): db_sub_dir = database.get_db_subdir(device = device) with open(f"{db_sub_dir}/overlays.json", "w") as f: overlay_doc = { - #"overlay_membership": {make_overlay_name(k):sorted(v) for k,v in overlays.items()}, "overlays": { stablehash(k): sorted(k) for k in overlays_to_tiles }, diff --git a/fuzzers/LFCPNX/shared/empty_40.v b/fuzzers/LFCPNX/shared/empty_40.v deleted file mode 100644 index 8647858..0000000 --- a/fuzzers/LFCPNX/shared/empty_40.v +++ /dev/null @@ -1,9 +0,0 @@ - -(* \db:architecture ="LFCPNX", \db:device ="LFCPNX-40", \db:package ="LFG672", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) -module top ( - -); - // A primitive is needed, but VHI should be harmless - (* \xref:LOG ="q_c@0@9" *) - VHI vhi_i(); -endmodule diff --git a/generate_database.sh b/generate_database.sh index f6f37a6..e73a728 100755 --- a/generate_database.sh +++ b/generate_database.sh @@ -8,6 +8,14 @@ fuzz=false merge=false git_commit=false +print_usage() { + echo "Usage: ./generate_database.sh " + echo "Flags:" + echo " -f - Run each fuzzer and store the results to their local fuzzer db" + echo " -m - Merge the local fuzzer db into the global db" + echo " -g - Run git commit as the local fuzzers are merged." +} + while getopts 'fgm' flag; do case "${flag}" in f) fuzz=true ;; diff --git a/tools/extract_tilegrid.py b/tools/extract_tilegrid.py index fd7686d..d1510b7 100644 --- a/tools/extract_tilegrid.py +++ b/tools/extract_tilegrid.py @@ -63,7 +63,7 @@ def get_tf2c(dev): if dev == "LFCPNX-100": return tap_frame_to_col_100 - elif dev == "LIFCL-40" or dev == "LFDN2X-40" or dev == "LFD2NX-40": + elif dev == "LIFCL-40" or dev == "LFDN2X-40": return tap_frame_to_col_40 elif dev == "LIFCL-17": return tap_frame_to_col_17 From b10f4eb20c5b02d77293d501cc1dda0649aabc3a Mon Sep 17 00:00:00 2001 From: Justin Berger Date: Thu, 19 Feb 2026 23:08:07 -0700 Subject: [PATCH 29/29] Fix overlay naming, make work for other family --- fuzzers/000-build-pip-overlays/fuzzer.py | 9 +++++++-- libprjoxide/prjoxide/src/database.rs | 13 +++++++------ libprjoxide/prjoxide/src/wires.rs | 2 +- util/common/tiles.py | 16 +++++++++------- util/fuzz/interconnect.py | 2 +- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/fuzzers/000-build-pip-overlays/fuzzer.py b/fuzzers/000-build-pip-overlays/fuzzer.py index 510dc13..6c9f72a 100644 --- a/fuzzers/000-build-pip-overlays/fuzzer.py +++ b/fuzzers/000-build-pip-overlays/fuzzer.py @@ -122,7 +122,7 @@ def pip_is_tiletype_dependent(p): for item in lst: if item not in tiles_to_overlays: tiles_to_overlays[item] = {item.split(":")[-1]} - tiles_to_overlays[item].add("overlay/" + make_overlay_name(k)) + tiles_to_overlays[item].add("overlays/" + make_overlay_name(k)) overlays_to_tiles = defaultdict(set) for tile,tile_overlays in tiles_to_overlays.items(): @@ -139,6 +139,11 @@ def pip_is_tiletype_dependent(p): } } + def set_default(obj): + if isinstance(obj, set): + return sorted(obj) + raise TypeError + json.dump(overlay_doc, f, default=set_default, indent=4, sort_keys=True) builder = DesignFileBuilder(device, executor) @@ -147,7 +152,7 @@ async def interconnect_group(overlay_key, ts): (anon_pips, *args) = overlay_key overlay = make_overlay_name(overlay_key) - config = FuzzConfig(job=f"{tiletype}-routes", device=device, tiles=ts) + config = FuzzConfig(job=f"pip-overlays", device=device, tiles=ts) return await interconnect.fuzz_interconnect_sinks_across_span(config, ts, anon_pips, executor=executor, overlay=overlay, check_pip_placement=False, builder=builder) logging.info(f"Overlay count: {len(overlays)}") diff --git a/libprjoxide/prjoxide/src/database.rs b/libprjoxide/prjoxide/src/database.rs index 06cff6f..bedb9d5 100644 --- a/libprjoxide/prjoxide/src/database.rs +++ b/libprjoxide/prjoxide/src/database.rs @@ -936,7 +936,7 @@ impl Database { let overlay_members : Vec = overlay.overlays.clone().into_iter() .sorted_by(|x, y| { - (y.starts_with("overlay"), y).cmp(&(x.starts_with("overlay"), x)) + (y.starts_with("overlays"), y).cmp(&(x.starts_with("overlays"), x)) }).collect(); for layer in overlay_members { @@ -1065,14 +1065,15 @@ impl Database { let tt_ron_buf = ron::ser::to_string_pretty(&tilebits.db, pretty).unwrap(); - fs::create_dir_all(format!("{}/{}/{}", self.root.as_ref().unwrap(), family, dir_name)).unwrap(); - File::create(format!( + fs::create_dir_all(format!("{}/{}/{}", self.root.as_ref().unwrap(), family, dir_name)).expect("Could not create directory for tiletype"); + let ron_file = format!( "{}/{}/{}/{}.ron", self.root.as_ref().unwrap(), family, dir_name, file_name - )) - .unwrap() + ); + File::create(&ron_file) + .expect(format!("Could not create ron file {}", ron_file).as_str()) .write_all(tt_ron_buf.as_bytes()) - .unwrap(); + .expect("Could not write ron file"); tilebits.dirty = false; } for kv in self.ipbits.iter_mut() { diff --git a/libprjoxide/prjoxide/src/wires.rs b/libprjoxide/prjoxide/src/wires.rs index 7aa60fa..ac703e7 100644 --- a/libprjoxide/prjoxide/src/wires.rs +++ b/libprjoxide/prjoxide/src/wires.rs @@ -279,7 +279,7 @@ pub fn normalize_wire(chip: &Chip, tile: &Tile, wire: &str) -> String { if tile.name.contains("TAP") && (wn.starts_with("HPRX") || wn.starts_with("HPBX")) && !wn.starts_with("HFIE") { let branch_dir = match chip.device.as_str() { - "LIFCL-40" | "LIFCL-17" | "LFD2NX-40" => + "LIFCL-40" | "LIFCL-17" | "LFD2NX-40" | "LFCPNX-100" => if wx < tx { Some("L") } else if wx > tx { diff --git a/util/common/tiles.py b/util/common/tiles.py index 241b759..adb9a14 100644 --- a/util/common/tiles.py +++ b/util/common/tiles.py @@ -172,14 +172,16 @@ def get_owning_tiles_for_rc(rc): tiles_at_rc = get_owning_tiles_for_rc(rc) - if len(tiles_at_rc) == 0: - logging.warning(f"Could not find tiles for {device} {rc} {name} {[t for t in get_tiles_by_rc(device, rc)]}") + tileless_rcs = set(["R37C52_H01E0100", "R73C160_JIVREFI4_IVREF_CORE"]) - if name == "R37C52_H01E0100" and len(tiles_at_rc) == 0: + if name in tileless_rcs and len(tiles_at_rc) == 0: # LIFCL-33 weirdness continue - assert len(tiles_at_rc) > 0 + + if len(tiles_at_rc) == 0: + logging.warning(f"Could not find tiles for {device} {rc} {name} {[t for t in get_tiles_by_rc(device, rc)]}") + continue if rc is None: continue @@ -281,13 +283,13 @@ def get_rc_from_name(device, name): return name if name[:6] in _get_rc_from_name_lookup: - return _get_rc_from_name_lookup[name[:6]] + return _get_rc_from_name_lookup[name[:7]] m = rc_regex.search(name) if m: rc = (int(m.group(1)), int(m.group(2))) if m.start() == 0: - _get_rc_from_name_lookup[name[:6]] = rc + _get_rc_from_name_lookup[name[:7]] = rc return rc m = edge_regex.match(name) @@ -808,7 +810,7 @@ def make_tile_unanon(self, anon_tile, rel_to): def get_related_tiles(self, anon_tile, rel_to): tiletype = anon_tile[0] - unique_prefixes = ["PCLK_DLY", "DDR_OSC", "IO_", "SYSIO_", "TMID_", "BMID_", "GPLL_"] + unique_prefixes = ["PCLK_DLY", "DDR_OSC", "IO_", "SYSIO_", "TMID_", "BMID_", "GPLL_", "DLY"] for unique_prefix in unique_prefixes: if tiletype.startswith(unique_prefix): diff --git a/util/fuzz/interconnect.py b/util/fuzz/interconnect.py index 1e02135..c96978b 100644 --- a/util/fuzz/interconnect.py +++ b/util/fuzz/interconnect.py @@ -501,7 +501,7 @@ def make_anon_pip(rc, p): chip = fuzzconfig.FuzzConfig.standard_chip(device) - tile_suffix = "" if overlay is None else ",overlay/" + overlay + tile_suffix = "" if overlay is None else ",overlays/" + overlay if len(modified_tiles_rcs_anon) == 0: if len(pips) > 0: