Skip to content
Merged
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
8 changes: 4 additions & 4 deletions scripts/static_analysis_baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
"counts": {}
},
"misra": {
"total": 2784,
"misra_total": 2757,
"total": 2785,
"misra_total": 2758,
"config_errors": 2,
"fingerprint_sha256": "9995520e51868a9c3ea1112fde1f764b6105704edff5a7c60cd3c1968137fbe5",
"fingerprint_sha256": "fcd02d4bbf71136f9927f3a7ecae40797199c42be79397a6b359a593f9ad1d6d",
"counts": {
"include/tigris.h": {
"misra-c2012-10.4": 4,
Expand Down Expand Up @@ -108,7 +108,7 @@
"misra-c2012-12.2": 5,
"misra-c2012-12.3": 5,
"misra-c2012-14.4": 19,
"misra-c2012-15.5": 65,
"misra-c2012-15.5": 66,
"misra-c2012-15.6": 76,
"misra-c2012-17.3": 1,
"misra-c2012-17.7": 4,
Expand Down
26 changes: 26 additions & 0 deletions src/tigris_kernels_cmsis_nn.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ static int adapt_conv2d(
pad_top = mem->tile.pad_top;
}

/* Line-buffer roll: emit the tile.out_h new rows starting at output row
* out_row_start, and fold (out_row_start*stride - in_row_start) into the
* input pointer - zero when in_row_start == out_row_start*stride (the common
* line-buffer case, where the roll is a pure output offset). Mirrors the
* oh_g / ih index math in kern_conv2d_s8. */
if (mem->tile.active &&
(mem->tile.out_row_start != 0 || mem->tile.in_row_start != 0)) {
int delta = mem->tile.out_row_start * op->spatial.stride_h
- mem->tile.in_row_start;
X += delta * IW * IC;
Y += mem->tile.out_row_start * OW * OC;
IH -= delta;
}

cmsis_nn_conv_params conv_params = {
.input_offset = in_qp ? -in_qp->zero_point : 0,
.output_offset = out_qp ? out_qp->zero_point : 0,
Expand Down Expand Up @@ -223,6 +237,18 @@ static int adapt_depthwise_conv2d(
pad_top = mem->tile.pad_top;
}

/* Line-buffer roll folds out_row_start into the output pointer and
* (out_row_start*stride - in_row_start) into the input pointer, as in
* adapt_conv2d. Runs after the tile-context block so IH is the tile height. */
if (mem->tile.active &&
(mem->tile.out_row_start != 0 || mem->tile.in_row_start != 0)) {
int delta = mem->tile.out_row_start * op->spatial.stride_h
- mem->tile.in_row_start;
X += delta * IW * C;
Y += mem->tile.out_row_start * OW * C;
IH -= delta;
}

/* Weight layout: [KH, KW, C] (HWC) from compiler.
* CMSIS-NN expects [1, KH, KW, C] via filter_dims.n=1 - same data. */

Expand Down
25 changes: 25 additions & 0 deletions src/tigris_kernels_esp_nn.c
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,19 @@ static int adapt_conv2d(
pb = mem->tile.pad_bottom;
}

/* Line-buffer roll: fold out_row_start into the output pointer and
* (out_row_start*stride - in_row_start) into the input pointer, matching
* kern_conv2d_s8 and the CMSIS-NN adapter. Rolled tiles are interior
* (pt==pb==0), so the asymmetric-pad path below is not taken. */
if (mem->tile.active &&
(mem->tile.out_row_start != 0 || mem->tile.in_row_start != 0)) {
int delta = mem->tile.out_row_start * op->spatial.stride_h
- mem->tile.in_row_start;
X += delta * IW * IC;
Y += mem->tile.out_row_start * OW * OC;
IH -= delta;
}

int32_t in_offset = in_qp ? -in_qp->zero_point : 0;

/* Handle asymmetric padding */
Expand Down Expand Up @@ -532,6 +545,18 @@ static int adapt_depthwise_conv2d(
OH = mem->tile.out_h;
}

/* Line-buffer roll: fold out_row_start into the output pointer and
* (out_row_start*stride - in_row_start) into the input pointer, matching
* kern_conv2d_s8 and the CMSIS-NN adapter. */
if (mem->tile.active &&
(mem->tile.out_row_start != 0 || mem->tile.in_row_start != 0)) {
int delta = mem->tile.out_row_start * op->spatial.stride_h
- mem->tile.in_row_start;
X += delta * IW * C;
Y += mem->tile.out_row_start * OW * C;
IH -= delta;
}

data_dims_t input_dims = { .width = IW, .height = IH, .channels = C, .extra = 1 };
data_dims_t filter_dims = { .width = KW, .height = KH, .channels = 0, .extra = 0 };
data_dims_t output_dims = { .width = OW, .height = OH, .channels = C, .extra = 1 };
Expand Down
29 changes: 18 additions & 11 deletions src/tigris_kernels_s8.c
Original file line number Diff line number Diff line change
Expand Up @@ -1481,19 +1481,26 @@ int tigris_accel_try_s8_ref(
if (!plan || !op || !mem || !handled)
return -1;

/* 2D tiles carry mem->tile.width_tiled and partition width via
* pad_left/tile-in_w as well as height (see exec_stage_tiled_2d). No vendor
* adapter honors the packed-width contract yet, so a 2D-tiled op routes to
* s8_ref on every backend. */
if (mem->tile.active && mem->tile.width_tiled) {
*handled = 1;
return tigris_dispatch_kernel_s8(
plan, op, op_index, mem, user_ctx);
}

/* Line-buffered chains roll overlap rows across tiles by setting
* mem->tile.out_row_start / in_row_start (see exec_chain_tiled). 2D tiles
* carry mem->tile.width_tiled and partition width via pad_left/tile-in_w
* as well as height (see exec_stage_tiled_2d). Only the reference and s8
* kernels honor those offset and width contracts; the ESP-NN/CMSIS-NN
* Conv/Depthwise adapters ignore them and would write rows or columns at
* the wrong position. Route rolled or 2D-tiled ops to s8_ref so a
* flagged chain or tile stays bit-exact on every backend, while
* non-rolled, non-2D tiles (tile 0, standalone height tiles, last-tile
* full compute) keep the vendor path. */
* mem->tile.out_row_start / in_row_start (see exec_chain_tiled). The
* ESP-NN/CMSIS-NN Conv/Depthwise adapters honor the roll natively - they
* fold out_row_start into the output pointer and (out_row_start*stride -
* in_row_start) into the input pointer, matching the s8 kernel's index math.
* Every other op ignores the offsets and would write rows at the wrong
* position, so a rolled non-conv/depthwise op still routes to s8_ref. */
if (mem->tile.active &&
(mem->tile.out_row_start != 0 || mem->tile.in_row_start != 0 ||
mem->tile.width_tiled)) {
(mem->tile.out_row_start != 0 || mem->tile.in_row_start != 0) &&
!is_conv_or_depthwise(op)) {
*handled = 1;
return tigris_dispatch_kernel_s8(
plan, op, op_index, mem, user_ctx);
Expand Down
58 changes: 38 additions & 20 deletions test/test_accel_routing.c
Original file line number Diff line number Diff line change
Expand Up @@ -370,18 +370,18 @@ static void test_esp_asymmetric_pad_workspace_policy(void)
}

/* A line-buffered chain sets mem->tile.out_row_start / in_row_start on rolled
* interior tiles. The ESP-NN Conv/Depthwise adapters ignore those offsets, so
* a rolled op must fall back to s8_ref. pre_route cannot see the offsets (it
* only takes tile_active), so the guard lives in tigris_accel_try_s8_ref and
* fires only on rolled tiles, leaving non-rolled tiles on the vendor path. */
static void test_rolled_tile_routes_reference(void)
* interior tiles. 1.5b: the ESP-NN/CMSIS-NN Conv/Depthwise adapters now honor
* those offsets natively, so a rolled conv/depthwise stays on the vendor path;
* every other op still falls back to s8_ref. pre_route cannot see the offsets
* (it only takes tile_active), so the roll routing lives in
* tigris_accel_try_s8_ref. */
static void test_rolled_tile_routing(void)
{
printf(" test_rolled_tile_routes_reference...\n");
printf(" test_rolled_tile_routing...\n");
route_fixture_t fx;
build_fixture(&fx, TIGRIS_OP_CONV, 0);

/* Non-dilated 1x1 height-preserving conv: an ESP adapter op absent a roll,
* and s8_ref execution stays trivially in bounds when the guard fires. */
/* Non-dilated 1x1 height-preserving conv. */
fx.op.spatial.dilation_h = 1;
fx.op.spatial.dilation_w = 1;
fx.op.spatial.kernel_h = 1;
Expand All @@ -391,8 +391,6 @@ static void test_rolled_tile_routes_reference(void)
fx.op.spatial.pad_left = 0;
fx.op.spatial.pad_right = 0;

/* pre_route sees only tile_active, so it keeps this on the ESP adapter even
* when tiled: the roll-awareness must come from the try_s8_ref guard. */
TEST_ASSERT_EQ(tigris_accel_pre_route(
TIGRIS_ACCEL_ESP_NN, &fx.plan, &fx.op, 1),
TIGRIS_ACCEL_ROUTE_ADAPTER,
Expand All @@ -403,28 +401,48 @@ static void test_rolled_tile_routes_reference(void)

/* Non-rolled tiled: guard must not over-fire; ESP keeps its adapter. */
memset(out, 0, sizeof(out));
handled = 1;
run_pre_route_rolled(&fx, TIGRIS_ACCEL_ESP_NN, out, 0, 0, 3, &handled);
TEST_ASSERT_EQ(handled, 0,
"non-rolled tiled conv stays on the ESP adapter");

/* Rolled via out_row_start: ESP falls back to s8_ref. */
/* 1.5b: a rolled conv now runs on the vendor adapter (it folds the roll
* offsets into the input/output pointers), on both backends. */
memset(out, 0, sizeof(out));
handled = 1;
run_pre_route_rolled(&fx, TIGRIS_ACCEL_ESP_NN, out, 2, 0, 3, &handled);
TEST_ASSERT_EQ(handled, 1,
"rolled conv (out_row_start) routes ESP to s8_ref");
TEST_ASSERT_EQ(handled, 0,
"rolled conv (out_row_start) stays on the ESP adapter");

/* Rolled via in_row_start alone also triggers the guard. */
memset(out, 0, sizeof(out));
handled = 1;
run_pre_route_rolled(&fx, TIGRIS_ACCEL_ESP_NN, out, 0, 2, 3, &handled);
TEST_ASSERT_EQ(handled, 1,
"rolled conv (in_row_start) routes ESP to s8_ref");
TEST_ASSERT_EQ(handled, 0,
"rolled conv (in_row_start) stays on the ESP adapter");

/* CMSIS: rolled ops also route to s8_ref (it already routes all tiled
* spatial ops there, so this stays correct as well). */
memset(out, 0, sizeof(out));
handled = 1;
run_pre_route_rolled(&fx, TIGRIS_ACCEL_CMSIS_NN, out, 2, 0, 3, &handled);
TEST_ASSERT_EQ(handled, 0,
"rolled conv stays on the CMSIS adapter");

/* A rolled NON-conv/depthwise op still routes to s8_ref: only conv/depthwise
* fold the roll offsets natively. Max-pool honors the roll in s8_ref, so the
* fallback execution stays in bounds. */
build_fixture(&fx, TIGRIS_OP_MAX_POOL, 0);
fx.op.spatial.dilation_h = 1;
fx.op.spatial.dilation_w = 1;
fx.op.spatial.kernel_h = 1;
fx.op.spatial.kernel_w = 1;
fx.op.spatial.pad_top = 0;
fx.op.spatial.pad_bottom = 0;
fx.op.spatial.pad_left = 0;
fx.op.spatial.pad_right = 0;
memset(out, 0, sizeof(out));
handled = 0;
run_pre_route_rolled(&fx, TIGRIS_ACCEL_ESP_NN, out, 2, 0, 3, &handled);
TEST_ASSERT_EQ(handled, 1,
"rolled conv routes CMSIS to s8_ref");
"rolled max-pool still routes ESP to s8_ref");
}

/* A 2D-tiled op sets mem->tile.width_tiled on tiles that partition width as
Expand Down Expand Up @@ -509,7 +527,7 @@ int main(void)
test_cmsis_dilation_and_tile_routes();
test_cmsis_plain_height_tile_routes_adapter();
test_unit_dilation_keeps_esp_adapter();
test_rolled_tile_routes_reference();
test_rolled_tile_routing();
test_width_tiled_routes_reference();
test_esp_asymmetric_pad_workspace_policy();

Expand Down
Loading