From aecb7fb303912683853b5c5bf8d5ff17bb993f85 Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Tue, 17 Sep 2024 16:15:49 +0200 Subject: [PATCH] floogen: Add support for single-AXI networks (#69) * hw(router): Add router wrapper for single-axi configs * floogen(link): Add `AxiLink` class * floogen(ni): Add `AxiNi` class * floogen(router): Add `AxiRouter` class * floogen(network): Support both single-AXI and narrow-wide configurations * floogen(tpl): Add templates for single-AXI configurations * floogen(examples): Rename `occamy_mesh` to `occamy_mesh_xy` * floogen(examples): Add new "narrow-wide" network type * floogen(examples): Add three new examples for single-AXI configurations * ci: Update name of config file * cfg: Fix network name of `occamy_mesh_xy` * docs: Update examples in README and floogen documentation * docs: Remove deprecated configurations and flags in FlooGen documentation * docs: Update CHANGELOG --- .github/workflows/floogen.yml | 2 +- Bender.yml | 2 + CHANGELOG.md | 3 + README.md | 10 +- docs/floogen.md | 13 +- floogen/examples/axi_mesh_id.yml | 81 ++++++++++ floogen/examples/axi_mesh_src.yml | 81 ++++++++++ floogen/examples/axi_mesh_xy.yml | 81 ++++++++++ floogen/examples/occamy_mesh_src.yml | 1 + .../{occamy_mesh.yml => occamy_mesh_xy.yml} | 3 +- floogen/examples/occamy_tree.yml | 1 + floogen/examples/single_cluster.yml | 1 + floogen/examples/terapool.yml | 1 + floogen/model/link.py | 35 ++++ floogen/model/network.py | 131 +++++++++------ floogen/model/network_interface.py | 19 ++- floogen/model/protocol.py | 2 +- floogen/model/router.py | 12 ++ floogen/templates/floo_axi_chimney.sv.mako | 58 +++++++ floogen/templates/floo_axi_router.sv.mako | 80 +++++++++ hw/floo_axi_router.sv | 153 ++++++++++++++++++ 21 files changed, 704 insertions(+), 66 deletions(-) create mode 100644 floogen/examples/axi_mesh_id.yml create mode 100644 floogen/examples/axi_mesh_src.yml create mode 100644 floogen/examples/axi_mesh_xy.yml rename floogen/examples/{occamy_mesh.yml => occamy_mesh_xy.yml} (97%) create mode 100644 floogen/templates/floo_axi_chimney.sv.mako create mode 100644 floogen/templates/floo_axi_router.sv.mako create mode 100644 hw/floo_axi_router.sv diff --git a/.github/workflows/floogen.yml b/.github/workflows/floogen.yml index 50c5e33b..5eca7203 100644 --- a/.github/workflows/floogen.yml +++ b/.github/workflows/floogen.yml @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - examples: ["single_cluster", "occamy_mesh", "occamy_tree", "occamy_mesh_src", "terapool"] + examples: ["single_cluster", "occamy_mesh_xy", "occamy_tree", "occamy_mesh_src", "terapool"] steps: - uses: actions/checkout@v4 - name: Set up Python diff --git a/Bender.yml b/Bender.yml index 9d5f96cf..e2233570 100644 --- a/Bender.yml +++ b/Bender.yml @@ -38,6 +38,8 @@ sources: - hw/floo_axi_chimney.sv - hw/floo_nw_chimney.sv - hw/floo_router.sv + # Level 3 (Wrappers) + - hw/floo_axi_router.sv - hw/floo_nw_router.sv - target: vc_router diff --git a/CHANGELOG.md b/CHANGELOG.md index 8df9f90c..ffa32d0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - The `RouteCfg` describes all the necessary routing information parameters required by the chimneys. - The `ChimneyCfg` describes all other parameters for the data path of the chimney (e.g. Mgr/Sbr port enable, number of oustanding transactions, RoB types & sizes, etc.) - The `floo_test_pkg` now defines default configurations for all the new configuration structs that are used by the testbenches. +- Add `floo_axi_router` module, which is a wrapper similar to the `floo_nw_router` but for single-AXI configurations, and can be used in conjunction with `floo_axi_chimney`. #### FlooGen - The `data_width` and `user_width` fields for `protocols` are now also validated to be compatible with each other. - All the various `*Cfg`'s is now rendered by _FlooGen_, either in the `*_noc_pkg` or in the `*_noc` module itself. +- Added support for single-AXI configuration networks. ### Changed @@ -42,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - The `direction` field in the `protocol` schema is no longer required, since the direction is determined when specifying `mgr_port_protocol` and `sbr_port_protocol`. - The `name` field must be unique now, since it is used by `mgr_port_protocol` and `sbr_port_protocol` to reference the exact protocol. - All examples were adapted to reflect those changes. +- A FlooGen configuration file now requires a `network_type` field, to determine the type of network to generate. The options are `axi` for single-AXI networks and `narrow-wide` for the narrow-wide AXI configurations. ### Fixed diff --git a/README.md b/README.md index 6477e0aa..1ff6a238 100644 --- a/README.md +++ b/README.md @@ -162,16 +162,14 @@ The following example shows the configuration for a simple mesh topology with 4x use_id_table: true protocols: - - name: "example_axi" + - name: "axi_in" type: "AXI4" - direction: "manager" data_width: 64 addr_width: 32 id_width: 3 user_width: 1 - - name: "example_axi" + - name: "axi_out" type: "AXI4" - direction: "subordinate" data_width: 64 addr_width: 32 id_width: 3 @@ -184,9 +182,9 @@ The following example shows the configuration for a simple mesh topology with 4x base: 0x1000_0000 size: 0x0004_0000 mgr_port_protocol: - - "example_axi" + - "axi_in" sbr_port_protocol: - - "example_axi" + - "axi_out" routers: - name: "router" diff --git a/docs/floogen.md b/docs/floogen.md index 6abb96fd..63c559f4 100644 --- a/docs/floogen.md +++ b/docs/floogen.md @@ -16,16 +16,14 @@ The following is an example of a configuration file for a 4x4 mesh network: use_id_table: true protocols: - - name: "example_axi" + - name: "axi_in" type: "AXI4" - direction: "manager" data_width: 64 addr_width: 32 id_width: 3 user_width: 1 - - name: "example_axi" + - name: "axi_out" type: "AXI4" - direction: "subordinate" data_width: 64 addr_width: 32 id_width: 3 @@ -38,9 +36,9 @@ The following is an example of a configuration file for a 4x4 mesh network: base: 0x1000_0000 size: 0x0004_0000 mgr_port_protocol: - - "example_axi" + - "axi_in" sbr_port_protocol: - - "example_axi" + - "axi_out" routers: - name: "router" @@ -86,7 +84,6 @@ The protocols section describes the protocols used in the network. It is compose - `name`: name of the protocol. This will be used as a reference in the framework and in the generated RTL code to name the protocol module and the protocol signals. If the narrow-wide channels are used, they need to be named `narrow` and `wide` respectively. - `type`: Currently only `AXI4` is supported -- `direction`: the direction of the protocol. It can be either `manager` or `subordinate`. If an endpoint is both manager and subordinate, two protocols need to be defined. - `data_width`: the data width of the protocol - `addr_width`: the address width of the protocol - `id_width`: the ID width of the protocol. Endpoints with different ID widths for the `manager` and `subordinate` protocols are supported. @@ -159,7 +156,5 @@ where `` is the configuration file and `` is the output Apart from the configuration file, `floogen` supports additional options to customize the generated RTL code. The following options are supported: - `--outdir`: the output directory where the generated RTL code will be placed. This is equivalent to the `-o` option. If it is not specified, the output is printed to stdout. -- `--only-pkg`: only generate the package. This is useful if you want to test single IPs without generating a whole network. -- `--pkg-outdir`: the output directory where the generated package will be placed. By default, the package in the `hw` folder is overwitten, since it is also the once that is used by `bender` for compiling the IPs. If you want to keep the original package, you can specify a different output directory here. - `--no-format`: do not format the generated RTL code. By default, the generated RTL code is formatted with verible format, for which the `verible-verilog-format` binary needs to be installed. If this option is set, the generated RTL code is not formatted. - `--visualize`: visualize the generated network. It will create a plot of the graph of the network. If the `--outdir` option is specified, the plot is saved in the output directory. Otherwise, it is shown in a window. This is mainly intended for a quick check of the generated network, not a tool for debugging. diff --git a/floogen/examples/axi_mesh_id.yml b/floogen/examples/axi_mesh_id.yml new file mode 100644 index 00000000..39c0b436 --- /dev/null +++ b/floogen/examples/axi_mesh_id.yml @@ -0,0 +1,81 @@ +# Copyright 2023 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +name: axi_mesh +description: "AXI mesh configuration for FlooGen" +network_type: "axi" + +routing: + route_algo: "ID" + use_id_table: true + +protocols: + - name: "axi_in" + protocol: "AXI4" + data_width: 64 + addr_width: 48 + id_width: 4 + user_width: 1 + type_prefix: # prevents `axi_axi` prefix + - name: "axi_out" + protocol: "AXI4" + data_width: 64 + addr_width: 48 + id_width: 2 + user_width: 1 + type_prefix: # prevents `axi_axi` prefix + +endpoints: + - name: "cluster" + array: [4, 4] + addr_range: + base: 0x0000_1000_0000 + size: 0x0000_0004_0000 + mgr_port_protocol: + - "axi_in" + sbr_port_protocol: + - "axi_out" + - name: "hbm" + array: [4] + addr_range: + base: 0x0000_8000_0000 + size: 0x0000_4000_0000 + sbr_port_protocol: + - "axi_out" + - name: "peripherals" + addr_range: + start: 0x0000_0000_0000 + end: 0x0000_0fff_ffff + mgr_port_protocol: + - "axi_in" + sbr_port_protocol: + - "axi_out" + +routers: + - name: "router" + array: [4, 4] + degree: 5 + +connections: + - src: "cluster" + dst: "router" + src_range: + - [0, 3] + - [0, 3] + dst_range: + - [0, 3] + - [0, 3] + dst_dir: "Eject" + - src: "hbm" + dst: "router" + src_range: + - [0, 3] + dst_range: + - [0, 0] + - [0, 3] + dst_dir: "West" + - src: "peripherals" + dst: "router" + dst_idx: [1, 3] + dst_dir: "North" diff --git a/floogen/examples/axi_mesh_src.yml b/floogen/examples/axi_mesh_src.yml new file mode 100644 index 00000000..5213032b --- /dev/null +++ b/floogen/examples/axi_mesh_src.yml @@ -0,0 +1,81 @@ +# Copyright 2023 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +name: axi_mesh +description: "AXI mesh configuration for FlooGen" +network_type: "axi" + +routing: + route_algo: "SRC" + use_id_table: true + +protocols: + - name: "axi_in" + protocol: "AXI4" + data_width: 64 + addr_width: 48 + id_width: 4 + user_width: 1 + type_prefix: # prevents `axi_axi` prefix + - name: "axi_out" + protocol: "AXI4" + data_width: 64 + addr_width: 48 + id_width: 2 + user_width: 1 + type_prefix: # prevents `axi_axi` prefix + +endpoints: + - name: "cluster" + array: [4, 4] + addr_range: + base: 0x0000_1000_0000 + size: 0x0000_0004_0000 + mgr_port_protocol: + - "axi_in" + sbr_port_protocol: + - "axi_out" + - name: "hbm" + array: [4] + addr_range: + base: 0x0000_8000_0000 + size: 0x0000_4000_0000 + sbr_port_protocol: + - "axi_out" + - name: "peripherals" + addr_range: + start: 0x0000_0000_0000 + end: 0x0000_0fff_ffff + mgr_port_protocol: + - "axi_in" + sbr_port_protocol: + - "axi_out" + +routers: + - name: "router" + array: [4, 4] + degree: 5 + +connections: + - src: "cluster" + dst: "router" + src_range: + - [0, 3] + - [0, 3] + dst_range: + - [0, 3] + - [0, 3] + dst_dir: "Eject" + - src: "hbm" + dst: "router" + src_range: + - [0, 3] + dst_range: + - [0, 0] + - [0, 3] + dst_dir: "West" + - src: "peripherals" + dst: "router" + dst_idx: [1, 3] + dst_dir: "North" diff --git a/floogen/examples/axi_mesh_xy.yml b/floogen/examples/axi_mesh_xy.yml new file mode 100644 index 00000000..7a8cd32c --- /dev/null +++ b/floogen/examples/axi_mesh_xy.yml @@ -0,0 +1,81 @@ +# Copyright 2023 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +name: axi_mesh +description: "AXI mesh configuration for FlooGen" +network_type: "axi" + +routing: + route_algo: "XY" + use_id_table: true + +protocols: + - name: "axi_in" + protocol: "AXI4" + data_width: 64 + addr_width: 48 + id_width: 4 + user_width: 1 + type_prefix: # prevents `axi_axi` prefix + - name: "axi_out" + protocol: "AXI4" + data_width: 64 + addr_width: 48 + id_width: 2 + user_width: 1 + type_prefix: # prevents `axi_axi` prefix + +endpoints: + - name: "cluster" + array: [4, 4] + addr_range: + base: 0x0000_1000_0000 + size: 0x0000_0004_0000 + mgr_port_protocol: + - "axi_in" + sbr_port_protocol: + - "axi_out" + - name: "hbm" + array: [4] + addr_range: + base: 0x0000_8000_0000 + size: 0x0000_4000_0000 + sbr_port_protocol: + - "axi_out" + - name: "peripherals" + addr_range: + start: 0x0000_0000_0000 + end: 0x0000_0fff_ffff + mgr_port_protocol: + - "axi_in" + sbr_port_protocol: + - "axi_out" + +routers: + - name: "router" + array: [4, 4] + degree: 5 + +connections: + - src: "cluster" + dst: "router" + src_range: + - [0, 3] + - [0, 3] + dst_range: + - [0, 3] + - [0, 3] + dst_dir: "Eject" + - src: "hbm" + dst: "router" + src_range: + - [0, 3] + dst_range: + - [0, 0] + - [0, 3] + dst_dir: "West" + - src: "peripherals" + dst: "router" + dst_idx: [1, 3] + dst_dir: "North" diff --git a/floogen/examples/occamy_mesh_src.yml b/floogen/examples/occamy_mesh_src.yml index 624b7f24..b0f85b8c 100644 --- a/floogen/examples/occamy_mesh_src.yml +++ b/floogen/examples/occamy_mesh_src.yml @@ -4,6 +4,7 @@ name: occamy_mesh_src description: "Occamy mesh configuration for FlooGen with source-based routing" +network_type: "narrow-wide" routing: route_algo: "SRC" diff --git a/floogen/examples/occamy_mesh.yml b/floogen/examples/occamy_mesh_xy.yml similarity index 97% rename from floogen/examples/occamy_mesh.yml rename to floogen/examples/occamy_mesh_xy.yml index 045d00c7..cc4976a0 100644 --- a/floogen/examples/occamy_mesh.yml +++ b/floogen/examples/occamy_mesh_xy.yml @@ -2,8 +2,9 @@ # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 -name: occamy_mesh +name: occamy_mesh_xy description: "Occamy mesh configuration for FlooGen" +network_type: "narrow-wide" routing: route_algo: "XY" diff --git a/floogen/examples/occamy_tree.yml b/floogen/examples/occamy_tree.yml index 45d80e9d..80e72841 100644 --- a/floogen/examples/occamy_tree.yml +++ b/floogen/examples/occamy_tree.yml @@ -4,6 +4,7 @@ name: occamy_tree description: "Occamy configuration for FlooGen" +network_type: "narrow-wide" routing: route_algo: "ID" diff --git a/floogen/examples/single_cluster.yml b/floogen/examples/single_cluster.yml index 68f3c0b2..0e94c910 100644 --- a/floogen/examples/single_cluster.yml +++ b/floogen/examples/single_cluster.yml @@ -4,6 +4,7 @@ name: single_cluster description: "Single Cluster Configuration for FlooGen" +network_type: "narrow-wide" routing: route_algo: "ID" diff --git a/floogen/examples/terapool.yml b/floogen/examples/terapool.yml index 0ac92757..c9a4d748 100644 --- a/floogen/examples/terapool.yml +++ b/floogen/examples/terapool.yml @@ -4,6 +4,7 @@ name: terapool description: "Terapool AXI NoC" +network_type: "narrow-wide" routing: route_algo: "SRC" diff --git a/floogen/model/link.py b/floogen/model/link.py index 2e6ce7e6..5162301b 100644 --- a/floogen/model/link.py +++ b/floogen/model/link.py @@ -37,6 +37,41 @@ def declare(self): def render_ports(self): """Declare the ports of the link.""" +class AxiLink(Link): + """Link class to describe a AxiLink.""" + + req_type: ClassVar[str] = "floo_req_t" + rsp_type: ClassVar[str] = "floo_rsp_t" + + def req_name(self): + """Return the narrow request name.""" + return f"{self.source}_to_{self.dest}_req" + + def rsp_name(self): + """Return the narrow response name.""" + return f"{self.dest}_to_{self.source}_rsp" + + @classmethod + def render_typedefs(cls, axi, cfg): + """Render the typedefs of the links.""" + string = f"`FLOO_TYPEDEF_AXI_CHAN_ALL(axi, req, rsp, {axi}, {cfg}, hdr_t)\n\n" + string += "`FLOO_TYPEDEF_AXI_LINK_ALL(req, rsp, req, rsp)\n" + return string + + def declare(self): + """Declare the link in the generated code.""" + string = f"{self.req_type} {self.req_name()};\n" + string += f"{self.rsp_type} {self.rsp_name()};\n" + return string + "\n" + + def render_ports(self, direction="input"): + """Declare the ports of the link.""" + reverse_direction = "output" if direction == "input" else "input" + ports = [] + ports.append(f"{direction} {self.req_type} {self.req_name()}") + ports.append(f"{reverse_direction} {self.rsp_type} {self.rsp_name()}") + return ports + class NarrowWideLink(Link): """Link class to describe a NarrowWidelink.""" diff --git a/floogen/model/network.py b/floogen/model/network.py index 3fd1d5e8..f7ede414 100644 --- a/floogen/model/network.py +++ b/floogen/model/network.py @@ -6,22 +6,23 @@ # Author: Tim Fischer import pathlib -from typing import Optional, List, ClassVar from importlib.resources import files, as_file +from typing import Optional, List, ClassVar +from typing_extensions import Annotated import networkx as nx import matplotlib.pyplot as plt from mako.lookup import Template -from pydantic import BaseModel, ConfigDict, field_validator, model_validator +from pydantic import BaseModel, ConfigDict, StringConstraints, field_validator, model_validator from floogen.model.routing import Routing, RouteAlgo, RouteMapRule, RouteRule, RouteMap, RouteTable from floogen.model.routing import Coord, SimpleId, AddrRange, XYDirections from floogen.model.graph import Graph from floogen.model.endpoint import EndpointDesc, Endpoint -from floogen.model.router import RouterDesc, NarrowWideRouter +from floogen.model.router import RouterDesc, NarrowWideRouter, AxiRouter from floogen.model.connection import ConnectionDesc -from floogen.model.link import NarrowWideLink, NarrowWideVCLink -from floogen.model.network_interface import NarrowWideAxiNI +from floogen.model.link import NarrowWideLink, NarrowWideVCLink, AxiLink +from floogen.model.network_interface import NarrowWideAxiNI, AxiNI from floogen.model.protocol import AXI4, AXI4Bus from floogen.utils import clog2, sv_enum_typedef, sv_param_decl import floogen.templates @@ -39,6 +40,7 @@ class Network(BaseModel): # pylint: disable=too-many-public-methods name: str description: Optional[str] + network_type: Annotated[str, StringConstraints(pattern=r"axi|narrow-wide")] protocols: List[AXI4] endpoints: List[EndpointDesc] routers: List[RouterDesc] @@ -83,24 +85,35 @@ def validate_routers(cls, routers): names.add(rt.name) return routers - @field_validator("protocols") - @classmethod - def validate_protocols(cls, protocols): + @model_validator(mode="after") + def validate_protocols(self): """Check that names are unique and parameters are compatible.""" # Check that address width is unique among all protocols - if len(set(prot.addr_width for prot in protocols)) != 1: + if len(set(prot.addr_width for prot in self.protocols)) != 1: raise ValueError("All protocols must have the same address width") - # Check that `narrow` and `wide` protocols have the same data width - if len(set(prot.data_width for prot in protocols if prot.type == "narrow")) != 1: - raise ValueError("All `narrow` protocols must have the same data width") - if len(set(prot.data_width for prot in protocols if prot.type == "wide")) != 1: - raise ValueError("All `wide` protocols must have the same data width") - # Check that `narrow` and `wide` protocols have the same user width - if len(set(prot.user_width for prot in protocols if prot.type == "narrow")) != 1: - raise ValueError("All `narrow` protocols must have the same user width") - if len(set(prot.user_width for prot in protocols if prot.type == "wide")) != 1: - raise ValueError("All `wide` protocols must have the same user width") - return protocols + if self.network_type == "narrow-wide": + # Check that `narrow` and `wide` protocols have the same data width + if len(set(prot.data_width for prot in self.protocols if prot.type == "narrow")) != 1: + raise ValueError("All `narrow` protocols must have the same data width") + if len(set(prot.data_width for prot in self.protocols if prot.type == "wide")) != 1: + raise ValueError("All `wide` protocols must have the same data width") + # Check that `narrow` and `wide` protocols have the same user width + if len(set(prot.user_width for prot in self.protocols if prot.type == "narrow")) != 1: + raise ValueError("All `narrow` protocols must have the same user width") + if len(set(prot.user_width for prot in self.protocols if prot.type == "wide")) != 1: + raise ValueError("All `wide` protocols must have the same user width") + # Check that `type` is defined when using `narrow-wide` network + if any(prot.type not in ["narrow", "wide"] for prot in self.protocols) and \ + "narrow-wide" in self.network_type: + raise ValueError("Protocols must define `type` for `narrow-wide` networks") + else: + # Check that data width is the same among all protocols + if len(set(prot.data_width for prot in self.protocols)) != 1: + raise ValueError("All protocols must have the same data width") + # Check that user width is the same among all protocols + if len(set(prot.user_width for prot in self.protocols)) != 1: + raise ValueError("All protocols must have the same user width") + return self @model_validator(mode="after") def set_addr_width(self): @@ -342,10 +355,17 @@ def compile_links(self): "dest_type": self.graph.nodes[edge[1]]["type"], "is_bidirectional": is_bidirectional, } - if self.routing.num_vc_id_bits > 0: - self.graph.set_edge_obj(edge, NarrowWideVCLink(**link)) - else: - self.graph.set_edge_obj(edge, NarrowWideLink(**link)) + match (self.network_type, self.routing.num_vc_id_bits): + case ("axi", 0): + self.graph.set_edge_obj(edge, AxiLink(**link)) + case ("narrow-wide", 0): + self.graph.set_edge_obj(edge, NarrowWideLink(**link)) + case ("narrow-wide", _): + self.graph.set_edge_obj(edge, NarrowWideVCLink(**link)) + case _: + raise NotImplementedError( + f"Network type {self.network_type} with VC routers is not supported yet" + ) def compile_routers(self): # pylint: disable=too-many-branches, too-many-locals """Infer the router type from the network.""" @@ -405,7 +425,11 @@ def compile_routers(self): # pylint: disable=too-many-branches, too-many-locals } if self.routing.route_algo == RouteAlgo.XY: router_dict["id"] = self.graph.get_node_id(rt_name) - self.graph.set_node_obj(rt_name, NarrowWideRouter(**router_dict)) + match self.network_type: + case "axi": + self.graph.set_node_obj(rt_name, AxiRouter(**router_dict)) + case "narrow-wide": + self.graph.set_node_obj(rt_name, NarrowWideRouter(**router_dict)) def compile_endpoints(self): """Infer the endpoint type from the network.""" @@ -492,18 +516,22 @@ def compile_nis(self): mgr_prot_edges = self.graph.get_edges_to(ni_name, filters=[self.graph.is_prot_edge]) for protocols in sbr_prot_edges: for prot in protocols: - match prot.type: - case "narrow": + match (self.network_type, prot.type): + case ("axi", _): + ni_dict["sbr_port"] = prot + case ("narrow-wide", "narrow"): ni_dict["sbr_narrow_port"] = prot - case "wide": + case ("narrow-wide", "wide"): ni_dict["sbr_wide_port"] = prot for protocols in mgr_prot_edges: for prot in protocols: - match prot.type: - case "narrow": + match (self.network_type, prot.type): + case ("axi", _): + ni_dict["mgr_port"] = prot + case ("narrow-wide", "narrow"): ni_dict["mgr_narrow_port"] = prot - case "wide": + case ("narrow-wide", "wide"): ni_dict["mgr_wide_port"] = prot ni_dict["mgr_link"] = self.graph.get_edges_from( @@ -512,8 +540,11 @@ def compile_nis(self): ni_dict["sbr_link"] = self.graph.get_edges_to( ni_name, filters=[self.graph.is_link_edge] )[0] - - self.graph.set_node_obj(ni_name, NarrowWideAxiNI(**ni_dict)) + match self.network_type: + case "axi": + self.graph.set_node_obj(ni_name, AxiNI(**ni_dict)) + case "narrow-wide": + self.graph.set_node_obj(ni_name, NarrowWideAxiNI(**ni_dict)) def gen_routing_info(self): """Wrapper function to generate all the routing info for the network, @@ -635,20 +666,26 @@ def render_ports(self, pkg_name=""): def render_link_typedefs(self): """Render the protocol configuration structs.""" string = "" - narrow_in_prot = next((prot for prot in self.protocols - if prot.type == "narrow" and prot.direction == "input"), None) - narrow_out_prot = next((prot for prot in self.protocols - if prot.type == "narrow" and prot.direction == "output"), None) - wide_in_prot = next((prot for prot in self.protocols - if prot.type == "wide" and prot.direction == "input"), None) - wide_out_prot = next((prot for prot in self.protocols - if prot.type == "wide" and prot.direction == "output"), None) - string += AXI4.render_cfg("AxiCfgN", narrow_in_prot, narrow_out_prot) - string += AXI4.render_cfg("AxiCfgW", wide_in_prot, wide_out_prot) - - string += NarrowWideLink.render_typedefs( - narrow_in_prot.type_name(), wide_in_prot.type_name(), "AxiCfgN", "AxiCfgW" - ) + if self.network_type == "narrow-wide": + narrow_in_prot = next((prot for prot in self.protocols + if prot.type == "narrow" and prot.direction == "input"), None) + narrow_out_prot = next((prot for prot in self.protocols + if prot.type == "narrow" and prot.direction == "output"), None) + wide_in_prot = next((prot for prot in self.protocols + if prot.type == "wide" and prot.direction == "input"), None) + wide_out_prot = next((prot for prot in self.protocols + if prot.type == "wide" and prot.direction == "output"), None) + string += AXI4.render_cfg("AxiCfgN", narrow_in_prot, narrow_out_prot) + string += AXI4.render_cfg("AxiCfgW", wide_in_prot, wide_out_prot) + + string += NarrowWideLink.render_typedefs( + narrow_in_prot.type_name(), wide_in_prot.type_name(), "AxiCfgN", "AxiCfgW" + ) + else: + in_prot = next((prot for prot in self.protocols if prot.direction == "input"), None) + out_prot = next((prot for prot in self.protocols if prot.direction == "output"), None) + string += AXI4.render_cfg("AxiCfg", in_prot, out_prot) + string += AxiLink.render_typedefs(in_prot.type_name(), "AxiCfg") return string def render_prots(self): diff --git a/floogen/model/network_interface.py b/floogen/model/network_interface.py index 468561ed..60c2f1ea 100644 --- a/floogen/model/network_interface.py +++ b/floogen/model/network_interface.py @@ -13,7 +13,7 @@ from floogen.model.routing import Id, AddrRange, Routing, RouteMap from floogen.model.protocol import AXI4 -from floogen.model.link import NarrowWideLink +from floogen.model.link import NarrowWideLink, AxiLink from floogen.model.endpoint import EndpointDesc import floogen.templates @@ -47,6 +47,23 @@ def is_only_mgr(self) -> bool: return self.endpoint.is_mgr() and not self.endpoint.is_sbr() +class AxiNI(NetworkInterface): + """ Axi Network Interface class.""" + + with as_file( + files(floogen.templates).joinpath("floo_axi_chimney.sv.mako") + ) as _tpl_path: + tpl: ClassVar = Template(filename=str(_tpl_path)) + + mgr_port: Optional[AXI4] = None + sbr_port: Optional[AXI4] = None + mgr_link: AxiLink + sbr_link: AxiLink + + def render(self, **kwargs) -> str: + """Render the network interface.""" + return self.tpl.render(ni=self, **kwargs) + class NarrowWideAxiNI(NetworkInterface): """ " NarrowWideNI class to describe a narrow-wide network interface.""" diff --git a/floogen/model/protocol.py b/floogen/model/protocol.py index 2fe2262a..dab2332a 100644 --- a/floogen/model/protocol.py +++ b/floogen/model/protocol.py @@ -17,7 +17,7 @@ class ProtocolDesc(BaseModel): name: str description: Optional[str] = "" protocol: Annotated[str, StringConstraints(pattern=r"AXI4")] - type: Annotated[str, StringConstraints(pattern=r"narrow|wide")] + type: Optional[Annotated[str, StringConstraints(pattern=r"narrow|wide")]] = None direction: Optional[str] = None class AXI4(ProtocolDesc): diff --git a/floogen/model/router.py b/floogen/model/router.py index dab48fcb..72675eb0 100644 --- a/floogen/model/router.py +++ b/floogen/model/router.py @@ -73,6 +73,18 @@ def check_links(self): f"outgoing links but should have {self.degree}") return self +class AxiRouter(Router): + """Router class to describe a single-AXI router""" + + with as_file( + files(floogen.templates).joinpath("floo_axi_router.sv.mako") + ) as _tpl_path: + _tpl: ClassVar = Template(filename=str(_tpl_path)) + + def render(self): + """Declare the router in the generated code.""" + return self._tpl.render(router=self) + "\n" + class NarrowWideRouter(Router): """Router class to describe a narrow-wide router""" diff --git a/floogen/templates/floo_axi_chimney.sv.mako b/floogen/templates/floo_axi_chimney.sv.mako new file mode 100644 index 00000000..a6952e79 --- /dev/null +++ b/floogen/templates/floo_axi_chimney.sv.mako @@ -0,0 +1,58 @@ +<%! from floogen.utils import snake_to_camel, bool_to_sv %>\ +<% actual_xy_id = ni.id - ni.routing.id_offset if ni.routing.id_offset is not None else ni.id %>\ +<% in_prot = next((prot for prot in noc.protocols if prot.direction == "input"), None) %>\ +<% out_prot = next((prot for prot in noc.protocols if prot.direction == "output"), None) %>\ + +floo_axi_chimney #( + .AxiCfg(AxiCfg), + .ChimneyCfg(set_ports(ChimneyDefaultCfg, ${bool_to_sv(ni.sbr_port != None)}, ${bool_to_sv(ni.mgr_port != None)})), + .RouteCfg(RouteCfg), + .id_t(id_t), + .rob_idx_t(rob_idx_t), +% if ni.routing.route_algo.value == 'SourceRouting': + .route_t (route_t), + .dst_t (route_t), +% endif + .hdr_t (hdr_t), + .sam_rule_t(sam_rule_t), + .Sam(Sam), + .axi_in_req_t(${in_prot.type_name()}_req_t), + .axi_in_rsp_t(${in_prot.type_name()}_rsp_t), + .axi_out_req_t(${out_prot.type_name()}_req_t), + .axi_out_rsp_t(${out_prot.type_name()}_rsp_t), + .floo_req_t(floo_req_t), + .floo_rsp_t(floo_rsp_t) +) ${ni.name} ( + .clk_i, + .rst_ni, + .test_enable_i, + .sram_cfg_i ( '0 ), +% if ni.mgr_port is not None: + .axi_in_req_i ( ${ni.mgr_port.req_name(port=True, idx=True)} ), + .axi_in_rsp_o ( ${ni.mgr_port.rsp_name(port=True, idx=True)} ), +% else: + .axi_in_req_i ( '0 ), + .axi_in_rsp_o ( ), +% endif +% if ni.sbr_port is not None: + .axi_out_req_o ( ${ni.sbr_port.req_name(port=True, idx=True)} ), + .axi_out_rsp_i ( ${ni.sbr_port.rsp_name(port=True, idx=True)} ), +% else: + .axi_out_req_o ( ), + .axi_out_rsp_i ( '0 ), +% endif +% if ni.routing.route_algo.value == 'XYRouting': + .id_i ( ${actual_xy_id.render()} ), +% else: + .id_i ( id_t'(${ni.id.render()}) ), +% endif +% if ni.routing.route_algo.value == 'SourceRouting': + .route_table_i ( RoutingTables[${snake_to_camel(ni.name)}] ), +% else: + .route_table_i ( '0 ), +% endif + .floo_req_o ( ${ni.mgr_link.req_name()} ), + .floo_rsp_i ( ${ni.mgr_link.rsp_name()} ), + .floo_req_i ( ${ni.sbr_link.req_name()} ), + .floo_rsp_o ( ${ni.sbr_link.rsp_name()} ) +); diff --git a/floogen/templates/floo_axi_router.sv.mako b/floogen/templates/floo_axi_router.sv.mako new file mode 100644 index 00000000..4d46ef8e --- /dev/null +++ b/floogen/templates/floo_axi_router.sv.mako @@ -0,0 +1,80 @@ +<%! + from floogen.model.routing import XYDirections, RouteAlgo +%>\ +<% def camelcase(s): + return ''.join(x.capitalize() or '_' for x in s.split('_')) +%>\ +<% req_type = next(d for d in router.incoming if d is not None).req_type %>\ +<% rsp_type = next(d for d in router.incoming if d is not None).rsp_type %>\ +% if router.route_algo == RouteAlgo.ID: +${router.table.render()} +% endif + +${req_type} [${len(router.incoming)-1}:0] ${router.name}_req_in; +${rsp_type} [${len(router.incoming)-1}:0] ${router.name}_rsp_out; +${req_type} [${len(router.outgoing)-1}:0] ${router.name}_req_out; +${rsp_type} [${len(router.outgoing)-1}:0] ${router.name}_rsp_in; + +% for i, link in enumerate(router.incoming): + % if link is not None: + assign ${router.name}_req_in[${i}] = ${link.req_name()}; + % else: + assign ${router.name}_req_in[${i}] = '0; + % endif +% endfor + +% for i, link in enumerate(router.incoming): + % if link is not None: + assign ${link.rsp_name()} = ${router.name}_rsp_out[${i}]; + % endif +% endfor + +% for i, link in enumerate(router.outgoing): + % if link is not None: + assign ${link.req_name()} = ${router.name}_req_out[${i}]; + % endif +% endfor + +% for i, link in enumerate(router.outgoing): + % if link is not None: + assign ${router.name}_rsp_in[${i}] = ${link.rsp_name()}; + % else: + assign ${router.name}_rsp_in[${i}] = '0; + % endif +% endfor + +floo_axi_router #( + .AxiCfg(AxiCfg), + .RouteAlgo (${router.route_algo.value}), + .NumRoutes (${router.degree}), + .NumInputs (${len(router.incoming)}), + .NumOutputs (${len(router.outgoing)}), + .InFifoDepth (2), + .OutFifoDepth (2), + .id_t(id_t), + .hdr_t(hdr_t), +% if router.route_algo == RouteAlgo.ID: + .NumAddrRules (${len(router.table.rules)}), + .addr_rule_t (${router.name}_map_rule_t), +% endif + .floo_req_t(floo_req_t), + .floo_rsp_t(floo_rsp_t) +) ${router.name} ( + .clk_i, + .rst_ni, + .test_enable_i, +% if router.route_algo == RouteAlgo.XY: + .id_i (${router.id.render()}), +% else: + .id_i ('0), +% endif +% if router.route_algo == RouteAlgo.ID: + .id_route_map_i (${camelcase(router.name + "_map")}), +% else: + .id_route_map_i ('0), +% endif + .floo_req_i (${router.name}_req_in), + .floo_rsp_o (${router.name}_rsp_out), + .floo_req_o (${router.name}_req_out), + .floo_rsp_i (${router.name}_rsp_in) +); diff --git a/hw/floo_axi_router.sv b/hw/floo_axi_router.sv new file mode 100644 index 00000000..9ee0d3e9 --- /dev/null +++ b/hw/floo_axi_router.sv @@ -0,0 +1,153 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 +// +// Author: Tim Fischer + +`include "axi/typedef.svh" +`include "floo_noc/typedef.svh" + +/// Wrapper of a multi-link router for single-AXI links +module floo_axi_router #( + /// Config of the AXI interfaces (see floo_pkg::axi_cfg_t for details) + parameter floo_pkg::axi_cfg_t AxiCfg = '0, + /// Routing algorithm + parameter floo_pkg::route_algo_e RouteAlgo = floo_pkg::XYRouting, + /// Number of input/output ports + parameter int unsigned NumRoutes = 0, + /// Number of input ports + parameter int unsigned NumInputs = NumRoutes, + /// Number of output ports + parameter int unsigned NumOutputs = NumRoutes, + /// Input buffer depth + parameter int unsigned InFifoDepth = 0, + /// Output buffer depth + parameter int unsigned OutFifoDepth = 0, + /// Disable illegal connections in router + /// (only applies for `RouteAlgo == XYRouting`) + parameter bit XYRouteOpt = 1'b1, + /// Node ID type + parameter type id_t = logic, + /// Header type + parameter type hdr_t = logic, + /// Number of rules in the route table + /// (only used for `RouteAlgo == IdTable`) + parameter int unsigned NumAddrRules = 0, + /// Address rule type + /// (only used for `RouteAlgo == IdTable`) + parameter type addr_rule_t = logic, + /// Floo `req` link type + parameter type floo_req_t = logic, + /// Floo `rsp` link type + parameter type floo_rsp_t = logic +) ( + input logic clk_i, + input logic rst_ni, + input logic test_enable_i, + /// Coordinate of the current node + /// (only used for `RouteAlgo == XYRouting`) + input id_t id_i, + /// Routing table + /// (only used for `RouteAlgo == IdTable`) + input addr_rule_t [NumAddrRules-1:0] id_route_map_i, + /// Input and output links + input floo_req_t [NumInputs-1:0] floo_req_i, + input floo_rsp_t [NumOutputs-1:0] floo_rsp_i, + output floo_req_t [NumOutputs-1:0] floo_req_o, + output floo_rsp_t [NumInputs-1:0] floo_rsp_o +); + + typedef logic [AxiCfg.AddrWidth-1:0] axi_addr_t; + typedef logic [AxiCfg.InIdWidth-1:0] axi_in_id_t; + typedef logic [AxiCfg.UserWidth-1:0] axi_user_t; + typedef logic [AxiCfg.DataWidth-1:0] axi_data_t; + typedef logic [AxiCfg.DataWidth/8-1:0] axi_strb_t; + + // (Re-) definitons of `axi_in` and `floo` types, for transport + `AXI_TYPEDEF_ALL_CT(axi, axi_req_t, axi_rsp_t, axi_addr_t, axi_in_id_t, + axi_data_t, axi_strb_t, axi_user_t) + `FLOO_TYPEDEF_AXI_CHAN_ALL(axi, req, rsp, axi, AxiCfg, hdr_t) + + floo_req_chan_t [NumInputs-1:0] req_in; + floo_rsp_chan_t [NumInputs-1:0] rsp_out; + floo_req_chan_t [NumOutputs-1:0] req_out; + floo_rsp_chan_t [NumOutputs-1:0] rsp_in; + logic [NumInputs-1:0] req_valid_in, req_ready_out; + logic [NumInputs-1:0] rsp_valid_out, rsp_ready_in; + logic [NumOutputs-1:0] req_valid_out, req_ready_in; + logic [NumOutputs-1:0] rsp_valid_in, rsp_ready_out; + + for (genvar i = 0; i < NumInputs; i++) begin : gen_chimney_req + assign req_valid_in[i] = floo_req_i[i].valid; + assign floo_req_o[i].ready = req_ready_out[i]; + assign req_in[i] = floo_req_i[i].req; + assign floo_rsp_o[i].valid = rsp_valid_out[i]; + assign rsp_ready_in[i] = floo_rsp_i[i].ready; + assign floo_rsp_o[i].rsp = rsp_out[i]; + end + + for (genvar i = 0; i < NumOutputs; i++) begin : gen_chimney_rsp + assign floo_req_o[i].valid = req_valid_out[i]; + assign req_ready_in[i] = floo_req_i[i].ready; + assign floo_req_o[i].req = req_out[i]; + assign rsp_valid_in[i] = floo_rsp_i[i].valid; + assign floo_rsp_o[i].ready = rsp_ready_out[i]; + assign rsp_in[i] = floo_rsp_i[i].rsp; + end + + floo_router #( + .NumPhysChannels ( 1 ), + .NumVirtChannels ( 1 ), + .NumInput ( NumInputs ), + .NumOutput ( NumOutputs ), + .flit_t ( floo_req_generic_flit_t ), + .InFifoDepth ( InFifoDepth ), + .OutFifoDepth ( OutFifoDepth ), + .RouteAlgo ( RouteAlgo ), + .XYRouteOpt ( XYRouteOpt ), + .id_t ( id_t ), + .NumAddrRules ( NumAddrRules ), + .addr_rule_t ( addr_rule_t ) + ) i_req_floo_router ( + .clk_i, + .rst_ni, + .test_enable_i, + .xy_id_i(id_i), + .id_route_map_i, + .valid_i ( req_valid_in ), + .ready_o ( req_ready_out ), + .data_i ( req_in ), + .valid_o ( req_valid_out ), + .ready_i ( req_ready_in ), + .data_o ( req_out ) + ); + + + floo_router #( + .NumPhysChannels ( 1 ), + .NumVirtChannels ( 1 ), + .NumInput ( NumInputs ), + .NumOutput ( NumOutputs ), + .InFifoDepth ( InFifoDepth ), + .OutFifoDepth ( OutFifoDepth ), + .RouteAlgo ( RouteAlgo ), + .XYRouteOpt ( XYRouteOpt ), + .flit_t ( floo_rsp_generic_flit_t ), + .id_t ( id_t ), + .NumAddrRules ( NumAddrRules ), + .addr_rule_t ( addr_rule_t ) + ) i_rsp_floo_router ( + .clk_i, + .rst_ni, + .test_enable_i, + .xy_id_i(id_i), + .id_route_map_i, + .valid_i ( rsp_valid_in ), + .ready_o ( rsp_ready_out ), + .data_i ( rsp_in ), + .valid_o ( rsp_valid_out ), + .ready_i ( rsp_ready_in ), + .data_o ( rsp_out ) + ); + +endmodule