diff --git a/scripts/static_analysis_baseline.json b/scripts/static_analysis_baseline.json index 8432b66..69c9c22 100644 --- a/scripts/static_analysis_baseline.json +++ b/scripts/static_analysis_baseline.json @@ -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, @@ -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, diff --git a/src/tigris_kernels_cmsis_nn.c b/src/tigris_kernels_cmsis_nn.c index 9247040..1e18e1c 100644 --- a/src/tigris_kernels_cmsis_nn.c +++ b/src/tigris_kernels_cmsis_nn.c @@ -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, @@ -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. */ diff --git a/src/tigris_kernels_esp_nn.c b/src/tigris_kernels_esp_nn.c index 721209f..3b7ee64 100644 --- a/src/tigris_kernels_esp_nn.c +++ b/src/tigris_kernels_esp_nn.c @@ -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 */ @@ -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 }; diff --git a/src/tigris_kernels_s8.c b/src/tigris_kernels_s8.c index 3e7faf7..934d01e 100644 --- a/src/tigris_kernels_s8.c +++ b/src/tigris_kernels_s8.c @@ -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); diff --git a/test/test_accel_routing.c b/test/test_accel_routing.c index 00a0767..3582b23 100644 --- a/test/test_accel_routing.c +++ b/test/test_accel_routing.c @@ -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; @@ -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, @@ -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 @@ -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();