Part 1: Background and Reverse Engineering the BLEs

Contents

CLB Background

The datasheet tells us that the CLB has 32 Basic Logic Elements (BLEs) that each have Look-Up Tables (LUTs). I generally use the terms interchangeably throughout this series of posts.

What is a LUT?

A LUT is the basic building block of an FPGA, it looks up a result based on the given input. The CLB uses a 4-LUT, a LUT with 4 inputs, as its base element.

A LUT does what it says on the box: based on the 4 inputs, it looks up an output value. It can be used to implement any 4-input boolean function, i.e., AND/OR/XOR/NOR, or even complex functions like an AND gate with the second input inverted and the third input overriding the 4th.

LUTs are typically also included with a latch, so they can be configured as various types of latches/flip-flops. When configured like this, it’s generally called a Basic Logic Element (BLE).

Digging into the Datasheet

The first step on our journey is to see what information Microchip does provide us about how the CLB works and what’s inside!.

Microchip’s Basic Logic Element

The first stop is there diagram of what’s inside a BLE:

Microchip BLE DS Diagram

Source: Microchip, PIC16F13145 Datasheet

Microchip’s BLE has a standard 4-input LUT with a D flip-flop. In the CLB, each BLE shares most of the inputs shown above. CLBCLK is a global input to the CLB peripheral; CLBMD/RESET/EN are all for the whole module. Only the BLE Input A/B/C/D and the BLE Flop select are per BLE as well as the LUT config itself.

We can start our definition of what makes up the CLB (the Data Model) with this defining a python dataclass to hold our BLE config:

@dataclass
class BLE_CFG:
    LUT_CONFIG: int  # 16-bit
    FLOPSEL: bool
    LUT_I_A: LUT_IN_A
    LUT_I_B: LUT_IN_B
    LUT_I_C: LUT_IN_C
    LUT_I_D: LUT_IN_D

BLE Input Selection

Next, they have an extremely helpful diagram showing what can be connected to every BLE input:

Microchip LUT select table DS Diagram

Source: Microchip, PIC16F13145 Datasheet

A couple of things to note:

  • Each LUT input configuration is 5 bits for a total of 20 bits of configuration for the BLE inputs.
  • The CLB_BLE[n] values are from the specified BLE, and not every BLE can go to every LUT input. This hints at the physical layout of the BLEs on the chip.
  • The CLB_IN_SYNC[n] inputs come from the 16 CLB module input signals (more on those later).
  • The COUNTER[n] inputs are from the built-in counter peripheral in the CLB (more on it later).

But luckily Microchip provides what the mappings from bits to inputs! This would have been a major pain to reverse engineer.

Let’s add it to our data model:

class LUT_IN_A(IntEnum):
    """BLE INPUT A[4:0]"""
    CLB_BLE_0 = 0b00000
    CLB_BLE_1 = 0b00001
    CLB_BLE_2 = 0b00010
    CLB_BLE_3 = 0b00011
    # ...
    CLBSWIN7 = 0b10011
    COUNT_IS_A1 = 0b10100
    COUNT_IS_A2 = 0b10101
# ...
Full BLE Input Data Model

For the eagle-eyed among you, you will notice I use IN0 below vs. the above CLB_IN_SYNC. The reason will become apparent later.

class LUT_IN_A(IntEnum):
    """BLE INPUT A[4:0]"""
    CLB_BLE_0 = 0b00000
    CLB_BLE_1 = 0b00001
    CLB_BLE_2 = 0b00010
    CLB_BLE_3 = 0b00011
    CLB_BLE_4 = 0b00100
    CLB_BLE_5 = 0b00101
    CLB_BLE_6 = 0b00110
    CLB_BLE_7 = 0b00111
    IN0 = 0b01000
    IN1 = 0b01001
    IN2 = 0b01010
    IN3 = 0b01011
    CLBSWIN0 = 0b01100
    CLBSWIN1 = 0b01101
    CLBSWIN2 = 0b01110
    CLBSWIN3 = 0b01111
    CLBSWIN4 = 0b10000
    CLBSWIN5 = 0b10001
    CLBSWIN6 = 0b10010
    CLBSWIN7 = 0b10011
    COUNT_IS_A1 = 0b10100
    COUNT_IS_A2 = 0b10101


class LUT_IN_B(IntEnum):
    """BLE INPUT B[4:0]"""
    CLB_BLE_8 = 0b00000
    CLB_BLE_9 = 0b00001
    CLB_BLE_10 = 0b00010
    CLB_BLE_11 = 0b00011
    CLB_BLE_12 = 0b00100
    CLB_BLE_13 = 0b00101
    CLB_BLE_14 = 0b00110
    CLB_BLE_15 = 0b00111
    IN4 = 0b01000
    IN5 = 0b01001
    IN6 = 0b01010
    IN7 = 0b01011
    CLBSWIN8 = 0b01100
    CLBSWIN9 = 0b01101
    CLBSWIN10 = 0b01110
    CLBSWIN11 = 0b01111
    CLBSWIN12 = 0b10000
    CLBSWIN13 = 0b10001
    CLBSWIN14 = 0b10010
    CLBSWIN15 = 0b10011
    COUNT_IS_B1 = 0b10100
    COUNT_IS_B2 = 0b10101


class LUT_IN_C(IntEnum):
    """BLE INPUT C[4:0]"""
    CLB_BLE_16 = 0b00000
    CLB_BLE_17 = 0b00001
    CLB_BLE_18 = 0b00010
    CLB_BLE_19 = 0b00011
    CLB_BLE_20 = 0b00100
    CLB_BLE_21 = 0b00101
    CLB_BLE_22 = 0b00110
    CLB_BLE_23 = 0b00111
    IN8 = 0b01000
    IN9 = 0b01001
    IN10 = 0b01010
    IN11 = 0b01011
    CLBSWIN16 = 0b01100
    CLBSWIN17 = 0b01101
    CLBSWIN18 = 0b01110
    CLBSWIN19 = 0b01111
    CLBSWIN20 = 0b10000
    CLBSWIN21 = 0b10001
    CLBSWIN22 = 0b10010
    CLBSWIN23 = 0b10011
    COUNT_IS_C1 = 0b10100
    COUNT_IS_C2 = 0b10101


class LUT_IN_D(IntEnum):
    """BLE INPUT D[4:0]"""
    CLB_BLE_24 = 0b00000
    CLB_BLE_25 = 0b00001
    CLB_BLE_26 = 0b00010
    CLB_BLE_27 = 0b00011
    CLB_BLE_28 = 0b00100
    CLB_BLE_29 = 0b00101
    CLB_BLE_30 = 0b00110
    CLB_BLE_31 = 0b00111
    IN12 = 0b01000
    IN13 = 0b01001
    IN14 = 0b01010
    IN15 = 0b01011
    CLBSWIN24 = 0b01100
    CLBSWIN25 = 0b01101
    CLBSWIN26 = 0b01110
    CLBSWIN27 = 0b01111
    CLBSWIN28 = 0b10000
    CLBSWIN29 = 0b10001
    CLBSWIN30 = 0b10010
    CLBSWIN31 = 0b10011
    COUNT_IS_D1 = 0b10100
    COUNT_IS_D2 = 0b10101

Digging into the tool

The next step is to start poking around at the tool Microchip provides for configuring the CLB.

CLB Synthesizer

Microchip CLB Synthesizer Example

Source: Microchip, CLB Synthesizer

It’s called CLB Synthesizer. You can use their web version or the version that gets auto-installed by MPLAB X (their IDE); it turns out it’s just an NPM module. It is a drag and drop interface that lets you build logic designs and translate them into the corresponding CLB configuration.

How Does it Work?

CLB Synthesizer lets you build your circuit in its drag-and-drop interface (optionally also with a Verilog module), and the web frontend generates some Verilog and corresponding supporting files.

For example, the inverter example above generates the files shown below. Let’s dig in.

Verilog

The first file is the main.v file that contains the body of the module you designed.

(* MUX0.CLBIN = 6'd0 *)
(* MUX0.INSYNC = 3'b100 *)
(* CLKDIV = 3'd1 *)


module main
    (CLBIN0PPS_synchronized, PPS_OUT1);
    input CLBIN0PPS_synchronized;
    output PPS_OUT1;
    logic net2;
    not U1 (net2, CLBIN0PPS_synchronized);
    assign PPS_OUT1 = net2;
endmodule

Constraints

The next file is main.xdc. It’s a constraints file that, in the CLB synthesizer, primarily specifies the names of signals, mapping them from internal names to more user-friendly ones.

set_property PACKAGE_PIN IN0 [get_ports CLBIN0PPS_synchronized]
set_property PACKAGE_PIN PPS_OUT1 [get_ports PPS_OUT1]

Here you can see we define the signal IN0 to be named CLBIN0PPS_synchronized for use above.

Project files

The last few files are files that define the project; they map to the visual layout as well as some metadata about the design.

It does not appear to include any personal information beyond the software version used and the creation date.

design.clb
{
    "backendversion": "25.3.1-4",
    "clockDivider": 1,
    "creationDate": "2023-09-26T12:59:39.897Z",
    "device": "PIC16F13145",
    "entryFile": "main",
    "hdls": [
    ],
    "mnmap": [
        {
            "id": "cd4ee2db-361f-4f1f-94dd-f6a50e67a83c",
            "name": "main",
            "type": 0
        }
    ],
    "name": "inverter",
    "projectId": "6d4d6a22-1273-4bf4-a115-eed4f658bc43",
    "schematics": [
        {
            "data": {
                "class": "GraphLinksModel",
                "linkDataArray": [
                    {
                        "from": 1,
                        "fromPort": "OUT",
                        "key": -1,
                        "points": [
                            -255,
                            -90,
                            -245,
                            -90,
                            -82,
                            -90,
                            -82,
                            -90,
                            -39,
                            -90,
                            -29,
                            -90
                        ],
                        "to": 2,
                        "toPort": "IN1"
                    },
                    {
                        "from": 2,
                        "fromPort": "OUT",
                        "key": -2,
                        "points": [
                            51,
                            -90,
                            61,
                            -90,
                            153,
                            -90,
                            153,
                            -90,
                            285,
                            -90,
                            295,
                            -90
                        ],
                        "to": 3,
                        "toPort": "IN"
                    }
                ],
                "linkFromPortIdProperty": "fromPort",
                "linkKeyProperty": "key",
                "linkToPortIdProperty": "toPort",
                "nodeDataArray": [
                    {
                        "category": "inputport",
                        "inputmodifier": "synchronized",
                        "key": 1,
                        "loc": "-320 -90",
                        "portname": "CLBIN0PPS",
                        "size": "130 20"
                    },
                    {
                        "category": "not",
                        "key": 2,
                        "loc": "10 -90"
                    },
                    {
                        "category": "outputport",
                        "inputmodifier": "synchronized",
                        "key": 3,
                        "loc": "360 -90",
                        "portname": "PPS_OUT1",
                        "size": "130 20"
                    },
                    {
                        "category": "comment",
                        "key": "comment",
                        "loc": "-200 110",
                        "text": "Input Port"
                    }
                ]
            },
            "id": "cd4ee2db-361f-4f1f-94dd-f6a50e67a83c"
        }
    ],
    "type": "Microchip CLB Synthesizer Design",
    "version": "1.0"
}
project.json
{
    "creationDate": "2023-09-26T12:59:39.897Z",
    "device": "PIC16F13145",
    "interface": {
        "inputs": [
            "CLBIN0PPS"
        ],
        "outputs": [
            "PPS_OUT1"
        ]
    },
    "projectId": "6d4d6a22-1273-4bf4-a115-eed4f658bc43",
    "topLevelFile": "main",
    "type": "Microchip CLB Synthesizer Design",
    "version": "0.2"
}
stats.json
{
    "client": "web",
    "clientVersion": "1.3.0-3",
    "device": "PIC16F13145",
    "projectId": "6d4d6a22-1273-4bf4-a115-eed4f658bc43",
    "schematics": 1,
    "symbols": {
        "inputport": 2,
        "not": 2,
        "outputport": 2
    },
    "verilog": 0
}

Submit to Backend

The last step is to zip the files and submit them to the backend for synthesis.

Response

The backend responds with a JSON message containing a zip file that has a lot of interesting information.

{
  "synthres": {
    "formatversion": "0.1",
    "clb": "pic16f131xx_v1",
    "id": "1743199249251",
    "bitstream": [
      "0x3C00",
      ...
      "0x0000"
    ],
    "outputmappings": {},
    "inputs": [
      "CLBIN0PPS"
    ],
    "outputs": [
      "PPS_OUT1"
    ],
    "resources": [
      {
        "type": "LUTs",
        "count": 1,
        "of": 32
      }
    ],
    "status": "success",
    "errormessage": ""
  },
  "process": {
    "returncode": 0,
    "killed": false,
    "stdout": "\nPerform synthesis:\n\n\n ...",
    "stderr": ""
  },
  "zip": "UEsDBB..."
}

Zip content:

zip
│   bitstream.s
│   clb1.c
│   clb1.h
│   clb1_output_mappings.h
│   pre-routing.svg
│   readme.txt
│   routed.svg
│
├───build
│       bitstream.json
│       funfacts.tmp
│       out.fasm
│       out.net
│       out.net.post_routing
│       out.netlist
│       out.phys
│       out.place
│       out.route
│       packing_pin_util.rpt
│       pre-routing.dot
│       pre-routing.v
│       routed.dot
│       routed.v
│       stderr.txt
│       stdout.txt
│       synth.json
│       vpr_stdout.log
│       yosys.log
│
└───input
        design.clb
        main.v
        main.xdc
        project.json
        stats.json

Lets dig in…

Digging into the backend files

We get quite a lot of information from the backend.

The first set of files we get are the outputs for you to load into the CLB on your microcontroller, including the raw bitstream:

│   bitstream.s
│   clb1.c
│   clb1.h
│   clb1_output_mappings.h

This bitstream has 102 words in it, 1428 bits to reverse engineer (this PIC has 14 bit words).

More interesting at the top level is routed.svg:

Routed inverter

routed.svg

This tells us quite a lot, like exactly what BLE is used, what inputs it uses, how it’s configured, etc.

Next, we can take a peek at the build dir. stdout.txt, it tells us that Microchip is using the open source Yosys design chain with custom extensions for the CLB.

stdout.txt
Perform synthesis:


 /----------------------------------------------------------------------------\
 |                                                                            |
 |  yosys -- Yosys Open SYnthesis Suite                                       |
 |                                                                            |
 |  Copyright (C) 2012 - 2020  Claire Xenia Wolf <claire@yosyshq.com>         |
 |                                                                            |
 |  Permission to use, copy, modify, and/or distribute this software for any  |
 |  purpose with or without fee is hereby granted, provided that the above    |
 |  copyright notice and this permission notice appear in all copies.         |
 |                                                                            |
 |  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES  |
 |  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF          |
 |  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR   |
 |  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES    |
 |  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN     |
 |  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF   |
 |  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.            |
 |                                                                            |
 \----------------------------------------------------------------------------/

 Yosys 0.25 (git sha1 e02b7f64b, gcc 10.2.1-6 -fPIC -Os)


-- Running command ` 	read_verilog -sv -I../input ../input/main.v; 	synth_microchip -top main -rmports; scc; 	write_json synth.json' --

1. Executing Verilog-2005 frontend: ../input/main.v
Parsing SystemVerilog input from `../input/main.v' to AST representation.
Generating RTLIL representation for module `\main'.
Successfully finished Verilog frontend.

2. Executing SYNTH_MICROCHIP pass.

2.1. Executing Verilog-2005 frontend: /usr/local/bin/../share/yosys/microchip/cells_sim.v
Parsing Verilog input from `/usr/local/bin/../share/yosys/microchip/cells_sim.v' to AST representation.
Generating RTLIL representation for module `\DFF'.
Generating RTLIL representation for module `\LUT1'.
Generating RTLIL representation for module `\LUT2'.
Generating RTLIL representation for module `\LUT3'.
Generating RTLIL representation for module `\LUT4'.
Generating RTLIL representation for module `\CLB_COUNTER'.
Generating RTLIL representation for module `\IB'.
Generating RTLIL representation for module `\OB'.
Successfully finished Verilog frontend.

2.2. Executing HIERARCHY pass (managing design hierarchy).

2.2.1. Analyzing design hierarchy..
Top module:  \main

2.2.2. Analyzing design hierarchy..
Top module:  \main
Removed 0 unused modules.

2.3. Executing PROC pass (convert processes to netlists).

2.3.1. Executing PROC_CLEAN pass (remove empty switches from decision trees).
Cleaned up 0 empty switches.

2.3.2. Executing PROC_RMDEAD pass (remove dead branches from decision trees).
Removed a total of 0 dead cases.

2.3.3. Executing PROC_PRUNE pass (remove redundant assignments in processes).
Removed 0 redundant assignments.
Promoted 0 assignments to connections.

2.3.4. Executing PROC_INIT pass (extract init attributes).

2.3.5. Executing PROC_ARST pass (detect async resets in processes).

2.3.6. Executing PROC_ROM pass (convert switches to ROMs).
Converted 0 switches.

2.3.7. Executing PROC_MUX pass (convert decision trees to multiplexers).

2.3.8. Executing PROC_DLATCH pass (convert process syncs to latches).

2.3.9. Executing PROC_DFF pass (convert process syncs to FFs).

2.3.10. Executing PROC_MEMWR pass (convert process memory writes to cells).

2.3.11. Executing PROC_CLEAN pass (remove empty switches from decision trees).
Cleaned up 0 empty switches.

2.3.12. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.4. Executing FLATTEN pass (flatten design).

2.5. Executing DEMINOUT pass (demote inout ports to input or output).

2.6. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.7. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..
Removed 0 unused cells and 1 unused wires.
<suppressed ~1 debug messages>

2.8. Executing CHECK pass (checking for obvious problems).
Checking module main...
Found and reported 0 problems.

2.9. Executing OPT pass (performing simple optimizations).

2.9.1. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.9.2. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.9.3. Executing OPT_MUXTREE pass (detect dead branches in mux trees).
Running muxtree optimizer on module \main..
  Creating internal representation of mux trees.
  No muxes found in this module.
Removed 0 multiplexer ports.

2.9.4. Executing OPT_REDUCE pass (consolidate $*mux and $reduce_* inputs).
  Optimizing cells in module \main.
Performed a total of 0 changes.

2.9.5. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.9.6. Executing OPT_DFF pass (perform DFF optimizations).

2.9.7. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.9.8. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.9.9. Finished OPT passes. (There is nothing left to do.)

2.10. Executing FSM pass (extract and optimize FSM).

2.10.1. Executing FSM_DETECT pass (finding FSMs in design).

2.10.2. Executing FSM_EXTRACT pass (extracting FSM from design).

2.10.3. Executing FSM_OPT pass (simple optimizations of FSMs).

2.10.4. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.10.5. Executing FSM_OPT pass (simple optimizations of FSMs).

2.10.6. Executing FSM_RECODE pass (re-assigning FSM state encoding).

2.10.7. Executing FSM_INFO pass (dumping all available information on FSM cells).

2.10.8. Executing FSM_MAP pass (mapping FSMs to basic logic).

2.11. Executing OPT pass (performing simple optimizations).

2.11.1. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.11.2. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.11.3. Executing OPT_MUXTREE pass (detect dead branches in mux trees).
Running muxtree optimizer on module \main..
  Creating internal representation of mux trees.
  No muxes found in this module.
Removed 0 multiplexer ports.

2.11.4. Executing OPT_REDUCE pass (consolidate $*mux and $reduce_* inputs).
  Optimizing cells in module \main.
Performed a total of 0 changes.

2.11.5. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.11.6. Executing OPT_DFF pass (perform DFF optimizations).

2.11.7. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.11.8. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.11.9. Finished OPT passes. (There is nothing left to do.)

2.12. Executing WREDUCE pass (reducing word size of cells).

2.13. Executing PEEPOPT pass (run peephole optimizers).

2.14. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.15. Executing TECHMAP pass (map to technology primitives).

2.15.1. Executing Verilog-2005 frontend: /usr/local/bin/../share/yosys/cmp2lut.v
Parsing Verilog input from `/usr/local/bin/../share/yosys/cmp2lut.v' to AST representation.
Generating RTLIL representation for module `\_90_lut_cmp_'.
Successfully finished Verilog frontend.

2.15.2. Continuing TECHMAP pass.
No more expansions possible.
<suppressed ~6 debug messages>

2.16. Executing SHARE pass (SAT-based resource sharing).

2.17. Executing OPT pass (performing simple optimizations).

2.17.1. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.17.2. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.17.3. Executing OPT_MUXTREE pass (detect dead branches in mux trees).
Running muxtree optimizer on module \main..
  Creating internal representation of mux trees.
  No muxes found in this module.
Removed 0 multiplexer ports.

2.17.4. Executing OPT_REDUCE pass (consolidate $*mux and $reduce_* inputs).
  Optimizing cells in module \main.
Performed a total of 0 changes.

2.17.5. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.17.6. Executing OPT_DFF pass (perform DFF optimizations).

2.17.7. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.17.8. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.17.9. Finished OPT passes. (There is nothing left to do.)

2.18. Executing MEMORY pass.

2.18.1. Executing OPT_MEM pass (optimize memories).
Performed a total of 0 transformations.

2.18.2. Executing OPT_MEM_PRIORITY pass (removing unnecessary memory write priority relations).
Performed a total of 0 transformations.

2.18.3. Executing OPT_MEM_FEEDBACK pass (finding memory read-to-write feedback paths).

2.18.4. Executing MEMORY_BMUX2ROM pass (converting muxes to ROMs).

2.18.5. Executing MEMORY_DFF pass (merging $dff cells to $memrd).

2.18.6. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.18.7. Executing MEMORY_SHARE pass (consolidating $memrd/$memwr cells).

2.18.8. Executing OPT_MEM_WIDEN pass (optimize memories where all ports are wide).
Performed a total of 0 transformations.

2.18.9. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.18.10. Executing MEMORY_COLLECT pass (generating $mem cells).

2.19. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.20. Executing OPT pass (performing simple optimizations).

2.20.1. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.20.2. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.20.3. Executing OPT_DFF pass (perform DFF optimizations).

2.20.4. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.20.5. Finished fast OPT passes.

2.21. Executing MEMORY_MAP pass (converting memories to logic and flip-flops).

2.22. Executing OPT pass (performing simple optimizations).

2.22.1. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.22.2. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.22.3. Executing OPT_MUXTREE pass (detect dead branches in mux trees).
Running muxtree optimizer on module \main..
  Creating internal representation of mux trees.
  No muxes found in this module.
Removed 0 multiplexer ports.

2.22.4. Executing OPT_REDUCE pass (consolidate $*mux and $reduce_* inputs).
  Optimizing cells in module \main.
Performed a total of 0 changes.

2.22.5. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.22.6. Executing OPT_SHARE pass.

2.22.7. Executing OPT_DFF pass (perform DFF optimizations).

2.22.8. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.22.9. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.22.10. Finished OPT passes. (There is nothing left to do.)

2.23. Executing TECHMAP pass (map to technology primitives).

2.23.1. Executing Verilog-2005 frontend: /usr/local/bin/../share/yosys/techmap.v
Parsing Verilog input from `/usr/local/bin/../share/yosys/techmap.v' to AST representation.
Generating RTLIL representation for module `\_90_simplemap_bool_ops'.
Generating RTLIL representation for module `\_90_simplemap_reduce_ops'.
Generating RTLIL representation for module `\_90_simplemap_logic_ops'.
Generating RTLIL representation for module `\_90_simplemap_compare_ops'.
Generating RTLIL representation for module `\_90_simplemap_various'.
Generating RTLIL representation for module `\_90_simplemap_registers'.
Generating RTLIL representation for module `\_90_shift_ops_shr_shl_sshl_sshr'.
Generating RTLIL representation for module `\_90_shift_shiftx'.
Generating RTLIL representation for module `\_90_fa'.
Generating RTLIL representation for module `\_90_lcu'.
Generating RTLIL representation for module `\_90_alu'.
Generating RTLIL representation for module `\_90_macc'.
Generating RTLIL representation for module `\_90_alumacc'.
Generating RTLIL representation for module `\$__div_mod_u'.
Generating RTLIL representation for module `\$__div_mod_trunc'.
Generating RTLIL representation for module `\_90_div'.
Generating RTLIL representation for module `\_90_mod'.
Generating RTLIL representation for module `\$__div_mod_floor'.
Generating RTLIL representation for module `\_90_divfloor'.
Generating RTLIL representation for module `\_90_modfloor'.
Generating RTLIL representation for module `\_90_pow'.
Generating RTLIL representation for module `\_90_pmux'.
Generating RTLIL representation for module `\_90_demux'.
Generating RTLIL representation for module `\_90_lut'.
Successfully finished Verilog frontend.

2.23.2. Continuing TECHMAP pass.
Using extmapper simplemap for cells of type $not.
No more expansions possible.
<suppressed ~72 debug messages>

2.24. Executing OPT pass (performing simple optimizations).

2.24.1. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.24.2. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.24.3. Executing OPT_DFF pass (perform DFF optimizations).

2.24.4. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.24.5. Finished fast OPT passes.

2.25. Executing DFFLEGALIZE pass (convert FFs to types supported by the target).

2.26. Executing OPT pass (performing simple optimizations).

2.26.1. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.26.2. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.26.3. Executing OPT_MUXTREE pass (detect dead branches in mux trees).
Running muxtree optimizer on module \main..
  Creating internal representation of mux trees.
  No muxes found in this module.
Removed 0 multiplexer ports.

2.26.4. Executing OPT_REDUCE pass (consolidate $*mux and $reduce_* inputs).
  Optimizing cells in module \main.
Performed a total of 0 changes.

2.26.5. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.26.6. Executing OPT_DFF pass (perform DFF optimizations).

2.26.7. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.26.8. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.26.9. Finished OPT passes. (There is nothing left to do.)

2.27. Executing ABC9 pass.

2.27.1. Executing ABC9_OPS pass (helper functions for ABC9).

2.27.2. Executing ABC9_OPS pass (helper functions for ABC9).

2.27.3. Executing SCC pass (detecting logic loops).
Found 0 SCCs in module main.
Found 0 SCCs.

2.27.4. Executing ABC9_OPS pass (helper functions for ABC9).

2.27.5. Executing PROC pass (convert processes to netlists).

2.27.5.1. Executing PROC_CLEAN pass (remove empty switches from decision trees).
Cleaned up 0 empty switches.

2.27.5.2. Executing PROC_RMDEAD pass (remove dead branches from decision trees).
Removed a total of 0 dead cases.

2.27.5.3. Executing PROC_PRUNE pass (remove redundant assignments in processes).
Removed 0 redundant assignments.
Promoted 0 assignments to connections.

2.27.5.4. Executing PROC_INIT pass (extract init attributes).

2.27.5.5. Executing PROC_ARST pass (detect async resets in processes).

2.27.5.6. Executing PROC_ROM pass (convert switches to ROMs).
Converted 0 switches.

2.27.5.7. Executing PROC_MUX pass (convert decision trees to multiplexers).

2.27.5.8. Executing PROC_DLATCH pass (convert process syncs to latches).

2.27.5.9. Executing PROC_DFF pass (convert process syncs to FFs).

2.27.5.10. Executing PROC_MEMWR pass (convert process memory writes to cells).

2.27.5.11. Executing PROC_CLEAN pass (remove empty switches from decision trees).
Cleaned up 0 empty switches.

2.27.5.12. Executing OPT_EXPR pass (perform const folding).

2.27.6. Executing TECHMAP pass (map to technology primitives).

2.27.6.1. Executing Verilog-2005 frontend: /usr/local/bin/../share/yosys/techmap.v
Parsing Verilog input from `/usr/local/bin/../share/yosys/techmap.v' to AST representation.
Generating RTLIL representation for module `\_90_simplemap_bool_ops'.
Generating RTLIL representation for module `\_90_simplemap_reduce_ops'.
Generating RTLIL representation for module `\_90_simplemap_logic_ops'.
Generating RTLIL representation for module `\_90_simplemap_compare_ops'.
Generating RTLIL representation for module `\_90_simplemap_various'.
Generating RTLIL representation for module `\_90_simplemap_registers'.
Generating RTLIL representation for module `\_90_shift_ops_shr_shl_sshl_sshr'.
Generating RTLIL representation for module `\_90_shift_shiftx'.
Generating RTLIL representation for module `\_90_fa'.
Generating RTLIL representation for module `\_90_lcu'.
Generating RTLIL representation for module `\_90_alu'.
Generating RTLIL representation for module `\_90_macc'.
Generating RTLIL representation for module `\_90_alumacc'.
Generating RTLIL representation for module `\$__div_mod_u'.
Generating RTLIL representation for module `\$__div_mod_trunc'.
Generating RTLIL representation for module `\_90_div'.
Generating RTLIL representation for module `\_90_mod'.
Generating RTLIL representation for module `\$__div_mod_floor'.
Generating RTLIL representation for module `\_90_divfloor'.
Generating RTLIL representation for module `\_90_modfloor'.
Generating RTLIL representation for module `\_90_pow'.
Generating RTLIL representation for module `\_90_pmux'.
Generating RTLIL representation for module `\_90_demux'.
Generating RTLIL representation for module `\_90_lut'.
Successfully finished Verilog frontend.

2.27.6.2. Continuing TECHMAP pass.
No more expansions possible.
<suppressed ~80 debug messages>

2.27.7. Executing OPT pass (performing simple optimizations).

2.27.7.1. Executing OPT_EXPR pass (perform const folding).

2.27.7.2. Executing OPT_MERGE pass (detect identical cells).
Removed a total of 0 cells.

2.27.7.3. Executing OPT_MUXTREE pass (detect dead branches in mux trees).
Removed 0 multiplexer ports.

2.27.7.4. Executing OPT_REDUCE pass (consolidate $*mux and $reduce_* inputs).
Performed a total of 0 changes.

2.27.7.5. Executing OPT_MERGE pass (detect identical cells).
Removed a total of 0 cells.

2.27.7.6. Executing OPT_DFF pass (perform DFF optimizations).

2.27.7.7. Executing OPT_CLEAN pass (remove unused cells and wires).

2.27.7.8. Executing OPT_EXPR pass (perform const folding).

2.27.7.9. Finished OPT passes. (There is nothing left to do.)

2.27.8. Executing TECHMAP pass (map to technology primitives).

2.27.8.1. Executing Verilog-2005 frontend: /usr/local/bin/../share/yosys/abc9_map.v
Parsing Verilog input from `/usr/local/bin/../share/yosys/abc9_map.v' to AST representation.
Successfully finished Verilog frontend.

2.27.8.2. Continuing TECHMAP pass.
No more expansions possible.
<suppressed ~2 debug messages>

2.27.9. Executing Verilog-2005 frontend: /usr/local/bin/../share/yosys/abc9_model.v
Parsing Verilog input from `/usr/local/bin/../share/yosys/abc9_model.v' to AST representation.
Generating RTLIL representation for module `$__ABC9_DELAY'.
Generating RTLIL representation for module `$__ABC9_SCC_BREAKER'.
Generating RTLIL representation for module `$__DFF_N__$abc9_flop'.
Generating RTLIL representation for module `$__DFF_P__$abc9_flop'.
Successfully finished Verilog frontend.

2.27.10. Executing ABC9_OPS pass (helper functions for ABC9).

2.27.11. Executing ABC9_OPS pass (helper functions for ABC9).
<suppressed ~2 debug messages>

2.27.12. Executing AIGMAP pass (map logic to AIG).
Module main: replaced 0 cells with 0 new cells, skipped 1 cells.
  not replaced 1 cell types:
       1 $_NOT_

2.27.12.1. Executing ABC9_OPS pass (helper functions for ABC9).

2.27.12.2. Executing XAIGER backend.
<suppressed ~5 debug messages>
Extracted 0 AND gates and 5 wires from module `main' to a netlist network with 1 inputs and 1 outputs.

2.27.12.3. Executing ABC9_EXE pass (technology mapping using ABC9).

2.27.12.4. Executing ABC9.
Running ABC command: "<yosys-exe-dir>/yosys-abc" -s -f <abc-temp-dir>/abc.script 2>&1
ABC: ABC command line: "source <abc-temp-dir>/abc.script".
ABC: 
ABC: + read_lut <abc-temp-dir>/lutdefs.txt 
ABC: + read_box <abc-temp-dir>/input.box 
ABC: + &read <abc-temp-dir>/input.xaig 
ABC: + &ps 
ABC: <abc-temp-dir>/input : i/o =      1/      1  and =       0  lev =    0 (0.00)  mem = 0.00 MB  box = 0  bb = 0
ABC: + &if -v 
ABC: K = 4. Memory (bytes): Truth =    0. Cut =   48. Obj =  128. Set =  528. CutMin = no
ABC: Node =       0.  Ch =     0.  Total mem =    0.00 MB. Peak cut mem =    0.00 MB.
ABC: P:  Del =    0.00.  Ar =       0.0.  Edge =        0.  Cut =        0.  T =     0.00 sec
ABC: P:  Del =    0.00.  Ar =       0.0.  Edge =        0.  Cut =        0.  T =     0.00 sec
ABC: P:  Del =    0.00.  Ar =       0.0.  Edge =        0.  Cut =        0.  T =     0.00 sec
ABC: E:  Del =    0.00.  Ar =       0.0.  Edge =        0.  Cut =        0.  T =     0.00 sec
ABC: F:  Del =    0.00.  Ar =       0.0.  Edge =        0.  Cut =        0.  T =     0.00 sec
ABC: E:  Del =    0.00.  Ar =       0.0.  Edge =        0.  Cut =        0.  T =     0.00 sec
ABC: A:  Del =    0.00.  Ar =       0.0.  Edge =        0.  Cut =        0.  T =     0.00 sec
ABC: E:  Del =    0.00.  Ar =       0.0.  Edge =        0.  Cut =        0.  T =     0.00 sec
ABC: A:  Del =    0.00.  Ar =       0.0.  Edge =        0.  Cut =        0.  T =     0.00 sec
ABC: E:  Del =    0.00.  Ar =       0.0.  Edge =        0.  Cut =        0.  T =     0.00 sec
ABC: Total time =     0.00 sec
ABC: + &ps -l 
ABC: <abc-temp-dir>/input : i/o =      1/      1  and =       0  lev =    0 (0.00)  mem = 0.00 MB  box = 0  bb = 0
ABC: Mapping (K=0)  :  lut =      0  edge =       0  lev =    0 (0.00)  mem = 0.00 MB
ABC: LUT = 0 : Ave = 0.00
ABC: + &write -n <abc-temp-dir>/output.aig 
ABC: + time 
ABC: elapse: 0.00 seconds, total: 0.00 seconds

2.27.12.5. Executing AIGER frontend.
<suppressed ~12 debug messages>
Removed 0 unused cells and 2 unused wires.

2.27.12.6. Executing ABC9_OPS pass (helper functions for ABC9).
ABC RESULTS:              $lut cells:        1
ABC RESULTS:           input signals:        1
ABC RESULTS:          output signals:        1
Removing temp directory.

2.27.13. Executing TECHMAP pass (map to technology primitives).

2.27.13.1. Executing Verilog-2005 frontend: /usr/local/bin/../share/yosys/abc9_unmap.v
Parsing Verilog input from `/usr/local/bin/../share/yosys/abc9_unmap.v' to AST representation.
Generating RTLIL representation for module `\$__DFF_x__$abc9_flop'.
Generating RTLIL representation for module `\$__ABC9_SCC_BREAKER'.
Successfully finished Verilog frontend.

2.27.13.2. Continuing TECHMAP pass.
No more expansions possible.
<suppressed ~5 debug messages>

2.28. Executing TECHMAP pass (map to technology primitives).

2.28.1. Executing Verilog-2005 frontend: /usr/local/bin/../share/yosys/microchip/cells_map.v
Parsing Verilog input from `/usr/local/bin/../share/yosys/microchip/cells_map.v' to AST representation.
Generating RTLIL representation for module `\$_DFF_P_'.
Generating RTLIL representation for module `\$lut'.
Successfully finished Verilog frontend.

2.28.2. Continuing TECHMAP pass.
Using template $paramod\$lut\WIDTH=32'00000000000000000000000000000001\LUT=2'01 for cells of type $lut.
No more expansions possible.
<suppressed ~18 debug messages>

2.29. Executing OPT pass (performing simple optimizations).

2.29.1. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.29.2. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.29.3. Executing OPT_DFF pass (perform DFF optimizations).

2.29.4. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..
Removed 0 unused cells and 4 unused wires.
<suppressed ~1 debug messages>

2.29.5. Finished fast OPT passes.

2.30. Executing SPLITNETS pass (splitting up multi-bit signals).

2.31. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..
Removed 0 unused cells and 1 unused wires.
<suppressed ~1 debug messages>

2.32. Executing RMPORTS pass (remove ports with no connections).
Finding unconnected ports in module \main
Removed 0 unused ports.
Removing now-unused cell ports in module \main

2.33. Executing OPT pass (performing simple optimizations).

2.33.1. Executing OPT_EXPR pass (perform const folding).
Optimizing module main.

2.33.2. Executing OPT_MERGE pass (detect identical cells).
Finding identical cells in module `\main'.
Removed a total of 0 cells.

2.33.3. Executing OPT_DFF pass (perform DFF optimizations).

2.33.4. Executing OPT_CLEAN pass (remove unused cells and wires).
Finding unused cells or wires in module \main..

2.33.5. Finished fast OPT passes.

2.34. Executing IOPADMAP pass (mapping inputs/outputs to IO-PAD cells).
Mapping port main.CLBIN0PPS_synchronized using IB.
Mapping port main.PPS_OUT1 using OB.

2.35. Executing SETUNDEF pass (replace undef values with defined constants).

2.36. Executing HIERARCHY pass (managing design hierarchy).

2.36.1. Analyzing design hierarchy..
Top module:  \main

2.36.2. Analyzing design hierarchy..
Top module:  \main
Removed 0 unused modules.

2.37. Printing statistics.

=== main ===

   Number of wires:                  4
   Number of wire bits:              4
   Number of public wires:           2
   Number of public wire bits:       2
   Number of memories:               0
   Number of memory bits:            0
   Number of processes:              0
   Number of cells:                  3
     IB                              1
     LUT1                            1
     OB                              1

2.38. Executing CHECK pass (checking for obvious problems).
Checking module main...
Found and reported 0 problems.

3. Executing SCC pass (detecting logic loops).
Found 0 SCCs in module main.
Found 0 SCCs.

4. Executing JSON backend.

End of script. Logfile hash: 602acaecb5, CPU: user 0.07s system 0.01s, MEM: 14.71 MB peak
Yosys 0.25 (git sha1 e02b7f64b, gcc 10.2.1-6 -fPIC -Os)
Time spent: 34% 1x abc9_exe (0 sec), 23% 12x read_verilog (0 sec), ...

Run place & route:

python -m fpga_interchange.yosys_json \
--schema_dir /usr/local/share/arch-defs/third_party/fpga-interchange-schema/interchange \
--device /usr/local/share/arch-defs/device/pic16f131xx_v1/fpga_interchange/chipdb.device \
synth.json out.netlist
python /usr/local/share/arch-defs/device/pic16f131xx_v1/fasm_generator.py --schema_dir /usr/local/share/arch-defs/third_party/fpga-interchange-schema/interchange /usr/local/share/arch-defs/device/pic16f131xx_v1/fpga_interchange/chipdb.device out.netlist out.phys out.fasm

build the output files:

python /usr/local/share/arch-defs/device/pic16f131xx_v1/bitstream/assembler.py -p ../input/project.json out.fasm bitstream.json
python /usr/local/share/arch-defs/device/pic16f131xx_v1/bitstream/format.py -t /usr/local/share/arch-defs/device/pic16f131xx_v1/templates/c_header_template.c bitstream.json ../bitstream.s
python /usr/local/share/arch-defs/result_generators/mapping_header.py -t /usr/local/share/arch-defs/device/pic16f131xx_v1/templates/output_mappings.h bitstream.json ../clb1_output_mappings.h

2. Executing SCC pass (detecting logic loops).
Found 0 SCCs in module main.
Found 0 SCCs.

3. Printing statistics.

=== main ===

   Number of wires:                  4
   Number of wire bits:              4
   Number of public wires:           2
   Number of public wire bits:       2
   Number of memories:               0
   Number of memory bits:            0
   Number of processes:              0
   Number of cells:                  3
     IB                              1
     LUT1                            1
     OB                              1


Draw the graphs:

Reading and parsing FASM file...
Reading XDC file...
Decoding...
Writing output file(s)...

But very interesting is the out.fasm file. .fasm stands for FPGA Assembly; it’s a low-level file that describes the FPGA layout before it’s converted into a bitstream.

# Created by the FPGA Interchange FASM Generator (v0.0.18)
BLE_X3Y3.BLE0.FLOPSEL.DISABLE
BLE_X3Y3.BLE0.LUT.INIT[15:0] = 16'b0101010101010101
CLKDIV[2:0] = 3'b001
MUX0.CLBIN[5:0] = 6'b000000
MUX0.INSYNC[2:0] = 3'b100
BLE_X3Y3.BLE0_LI0.IN0
PPS_X5Y3.OPAD0_O.LO_Y_2

This is very exciting, it has an exact detailed description of the configurations, including the exact BLE LUT config, the settings of the clock divider, and it shows the BLE input 0 is connected to IN0 from our constraints file.

The PPS_X5Y3.OPAD0_O.LO_Y_2 line is a bit confusing; let’s ignore that for now.

The rest of the files are not too useful for our work.

Decoding the BLE LUT Configuration

Based on observing the requests, I was able to make a script that submits a zip file and unpacks the response.

As I was playing around I figured out that I can specify LUTs directly int he verilog in a compact form like this, where 16'hAAAA is the LUT config:

LUT4 #(.INIT(16'hAAAA)) name (
        .I0(...),
        .I1(...),
        .I2(...),
        .I3(...),
        .O(...)
);

This makes reading and writing lots of LUT configurations really easy.

The Plan

My first thought was to make a single configuration where I filled up all the LUTs in the system, then I could just flip bits one at a time in the 16-bit LUT and find where the corresponding bit flipped in the bitstream, simple right?

The Reality

Note

If you want to actually run this yourself, you need to carefully manage the includes (main.xdc) to match the Verilog. If it does not, it will refuse to run.

It turns out that if you try to make a logic design that comes even close to filling all LUTs, the synthesis engine just fails.

Based on the datasheet configuration (recall our data model?) each LUT input can connect to a subset of the signals. The most useful ones are the CLBSWIN signals, a 32-bit wide input to the CLB block: CLBSWIN0-7 can connect to the A input, CLBSWIN8-15 to B, CLBSWIN16-23 to C, and CLBSWIN24-31 to D. This means we can (try) to prevent the router from altering our design by connecting the SW inputs to the corresponding LUT inputs in our design.

So we can make the following design:

LUT4 #(.INIT(16'hAAAA)) name (
        .I0(CLBSWIN0),
        .I1(CLBSWIN8),
        .I2(CLBSWIN16),
        .I3(CLBSWIN24),
        .O(PPS_OUT1)
);

When we synthesize this it shows the expected results:

Routed inverter

routed.svg

# Created by the FPGA Interchange FASM Generator (v0.0.18)
BLE_X3Y3.BLE0.FLOPSEL.DISABLE
BLE_X3Y3.BLE0.LUT.INIT[15:0] = 16'b1010101010101010
BLE_X3Y3.BLE0_LI0.CLBSWIN0
BLE_X3Y3.BLE0_LI1.CLBSWIN8
BLE_X3Y3.BLE0_LI2.CLBSWIN16
BLE_X3Y3.BLE0_LI3.CLBSWIN24
PPS_X5Y3.OPAD0_O.LO_Y_2

I tried to expand this by fanning out from the single output, layer by layer, with a random LUT configuration so it couldn’t be optimized out. (If a LUT configuration ignores an input signal, it will be optimized out).

Here is an example of a design that uses all 32 BLEs that can be implemented but fails to synthesize.

ble_fill_32.v

Note

Note: PPS 0 can actually be connected to BLE0\nI also use random LUT configs so they can’t be optimized out.

module main(
    CLBSWIN0, CLBSWIN1, CLBSWIN2, CLBSWIN3, CLBSWIN4, CLBSWIN5, CLBSWIN6, CLBSWIN7,
    CLBSWIN8, CLBSWIN9, CLBSWIN10, CLBSWIN11, CLBSWIN12, CLBSWIN13, CLBSWIN14, CLBSWIN15,
    CLBSWIN16, CLBSWIN17, CLBSWIN18, CLBSWIN19, CLBSWIN20, CLBSWIN21, CLBSWIN22, CLBSWIN23,
    CLBSWIN24, CLBSWIN25, CLBSWIN26, CLBSWIN27, CLBSWIN28, CLBSWIN29, CLBSWIN30, CLBSWIN31,
    PPS_OUT0
);
    input CLBSWIN0, CLBSWIN1, CLBSWIN2, CLBSWIN3, CLBSWIN4, CLBSWIN5, CLBSWIN6, CLBSWIN7;
    input CLBSWIN8, CLBSWIN9, CLBSWIN10, CLBSWIN11, CLBSWIN12, CLBSWIN13, CLBSWIN14, CLBSWIN15;
    input CLBSWIN16, CLBSWIN17, CLBSWIN18, CLBSWIN19, CLBSWIN20, CLBSWIN21, CLBSWIN22, CLBSWIN23;
    input CLBSWIN24, CLBSWIN25, CLBSWIN26, CLBSWIN27, CLBSWIN28, CLBSWIN29, CLBSWIN30, CLBSWIN31;
    output PPS_OUT0;

    wire LUT4_0, LUT4_1, LUT4_2, LUT4_3, LUT4_4, LUT4_5, LUT4_6, LUT4_7, LUT4_8, LUT4_9;
    wire LUT4_10, LUT4_11, LUT4_12, LUT4_13, LUT4_14, LUT4_15, LUT4_16, LUT4_17;
    wire LUT4_18, LUT4_19, LUT4_20, LUT4_21, LUT4_22, LUT4_23, LUT4_24, LUT4_25;
    wire LUT4_26, LUT4_27, LUT4_28, LUT4_29, LUT4_30, LUT4_31;

    // First stage LUT connections
    LUT4 #(.INIT(16'h2087)) lut0 (.I0(LUT4_1), .I1(LUT4_9), .I2(LUT4_17), .I3(LUT4_25), .O(LUT4_0));
    assign PPS_OUT0 = LUT4_0;

    // Second stage LUT connections
    LUT4 #(.INIT(16'hf939)) lut1 (.I0(LUT4_2), .I1(LUT4_10), .I2(LUT4_18), .I3(LUT4_26), .O(LUT4_1));
    LUT4 #(.INIT(16'hf7c2)) lut9 (.I0(LUT4_3), .I1(LUT4_11), .I2(LUT4_19), .I3(LUT4_27), .O(LUT4_9));
    LUT4 #(.INIT(16'h944f)) lut17 (.I0(LUT4_4), .I1(LUT4_12), .I2(LUT4_20), .I3(LUT4_28), .O(LUT4_17));
    LUT4 #(.INIT(16'h6f52)) lut25 (.I0(LUT4_5), .I1(LUT4_13), .I2(LUT4_21), .I3(LUT4_29), .O(LUT4_25));

    // Third stage LUT connections
	LUT4 #(.INIT(16'h68a2)) lut2 (.I0(LUT4_6), .I1(LUT4_14), .I2(LUT4_22), .I3(LUT4_30), .O(LUT4_2));
    LUT4 #(.INIT(16'h2e34)) lut10 (.I0(LUT4_7), .I1(LUT4_15), .I2(LUT4_23), .I3(LUT4_31), .O(LUT4_10));
    LUT4 #(.INIT(16'he58a)) lut18 (.I0(CLBSWIN24), .I1(LUT4_8), .I2(LUT4_16), .I3(LUT4_24), .O(LUT4_18));

    // Leaf LUT connections
    LUT4 #(.INIT(16'bb22)) lut3 (.I0(CLBSWIN0), .I1(CLBSWIN8), .I2(CLBSWIN16), .I3(CLBSWIN24), .O(LUT4_3));
    
    LUT4 #(.INIT(16'h2718)) lut4 (.I0(CLBSWIN0), .I1(CLBSWIN8), .I2(CLBSWIN16), .I3(CLBSWIN24), .O(LUT4_4));
    LUT4 #(.INIT(16'hdcf2)) lut5 (.I0(CLBSWIN0), .I1(CLBSWIN8), .I2(CLBSWIN16), .I3(CLBSWIN24), .O(LUT4_5));
    LUT4 #(.INIT(16'hf4d6)) lut6 (.I0(CLBSWIN1), .I1(CLBSWIN9), .I2(CLBSWIN17), .I3(CLBSWIN25), .O(LUT4_6));
    LUT4 #(.INIT(16'h83ff)) lut7 (.I0(CLBSWIN2), .I1(CLBSWIN10), .I2(CLBSWIN18), .I3(CLBSWIN26), .O(LUT4_7));
    LUT4 #(.INIT(16'hebf8)) lut8 (.I0(CLBSWIN3), .I1(CLBSWIN11), .I2(CLBSWIN19), .I3(CLBSWIN27), .O(LUT4_8));

    LUT4 #(.INIT(16'h05fa)) lut11 (.I0(CLBSWIN4), .I1(CLBSWIN12), .I2(CLBSWIN20), .I3(CLBSWIN28), .O(LUT4_11));
    LUT4 #(.INIT(16'h8d2c)) lut12 (.I0(CLBSWIN5), .I1(CLBSWIN13), .I2(CLBSWIN21), .I3(CLBSWIN29), .O(LUT4_12));
    LUT4 #(.INIT(16'h8c2f)) lut13 (.I0(CLBSWIN6), .I1(CLBSWIN14), .I2(CLBSWIN22), .I3(CLBSWIN30), .O(LUT4_13));
    LUT4 #(.INIT(16'hd720)) lut14 (.I0(CLBSWIN7), .I1(CLBSWIN15), .I2(CLBSWIN23), .I3(CLBSWIN31), .O(LUT4_14));

    LUT4 #(.INIT(16'ha9cf)) lut15 (.I0(CLBSWIN0), .I1(CLBSWIN8), .I2(CLBSWIN16), .I3(CLBSWIN24), .O(LUT4_15));
    LUT4 #(.INIT(16'h8837)) lut16 (.I0(CLBSWIN1), .I1(CLBSWIN9), .I2(CLBSWIN17), .I3(CLBSWIN25), .O(LUT4_16));
    LUT4 #(.INIT(16'hf3f9)) lut19 (.I0(CLBSWIN2), .I1(CLBSWIN10), .I2(CLBSWIN18), .I3(CLBSWIN26), .O(LUT4_19));
    LUT4 #(.INIT(16'h3b19)) lut20 (.I0(CLBSWIN3), .I1(CLBSWIN11), .I2(CLBSWIN19), .I3(CLBSWIN27), .O(LUT4_20));

    LUT4 #(.INIT(16'hc980)) lut21 (.I0(CLBSWIN4), .I1(CLBSWIN12), .I2(CLBSWIN20), .I3(CLBSWIN28), .O(LUT4_21));
    LUT4 #(.INIT(16'h68f9)) lut22 (.I0(CLBSWIN5), .I1(CLBSWIN13), .I2(CLBSWIN21), .I3(CLBSWIN29), .O(LUT4_22));
    LUT4 #(.INIT(16'h7367)) lut23 (.I0(CLBSWIN6), .I1(CLBSWIN14), .I2(CLBSWIN22), .I3(CLBSWIN30), .O(LUT4_23));
    LUT4 #(.INIT(16'hc07f)) lut24 (.I0(CLBSWIN7), .I1(CLBSWIN15), .I2(CLBSWIN23), .I3(CLBSWIN31), .O(LUT4_24));

    LUT4 #(.INIT(16'hd4ec)) lut26 (.I0(CLBSWIN0), .I1(CLBSWIN8), .I2(CLBSWIN16), .I3(CLBSWIN24), .O(LUT4_26));
    LUT4 #(.INIT(16'h9f5e)) lut27 (.I0(CLBSWIN1), .I1(CLBSWIN9), .I2(CLBSWIN17), .I3(CLBSWIN25), .O(LUT4_27));
    LUT4 #(.INIT(16'hc319)) lut28 (.I0(CLBSWIN2), .I1(CLBSWIN10), .I2(CLBSWIN18), .I3(CLBSWIN26), .O(LUT4_28));
    LUT4 #(.INIT(16'hab98)) lut29 (.I0(CLBSWIN3), .I1(CLBSWIN11), .I2(CLBSWIN19), .I3(CLBSWIN27), .O(LUT4_29));

    LUT4 #(.INIT(16'hff75)) lut30 (.I0(CLBSWIN4), .I1(CLBSWIN12), .I2(CLBSWIN20), .I3(CLBSWIN28), .O(LUT4_30));
    LUT4 #(.INIT(16'h2ac5)) lut31 (.I0(CLBSWIN5), .I1(CLBSWIN13), .I2(CLBSWIN21), .I3(CLBSWIN29), .O(LUT4_31));
endmodule

The best I could do was 23 LUTs in a single design:

ble_fill_23.v
(* CLKDIV = 3'd0 *)

module lut5group #(
    // One 80-bit parameter: 5 * 16-bit LUT tables (5*4 nibbles = 20 nibbles)
    parameter [79:0] LUTS_INIT = 80'h00000000000000000000
)(
    // Single 16-bit input, where bits are grouped as:
    // in[3:0]   -> Group 1 (for LUT1)
    // in[7:4]   -> Group 2 (for LUT2)
    // in[11:8]  -> Group 3 (for LUT3)
    // in[15:12] -> Group 4 (for LUT4)
    input  [15:0] in,
    output        out
);

    // Slice the 80-bit LUTS_INIT into five 16-bit LUT INIT values.
    localparam [15:0] LUT1_INIT = LUTS_INIT[15:0];
    localparam [15:0] LUT2_INIT = LUTS_INIT[31:16];
    localparam [15:0] LUT3_INIT = LUTS_INIT[47:32];
    localparam [15:0] LUT4_INIT = LUTS_INIT[63:48];
    localparam [15:0] LUT5_INIT = LUTS_INIT[79:64];

    // Wires for outputs of the first-stage LUTs.
    wire group1_out, group2_out, group3_out, group4_out;

    // First-stage LUTs: each processes one group of 4 input bits.
    LUT4 #(.INIT(LUT1_INIT)) lut1 (
        .I0(in[0]),
        .I1(in[1]),
        .I2(in[2]),
        .I3(in[3]),
        .O(group1_out)
    );

    LUT4 #(.INIT(LUT2_INIT)) lut2 (
        .I0(in[4]),
        .I1(in[5]),
        .I2(in[6]),
        .I3(in[7]),
        .O(group2_out)
    );

    LUT4 #(.INIT(LUT3_INIT)) lut3 (
        .I0(in[8]),
        .I1(in[9]),
        .I2(in[10]),
        .I3(in[11]),
        .O(group3_out)
    );

    LUT4 #(.INIT(LUT4_INIT)) lut4 (
        .I0(in[12]),
        .I1(in[13]),
        .I2(in[14]),
        .I3(in[15]),
        .O(group4_out)
    );

    // Second-stage LUT: combines the four group outputs.
    LUT4 #(.INIT(LUT5_INIT)) lut5 (
        .I0(group1_out),
        .I1(group2_out),
        .I2(group3_out),
        .I3(group4_out),
        .O(out)
    );

endmodule

module main (
    // 16 switch inputs
    input  CLBSWIN0,  CLBSWIN8,  CLBSWIN16, CLBSWIN24,
    input  CLBSWIN1,  CLBSWIN9,  CLBSWIN17, CLBSWIN25,
    input  CLBSWIN2,  CLBSWIN10, CLBSWIN18, CLBSWIN26,
    input  CLBSWIN3,  CLBSWIN11, CLBSWIN19, CLBSWIN27,

    // Four outputs
    output PPS_OUT0,
    output PPS_OUT1,
    output PPS_OUT2,
    output PPS_OUT3,
    output PPS_OUT4,
    output PPS_OUT5,
);

    // Combine the 16 individual inputs into one 16-bit signal.
    wire [15:0] my_inputs = {
        CLBSWIN27, CLBSWIN19, CLBSWIN11, CLBSWIN3,
        CLBSWIN26, CLBSWIN18, CLBSWIN10, CLBSWIN2,
        CLBSWIN25, CLBSWIN17, CLBSWIN9,  CLBSWIN1,
        CLBSWIN24, CLBSWIN16, CLBSWIN8,  CLBSWIN0
    };

    // Instantiate the first LUT grouping module using a single 80-bit literal.
    // The 80-bit value encodes five 16-bit LUT INIT values.
    // Bits [15:0]    -> LUT1_INIT, [31:16]  -> LUT2_INIT,
    // Bits [47:32]   -> LUT3_INIT, [63:48]  -> LUT4_INIT,
    // Bits [79:64]   -> LUT5_INIT.
    lut5group #(
        .LUTS_INIT(80'hf6074086bd89639debf4)
    ) my_lut_group (
        .in(my_inputs),
        .out(PPS_OUT0)
    );

    // Instantiate the second LUT grouping module.
    lut5group #(
        .LUTS_INIT(80'he698a9fb27e04f49ffda)
    ) my_lut_group2 (
        .in(my_inputs),
        .out(PPS_OUT1)
    );

    // Instantiate the third LUT grouping module.
    lut5group #(
        .LUTS_INIT(80'hfb8a1ade450a1e5c9408)
    ) my_lut_group3 (
        .in(my_inputs),
        .out(PPS_OUT2)
    );



    LUT4 #(.INIT(16'hf607)) lut1 (
        .I0(my_inputs[4]),
        .I1(my_inputs[5]),
        .I2(my_inputs[6]),
        .I3(my_inputs[7]),
        .O(PPS_OUT3)
    );

    LUT4 #(.INIT(16'h8821)) lut2 (
        .I0(my_inputs[0]),
        .I1(my_inputs[1]),
        .I2(my_inputs[2]),
        .I3(my_inputs[3]),
        .O(PPS_OUT4)
    );

endmodule

Identifying BLE LUT Locations

But once I have this file I can submit it to make the base configuration, and then generate variants where I flip one bit in the LUT config, then look for a corresponding bit in the bitstream output.

After we submit all the variants we now need to process the results. I want to process the fasm file because it has an exact description of the hardware, but it looks like this:

BLE_X1Y2.BLE0.LUT.INIT[15:0] = 16'b1110101111110100
BLE_X1Y3.BLE0.LUT.INIT[15:0] = 16'b1111111100000000
BLE_X1Y4.BLE0.LUT.INIT[15:0] = 16'b1001010000001000
BLE_X1Y6.BLE0.LUT.INIT[15:0] = 16'b0001111001011100
BLE_X1Y7.BLE0.LUT.INIT[15:0] = 1111000001100111
BLE_X1Y8.BLE0.LUT.INIT[15:0] = 1010101010101010
BLE_X1Y9.BLE0.LUT.INIT[15:0] = 1111111100000000
BLE_X2Y3.BLE0.LUT.INIT[15:0] = 0001101011011110
BLE_X2Y4.BLE0.LUT.INIT[15:0] = 0110001110011101
BLE_X2Y6.BLE0.LUT.INIT[15:0] = 0100111101001001
BLE_X2Y8.BLE0.LUT.INIT[15:0] = 0100000010000110
BLE_X2Y9.BLE0.LUT.INIT[15:0] = 1111111111011010
BLE_X3Y3.BLE0.LUT.INIT[15:0] = 1111011000000111
BLE_X3Y5.BLE0.LUT.INIT[15:0] = 1111000011110000
BLE_X3Y7.BLE0.LUT.INIT[15:0] = 1111111100000000
BLE_X3Y8.BLE0.LUT.INIT[15:0] = 1011110110001001
BLE_X3Y9.BLE0.LUT.INIT[15:0] = 1000100000100001
BLE_X4Y3.BLE0.LUT.INIT[15:0] = 1010100111111011
BLE_X4Y4.BLE0.LUT.INIT[15:0] = 0100010100001010
BLE_X4Y5.BLE0.LUT.INIT[15:0] = 0010011111100000
BLE_X4Y7.BLE0.LUT.INIT[15:0] = 1101101010100100
BLE_X4Y8.BLE0.LUT.INIT[15:0] = 1111101110001010
BLE_X4Y9.BLE0.LUT.INIT[15:0] = 1100110011001100

We need to map the BLE_X3Y3 etc. to the BLE number, luckely we have the routed.svg:

Routed inverter

routed.svg

If you squint long and hard enough you will realize the BLEs are arranged in rows of 4:

Y X=1 X=2 X=3 X=4
1        
2 BLE0 BLE1 BLE2 BLE3
3 BLE4 BLE5 BLE6 BLE7
4 BLE8 BLE9 BLE10 BLE11
5 BLE12 BLE13 BLE14 BLE15
6 BLE16 BLE17 BLE18 BLE19
7 BLE20 BLE21 BLE22 BLE23
8 BLE24 BLE25 BLE26 BLE27
9 BLE28 BLE29 BLE30 BLE31

We can add this to our data model:

class BLEXY(Enum):
    BLE_X1Y2 = 0
    ...
    BLE_X4Y9 = 31
Full BLE Location Data Model

The names exactly match the fasm to ease parsing.

class BLEXY(Enum):
    BLE_X1Y2 = 0
    BLE_X2Y2 = 1
    BLE_X3Y2 = 2
    BLE_X4Y2 = 3
    BLE_X1Y3 = 4
    BLE_X2Y3 = 5
    BLE_X3Y3 = 6
    BLE_X4Y3 = 7
    BLE_X1Y4 = 8
    BLE_X2Y4 = 9
    BLE_X3Y4 = 10
    BLE_X4Y4 = 11
    BLE_X1Y5 = 12
    BLE_X2Y5 = 13
    BLE_X3Y5 = 14
    BLE_X4Y5 = 15
    BLE_X1Y6 = 16
    BLE_X2Y6 = 17
    BLE_X3Y6 = 18
    BLE_X4Y6 = 19
    BLE_X1Y7 = 20
    BLE_X2Y7 = 21
    BLE_X3Y7 = 22
    BLE_X4Y7 = 23
    BLE_X1Y8 = 24
    BLE_X2Y8 = 25
    BLE_X3Y8 = 26
    BLE_X4Y8 = 27
    BLE_X1Y9 = 28
    BLE_X2Y9 = 29
    BLE_X3Y9 = 30
    BLE_X4Y9 = 31

We can now make our first pass FASM parsing class for the BLE FLOPSEL and LUT lines

example fasm lines:

BLE_X1Y2.BLE0.FLOPSEL.DISABLE
BLE_X1Y2.BLE0.LUT.INIT[15:0] = 16'b1110101111110100
class FASM:
    def __init__(self, fasm_file: Path):
        self.LUTS = defaultdict(BLE_CFG)

        with open(fasm_file, "r") as f:
            for l in f.readlines():
                if l.startswith("#"):
                    continue
                if l.startswith("BLE_X"):
                    self.parse_ble(l)
                    continue
                print(f"Unhandled line: {repr(l)}")

    def parse_ble(self, line):
        parts = line.split(".")
        try:
            ble_sel = BLEXY.__members__[parts[0]]

            if parts[1] == "BLE0":
                if parts[2] == "FLOPSEL":
                    # ex: BLE_X1Y2.BLE0.FLOPSEL.DISABLE
                    self.LUTS[ble_sel].FLOPSEL = FLOPSEL.__members__[parts[3].strip()]
                    return
                if parts[2] == "LUT":
                    # ex: BLE_X1Y2.BLE0.LUT.INIT[15:0] = 16'b1110101111110100
                    self.LUTS[ble_sel].LUT_CONFIG = parts[-1][-17:-1]
                    return
        except Exception as e:
            raise RuntimeError(f"Error parsing line '{line}'") from e

        print(f"Unhandled ble line: {parts}")
        return

After parsing both the generated bitstreams and the FASM files we can visualize the result below, it’s sparse because we only obtained data for 28 more or less random LUTs in the output. Below, I annotate them in the format <BLE #>:<LUT Bit>. Luckily, we got BLE 0 and BLE 31 so we know the bounds. If you look long enough you will see there is a pattern repeating every 8 words (I added a bold line to help you see it).

28 BLE LUT Bit Map
Word +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 31:12 31:13 31:14 31:15 31:8 31:9 31:10 31:11
18 31:4 31:5 31:6 31:7
19 31:0 31:1 31:2 31:3 30:12 30:13 30:14 30:15
20 30:8 30:9 30:10 30:11
21 30:4 30:5 30:6 30:7 30:0 30:1 30:2 30:3
22 29:12 29:13 29:14 29:15
23 29:8 29:9 29:10 29:11 29:4 29:5 29:6 29:7
24 29:0 29:1 29:2 29:3
25 28:12 28:13 28:14 28:15 28:8 28:9 28:10 28:11
26 28:4 28:5 28:6 28:7
27 28:0 28:1 28:2 28:3 27:12 27:13 27:14 27:15
28 27:8 27:9 27:10 27:11
29 27:4 27:5 27:6 27:7 27:0 27:1 27:2 27:3
30 26:12 26:13 26:14 26:15
31 26:8 26:9 26:10 26:11 26:4 26:5 26:6 26:7
32 26:0 26:1 26:2 26:3
33 25:12 25:13 25:14 25:15 25:8 25:9 25:10 25:11
34 25:4 25:5 25:6 25:7
35 25:0 25:1 25:2 25:3 24:12 24:13 24:14 24:15
36 24:8 24:9 24:10 24:11
37 24:4 24:5 24:6 24:7 24:0 24:1 24:2 24:3
38 23:12 23:13 23:14 23:15
39 23:8 23:9 23:10 23:11 23:4 23:5 23:6 23:7
40 23:0 23:1 23:2 23:3
41 22:12 22:13 22:14 22:15 22:8 22:9 22:10 22:11
42 22:4 22:5 22:6 22:7
43 22:0 22:1 22:2 22:3
44
45
46 20:12 20:13 20:14 20:15
47 20:8 20:9 20:10 20:11 20:4 20:5 20:6 20:7
48 20:0 20:1 20:2 20:3
49
50
51
52
53
54 17:12 17:13 17:14 17:15
55 17:8 17:9 17:10 17:11 17:4 17:5 17:6 17:7
56 17:0 17:1 17:2 17:3
57 16:12 16:13 16:14 16:15 16:8 16:9 16:10 16:11
58 16:4 16:5 16:6 16:7
59 16:0 16:1 16:2 16:3 15:12 15:13 15:14 15:15
60 15:8 15:9 15:10 15:11
61 15:4 15:5 15:6 15:7 15:0 15:1 15:2 15:3
62 14:12 14:13 14:14 14:15
63 14:8 14:9 14:10 14:11 14:4 14:5 14:6 14:7
64 14:0 14:1 14:2 14:3
65
66
67
68
69
70 11:12 11:13 11:14 11:15
71 11:8 11:9 11:10 11:11 11:4 11:5 11:6 11:7
72 11:0 11:1 11:2 11:3
73
74
75 9:12 9:13 9:14 9:15
76 9:8 9:9 9:10 9:11
77 9:4 9:5 9:6 9:7 9:0 9:1 9:2 9:3
78 8:12 8:13 8:14 8:15
79 8:8 8:9 8:10 8:11 8:4 8:5 8:6 8:7
80 8:0 8:1 8:2 8:3
81 7:12 7:13 7:14 7:15 7:8 7:9 7:10 7:11
82 7:4 7:5 7:6 7:7
83 7:0 7:1 7:2 7:3 6:12 6:13 6:14 6:15
84 6:8 6:9 6:10 6:11
85 6:4 6:5 6:6 6:7 6:0 6:1 6:2 6:3
86 5:12 5:13 5:14 5:15
87 5:8 5:9 5:10 5:11 5:4 5:5 5:6 5:7
88 5:0 5:1 5:2 5:3
89 4:12 4:13 4:14 4:15 4:8 4:9 4:10 4:11
90 4:4 4:5 4:6 4:7
91 4:0 4:1 4:2 4:3
92
93
94
95
96
97
98
99 0:12 0:13 0:14 0:15
100 0:8 0:9 0:10 0:11
101 0:4 0:5 0:6 0:7 0:0 0:1 0:2 0:3


We can use that to extrapolate the LUT locations for every BLE:

Word +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 31:12 31:13 31:14 31:15 31:8 31:9 31:10 31:11
18 31:4 31:5 31:6 31:7
19 31:0 31:1 31:2 31:3 30:12 30:13 30:14 30:15
20 30:8 30:9 30:10 30:11
21 30:4 30:5 30:6 30:7 30:0 30:1 30:2 30:3
22 29:12 29:13 29:14 29:15
23 29:8 29:9 29:10 29:11 29:4 29:5 29:6 29:7
24 29:0 29:1 29:2 29:3
25 28:12 28:13 28:14 28:15 28:8 28:9 28:10 28:11
26 28:4 28:5 28:6 28:7
27 28:0 28:1 28:2 28:3 27:12 27:13 27:14 27:15
28 27:8 27:9 27:10 27:11
29 27:4 27:5 27:6 27:7 27:0 27:1 27:2 27:3
30 26:12 26:13 26:14 26:15
31 26:8 26:9 26:10 26:11 26:4 26:5 26:6 26:7
32 26:0 26:1 26:2 26:3
33 25:12 25:13 25:14 25:15 25:8 25:9 25:10 25:11
34 25:4 25:5 25:6 25:7
35 25:0 25:1 25:2 25:3 24:12 24:13 24:14 24:15
36 24:8 24:9 24:10 24:11
37 24:4 24:5 24:6 24:7 24:0 24:1 24:2 24:3
38 23:12 23:13 23:14 23:15
39 23:8 23:9 23:10 23:11 23:4 23:5 23:6 23:7
40 23:0 23:1 23:2 23:3
41 22:12 22:13 22:14 22:15 22:8 22:9 22:10 22:11
42 22:4 22:5 22:6 22:7
43 22:0 22:1 22:2 22:3 21:12 21:13 21:14 21:15
44 21:8 21:9 21:10 21:11
45 21:4 21:5 21:6 21:7 21:0 21:1 21:2 21:3
46 20:12 20:13 20:14 20:15
47 20:8 20:9 20:10 20:11 20:4 20:5 20:6 20:7
48 20:0 20:1 20:2 20:3
49 19:12 19:13 19:14 19:15 19:8 19:9 19:10 19:11
50 19:4 19:5 19:6 19:7
51 19:0 19:1 19:2 19:3 18:12 18:13 18:14 18:15
52 18:8 18:9 18:10 18:11
53 18:4 18:5 18:6 18:7 18:0 18:1 18:2 18:3
54 17:12 17:13 17:14 17:15
55 17:8 17:9 17:10 17:11 17:4 17:5 17:6 17:7
56 17:0 17:1 17:2 17:3
57 16:12 16:13 16:14 16:15 16:8 16:9 16:10 16:11
58 16:4 16:5 16:6 16:7
59 16:0 16:1 16:2 16:3 15:12 15:13 15:14 15:15
60 15:8 15:9 15:10 15:11
61 15:4 15:5 15:6 15:7 15:0 15:1 15:2 15:3
62 14:12 14:13 14:14 14:15
63 14:8 14:9 14:10 14:11 14:4 14:5 14:6 14:7
64 14:0 14:1 14:2 14:3
65 13:12 13:13 13:14 13:15 13:8 13:9 13:10 13:11
66 13:4 13:5 13:6 13:7
67 13:0 13:1 13:2 13:3 12:12 12:13 12:14 12:15
68 12:8 12:9 12:10 12:11
69 12:4 12:5 12:6 12:7 12:0 12:1 12:2 12:3
70 11:12 11:13 11:14 11:15
71 11:8 11:9 11:10 11:11 11:4 11:5 11:6 11:7
72 11:0 11:1 11:2 11:3
73 10:12 10:13 10:14 10:15 10:8 10:9 10:10 10:11
74 10:4 10:5 10:6 10:7
75 10:0 10:1 10:2 10:3 9:12 9:13 9:14 9:15
76 9:8 9:9 9:10 9:11
77 9:4 9:5 9:6 9:7 9:0 9:1 9:2 9:3
78 8:12 8:13 8:14 8:15
79 8:8 8:9 8:10 8:11 8:4 8:5 8:6 8:7
80 8:0 8:1 8:2 8:3
81 7:12 7:13 7:14 7:15 7:8 7:9 7:10 7:11
82 7:4 7:5 7:6 7:7
83 7:0 7:1 7:2 7:3 6:12 6:13 6:14 6:15
84 6:8 6:9 6:10 6:11
85 6:4 6:5 6:6 6:7 6:0 6:1 6:2 6:3
86 5:12 5:13 5:14 5:15
87 5:8 5:9 5:10 5:11 5:4 5:5 5:6 5:7
88 5:0 5:1 5:2 5:3
89 4:12 4:13 4:14 4:15 4:8 4:9 4:10 4:11
90 4:4 4:5 4:6 4:7
91 4:0 4:1 4:2 4:3 3:12 3:13 3:14 3:15
92 3:8 3:9 3:10 3:11
93 3:4 3:5 3:6 3:7 3:0 3:1 3:2 3:3
94 2:12 2:13 2:14 2:15
95 2:8 2:9 2:10 2:11 2:4 2:5 2:6 2:7
96 2:0 2:1 2:2 2:3
97 1:12 1:13 1:14 1:15 1:8 1:9 1:10 1:11
98 1:4 1:5 1:6 1:7
99 1:0 1:1 1:2 1:3 0:12 0:13 0:14 0:15
100 0:8 0:9 0:10 0:11
101 0:4 0:5 0:6 0:7 0:0 0:1 0:2 0:3

Decoding the BLE LUT Input Configuration

Next we need to decode what bits in the BLE configuration correspond to the input settings. To do this we need to change the inputs for a LUT without the rest of the design changing, this is much more challenging.

The first thing to notice is that if we look at the settings for the LUT input register, we see that the SW setting bits cover the whole bit width of the input register (i.e., the most significant bit (MSB) isn’t fixed but varies with the SWIN selection). So, if we iterate through the CLBSWIN values we can cover all the bit changes.

class LUT_IN_A(IntEnum):
    """BLE INPUT A[4:0]"""
    # ...
    CLBSWIN0 = 0b01100
    CLBSWIN1 = 0b01101
    CLBSWIN2 = 0b01110
    CLBSWIN3 = 0b01111
    CLBSWIN4 = 0b10000
    CLBSWIN5 = 0b10001
    CLBSWIN6 = 0b10010
    CLBSWIN7 = 0b10011
    # ...

Generating Variants

We can iterate on this simple base design cycling the inputs through the SWIN values (generating all 32 variants).

module main (
    input CLBSWIN0, CLBSWIN8, CLBSWIN16, CLBSWIN24,
    output BLE_OUT
);
    LUT4 #(.INIT(16'hABCD)) ble (
        .I0(CLBSWIN0),
        .I1(CLBSWIN8),
        .I2(CLBSWIN16),
        .I3(CLBSWIN24),
        .O(BLE_OUT)
    );
endmodule
Iter SWIN Vals
import shutil, tempfile
from pathlib import Path
from request_func import submit_and_extract

base_dir = Path("blog_single_lut_in_iter")
out_dir = Path("blog_single_lut_in_iter_out")
out_dir.mkdir(exist_ok=True)

# Submit base configuration
with tempfile.TemporaryDirectory() as tdir:
    proj = Path(tdir) / "project"
    shutil.copytree(base_dir, proj)
    print("Submitting base configuration")
    res = submit_and_extract(proj, out_dir)
    print("Output at", res.output_dir.resolve())

# Base mapping: I0->CLBSWIN0, I1->CLBSWIN8, I2->CLBSWIN16, I3->CLBSWIN24.
base_ports = {0: "CLBSWIN0", 1: "CLBSWIN8", 2: "CLBSWIN16", 3: "CLBSWIN24"}
port_ranges = {0: range(0, 8), 1: range(8, 16), 2: range(16, 24), 3: range(24, 32)}

for i, rng in port_ranges.items():
    for num in rng:
        new_port = f"CLBSWIN{num}"
        if new_port == base_ports[i]:
            continue
        with tempfile.TemporaryDirectory() as tdir:
            proj = Path(tdir) / "project"
            shutil.copytree(base_dir, proj)
            for fname in ["main.v", "main.xdc"]:
                content = (proj / fname).read_text(encoding="utf-8")
                (proj / fname).write_text(content.replace(base_ports[i], new_port), encoding="utf-8")
            print(f"Submitting I{i} with {new_port}")
            res = submit_and_extract(proj, out_dir)
            print("Output at", res.output_dir.resolve())

Decoding the Results

We first update our FASM to parse the input lines:

FASM with Input
class FASM:
    def __init__(self, fasm_file: Path):
        self.LUTS = defaultdict(BLE_CFG)
        self.PPS_OUT = dict()
        self.IRQ_OUT = dict()
        self.OE = defaultdict(OESELn)
        self.MUXS = defaultdict(MUX_CFG)
        self.CLKDIV = CLKDIV.DIV_BY_1
        self.COUNTER = COUNTER()
        self.TIMR0_IN = None

        with open(fasm_file, "r") as f:
            for l in f.readlines():
                if l.startswith("#"):
                    continue
                if l.startswith("BLE_X"):
                    self.parse_ble(l)
                    continue
                print(f"Unhandled line: {repr(l)}")

    @staticmethod
    def lo_to_ble(lo_str: str) -> str:
        """Convert 'LO_Y_X' to 'BLE_XY'."""
        _, lo_y, lo_x = lo_str.split("_")
        return f"BLE_X{int(lo_x) + 1}Y{int(lo_y) + 2}"

    @staticmethod
    def ble_to_lo(ble_str: str) -> str:
        """Convert 'BLE_XY' to 'LO_Y_X'."""
        ble_x, ble_y = ble_str[5:].split("Y")
        return f"LO_{int(ble_y) - 2}_{int(ble_x) - 1}"

    def parse_ble(self, line):
        parts = line.split(".")
        try:
            ble_sel = BLEXY.__members__[parts[0]]

            if parts[1] == "BLE0":
                if parts[2] == "FLOPSEL":
                    # ex: BLE_X1Y2.BLE0.FLOPSEL.DISABLE
                    self.LUTS[ble_sel].FLOPSEL = FLOPSEL.__members__[parts[3].strip()]
                    return
                if parts[2] == "LUT":
                    # ex: BLE_X1Y2.BLE0.LUT.INIT[15:0] = 16'b1110101111110100
                    self.LUTS[ble_sel].LUT_CONFIG = parts[-1][-17:-1]
                    return

            if parts[1].startswith("BLE0_LI"):
                if parts[2].startswith("LO_"):
                    # ex. BLE_X3Y2.BLE0_LI0.LO_1_2
                    # Input from another BLE
                    ble_num = BLEXY.__members__[self.lo_to_ble(parts[2])].value
                    ble_id = f"CLB_BLE_{ble_num}"
                else:
                    # ex. BLE_X3Y3.BLE0_LI0.CLBSWIN0
                    ble_id = parts[2].strip()

                if parts[1][-1] == "0":
                    self.LUTS[ble_sel].LUT_I_A = LUT_IN_A.__members__[ble_id]
                    return
                if parts[1][-1] == "1":
                    self.LUTS[ble_sel].LUT_I_B = LUT_IN_B.__members__[ble_id]
                    return
                if parts[1][-1] == "2":
                    self.LUTS[ble_sel].LUT_I_C = LUT_IN_C.__members__[ble_id]
                    return
                if parts[1][-1] == "3":
                    self.LUTS[ble_sel].LUT_I_D = LUT_IN_D.__members__[ble_id]
                    return
        except Exception as e:
            raise RuntimeError(f"Error parsing line '{line}'") from e

        print(f"Unhandled ble line: {parts}")
        return

Now to analyze what bits correspond with these changes we can track correlated bit changes. For example if we see a setting (like the LUT input) has a bit change from 0 to 1 and from a 1 to a 0 we can then look for correlated changes in the bitstream.

If we see a couple of bits in the input and output change, we can save a list of candidate bitstream bits for the setting bit. If we see it change again, we can identify which bitstream bits also changed and then find the intersection of bitstream changes.

The code is deceptively simple, but this powerful technique will serve us for the rest of our reverse engineering.

Note

Throughout the Python code, I use strings of bits (i.e., 10101) for bits because they are easier and clearer to manipulate in Python.

Bit Corr Tracker
class BitCorrelationTracker:
    def __init__(self):
        """
        A dictionary that maps:
          setting_bit (hashable) -> set of candidate bitstream indices
        """
        self.candidates_for_setting_bit = {}

    def add_observation(
        self,
        bitstream_changes: list[tuple[int, bool]],
        setting_changes: list[tuple[object, bool]],
    ):
        """
        bitstream_changes:
            A list of (bit_index, direction) for bits that changed in the bitstream.
            direction = True means 1->0, direction = False means 0->1.

        setting_changes:
            A list of (setting_bit, direction) for bits that changed in the setting.
            setting_bit can be any hashable (str, int, tuple, etc.).
            direction = True means 1->0, direction = False means 0->1.

        Raises:
            ValueError if a new observation contradicts the existing candidates (i.e.,
            it would force a candidate set to become empty).
        """
        # Group bitstream bits by direction
        bitstream_by_dir = {True: set(), False: set()}
        for bit_idx, direction in bitstream_changes:
            bitstream_by_dir[direction].add(bit_idx)

        # Update candidate sets for each setting bit
        for setting_bit, set_dir in setting_changes:
            # All bitstream bits that moved in the same direction
            possible_bits_now = bitstream_by_dir[set_dir]

            # If we've never seen this setting bit before, just initialize:
            if setting_bit not in self.candidates_for_setting_bit:
                self.candidates_for_setting_bit[setting_bit] = set(possible_bits_now)
            else:
                # Intersect the old candidate set with the new set of possible bits
                current_candidates = self.candidates_for_setting_bit[setting_bit]
                new_candidates = current_candidates.intersection(possible_bits_now)

                if not new_candidates:
                    # Contradiction – we have no overlap
                    raise ValueError(
                        f"Contradiction for setting bit {setting_bit} – "
                        f"no common bitstream bits remain candidates."
                    )

                self.candidates_for_setting_bit[setting_bit] = new_candidates

    def get_candidates(self) -> dict:
        """
        Returns the current dictionary of candidate bitstream bits for each setting bit.
        Format:
            {
                setting_bit (hashable): set of candidate bitstream_indices (int),
                ...
            }
        """
        return self.candidates_for_setting_bit


def find_delta_bits(old_attr: str, new_attr: str) -> list[tuple[int, bool]]:
"""
Returns a list of tuples:
- Each tuple contains:
- The bit index (with LSB at index 0) where old_attr and new_attr differ.
- A boolean indicating direction of change: True for 1 -> 0, False for 0 -> 1.

    old_attr and new_attr are binary strings, MSB first.
    """
    # Reverse both strings so that index 0 = LSB
    old_attr_reversed = old_attr[::-1]
    new_attr_reversed = new_attr[::-1]

    changes = []

    for i, (b_old, b_new) in enumerate(zip(old_attr_reversed, new_attr_reversed)):
        if b_old != b_new:
            changes.append((i, b_old == "1" and b_new == "0"))

    return changes
Find Delta LUT Input Bits
import itertools
import json
import pprint
from collections import defaultdict
from pathlib import Path

from bit_corr_tracker import BitCorrelationTracker, find_delta_bits
from data_model import (
    FASM,
    get_lut_setting_bits,
)


def load_bitstream_hex_array(json_file: Path) -> str | None:
    """
    Loads a .bitstream.json file that contains a list of hex strings
    and converts it into a single concatenated binary string.
    """
    data = json.loads(json_file.read_text(encoding="utf-8"))

    bit_chunks = []
    for hex_str in data["bitstream"]:
        val = int(hex_str, 16)
        bits_16 = f"{val:016b}"  # Convert to 16-bit binary
        bit_chunks.append(bits_16)

    return "".join(bit_chunks)


def convert_defaultdict(d):
    """Recursively converts defaultdict to dict."""
    if isinstance(d, defaultdict):
        d = dict(d)  # Convert to a regular dict
    if isinstance(d, dict):
        return {k: convert_defaultdict(v) for k, v in d.items()}
    elif isinstance(d, list):  # Handle nested lists
        return [convert_defaultdict(v) for v in d]
    return d  # Return the value if it's neither dict nor list


def mask_bitstream(bitstream: str, ignored_positions: set[int]) -> str:
    """
    Given a binary string (MSB first) and a set of bit positions (LSB = position 0),
    returns a new binary string with the bits at those positions forced to '0'.
    """
    bits = list(bitstream)
    n = len(bits)
    for pos in ignored_positions:
        index = n - 1 - pos
        if 0 <= index < n:
            bits[index] = "0"
    return "".join(bits)


def load_bs_and_config(request_dir: Path):
    build_dir = request_dir / "response_files" / "build"
    bitstream_file = list(build_dir.glob("bitstream.json"))[0]
    bitstream = load_bitstream_hex_array(bitstream_file)
    fasm_file = list(build_dir.glob("*.fasm"))[0]
    config = FASM(fasm_file)
    return {"bitstream": bitstream, "config": config}


def build_known_bits() -> set[int]:
    known = set()
    for lut in range(0, 31 + 1):
        setting = get_lut_setting_bits(lut)
        known.update(setting.values())
    return known


def main():
    root = Path(".\\blog_single_lut_in_iter_out")
    print(f"Processing: {root}")
    tracker = BitCorrelationTracker()

    all_data = {p: load_bs_and_config(p) for p in root.iterdir() if p.is_dir()}
    print(f"Loaded {len(all_data)} experiments.")
    print("Comparing...")

    input_attr_names = ["LUT_I_A", "LUT_I_B", "LUT_I_C", "LUT_I_D"]

    for dataA, dataB in itertools.combinations(all_data.values(), 2):
        bitsA, configA = dataA["bitstream"], dataA["config"]
        bitsB, configB = dataB["bitstream"], dataB["config"]

        if bitsA == bitsB: continue

        bitstream_changes = find_delta_bits(bitsA, bitsB)
        if not bitstream_changes: continue

        # --- Find ALL setting bit changes for this pair ---
        all_setting_changes = []

        for key in configA.LUTS.keys() | configB.LUTS.keys():
            lut_idx = key.value
            bleA = configA.LUTS.get(key)
            bleB = configB.LUTS.get(key)

            for attr_name in input_attr_names:
                enumA = getattr(bleA, attr_name, None) if bleA else None
                valA = enumA.value if enumA is not None else 0
                binA = format(valA, '05b')

                enumB = getattr(bleB, attr_name, None) if bleB else None
                valB = enumB.value if enumB is not None else 0
                binB = format(valB, '05b')

                if binA != binB:
                    for local_bit_pos, direction in find_delta_bits(binA, binB):
                        setting_id = (lut_idx, attr_name, local_bit_pos)
                        all_setting_changes.append((setting_id, direction))
        if all_setting_changes:
            tracker.add_observation(bitstream_changes, all_setting_changes)

    print("\n--- Raw Tracker Results ---")
    print("Format: (lut_idx, 'A'|'B'|'C'|'D', bit_pos)")
    pprint.pprint(tracker.candidates_for_setting_bit, width=120)


if __name__ == "__main__":
    main()

This code returns:

--- Raw Tracker Results ---
Format: (lut_idx, 'A'|'B'|'C'|'D', bit_pos)
{(6, 'LUT_I_A', 0): {1365},
 (6, 'LUT_I_A', 1): {1366},
 (6, 'LUT_I_A', 2): {1368, 1367},
 (6, 'LUT_I_A', 3): {1368, 1367},
 (6, 'LUT_I_A', 4): {1369},
 (6, 'LUT_I_B', 0): {1354},
 (6, 'LUT_I_B', 1): {1355},
 (6, 'LUT_I_B', 2): {1356, 1357},
 (6, 'LUT_I_B', 3): {1356, 1357},
 (6, 'LUT_I_B', 4): {1360},
 (6, 'LUT_I_C', 0): {1344},
 (6, 'LUT_I_C', 1): {1345},
 (6, 'LUT_I_C', 2): {1346, 1347},
 (6, 'LUT_I_C', 3): {1346, 1347},
 (6, 'LUT_I_C', 4): {1348},
 (6, 'LUT_I_D', 0): {1332},
 (6, 'LUT_I_D', 1): {1333},
 (6, 'LUT_I_D', 2): {1334, 1335},
 (6, 'LUT_I_D', 3): {1334, 1335},
 (6, 'LUT_I_D', 4): {1336}}

It’s pretty easy to see the ambiguous bits are in a row, i.e. LUT_I_A[0:4] is bits [1365:1369]. We can then fill this in (BLE:INP:Bit format):

Word +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13
83 6:D:0 6:D:1 6:D:2 6:D:3 6:D:4 6:12 6:13 6:14 6:15
84 6:C:0 6:C:1 6:C:2 6:C:3 6:C:4 6:8 6:9 6:10 6:11 6:B:0 6:B:1 6:B:2 6:B:3
85 6:B:4 6:4 6:5 6:6 6:7 6:A:0 6:A:1 6:A:2 6:A:3 6:A:4 6:0 6:1 6:2 6:3

Expanding for all BLEs

Now we just need to get this for all the other LUTs. But this leaves us with a problem: how can we change which BLEs are used by our test logic?

We can do this by adding random LUTs to the design forcing the layout engine to move around the block we are working on. This code accepts an output and a set of inputs (here we allow CLBSWIN0-31). Then it makes a random layout by iteratively keeping track of the available inputs (just the output port in the beginning) and then placing a LUT connecting it to one of them; the placed LUT’s inputs are added to the list of global inputs. After the specified number of LUTs have been placed, the remaining ones are filled from the allowed inputs.

We return a function that allows you to include the static Verilog you want to actually change.

Gen Rand Layout
import random
from pathlib import Path

stats_json_content = ...
project_json_content = ...
design_clb_content = ...

def generate_design_structure(dynamic_output_name, available_inputs, num_luts):
    def random_init():
        return f"16'h{random.getrandbits(16):04X}"
    dynamic_luts = []
    unconnected_dynamic = []
    for i in range(num_luts):
        lut = {
            "name": f"lut{i}",
            "output": f"lut{i}_out",
            "inputs": [f"lut{i}_in{k}" for k in range(4)],
            "init": random_init()
        }
        dynamic_luts.append(lut)
        if i > 0:
            if not unconnected_dynamic:
                raise RuntimeError("No free inputs.")
            tgt_lut_idx, tgt_pin_idx = random.choice(unconnected_dynamic)
            unconnected_dynamic.remove((tgt_lut_idx, tgt_pin_idx))
            dynamic_luts[tgt_lut_idx]["inputs"][tgt_pin_idx] = lut["output"]
        unconnected_dynamic.extend([(i, k) for k in range(4)])
    used_dynamic_top_inputs = set()
    if not available_inputs and unconnected_dynamic:
        raise ValueError("No inputs.")
    for lut_idx, pin_idx in unconnected_dynamic:
        chosen_input = random.choice(available_inputs)
        dynamic_luts[lut_idx]["inputs"][pin_idx] = chosen_input
        used_dynamic_top_inputs.add(chosen_input)

    def _write_design_variant(static_verilog_block, static_inputs_list, static_outputs_list, subfolder):
        subfolder = Path(subfolder)
        subfolder.mkdir(parents=True, exist_ok=True)
        all_inputs = sorted(list(used_dynamic_top_inputs.union(static_inputs_list)))
        all_outputs = sorted(list(set(static_outputs_list + [dynamic_output_name])))
        port_defs = ([f"input  {p}" for p in all_inputs] + [f"output {p}" for p in all_outputs])
        wire_defs = ([f"wire {lut['output']};" for lut in dynamic_luts] +
                     [f"wire {p};" for p in static_outputs_list])
        lut_insts = []
        for lut in dynamic_luts:
            pins = [f".I{n}({lut['inputs'][n]})" for n in range(4)] + [f".O({lut['output']})"]
            lut_insts.append(f"LUT4 #(.INIT({lut['init']})) {lut['name']} ({', '.join(pins)});")
        v_content = f"""
module main (
    {", ".join(port_defs)}
);
{chr(10).join(wire_defs)}
{static_verilog_block if static_verilog_block else ''}
{chr(10).join(lut_insts)}
assign {dynamic_output_name} = {dynamic_luts[0]['output'] if dynamic_luts else "1'b0"};
endmodule
"""
        xdc_content = "\n".join(f"set_property PACKAGE_PIN {p} [get_ports {p}]" for p in (all_inputs+all_outputs))+"\n"
        (subfolder / "main.v").write_text(v_content)
        (subfolder / "main.xdc").write_text(xdc_content)
        (subfolder / "stats.json").write_text(stats_json_content)
        (subfolder / "project.json").write_text(project_json_content)
        (subfolder / "design.clb").write_text(design_clb_content)
        return subfolder
    return _write_design_variant
Iter with rand layout
import random
import shutil, tempfile, os
from pathlib import Path
from gen_rand_layout import generate_design_structure
from request_func import submit_and_extract

STATIC_CODE_TEMPLATE = "LUT4 #(.INIT(16'hABCD)) ble (.I0({i0}),.I1({i1}),.I2({i2}),.I3({i3}),.O({o0}));"
STATIC_TEMPLATE_PORTS = {"inputs": ["i0", "i1", "i2", "i3"], "outputs": ["o0"]}
BASE_STATIC_INPUT_MAP = {"i0": "CLBSWIN0", "i1": "CLBSWIN8", "i2": "CLBSWIN16", "i3": "CLBSWIN24"}
STATIC_INPUT_RANGES = {0: range(0, 8), 1: range(8, 16), 2: range(16, 24), 3: range(24, 32)}
STATIC_OUTPUT_MAP = {STATIC_TEMPLATE_PORTS["outputs"][0]: "PPS_OUT0"}

base_output_dir = Path("blog_lut_swap_iter_out") # Define once for clarity
base_output_dir.mkdir(exist_ok=True)
print(f"--- Outputs -> subfolders in: {base_output_dir.resolve()} ---")

total_vars_processed = 0
for layout_idx in range(10):
    print(f"\n--- Layout {layout_idx} ---")
    try:
        write_variant = generate_design_structure(
            "PPS_OUT1",
            [f"CLBSWIN{i}" for i in range(32)],
            random.randint(4, 10)
        )
    except Exception as e:
        print(f"  Structure Gen Error: {e}")
        continue

    for i, swap_range in STATIC_INPUT_RANGES.items():
        template_in_name = STATIC_TEMPLATE_PORTS["inputs"][i]
        for pin_num in swap_range:
            total_vars_processed += 1
            new_phys_port = f"CLBSWIN{pin_num}"
            variant_id = f"l{layout_idx}_sI{i}_{new_phys_port}"
            print(f"  Var {total_vars_processed}: {template_in_name}={new_phys_port} ({variant_id})", end="")

            current_input_map = BASE_STATIC_INPUT_MAP.copy()
            current_input_map[template_in_name] = new_phys_port
            static_block = STATIC_CODE_TEMPLATE.format(**current_input_map, **STATIC_OUTPUT_MAP)
            static_inputs = list(current_input_map.values())
            static_outputs = list(STATIC_OUTPUT_MAP.values())

            with tempfile.TemporaryDirectory() as tdir:
                try:
                    temp_proj = Path(tdir) / "proj"
                    write_variant(static_block, static_inputs, static_outputs, temp_proj)
                    res = submit_and_extract(in_folder=temp_proj, out_dir=base_output_dir)
                    final_out = res.unzipped_files_dir if res.unzipped_files_dir else res.output_dir
                    print(f" -> OK ({final_out.name if final_out else '?'})")
                except Exception as e:
                    print(f" -> FAIL ({e})")

print(f"\n--- Complete ({total_vars_processed} variations) ---")

After running a few variants we have enough data to extrapolate for all the LUTs! The itertools.combinations(all_data.values(), 2) line in our analysis script is critical, by computing the full mesh of every combination of inputs we get significantly better results; generating 10 designs and iterating the static data for them gives us over 80% of bits! The rest are easily identified by pattern.

--- Overall Coverage Stats ---
 0-candidate bits: 57   (8.9%)
 1-candidate bits: 514  (80.3%)
>1-candidate bits: 69   (10.8%)

--- Per-BLE Coverage Stats ---
BLE  zero  single  multi  [out of 20 bits total]
  0     0      17      3
  1     0      20      0
  2     0      18      2
  3     2      12      6
  4     0      20      0
  5     0      20      0
  6     0      18      2
  7     0      18      2
  8     1      19      0
  9     0      20      0
 10     0      20      0
 11     1      14      5
 12     1      19      0
 13     0      20      0
 14     0      18      2
 15     0      16      4
 16     0      20      0
 17     0      20      0
 18     0      20      0
 19     3      16      1
 20     0      15      5
 21     0      18      2
 22     0      20      0
 23     7       6      7
 24     5      15      0
 25     0      20      0
 26     1      19      0
 27     7       0     13
 28     6       9      5
 29     1      12      7
 30     2      15      3
 31    20       0      0
Layout with all LUT inputs
Word +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 31:D:0 31:D:1 31:D:2 31:D:3 31:D:4
17 31:12 31:13 31:14 31:15 31:C:0 31:C:1 31:C:2 31:C:3 31:C:4 31:8 31:9 31:10 31:11
18 31:B:0 31:B:1 31:B:2 31:B:3 31:B:4 31:4 31:5 31:6 31:7 31:A:0 31:A:1 31:A:2 31:A:3 31:A:4
19 31:0 31:1 31:2 31:3 30:D:0 30:D:1 30:D:2 30:D:3 30:D:4 30:12 30:13 30:14 30:15
20 30:C:0 30:C:1 30:C:2 30:C:3 30:C:4 30:8 30:9 30:10 30:11 30:B:0 30:B:1 30:B:2 30:B:3
21 30:B:4 30:4 30:5 30:6 30:7 30:A:0 30:A:1 30:A:2 30:A:3 30:A:4 30:0 30:1 30:2 30:3
22 29:D:0 29:D:1 29:D:2 29:D:3 29:D:4 29:12 29:13 29:14 29:15 29:C:0 29:C:1 29:C:2 29:C:3
23 29:C:4 29:8 29:9 29:10 29:11 29:B:0 29:B:1 29:B:2 29:B:3 29:B:4 29:4 29:5 29:6 29:7
24 29:A:0 29:A:1 29:A:2 29:A:3 29:A:4 29:0 29:1 29:2 29:3 28:D:0 28:D:1 28:D:2 28:D:3 28:D:4
25 28:12 28:13 28:14 28:15 28:C:0 28:C:1 28:C:2 28:C:3 28:C:4 28:8 28:9 28:10 28:11
26 28:B:0 28:B:1 28:B:2 28:B:3 28:B:4 28:4 28:5 28:6 28:7 28:A:0 28:A:1 28:A:2 28:A:3 28:A:4
27 28:0 28:1 28:2 28:3 27:D:0 27:D:1 27:D:2 27:D:3 27:D:4 27:12 27:13 27:14 27:15
28 27:C:0 27:C:1 27:C:2 27:C:3 27:C:4 27:8 27:9 27:10 27:11 27:B:0 27:B:1 27:B:2 27:B:3
29 27:B:4 27:4 27:5 27:6 27:7 27:A:0 27:A:1 27:A:2 27:A:3 27:A:4 27:0 27:1 27:2 27:3
30 26:D:0 26:D:1 26:D:2 26:D:3 26:D:4 26:12 26:13 26:14 26:15 26:C:0 26:C:1 26:C:2 26:C:3
31 26:C:4 26:8 26:9 26:10 26:11 26:B:0 26:B:1 26:B:2 26:B:3 26:B:4 26:4 26:5 26:6 26:7
32 26:A:0 26:A:1 26:A:2 26:A:3 26:A:4 26:0 26:1 26:2 26:3 25:D:0 25:D:1 25:D:2 25:D:3 25:D:4
33 25:12 25:13 25:14 25:15 25:C:0 25:C:1 25:C:2 25:C:3 25:C:4 25:8 25:9 25:10 25:11
34 25:B:0 25:B:1 25:B:2 25:B:3 25:B:4 25:4 25:5 25:6 25:7 25:A:0 25:A:1 25:A:2 25:A:3 25:A:4
35 25:0 25:1 25:2 25:3 24:D:0 24:D:1 24:D:2 24:D:3 24:D:4 24:12 24:13 24:14 24:15
36 24:C:0 24:C:1 24:C:2 24:C:3 24:C:4 24:8 24:9 24:10 24:11 24:B:0 24:B:1 24:B:2 24:B:3
37 24:B:4 24:4 24:5 24:6 24:7 24:A:0 24:A:1 24:A:2 24:A:3 24:A:4 24:0 24:1 24:2 24:3
38 23:D:0 23:D:1 23:D:2 23:D:3 23:D:4 23:12 23:13 23:14 23:15 23:C:0 23:C:1 23:C:2 23:C:3
39 23:C:4 23:8 23:9 23:10 23:11 23:B:0 23:B:1 23:B:2 23:B:3 23:B:4 23:4 23:5 23:6 23:7
40 23:A:0 23:A:1 23:A:2 23:A:3 23:A:4 23:0 23:1 23:2 23:3 22:D:0 22:D:1 22:D:2 22:D:3 22:D:4
41 22:12 22:13 22:14 22:15 22:C:0 22:C:1 22:C:2 22:C:3 22:C:4 22:8 22:9 22:10 22:11
42 22:B:0 22:B:1 22:B:2 22:B:3 22:B:4 22:4 22:5 22:6 22:7 22:A:0 22:A:1 22:A:2 22:A:3 22:A:4
43 22:0 22:1 22:2 22:3 21:D:0 21:D:1 21:D:2 21:D:3 21:D:4 21:12 21:13 21:14 21:15
44 21:C:0 21:C:1 21:C:2 21:C:3 21:C:4 21:8 21:9 21:10 21:11 21:B:0 21:B:1 21:B:2 21:B:3
45 21:B:4 21:4 21:5 21:6 21:7 21:A:0 21:A:1 21:A:2 21:A:3 21:A:4 21:0 21:1 21:2 21:3
46 20:D:0 20:D:1 20:D:2 20:D:3 20:D:4 20:12 20:13 20:14 20:15 20:C:0 20:C:1 20:C:2 20:C:3
47 20:C:4 20:8 20:9 20:10 20:11 20:B:0 20:B:1 20:B:2 20:B:3 20:B:4 20:4 20:5 20:6 20:7
48 20:A:0 20:A:1 20:A:2 20:A:3 20:A:4 20:0 20:1 20:2 20:3 19:D:0 19:D:1 19:D:2 19:D:3 19:D:4
49 19:12 19:13 19:14 19:15 19:C:0 19:C:1 19:C:2 19:C:3 19:C:4 19:8 19:9 19:10 19:11
50 19:B:0 19:B:1 19:B:2 19:B:3 19:B:4 19:4 19:5 19:6 19:7 19:A:0 19:A:1 19:A:2 19:A:3 19:A:4
51 19:0 19:1 19:2 19:3 18:D:0 18:D:1 18:D:2 18:D:3 18:D:4 18:12 18:13 18:14 18:15
52 18:C:0 18:C:1 18:C:2 18:C:3 18:C:4 18:8 18:9 18:10 18:11 18:B:0 18:B:1 18:B:2 18:B:3
53 18:B:4 18:4 18:5 18:6 18:7 18:A:0 18:A:1 18:A:2 18:A:3 18:A:4 18:0 18:1 18:2 18:3
54 17:D:0 17:D:1 17:D:2 17:D:3 17:D:4 17:12 17:13 17:14 17:15 17:C:0 17:C:1 17:C:2 17:C:3
55 17:C:4 17:8 17:9 17:10 17:11 17:B:0 17:B:1 17:B:2 17:B:3 17:B:4 17:4 17:5 17:6 17:7
56 17:A:0 17:A:1 17:A:2 17:A:3 17:A:4 17:0 17:1 17:2 17:3 16:D:0 16:D:1 16:D:2 16:D:3 16:D:4
57 16:12 16:13 16:14 16:15 16:C:0 16:C:1 16:C:2 16:C:3 16:C:4 16:8 16:9 16:10 16:11
58 16:B:0 16:B:1 16:B:2 16:B:3 16:B:4 16:4 16:5 16:6 16:7 16:A:0 16:A:1 16:A:2 16:A:3 16:A:4
59 16:0 16:1 16:2 16:3 15:D:0 15:D:1 15:D:2 15:D:3 15:D:4 15:12 15:13 15:14 15:15
60 15:C:0 15:C:1 15:C:2 15:C:3 15:C:4 15:8 15:9 15:10 15:11 15:B:0 15:B:1 15:B:2 15:B:3
61 15:B:4 15:4 15:5 15:6 15:7 15:A:0 15:A:1 15:A:2 15:A:3 15:A:4 15:0 15:1 15:2 15:3
62 14:D:0 14:D:1 14:D:2 14:D:3 14:D:4 14:12 14:13 14:14 14:15 14:C:0 14:C:1 14:C:2 14:C:3
63 14:C:4 14:8 14:9 14:10 14:11 14:B:0 14:B:1 14:B:2 14:B:3 14:B:4 14:4 14:5 14:6 14:7
64 14:A:0 14:A:1 14:A:2 14:A:3 14:A:4 14:0 14:1 14:2 14:3 13:D:0 13:D:1 13:D:2 13:D:3 13:D:4
65 13:12 13:13 13:14 13:15 13:C:0 13:C:1 13:C:2 13:C:3 13:C:4 13:8 13:9 13:10 13:11
66 13:B:0 13:B:1 13:B:2 13:B:3 13:B:4 13:4 13:5 13:6 13:7 13:A:0 13:A:1 13:A:2 13:A:3 13:A:4
67 13:0 13:1 13:2 13:3 12:D:0 12:D:1 12:D:2 12:D:3 12:D:4 12:12 12:13 12:14 12:15
68 12:C:0 12:C:1 12:C:2 12:C:3 12:C:4 12:8 12:9 12:10 12:11 12:B:0 12:B:1 12:B:2 12:B:3
69 12:B:4 12:4 12:5 12:6 12:7 12:A:0 12:A:1 12:A:2 12:A:3 12:A:4 12:0 12:1 12:2 12:3
70 11:D:0 11:D:1 11:D:2 11:D:3 11:D:4 11:12 11:13 11:14 11:15 11:C:0 11:C:1 11:C:2 11:C:3
71 11:C:4 11:8 11:9 11:10 11:11 11:B:0 11:B:1 11:B:2 11:B:3 11:B:4 11:4 11:5 11:6 11:7
72 11:A:0 11:A:1 11:A:2 11:A:3 11:A:4 11:0 11:1 11:2 11:3 10:D:0 10:D:1 10:D:2 10:D:3 10:D:4
73 10:12 10:13 10:14 10:15 10:C:0 10:C:1 10:C:2 10:C:3 10:C:4 10:8 10:9 10:10 10:11
74 10:B:0 10:B:1 10:B:2 10:B:3 10:B:4 10:4 10:5 10:6 10:7 10:A:0 10:A:1 10:A:2 10:A:3 10:A:4
75 10:0 10:1 10:2 10:3 9:D:0 9:D:1 9:D:2 9:D:3 9:D:4 9:12 9:13 9:14 9:15
76 9:C:0 9:C:1 9:C:2 9:C:3 9:C:4 9:8 9:9 9:10 9:11 9:B:0 9:B:1 9:B:2 9:B:3
77 9:B:4 9:4 9:5 9:6 9:7 9:A:0 9:A:1 9:A:2 9:A:3 9:A:4 9:0 9:1 9:2 9:3
78 8:D:0 8:D:1 8:D:2 8:D:3 8:D:4 8:12 8:13 8:14 8:15 8:C:0 8:C:1 8:C:2 8:C:3
79 8:C:4 8:8 8:9 8:10 8:11 8:B:0 8:B:1 8:B:2 8:B:3 8:B:4 8:4 8:5 8:6 8:7
80 8:A:0 8:A:1 8:A:2 8:A:3 8:A:4 8:0 8:1 8:2 8:3 7:D:0 7:D:1 7:D:2 7:D:3 7:D:4
81 7:12 7:13 7:14 7:15 7:C:0 7:C:1 7:C:2 7:C:3 7:C:4 7:8 7:9 7:10 7:11
82 7:B:0 7:B:1 7:B:2 7:B:3 7:B:4 7:4 7:5 7:6 7:7 7:A:0 7:A:1 7:A:2 7:A:3 7:A:4
83 7:0 7:1 7:2 7:3 6:D:0 6:D:1 6:D:2 6:D:3 6:D:4 6:12 6:13 6:14 6:15
84 6:C:0 6:C:1 6:C:2 6:C:3 6:C:4 6:8 6:9 6:10 6:11 6:B:0 6:B:1 6:B:2 6:B:3
85 6:B:4 6:4 6:5 6:6 6:7 6:A:0 6:A:1 6:A:2 6:A:3 6:A:4 6:0 6:1 6:2 6:3
86 5:D:0 5:D:1 5:D:2 5:D:3 5:D:4 5:12 5:13 5:14 5:15 5:C:0 5:C:1 5:C:2 5:C:3
87 5:C:4 5:8 5:9 5:10 5:11 5:B:0 5:B:1 5:B:2 5:B:3 5:B:4 5:4 5:5 5:6 5:7
88 5:A:0 5:A:1 5:A:2 5:A:3 5:A:4 5:0 5:1 5:2 5:3 4:D:0 4:D:1 4:D:2 4:D:3 4:D:4
89 4:12 4:13 4:14 4:15 4:C:0 4:C:1 4:C:2 4:C:3 4:C:4 4:8 4:9 4:10 4:11
90 4:B:0 4:B:1 4:B:2 4:B:3 4:B:4 4:4 4:5 4:6 4:7 4:A:0 4:A:1 4:A:2 4:A:3 4:A:4
91 4:0 4:1 4:2 4:3 3:D:0 3:D:1 3:D:2 3:D:3 3:D:4 3:12 3:13 3:14 3:15
92 3:C:0 3:C:1 3:C:2 3:C:3 3:C:4 3:8 3:9 3:10 3:11 3:B:0 3:B:1 3:B:2 3:B:3
93 3:B:4 3:4 3:5 3:6 3:7 3:A:0 3:A:1 3:A:2 3:A:3 3:A:4 3:0 3:1 3:2 3:3
94 2:D:0 2:D:1 2:D:2 2:D:3 2:D:4 2:12 2:13 2:14 2:15 2:C:0 2:C:1 2:C:2 2:C:3
95 2:C:4 2:8 2:9 2:10 2:11 2:B:0 2:B:1 2:B:2 2:B:3 2:B:4 2:4 2:5 2:6 2:7
96 2:A:0 2:A:1 2:A:2 2:A:3 2:A:4 2:0 2:1 2:2 2:3 1:D:0 1:D:1 1:D:2 1:D:3 1:D:4
97 1:12 1:13 1:14 1:15 1:C:0 1:C:1 1:C:2 1:C:3 1:C:4 1:8 1:9 1:10 1:11
98 1:B:0 1:B:1 1:B:2 1:B:3 1:B:4 1:4 1:5 1:6 1:7 1:A:0 1:A:1 1:A:2 1:A:3 1:A:4
99 1:0 1:1 1:2 1:3 0:D:0 0:D:1 0:D:2 0:D:3 0:D:4 0:12 0:13 0:14 0:15
100 0:C:0 0:C:1 0:C:2 0:C:3 0:C:4 0:8 0:9 0:10 0:11 0:B:0 0:B:1 0:B:2 0:B:3
101 0:B:4 0:4 0:5 0:6 0:7 0:A:0 0:A:1 0:A:2 0:A:3 0:A:4 0:0 0:1 0:2 0:3

FLOPSEL

For FLOPSEL, we repeat the above, but instead of varying the inputs, we vary if the LUT has a flip-flop after it, i.e., add this block:

 always @(posedge CLK)
    begin
        PPS_OUT1 <= LUT_OUT;
    end

I will spare you the details, but the process is similar.

Here is the updated table with FLOPSEL (<BLE>:F).

BLE Map with FLOPSEL
Word +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 31:D:0 31:D:1 31:D:2 31:D:3 31:D:4
17 31:12 31:13 31:14 31:15 31:F 31:C:0 31:C:1 31:C:2 31:C:3 31:C:4 31:8 31:9 31:10 31:11
18 31:B:0 31:B:1 31:B:2 31:B:3 31:B:4 31:4 31:5 31:6 31:7 31:A:0 31:A:1 31:A:2 31:A:3 31:A:4
19 31:0 31:1 31:2 31:3 30:D:0 30:D:1 30:D:2 30:D:3 30:D:4 30:12 30:13 30:14 30:15 30:F
20 30:C:0 30:C:1 30:C:2 30:C:3 30:C:4 30:8 30:9 30:10 30:11 30:B:0 30:B:1 30:B:2 30:B:3
21 30:B:4 30:4 30:5 30:6 30:7 30:A:0 30:A:1 30:A:2 30:A:3 30:A:4 30:0 30:1 30:2 30:3
22 29:D:0 29:D:1 29:D:2 29:D:3 29:D:4 29:12 29:13 29:14 29:15 29:F 29:C:0 29:C:1 29:C:2 29:C:3
23 29:C:4 29:8 29:9 29:10 29:11 29:B:0 29:B:1 29:B:2 29:B:3 29:B:4 29:4 29:5 29:6 29:7
24 29:A:0 29:A:1 29:A:2 29:A:3 29:A:4 29:0 29:1 29:2 29:3 28:D:0 28:D:1 28:D:2 28:D:3 28:D:4
25 28:12 28:13 28:14 28:15 28:F 28:C:0 28:C:1 28:C:2 28:C:3 28:C:4 28:8 28:9 28:10 28:11
26 28:B:0 28:B:1 28:B:2 28:B:3 28:B:4 28:4 28:5 28:6 28:7 28:A:0 28:A:1 28:A:2 28:A:3 28:A:4
27 28:0 28:1 28:2 28:3 27:D:0 27:D:1 27:D:2 27:D:3 27:D:4 27:12 27:13 27:14 27:15 27:F
28 27:C:0 27:C:1 27:C:2 27:C:3 27:C:4 27:8 27:9 27:10 27:11 27:B:0 27:B:1 27:B:2 27:B:3
29 27:B:4 27:4 27:5 27:6 27:7 27:A:0 27:A:1 27:A:2 27:A:3 27:A:4 27:0 27:1 27:2 27:3
30 26:D:0 26:D:1 26:D:2 26:D:3 26:D:4 26:12 26:13 26:14 26:15 26:F 26:C:0 26:C:1 26:C:2 26:C:3
31 26:C:4 26:8 26:9 26:10 26:11 26:B:0 26:B:1 26:B:2 26:B:3 26:B:4 26:4 26:5 26:6 26:7
32 26:A:0 26:A:1 26:A:2 26:A:3 26:A:4 26:0 26:1 26:2 26:3 25:D:0 25:D:1 25:D:2 25:D:3 25:D:4
33 25:12 25:13 25:14 25:15 25:F 25:C:0 25:C:1 25:C:2 25:C:3 25:C:4 25:8 25:9 25:10 25:11
34 25:B:0 25:B:1 25:B:2 25:B:3 25:B:4 25:4 25:5 25:6 25:7 25:A:0 25:A:1 25:A:2 25:A:3 25:A:4
35 25:0 25:1 25:2 25:3 24:D:0 24:D:1 24:D:2 24:D:3 24:D:4 24:12 24:13 24:14 24:15 24:F
36 24:C:0 24:C:1 24:C:2 24:C:3 24:C:4 24:8 24:9 24:10 24:11 24:B:0 24:B:1 24:B:2 24:B:3
37 24:B:4 24:4 24:5 24:6 24:7 24:A:0 24:A:1 24:A:2 24:A:3 24:A:4 24:0 24:1 24:2 24:3
38 23:D:0 23:D:1 23:D:2 23:D:3 23:D:4 23:12 23:13 23:14 23:15 23:F 23:C:0 23:C:1 23:C:2 23:C:3
39 23:C:4 23:8 23:9 23:10 23:11 23:B:0 23:B:1 23:B:2 23:B:3 23:B:4 23:4 23:5 23:6 23:7
40 23:A:0 23:A:1 23:A:2 23:A:3 23:A:4 23:0 23:1 23:2 23:3 22:D:0 22:D:1 22:D:2 22:D:3 22:D:4
41 22:12 22:13 22:14 22:15 22:F 22:C:0 22:C:1 22:C:2 22:C:3 22:C:4 22:8 22:9 22:10 22:11
42 22:B:0 22:B:1 22:B:2 22:B:3 22:B:4 22:4 22:5 22:6 22:7 22:A:0 22:A:1 22:A:2 22:A:3 22:A:4
43 22:0 22:1 22:2 22:3 21:D:0 21:D:1 21:D:2 21:D:3 21:D:4 21:12 21:13 21:14 21:15 21:F
44 21:C:0 21:C:1 21:C:2 21:C:3 21:C:4 21:8 21:9 21:10 21:11 21:B:0 21:B:1 21:B:2 21:B:3
45 21:B:4 21:4 21:5 21:6 21:7 21:A:0 21:A:1 21:A:2 21:A:3 21:A:4 21:0 21:1 21:2 21:3
46 20:D:0 20:D:1 20:D:2 20:D:3 20:D:4 20:12 20:13 20:14 20:15 20:F 20:C:0 20:C:1 20:C:2 20:C:3
47 20:C:4 20:8 20:9 20:10 20:11 20:B:0 20:B:1 20:B:2 20:B:3 20:B:4 20:4 20:5 20:6 20:7
48 20:A:0 20:A:1 20:A:2 20:A:3 20:A:4 20:0 20:1 20:2 20:3 19:D:0 19:D:1 19:D:2 19:D:3 19:D:4
49 19:12 19:13 19:14 19:15 19:F 19:C:0 19:C:1 19:C:2 19:C:3 19:C:4 19:8 19:9 19:10 19:11
50 19:B:0 19:B:1 19:B:2 19:B:3 19:B:4 19:4 19:5 19:6 19:7 19:A:0 19:A:1 19:A:2 19:A:3 19:A:4
51 19:0 19:1 19:2 19:3 18:D:0 18:D:1 18:D:2 18:D:3 18:D:4 18:12 18:13 18:14 18:15 18:F
52 18:C:0 18:C:1 18:C:2 18:C:3 18:C:4 18:8 18:9 18:10 18:11 18:B:0 18:B:1 18:B:2 18:B:3
53 18:B:4 18:4 18:5 18:6 18:7 18:A:0 18:A:1 18:A:2 18:A:3 18:A:4 18:0 18:1 18:2 18:3
54 17:D:0 17:D:1 17:D:2 17:D:3 17:D:4 17:12 17:13 17:14 17:15 17:F 17:C:0 17:C:1 17:C:2 17:C:3
55 17:C:4 17:8 17:9 17:10 17:11 17:B:0 17:B:1 17:B:2 17:B:3 17:B:4 17:4 17:5 17:6 17:7
56 17:A:0 17:A:1 17:A:2 17:A:3 17:A:4 17:0 17:1 17:2 17:3 16:D:0 16:D:1 16:D:2 16:D:3 16:D:4
57 16:12 16:13 16:14 16:15 16:F 16:C:0 16:C:1 16:C:2 16:C:3 16:C:4 16:8 16:9 16:10 16:11
58 16:B:0 16:B:1 16:B:2 16:B:3 16:B:4 16:4 16:5 16:6 16:7 16:A:0 16:A:1 16:A:2 16:A:3 16:A:4
59 16:0 16:1 16:2 16:3 15:D:0 15:D:1 15:D:2 15:D:3 15:D:4 15:12 15:13 15:14 15:15 15:F
60 15:C:0 15:C:1 15:C:2 15:C:3 15:C:4 15:8 15:9 15:10 15:11 15:B:0 15:B:1 15:B:2 15:B:3
61 15:B:4 15:4 15:5 15:6 15:7 15:A:0 15:A:1 15:A:2 15:A:3 15:A:4 15:0 15:1 15:2 15:3
62 14:D:0 14:D:1 14:D:2 14:D:3 14:D:4 14:12 14:13 14:14 14:15 14:F 14:C:0 14:C:1 14:C:2 14:C:3
63 14:C:4 14:8 14:9 14:10 14:11 14:B:0 14:B:1 14:B:2 14:B:3 14:B:4 14:4 14:5 14:6 14:7
64 14:A:0 14:A:1 14:A:2 14:A:3 14:A:4 14:0 14:1 14:2 14:3 13:D:0 13:D:1 13:D:2 13:D:3 13:D:4
65 13:12 13:13 13:14 13:15 13:F 13:C:0 13:C:1 13:C:2 13:C:3 13:C:4 13:8 13:9 13:10 13:11
66 13:B:0 13:B:1 13:B:2 13:B:3 13:B:4 13:4 13:5 13:6 13:7 13:A:0 13:A:1 13:A:2 13:A:3 13:A:4
67 13:0 13:1 13:2 13:3 12:D:0 12:D:1 12:D:2 12:D:3 12:D:4 12:12 12:13 12:14 12:15 12:F
68 12:C:0 12:C:1 12:C:2 12:C:3 12:C:4 12:8 12:9 12:10 12:11 12:B:0 12:B:1 12:B:2 12:B:3
69 12:B:4 12:4 12:5 12:6 12:7 12:A:0 12:A:1 12:A:2 12:A:3 12:A:4 12:0 12:1 12:2 12:3
70 11:D:0 11:D:1 11:D:2 11:D:3 11:D:4 11:12 11:13 11:14 11:15 11:F 11:C:0 11:C:1 11:C:2 11:C:3
71 11:C:4 11:8 11:9 11:10 11:11 11:B:0 11:B:1 11:B:2 11:B:3 11:B:4 11:4 11:5 11:6 11:7
72 11:A:0 11:A:1 11:A:2 11:A:3 11:A:4 11:0 11:1 11:2 11:3 10:D:0 10:D:1 10:D:2 10:D:3 10:D:4
73 10:12 10:13 10:14 10:15 10:F 10:C:0 10:C:1 10:C:2 10:C:3 10:C:4 10:8 10:9 10:10 10:11
74 10:B:0 10:B:1 10:B:2 10:B:3 10:B:4 10:4 10:5 10:6 10:7 10:A:0 10:A:1 10:A:2 10:A:3 10:A:4
75 10:0 10:1 10:2 10:3 9:D:0 9:D:1 9:D:2 9:D:3 9:D:4 9:12 9:13 9:14 9:15 9:F
76 9:C:0 9:C:1 9:C:2 9:C:3 9:C:4 9:8 9:9 9:10 9:11 9:B:0 9:B:1 9:B:2 9:B:3
77 9:B:4 9:4 9:5 9:6 9:7 9:A:0 9:A:1 9:A:2 9:A:3 9:A:4 9:0 9:1 9:2 9:3
78 8:D:0 8:D:1 8:D:2 8:D:3 8:D:4 8:12 8:13 8:14 8:15 8:F 8:C:0 8:C:1 8:C:2 8:C:3
79 8:C:4 8:8 8:9 8:10 8:11 8:B:0 8:B:1 8:B:2 8:B:3 8:B:4 8:4 8:5 8:6 8:7
80 8:A:0 8:A:1 8:A:2 8:A:3 8:A:4 8:0 8:1 8:2 8:3 7:D:0 7:D:1 7:D:2 7:D:3 7:D:4
81 7:12 7:13 7:14 7:15 7:F 7:C:0 7:C:1 7:C:2 7:C:3 7:C:4 7:8 7:9 7:10 7:11
82 7:B:0 7:B:1 7:B:2 7:B:3 7:B:4 7:4 7:5 7:6 7:7 7:A:0 7:A:1 7:A:2 7:A:3 7:A:4
83 7:0 7:1 7:2 7:3 6:D:0 6:D:1 6:D:2 6:D:3 6:D:4 6:12 6:13 6:14 6:15 6:F
84 6:C:0 6:C:1 6:C:2 6:C:3 6:C:4 6:8 6:9 6:10 6:11 6:B:0 6:B:1 6:B:2 6:B:3
85 6:B:4 6:4 6:5 6:6 6:7 6:A:0 6:A:1 6:A:2 6:A:3 6:A:4 6:0 6:1 6:2 6:3
86 5:D:0 5:D:1 5:D:2 5:D:3 5:D:4 5:12 5:13 5:14 5:15 5:F 5:C:0 5:C:1 5:C:2 5:C:3
87 5:C:4 5:8 5:9 5:10 5:11 5:B:0 5:B:1 5:B:2 5:B:3 5:B:4 5:4 5:5 5:6 5:7
88 5:A:0 5:A:1 5:A:2 5:A:3 5:A:4 5:0 5:1 5:2 5:3 4:D:0 4:D:1 4:D:2 4:D:3 4:D:4
89 4:12 4:13 4:14 4:15 4:F 4:C:0 4:C:1 4:C:2 4:C:3 4:C:4 4:8 4:9 4:10 4:11
90 4:B:0 4:B:1 4:B:2 4:B:3 4:B:4 4:4 4:5 4:6 4:7 4:A:0 4:A:1 4:A:2 4:A:3 4:A:4
91 4:0 4:1 4:2 4:3 3:D:0 3:D:1 3:D:2 3:D:3 3:D:4 3:12 3:13 3:14 3:15 3:F
92 3:C:0 3:C:1 3:C:2 3:C:3 3:C:4 3:8 3:9 3:10 3:11 3:B:0 3:B:1 3:B:2 3:B:3
93 3:B:4 3:4 3:5 3:6 3:7 3:A:0 3:A:1 3:A:2 3:A:3 3:A:4 3:0 3:1 3:2 3:3
94 2:D:0 2:D:1 2:D:2 2:D:3 2:D:4 2:12 2:13 2:14 2:15 2:F 2:C:0 2:C:1 2:C:2 2:C:3
95 2:C:4 2:8 2:9 2:10 2:11 2:B:0 2:B:1 2:B:2 2:B:3 2:B:4 2:4 2:5 2:6 2:7
96 2:A:0 2:A:1 2:A:2 2:A:3 2:A:4 2:0 2:1 2:2 2:3 1:D:0 1:D:1 1:D:2 1:D:3 1:D:4
97 1:12 1:13 1:14 1:15 1:F 1:C:0 1:C:1 1:C:2 1:C:3 1:C:4 1:8 1:9 1:10 1:11
98 1:B:0 1:B:1 1:B:2 1:B:3 1:B:4 1:4 1:5 1:6 1:7 1:A:0 1:A:1 1:A:2 1:A:3 1:A:4
99 1:0 1:1 1:2 1:3 0:D:0 0:D:1 0:D:2 0:D:3 0:D:4 0:12 0:13 0:14 0:15 0:F
100 0:C:0 0:C:1 0:C:2 0:C:3 0:C:4 0:8 0:9 0:10 0:11 0:B:0 0:B:1 0:B:2 0:B:3
101 0:B:4 0:4 0:5 0:6 0:7 0:A:0 0:A:1 0:A:2 0:A:3 0:A:4 0:0 0:1 0:2 0:3

Counter

The counter has two inputs, Stop and Reset. They helpfully document the inputs, and the COUNT_IS_B1 etc. outputs. The only thing to figure out is the Counter latches.

Counter Figure

Source: Microchip, PIC16F13145 Datasheet

I derived them similarly with the Bit Corr Tracker and random counter configurations.


class COUNTERIN(IntEnum):
    CLB_BLE_31 = 0b11111
    # ...
    CLB_BLE_0 = 0b00000

class CNTMUX(IntEnum):
    CNT0_COUNT_IS_0 = 0b000
    CNT0_COUNT_IS_1 = 0b001
    CNT0_COUNT_IS_2 = 0b010
    CNT0_COUNT_IS_3 = 0b011
    CNT0_COUNT_IS_4 = 0b100
    CNT0_COUNT_IS_5 = 0b101
    CNT0_COUNT_IS_6 = 0b110
    CNT0_COUNT_IS_7 = 0b111

@dataclass
class COUNTER:
    CNT_STOP: COUNTERIN = None
    CNT_RESET: COUNTERIN = None
    COUNT_IS_A1: CNTMUX = None
    COUNT_IS_A2: CNTMUX = None
    COUNT_IS_B1: CNTMUX = None
    COUNT_IS_B2: CNTMUX = None
    COUNT_IS_C1: CNTMUX = None
    COUNT_IS_C2: CNTMUX = None
    COUNT_IS_D1: CNTMUX = None
    COUNT_IS_D2: CNTMUX = None
Full Counter Model and Bits
class COUNTERIN(IntEnum):
    CLB_BLE_31 = 0b11111
    CLB_BLE_30 = 0b11110
    CLB_BLE_29 = 0b11101
    CLB_BLE_28 = 0b11100
    CLB_BLE_27 = 0b11011
    CLB_BLE_26 = 0b11010
    CLB_BLE_25 = 0b11001
    CLB_BLE_24 = 0b11000
    CLB_BLE_23 = 0b10111
    CLB_BLE_22 = 0b10110
    CLB_BLE_21 = 0b10101
    CLB_BLE_20 = 0b10100
    CLB_BLE_19 = 0b10011
    CLB_BLE_18 = 0b10010
    CLB_BLE_17 = 0b10001
    CLB_BLE_16 = 0b10000
    CLB_BLE_15 = 0b01111
    CLB_BLE_14 = 0b01110
    CLB_BLE_13 = 0b01101
    CLB_BLE_12 = 0b01100
    CLB_BLE_11 = 0b01011
    CLB_BLE_10 = 0b01010
    CLB_BLE_9 = 0b01001
    CLB_BLE_8 = 0b01000
    CLB_BLE_7 = 0b00111
    CLB_BLE_6 = 0b00110
    CLB_BLE_5 = 0b00101
    CLB_BLE_4 = 0b00100
    CLB_BLE_3 = 0b00011
    CLB_BLE_2 = 0b00010
    CLB_BLE_1 = 0b00001
    CLB_BLE_0 = 0b00000

class CNTMUX(IntEnum):
    CNT0_COUNT_IS_0 = 0b000
    CNT0_COUNT_IS_1 = 0b001
    CNT0_COUNT_IS_2 = 0b010
    CNT0_COUNT_IS_3 = 0b011
    CNT0_COUNT_IS_4 = 0b100
    CNT0_COUNT_IS_5 = 0b101
    CNT0_COUNT_IS_6 = 0b110
    CNT0_COUNT_IS_7 = 0b111

@dataclass
class COUNTER:
    CNT_STOP: COUNTERIN = None
    CNT_RESET: COUNTERIN = None
    COUNT_IS_A1: CNTMUX = None
    COUNT_IS_A2: CNTMUX = None
    COUNT_IS_B1: CNTMUX = None
    COUNT_IS_B2: CNTMUX = None
    COUNT_IS_C1: CNTMUX = None
    COUNT_IS_C2: CNTMUX = None
    COUNT_IS_D1: CNTMUX = None
    COUNT_IS_D2: CNTMUX = None

COUNT_MUX_CFG_bits = {
    "COUNT_IS_A1": {0: 41, 1: 42, 2: 43},
    "COUNT_IS_A2": {0: 44, 1: 45, 2: 48},
    "COUNT_IS_B1": {0: 49, 1: 50, 2: 51},
    "COUNT_IS_B2": {0: 32, 1: 33, 2: 34},
    "COUNT_IS_C1": {0: 35, 1: 36, 2: 37},
    "COUNT_IS_C2": {0: 38, 1: 39, 2: 40},
    "COUNT_IS_D1": {0: 21, 1: 22, 2: 23},
    "COUNT_IS_D2": {0: 24, 1: 25, 2: 26},
}

Inputs

You might wonder how the IN0-15 inputs map to the listed inputs; the PIC16F13145 Datasheet documents the below table (transcribed into Python). But how are those connected? Turns out there is an input mux, where you select which of the CLBIN values you want to go to each mux; you can even route the same input to more than one INx location.

In addition, you can select how you want the externally, possibly asynchronous logic to be synchronized with the CLB global clock. There are 3 setting bits; you can see the CLB Input Synchronizer[0:2] below.

Counter Figure

Source: Microchip, PIC16F13145 Datasheet

I reverse engineered these similarly by selecting a few of the below, and generating random BLE configs that use the inputs to identify the IN mux locations.

class MUX_CFG:
    INSYNC: CLBInputSync = None
    CLBIN: CLBIN = None

class CLBInputSync(IntFlag):
    DIRECT_IN = 0b000
    SYNC = 0b100
    EDGE_DETECT = 0b010
    EDGE_INVERT = 0b001

class CLBIN(IntFlag):
    RESERVED_BIT = 0b100000
    ZERO = 0b11111
    C2_OUT = 0b11100
    # ...
    CLBIN1PPS = 0b00001
    CLBIN0PPS = 0b00000
IN Mux Model
@dataclass
class MUX_CFG:
    INSYNC: CLBInputSync = None
    CLBIN: CLBIN = None

class CLBInputSync(IntFlag):
    DIRECT_IN = 0b000
    SYNC = 0b100
    EDGE_DETECT = 0b010
    EDGE_INVERT = 0b001

class CLBIN(IntFlag):
    RESERVED_BIT = 0b100000
    ZERO = 0b11111
    C2_OUT = 0b11100
    C1_OUT = 0b11011
    CLBSWIN_WRITE_HOLD = 0b11010
    SCK1 = 0b11001
    SDO1 = 0b11000
    TX1 = 0b10111
    CLC4_OUT = 0b10110
    CLC3_OUT = 0b10101
    CLC2_OUT = 0b10100
    CLC1_OUT = 0b10011
    IOCIF = 0b10010
    PWM2_OUT = 0b10001
    PWM1_OUT = 0b10000
    CCP2_OUT = 0b01111
    CCP1_OUT = 0b01110
    TMR2_POSTSCALED_OUT = 0b01101
    TMR1_OVERFLOW_OUT = 0b01100
    TMR0_OVERFLOW_OUT = 0b01011
    ADCRC = 0b01010
    EXTOSC = 0b01001
    MFINTOSC_32KHZ = 0b01000
    MFINTOSC_500KHZ = 0b00111
    LFINTOSC = 0b00110
    HFINTOSC = 0b00101
    FOSC = 0b00100
    CLBIN3PPS = 0b00011
    CLBIN2PPS = 0b00010
    CLBIN1PPS = 0b00001
    CLBIN0PPS = 0b00000

MUX_CFG_bits = {
    0: {
        "CLBIN": {0: 256, 1: 257, 2: 258, 3: 259, 4: 260, 5: 261},
        "INSYNC": {0: 262, 1: 263, 2: 264},
    },
    1: {
        "CLBIN": {0: 245, 1: 246, 2: 247, 3: 248, 4: 249, 5: 250},
        "INSYNC": {0: 251, 1: 252, 2: 253},
    },
    2: {
        "CLBIN": {0: 234, 1: 235, 2: 236, 3: 237, 4: 240, 5: 241},
        "INSYNC": {0: 242, 1: 243, 2: 244},
    },
    3: {
        "CLBIN": {0: 224, 1: 225, 2: 226, 3: 227, 4: 228, 5: 229},
        "INSYNC": {0: 230, 1: 231, 2: 232},
    },
    4: {
        "CLBIN": {0: 213, 1: 214, 2: 215, 3: 216, 4: 217, 5: 218},
        "INSYNC": {0: 219, 1: 220, 2: 221},
    },
    5: {
        "CLBIN": {0: 202, 1: 203, 2: 204, 3: 205, 4: 208, 5: 209},
        "INSYNC": {0: 210, 1: 211, 2: 212},
    },
    6: {
        "CLBIN": {0: 192, 1: 193, 2: 194, 3: 195, 4: 196, 5: 197},
        "INSYNC": {0: 198, 1: 199, 2: 200},
    },
    7: {
        "CLBIN": {0: 180, 1: 181, 2: 182, 3: 183, 4: 184, 5: 185},
        "INSYNC": {0: 186, 1: 187, 2: 188},
    },
    8: {
        "CLBIN": {0: 169, 1: 170, 2: 171, 3: 172, 4: 173, 5: 176},
        "INSYNC": {0: 177, 1: 178, 2: 179},
    },
    9: {
        "CLBIN": {0: 160, 1: 161, 2: 162, 3: 163, 4: 164, 5: 165},
        "INSYNC": {0: 166, 1: 167, 2: 168},
    },
    10: {
        "CLBIN": {0: 149, 1: 150, 2: 151, 3: 152, 4: 153, 5: 154},
        "INSYNC": {0: 155, 1: 156, 2: 157},
    },
    11: {
        "CLBIN": {0: 137, 1: 138, 2: 139, 3: 140, 4: 141, 5: 144},
        "INSYNC": {0: 145, 1: 146, 2: 147},
    },
    12: {
        "CLBIN": {0: 128, 1: 129, 2: 130, 3: 131, 4: 132, 5: 133},
        "INSYNC": {0: 134, 1: 135, 2: 136},
    },
    13: {
        "CLBIN": {0: 117, 1: 118, 2: 119, 3: 120, 4: 121, 5: 122},
        "INSYNC": {0: 123, 1: 124, 2: 125},
    },
    14: {
        "CLBIN": {0: 106, 1: 107, 2: 108, 3: 109, 4: 112, 5: 113},
        "INSYNC": {0: 114, 1: 115, 2: 116},
    },
    15: {
        "CLBIN": {0: 96, 1: 97, 2: 98, 3: 99, 4: 100, 5: 101},
        "INSYNC": {0: 102, 1: 103, 2: 104},
    },
}

Outputs

PPS Out

The PPS outputs are selected from within the bitstream, they helpfully provide the mapping.

Just needed to iterate and get the bit positions.

PPS Model/Bits
class CLBPPSOUT0(IntEnum):
    CLB_BLE_0 = 0b00
    CLB_BLE_1 = 0b01
    CLB_BLE_2 = 0b10
    CLB_BLE_3 = 0b11


class CLBPPSOUT1(IntEnum):
    CLB_BLE_4 = 0b00
    CLB_BLE_5 = 0b01
    CLB_BLE_6 = 0b10
    CLB_BLE_7 = 0b11


class CLBPPSOUT2(IntEnum):
    CLB_BLE_8 = 0b00
    CLB_BLE_9 = 0b01
    CLB_BLE_10 = 0b10
    CLB_BLE_11 = 0b11


class CLBPPSOUT3(IntEnum):
    CLB_BLE_12 = 0b00
    CLB_BLE_13 = 0b01
    CLB_BLE_14 = 0b10
    CLB_BLE_15 = 0b11


class CLBPPSOUT4(IntEnum):
    CLB_BLE_16 = 0b00
    CLB_BLE_17 = 0b01
    CLB_BLE_18 = 0b10
    CLB_BLE_19 = 0b11


class CLBPPSOUT5(IntEnum):
    CLB_BLE_20 = 0b00
    CLB_BLE_21 = 0b01
    CLB_BLE_22 = 0b10
    CLB_BLE_23 = 0b11


class CLBPPSOUT6(IntEnum):
    CLB_BLE_24 = 0b00
    CLB_BLE_25 = 0b01
    CLB_BLE_26 = 0b10
    CLB_BLE_27 = 0b11


class CLBPPSOUT7(IntEnum):
    CLB_BLE_28 = 0b00
    CLB_BLE_29 = 0b01
    CLB_BLE_30 = 0b10
    CLB_BLE_31 = 0b11


PPS_OUT_BITS = {
    PPS_OUT0: {0: 85, 1: 86},
    PPS_OUT1: {0: 87, 1: 88},
    PPS_OUT2: {0: 74, 1: 75},
    PPS_OUT3: {0: 76, 1: 77},
    PPS_OUT4: {0: 64, 1: 65},
    PPS_OUT5: {0: 66, 1: 67},
    PPS_OUT6: {0: 52, 1: 53},
    PPS_OUT7: {0: 54, 1: 55},
}



@dataclass
class PPS_OUT0:
    OUT: CLBPPSOUT0 = None


@dataclass
class PPS_OUT1:
    OUT: CLBPPSOUT1 = None


@dataclass
class PPS_OUT2:
    OUT: CLBPPSOUT2 = None


@dataclass
class PPS_OUT3:
    OUT: CLBPPSOUT3 = None


@dataclass
class PPS_OUT4:
    OUT: CLBPPSOUT4 = None


@dataclass
class PPS_OUT5:
    OUT: CLBPPSOUT5 = None


@dataclass
class PPS_OUT6:
    OUT: CLBPPSOUT6 = None


@dataclass
class PPS_OUT7:
    OUT: CLBPPSOUT7 = None

Interrupts

Just like PPS, they helpfully provide the mapping.

Just needed to iterate and get the bit positions.

IRQ Model/Bits
@dataclass
class IRQ_OUT0:
    OUT: CLB1IF0 = None


@dataclass
class IRQ_OUT1:
    OUT: CLB1IF1 = None


@dataclass
class IRQ_OUT2:
    OUT: CLB1IF2 = None


@dataclass
class IRQ_OUT3:
    OUT: CLB1IF3 = None



# Interrupts
class CLB1IF0(IntEnum):
    CLB_BLE_0 = 0b000
    CLB_BLE_1 = 0b001
    CLB_BLE_2 = 0b010
    CLB_BLE_3 = 0b011
    CLB_BLE_4 = 0b100
    CLB_BLE_5 = 0b101
    CLB_BLE_6 = 0b110
    CLB_BLE_7 = 0b111


class CLB1IF1(IntEnum):
    CLB_BLE_8 = 0b000
    CLB_BLE_9 = 0b001
    CLB_BLE_10 = 0b010
    CLB_BLE_11 = 0b011
    CLB_BLE_12 = 0b100
    CLB_BLE_13 = 0b101
    CLB_BLE_14 = 0b110
    CLB_BLE_15 = 0b111


class CLB1IF2(IntEnum):
    CLB_BLE_16 = 0b000
    CLB_BLE_17 = 0b001
    CLB_BLE_18 = 0b010
    CLB_BLE_19 = 0b011
    CLB_BLE_20 = 0b100
    CLB_BLE_21 = 0b101
    CLB_BLE_22 = 0b110
    CLB_BLE_23 = 0b111


class CLB1IF3(IntEnum):
    CLB_BLE_24 = 0b000
    CLB_BLE_25 = 0b001
    CLB_BLE_26 = 0b010
    CLB_BLE_27 = 0b011
    CLB_BLE_28 = 0b100
    CLB_BLE_29 = 0b101
    CLB_BLE_30 = 0b110
    CLB_BLE_31 = 0b111


IRQ_bits = {
    0: {0: 89, 1: 90, 2: 91},
    1: {0: 80, 1: 81, 2: 82},
    2: {0: 68, 1: 69, 2: 70},
    3: {0: 56, 1: 57, 2: 58},
}

PPS Output Enable

They helpfully document these as well, I actually spent an embarrassingly long time trying to find where they were in the bits before reading the datasheet and they are set in a register outside the bistream CLBPPSCON.

ADC/CCP/TIMR0-3

The inputs to the ADC/CCP/TMRs are configured at the far end by bits in those peripherals.

  • ADC can use BLE0-7 as conversion inputs
  • The CCP can use BLE14-18 as capture inputs
  • TIMR0 can use BLE31 as a clock source
  • TIMR1 can use BLE0-3 as a clock source and BLE2-5 as gate inputs
  • TIMR2 can use BLE7-10 as a clock source and BLE7-14 as reset sources

CLKDIV

CLKDIV is just a bitfield that sets a flip-flop divider, which I manually iterated through settings to identify.

class CLKDIV(IntEnum):
    DIV_BY_1 = 0b000
    DIV_BY_2 = 0b001
    DIV_BY_4 = 0b010
    DIV_BY_8 = 0b011
    DIV_BY_16 = 0b100
    DIV_BY_32 = 0b101
    DIV_BY_64 = 0b110
    DIV_BY_128 = 0b111

CLKDIV_bits = {0: 0, 1: 1, 2: 2}

Final Config Table

Final Config Table
Word +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13
0 CKDIV:0 CKDIV:1 CKDIV:2 CNT:S:0 CNT:S:1 CNT:S:2 CNT:S:3 CNT:S:4
1 CNT:R:0 CNT:R:1 CNT:R:2 CNT:R:3 CNT:R:4 CNT:D1:0 CNT:D1:1 CNT:D1:2 CNT:D2:0 CNT:D2:1 CNT:D2:2
2 CNT:B2:0 CNT:B2:1 CNT:B2:2 CNT:C1:0 CNT:C1:1 CNT:C1:2 CNT:C2:0 CNT:C2:1 CNT:C2:2 CNT:A1:0 CNT:A1:1 CNT:A1:2 CNT:A2:0 CNT:A2:1
3 CNT:A2:2 CNT:B1:0 CNT:B1:1 CNT:B1:2 PPS:6:0 PPS:6:1 PPS:7:0 PPS:7:1 IRQ3:0 IRQ3:1 IRQ3:2
4 PPS:4:0 PPS:4:1 PPS:5:0 PPS:5:1 IRQ2:0 IRQ2:1 IRQ2:2 PPS:2:0 PPS:2:1 PPS:3:0 PPS:3:1
5 IRQ1:0 IRQ1:1 IRQ1:2 PPS:0:0 PPS:0:1 PPS:1:0 PPS:1:1 IRQ0:0 IRQ0:1 IRQ0:2
6 MX:15:I:0 MX:15:I:1 MX:15:I:2 MX:15:I:3 MX:15:I:4 MX:15:I:5 MX:15:S:0 MX:15:S:1 MX:15:S:2 MX:14:I:0 MX:14:I:1 MX:14:I:2 MX:14:I:3
7 MX:14:I:4 MX:14:I:5 MX:14:S:0 MX:14:S:1 MX:14:S:2 MX:13:I:0 MX:13:I:1 MX:13:I:2 MX:13:I:3 MX:13:I:4 MX:13:I:5 MX:13:S:0 MX:13:S:1 MX:13:S:2
8 MX:12:I:0 MX:12:I:1 MX:12:I:2 MX:12:I:3 MX:12:I:4 MX:12:I:5 MX:12:S:0 MX:12:S:1 MX:12:S:2 MX:11:I:0 MX:11:I:1 MX:11:I:2 MX:11:I:3 MX:11:I:4
9 MX:11:I:5 MX:11:S:0 MX:11:S:1 MX:11:S:2 MX:10:I:0 MX:10:I:1 MX:10:I:2 MX:10:I:3 MX:10:I:4 MX:10:I:5 MX:10:S:0 MX:10:S:1 MX:10:S:2
10 MX:9:I:0 MX:9:I:1 MX:9:I:2 MX:9:I:3 MX:9:I:4 MX:9:I:5 MX:9:S:0 MX:9:S:1 MX:9:S:2 MX:8:I:0 MX:8:I:1 MX:8:I:2 MX:8:I:3 MX:8:I:4
11 MX:8:I:5 MX:8:S:0 MX:8:S:1 MX:8:S:2 MX:7:I:0 MX:7:I:1 MX:7:I:2 MX:7:I:3 MX:7:I:4 MX:7:I:5 MX:7:S:0 MX:7:S:1 MX:7:S:2
12 MX:6:I:0 MX:6:I:1 MX:6:I:2 MX:6:I:3 MX:6:I:4 MX:6:I:5 MX:6:S:0 MX:6:S:1 MX:6:S:2 MX:5:I:0 MX:5:I:1 MX:5:I:2 MX:5:I:3
13 MX:5:I:4 MX:5:I:5 MX:5:S:0 MX:5:S:1 MX:5:S:2 MX:4:I:0 MX:4:I:1 MX:4:I:2 MX:4:I:3 MX:4:I:4 MX:4:I:5 MX:4:S:0 MX:4:S:1 MX:4:S:2
14 MX:3:I:0 MX:3:I:1 MX:3:I:2 MX:3:I:3 MX:3:I:4 MX:3:I:5 MX:3:S:0 MX:3:S:1 MX:3:S:2 MX:2:I:0 MX:2:I:1 MX:2:I:2 MX:2:I:3
15 MX:2:I:4 MX:2:I:5 MX:2:S:0 MX:2:S:1 MX:2:S:2 MX:1:I:0 MX:1:I:1 MX:1:I:2 MX:1:I:3 MX:1:I:4 MX:1:I:5 MX:1:S:0 MX:1:S:1 MX:1:S:2
16 MX:0:I:0 MX:0:I:1 MX:0:I:2 MX:0:I:3 MX:0:I:4 MX:0:I:5 MX:0:S:0 MX:0:S:1 MX:0:S:2 31:D:0 31:D:1 31:D:2 31:D:3 31:D:4
17 31:12 31:13 31:14 31:15 31:FS 31:C:0 31:C:1 31:C:2 31:C:3 31:C:4 31:8 31:9 31:10 31:11
18 31:B:0 31:B:1 31:B:2 31:B:3 31:B:4 31:4 31:5 31:6 31:7 31:A:0 31:A:1 31:A:2 31:A:3 31:A:4
19 31:0 31:1 31:2 31:3 30:D:0 30:D:1 30:D:2 30:D:3 30:D:4 30:12 30:13 30:14 30:15 30:FS
20 30:C:0 30:C:1 30:C:2 30:C:3 30:C:4 30:8 30:9 30:10 30:11 30:B:0 30:B:1 30:B:2 30:B:3
21 30:B:4 30:4 30:5 30:6 30:7 30:A:0 30:A:1 30:A:2 30:A:3 30:A:4 30:0 30:1 30:2 30:3
22 29:D:0 29:D:1 29:D:2 29:D:3 29:D:4 29:12 29:13 29:14 29:15 29:FS 29:C:0 29:C:1 29:C:2 29:C:3
23 29:C:4 29:8 29:9 29:10 29:11 29:B:0 29:B:1 29:B:2 29:B:3 29:B:4 29:4 29:5 29:6 29:7
24 29:A:0 29:A:1 29:A:2 29:A:3 29:A:4 29:0 29:1 29:2 29:3 28:D:0 28:D:1 28:D:2 28:D:3 28:D:4
25 28:12 28:13 28:14 28:15 28:FS 28:C:0 28:C:1 28:C:2 28:C:3 28:C:4 28:8 28:9 28:10 28:11
26 28:B:0 28:B:1 28:B:2 28:B:3 28:B:4 28:4 28:5 28:6 28:7 28:A:0 28:A:1 28:A:2 28:A:3 28:A:4
27 28:0 28:1 28:2 28:3 27:D:0 27:D:1 27:D:2 27:D:3 27:D:4 27:12 27:13 27:14 27:15 27:FS
28 27:C:0 27:C:1 27:C:2 27:C:3 27:C:4 27:8 27:9 27:10 27:11 27:B:0 27:B:1 27:B:2 27:B:3
29 27:B:4 27:4 27:5 27:6 27:7 27:A:0 27:A:1 27:A:2 27:A:3 27:A:4 27:0 27:1 27:2 27:3
30 26:D:0 26:D:1 26:D:2 26:D:3 26:D:4 26:12 26:13 26:14 26:15 26:FS 26:C:0 26:C:1 26:C:2 26:C:3
31 26:C:4 26:8 26:9 26:10 26:11 26:B:0 26:B:1 26:B:2 26:B:3 26:B:4 26:4 26:5 26:6 26:7
32 26:A:0 26:A:1 26:A:2 26:A:3 26:A:4 26:0 26:1 26:2 26:3 25:D:0 25:D:1 25:D:2 25:D:3 25:D:4
33 25:12 25:13 25:14 25:15 25:FS 25:C:0 25:C:1 25:C:2 25:C:3 25:C:4 25:8 25:9 25:10 25:11
34 25:B:0 25:B:1 25:B:2 25:B:3 25:B:4 25:4 25:5 25:6 25:7 25:A:0 25:A:1 25:A:2 25:A:3 25:A:4
35 25:0 25:1 25:2 25:3 24:D:0 24:D:1 24:D:2 24:D:3 24:D:4 24:12 24:13 24:14 24:15 24:FS
36 24:C:0 24:C:1 24:C:2 24:C:3 24:C:4 24:8 24:9 24:10 24:11 24:B:0 24:B:1 24:B:2 24:B:3
37 24:B:4 24:4 24:5 24:6 24:7 24:A:0 24:A:1 24:A:2 24:A:3 24:A:4 24:0 24:1 24:2 24:3
38 23:D:0 23:D:1 23:D:2 23:D:3 23:D:4 23:12 23:13 23:14 23:15 23:FS 23:C:0 23:C:1 23:C:2 23:C:3
39 23:C:4 23:8 23:9 23:10 23:11 23:B:0 23:B:1 23:B:2 23:B:3 23:B:4 23:4 23:5 23:6 23:7
40 23:A:0 23:A:1 23:A:2 23:A:3 23:A:4 23:0 23:1 23:2 23:3 22:D:0 22:D:1 22:D:2 22:D:3 22:D:4
41 22:12 22:13 22:14 22:15 22:FS 22:C:0 22:C:1 22:C:2 22:C:3 22:C:4 22:8 22:9 22:10 22:11
42 22:B:0 22:B:1 22:B:2 22:B:3 22:B:4 22:4 22:5 22:6 22:7 22:A:0 22:A:1 22:A:2 22:A:3 22:A:4
43 22:0 22:1 22:2 22:3 21:D:0 21:D:1 21:D:2 21:D:3 21:D:4 21:12 21:13 21:14 21:15 21:FS
44 21:C:0 21:C:1 21:C:2 21:C:3 21:C:4 21:8 21:9 21:10 21:11 21:B:0 21:B:1 21:B:2 21:B:3
45 21:B:4 21:4 21:5 21:6 21:7 21:A:0 21:A:1 21:A:2 21:A:3 21:A:4 21:0 21:1 21:2 21:3
46 20:D:0 20:D:1 20:D:2 20:D:3 20:D:4 20:12 20:13 20:14 20:15 20:FS 20:C:0 20:C:1 20:C:2 20:C:3
47 20:C:4 20:8 20:9 20:10 20:11 20:B:0 20:B:1 20:B:2 20:B:3 20:B:4 20:4 20:5 20:6 20:7
48 20:A:0 20:A:1 20:A:2 20:A:3 20:A:4 20:0 20:1 20:2 20:3 19:D:0 19:D:1 19:D:2 19:D:3 19:D:4
49 19:12 19:13 19:14 19:15 19:FS 19:C:0 19:C:1 19:C:2 19:C:3 19:C:4 19:8 19:9 19:10 19:11
50 19:B:0 19:B:1 19:B:2 19:B:3 19:B:4 19:4 19:5 19:6 19:7 19:A:0 19:A:1 19:A:2 19:A:3 19:A:4
51 19:0 19:1 19:2 19:3 18:D:0 18:D:1 18:D:2 18:D:3 18:D:4 18:12 18:13 18:14 18:15 18:FS
52 18:C:0 18:C:1 18:C:2 18:C:3 18:C:4 18:8 18:9 18:10 18:11 18:B:0 18:B:1 18:B:2 18:B:3
53 18:B:4 18:4 18:5 18:6 18:7 18:A:0 18:A:1 18:A:2 18:A:3 18:A:4 18:0 18:1 18:2 18:3
54 17:D:0 17:D:1 17:D:2 17:D:3 17:D:4 17:12 17:13 17:14 17:15 17:FS 17:C:0 17:C:1 17:C:2 17:C:3
55 17:C:4 17:8 17:9 17:10 17:11 17:B:0 17:B:1 17:B:2 17:B:3 17:B:4 17:4 17:5 17:6 17:7
56 17:A:0 17:A:1 17:A:2 17:A:3 17:A:4 17:0 17:1 17:2 17:3 16:D:0 16:D:1 16:D:2 16:D:3 16:D:4
57 16:12 16:13 16:14 16:15 16:FS 16:C:0 16:C:1 16:C:2 16:C:3 16:C:4 16:8 16:9 16:10 16:11
58 16:B:0 16:B:1 16:B:2 16:B:3 16:B:4 16:4 16:5 16:6 16:7 16:A:0 16:A:1 16:A:2 16:A:3 16:A:4
59 16:0 16:1 16:2 16:3 15:D:0 15:D:1 15:D:2 15:D:3 15:D:4 15:12 15:13 15:14 15:15 15:FS
60 15:C:0 15:C:1 15:C:2 15:C:3 15:C:4 15:8 15:9 15:10 15:11 15:B:0 15:B:1 15:B:2 15:B:3
61 15:B:4 15:4 15:5 15:6 15:7 15:A:0 15:A:1 15:A:2 15:A:3 15:A:4 15:0 15:1 15:2 15:3
62 14:D:0 14:D:1 14:D:2 14:D:3 14:D:4 14:12 14:13 14:14 14:15 14:FS 14:C:0 14:C:1 14:C:2 14:C:3
63 14:C:4 14:8 14:9 14:10 14:11 14:B:0 14:B:1 14:B:2 14:B:3 14:B:4 14:4 14:5 14:6 14:7
64 14:A:0 14:A:1 14:A:2 14:A:3 14:A:4 14:0 14:1 14:2 14:3 13:D:0 13:D:1 13:D:2 13:D:3 13:D:4
65 13:12 13:13 13:14 13:15 13:FS 13:C:0 13:C:1 13:C:2 13:C:3 13:C:4 13:8 13:9 13:10 13:11
66 13:B:0 13:B:1 13:B:2 13:B:3 13:B:4 13:4 13:5 13:6 13:7 13:A:0 13:A:1 13:A:2 13:A:3 13:A:4
67 13:0 13:1 13:2 13:3 12:D:0 12:D:1 12:D:2 12:D:3 12:D:4 12:12 12:13 12:14 12:15 12:FS
68 12:C:0 12:C:1 12:C:2 12:C:3 12:C:4 12:8 12:9 12:10 12:11 12:B:0 12:B:1 12:B:2 12:B:3
69 12:B:4 12:4 12:5 12:6 12:7 12:A:0 12:A:1 12:A:2 12:A:3 12:A:4 12:0 12:1 12:2 12:3
70 11:D:0 11:D:1 11:D:2 11:D:3 11:D:4 11:12 11:13 11:14 11:15 11:FS 11:C:0 11:C:1 11:C:2 11:C:3
71 11:C:4 11:8 11:9 11:10 11:11 11:B:0 11:B:1 11:B:2 11:B:3 11:B:4 11:4 11:5 11:6 11:7
72 11:A:0 11:A:1 11:A:2 11:A:3 11:A:4 11:0 11:1 11:2 11:3 10:D:0 10:D:1 10:D:2 10:D:3 10:D:4
73 10:12 10:13 10:14 10:15 10:FS 10:C:0 10:C:1 10:C:2 10:C:3 10:C:4 10:8 10:9 10:10 10:11
74 10:B:0 10:B:1 10:B:2 10:B:3 10:B:4 10:4 10:5 10:6 10:7 10:A:0 10:A:1 10:A:2 10:A:3 10:A:4
75 10:0 10:1 10:2 10:3 9:D:0 9:D:1 9:D:2 9:D:3 9:D:4 9:12 9:13 9:14 9:15 9:FS
76 9:C:0 9:C:1 9:C:2 9:C:3 9:C:4 9:8 9:9 9:10 9:11 9:B:0 9:B:1 9:B:2 9:B:3
77 9:B:4 9:4 9:5 9:6 9:7 9:A:0 9:A:1 9:A:2 9:A:3 9:A:4 9:0 9:1 9:2 9:3
78 8:D:0 8:D:1 8:D:2 8:D:3 8:D:4 8:12 8:13 8:14 8:15 8:FS 8:C:0 8:C:1 8:C:2 8:C:3
79 8:C:4 8:8 8:9 8:10 8:11 8:B:0 8:B:1 8:B:2 8:B:3 8:B:4 8:4 8:5 8:6 8:7
80 8:A:0 8:A:1 8:A:2 8:A:3 8:A:4 8:0 8:1 8:2 8:3 7:D:0 7:D:1 7:D:2 7:D:3 7:D:4
81 7:12 7:13 7:14 7:15 7:FS 7:C:0 7:C:1 7:C:2 7:C:3 7:C:4 7:8 7:9 7:10 7:11
82 7:B:0 7:B:1 7:B:2 7:B:3 7:B:4 7:4 7:5 7:6 7:7 7:A:0 7:A:1 7:A:2 7:A:3 7:A:4
83 7:0 7:1 7:2 7:3 6:D:0 6:D:1 6:D:2 6:D:3 6:D:4 6:12 6:13 6:14 6:15 6:FS
84 6:C:0 6:C:1 6:C:2 6:C:3 6:C:4 6:8 6:9 6:10 6:11 6:B:0 6:B:1 6:B:2 6:B:3
85 6:B:4 6:4 6:5 6:6 6:7 6:A:0 6:A:1 6:A:2 6:A:3 6:A:4 6:0 6:1 6:2 6:3
86 5:D:0 5:D:1 5:D:2 5:D:3 5:D:4 5:12 5:13 5:14 5:15 5:FS 5:C:0 5:C:1 5:C:2 5:C:3
87 5:C:4 5:8 5:9 5:10 5:11 5:B:0 5:B:1 5:B:2 5:B:3 5:B:4 5:4 5:5 5:6 5:7
88 5:A:0 5:A:1 5:A:2 5:A:3 5:A:4 5:0 5:1 5:2 5:3 4:D:0 4:D:1 4:D:2 4:D:3 4:D:4
89 4:12 4:13 4:14 4:15 4:FS 4:C:0 4:C:1 4:C:2 4:C:3 4:C:4 4:8 4:9 4:10 4:11
90 4:B:0 4:B:1 4:B:2 4:B:3 4:B:4 4:4 4:5 4:6 4:7 4:A:0 4:A:1 4:A:2 4:A:3 4:A:4
91 4:0 4:1 4:2 4:3 3:D:0 3:D:1 3:D:2 3:D:3 3:D:4 3:12 3:13 3:14 3:15 3:FS
92 3:C:0 3:C:1 3:C:2 3:C:3 3:C:4 3:8 3:9 3:10 3:11 3:B:0 3:B:1 3:B:2 3:B:3
93 3:B:4 3:4 3:5 3:6 3:7 3:A:0 3:A:1 3:A:2 3:A:3 3:A:4 3:0 3:1 3:2 3:3
94 2:D:0 2:D:1 2:D:2 2:D:3 2:D:4 2:12 2:13 2:14 2:15 2:FS 2:C:0 2:C:1 2:C:2 2:C:3
95 2:C:4 2:8 2:9 2:10 2:11 2:B:0 2:B:1 2:B:2 2:B:3 2:B:4 2:4 2:5 2:6 2:7
96 2:A:0 2:A:1 2:A:2 2:A:3 2:A:4 2:0 2:1 2:2 2:3 1:D:0 1:D:1 1:D:2 1:D:3 1:D:4
97 1:12 1:13 1:14 1:15 1:FS 1:C:0 1:C:1 1:C:2 1:C:3 1:C:4 1:8 1:9 1:10 1:11
98 1:B:0 1:B:1 1:B:2 1:B:3 1:B:4 1:4 1:5 1:6 1:7 1:A:0 1:A:1 1:A:2 1:A:3 1:A:4
99 1:0 1:1 1:2 1:3 0:D:0 0:D:1 0:D:2 0:D:3 0:D:4 0:12 0:13 0:14 0:15 0:FS
100 0:C:0 0:C:1 0:C:2 0:C:3 0:C:4 0:8 0:9 0:10 0:11 0:B:0 0:B:1 0:B:2 0:B:3
101 0:B:4 0:4 0:5 0:6 0:7 0:A:0 0:A:1 0:A:2 0:A:3 0:A:4 0:0 0:1 0:2 0:3


Try it yourself in browser here.

You can live in the browser generate bitstreams and visualize them.

Or see some of the characterization I did here.



Back to home