aboutsummaryrefslogtreecommitdiff
path: root/src/bfly/fat_bfly.c
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2025-03-04 20:08:04 +0200
committerKimplul <kimi.h.kuparinen@gmail.com>2025-03-04 20:08:04 +0200
commitf03c2027c3ed7f385842b8583bb1953e04dd9386 (patch)
tree5d5d42e0922fcad989361745778a00d529ff2e48 /src/bfly/fat_bfly.c
parent0464b7765aa4d1fc2178e443a3b37d19cfe541a0 (diff)
downloadgran-f03c2027c3ed7f385842b8583bb1953e04dd9386.tar.gz
gran-f03c2027c3ed7f385842b8583bb1953e04dd9386.zip
add fat butterfly
Diffstat (limited to 'src/bfly/fat_bfly.c')
-rw-r--r--src/bfly/fat_bfly.c271
1 files changed, 271 insertions, 0 deletions
diff --git a/src/bfly/fat_bfly.c b/src/bfly/fat_bfly.c
new file mode 100644
index 0000000..39ad657
--- /dev/null
+++ b/src/bfly/fat_bfly.c
@@ -0,0 +1,271 @@
+#include <gran/utils.h>
+#include <gran/bfly/fat_bfly.h>
+
+/*
+ * Essentially, we build up a binary tree of nodes, where each node has two
+ * input 'banks' of width 2^height, and an output 'bank' twice that. The highest
+ * node has an 'output bank' the same width as the number of nodes in the
+ * network, and data flows upwards in this tree such that packets automatically
+ * end up in the correct order in the final output bank. From there, we can just
+ * directly write the output registers to child nodes. Something like the
+ * following:
+ *
+ * ||||
+ * 0123 - height 2, 2^2 nodes with 2*2^1 input registers
+ * // \\
+ * 01 23 - height 1, 2^1 nodes with 2*2^0 input registers
+ * / \ / \
+ * 0 1 2 3 - height 0, input layer
+ *
+ * The cool thing with this approach is that each output register has to look at just
+ * two input registers, since the input is always (partially) sorted.
+ *
+ * The implementation is a bit nontrivial, unfortunately. Another possibility
+ * could be to initialize a plain 2D array with registers and do some index
+ * calculations to directly associate registers with eachother (our nodes are
+ * ultimately just wrappers around some registers, along with some selection
+ * logic) that I tried to go for, but there are some subtleties in how data
+ * flows in the tree that caused my other attempt to deadlock frequently.
+ * Shouldn't be impossible to fix, but there was even more weird bit math than
+ * in this version so I'm sticking with this for now at least.
+ */
+
+struct reg {
+ struct packet pkt;
+ bool busy;
+};
+
+struct fat_node {
+ /* parent in tree */
+ struct fat_node *up;
+
+ /* arrays, length is calculated based on the node height */
+ struct reg *left, *right;
+ struct reg *out;
+};
+
+struct fat_bfly {
+ struct component component;
+
+ uint32_t elems;
+ struct reg *in; /* elems */
+ struct component **send; /* elems */
+
+ size_t layers;
+ /* noe that in this case output is layer 0 and input layers - 1 */
+ struct fat_node **layer;
+};
+
+/* left selects which input bank to use and idx which index within it */
+static stat node_receive(struct fat_node *node, struct packet pkt, size_t idx, bool left)
+{
+ struct reg *rs = left ? node->left : node->right;
+ if (rs[idx].busy)
+ return EBUSY;
+
+ rs[idx].pkt = pkt;
+ rs[idx].busy = true;
+ return OK;
+}
+
+static stat node_clock(struct fat_node *node, size_t count, size_t height)
+{
+ /* can't go higher than the top layer */
+ if (node->up)
+ for (size_t i = 0; i < count; ++i) {
+ if (!node->out[i].busy)
+ continue;
+
+ struct packet *pkt = &node->out[i].pkt;
+
+ uint32_t elem;
+ addr_fat_bfly(pkt->to, &elem, NULL);
+
+ /* here's a pretty tricky spot, note how we select the input
+ * bank based on the address of our target. This increases
+ * hardware complexity, but avoids at least one possible
+ * deadlock situation compared to just having a fixed right/left
+ * selector. */
+ stat ret = node_receive(node->up, *pkt, i, (elem >> height) & 1);
+ if (ret == EBUSY)
+ continue;
+
+ node->out[i].busy = false;
+ }
+
+ /* populate output registers */
+ for (size_t i = 0; i < count; ++i) {
+ /* waiting on something upstream */
+ if (node->out[i].busy)
+ continue;
+
+ /* quick modulo, we know we're dealing with powers of 2 */
+ /* there are twice as few input registers per bank, when
+ * iterating over all output registers we want to loop back to
+ * the start of the input when going over the halway mark */
+ struct reg *l = &node->left[i & (count/2 - 1)];
+ struct reg *r = &node->right[i & (count/2 - 1)];
+
+ uint32_t el;
+ uint32_t er;
+ addr_fat_bfly(l->pkt.to, &el, NULL);
+ addr_fat_bfly(r->pkt.to, &er, NULL);
+
+ /* packet 'wants' to go right
+ * (wl = left packet wants to go right, could be better) */
+ bool wl = (el >> height) & 1;
+ bool wr = (er >> height) & 1;
+
+ /* we accept a packet if the input register is busy and the
+ * packet wants to go in our direction */
+ bool al = l->busy && (i < count / 2 ? !wl : wl);
+ bool ar = r->busy && (i < count / 2 ? !wr : wr);
+
+ /* can't accept either packet */
+ if (!al && !ar)
+ continue;
+
+ /* select packet */
+ struct reg *s = al ? l : r;
+ if (al && ar)
+ s = l->pkt.timestamp < r->pkt.timestamp ? l : r;
+
+ node->out[i].pkt = s->pkt;
+ node->out[i].busy = true;
+ s->busy = false;
+ }
+
+ return OK;
+}
+
+static stat layer_clock(struct fat_node *layer, size_t count, size_t height)
+{
+ for (size_t i = 0; i < count; ++i)
+ node_clock(&layer[i], 2ULL << height, height);
+
+ return OK;
+}
+
+static stat fat_bfly_clock(struct fat_bfly *bfly)
+{
+ struct reg *out = bfly->layer[0]->out;
+ for (size_t i = 0; i < bfly->elems; ++i) {
+ if (!out[i].busy)
+ continue;
+
+ uint32_t elem;
+ addr_fat_bfly(out[i].pkt.to, &elem, NULL);
+ assert(elem == i);
+
+ stat ret = SEND(bfly, bfly->send[i], out[i].pkt);
+ if (ret == EBUSY)
+ continue;
+
+ out[i].busy = false;
+ }
+
+ for (size_t i = 0; i < bfly->layers; ++i)
+ layer_clock(bfly->layer[i], 1ULL << i, bfly->layers - i - 1);
+
+ for (size_t i = 0; i < bfly->elems; ++i) {
+ if (!bfly->in[i].busy)
+ continue;
+
+ struct fat_node *node = &bfly->layer[bfly->layers - 1][i / 2];
+ stat ret = node_receive(node, bfly->in[i].pkt, 0, i % 2);
+ if (ret == EBUSY)
+ continue;
+
+ bfly->in[i].busy = false;
+ }
+
+ return OK;
+}
+
+static stat fat_bfly_receive(struct fat_bfly *bfly, struct component *from, struct packet pkt)
+{
+ uint32_t elem;
+ addr_fat_bfly(pkt.from, &elem, NULL);
+ assert(elem < bfly->elems);
+ assert(bfly->send[elem] == from);
+
+ if (bfly->in[elem].busy)
+ return EBUSY;
+
+ bfly->in[elem].pkt = pkt;
+ bfly->in[elem].busy = true;
+ return OK;
+}
+
+static void fat_bfly_destroy(struct fat_bfly *bfly)
+{
+ for (size_t i = 0; i < bfly->layers; ++i) {
+ size_t count = 1ULL << i;
+ for (size_t j = 0; j < count; ++j) {
+ free(bfly->layer[i][j].out);
+ free(bfly->layer[i][j].left);
+ free(bfly->layer[i][j].right);
+ }
+
+ free(bfly->layer[i]);
+ }
+
+ free(bfly->in);
+ free(bfly->send);
+ free(bfly->layer);
+ free(bfly);
+}
+
+struct component *create_fat_bfly(uint32_t elems)
+{
+ /* initialization of deeply nested pointer hierarchies is a bit of a
+ * pain, so just assert that allocations succeed for now. Might look
+ * into more graceful cleanup in the future. */
+ assert(is_powerof2(elems));
+ struct fat_bfly *bfly = calloc(1, sizeof(struct fat_bfly));
+ assert(bfly);
+
+ bfly->elems = elems;
+ bfly->send = calloc(elems, sizeof(struct component *));
+ assert(bfly->send);
+
+ bfly->in = calloc(elems, sizeof(struct reg));
+ assert(bfly->in);
+
+ unsigned long long layers = log2ull(elems);
+
+ bfly->layers = layers;
+ bfly->layer = calloc(layers, sizeof(bfly->layers));
+ assert(bfly->layers);
+
+ for (size_t i = 0; i < layers; ++i) {
+ size_t count = 1ULL << i;
+ bfly->layer[i] = calloc(count, sizeof(struct fat_node));
+ assert(bfly->layer[i]);
+
+ for (size_t j = 0; j < count; ++j) {
+ /* j / 2 since there are always twice as few nodes per layer */
+ bfly->layer[i][j].up = i == 0 ? NULL : &bfly->layer[i - 1][j / 2];
+ bfly->layer[i][j].left = calloc(elems / count, sizeof(struct reg));
+ bfly->layer[i][j].right = calloc(elems / count, sizeof(struct reg));
+ bfly->layer[i][j].out = calloc(2 * elems / count, sizeof(struct reg));
+ assert(bfly->layer[i][j].left);
+ assert(bfly->layer[i][j].right);
+ assert(bfly->layer[i][j].out);
+ }
+ }
+
+ bfly->component.clock = (clock_callback)fat_bfly_clock;
+ bfly->component.receive = (receive_callback)fat_bfly_receive;
+ bfly->component.destroy = (destroy_callback)fat_bfly_destroy;
+ return (struct component *)bfly;
+}
+
+stat fat_bfly_connect(struct component *bfly, struct component *component, uint32_t elem)
+{
+ struct fat_bfly *b = (struct fat_bfly *)bfly;
+ assert(elem < b->elems);
+ assert(b->send[elem] == NULL);
+ b->send[elem] = component;
+ return OK;
+}