aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrank Barchard <fbarchard@google.com>2022-05-17 10:40:45 -0700
committerXNNPACK Team <xnnpack-github-robot@google.com>2022-05-17 10:41:53 -0700
commited4e2032a4c3c98ad21d4e27c5a98879385ffd65 (patch)
treefcde32931e047ec84bcf70d12ebc98729ce90a1f
parentddeeed7df85a0cd0297cea6901590b2ac1695404 (diff)
downloadXNNPACK-ed4e2032a4c3c98ad21d4e27c5a98879385ffd65.tar.gz
4X8 IGEMM for Cortex A7/32/A35
- Adapted from A53 IGEMM but with NEON loads. PiperOrigin-RevId: 449258615
-rw-r--r--BUILD.bazel8
-rwxr-xr-xCMakeLists.txt8
-rw-r--r--bench/qs8-gemm-e2e.cc12
-rw-r--r--bench/qu8-gemm-e2e.cc8
-rwxr-xr-xscripts/generate-qs8-igemm.sh10
-rw-r--r--src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-cortex-a7.S581
-rw-r--r--src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-prfm-cortex-a7.S588
-rw-r--r--src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-cortex-a35.S572
-rw-r--r--src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-prfm-cortex-a35.S579
-rw-r--r--src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in784
-rw-r--r--src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S577
-rw-r--r--src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S584
-rw-r--r--src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S578
-rw-r--r--src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S585
-rw-r--r--src/xnnpack/igemm.h8
15 files changed, 5472 insertions, 10 deletions
diff --git a/BUILD.bazel b/BUILD.bazel
index b94809a3f..29b9982ca 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -7878,6 +7878,14 @@ AARCH32_ASM_MICROKERNEL_SRCS = [
"src/f32-gemm/4x4-aarch32-vfp-ld64.S",
"src/f32-gemm/4x4-minmax-aarch32-vfp-ld64.S",
"src/f32-gemm/4x8-minmax-aarch32-neon-cortex-a55.S",
+ "src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-cortex-a7.S",
+ "src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-prfm-cortex-a7.S",
+ "src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-cortex-a35.S",
+ "src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-prfm-cortex-a35.S",
+ "src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S",
+ "src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S",
+ "src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S",
+ "src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S",
"src/f32-igemm/gen/4x8-minmax-aarch32-neon-cortex-a7.S",
"src/f32-igemm/gen/4x8-minmax-aarch32-neon-cortex-a53.S",
"src/f32-igemm/gen/4x8-minmax-aarch32-neon-cortex-a75.S",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e14fd789a..269316849 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6392,6 +6392,14 @@ SET(AARCH32_ASM_MICROKERNEL_SRCS
src/f32-gemm/4x4-aarch32-vfp-ld64.S
src/f32-gemm/4x4-minmax-aarch32-vfp-ld64.S
src/f32-gemm/4x8-minmax-aarch32-neon-cortex-a55.S
+ src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-cortex-a7.S
+ src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-prfm-cortex-a7.S
+ src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-cortex-a35.S
+ src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-prfm-cortex-a35.S
+ src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S
+ src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S
+ src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S
+ src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S
src/f32-igemm/gen/4x8-minmax-aarch32-neon-cortex-a7.S
src/f32-igemm/gen/4x8-minmax-aarch32-neon-cortex-a53.S
src/f32-igemm/gen/4x8-minmax-aarch32-neon-cortex-a75.S
diff --git a/bench/qs8-gemm-e2e.cc b/bench/qs8-gemm-e2e.cc
index d419d251f..e45767501 100644
--- a/bench/qs8-gemm-e2e.cc
+++ b/bench/qs8-gemm-e2e.cc
@@ -102,7 +102,7 @@ static void GEMMEnd2EndBenchmark(
GEMMEnd2EndBenchmark(state, model,
xnn_qs8_gemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a53,
xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a53,
- xnn_qs8_gemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
+ xnn_qs8_gemm_minmax_rndnu_ukernel_1x8__aarch32_neon_mlal_lane_cortex_a7,
xnn_qs8_igemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
xnn_init_qs8_conv_minmax_rndnu_neon_params,
4 /* mr */, 8 /* nr */, 0 /* log2_kr */, 0 /* log2_sr */,
@@ -112,7 +112,7 @@ static void GEMMEnd2EndBenchmark(
GEMMEnd2EndBenchmark(state, model,
xnn_qs8_gemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a53,
xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a53,
- xnn_qs8_gemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
+ xnn_qs8_gemm_minmax_rndnu_ukernel_1x8__aarch32_neon_mlal_lane_prfm_cortex_a7,
xnn_qs8_igemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
xnn_init_qs8_conv_minmax_rndnu_neon_params,
4 /* mr */, 8 /* nr */, 0 /* log2_kr */, 0 /* log2_sr */,
@@ -121,7 +121,7 @@ static void GEMMEnd2EndBenchmark(
static void qs8_gemm_4x8__aarch32_neon_mlal_lane_cortex_a7(benchmark::State& state, models::ExecutionPlanFactory model) {
GEMMEnd2EndBenchmark(state, model,
xnn_qs8_gemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7,
- xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_ld64,
+ xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7,
xnn_qs8_gemm_minmax_rndnu_ukernel_1x8__aarch32_neon_mlal_lane_cortex_a7,
xnn_qs8_igemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
xnn_init_qs8_conv_minmax_rndnu_neon_params,
@@ -131,7 +131,7 @@ static void GEMMEnd2EndBenchmark(
static void qs8_gemm_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7(benchmark::State& state, models::ExecutionPlanFactory model) {
GEMMEnd2EndBenchmark(state, model,
xnn_qs8_gemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7,
- xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_ld64,
+ xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7,
xnn_qs8_gemm_minmax_rndnu_ukernel_1x8__aarch32_neon_mlal_lane_prfm_cortex_a7,
xnn_qs8_igemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
xnn_init_qs8_conv_minmax_rndnu_neon_params,
@@ -142,7 +142,7 @@ static void GEMMEnd2EndBenchmark(
GEMMEnd2EndBenchmark(state, model,
xnn_qs8_gemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_ld64,
xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_ld64,
- xnn_qs8_gemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
+ xnn_qs8_gemm_minmax_rndnu_ukernel_1x8__aarch32_neon_mlal_lane_cortex_a7,
xnn_qs8_igemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
xnn_init_qs8_conv_minmax_rndnu_neon_params,
4 /* mr */, 8 /* nr */, 0 /* log2_kr */, 0 /* log2_sr */,
@@ -152,7 +152,7 @@ static void GEMMEnd2EndBenchmark(
GEMMEnd2EndBenchmark(state, model,
xnn_qs8_gemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_ld64,
xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_ld64,
- xnn_qs8_gemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
+ xnn_qs8_gemm_minmax_rndnu_ukernel_1x8__aarch32_neon_mlal_lane_prfm_cortex_a7,
xnn_qs8_igemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
xnn_init_qs8_conv_minmax_rndnu_neon_params,
4 /* mr */, 8 /* nr */, 0 /* log2_kr */, 0 /* log2_sr */,
diff --git a/bench/qu8-gemm-e2e.cc b/bench/qu8-gemm-e2e.cc
index 91f0005ab..838b75816 100644
--- a/bench/qu8-gemm-e2e.cc
+++ b/bench/qu8-gemm-e2e.cc
@@ -82,7 +82,7 @@ static void GEMMEnd2EndBenchmark(
GEMMEnd2EndBenchmark(state, model,
xnn_qu8_gemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a53,
xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a53,
- xnn_qu8_gemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
+ xnn_qu8_gemm_minmax_rndnu_ukernel_1x8__aarch32_neon_mlal_lane_cortex_a7,
xnn_qu8_igemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
xnn_init_qu8_conv_minmax_rndnu_neon_params,
4 /* mr */, 8 /* nr */, 0 /* log2_kr */, 0 /* log2_sr */,
@@ -92,7 +92,7 @@ static void GEMMEnd2EndBenchmark(
GEMMEnd2EndBenchmark(state, model,
xnn_qu8_gemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a53,
xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a53,
- xnn_qu8_gemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
+ xnn_qu8_gemm_minmax_rndnu_ukernel_1x8__aarch32_neon_mlal_lane_prfm_cortex_a7,
xnn_qu8_igemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
xnn_init_qu8_conv_minmax_rndnu_neon_params,
4 /* mr */, 8 /* nr */, 0 /* log2_kr */, 0 /* log2_sr */,
@@ -101,7 +101,7 @@ static void GEMMEnd2EndBenchmark(
static void qu8_gemm_4x8__aarch32_neon_mlal_lane_cortex_a7(benchmark::State& state, models::ExecutionPlanFactory model) {
GEMMEnd2EndBenchmark(state, model,
xnn_qu8_gemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7,
- xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_ld64,
+ xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7,
xnn_qu8_gemm_minmax_rndnu_ukernel_1x8__aarch32_neon_mlal_lane_cortex_a7,
xnn_qu8_igemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
xnn_init_qu8_conv_minmax_rndnu_neon_params,
@@ -111,7 +111,7 @@ static void GEMMEnd2EndBenchmark(
static void qu8_gemm_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7(benchmark::State& state, models::ExecutionPlanFactory model) {
GEMMEnd2EndBenchmark(state, model,
xnn_qu8_gemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7,
- xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_ld64,
+ xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7,
xnn_qu8_gemm_minmax_rndnu_ukernel_1x8__aarch32_neon_mlal_lane_prfm_cortex_a7,
xnn_qu8_igemm_minmax_rndnu_ukernel_1x8__neon_mlal_lane,
xnn_init_qu8_conv_minmax_rndnu_neon_params,
diff --git a/scripts/generate-qs8-igemm.sh b/scripts/generate-qs8-igemm.sh
index 05a82eff8..20f8d3593 100755
--- a/scripts/generate-qs8-igemm.sh
+++ b/scripts/generate-qs8-igemm.sh
@@ -701,6 +701,13 @@ tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a53.S.in -D PREFETC
tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a53.S.in -D PREFETCH=0 -D REQUANTIZATION=FP32 -D CHANNELWISE=1 -D DATATYPE=QC8 -D ARMV8=1 -o src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-cortex-a53.S &
tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a53.S.in -D PREFETCH=1 -D REQUANTIZATION=FP32 -D CHANNELWISE=1 -D DATATYPE=QC8 -D ARMV8=1 -o src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-prfm-cortex-a53.S &
+tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in -D PREFETCH=0 -D REQUANTIZATION=RNDNU -D CHANNELWISE=0 -D DATATYPE=QS8 -D ARMV8=0 -o src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S &
+tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in -D PREFETCH=1 -D REQUANTIZATION=RNDNU -D CHANNELWISE=0 -D DATATYPE=QS8 -D ARMV8=0 -o src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S &
+tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in -D PREFETCH=0 -D REQUANTIZATION=FP32 -D CHANNELWISE=1 -D DATATYPE=QC8 -D ARMV8=0 -o src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-cortex-a7.S &
+tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in -D PREFETCH=1 -D REQUANTIZATION=FP32 -D CHANNELWISE=1 -D DATATYPE=QC8 -D ARMV8=0 -o src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-prfm-cortex-a7.S &
+tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in -D PREFETCH=0 -D REQUANTIZATION=FP32 -D CHANNELWISE=1 -D DATATYPE=QC8 -D ARMV8=1 -o src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-cortex-a35.S &
+tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in -D PREFETCH=1 -D REQUANTIZATION=FP32 -D CHANNELWISE=1 -D DATATYPE=QC8 -D ARMV8=1 -o src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-prfm-cortex-a35.S &
+
### QU8 micro-kernels
tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-ld64.S.in -D PREFETCH=0 -D REQUANTIZATION=RNDNU -D CHANNELWISE=0 -D DATATYPE=QU8 -D ARMV8=0 -o src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-ld64.S &
tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-ld64.S.in -D PREFETCH=1 -D REQUANTIZATION=RNDNU -D CHANNELWISE=0 -D DATATYPE=QU8 -D ARMV8=0 -o src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-ld64.S &
@@ -708,6 +715,9 @@ tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-ld64.S.in -D PREFETC
tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a53.S.in -D PREFETCH=0 -D REQUANTIZATION=RNDNU -D CHANNELWISE=0 -D DATATYPE=QU8 -D ARMV8=0 -o src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a53.S &
tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a53.S.in -D PREFETCH=1 -D REQUANTIZATION=RNDNU -D CHANNELWISE=0 -D DATATYPE=QU8 -D ARMV8=0 -o src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a53.S &
+tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in -D PREFETCH=0 -D REQUANTIZATION=RNDNU -D CHANNELWISE=0 -D DATATYPE=QU8 -D ARMV8=0 -o src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S &
+tools/xngen src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in -D PREFETCH=1 -D REQUANTIZATION=RNDNU -D CHANNELWISE=0 -D DATATYPE=QU8 -D ARMV8=0 -o src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S &
+
### C4 micro-kernels
tools/xngen src/qs8-igemm/4x8c4-aarch32-neondot-ld64.S.in -D REQUANTIZATION=RNDNU -D CHANNELWISE=0 -D DATATYPE=QS8 -o src/qs8-igemm/gen/4x8c4-minmax-rndnu-aarch32-neondot-ld64.S &
tools/xngen src/qs8-igemm/4x8c4-aarch32-neondot-ld64.S.in -D REQUANTIZATION=FP32 -D CHANNELWISE=1 -D DATATYPE=QC8 -o src/qc8-igemm/gen/4x8c4-minmax-fp32-aarch32-neondot-ld64.S &
diff --git a/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-cortex-a7.S b/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-cortex-a7.S
new file mode 100644
index 000000000..177bbb739
--- /dev/null
+++ b/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-cortex-a7.S
@@ -0,0 +1,581 @@
+// Auto-generated file. Do not edit!
+// Template: src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in
+// Generator: tools/xngen
+//
+// Copyright 2021 Google LLC
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree.
+
+
+#include <xnnpack/assembly.h>
+
+.syntax unified
+
+// void xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7(
+// size_t mr, (r0)
+// size_t nc, r1 -> sp + 56
+// size_t kc, (r2) -> r5 -> sp + 60
+// size_t ks, (r3) -> sp + 64 -> r14
+// const int8_t**restrict a, sp + 104 -> r2
+// const void*restrict w, sp + 108 -> r9
+// int8_t*restrict c, sp + 112 -> r11
+// size_t cm_stride, sp + 116 -> (r6)
+// size_t cn_stride, sp + 120 -> (r7)
+// size_t a_offset, sp + 124 -> (r5)
+// const int8_t* zero, sp + 128 -> (r7)
+// xnn_qs8_minmax_params*params); sp + 132 -> (r5)
+
+// d8-d15, r4-r11,r14(lr) need to be preserved if used. r13(sp),r15(pc) are reserved.
+
+// Register usage
+// A0 r3 d0-d1 q0
+// A1 r12 d2-d3 q1
+// A2 r10 d4-d5 q2
+// A3 r0 d6-d7 q3
+
+// B r9 d8-d9 q4 q5
+
+// C0 r11 d16-d17 q8 d18-d19 q9
+// C1 r4 d20-d21 q10 d22-d23 q11
+// C2 r8 d24-d25 q12 d26-d27 q13
+// C3 r6 d28-d29 q14 d30-d31 q15
+
+// Unused d15
+
+// params structure is 10 bytes
+// struct {
+// float magic_bias; d12[0]
+// int32_t magic_bias_less_output_zero_point; d12[1]
+// int8_t output_min; d13[6]
+// int8_t output_max; d13[7]
+// } xnn_qs8_minmax_params.neon;
+
+BEGIN_FUNCTION xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7
+ # Push 104 bytes
+ # r1, r2 will be reloaded in outer loop. r3 is ks
+ PUSH {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, lr} // +48
+ SUB sp, sp, 8 // +8
+ VPUSH {d8-d13} // +48 = 104
+
+ LDR r11, [sp, 112] // c
+ LDR r6, [sp, 116] // cm_stride
+ LDR r2, [sp, 104] // a
+ LDR r9, [sp, 108] // w
+ LDR r5, [sp, 132] // params
+ MOV r14, r3 // p = ks
+
+ # Clamp C pointers
+ CMP r0, 2 // if mr >= 2
+ ADD r4, r11, r6 // c1 = c0 + cm_stride
+ MOVLO r4, r11 // c1
+ // if mr > 2
+ ADD r8, r4, r6 // c2 = c1 + cm_stride
+ MOVLS r8, r4 // c2
+ CMP r0, 4 // if mr >=4
+ ADD r6, r8, r6 // c3 = c2 + cm_stride
+ MOVLO r6, r8 // c3
+
+ # Load params values
+ VLDM r5!, {d12} // QC8 neon params
+ VLD1.16 {d13[]}, [r5]
+
+
+ .p2align 3
+0:
+ # Load initial bias from w into accumulators
+ VLDM r9!, {d16-d19} // Bias
+ VMOV q10, q8
+ VMOV q11, q9
+ STR r1, [sp, 56] // save nc
+ VMOV q12, q8
+ VMOV q13, q9
+ VMOV q14, q8
+ VMOV q15, q9
+
+ .p2align 3
+1:
+ # Load next 4 A pointers
+ LDR r3, [r2, 0]
+ LDR r12, [r2, 4]
+ LDR r10, [r2, 8]
+ LDR r0, [r2, 12]
+
+ # Add a_offset
+ LDR r5, [sp, 124] // a_offset
+ LDR r7, [sp, 128] // zero
+ ADD r2, r2, 16
+ CMP r3, r7 // if a0 == zero
+ ADD r3, r3, r5 // a0 += a_offset
+ MOVEQ r3, r7 // a0 = zero, else += a0 + a_offset
+ CMP r12, r7 // if a1 == zero
+ ADD r12, r12, r5 // a1 += a_offset
+ MOVEQ r12, r7 // a1 = zero, else += a1 + a_offset
+ CMP r10, r7 // if a2 == zero
+ ADD r10, r10, r5 // a2 += a_offset
+ MOVEQ r10, r7 // a2 = zero, else += a2 + a_offset
+ CMP r0, r7 // if a3 == zero
+ ADD r0, r0, r5 // a3 += a_offset
+ LDR r5, [sp, 60] // kc
+ MOVEQ r0, r7 // a3 = zero, else += a3 + a_offset
+ SUBS r5, r5, 8 // kc - 8
+ BLO 5f // less than 8 channels?
+
+ // Prologue - load 4A's and B0
+ VLD1.8 {d0}, [r3]! // A0
+ VLD1.8 {d2}, [r12]! // A1
+ VLD1.8 {d4}, [r10]! // A2
+ VLD1.8 {d6}, [r0]! // A3
+ VLD1.8 {d8}, [r9]! // B0
+
+ SUBS r5, r5, 8 // k = k - 8
+ BLO 3f // less than 8 channels?
+
+ // Main loop - 8 bytes
+ // 64 bytes for weights.
+ // 5 VMOVL = 4 A and 1 B = 5 cycles
+ // 7 blocks with VLD B, VMOVL, 8 VMLA = 10 cycles
+ // 1 blocks with VLD B, VMLA = 9 cycles
+ // total = 84 cycles
+ .p2align 3
+2:
+ // Extend - 5 cycles
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ // BLOCK 0 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ // BLOCK 1 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ // BLOCK 2 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ // BLOCK 3 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VLD1.8 {d0}, [r3]! // A0
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ // BLOCK 4 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VLD1.8 {d2}, [r12]! // A1
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ // BLOCK 5 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VLD1.8 {d4}, [r10]! // A2
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ // BLOCK 6 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VLD1.8 {d6}, [r0]! // A3
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ // BLOCK 7 - 9 cycles
+ VLD1.8 {d8}, [r9]! // B0
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ SUBS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+ BHS 2b
+
+ // Epilogue
+
+ .p2align 3
+3:
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ ADDS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+
+ # Is there a remainder?- 1-7 bytes of A
+ BNE 6f
+
+4:
+ # ks loop
+ SUBS r14, r14, 16 // ks -= MR * sizeof(void*)
+ BHI 1b
+
+ LDR r7, [sp, 120] // cn_stride
+ LDR r14, [sp, 64] // p = ks
+
+ # QC8 FP32 quantization
+ VLD1.8 {q0-q1}, [r9]!
+
+ VDUP.32 q2, d12[0] // magic_bias
+ VDUP.32 q3, d12[1] // magic_bias_less_output_zero_point
+
+ VCVT.F32.S32 q8, q8
+ VCVT.F32.S32 q9, q9
+ VCVT.F32.S32 q10, q10
+ VCVT.F32.S32 q11, q11
+ VCVT.F32.S32 q12, q12
+ VCVT.F32.S32 q13, q13
+ VCVT.F32.S32 q14, q14
+ VCVT.F32.S32 q15, q15
+
+ VMUL.F32 q8, q8, q0 // multiplier
+ VMUL.F32 q9, q9, q1
+ VMUL.F32 q10, q10, q0
+ VMUL.F32 q11, q11, q1
+ VMUL.F32 q12, q12, q0
+ VMUL.F32 q13, q13, q1
+ VMUL.F32 q14, q14, q0
+ VMUL.F32 q15, q15, q1
+
+ VADD.F32 q8, q8, q2 // magic_bias
+ VADD.F32 q9, q9, q2
+ VADD.F32 q10, q10, q2
+ VADD.F32 q11, q11, q2
+ VADD.F32 q12, q12, q2
+ VADD.F32 q13, q13, q2
+ VADD.F32 q14, q14, q2
+ VADD.F32 q15, q15, q2
+
+ VQSUB.S32 q8, q8, q3 // magic_bias_less_output_zero_point
+ VQSUB.S32 q9, q9, q3
+ VQSUB.S32 q10, q10, q3
+ VQSUB.S32 q11, q11, q3
+ VQSUB.S32 q12, q12, q3
+ VQSUB.S32 q13, q13, q3
+ VQSUB.S32 q14, q14, q3
+ VQSUB.S32 q15, q15, q3
+
+
+ VQMOVN.S32 d16, q8
+ VQMOVN.S32 d17, q9
+ VQMOVN.S32 d18, q10
+ VQMOVN.S32 d19, q11
+ VQMOVN.S32 d20, q12
+ VQMOVN.S32 d21, q13
+ VQMOVN.S32 d22, q14
+ VQMOVN.S32 d23, q15
+
+
+ LDR r1, [sp, 56] // restore nc
+ VDUP.8 q12, d13[6] // output_min
+
+ VQMOVN.S16 d0, q8
+ VQMOVN.S16 d1, q9
+ VQMOVN.S16 d2, q10
+ VQMOVN.S16 d3, q11
+
+ VDUP.8 q13, d13[7] // output_max
+
+ VMAX.S8 q0, q0, q12
+ VMAX.S8 q1, q1, q12
+
+ SUBS r1, r1, 8 // nc -= 8
+
+ VMIN.S8 q0, q0, q13
+ VMIN.S8 q1, q1, q13
+
+ # Store full 4 x 8
+ BLO 7f
+ VST1.8 {d3}, [r6], r7
+ VST1.8 {d2}, [r8], r7
+ VST1.8 {d1}, [r4], r7
+ VST1.8 {d0}, [r11], r7
+ SUB r2, r2, r14 // a -= ks
+ BHI 0b
+
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+ # Remainder- 1 to 7 bytes of A
+ .p2align 3
+5:
+ AND r5, r5, 7 // kc remainder 1 to 7
+6:
+ VLD1.8 {d0}, [r3]
+ VLD1.8 {d8}, [r9]!
+ VLD1.8 {d2}, [r12]
+ VLD1.8 {d4}, [r10]
+ VLD1.8 {d6}, [r0]
+
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+ CMP r5, 2
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[1]
+ VMLAL.S16 q9, d9, d0[1]
+ VMLAL.S16 q10, d8, d2[1]
+ VMLAL.S16 q11, d9, d2[1]
+ VMLAL.S16 q12, d8, d4[1]
+ VMLAL.S16 q13, d9, d4[1]
+ VMLAL.S16 q14, d8, d6[1]
+ VMLAL.S16 q15, d9, d6[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+ CMP r5, 4
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[3]
+ VMLAL.S16 q9, d9, d0[3]
+ VMLAL.S16 q10, d8, d2[3]
+ VMLAL.S16 q11, d9, d2[3]
+ VMLAL.S16 q12, d8, d4[3]
+ VMLAL.S16 q13, d9, d4[3]
+ VMLAL.S16 q14, d8, d6[3]
+ VMLAL.S16 q15, d9, d6[3]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+ CMP r5, 6
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[1]
+ VMLAL.S16 q9, d9, d1[1]
+ VMLAL.S16 q10, d8, d3[1]
+ VMLAL.S16 q11, d9, d3[1]
+ VMLAL.S16 q12, d8, d5[1]
+ VMLAL.S16 q13, d9, d5[1]
+ VMLAL.S16 q14, d8, d7[1]
+ VMLAL.S16 q15, d9, d7[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+ B 4b
+
+ # Store odd width
+ .p2align 3
+7:
+ TST r1, 4
+ BEQ 8f
+ VST1.32 {d3[0]}, [r6]!
+ VST1.32 {d2[0]}, [r8]!
+ VST1.32 {d1[0]}, [r4]!
+ VST1.32 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 4
+ VEXT.8 q1, q1, q1, 4
+8:
+ TST r1, 2
+ BEQ 9f
+ VST1.16 {d3[0]}, [r6]!
+ VST1.16 {d2[0]}, [r8]!
+ VST1.16 {d1[0]}, [r4]!
+ VST1.16 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 2
+ VEXT.8 q1, q1, q1, 2
+
+9:
+ TST r1, 1
+ BEQ 10f
+ VST1.8 {d3[0]}, [r6]
+ VST1.8 {d2[0]}, [r8]
+ VST1.8 {d1[0]}, [r4]
+ VST1.8 {d0[0]}, [r11]
+
+10:
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+END_FUNCTION xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7
+
+#ifdef __ELF__
+.section ".note.GNU-stack","",%progbits
+#endif
diff --git a/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-prfm-cortex-a7.S b/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-prfm-cortex-a7.S
new file mode 100644
index 000000000..57e8db33c
--- /dev/null
+++ b/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neon-mlal-lane-prfm-cortex-a7.S
@@ -0,0 +1,588 @@
+// Auto-generated file. Do not edit!
+// Template: src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in
+// Generator: tools/xngen
+//
+// Copyright 2021 Google LLC
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree.
+
+
+#include <xnnpack/assembly.h>
+
+.syntax unified
+
+// void xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7(
+// size_t mr, (r0)
+// size_t nc, r1 -> sp + 56
+// size_t kc, (r2) -> r5 -> sp + 60
+// size_t ks, (r3) -> sp + 64 -> r14
+// const int8_t**restrict a, sp + 104 -> r2
+// const void*restrict w, sp + 108 -> r9
+// int8_t*restrict c, sp + 112 -> r11
+// size_t cm_stride, sp + 116 -> (r6)
+// size_t cn_stride, sp + 120 -> (r7)
+// size_t a_offset, sp + 124 -> (r5)
+// const int8_t* zero, sp + 128 -> (r7)
+// xnn_qs8_minmax_params*params); sp + 132 -> (r5)
+
+// d8-d15, r4-r11,r14(lr) need to be preserved if used. r13(sp),r15(pc) are reserved.
+
+// Register usage
+// A0 r3 d0-d1 q0
+// A1 r12 d2-d3 q1
+// A2 r10 d4-d5 q2
+// A3 r0 d6-d7 q3
+
+// B r9 d8-d9 q4 q5
+
+// C0 r11 d16-d17 q8 d18-d19 q9
+// C1 r4 d20-d21 q10 d22-d23 q11
+// C2 r8 d24-d25 q12 d26-d27 q13
+// C3 r6 d28-d29 q14 d30-d31 q15
+
+// Unused d15
+
+// params structure is 10 bytes
+// struct {
+// float magic_bias; d12[0]
+// int32_t magic_bias_less_output_zero_point; d12[1]
+// int8_t output_min; d13[6]
+// int8_t output_max; d13[7]
+// } xnn_qs8_minmax_params.neon;
+
+BEGIN_FUNCTION xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7
+ # Push 104 bytes
+ # r1, r2 will be reloaded in outer loop. r3 is ks
+ PUSH {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, lr} // +48
+ SUB sp, sp, 8 // +8
+ VPUSH {d8-d13} // +48 = 104
+
+ LDR r11, [sp, 112] // c
+ LDR r6, [sp, 116] // cm_stride
+ LDR r2, [sp, 104] // a
+ LDR r9, [sp, 108] // w
+ LDR r5, [sp, 132] // params
+ MOV r14, r3 // p = ks
+
+ # Clamp C pointers
+ CMP r0, 2 // if mr >= 2
+ ADD r4, r11, r6 // c1 = c0 + cm_stride
+ MOVLO r4, r11 // c1
+ // if mr > 2
+ ADD r8, r4, r6 // c2 = c1 + cm_stride
+ MOVLS r8, r4 // c2
+ CMP r0, 4 // if mr >=4
+ ADD r6, r8, r6 // c3 = c2 + cm_stride
+ MOVLO r6, r8 // c3
+
+ # Load params values
+ VLDM r5!, {d12} // QC8 neon params
+ VLD1.16 {d13[]}, [r5]
+
+ PLD [r9, 64] // Prefetch B
+ PLD [r9, 128]
+ PLD [r9, 192]
+ PLD [r9, 256]
+ PLD [r9, 320]
+ PLD [r9, 384]
+
+ .p2align 3
+0:
+ # Load initial bias from w into accumulators
+ VLDM r9!, {d16-d19} // Bias
+ VMOV q10, q8
+ VMOV q11, q9
+ STR r1, [sp, 56] // save nc
+ VMOV q12, q8
+ VMOV q13, q9
+ VMOV q14, q8
+ VMOV q15, q9
+
+ .p2align 3
+1:
+ # Load next 4 A pointers
+ LDR r3, [r2, 0]
+ LDR r12, [r2, 4]
+ LDR r10, [r2, 8]
+ LDR r0, [r2, 12]
+
+ # Add a_offset
+ LDR r5, [sp, 124] // a_offset
+ LDR r7, [sp, 128] // zero
+ ADD r2, r2, 16
+ CMP r3, r7 // if a0 == zero
+ ADD r3, r3, r5 // a0 += a_offset
+ MOVEQ r3, r7 // a0 = zero, else += a0 + a_offset
+ CMP r12, r7 // if a1 == zero
+ ADD r12, r12, r5 // a1 += a_offset
+ MOVEQ r12, r7 // a1 = zero, else += a1 + a_offset
+ CMP r10, r7 // if a2 == zero
+ ADD r10, r10, r5 // a2 += a_offset
+ MOVEQ r10, r7 // a2 = zero, else += a2 + a_offset
+ CMP r0, r7 // if a3 == zero
+ ADD r0, r0, r5 // a3 += a_offset
+ LDR r5, [sp, 60] // kc
+ MOVEQ r0, r7 // a3 = zero, else += a3 + a_offset
+ SUBS r5, r5, 8 // kc - 8
+ BLO 5f // less than 8 channels?
+
+ // Prologue - load 4A's and B0
+ VLD1.8 {d0}, [r3]! // A0
+ VLD1.8 {d2}, [r12]! // A1
+ VLD1.8 {d4}, [r10]! // A2
+ VLD1.8 {d6}, [r0]! // A3
+ VLD1.8 {d8}, [r9]! // B0
+
+ SUBS r5, r5, 8 // k = k - 8
+ BLO 3f // less than 8 channels?
+
+ // Main loop - 8 bytes
+ // 64 bytes for weights.
+ // 5 VMOVL = 4 A and 1 B = 5 cycles
+ // 7 blocks with VLD B, VMOVL, 8 VMLA = 10 cycles
+ // 1 blocks with VLD B, VMLA = 9 cycles
+ // total = 84 cycles
+ .p2align 3
+2:
+ // Extend - 5 cycles
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ PLD [r9, 448]
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ // BLOCK 0 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ // BLOCK 1 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ // BLOCK 2 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ // BLOCK 3 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VLD1.8 {d0}, [r3]! // A0
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ // BLOCK 4 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VLD1.8 {d2}, [r12]! // A1
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ // BLOCK 5 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VLD1.8 {d4}, [r10]! // A2
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ // BLOCK 6 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VLD1.8 {d6}, [r0]! // A3
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ // BLOCK 7 - 9 cycles
+ VLD1.8 {d8}, [r9]! // B0
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ SUBS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+ BHS 2b
+
+ // Epilogue
+
+ .p2align 3
+3:
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ ADDS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+
+ # Is there a remainder?- 1-7 bytes of A
+ BNE 6f
+
+4:
+ # ks loop
+ SUBS r14, r14, 16 // ks -= MR * sizeof(void*)
+ BHI 1b
+
+ LDR r7, [sp, 120] // cn_stride
+ LDR r14, [sp, 64] // p = ks
+
+ # QC8 FP32 quantization
+ VLD1.8 {q0-q1}, [r9]!
+
+ VDUP.32 q2, d12[0] // magic_bias
+ VDUP.32 q3, d12[1] // magic_bias_less_output_zero_point
+
+ VCVT.F32.S32 q8, q8
+ VCVT.F32.S32 q9, q9
+ VCVT.F32.S32 q10, q10
+ VCVT.F32.S32 q11, q11
+ VCVT.F32.S32 q12, q12
+ VCVT.F32.S32 q13, q13
+ VCVT.F32.S32 q14, q14
+ VCVT.F32.S32 q15, q15
+
+ VMUL.F32 q8, q8, q0 // multiplier
+ VMUL.F32 q9, q9, q1
+ VMUL.F32 q10, q10, q0
+ VMUL.F32 q11, q11, q1
+ VMUL.F32 q12, q12, q0
+ VMUL.F32 q13, q13, q1
+ VMUL.F32 q14, q14, q0
+ VMUL.F32 q15, q15, q1
+
+ VADD.F32 q8, q8, q2 // magic_bias
+ VADD.F32 q9, q9, q2
+ VADD.F32 q10, q10, q2
+ VADD.F32 q11, q11, q2
+ VADD.F32 q12, q12, q2
+ VADD.F32 q13, q13, q2
+ VADD.F32 q14, q14, q2
+ VADD.F32 q15, q15, q2
+
+ VQSUB.S32 q8, q8, q3 // magic_bias_less_output_zero_point
+ VQSUB.S32 q9, q9, q3
+ VQSUB.S32 q10, q10, q3
+ VQSUB.S32 q11, q11, q3
+ VQSUB.S32 q12, q12, q3
+ VQSUB.S32 q13, q13, q3
+ VQSUB.S32 q14, q14, q3
+ VQSUB.S32 q15, q15, q3
+
+
+ VQMOVN.S32 d16, q8
+ VQMOVN.S32 d17, q9
+ VQMOVN.S32 d18, q10
+ VQMOVN.S32 d19, q11
+ VQMOVN.S32 d20, q12
+ VQMOVN.S32 d21, q13
+ VQMOVN.S32 d22, q14
+ VQMOVN.S32 d23, q15
+
+
+ LDR r1, [sp, 56] // restore nc
+ VDUP.8 q12, d13[6] // output_min
+
+ VQMOVN.S16 d0, q8
+ VQMOVN.S16 d1, q9
+ VQMOVN.S16 d2, q10
+ VQMOVN.S16 d3, q11
+
+ VDUP.8 q13, d13[7] // output_max
+
+ VMAX.S8 q0, q0, q12
+ VMAX.S8 q1, q1, q12
+
+ SUBS r1, r1, 8 // nc -= 8
+
+ VMIN.S8 q0, q0, q13
+ VMIN.S8 q1, q1, q13
+
+ # Store full 4 x 8
+ BLO 7f
+ VST1.8 {d3}, [r6], r7
+ VST1.8 {d2}, [r8], r7
+ VST1.8 {d1}, [r4], r7
+ VST1.8 {d0}, [r11], r7
+ SUB r2, r2, r14 // a -= ks
+ BHI 0b
+
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+ # Remainder- 1 to 7 bytes of A
+ .p2align 3
+5:
+ AND r5, r5, 7 // kc remainder 1 to 7
+6:
+ VLD1.8 {d0}, [r3]
+ VLD1.8 {d8}, [r9]!
+ VLD1.8 {d2}, [r12]
+ VLD1.8 {d4}, [r10]
+ VLD1.8 {d6}, [r0]
+
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+ CMP r5, 2
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[1]
+ VMLAL.S16 q9, d9, d0[1]
+ VMLAL.S16 q10, d8, d2[1]
+ VMLAL.S16 q11, d9, d2[1]
+ VMLAL.S16 q12, d8, d4[1]
+ VMLAL.S16 q13, d9, d4[1]
+ VMLAL.S16 q14, d8, d6[1]
+ VMLAL.S16 q15, d9, d6[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+ CMP r5, 4
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[3]
+ VMLAL.S16 q9, d9, d0[3]
+ VMLAL.S16 q10, d8, d2[3]
+ VMLAL.S16 q11, d9, d2[3]
+ VMLAL.S16 q12, d8, d4[3]
+ VMLAL.S16 q13, d9, d4[3]
+ VMLAL.S16 q14, d8, d6[3]
+ VMLAL.S16 q15, d9, d6[3]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+ CMP r5, 6
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[1]
+ VMLAL.S16 q9, d9, d1[1]
+ VMLAL.S16 q10, d8, d3[1]
+ VMLAL.S16 q11, d9, d3[1]
+ VMLAL.S16 q12, d8, d5[1]
+ VMLAL.S16 q13, d9, d5[1]
+ VMLAL.S16 q14, d8, d7[1]
+ VMLAL.S16 q15, d9, d7[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+ B 4b
+
+ # Store odd width
+ .p2align 3
+7:
+ TST r1, 4
+ BEQ 8f
+ VST1.32 {d3[0]}, [r6]!
+ VST1.32 {d2[0]}, [r8]!
+ VST1.32 {d1[0]}, [r4]!
+ VST1.32 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 4
+ VEXT.8 q1, q1, q1, 4
+8:
+ TST r1, 2
+ BEQ 9f
+ VST1.16 {d3[0]}, [r6]!
+ VST1.16 {d2[0]}, [r8]!
+ VST1.16 {d1[0]}, [r4]!
+ VST1.16 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 2
+ VEXT.8 q1, q1, q1, 2
+
+9:
+ TST r1, 1
+ BEQ 10f
+ VST1.8 {d3[0]}, [r6]
+ VST1.8 {d2[0]}, [r8]
+ VST1.8 {d1[0]}, [r4]
+ VST1.8 {d0[0]}, [r11]
+
+10:
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+END_FUNCTION xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7
+
+#ifdef __ELF__
+.section ".note.GNU-stack","",%progbits
+#endif
diff --git a/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-cortex-a35.S b/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-cortex-a35.S
new file mode 100644
index 000000000..9fa909c24
--- /dev/null
+++ b/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-cortex-a35.S
@@ -0,0 +1,572 @@
+// Auto-generated file. Do not edit!
+// Template: src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in
+// Generator: tools/xngen
+//
+// Copyright 2021 Google LLC
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree.
+
+
+#include <xnnpack/assembly.h>
+
+.syntax unified
+
+// void xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_cortex_a35(
+// size_t mr, (r0)
+// size_t nc, r1 -> sp + 56
+// size_t kc, (r2) -> r5 -> sp + 60
+// size_t ks, (r3) -> sp + 64 -> r14
+// const int8_t**restrict a, sp + 104 -> r2
+// const void*restrict w, sp + 108 -> r9
+// int8_t*restrict c, sp + 112 -> r11
+// size_t cm_stride, sp + 116 -> (r6)
+// size_t cn_stride, sp + 120 -> (r7)
+// size_t a_offset, sp + 124 -> (r5)
+// const int8_t* zero, sp + 128 -> (r7)
+// xnn_qs8_minmax_params*params); sp + 132 -> (r5)
+
+// d8-d15, r4-r11,r14(lr) need to be preserved if used. r13(sp),r15(pc) are reserved.
+
+// Register usage
+// A0 r3 d0-d1 q0
+// A1 r12 d2-d3 q1
+// A2 r10 d4-d5 q2
+// A3 r0 d6-d7 q3
+
+// B r9 d8-d9 q4 q5
+
+// C0 r11 d16-d17 q8 d18-d19 q9
+// C1 r4 d20-d21 q10 d22-d23 q11
+// C2 r8 d24-d25 q12 d26-d27 q13
+// C3 r6 d28-d29 q14 d30-d31 q15
+
+// Unused d15
+
+// params structure is 4 bytes
+// struct {
+// int16_t output_zero_point; d13[2]
+// int8_t output_min; d13[6]
+// int8_t output_max; d13[7]
+// } xnn_qs8_minmax_params.neonv8;
+
+BEGIN_FUNCTION xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_cortex_a35
+ # Push 104 bytes
+ # r1, r2 will be reloaded in outer loop. r3 is ks
+ PUSH {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, lr} // +48
+ SUB sp, sp, 8 // +8
+ VPUSH {d8-d13} // +48 = 104
+
+ LDR r11, [sp, 112] // c
+ LDR r6, [sp, 116] // cm_stride
+ LDR r2, [sp, 104] // a
+ LDR r9, [sp, 108] // w
+ LDR r5, [sp, 132] // params
+ MOV r14, r3 // p = ks
+
+ # Clamp C pointers
+ CMP r0, 2 // if mr >= 2
+ ADD r4, r11, r6 // c1 = c0 + cm_stride
+ MOVLO r4, r11 // c1
+ // if mr > 2
+ ADD r8, r4, r6 // c2 = c1 + cm_stride
+ MOVLS r8, r4 // c2
+ CMP r0, 4 // if mr >=4
+ ADD r6, r8, r6 // c3 = c2 + cm_stride
+ MOVLO r6, r8 // c3
+
+ # Load params values
+ VLD1.32 {d13[]}, [r5] // QC8 neonv8 params
+
+
+ .p2align 3
+0:
+ # Load initial bias from w into accumulators
+ VLDM r9!, {d16-d19} // Bias
+ VMOV q10, q8
+ VMOV q11, q9
+ STR r1, [sp, 56] // save nc
+ VMOV q12, q8
+ VMOV q13, q9
+ VMOV q14, q8
+ VMOV q15, q9
+
+ .p2align 3
+1:
+ # Load next 4 A pointers
+ LDR r3, [r2, 0]
+ LDR r12, [r2, 4]
+ LDR r10, [r2, 8]
+ LDR r0, [r2, 12]
+
+ # Add a_offset
+ LDR r5, [sp, 124] // a_offset
+ LDR r7, [sp, 128] // zero
+ ADD r2, r2, 16
+ CMP r3, r7 // if a0 == zero
+ ADD r3, r3, r5 // a0 += a_offset
+ MOVEQ r3, r7 // a0 = zero, else += a0 + a_offset
+ CMP r12, r7 // if a1 == zero
+ ADD r12, r12, r5 // a1 += a_offset
+ MOVEQ r12, r7 // a1 = zero, else += a1 + a_offset
+ CMP r10, r7 // if a2 == zero
+ ADD r10, r10, r5 // a2 += a_offset
+ MOVEQ r10, r7 // a2 = zero, else += a2 + a_offset
+ CMP r0, r7 // if a3 == zero
+ ADD r0, r0, r5 // a3 += a_offset
+ LDR r5, [sp, 60] // kc
+ MOVEQ r0, r7 // a3 = zero, else += a3 + a_offset
+ SUBS r5, r5, 8 // kc - 8
+ BLO 5f // less than 8 channels?
+
+ // Prologue - load 4A's and B0
+ VLD1.8 {d0}, [r3]! // A0
+ VLD1.8 {d2}, [r12]! // A1
+ VLD1.8 {d4}, [r10]! // A2
+ VLD1.8 {d6}, [r0]! // A3
+ VLD1.8 {d8}, [r9]! // B0
+
+ SUBS r5, r5, 8 // k = k - 8
+ BLO 3f // less than 8 channels?
+
+ // Main loop - 8 bytes
+ // 64 bytes for weights.
+ // 5 VMOVL = 4 A and 1 B = 5 cycles
+ // 7 blocks with VLD B, VMOVL, 8 VMLA = 10 cycles
+ // 1 blocks with VLD B, VMLA = 9 cycles
+ // total = 84 cycles
+ .p2align 3
+2:
+ // Extend - 5 cycles
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ // BLOCK 0 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ // BLOCK 1 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ // BLOCK 2 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ // BLOCK 3 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VLD1.8 {d0}, [r3]! // A0
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ // BLOCK 4 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VLD1.8 {d2}, [r12]! // A1
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ // BLOCK 5 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VLD1.8 {d4}, [r10]! // A2
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ // BLOCK 6 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VLD1.8 {d6}, [r0]! // A3
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ // BLOCK 7 - 9 cycles
+ VLD1.8 {d8}, [r9]! // B0
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ SUBS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+ BHS 2b
+
+ // Epilogue
+
+ .p2align 3
+3:
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ ADDS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+
+ # Is there a remainder?- 1-7 bytes of A
+ BNE 6f
+
+4:
+ # ks loop
+ SUBS r14, r14, 16 // ks -= MR * sizeof(void*)
+ BHI 1b
+
+ LDR r7, [sp, 120] // cn_stride
+ LDR r14, [sp, 64] // p = ks
+
+ # QC8 FP32 quantization
+ VLD1.8 {q0-q1}, [r9]!
+
+ VCVT.F32.S32 q8, q8
+ VCVT.F32.S32 q9, q9
+ VCVT.F32.S32 q10, q10
+ VCVT.F32.S32 q11, q11
+ VCVT.F32.S32 q12, q12
+ VCVT.F32.S32 q13, q13
+ VCVT.F32.S32 q14, q14
+ VCVT.F32.S32 q15, q15
+
+ VMUL.F32 q8, q8, q0 // multiplier
+ VMUL.F32 q9, q9, q1
+ VMUL.F32 q10, q10, q0
+ VMUL.F32 q11, q11, q1
+ VMUL.F32 q12, q12, q0
+ VMUL.F32 q13, q13, q1
+ VMUL.F32 q14, q14, q0
+ VMUL.F32 q15, q15, q1
+
+ VCVTN.S32.F32 q8, q8
+ VCVTN.S32.F32 q9, q9
+ VCVTN.S32.F32 q10, q10
+ VCVTN.S32.F32 q11, q11
+ VCVTN.S32.F32 q12, q12
+ VCVTN.S32.F32 q13, q13
+ VCVTN.S32.F32 q14, q14
+ VCVTN.S32.F32 q15, q15
+
+ VDUP.16 q0, d13[2] // output_zero_point
+
+ VQMOVN.S32 d16, q8
+ VQMOVN.S32 d17, q9
+ VQMOVN.S32 d18, q10
+ VQMOVN.S32 d19, q11
+ VQMOVN.S32 d20, q12
+ VQMOVN.S32 d21, q13
+ VQMOVN.S32 d22, q14
+ VQMOVN.S32 d23, q15
+
+ VQADD.S16 q8, q8, q0
+ VQADD.S16 q9, q9, q0
+ VQADD.S16 q10, q10, q0
+ VQADD.S16 q11, q11, q0
+
+ LDR r1, [sp, 56] // restore nc
+ VDUP.8 q12, d13[6] // output_min
+
+ VQMOVN.S16 d0, q8
+ VQMOVN.S16 d1, q9
+ VQMOVN.S16 d2, q10
+ VQMOVN.S16 d3, q11
+
+ VDUP.8 q13, d13[7] // output_max
+
+ VMAX.S8 q0, q0, q12
+ VMAX.S8 q1, q1, q12
+
+ SUBS r1, r1, 8 // nc -= 8
+
+ VMIN.S8 q0, q0, q13
+ VMIN.S8 q1, q1, q13
+
+ # Store full 4 x 8
+ BLO 7f
+ VST1.8 {d3}, [r6], r7
+ VST1.8 {d2}, [r8], r7
+ VST1.8 {d1}, [r4], r7
+ VST1.8 {d0}, [r11], r7
+ SUB r2, r2, r14 // a -= ks
+ BHI 0b
+
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+ # Remainder- 1 to 7 bytes of A
+ .p2align 3
+5:
+ AND r5, r5, 7 // kc remainder 1 to 7
+6:
+ VLD1.8 {d0}, [r3]
+ VLD1.8 {d8}, [r9]!
+ VLD1.8 {d2}, [r12]
+ VLD1.8 {d4}, [r10]
+ VLD1.8 {d6}, [r0]
+
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+ CMP r5, 2
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[1]
+ VMLAL.S16 q9, d9, d0[1]
+ VMLAL.S16 q10, d8, d2[1]
+ VMLAL.S16 q11, d9, d2[1]
+ VMLAL.S16 q12, d8, d4[1]
+ VMLAL.S16 q13, d9, d4[1]
+ VMLAL.S16 q14, d8, d6[1]
+ VMLAL.S16 q15, d9, d6[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+ CMP r5, 4
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[3]
+ VMLAL.S16 q9, d9, d0[3]
+ VMLAL.S16 q10, d8, d2[3]
+ VMLAL.S16 q11, d9, d2[3]
+ VMLAL.S16 q12, d8, d4[3]
+ VMLAL.S16 q13, d9, d4[3]
+ VMLAL.S16 q14, d8, d6[3]
+ VMLAL.S16 q15, d9, d6[3]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+ CMP r5, 6
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[1]
+ VMLAL.S16 q9, d9, d1[1]
+ VMLAL.S16 q10, d8, d3[1]
+ VMLAL.S16 q11, d9, d3[1]
+ VMLAL.S16 q12, d8, d5[1]
+ VMLAL.S16 q13, d9, d5[1]
+ VMLAL.S16 q14, d8, d7[1]
+ VMLAL.S16 q15, d9, d7[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+ B 4b
+
+ # Store odd width
+ .p2align 3
+7:
+ TST r1, 4
+ BEQ 8f
+ VST1.32 {d3[0]}, [r6]!
+ VST1.32 {d2[0]}, [r8]!
+ VST1.32 {d1[0]}, [r4]!
+ VST1.32 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 4
+ VEXT.8 q1, q1, q1, 4
+8:
+ TST r1, 2
+ BEQ 9f
+ VST1.16 {d3[0]}, [r6]!
+ VST1.16 {d2[0]}, [r8]!
+ VST1.16 {d1[0]}, [r4]!
+ VST1.16 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 2
+ VEXT.8 q1, q1, q1, 2
+
+9:
+ TST r1, 1
+ BEQ 10f
+ VST1.8 {d3[0]}, [r6]
+ VST1.8 {d2[0]}, [r8]
+ VST1.8 {d1[0]}, [r4]
+ VST1.8 {d0[0]}, [r11]
+
+10:
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+END_FUNCTION xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_cortex_a35
+
+#ifdef __ELF__
+.section ".note.GNU-stack","",%progbits
+#endif
diff --git a/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-prfm-cortex-a35.S b/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-prfm-cortex-a35.S
new file mode 100644
index 000000000..21c5cbbe2
--- /dev/null
+++ b/src/qc8-igemm/gen/4x8-minmax-fp32-aarch32-neonv8-mlal-lane-prfm-cortex-a35.S
@@ -0,0 +1,579 @@
+// Auto-generated file. Do not edit!
+// Template: src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in
+// Generator: tools/xngen
+//
+// Copyright 2021 Google LLC
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree.
+
+
+#include <xnnpack/assembly.h>
+
+.syntax unified
+
+// void xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_prfm_cortex_a35(
+// size_t mr, (r0)
+// size_t nc, r1 -> sp + 56
+// size_t kc, (r2) -> r5 -> sp + 60
+// size_t ks, (r3) -> sp + 64 -> r14
+// const int8_t**restrict a, sp + 104 -> r2
+// const void*restrict w, sp + 108 -> r9
+// int8_t*restrict c, sp + 112 -> r11
+// size_t cm_stride, sp + 116 -> (r6)
+// size_t cn_stride, sp + 120 -> (r7)
+// size_t a_offset, sp + 124 -> (r5)
+// const int8_t* zero, sp + 128 -> (r7)
+// xnn_qs8_minmax_params*params); sp + 132 -> (r5)
+
+// d8-d15, r4-r11,r14(lr) need to be preserved if used. r13(sp),r15(pc) are reserved.
+
+// Register usage
+// A0 r3 d0-d1 q0
+// A1 r12 d2-d3 q1
+// A2 r10 d4-d5 q2
+// A3 r0 d6-d7 q3
+
+// B r9 d8-d9 q4 q5
+
+// C0 r11 d16-d17 q8 d18-d19 q9
+// C1 r4 d20-d21 q10 d22-d23 q11
+// C2 r8 d24-d25 q12 d26-d27 q13
+// C3 r6 d28-d29 q14 d30-d31 q15
+
+// Unused d15
+
+// params structure is 4 bytes
+// struct {
+// int16_t output_zero_point; d13[2]
+// int8_t output_min; d13[6]
+// int8_t output_max; d13[7]
+// } xnn_qs8_minmax_params.neonv8;
+
+BEGIN_FUNCTION xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_prfm_cortex_a35
+ # Push 104 bytes
+ # r1, r2 will be reloaded in outer loop. r3 is ks
+ PUSH {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, lr} // +48
+ SUB sp, sp, 8 // +8
+ VPUSH {d8-d13} // +48 = 104
+
+ LDR r11, [sp, 112] // c
+ LDR r6, [sp, 116] // cm_stride
+ LDR r2, [sp, 104] // a
+ LDR r9, [sp, 108] // w
+ LDR r5, [sp, 132] // params
+ MOV r14, r3 // p = ks
+
+ # Clamp C pointers
+ CMP r0, 2 // if mr >= 2
+ ADD r4, r11, r6 // c1 = c0 + cm_stride
+ MOVLO r4, r11 // c1
+ // if mr > 2
+ ADD r8, r4, r6 // c2 = c1 + cm_stride
+ MOVLS r8, r4 // c2
+ CMP r0, 4 // if mr >=4
+ ADD r6, r8, r6 // c3 = c2 + cm_stride
+ MOVLO r6, r8 // c3
+
+ # Load params values
+ VLD1.32 {d13[]}, [r5] // QC8 neonv8 params
+
+ PLD [r9, 64] // Prefetch B
+ PLD [r9, 128]
+ PLD [r9, 192]
+ PLD [r9, 256]
+ PLD [r9, 320]
+ PLD [r9, 384]
+
+ .p2align 3
+0:
+ # Load initial bias from w into accumulators
+ VLDM r9!, {d16-d19} // Bias
+ VMOV q10, q8
+ VMOV q11, q9
+ STR r1, [sp, 56] // save nc
+ VMOV q12, q8
+ VMOV q13, q9
+ VMOV q14, q8
+ VMOV q15, q9
+
+ .p2align 3
+1:
+ # Load next 4 A pointers
+ LDR r3, [r2, 0]
+ LDR r12, [r2, 4]
+ LDR r10, [r2, 8]
+ LDR r0, [r2, 12]
+
+ # Add a_offset
+ LDR r5, [sp, 124] // a_offset
+ LDR r7, [sp, 128] // zero
+ ADD r2, r2, 16
+ CMP r3, r7 // if a0 == zero
+ ADD r3, r3, r5 // a0 += a_offset
+ MOVEQ r3, r7 // a0 = zero, else += a0 + a_offset
+ CMP r12, r7 // if a1 == zero
+ ADD r12, r12, r5 // a1 += a_offset
+ MOVEQ r12, r7 // a1 = zero, else += a1 + a_offset
+ CMP r10, r7 // if a2 == zero
+ ADD r10, r10, r5 // a2 += a_offset
+ MOVEQ r10, r7 // a2 = zero, else += a2 + a_offset
+ CMP r0, r7 // if a3 == zero
+ ADD r0, r0, r5 // a3 += a_offset
+ LDR r5, [sp, 60] // kc
+ MOVEQ r0, r7 // a3 = zero, else += a3 + a_offset
+ SUBS r5, r5, 8 // kc - 8
+ BLO 5f // less than 8 channels?
+
+ // Prologue - load 4A's and B0
+ VLD1.8 {d0}, [r3]! // A0
+ VLD1.8 {d2}, [r12]! // A1
+ VLD1.8 {d4}, [r10]! // A2
+ VLD1.8 {d6}, [r0]! // A3
+ VLD1.8 {d8}, [r9]! // B0
+
+ SUBS r5, r5, 8 // k = k - 8
+ BLO 3f // less than 8 channels?
+
+ // Main loop - 8 bytes
+ // 64 bytes for weights.
+ // 5 VMOVL = 4 A and 1 B = 5 cycles
+ // 7 blocks with VLD B, VMOVL, 8 VMLA = 10 cycles
+ // 1 blocks with VLD B, VMLA = 9 cycles
+ // total = 84 cycles
+ .p2align 3
+2:
+ // Extend - 5 cycles
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ PLD [r9, 448]
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ // BLOCK 0 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ // BLOCK 1 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ // BLOCK 2 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ // BLOCK 3 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VLD1.8 {d0}, [r3]! // A0
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ // BLOCK 4 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VLD1.8 {d2}, [r12]! // A1
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ // BLOCK 5 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VLD1.8 {d4}, [r10]! // A2
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ // BLOCK 6 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VLD1.8 {d6}, [r0]! // A3
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ // BLOCK 7 - 9 cycles
+ VLD1.8 {d8}, [r9]! // B0
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ SUBS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+ BHS 2b
+
+ // Epilogue
+
+ .p2align 3
+3:
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ ADDS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+
+ # Is there a remainder?- 1-7 bytes of A
+ BNE 6f
+
+4:
+ # ks loop
+ SUBS r14, r14, 16 // ks -= MR * sizeof(void*)
+ BHI 1b
+
+ LDR r7, [sp, 120] // cn_stride
+ LDR r14, [sp, 64] // p = ks
+
+ # QC8 FP32 quantization
+ VLD1.8 {q0-q1}, [r9]!
+
+ VCVT.F32.S32 q8, q8
+ VCVT.F32.S32 q9, q9
+ VCVT.F32.S32 q10, q10
+ VCVT.F32.S32 q11, q11
+ VCVT.F32.S32 q12, q12
+ VCVT.F32.S32 q13, q13
+ VCVT.F32.S32 q14, q14
+ VCVT.F32.S32 q15, q15
+
+ VMUL.F32 q8, q8, q0 // multiplier
+ VMUL.F32 q9, q9, q1
+ VMUL.F32 q10, q10, q0
+ VMUL.F32 q11, q11, q1
+ VMUL.F32 q12, q12, q0
+ VMUL.F32 q13, q13, q1
+ VMUL.F32 q14, q14, q0
+ VMUL.F32 q15, q15, q1
+
+ VCVTN.S32.F32 q8, q8
+ VCVTN.S32.F32 q9, q9
+ VCVTN.S32.F32 q10, q10
+ VCVTN.S32.F32 q11, q11
+ VCVTN.S32.F32 q12, q12
+ VCVTN.S32.F32 q13, q13
+ VCVTN.S32.F32 q14, q14
+ VCVTN.S32.F32 q15, q15
+
+ VDUP.16 q0, d13[2] // output_zero_point
+
+ VQMOVN.S32 d16, q8
+ VQMOVN.S32 d17, q9
+ VQMOVN.S32 d18, q10
+ VQMOVN.S32 d19, q11
+ VQMOVN.S32 d20, q12
+ VQMOVN.S32 d21, q13
+ VQMOVN.S32 d22, q14
+ VQMOVN.S32 d23, q15
+
+ VQADD.S16 q8, q8, q0
+ VQADD.S16 q9, q9, q0
+ VQADD.S16 q10, q10, q0
+ VQADD.S16 q11, q11, q0
+
+ LDR r1, [sp, 56] // restore nc
+ VDUP.8 q12, d13[6] // output_min
+
+ VQMOVN.S16 d0, q8
+ VQMOVN.S16 d1, q9
+ VQMOVN.S16 d2, q10
+ VQMOVN.S16 d3, q11
+
+ VDUP.8 q13, d13[7] // output_max
+
+ VMAX.S8 q0, q0, q12
+ VMAX.S8 q1, q1, q12
+
+ SUBS r1, r1, 8 // nc -= 8
+
+ VMIN.S8 q0, q0, q13
+ VMIN.S8 q1, q1, q13
+
+ # Store full 4 x 8
+ BLO 7f
+ VST1.8 {d3}, [r6], r7
+ VST1.8 {d2}, [r8], r7
+ VST1.8 {d1}, [r4], r7
+ VST1.8 {d0}, [r11], r7
+ SUB r2, r2, r14 // a -= ks
+ BHI 0b
+
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+ # Remainder- 1 to 7 bytes of A
+ .p2align 3
+5:
+ AND r5, r5, 7 // kc remainder 1 to 7
+6:
+ VLD1.8 {d0}, [r3]
+ VLD1.8 {d8}, [r9]!
+ VLD1.8 {d2}, [r12]
+ VLD1.8 {d4}, [r10]
+ VLD1.8 {d6}, [r0]
+
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+ CMP r5, 2
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[1]
+ VMLAL.S16 q9, d9, d0[1]
+ VMLAL.S16 q10, d8, d2[1]
+ VMLAL.S16 q11, d9, d2[1]
+ VMLAL.S16 q12, d8, d4[1]
+ VMLAL.S16 q13, d9, d4[1]
+ VMLAL.S16 q14, d8, d6[1]
+ VMLAL.S16 q15, d9, d6[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+ CMP r5, 4
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[3]
+ VMLAL.S16 q9, d9, d0[3]
+ VMLAL.S16 q10, d8, d2[3]
+ VMLAL.S16 q11, d9, d2[3]
+ VMLAL.S16 q12, d8, d4[3]
+ VMLAL.S16 q13, d9, d4[3]
+ VMLAL.S16 q14, d8, d6[3]
+ VMLAL.S16 q15, d9, d6[3]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+ CMP r5, 6
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[1]
+ VMLAL.S16 q9, d9, d1[1]
+ VMLAL.S16 q10, d8, d3[1]
+ VMLAL.S16 q11, d9, d3[1]
+ VMLAL.S16 q12, d8, d5[1]
+ VMLAL.S16 q13, d9, d5[1]
+ VMLAL.S16 q14, d8, d7[1]
+ VMLAL.S16 q15, d9, d7[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+ B 4b
+
+ # Store odd width
+ .p2align 3
+7:
+ TST r1, 4
+ BEQ 8f
+ VST1.32 {d3[0]}, [r6]!
+ VST1.32 {d2[0]}, [r8]!
+ VST1.32 {d1[0]}, [r4]!
+ VST1.32 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 4
+ VEXT.8 q1, q1, q1, 4
+8:
+ TST r1, 2
+ BEQ 9f
+ VST1.16 {d3[0]}, [r6]!
+ VST1.16 {d2[0]}, [r8]!
+ VST1.16 {d1[0]}, [r4]!
+ VST1.16 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 2
+ VEXT.8 q1, q1, q1, 2
+
+9:
+ TST r1, 1
+ BEQ 10f
+ VST1.8 {d3[0]}, [r6]
+ VST1.8 {d2[0]}, [r8]
+ VST1.8 {d1[0]}, [r4]
+ VST1.8 {d0[0]}, [r11]
+
+10:
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+END_FUNCTION xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_prfm_cortex_a35
+
+#ifdef __ELF__
+.section ".note.GNU-stack","",%progbits
+#endif
diff --git a/src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in b/src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in
new file mode 100644
index 000000000..5d2bc52c5
--- /dev/null
+++ b/src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in
@@ -0,0 +1,784 @@
+// Copyright 2021 Google LLC
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree.
+
+$assert REQUANTIZATION in ["FP32", "RNDNU"]
+$assert not CHANNELWISE or REQUANTIZATION == "FP32"
+$assert DATATYPE in ["QC8", "QS8", "QU8"]
+$assert DATATYPE != "QC8" or REQUANTIZATION == "FP32"
+
+#include <xnnpack/assembly.h>
+
+.syntax unified
+
+$PARAMS_UNION = "xnn_qs8_minmax_params" if CHANNELWISE else "xnn_qs8_conv_minmax_params"
+$ISA = "neonv8" if ARMV8 else "neon"
+$CPU = "a35" if ARMV8 else "a7"
+$XMIN = "VMIN.U8" if DATATYPE == "QU8" else "VMIN.S8"
+$XMAX = "VMAX.U8" if DATATYPE == "QU8" else "VMAX.S8"
+$XXTL = "VMOVL.U8" if DATATYPE == "QU8" else "VMOVL.S8"
+$SQXTXN = "VQMOVUN.S16" if DATATYPE == "QU8" else "VQMOVN.S16"
+$XINT8_T = "uint8_t" if DATATYPE == "QU8" else "int8_t"
+// void xnn_${DATATYPE.lower()}_igemm_minmax_${REQUANTIZATION.lower()}_ukernel_4x8__aarch32_${ISA}_mlal_lane${"_prfm" if PREFETCH else ""}_cortex_${CPU}(
+// size_t mr, (r0)
+// size_t nc, r1 -> sp + 56
+// size_t kc, (r2) -> r5 -> sp + 60
+// size_t ks, (r3) -> sp + 64 -> r14
+// const ${XINT8_T}**restrict a, sp + 104 -> r2
+// const void*restrict w, sp + 108 -> r9
+// ${XINT8_T}*restrict c, sp + 112 -> r11
+// size_t cm_stride, sp + 116 -> (r6)
+// size_t cn_stride, sp + 120 -> (r7)
+// size_t a_offset, sp + 124 -> (r5)
+// const ${XINT8_T}* zero, sp + 128 -> (r7)
+// ${PARAMS_UNION}*params); sp + 132 -> (r5)
+
+// d8-d15, r4-r11,r14(lr) need to be preserved if used. r13(sp),r15(pc) are reserved.
+
+// Register usage
+// A0 r3 d0-d1 q0
+// A1 r12 d2-d3 q1
+// A2 r10 d4-d5 q2
+// A3 r0 d6-d7 q3
+
+// B r9 d8-d9 q4 q5
+
+// C0 r11 d16-d17 q8 d18-d19 q9
+// C1 r4 d20-d21 q10 d22-d23 q11
+// C2 r8 d24-d25 q12 d26-d27 q13
+// C3 r6 d28-d29 q14 d30-d31 q15
+
+// Unused d15
+
+$if REQUANTIZATION == "RNDNU" and DATATYPE != "QU8":
+ // params structure is 16 bytes
+ // struct {
+ // int32_t right_pre_shift; d12[0]
+ // int32_t multiplier; d12[1]
+ // int32_t right_post_shift; d13[0]
+ // int16_t output_zero_point; d13[2]
+ // int8_t output_min; d13[6]
+ // int8_t output_max; d13[7]
+ // } rndnu_neon;
+$elif REQUANTIZATION == "RNDNU" and DATATYPE == "QU8":
+ // params structure is 20 bytes
+ // struct {
+ // uint8_t kernel_zero_point[4]; d14
+ // int32_t right_pre_shift; d12[0]
+ // int32_t multiplier; d12[1]
+ // int32_t right_post_shift; d13[0]
+ // int16_t output_zero_point; d13[2]
+ // uint8_t output_min; d13[6]
+ // uint8_t output_max; d13[7]
+ // } rndnu_neon;
+$elif DATATYPE == "QC8" and not ARMV8:
+ // params structure is 10 bytes
+ // struct {
+ // float magic_bias; d12[0]
+ // int32_t magic_bias_less_output_zero_point; d12[1]
+ // int8_t output_min; d13[6]
+ // int8_t output_max; d13[7]
+ // } xnn_qs8_minmax_params.neon;
+$else:
+ // params structure is 4 bytes
+ // struct {
+ // int16_t output_zero_point; d13[2]
+ // int8_t output_min; d13[6]
+ // int8_t output_max; d13[7]
+ // } xnn_qs8_minmax_params.neonv8;
+
+BEGIN_FUNCTION xnn_${DATATYPE.lower()}_igemm_minmax_${REQUANTIZATION.lower()}_ukernel_4x8__aarch32_${ISA}_mlal_lane${"_prfm" if PREFETCH else ""}_cortex_${CPU}
+ # Push 104 bytes
+ # r1, r2 will be reloaded in outer loop. r3 is ks
+ PUSH {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, lr} // +48
+ $if DATATYPE == "QU8":
+ VPUSH {d8-d14} // +56 = 104
+ $else:
+ SUB sp, sp, 8 // +8
+ VPUSH {d8-d13} // +48 = 104
+
+ LDR r11, [sp, 112] // c
+ LDR r6, [sp, 116] // cm_stride
+ LDR r2, [sp, 104] // a
+ LDR r9, [sp, 108] // w
+ LDR r5, [sp, 132] // params
+ MOV r14, r3 // p = ks
+
+ # Clamp C pointers
+ CMP r0, 2 // if mr >= 2
+ ADD r4, r11, r6 // c1 = c0 + cm_stride
+ MOVLO r4, r11 // c1
+ // if mr > 2
+ ADD r8, r4, r6 // c2 = c1 + cm_stride
+ MOVLS r8, r4 // c2
+ CMP r0, 4 // if mr >=4
+ ADD r6, r8, r6 // c3 = c2 + cm_stride
+ MOVLO r6, r8 // c3
+
+ # Load params values
+ $if DATATYPE == "QU8":
+ VLD1.32 {d14[]}, [r5]! // QU8 kernel_zero_point
+ $if REQUANTIZATION == "RNDNU":
+ VLDM r5, {d12-d13} // RNDNU params
+ $elif DATATYPE == "QC8" and ARMV8:
+ VLD1.32 {d13[]}, [r5] // QC8 neonv8 params
+ $elif DATATYPE == "QC8" and not ARMV8:
+ VLDM r5!, {d12} // QC8 neon params
+ VLD1.16 {d13[]}, [r5]
+
+ $if PREFETCH:
+ PLD [r9, 64] // Prefetch B
+ PLD [r9, 128]
+ PLD [r9, 192]
+ PLD [r9, 256]
+ PLD [r9, 320]
+ PLD [r9, 384]
+
+ .p2align 3
+0:
+ # Load initial bias from w into accumulators
+ VLDM r9!, {d16-d19} // Bias
+ VMOV q10, q8
+ VMOV q11, q9
+ STR r1, [sp, 56] // save nc
+ VMOV q12, q8
+ VMOV q13, q9
+ VMOV q14, q8
+ VMOV q15, q9
+
+ .p2align 3
+1:
+ # Load next 4 A pointers
+ LDR r3, [r2, 0]
+ LDR r12, [r2, 4]
+ LDR r10, [r2, 8]
+ LDR r0, [r2, 12]
+
+ # Add a_offset
+ LDR r5, [sp, 124] // a_offset
+ LDR r7, [sp, 128] // zero
+ ADD r2, r2, 16
+ CMP r3, r7 // if a0 == zero
+ ADD r3, r3, r5 // a0 += a_offset
+ MOVEQ r3, r7 // a0 = zero, else += a0 + a_offset
+ CMP r12, r7 // if a1 == zero
+ ADD r12, r12, r5 // a1 += a_offset
+ MOVEQ r12, r7 // a1 = zero, else += a1 + a_offset
+ CMP r10, r7 // if a2 == zero
+ ADD r10, r10, r5 // a2 += a_offset
+ MOVEQ r10, r7 // a2 = zero, else += a2 + a_offset
+ CMP r0, r7 // if a3 == zero
+ ADD r0, r0, r5 // a3 += a_offset
+ LDR r5, [sp, 60] // kc
+ MOVEQ r0, r7 // a3 = zero, else += a3 + a_offset
+ SUBS r5, r5, 8 // kc - 8
+ BLO 5f // less than 8 channels?
+
+ // Prologue - load 4A's and B0
+ VLD1.8 {d0}, [r3]! // A0
+ VLD1.8 {d2}, [r12]! // A1
+ VLD1.8 {d4}, [r10]! // A2
+ VLD1.8 {d6}, [r0]! // A3
+ VLD1.8 {d8}, [r9]! // B0
+
+ SUBS r5, r5, 8 // k = k - 8
+ BLO 3f // less than 8 channels?
+
+ // Main loop - 8 bytes
+ // 64 bytes for weights.
+ // 5 VMOVL = 4 A and 1 B = 5 cycles
+ // 7 blocks with VLD B, VMOVL, 8 VMLA = 10 cycles
+ // 1 blocks with VLD B, VMLA = 9 cycles
+ // total = 84 cycles
+ .p2align 3
+2:
+ // Extend - 5 cycles
+ ${XXTL} q0, d0
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ $if PREFETCH:
+ PLD [r9, 448]
+ ${XXTL} q1, d2
+ ${XXTL} q2, d4
+ ${XXTL} q3, d6
+
+ // BLOCK 0 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q5, d10, d14
+ $else:
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ // BLOCK 1 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ // BLOCK 2 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q5, d10, d14
+ $else:
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ // BLOCK 3 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VLD1.8 {d0}, [r3]! // A0
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ // BLOCK 4 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VLD1.8 {d2}, [r12]! // A1
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q5, d10, d14
+ $else:
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ // BLOCK 5 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VLD1.8 {d4}, [r10]! // A2
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ // BLOCK 6 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VLD1.8 {d6}, [r0]! // A3
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q5, d10, d14
+ $else:
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ // BLOCK 7 - 9 cycles
+ VLD1.8 {d8}, [r9]! // B0
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ SUBS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+ BHS 2b
+
+ // Epilogue
+
+ .p2align 3
+3:
+ ${XXTL} q0, d0
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ ${XXTL} q1, d2
+ ${XXTL} q2, d4
+ ${XXTL} q3, d6
+
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q5, d10, d14
+ $else:
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q5, d10, d14
+ $else:
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q5, d10, d14
+ $else:
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q5, d10, d14
+ $else:
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ ADDS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+
+ # Is there a remainder?- 1-7 bytes of A
+ BNE 6f
+
+4:
+ # ks loop
+ SUBS r14, r14, 16 // ks -= MR * sizeof(void*)
+ BHI 1b
+
+ LDR r7, [sp, 120] // cn_stride
+ LDR r14, [sp, 64] // p = ks
+
+ $if REQUANTIZATION == "RNDNU":
+ # RNDNU quantization
+ VDUP.32 q0, d12[0] // right_pre_shift
+
+ VQSHL.S32 q8, q8, q0
+ VQSHL.S32 q9, q9, q0
+ VQSHL.S32 q10, q10, q0
+ VQSHL.S32 q11, q11, q0
+ VQSHL.S32 q12, q12, q0
+ VQSHL.S32 q13, q13, q0
+ VQSHL.S32 q14, q14, q0
+ VQSHL.S32 q15, q15, q0
+
+ VDUP.32 q2, d13[0] // right_post_shift
+
+ VQDMULH.S32 q8, q8, d12[1] // multiplier
+ VQDMULH.S32 q9, q9, d12[1]
+ VQDMULH.S32 q10, q10, d12[1]
+ VQDMULH.S32 q11, q11, d12[1]
+ VQDMULH.S32 q12, q12, d12[1]
+ VQDMULH.S32 q13, q13, d12[1]
+ VQDMULH.S32 q14, q14, d12[1]
+ VQDMULH.S32 q15, q15, d12[1]
+
+ VRSHL.S32 q8, q8, q2
+ VRSHL.S32 q9, q9, q2
+ VRSHL.S32 q10, q10, q2
+ VRSHL.S32 q11, q11, q2
+ VRSHL.S32 q12, q12, q2
+ VRSHL.S32 q13, q13, q2
+ VRSHL.S32 q14, q14, q2
+ VRSHL.S32 q15, q15, q2
+ $elif DATATYPE == "QC8" and ARMV8:
+ # QC8 FP32 quantization
+ VLD1.8 {q0-q1}, [r9]!
+
+ VCVT.F32.S32 q8, q8
+ VCVT.F32.S32 q9, q9
+ VCVT.F32.S32 q10, q10
+ VCVT.F32.S32 q11, q11
+ VCVT.F32.S32 q12, q12
+ VCVT.F32.S32 q13, q13
+ VCVT.F32.S32 q14, q14
+ VCVT.F32.S32 q15, q15
+
+ VMUL.F32 q8, q8, q0 // multiplier
+ VMUL.F32 q9, q9, q1
+ VMUL.F32 q10, q10, q0
+ VMUL.F32 q11, q11, q1
+ VMUL.F32 q12, q12, q0
+ VMUL.F32 q13, q13, q1
+ VMUL.F32 q14, q14, q0
+ VMUL.F32 q15, q15, q1
+
+ VCVTN.S32.F32 q8, q8
+ VCVTN.S32.F32 q9, q9
+ VCVTN.S32.F32 q10, q10
+ VCVTN.S32.F32 q11, q11
+ VCVTN.S32.F32 q12, q12
+ VCVTN.S32.F32 q13, q13
+ VCVTN.S32.F32 q14, q14
+ VCVTN.S32.F32 q15, q15
+ $elif DATATYPE == "QC8" and not ARMV8:
+ # QC8 FP32 quantization
+ VLD1.8 {q0-q1}, [r9]!
+
+ VDUP.32 q2, d12[0] // magic_bias
+ VDUP.32 q3, d12[1] // magic_bias_less_output_zero_point
+
+ VCVT.F32.S32 q8, q8
+ VCVT.F32.S32 q9, q9
+ VCVT.F32.S32 q10, q10
+ VCVT.F32.S32 q11, q11
+ VCVT.F32.S32 q12, q12
+ VCVT.F32.S32 q13, q13
+ VCVT.F32.S32 q14, q14
+ VCVT.F32.S32 q15, q15
+
+ VMUL.F32 q8, q8, q0 // multiplier
+ VMUL.F32 q9, q9, q1
+ VMUL.F32 q10, q10, q0
+ VMUL.F32 q11, q11, q1
+ VMUL.F32 q12, q12, q0
+ VMUL.F32 q13, q13, q1
+ VMUL.F32 q14, q14, q0
+ VMUL.F32 q15, q15, q1
+
+ VADD.F32 q8, q8, q2 // magic_bias
+ VADD.F32 q9, q9, q2
+ VADD.F32 q10, q10, q2
+ VADD.F32 q11, q11, q2
+ VADD.F32 q12, q12, q2
+ VADD.F32 q13, q13, q2
+ VADD.F32 q14, q14, q2
+ VADD.F32 q15, q15, q2
+
+ VQSUB.S32 q8, q8, q3 // magic_bias_less_output_zero_point
+ VQSUB.S32 q9, q9, q3
+ VQSUB.S32 q10, q10, q3
+ VQSUB.S32 q11, q11, q3
+ VQSUB.S32 q12, q12, q3
+ VQSUB.S32 q13, q13, q3
+ VQSUB.S32 q14, q14, q3
+ VQSUB.S32 q15, q15, q3
+
+ $if DATATYPE != "QC8" or ARMV8:
+ VDUP.16 q0, d13[2] // output_zero_point
+
+ VQMOVN.S32 d16, q8
+ VQMOVN.S32 d17, q9
+ VQMOVN.S32 d18, q10
+ VQMOVN.S32 d19, q11
+ VQMOVN.S32 d20, q12
+ VQMOVN.S32 d21, q13
+ VQMOVN.S32 d22, q14
+ VQMOVN.S32 d23, q15
+
+ $if DATATYPE != "QC8" or ARMV8:
+ VQADD.S16 q8, q8, q0
+ VQADD.S16 q9, q9, q0
+ VQADD.S16 q10, q10, q0
+ VQADD.S16 q11, q11, q0
+
+ LDR r1, [sp, 56] // restore nc
+ VDUP.8 q12, d13[6] // output_min
+
+ ${SQXTXN} d0, q8
+ ${SQXTXN} d1, q9
+ ${SQXTXN} d2, q10
+ ${SQXTXN} d3, q11
+
+ VDUP.8 q13, d13[7] // output_max
+
+ ${XMAX} q0, q0, q12
+ ${XMAX} q1, q1, q12
+
+ SUBS r1, r1, 8 // nc -= 8
+
+ ${XMIN} q0, q0, q13
+ ${XMIN} q1, q1, q13
+
+ # Store full 4 x 8
+ BLO 7f
+ VST1.8 {d3}, [r6], r7
+ VST1.8 {d2}, [r8], r7
+ VST1.8 {d1}, [r4], r7
+ VST1.8 {d0}, [r11], r7
+ SUB r2, r2, r14 // a -= ks
+ BHI 0b
+
+ $if DATATYPE == "QU8":
+ VPOP {d8-d14}
+ ADD sp, sp, 12 // skip r1, r2, r3
+ $else:
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+ # Remainder- 1 to 7 bytes of A
+ .p2align 3
+5:
+ AND r5, r5, 7 // kc remainder 1 to 7
+6:
+ VLD1.8 {d0}, [r3]
+ VLD1.8 {d8}, [r9]!
+ VLD1.8 {d2}, [r12]
+ VLD1.8 {d4}, [r10]
+ VLD1.8 {d6}, [r0]
+
+ ${XXTL} q0, d0
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ ${XXTL} q1, d2
+ ${XXTL} q2, d4
+ ${XXTL} q3, d6
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+ CMP r5, 2
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[1]
+ VMLAL.S16 q9, d9, d0[1]
+ VMLAL.S16 q10, d8, d2[1]
+ VMLAL.S16 q11, d9, d2[1]
+ VMLAL.S16 q12, d8, d4[1]
+ VMLAL.S16 q13, d9, d4[1]
+ VMLAL.S16 q14, d8, d6[1]
+ VMLAL.S16 q15, d9, d6[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+ CMP r5, 4
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[3]
+ VMLAL.S16 q9, d9, d0[3]
+ VMLAL.S16 q10, d8, d2[3]
+ VMLAL.S16 q11, d9, d2[3]
+ VMLAL.S16 q12, d8, d4[3]
+ VMLAL.S16 q13, d9, d4[3]
+ VMLAL.S16 q14, d8, d6[3]
+ VMLAL.S16 q15, d9, d6[3]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+ CMP r5, 6
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[1]
+ VMLAL.S16 q9, d9, d1[1]
+ VMLAL.S16 q10, d8, d3[1]
+ VMLAL.S16 q11, d9, d3[1]
+ VMLAL.S16 q12, d8, d5[1]
+ VMLAL.S16 q13, d9, d5[1]
+ VMLAL.S16 q14, d8, d7[1]
+ VMLAL.S16 q15, d9, d7[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ $if DATATYPE == "QU8":
+ VSUBL.U8 q4, d8, d14
+ $else:
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+ B 4b
+
+ # Store odd width
+ .p2align 3
+7:
+ TST r1, 4
+ BEQ 8f
+ VST1.32 {d3[0]}, [r6]!
+ VST1.32 {d2[0]}, [r8]!
+ VST1.32 {d1[0]}, [r4]!
+ VST1.32 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 4
+ VEXT.8 q1, q1, q1, 4
+8:
+ TST r1, 2
+ BEQ 9f
+ VST1.16 {d3[0]}, [r6]!
+ VST1.16 {d2[0]}, [r8]!
+ VST1.16 {d1[0]}, [r4]!
+ VST1.16 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 2
+ VEXT.8 q1, q1, q1, 2
+
+9:
+ TST r1, 1
+ BEQ 10f
+ VST1.8 {d3[0]}, [r6]
+ VST1.8 {d2[0]}, [r8]
+ VST1.8 {d1[0]}, [r4]
+ VST1.8 {d0[0]}, [r11]
+
+10:
+ $if DATATYPE == "QU8":
+ VPOP {d8-d14}
+ ADD sp, sp, 12 // skip r1, r2, r3
+ $else:
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+END_FUNCTION xnn_${DATATYPE.lower()}_igemm_minmax_${REQUANTIZATION.lower()}_ukernel_4x8__aarch32_${ISA}_mlal_lane${"_prfm" if PREFETCH else ""}_cortex_${CPU}
+
+#ifdef __ELF__
+.section ".note.GNU-stack","",%progbits
+#endif
diff --git a/src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S b/src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S
new file mode 100644
index 000000000..77c0e8c4b
--- /dev/null
+++ b/src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S
@@ -0,0 +1,577 @@
+// Auto-generated file. Do not edit!
+// Template: src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in
+// Generator: tools/xngen
+//
+// Copyright 2021 Google LLC
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree.
+
+
+#include <xnnpack/assembly.h>
+
+.syntax unified
+
+// void xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7(
+// size_t mr, (r0)
+// size_t nc, r1 -> sp + 56
+// size_t kc, (r2) -> r5 -> sp + 60
+// size_t ks, (r3) -> sp + 64 -> r14
+// const int8_t**restrict a, sp + 104 -> r2
+// const void*restrict w, sp + 108 -> r9
+// int8_t*restrict c, sp + 112 -> r11
+// size_t cm_stride, sp + 116 -> (r6)
+// size_t cn_stride, sp + 120 -> (r7)
+// size_t a_offset, sp + 124 -> (r5)
+// const int8_t* zero, sp + 128 -> (r7)
+// xnn_qs8_conv_minmax_params*params); sp + 132 -> (r5)
+
+// d8-d15, r4-r11,r14(lr) need to be preserved if used. r13(sp),r15(pc) are reserved.
+
+// Register usage
+// A0 r3 d0-d1 q0
+// A1 r12 d2-d3 q1
+// A2 r10 d4-d5 q2
+// A3 r0 d6-d7 q3
+
+// B r9 d8-d9 q4 q5
+
+// C0 r11 d16-d17 q8 d18-d19 q9
+// C1 r4 d20-d21 q10 d22-d23 q11
+// C2 r8 d24-d25 q12 d26-d27 q13
+// C3 r6 d28-d29 q14 d30-d31 q15
+
+// Unused d15
+
+// params structure is 16 bytes
+// struct {
+// int32_t right_pre_shift; d12[0]
+// int32_t multiplier; d12[1]
+// int32_t right_post_shift; d13[0]
+// int16_t output_zero_point; d13[2]
+// int8_t output_min; d13[6]
+// int8_t output_max; d13[7]
+// } rndnu_neon;
+
+BEGIN_FUNCTION xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7
+ # Push 104 bytes
+ # r1, r2 will be reloaded in outer loop. r3 is ks
+ PUSH {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, lr} // +48
+ SUB sp, sp, 8 // +8
+ VPUSH {d8-d13} // +48 = 104
+
+ LDR r11, [sp, 112] // c
+ LDR r6, [sp, 116] // cm_stride
+ LDR r2, [sp, 104] // a
+ LDR r9, [sp, 108] // w
+ LDR r5, [sp, 132] // params
+ MOV r14, r3 // p = ks
+
+ # Clamp C pointers
+ CMP r0, 2 // if mr >= 2
+ ADD r4, r11, r6 // c1 = c0 + cm_stride
+ MOVLO r4, r11 // c1
+ // if mr > 2
+ ADD r8, r4, r6 // c2 = c1 + cm_stride
+ MOVLS r8, r4 // c2
+ CMP r0, 4 // if mr >=4
+ ADD r6, r8, r6 // c3 = c2 + cm_stride
+ MOVLO r6, r8 // c3
+
+ # Load params values
+ VLDM r5, {d12-d13} // RNDNU params
+
+
+ .p2align 3
+0:
+ # Load initial bias from w into accumulators
+ VLDM r9!, {d16-d19} // Bias
+ VMOV q10, q8
+ VMOV q11, q9
+ STR r1, [sp, 56] // save nc
+ VMOV q12, q8
+ VMOV q13, q9
+ VMOV q14, q8
+ VMOV q15, q9
+
+ .p2align 3
+1:
+ # Load next 4 A pointers
+ LDR r3, [r2, 0]
+ LDR r12, [r2, 4]
+ LDR r10, [r2, 8]
+ LDR r0, [r2, 12]
+
+ # Add a_offset
+ LDR r5, [sp, 124] // a_offset
+ LDR r7, [sp, 128] // zero
+ ADD r2, r2, 16
+ CMP r3, r7 // if a0 == zero
+ ADD r3, r3, r5 // a0 += a_offset
+ MOVEQ r3, r7 // a0 = zero, else += a0 + a_offset
+ CMP r12, r7 // if a1 == zero
+ ADD r12, r12, r5 // a1 += a_offset
+ MOVEQ r12, r7 // a1 = zero, else += a1 + a_offset
+ CMP r10, r7 // if a2 == zero
+ ADD r10, r10, r5 // a2 += a_offset
+ MOVEQ r10, r7 // a2 = zero, else += a2 + a_offset
+ CMP r0, r7 // if a3 == zero
+ ADD r0, r0, r5 // a3 += a_offset
+ LDR r5, [sp, 60] // kc
+ MOVEQ r0, r7 // a3 = zero, else += a3 + a_offset
+ SUBS r5, r5, 8 // kc - 8
+ BLO 5f // less than 8 channels?
+
+ // Prologue - load 4A's and B0
+ VLD1.8 {d0}, [r3]! // A0
+ VLD1.8 {d2}, [r12]! // A1
+ VLD1.8 {d4}, [r10]! // A2
+ VLD1.8 {d6}, [r0]! // A3
+ VLD1.8 {d8}, [r9]! // B0
+
+ SUBS r5, r5, 8 // k = k - 8
+ BLO 3f // less than 8 channels?
+
+ // Main loop - 8 bytes
+ // 64 bytes for weights.
+ // 5 VMOVL = 4 A and 1 B = 5 cycles
+ // 7 blocks with VLD B, VMOVL, 8 VMLA = 10 cycles
+ // 1 blocks with VLD B, VMLA = 9 cycles
+ // total = 84 cycles
+ .p2align 3
+2:
+ // Extend - 5 cycles
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ // BLOCK 0 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ // BLOCK 1 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ // BLOCK 2 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ // BLOCK 3 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VLD1.8 {d0}, [r3]! // A0
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ // BLOCK 4 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VLD1.8 {d2}, [r12]! // A1
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ // BLOCK 5 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VLD1.8 {d4}, [r10]! // A2
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ // BLOCK 6 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VLD1.8 {d6}, [r0]! // A3
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ // BLOCK 7 - 9 cycles
+ VLD1.8 {d8}, [r9]! // B0
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ SUBS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+ BHS 2b
+
+ // Epilogue
+
+ .p2align 3
+3:
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ ADDS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+
+ # Is there a remainder?- 1-7 bytes of A
+ BNE 6f
+
+4:
+ # ks loop
+ SUBS r14, r14, 16 // ks -= MR * sizeof(void*)
+ BHI 1b
+
+ LDR r7, [sp, 120] // cn_stride
+ LDR r14, [sp, 64] // p = ks
+
+ # RNDNU quantization
+ VDUP.32 q0, d12[0] // right_pre_shift
+
+ VQSHL.S32 q8, q8, q0
+ VQSHL.S32 q9, q9, q0
+ VQSHL.S32 q10, q10, q0
+ VQSHL.S32 q11, q11, q0
+ VQSHL.S32 q12, q12, q0
+ VQSHL.S32 q13, q13, q0
+ VQSHL.S32 q14, q14, q0
+ VQSHL.S32 q15, q15, q0
+
+ VDUP.32 q2, d13[0] // right_post_shift
+
+ VQDMULH.S32 q8, q8, d12[1] // multiplier
+ VQDMULH.S32 q9, q9, d12[1]
+ VQDMULH.S32 q10, q10, d12[1]
+ VQDMULH.S32 q11, q11, d12[1]
+ VQDMULH.S32 q12, q12, d12[1]
+ VQDMULH.S32 q13, q13, d12[1]
+ VQDMULH.S32 q14, q14, d12[1]
+ VQDMULH.S32 q15, q15, d12[1]
+
+ VRSHL.S32 q8, q8, q2
+ VRSHL.S32 q9, q9, q2
+ VRSHL.S32 q10, q10, q2
+ VRSHL.S32 q11, q11, q2
+ VRSHL.S32 q12, q12, q2
+ VRSHL.S32 q13, q13, q2
+ VRSHL.S32 q14, q14, q2
+ VRSHL.S32 q15, q15, q2
+
+ VDUP.16 q0, d13[2] // output_zero_point
+
+ VQMOVN.S32 d16, q8
+ VQMOVN.S32 d17, q9
+ VQMOVN.S32 d18, q10
+ VQMOVN.S32 d19, q11
+ VQMOVN.S32 d20, q12
+ VQMOVN.S32 d21, q13
+ VQMOVN.S32 d22, q14
+ VQMOVN.S32 d23, q15
+
+ VQADD.S16 q8, q8, q0
+ VQADD.S16 q9, q9, q0
+ VQADD.S16 q10, q10, q0
+ VQADD.S16 q11, q11, q0
+
+ LDR r1, [sp, 56] // restore nc
+ VDUP.8 q12, d13[6] // output_min
+
+ VQMOVN.S16 d0, q8
+ VQMOVN.S16 d1, q9
+ VQMOVN.S16 d2, q10
+ VQMOVN.S16 d3, q11
+
+ VDUP.8 q13, d13[7] // output_max
+
+ VMAX.S8 q0, q0, q12
+ VMAX.S8 q1, q1, q12
+
+ SUBS r1, r1, 8 // nc -= 8
+
+ VMIN.S8 q0, q0, q13
+ VMIN.S8 q1, q1, q13
+
+ # Store full 4 x 8
+ BLO 7f
+ VST1.8 {d3}, [r6], r7
+ VST1.8 {d2}, [r8], r7
+ VST1.8 {d1}, [r4], r7
+ VST1.8 {d0}, [r11], r7
+ SUB r2, r2, r14 // a -= ks
+ BHI 0b
+
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+ # Remainder- 1 to 7 bytes of A
+ .p2align 3
+5:
+ AND r5, r5, 7 // kc remainder 1 to 7
+6:
+ VLD1.8 {d0}, [r3]
+ VLD1.8 {d8}, [r9]!
+ VLD1.8 {d2}, [r12]
+ VLD1.8 {d4}, [r10]
+ VLD1.8 {d6}, [r0]
+
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+ CMP r5, 2
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[1]
+ VMLAL.S16 q9, d9, d0[1]
+ VMLAL.S16 q10, d8, d2[1]
+ VMLAL.S16 q11, d9, d2[1]
+ VMLAL.S16 q12, d8, d4[1]
+ VMLAL.S16 q13, d9, d4[1]
+ VMLAL.S16 q14, d8, d6[1]
+ VMLAL.S16 q15, d9, d6[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+ CMP r5, 4
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[3]
+ VMLAL.S16 q9, d9, d0[3]
+ VMLAL.S16 q10, d8, d2[3]
+ VMLAL.S16 q11, d9, d2[3]
+ VMLAL.S16 q12, d8, d4[3]
+ VMLAL.S16 q13, d9, d4[3]
+ VMLAL.S16 q14, d8, d6[3]
+ VMLAL.S16 q15, d9, d6[3]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+ CMP r5, 6
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[1]
+ VMLAL.S16 q9, d9, d1[1]
+ VMLAL.S16 q10, d8, d3[1]
+ VMLAL.S16 q11, d9, d3[1]
+ VMLAL.S16 q12, d8, d5[1]
+ VMLAL.S16 q13, d9, d5[1]
+ VMLAL.S16 q14, d8, d7[1]
+ VMLAL.S16 q15, d9, d7[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+ B 4b
+
+ # Store odd width
+ .p2align 3
+7:
+ TST r1, 4
+ BEQ 8f
+ VST1.32 {d3[0]}, [r6]!
+ VST1.32 {d2[0]}, [r8]!
+ VST1.32 {d1[0]}, [r4]!
+ VST1.32 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 4
+ VEXT.8 q1, q1, q1, 4
+8:
+ TST r1, 2
+ BEQ 9f
+ VST1.16 {d3[0]}, [r6]!
+ VST1.16 {d2[0]}, [r8]!
+ VST1.16 {d1[0]}, [r4]!
+ VST1.16 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 2
+ VEXT.8 q1, q1, q1, 2
+
+9:
+ TST r1, 1
+ BEQ 10f
+ VST1.8 {d3[0]}, [r6]
+ VST1.8 {d2[0]}, [r8]
+ VST1.8 {d1[0]}, [r4]
+ VST1.8 {d0[0]}, [r11]
+
+10:
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+END_FUNCTION xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7
+
+#ifdef __ELF__
+.section ".note.GNU-stack","",%progbits
+#endif
diff --git a/src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S b/src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S
new file mode 100644
index 000000000..f5eb16979
--- /dev/null
+++ b/src/qs8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S
@@ -0,0 +1,584 @@
+// Auto-generated file. Do not edit!
+// Template: src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in
+// Generator: tools/xngen
+//
+// Copyright 2021 Google LLC
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree.
+
+
+#include <xnnpack/assembly.h>
+
+.syntax unified
+
+// void xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7(
+// size_t mr, (r0)
+// size_t nc, r1 -> sp + 56
+// size_t kc, (r2) -> r5 -> sp + 60
+// size_t ks, (r3) -> sp + 64 -> r14
+// const int8_t**restrict a, sp + 104 -> r2
+// const void*restrict w, sp + 108 -> r9
+// int8_t*restrict c, sp + 112 -> r11
+// size_t cm_stride, sp + 116 -> (r6)
+// size_t cn_stride, sp + 120 -> (r7)
+// size_t a_offset, sp + 124 -> (r5)
+// const int8_t* zero, sp + 128 -> (r7)
+// xnn_qs8_conv_minmax_params*params); sp + 132 -> (r5)
+
+// d8-d15, r4-r11,r14(lr) need to be preserved if used. r13(sp),r15(pc) are reserved.
+
+// Register usage
+// A0 r3 d0-d1 q0
+// A1 r12 d2-d3 q1
+// A2 r10 d4-d5 q2
+// A3 r0 d6-d7 q3
+
+// B r9 d8-d9 q4 q5
+
+// C0 r11 d16-d17 q8 d18-d19 q9
+// C1 r4 d20-d21 q10 d22-d23 q11
+// C2 r8 d24-d25 q12 d26-d27 q13
+// C3 r6 d28-d29 q14 d30-d31 q15
+
+// Unused d15
+
+// params structure is 16 bytes
+// struct {
+// int32_t right_pre_shift; d12[0]
+// int32_t multiplier; d12[1]
+// int32_t right_post_shift; d13[0]
+// int16_t output_zero_point; d13[2]
+// int8_t output_min; d13[6]
+// int8_t output_max; d13[7]
+// } rndnu_neon;
+
+BEGIN_FUNCTION xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7
+ # Push 104 bytes
+ # r1, r2 will be reloaded in outer loop. r3 is ks
+ PUSH {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, lr} // +48
+ SUB sp, sp, 8 // +8
+ VPUSH {d8-d13} // +48 = 104
+
+ LDR r11, [sp, 112] // c
+ LDR r6, [sp, 116] // cm_stride
+ LDR r2, [sp, 104] // a
+ LDR r9, [sp, 108] // w
+ LDR r5, [sp, 132] // params
+ MOV r14, r3 // p = ks
+
+ # Clamp C pointers
+ CMP r0, 2 // if mr >= 2
+ ADD r4, r11, r6 // c1 = c0 + cm_stride
+ MOVLO r4, r11 // c1
+ // if mr > 2
+ ADD r8, r4, r6 // c2 = c1 + cm_stride
+ MOVLS r8, r4 // c2
+ CMP r0, 4 // if mr >=4
+ ADD r6, r8, r6 // c3 = c2 + cm_stride
+ MOVLO r6, r8 // c3
+
+ # Load params values
+ VLDM r5, {d12-d13} // RNDNU params
+
+ PLD [r9, 64] // Prefetch B
+ PLD [r9, 128]
+ PLD [r9, 192]
+ PLD [r9, 256]
+ PLD [r9, 320]
+ PLD [r9, 384]
+
+ .p2align 3
+0:
+ # Load initial bias from w into accumulators
+ VLDM r9!, {d16-d19} // Bias
+ VMOV q10, q8
+ VMOV q11, q9
+ STR r1, [sp, 56] // save nc
+ VMOV q12, q8
+ VMOV q13, q9
+ VMOV q14, q8
+ VMOV q15, q9
+
+ .p2align 3
+1:
+ # Load next 4 A pointers
+ LDR r3, [r2, 0]
+ LDR r12, [r2, 4]
+ LDR r10, [r2, 8]
+ LDR r0, [r2, 12]
+
+ # Add a_offset
+ LDR r5, [sp, 124] // a_offset
+ LDR r7, [sp, 128] // zero
+ ADD r2, r2, 16
+ CMP r3, r7 // if a0 == zero
+ ADD r3, r3, r5 // a0 += a_offset
+ MOVEQ r3, r7 // a0 = zero, else += a0 + a_offset
+ CMP r12, r7 // if a1 == zero
+ ADD r12, r12, r5 // a1 += a_offset
+ MOVEQ r12, r7 // a1 = zero, else += a1 + a_offset
+ CMP r10, r7 // if a2 == zero
+ ADD r10, r10, r5 // a2 += a_offset
+ MOVEQ r10, r7 // a2 = zero, else += a2 + a_offset
+ CMP r0, r7 // if a3 == zero
+ ADD r0, r0, r5 // a3 += a_offset
+ LDR r5, [sp, 60] // kc
+ MOVEQ r0, r7 // a3 = zero, else += a3 + a_offset
+ SUBS r5, r5, 8 // kc - 8
+ BLO 5f // less than 8 channels?
+
+ // Prologue - load 4A's and B0
+ VLD1.8 {d0}, [r3]! // A0
+ VLD1.8 {d2}, [r12]! // A1
+ VLD1.8 {d4}, [r10]! // A2
+ VLD1.8 {d6}, [r0]! // A3
+ VLD1.8 {d8}, [r9]! // B0
+
+ SUBS r5, r5, 8 // k = k - 8
+ BLO 3f // less than 8 channels?
+
+ // Main loop - 8 bytes
+ // 64 bytes for weights.
+ // 5 VMOVL = 4 A and 1 B = 5 cycles
+ // 7 blocks with VLD B, VMOVL, 8 VMLA = 10 cycles
+ // 1 blocks with VLD B, VMLA = 9 cycles
+ // total = 84 cycles
+ .p2align 3
+2:
+ // Extend - 5 cycles
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ PLD [r9, 448]
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ // BLOCK 0 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ // BLOCK 1 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ // BLOCK 2 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ // BLOCK 3 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VLD1.8 {d0}, [r3]! // A0
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ // BLOCK 4 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VLD1.8 {d2}, [r12]! // A1
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ // BLOCK 5 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VLD1.8 {d4}, [r10]! // A2
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ // BLOCK 6 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VLD1.8 {d6}, [r0]! // A3
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ // BLOCK 7 - 9 cycles
+ VLD1.8 {d8}, [r9]! // B0
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ SUBS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+ BHS 2b
+
+ // Epilogue
+
+ .p2align 3
+3:
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMOVL.S8 q5, d10
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ ADDS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+
+ # Is there a remainder?- 1-7 bytes of A
+ BNE 6f
+
+4:
+ # ks loop
+ SUBS r14, r14, 16 // ks -= MR * sizeof(void*)
+ BHI 1b
+
+ LDR r7, [sp, 120] // cn_stride
+ LDR r14, [sp, 64] // p = ks
+
+ # RNDNU quantization
+ VDUP.32 q0, d12[0] // right_pre_shift
+
+ VQSHL.S32 q8, q8, q0
+ VQSHL.S32 q9, q9, q0
+ VQSHL.S32 q10, q10, q0
+ VQSHL.S32 q11, q11, q0
+ VQSHL.S32 q12, q12, q0
+ VQSHL.S32 q13, q13, q0
+ VQSHL.S32 q14, q14, q0
+ VQSHL.S32 q15, q15, q0
+
+ VDUP.32 q2, d13[0] // right_post_shift
+
+ VQDMULH.S32 q8, q8, d12[1] // multiplier
+ VQDMULH.S32 q9, q9, d12[1]
+ VQDMULH.S32 q10, q10, d12[1]
+ VQDMULH.S32 q11, q11, d12[1]
+ VQDMULH.S32 q12, q12, d12[1]
+ VQDMULH.S32 q13, q13, d12[1]
+ VQDMULH.S32 q14, q14, d12[1]
+ VQDMULH.S32 q15, q15, d12[1]
+
+ VRSHL.S32 q8, q8, q2
+ VRSHL.S32 q9, q9, q2
+ VRSHL.S32 q10, q10, q2
+ VRSHL.S32 q11, q11, q2
+ VRSHL.S32 q12, q12, q2
+ VRSHL.S32 q13, q13, q2
+ VRSHL.S32 q14, q14, q2
+ VRSHL.S32 q15, q15, q2
+
+ VDUP.16 q0, d13[2] // output_zero_point
+
+ VQMOVN.S32 d16, q8
+ VQMOVN.S32 d17, q9
+ VQMOVN.S32 d18, q10
+ VQMOVN.S32 d19, q11
+ VQMOVN.S32 d20, q12
+ VQMOVN.S32 d21, q13
+ VQMOVN.S32 d22, q14
+ VQMOVN.S32 d23, q15
+
+ VQADD.S16 q8, q8, q0
+ VQADD.S16 q9, q9, q0
+ VQADD.S16 q10, q10, q0
+ VQADD.S16 q11, q11, q0
+
+ LDR r1, [sp, 56] // restore nc
+ VDUP.8 q12, d13[6] // output_min
+
+ VQMOVN.S16 d0, q8
+ VQMOVN.S16 d1, q9
+ VQMOVN.S16 d2, q10
+ VQMOVN.S16 d3, q11
+
+ VDUP.8 q13, d13[7] // output_max
+
+ VMAX.S8 q0, q0, q12
+ VMAX.S8 q1, q1, q12
+
+ SUBS r1, r1, 8 // nc -= 8
+
+ VMIN.S8 q0, q0, q13
+ VMIN.S8 q1, q1, q13
+
+ # Store full 4 x 8
+ BLO 7f
+ VST1.8 {d3}, [r6], r7
+ VST1.8 {d2}, [r8], r7
+ VST1.8 {d1}, [r4], r7
+ VST1.8 {d0}, [r11], r7
+ SUB r2, r2, r14 // a -= ks
+ BHI 0b
+
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+ # Remainder- 1 to 7 bytes of A
+ .p2align 3
+5:
+ AND r5, r5, 7 // kc remainder 1 to 7
+6:
+ VLD1.8 {d0}, [r3]
+ VLD1.8 {d8}, [r9]!
+ VLD1.8 {d2}, [r12]
+ VLD1.8 {d4}, [r10]
+ VLD1.8 {d6}, [r0]
+
+ VMOVL.S8 q0, d0
+ VMOVL.S8 q4, d8
+ VMOVL.S8 q1, d2
+ VMOVL.S8 q2, d4
+ VMOVL.S8 q3, d6
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+ CMP r5, 2
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[1]
+ VMLAL.S16 q9, d9, d0[1]
+ VMLAL.S16 q10, d8, d2[1]
+ VMLAL.S16 q11, d9, d2[1]
+ VMLAL.S16 q12, d8, d4[1]
+ VMLAL.S16 q13, d9, d4[1]
+ VMLAL.S16 q14, d8, d6[1]
+ VMLAL.S16 q15, d9, d6[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+ CMP r5, 4
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d0[3]
+ VMLAL.S16 q9, d9, d0[3]
+ VMLAL.S16 q10, d8, d2[3]
+ VMLAL.S16 q11, d9, d2[3]
+ VMLAL.S16 q12, d8, d4[3]
+ VMLAL.S16 q13, d9, d4[3]
+ VMLAL.S16 q14, d8, d6[3]
+ VMLAL.S16 q15, d9, d6[3]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+ CMP r5, 6
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[1]
+ VMLAL.S16 q9, d9, d1[1]
+ VMLAL.S16 q10, d8, d3[1]
+ VMLAL.S16 q11, d9, d3[1]
+ VMLAL.S16 q12, d8, d5[1]
+ VMLAL.S16 q13, d9, d5[1]
+ VMLAL.S16 q14, d8, d7[1]
+ VMLAL.S16 q15, d9, d7[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VMOVL.S8 q4, d8
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+ B 4b
+
+ # Store odd width
+ .p2align 3
+7:
+ TST r1, 4
+ BEQ 8f
+ VST1.32 {d3[0]}, [r6]!
+ VST1.32 {d2[0]}, [r8]!
+ VST1.32 {d1[0]}, [r4]!
+ VST1.32 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 4
+ VEXT.8 q1, q1, q1, 4
+8:
+ TST r1, 2
+ BEQ 9f
+ VST1.16 {d3[0]}, [r6]!
+ VST1.16 {d2[0]}, [r8]!
+ VST1.16 {d1[0]}, [r4]!
+ VST1.16 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 2
+ VEXT.8 q1, q1, q1, 2
+
+9:
+ TST r1, 1
+ BEQ 10f
+ VST1.8 {d3[0]}, [r6]
+ VST1.8 {d2[0]}, [r8]
+ VST1.8 {d1[0]}, [r4]
+ VST1.8 {d0[0]}, [r11]
+
+10:
+ VPOP {d8-d13}
+ ADD sp, sp, 20 // skip pad of 8, r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+END_FUNCTION xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7
+
+#ifdef __ELF__
+.section ".note.GNU-stack","",%progbits
+#endif
diff --git a/src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S b/src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S
new file mode 100644
index 000000000..aa3322456
--- /dev/null
+++ b/src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-cortex-a7.S
@@ -0,0 +1,578 @@
+// Auto-generated file. Do not edit!
+// Template: src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in
+// Generator: tools/xngen
+//
+// Copyright 2021 Google LLC
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree.
+
+
+#include <xnnpack/assembly.h>
+
+.syntax unified
+
+// void xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7(
+// size_t mr, (r0)
+// size_t nc, r1 -> sp + 56
+// size_t kc, (r2) -> r5 -> sp + 60
+// size_t ks, (r3) -> sp + 64 -> r14
+// const uint8_t**restrict a, sp + 104 -> r2
+// const void*restrict w, sp + 108 -> r9
+// uint8_t*restrict c, sp + 112 -> r11
+// size_t cm_stride, sp + 116 -> (r6)
+// size_t cn_stride, sp + 120 -> (r7)
+// size_t a_offset, sp + 124 -> (r5)
+// const uint8_t* zero, sp + 128 -> (r7)
+// xnn_qs8_conv_minmax_params*params); sp + 132 -> (r5)
+
+// d8-d15, r4-r11,r14(lr) need to be preserved if used. r13(sp),r15(pc) are reserved.
+
+// Register usage
+// A0 r3 d0-d1 q0
+// A1 r12 d2-d3 q1
+// A2 r10 d4-d5 q2
+// A3 r0 d6-d7 q3
+
+// B r9 d8-d9 q4 q5
+
+// C0 r11 d16-d17 q8 d18-d19 q9
+// C1 r4 d20-d21 q10 d22-d23 q11
+// C2 r8 d24-d25 q12 d26-d27 q13
+// C3 r6 d28-d29 q14 d30-d31 q15
+
+// Unused d15
+
+// params structure is 20 bytes
+// struct {
+// uint8_t kernel_zero_point[4]; d14
+// int32_t right_pre_shift; d12[0]
+// int32_t multiplier; d12[1]
+// int32_t right_post_shift; d13[0]
+// int16_t output_zero_point; d13[2]
+// uint8_t output_min; d13[6]
+// uint8_t output_max; d13[7]
+// } rndnu_neon;
+
+BEGIN_FUNCTION xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7
+ # Push 104 bytes
+ # r1, r2 will be reloaded in outer loop. r3 is ks
+ PUSH {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, lr} // +48
+ VPUSH {d8-d14} // +56 = 104
+
+ LDR r11, [sp, 112] // c
+ LDR r6, [sp, 116] // cm_stride
+ LDR r2, [sp, 104] // a
+ LDR r9, [sp, 108] // w
+ LDR r5, [sp, 132] // params
+ MOV r14, r3 // p = ks
+
+ # Clamp C pointers
+ CMP r0, 2 // if mr >= 2
+ ADD r4, r11, r6 // c1 = c0 + cm_stride
+ MOVLO r4, r11 // c1
+ // if mr > 2
+ ADD r8, r4, r6 // c2 = c1 + cm_stride
+ MOVLS r8, r4 // c2
+ CMP r0, 4 // if mr >=4
+ ADD r6, r8, r6 // c3 = c2 + cm_stride
+ MOVLO r6, r8 // c3
+
+ # Load params values
+ VLD1.32 {d14[]}, [r5]! // QU8 kernel_zero_point
+ VLDM r5, {d12-d13} // RNDNU params
+
+
+ .p2align 3
+0:
+ # Load initial bias from w into accumulators
+ VLDM r9!, {d16-d19} // Bias
+ VMOV q10, q8
+ VMOV q11, q9
+ STR r1, [sp, 56] // save nc
+ VMOV q12, q8
+ VMOV q13, q9
+ VMOV q14, q8
+ VMOV q15, q9
+
+ .p2align 3
+1:
+ # Load next 4 A pointers
+ LDR r3, [r2, 0]
+ LDR r12, [r2, 4]
+ LDR r10, [r2, 8]
+ LDR r0, [r2, 12]
+
+ # Add a_offset
+ LDR r5, [sp, 124] // a_offset
+ LDR r7, [sp, 128] // zero
+ ADD r2, r2, 16
+ CMP r3, r7 // if a0 == zero
+ ADD r3, r3, r5 // a0 += a_offset
+ MOVEQ r3, r7 // a0 = zero, else += a0 + a_offset
+ CMP r12, r7 // if a1 == zero
+ ADD r12, r12, r5 // a1 += a_offset
+ MOVEQ r12, r7 // a1 = zero, else += a1 + a_offset
+ CMP r10, r7 // if a2 == zero
+ ADD r10, r10, r5 // a2 += a_offset
+ MOVEQ r10, r7 // a2 = zero, else += a2 + a_offset
+ CMP r0, r7 // if a3 == zero
+ ADD r0, r0, r5 // a3 += a_offset
+ LDR r5, [sp, 60] // kc
+ MOVEQ r0, r7 // a3 = zero, else += a3 + a_offset
+ SUBS r5, r5, 8 // kc - 8
+ BLO 5f // less than 8 channels?
+
+ // Prologue - load 4A's and B0
+ VLD1.8 {d0}, [r3]! // A0
+ VLD1.8 {d2}, [r12]! // A1
+ VLD1.8 {d4}, [r10]! // A2
+ VLD1.8 {d6}, [r0]! // A3
+ VLD1.8 {d8}, [r9]! // B0
+
+ SUBS r5, r5, 8 // k = k - 8
+ BLO 3f // less than 8 channels?
+
+ // Main loop - 8 bytes
+ // 64 bytes for weights.
+ // 5 VMOVL = 4 A and 1 B = 5 cycles
+ // 7 blocks with VLD B, VMOVL, 8 VMLA = 10 cycles
+ // 1 blocks with VLD B, VMLA = 9 cycles
+ // total = 84 cycles
+ .p2align 3
+2:
+ // Extend - 5 cycles
+ VMOVL.U8 q0, d0
+ VSUBL.U8 q4, d8, d14
+ VMOVL.U8 q1, d2
+ VMOVL.U8 q2, d4
+ VMOVL.U8 q3, d6
+
+ // BLOCK 0 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ // BLOCK 1 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ // BLOCK 2 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ // BLOCK 3 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VLD1.8 {d0}, [r3]! // A0
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ // BLOCK 4 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VLD1.8 {d2}, [r12]! // A1
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ // BLOCK 5 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VLD1.8 {d4}, [r10]! // A2
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ // BLOCK 6 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VLD1.8 {d6}, [r0]! // A3
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ // BLOCK 7 - 9 cycles
+ VLD1.8 {d8}, [r9]! // B0
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ SUBS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+ BHS 2b
+
+ // Epilogue
+
+ .p2align 3
+3:
+ VMOVL.U8 q0, d0
+ VSUBL.U8 q4, d8, d14
+ VMOVL.U8 q1, d2
+ VMOVL.U8 q2, d4
+ VMOVL.U8 q3, d6
+
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ ADDS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+
+ # Is there a remainder?- 1-7 bytes of A
+ BNE 6f
+
+4:
+ # ks loop
+ SUBS r14, r14, 16 // ks -= MR * sizeof(void*)
+ BHI 1b
+
+ LDR r7, [sp, 120] // cn_stride
+ LDR r14, [sp, 64] // p = ks
+
+ # RNDNU quantization
+ VDUP.32 q0, d12[0] // right_pre_shift
+
+ VQSHL.S32 q8, q8, q0
+ VQSHL.S32 q9, q9, q0
+ VQSHL.S32 q10, q10, q0
+ VQSHL.S32 q11, q11, q0
+ VQSHL.S32 q12, q12, q0
+ VQSHL.S32 q13, q13, q0
+ VQSHL.S32 q14, q14, q0
+ VQSHL.S32 q15, q15, q0
+
+ VDUP.32 q2, d13[0] // right_post_shift
+
+ VQDMULH.S32 q8, q8, d12[1] // multiplier
+ VQDMULH.S32 q9, q9, d12[1]
+ VQDMULH.S32 q10, q10, d12[1]
+ VQDMULH.S32 q11, q11, d12[1]
+ VQDMULH.S32 q12, q12, d12[1]
+ VQDMULH.S32 q13, q13, d12[1]
+ VQDMULH.S32 q14, q14, d12[1]
+ VQDMULH.S32 q15, q15, d12[1]
+
+ VRSHL.S32 q8, q8, q2
+ VRSHL.S32 q9, q9, q2
+ VRSHL.S32 q10, q10, q2
+ VRSHL.S32 q11, q11, q2
+ VRSHL.S32 q12, q12, q2
+ VRSHL.S32 q13, q13, q2
+ VRSHL.S32 q14, q14, q2
+ VRSHL.S32 q15, q15, q2
+
+ VDUP.16 q0, d13[2] // output_zero_point
+
+ VQMOVN.S32 d16, q8
+ VQMOVN.S32 d17, q9
+ VQMOVN.S32 d18, q10
+ VQMOVN.S32 d19, q11
+ VQMOVN.S32 d20, q12
+ VQMOVN.S32 d21, q13
+ VQMOVN.S32 d22, q14
+ VQMOVN.S32 d23, q15
+
+ VQADD.S16 q8, q8, q0
+ VQADD.S16 q9, q9, q0
+ VQADD.S16 q10, q10, q0
+ VQADD.S16 q11, q11, q0
+
+ LDR r1, [sp, 56] // restore nc
+ VDUP.8 q12, d13[6] // output_min
+
+ VQMOVUN.S16 d0, q8
+ VQMOVUN.S16 d1, q9
+ VQMOVUN.S16 d2, q10
+ VQMOVUN.S16 d3, q11
+
+ VDUP.8 q13, d13[7] // output_max
+
+ VMAX.U8 q0, q0, q12
+ VMAX.U8 q1, q1, q12
+
+ SUBS r1, r1, 8 // nc -= 8
+
+ VMIN.U8 q0, q0, q13
+ VMIN.U8 q1, q1, q13
+
+ # Store full 4 x 8
+ BLO 7f
+ VST1.8 {d3}, [r6], r7
+ VST1.8 {d2}, [r8], r7
+ VST1.8 {d1}, [r4], r7
+ VST1.8 {d0}, [r11], r7
+ SUB r2, r2, r14 // a -= ks
+ BHI 0b
+
+ VPOP {d8-d14}
+ ADD sp, sp, 12 // skip r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+ # Remainder- 1 to 7 bytes of A
+ .p2align 3
+5:
+ AND r5, r5, 7 // kc remainder 1 to 7
+6:
+ VLD1.8 {d0}, [r3]
+ VLD1.8 {d8}, [r9]!
+ VLD1.8 {d2}, [r12]
+ VLD1.8 {d4}, [r10]
+ VLD1.8 {d6}, [r0]
+
+ VMOVL.U8 q0, d0
+ VSUBL.U8 q4, d8, d14
+ VMOVL.U8 q1, d2
+ VMOVL.U8 q2, d4
+ VMOVL.U8 q3, d6
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+ CMP r5, 2
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d0[1]
+ VMLAL.S16 q9, d9, d0[1]
+ VMLAL.S16 q10, d8, d2[1]
+ VMLAL.S16 q11, d9, d2[1]
+ VMLAL.S16 q12, d8, d4[1]
+ VMLAL.S16 q13, d9, d4[1]
+ VMLAL.S16 q14, d8, d6[1]
+ VMLAL.S16 q15, d9, d6[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+ CMP r5, 4
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d0[3]
+ VMLAL.S16 q9, d9, d0[3]
+ VMLAL.S16 q10, d8, d2[3]
+ VMLAL.S16 q11, d9, d2[3]
+ VMLAL.S16 q12, d8, d4[3]
+ VMLAL.S16 q13, d9, d4[3]
+ VMLAL.S16 q14, d8, d6[3]
+ VMLAL.S16 q15, d9, d6[3]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+ CMP r5, 6
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d1[1]
+ VMLAL.S16 q9, d9, d1[1]
+ VMLAL.S16 q10, d8, d3[1]
+ VMLAL.S16 q11, d9, d3[1]
+ VMLAL.S16 q12, d8, d5[1]
+ VMLAL.S16 q13, d9, d5[1]
+ VMLAL.S16 q14, d8, d7[1]
+ VMLAL.S16 q15, d9, d7[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+ B 4b
+
+ # Store odd width
+ .p2align 3
+7:
+ TST r1, 4
+ BEQ 8f
+ VST1.32 {d3[0]}, [r6]!
+ VST1.32 {d2[0]}, [r8]!
+ VST1.32 {d1[0]}, [r4]!
+ VST1.32 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 4
+ VEXT.8 q1, q1, q1, 4
+8:
+ TST r1, 2
+ BEQ 9f
+ VST1.16 {d3[0]}, [r6]!
+ VST1.16 {d2[0]}, [r8]!
+ VST1.16 {d1[0]}, [r4]!
+ VST1.16 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 2
+ VEXT.8 q1, q1, q1, 2
+
+9:
+ TST r1, 1
+ BEQ 10f
+ VST1.8 {d3[0]}, [r6]
+ VST1.8 {d2[0]}, [r8]
+ VST1.8 {d1[0]}, [r4]
+ VST1.8 {d0[0]}, [r11]
+
+10:
+ VPOP {d8-d14}
+ ADD sp, sp, 12 // skip r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+END_FUNCTION xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7
+
+#ifdef __ELF__
+.section ".note.GNU-stack","",%progbits
+#endif
diff --git a/src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S b/src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S
new file mode 100644
index 000000000..9e354fa62
--- /dev/null
+++ b/src/qu8-igemm/gen/4x8-minmax-rndnu-aarch32-neon-mlal-lane-prfm-cortex-a7.S
@@ -0,0 +1,585 @@
+// Auto-generated file. Do not edit!
+// Template: src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in
+// Generator: tools/xngen
+//
+// Copyright 2021 Google LLC
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree.
+
+
+#include <xnnpack/assembly.h>
+
+.syntax unified
+
+// void xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7(
+// size_t mr, (r0)
+// size_t nc, r1 -> sp + 56
+// size_t kc, (r2) -> r5 -> sp + 60
+// size_t ks, (r3) -> sp + 64 -> r14
+// const uint8_t**restrict a, sp + 104 -> r2
+// const void*restrict w, sp + 108 -> r9
+// uint8_t*restrict c, sp + 112 -> r11
+// size_t cm_stride, sp + 116 -> (r6)
+// size_t cn_stride, sp + 120 -> (r7)
+// size_t a_offset, sp + 124 -> (r5)
+// const uint8_t* zero, sp + 128 -> (r7)
+// xnn_qs8_conv_minmax_params*params); sp + 132 -> (r5)
+
+// d8-d15, r4-r11,r14(lr) need to be preserved if used. r13(sp),r15(pc) are reserved.
+
+// Register usage
+// A0 r3 d0-d1 q0
+// A1 r12 d2-d3 q1
+// A2 r10 d4-d5 q2
+// A3 r0 d6-d7 q3
+
+// B r9 d8-d9 q4 q5
+
+// C0 r11 d16-d17 q8 d18-d19 q9
+// C1 r4 d20-d21 q10 d22-d23 q11
+// C2 r8 d24-d25 q12 d26-d27 q13
+// C3 r6 d28-d29 q14 d30-d31 q15
+
+// Unused d15
+
+// params structure is 20 bytes
+// struct {
+// uint8_t kernel_zero_point[4]; d14
+// int32_t right_pre_shift; d12[0]
+// int32_t multiplier; d12[1]
+// int32_t right_post_shift; d13[0]
+// int16_t output_zero_point; d13[2]
+// uint8_t output_min; d13[6]
+// uint8_t output_max; d13[7]
+// } rndnu_neon;
+
+BEGIN_FUNCTION xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7
+ # Push 104 bytes
+ # r1, r2 will be reloaded in outer loop. r3 is ks
+ PUSH {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, lr} // +48
+ VPUSH {d8-d14} // +56 = 104
+
+ LDR r11, [sp, 112] // c
+ LDR r6, [sp, 116] // cm_stride
+ LDR r2, [sp, 104] // a
+ LDR r9, [sp, 108] // w
+ LDR r5, [sp, 132] // params
+ MOV r14, r3 // p = ks
+
+ # Clamp C pointers
+ CMP r0, 2 // if mr >= 2
+ ADD r4, r11, r6 // c1 = c0 + cm_stride
+ MOVLO r4, r11 // c1
+ // if mr > 2
+ ADD r8, r4, r6 // c2 = c1 + cm_stride
+ MOVLS r8, r4 // c2
+ CMP r0, 4 // if mr >=4
+ ADD r6, r8, r6 // c3 = c2 + cm_stride
+ MOVLO r6, r8 // c3
+
+ # Load params values
+ VLD1.32 {d14[]}, [r5]! // QU8 kernel_zero_point
+ VLDM r5, {d12-d13} // RNDNU params
+
+ PLD [r9, 64] // Prefetch B
+ PLD [r9, 128]
+ PLD [r9, 192]
+ PLD [r9, 256]
+ PLD [r9, 320]
+ PLD [r9, 384]
+
+ .p2align 3
+0:
+ # Load initial bias from w into accumulators
+ VLDM r9!, {d16-d19} // Bias
+ VMOV q10, q8
+ VMOV q11, q9
+ STR r1, [sp, 56] // save nc
+ VMOV q12, q8
+ VMOV q13, q9
+ VMOV q14, q8
+ VMOV q15, q9
+
+ .p2align 3
+1:
+ # Load next 4 A pointers
+ LDR r3, [r2, 0]
+ LDR r12, [r2, 4]
+ LDR r10, [r2, 8]
+ LDR r0, [r2, 12]
+
+ # Add a_offset
+ LDR r5, [sp, 124] // a_offset
+ LDR r7, [sp, 128] // zero
+ ADD r2, r2, 16
+ CMP r3, r7 // if a0 == zero
+ ADD r3, r3, r5 // a0 += a_offset
+ MOVEQ r3, r7 // a0 = zero, else += a0 + a_offset
+ CMP r12, r7 // if a1 == zero
+ ADD r12, r12, r5 // a1 += a_offset
+ MOVEQ r12, r7 // a1 = zero, else += a1 + a_offset
+ CMP r10, r7 // if a2 == zero
+ ADD r10, r10, r5 // a2 += a_offset
+ MOVEQ r10, r7 // a2 = zero, else += a2 + a_offset
+ CMP r0, r7 // if a3 == zero
+ ADD r0, r0, r5 // a3 += a_offset
+ LDR r5, [sp, 60] // kc
+ MOVEQ r0, r7 // a3 = zero, else += a3 + a_offset
+ SUBS r5, r5, 8 // kc - 8
+ BLO 5f // less than 8 channels?
+
+ // Prologue - load 4A's and B0
+ VLD1.8 {d0}, [r3]! // A0
+ VLD1.8 {d2}, [r12]! // A1
+ VLD1.8 {d4}, [r10]! // A2
+ VLD1.8 {d6}, [r0]! // A3
+ VLD1.8 {d8}, [r9]! // B0
+
+ SUBS r5, r5, 8 // k = k - 8
+ BLO 3f // less than 8 channels?
+
+ // Main loop - 8 bytes
+ // 64 bytes for weights.
+ // 5 VMOVL = 4 A and 1 B = 5 cycles
+ // 7 blocks with VLD B, VMOVL, 8 VMLA = 10 cycles
+ // 1 blocks with VLD B, VMLA = 9 cycles
+ // total = 84 cycles
+ .p2align 3
+2:
+ // Extend - 5 cycles
+ VMOVL.U8 q0, d0
+ VSUBL.U8 q4, d8, d14
+ PLD [r9, 448]
+ VMOVL.U8 q1, d2
+ VMOVL.U8 q2, d4
+ VMOVL.U8 q3, d6
+
+ // BLOCK 0 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ // BLOCK 1 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ // BLOCK 2 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ // BLOCK 3 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VLD1.8 {d0}, [r3]! // A0
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ // BLOCK 4 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VLD1.8 {d2}, [r12]! // A1
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ // BLOCK 5 - 10 cycles
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VLD1.8 {d4}, [r10]! // A2
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ // BLOCK 6 - 10 cycles
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VLD1.8 {d6}, [r0]! // A3
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ // BLOCK 7 - 9 cycles
+ VLD1.8 {d8}, [r9]! // B0
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ SUBS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+ BHS 2b
+
+ // Epilogue
+
+ .p2align 3
+3:
+ VMOVL.U8 q0, d0
+ VSUBL.U8 q4, d8, d14
+ VMOVL.U8 q1, d2
+ VMOVL.U8 q2, d4
+ VMOVL.U8 q3, d6
+
+ VLD1.8 {d10}, [r9]! // B1
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+
+ VLD1.8 {d8}, [r9]! // B2
+ VMLAL.S16 q8, d10, d0[1]
+ VMLAL.S16 q9, d11, d0[1]
+ VMLAL.S16 q10, d10, d2[1]
+ VMLAL.S16 q11, d11, d2[1]
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d4[1]
+ VMLAL.S16 q13, d11, d4[1]
+ VMLAL.S16 q14, d10, d6[1]
+ VMLAL.S16 q15, d11, d6[1]
+
+ VLD1.8 {d10}, [r9]! // B3
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+
+ VLD1.8 {d8}, [r9]! // B4
+ VMLAL.S16 q8, d10, d0[3]
+ VMLAL.S16 q9, d11, d0[3]
+ VMLAL.S16 q10, d10, d2[3]
+ VMLAL.S16 q11, d11, d2[3]
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d4[3]
+ VMLAL.S16 q13, d11, d4[3]
+ VMLAL.S16 q14, d10, d6[3]
+ VMLAL.S16 q15, d11, d6[3]
+
+ VLD1.8 {d10}, [r9]! // B5
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+
+ VLD1.8 {d8}, [r9]! // B6
+ VMLAL.S16 q8, d10, d1[1]
+ VMLAL.S16 q9, d11, d1[1]
+ VMLAL.S16 q10, d10, d3[1]
+ VMLAL.S16 q11, d11, d3[1]
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q12, d10, d5[1]
+ VMLAL.S16 q13, d11, d5[1]
+ VMLAL.S16 q14, d10, d7[1]
+ VMLAL.S16 q15, d11, d7[1]
+
+ VLD1.8 {d10}, [r9]! // B7
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VSUBL.U8 q5, d10, d14
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+
+ VMLAL.S16 q8, d10, d1[3]
+ VMLAL.S16 q9, d11, d1[3]
+ VMLAL.S16 q10, d10, d3[3]
+ VMLAL.S16 q11, d11, d3[3]
+ VMLAL.S16 q12, d10, d5[3]
+ VMLAL.S16 q13, d11, d5[3]
+ ADDS r5, r5, 8
+ VMLAL.S16 q14, d10, d7[3]
+ VMLAL.S16 q15, d11, d7[3]
+
+ # Is there a remainder?- 1-7 bytes of A
+ BNE 6f
+
+4:
+ # ks loop
+ SUBS r14, r14, 16 // ks -= MR * sizeof(void*)
+ BHI 1b
+
+ LDR r7, [sp, 120] // cn_stride
+ LDR r14, [sp, 64] // p = ks
+
+ # RNDNU quantization
+ VDUP.32 q0, d12[0] // right_pre_shift
+
+ VQSHL.S32 q8, q8, q0
+ VQSHL.S32 q9, q9, q0
+ VQSHL.S32 q10, q10, q0
+ VQSHL.S32 q11, q11, q0
+ VQSHL.S32 q12, q12, q0
+ VQSHL.S32 q13, q13, q0
+ VQSHL.S32 q14, q14, q0
+ VQSHL.S32 q15, q15, q0
+
+ VDUP.32 q2, d13[0] // right_post_shift
+
+ VQDMULH.S32 q8, q8, d12[1] // multiplier
+ VQDMULH.S32 q9, q9, d12[1]
+ VQDMULH.S32 q10, q10, d12[1]
+ VQDMULH.S32 q11, q11, d12[1]
+ VQDMULH.S32 q12, q12, d12[1]
+ VQDMULH.S32 q13, q13, d12[1]
+ VQDMULH.S32 q14, q14, d12[1]
+ VQDMULH.S32 q15, q15, d12[1]
+
+ VRSHL.S32 q8, q8, q2
+ VRSHL.S32 q9, q9, q2
+ VRSHL.S32 q10, q10, q2
+ VRSHL.S32 q11, q11, q2
+ VRSHL.S32 q12, q12, q2
+ VRSHL.S32 q13, q13, q2
+ VRSHL.S32 q14, q14, q2
+ VRSHL.S32 q15, q15, q2
+
+ VDUP.16 q0, d13[2] // output_zero_point
+
+ VQMOVN.S32 d16, q8
+ VQMOVN.S32 d17, q9
+ VQMOVN.S32 d18, q10
+ VQMOVN.S32 d19, q11
+ VQMOVN.S32 d20, q12
+ VQMOVN.S32 d21, q13
+ VQMOVN.S32 d22, q14
+ VQMOVN.S32 d23, q15
+
+ VQADD.S16 q8, q8, q0
+ VQADD.S16 q9, q9, q0
+ VQADD.S16 q10, q10, q0
+ VQADD.S16 q11, q11, q0
+
+ LDR r1, [sp, 56] // restore nc
+ VDUP.8 q12, d13[6] // output_min
+
+ VQMOVUN.S16 d0, q8
+ VQMOVUN.S16 d1, q9
+ VQMOVUN.S16 d2, q10
+ VQMOVUN.S16 d3, q11
+
+ VDUP.8 q13, d13[7] // output_max
+
+ VMAX.U8 q0, q0, q12
+ VMAX.U8 q1, q1, q12
+
+ SUBS r1, r1, 8 // nc -= 8
+
+ VMIN.U8 q0, q0, q13
+ VMIN.U8 q1, q1, q13
+
+ # Store full 4 x 8
+ BLO 7f
+ VST1.8 {d3}, [r6], r7
+ VST1.8 {d2}, [r8], r7
+ VST1.8 {d1}, [r4], r7
+ VST1.8 {d0}, [r11], r7
+ SUB r2, r2, r14 // a -= ks
+ BHI 0b
+
+ VPOP {d8-d14}
+ ADD sp, sp, 12 // skip r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+ # Remainder- 1 to 7 bytes of A
+ .p2align 3
+5:
+ AND r5, r5, 7 // kc remainder 1 to 7
+6:
+ VLD1.8 {d0}, [r3]
+ VLD1.8 {d8}, [r9]!
+ VLD1.8 {d2}, [r12]
+ VLD1.8 {d4}, [r10]
+ VLD1.8 {d6}, [r0]
+
+ VMOVL.U8 q0, d0
+ VSUBL.U8 q4, d8, d14
+ VMOVL.U8 q1, d2
+ VMOVL.U8 q2, d4
+ VMOVL.U8 q3, d6
+ VMLAL.S16 q8, d8, d0[0]
+ VMLAL.S16 q9, d9, d0[0]
+ VMLAL.S16 q10, d8, d2[0]
+ VMLAL.S16 q11, d9, d2[0]
+ VMLAL.S16 q12, d8, d4[0]
+ VMLAL.S16 q13, d9, d4[0]
+ VMLAL.S16 q14, d8, d6[0]
+ VMLAL.S16 q15, d9, d6[0]
+ CMP r5, 2
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d0[1]
+ VMLAL.S16 q9, d9, d0[1]
+ VMLAL.S16 q10, d8, d2[1]
+ VMLAL.S16 q11, d9, d2[1]
+ VMLAL.S16 q12, d8, d4[1]
+ VMLAL.S16 q13, d9, d4[1]
+ VMLAL.S16 q14, d8, d6[1]
+ VMLAL.S16 q15, d9, d6[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d0[2]
+ VMLAL.S16 q9, d9, d0[2]
+ VMLAL.S16 q10, d8, d2[2]
+ VMLAL.S16 q11, d9, d2[2]
+ VMLAL.S16 q12, d8, d4[2]
+ VMLAL.S16 q13, d9, d4[2]
+ VMLAL.S16 q14, d8, d6[2]
+ VMLAL.S16 q15, d9, d6[2]
+ CMP r5, 4
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d0[3]
+ VMLAL.S16 q9, d9, d0[3]
+ VMLAL.S16 q10, d8, d2[3]
+ VMLAL.S16 q11, d9, d2[3]
+ VMLAL.S16 q12, d8, d4[3]
+ VMLAL.S16 q13, d9, d4[3]
+ VMLAL.S16 q14, d8, d6[3]
+ VMLAL.S16 q15, d9, d6[3]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d1[0]
+ VMLAL.S16 q9, d9, d1[0]
+ VMLAL.S16 q10, d8, d3[0]
+ VMLAL.S16 q11, d9, d3[0]
+ VMLAL.S16 q12, d8, d5[0]
+ VMLAL.S16 q13, d9, d5[0]
+ VMLAL.S16 q14, d8, d7[0]
+ VMLAL.S16 q15, d9, d7[0]
+ CMP r5, 6
+ BLO 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d1[1]
+ VMLAL.S16 q9, d9, d1[1]
+ VMLAL.S16 q10, d8, d3[1]
+ VMLAL.S16 q11, d9, d3[1]
+ VMLAL.S16 q12, d8, d5[1]
+ VMLAL.S16 q13, d9, d5[1]
+ VMLAL.S16 q14, d8, d7[1]
+ VMLAL.S16 q15, d9, d7[1]
+ BEQ 4b
+
+ VLD1.8 {d8}, [r9]!
+ VSUBL.U8 q4, d8, d14
+ VMLAL.S16 q8, d8, d1[2]
+ VMLAL.S16 q9, d9, d1[2]
+ VMLAL.S16 q10, d8, d3[2]
+ VMLAL.S16 q11, d9, d3[2]
+ VMLAL.S16 q12, d8, d5[2]
+ VMLAL.S16 q13, d9, d5[2]
+ VMLAL.S16 q14, d8, d7[2]
+ VMLAL.S16 q15, d9, d7[2]
+ B 4b
+
+ # Store odd width
+ .p2align 3
+7:
+ TST r1, 4
+ BEQ 8f
+ VST1.32 {d3[0]}, [r6]!
+ VST1.32 {d2[0]}, [r8]!
+ VST1.32 {d1[0]}, [r4]!
+ VST1.32 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 4
+ VEXT.8 q1, q1, q1, 4
+8:
+ TST r1, 2
+ BEQ 9f
+ VST1.16 {d3[0]}, [r6]!
+ VST1.16 {d2[0]}, [r8]!
+ VST1.16 {d1[0]}, [r4]!
+ VST1.16 {d0[0]}, [r11]!
+ VEXT.8 q0, q0, q0, 2
+ VEXT.8 q1, q1, q1, 2
+
+9:
+ TST r1, 1
+ BEQ 10f
+ VST1.8 {d3[0]}, [r6]
+ VST1.8 {d2[0]}, [r8]
+ VST1.8 {d1[0]}, [r4]
+ VST1.8 {d0[0]}, [r11]
+
+10:
+ VPOP {d8-d14}
+ ADD sp, sp, 12 // skip r1, r2, r3
+ POP {r4, r5, r6, r7, r8, r9, r10, r11, pc}
+
+END_FUNCTION xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7
+
+#ifdef __ELF__
+.section ".note.GNU-stack","",%progbits
+#endif
diff --git a/src/xnnpack/igemm.h b/src/xnnpack/igemm.h
index 45ecb333e..95b998828 100644
--- a/src/xnnpack/igemm.h
+++ b/src/xnnpack/igemm.h
@@ -493,8 +493,10 @@ DECLARE_QU8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qu8_igemm_minmax_fp32_ukernel_1x16
DECLARE_QU8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qu8_igemm_minmax_fp32_ukernel_2x16c4__neondot)
DECLARE_QU8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qu8_igemm_minmax_fp32_ukernel_4x16c4__neondot)
+DECLARE_QU8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7)
DECLARE_QU8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a53)
DECLARE_QU8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_ld64)
+DECLARE_QU8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7)
DECLARE_QU8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a53)
DECLARE_QU8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qu8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_ld64)
@@ -1045,8 +1047,10 @@ DECLARE_QS8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qs8_igemm_minmax_fp32_ukernel_4x16
DECLARE_QS8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qs8_igemm_minmax_rndnu_ukernel_4x8c4__aarch32_neondot_cortex_a55)
DECLARE_QS8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qs8_igemm_minmax_rndnu_ukernel_4x8c4__aarch32_neondot_ld64)
+DECLARE_QS8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7)
DECLARE_QS8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a53)
DECLARE_QS8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_ld64)
+DECLARE_QS8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7)
DECLARE_QS8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a53)
DECLARE_QS8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qs8_igemm_minmax_rndnu_ukernel_4x8__aarch32_neon_mlal_lane_prfm_ld64)
@@ -1396,12 +1400,16 @@ DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x16
DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8c4__aarch32_neondot_cortex_a55)
DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8c4__aarch32_neondot_ld64)
+DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a7)
DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_cortex_a53)
DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_ld64)
+DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a7)
DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_prfm_cortex_a53)
DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neon_mlal_lane_prfm_ld64)
+DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_cortex_a35)
DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_cortex_a53)
DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_ld64)
+DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_prfm_cortex_a35)
DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_prfm_cortex_a53)
DECLARE_QC8_IGEMM_MINMAX_UKERNEL_FUNCTION(xnn_qc8_igemm_minmax_fp32_ukernel_4x8__aarch32_neonv8_mlal_lane_prfm_ld64)