Vai al contenuto

3.3 Parameters and configuration

PRISM HDL customization is intentionally parameter-driven. The architecture separates immutable wire-level constants, elaboration-time build defaults, and runtime CONTROL-programmed operation state. This split lets hardware teams tune resource/throughput tradeoffs while preserving protocol correctness and a stable software-visible contract.

Protocol constants

These constants define the wire ABI, so they are protocol semantics, not free deployment knobs.

// repo/prism-hdl/src/prism_pkg.sv (conceptual view)
localparam int unsigned PRISM_PROTOCOL_VERSION_MAJOR = 1;      // Protocol major version
localparam int unsigned PRISM_PROTOCOL_VERSION_MINOR = 0;      // Protocol minor version
localparam logic [15:0] PRISM_ETHERTYPE            = 16'h88B5; // L2 frame discriminator
localparam int unsigned PRISM_ETH_PAYLOAD_MTU_B    = 1500;     // Max Ethernet payload bytes

localparam int unsigned PRISM_CONTROL_WORD_BYTES   = 2;        // CONTROL command word length
localparam int unsigned PRISM_CLASS_HDR_BYTES      = 24;       // CONTROL/DATA/RES fixed header bytes
localparam int unsigned PRISM_PROGRESS_HDR_BYTES   = 40;       // PROGRESS fixed header bytes
localparam int unsigned PRISM_MAX_NUM_PAYLOAD_B    = 1474;     // Max DATA/RES numeric payload bytes
localparam int unsigned PRISM_CHUNK_INDEX_W        = 24;       // Chunk index width (bits)

Changing one of these values is a protocol change and requires matching updates to the CCL, RTL, tests, PCAP parser, and Wireshark dissector.

Platform and deployment capacity

prism_build_config_pkg.sv is the default build profile. These parameters set physical stream boundaries and buffering.

// prism_build_config_pkg.sv (default platform envelope)
parameter int unsigned PRISM_DEFAULT_DATA_W             = 512; // AXI stream data width in bits
parameter int unsigned PRISM_DEFAULT_NUM_PORTS          = 2;   // Independent full-duplex RX/TX pairs
parameter int unsigned PRISM_DEFAULT_USER_W             = 1;   // AXI tuser width

parameter int unsigned PRISM_DEFAULT_INGRESS_FIFO_DEPTH = 64;  // Ingress FIFO depth in beats per port
parameter int unsigned PRISM_DEFAULT_EGRESS_FIFO_DEPTH  = 64;  // Egress FIFO depth in beats per port
parameter int unsigned PRISM_DEFAULT_WORK_FIFO_DEPTH    = 32;  // Depacketized work entries per port

These values are chosen at elaboration. They are not negotiated by a CONTROL packet and changing them produces a different bitstream.

Compiled protocol and arithmetic support

Arithmetic capacity controls the work accepted in parallel and the single Kulisch accumulator implementation:

// Arithmetic parallelism and accumulator width
parameter int unsigned PRISM_DEFAULT_CONTRIBUTORS        = 2;   // Local contributors represented in RTL
parameter int unsigned TILE_ELEMENTS                      = 16;  // Logical elements per tile contract

parameter int unsigned ISSUE_SLICES                      = 4;   // Tiles issued per cycle per shared bank
parameter int unsigned KULISCH_ACC_W                     = 279; // State bits per Kulisch element

The build package provides the same values with the PRISM_DEFAULT_ prefix. The CONTROL capability response advertises compiled semantics, operators, formats, profiles, and the configured state width. A registry value does not imply that every bitstream implements it.

Why sixteen elements per tile?

Sixteen FP32 elements occupy exactly one 512-bit beat. Sixteen is also a power of two, keeps masks and range indexes compact, and lets narrower formats scale by replicating a stable tile contract. It is a clean architectural baseline, not a claim that sixteen is universally optimal after place and route.

Concurrent operations and storage resources

The remaining defaults bound persistent tables and in-flight work:

// Stateful resource ceilings (independent capacities)
parameter int unsigned PRISM_DEFAULT_SESSIONS            = 4;    // Session table slots
parameter int unsigned PRISM_DEFAULT_GROUPS              = 8;    // Group table slots
parameter int unsigned PRISM_DEFAULT_OPERATION_CONTEXTS  = 8;    // Operation context slots
parameter int unsigned PRISM_DEFAULT_ACCUMULATOR_RANGES  = 32;   // Total active ranges, partitioned over banks and slices

parameter int unsigned PRISM_DEFAULT_CONTROL_CONTEXTS    = 2;    // Fragmented CONTROL reassembly contexts
parameter int unsigned PRISM_DEFAULT_CONTROL_BYTES       = 8192; // CONTROL reassembly byte budget

parameter int unsigned PRISM_DEFAULT_RESULT_SLOTS        = 64;   // Immutable result chunk slots
parameter int unsigned PRISM_DEFAULT_RESULT_ROUTE_RANGES = 2;    // Route ranges tracked per operation
parameter int unsigned PRISM_DEFAULT_REPLICATION_GROUPS  = 2;    // Core-profile replication records

These capacities are independent: increasing operation contexts does not automatically increase range contexts, result slots, or CONTROL storage. The range budget is divided across the NUM_PORTS issue banks and their ISSUE_SLICES; each physical slice receives its partition, rounded up with a minimum depth of two. With the default two-port profile this is eight contexts per slice and 64 contexts in total; the one-port U50 profile assigns sixteen contexts to each of its four slices. FP32 and Kulisch operations share these issue banks and are distinguished by the installed operation metadata.

The OpenNIC build profile deliberately overrides this generic default with 32 total ranges. It therefore assigns four ranges to each U280 issue slice and eight ranges to each U50 issue slice, without changing either board's four issue slices or port count.

Throughput sizing

For datatype \(d\), let \(b_d\) be its wire bits per element, \(E\) the tile size, \(S\) the issue slices per shared bank, \(B\) the issue-bank count, \(D\) the AXI width, \(f\) the clock, and \(II\) the initiation interval. The active lanes per port and payload rates are:

\[ A_d=\min\left(E S,\frac{D}{b_d}\right), \qquad T_{port,d}=\frac{A_db_df}{II}, \qquad T_{aggregate,d}=B T_{port,d}, \qquad A_{min}(d)=\left\lceil\frac{T_{goal}II}{b_df}\right\rceil. \]

For \(D=512\), \(f=250\,\mathrm{MHz}\), \(II=1\), and \(T_{goal}=100\,\mathrm{Gbit/s}\):

// Derived lane utilization at D=512, f=250MHz, II=1, T_goal=100Gbit/s
// Shared bank: S=4 -> up to 64 physical lanes, full-beat for INT8/FP16/BF16/FP32, 128 Gbit/s payload

Every arithmetic profile uses the same banks and the same configured issue width. An FP32 beat activates one of the four slices, while narrower formats can use more slices to cover the same 512-bit beat. The core instantiates NUM_PORTS shared issue banks and stripes consecutive groups across them. For two ports this provides 256 Gbit/s aggregate input capacity while completed results can drain at one 512-bit group per cycle.

For \(C\) simultaneous contributors, an aligned binary reduction requires:

\[ E_{input}=CL_d, \qquad A_{binary}=(C-1)L_d. \]

Kulisch instead accepts \(CL_d\) deposits. Both paths output \(L_d\) reduced elements. A wider output type can then become the bottleneck:

\[ P_{e2e} \le \min\left(P_{in},\left\lfloor\frac{D}{b_o}\right\rfloor\right). \]

For example, 64 INT8 inputs converted to FP32 need 2048 output bits per cycle. One 512-bit egress must serialize or backpressure them. The rates above describe numeric payload, not Ethernet overhead or timing closure.

Logical tiles and physical issue width

The RTL now separates three concepts. A tile always represents sixteen consecutive elements. An issue slice is one physical tile execution path. An issue bank owns a set of slices and independent range state. The depacketizer groups one FP32 tile, two FP16/BF16 tiles, or four INT8 tiles from a full payload beat. prism_issue_backend executes the active slices in parallel and rejoins them before result conversion and storage. The core stripes groups over issue banks so independent ingress ports are not serialized.

Kulisch width and cost

The accumulator is configurable from 8 to 320 bits and defaults to 279. Its required width is checked against:

\[ W=E_{max}-E_{min}+p+1+\lceil\log_2(\text{maximum terms})\rceil. \]

For FP32, the base width is 278 bits. The default design accepts two local contributors, so 279 bits is sufficient for exact accumulation. The advertised Kulisch capability therefore reports two maximum reduction terms. A build that supports a larger reduction must increase KULISCH_ACC_W using the formula above. A width of 310 bits covers the complete 32 bit protocol term field.

If the configured width is insufficient for an operation, the result carries ACCUMULATION_INEXACT; saturating arithmetic prevents wrap.

Its approximate state cost is:

\[ S_{Kulisch} \approx B R_b L_K W_K. \]

Here \(B=N S\) is the physical issue-slice count, \(R_b\) the range contexts per slice, \(L_K=\texttt{TILE_ELEMENTS}\), and \(W_K\) the stored width. Width and issue parallelism remain independent build parameters. Every Kulisch operation in a bitstream shares that representation.

Runtime configuration

CONTROL commands install or select operation state within compiled limits.

// Runtime-programmed (CONTROL) state, not elaboration-time parameters:
// - session ownership/cookie/lease
// - node role and group lifecycle
// - contributor ID, ingress port, source MAC, endpoint/child binding
// - result route ranges and replication destinations
// - collective semantic (REDUCE/ALLREDUCE) and operator (SUM in current RTL)
// - input/accumulator/partial/output format tuple
// - element count, max reduction terms, window/replay controls, timeout/retry
// - operation generation and prepared/active/complete lifecycle

The first RTL integration uses one SINGLE node and two endpoint contributors. Roles and reliability fields present in the protocol do not establish a multi-switch or replay datapath in this implementation.

Elaboration checks and verification

Modules use $fatal guards for illegal combinations such as non-byte AXI widths, zero capacities, a parser width smaller than the fixed prefix, invalid port indices, or an invalid accumulator width. The parameter suite elaborates the full top with 1, 2, and 4 ports and varied FIFO/context/lane/Kulisch settings. These checks prove that selected configurations elaborate; synthesis resource use and timing must still be measured for each deployment profile.