diff --git a/.gitignore b/.gitignore index 721db2b..9103205 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,14 @@ __pycache__ libprjoxide/target Cargo.lock .bitstreamcache/ +radiantc.tcl* +radiantc.log* +*~ +\#* +*.log.gz +.deltas +work/ +.\#* +*.fasm +.cache +fuzzers/*/*/db diff --git a/docs/bels/overview.md b/docs/bels/overview.md new file mode 100644 index 0000000..94451f8 --- /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 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. + +# References + +- [Terminology](https://fpga-interchange-schema.readthedocs.io/device_resources.html) + diff --git a/docs/database.md b/docs/database.md new file mode 100644 index 0000000..f4cd135 --- /dev/null +++ b/docs/database.md @@ -0,0 +1,64 @@ +# 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. + +### 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/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/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/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..efbc025 --- /dev/null +++ b/docs/general/structure.md @@ -0,0 +1,114 @@ +# 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. + +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. + +#### 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 + +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 + +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 +- 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 + 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/examples/common.mk b/examples/common.mk index 5bdfbc7..f31f45d 100644 --- a/examples/common.mk +++ b/examples/common.mk @@ -6,14 +6,15 @@ YOSYS?=yosys NEXTPNR?=nextpnr-nexus PRJOXIDE?=prjoxide ECPPROG?=ecpprog +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 new file mode 100644 index 0000000..2344768 --- /dev/null +++ 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/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..9e2ba59 --- /dev/null +++ b/examples/spinex_minimal/tinyclunx.pdc @@ -0,0 +1,2 @@ +ldc_set_location -site {A1} [get_ports clk] +ldc_set_location -site {B1} [get_ports reset] 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] diff --git a/fuzzers/000-build-pip-overlays/fuzzer.py b/fuzzers/000-build-pip-overlays/fuzzer.py new file mode 100644 index 0000000..6c9f72a --- /dev/null +++ b/fuzzers/000-build-pip-overlays/fuzzer.py @@ -0,0 +1,183 @@ +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 + +### 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): + 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 + +def make_overlay_name(k): + (anon_pips, *args) = k + return "-".join([*args, stablehash(anon_pips)]) + +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: + 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) + + def pip_is_tiletype_dependent(p): + # Currently we seperate everything out by tiletype; but this might be unnecessary in some cases. + return True + + 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] + + # TAP_CIB has conflicts amongst devices; so add device to the overlay key + 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) + + 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("overlays/" + 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 = { + "overlays": { + stablehash(k): sorted(k) for k in overlays_to_tiles + }, + "tiletypes": { + stablehash(k): sorted(v) for k,v in overlays_to_tiles.items() + } + } + + 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) + + async def interconnect_group(overlay_key, ts): + (anon_pips, *args) = overlay_key + overlay = make_overlay_name(overlay_key) + + 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)}") + + 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/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/001-plc-routing/fuzzer.py b/fuzzers/LIFCL/001-plc-routing/fuzzer.py deleted file mode 100644 index c71838e..0000000 --- a/fuzzers/LIFCL/001-plc-routing/fuzzer.py +++ /dev/null @@ -1,25 +0,0 @@ -from fuzzconfig import FuzzConfig -from interconnect import fuzz_interconnect -import re - -cfg = FuzzConfig(job="PLCROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["R16C22:PLC"]) - -def main(): - cfg.setup() - r = 16 - c = 22 - 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)] - 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"])) - -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 734d403..0000000 --- a/fuzzers/LIFCL/002-cib-routing/fuzzer.py +++ /dev/null @@ -1,45 +0,0 @@ -from fuzzconfig import FuzzConfig -from interconnect import fuzz_interconnect -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"])), -] - -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) - fuzz_interconnect(config=cfg, nodenames=extra_sources, regex=False, bidir=False, ignore_tiles=ignore, - pip_predicate=pip_filter, fc_filter=fc_filter) - -if __name__ == "__main__": - main() diff --git a/fuzzers/LIFCL/010-lut-init/fuzzer.py b/fuzzers/LIFCL/010-lut-init/fuzzer.py index 232d41d..6313eb0 100644 --- a/fuzzers/LIFCL/010-lut-init/fuzzer.py +++ b/fuzzers/LIFCL/010-lut-init/fuzzer.py @@ -1,9 +1,11 @@ +import asyncio + from fuzzconfig import FuzzConfig import nonrouting 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 = [] @@ -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/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 = "" diff --git a/fuzzers/LIFCL/016-site-mappings/fuzzer.py b/fuzzers/LIFCL/016-site-mappings/fuzzer.py new file mode 100644 index 0000000..16c90ef --- /dev/null +++ b/fuzzers/LIFCL/016-site-mappings/fuzzer.py @@ -0,0 +1,327 @@ +import asyncio +import logging +import re +import sys +import lapie + +import cachecontrol +import fuzzconfig +import fuzzloops +import interconnect +import libpyprjoxide +import nonrouting +import primitives +import radiant +import tiles +from fuzzconfig import FuzzConfig +from interconnect import fuzz_interconnect_sinks + +import database + +### +# 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() + +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)] + + [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 + +# 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 = 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] + 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 + +# 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, _ = 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)) + + 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"] + + 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/mode-{primitive.mode}/")) + logging.info(f"Getting relevant tiles for {device} {site}:{site_type} for {primitive.mode}") + + return 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) + +mux_re = re.compile("MUX[0-9]*$") + +# Use the primitive definitions to map out each mode's options. Works for IP and non IP settings +async 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 + + 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}"]) + + 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) + } + + 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"{site}.{setting.name}",# "name": f"{mode.mode}.{setting.name}", + "desc": setting.desc, + "executor": executor + } + + if isinstance(setting, primitives.EnumSetting): + 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 isinstance(setting, primitives.ProgrammablePin) and not is_ip_config: + args["include_zeros"] = True + + 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))])} + + await wrap_future(fuzz_word_setting(length=setting.bits, get_sv_substs=subs_fn, **args)) + else: + raise Exception(f"Unknown setting type: {setting}") + + await asyncio.gather(*[map_mode_setting(s) for s in mode.settings]) + + 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): + 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 [], [], [] + + 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"] + + tiletype = driving_tiles[0].split(":")[1] + logging.info(f"====== {site} : {tiletype} {driving_tiles} ==========") + + # Map primitive parameter settings + 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 = [] + 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)) + +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)) + return + + await asyncio.gather(*[ run_for_device(device, executor) for device in devices ]) + +if __name__ == "__main__": + fuzzloops.FuzzerAsyncMain(FuzzAsync) diff --git a/fuzzers/LIFCL/016-site-mappings/primitive.v b/fuzzers/LIFCL/016-site-mappings/primitive.v new file mode 100644 index 0000000..2241682 --- /dev/null +++ b/fuzzers/LIFCL/016-site-mappings/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/020-plc_tap/fuzzer.py b/fuzzers/LIFCL/020-plc_tap/fuzzer.py deleted file mode 100644 index ead72f2..0000000 --- a/fuzzers/LIFCL/020-plc_tap/fuzzer.py +++ /dev/null @@ -1,29 +0,0 @@ -from fuzzconfig import FuzzConfig -from interconnect import fuzz_interconnect -import re - -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"])), - - ([(11, 7), ], [(11, 13), ], FuzzConfig(job="TAPROUTE_1SL", device="LIFCL-17", sv="../shared/route_17.v", tiles=["TAP_PLC_1S_L_R11C14:TAP_PLC_1S_L"])), - ([(10, 7), ], [(10, 13), ], FuzzConfig(job="TAPROUTECIB_1SL", device="LIFCL-17", sv="../shared/route_17.v", tiles=["TAP_CIB_1S_L_R10C14:TAP_CIB_1S_L"])), - ([(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(): - for locs, rlocs, cfg in configs: - cfg.setup() - nodes = [] - for r, c in locs: - 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) - -if __name__ == "__main__": - main() 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..6c6549d 100644 --- a/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py +++ b/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py @@ -2,7 +2,38 @@ import nonrouting import fuzzloops import re +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" @@ -17,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" @@ -49,7 +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)) + + for dev in ["LIFCL-33", "LIFCL-33U"]: + + 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) + + 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 5e07c12..ad06eb0 100644 --- a/fuzzers/LIFCL/031-io_mode/fuzzer.py +++ b/fuzzers/LIFCL/031-io_mode/fuzzer.py @@ -1,90 +1,144 @@ -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 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] + if len(ts) == 0: + logging.warning(f"Could not find tile for {pad} for {device}") + return + + tiletype = ts[0].split(":")[1] -configs = [ - ("B", "E11", # PR3A, - 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(): + return ( + f"{tiletype}-{pio}", + (pio_names[pad["pio"]], pin, + FuzzConfig(job=f"IO{pin}_{device}_{tiletype}", device=device, + tiles=ts + all_sysio)) + ) + + +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() 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): + logging.info(f"Skipping {site} {cfg.tiles[:3]}; no SEIO33 tile") + return + primtype = "SEIO33_CORE" + + suffix = "" + def get_bank_vccio(iotype): if iotype == "": return "3.3" @@ -116,73 +170,110 @@ 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) + def fuzz_enum_setting(*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]) + + 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) + + 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 + + for config in filter(cfg_filter, configs): + per_config(config) + if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(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/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 440ea59..da1199c 100644 --- a/fuzzers/LIFCL/032-hsio_mode/fuzzer.py +++ b/fuzzers/LIFCL/032-hsio_mode/fuzzer.py @@ -1,24 +1,69 @@ -from fuzzconfig import FuzzConfig +import asyncio +import hashlib +import logging + +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] -] + 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] + + (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"{pin}/{ts[0]}/{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(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") ).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"])), +# ("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 +95,30 @@ ("HSTL15D_I", 1.5, None), ("HSUL12D", 1.2, None), ] -def main(): - def per_config(config): - pio, site, cfg = config + +device_empty_bitfile = {} + +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 \ + f"R{r}C{c}_JPADDO_DIFFIO18_CORE_IO{pio}" not in tiles.get_full_node_set(cfg.device): + print(f"Skipping {site}; it's an SEIO33 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 +170,97 @@ 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) + futures = [] + def fuzz_enum_setting(*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) + + 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) + + await asyncio.gather(*futures) + + 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 + + await asyncio.gather(*[per_config(config) for config in filter(cfg_filter, configs)]) + if __name__ == "__main__": - main() + fuzzloops.FuzzerAsyncMain(main) 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/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/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/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 c183360..37ea863 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() + fuzzloops.FuzzerMain(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/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 51a425d..d5c89f7 100644 --- a/fuzzers/LIFCL/071-iodelay/fuzzer.py +++ b/fuzzers/LIFCL/071-iodelay/fuzzer.py @@ -1,9 +1,27 @@ +import asyncio + from fuzzconfig import FuzzConfig import nonrouting 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"])), @@ -48,8 +66,8 @@ ] -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, {}) @@ -85,26 +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) - 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) + 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/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/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 0c3722a..69d2759 100644 --- a/fuzzers/LIFCL/091-osc/fuzzer.py +++ b/fuzzers/LIFCL/091-osc/fuzzer.py @@ -1,22 +1,24 @@ +import logging + from fuzzconfig import FuzzConfig 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="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(): +def main(executor): for cfg in cfgs: cfg.setup() empty = cfg.build_design(cfg.sv, {}) - if cfg.device == "LIFCL-17": - cfg.sv = "osc_17.v" - else: - cfg.sv = "osc.v" + cfg.sv = "osc.v" def bin_to_int(x): val = 0 @@ -27,6 +29,13 @@ def bin_to_int(x): mul *= 2 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): if kv is None: config = "" @@ -39,7 +48,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") @@ -49,6 +58,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="") @@ -70,15 +80,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 +106,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() + fuzzloops.FuzzerMain(main) 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/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/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 new file mode 100644 index 0000000..f78ef08 --- /dev/null +++ b/fuzzers/LIFCL/093-oscd/fuzzer.py @@ -0,0 +1,58 @@ +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__': + fuzzloops.FuzzerMain(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/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/100-ip-base/fuzzer.py b/fuzzers/LIFCL/100-ip-base/fuzzer.py index 2411af0..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 @@ -26,6 +28,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"), @@ -83,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: @@ -100,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/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..11fa246 100644 --- a/fuzzers/LIFCL/110-global-structure/fuzzer.py +++ b/fuzzers/LIFCL/110-global-structure/fuzzer.py @@ -1,18 +1,28 @@ 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 +import fuzzloops # 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 +36,20 @@ 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"] + + 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 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 = {} + 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) hpbx_c = None @@ -40,15 +59,14 @@ 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) + 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 @@ -69,17 +87,20 @@ 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() # 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 @@ -102,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 @@ -121,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 b2c997e..0c4dd52 100644 --- a/fuzzers/LIFCL/900-always-on/fuzzer.py +++ b/fuzzers/LIFCL/900-always-on/fuzzer.py @@ -8,13 +8,16 @@ 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(): - 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.bitstream) if __name__ == "__main__": - main() + fuzzloops.FuzzerMain(main) + 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/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.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/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/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/020-plc_tap/fuzzer.py b/fuzzers/LIFCL/unused/020-plc_tap/fuzzer.py new file mode 100644 index 0000000..5e83864 --- /dev/null +++ b/fuzzers/LIFCL/unused/020-plc_tap/fuzzer.py @@ -0,0 +1,76 @@ +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"])), + + ([(11, 7), ], [(11, 13), ], FuzzConfig(job="TAPROUTE_1SL", device="LIFCL-17", sv="../shared/route_17.v", tiles=["TAP_PLC_1S_L_R11C14:TAP_PLC_1S_L"])), + ([(10, 7), ], [(10, 13), ], FuzzConfig(job="TAPROUTECIB_1SL", device="LIFCL-17", sv="../shared/route_17.v", tiles=["TAP_CIB_1S_L_R10C14:TAP_CIB_1S_L"])), + ([(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"])), +] + +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 = [] + for r, c in locs: + 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)] + + 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__": + 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/unused/021-cmux/fuzzer.py similarity index 72% rename from fuzzers/LIFCL/021-cmux/fuzzer.py rename to fuzzers/LIFCL/unused/021-cmux/fuzzer.py index 9835539..661d848 100644 --- a/fuzzers/LIFCL/021-cmux/fuzzer.py +++ b/fuzzers/LIFCL/unused/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.*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) + +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/022-midmux/fuzzer.py b/fuzzers/LIFCL/unused/022-midmux/fuzzer.py similarity index 83% rename from fuzzers/LIFCL/022-midmux/fuzzer.py rename to fuzzers/LIFCL/unused/022-midmux/fuzzer.py index d9f2f21..e486feb 100644 --- a/fuzzers/LIFCL/022-midmux/fuzzer.py +++ b/fuzzers/LIFCL/unused/022-midmux/fuzzer.py @@ -1,3 +1,6 @@ +import asyncio + +import fuzzloops from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect @@ -17,15 +20,21 @@ 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(): +async def main(executor): for feed, rc, ndcc, side, cfg in configs: 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 = [] @@ -41,13 +50,14 @@ def main(): 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) + 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) + pip_predicate=pip_filter, executor=executor) if __name__ == "__main__": - main() + fuzzloops.FuzzerAsyncMain(main) + diff --git a/fuzzers/LIFCL/023-trunk-spine/fuzzer.py b/fuzzers/LIFCL/unused/023-trunk-spine/fuzzer.py similarity index 61% rename from fuzzers/LIFCL/023-trunk-spine/fuzzer.py rename to fuzzers/LIFCL/unused/023-trunk-spine/fuzzer.py index 53b1495..4a32533 100644 --- a/fuzzers/LIFCL/023-trunk-spine/fuzzer.py +++ b/fuzzers/LIFCL/unused/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/030-io_route/fuzzer.py b/fuzzers/LIFCL/unused/030-io_route/fuzzer.py similarity index 70% rename from fuzzers/LIFCL/030-io_route/fuzzer.py rename to fuzzers/LIFCL/unused/030-io_route/fuzzer.py index 8c378a4..1e01f37 100644 --- a/fuzzers/LIFCL/030-io_route/fuzzer.py +++ b/fuzzers/LIFCL/unused/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/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 66% rename from fuzzers/LIFCL/063-lram-routing/fuzzer.py rename to fuzzers/LIFCL/unused/063-lram-routing/fuzzer.py index 6c480aa..84ca3d7 100644 --- a/fuzzers/LIFCL/063-lram-routing/fuzzer.py +++ b/fuzzers/LIFCL/unused/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/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/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/generate_database.sh b/generate_database.sh new file mode 100755 index 0000000..e73a728 --- /dev/null +++ b/generate_database.sh @@ -0,0 +1,70 @@ +#!/bin/bash -i + +set -o allexport + +. user_environment.sh + +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 ;; + m) merge=true ;; + g) git_commit=true ;; + *) print_usage + exit 1 ;; + esac +done + +pushd tools +#python3 tilegrid_all.py +popd + +PRJOXIDE_ROOT=`pwd` + +run_fuzzer() { + dir="$1" + if [ -f "$dir/fuzzer.py" ]; then + set -ex + + echo "=================== Entering $dir ===================" + pushd "$dir" > /dev/null || return + + 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 + +pushd fuzzers +find . -mindepth 1 -maxdepth 2 -type d | sort | xargs -I {} bash -c 'run_fuzzer "{}"' diff --git a/libprjoxide/prjoxide/Cargo.toml b/libprjoxide/prjoxide/Cargo.toml index 95b52aa..1486ec9 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,8 @@ 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" } +env_logger = "0.11.8" [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..7fe8c80 100644 --- a/libprjoxide/prjoxide/src/bba/tileloc.rs +++ b/libprjoxide/prjoxide/src/bba/tileloc.rs @@ -12,7 +12,9 @@ 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; lazy_static! { static ref LUT_INPUT_RE: Regex = Regex::new(r"^J([ABCD])([01])_SLICE([ABCD])$").unwrap(); @@ -38,7 +40,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 +83,8 @@ pub struct LocationGrid { pub height: usize, tiles: Vec, glb: DeviceGlobalsData, - iodb: DeviceIOData + iodb: DeviceIOData, + device: String } impl LocationGrid { @@ -94,6 +103,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 +147,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 +187,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) => { + warn!("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 +371,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 dc627c7..203c0af 100644 --- a/libprjoxide/prjoxide/src/bels.rs +++ b/libprjoxide/prjoxide/src/bels.rs @@ -1,7 +1,9 @@ -use std::collections::BTreeSet; use crate::chip::*; 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)] @@ -445,6 +447,7 @@ impl Bel { } } + pub fn make_diffio18() -> Bel { let postfix = format!("DIFFIO18_CORE_IOA"); Bel { @@ -658,6 +661,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 { @@ -681,17 +692,18 @@ 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)], + // "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_0" => vec![ Bel::make_config(&tiledata, "CONFIG_MULTIBOOT_CORE", "_CONFIG_MULTIBOOT_CORE_CONFIG_MULTIBOOT", -2, 0, 0), Bel::make_config(&tiledata, "CONFIG_HSE_CORE", "_CONFIG_HSE_CORE_CONFIG_HSE", -2, 0, 1), @@ -706,7 +718,7 @@ pub fn get_tile_bels(tiletype: &str, tiledata: &TileBitsDatabase) -> Vec { Bel::make_config(&tiledata, "CONFIG_CLKRST_CORE", "_CONFIG_CLKRST_CORE_CONFIG_CLKRST", -14, 0, 3), ], - "EFB_1_OSC" | "OSC_15K" => vec![Bel::make_osc_core()], + "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)], @@ -763,7 +775,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 @@ -782,37 +794,75 @@ pub fn get_tile_bels(tiletype: &str, tiledata: &TileBitsDatabase) -> Vec { "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![], + "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((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) + }); + 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<(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() { - 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); + 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() { + return tile.name.to_string(); + } + 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.0, rel_offset.1)]; + } + let tt = &tile.tiletype[..]; match &bel.beltype[..] { "SEIO33_CORE" | "SIOLOGIC" => match tt { @@ -831,7 +881,7 @@ pub fn get_bel_tiles(chip: &Chip, tile: &Tile, bel: &Bel) -> Vec { 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 8eca597..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); @@ -211,6 +211,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 008f185..4b4d6be 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::{debug, warn}; 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; @@ -82,12 +88,19 @@ 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(); - // 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| format!("failed to read file {filename}: {x:?}"))?; + let mut parser = BitstreamParser::new(&buffer); let mut c = parser.parse(db)?; c.cram_to_tiles(); @@ -432,9 +445,13 @@ impl BitstreamParser { let mut curr_meta = String::new(); while !self.done() { if self.check_preamble(&PREAMBLE) { - println!("bitstream start at {}", self.index); + debug!("bitstream start at {}", self.index); return Ok(BitstreamType::NORMAL); } + if self.check_preamble(&PREAMBLE_IP_EVAL) { + debug!("bitstream (ip eval) start at {}", self.index); + return Ok(BitstreamType::NORMAL); + } if !in_metadata && self.check_preamble(&COMMENT_START) { in_metadata = true; continue; @@ -442,6 +459,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() { + debug!("Metadata: {}", &curr_meta); + } else { + warn!("Warning: Metadata of len {} contains non ascii data", curr_meta.len()); + } curr_meta.clear(); } in_metadata = false; @@ -458,7 +480,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() { + debug!("Metadata: {}", &curr_meta); + } else { + warn!("Warning: Metadata of len {} contains non ascii data", curr_meta.len()); + } } self.metadata.push(curr_meta.to_string()); curr_meta.clear(); @@ -481,14 +507,14 @@ impl BitstreamParser { let cmd = self.get_opcode_byte(); match cmd { LSC_RESET_CRC => { - println!("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(); - println!("set CTRL0 to 0x{:08X}", ctrl0); + debug!("set CTRL0 to 0x{:08X}", ctrl0); } VERIFY_ID => { self.skip_bytes(3); @@ -496,22 +522,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); + debug!("check IDCODE is 0x{:08X}", idcode); } LSC_INIT_ADDRESS => { self.skip_bytes(3); - println!("reset frame address"); + debug!("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); + debug!("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!)"); + debug!("LSC_AUTH_CTRL (bitstream is probably signed!)"); } LSC_PROG_INCR_RTI => { let cfg = self.get_byte(); @@ -529,24 +555,28 @@ impl BitstreamParser { return Err("got bitstream before idcode"); } } - println!("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); + 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 { + debug!("Decoded frame {} exceeds frame size {}", decoded_frame, chip.cram.frames); } if self.verbose { - println!("F0x{:08x}B{:04}", curr_frame, j); + debug!("F0x{:08x}B{:04}", curr_frame, j); } self.update_ecc(true); } else { @@ -557,13 +587,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); + debug!("F0x{:08x}P{:014b}E{:014b}", curr_frame, parity, exp_parity); } self.check_crc16(); let d = self.get_byte(); @@ -574,13 +603,13 @@ impl BitstreamParser { LSC_POWER_CTRL => { self.skip_bytes(2); let pwr = self.get_byte(); - println!("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(); - println!("set usercode to 0x{:08X}", usercode); + debug!("set usercode to 0x{:08X}", usercode); if cmp_crc { self.check_crc16(); } @@ -607,12 +636,21 @@ impl BitstreamParser { } ISC_PROGRAM_DONE => { self.skip_bytes(3); - println!("done"); + debug!("done"); } + LSC_READ_DR_UES => { + self.skip_bytes(3); + debug!("read DR_UES"); + } + LSC_READ_CNTRL0 => { + self.skip_bytes(3); + debug!("read CNTRL0"); + } DUMMY => {} _ => { - println!("unknown command 0x{:02X} at {}", cmd, self.index); - return Err("unknown bitstream command"); + 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 cc0a314..f23295a 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,10 @@ 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); } } chip.tiles_to_cram(); @@ -236,6 +235,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 +326,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 +366,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 +375,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); } @@ -384,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 @@ -400,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 @@ -427,6 +474,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..bedb9d5 100644 --- a/libprjoxide/prjoxide/src/database.rs +++ b/libprjoxide/prjoxide/src/database.rs @@ -1,12 +1,31 @@ +use itertools::Itertools; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, BTreeSet, HashMap}; -use std::fmt; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::{env, fmt, fs}; + 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 { + // Expands to either `$crate::panic::panic_2015` or `$crate::panic::panic_2021` + // depending on the edition of the caller. + ($($arg:tt)*) => { + /* compiler built-in */ + + warn!($($arg)*); + if env::var("PRJOXIDE_ALLOW_BIT_CHANGE").is_ok() { + + } else { + return Err(format!($($arg)*)); + } + }; +} + #[derive(Deserialize)] pub struct DevicesDatabase { pub families: BTreeMap, @@ -35,6 +54,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 +64,13 @@ pub struct DeviceTilegrid { pub tiles: BTreeMap, } -#[derive(Deserialize)] +#[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, pub x: u32, @@ -117,21 +143,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) } @@ -241,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, @@ -267,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)] @@ -284,6 +310,12 @@ pub struct TileBitsDatabase { #[serde(default)] #[serde(skip_serializing_if = "BTreeSet::is_empty")] pub always_on: BTreeSet, + #[serde(default)] + + // 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<(i32, i32)>, } impl TileBitsDatabase { @@ -314,39 +346,114 @@ 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) { + self.db.conns.iter_mut().for_each(|(_,conn)| conn.sort()); + self.db.pips.iter_mut().for_each(|(_,pip)| pip.sort()); + } + pub fn new(tiletype: &str, db: TileBitsDatabase) -> TileBitsData { TileBitsData { tiletype: tiletype.to_string(), db: db.clone(), dirty: false, + new_pips: 0, + new_enums: 0, + new_words : 0 } } - pub fn add_pip(&mut self, from: &str, to: &str, bits: BTreeSet) { + + 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())?; + }; + 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 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> { + debug!("Merging {}", self.tiletype); + 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 { + self.add_conn(&*to, &*from.from_wire); + } + } + } + self.dirty = true; + + 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) -> Result<(), String> { if !self.db.pips.contains_key(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(); - for ad in ac.iter() { + for ad in ac.iter_mut() { if ad.from_wire == from { if bits != ad.bits { - panic!( - "Bit conflict for {}.{}<-{} existing: {:?} new: {:?}", + emit_bit_change_error!( + "Bit conflict for {}. {}<-{} existing: {:?} new: {:?}", self.tiletype, from, to, ad.bits, bits ); + + ad.bits = bits; + self.dirty = true; + self.new_pips += 1; + + return Ok(()); } - return; + + debug!("Pip {from} -> {to} already exists for {}", self.tiletype); + return Ok(()); } } self.dirty = true; + self.new_pips += 1; + + 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 => { @@ -357,13 +464,15 @@ impl TileBitsData { bits: bits.clone(), }, ); + + self.new_words += 1; } Some(word) => { if !desc.is_empty() && desc != &word.desc { 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 +482,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 ); @@ -381,14 +490,34 @@ impl TileBitsData { } } } + + Ok(()) + } + + 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!( + "Bel offset conflict for {}. existing: {:?} new: {:?}", + self.tiletype, self.db.tile_configures_external_tiles, bel_relative_location + ); + } + info!("Setting bel offset {} {:?}", self.tiletype, bel_relative_location); + + bel_relative_location.iter().for_each( + |loc| { self.db.tile_configures_external_tiles.insert(loc.clone()); } + ); + self.dirty = true; + + Ok(()) } pub fn add_enum_option( &mut self, name: &str, option: &str, desc: &str, - bits: BTreeSet, - ) { + bits: BTreeSet + ) -> Result<(), String> { if !self.db.enums.contains_key(name) { self.db.enums.insert( name.to_string(), @@ -401,22 +530,30 @@ 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(option) { + match ec.options.get_mut(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 ); + + 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; } } + + Ok(()) } pub fn add_conn(&mut self, from: &str, to: &str) { if !self.db.conns.contains_key(to) { @@ -425,7 +562,9 @@ impl TileBitsData { let pc = self.db.conns.get_mut(to).unwrap(); if pc.iter().any(|fc| fc.from_wire == from) { // Connection already exists + debug!("Connection {from} -> {to} already exists {}", self.tiletype); } else { + info!("Connection {from} -> {to} added {}", self.tiletype); self.dirty = true; pc.push(FixedConnectionData { from_wire: from.to_string(), @@ -441,32 +580,60 @@ 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 { 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) .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(), @@ -475,14 +642,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(), @@ -491,6 +672,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 @@ -540,12 +724,125 @@ 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() + } + 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()); 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() @@ -600,27 +897,100 @@ 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(), + 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("overlays"), y).cmp(&(x.starts_with("overlays"), x)) + }).collect(); + + for layer in overlay_members { + info!("Merging {layer} into {tiletype}"); + let overlay_bits = self.tile_bitdb(family, layer.as_str()); + 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(), + 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("overlays/"); + let filename = if is_overlay { + format!("{}/overlays/{}.ron", family, tiletype.replace("overlays/", "")) + } 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 @@ -639,6 +1009,7 @@ impl Database { enums: BTreeMap::new(), conns: BTreeMap::new(), always_on: BTreeSet::new(), + tile_configures_external_tiles : BTreeSet::new(), } }; self.ipbits @@ -646,8 +1017,23 @@ 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) { + 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; @@ -661,14 +1047,33 @@ impl Database { enumerate_arrays: false, separate_tuple_members: false, }; + + tilebits.sort(); + let is_overlay = tiletype.starts_with("overlays/"); + + let (dir_name, file_name) = if is_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(); - File::create(format!( - "{}/{}/tiletypes/{}.ron", - self.root.as_ref().unwrap(), family, tiletype - )) - .unwrap() + + 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 + ); + 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() { @@ -681,6 +1086,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(), @@ -695,5 +1102,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/prjoxide/src/fuzz.rs b/libprjoxide/prjoxide/src/fuzz.rs index 7d76cbb..9e1997f 100644 --- a/libprjoxide/prjoxide/src/fuzz.rs +++ b/libprjoxide/prjoxide/src/fuzz.rs @@ -5,6 +5,14 @@ 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::{debug, info, trace, warn}; + +#[derive(Clone, Debug)] pub enum FuzzMode { Pip { to_wire: String, @@ -22,12 +30,14 @@ 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 + overlay: String + } } -#[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 +101,17 @@ impl Fuzzer { desc: &str, include_zeros: bool, assume_zero_base: bool, + mark_relative_to: Option, + overlay: &str ) -> 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, + overlay: overlay.to_string(), }, tiles: fuzz_tiles.clone(), base: base_bit.clone(), @@ -105,9 +119,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 @@ -126,11 +138,38 @@ 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, FuzzKey::PipKey { from_wire: from_wire.to_string(), + allow_partial_deltas : false + }, + 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, + FuzzKey::PipKey { + from_wire: from_wire.to_string(), + allow_partial_deltas : true }, bitfile, ); @@ -138,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, @@ -147,126 +199,172 @@ 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 { + debug!("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, + ).unwrap(); + findings += 1; + } + } + } + } + + 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(), @@ -287,7 +385,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 { @@ -295,8 +393,11 @@ impl Fuzzer { include_zeros, disambiguate: _, assume_zero_base, + mark_relative_to, + overlay } => { if self.deltas.len() < 2 { + warn!("Need at least two deltas got {} for fuzzmode {name}", self.deltas.len()); return; } for tile in changed_tiles { @@ -326,7 +427,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 +437,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 +454,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 +476,31 @@ impl Fuzzer { }; // Add the enum to the tile data let tile_data = self.base.tile_by_name(&tile).unwrap(); + + 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); - tile_db.add_enum_option(name, &option, &self.desc, b); + 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.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.clone())).unwrap(); + }; + } } } @@ -405,7 +528,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(); } } } @@ -414,7 +537,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(); } } } @@ -422,7 +545,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 d9cac04..c3fbe84 100644 --- a/libprjoxide/prjoxide/src/ipfuzz.rs +++ b/libprjoxide/prjoxide/src/ipfuzz.rs @@ -4,12 +4,18 @@ 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 log::error; +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 }, @@ -68,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); @@ -132,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(); } } } @@ -172,9 +183,25 @@ 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(); } + + 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/prjoxide/src/wires.rs b/libprjoxide/prjoxide/src/wires.rs index 4941938..ac703e7 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); } @@ -265,20 +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("H") { - 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); - } + + 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" | "LFCPNX-100" => + if wx < tx { + Some("L") + } else if wx > tx { + Some("R") + } else { + None + } + + // 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/Cargo.toml b/libprjoxide/pyprjoxide/Cargo.toml index 55fb0ee..e395c73 100644 --- a/libprjoxide/pyprjoxide/Cargo.toml +++ b/libprjoxide/pyprjoxide/Cargo.toml @@ -5,11 +5,17 @@ edition = "2018" [dependencies] prjoxide = { path = "../prjoxide" } +log = "0.4.29" +env_logger = "0.11.8" +pyo3-log = "0.13.2" [dependencies.pyo3] 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..8dcd787 100644 --- a/libprjoxide/pyprjoxide/src/lib.rs +++ b/libprjoxide/pyprjoxide/src/lib.rs @@ -1,33 +1,98 @@ -use pyo3::prelude::*; -use pyo3::types::{PyList, PySet}; -use pyo3::wrap_pyfunction; - -use std::fs::File; -use std::io::*; - use prjoxide::bitstream; use prjoxide::chip; use prjoxide::database; +use prjoxide::database::ConfigBit; use prjoxide::database_html; 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; +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::*; +use prjoxide::chip::ChipDelta; #[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 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(); + } + 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)) } } } @@ -35,6 +100,7 @@ impl Database { #[pyclass] struct Fuzzer { fz: fuzz::Fuzzer, + name: String, } #[pymethods] @@ -64,6 +130,7 @@ impl Fuzzer { width, zero_bitfile, ), + name: name.to_string() } } @@ -77,26 +144,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, - ), - } + 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] @@ -108,6 +182,8 @@ impl Fuzzer { desc: &str, include_zeros: bool, assume_zero_base: bool, + mark_relative_to: Option, + overlay: &str ) -> Fuzzer { let base_chip = bitstream::BitstreamParser::parse_file(&mut db.db, base_bitfile).unwrap(); @@ -122,30 +198,62 @@ impl Fuzzer { desc, include_zeros, assume_zero_base, + mark_relative_to, + overlay ), + name: name.to_string() } } 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); + } + + 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); } + fn add_enum_delta(&mut self, option: &str, delta: ChipDelta) { + self.fz.add_enum_delta(option, delta); + } - 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) { + self.fz.serialize_deltas(filename); + } + + fn get_name(&self) -> String { + self.name.clone() } } #[pyclass] struct IPFuzzer { fz: ipfuzz::IPFuzzer, + name: String } #[pymethods] @@ -174,6 +282,7 @@ impl IPFuzzer { width, inverted_mode, ), + name: name.to_string() } } @@ -196,6 +305,7 @@ impl IPFuzzer { name, desc, ), + name: name.to_string() } } @@ -214,6 +324,14 @@ 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); + } + + fn get_name(&self) -> String { + self.name.clone() + } } #[pyfunction] @@ -253,16 +371,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 { @@ -272,6 +394,19 @@ impl Chip { fn get_ip_values(&mut self) -> Vec<(u32, u8)> { self.c.ipconfig.iter().map(|(a, d)| (*a, *d)).collect() } + + 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)) + }) + } } #[pyfunction] @@ -347,6 +482,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<()> { + 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/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 diff --git a/radiant.sh b/radiant.sh index c1ccf48..7b1582f 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 @@ -27,6 +27,19 @@ 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:-8_High-Performance_1.0V}" + ;; + LIFCL-33U) + 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) PACKAGE="${DEV_PACKAGE:-CABGA400}" DEVICE="LIFCL-40" @@ -67,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" \ @@ -82,19 +96,30 @@ 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 - "$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="" @@ -104,7 +129,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/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 new file mode 100644 index 0000000..a2b6349 --- /dev/null +++ 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 7dd7a88..e83a5ac 100755 --- a/tools/bitstreamcache.py +++ b/tools/bitstreamcache.py @@ -20,68 +20,152 @@ gzip and gunzip must be on your path for it to work """ - +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): +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 + 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')) - for fname in input_files: - ext = os.path.splitext(fname)[1] + hasher.update(env[envkey].encode('utf-8')) + 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() - -if len(sys.argv) < 2: - print("Expected command (init|fetch|commit)") - sys.exit(1) -cmd = sys.argv[1] -if cmd == "init": + hasher.update(contents) + + # 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): - os.mkdir(cache_dir) -if cmd == "fetch": + return + + 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))] + + 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 + + input_file_contents = { + fname:open(fname, "rb").read() + for fname in input_files + } + + return fetch_by_contents(device, input_file_contents, env) + +def main(): + if len(sys.argv) < 2: + print("Expected command (init|fetch|commit)") 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) - cache_entry = os.path.join(cache_dir, h) - if not os.path.exists(cache_entry) or len(os.listdir(cache_entry)) == 0: - sys.exit(1) - 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()) - sys.exit(0) -if cmd == "commit": - if not os.path.exists(cache_dir): + 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: + 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") - 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, version_directory, *h) + if not os.path.exists(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") + + 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/extract_tilegrid.py b/tools/extract_tilegrid.py index ae7208c..d1510b7 100644 --- a/tools/extract_tilegrid.py +++ b/tools/extract_tilegrid.py @@ -67,7 +67,12 @@ def get_tf2c(dev): 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,27 @@ def main(argv): tiles = {} current_tile = None tap_frame_to_col = get_tf2c(args.device) + + 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_", "CIB_LR"] + 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)) 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 +119,8 @@ 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/tools/merge-databases.py b/tools/merge-databases.py new file mode 100755 index 0000000..8f08e1b --- /dev/null +++ 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_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/tools/parse_webdoc.py b/tools/parse_webdoc.py new file mode 100644 index 0000000..4581f24 --- /dev/null +++ b/tools/parse_webdoc.py @@ -0,0 +1,106 @@ +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) == "": + 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, out_dir = "./primitives"): + 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 = 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) + + 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/tools/reformat_database.py b/tools/reformat_database.py new file mode 100644 index 0000000..1b74799 --- /dev/null +++ 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() + diff --git a/util/common/cachecontrol.py b/util/common/cachecontrol.py new file mode 100644 index 0000000..f5c1425 --- /dev/null +++ b/util/common/cachecontrol.py @@ -0,0 +1,31 @@ +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(hashfunc = None): + RADIANT_DIR = os.environ.get("RADIANTDIR") + + def default_hashfunc(args, kwds): + kwds["RADIANT_DIR"] = RADIANT_DIR + kwds["RADIANT_VERSION"] = radiant_version + + 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=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 749ab76..58e5fe0 100644 --- a/util/common/database.py +++ b/util/common/database.py @@ -1,17 +1,70 @@ """ Database and Database Path Management """ +import logging 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 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 + # 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_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(): """ Return the path containing the Project Oxide database @@ -19,11 +72,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 @@ -31,6 +84,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 = get_family_for_device(device) + dparts = [family, device, package] for dpart in dparts: if dpart is None: @@ -40,16 +96,53 @@ def get_db_subdir(family = None, device = None, package = None): os.mkdir(subdir) return subdir +def get_base_addrs(family, device = None): + if device is None: + device = family + family = get_family_for_device(device) + + 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 {} -def get_tilegrid(family, device): +@cache +def get_tilegrid(family, device = None): """ Return the deserialised tilegrid for a family, device """ + if device is None: + device = family + family = get_family_for_device(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): + """ + Return the deserialised iodb for a family, device + """ + if device is None: + device = family + 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) - +@cache def get_devices(): """ Return the deserialised content of devices.json @@ -58,6 +151,91 @@ def get_devices(): with open(djson, "r") as f: return json.load(f) +def get_tiletypes(family): + family = get_family_for_device(family) + 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())) + +@cache +def get_sites(family, device = None): + import lapie + + if device is None: + device = family + family = get_family_for_device(family) + + return lapie.get_sites_with_pin(device) + +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 76b8db2..301634e 100644 --- a/util/common/lapie.py +++ b/util/common/lapie.py @@ -1,14 +1,50 @@ """ Python wrapper for `lapie` """ -from os import path +import asyncio +import hashlib +import itertools +import logging import os +import re +import shutil +import sqlite3 import subprocess -import database import tempfile -import re +import time +from collections import defaultdict +from functools import cache +from os import path + +import cachier +import fuzzconfig + +import cachecontrol +import database + +radiant_version = database.get_radiant_version() + +get_nodes = "dev_get_nodes" +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" + 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: @@ -18,11 +54,14 @@ 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 - # meh, fails sometimes - # assert result == 0, "lapie returned non-zero status code {}".format(result) - outfile = path.join(workdir, 'lapie.log') + 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) + + result = result_struct.returncode + + outfile = path.join(workdir, tcltool_log) output = "" with open(outfile, 'r') as f: for line in f: @@ -37,8 +76,19 @@ 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) +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: def __init__(self, from_wire, to_wire, is_bidi = False, flags = 0, buffertype = ""): @@ -47,6 +97,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)) class PinInfo: def __init__(self, site, pin, wire, pindir): @@ -58,29 +111,68 @@ 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 = [] - -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_]+)\)') + + 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_]+)\)') 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, node_keys): 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() - nm = node_re.match(sl) - if nm: - curr_node = NodeInfo(nm.group(2)) - nodes.append(curr_node) + + 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) + + 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 + 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}") if pm.group(2) == "<--": curr_node.uphill_pips.append( PipInfo(pm.group(3), pm.group(1), False, flg, btyp) @@ -100,25 +192,280 @@ 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 +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 + +@cache +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 {res for line in nf.read().split("\n") + if len(res:=line.split(":")[-1].strip()) != 0 } + +@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_jump_wires(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 + +@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): + 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 -def get_node_data(udb, nodes, regex=False): workdir = tempfile.mkdtemp() nodefile = path.join(workdir, "nodes.txt") - nodelist = "" - if len(nodes) == 1: - nodelist = nodes[0] - elif len(nodes) > 1: - nodelist = "[list {}]".format(" ".join(nodes)) - run_with_udb(udb, ['dev_report_node -file {} [get_nodes {}{}]'. - format(nodefile, "-re " if regex else "", nodelist)]) + nodelist = "[list {}]".format(" ".join(nodes)) + + logging.info(f"Querying for {len(nodes)} nodes {nodes[:10]}") + + 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()) + return parse_node_report(nf.read(), nodes) + +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(device, nodes, regex=False, executor = None, filter_by_name=True, skip_missing = False, skip_pips=False): + 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(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(device) + nodes = sorted(set(nodes) & all_nodes) + + if len(nodes) == 0: + return [] + + 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") + missing = sorted({k for k in nodes if k not in nis}) + futures = [] + + if not skip_missing and len(missing): + cnt = 5000 + 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(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(device) + 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 + + 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: + 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]}" + rpt = run_with_udb(udb, [f'dev_list_site {rc_slug}'], stdout = subprocess.DEVNULL) + + 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+' + 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 + +@cachecontrol.cache_fn() +def get_sites_with_pin(device): + from nodes_database import NodesDatabase + + node_db = NodesDatabase.get(device) + + sites = node_db.get_sites() + + 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): # des_list_net no longer works? diff --git a/util/common/nodes_database.py b/util/common/nodes_database.py new file mode 100644 index 0000000..895882f --- /dev/null +++ b/util/common/nodes_database.py @@ -0,0 +1,536 @@ +import logging +import sqlite3 +import threading +import time +from collections import defaultdict +from threading import RLock + +_thread_local = threading.local() + +class NodesDatabase: + _lock = RLock() + _write_locks = {} + _last_checkpoint = {} + current_version = 3 + + @staticmethod + def get(device): + with NodesDatabase._lock: + 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} thread: {threading.get_ident()}") + + self.device = device + self.conn = sqlite3.connect(self.db_path) + self.init_db() + + def init_db(self): + conn = self.conn + cur = conn.cursor() + cur.execute("PRAGMA user_version;") + + version = cur.fetchone() + + with conn: + cur.execute(""" + CREATE TEMP TABLE IF NOT EXISTS tmp_node_ids ( + id INTEGER PRIMARY KEY + ); + """) + cur.execute(""" + CREATE + TEMP TABLE IF NOT EXISTS tmp_node_names ( + name TEXT 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 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)) + ); + """) + + 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") + + 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_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() + + 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, skip_pips=False): + from lapie import NodeInfo, PipInfo + + conn = self.conn + cur = conn.cursor() + cur.arraysize = 100000 + + 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} + if skip_pips: + return result + + for k,v in result.items(): + v.aliases.append(k) + + self._populate_tmp(cur, "id", list(id_to_name.keys())) + + 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) + """) + + 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.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() + + # 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) + """ + ) + + 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 + ) + + 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: + 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 42a5e82..24dd8cf 100644 --- a/util/common/radiant.py +++ b/util/common/radiant.py @@ -1,10 +1,73 @@ """ Python wrapper for `radiant.sh` """ +import asyncio +import logging +import time from os import path import os import subprocess 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) + + subprocess_args = { + "args": ["bash", *args], + "env": env, + "cwd": cwd, + "stdout": stdout, + "stderr": stderr + } + + 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 RadiantRunError(f"Error encountered running radiant: {slug} {returncode} cwd: {cwd}", error_lines) + + # 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): @@ -18,5 +81,46 @@ 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) + 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 2d8890a..adb9a14 100644 --- a/util/common/tiles.py +++ b/util/common/tiles.py @@ -1,4 +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+)') @@ -11,9 +28,838 @@ 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"][database.get_family_for_device(device)]["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_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'] + + 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)} + +@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)]) + +@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]*") + + 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_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) + + tileless_rcs = set(["R37C52_H01E0100", "R73C160_JIVREFI4_IVREF_CORE"]) + + if name in tileless_rcs and len(tiles_at_rc) == 0: + # LIFCL-33 weirdness + continue + + + 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 + + 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: + 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).get(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(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, ninfo in node_info.items(): + tile_nodes[nodes2tile[n]][n.name] = ninfo + + return tile_nodes + else: + 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)} + +_get_tiles_by_rc = {} +def get_tiles_by_rc(device, rc = None): + if isinstance(rc, str): + rc = get_rc_from_name(device, 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] + + + +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]+)") +_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[: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[:7]] = rc + return rc + + 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): + 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(r) + ])) + +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(device, rcs): + devices = database.get_devices() + device_info = devices["families"][database.get_family_for_device(device)]["devices"][device] + + max_row = device_info["max_row"] + max_col = device_info["max_col"] + + rcs = set(rcs) + 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): + 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) + graph.update({n.name:n for n in new_nodes}) + + 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)] + + 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 + +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 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: + if union: + node_set = 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_jump_wires(device) + + connections = defaultdict(set) + for frm, to in arcs: + connections[frm].add(to) + + 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 + +_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"][database.get_family_for_device(device)]["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_", "DLY"] + 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 new file mode 100644 index 0000000..d1917c9 --- /dev/null +++ b/util/fuzz/DesignFileBuilder.py @@ -0,0 +1,308 @@ +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_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) + 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 f294ba5..0e0769c 100644 --- a/util/fuzz/fuzzconfig.py +++ b/util/fuzz/fuzzconfig.py @@ -1,17 +1,130 @@ """ 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 import radiant import database import libpyprjoxide +import cachecontrol + +#db = None + +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) + +_platform_skip_warnings = set() +def should_fuzz_platform(device): + if PLATFORM_FILTER is not None and PLATFORM_FILTER not in device: + if device not in _platform_skip_warnings: + logging.warning(f"FUZZER_PLATFORM set to {PLATFORM_FILTER}, skipping {device}") + _platform_skip_warnings.add(device) + return False + return True + +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() + + 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 + assert isinstance(bitstream_file, str) + self.bitstream = bitstream_file + self.vfiles = vfiles + + def __str__(self): + return f"BitstreamInfo: {self.bitstream}" -db = None class FuzzConfig: - def __init__(self, device, job, tiles, sv): + _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 @@ -21,34 +134,103 @@ def __init__(self, device, job, tiles, sv): self.device = device self.job = job self.tiles = tiles + if sv is None: + 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 - 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 + @staticmethod + @cache + 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] + + @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.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""" 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 = ""): + 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"{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"{self.delta_dir()}/{self.job}/{name}.ron miss") + return False + + def solve(self, fz, db): + try: + fz.solve(db) + self.serialize_deltas(fz, fz.get_name()) + except: + self.serialize_deltas(fz, f"{fz.get_name()}-FAILED") + raise + 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 """ # 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: self.build_design(self.sv, {}) - def build_design(self, des_template, substitutions, prefix="", substitute=True): + def subst_defaults(self): + packages = { + "LIFCL-33": "WLCSP84", + "LIFCL-33U": "WLCSP84", + "LFCPNX-40": "LFG672", + "LFCPNX-100": "LFG672" + } + + return { + "arch": database.get_family_for_device(self.device), + "arcs_attr": "", + "device": self.device, + "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}" + future.executor = executor + 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. @@ -60,30 +242,88 @@ 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 + + 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") + logging.debug(f"Building {des_template} with subs {substitutions} into {desfile}") if "sysconfig" in subst: pdcfile = path.join(self.workdir, prefix + "design.pdc") with open(pdcfile, "w") as pdcf: pdcf.write("ldc_set_sysconfig {{{}}}\n".format(subst["sysconfig"])) - if path.exists(bitfile): - os.remove(bitfile) - 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) - if self.struct_mode and self.udb_specimen is None: - self.udb_specimen = path.join(self.workdir, prefix + "design.tmp", "par.udb") - return bitfile + for bf in [bitfile, bitfile_gz]: + if path.exists(bf): + os.remove(bf) + + 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: + 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: + rtn = BitstreamInfo(self, foundFile, desfile) + if executor is not None: + f = Future() + f.set_result(rtn) + return f + return rtn + + 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} (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") + if path.exists(bitfile): + return BitstreamInfo(self, bitfile, desfile) + if path.exists(bitfile_gz): + return BitstreamInfo(self, bitfile_gz, desfile) + + raise Exception(f"Could not generate bitstream file {bitfile} {bitfile_gz}") + + if executor is None: + return run_radiant_sh() + + return executor.submit(run_radiant_sh) @property @@ -91,5 +331,7 @@ 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..08f897d 100644 --- a/util/fuzz/fuzzloops.py +++ b/util/fuzz/fuzzloops.py @@ -1,34 +1,389 @@ """ General Utilities for Fuzzing """ - +import asyncio +import logging import os +import signal +import threading +import time +import traceback +from asyncio import CancelledError +from collections import defaultdict +from concurrent.futures import ThreadPoolExecutor, Future +from contextlib import contextmanager +from signal import SIGINT, SIGTERM from threading import Thread, RLock -def parallel_foreach(items, func): +import lapie + +async def wrap_future(f): + if f is not None: + await asyncio.wrap_future(f) + +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) + +error_count = 0 +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 + + global is_in_loop + is_in_loop = True 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) + + func(item) + except Exception as e: + global error_count + if error_count < 10: + error_count = error_count + 1 + logging.error(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 + 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 and hasattr(executor, "register_future"): + executor.register_future(out) + + if name is not None: + out.name = name + + if n == 0: + out.set_result([]) + + return out + +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) + + fut = Future() + + def _done(f): + r = None + try: + r = f.result() + if hasattr(f, 'executor'): + new_f = f.executor.submit(func, r, *args, **kwargs) + 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: + 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, 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 + + 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 shutdown(self): + self.executor.shutdown(wait=True, cancel_futures=True) + +def FuzzerAsyncMain(f, *args, **kwargs): + from fuzzconfig import FuzzConfig + + import rich.console + from rich.live import Live + from rich.panel import Panel + + 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, rich_tracebacks=True)], + force=True + ) + + start_time = time.time() + + async def start(f): + async_executor = None + + int_count = 0 + def sighandler(sig, frame): + nonlocal int_count + 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) + + try: + FUZZER_TITLE = os.environ.get("FUZZER_TITLE", "") + def status_panel(status: str) -> Panel: + return Panel( + f"[bold cyan]{status}[/bold cyan]", + title=f"Status - {FUZZER_TITLE}", + border_style="blue", + height=3, + ) + + 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: + 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: + logging.info("Exit ui thread") + + + with Executor() as executor: + try: + asyncio.get_running_loop().set_default_executor(executor) + + async_executor = AsyncExecutor(executor) + + all_exceptions = [] + + task = asyncio.create_task(f(async_executor, *args, **kwargs)) + ui_task = ui(async_executor, task) + + (_, task_result) = await asyncio.gather(ui_task, task, return_exceptions=False) + + logging.info(f"UI and main task finished {task_result}") + + 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") + except CancelledError: + logging.warning("Cancelled") + raise + + 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): + 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 5646e3d..c96978b 100644 --- a/util/fuzz/interconnect.py +++ b/util/fuzz/interconnect.py @@ -1,13 +1,175 @@ """ Utilities for fuzzing interconect """ +import asyncio +import logging +import os +import random +import re +from collections import defaultdict +from functools import cache +from logging.handlers import RotatingFileHandler -import threading -import tiles +import cachecontrol +import lapie import libpyprjoxide +import tiles + +import database import fuzzconfig import fuzzloops -import lapie +from DesignFileBuilder import get_wires_delta, DesignsForPips, BitConflictException, create_wires_file + +def make_dict_of_lists(lst, key = None): + if key is None: + key = lambda x: x[0] + + rtn = defaultdict(list) + for item in lst: + rtn[key(item)].append(item) + return rtn + +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 = {} + + 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] + 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.device, nodenames, regex) + + 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: + logging.warning(f"No fuzz_pips defined for job {config}. Nodes: {nodes} {all_pips}") + return {} + + 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, + executor = None + ): + if sinks is None: + return [] + + + if not isinstance(sinks, dict): + sinks = pips_to_sinks(sinks) + + assert(len(config.tiles) > 0) + + def process_bits(bitstreams, from_wires, to_wire): + base_bitf = bitstreams[0] + 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.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.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}") + config.solve(fz, db) + + conns = tiles.get_connections_for_device(config.device) + + 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 = [] + + 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 + + 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}/") + futures.append(arc_bit) + + bitstream_futures.append(arc_bit) + + 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( config, @@ -20,7 +182,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 @@ -42,43 +205,467 @@ 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_") + if not fuzzconfig.should_fuzz_platform(config.device): + return [] - 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: - 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) + + 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] + + 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]: + + 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) + + with fuzzconfig.db_lock() as db: + 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))) + 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) - fz.solve(fuzzconfig.db) - fuzzloops.parallel_foreach(list(sorted(sinks.keys())), per_sink) + fz.add_pip_sample(db, from_wire, arc_bit.bitstream) + + 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 ",overlays/" + 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 f7400e4..06e691c 100644 --- a/util/fuzz/nonrouting.py +++ b/util/fuzz/nonrouting.py @@ -1,13 +1,27 @@ """ Utilities for fuzzing non-routing configuration. This is the counterpart to interconnect.py """ - +import logging import threading import tiles import libpyprjoxide + import fuzzconfig +import fuzzloops +import os +from interconnect import transaction_log +from DesignFileBuilder import BitConflictException + +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 @@ -16,15 +30,40 @@ 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 """ - 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=""): + if not fuzzconfig.should_fuzz_platform(config.device): + 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:] + with fuzzconfig.db_lock() as db: + 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].bitstream) + + 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 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, overlay=""): """ Fuzz a setting with multiple possible values @@ -39,19 +78,60 @@ 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) - for opt in values: - 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) - 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) -def fuzz_ip_word_setting(config, name, length, get_sv_substs, desc="", default=None): + assert len(values) > 1, f"Enum setting {name} requires more than one option (given {values})" + + if not fuzzconfig.should_fuzz_platform(config.device): + return + + 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.replace(" ", "")) + 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 = f"Build enum design" + + def integrate_bitstreams(bitstreams): + with fuzzconfig.db_lock() as 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 ) + +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 @@ -60,7 +140,13 @@ 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 """ - prefix = "thread{}_".format(threading.get_ident()) + if not fuzzconfig.should_fuzz_platform(config.device): + return + + if config.check_deltas(name): + return + + prefix = f"{name}/" inverted_mode = False if default is not None: @@ -70,17 +156,27 @@ 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) - fz.solve(fuzzconfig.db) + 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()) + ] + + 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 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=""): +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 @@ -90,10 +186,55 @@ 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, """ - prefix = "thread{}_".format(threading.get_ident()) + if not fuzzconfig.should_fuzz_platform(config.device): + return + + 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) - fz.solve(fuzzconfig.db) + prefix = f"{ipcore}/{name}" + + 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.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 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): + 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.mode + + 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 new file mode 100644 index 0000000..02738c7 --- /dev/null +++ b/util/fuzz/primitives.py @@ -0,0 +1,443 @@ +import json +from collections import defaultdict +from pathlib import Path + +from cffi.model import PrimitiveType +import logging + +class PrimitiveSetting: + 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): + 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 + + 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.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 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 + + parsed = database.get_primitive_json(primitive) + + 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 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])) + + 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.get("description", ""), + mode=mode, + primitive=primitive + ) + +lram_core = PrimitiveDefinition( + "LRAM_CORE", + [ + 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("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" +) + +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", + [ + 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( + "OSCD_CORE", + settings=[ + EnumSetting("DTR_EN", ["ENABLED", "DISABLED"]), + + WordSetting("HF_CLK_DIV", 8, default=1), + + WordSetting("HF_SED_SEC_DIV", 8), + + EnumSetting("HF_FABRIC_EN", ["ENABLED", "DISABLED"]), + + EnumSetting("HF_OSC_EN", ["ENABLED", "DISABLED"], + default="ENABLED", enable_value="ENABLED"), + + EnumSetting("LF_FABRIC_EN", ["ENABLED", "DISABLED"]), + + EnumSetting("LF_OUTPUT_EN", ["ENABLED", "DISABLED"]), + + EnumSetting("DEBUG_N", ["ENABLED", "DISABLED"]), + ], + pins=[ + 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", + 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.settings = [s for s in pll_core.settings if s.name != "CONFIG_WAIT_FOR_LOCK"] +pll_core.pins = [ + 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", bits=None), + PinSetting(name="LMMIWRRDN", dir="in", bits=None), + PinSetting(name="LMMIOFFSET", dir="in", + bits=6), + PinSetting(name="LMMIWDATA", dir="in", + bits=7), + + 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", + bits=None), + 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 +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 + +# 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") + +eclkdiv = PrimitiveDefinition.parse_primitive_json("ECLKDIV") +eclkdiv.get_setting("ECLK_DIV").enable_value = "2" + +# 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" +# i2cfifo.get_setting("CR1I2CEN").enable_value = "EN"