Skip to content

Commit 9e60b70

Browse files
committed
WIP CFFI Wrapper for routing table tools
- [x] CFFI wrapper for m-Trie - [x] CFFI wrapper for Ordered Covering - [x] Utilities for converting to/from C format - [ ] Scripts to replace desktop/* executables - [ ] Travis updates - [ ] Ignore coverage on cffi_compile.py - [ ] Utilities for writing/reading routing table files - [ ] CFFI wrapper for remove_default_routes when merged For a later PR - SpiNNaker interface for routing table minimisation
1 parent 2de92f8 commit 9e60b70

File tree

14 files changed

+494
-22
lines changed

14 files changed

+494
-22
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
*.pyc
2+
.coverage
3+
.cache/*
4+
build/*
5+
*.egg-info/*
16
tests/tests
27
*.o
38
*.gcov
@@ -7,3 +12,4 @@ tests/tests
712
*.aplx
813
*.elf
914
spinnaker/sark_build.c
15+
_rig_routing_tables.*

desktop/ordered_covering.c

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,6 @@ int entry_cmp(const void *a, const void *b)
4343
}
4444

4545

46-
void minimise(table_t *table, unsigned int target_length)
47-
{
48-
// Create an empty aliases table
49-
aliases_t aliases = aliases_init();
50-
51-
// Minimise
52-
oc_minimise(table, target_length, &aliases);
53-
54-
// Tidy up the aliases table
55-
for (unsigned int i = 0; i < table->size; i++)
56-
{
57-
keymask_t km = table->entries[i].keymask;
58-
if (aliases_contains(&aliases, km))
59-
{
60-
alias_list_delete((alias_list_t *) aliases_find(&aliases, km));
61-
aliases_remove(&aliases, km);
62-
}
63-
}
64-
}
65-
66-
6746
int main(int argc, char *argv[])
6847
{
6948
// Usage:
@@ -128,7 +107,7 @@ int main(int argc, char *argv[])
128107
qsort(table.entries, table.size, sizeof(entry_t), entry_cmp);
129108

130109
// Perform the minimisation
131-
minimise(&table, target_length);
110+
oc_minimise_na(&table, target_length);
132111

133112
printf("%u\n", table.size);
134113

include/ordered_covering.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,28 @@ static inline void oc_minimise(
522522
}
523523
}
524524

525+
// Apply the ordered covering algorithm to a routing table without a
526+
// pre-existing aliases table.
527+
static inline void oc_minimise_na(table_t *table, unsigned int target_length)
528+
{
529+
// Create an empty aliases table
530+
aliases_t aliases = aliases_init();
531+
532+
// Minimise
533+
oc_minimise(table, target_length, &aliases);
534+
535+
// Tidy up the aliases table
536+
for (unsigned int i = 0; i < table->size; i++)
537+
{
538+
keymask_t km = table->entries[i].keymask;
539+
if (aliases_contains(&aliases, km))
540+
{
541+
alias_list_delete((alias_list_t *) aliases_find(&aliases, km));
542+
aliases_remove(&aliases, km);
543+
}
544+
}
545+
}
546+
525547

526548
#define __ORDERED_COVERING_H__
527549
#endif // __ORDERED_COVERING_H__

include/routing_table.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,36 @@ typedef struct _table_t
5959
} table_t;
6060

6161

62+
// Table management methods (mostly here for the CFFI wrapper)
63+
static table_t *new_table(unsigned int size)
64+
{
65+
// Malloc space for the table and associated entries
66+
table_t *table = MALLOC(sizeof(table_t));
67+
table->size = 0;
68+
table->entries = NULL;
69+
70+
if (table != NULL)
71+
{
72+
table->entries = MALLOC(sizeof(entry_t) * size);
73+
74+
if (table->entries != NULL)
75+
{
76+
table->size = size;
77+
return table;
78+
}
79+
}
80+
81+
return NULL;
82+
}
83+
84+
static void free_table(table_t *table)
85+
{
86+
// Free the entries and then the table
87+
FREE(table->entries);
88+
table->entries = NULL;
89+
table->size = 0;
90+
FREE(table);
91+
}
92+
6293
#define __ROUTING_TABLE_H__
6394
#endif // __ROUTING_TABLE_H__

py_tests/test_mtrie.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import pytest
2+
from rig.routing_table import RoutingTableEntry as RTE
3+
from rig.routing_table import Routes, MinimisationFailedError
4+
from rig_routing_tables.mtrie import minimise
5+
6+
7+
def test_minimise_simple():
8+
"""Test minimisation of a simple routing table, we assume the C tests are
9+
extensive!
10+
"""
11+
# Minimise the table:
12+
# N -> 0000 -> S SW
13+
# NE -> 0001 -> S SW
14+
# ? -> 001X -> N
15+
table = [
16+
RTE({Routes.south, Routes.south_west}, 0x0, 0xf, {Routes.north}),
17+
RTE({Routes.south, Routes.south_west}, 0x1, 0xf, {Routes.north_east}),
18+
RTE({Routes.north}, 0x2, 0xe),
19+
]
20+
21+
# We expect the result
22+
# N NE -> 000X -> S SW
23+
# ? -> 001X -> N
24+
expected = [
25+
RTE({Routes.south, Routes.south_west}, 0x0, 0xe,
26+
{Routes.north, Routes.north_east}),
27+
RTE({Routes.north}, 0x2, 0xe),
28+
]
29+
30+
# Try the minimisation
31+
assert minimise(table, None) == expected
32+
33+
34+
def test_minimise_fails_if_oversize():
35+
"""Test minimisation of a simple routing table, we assume the C tests are
36+
extensive!
37+
"""
38+
# Minimise the table:
39+
# N -> 0000 -> S SW
40+
# NE -> 0001 -> S SW
41+
# ? -> 001X -> N
42+
table = [
43+
RTE({Routes.south, Routes.south_west}, 0x0, 0xf, {Routes.north}),
44+
RTE({Routes.south, Routes.south_west}, 0x1, 0xf, {Routes.north_east}),
45+
RTE({Routes.north}, 0x2, 0xe),
46+
]
47+
48+
# We expect the result to be:
49+
# N NE -> 000X -> S SW
50+
# ? -> 001X -> N
51+
# Which is, clearly, more than 1 entry so an error should be raised
52+
53+
with pytest.raises(MinimisationFailedError) as exc:
54+
minimise(table, 1)
55+
56+
assert exc.value.target_length == 1
57+
assert exc.value.final_length == 2
58+
assert exc.value.chip is None

py_tests/test_ordered_covering.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import pytest
2+
from rig.routing_table import RoutingTableEntry as RTE
3+
from rig.routing_table import Routes, MinimisationFailedError
4+
from rig_routing_tables.ordered_covering import minimise
5+
6+
7+
@pytest.mark.parametrize("target_length, no_raise",
8+
((None, False), (0, True)))
9+
def test_ordered_covering_simple(target_length, no_raise):
10+
"""Test that a very simple routing table can be minimised, only one
11+
merge should be made as there are no conflicts.
12+
13+
Table::
14+
15+
0000 -> N, NE
16+
0001 -> N, NE
17+
001X -> S
18+
19+
Can be minimised to::
20+
21+
000X -> N, NE
22+
001X -> S
23+
"""
24+
# Original table
25+
table = [
26+
RTE({Routes.north, Routes.north_east}, 0b0000, 0b1111),
27+
RTE({Routes.north, Routes.north_east}, 0b0001, 0b1111),
28+
RTE({Routes.south}, 0b0010, 0b1110),
29+
]
30+
31+
# Expected table
32+
expected_table = [
33+
RTE({Routes.north, Routes.north_east}, 0b0000, 0b1110),
34+
RTE({Routes.south}, 0b0010, 0b1110),
35+
]
36+
37+
# Minimise and check the result
38+
new_table = minimise(table, target_length, no_raise)
39+
40+
assert new_table == expected_table
41+
42+
43+
def test_ordered_covering_simple_fails_if_too_large():
44+
"""Test that a very simple routing table can be minimised, and that an
45+
exception is raised if that minimisation is still too large.
46+
47+
Table::
48+
49+
0000 -> N, NE
50+
0001 -> N, NE
51+
001X -> S
52+
"""
53+
# Original table
54+
table = [
55+
RTE({Routes.north, Routes.north_east}, 0b0000, 0b1111),
56+
RTE({Routes.north, Routes.north_east}, 0b0001, 0b1111),
57+
RTE({Routes.south}, 0b0010, 0b1110),
58+
]
59+
60+
with pytest.raises(MinimisationFailedError) as exc:
61+
minimise(table, target_length=1)
62+
63+
assert exc.value.target_length == 1
64+
assert exc.value.final_length == 2
65+
assert exc.value.chip is None

py_tests/test_utils.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from rig.routing_table import RoutingTableEntry as RTE
2+
from rig.routing_table import Routes
3+
from rig_routing_tables import utils
4+
5+
6+
def test_table_to_cffi_and_back():
7+
"""Test conversion of a Rig routing table to the CFFI format and conversion
8+
back again.
9+
"""
10+
# Create the initial table
11+
table = [RTE({r}, i, 0xffffffff) for i, r in enumerate(Routes)]
12+
table += [
13+
RTE({Routes.north, Routes.south}, 0x80000000, 0xffffffff,
14+
{Routes.east, Routes.west}),
15+
]
16+
17+
# Convert the table to C format
18+
c_table = utils.rig_to_c_table(table)
19+
20+
# Check for equivalence
21+
assert c_table.size == len(table)
22+
for i, entry in enumerate(table):
23+
c_entry = c_table.entries[i]
24+
25+
assert c_entry.keymask.key == entry.key
26+
assert c_entry.keymask.mask == entry.mask
27+
28+
# Check the route is equivalent
29+
route = 0x0
30+
for r in entry.route:
31+
route |= 1 << r
32+
33+
assert c_entry.route == route
34+
35+
# Check the source is equivalent
36+
source = 0x0
37+
for r in entry.sources:
38+
source |= 1 << (r if r is not None else 31)
39+
40+
assert c_entry.source == source
41+
42+
# Convert back again
43+
returned_table = utils.c_to_rig_table(c_table)
44+
assert table == returned_table

rig_routing_tables/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Faster C implementation of common routing table minimisers."""
2+
from rig_routing_tables.version import __version__
3+
4+
from rig_routing_tables._rig_routing_tables import ffi, lib
5+
from rig_routing_tables import utils

rig_routing_tables/cffi_compile.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import os
2+
from cffi import FFI
3+
ffi = FFI()
4+
5+
6+
# Get the include directory
7+
include_dir = os.path.join(os.path.dirname(__file__), "..", "include")
8+
9+
ffi.set_source(
10+
"_rig_routing_tables",
11+
"""
12+
#include "mtrie.h"
13+
#include "routing_table.h"
14+
#include "ordered_covering.h"
15+
""",
16+
include_dirs=[include_dir]
17+
)
18+
19+
ffi.cdef("""
20+
// Data structures
21+
typedef struct _keymask_t
22+
{
23+
uint32_t key; // Key for the keymask
24+
uint32_t mask; // Mask for the keymask
25+
...;
26+
} keymask_t;
27+
28+
typedef struct _entry_t
29+
{
30+
keymask_t keymask; // Key and mask
31+
uint32_t route; // Routing direction
32+
uint32_t source; // Source of packets arriving at this entry
33+
...;
34+
} entry_t;
35+
36+
typedef struct _table_t
37+
{
38+
unsigned int size; // Number of entries in the table
39+
entry_t *entries; // Entries in the table
40+
...;
41+
} table_t;
42+
43+
// Table management
44+
table_t* new_table(unsigned int size);
45+
void free_table(table_t *table);
46+
47+
// Minimisation methods
48+
void mtrie_minimise(table_t *table);
49+
void oc_minimise_na(table_t *table, unsigned int target_length);
50+
""")
51+
52+
if __name__ == "__main__":
53+
ffi.compile()

rig_routing_tables/mtrie.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from rig.routing_table import MinimisationFailedError
2+
from rig_routing_tables.utils import rig_to_c_table, c_to_rig_table
3+
from rig_routing_tables._rig_routing_tables import lib
4+
5+
6+
def minimise(table, target_length):
7+
"""Minimise a Rig routing table using the m-Trie method of Ahmad and
8+
Mahapatra [1]_.
9+
10+
Parameters
11+
----------
12+
routing_table : [:py:class:`~rig.routing_table.RoutingTableEntry`, ...]
13+
Routing table from which to remove entries which could be handled by
14+
default routing.
15+
target_length : int or None
16+
Target length of the routing table.
17+
18+
Raises
19+
------
20+
MinimisationFailedError
21+
If the smallest table that can be produced is larger than
22+
`target_length`.
23+
24+
Returns
25+
-------
26+
[:py:class:`~rig.routing_table.RoutingTableEntry`, ...]
27+
Reduced routing table entries.
28+
29+
[1]_ Ahmad, S. and Mahapatra, R.N., 2007. An efficient approach to on-chip
30+
logic minimization. *Very Large Scale Integration (VLSI) Systems, IEEE
31+
Transactions on*, 15(9), pp.1040-1050.
32+
"""
33+
# Convert the table to C format and minimise
34+
c_table = rig_to_c_table(table)
35+
lib.mtrie_minimise(c_table)
36+
37+
# If the table is larger than the target length then raise an exception
38+
if target_length is not None and c_table.size > target_length:
39+
raise MinimisationFailedError(target_length, c_table.size)
40+
41+
# Otherwise convert back to Rig form and return
42+
return c_to_rig_table(c_table)

0 commit comments

Comments
 (0)