Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,14 @@ __pycache__
libprjoxide/target
Cargo.lock
.bitstreamcache/
radiantc.tcl*
radiantc.log*
*~
\#*
*.log.gz
.deltas
work/
.\#*
*.fasm
.cache
fuzzers/*/*/db
14 changes: 14 additions & 0 deletions docs/bels/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Overview

The tiletype of a tile dictates which BEL's are available on a given tile and which options they might have exposed. This is mapped out fully in prjoxide/libprjoxide/prjoxide/src/bels.rs, namely in `get_tile_bels`.

A given bel might span over multiple logical tiles. It's anchor tile is the one with the appropriate tiletype but for routing information on where the related tile is there is rel_x and rel_y; which encode the relative tile offset for the related tile. This data can be varied based on the family, device and actual tile in question.

For bels with these offsets, the offset information is used in fuzzing the routing to map the interconnect. The offset themselves can be devined from the output of the dev_get_nodes command and report for nearby CIB tiles. For instance, related tiles to LRAM will have LRAM_CORE wires in their tile.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: derived


Bel information is encoded in the bba file which is produced by prjoxide and ingested in the build process of nextpnr.

# References

- [Terminology](https://fpga-interchange-schema.readthedocs.io/device_resources.html)

54 changes: 54 additions & 0 deletions docs/database.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Database

The chip database for prjoxide is ingested into libprjoxide and used to construct the BBA database used by nextpnr.

## Per device files

Each supported device has the following json files to describe it:

### baseaddr.json

Some primitives are internally routed on an LMMI bus that is used for configuration. This file describes the addresses of mapped primitives and the length of their address in bits.

These addresses are mapped with the fuzzer in fuzzers/LIFCL/100-ip-base. This requires, for each device, a mapping between the primitive type in question and it's site. This mapping isn't immediately apparent; but can usually be derived by placing the known number of that primitive in a design and looking at it in the physical designer / routing tool in lattice.

### iodb.json

The IO database file contains information on a particular pins function and logical name for each given package.

Each entry describes the pin in terms of it's physical location. Since all the IO pins are on the outside edge of the chip, you can map the tile as a combination of it's 'offset' and it's 'side' -- a pin on the top for instance with an offset of 58 maps to tile 58, 0. The pio attribute describes which pin in that tile is being described.

The iodb.json files are constructed by running tools/parse_pins.py against the CSV files lattice makes available in the documentation section for each device.

### tilegrid.json

The tile grid file describes where the physical location of the tile is, what it's type is, and where it is encoded into the final bitstream file.

This file is generated by tools/tilegrid_all.py. A new device will require some custom fields in the devices.json file but also tools/extract_tilegrid.py.

## Shared database files

The various .ron files are serialized/deserialized to rust. Each fuzzer deserializes the current database, processes samples, and then reserializes out the updated database.

### devices.json

This serves as a master listing of each device and metadata associated with that device:

- packages: Comes from various lattice documentation, can also be seen by looking at the radiant device selection dialog.
- frame metadata: There are various necessary peices of data here. All are available in the "sysCONFIG Guide for Nexus Platform" document from the lattice website.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be nice to expand this a bit more?

- idcode: This is also availble in the above document.
- max_row / max_cols: These are retreivable from the physical designer for a given chip, or from the tilegrid metadata file.

This file serves as the central index for devices in the libprjoxide library.

### iptypes

These files are generated by various fuzzers associated with the given primitive. They map out the relationship between configuration parameters and the bits set in the bitstream.

### tiletypes

These files are also generated by fuzzers. While they also map out the relationship between various parameters and bits in the bitstream, they mainly focus on the interconnection between tiles themselves. This gives both a graph on what connections are possible but also the way those connections are configured in the bitstream.

### timing

This is a collection of a bunch of cell types and a description of the delay timing between pins as well as the setup and hold time for each pin in the cell.
6 changes: 6 additions & 0 deletions docs/general/bitstream.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@ All commands (except the `0xFF` dummy command) are followed by three "parameter"
- **ISC_PROGRAM_DONE** (`0x5E`): ends configuration and starts FPGA fabric running
- **LSC_POWER_CTRL** (`0x56`): third param byte configures internal power switches (detail unknown)

Fuller table available [here](https://www.latticesemi.com/-/media/LatticeSemi/Documents/ApplicationNotes/PT3/FPGA-TN-02099-3-5-sysCONFIG-User-Guide-for-Nexus-Platform.ashx?document_id=52790).

## Config Frames

Configuration bits are two dimensional. For a given device, there is a grid of frame_cnt by bits_per_frame size. Each tile configuration exists as a sub rectangle inside of the overall device grid. Each tile in the tilegrid.json file specifies the coordinates for that tiles configuration data.

An important detail is that the relative bits in each tile sharing a tiletype mean the same thing for that tile. This means that when we have the configuration mapping for a single tile, we have it for all tiles of that type as well.

Config frames are written in three chunks (numbers for LIFCL):

- 32 frames at address 0x8000 set up left and right side IO/IP
Expand Down
36 changes: 36 additions & 0 deletions docs/general/cacheing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Cacheing

The fuzzers all require a lot of runtime in building bitfiles and pulling data from radiant tools.

## Bitstream cacheing

The main cacheing mechanism is the bitstreamcache -- tools/bitstreamcache.py. This stores checksums for input files and
the bitstream in `.bitstreamcache`.

The bitstreams are stored compressed and libprjoxide can read them compressed. Instead of copying the files around, the
cache fetch generates symbolic links.

This folder should be cleared very rarely.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most notably it needs to be cleared if you want to switch radiant version

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not realize this. I think it makes sense to make the radiant version as part of the hash path. I'll probably make it check both old and new too so my current cache isn't burnt

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like a good plan


## Stored deltas

Each solve generates serialized delta files in `.deltas` of the given fuzzer. This is useful to see what changed for each
solver run, but is also used as a marker -- if that file exists, the solver assumes it has been applied already and skips
generating / fetching bitstreams and calling into the fuzzer solvers.

Delete these folders when making heavy changes to fuzz.rs or other portions of the rust library.

## Lapie cache database

Lapie / Lark is a radiant tool used to query the internal chip lattice database. Each run of this program has around
10 seconds of overhead and it only returns around 60 results per second after that.

To speed these accesses up, a sqlite database is generated at `.cache/<radiant version>/<device>-nodes.sqlite` to cache
the results of these queries. Specifically, it caches node data and site data per device as well as the jumpwires list.

Queries, once cached, return nearly instantaneously in comparison, but these files do end up being around 100M in size.

## Cachier

Other methods are annotated with cachier -- a decoration that caches calls into a function by its arguments. These
entries are stored in `.cache/<radiant version>/cache.db`.
46 changes: 46 additions & 0 deletions docs/general/fuzzing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Overview

The primary thing to solve for to be able to run placement and routing on a given device is the relationship between BELs
that nextpnr understands to sites on the device, as well as the routing pips available, and what bits need to be flipped
in the bitstream frames to achieve a given route / BEL configuration.

The main process is to create a minimal verilog file with the features that need to be isolated, generate a bitstream
from that file, and compare it to a baseline bitstream without that feature. This generates a bitstream delta which is
represented as a list of (tile, frame, bit) tuples which changed between the two.

The main difficulty presented is that generating a bitstream takes a few seconds, and given the scale of number of things
to test, some care has to be taken to minimize the state space to attempt. To completely map a single device requires
thousands to tens of thousands of bitstream generations.

The results of all this fuzzing ends up in the database.

## BEL Fuzzing

BEL fuzzing tends to be straightforward, although it does rely on knowing how to configure the primitive in question, in
terms of what options it supports and how to specify those options. Most configuration options can be fuzzed in isolation
with the others which keeps down the number of bitstreams to generate.

### Enum Fuzzing

Many primitives have documented series of enumerated values. One bitstream is typically generated per enum value, and the
mapping is relatively simple. The main work required here is to identify which options are valid and when they are
operative.

### Word Fuzzing

Word fuzzing is conceptually the same as enum fuzzing, but is used against parameters that exist as integers. There is
an assumption here that a word of `N` bits long will require only `N` evaluations to map; ie that any bit in the value
maps to only one bit in the config block. Otherwise something like the initialization values of a LUT would be intractable.

## PIP Fuzzing

PIP fuzzing is the most difficult aspect to constrain to a limited number of trials. We can test these by placing a single
ARC (with a SLICE so it is not optimized away), and then looking at which tiles were impacted. This is usually done by
passing in a set of nodes to pull the PIPs from, and then creating a design with each of those pips placed in isolation.

While BEL fuzzing tends to operate against only one or two tiles at a time, a given node is hard to constrain to a given
tile. Some PIPs are also predicated on a related site being active.

Some edges between nodes are always active. These are detected by placing the ARC and observing no change in relevant
tiles. These are refered to as 'connections' in the database. For fuzzing purposes, it is important to pass the correct
ignore list to the solver since if an irrelevant tile has a change in it, it will not mark it as a pure connection.
91 changes: 91 additions & 0 deletions docs/general/structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Overview

At it's most basic level, the nexus (and most other FPGAs) is composed of basic elements (BELs), pins, wires and
Programmable Interconnect Points (PIPs). The bitstream configures the BELs and which PIPs are active.

Loosely speaking, on the lattice components each BEL corresponds to a site. The internal tooling for lattice refers to
wires as nodes and the terms are used interchangeably.

The lattice parts overlay a tile grid over this structure. Largely speaking the tile grid informs on where the component
might be on the chip, but also where the configuration data can be found / specified for any given chip.

## Sites (BELs)

Every site has a type, and the type dictates both it's pin capabilities and what programmable options and modes exist for
that given site. Sites correspond most closely to the primitives found in lattice documentation, but sometimes a site
isn't directly translated as a primitive, and instead has multiple modes which map the same site to multiple primitives
as defined in the manual.

Nearly every site name contains a prefix indicating the row and column it is most aligned to, and that tile is used to
configure that site. A tile can have multiple sites in it, or the same site type can occur in multiple tile types where
it's configuration bits occur at different offsets.

Many exceptions do exist where a site is named for one row-column pair but it's configuration lives in another tile, and
that tile has the appropriate tile type. For instance, LRAM's typically are like this. Part of what the fuzzers are configured
for is to represent the mapping between the site tile location and the config tile location.

## Nodes (Wires) and PIPs

Nodes represent physical wires with gates connecting it to other nodes. Nodes can have pins tied directly onto them.

Lattice has a TCL library exposed in a tool -- lapie / lark depending on version -- which can be used to query the node
graph. This tooling gives you which PIPs and pins are associated with the node, as well as what aliases are associated
with it.

In terms of scale, there are about 1.7 million nodes on the LIFCL-40 part.

Nodes also have aliases. The typical reason for this is that nodes can span multiple tiles, and so each tile has a local
name for that node. Only the primary name associated with the node is directly queryable, so there is no robust way in
general to determine every node that is associated with a given tile.

### Node Naming

Nodes have a semantically meaningful structure to their naming. They are all prefixed with `R<r>C<c>_` which gives a hint
to it's location; although nodes can span multiple tiles.

After that there are the following naming conventions:

## Tile types

Tiles of a given tile type will always have the same set of:

- Sites
- Nodes
- PIPs

Often they will also dictate the relationship between neighboring tiles in a rigid way. For instance, LRAM instances
have an associated `CIB_LR` tiletype at an offset determined by it's tiletype.

Tile types also are the fundamental building block to configuring the chip since it rigidly maps the bits in it's
configuration bits to the sites and pips associated with it.

Tile types are also standard across devices -- the way you configure a PLC tile is identical in LIFCL-17 as it is in LIFCL-40,
for instance. It should be noted though that lattice is inconsistent with this principal, and so some tile types are
flagged and changed when the tilegrid is imported from lattice's interchange format.

## Global Routes

There is a global distribution network on the LIFCL devices for clocks and resets to limit skew to any given logic cell:

- Starts at CMUX
- Branch out left or right -- LHPRX or RHPRX
- Distributed along HROW's to SPINEs - HPRX1000 -> VPSX1000
- SPINEs push to branch nodes VPSX1000 -> R..C44_HPBX..00
- PLCs local to the SPINE can be fed from here. An additional branch jump HPBX0 can reach the rest.

See global.json for a listing of those cells for each device.

### Example routing:

To get from R82C25_JCLKO_DCC_DCC0 -> R4C35_JCLK_SLICED on LIFCL-33

- R37C25_JCLKO_DCC_DCC0 feeds [R37C25_JJCLKUL_CMUX_CORE_CMUX0, R37C25_JJCLKUL_CMUX_CORE_CMUX1]
- These feed out to [R37C25_JHPRX{LANE}_CMUX_CORE_CMUX0, R37C25_JHPRX{LANE}_CMUX_CORE_CMUX1] respectively
- These feed out to [R37C25_LHPRX{LANE}, R37C25_RHPRX{LANE}]. LHPRX branches out to C0 to C25. RHPRX branches out from C25 to C50
- Following R37C25_LHPRX{LANE}, it drives R37C31_HPRX{LANE}00
- This drives R41C37_VPSX{LANE}00
- R41C37_VPSX{LANE}00 drives R{ROW}C44_HPBX{INST}00
- R4C32_HPBX{INST}00 provides local access to tiles R38 to R50. It also provides access to a branch R4C32_HPBX{INST}00 node.
- R4C32_HPBX{INST}00 provides local access to tiles R25 to R37, namely R4C35_JCLK1
- R4C35_JCLK1 drives R4C35_JCLK_SLICED

2 changes: 1 addition & 1 deletion environment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fi

SCRIPT_PATH=$(readlink -f "${BASH_SOURCE:-$0}")
SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
PYTHONLIBS_DIR="${SCRIPT_DIR}/util:${SCRIPT_DIR}/util/common:${SCRIPT_DIR}/util/fuzz:${SCRIPT_DIR}/timing/util:${SCRIPT_DIR}/libprjoxide/target/${TARGET:-release}"
PYTHONLIBS_DIR="${SCRIPT_DIR}/tools:${SCRIPT_DIR}/util:${SCRIPT_DIR}/util/common:${SCRIPT_DIR}/util/fuzz:${SCRIPT_DIR}/timing/util:${SCRIPT_DIR}/libprjoxide/target/${TARGET:-release}"
export PYTHONPATH="${PYTHONLIBS_DIR}:${PYTHONPATH}"
export RADIANTDIR=$HOME/lscc/radiant/3.1
USER_ENV="${SCRIPT_DIR}/user_environment.sh"
Expand Down
5 changes: 3 additions & 2 deletions examples/common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ YOSYS?=yosys
NEXTPNR?=nextpnr-nexus
PRJOXIDE?=prjoxide
ECPPROG?=ecpprog
TOP?=top

all: $(PROJ).bit

$(PROJ).json: $(PROJ).v $(EXTRA_VERILOG) $(MEM_INIT_FILES)
$(YOSYS) -ql $(PROJ)_syn.log -p "synth_nexus $(SYNTH_ARGS) -top top -json $(PROJ).json" $(PROJ).v $(EXTRA_VERILOG)
$(YOSYS) -ql $(PROJ)_syn.log -p "synth_nexus $(SYNTH_ARGS) -top $(TOP) -json $(PROJ).json -v -debug" $(PROJ).v $(EXTRA_VERILOG)

$(PROJ).fasm: $(PROJ).json $(PDC)
$(NEXTPNR) --device $(DEVICE) --pdc $(PDC) --json $(PROJ).json --fasm $(PROJ).fasm
$(NEXTPNR) --device $(DEVICE) --pdc $(PDC) --json $(PROJ).json --fasm $(PROJ).fasm --debug -v

$(PROJ).bit: $(PROJ).fasm
$(PRJOXIDE) pack $(PROJ).fasm $(PROJ).bit
Expand Down
8 changes: 8 additions & 0 deletions examples/spinex_minimal/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
PROJ=SpinexMinimal
#DEVICE=LIFCL-33-8CTG104C
DEVICE=LIFCL-33U-8CTG104C
PDC=tinyclunx.pdc
TOP=SpinexMinimal
EXTRA_VERILOG=SpinexMinimal_references.v

include ../common.mk
Loading