Skip to content

Add generative IOs #380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: FABulous2.0-development
Choose a base branch
from
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
84 changes: 84 additions & 0 deletions FABulous/FABulous_API.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,87 @@ def generateUserDesignTopWrapper(self, userDesign: Path, topWrapper: Path):
Path to the output top wrapper file.
"""
generateUserDesignTopWrapper(self.fabric, userDesign, topWrapper)

def genIOBelForTile(self, tile_name: str) -> list[Bel]:
"""
Generates the IO BELs for the generative IOs of a tile.
Config Access Generative IOs will be a separate Bel.
Updates the tileDic with the generated IO BELs.
Parameters
----------
tile_name : str
Name of the tile to generate IO Bels.
Returns
-------
bels : List[Bel]
The bel object representing the generative IOs.
Raises
------
ValueError
If tile not found in fabric.
In case of an invalid IO type for generative IOs.
If the number of config access ports does not match the number of config bits.
"""
tile = self.fabric.getTileByName(tile_name)
bels: list[Bel] = []
if not tile:
logger.error(f"Tile {tile_name} not found in fabric.")
raise ValueError

if self.writer.__class__ is VHDLWriter:
suffix = "vhdl"
else:
suffix = "v"

gios = [gio for gio in tile.gen_ios if not gio.configAccess]
gio_config_access = [gio for gio in tile.gen_ios if gio.configAccess]

if gios:
self.setWriterOutputFile(
tile.tileDir.parent / f"{tile.name}_GenIO.{suffix}"
)
bel = self.fabricGenerator.genIOBel(gios, f"{tile.name}_GenIO")
if bel:
bels.append(bel)
if gio_config_access:
self.setWriterOutputFile(
tile.tileDir.parent / f"{tile.name}_ConfigAccess_GenIO.{suffix}"
)
bel = self.fabricGenerator.genIOBel(
gio_config_access, f"{tile.name}_ConfigAccess_GenIO"
)
if bel:
bels.append(bel)

# update fabric tileDic with generated IO BELs
if self.fabric.tileDic.get(tile_name):
self.fabric.tileDic[tile_name].bels += bels
elif not self.fabric.unusedTileDic[tile_name].bels:
logger.warning(
f"Tile {tile_name} is not used in fabric, but defined in fabric.csv."
)
self.fabric.unusedTileDic[tile_name].bels += bels
else:
logger.error(
f"Tile {tile_name} is not defined in fabric, please add to fabric.csv."
)
raise ValueError

# update bels on all tiles in fabric.tile
for row in self.fabric.tile:
for tile in row:
if tile and tile.name == tile_name:
tile.bels += bels

return bels

def genFabricIOBels(self):
"""Generates the IO BELs for the generative IOs of the fabric."""

for tile in self.fabric.tileDic.values():
if tile.gen_ios:
logger.info(f"Generating IO BELs for tile {tile.name}")
self.genIOBelForTile(tile.name)
19 changes: 19 additions & 0 deletions FABulous/FABulous_CLI/FABulous_CLI.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,13 @@ def do_run_FABulous_fabric(self, *ignored):
Does this by calling the respective functions 'do_gen_[function]'.
"""
logger.info("Running FABulous")
self.onecmd_plus_hooks("gen_io_fabric")
if self.exit_code != 0:
logger.opt(exception=CommandError()).error(
"GEN_IO IO generation failed. Please check the logs for more details."
)
if not self.force:
return

self.onecmd_plus_hooks("gen_fabric")
if self.exit_code != 0:
Expand Down Expand Up @@ -1102,3 +1109,15 @@ def do_generate_custom_tile_config(self, args):

if not args.no_switch_matrix:
parseTiles(tile_csv)

@with_category(CMD_FABRIC_FLOW)
@with_argparser(tile_list_parser)
def do_gen_io_tiles(self, args):
if args.tiles:
for tile in args.tiles:
self.fabulousAPI.genIOBelForTile(tile)

@with_category(CMD_FABRIC_FLOW)
@allow_blank
def do_gen_io_fabric(self, args):
self.fabulousAPI.genFabricIOBels()
18 changes: 16 additions & 2 deletions FABulous/fabric_definition/Fabric.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ class Fabric:
A dictionary of tiles used in the fabric. The key is the name of the tile and the value is the tile.
superTileDic : dict[str, SuperTile]
A dictionary of super tiles used in the fabric. The key is the name of the super tile and the value is the super tile.
unusedTileDic: dict[str, Tile]
A dictionary of tiles that are not used in the fabric, but defined in the fabric.csv.
The key is the name of the tile and the value is the tile.
unusedSuperTileDic: dict[str, Tile]
A dictionary of super tiles that are not used in the fabric, but defined in the fabric.csv.
The key is the name of the tile and the value is the tile.
"""

tile: list[list[Tile]] = field(default_factory=list)
Expand All @@ -76,6 +82,8 @@ class Fabric:

tileDic: dict[str, Tile] = field(default_factory=dict)
superTileDic: dict[str, SuperTile] = field(default_factory=dict)
unusedTileDic: dict[str, Tile] = field(default_factory=dict)
unusedSuperTileDic: dict[str, SuperTile] = field(default_factory=dict)
# wires: list[Wire] = field(default_factory=list)
commonWirePair: list[tuple[str, str]] = field(default_factory=list)

Expand Down Expand Up @@ -252,10 +260,16 @@ def __repr__(self) -> str:
return fabric

def getTileByName(self, name: str) -> Tile | None:
return self.tileDic.get(name)
ret = self.tileDic.get(name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you will need the .get(name, None); otherwise, it will raise an error on the first get miss

if ret is None:
ret = self.unusedTileDic.get(name)
return ret
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of returning None, I think we should raise an error.


def getSuperTileByName(self, name: str) -> SuperTile | None:
return self.superTileDic.get(name)
ret = self.superTileDic.get(name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here

if ret is None:
ret = self.unusedSuperTileDic.get(name)
return ret

def getAllUniqueBels(self) -> list[Bel]:
bels = list()
Expand Down
57 changes: 57 additions & 0 deletions FABulous/fabric_definition/Gen_IO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from dataclasses import dataclass, field
from FABulous.fabric_definition.define import IO


@dataclass
class Gen_IO:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you can make this can inherit the Bel class, which will allow this to treat it as normal bel as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I thought about this, but currently all Gen_IO of a tile get assembled together to one Bel.

I could split this, that one Gen_IO creates on Bel, but it felt more robust and easier to handle if you have one big IO Bel instead of a plenty small one.

Copy link
Collaborator

@KelvinChung2000 KelvinChung2000 Jun 24, 2025

Choose a reason for hiding this comment

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

This will still be a single Bel. It will just have a special attribute.

So rather than:

module IO_bidir_1(...);
endmodule

and you have lot of them,
become

module IO_bidir_3(...);
IO_bidir_1 inst_1(...);
IO_bidir_1 inst_2(...);
IO_bidir_1 inst_3(...);
endmodule

but still a single bel.

"""
Contains all the information about a generated IO port (GEN_IO).
The information are parsed from the GEN_IO definitions in
the tile CSV file.

Attributes
----------
prefix : str
The prefix of the GEN_IO given in the CSV file.
pins : int
Number of IOs.
IO : IO
Direction of the IOs, either INPUT or OUTPUT, seen from the fabric side.
This means a fabric INPUT is an OUTPUT globally and vice versa.
configBit : int
The number of accessible config bits for config access GEN_IO.
configAccess : bool
Whether the GEN_IO is config access.
Routes access to config bits, directly to TOP.
This GEN_IOs are not connected to the switchmatrix,
Can only be used as an OUTPUT.
inverted : bool
GEN_IO will be inverted.
clocked : bool
Adds a register to GEN_IO.
clockedComb: bool
Creates two signals for every GEN_IO.
<prefix><Number>_Q: The clocked signal.
<prefix><Number>: The original combinatorial signal.
If the GEN_IO is an INPUT, then there will be created
two signals to the top, <prefix><Number>_Q_top is the clocked input
signal and <prefix><Number>_top is the combinatorial input signal.
If the GEN_IO is an OUTPUT, then there will be two signals connected
to the switch matrix, <prefix><Number>_Q is the clocked output signal
and <prefix><Number> is the combinatorial output signal.
clockedMux: bool
GEN_IO will be multiplexed between the combinatorial and clocked signal.
The multiplexer can be switched via config bits.
"""

prefix: str
pins: int
IO: IO
configBit: int = 0

# Parameters for GEN_IO:
configAccess: bool = False
inverted: bool = False
clocked: bool = False
clockedComb: bool = False
clockedMux: bool = False
10 changes: 10 additions & 0 deletions FABulous/fabric_definition/Tile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from dataclasses import dataclass, field
from FABulous.fabric_definition.define import IO, Direction, Side
from FABulous.fabric_definition.Bel import Bel
from FABulous.fabric_definition.Gen_IO import Gen_IO
from FABulous.fabric_definition.Port import Port
from FABulous.fabric_definition.Wire import Wire
from typing import Any
Expand All @@ -21,6 +22,8 @@ class Tile:
The list of ports of the tile
matrixDir : str
The directory of the tile matrix
gen_ios : List[Gen_IO]
The list of GEN_IOs of the tile
globalConfigBits : int
The number of config bits the tile has
withUserCLK : bool
Expand All @@ -35,6 +38,7 @@ class Tile:
portsInfo: list[Port]
bels: list[Bel]
matrixDir: pathlib.Path
gen_ios: list[Gen_IO]
globalConfigBits: int = 0
withUserCLK: bool = False
wireList: list[Wire] = field(default_factory=list)
Expand All @@ -48,12 +52,14 @@ def __init__(
bels: list[Bel],
tileDir: pathlib.Path,
matrixDir: pathlib.Path,
gen_ios: list[Gen_IO],
userCLK: bool,
configBit: int = 0,
) -> None:
self.name = name
self.portsInfo = ports
self.bels = bels
self.gen_ios = gen_ios
self.matrixDir = matrixDir
self.withUserCLK = userCLK
self.globalConfigBits = configBit
Expand All @@ -63,6 +69,10 @@ def __init__(
for b in self.bels:
self.globalConfigBits += b.configBit

for gio in self.gen_ios:
if gio.configAccess:
self.globalConfigBits += gio.configBit
Copy link
Collaborator

Choose a reason for hiding this comment

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

It might lead to a problem. Since if we use Gen IO after tile creation, this will not be updated. We should make the global config bit a class attribute, and compute the result every time this is used. Less efficient, but always right.

Something like:

@property
def globalConfigBits() -> int:
  configBit = 0
  configBit += sum([...]) #bel sum
  configBit += sum(...) #switch matrix sum
  return configBit
  

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea, I'll implement this. :)


def __eq__(self, __o: Any) -> bool:
if __o is None or not isinstance(__o, Tile):
return False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ GHDL_FLAGS=--std=08 -O2 --workdir=${BUILD_DIR}

#only add if custom_prims.v exists
ifneq ($(wildcard ${USER_DESIGN_DIR}/custom_prims.v),)
CUSTOM_PRIMS=-extra-plib ${USER_DESIGN_DIR}/custom_prims.v;
CUSTOM_PRIMS=-extra-plib ${USER_DESIGN_DIR}/custom_prims.v
endif

.PHONY: all run_FABulous_demo full build_test_design run_simulation mkdir_build clean
Expand All @@ -32,7 +32,7 @@ run_FABulous_demo:
FABulous ${FAB_PROJ_ROOT} -fs ${FAB_PROJ_ROOT}/FABulous.tcl

run_yosys: mkdir_build
yosys -m ghdl -p "ghdl ${USER_DESIGN_VHDL} -e ${DESIGN}; read_verilog ${TOP_WRAPPER_VERILOG}; synth_fabulous -top ${TOP_WRAPPER} -json ${BUILD_DIR}/${DESIGN}.json; ${CUSTOM_PRIMS}"
yosys -m ghdl -p "ghdl ${USER_DESIGN_VHDL} -e ${DESIGN}; read_verilog ${TOP_WRAPPER_VERILOG}; synth_fabulous -top ${TOP_WRAPPER} -json ${BUILD_DIR}/${DESIGN}.json ${CUSTOM_PRIMS};"

run_nextpnr: mkdir_build
FAB_ROOT=${FAB_PROJ_ROOT} nextpnr-generic --uarch fabulous --json ${BUILD_DIR}/${DESIGN}.json -o fasm=${BUILD_DIR}/${DESIGN}.fasm
Expand Down
Loading