diff --git a/.gitignore b/.gitignore index 721db2b..224a1f7 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 \ No newline at end of file diff --git a/docs/bels/overview.md b/docs/bels/overview.md new file mode 100644 index 0000000..3517c56 --- /dev/null +++ b/docs/bels/overview.md @@ -0,0 +1,14 @@ +# Overview + +The tiletype of a tile dictates which BEL's are available on a given tile and which options they might have exposed. This is mapped out fully in prjoxide/libprjoxide/prjoxide/src/bels.rs, namely in `get_tile_bels`. + +A given bel might span over multiple logical tiles. It's anchor tile is the one with the appropriate tiletype but for routing information on where the related tile is there is rel_x and rel_y; which encode the relative tile offset for the related tile. This data can be varied based on the family, device and actual tile in question. + +For bels with these offsets, the offset information is used in fuzzing the routing to map the interconnect. The offset themselves can be devined from the output of the dev_get_nodes command and report for nearby CIB tiles. For instance, related tiles to LRAM will have LRAM_CORE wires in their tile. + +Bel information is encoded in the bba file which is produced by prjoxide and ingested in the build process of nextpnr. + +# References + +- [Terminology](https://fpga-interchange-schema.readthedocs.io/device_resources.html) + diff --git a/docs/database.md b/docs/database.md new file mode 100644 index 0000000..8fcf52b --- /dev/null +++ b/docs/database.md @@ -0,0 +1,54 @@ +# Database + +The chip database for prjoxide is ingested into libprjoxide and used to construct the BBA database used by nextpnr. + +## Per device files + +Each supported device has the following json files to describe it: + +### baseaddr.json + +Some primitives are internally routed on an LMMI bus that is used for configuration. This file describes the addresses of mapped primitives and the length of their address in bits. + +These addresses are mapped with the fuzzer in fuzzers/LIFCL/100-ip-base. This requires, for each device, a mapping between the primitive type in question and it's site. This mapping isn't immediately apparent; but can usually be derived by placing the known number of that primitive in a design and looking at it in the physical designer / routing tool in lattice. + +### iodb.json + +The IO database file contains information on a particular pins function and logical name for each given package. + +Each entry describes the pin in terms of it's physical location. Since all the IO pins are on the outside edge of the chip, you can map the tile as a combination of it's 'offset' and it's 'side' -- a pin on the top for instance with an offset of 58 maps to tile 58, 0. The pio attribute describes which pin in that tile is being described. + +The iodb.json files are constructed by running tools/parse_pins.py against the CSV files lattice makes available in the documentation section for each device. + +### tilegrid.json + +The tile grid file describes where the physical location of the tile is, what it's type is, and where it is encoded into the final bitstream file. + +This file is generated by tools/tilegrid_all.py. A new device will require some custom fields in the devices.json file but also tools/extract_tilegrid.py. + +## Shared database files + +The various .ron files are serialized/deserialized to rust. Each fuzzer deserializes the current database, processes samples, and then reserializes out the updated database. + +### devices.json + +This serves as a master listing of each device and metadata associated with that device: + +- packages: Comes from various lattice documentation, can also be seen by looking at the radiant device selection dialog. +- frame metadata: There are various necessary peices of data here. All are available in the "sysCONFIG Guide for Nexus Platform" document from the lattice website. +- idcode: This is also availble in the above document. +- max_row / max_cols: These are retreivable from the physical designer for a given chip, or from the tilegrid metadata file. + +This file serves as the central index for devices in the libprjoxide library. + +### iptypes + +These files are generated by various fuzzers associated with the given primitive. They map out the relationship between configuration parameters and the bits set in the bitstream. + +### tiletypes + +These files are also generated by fuzzers. While they also map out the relationship between various parameters and bits in the bitstream, they mainly focus on the interconnection between tiles themselves. This gives both a graph on what connections are possible but also the way those connections are configured in the bitstream. + +### timing + +This is a collection of a bunch of cell types and a description of the delay timing between pins as well as the setup and hold time for each pin in the cell. \ No newline at end of file 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..f0a538c --- /dev/null +++ b/docs/general/structure.md @@ -0,0 +1,91 @@ +# Overview + +At it's most basic level, the nexus (and most other FPGAs) is composed of basic elements (BELs), pins, wires and +Programmable Interconnect Points (PIPs). The bitstream configures the BELs and which PIPs are active. + +Loosely speaking, on the lattice components each BEL corresponds to a site. The internal tooling for lattice refers to +wires as nodes and the terms are used interchangeably. + +The lattice parts overlay a tile grid over this structure. Largely speaking the tile grid informs on where the component +might be on the chip, but also where the configuration data can be found / specified for any given chip. + +## Sites (BELs) + +Every site has a type, and the type dictates both it's pin capabilities and what programmable options and modes exist for +that given site. Sites correspond most closely to the primitives found in lattice documentation, but sometimes a site +isn't directly translated as a primitive, and instead has multiple modes which map the same site to multiple primitives +as defined in the manual. + +Nearly every site name contains a prefix indicating the row and column it is most aligned to, and that tile is used to +configure that site. A tile can have multiple sites in it, or the same site type can occur in multiple tile types where +it's configuration bits occur at different offsets. + +Many exceptions do exist where a site is named for one row-column pair but it's configuration lives in another tile, and +that tile has the appropriate tile type. For instance, LRAM's typically are like this. Part of what the fuzzers are configured +for is to represent the mapping between the site tile location and the config tile location. + +## Nodes (Wires) and PIPs + +Nodes represent physical wires with gates connecting it to other nodes. Nodes can have pins tied directly onto them. + +Lattice has a TCL library exposed in a tool -- lapie / lark depending on version -- which can be used to query the node +graph. This tooling gives you which PIPs and pins are associated with the node, as well as what aliases are associated +with it. + +In terms of scale, there are about 1.7 million nodes on the LIFCL-40 part. + +Nodes also have aliases. The typical reason for this is that nodes can span multiple tiles, and so each tile has a local +name for that node. Only the primary name associated with the node is directly queryable, so there is no robust way in +general to determine every node that is associated with a given tile. + +### Node Naming + +Nodes have a semantically meaningful structure to their naming. They are all prefixed with `RC_` which gives a hint +to it's location; although nodes can span multiple tiles. + +After that there are the following naming conventions: + +## Tile types + +Tiles of a given tile type will always have the same set of: + +- Sites +- Nodes +- PIPs + +Often they will also dictate the relationship between neighboring tiles in a rigid way. For instance, LRAM instances +have an associated `CIB_LR` tiletype at an offset determined by it's tiletype. + +Tile types also are the fundamental building block to configuring the chip since it rigidly maps the bits in it's +configuration bits to the sites and pips associated with it. + +Tile types are also standard across devices -- the way you configure a PLC tile is identical in LIFCL-17 as it is in LIFCL-40, +for instance. It should be noted though that lattice is inconsistent with this principal, and so some tile types are +flagged and changed when the tilegrid is imported from lattice's interchange format. + +## Global Routes + +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/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/empty_40.v b/fuzzers/LFCPNX/shared/empty_40.v new file mode 100644 index 0000000..8647858 --- /dev/null +++ b/fuzzers/LFCPNX/shared/empty_40.v @@ -0,0 +1,9 @@ + +(* \db:architecture ="LFCPNX", \db:device ="LFCPNX-40", \db:package ="LFG672", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + +); + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); +endmodule diff --git a/fuzzers/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 index c71838e..cafa99b 100644 --- a/fuzzers/LIFCL/001-plc-routing/fuzzer.py +++ b/fuzzers/LIFCL/001-plc-routing/fuzzer.py @@ -1,14 +1,17 @@ from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect import re +import tiles -cfg = FuzzConfig(job="PLCROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["R16C22:PLC"]) +def run_cfg(device): + tile = list(tiles.get_tiles_by_tiletype(device, "PLC").keys())[0] + (r,c) = tiles.get_rc_from_name(device, tile) -def main(): + cfg = FuzzConfig(job=f"PLCROUTE-{device}-{tile}", device=device, sv=f"../shared/route_{device.split('-')[-1]}.v", tiles=[tile]) + cfg.setup() - r = 16 - c = 22 - nodes = ["R{}C{}_J*".format(r, c)] + + nodes = ["R{}C{}_J.*".format(r, c)] extra_sources = [] extra_sources += ["R{}C{}_H02E{:02}01".format(r, c+1, i) for i in range(8)] extra_sources += ["R{}C{}_H06E{:02}03".format(r, c+3, i) for i in range(4)] @@ -18,8 +21,14 @@ def main(): extra_sources += ["R{}C{}_V06S{:02}03".format(r+3, c, i) for i in range(4)] extra_sources += ["R{}C{}_H02W{:02}01".format(r, c-1, i) for i in range(8)] extra_sources += ["R{}C{}_H06W{:02}03".format(r, c-3, i) for i in range(4)] - fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=True, ignore_tiles=set(["TAP_PLC_R16C14:TAP_PLC"])) - fuzz_interconnect(config=cfg, nodenames=extra_sources, regex=False, bidir=False, ignore_tiles=set(["TAP_PLC_R16C14:TAP_PLC"])) + #nodes = [n.name for n in tiles.get_nodes_for_tile(cfg.device, tile)] + #fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=True)#, ignore_tiles=set(["TAP_PLC_R16C14:TAP_PLC"])) + cfg.job = cfg.job + "-extra-srcs" + fuzz_interconnect(config=cfg, nodenames=extra_sources, regex=True, bidir=False)#, ignore_tiles=set(["TAP_PLC_R16C14:TAP_PLC"])) + +def main(): + run_cfg("LIFCL-40") + if __name__ == "__main__": main() diff --git a/fuzzers/LIFCL/002-cib-routing/fuzzer.py b/fuzzers/LIFCL/002-cib-routing/fuzzer.py index 734d403..3d3fffb 100644 --- a/fuzzers/LIFCL/002-cib-routing/fuzzer.py +++ b/fuzzers/LIFCL/002-cib-routing/fuzzer.py @@ -1,45 +1,57 @@ +import asyncio + from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect +from fuzzloops import FuzzerAsyncMain import re configs = [ - ((1, 18), FuzzConfig(job="CIBTROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R1C18:CIB_T"]), set(["TAP_CIBT_R1C14:TAP_CIBT"])), - ((18, 1), FuzzConfig(job="CIBLRROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R18C1:CIB_LR"]), set(["TAP_PLC_R18C14:TAP_PLC"])), - ((28, 17), FuzzConfig(job="CIBROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R28C17:CIB"]), set(["TAP_CIB_R28C14:TAP_CIB"])), - ((28, 1), FuzzConfig(job="CIBLRAROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R28C1:CIB_LR_A"]), set(["TAP_CIB_R28C14:TAP_CIB"])), + ((1, 18), FuzzConfig(job="CIBTROUTE", device="LIFCL-40", tiles=["CIB_R1C18:CIB_T"]), set(["TAP_CIBT_R1C14:TAP_CIBT"])), + ((18, 1), FuzzConfig(job="CIBLRROUTE", device="LIFCL-40", tiles=["CIB_R18C1:CIB_LR"]), set(["TAP_PLC_R18C14:TAP_PLC"])), + ((28, 17), FuzzConfig(job="CIBROUTE", device="LIFCL-40", tiles=["CIB_R28C17:CIB"]), set(["TAP_CIB_R28C14:TAP_CIB"])), + ((28, 1), FuzzConfig(job="CIBLRAROUTE", device="LIFCL-40", tiles=["CIB_R28C1:CIB_LR_A"]), set(["TAP_CIB_R28C14:TAP_CIB"])), ] -def main(): - for rc, cfg, ignore in configs: - cfg.setup() - r, c = rc - nodes = ["R{}C{}_J*".format(r, c)] - extra_sources = [] - extra_sources += ["R{}C{}_H02E{:02}01".format(r, c+1, i) for i in range(8)] - extra_sources += ["R{}C{}_H06E{:02}03".format(r, c+3, i) for i in range(4)] - if r != 1: - extra_sources += ["R{}C{}_V02N{:02}01".format(r-1, c, i) for i in range(8)] - extra_sources += ["R{}C{}_V06N{:02}03".format(r-3, c, i) for i in range(4)] - else: - extra_sources += ["R{}C{}_V02N{:02}00".format(r, c, i) for i in range(8)] - extra_sources += ["R{}C{}_V06N{:02}00".format(r, c, i) for i in range(4)] - extra_sources += ["R{}C{}_V02S{:02}01".format(r+1, c, i) for i in range(8)] - extra_sources += ["R{}C{}_V06S{:02}03".format(r+3, c, i) for i in range(4)] - if c != 1: - extra_sources += ["R{}C{}_H02W{:02}01".format(r, c-1, i) for i in range(8)] - extra_sources += ["R{}C{}_H06W{:02}03".format(r, c-3, i) for i in range(4)] - else: - extra_sources += ["R{}C{}_H02W{:02}00".format(r, c, i) for i in range(8)] - extra_sources += ["R{}C{}_H06W{:02}00".format(r, c, i) for i in range(4)] - def pip_filter(pip, nodes): - from_wire, to_wire = pip - return not ("_CORE" in from_wire or "_CORE" in to_wire or "JCIBMUXOUT" in to_wire) - def fc_filter(to_wire): - return "CIBMUX" in to_wire or "CIBTEST" in to_wire or to_wire.startswith("R{}C{}_J".format(r, c)) - fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=True, ignore_tiles=ignore, - pip_predicate=pip_filter, fc_filter=fc_filter) +async def per_config(rc, cfg, ignore, executor): + cfg.setup() + r, c = rc + nodes = ["R{}C{}_J*".format(r, c)] + extra_sources = [] + extra_sources += ["R{}C{}_H02E{:02}01".format(r, c+1, i) for i in range(8)] + extra_sources += ["R{}C{}_H06E{:02}03".format(r, c+3, i) for i in range(4)] + if r != 1: + extra_sources += ["R{}C{}_V02N{:02}01".format(r-1, c, i) for i in range(8)] + extra_sources += ["R{}C{}_V06N{:02}03".format(r-3, c, i) for i in range(4)] + else: + extra_sources += ["R{}C{}_V02N{:02}00".format(r, c, i) for i in range(8)] + extra_sources += ["R{}C{}_V06N{:02}00".format(r, c, i) for i in range(4)] + extra_sources += ["R{}C{}_V02S{:02}01".format(r+1, c, i) for i in range(8)] + extra_sources += ["R{}C{}_V06S{:02}03".format(r+3, c, i) for i in range(4)] + if c != 1: + extra_sources += ["R{}C{}_H02W{:02}01".format(r, c-1, i) for i in range(8)] + extra_sources += ["R{}C{}_H06W{:02}03".format(r, c-3, i) for i in range(4)] + else: + extra_sources += ["R{}C{}_H02W{:02}00".format(r, c, i) for i in range(8)] + extra_sources += ["R{}C{}_H06W{:02}00".format(r, c, i) for i in range(4)] + def pip_filter(pip, nodes): + from_wire, to_wire = pip + return not ("_CORE" in from_wire or "_CORE" in to_wire or "JCIBMUXOUT" in to_wire) + def fc_filter(to_wire): + return "CIBMUX" in to_wire or "CIBTEST" in to_wire or to_wire.startswith("R{}C{}_J".format(r, c)) + + futures = fuzz_interconnect(config=cfg, nodenames=nodes, regex=True, bidir=True, ignore_tiles=ignore, + pip_predicate=pip_filter, fc_filter=fc_filter, executor=executor) + \ fuzz_interconnect(config=cfg, nodenames=extra_sources, regex=False, bidir=False, ignore_tiles=ignore, - pip_predicate=pip_filter, fc_filter=fc_filter) + pip_predicate=pip_filter, fc_filter=fc_filter, executor=executor) + + await asyncio.gather(*[asyncio.wrap_future(f) for f in futures]) + +async def run(executor): + await asyncio.gather(*[(per_config(*cfg, executor=executor))for cfg in configs]) + + +def main(): + FuzzerAsyncMain(run) if __name__ == "__main__": main() diff --git a/fuzzers/LIFCL/010-lut-init/fuzzer.py b/fuzzers/LIFCL/010-lut-init/fuzzer.py index 232d41d..bcbc80a 100644 --- a/fuzzers/LIFCL/010-lut-init/fuzzer.py +++ b/fuzzers/LIFCL/010-lut-init/fuzzer.py @@ -3,7 +3,7 @@ import fuzzloops import re -cfg = FuzzConfig(job="PLCINIT", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["R2C2:PLC"]) +cfg = FuzzConfig(job="PLCINIT", device="LIFCL-40", tiles=["R2C2:PLC"]) def get_lut_function(init_bits): sop_terms = [] diff --git a/fuzzers/LIFCL/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/015-local-routes/fuzzer.py b/fuzzers/LIFCL/015-local-routes/fuzzer.py new file mode 100644 index 0000000..68895a6 --- /dev/null +++ b/fuzzers/LIFCL/015-local-routes/fuzzer.py @@ -0,0 +1,303 @@ +import asyncio +import logging +import re +import sys +from collections import defaultdict + +import cachecontrol +import fuzzconfig +import fuzzloops +import interconnect +import lapie +import libpyprjoxide +import nonrouting +import primitives +import radiant +import tiles +from fuzzconfig import FuzzConfig, get_db +from interconnect import fuzz_interconnect_sinks + +import database + +### +# The idea for this fuzzer is that we can map out the internal pips and connections for a given tiletype in relatively +# short order without knowing much about them by using the introspection from the radiant tools. The basic process is: +# - Generate a list of wires and pips a given tile type has +# - Figure out which tiles configure these pips +# - Use all the tiles of that tiletype on the board to test. So if there are 16 tiles with that tiletype, we can solve +# for 16 PIP configurations at a time. +### + +processed_tiletypes = set("PLC") + +exclusion_list = { + # I think this particular pip needs other things in SYSIO_B3 to trigger, but when SYSIO_B3_1 is + # driving SYSIO_B3_0_ECLK_L, the pip seems active. Just blacklist this one and accept the bit flip. + ("SYSIO_B3_0", "JECLKIN1_I218", "JECLKOUT_I218") +} + +# Cache this so we only do it once. Could also probably read the ron file and check it. +@cachecontrol.cache_fn() +def register_tile_connections(device, tiletype, tile, conn_pips): + connection_sinks = defaultdict(list) + for (frm, to) in conn_pips: + connection_sinks[to].append(frm) + + db = libpyprjoxide.Database(database.get_db_root()) + family = device.split("-")[0] + for (to_wire, froms) in connection_sinks.items(): + for from_wire in froms: + db.add_conn(family, tiletype, to_wire, from_wire) + db.flush() + +### Gather up all the consistent internal pips for a tiletype, then use however many tiles of that tiletype exist +### to create design sets for each PIP. This also tracks and manages tiles that configure the tiletype but are positioned +### relative to it and have different tiles types +async def get_tiletype_design_sets(device, tiletype, executor = None): + + # representative nodes is all wires that are common to all instances of that tiletype for the device + wires = tiles.get_representative_nodes_for_tiletype(device, tiletype) + + if len(wires) == 0: + logging.debug(f"{tiletype} has no consistent internal wires") + return tiletype, [], [] + + ts = sorted(list(tiles.get_tiles_by_tiletype(device, tiletype).keys())) + + # Treat this as an exemplar node to gather the pips from + (r, c) = tiles.get_rc_from_name(device, ts[0]) + nodes = set([f"R{r}C{c}_{w}" for w in wires]) + pips, tiletype_graph = await asyncio.wrap_future(tiles.get_local_pips_for_nodes(device, nodes, include_interface_pips=False, + should_expand=lambda p: p[0] in nodes and p[1] in nodes, + executor = executor)) + + # Jump wires are what lattice tools refer to as connections -- basically PIPs that are always on + connected_arcs = lapie.get_jump_wires_by_nodes(device, nodes) + + # Remove the jump wires -- no point in including them in our designs, we already know they are connections + conn_pips = set(pips) & connected_arcs + actual_pips = set(pips) - conn_pips + pips = sorted(actual_pips) + + # While we have the list, we might as well mark down that they are connections + register_tile_connections(device, tiletype, ts[0], sorted(conn_pips)) + + anon_pips = sorted(set([tuple(["_".join(w.split("_")[1:]) for w in p]) for p in pips])) + + baseline = FuzzConfig.standard_empty(device) + cfg = FuzzConfig(job=f"find-tile-set-{device}-{tiletype}", device=device) + + bitstream = await asyncio.wrap_future(interconnect.create_wires_file(cfg, pips, executor=executor)) + + deltas, _ =fuzzconfig.find_baseline_differences(device, bitstream) + + # PLC is used to affix the wires, strip those from the delta + filtered_deltas = {k:v for k,v in deltas.items() if k.split(":")[1] != "PLC"} + + # PIPs are often controlled by nearby tiles. Convert those to relative positioned tiles. Since sometimes two tiles + # will share an RC, we grab the tiletype too. + modified_tiles_rcs = set([(tiles.get_rc_from_name(device, n),n.split(":")[-1]) for n in filtered_deltas.keys()]) + modified_tiles_rcs_anon = [((r0-r),(c0-c),tt) for ((r0,c0),tt) in modified_tiles_rcs] + + logging.info(f"{tiletype} has {len(anon_pips)} PIPs for {len(ts)} tiles {len(conn_pips)} connections and {len(nodes)} nodes with {modified_tiles_rcs_anon} modified tiles") + + logging.debug(f"{tiletype} Connections:") + for c in conn_pips: + logging.debug(f" - {c}") + + logging.debug(f"{tiletype} pips:") + for c in anon_pips: + logging.debug(f" - {c}") + + # Either there are no pips or a primitive enables them + if len(modified_tiles_rcs_anon) == 0: + return tiletype, [], [] + + # TAP_PLC's are weird and need to be mapped separately. + if "TAP_PLC" in [tt for (_,_,tt) in modified_tiles_rcs_anon]: + logging.warning(f"Ignoring {tiletype}; {modified_tiles_rcs_anon}") + return tiletype, [], [] + + design_sets = [] + rcs_for_tiles_of_tiletype = sorted([(tile,tiles.get_rc_from_name(device, tile)) for tile in ts]) + + extra_rcs = [((r+rd), (c+cd), tt) + for (_, (r,c)) in rcs_for_tiles_of_tiletype + for (rd,cd,tt) in modified_tiles_rcs_anon] + + # Make sure there isn't overlap between modified tiles for all the tiles of the type. + assert(len(extra_rcs) == len(set(extra_rcs))) + extra_rcs = set(extra_rcs) + + # Generate design sets by continuously iterating through tile locations and putting a random PIP there. + while len(anon_pips): + design_set = {} + + # Just place all the extra tiles. We dont have pips for these tiles but this marks it as used. + for rc in extra_rcs: + for tile in tiles.get_tiles_by_rc(device, rc): + design_set[tile] = None + for (tile, (r,c)) in rcs_for_tiles_of_tiletype: + pip = anon_pips.pop() + pip = [f"R{r}C{c}_{w}" for w in pip] + design_set[tile] = pip + + if len(anon_pips) == 0: + break + + if len(design_set): + design_sets.append(design_set) + + return tiletype, design_sets, modified_tiles_rcs_anon + +def get_filtered_typetypes(device): + tiletypes = tiles.get_tiletypes(device) + for tiletype, ts in sorted(tiletypes.items()): + + if tiletype in ["PLC", "TAP_PLC"]: + continue + + if len(sys.argv) > 1 and re.compile(sys.argv[1]).search(tiletype) is None: + continue + + if tiletype in processed_tiletypes: + continue + processed_tiletypes.add(tiletype) + yield tiletype + +async def run_for_device(device, executor = None): + if not fuzzconfig.should_fuzz_platform(device): + logging.warning(f"Ignoring device {device}") + return [] + + logging.info("Fuzzing device: " + device) + + device_futures = [ + get_tiletype_design_sets(device, tiletype, executor=executor) + for tiletype in get_filtered_typetypes(device) + ] + + # list of list of dicts + logging.info(f"Gathering {len(device_futures)} tile type pips") + all_design_sets = await asyncio.gather(*device_futures) + + owned_rcs = {tt:e for (tt,d,e) in all_design_sets} + + design_sets = [] + while True: + design_set = {} + owners = defaultdict(list) + for (tiletype, designs, extra_rcs) in all_design_sets: + if len(designs) and len(designs[-1].keys() & design_set.keys()) == 0: + owners[tiletype].extend(designs[-1].keys()) + design_set.update(designs.pop()) + + # The original idea here was that the tile types could be combined. However, this + # does seem to trigger some bit changes + break + if len(design_set) == 0: + break + design_sets.append((owners, design_set)) + + logging.info(f"Building {len(design_sets)} designs") + cfg = FuzzConfig(job="all-routing", device=device, tiles=[]) + + diff_designs_futures = [] + empty_file = FuzzConfig.standard_empty(device) + for idx, (owners, design_set) in enumerate(design_sets): + pips = [pip for tile, pip in design_set.items() if pip is not None] + create_bitstream_future = interconnect.create_wires_file(cfg, pips, executor=executor, prefix=f"{idx}/") + diff_designs_futures.append(fuzzloops.chain(create_bitstream_future, "solve_design", lambda x,device=device: fuzzconfig.find_baseline_differences(device, x)[0])) + + all_design_diffs = await asyncio.gather(*[asyncio.wrap_future(f) for f in diff_designs_futures]) + + def anon_pip(p): + return ["_".join(w.split("_")[1:]) for w in p] + + pip_deltas = defaultdict(list) + for (deltas, (owners, design_set)) in zip(all_design_diffs, design_sets): + rc_deltas = {(*tiles.get_rc_from_name(device, k), k.split(":")[-1]):v for k,v in deltas.items()} + + owned_by = {} + for k,ts in owners.items(): + for tile in ts: + owned_by[tile] = k + for tile, pip in design_set.items(): + tiletype = tile.split(":")[1] + if pip is not None: + rc = tiles.get_rc_from_name(device, pip[0]) + tile_owned_rcs = set([ + (orc[0]+rc[0], orc[1]+rc[1], orc[2]) + for orc in owned_rcs[tiletype] + ]) + + owned_tiles_for_tiletype = { + k:rc_deltas.get(k, []) + for k in tile_owned_rcs + } + + rc_tiles_for_tiletype = {(r-rc[0], c-rc[1], tiletype):d for (r,c,tiletype),d in owned_tiles_for_tiletype.items()} + + apip = anon_pip(pip) + pip_deltas[tiletype].append((apip, rc_tiles_for_tiletype)) + + for tiletype, pips_with_deltas in pip_deltas.items(): + sinks = defaultdict(list) + for (pip, deltas) in pips_with_deltas: + sinks[pip[1]].append((pip[0], deltas)) + + all_ts = sorted(list(tiles.get_tiles_by_tiletype(device, tiletype).keys())) + tile = all_ts[0] + ts = [tile] + + logging.debug(f"Solving for {len(sinks)} sinks on {tiletype}; ref tile {tile}") + for to_wire, full_deltas in sinks.items(): + + rc = tiles.get_rc_from_name(device, tile) + rc_prefix = f"R{rc[0]}C{rc[1]}_" + tile_lookup = {} + for from_wire, deltas in full_deltas: + for (r1, c1, tiletype), d in deltas.items(): + for t in tiles.get_tiles_by_rc(device, (r1+rc[0],c1+rc[1])): + if t.split(":")[-1] == tiletype: + tile_lookup[(r1,c1,tiletype)] = t + ts.append(t) + + cfg = FuzzConfig(job=f"{tiletype}/{to_wire}", device=device, tiles=ts) + fz = libpyprjoxide.Fuzzer.pip_fuzzer(fuzzconfig.db, empty_file, set(ts), rc_prefix + to_wire, tile, + set(), "MUX" in to_wire, False) + for from_wire, deltas in full_deltas: + if (tiletype, from_wire, to_wire) in exclusion_list: + continue + + concrete_deltas = {tile_lookup[k]: v for k, v in deltas.items() if len(v)} + logging.debug(f"{tiletype} {from_wire} -> {to_wire} has {len(concrete_deltas)} delta tiles") + fz.add_pip_sample_delta(rc_prefix + from_wire, concrete_deltas) + cfg.solve(fz) + + return [] + +async def run_for_devices(executor): + get_db() + families = database.get_devices()["families"] + devices = sorted([ + device + for family in families + for device in families[family]["devices"] + if fuzzconfig.should_fuzz_platform(device) + ]) + + all_tiletypes = sorted(set([tile.split(":")[-1] + for device in devices + for tile in database.get_tilegrid(device)["tiles"] + ])) + + if len(sys.argv) > 1 and not any(map(lambda tt: re.compile(sys.argv[1]).search(tt), all_tiletypes)): + logging.warning(f"Tiletype filter doesn't match any known tiles") + logging.warning(sorted(all_tiletypes)) + return [] + + return await asyncio.gather(*[run_for_device(device, executor) for device in devices]) + +if __name__ == "__main__": + fuzzloops.FuzzerAsyncMain(run_for_devices) diff --git a/fuzzers/LIFCL/016-site-mappings/fuzzer.py b/fuzzers/LIFCL/016-site-mappings/fuzzer.py new file mode 100644 index 0000000..d379bc9 --- /dev/null +++ b/fuzzers/LIFCL/016-site-mappings/fuzzer.py @@ -0,0 +1,375 @@ +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, get_db +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() + +# 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, _ = await asyncio.wrap_future( + tiles.get_local_pips_for_nodes(device, nodes, include_interface_pips=True, + should_expand=lambda p: p[0] in nodes or p[1] in nodes, + executor = executor) + ) + + wires_bitstream = await asyncio.wrap_future(interconnect.create_wires_file(cfg, pips, prefix=f"find-relevant-tiles/", executor = executor)) + + driving_tiles, site_tiles, ip_values = find_relevant_tiles_from_bitstream(device, site, wires_bitstream) + + return ([t for t in driving_tiles if t.split(":")[-1] != "PLC"], + [t for t in site_tiles if t.split(":")[-1] != "PLC"], + ip_values + ) + +# If we have a primitive definition, use it to generate a bitstream and compare it to baseline. This delta shows which +# tiles the site belongs to. +async def find_relevant_tiles_from_primitive(device, primitive, site, site_info, executor): + site_type = site_info["type"] + + cfg = FuzzConfig(job=f"{site}:{site_type}", device=device, tiles=[]) + + primitive_bitstream = await asyncio.wrap_future(cfg.build_design_future(executor, "./primitive.v", { + "config": primitive.fill_config(), + "site": site, + "site_type": site_type, + "extra": "", + "signals": "" + }, prefix=f"find-relevant-tiles/{primitive.mode}/")) + logging.info(f"Getting relevant tiles for {device} {site}:{site_type} for {primitive.mode}") + + driving_tiles, site_tiles, ip_values = find_relevant_tiles_from_bitstream(device, site, primitive_bitstream) + + # Also get the tiling from just the wiring + pin_driving_tiles, pin_site_tiles, pin_ip_values = await find_relevant_tiles(device, site, site_type, site_info, executor = executor) + + # Note: We do this to keep ordering but removing dups + def uniq(x): + return list(dict.fromkeys(x)) + + return uniq(driving_tiles + pin_driving_tiles), uniq(site_tiles + pin_site_tiles), uniq(ip_values + pin_ip_values) + +mux_re = re.compile("MUX[0-9]*$") + +# Take all a given sites local graph and pips, and solve for all of it +def map_local_pips(site, site_type, device, ts, pips, local_graph, executor=None): + cfg = FuzzConfig(job=f"{site}:{site_type}", sv="../shared/route.v", device=device, tiles=ts) + + logging.debug(f"PIPs for {site}:") + for p in pips: + logging.debug(f" - {p[0]} -> {p[1]}") + + external_nodes = [wire for pip in pips for wire in pip if wire not in local_graph] + + # CIB is routed separately + cfg.tiles.extend( + [tile for n in external_nodes for tile in tiles.get_tiles_by_rc(device, n) if tile.split(":")[-1] != "CIB"]) + cfg.tiles = [t for t in cfg.tiles if not t.split(":")[-1].startswith("CIB")] + + if len(cfg.tiles) == 0: + logging.warning(f"Local pips for {site} only corresponded to CIB tiles") + return + + mux_pips = [p for p in pips if mux_re.search(p[0]) and mux_re.search(p[1])] + non_mux_pips = [p for p in pips if not p in mux_pips] + if len(non_mux_pips): + yield from fuzz_interconnect_sinks(cfg, non_mux_pips, False, executor = executor) + + if len(mux_pips): + mux_cfg = FuzzConfig(job=f"{site}:{site_type}-MUX", sv="../shared/route.v", device=device, tiles=cfg.tiles) + yield from fuzz_interconnect_sinks(mux_cfg, mux_pips, True, executor = executor) + +# Use the primitive definitions to map out each mode's options. Works for IP and non IP settings +def map_primitive_settings(device, ts, site, site_tiles, site_type, ip_values, executor = None): + if site_type not in primitives.primitives: + return [] + + empty_file = FuzzConfig.standard_empty(device) + + base_addrs = database.get_base_addrs(device) + + if site not in base_addrs: + ip_values = [] + + is_ip_config = len(ip_values) > 0 + if len(ip_values): + fuzz_enum_setting = nonrouting.fuzz_ip_enum_setting + fuzz_word_setting = nonrouting.fuzz_ip_word_setting + else: + fuzz_enum_setting = nonrouting.fuzz_enum_setting + fuzz_word_setting = nonrouting.fuzz_word_setting + + def map_mode(mode): + logging.info(f"====== {mode.mode} : {site_type} IP: {len(ip_values)} ==========") + related_tiles = (ts + site_tiles) + cfg = FuzzConfig(job=f"config/{site_type}/{site}/{mode.mode}", device=device, sv="primitive.v", tiles= related_tiles if len(ip_values) == 0 else [f"{site}:{site_type}"]) + + slice_sites = tiles.get_tiles_by_tiletype(device, "PLC") + slice_iter = iter([x for x in slice_sites if tiles.get_rc_from_name(device, x) not in related_tiles]) + + extra_lines = [] + signals = [] + + avail_in_pins = [] + for p in mode.pins: + if p.dir == "in" or p.dir == "inout": + for r in range(0, p.bits if p.bits is not None else 1): + suffix = str(r) if p.bits != None else "" + avail_in_pins.append(f"{p.name}{suffix}") + q_driver = None + def get_sink_pin(): + if len(avail_in_pins): + in_pin = avail_in_pins.pop() + extra_lines.append(f"wire q_{in_pin};") + signals.append(f".{in_pin}(q_{in_pin})") + return f"q_{in_pin}" + + idx = len(extra_lines) + extra_lines.append(f""" + wire q_{idx}; + (* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) + SLICE SLICE_I_{idx} ( .A0(q_{idx}) ); + """) + return f"q_{idx}" + + for p in mode.pins: + for r in range(0, p.bits if p.bits is not None else 1): + suffix = str(r) if p.bits != None else "" + if p.dir == "out": + q = get_sink_pin() + q_driver = q + signals.append(f".{p.name}{suffix}({q})") + + if len(avail_in_pins) and q_driver is None: + extra_lines.append(f""" + wire q_driver; + (* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) + SLICE SLICE_I_driver ( .A0(q_driver), .Q0(q_driver) ); + """) + q_driver = "q_driver" + + for undriven_pin in avail_in_pins: + signals.append(f".{undriven_pin}({q_driver})") + + subs = { + "site": site, + "site_type": site_type, + "extra": "\n".join(extra_lines), + "signals": ", ".join(signals) + } + + def map_mode_setting(setting): + mark_relative_to = None + if site_tiles[0] != ts[0]: + mark_relative_to = site_tiles[0] + + args = { + "config": cfg, + "name": f"{mode.mode}.{setting.name}", + "desc": setting.desc, + "executor": executor + } + + if isinstance(setting, primitives.EnumSetting): + def subs_fn(val): + return subs | {"config": mode.configuration([(setting, val)])} + + if len(ip_values) == 0: + args["mark_relative_to"] = mark_relative_to + + if isinstance(setting, primitives.ProgrammablePin) and not is_ip_config: + args["include_zeros"] = True + + return fuzz_enum_setting(empty_bitfile = empty_file, values = setting.values, get_sv_substs = subs_fn, **args) + elif isinstance(setting, primitives.WordSetting): + def subs_fn(val): + return subs | {"config": mode.configuration([(setting, nonrouting.fuzz_intval(val))])} + + return fuzz_word_setting(length=setting.bits, get_sv_substs=subs_fn, **args) + else: + raise Exception(f"Unknown setting type: {setting}") + + return [map_mode_setting(s) for s in mode.settings] + + return [f for mode in primitives.primitives[site_type] for f in map_mode(mode)] + +async def run_for_device(device, executor = None): + if not fuzzconfig.should_fuzz_platform(device): + return + + async def find_relevant_tiles_for_site(site, site_info, executor): + if should_skip_site(site, site_info): + return None + + site_type = site_info["type"] + + if site_type in primitives.primitives: + primitive = primitives.primitives[site_type][0] + + return await find_relevant_tiles_from_primitive(device, primitive, site, site_info, executor=executor) + + return await find_relevant_tiles(device, site, site_type, site_info, executor=executor) + + def should_skip_site(site, site_info): + site_type = site_info["type"] + if len(sys.argv) > 1 and sys.argv[1] != site_type: + return True + + if site_type in ["PLL_CORE"] and device in ["LIFCL-33U"]: + logging.warning(f"Can't map out IP core {site_type} with device {device} which is in readback mode") + return True + + if site_type in ["CIBTEST", "SLICE"]: + return True + + return False + + async def per_site(site, site_info, driving_tiles, executor): + (driving_tiles, site_tiles, ip_values) = driving_tiles + + site_type = site_info["type"] + + logging.info(f"====== {site} : {driving_tiles} ==========") + tiletype = driving_tiles[0].split(":")[1] + + logging.info(f"====== {site} : {tiletype} ==========") + pips, local_graph = await asyncio.wrap_future( + executor.submit(tiles.get_local_pips_for_site, device, site) + ) + pips_future = list(map_local_pips(site, site_type, device, driving_tiles + site_tiles, pips, local_graph, executor=executor)) + + # Map primitive parameter settings + settings_future = map_primitive_settings(device, driving_tiles + site_tiles, site, site_tiles, site_type, ip_values, executor = executor) + + return [pips_future, settings_future] + + sites = database.get_sites(device) + sites_items = [(k,v) for k,v in sorted(sites.items()) if v["type"] not in ["CIBTEST", "SLICE"]] + + sitetypes = {v["type"] for s,v in sites_items} + + await asyncio.wrap_future(lapie.get_node_data(device, sitetypes, True, executor)) + + driving_tiles_futures = [] + async with asyncio.TaskGroup() as tg: + for site, site_info in sites_items: + driving_tiles_futures.append(find_relevant_tiles_for_site(site, site_info, executor=executor)) + + all_driving_tiles = await asyncio.gather(*driving_tiles_futures) + + async with (asyncio.TaskGroup() as tg): + for (site, site_info), driving_tiles_rtn in zip(sites_items, all_driving_tiles): + if driving_tiles_rtn is None: continue + + driving_tiles, site_tiles, ip_values = driving_tiles_rtn + + driving_tiles = [t for t in driving_tiles if t.split(":")[1] not in ["PLC", "TAB_CIB", "CIB"]] + + if len(driving_tiles) == 0: + continue + + logging.debug(f"Driving sites for {site}:") + for t in set(driving_tiles + site_tiles): + logging.debug(f" - {t}") + + # Certain sites present different even with the same site_type and tile_type surrounding it. Specifically + # IO types have A and B suffixes. The IP and configuration is the same, but the pins map to differently + # named wires and it is the wire name that matters for the DB. So we key on the wire names too + site_key = ( + site_info["type"], + tuple(sorted(t.split(":")[-1] for t in driving_tiles)), + tuple(sorted(f"{p["pin_name"]}:{"_".join(p["pin_node"].split("_")[1:])}" for p in site_info["pins"])) + ) + + logging.debug(f"Site key: {site_key}") + if mapped_sites in site_key: + continue + + mapped_sites.add(site_key) + tg.create_task(per_site(site, site_info, (driving_tiles, site_tiles, ip_values), executor)) + + +def main(): + async def run_for_devices(executor): + get_db() + + families = database.get_devices()["families"] + devices = sorted([ + device + for family in families + for device in families[family]["devices"] + if fuzzconfig.should_fuzz_platform(device) + ]) + + all_sites = set([site_info["type"] + for device in devices + if device.startswith("LIFCL") + for site, site_info in database.get_sites(device).items() + ]) + + if len(sys.argv) > 1 and sys.argv[1] not in all_sites: + logging.warning(f"Site filter doesn't match any known sites") + logging.info(sorted(all_sites)) + + return [] + + return await asyncio.gather(*[ run_for_device(device, executor) for device in devices ]) + + fuzzloops.FuzzerAsyncMain(run_for_devices) + +if __name__ == "__main__": + main() diff --git a/fuzzers/LIFCL/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 index ead72f2..5e83864 100644 --- a/fuzzers/LIFCL/020-plc_tap/fuzzer.py +++ b/fuzzers/LIFCL/020-plc_tap/fuzzer.py @@ -1,11 +1,19 @@ +import asyncio +import logging + from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect import re +import tiles +import fuzzloops +import database +import lapie configs = [ ([(11, 7), (11, 19)], [], FuzzConfig(job="TAPROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_PLC_R11C14:TAP_PLC"])), ([(10, 7), (10, 19)], [], FuzzConfig(job="TAPROUTECIB", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_CIB_R10C14:TAP_CIB"])), ([(1, 7), (1, 19)], [], FuzzConfig(job="TAPROUTECIBT", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_CIBT_R1C14:TAP_CIBT"])), + ([(11, 80)], [], FuzzConfig(job="TAPROUTE_1S", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_PLC_1S_R11C74:TAP_PLC_1S"])), ([(10, 80)], [], FuzzConfig(job="TAPROUTECIB_1S", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_CIB_1S_R10C74:TAP_CIB_1S"])), ([(1, 80)], [], FuzzConfig(job="TAPROUTECIBT_1S", device="LIFCL-40", sv="../shared/route_40.v", tiles=["TAP_CIBT_1S_R1C74:TAP_CIBT_1S"])), @@ -15,7 +23,35 @@ ([(1, 7), ], [(1, 13), ], FuzzConfig(job="TAPROUTECIBT_1SL", device="LIFCL-17", sv="../shared/route_17.v", tiles=["TAP_CIBT_1S_L_R1C14:TAP_CIBT_1S_L"])), ] -def main(): +async def resolve_all_tiles_for_device(device,executor): + tg = database.get_tilegrid(device)["tiles"] + + tap_tiletypes = {i["tiletype"] for k,i in tg.items() if i['tiletype'].startswith("TAP_")} + + for tiletype in tap_tiletypes: + ts = {k for k,i in tg.items() if tiletype == i['tiletype']} + + sorted_tiles = sorted(ts, key=lambda t: tuple(reversed(tiles.get_rc_from_name(device, t)))) + tile = sorted_tiles[0] + + r = tiles.get_rc_from_name(device, tile)[0] + + tile_columns = sorted({tiles.get_rc_from_name(device, n)[1] for n in ts}) + + nodenames = [f"R{r}C..?_R?HPBX..00"] + nodes = lapie.get_node_data(device, nodenames, True) + + node_columns = sorted({tiles.get_rc_from_name(device, n.name)[1] for n in nodes}) + nodes_per_tile = len(node_columns) // len(tile_columns) + relevant_nodes = [n.name for n in nodes if tiles.get_rc_from_name(device, n.name)[1] in node_columns[:nodes_per_tile]] + + print(relevant_nodes, tile) + cfg = FuzzConfig(job=f"TAPROUTE-{tile}", device=device, sv="../shared/route.v", tiles=[tile]) + for f in fuzz_interconnect(config=cfg, nodenames=relevant_nodes, regex=False, bidir=False, full_mux_style=True, + executor=executor): + yield f + +async def main(executor): for locs, rlocs, cfg in configs: cfg.setup() nodes = [] @@ -23,7 +59,18 @@ def main(): nodes += ["R{}C{}_HPBX{:02}00".format(r, c, i) for i in range(8)] for r, c in rlocs: nodes += ["R{}C{}_RHPBX{:02}00".format(r, c, i) for i in range(8)] - fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=True) + + for f in fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=True, executor=executor): + yield f + + for device in ["LIFCL-33", "LIFCL-33U"]: + async for f in resolve_all_tiles_for_device(device, executor=executor): + yield f if __name__ == "__main__": - main() + async def async_main(executor): + await asyncio.gather(*[asyncio.wrap_future(f) async for f in main(executor)]) + + fuzzloops.FuzzerAsyncMain(async_main) + + diff --git a/fuzzers/LIFCL/021-cmux/fuzzer.py b/fuzzers/LIFCL/021-cmux/fuzzer.py index 9835539..661d848 100644 --- a/fuzzers/LIFCL/021-cmux/fuzzer.py +++ b/fuzzers/LIFCL/021-cmux/fuzzer.py @@ -1,10 +1,27 @@ from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect import re +import tiles +import database -cfg = FuzzConfig(job="CMUXROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R29C49:CMUX_0", "CIB_R29C50:CMUX_1", "CIB_R38C49:CMUX_2", "CIB_R38C50:CMUX_3"]) +def fuzz_33(): + device = "LIFCL-33" + tilegrid = database.get_tilegrid(device)['tiles'] + ts = [t for t,tinfo in tilegrid.items() if tinfo["tiletype"].startswith("CMUX")] + cfg = FuzzConfig(job="CMUXROUTE-33", device=device, sv="../shared/route_33.v", tiles=ts) + + cfg.setup() + + (r,c) = (37, 25) + tile_prefix = f"R{r}C{c}" + + nodes = [f"{tile_prefix}_J.*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/022-midmux/fuzzer.py index d9f2f21..a8746a0 100644 --- a/fuzzers/LIFCL/022-midmux/fuzzer.py +++ b/fuzzers/LIFCL/022-midmux/fuzzer.py @@ -17,6 +17,10 @@ FuzzConfig(job="RMIDROUTE", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R10C75:RMID_PICB_DLY10"])), ("VPFS", (0, 37), 16, "T", FuzzConfig(job="TMIDROUTE", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R0C37:TMID_0", "CIB_R0C38:TMID_1_15K", "CIB_R0C39:CLKBUF_T_15K"])), + + ("VPFN", (83, 25), 16, "B", + FuzzConfig(job="BMIDROUTE-33U", device="LIFCL-33U", sv="../shared/route.v", + tiles=["CIB_R83C25:BMID_0_ECLK_1", "CIB_R83C26:BMID_1_ECLK_2"])), ] def main(): @@ -24,8 +28,10 @@ def main(): cfg.setup() if cfg.device == "LIFCL-40": cr, cc = (28, 49) - else: + elif cfg.device == "LIFCL-17": cr, cc = (10, 37) + else: + cr, cc = (37, 25) r, c = rc nodes = [] mux_nodes = [] diff --git a/fuzzers/LIFCL/023-trunk-spine/fuzzer.py b/fuzzers/LIFCL/023-trunk-spine/fuzzer.py index 53b1495..4a32533 100644 --- a/fuzzers/LIFCL/023-trunk-spine/fuzzer.py +++ b/fuzzers/LIFCL/023-trunk-spine/fuzzer.py @@ -1,17 +1,25 @@ from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect import re +import database +import tiles spine_cfgs = { ("CIB_R29C13:SPINE_L1", "R28C13"), ("CIB_R29C37:SPINE_L0", "R28C37"), ("CIB_R29C62:SPINE_R0", "R28C61"), ("CIB_R29C74:SPINE_R1", "R28C73"), + + ("CIB_R38C13:SPINE_L0_33K", "R41C13"), + ("CIB_R38C38:SPINE_R0_33K", "R41C37"), } hrow_cfgs = { ("CIB_R29C37:SPINE_L0", "R28C31"), ("CIB_R29C62:SPINE_R0", "R28C61"), + + ("CIB_R38C13:SPINE_L0_33K", "R37C19"), + ("CIB_R38C38:SPINE_R0_33K", "R37C31"), } trunk_cfgs = { @@ -21,17 +29,20 @@ def main(): for tile, rc in spine_cfgs: - cfg = FuzzConfig(job="TAPROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=[tile]) + suffix = "33" if "33K" in tile else "40" + cfg = FuzzConfig(job=f"TAPROUTE-{tile}", device=f"LIFCL-{suffix}", sv=f"../shared/route_{suffix}.v", tiles=[tile]) cfg.setup() nodes = ["{}_VPSX{:02}00".format(rc, i) for i in range(16)] fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=False) for tile, rc in hrow_cfgs: - cfg = FuzzConfig(job="ROWROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=[tile]) + suffix = "33" if "33K" in tile else "40" + cfg = FuzzConfig(job=f"ROWROUTE-{tile}", device=f"LIFCL-{suffix}", sv=f"../shared/route_{suffix}.v", tiles=[tile]) cfg.setup() nodes = ["{}_HPRX{:02}00".format(rc, i) for i in range(16)] fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=False) for tile, rcs in trunk_cfgs: - cfg = FuzzConfig(job="TRUNKROUTE", device="LIFCL-40", sv="../shared/route_40.v", tiles=[tile]) + suffix = "33" if "33K" in tile else "40" + cfg = FuzzConfig(job="TRUNKROUTE", device=f"LIFCL-{suffix}", sv=f"../shared/route_{suffix}.v", tiles=[tile]) cfg.setup() nodes = ["{}HPRX{}".format(rcs, i) for i in range(16)] fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=False, full_mux_style=False) diff --git a/fuzzers/LIFCL/024-dcc-dcs/dcc.v b/fuzzers/LIFCL/024-dcc-dcs/dcc.v index f709b17..da10614 100644 --- a/fuzzers/LIFCL/024-dcc-dcs/dcc.v +++ b/fuzzers/LIFCL/024-dcc-dcs/dcc.v @@ -1,5 +1,5 @@ -(* \db:architecture ="LIFCL", \db:device ="${dev}", \db:package ="QFN72", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +(* \db:architecture ="LIFCL", \db:device ="${dev}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) module top ( ); diff --git a/fuzzers/LIFCL/024-dcc-dcs/dcs.v b/fuzzers/LIFCL/024-dcc-dcs/dcs.v index 29aa8ad..e02aa3b 100644 --- a/fuzzers/LIFCL/024-dcc-dcs/dcs.v +++ b/fuzzers/LIFCL/024-dcc-dcs/dcs.v @@ -1,5 +1,5 @@ -(* \db:architecture ="LIFCL", \db:device ="${dev}", \db:package ="QFN72", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +(* \db:architecture ="LIFCL", \db:device ="${dev}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) module top ( ); diff --git a/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py b/fuzzers/LIFCL/024-dcc-dcs/fuzzer.py index 1067bae..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/030-io_route/fuzzer.py b/fuzzers/LIFCL/030-io_route/fuzzer.py index 8c378a4..1e01f37 100644 --- a/fuzzers/LIFCL/030-io_route/fuzzer.py +++ b/fuzzers/LIFCL/030-io_route/fuzzer.py @@ -1,8 +1,37 @@ from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect import re +import tiles -configs = [ +tiles_33 = [ + [(0, 30)],# "B0"), + [(0, 2)],# "B5"), + [(0, 38)],# "B1_DED"), + [(0, 40)],# "B1"), + [(83, 12), (83, 11)],# B3_0, B3_1 + [(83, 14), (83, 15)],# B3_0_ECLK_L, B3_1 + [(83, 18), (83, 19)], # B3_0, B3_1_V18_32 + [(83, 32), (83, 33)], # B2_0, B2_1_V18_21 + [(83, 34), (83, 35)], # B2_0, B2_1 + [(83, 46), (83, 47)], # B2_0, B2_1_1_V18_22 + [(83, 4), (83, 5)],# B4_0 B4_1_V18_41 + [(83, 6), (83, 7)],# B4_0 B4_1_V18_42 + [(83, 8), (83, 9)],# B3_0, B3_1_V18_31 + [(0, 10)], # SYSIO_b5 + ] + +def create_io_config(device, rcs): + ts = [ tile for rc in rcs for tile in tiles.get_tiles_by_rc(device, rc) ] + job_name = "IOROUTE_" + "_".join([f"R{rc[0]}C{rc[1]}" for rc in rcs]) + return { + "cfg": FuzzConfig(job=job_name, device="LIFCL-33", sv="../shared/route_33.v", + tiles=ts), + "rcs": rcs + } + +configs_33 = [create_io_config("LIFCL-33", x) for x in tiles_33] + +configs = configs_33 + [ { "cfg": FuzzConfig(job="IOROUTE0_17K", device="LIFCL-17", sv="../shared/route_17.v", tiles=["CIB_R0C59:SYSIO_B0_0_15K"]), "rc": (0, 59), @@ -73,7 +102,7 @@ }, ] -ignore_tiles = set([ +ignore_tiles_40k = set([ "CIB_R55C8:CIB", "CIB_R55C9:CIB", "CIB_R55C16:CIB", @@ -127,17 +156,37 @@ def main(): for config in configs: cfg = config["cfg"] cfg.setup() - r, c = config["rc"] - nodes = ["R{}C{}_*".format(r, c)] + + rcs = set([]) + if "rc" in config: + rcs = set([ config["rc"] ]) + else: + rcs = set(config["rcs"]) + + nodes = [f"R{r}C{c}_.*" for (r,c) in rcs] def nodename_filter(x, nodes): - return ("R{}C{}_".format(r, c) in x) and ("_GEARING_PIC_TOP_" in x or "SEIO18_CORE" in x or "DIFFIO18_CORE" in x or "I217" in x or "I218" in x or "SEIO33_CORE" in x or "SIOLOGIC_CORE" in x) + node_in_tiles = tiles.get_rc_from_name(cfg.device, x) in rcs + return ("_GEARING_PIC_TOP_" in x or "SEIO18_CORE" in x or "DIFFIO18_CORE" in x or "I217" in x or "I218" in x or "SEIO33_CORE" in x or "SIOLOGIC_CORE" in x) def pip_filter(pip, nodes): from_wire, to_wire = pip return not ("ADC_CORE" in to_wire or "ECLKBANK_CORE" in to_wire or "MID_CORE" in to_wire or "REFMUX_CORE" in to_wire or "CONFIG_JTAG_CORE" in to_wire or "CONFIG_JTAG_CORE" in from_wire or "REFCLOCK_MUX_CORE" in to_wire) + + ignore_tiles = [tile + for (r,c) in rcs + for ro in [r+1,r-1,r] + for co in [c+1,c-1,c] + for tile in tiles.get_tiles_by_rc(cfg.device, (ro,co)) + ] + if cfg.device == "LIFCL-17": + ignore_tiles = ignore_tiles_17k + elif cfg.device == "LIFCL-40": + ignore_tiles = ignore_tiles_40k + + print("Ignore tiles: ", ignore_tiles) fuzz_interconnect(config=cfg, nodenames=nodes, nodename_predicate=nodename_filter, pip_predicate=pip_filter, regex=True, bidir=True, - ignore_tiles=ignore_tiles_17k if cfg.device == "LIFCL-17" else ignore_tiles) + ignore_tiles=ignore_tiles) if __name__ == "__main__": main() diff --git a/fuzzers/LIFCL/031-io_mode/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/032-hsio_mode/fuzzer.py b/fuzzers/LIFCL/032-hsio_mode/fuzzer.py index 440ea59..16d77c2 100644 --- a/fuzzers/LIFCL/032-hsio_mode/fuzzer.py +++ b/fuzzers/LIFCL/032-hsio_mode/fuzzer.py @@ -1,24 +1,67 @@ -from fuzzconfig import FuzzConfig +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"IO{pin}_{tiletype}", device=device, + tiles=ts + all_sysio)) + ) + +def create_configs_for_device(device): + pads = [x for x in database.get_iodb(device)["pads"]] + configs = dict(filter(None, [ + create_config_from_pad(x, device) for x in pads if x["offset"] >= 0 + ])) + return configs + +configs = (create_configs_for_device("LIFCL-33") | create_configs_for_device("LIFCL-40") ).values() +# + [ +# ("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 +93,29 @@ ("HSTL15D_I", 1.5, None), ("HSUL12D", 1.2, None), ] -def main(): + +device_empty_bitfile = {} + +def main(executor): def per_config(config): pio, site, cfg = config + + (r,c) = tiles.get_rc_from_name(cfg.device, cfg.tiles[0]) + + if f"R{r}C{c}_JPADDO_SEIO18_CORE_IO{pio}" not in tiles.get_full_node_set(cfg.device) and \ + f"R{r}C{c}_JPADDO_DIFFIO18_CORE_IO{pio}" not in tiles.get_full_node_set(cfg.device): + print(f"Skipping {site}; 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 +167,95 @@ 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) + def fuzz_enum_setting(*args, **kwargs): + nonrouting.fuzz_enum_setting(cfg, empty, executor=executor,*args, **kwargs) + + fuzz_enum_setting("PIO{}.SEIO18.DRIVE_1V0".format(pio), ["2", "4"], + lambda x: get_substs(iotype="OUTPUT_LVCMOS10H", kv=("DRIVE", x)), True) + + 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) + + 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/032-hsio_mode/iob.v b/fuzzers/LIFCL/032-hsio_mode/iob.v new file mode 100644 index 0000000..164da3c --- /dev/null +++ b/fuzzers/LIFCL/032-hsio_mode/iob.v @@ -0,0 +1,13 @@ +(* \db:architecture ="LIFCL", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +module top ( + ${cmt} ${pintype} q +); + + // A primitive is needed, but VHI should be harmless + (* \xref:LOG ="q_c@0@9" *) + VHI vhi_i(); + + ${cmt}(* \dm:primitive ="${primtype}", \dm:programming ="MODE:${primtype} ${primtype}:::IO_TYPE=${iotype},BANK_VCCIO=${vcc}${extra_config}:T=${t}", \dm:site ="${site}" *) + ${cmt}${primtype} INST (.PADDO(q)); + +endmodule diff --git a/fuzzers/LIFCL/032-hsio_mode/iob_40.v b/fuzzers/LIFCL/032-hsio_mode/iob_40.v index 15cfbbd..61a9880 100644 --- a/fuzzers/LIFCL/032-hsio_mode/iob_40.v +++ b/fuzzers/LIFCL/032-hsio_mode/iob_40.v @@ -7,10 +7,7 @@ module top ( (* \xref:LOG ="q_c@0@9" *) VHI vhi_i(); - (* \xref:LOG ="q_c@0@9" *) - wire q_c; - ${cmt}(* \xref:LOG ="${primtype}=q_pad.bb_inst@0@8", \dm:cellmodel_primitives ="${primtype}=q_pad.bb_inst", \dm:primitive ="${primtype}", \dm:programming ="MODE:${primtype} ${primtype}:::IO_TYPE=${iotype},BANK_VCCIO=${vcc}${extra_config}:T=${t}", \dm:site ="${site}" *) - ${cmt}${primtype} \q_pad.bb_inst (.PADDO(q_c)); + ${cmt}${primtype} \q_pad.bb_inst (.PADDO(q)); -endmodule \ No newline at end of file +endmodule diff --git a/fuzzers/LIFCL/039-copy-io/fuzzer.py b/fuzzers/LIFCL/039-copy-io/fuzzer.py index 7f3098f..3ed226d 100644 --- a/fuzzers/LIFCL/039-copy-io/fuzzer.py +++ b/fuzzers/LIFCL/039-copy-io/fuzzer.py @@ -1,14 +1,20 @@ import database import libpyprjoxide +import database def main(): + + lilfcl_tile_types = database.get_tiletypes("LIFCL") + def get_tiletypes_with_prefix(prefix): + return [k for k in lilfcl_tile_types if k.startswith(prefix)] + db = libpyprjoxide.Database(database.get_db_root()) libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B5_1", ["SYSIO_B5_1_V18", "SYSIO_B5_1_15K_DQS51", "SYSIO_B5_1_15K_DQS50", "SYSIO_B5_1_15K_ECLK_L_V52"], "PEWC", "") libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B5_0", ["SYSIO_B5_0_15K_DQS52"], "PEWC", "") libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B4_0", ["SYSIO_B4_0_DQS1", "SYSIO_B4_0_DQS3", "SYSIO_B4_0_DLY50", "SYSIO_B4_0_DLY42", "SYSIO_B4_0_15K_DQS42", "SYSIO_B4_0_15K_BK4_V42", "SYSIO_B4_0_15K_V31"], "PEWC", "") libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B4_1", ["SYSIO_B4_1_DQS0", "SYSIO_B4_1_DQS2", "SYSIO_B4_1_DQS4", "SYSIO_B4_1_DLY52", "SYSIO_B4_1_15K_DQS41"], "PEWC", "") - libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B3_0", ["SYSIO_B3_0_DLY30_V18", "SYSIO_B3_0_DQS1", "SYSIO_B3_0_DQS3", "SYSIO_B3_0_15K_DQS32"], "PEWC", "") - libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B3_1", ["SYSIO_B3_1_DLY32", "SYSIO_B3_1_DQS0", "SYSIO_B3_1_DQS2", "SYSIO_B3_1_DQS4", "SYSIO_B3_1_ECLK_R", "SYSIO_B3_1_V18", "SYSIO_B3_1_15K_DQS30", "SYSIO_B3_1_15K_ECLK_R_DQS31"], "PEWC", "") + libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B3_0", get_tiletypes_with_prefix("SYSIO_B3_0_"), "PEWC", "") + libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B3_1", get_tiletypes_with_prefix("SYSIO_B3_1_"), "PEWC", "") libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B1_0_ODD", ["SYSIO_B1_0_C"], "C", "") libpyprjoxide.copy_db(db, "LIFCL", "SYSIO_B2_0_ODD", ["SYSIO_B2_0_C"], "C", "") diff --git a/fuzzers/LIFCL/062-lram-config/fuzzer.py b/fuzzers/LIFCL/062-lram-config/fuzzer.py index c183360..144fb12 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() diff --git a/fuzzers/LIFCL/062-lram-config/lram.v b/fuzzers/LIFCL/062-lram-config/lram.v index d943424..fa80efc 100644 --- a/fuzzers/LIFCL/062-lram-config/lram.v +++ b/fuzzers/LIFCL/062-lram-config/lram.v @@ -1,5 +1,5 @@ -(* \db:architecture ="LIFCL", \db:device ="${device}", \db:package ="QFN72", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +(* \db:architecture ="LIFCL", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) module top ( ); diff --git a/fuzzers/LIFCL/063-lram-routing/fuzzer.py b/fuzzers/LIFCL/063-lram-routing/fuzzer.py index 6c480aa..84ca3d7 100644 --- a/fuzzers/LIFCL/063-lram-routing/fuzzer.py +++ b/fuzzers/LIFCL/063-lram-routing/fuzzer.py @@ -1,8 +1,26 @@ from fuzzconfig import FuzzConfig from interconnect import fuzz_interconnect + +import database import re -configs = [ +lifcl33_sites = { + 'R4C50':["CIB_R4C51:LRAM_0_33K", "CIB_R4C1:CIB_LR"], + 'R20C50':["CIB_R20C51:LRAM_1_33K", "CIB_R20C1:CIB_LR"], + 'R34C50':["CIB_R34C51:LRAM_2_33K", "CIB_R34C1:CIB_LR"] , + 'R47C50':["CIB_R47C51:LRAM_3_33K", "CIB_R47C1:CIB_LR"], + 'R65C50':["CIB_R65C51:LRAM_4_33K", "CIB_R65C1:CIB_LR"] +} + + +def create_config(site, tiles, device): + lram = tiles[0].split(":")[1].replace("_", "") + rc = site[1:].split("C") + return { "cfg": FuzzConfig(job=f"{lram}_{device}", device=f"LIFCL-{device}", sv=f"../shared/route_{device}.v", tiles=tiles), "rc": rc } + + +configs = [ create_config(site, tiles, "33") for (site, tiles) in lifcl33_sites.items() ] +\ +[ { "cfg": FuzzConfig(job="LRAMROUTE0", device="LIFCL-40", sv="../shared/route_40.v", tiles=["CIB_R23C87:LRAM_0"]), "rc": (18, 86), @@ -34,7 +52,7 @@ }, ] -ignore_tiles = set([ +ignore_tiles_40 = set([ "CIB_R{}C86:CIB_LR".format(c) for c in range(2, 55) ] + [ "CIB_R19C86:CIB_LR_A", @@ -64,7 +82,15 @@ def main(): nodes = ["R{}C{}_*".format(r, c)] def nodename_filter(x, nodes): return ("R{}C{}_".format(r, c) in x) and ("LRAM_CORE" in x) - fuzz_interconnect(config=cfg, nodenames=nodes, nodename_predicate=nodename_filter, regex=True, bidir=True, ignore_tiles=ignore_tiles_17 if cfg.device == "LIFCL-17" else ignore_tiles) + + tg = database.get_tilegrid(cfg.device)["tiles"] + ignore_tiles = [tile for tile in tg if "CIB_LR" in tile or "CIB_T" in tile] + if cfg.device == "LIFCL-17": + ignore_tiles = ignore_tiles_17 + elif cfg.device == "LIFCL-40": + ignore_tiles = ignore_tiles_40 + + fuzz_interconnect(config=cfg, nodenames=nodes, nodename_predicate=nodename_filter, regex=True, bidir=True, ignore_tiles=ignore_tiles) if __name__ == "__main__": main() diff --git a/fuzzers/LIFCL/071-iodelay/fuzzer.py b/fuzzers/LIFCL/071-iodelay/fuzzer.py index 51a425d..23a7858 100644 --- a/fuzzers/LIFCL/071-iodelay/fuzzer.py +++ b/fuzzers/LIFCL/071-iodelay/fuzzer.py @@ -3,7 +3,23 @@ import fuzzloops import re -configs = [ +import tiles + + +def create_cfgs(device): + cfgs = [] + for primitive in ["IOLOGIC_CORE", "SIOLOGIC_CORE"]: + for (tiletype, infos) in tiles.get_tiletypes_by_primitive(device, "IOLOGIC_CORE").items(): + if tiletype.startswith("SYSIO"): + print(f"Adding {device} {infos[0]}") + cfgs.append( + (infos[0][0], primitive, FuzzConfig(job=f"{device}_{infos[0][0]}_{infos[0][1]}", device=device, tiles=infos[0][1])) + ) + return cfgs + + + +configs = create_cfgs("LIFCL-33") + [ ("IOL_B8A", "IOLOGICA", FuzzConfig(job="IOL5AMODE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C8:SYSIO_B5_0", "CIB_R56C9:SYSIO_B5_1"])), ("IOL_B8B", "IOLOGICB", FuzzConfig(job="IOL5BMODE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C8:SYSIO_B5_0", "CIB_R56C9:SYSIO_B5_1"])), ("IOL_B18A", "IOLOGICA", FuzzConfig(job="IOL4AMODE", device="LIFCL-40", sv="../shared/empty_40.v", tiles=["CIB_R56C18:SYSIO_B4_0", "CIB_R56C19:SYSIO_B4_1"])), @@ -99,7 +115,6 @@ def intval(vec): nonrouting.fuzz_enum_setting(cfg, empty, "{}.DELAY.WAIT_FOR_EDGE".format(prim), ["ENABLED", "DISABLED"], lambda x: get_substs(kv=("WAIT_FOR_EDGE", x)), False) - if not s: for pin in ["CIBCRS0", "CIBCRS1", "RANKSELECT", "RANKENABLE", "RANK0UPDATE", "RANK1UPDATE"]: nonrouting.fuzz_enum_setting(cfg, empty, "{}.{}MUX".format(prim, pin), ["OFF", pin], lambda x: get_substs(kv=(pin, x), mux=True), False) diff --git a/fuzzers/LIFCL/071-iodelay/iodelay.v b/fuzzers/LIFCL/071-iodelay/iodelay.v index 3e65137..eefcd74 100644 --- a/fuzzers/LIFCL/071-iodelay/iodelay.v +++ b/fuzzers/LIFCL/071-iodelay/iodelay.v @@ -1,5 +1,5 @@ -(* \db:architecture ="LIFCL", \db:device ="LIFCL-40", \db:package ="QFN72", \db:speed ="7_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) +(* \db:architecture ="LIFCL", \db:device ="${device}", \db:package ="${package}", \db:speed ="${speed_grade}_High-Performance_1.0V", \db:timestamp =1576073342, \db:view ="physical" *) module top ( ); diff --git a/fuzzers/LIFCL/091-osc/fuzzer.py b/fuzzers/LIFCL/091-osc/fuzzer.py index 0c3722a..0a58d1e 100644 --- a/fuzzers/LIFCL/091-osc/fuzzer.py +++ b/fuzzers/LIFCL/091-osc/fuzzer.py @@ -2,10 +2,13 @@ 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"]), ] @@ -13,10 +16,7 @@ def main(): for cfg in cfgs: cfg.setup() empty = cfg.build_design(cfg.sv, {}) - if cfg.device == "LIFCL-17": - cfg.sv = "osc_17.v" - else: - cfg.sv = "osc.v" + cfg.sv = "osc.v" def bin_to_int(x): val = 0 @@ -27,6 +27,9 @@ def bin_to_int(x): mul *= 2 return val + sites = tiles.get_sites_from_primitive(cfg.device, "OSC_CORE") + site = list(sites.keys())[0] + def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): if kv is None: config = "" @@ -39,7 +42,7 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): config = "{}::::{}={}".format(mode, kv[0], val) else: config = "{}:::{}={}".format(mode, kv[0], kv[1]) - return dict(mode=mode, cmt="//" if mode == "NONE" else "", config=config) + return dict(mode=mode, cmt="//" if mode == "NONE" else "", config=config, site=site) nonrouting.fuzz_enum_setting(cfg, empty, "OSC_CORE.MODE", ["NONE", "OSC_CORE"], lambda x: get_substs(mode=x), False, desc="OSC_CORE primitive mode") @@ -49,6 +52,7 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): nonrouting.fuzz_word_setting(cfg, "OSC_CORE.HF_SED_SEC_DIV", 8, lambda x: get_substs(mode="OSC_CORE", kv=("HF_SED_SEC_DIV", str(bin_to_int(x)))), desc="high frequency oscillator output divider") + nonrouting.fuzz_enum_setting(cfg, empty, "OSC_CORE.DTR_EN", ["ENABLED", "DISABLED"], lambda x: get_substs(mode="OSC_CORE", kv=("DTR_EN", x)), False, desc="") @@ -70,15 +74,24 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): nonrouting.fuzz_enum_setting(cfg, empty, "OSC_CORE.DEBUG_N", ["ENABLED", "DISABLED"], lambda x: get_substs(mode="OSC_CORE", kv=("DEBUG_N", x)), False, desc="enable debug mode") + + rc = tiles.get_rc_from_name(cfg.device, cfg.tiles[0]) # Fuzz oscillator routing + regex = False + full_mux = False if cfg.device == "LIFCL-17": cfg.sv = "../shared/route_17.v" - nodes = ["R1C71_JLFCLKOUT_OSC_CORE", "R1C71_JHFCLKOUT_OSC_CORE", - "R1C71_JHFSDCOUT_OSC_CORE", "R1C71_JHFCLKCFG_OSC_CORE", - "R1C71_JHFOUTEN_OSC_CORE", "R1C71_JHFSDSCEN_OSC_CORE"] - for i in range(9): - nodes.append("R1C71_JHFTRMFAB{}_OSC_CORE".format(i)) - nodes.append("R1C71_JLFTRMFAB{}_OSC_CORE".format(i)) + regex = True + nodes = [".*_OSC_CORE"] + elif cfg.device.startswith("LIFCL-33"): + #cfg.sv = "osc_pins.v" + cfg.sv = "../shared/route_33.v" + regex = True + nodes = [".*_OSC_CORE" ] + full_mux = True +# DTR_EN:#ON HF_CLK_DIV:::HF_CLK_DIV=+1 HF_SED_SEC_DIV:::HF_SED_SEC_DIV=+1 HF_FABRIC_EN:#ON HF_OSC_EN:#ON HFDIV_FABRIC_EN:#ON LF_FABRIC_EN:#ON LF_OUTPUT_EN:#ON DEBUG_N:#ON +# OSC_CORE:::LF_FABRIC_EN=ENABLED OSC_CORE:::HF_FABRIC_EN=ENABLED OSC_CORE:::DTR_EN=ENABLED OSC_CORE:::HF_OSC_EN=ENABLED OSC_CORE:::HFDIV_FABRIC_EN=ENABLED +#fuzz_interconnect_pins(cfg, "OSC_CORE_R1C29", {"config": "OSC_CORE:::LF_FABRIC_EN=ENABLED OSC_CORE:::HF_FABRIC_EN=ENABLED OSC_CORE:::DTR_EN=ENABLED OSC_CORE:::HF_OSC_EN=ENABLED OSC_CORE:::HFDIV_FABRIC_EN=ENABLED OSC_CORE:::DEBUG_N=DISABLED OSC_CORE:::HF_CLK_DIV=1 OSC_CORE:::HF_SED_SEC_DIV=1"}) else: cfg.sv = "../shared/route_40.v" nodes = ["R1C77_JLFCLKOUT_OSC_CORE", "R1C77_JHFCLKOUT_OSC_CORE", @@ -87,7 +100,8 @@ def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): for i in range(9): nodes.append("R1C77_JHFTRMFAB{}_OSC_CORE".format(i)) nodes.append("R1C77_JLFTRMFAB{}_OSC_CORE".format(i)) - fuzz_interconnect(config=cfg, nodenames=nodes, regex=False, bidir=True, full_mux_style=False) + fuzz_interconnect(config=cfg, nodenames=nodes, regex=regex, bidir=True, full_mux_style=full_mux) + if __name__ == '__main__': main() diff --git a/fuzzers/LIFCL/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/093-oscd/fuzzer.py b/fuzzers/LIFCL/093-oscd/fuzzer.py new file mode 100644 index 0000000..44a0ced --- /dev/null +++ b/fuzzers/LIFCL/093-oscd/fuzzer.py @@ -0,0 +1,57 @@ +from fuzzconfig import FuzzConfig +import nonrouting +import fuzzloops +import re +from interconnect import fuzz_interconnect, fuzz_interconnect_pins +import tiles + +from primitives import oscd_core + +cfgs = [ + FuzzConfig(job="OSCD", device="LIFCL-33U", tiles=["CIB_R0C29:OSCD"]), +] + +def main(): + for cfg in cfgs: + cfg.setup() + empty = cfg.build_design(cfg.sv, {}) + cfg.sv = "oscd.v" + + def bin_to_int(x): + val = 0 + mul = 1 + for bit in x: + if bit: + val |= mul + mul *= 2 + return val + + sites = tiles.get_sites_from_primitive(cfg.device, "OSCD_CORE") + site = list(sites.keys())[0] + + def get_substs(mode="NONE", default_cfg=False, kv=None, mux=False): + if kv is None: + config = "" + elif mux: + val = "#SIG" + if kv[1] in ("0", "1"): + val = kv[1] + if kv[1] == "INV": + val = "#INV" + config = "{}::::{}={}".format(mode, kv[0], val) + else: + config = "{}:::{}={}".format(mode, kv[0], kv[1]) + return dict(mode=mode, cmt="//" if mode == "NONE" else "", config=config, site=site) + + nonrouting.fuzz_primitive_definition(cfg, empty, site, oscd_core) + + cfg.sv = "../shared/route.v" + regex = True + nodes = [".*_OSCD_CORE" ] + full_mux = True + + fuzz_interconnect(config=cfg, nodenames=nodes, regex=regex, bidir=True, full_mux_style=full_mux) + + +if __name__ == '__main__': + main() diff --git a/fuzzers/LIFCL/093-oscd/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..df5c21c 100644 --- a/fuzzers/LIFCL/100-ip-base/fuzzer.py +++ b/fuzzers/LIFCL/100-ip-base/fuzzer.py @@ -26,6 +26,61 @@ # ("LRAM_CORE_R40C86", "LRAM_CORE"), # ] # ), + (fuzzconfig.FuzzConfig(job="IPADDR33", device="LIFCL-33", sv="ip33.v", tiles=[]), + [ + ("PLL_LLC", "PLL_CORE"), +# ("PLL_LRC", "PLL_CORE"), +# ("PMU_CORE_R1C70", "PMU_CORE"), +# ("SGMIICDR_CORE_R28C5", "SGMIICDR_CORE"), +# ("SGMIICDR_CORE_R28C4", "SGMIICDR_CORE"), + ("I2CFIFO_CORE_R1C37", "I2CFIFO_CORE"), + ("EBR_CORE_R10C5", "EBR_CORE_WID0"), + ("EBR_CORE_R10C5", "EBR_CORE_WID1"), + ("EBR_CORE_R10C5", "EBR_CORE_WID2047"), + ("LRAM_CORE_R2C1", "LRAM_CORE"), + ("LRAM_CORE_R11C1", "LRAM_CORE"), + ("LRAM_CORE_R20C1", "LRAM_CORE"), + ("LRAM_CORE_R15C74", "LRAM_CORE"), + ("LRAM_CORE_R16C74", "LRAM_CORE"), + ] + ), + (fuzzconfig.FuzzConfig(job="IPADDR33U", device="LIFCL-33U", sv="ip_33u.v", tiles=[]), + [ + ("PLL_LLC", "PLL_CORE"), +# ("PLL_LRC", "PLL_CORE"), +# ("PMU_CORE_R1C70", "PMU_CORE"), +# ("SGMIICDR_CORE_R28C5", "SGMIICDR_CORE"), +# ("SGMIICDR_CORE_R28C4", "SGMIICDR_CORE"), + ("I2CFIFO_CORE_R1C37", "I2CFIFO_CORE"), + ("EBR_CORE_R10C5", "EBR_CORE_WID0"), + ("EBR_CORE_R10C5", "EBR_CORE_WID1"), + ("EBR_CORE_R10C5", "EBR_CORE_WID2047"), + ("LRAM_CORE_R2C1", "LRAM_CORE"), + ("LRAM_CORE_R11C1", "LRAM_CORE"), + ("LRAM_CORE_R20C1", "LRAM_CORE"), + ("LRAM_CORE_R15C74", "LRAM_CORE"), + ("LRAM_CORE_R16C74", "LRAM_CORE"), + ] + ), + + # (fuzzconfig.FuzzConfig(job="IPADDR33U", device="LIFCL-33U", sv="ip_33u.v", tiles=[]), + # [ + # ("PLL_LLC", "PLL_CORE"), + # # ("PLL_LRC", "PLL_CORE"), + # # ("PMU_CORE_R1C70", "PMU_CORE"), + # # ("SGMIICDR_CORE_R28C5", "SGMIICDR_CORE"), + # # ("SGMIICDR_CORE_R28C4", "SGMIICDR_CORE"), + # # ("I2CFIFO_CORE_R1C72", "I2CFIFO_CORE"), + # # ("EBR_CORE_R10C5", "EBR_CORE_WID0"), + # # ("EBR_CORE_R10C5", "EBR_CORE_WID1"), + # # ("EBR_CORE_R10C5", "EBR_CORE_WID2047"), + # # ("LRAM_CORE_R2C1", "LRAM_CORE"), + # # ("LRAM_CORE_R11C1", "LRAM_CORE"), + # # ("LRAM_CORE_R20C1", "LRAM_CORE"), + # # ("LRAM_CORE_R15C74", "LRAM_CORE"), + # # ("LRAM_CORE_R16C74", "LRAM_CORE"), + # ] + # ), (fuzzconfig.FuzzConfig(job="IPADDR17", device="LIFCL-17", sv="ip.v", tiles=[]), [ ("DPHY0", "DPHY_CORE"), 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..0d57a3a 100644 --- a/fuzzers/LIFCL/110-global-structure/fuzzer.py +++ b/fuzzers/LIFCL/110-global-structure/fuzzer.py @@ -1,18 +1,27 @@ import database import lapie import json -from fuzzconfig import FuzzConfig +from fuzzconfig import FuzzConfig, should_fuzz_platform from tiles import pos_from_name from os import path +import database +from collections import defaultdict +import tiles # name max_row max_col + configs = [ - ("LIFCL-40", 56, 87, "../shared/empty_40.v"), - ("LIFCL-17", 29, 75, "../shared/empty_17.v"), + ("LIFCL-33", "../shared/empty_33.v"), + ("LIFCL-33U", "../shared/empty_33u.v"), + ("LIFCL-40", "../shared/empty_40.v"), + ("LIFCL-17", "../shared/empty_17.v"), ] def main(): - for name, max_row, max_col, sv in configs: + for name, sv in configs: + if not should_fuzz_platform(name): + continue + cfg = FuzzConfig(job="GLOBAL_{}".format(name), device=name, sv=sv, tiles=[]) cfg.setup() db_path = path.join(database.get_db_root(), "LIFCL", name, "globals.json") @@ -26,11 +35,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 = {} + branch_to_col = defaultdict(list) for n in clock_info: r, c = pos_from_name(n.name) hpbx_c = None @@ -40,12 +58,11 @@ def save_db(): assert hpbx_r == r break assert hpbx_c is not None - if hpbx_c not in branch_to_col: - branch_to_col[hpbx_c] = [] branch_to_col[hpbx_c].append(c) branches = [] - branch_wires = ["R{}C{}_HPBX0000".format(test_row, bc) for bc in sorted(branch_to_col.keys())] + # Trace back the nodes which connect the tap to the spine + branch_wires = [f"R{test_row}C{bc}_HPBX0000" for bc in sorted(branch_to_col.keys())] if name == "LIFCL-17": branch_wires.append("R{}C13_RHPBX0000".format(test_row)) branch_wire_info = lapie.get_node_data(cfg.udb, branch_wires) @@ -69,10 +86,13 @@ def save_db(): for uh in bw.uphill_pips: if "HPBX0" in uh.from_wire: hpbx_r, hpbx_c = pos_from_name(uh.from_wire) - branch_driver_col[c] = branch_driver_col[hpbx_c] + branch_driver_col[c] = branch_driver_col[hpbx_c] for bc, scs in sorted(branch_to_col.items()): - tap_drv_col = branch_driver_col[bc] + 1 - side = "R" if tap_drv_col < bc else "L" + tap_drv_distances = [(abs(x - branch_driver_col[bc]), x) for x in tap_plcs] + tap_drv_col = min(tap_drv_distances)[1] + side = "R" if branch_driver_col[bc] < bc else "L" + if tap_drv_col in tap_plcs: + print("Tap drv col", tap_drv_col, bc, sorted(scs)) branches.append(dict(branch_col=bc, tap_driver_col=tap_drv_col, tap_side=side, from_col=min(scs), to_col=max(scs))) gdb["branches"] = branches save_db() diff --git a/fuzzers/LIFCL/900-always-on/fuzzer.py b/fuzzers/LIFCL/900-always-on/fuzzer.py index b2c997e..c860a48 100644 --- a/fuzzers/LIFCL/900-always-on/fuzzer.py +++ b/fuzzers/LIFCL/900-always-on/fuzzer.py @@ -8,9 +8,11 @@ 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(): + db = fuzzconfig.get_db() for cfg in cfgs: cfg.setup() empty = cfg.build_design(cfg.sv, {}) 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/generate_database.sh b/generate_database.sh new file mode 100755 index 0000000..d8767a0 --- /dev/null +++ b/generate_database.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +. user_environment.sh + +pushd tools +python3 tilegrid_all.py +popd + +pushd fuzzers +for dir in LIFCL/* ; do + if [ -f "$dir/fuzzer.py" ]; then + echo "=================== Entering $dir ===================" + pushd $dir + ../../../link-db-root.sh + PRJOXIDE_DB=`pwd`/db python3 fuzzer.py 2>&1 | tee >(gzip --stdout > fuzzer.log.gz) + popd + fi +done 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..a9c10d9 100644 --- a/libprjoxide/prjoxide/src/bba/tileloc.rs +++ b/libprjoxide/prjoxide/src/bba/tileloc.rs @@ -13,6 +13,7 @@ use std::collections::{BTreeSet, HashMap}; use std::convert::TryInto; use std::iter::FromIterator; use regex::Regex; +use crate::bba::tiletype::Neighbour::RelXY; lazy_static! { static ref LUT_INPUT_RE: Regex = Regex::new(r"^J([ABCD])([01])_SLICE([ABCD])$").unwrap(); @@ -38,7 +39,13 @@ impl TileLocation { let mut tiletypes: Vec = tiles .iter() .map(|t| t.tiletype.to_string()) - .filter(|tt| tts.get(tt).unwrap().has_routing()) + .filter(|tt| { + let has_routing = tts.get(tt).unwrap().has_routing(); + if !has_routing { + println!("Warning: {tt} has no routing and has been omitted. Please check it's PIPs and connections."); + } + has_routing + }) .collect(); // (0, 0) is a special case as we keep all the global signals here, // but don't want to pollute other null tiles @@ -75,7 +82,8 @@ pub struct LocationGrid { pub height: usize, tiles: Vec, glb: DeviceGlobalsData, - iodb: DeviceIOData + iodb: DeviceIOData, + device: String } impl LocationGrid { @@ -94,6 +102,7 @@ impl LocationGrid { tiles: locs, glb: globals.clone(), iodb: iodb, + device: ch.device.to_string() } } pub fn get(&self, x: usize, y: usize) -> Option<&TileLocation> { @@ -137,22 +146,39 @@ impl LocationGrid { } } Neighbour::Branch => { - let branch_col = self.glb.branch_sink_to_origin(x).unwrap(); - Some((branch_col, y)) + self.glb.branch_sink_to_origin(x).map(|x| (x, y)) } Neighbour::BranchDriver { side } => { - let offset: i32 = match side { - BranchSide::Right => 2, - BranchSide::Left => -2, + let tap_side = match side { + BranchSide::Right => "R", + BranchSide::Left => "L", }; - let branch_col = self - .glb - .branch_sink_to_origin((x as i32 + offset) as usize) - .unwrap(); - Some((branch_col, y)) + self.glb.branches + .iter() + .find(|b| x == b.tap_driver_col && tap_side == b.tap_side) + .map(|b| (b.branch_col, y)) + // + // self + // .glb + // .branch_sink_to_origin((x as i32 + offset) as usize) + // .map(|x| (x, y)) } - Neighbour::Spine => return Some(self.glb.spine_sink_to_origin(x, y).unwrap()), - Neighbour::HRow => return Some(self.glb.hrow_sink_to_origin(x, y).unwrap()), + Neighbour::Spine => { + let origin = self.glb.spine_sink_to_origin(x, y); + if origin.is_none() { + println!("WARNING: Branch at {x} {y} can not resolve the spine origin") + } + origin + }, + Neighbour::HRow => { + let origin = self.glb.hrow_sink_to_origin(x, y); + if origin.is_none() { + println!("WARNING: Spine at {x} {y} can not resolve the HRow origin"); + } else { + println!("INFO: Spine at {x} {y} resolved to {origin:?}"); + } + origin + }, _ => None, } } @@ -160,15 +186,23 @@ impl LocationGrid { pub fn stamp_neighbours(&mut self) { for y in 0..self.height { for x in 0..self.width { - let neighbours: Vec = self - .get(x, y) - .unwrap() + let tile = self.get(x, y).unwrap(); + let tiletypes = tile.tiletypes.clone(); + let neighbours: Vec = tile .neighbours .iter() .map(|x| x.0.clone()) .collect(); + for n in neighbours { let nt = self.neighbour_tile(x, y, &n); + match (n.clone(), nt) { + (RelXY {rel_x: _, rel_y: _}, None) => {} + (n, None) => { + println!("Could not resolve the neighbor {n:?} at {x} {y} for {tiletypes:?}") + } + _ => {} + } if let Some((nx, ny)) = nt { let other = self.get_mut(nx as usize, ny as usize).unwrap(); other.neighbours.insert(( @@ -336,7 +370,7 @@ impl LocationGrid { out.list_begin(&format!("d{}_packages", device_idx))?; for package in self.iodb.packages.iter() { - out.package_info(package, &Chip::get_package_short_name(package))?; + out.package_info(package, &Chip::get_package_short_name(package, self.device.as_str()))?; } Ok(()) diff --git a/libprjoxide/prjoxide/src/bels.rs b/libprjoxide/prjoxide/src/bels.rs index dc627c7..c33fc0a 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,77 @@ 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(("".to_string(), 0, 0)); + + let enum_bels = tiledata.enums.iter().flat_map(|(key, _value)|match key.as_str() { + "PIOA.BASE_TYPE" => vec![Bel::make_seio33(0)], + "PIOB.BASE_TYPE" => vec![Bel::make_seio33(1)], + "PIOA.SEIO18.BASE_TYPE" => vec![Bel::make_seio18(0)], + "PIOB.SEIO18.BASE_TYPE" => vec![Bel::make_seio18(1)], + "PIOA.DIFFIO18.BASE_TYPE" => vec![Bel::make_diffio18()], + "PIOB.DIFFIO18.BASE_TYPE" => vec![Bel::make_diffio18()], + "SIOLOGICA.GSR" => vec![Bel::make_iol(tiledata, true, 0)], + "SIOLOGICB.GSR" => vec![Bel::make_iol(tiledata, true, 1)], + "IOLOGICA.GSR" => vec![Bel::make_iol(tiledata, false, 0)], + "IOLOGICB.GSR" => vec![Bel::make_iol(tiledata, false, 1)], + _ => vec![] + }).map(|x| { + x.with_rel((bel_relative_location.1, bel_relative_location.2)) + }); + let inferred_bels = enum_bels.collect_vec(); + if inferred_bels.is_empty() { + debug!("No BEL for tile type {}", &stt); + } + inferred_bels + }, } } // Get the tiles that a bel's configuration might be split across -pub fn get_bel_tiles(chip: &Chip, tile: &Tile, bel: &Bel) -> Vec { +pub fn get_bel_tiles(chip: &Chip, tile: &Tile, bel: &Bel, rel: &Option<(String, i32, i32)>) -> Vec { let tn = tile.name.to_string(); let rel_tile = |dx: i32, dy: i32, tt: &str| { chip.tile_by_xy_type((tile.x as i32 + dx).try_into().unwrap(), (tile.y as i32 + dy).try_into().unwrap(), tt).unwrap().name.to_string() }; - let rel_tile_prefix = |dx, dy, tt_prefix| { - for tile in chip.tiles_by_xy(tile.x + dx, tile.y + dy).iter() { + let rel_tile_prefix = |dx:i32, dy:i32, tt_prefix| { + let nx = tile.x.checked_add_signed(dx).unwrap(); + let ny = tile.y.checked_add_signed(dy).unwrap(); + for tile in chip.tiles_by_xy(nx, ny).iter() { if tile.tiletype.starts_with(tt_prefix) { return tile.name.to_string(); } } - panic!("no tile matched prefix ({}, {}, {})", tile.x + dx, tile.y + dy, tt_prefix); + warn!("no tile matched prefix ({}, {}, {}) for {}", nx, ny, tt_prefix, tile.name); + "".to_string() }; let rel_tile_suffix = |dx, dy, tt_suffix| { - for tile in chip.tiles_by_xy(tile.x + dx, tile.y + dy).iter() { + let nx = tile.x.checked_add_signed(dx).unwrap(); + let ny = tile.y.checked_add_signed(dy).unwrap(); + for tile in chip.tiles_by_xy(nx, ny).iter() { if tile.tiletype.ends_with(tt_suffix) { return tile.name.to_string(); } } - panic!("no tile matched suffix ({}, {}, {})", tile.x + dx, tile.y + dy, tt_suffix); + warn!("no tile matched suffix ({}, {}, {}) for {}", nx, ny, tt_suffix, tile.name); + "".to_string() }; + if let Some(rel_offset) = rel { + return vec![rel_tile_suffix(rel_offset.1, rel_offset.2, "")]; + } + let tt = &tile.tiletype[..]; match &bel.beltype[..] { "SEIO33_CORE" | "SIOLOGIC" => match tt { diff --git a/libprjoxide/prjoxide/src/bin/prjoxide.rs b/libprjoxide/prjoxide/src/bin/prjoxide.rs index 8eca597..301eb5b 100644 --- a/libprjoxide/prjoxide/src/bin/prjoxide.rs +++ b/libprjoxide/prjoxide/src/bin/prjoxide.rs @@ -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..7ae3a25 100644 --- a/libprjoxide/prjoxide/src/database.rs +++ b/libprjoxide/prjoxide/src/database.rs @@ -1,12 +1,27 @@ +use itertools::Itertools; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet, HashMap}; -use std::fmt; +use std::{env, fmt}; use std::fs::File; use std::io::prelude::*; use std::path::Path; +use log::{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 */ + if env::var("PRJOXIDE_ALLOW_BIT_CHANGE").is_ok() { + warn!($($arg)*); + } else { + panic!($($arg)*); + } + }; +} + #[derive(Deserialize)] pub struct DevicesDatabase { pub families: BTreeMap, @@ -35,6 +50,7 @@ pub struct DeviceData { pub col_bias: u32, pub fuzz: bool, pub variants: BTreeMap, + pub tap_frame_count: usize } // Deserialization of 'tilegrid.json' @@ -44,7 +60,7 @@ pub struct DeviceTilegrid { pub tiles: BTreeMap, } -#[derive(Deserialize)] +#[derive(Serialize, Deserialize)] pub struct TileData { pub tiletype: String, pub x: u32, @@ -117,21 +133,21 @@ impl DeviceGlobalsData { && self.spines.iter().any(|s| s.spine_row == y) } pub fn spine_sink_to_origin(&self, x: usize, y: usize) -> Option<(usize, usize)> { - match self - .hrows - .iter() - .map(|h| h.spine_cols.iter()) - .flatten() - .find(|c| ((x as i32) - (**c as i32)).abs() < 3) - { - None => None, - Some(spine_col) => self - .spines - .iter() - .find(|s| y >= s.from_row && y <= s.to_row) - .map(|s| (*spine_col, s.spine_row)), - } + let spine_column = self.hrows.iter() + .flat_map(|x|x.spine_cols.clone()) + .map(|c| (x.abs_diff(c), c)) + .sorted() + .map(|x| x.1) + .next(); + + let spine_data = + self.spines.iter() + .find(|s| y >= s.from_row && y <= s.to_row); + + spine_data.zip(spine_column) + .map(|(spine, spine_col)| (spine_col, spine.spine_row)) } + pub fn is_hrow_loc(&self, x: usize, y: usize) -> bool { self.hrows.iter().any(|h| h.hrow_col == x) && self.spines.iter().any(|s| s.spine_row == y) } @@ -284,6 +300,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<(String, i32, i32)>, } impl TileBitsDatabase { @@ -324,23 +346,61 @@ impl TileBitsData { dirty: false, } } + + pub fn merge(&mut self, other_db: TileBitsDatabase) { + for (to, pip_data) in other_db.pips.iter() { + for from in pip_data.iter() { + self.add_pip(&from.from_wire, to, from.bits.clone()); + } + } + for (word, word_config) in other_db.words.iter() { + self.add_word(word, &*word_config.desc, word_config.bits.clone()); + }; + for (enm, enum_config) in other_db.enums.iter() { + for (option, option_bits) in enum_config.options.iter() { + self.add_enum_option(enm, option, &*enum_config.desc, option_bits.clone()); + } + } + + for (to, from_wires) in other_db.conns { + for from in from_wires { + self.add_conn(&*from.from_wire, &*to); + if from.bidir { + self.add_conn(&*to, &*from.from_wire); + } + } + } + + for external_tile_configs in other_db.tile_configures_external_tiles { + self.set_bel_offset(Some(external_tile_configs)); + } + } + pub fn add_pip(&mut self, from: &str, to: &str, bits: BTreeSet) { if !self.db.pips.contains_key(to) { + info!("Inserting new pip destination {to}"); self.db.pips.insert(to.to_string(), Vec::new()); } let ac = self.db.pips.get_mut(to).unwrap(); - for ad in ac.iter() { + 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; + return; } + + debug!("Pip {from} -> {to} already exists for {}", self.tiletype); return; } } self.dirty = true; + info!("Inserting new pip {from} -> {to} for {}", self.tiletype); ac.push(ConfigPipData { from_wire: from.to_string(), bits: bits.clone(), @@ -363,7 +423,7 @@ impl TileBitsData { word.desc = desc.to_string(); } if bits.len() != word.bits.len() { - panic!( + emit_bit_change_error!( "Width conflict {}.{} existing: {:?} new: {:?}", self.tiletype, name, @@ -373,7 +433,7 @@ impl TileBitsData { } for (bit, (e, n)) in word.bits.iter().zip(bits.iter()).enumerate() { if e != n { - panic!( + emit_bit_change_error!( "Bit conflict for {}.{}[{}] existing: {:?} new: {:?}", self.tiletype, name, bit, e, n ); @@ -382,12 +442,28 @@ impl TileBitsData { } } } + + pub fn set_bel_offset(&mut self, bel_relative_location : Option<(String, i32, i32)>) { + if !self.db.tile_configures_external_tiles.is_empty() && + self.db.tile_configures_external_tiles.iter().next() != bel_relative_location.as_ref() { + emit_bit_change_error!( + "Bel offset conflict for {}. existing: {:?} new: {:?}", + self.tiletype, self.db.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; + } pub fn add_enum_option( &mut self, name: &str, option: &str, desc: &str, - bits: BTreeSet, + bits: BTreeSet ) { if !self.db.enums.contains_key(name) { self.db.enums.insert( @@ -403,13 +479,16 @@ impl TileBitsData { ec.desc = desc.to_string(); self.dirty = true; } - match ec.options.get(option) { + match ec.options.get_mut(option) { Some(old_bits) => { if bits != *old_bits { - 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.dirty = true; } } None => { @@ -425,7 +504,9 @@ impl TileBitsData { let pc = self.db.conns.get_mut(to).unwrap(); if pc.iter().any(|fc| fc.from_wire == from) { // Connection already exists + 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(), @@ -616,6 +697,7 @@ impl Database { enums: BTreeMap::new(), conns: BTreeMap::new(), always_on: BTreeSet::new(), + tile_configures_external_tiles : BTreeSet::new(), } }; self.tilebits @@ -639,6 +721,7 @@ impl Database { enums: BTreeMap::new(), conns: BTreeMap::new(), always_on: BTreeSet::new(), + tile_configures_external_tiles : BTreeSet::new(), } }; self.ipbits diff --git a/libprjoxide/prjoxide/src/fuzz.rs b/libprjoxide/prjoxide/src/fuzz.rs index 7d76cbb..6e18f44 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,13 @@ pub enum FuzzMode { include_zeros: bool, // if true, explicit 0s instead of base will be created for unset bits for a setting disambiguate: bool, // add explicit 0s to disambiguate settings only assume_zero_base: bool, - }, + mark_relative_to: Option, // Track relative to this tile + } } -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] +#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] enum FuzzKey { - PipKey { from_wire: String }, + PipKey { from_wire: String, allow_partial_deltas: bool }, WordKey { bit: usize }, EnumKey { option: String }, } @@ -91,13 +100,15 @@ impl Fuzzer { desc: &str, include_zeros: bool, assume_zero_base: bool, + mark_relative_to: Option ) -> Fuzzer { Fuzzer { mode: FuzzMode::Enum { name: name.to_string(), - include_zeros: include_zeros, + include_zeros, disambiguate: false, // fixme - assume_zero_base: assume_zero_base, + assume_zero_base, + mark_relative_to }, tiles: fuzz_tiles.clone(), base: base_bit.clone(), @@ -105,9 +116,7 @@ impl Fuzzer { desc: desc.to_string(), } } - fn add_sample(&mut self, db: &mut Database, key: FuzzKey, bitfile: &str) { - let parsed_bitstream = BitstreamParser::parse_file(db, bitfile).unwrap(); - let delta: ChipDelta = parsed_bitstream.delta(&self.base); + fn add_sample_delta(&mut self, key: FuzzKey, delta: ChipDelta) { if let Some(d) = self.deltas.get_mut(&key) { // If key already in delta, take the intersection of the two let intersect: ChipDelta = d @@ -126,11 +135,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, ); @@ -147,126 +183,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, + ); + 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(), @@ -295,8 +377,10 @@ impl Fuzzer { include_zeros, disambiguate: _, assume_zero_base, + mark_relative_to } => { 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 +410,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 +420,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 +437,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 +459,24 @@ impl Fuzzer { }; // Add the enum to the tile data let tile_data = self.base.tile_by_name(&tile).unwrap(); + info!("Resolved {} {} {:?} {}", name, option, b, &tile_data.tiletype); + let tile_db = db.tile_bitdb(&self.base.family, &tile_data.tiletype); - tile_db.add_enum_option(name, &option, &self.desc, b); + + tile_db.add_enum_option(&name, &option, &self.desc, b); + + if let Some(relative_tile) = mark_relative_to.clone() { + let ref_tile = self.base.tile_by_name(&relative_tile).unwrap(); + let offset = { + (ref_tile.tiletype.clone(), + ref_tile.x as i32 - tile_data.x as i32, + ref_tile.y as i32 - tile_data.y as i32) + }; + + tile_db.set_bel_offset(Some(offset.clone())); + }; + } } } diff --git a/libprjoxide/prjoxide/src/ipfuzz.rs b/libprjoxide/prjoxide/src/ipfuzz.rs index d9cac04..e1de77f 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); @@ -177,4 +188,20 @@ impl IPFuzzer { } db.flush(); } + + pub fn serialize_deltas(&mut self, filename: &str) { + let pretty = PrettyConfig { + depth_limit: 5, + new_line: "\n".to_string(), + indentor: " ".to_string(), + enumerate_arrays: false, + separate_tuple_members: false, + }; + + let buf = ron::ser::to_string_pretty(&self.deltas, pretty).unwrap(); + File::create(format!("{}.ron", filename)) + .unwrap() + .write_all(buf.as_bytes()) + .unwrap(); + } } diff --git a/libprjoxide/prjoxide/src/wires.rs b/libprjoxide/prjoxide/src/wires.rs index 4941938..79ccab9 100644 --- a/libprjoxide/prjoxide/src/wires.rs +++ b/libprjoxide/prjoxide/src/wires.rs @@ -1,3 +1,4 @@ +use log::warn; // Wire normalisation for Nexus use crate::chip::*; use regex::Regex; @@ -77,6 +78,9 @@ pub fn handle_edge_name( "01" => { // H01xyy00 --> x+1, H01xyy01 if tx == max_x - 1 { + if hm[4].to_string() != "00" { + warn!("Invalid edge name {wn} - {hm:?}. hm[4] == '00'") + } assert_eq!(hm[4].to_string(), "00"); return (format!("H01{}{}01", &hm[2], &hm[3]), wx + 1, wy); } @@ -270,7 +274,9 @@ pub fn normalize_wire(chip: &Chip, tile: &Tile, wire: &str) -> String { } let tx = tile.x as i32; let ty = tile.y as i32; - if tile.name.contains("TAP") && wn.starts_with("H") { + + + if tile.name.contains("TAP") && wn.starts_with("H") && !wn.starts_with("HFIE") { if wx < tx { return format!("BRANCH_L:{}", wn); } else if wx > tx { diff --git a/libprjoxide/pyprjoxide/Cargo.toml b/libprjoxide/pyprjoxide/Cargo.toml index 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..e50d189 100644 --- a/libprjoxide/pyprjoxide/src/lib.rs +++ b/libprjoxide/pyprjoxide/src/lib.rs @@ -4,7 +4,7 @@ use pyo3::wrap_pyfunction; use std::fs::File; use std::io::*; - +use pyo3_log::{Caching, Logger}; use prjoxide::bitstream; use prjoxide::chip; use prjoxide::database; @@ -13,9 +13,10 @@ use prjoxide::docs; use prjoxide::fuzz; use prjoxide::ipfuzz; use prjoxide::nodecheck; -use prjoxide::wires; use prjoxide::pip_classes; use prjoxide::sites; +use prjoxide::wires; + #[pyclass] struct Database { @@ -30,11 +31,19 @@ impl Database { db: database::Database::new(root), } } + pub fn add_conn(&mut self, family: &str, tiletype: &str, from: &str, to: &str) { + self.db.tile_bitdb(family, tiletype).add_conn(from, to); + } + + pub fn flush(&mut self) { + self.db.flush(); + } } #[pyclass] struct Fuzzer { fz: fuzz::Fuzzer, + name: String, } #[pymethods] @@ -64,6 +73,7 @@ impl Fuzzer { width, zero_bitfile, ), + name: name.to_string() } } @@ -96,6 +106,7 @@ impl Fuzzer { full_mux, skip_fixed, ), + name: to_wire.to_string() } } @@ -108,6 +119,7 @@ impl Fuzzer { desc: &str, include_zeros: bool, assume_zero_base: bool, + mark_relative_to: Option ) -> Fuzzer { let base_chip = bitstream::BitstreamParser::parse_file(&mut db.db, base_bitfile).unwrap(); @@ -122,7 +134,9 @@ impl Fuzzer { desc, include_zeros, assume_zero_base, + mark_relative_to ), + name: name.to_string() } } @@ -134,6 +148,14 @@ impl Fuzzer { self.fz.add_pip_sample(&mut db.db, from_wire, base_bitfile); } + fn add_pip_sample_delta(&mut self, from_wire: &str, delta: chip::ChipDelta) { + self.fz.add_pip_sample_delta(from_wire, delta); + } + + fn add_pip_sample_with_partial_delta(&mut self, db: &mut Database, from_wire: &str, base_bitfile: &str) { + self.fz.add_pip_sample_with_partial_delta(&mut db.db, from_wire, base_bitfile); + } + fn add_enum_sample(&mut self, db: &mut Database, option: &str, base_bitfile: &str) { self.fz.add_enum_sample(&mut db.db, option, base_bitfile); } @@ -141,11 +163,20 @@ impl Fuzzer { fn solve(&mut self, db: &mut Database) { self.fz.solve(&mut db.db); } + + fn serialize_deltas(&mut self, filename: &str) { + self.fz.serialize_deltas(filename); + } + + fn get_name(&self) -> String { + self.name.clone() + } } #[pyclass] struct IPFuzzer { fz: ipfuzz::IPFuzzer, + name: String } #[pymethods] @@ -174,6 +205,7 @@ impl IPFuzzer { width, inverted_mode, ), + name: name.to_string() } } @@ -196,6 +228,7 @@ impl IPFuzzer { name, desc, ), + name: name.to_string() } } @@ -214,6 +247,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] @@ -272,6 +313,11 @@ impl Chip { fn get_ip_values(&mut self) -> Vec<(u32, u8)> { self.c.ipconfig.iter().map(|(a, d)| (*a, *d)).collect() } + + fn delta(&self, db: &mut Database, new_bitstream: &str) -> PyResult { + let parsed_bitstream = bitstream::BitstreamParser::parse_file(&mut db.db, new_bitstream).unwrap(); + Ok(parsed_bitstream.delta(&self.c)) + } } #[pyfunction] @@ -346,7 +392,9 @@ fn classify_pip(src_x: i32, src_y: i32, src_name: &str, dst_x: i32, dst_y: i32, } #[pymodule] -fn libpyprjoxide(_py: Python, m: &PyModule) -> PyResult<()> { +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..f142b7a 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" \ @@ -86,15 +100,26 @@ else fi if [ -n "$GEN_RBF" ]; then - "$fpgabindir"/bitgen $EXTRA_BIT_ARGS -b -d -w par.udb + OUTPUT=$("$fpgabindir"/bitgen $EXTRA_BIT_ARGS -b -d -w par.udb 2>&1) + if [[ $OUTPUT == *"ERROR <"* ]]; then + echo "Exiting due to error found during bitgen" + exit -1 + fi + LD_LIBRARY_PATH=$ld_lib_path_orig $bscache commit $PART "input.v" $MAP_PDC output "par.udb" "par.rbt" else if [ -n "$RBK_MODE" ]; then - "$fpgabindir"/bitgen $EXTRA_BIT_ARGS -d -w -m 1 par.udb + OUTPUT=$("$fpgabindir"/bitgen $EXTRA_BIT_ARGS -d -w -m 1 par.udb 2>&1) mv par.rbk par.bit else - "$fpgabindir"/bitgen $EXTRA_BIT_ARGS -d -w par.udb + OUTPUT=$("$fpgabindir"/bitgen $EXTRA_BIT_ARGS -d -w par.udb 2>&1) fi + + if [[ $OUTPUT == *"ERROR <"* ]]; then + echo "Exiting due to error found during bitgen" + exit -1 + fi + LD_LIBRARY_PATH=$ld_lib_path_orig $bscache commit $PART "input.v" $MAP_PDC output "par.udb" "par.bit" fi export LD_LIBRARY_PATH="" @@ -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/tools/bitstreamcache.py b/tools/bitstreamcache.py index 7dd7a88..7c15f15 100755 --- a/tools/bitstreamcache.py +++ b/tools/bitstreamcache.py @@ -20,20 +20,25 @@ gzip and gunzip must be on your path for it to work """ - +import logging import sys, os, shutil, hashlib, gzip +from logging import exception +from pathlib import Path root_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") cache_dir = os.path.join(root_dir, ".bitstreamcache") -def get_hash(device, input_files): +def get_hash(device, input_files, env = None): + if env is None: + env = os.environ + hasher = hashlib.sha1() hasher.update(b"DEVICE") hasher.update(device.encode('utf-8')) for envkey in ("GEN_RBF", "DEV_PACKAGE", "SPEED_GRADE", "STRUCT_VER", "RBK_MODE"): - if envkey in os.environ: + if envkey in env: hasher.update(envkey.encode('utf-8')) - hasher.update(os.environ[envkey].encode('utf-8')) + hasher.update(env[envkey].encode('utf-8')) for fname in input_files: ext = os.path.splitext(fname)[1] hasher.update("input{}".format(ext).encode('utf-8')) @@ -41,47 +46,86 @@ def get_hash(device, input_files): hasher.update(f.read()) return hasher.hexdigest() -if len(sys.argv) < 2: - print("Expected command (init|fetch|commit)") - sys.exit(1) -cmd = sys.argv[1] -if cmd == "init": +def fetch(device, input_files, env = None): 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) - h = get_hash(sys.argv[2], sys.argv[4:]) - print(h) + return + + h = get_hash(device, input_files, env=env) + #print(h) + cache_entry = os.path.join(cache_dir, h) - if not os.path.exists(cache_entry) or len(os.listdir(cache_entry)) == 0: - sys.exit(1) + if not os.path.exists(cache_entry) or len(os.listdir(cache_entry)) < 2: + return + + # Touch the directory and it's contents + Path(cache_entry).touch() for outprod in os.listdir(cache_entry): - bn = outprod - assert bn.endswith(".gz") - bn = bn[:-3] - 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): - sys.exit(0) - idx = sys.argv.index("output") - if len(sys.argv) < 6 or idx == -1: - print("Usage: tools/bitstreamcache.py commit output ..") + gz_path = os.path.join(cache_entry, outprod) + Path(gz_path).touch() + + yield (outprod, gz_path) + +def main(): + if len(sys.argv) < 2: + print("Expected command (init|fetch|commit)") 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) + 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) + + if cmd == "commit": + if not os.path.exists(cache_dir): + sys.exit(0) + idx = sys.argv.index("output") + if len(sys.argv) < 6 or idx == -1: + print("Usage: tools/bitstreamcache.py commit output ..") + sys.exit(1) + h = get_hash(sys.argv[2], sys.argv[3:idx]) + cache_entry = os.path.join(cache_dir, h) + if not os.path.exists(cache_entry): + os.mkdir(cache_entry) + for outprod in sys.argv[idx+1:]: + bn = os.path.basename(outprod) + cn = os.path.join(cache_entry, bn + ".gz") + + if not os.path.exists(outprod): + raise Exception(f"Output product does not exist") + + if os.path.getsize(outprod) == 0: + raise Exception(f"Output product has zero length; refusing to gzip {outprod}") + + with gzip.open(cn, 'wb') as gzf: + with open(outprod, 'rb') as inf: + gzf.write(inf.read()) + sys.exit(0) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tools/extract_tilegrid.py b/tools/extract_tilegrid.py index ae7208c..bb8b2af 100644 --- a/tools/extract_tilegrid.py +++ b/tools/extract_tilegrid.py @@ -63,11 +63,16 @@ def get_tf2c(dev): if dev == "LFCPNX-100": return tap_frame_to_col_100 - elif dev == "LIFCL-40" or dev == "LFDN2X-40": + elif dev == "LIFCL-40" or dev == "LFDN2X-40" or dev == "LFD2NX-40": return tap_frame_to_col_40 elif dev == "LIFCL-17": return tap_frame_to_col_17 + elif dev == "LIFCL-33": + return tap_frame_to_col_17 + elif dev == "LIFCL-33U": + return tap_frame_to_col_17 else: + print(f"Could not find dev {dev}") assert False def main(argv): @@ -75,12 +80,26 @@ def main(argv): tiles = {} current_tile = None tap_frame_to_col = get_tf2c(args.device) + + def fixup(tiletype): + if args.device.find("-33") > 0: + tiletypes_with_variants = ["LRAM_", "SYSIO_B1_DED", "SPINE_"] + for v in tiletypes_with_variants: + if tiletype.startswith(v): + return tiletype + "_33K" + 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 +118,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/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..6011d5a --- /dev/null +++ b/tools/parse_webdoc.py @@ -0,0 +1,103 @@ +import json +import os +import re + +from bs4 import BeautifulSoup +from pathlib import Path + + +def normalize_text(s: str) -> str: + return " ".join(s.encode('ascii', errors='ignore').decode("ascii").replace("(default)", "").split()) + + +def extract_cell_value(td): + text = normalize_text(td.get_text()) + matches = re.findall(r'"([^"]*)"', text) + if len(matches): + return matches + + divs = td.find_all("div", class_="CellBody", recursive=False) + if not divs: + return text + + values = [normalize_text(d.get_text()) for d in divs if normalize_text(d.get_text())] + if len(values) == 0: + return "" + + if len(values) == 1: + return values[0] + return values + + +def parse_table(table): + rows = table.find_all("tr") + if not rows: + return [] + + # Extract headers + header_cells = rows[0].find_all(["th", "td"]) + headers = [normalize_text(h.get_text()) or f"col_{i}" for i, h in enumerate(header_cells)] + + data = [] + for row in rows[1:]: + cells = row.find_all("td") + if not cells: + continue + + row_obj = {} + for header, cell in zip(headers, cells): + if header == "col_1": + header = "Values" + cell_value = extract_cell_value(cell) + if header == "Values" and not isinstance(cell_value, list): + cell_value = [cell_value] + row_obj[header] = cell_value + + if row_obj.get("Name", None) == "": + data[-1]["Values"].extend(row_obj["Values"]) + #data[-1]["Description"] += (row_obj["Description"]) + else: + data.append(row_obj) + + return data + + +def scrape_html(html_path: Path): + with open(html_path, "r", encoding="utf-8") as f: + soup = BeautifulSoup(f, "lxml") + + title = soup.title.get_text(strip=True) if soup.title else html_path.stem + output = {} + + output["description"] = "".join([div.get_text() for div in soup.select(".BodyAfterHead")]) + output["platforms"] = [ supported_platforms.get_text() for supported_platforms in soup.select(".Bulleted")] + + + for title_div in soup.select("div.TableTitle"): + table_title = normalize_text(title_div.get_text()) + if not table_title: + continue + + table = title_div.find_parent("table") + if not table: + continue + + output[table_title] = parse_table(table) + + output_path = f"primitives/{title}.json" + with open(output_path, "w", encoding="utf-8") as f: + json.dump(output, f, indent=2) + + return output_path + + +if __name__ == "__main__": + import sys + + if len(sys.argv) != 2: + print("Usage: python scrape.py ") + sys.exit(1) + + html_file = Path(sys.argv[1]) + out = scrape_html(html_file) + print(f"Wrote {out}") diff --git a/util/common/cachecontrol.py b/util/common/cachecontrol.py new file mode 100644 index 0000000..7d6124a --- /dev/null +++ b/util/common/cachecontrol.py @@ -0,0 +1,28 @@ +import hashlib +import os +import pickle + +import cachier +from sqlalchemy import create_engine + +from database import get_cache_dir + +radiant_version = os.environ.get("RADIANTVERSION", None) +engine = create_engine(f'sqlite:///{get_cache_dir()}/cache.db') +#@cachier.cachier(backend="sql", sql_engine=engine, cache_dir=database.get_cache_dir(), pickle_reload=False,separate_files=True) + +def cache_fn(): + RADIANT_DIR = os.environ.get("RADIANTDIR") + + def hashfunc(args, kwds): + # Sort the kwargs to ensure consistent ordering + kwds["RADIANT_DIR"] = RADIANT_DIR + kwds["RADIANT_VERSION"] = radiant_version + + sorted_kwargs = sorted(kwds.items()) + # Serialize args and sorted_kwargs using pickle or similar + serialized = pickle.dumps((args, sorted_kwargs)) + # Create a hash of the serialized data + return hashlib.sha256(serialized).hexdigest() + + return cachier.cachier(hash_func=hashfunc, backend="sql", sql_engine=engine, pickle_reload=False,separate_files=True, wait_for_calc_timeout=5) \ No newline at end of file diff --git a/util/common/database.py b/util/common/database.py index 749ab76..e64076b 100644 --- a/util/common/database.py +++ b/util/common/database.py @@ -2,15 +2,39 @@ Database and Database Path Management """ import os -from os import path +from functools import lru_cache, cache +from os import path, makedirs import json import subprocess - +from pathlib import Path +import pyron as ron +import gzip def get_oxide_root(): """Return the absolute path to the Project Oxide repo root""" return path.abspath(path.join(__file__, "../../../")) +def get_radiant_version(): + # `lapie` seems to be renamed every version or so. Map that out here. Most installations will have + # the version name at the end of their path, so we just look at the radiant dir for a hint. The user + # can override this setting with a RADIANTVERSION env variable + known_versions = [ "2.2", "3.1", "2023", "2024", "2025" ] + RADIANT_DIR = os.environ.get("RADIANTDIR") + radiant_version= os.environ.get("RADIANTVERSION", None) + + if radiant_version is None: + for version in known_versions: + if RADIANT_DIR.find(version) > -1: + radiant_version = version + + if radiant_version is None: + radiant_version = "3.1" + return radiant_version + +def get_cache_dir(): + path = get_oxide_root() + "/.cache/" + get_radiant_version() + makedirs(path, exist_ok=True) + return path def get_db_root(): """ @@ -40,12 +64,49 @@ 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 = device.split('-')[0] -def get_tilegrid(family, 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 {} + +@cache +def get_tilegrid(family, device = None): """ Return the deserialised tilegrid for a family, device """ + if device is None: + device = family + family = device.split('-')[0] + 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 = device.split('-')[0] + tgjson = path.join(get_db_subdir(family, device), "iodb.json") with open(tgjson, "r") as f: return json.load(f) @@ -58,6 +119,91 @@ def get_devices(): with open(djson, "r") as f: return json.load(f) +def get_tiletypes(family): + family = family.split("-")[0] + p = path.join(get_db_root(), family, "tiletypes") + + tiletypes = {} + + if path.exists(p): + for entry in Path(p).iterdir(): + if entry.name.endswith(".ron"): + with open(entry.absolute(), "r") as f: + tiletypes[entry.name.split(".")[0]] = ron.loads(f.read().replace("\\'", "'")) + + return tiletypes + def get_db_commit(): return subprocess.getoutput('git -C "{}" rev-parse HEAD'.format(get_db_root())) + +@cache +def get_sites(family, device = None): + import lapie + + if device is None: + device = family + family = device.split('-')[0] + + 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..8c48682 100644 --- a/util/common/lapie.py +++ b/util/common/lapie.py @@ -1,14 +1,47 @@ """ Python wrapper for `lapie` """ -from os import path +import hashlib +import logging import os +import re +import shutil 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 +51,13 @@ 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" + + 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 +72,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 +93,9 @@ def __init__(self, from_wire, to_wire, is_bidi = False, flags = 0, buffertype = self.flags = flags self.buffertype = buffertype self.is_bidi = is_bidi + + def __repr__(self): + return str((self.from_wire, self.to_wire, self.flags, self.buffertype, self.is_bidi)) class PinInfo: def __init__(self, site, pin, wire, pindir): @@ -58,29 +107,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 +188,249 @@ 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 + +@cachecontrol.cache_fn() +def get_full_node_list(udb): + workdir = f"/tmp/prjoxide_node_data/{udb}" + nodefile = path.join(workdir, "full_nodes.txt") + os.makedirs(workdir, exist_ok=True) + + if not os.path.exists(nodefile): + if not udb.endswith(".udb"): + config = fuzzconfig.FuzzConfig(udb, "extract-site-info", []) + config.setup() + udb = config.udb + run_with_udb(udb, [f'dev_list_node_by_name -file {nodefile}']) + with open(nodefile, 'r') as nf: + return [line.split(":")[-1].strip() for line in nf.read().split("\n")] + +@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 + +def get_jump_wires_by_nodes(device, nodes): + return set([ + (frm_wire, to_wire) + for (frm_wire, to_wire) in get_jump_wires(device) + if frm_wire in nodes or to_wire in nodes + ]) + +def _get_node_data(udb, nodes): + regex = False -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]}") + key_input = "\n".join([radiant_version, udb, f"regex: {regex}", ''] + nodes) + key = hashlib.md5(key_input.encode('utf-8')).hexdigest() + key_path = f"/tmp/prjoxide_node_data/{key}" + os.makedirs("/tmp/prjoxide_node_data", exist_ok=True) + + if os.path.exists(key_path): + logging.debug(f"Nodefile found at {key_path}") + shutil.copyfile(key_path, nodefile) + else: + if not udb.endswith(".udb"): + device = udb + udb = f"/tmp/prjoxide_node_data/{device}.udb" + if not os.path.exists(udb): + config = fuzzconfig.FuzzConfig(device, f"extract-site-info-{device}", []) + config.setup() + shutil.copyfile(config.udb, udb) + + re_slug = "-re " if regex else "" + run_with_udb(udb, [f'dev_report_node -file {nodefile} [{get_nodes} {re_slug}{nodelist}]'], stdout = subprocess.DEVNULL) + shutil.copyfile(nodefile, key_path) + with open(key_path + ".input", 'w') as f: + f.write(key_input) + logging.debug(f"Nodefile cached at {key_path}") + with open(nodefile, 'r') as nf: - return parse_node_report(nf.read()) + return parse_node_report(nf.read(), nodes) + +def get_node_data(udb, nodes, regex=False, executor = None): + from nodes_database import NodesDatabase + import fuzzloops + + if not isinstance(nodes, (list, set)): + nodes = [nodes] + else: + nodes = sorted(set(nodes)) + + if regex: + all_nodes = get_full_node_list(udb) + regex = [re.compile(n) for n in nodes] + nodes = sorted(set([n for n in all_nodes if any([r for r in regex if r.search(n) is not None])])) + + db = NodesDatabase.get(udb) + nis = db.get_node_data(nodes) + missing = sorted({k for k in nodes if k not in nis}) + futures = [] + + if len(missing): + cnt = 5000 + logging.info(f"Getting from lapie: {missing[:10]}...") + + with fuzzloops.Executor(executor) as local_executor: + def lapie_get_node_data(query): + s = time.time() + nodes = _get_node_data(udb, missing[:cnt]) + logging.debug(f"{len(query)} N {len(query) / (time.time() - s)} N/sec ({(time.time() - s)} deltas)") + return nodes + + def integrate_nodes(nodes): + db.insert_nodeinfos(nodes) + for n in nodes: + nis[n.name] = n + + while len(missing): + f = local_executor.submit(lapie_get_node_data, missing[:cnt]) + missing = missing[cnt:] + futures.append(fuzzloops.chain(f, integrate_nodes)) + if executor is not None: + return fuzzloops.chain(futures, lambda _: list(nis.values())) + else: + return list(nis.values()) + +def _get_sites(udb, rc = None): + rc_slug = "" + if rc is not None: + rc_slug = f"-row {rc[0]} -column {rc[1]}" + 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 + +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..502de1c --- /dev/null +++ b/util/common/nodes_database.py @@ -0,0 +1,421 @@ +import logging +import sqlite3 +from threading import RLock + + +class NodesDatabase: + _dbs = {} + _lock = RLock() + + @staticmethod + def get(device): + with NodesDatabase._lock: + if device not in NodesDatabase._dbs: + NodesDatabase._dbs[device] = NodesDatabase(device) + return NodesDatabase._dbs[device] + + def __init__(self, device): + import database + + self.db_path = f"{database.get_cache_dir()}/{device}-nodes.sqlite" + logging.debug(f"Opening node database at {self.db_path}") + + self.device = device + self.conn = sqlite3.connect(self.db_path, check_same_thread=False) + self.lock = RLock() + self.init_db() + + def init_db(self): + with self.lock: + conn = self.conn + conn.execute("PRAGMA foreign_keys = ON") + cur = conn.cursor() + + cur.execute(""" + CREATE TABLE IF NOT EXISTS nodes ( + id INTEGER PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + has_full_data INTEGER NOT NULL DEFAULT 0 CHECK (has_full_data IN (0, 1)) + ); + """) + + # PIPs table: + # from_wire and to_wire are node IDs + # bidir = 0 (unidirectional) or 1 (bidirectional) + cur.execute(""" + CREATE TABLE IF NOT EXISTS pips ( + from_id INTEGER NOT NULL, + to_id INTEGER NOT NULL, + bidir INTEGER NOT NULL CHECK (bidir IN (0,1)), + jumpwire INTEGER NOT NULL CHECK (jumpwire IN (0,1)) DEFAULT 0, + flags INTEGER NOT NULL DEFAULT 0, + buffertype TEXT NOT NULL DEFAULT "", + PRIMARY KEY (from_id, to_id), + FOREIGN KEY (from_id) REFERENCES nodes(id), + FOREIGN KEY (to_id) REFERENCES nodes(id) + ) WITHOUT ROWID; + """) + + try: + cur.execute("ALTER TABLE pips ADD COLUMN jumpwire INTEGER") + except sqlite3.OperationalError as e: + pass + + cur.execute(""" + CREATE TEMP TABLE IF NOT EXISTS tmp_node_names ( + name TEXT PRIMARY KEY + ); + """) + + cur.execute(""" + CREATE TEMP TABLE IF NOT EXISTS tmp_node_ids ( + id INTEGER PRIMARY KEY + ); + """) + + cur.execute(""" + CREATE TABLE IF NOT EXISTS sites ( + id INTEGER PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + type TEXT NOT NULL, + x INTEGER NOT NULL, + y INTEGER NOT NULL + ); + """) + + cur.execute(""" +CREATE TABLE IF NOT EXISTS site_pins ( + site_id INTEGER NOT NULL, + pin_name TEXT NOT NULL, + node_id INTEGER NOT NULL, + + PRIMARY KEY (site_id, pin_name), + + FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE, + FOREIGN KEY (node_id) REFERENCES nodes(id) +) WITHOUT ROWID; + """) + + conn.commit() + + def _populate_tmp(self, cur, type, values): + cur.execute(f"DELETE FROM tmp_node_{type}s") + + cur.executemany( + f"INSERT INTO tmp_node_{type}s ({type}) VALUES (?)", + ((n,) for n in values) + ) + + def get_node_ids(self, names): + conn = self.conn + cur = conn.cursor() + self._populate_tmp(cur, "name", names) + + cur.executemany( + "INSERT OR IGNORE INTO nodes (name) VALUES (?)", + ((ni,) for ni in names) + ) + + cur.execute( + f"SELECT id, name FROM nodes where name IN (SELECT name from tmp_node_names)", + ) + + id_to_name = dict(cur.fetchall()) + name_to_id = {v: k for k, v in id_to_name.items()} + return name_to_id + + def get_jumpwires(self): + conn = self.conn + cur = conn.cursor() + + cur.execute(f""" + SELECT n1.name, n2.name, p.bidir, p.flags, p.buffertype + FROM pips p + JOIN nodes n1 ON n1.id = p.from_id + JOIN nodes n2 ON n2.id = p.to_id + WHERE jumpwire = 1 + """) + + for from_name, to_name, bidir, flags, bt in cur.fetchall(): + yield from_name, to_name + + def insert_jumpwires(self, jumpwires): + conn = self.conn + cur = conn.cursor() + + touched_names = set([w for ni in jumpwires for w in ni]) + + cur.executemany( + "INSERT OR IGNORE INTO nodes (name) VALUES (?)", + ((ni,) for ni in touched_names) + ) + + self._populate_tmp(cur, "name", touched_names) + + cur.execute( + f"SELECT id, name FROM nodes WHERE name IN (SELECT name from tmp_node_names)" + ) + id_to_name = dict(cur.fetchall()) + name_to_id = {v: k for k, v in id_to_name.items()} + + cur.executemany( + """ + INSERT OR IGNORE INTO pips (from_id, to_id, bidir, flags, buffertype) VALUES (?, ?, ?, ?, ?) + """, + [(name_to_id[j[0]], name_to_id[j[1]], 0, -1, "") for j in jumpwires] + ) + + cur.executemany( + """ + UPDATE pips + SET jumpwire = 1 + WHERE from_id = ? AND to_id = ? + """, + [(name_to_id[j[0]], name_to_id[j[1]]) for j in jumpwires] + ) + print("jmp", len(jumpwires)) + + conn.commit() + + def get_node_data(self, names): + from lapie import NodeInfo, PipInfo + + with self.lock: + conn = self.conn + cur = conn.cursor() + + self._populate_tmp(cur, "name", names) + + cur.execute( + f"SELECT id, name FROM nodes WHERE has_full_data = 1 and name IN (SELECT name from tmp_node_names)", + ) + id_to_name = dict(cur.fetchall()) + name_to_id = {v:k for k,v in id_to_name.items()} + + # Prepare result dict + result = {name: NodeInfo(name) for name in name_to_id} + + self._populate_tmp(cur, "id", list(id_to_name.keys())) + # ---- Downhill PIPs ---- + cur.execute(f""" + SELECT p.from_id, n2.name, p.bidir, p.flags, p.buffertype + FROM pips p + JOIN nodes n2 ON n2.id = p.to_id + WHERE p.from_id IN (SELECT id from tmp_node_ids) + """) + + for from_id, to_name, bidir, flags, bt in cur.fetchall(): + from_name = id_to_name[from_id] + result[from_name].downhill_pips.append( + PipInfo(from_name, to_name, + is_bidi=bool(bidir), + flags=flags, + buffertype=bt) + ) + + # ---- Uphill PIPs ---- + cur.execute(f""" + SELECT p.to_id, n1.name, p.bidir, p.flags, p.buffertype + FROM pips p + JOIN nodes n1 ON n1.id = p.from_id + WHERE p.to_id IN (SELECT id from tmp_node_ids) + """) + + for to_id, from_name, bidir, flags, bt in cur.fetchall(): + to_name = id_to_name[to_id] + result[to_name].uphill_pips.append( + PipInfo(from_name, to_name, + is_bidi=bool(bidir), + flags=flags, + buffertype=bt) + ) + + return result + + def insert_nodeinfos(self, nodeinfos): + with self.lock: + conn = self.conn + cur = conn.cursor() + + touched_names = set([w for ni in nodeinfos for p in ni.pips() for w in [p.to_wire, p.from_wire]]) | set([n.name for n in nodeinfos]) + + # 1. Insert all nodes + cur.executemany( + "INSERT OR IGNORE INTO nodes (name) VALUES (?)", + ((ni,) for ni in touched_names) + ) + + self._populate_tmp(cur, "name", {n.name for n in nodeinfos}) + + cur.execute( + f""" + UPDATE nodes + SET has_full_data = 1 + WHERE name IN (SELECT name from tmp_node_names) + """ + ) + # 2. Resolve node ids + names = [ni.name for ni in nodeinfos] + + self._populate_tmp(cur, "name", touched_names) + + cur.execute( + f"SELECT id, name FROM nodes WHERE name IN (SELECT name from tmp_node_names)" + ) + id_to_name = dict(cur.fetchall()) + name_to_id = {v:k for k,v in id_to_name.items()} + + pip_rows = [] + + for ni in nodeinfos: + for p in ni.pips(): + from_id = name_to_id.get(p.from_wire) + to_id = name_to_id.get(p.to_wire) + + pip_rows.append( + (from_id, to_id, + 1 if p.is_bidi else 0, + p.flags, + p.buffertype) + ) + + cur.executemany( + """ + INSERT OR IGNORE INTO pips + (from_id, to_id, bidir, flags, buffertype) + VALUES (?, ?, ?, ?, ?) + """, + pip_rows + ) + + conn.commit() + + def insert_sites_and_fetch_ids(self, sites): + if not sites: + return {} + + with self.conn: + cur = self.conn.cursor() + + self._populate_tmp(cur, "name", {s for s in sites}) + + cur.execute(""" + INSERT INTO sites (name) + SELECT t.name + FROM tmp_node_names t + LEFT JOIN sites s ON s.name = t.name + WHERE s.name IS NULL + """) + + rows = cur.execute(""" + SELECT s.name, s.id + FROM sites s + JOIN tmp_names t ON t.name = s.name + """).fetchall() + + return dict(rows) + + def insert_sites(self, sites): + conn = self.conn + cur = conn.cursor() + + # ---- Insert sites ---- + site_rows = [ + (name, + data["type"], + int(data["x"]), + int(data["y"])) + for name, data in sites.items() + ] + + cur.executemany( + """ + INSERT OR IGNORE INTO sites (name, type, x, y) + VALUES (?, ?, ?, ?) + """, + site_rows + ) + + site2id = dict(cur.execute(""" + SELECT s.name, s.id + FROM sites s + """).fetchall()) + + # ---- Resolve node IDs (from pin_node) ---- + node_names = { + pin["pin_node"] + for data in sites.values() + for pin in data["pins"] + } + + node2id = self.get_node_ids(node_names) + + # ---- Insert pins ---- + pin_rows = [] + + for site_name, data in sites.items(): + sid = site2id[site_name] + + for pin in data["pins"]: + nid = node2id.get(pin["pin_node"]) + if nid is None: + continue # or raise if missing nodes are an error + + pin_rows.append( + (sid, pin["pin_name"], nid) + ) + + cur.executemany( + """ + INSERT OR IGNORE INTO site_pins + (site_id, pin_name, node_id) + VALUES (?, ?, ?) + """, + pin_rows + ) + + conn.commit() + + def get_sites(self): + conn = self.conn + cur = conn.cursor() + result = {} + + # ---- Fetch sites ---- + cur.execute( + f""" + SELECT id, name, type, x, y + FROM sites + """, + ) + + site_rows = cur.fetchall() + if not site_rows: + return result + + site_id = {} + for sid, name, typ, x, y in site_rows: + site_id[sid] = name + result[name] = { + "type": typ, + "x": x, + "y": y, + "pins": [] + } + + # ---- Fetch pins ---- + cur.execute( + f""" + SELECT sp.site_id, sp.pin_name, n.name + FROM site_pins sp + JOIN nodes n ON n.id = sp.node_id + """, + ) + + for sid, pin_name, node_name in cur.fetchall(): + result[site_id[sid]]["pins"].append({ + "pin_name": pin_name, + "pin_node": node_name + }) + + return result diff --git a/util/common/radiant.py b/util/common/radiant.py index 42a5e82..89c0230 100644 --- a/util/common/radiant.py +++ b/util/common/radiant.py @@ -1,10 +1,61 @@ """ Python wrapper for `radiant.sh` """ +import asyncio +import logging +import time from os import path import os import subprocess import database +import sys + +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 + + if show_output or logging.DEBUG >= logging.root.level: + for stream in [("", stdout, sys.stdout), ("ERR:", stderr, sys.stdout)]: + if stream[1] is not None: + for l in stream[1].decode().splitlines(): + logging.info(f"[{stream[0]} {slug}] {l}") + + if returncode != 0: + raise Exception(f"Error encountered running radiant: {slug} {returncode}") + + # 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 +69,7 @@ def run(device, source, struct_ver=True, raw_bit=False, pdcfile=None, rbk_mode=F env["GEN_RBT"] = "1" if rbk_mode: env["RBK_MODE"] = "1" + dsh_path = path.join(database.get_oxide_root(), "radiant.sh") - return subprocess.run(["bash",dsh_path,device,source], env=env) + logging.info(f"Building [{device}] {source}") + return run_bash_script(env, dsh_path, device, source) diff --git a/util/common/tiles.py b/util/common/tiles.py index 2d8890a..71b3da8 100644 --- a/util/common/tiles.py +++ b/util/common/tiles.py @@ -1,4 +1,13 @@ +import itertools +import random import re +from collections.abc import Iterable + +import database +from collections import defaultdict +import lapie + +import cachecontrol pos_re = re.compile(r'R(\d+)C(\d+)') @@ -11,9 +20,477 @@ def pos_from_name(tile): assert s return int(s.group(1)), int(s.group(2)) - def type_from_fullname(tile): """ Extract the type from a full tile name (in name:type) format """ return tile.split(":")[1] + +def get_rc_from_edge(device, side, offset): + devices = database.get_devices() + device_info = devices["families"][device.split("-")[0]]["devices"][device] + + max_row = device_info["max_row"] + max_col = device_info["max_col"] + + if side == "T": + return (0, int(offset)) + elif side == "B": + return (int(max_row), int(offset)) + elif side == "R": + return (int(offset), int(max_col)) + elif side == "L": + return (int(offset), 0) + + assert False, f"Could not match IO with side as side {side} offset {offset}" + +def get_tiles_from_edge(device, side, offset = -1): + (r, c) = get_rc_from_edge(device, side, offset) + tg = database.get_tilegrid(device)["tiles"] + + return [t for t, tinfo in tg.items() if (c == -1 or tinfo["x"] == c) and (r == -1 or tinfo["y"] == r)] + +def get_sites_from_primitive(device, primitive): + sites = database.get_sites(device) + return {k:s for (k,s) in sites.items() if s['type'] == primitive} + + +def get_tiletypes(device): + tilegrid = database.get_tilegrid(device)['tiles'] + tiletypes = defaultdict(list) + for (k,v) in tilegrid.items(): + tiletypes[k.split(":")[-1]].append(k) + return tiletypes + +def get_tiles_by_filter(device, fn): + tilegrid = database.get_tilegrid(device)['tiles'] + + return {k:v for k,v in tilegrid.items() if fn(k, v)} + + +def get_tiles_by_tiletype(device, tiletype): + tilegrid = database.get_tilegrid(device)['tiles'] + + return {k:v for k,v in tilegrid.items() if k.split(":")[-1] == tiletype} + +def get_tiles_by_primitive(device, primitive): + tilegrid = database.get_tilegrid(device)['tiles'] + + rc_regex = re.compile("R([0-9]*)C([0-9]*)") + edge_regex = re.compile("IOL_(.)([0-9]*)") + sites = get_sites_from_primitive(device, primitive) + + tg_by_rc = { (t['y'], t['x']):(k, t) for (k, t) in tilegrid.items() } + + rcs = {} + for (a,v) in sites.items(): + rc = get_rc_from_name(device, a) + + (name, t) = tg_by_rc[rc] + rcs[(a,name)] = t + + return rcs + +def get_tiletypes_by_primitive(device, primitive): + tiles = get_tiles_by_primitive(device, primitive) + + rtn = defaultdict(list) + for ((site,tilename),v) in tiles.items(): + tiletype = tilename.split(":")[1] + rtn[tiletype].append((site, tilename, v)) + return rtn + +def get_sites_for_tile(device, tile): + tilegrid = database.get_tilegrid(device)['tiles'] + tile = [v for (k,v) in tilegrid.items() if k.startswith(tile) ][0] + + sites = database.get_sites(device) + + RC = (tile["y"], tile["x"]) + + return {k:v for k,v in sites.items() if RC == get_rc_from_name(device, k)} + +_node_list_lookup = {} +_node_owned_lookup = {} + +_spine_regex = re.compile("(.)([0-9][0-9])(.)([0-9][0-9])([0-9][0-9])") + +_full_node_set = {} +def get_full_node_set(device): + if device not in _full_node_set: + all_nodes = lapie.get_full_node_list(device) + _full_node_set[device] = sorted(list(set([n for n in all_nodes if len(n)]))) + return _full_node_set[device] + + +def get_node_list_for_tile(device, tile, owned = False): + if device not in _node_list_lookup: + all_nodes = lapie.get_full_node_list(device) + _node_list_lookup[device] = defaultdict(list) + _node_owned_lookup[device] = defaultdict(list) + for name in all_nodes: + rc = get_rc_from_name(device, name) + + if rc is None: + continue + elif rc[0] < 0 or rc[1] < 0: + print(f"Nodename {name} has negative rc: {rc}") + name_no_rc = "_".join(name.split("_")[1:]) + m = _spine_regex.match(name_no_rc) + if m is not None: + (r, c) = rc + orientation = m.group(1) + size = int(m.group(2)) + direction = m.group(3) + track = int(m.group(4)) + segment = int(m.group(5)) + + if size == 0: + continue + + assert (orientation in ["H", "V"]) + assert (direction in ["N", "E", "W", "S"]) + + (dir_x, dir_y) = (0, 0) + if direction == "N": + dir_y = -1 + elif direction == "S": + dir_y = 1 + elif direction == "E": + dir_x = 1 + else: + dir_x = -1 + + rs = r - dir_y * segment + cs = c - dir_x * segment + + for i in range(0, size + 1): + ro = rs + dir_y * i + co = cs + dir_x * i + alias_name = f"R{ro}C{co}{orientation}{size:02}{direction}{track:02}{i:02}" + # _node_list_lookup[device][ro, co].append(name) + if i == 0: + _node_owned_lookup[device][rc].append(name) + else: + _node_list_lookup[device][rc].append(name) + _node_owned_lookup[device][rc].append(name) + + def _get_node_list_for_tile(t): + return (_node_owned_lookup if owned else _node_list_lookup)[device].get(get_rc_from_name(device, t), []) + + if isinstance(tile, list): + 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]+)") +def get_rc_from_name(device, name): + if isinstance(name, tuple): + return name + + m = rc_regex.search(name) + if m: + return (int(m.group(1)), int(m.group(2))) + + m = edge_regex.search(name) + if m: + return get_rc_from_edge(device, m.group(1), m.group(2)) + + return None + +def get_tile_from_node(device, node): + rc = get_rc_from_name(device, node) + tilegrid = database.get_tilegrid(device)['tiles'] + + for k,v in tilegrid.items(): + if (v['y'], v['x']) == rc: + return k + +def get_connected_nodes(device, tilename): + routes = get_tile_routes(device, tilename) + + def tile_route(route): + return list(set([ + wire + for (n,r) in route.items() + for p in r.pips() + for wire in [p.from_wire, p.to_wire] + ])) + + + if isinstance(tilename, list): + return {t:tile_route(route) for t,route in routes.items()} + + print(routes) + return tile_route(routes) + + +def get_pins_for_site(device, site): + sites = database.get_sites(device) + site_info = sites[site] + + nodes = {n.name:n for n in lapie.get_node_data(device, [p['pin_node'] for p in site_info['pins']])} + + return [(p, nodes[p['pin_node']]) for p in site_info['pins']] + +def get_pips_for_tile(device, tilename, owned = False, dir = None): + assert(dir is None or dir == "uphill" or dir == "downhill") + + def pips(r): + if dir is None: + return r.pips() + elif dir == "uphill": + return r.uphill_pips + elif dir == "downhill": + return r.downhill_pips + + routes = get_tile_routes(device, tilename, owned = owned) + return list(set([ + (p.from_wire, + p.to_wire) + for (n,r) in routes.items() + for p in pips(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(rcs): + rcs = set(rcs) + for y in range(0, 55): + for x in range(0, 83): + print("■" if (x,y) in rcs else " " , end='') + print() + +def get_wires_for_tiles(device): + anon_nodes = defaultdict(lambda : defaultdict(list)) + for n in get_full_node_set(device): + wire_name = "_".join(n.split("_")[1:]) + rc = get_rc_from_name(device, n) + for tile in sorted(get_tiles_by_rc(device, rc)): + tiletype = tile.split(":")[-1] + anon_nodes[tiletype][wire_name].append(tile) + + return anon_nodes + +def get_wires_for_sites(device): + anon_nodes = defaultdict(lambda : defaultdict(list)) + sites = database.get_sites(device) + + for site, site_info in sites.items(): + pins = site_info['pins'] + pin_nodes = [p["pin_node"] for p in pins] + + for n in pin_nodes: + wire_name = "_".join(n.split("_")[1:]) + rc = get_rc_from_name(device, n) + + anon_nodes[site_info["type"]][wire_name].append(site) + return anon_nodes + +def get_representative_nodes_data(device, seed = 42, exclude_set = []): + rep_nodes = get_wires_for_tiles(device) + nodes = [] + random.seed(42) + + lookup = {} + for tiletype, wire_dict in sorted(rep_nodes.items()): + if tiletype not in exclude_set: + for wire, tiles in sorted(wire_dict.items()): + tile = random.choice(tiles) + (r,c) = get_rc_from_name(device, tile) + wire_name = f"R{r}C{c}_{wire}" + nodes.append(f"R{r}C{c}_{wire}") + lookup[wire_name] = (tiletype, wire, tile) + + nodes = sorted(nodes) + + batches = list(itertools.batched(nodes, 5000)) + batch_returns = [None] * len(batches) + + def f(idx_batch): + (idx, batch) = idx_batch + batch_returns[idx] = lapie.get_node_data(device, list(batch)) + + import fuzzloops + fuzzloops.parallel_foreach(enumerate(batches), f, jobs=len(batches)) + + node_data = {a:v + for d in batch_returns + for v in d + for a in v.aliases} + + rtn = defaultdict(list) + for wire_name, lu in lookup.items(): + rtn[lu[0]].append((lu[2], node_data[wire_name])) + + return rtn + +def get_node_data_local_graph(device, node, should_expand = None): + if isinstance(node, Iterable): + node = list(node) + + if not isinstance(node, list): + node = [node] + + rc = get_rc_from_name(device, node[0]) + def def_should_expand(node): + return rc == get_rc_from_name(device, node) + + if should_expand is None: + should_expand = def_should_expand + + query_list = node + + graph = {} + while len(query_list) > 0: + new_nodes = lapie.get_node_data(device, query_list) + #new_nodes = [k for k in lapie.get_list_arc(device) + query_list = [] + + for n in new_nodes: + graph[n.name] = n + + for p in n.pips(): + for wire in [p.to_wire, p.from_wire]: + if wire not in graph and should_expand(wire): + query_list.append(wire) + return graph + +def get_local_pips_for_site(device, site, include_interface_pips = True): + if isinstance(site, str): + sites = database.get_sites(device) + site = sites[site] + + site_nodes = [p["pin_node"] for p in site["pins"]] + + return get_local_pips_for_nodes(device, site_nodes, + include_interface_pips = include_interface_pips, + should_expand = lambda x: site["type"] in x) + +def get_local_pips_for_nodes(device, nodes, should_expand = None, include_interface_pips = True, executor = None): + if executor is not None: + return executor.submit(get_local_pips_for_nodes, device, nodes, should_expand = should_expand ,include_interface_pips = include_interface_pips) + + local_graph = get_node_data_local_graph(device, nodes, should_expand = should_expand) + + def should_include(p): + if include_interface_pips: + return p.from_wire in local_graph or p.to_wire in local_graph + else: + return p.from_wire in local_graph and p.to_wire in local_graph + + pips = [(p.from_wire, p.to_wire) for n, info, in local_graph.items() for p in info.pips() if + should_include(p)] + + return sorted(set(pips)), local_graph + +def get_representative_nodes_for_tiletype(device, tiletype): + node_set = None + + for tile in get_tiles_by_tiletype(device, tiletype): + nodes = set(["_".join(n.split("_")[1:]) for n in get_node_list_for_tile(device, tile)]) + if node_set is None: + node_set = nodes + else: + node_set = node_set & nodes + if node_set is None: + return set() + + return node_set + +def get_outlier_nodes_for_tiletype(device, tiletype): + repr_nodes = get_representative_nodes_for_tiletype(device, tiletype) + + outliers = {} + for tile in get_tiles_by_tiletype(device, tiletype): + nodes = set(["_".join(n.split("_")[1:]) for n in get_node_list_for_tile(device, tile)]) + + node_outliers = nodes - repr_nodes + + if len(node_outliers) > 0: + outliers[tile] = node_outliers + + return outliers + +@cachecontrol.cache_fn() +def get_connections_for_device(device): + arcs = lapie.get_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 \ No newline at end of file diff --git a/util/fuzz/fuzzconfig.py b/util/fuzz/fuzzconfig.py index f294ba5..1131ffa 100644 --- a/util/fuzz/fuzzconfig.py +++ b/util/fuzz/fuzzconfig.py @@ -1,17 +1,60 @@ """ This module provides a structure to define the fuzz environment """ +import gzip +import logging import os +import threading +from concurrent.futures import Future from os import path +from pathlib import Path from string import Template import radiant import database import libpyprjoxide +import cachecontrol -db = None +#db = None + +def get_db(): + #global db + l = threading.local() + if "fuzz_db" in l.__dict__: + return l.__dict__["fuzz_db"] + l.__dict__["fuzz_db"] = libpyprjoxide.Database(database.get_db_root()) + #if db is None: + # db = l.__dict__["fuzz_db"] + return l.__dict__["fuzz_db"] + +PLATFORM_FILTER = os.environ.get("FUZZER_PLATFORM", None) + +_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 + +@cachecontrol.cache_fn() +def find_baseline_differences(device, active_bitstream): + baseline = FuzzConfig.standard_empty(device) + db = get_db() + deltas = libpyprjoxide.Chip.from_bitstream(db, baseline).delta(db, active_bitstream) + + ip_values = libpyprjoxide.Chip.from_bitstream(db, active_bitstream).get_ip_values() + ip_values = [(a, v) for a, v in ip_values if v != 0] + + return deltas, ip_values class FuzzConfig: - 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 +64,94 @@ def __init__(self, device, job, tiles, sv): self.device = device self.job = job self.tiles = tiles + if sv is None: + family = device.split("-")[0] + suffix = device.split("-")[1] + sv = database.get_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 + def standard_empty(device): + if device not in FuzzConfig._standard_empty_bitfile: + cfg = FuzzConfig(job=f"standard-empty-file", device=device, tiles=[]) + FuzzConfig._standard_empty_bitfile[device] = cfg.build_design(cfg.sv, {}, prefix="baseline/") + pass + return FuzzConfig._standard_empty_bitfile[device] + @property def workdir(self): - return path.join(".", "work", self.job) + return path.join(".", "work", self.device, self.job) def make_workdir(self): """Create the working directory for this job, if it doesn't exist already""" os.makedirs(self.workdir, exist_ok=True) + def delta_dir(self): + db_dir = os.environ.get("PRJOXIDE_DB", None) + if db_dir is not None: + db_name = Path(db_dir).name + return f".deltas/{db_name}/{self.device}" + + return f".deltas/{self.device}" + + def serialize_deltas(self, fz, prefix = ""): + 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): + try: + fz.solve(get_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": self.device.split("-")[0], + "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): """ Run Radiant on a given design template, applying a map of substitutions, plus some standard substitutions if not overriden. @@ -60,30 +163,85 @@ 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") 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) + for bf in [bitfile, bitfile_gz]: + if path.exists(bf): + os.remove(bf) + with open(des_template, "r") as inf: with open(desfile, "w") as ouf: if substitute: ouf.write(Template(inf.read()).substitute(**subst)) else: ouf.write(inf.read()) - radiant.run(self.device, desfile, struct_ver=self.struct_mode, raw_bit=False, rbk_mode=self.rbk_mode) - if self.struct_mode and self.udb_specimen is None: - self.udb_specimen = path.join(self.workdir, prefix + "design.tmp", "par.udb") - return bitfile + + 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: + if executor is not None: + f = Future() + f.set_result(foundFile) + return f + return foundFile + + def run_radiant_sh(): + FuzzConfig.radiant_builds = FuzzConfig.radiant_builds + 1 + process_results = radiant.run(self.device, desfile, struct_ver=self.struct_mode, raw_bit=False, rbk_mode=self.rbk_mode) + + error_output = process_results.stderr.decode().strip() + if "ERROR <" in error_output: + raise Exception(f"Error found during bitstream build: {error_output}") + + if self.struct_mode and self.udb_specimen is None: + self.udb_specimen = path.join(self.workdir, prefix + "design.tmp", "par.udb") + if path.exists(bitfile): + return bitfile + if path.exists(bitfile_gz): + return bitfile_gz + + raise Exception(f"Could not generate bitstream file {bitfile} {bitfile_gz}") + + if executor is None: + return run_radiant_sh() + + return executor.submit(run_radiant_sh) @property @@ -91,5 +249,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..41a2ce0 100644 --- a/util/fuzz/fuzzloops.py +++ b/util/fuzz/fuzzloops.py @@ -1,34 +1,376 @@ """ General Utilities for Fuzzing """ - +import asyncio +import concurrent +import logging import os +import shutil +import signal +import threading +import time +from asyncio import CancelledError +from os import abort +from pathlib import Path +from signal import SIGINT, SIGTERM + +import lapie + +from collections import defaultdict + +from concurrent.futures import ThreadPoolExecutor, Future +from contextlib import contextmanager from threading import Thread, RLock +import traceback + +is_in_loop = False + +def jobs(): + if "OXIDE_JOBS" in os.environ: + jobs = int(os.environ["OXIDE_JOBS"]) + else: + jobs = 4 + return jobs + +@contextmanager +def Executor(executor=None): + cleanup = executor is None + if executor is None: + executor = ThreadPoolExecutor(jobs()) + try: + yield executor + finally: + if cleanup: + executor.shutdown(wait=True) -def parallel_foreach(items, func): +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 + print(f"Starting loop with {exception} jobs") + + 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) + print(f"{len(items_queue)} jobs remaining") + + func(item) + except Exception as e: + global error_count + if error_count < 10: + error_count = error_count + 1 + print(f"Error: {e}") + 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: + 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(fut.set_result) + else: + fut.set_result(func(r, *args, **kwargs)) + except BaseException as e: + logging.error(f"Encountered exception while calling {func} with {r} {args} {kwargs}") + traceback.print_exception(e) + 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): + from fuzzconfig import FuzzConfig + + import rich + import rich.console + from rich.live import Live + from rich.panel import Panel + from rich.text import Text + + console = rich.console.Console() + import sys + sys.stdout = console.file + + from rich.logging import RichHandler + + LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() + + logging.basicConfig( + level=LOGLEVEL, + handlers=[RichHandler(console=console, show_time=False, show_path=False)], + ) + + + start_time = time.time() + + async def start(f): + async_executor = None + + 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: + def status_panel(status: str) -> Panel: + return Panel( + f"[bold cyan]{status}[/bold cyan]", + title="Status", + border_style="blue", + height=3, + ) + + async def ui(async_executor): + with Live(status_panel(""), refresh_per_second=10, console=console) as live: + finished_tasks = 0 + + while async_executor.busy() or not task.done(): + histogram = defaultdict(int) + + def process_future(fut): + nonlocal finished_tasks + + name = "anon" + if hasattr(fut, "name"): + name = fut.name + elif hasattr(fut, "get_stack"): + fn = fut.get_stack()[-1].f_code.co_name + if fn != "ui" and fn != "start": + ln = fut.get_stack()[-1].f_lineno + name = f"{fn}:{ln}" + else: + name = None + + if name is not None: + histogram[name] = histogram[name] + 1 + + if fut.done(): + if fut.exception() is not None: + logging.error(f"Encountered exception in future {fut}: {fut.exception()}") + try: + raise fut.exception() + except: + traceback.print_exc() + all_exceptions.append(fut.exception()) + else: + finished_tasks = finished_tasks + 1 + fut.result() + + for fut in async_executor.iterate_futures(): process_future(fut) + for fut in asyncio.all_tasks(): process_future(fut) + + width = shutil.get_terminal_size().columns + text = f"{list(histogram.items())} {async_executor.task_count()} {finished_tasks} finished {len(all_exceptions)} errors, built/cached {FuzzConfig.radiant_builds}/{FuzzConfig.radiant_cache_hits} tool queries {lapie.run_with_udb_cnt} {int(time.time() - start_time)}s" + # print("{text:>{width}}".format(text=text, width=width), end="\r") + live.update(status_panel(text)) + await asyncio.sleep(.1) + + + with Executor() as executor: + try: + asyncio.get_running_loop().set_default_executor(executor) + + async_executor = AsyncExecutor(executor) + + all_exceptions = [] + + task = asyncio.create_task(f(async_executor)) + ui_task = asyncio.create_task(ui(async_executor)) + + await asyncio.gather(task, ui_task) + + logging.info("UI and main task finished") + + except CancelledError: + logging.warning("Cancelling all executor jobs") + executor.shutdown(wait=False, cancel_futures=True) + raise + + except KeyboardInterrupt: + logging.warning("Keyboard interrupt") + except CancelledError: + 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): + return f(executor) + + FuzzerAsyncMain(async_main) \ No newline at end of file diff --git a/util/fuzz/interconnect.py b/util/fuzz/interconnect.py index 5646e3d..33cc57d 100644 --- a/util/fuzz/interconnect.py +++ b/util/fuzz/interconnect.py @@ -1,13 +1,202 @@ """ Utilities for fuzzing interconect """ - +import logging import threading +from concurrent.futures.thread import ThreadPoolExecutor +from pathlib import Path + import tiles import libpyprjoxide import fuzzconfig import fuzzloops import lapie +import database +import os +import math +import tempfile +from os import path +import heapq +import bisect +import re + +from collections import defaultdict + +workdir = tempfile.mkdtemp() + +def create_wires_file(config, wires, prefix = "", empty_version = False, executor = None): + if empty_version: + prefix = prefix + "_empty" + if isinstance(wires, set): + wires = sorted(wires) + + if isinstance(wires, list): + + touched_tiles = set([tiles.get_rc_from_name(config.device, n) for w in wires for n in w]) + + slice_sites = tiles.get_tiles_by_tiletype(config.device, "PLC") + slice_iter = iter([x for x in slice_sites if tiles.get_rc_from_name(config.device, x) not in touched_tiles]) + + + if empty_version: + wires = "\n".join([f""" +wire q_{idx}; +(* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) +SLICE SLICE_I_{idx} ( .A0(q_{idx}), .Q0(q_{idx}) ); + """ for idx, (frm, to) in enumerate(sorted(wires))]) + else: + wires = "\n".join([f""" +(* keep = "true", dont_touch = "true", keep, dont_touch,\\xref:LOG ="q_c@0@0", \\dm:arcs ="{to}.{frm}" *) +wire q_{idx}; + +(* \\dm:cellmodel_primitives ="REG0=reg", \\dm:primitive ="SLICE", \\dm:programming ="MODE:LOGIC Q0:Q0 ", \\dm:site ="{next(slice_iter).split(":")[0]}A" *) +SLICE SLICE_I_{idx} ( .A0(q_{idx}), .Q0(q_{idx}) ); + + """ for idx, (frm, to) in enumerate(sorted(wires))]) + + subst = config.subst_defaults() + arch = config.device.split("-")[0] + device = config.device + package = subst["package"] + speed_grade = subst["speed_grade"] + + source = f"""\ +(* \\db:architecture ="{arch}", \\db:device ="{device}", \\db:package ="{package}", \\db:speed ="{speed_grade}_High-Performance_1.0V", \\db:timestamp = 0, \\db:view ="physical" *) +module top ( +); +{wires} + (* \\xref:LOG ="q_c@0@0" *) + VHI vhi_i(); +endmodule + """ + + vfile = path.join(workdir, f"{prefix}{config.job}.v") + Path(vfile).parent.mkdir(parents=True, exist_ok=True) + + with open(vfile, 'w') as f: + f.write(source) + + if executor is not None: + return config.build_design_future(executor, vfile, prefix=prefix) + return config.build_design(vfile, prefix=prefix) + +def pips_to_sinks(pips): + sinks = {} + + 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 {} + logging.debug(f"Fuzz pips {len(fuzz_pips)}") + + return pips_to_sinks(fuzz_pips) + +def fuzz_interconnect_sinks( + config, + sinks, + full_mux_style=False, + ignore_tiles=set(), + extra_substs={}, + fc_filter=lambda x: True, + executor = None + ): + if sinks is None: + return [] + + + if not isinstance(sinks, dict): + sinks = pips_to_sinks(sinks) + + base_bitf_future = config.build_design_future(executor, config.sv, extra_substs, "base_") + + logging.info(f"Processing {len(sinks)} sinks for {sum([len(v) for k,v in sinks.items()])} designs for {config.job} {config.device}") + + assert(len(config.tiles) > 0) + + def process_bits(bitstreams, from_wires, to_wire): + base_bitf = bitstreams[0] + bitstreams = bitstreams[1:] + db = fuzzconfig.get_db() + + fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf, set(config.tiles), to_wire, + config.tiles[0], + set(ignore_tiles), full_mux_style, not (fc_filter(to_wire))) + + for (from_wire, arc_bit) in zip(from_wires, bitstreams): + fz.add_pip_sample(db, from_wire, arc_bit if arc_bit is not None else base_bitf) + + logging.debug(f"Solving for {to_wire}") + config.solve(fz) + + conns = tiles.get_connections_for_device(config.device) + + futures = [] + with fuzzloops.Executor(executor) as executor: + for to_wire in sinks: + if config.check_deltas(to_wire): + 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)) + + return futures def fuzz_interconnect( config, @@ -20,7 +209,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 +232,66 @@ 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]: - arcs_attr = r', \dm:arcs ="{}.{}"'.format(to_wire, from_wire) - substs = extra_substs.copy() - substs["arcs_attr"] = arcs_attr - arc_bit = config.build_design(config.sv, substs, prefix) - fz.add_pip_sample(fuzzconfig.db, from_wire, arc_bit) - fz.solve(fuzzconfig.db) - fuzzloops.parallel_foreach(list(sorted(sinks.keys())), per_sink) + + print(pin_info, pin_pip) + pin_name = pin_info['pin_name'] + to_wire = pin_pip.to_wire + from_wire = pin_pip.from_wire + is_output = pin_info['pin_node'] == pin_pip.from_wire + + prefix = "{}_{}_{}_".format(config.job, config.device, to_wire) + db = fuzzconfig.get_db() + fz = libpyprjoxide.Fuzzer.pip_fuzzer(db, base_bitf, + set(config.tiles), + to_wire, + config.tiles[0], set(), full_mux_style, not (fc_filter(to_wire))) + + arcs_attr = r', \dm:arcs ="{}.{}"'.format(to_wire, from_wire) + substs = extra_substs.copy() + substs["pin_name"] = pin_name + substs["target"] = ".A0(q)" if is_output else ".Q0(q),.A0(q)" + substs["arcs_attr"] = arcs_attr + + print(f"Building design for ({config.job} {config.device}) {to_wire} to {from_wire}") + arc_bit = config.build_design(config.sv, substs, prefix) + fz.add_pip_sample(db, from_wire, arc_bit) + + config.solve(fz) + + for p, pnode in pins: + assert(len(pnode.pips()) == 1) + per_pip(p, pnode.pips()[0]) + + diff --git a/util/fuzz/nonrouting.py b/util/fuzz/nonrouting.py index f7400e4..f95dddb 100644 --- a/util/fuzz/nonrouting.py +++ b/util/fuzz/nonrouting.py @@ -1,13 +1,24 @@ """ Utilities for fuzzing non-routing configuration. This is the counterpart to interconnect.py """ - import threading import tiles import libpyprjoxide + import fuzzconfig +import fuzzloops +import os + +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 +27,35 @@ 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:] + db = fuzzconfig.get_db() + fz = libpyprjoxide.Fuzzer.word_fuzzer(db, baseline, set(config.tiles), name, desc, length, + baseline) + for i in range(length): + fz.add_word_sample(db, i, bitstreams[i]) + + config.solve(fz) + + return [*bitstream_futures, fuzzloops.chain([baseline, *bitstream_futures], "Solve word", integrate_bitstreams)] + +def fuzz_enum_setting(config, empty_bitfile, name, values, get_sv_substs, include_zeros=False, + assume_zero_base=False, min_cover={}, desc="", mark_relative_to=None, executor = None): """ Fuzz a setting with multiple possible values @@ -39,19 +70,48 @@ 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) + return (opt_name, bitstream) + + for opt in values: + opt_name = opt + if opt == "#SIG" and name.endswith("MUX"): + opt_name = name[:-3].split(".")[1] + if opt == "#INV": + opt_name = "INV" + + if opt in min_cover: + for c in min_cover[opt]: + futures.append(executor.submit(integrate_build, get_sv_substs((opt, c)), f"cover/{name}/{opt}/{c}", opt_name)) + else: + futures.append(executor.submit(integrate_build, get_sv_substs(opt), f"{name}/{opt}/", opt_name)) + for future in futures: + future.name = "Build design" + + def integrate_bitstreams(bitstreams): + db = fuzzconfig.get_db() + fz = libpyprjoxide.Fuzzer.enum_fuzzer(db, empty_bitfile, set(config.tiles), name, desc, + include_zeros, assume_zero_base, mark_relative_to=mark_relative_to) + for (opt, bitstream) in bitstreams: + fz.add_enum_sample(db, opt, bitstream) + config.solve(fz) + + 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 +120,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 +136,26 @@ 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) + 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 fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, desc=""): + def integrate_bitstreams(bitstreams): + baseline = bitstreams[0] + ipcore, iptype = config.tiles[0].split(":") + db = fuzzconfig.get_db() + fz = libpyprjoxide.IPFuzzer.word_fuzzer(db, baseline, ipcore, iptype, name, desc, length, inverted_mode) + for (i, bitfile) in enumerate(bitstreams[1:]): + bits = [(j >> i) & 0x1 == (1 if inverted_mode else 0) for j in range(length)] + fz.add_word_sample(db, bits, bitfile) + config.solve(fz) + + return [*bitstream_futures, fuzzloops.chain([baseline_future, *bitstream_futures], "Solve IP word", integrate_bitstreams)] + +def fuzz_ip_enum_setting(config, empty_bitfile, name, values, get_sv_substs, desc="", executor = None): """ Fuzz a multi-bit IP enum with an optimum number of bitstreams @@ -90,10 +165,54 @@ 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}" + + bitstream_futures = [ + config.build_design_future(executor, config.sv, get_sv_substs(opt), f"{prefix}/{opt}/") + for opt in values + ] + + def integrate_bitstreams(bitstreams): + db = fuzzconfig.get_db() + fz = libpyprjoxide.IPFuzzer.enum_fuzzer(db, empty_bitfile, ipcore, iptype, name, desc) + for (opt, bitfile) in zip(values, bitstreams): + fz.add_enum_sample(db, opt, bitfile) + config.solve(fz) + + return [*bitstream_futures, fuzzloops.chain(bitstream_futures, "Solve IP enum", integrate_bitstreams)] + +def fuzz_primitive_definition(cfg, empty, site, primitive, mark_relative_to = None, mode_name = None, get_substs=None): + def default_get_substs(mode="NONE", kv=None): + if kv is None: + config = "" + else: + key = kv[0] + if key.endswith("MUX"): + key = ":" + key[:-3] + config = f"{mode}:::{key}={kv[1]}" + return dict(cmt="//" if mode == "NONE" else "", + config=config, + site=site) + if get_substs is None: + get_substs = default_get_substs + + if mode_name is None: + mode_name = primitive.name + + for setting in primitive.settings: + subs_fn = lambda x, name=setting.name: get_substs(mode=mode_name, kv=(name, x)) + if setting.name == "MODE": + subs_fn = lambda x: get_substs(mode=x) + + if isinstance(setting, EnumSetting): + fuzz_enum_setting(cfg, empty, f"{mode_name}.{setting.name}", setting.values, subs_fn,False, + desc=setting.desc, mark_relative_to=mark_relative_to) + + diff --git a/util/fuzz/primitives.py b/util/fuzz/primitives.py new file mode 100644 index 0000000..2579f71 --- /dev/null +++ b/util/fuzz/primitives.py @@ -0,0 +1,434 @@ +import json +from collections import defaultdict +from pathlib import Path + +from cffi.model import PrimitiveType + + +class PrimitiveSetting: + def __init__(self, name, desc, depth=3, enable_value=None): + self.name = name + self.desc = desc + self.depth = depth + 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 + + fn = database.get_oxide_root() + f"/tools/primitives/{primitive}.json" + with open(fn) as f: + parsed = json.load(f) + + if core_suffix and site_type is None: + primitive = primitive + "_CORE" + + if mode is None: + mode = primitive + + def create_setting(s): + values = s.get("Value", s.get("Values")) + name = s.get("Name", s.get("Attribute")) + + if len(values) == 1 and name in value_sizes: + return WordSetting(name, value_sizes[name], desc=name, default=int(values[0])) + + if values[0].replace("`", "").startswith("0b"): + bit_cnt = len(values[0].replace("`", "").split(" ")[0].split("0b")[-1]) + return WordSetting(name, bit_cnt, desc=name, number_formatter=WordSetting.binary_formatter) + + return EnumSetting(name, values, desc=str(s.get("Description")), default=values[0]) + + parameters_key = "Parameters" + if parameters_key not in parsed: + parameters_key = [k for k in parsed.keys() if "parameters" in k.lower()][0] + + def create_pin(pin_def, dir): + range = pin_def.get("Range", "") + name = pin_def["Name"] + if '[' in name: + range = name.split("[")[1].replace("]", "") + name = name.split("[")[0] + desc = pin_def["Description"] + + if ":" in range: + range = range.split(":") + assert range[1] == "0" + range = int(range[0]) + else: + range = None + + return PinSetting(name, dir, desc, bits=range) + + pins = [create_pin(p, dir) for (name, dir) in {"Output Ports": "out", "Input Ports": "in"}.items() for p in + parsed[name]] + + return PrimitiveDefinition( + site_type=site_type if site_type else mode, + settings=[create_setting(s) for s in parsed[parameters_key]], + pins=pins, + desc=parsed["description"], + mode=mode, + primitive=primitive + ) + +lram_core = PrimitiveDefinition( + "LRAM_CORE", + [ + EnumSetting("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"], + desc="DTR block enable from MIB"), + + WordSetting("HF_CLK_DIV", 8, default=1, + desc="HF oscillator user output divider (div2–div256)"), + + WordSetting("HF_SED_SEC_DIV", 8, + desc="HF oscillator SED/secondary divider (div2–div256)"), + + EnumSetting("HF_FABRIC_EN", ["ENABLED", "DISABLED"], + desc="HF oscillator trim source mux select"), + + EnumSetting("HF_OSC_EN", ["ENABLED", "DISABLED"], + desc="HF oscillator enable", default="ENABLED", enable_value="ENABLED"), + + EnumSetting("LF_FABRIC_EN", ["ENABLED", "DISABLED"], + desc="LF oscillator trim source mux select"), + + EnumSetting("LF_OUTPUT_EN", ["ENABLED", "DISABLED"], + desc="LF clock output enable"), + + EnumSetting("DEBUG_N", ["ENABLED", "DISABLED"], + desc="Ignore SLEEP/STOP during USER mode when disabled"), + ], + pins=[ + PinSetting("HFOUTEN", dir="in", + desc="HF clock (225MHz) output enable (test only)"), + PinSetting("HFSDSCEN", dir="in", + desc="HF user clock output enable"), + PinSetting("HFOUTCIBEN", dir="in", + desc="CIB control to enable/disable HF oscillator during user mode"), + PinSetting("REBOOT", dir="in", + desc="CIB control to enable/disable hf_clk_config output"), + PinSetting("HFCLKOUT", dir="out", + desc="450MHz with programmable divider (2–256) to user"), + PinSetting("LFCLKOUT", dir="out", + desc="Low frequency clock output after div4 (32kHz)"), + PinSetting("HFCLKCFG", dir="out", + desc="450MHz clock to configuration block"), + PinSetting("HFSDCOUT", dir="out", + desc="450MHz with programmable divider (2–256) to configuration"), + ], +) + +dcc = PrimitiveDefinition.parse_primitive_json("DCC", core_suffix=False) +dcc.get_setting("DCCEN").enable_value = "1" + +PrimitiveDefinition( + "DCS", + settings=[ + EnumSetting("DCSMODE", + ["VCC", "GND", "DCS", "DCS_1", "BUFGCECLK0", "BUFGCECLK0_1", "BUFGCECLK1", "BUFGCECLK1_1", "BUF0", + "BUF1"], desc="clock selector mode", enable_value="DCS"), + ] +) + +pll_core = PrimitiveDefinition.parse_primitive_json("PLL", core_suffix=True) +for s in pll_core.settings: + if s.name.startswith("ENCLK_"): + s.enable_value = "ENABLED" +pll_core.pins = [ + PinSetting(name="INTFBK0", dir="out", desc="", bits=None), + PinSetting(name="INTFBK1", dir="out", desc="", bits=None), + PinSetting(name="INTFBK2", dir="out", desc="", bits=None), + PinSetting(name="INTFBK3", dir="out", desc="", bits=None), + PinSetting(name="INTFBK4", dir="out", desc="", bits=None), + PinSetting(name="INTFBK5", dir="out", desc="", bits=None), + PinSetting(name="LMMIRDATA", dir="out", desc="LMMI read data to fabric.", bits=7), + PinSetting(name="LMMIRDATAVALID", dir="out", desc="LMMI read data valid to fabric.", bits=None), + PinSetting(name="LMMIREADY", dir="out", desc="LMMI ready signal to fabric.", bits=None), + PinSetting(name="CLKOP", dir="out", desc="Primary (A) output clock.", bits=None), + PinSetting(name="CLKOS", dir="out", desc="Secondary (B) output clock.", bits=None), + PinSetting(name="CLKOS2", dir="out", desc="Secondary (C) output clock.", bits=None), + PinSetting(name="CLKOS3", dir="out", desc="Secondary (D) output clock.", bits=None), + PinSetting(name="CLKOS4", dir="out", desc="Secondary (E) output clock.", bits=None), + PinSetting(name="CLKOS5", dir="out", desc="Secondary (F) output clock.", bits=None), + PinSetting(name="INTLOCK", dir="out", desc="PLL internal lock indicator. PLL CIB output.", bits=None), + PinSetting(name="LEGRDYN", dir="out", desc="PLL lock indicator. PLL CIB output.", bits=None), + PinSetting(name="LOCK", dir="out", desc="", bits=None), + PinSetting(name="PFDDN", dir="out", desc="PFD DN output signal to PLL CIB port.", bits=None), + PinSetting(name="PFDUP", dir="out", desc="PFD UP output signal to PLL CIB port.", bits=None), + PinSetting(name="REFMUXCK", dir="out", desc="Reference CLK mux output. PLL CIB output.", bits=None), + PinSetting(name="REGQA", dir="out", desc="", bits=None), PinSetting(name="REGQB", dir="out", desc="", bits=None), + PinSetting(name="REGQB1", dir="out", desc="", bits=None), + PinSetting(name="CLKOUTDL", dir="out", desc="", bits=None), + + #PinSetting(name="LOADREG", dir="in",desc="Valid if MC1_DYN_SOURCE = 1. Initiates a divider output phase shift on negative edge of CIB_LOAD_REG.",bits=None), + #PinSetting(name="DYNROTATE", dir="in", desc="Valid if MC1_DYN_SOURCE = 1. Initiates a change from current VCO clock phase to an earlier or later phase on the negative edge of CIB_ROTATE.", bits=None), + PinSetting(name="LMMICLK", dir="in", desc="LMMI clock from fabric.", bits=None), + PinSetting(name="LMMIRESETN", dir="in", desc="LMMI reset signal to reset the state machine if the IP gets locked.", + bits=None), + PinSetting(name="LMMIREQUEST", dir="in", desc="LMMI request signal from fabric.", bits=None), + PinSetting(name="LMMIWRRDN", dir="in", desc="LMMI write-high/read-low from fabric.", bits=None), + PinSetting(name="LMMIOFFSET", dir="in", + desc="LMMI offset address from fabric. Not all bits are required for an IP.", bits=6), + PinSetting(name="LMMIWDATA", dir="in", desc="LMMI write data from fabric. Not all bits are required for an IP.", + bits=7), + + PinSetting(name="REFCK", dir="in", desc="", bits=None), + PinSetting(name="ENCLKOP", dir="in", desc="Enable A output (CLKOP). Active high. PLL CIB input.", bits=None), + PinSetting(name="ENCLKOS", dir="in", desc="Enable B output (CLKOS). Active high. PLL CIB input.", bits=None), + PinSetting(name="ENCLKOS2", dir="in", desc="Enable C output (CLKOS2). Active high. PLL CIB input.", bits=None), + PinSetting(name="ENCLKOS3", dir="in", desc="Enable D output (CLKOS3). Active high. PLL CIB input.", bits=None), + PinSetting(name="ENCLKOS4", dir="in", desc="Enable E output (CLKOS4). Active high. PLL CIB input.", bits=None), + PinSetting(name="ENCLKOS5", dir="in", desc="Enable F output (CLKOS5). Active high. PLL CIB input.", bits=None), + PinSetting(name="FBKCK", dir="in", desc="", bits=None), + PinSetting(name="LEGACY", dir="in", desc="PLL legacy mode signal. Active high to enter the mode. Enabled by lmmi_legacy fuse. PLL CIB input.", bits=None), + PinSetting(name="PLLRESET", dir="in", desc="Active high to reset PLL. Enabled by MC1_PLLRESET. PLL CIB input.", + bits=None), + PinSetting(name="STDBY", dir="in", desc="PLL standby signal. Active high to put PLL clocks in low. Not used.", + bits=None), + PinSetting(name="ROTDEL", dir="in", desc="", bits=None), + PinSetting(name="DIRDEL", dir="in", desc="", bits=None), + PinSetting(name="ROTDELP1", dir="in", desc="", bits=None), + + PinSetting(name="BINTEST", dir="in", desc="", bits=1), + PinSetting(name="DIRDELP1", dir="in", desc="", bits=None), + PinSetting(name="GRAYACT", dir="in", desc="", bits=4), + PinSetting(name="BINACT", dir="in", desc="", bits=1) +] + +# The documentation has this but its for DIFFIO +def remove_failsafe_enum(definition): + for setting in definition.settings: + if hasattr(setting, "values") and "FAILSAFE" in setting.values: + setting.values.remove("FAILSAFE") + return definition + +seio33 = remove_failsafe_enum(PrimitiveDefinition.parse_primitive_json("SEIO33")) +seio18 = remove_failsafe_enum(PrimitiveDefinition.parse_primitive_json("SEIO18")) + +PrimitiveDefinition.parse_primitive_json("DIFFIO18") +eclkdiv = PrimitiveDefinition.parse_primitive_json("ECLKDIV") +eclkdiv.get_setting("ECLK_DIV").enable_value = "2" + +PrimitiveDefinition.parse_primitive_json("PCLKDIV", core_suffix=False) + +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"