From cbfc7c5341f104a7c453d6dd6be744da7f4f1881 Mon Sep 17 00:00:00 2001 From: Mac Date: Tue, 7 Apr 2026 15:20:56 +0800 Subject: [PATCH 01/12] docs(outerCube): add VEC-4K vector4k spec and PTOISA reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - vector4k.md: dataflow (TRegFile, control, crossbar, 128 groups, ping-pong Acc), N_run/N_tree/#W scheduling, simplified §9.7 shape table, Acc RMW vs bypass-to-DFF - PTOISA/: tile ISA reference (linked from vector4k.md) Made-with: Cursor --- designs/outerCube/PTOISA/MGATHER.md | 100 ++ designs/outerCube/PTOISA/MGATHER_zh.md | 100 ++ designs/outerCube/PTOISA/MSCATTER.md | 105 ++ designs/outerCube/PTOISA/MSCATTER_zh.md | 105 ++ designs/outerCube/PTOISA/PTOISA.md | 152 +++ designs/outerCube/PTOISA/README.md | 153 +++ designs/outerCube/PTOISA/README_zh.md | 153 +++ designs/outerCube/PTOISA/TABS.md | 133 +++ designs/outerCube/PTOISA/TABS_zh.md | 106 ++ designs/outerCube/PTOISA/TADD.md | 129 +++ designs/outerCube/PTOISA/TADDC.md | 103 ++ designs/outerCube/PTOISA/TADDC_zh.md | 76 ++ designs/outerCube/PTOISA/TADDS.md | 134 +++ designs/outerCube/PTOISA/TADDSC.md | 116 +++ designs/outerCube/PTOISA/TADDSC_zh.md | 89 ++ designs/outerCube/PTOISA/TADDS_zh.md | 107 ++ designs/outerCube/PTOISA/TADD_zh.md | 102 ++ designs/outerCube/PTOISA/TALIAS.md | 40 + designs/outerCube/PTOISA/TALIAS_zh.md | 41 + designs/outerCube/PTOISA/TAND.md | 103 ++ designs/outerCube/PTOISA/TANDS.md | 106 ++ designs/outerCube/PTOISA/TANDS_zh.md | 106 ++ designs/outerCube/PTOISA/TAND_zh.md | 103 ++ designs/outerCube/PTOISA/TASSIGN.md | 219 ++++ designs/outerCube/PTOISA/TASSIGN_zh.md | 192 ++++ designs/outerCube/PTOISA/TAXPY.md | 40 + designs/outerCube/PTOISA/TAXPY_zh.md | 41 + designs/outerCube/PTOISA/TCI.md | 133 +++ designs/outerCube/PTOISA/TCI_zh.md | 106 ++ designs/outerCube/PTOISA/TCMP.md | 142 +++ designs/outerCube/PTOISA/TCMPS.md | 140 +++ designs/outerCube/PTOISA/TCMPS_zh.md | 113 ++ designs/outerCube/PTOISA/TCMP_zh.md | 115 +++ designs/outerCube/PTOISA/TCOLARGMAX.md | 178 ++++ designs/outerCube/PTOISA/TCOLARGMAX_zh.md | 178 ++++ designs/outerCube/PTOISA/TCOLARGMIN.md | 172 ++++ designs/outerCube/PTOISA/TCOLARGMIN_zh.md | 172 ++++ designs/outerCube/PTOISA/TCOLEXPAND.md | 103 ++ designs/outerCube/PTOISA/TCOLEXPANDADD.md | 89 ++ designs/outerCube/PTOISA/TCOLEXPANDADD_zh.md | 89 ++ designs/outerCube/PTOISA/TCOLEXPANDDIV.md | 89 ++ designs/outerCube/PTOISA/TCOLEXPANDDIV_zh.md | 89 ++ designs/outerCube/PTOISA/TCOLEXPANDEXPDIF.md | 89 ++ .../outerCube/PTOISA/TCOLEXPANDEXPDIF_zh.md | 89 ++ designs/outerCube/PTOISA/TCOLEXPANDMAX.md | 90 ++ designs/outerCube/PTOISA/TCOLEXPANDMAX_zh.md | 89 ++ designs/outerCube/PTOISA/TCOLEXPANDMIN.md | 90 ++ designs/outerCube/PTOISA/TCOLEXPANDMIN_zh.md | 89 ++ designs/outerCube/PTOISA/TCOLEXPANDMUL.md | 89 ++ designs/outerCube/PTOISA/TCOLEXPANDMUL_zh.md | 89 ++ designs/outerCube/PTOISA/TCOLEXPANDSUB.md | 89 ++ designs/outerCube/PTOISA/TCOLEXPANDSUB_zh.md | 89 ++ designs/outerCube/PTOISA/TCOLEXPAND_zh.md | 76 ++ designs/outerCube/PTOISA/TCOLMAX.md | 135 +++ designs/outerCube/PTOISA/TCOLMAX_zh.md | 108 ++ designs/outerCube/PTOISA/TCOLMIN.md | 135 +++ designs/outerCube/PTOISA/TCOLMIN_zh.md | 108 ++ designs/outerCube/PTOISA/TCOLPROD.md | 135 +++ designs/outerCube/PTOISA/TCOLPROD_zh.md | 108 ++ designs/outerCube/PTOISA/TCOLSUM.md | 149 +++ designs/outerCube/PTOISA/TCOLSUM_zh.md | 122 +++ designs/outerCube/PTOISA/TCONCAT.md | 40 + designs/outerCube/PTOISA/TCONCAT_zh.md | 41 + designs/outerCube/PTOISA/TCVT.md | 124 +++ designs/outerCube/PTOISA/TCVT_zh.md | 124 +++ designs/outerCube/PTOISA/TDEQUANT.md | 40 + designs/outerCube/PTOISA/TDEQUANT_zh.md | 41 + designs/outerCube/PTOISA/TDIV.md | 137 +++ designs/outerCube/PTOISA/TDIVS.md | 155 +++ designs/outerCube/PTOISA/TDIVS_zh.md | 128 +++ designs/outerCube/PTOISA/TDIV_zh.md | 110 ++ designs/outerCube/PTOISA/TEXP.md | 128 +++ designs/outerCube/PTOISA/TEXPANDS.md | 146 +++ designs/outerCube/PTOISA/TEXPANDS_zh.md | 134 +++ designs/outerCube/PTOISA/TEXP_zh.md | 101 ++ designs/outerCube/PTOISA/TEXTRACT.md | 138 +++ designs/outerCube/PTOISA/TEXTRACT_FP.md | 90 ++ designs/outerCube/PTOISA/TEXTRACT_FP_zh.md | 59 ++ designs/outerCube/PTOISA/TEXTRACT_zh.md | 138 +++ designs/outerCube/PTOISA/TFILLPAD.md | 133 +++ designs/outerCube/PTOISA/TFILLPAD_EXPAND.md | 89 ++ .../outerCube/PTOISA/TFILLPAD_EXPAND_zh.md | 58 ++ designs/outerCube/PTOISA/TFILLPAD_INPLACE.md | 89 ++ .../outerCube/PTOISA/TFILLPAD_INPLACE_zh.md | 58 ++ designs/outerCube/PTOISA/TFILLPAD_zh.md | 108 ++ designs/outerCube/PTOISA/TFMOD.md | 104 ++ designs/outerCube/PTOISA/TFMODS.md | 107 ++ designs/outerCube/PTOISA/TFMODS_zh.md | 107 ++ designs/outerCube/PTOISA/TFMOD_zh.md | 77 ++ designs/outerCube/PTOISA/TFREE.md | 40 + designs/outerCube/PTOISA/TFREE_zh.md | 41 + designs/outerCube/PTOISA/TGATHER.md | 156 +++ designs/outerCube/PTOISA/TGATHERB.md | 141 +++ designs/outerCube/PTOISA/TGATHERB_zh.md | 114 +++ designs/outerCube/PTOISA/TGATHER_zh.md | 154 +++ designs/outerCube/PTOISA/TGEMV.md | 282 +++++ designs/outerCube/PTOISA/TGEMV_ACC.md | 166 +++ designs/outerCube/PTOISA/TGEMV_ACC_zh.md | 165 +++ designs/outerCube/PTOISA/TGEMV_BIAS.md | 178 ++++ designs/outerCube/PTOISA/TGEMV_BIAS_zh.md | 177 ++++ designs/outerCube/PTOISA/TGEMV_MX.md | 127 +++ designs/outerCube/PTOISA/TGEMV_MX_zh.md | 98 ++ designs/outerCube/PTOISA/TGEMV_zh.md | 282 +++++ designs/outerCube/PTOISA/TGET_SCALE_ADDR.md | 83 ++ .../outerCube/PTOISA/TGET_SCALE_ADDR_zh.md | 81 ++ designs/outerCube/PTOISA/THISTOGRAM.md | 40 + designs/outerCube/PTOISA/THISTOGRAM_zh.md | 41 + designs/outerCube/PTOISA/TIMG2COL.md | 85 ++ designs/outerCube/PTOISA/TIMG2COL_zh.md | 58 ++ designs/outerCube/PTOISA/TINSERT.md | 107 ++ designs/outerCube/PTOISA/TINSERT_FP.md | 90 ++ designs/outerCube/PTOISA/TINSERT_FP_zh.md | 59 ++ designs/outerCube/PTOISA/TINSERT_zh.md | 107 ++ designs/outerCube/PTOISA/TLOAD.md | 159 +++ designs/outerCube/PTOISA/TLOAD_zh.md | 132 +++ designs/outerCube/PTOISA/TLOG.md | 114 +++ designs/outerCube/PTOISA/TLOG_zh.md | 85 ++ designs/outerCube/PTOISA/TLRELU.md | 104 ++ designs/outerCube/PTOISA/TLRELU_zh.md | 104 ++ designs/outerCube/PTOISA/TMATMUL.md | 159 +++ designs/outerCube/PTOISA/TMATMUL_ACC.md | 147 +++ designs/outerCube/PTOISA/TMATMUL_ACC_zh.md | 120 +++ designs/outerCube/PTOISA/TMATMUL_BIAS.md | 153 +++ designs/outerCube/PTOISA/TMATMUL_BIAS_zh.md | 126 +++ designs/outerCube/PTOISA/TMATMUL_MX.md | 202 ++++ designs/outerCube/PTOISA/TMATMUL_MX_zh.md | 173 ++++ designs/outerCube/PTOISA/TMATMUL_zh.md | 132 +++ designs/outerCube/PTOISA/TMAX.md | 135 +++ designs/outerCube/PTOISA/TMAXS.md | 104 ++ designs/outerCube/PTOISA/TMAXS_zh.md | 104 ++ designs/outerCube/PTOISA/TMAX_zh.md | 108 ++ designs/outerCube/PTOISA/TMIN.md | 135 +++ designs/outerCube/PTOISA/TMINS.md | 121 +++ designs/outerCube/PTOISA/TMINS_zh.md | 121 +++ designs/outerCube/PTOISA/TMIN_zh.md | 108 ++ designs/outerCube/PTOISA/TMOV.md | 161 +++ designs/outerCube/PTOISA/TMOV_FP.md | 141 +++ designs/outerCube/PTOISA/TMOV_FP_zh.md | 112 ++ designs/outerCube/PTOISA/TMOV_zh.md | 161 +++ designs/outerCube/PTOISA/TMRGSORT.md | 155 +++ designs/outerCube/PTOISA/TMRGSORT_zh.md | 128 +++ designs/outerCube/PTOISA/TMUL.md | 135 +++ designs/outerCube/PTOISA/TMULS.md | 134 +++ designs/outerCube/PTOISA/TMULS_zh.md | 107 ++ designs/outerCube/PTOISA/TMUL_zh.md | 108 ++ designs/outerCube/PTOISA/TNEG.md | 103 ++ designs/outerCube/PTOISA/TNEG_zh.md | 76 ++ designs/outerCube/PTOISA/TNOT.md | 119 +++ designs/outerCube/PTOISA/TNOT_zh.md | 89 ++ designs/outerCube/PTOISA/TOR.md | 103 ++ designs/outerCube/PTOISA/TORS.md | 106 ++ designs/outerCube/PTOISA/TORS_zh.md | 106 ++ designs/outerCube/PTOISA/TOR_zh.md | 103 ++ designs/outerCube/PTOISA/TPACK.md | 40 + designs/outerCube/PTOISA/TPACK_zh.md | 41 + designs/outerCube/PTOISA/TPARTADD.md | 137 +++ designs/outerCube/PTOISA/TPARTADD_zh.md | 110 ++ designs/outerCube/PTOISA/TPARTMAX.md | 137 +++ designs/outerCube/PTOISA/TPARTMAX_zh.md | 110 ++ designs/outerCube/PTOISA/TPARTMIN.md | 137 +++ designs/outerCube/PTOISA/TPARTMIN_zh.md | 110 ++ designs/outerCube/PTOISA/TPARTMUL.md | 129 +++ designs/outerCube/PTOISA/TPARTMUL_zh.md | 102 ++ designs/outerCube/PTOISA/TPOP.md | 40 + designs/outerCube/PTOISA/TPOP_zh.md | 41 + designs/outerCube/PTOISA/TPREFETCH.md | 94 ++ designs/outerCube/PTOISA/TPREFETCH_zh.md | 65 ++ designs/outerCube/PTOISA/TPRELU.md | 106 ++ designs/outerCube/PTOISA/TPRELU_zh.md | 79 ++ designs/outerCube/PTOISA/TPRINT.md | 157 +++ designs/outerCube/PTOISA/TPRINT_zh.md | 113 ++ designs/outerCube/PTOISA/TPUSH.md | 40 + designs/outerCube/PTOISA/TPUSH_zh.md | 41 + designs/outerCube/PTOISA/TQUANT.md | 94 ++ designs/outerCube/PTOISA/TQUANT_zh.md | 67 ++ designs/outerCube/PTOISA/TRANDOM.md | 122 +++ designs/outerCube/PTOISA/TRANDOM_zh.md | 122 +++ designs/outerCube/PTOISA/TRECIP.md | 113 ++ designs/outerCube/PTOISA/TRECIP_zh.md | 86 ++ designs/outerCube/PTOISA/TRELU.md | 116 +++ designs/outerCube/PTOISA/TRELU_zh.md | 89 ++ designs/outerCube/PTOISA/TREM.md | 115 +++ designs/outerCube/PTOISA/TREMS.md | 115 +++ designs/outerCube/PTOISA/TREMS_zh.md | 115 +++ designs/outerCube/PTOISA/TREM_zh.md | 113 ++ designs/outerCube/PTOISA/TRESHAPE.md | 115 +++ designs/outerCube/PTOISA/TRESHAPE_zh.md | 81 ++ designs/outerCube/PTOISA/TROWARGMAX.md | 148 +++ designs/outerCube/PTOISA/TROWARGMAX_zh.md | 147 +++ designs/outerCube/PTOISA/TROWARGMIN.md | 148 +++ designs/outerCube/PTOISA/TROWARGMIN_zh.md | 147 +++ designs/outerCube/PTOISA/TROWEXPAND.md | 132 +++ designs/outerCube/PTOISA/TROWEXPANDADD.md | 106 ++ designs/outerCube/PTOISA/TROWEXPANDADD_zh.md | 79 ++ designs/outerCube/PTOISA/TROWEXPANDDIV.md | 126 +++ designs/outerCube/PTOISA/TROWEXPANDDIV_zh.md | 126 +++ designs/outerCube/PTOISA/TROWEXPANDEXPDIF.md | 106 ++ .../outerCube/PTOISA/TROWEXPANDEXPDIF_zh.md | 79 ++ designs/outerCube/PTOISA/TROWEXPANDMAX.md | 106 ++ designs/outerCube/PTOISA/TROWEXPANDMAX_zh.md | 79 ++ designs/outerCube/PTOISA/TROWEXPANDMIN.md | 106 ++ designs/outerCube/PTOISA/TROWEXPANDMIN_zh.md | 79 ++ designs/outerCube/PTOISA/TROWEXPANDMUL.md | 126 +++ designs/outerCube/PTOISA/TROWEXPANDMUL_zh.md | 126 +++ designs/outerCube/PTOISA/TROWEXPANDSUB.md | 126 +++ designs/outerCube/PTOISA/TROWEXPANDSUB_zh.md | 126 +++ designs/outerCube/PTOISA/TROWEXPAND_zh.md | 105 ++ designs/outerCube/PTOISA/TROWMAX.md | 135 +++ designs/outerCube/PTOISA/TROWMAX_zh.md | 135 +++ designs/outerCube/PTOISA/TROWMIN.md | 135 +++ designs/outerCube/PTOISA/TROWMIN_zh.md | 135 +++ designs/outerCube/PTOISA/TROWPROD.md | 150 +++ designs/outerCube/PTOISA/TROWPROD_zh.md | 115 +++ designs/outerCube/PTOISA/TROWSUM.md | 147 +++ designs/outerCube/PTOISA/TROWSUM_zh.md | 120 +++ designs/outerCube/PTOISA/TRSQRT.md | 134 +++ designs/outerCube/PTOISA/TRSQRT_zh.md | 107 ++ designs/outerCube/PTOISA/TSCATTER.md | 139 +++ designs/outerCube/PTOISA/TSCATTER_zh.md | 112 ++ designs/outerCube/PTOISA/TSEL.md | 140 +++ designs/outerCube/PTOISA/TSELS.md | 157 +++ designs/outerCube/PTOISA/TSELS_zh.md | 147 +++ designs/outerCube/PTOISA/TSEL_zh.md | 140 +++ designs/outerCube/PTOISA/TSETFMATRIX.md | 89 ++ designs/outerCube/PTOISA/TSETFMATRIX_zh.md | 58 ++ designs/outerCube/PTOISA/TSETHF32MODE.md | 62 ++ designs/outerCube/PTOISA/TSETHF32MODE_zh.md | 61 ++ designs/outerCube/PTOISA/TSETTF32MODE.md | 62 ++ designs/outerCube/PTOISA/TSETTF32MODE_zh.md | 61 ++ .../outerCube/PTOISA/TSET_IMG2COL_PADDING.md | 106 ++ .../PTOISA/TSET_IMG2COL_PADDING_zh.md | 80 ++ designs/outerCube/PTOISA/TSET_IMG2COL_RPT.md | 106 ++ .../outerCube/PTOISA/TSET_IMG2COL_RPT_zh.md | 80 ++ designs/outerCube/PTOISA/TSHL.md | 103 ++ designs/outerCube/PTOISA/TSHLS.md | 108 ++ designs/outerCube/PTOISA/TSHLS_zh.md | 108 ++ designs/outerCube/PTOISA/TSHL_zh.md | 103 ++ designs/outerCube/PTOISA/TSHR.md | 103 ++ designs/outerCube/PTOISA/TSHRS.md | 108 ++ designs/outerCube/PTOISA/TSHRS_zh.md | 108 ++ designs/outerCube/PTOISA/TSHR_zh.md | 103 ++ designs/outerCube/PTOISA/TSORT32.md | 150 +++ designs/outerCube/PTOISA/TSORT32_zh.md | 150 +++ designs/outerCube/PTOISA/TSQRT.md | 130 +++ designs/outerCube/PTOISA/TSQRT_zh.md | 103 ++ designs/outerCube/PTOISA/TSTORE.md | 158 +++ designs/outerCube/PTOISA/TSTORE_FP.md | 147 +++ designs/outerCube/PTOISA/TSTORE_FP_zh.md | 118 +++ designs/outerCube/PTOISA/TSTORE_zh.md | 131 +++ designs/outerCube/PTOISA/TSUB.md | 135 +++ designs/outerCube/PTOISA/TSUBC.md | 103 ++ designs/outerCube/PTOISA/TSUBC_zh.md | 76 ++ designs/outerCube/PTOISA/TSUBS.md | 105 ++ designs/outerCube/PTOISA/TSUBSC.md | 116 +++ designs/outerCube/PTOISA/TSUBSC_zh.md | 89 ++ designs/outerCube/PTOISA/TSUBS_zh.md | 105 ++ designs/outerCube/PTOISA/TSUBVIEW.md | 89 ++ designs/outerCube/PTOISA/TSUBVIEW_zh.md | 87 ++ designs/outerCube/PTOISA/TSUB_zh.md | 108 ++ designs/outerCube/PTOISA/TSYNC.md | 143 +++ designs/outerCube/PTOISA/TSYNC_zh.md | 141 +++ designs/outerCube/PTOISA/TTRANS.md | 144 +++ designs/outerCube/PTOISA/TTRANS_zh.md | 144 +++ designs/outerCube/PTOISA/TTRI.md | 98 ++ designs/outerCube/PTOISA/TTRI_zh.md | 71 ++ designs/outerCube/PTOISA/TXOR.md | 110 ++ designs/outerCube/PTOISA/TXORS.md | 105 ++ designs/outerCube/PTOISA/TXORS_zh.md | 105 ++ designs/outerCube/PTOISA/TXOR_zh.md | 110 ++ designs/outerCube/PTOISA/comm/README.md | 130 +++ designs/outerCube/PTOISA/comm/README_zh.md | 131 +++ designs/outerCube/PTOISA/comm/TBROADCAST.md | 122 +++ .../outerCube/PTOISA/comm/TBROADCAST_zh.md | 123 +++ designs/outerCube/PTOISA/comm/TGATHER.md | 128 +++ designs/outerCube/PTOISA/comm/TGATHER_zh.md | 121 +++ designs/outerCube/PTOISA/comm/TGET.md | 109 ++ designs/outerCube/PTOISA/comm/TGET_ASYNC.md | 130 +++ .../outerCube/PTOISA/comm/TGET_ASYNC_zh.md | 126 +++ designs/outerCube/PTOISA/comm/TGET_zh.md | 106 ++ designs/outerCube/PTOISA/comm/TNOTIFY.md | 100 ++ designs/outerCube/PTOISA/comm/TNOTIFY_zh.md | 101 ++ designs/outerCube/PTOISA/comm/TPUT.md | 131 +++ designs/outerCube/PTOISA/comm/TPUT_ASYNC.md | 132 +++ .../outerCube/PTOISA/comm/TPUT_ASYNC_zh.md | 126 +++ designs/outerCube/PTOISA/comm/TPUT_zh.md | 128 +++ designs/outerCube/PTOISA/comm/TREDUCE.md | 118 +++ designs/outerCube/PTOISA/comm/TREDUCE_zh.md | 112 ++ designs/outerCube/PTOISA/comm/TSCATTER.md | 126 +++ designs/outerCube/PTOISA/comm/TSCATTER_zh.md | 120 +++ designs/outerCube/PTOISA/comm/TTEST.md | 150 +++ designs/outerCube/PTOISA/comm/TTEST_zh.md | 151 +++ designs/outerCube/PTOISA/comm/TWAIT.md | 131 +++ designs/outerCube/PTOISA/comm/TWAIT_zh.md | 131 +++ designs/outerCube/PTOISA/conventions.md | 41 + designs/outerCube/PTOISA/conventions_zh.md | 42 + designs/outerCube/vector4k.md | 962 ++++++++++++++++++ 296 files changed, 34170 insertions(+) create mode 100644 designs/outerCube/PTOISA/MGATHER.md create mode 100644 designs/outerCube/PTOISA/MGATHER_zh.md create mode 100644 designs/outerCube/PTOISA/MSCATTER.md create mode 100644 designs/outerCube/PTOISA/MSCATTER_zh.md create mode 100644 designs/outerCube/PTOISA/PTOISA.md create mode 100644 designs/outerCube/PTOISA/README.md create mode 100644 designs/outerCube/PTOISA/README_zh.md create mode 100644 designs/outerCube/PTOISA/TABS.md create mode 100644 designs/outerCube/PTOISA/TABS_zh.md create mode 100644 designs/outerCube/PTOISA/TADD.md create mode 100644 designs/outerCube/PTOISA/TADDC.md create mode 100644 designs/outerCube/PTOISA/TADDC_zh.md create mode 100644 designs/outerCube/PTOISA/TADDS.md create mode 100644 designs/outerCube/PTOISA/TADDSC.md create mode 100644 designs/outerCube/PTOISA/TADDSC_zh.md create mode 100644 designs/outerCube/PTOISA/TADDS_zh.md create mode 100644 designs/outerCube/PTOISA/TADD_zh.md create mode 100644 designs/outerCube/PTOISA/TALIAS.md create mode 100644 designs/outerCube/PTOISA/TALIAS_zh.md create mode 100644 designs/outerCube/PTOISA/TAND.md create mode 100644 designs/outerCube/PTOISA/TANDS.md create mode 100644 designs/outerCube/PTOISA/TANDS_zh.md create mode 100644 designs/outerCube/PTOISA/TAND_zh.md create mode 100644 designs/outerCube/PTOISA/TASSIGN.md create mode 100644 designs/outerCube/PTOISA/TASSIGN_zh.md create mode 100644 designs/outerCube/PTOISA/TAXPY.md create mode 100644 designs/outerCube/PTOISA/TAXPY_zh.md create mode 100644 designs/outerCube/PTOISA/TCI.md create mode 100644 designs/outerCube/PTOISA/TCI_zh.md create mode 100644 designs/outerCube/PTOISA/TCMP.md create mode 100644 designs/outerCube/PTOISA/TCMPS.md create mode 100644 designs/outerCube/PTOISA/TCMPS_zh.md create mode 100644 designs/outerCube/PTOISA/TCMP_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLARGMAX.md create mode 100644 designs/outerCube/PTOISA/TCOLARGMAX_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLARGMIN.md create mode 100644 designs/outerCube/PTOISA/TCOLARGMIN_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPAND.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDADD.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDADD_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDDIV.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDDIV_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDEXPDIF.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDEXPDIF_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDMAX.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDMAX_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDMIN.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDMIN_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDMUL.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDMUL_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDSUB.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPANDSUB_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLEXPAND_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLMAX.md create mode 100644 designs/outerCube/PTOISA/TCOLMAX_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLMIN.md create mode 100644 designs/outerCube/PTOISA/TCOLMIN_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLPROD.md create mode 100644 designs/outerCube/PTOISA/TCOLPROD_zh.md create mode 100644 designs/outerCube/PTOISA/TCOLSUM.md create mode 100644 designs/outerCube/PTOISA/TCOLSUM_zh.md create mode 100644 designs/outerCube/PTOISA/TCONCAT.md create mode 100644 designs/outerCube/PTOISA/TCONCAT_zh.md create mode 100644 designs/outerCube/PTOISA/TCVT.md create mode 100644 designs/outerCube/PTOISA/TCVT_zh.md create mode 100644 designs/outerCube/PTOISA/TDEQUANT.md create mode 100644 designs/outerCube/PTOISA/TDEQUANT_zh.md create mode 100644 designs/outerCube/PTOISA/TDIV.md create mode 100644 designs/outerCube/PTOISA/TDIVS.md create mode 100644 designs/outerCube/PTOISA/TDIVS_zh.md create mode 100644 designs/outerCube/PTOISA/TDIV_zh.md create mode 100644 designs/outerCube/PTOISA/TEXP.md create mode 100644 designs/outerCube/PTOISA/TEXPANDS.md create mode 100644 designs/outerCube/PTOISA/TEXPANDS_zh.md create mode 100644 designs/outerCube/PTOISA/TEXP_zh.md create mode 100644 designs/outerCube/PTOISA/TEXTRACT.md create mode 100644 designs/outerCube/PTOISA/TEXTRACT_FP.md create mode 100644 designs/outerCube/PTOISA/TEXTRACT_FP_zh.md create mode 100644 designs/outerCube/PTOISA/TEXTRACT_zh.md create mode 100644 designs/outerCube/PTOISA/TFILLPAD.md create mode 100644 designs/outerCube/PTOISA/TFILLPAD_EXPAND.md create mode 100644 designs/outerCube/PTOISA/TFILLPAD_EXPAND_zh.md create mode 100644 designs/outerCube/PTOISA/TFILLPAD_INPLACE.md create mode 100644 designs/outerCube/PTOISA/TFILLPAD_INPLACE_zh.md create mode 100644 designs/outerCube/PTOISA/TFILLPAD_zh.md create mode 100644 designs/outerCube/PTOISA/TFMOD.md create mode 100644 designs/outerCube/PTOISA/TFMODS.md create mode 100644 designs/outerCube/PTOISA/TFMODS_zh.md create mode 100644 designs/outerCube/PTOISA/TFMOD_zh.md create mode 100644 designs/outerCube/PTOISA/TFREE.md create mode 100644 designs/outerCube/PTOISA/TFREE_zh.md create mode 100644 designs/outerCube/PTOISA/TGATHER.md create mode 100644 designs/outerCube/PTOISA/TGATHERB.md create mode 100644 designs/outerCube/PTOISA/TGATHERB_zh.md create mode 100644 designs/outerCube/PTOISA/TGATHER_zh.md create mode 100644 designs/outerCube/PTOISA/TGEMV.md create mode 100644 designs/outerCube/PTOISA/TGEMV_ACC.md create mode 100644 designs/outerCube/PTOISA/TGEMV_ACC_zh.md create mode 100644 designs/outerCube/PTOISA/TGEMV_BIAS.md create mode 100644 designs/outerCube/PTOISA/TGEMV_BIAS_zh.md create mode 100644 designs/outerCube/PTOISA/TGEMV_MX.md create mode 100644 designs/outerCube/PTOISA/TGEMV_MX_zh.md create mode 100644 designs/outerCube/PTOISA/TGEMV_zh.md create mode 100644 designs/outerCube/PTOISA/TGET_SCALE_ADDR.md create mode 100644 designs/outerCube/PTOISA/TGET_SCALE_ADDR_zh.md create mode 100644 designs/outerCube/PTOISA/THISTOGRAM.md create mode 100644 designs/outerCube/PTOISA/THISTOGRAM_zh.md create mode 100644 designs/outerCube/PTOISA/TIMG2COL.md create mode 100644 designs/outerCube/PTOISA/TIMG2COL_zh.md create mode 100644 designs/outerCube/PTOISA/TINSERT.md create mode 100644 designs/outerCube/PTOISA/TINSERT_FP.md create mode 100644 designs/outerCube/PTOISA/TINSERT_FP_zh.md create mode 100644 designs/outerCube/PTOISA/TINSERT_zh.md create mode 100644 designs/outerCube/PTOISA/TLOAD.md create mode 100644 designs/outerCube/PTOISA/TLOAD_zh.md create mode 100644 designs/outerCube/PTOISA/TLOG.md create mode 100644 designs/outerCube/PTOISA/TLOG_zh.md create mode 100644 designs/outerCube/PTOISA/TLRELU.md create mode 100644 designs/outerCube/PTOISA/TLRELU_zh.md create mode 100644 designs/outerCube/PTOISA/TMATMUL.md create mode 100644 designs/outerCube/PTOISA/TMATMUL_ACC.md create mode 100644 designs/outerCube/PTOISA/TMATMUL_ACC_zh.md create mode 100644 designs/outerCube/PTOISA/TMATMUL_BIAS.md create mode 100644 designs/outerCube/PTOISA/TMATMUL_BIAS_zh.md create mode 100644 designs/outerCube/PTOISA/TMATMUL_MX.md create mode 100644 designs/outerCube/PTOISA/TMATMUL_MX_zh.md create mode 100644 designs/outerCube/PTOISA/TMATMUL_zh.md create mode 100644 designs/outerCube/PTOISA/TMAX.md create mode 100644 designs/outerCube/PTOISA/TMAXS.md create mode 100644 designs/outerCube/PTOISA/TMAXS_zh.md create mode 100644 designs/outerCube/PTOISA/TMAX_zh.md create mode 100644 designs/outerCube/PTOISA/TMIN.md create mode 100644 designs/outerCube/PTOISA/TMINS.md create mode 100644 designs/outerCube/PTOISA/TMINS_zh.md create mode 100644 designs/outerCube/PTOISA/TMIN_zh.md create mode 100644 designs/outerCube/PTOISA/TMOV.md create mode 100644 designs/outerCube/PTOISA/TMOV_FP.md create mode 100644 designs/outerCube/PTOISA/TMOV_FP_zh.md create mode 100644 designs/outerCube/PTOISA/TMOV_zh.md create mode 100644 designs/outerCube/PTOISA/TMRGSORT.md create mode 100644 designs/outerCube/PTOISA/TMRGSORT_zh.md create mode 100644 designs/outerCube/PTOISA/TMUL.md create mode 100644 designs/outerCube/PTOISA/TMULS.md create mode 100644 designs/outerCube/PTOISA/TMULS_zh.md create mode 100644 designs/outerCube/PTOISA/TMUL_zh.md create mode 100644 designs/outerCube/PTOISA/TNEG.md create mode 100644 designs/outerCube/PTOISA/TNEG_zh.md create mode 100644 designs/outerCube/PTOISA/TNOT.md create mode 100644 designs/outerCube/PTOISA/TNOT_zh.md create mode 100644 designs/outerCube/PTOISA/TOR.md create mode 100644 designs/outerCube/PTOISA/TORS.md create mode 100644 designs/outerCube/PTOISA/TORS_zh.md create mode 100644 designs/outerCube/PTOISA/TOR_zh.md create mode 100644 designs/outerCube/PTOISA/TPACK.md create mode 100644 designs/outerCube/PTOISA/TPACK_zh.md create mode 100644 designs/outerCube/PTOISA/TPARTADD.md create mode 100644 designs/outerCube/PTOISA/TPARTADD_zh.md create mode 100644 designs/outerCube/PTOISA/TPARTMAX.md create mode 100644 designs/outerCube/PTOISA/TPARTMAX_zh.md create mode 100644 designs/outerCube/PTOISA/TPARTMIN.md create mode 100644 designs/outerCube/PTOISA/TPARTMIN_zh.md create mode 100644 designs/outerCube/PTOISA/TPARTMUL.md create mode 100644 designs/outerCube/PTOISA/TPARTMUL_zh.md create mode 100644 designs/outerCube/PTOISA/TPOP.md create mode 100644 designs/outerCube/PTOISA/TPOP_zh.md create mode 100644 designs/outerCube/PTOISA/TPREFETCH.md create mode 100644 designs/outerCube/PTOISA/TPREFETCH_zh.md create mode 100644 designs/outerCube/PTOISA/TPRELU.md create mode 100644 designs/outerCube/PTOISA/TPRELU_zh.md create mode 100644 designs/outerCube/PTOISA/TPRINT.md create mode 100644 designs/outerCube/PTOISA/TPRINT_zh.md create mode 100644 designs/outerCube/PTOISA/TPUSH.md create mode 100644 designs/outerCube/PTOISA/TPUSH_zh.md create mode 100644 designs/outerCube/PTOISA/TQUANT.md create mode 100644 designs/outerCube/PTOISA/TQUANT_zh.md create mode 100644 designs/outerCube/PTOISA/TRANDOM.md create mode 100644 designs/outerCube/PTOISA/TRANDOM_zh.md create mode 100644 designs/outerCube/PTOISA/TRECIP.md create mode 100644 designs/outerCube/PTOISA/TRECIP_zh.md create mode 100644 designs/outerCube/PTOISA/TRELU.md create mode 100644 designs/outerCube/PTOISA/TRELU_zh.md create mode 100644 designs/outerCube/PTOISA/TREM.md create mode 100644 designs/outerCube/PTOISA/TREMS.md create mode 100644 designs/outerCube/PTOISA/TREMS_zh.md create mode 100644 designs/outerCube/PTOISA/TREM_zh.md create mode 100644 designs/outerCube/PTOISA/TRESHAPE.md create mode 100644 designs/outerCube/PTOISA/TRESHAPE_zh.md create mode 100644 designs/outerCube/PTOISA/TROWARGMAX.md create mode 100644 designs/outerCube/PTOISA/TROWARGMAX_zh.md create mode 100644 designs/outerCube/PTOISA/TROWARGMIN.md create mode 100644 designs/outerCube/PTOISA/TROWARGMIN_zh.md create mode 100644 designs/outerCube/PTOISA/TROWEXPAND.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDADD.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDADD_zh.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDDIV.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDDIV_zh.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDEXPDIF.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDEXPDIF_zh.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDMAX.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDMAX_zh.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDMIN.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDMIN_zh.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDMUL.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDMUL_zh.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDSUB.md create mode 100644 designs/outerCube/PTOISA/TROWEXPANDSUB_zh.md create mode 100644 designs/outerCube/PTOISA/TROWEXPAND_zh.md create mode 100644 designs/outerCube/PTOISA/TROWMAX.md create mode 100644 designs/outerCube/PTOISA/TROWMAX_zh.md create mode 100644 designs/outerCube/PTOISA/TROWMIN.md create mode 100644 designs/outerCube/PTOISA/TROWMIN_zh.md create mode 100644 designs/outerCube/PTOISA/TROWPROD.md create mode 100644 designs/outerCube/PTOISA/TROWPROD_zh.md create mode 100644 designs/outerCube/PTOISA/TROWSUM.md create mode 100644 designs/outerCube/PTOISA/TROWSUM_zh.md create mode 100644 designs/outerCube/PTOISA/TRSQRT.md create mode 100644 designs/outerCube/PTOISA/TRSQRT_zh.md create mode 100644 designs/outerCube/PTOISA/TSCATTER.md create mode 100644 designs/outerCube/PTOISA/TSCATTER_zh.md create mode 100644 designs/outerCube/PTOISA/TSEL.md create mode 100644 designs/outerCube/PTOISA/TSELS.md create mode 100644 designs/outerCube/PTOISA/TSELS_zh.md create mode 100644 designs/outerCube/PTOISA/TSEL_zh.md create mode 100644 designs/outerCube/PTOISA/TSETFMATRIX.md create mode 100644 designs/outerCube/PTOISA/TSETFMATRIX_zh.md create mode 100644 designs/outerCube/PTOISA/TSETHF32MODE.md create mode 100644 designs/outerCube/PTOISA/TSETHF32MODE_zh.md create mode 100644 designs/outerCube/PTOISA/TSETTF32MODE.md create mode 100644 designs/outerCube/PTOISA/TSETTF32MODE_zh.md create mode 100644 designs/outerCube/PTOISA/TSET_IMG2COL_PADDING.md create mode 100644 designs/outerCube/PTOISA/TSET_IMG2COL_PADDING_zh.md create mode 100644 designs/outerCube/PTOISA/TSET_IMG2COL_RPT.md create mode 100644 designs/outerCube/PTOISA/TSET_IMG2COL_RPT_zh.md create mode 100644 designs/outerCube/PTOISA/TSHL.md create mode 100644 designs/outerCube/PTOISA/TSHLS.md create mode 100644 designs/outerCube/PTOISA/TSHLS_zh.md create mode 100644 designs/outerCube/PTOISA/TSHL_zh.md create mode 100644 designs/outerCube/PTOISA/TSHR.md create mode 100644 designs/outerCube/PTOISA/TSHRS.md create mode 100644 designs/outerCube/PTOISA/TSHRS_zh.md create mode 100644 designs/outerCube/PTOISA/TSHR_zh.md create mode 100644 designs/outerCube/PTOISA/TSORT32.md create mode 100644 designs/outerCube/PTOISA/TSORT32_zh.md create mode 100644 designs/outerCube/PTOISA/TSQRT.md create mode 100644 designs/outerCube/PTOISA/TSQRT_zh.md create mode 100644 designs/outerCube/PTOISA/TSTORE.md create mode 100644 designs/outerCube/PTOISA/TSTORE_FP.md create mode 100644 designs/outerCube/PTOISA/TSTORE_FP_zh.md create mode 100644 designs/outerCube/PTOISA/TSTORE_zh.md create mode 100644 designs/outerCube/PTOISA/TSUB.md create mode 100644 designs/outerCube/PTOISA/TSUBC.md create mode 100644 designs/outerCube/PTOISA/TSUBC_zh.md create mode 100644 designs/outerCube/PTOISA/TSUBS.md create mode 100644 designs/outerCube/PTOISA/TSUBSC.md create mode 100644 designs/outerCube/PTOISA/TSUBSC_zh.md create mode 100644 designs/outerCube/PTOISA/TSUBS_zh.md create mode 100644 designs/outerCube/PTOISA/TSUBVIEW.md create mode 100644 designs/outerCube/PTOISA/TSUBVIEW_zh.md create mode 100644 designs/outerCube/PTOISA/TSUB_zh.md create mode 100644 designs/outerCube/PTOISA/TSYNC.md create mode 100644 designs/outerCube/PTOISA/TSYNC_zh.md create mode 100644 designs/outerCube/PTOISA/TTRANS.md create mode 100644 designs/outerCube/PTOISA/TTRANS_zh.md create mode 100644 designs/outerCube/PTOISA/TTRI.md create mode 100644 designs/outerCube/PTOISA/TTRI_zh.md create mode 100644 designs/outerCube/PTOISA/TXOR.md create mode 100644 designs/outerCube/PTOISA/TXORS.md create mode 100644 designs/outerCube/PTOISA/TXORS_zh.md create mode 100644 designs/outerCube/PTOISA/TXOR_zh.md create mode 100644 designs/outerCube/PTOISA/comm/README.md create mode 100644 designs/outerCube/PTOISA/comm/README_zh.md create mode 100644 designs/outerCube/PTOISA/comm/TBROADCAST.md create mode 100644 designs/outerCube/PTOISA/comm/TBROADCAST_zh.md create mode 100644 designs/outerCube/PTOISA/comm/TGATHER.md create mode 100644 designs/outerCube/PTOISA/comm/TGATHER_zh.md create mode 100644 designs/outerCube/PTOISA/comm/TGET.md create mode 100644 designs/outerCube/PTOISA/comm/TGET_ASYNC.md create mode 100644 designs/outerCube/PTOISA/comm/TGET_ASYNC_zh.md create mode 100644 designs/outerCube/PTOISA/comm/TGET_zh.md create mode 100644 designs/outerCube/PTOISA/comm/TNOTIFY.md create mode 100644 designs/outerCube/PTOISA/comm/TNOTIFY_zh.md create mode 100644 designs/outerCube/PTOISA/comm/TPUT.md create mode 100644 designs/outerCube/PTOISA/comm/TPUT_ASYNC.md create mode 100644 designs/outerCube/PTOISA/comm/TPUT_ASYNC_zh.md create mode 100644 designs/outerCube/PTOISA/comm/TPUT_zh.md create mode 100644 designs/outerCube/PTOISA/comm/TREDUCE.md create mode 100644 designs/outerCube/PTOISA/comm/TREDUCE_zh.md create mode 100644 designs/outerCube/PTOISA/comm/TSCATTER.md create mode 100644 designs/outerCube/PTOISA/comm/TSCATTER_zh.md create mode 100644 designs/outerCube/PTOISA/comm/TTEST.md create mode 100644 designs/outerCube/PTOISA/comm/TTEST_zh.md create mode 100644 designs/outerCube/PTOISA/comm/TWAIT.md create mode 100644 designs/outerCube/PTOISA/comm/TWAIT_zh.md create mode 100644 designs/outerCube/PTOISA/conventions.md create mode 100644 designs/outerCube/PTOISA/conventions_zh.md create mode 100644 designs/outerCube/vector4k.md diff --git a/designs/outerCube/PTOISA/MGATHER.md b/designs/outerCube/PTOISA/MGATHER.md new file mode 100644 index 00000000..e092860b --- /dev/null +++ b/designs/outerCube/PTOISA/MGATHER.md @@ -0,0 +1,100 @@ +# MGATHER + + +## Tile Operation Diagram + +![MGATHER tile operation](../figures/isa/MGATHER.svg) + +## Introduction + +Gather-load elements from global memory into a tile using per-element indices. + +## Math Interpretation + +For each element `(i, j)` in the destination valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{mem}[\mathrm{idx}_{i,j}] $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = mgather %mem, %idx : !pto.memref<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.mgather %mem, %idx : (!pto.partition_tensor_view, pto.tile<...>) +-> !pto.tile +``` + +### AS Level 2 (DPS) + +```text +pto.mgather ins(%mem, %idx : !pto.partition_tensor_view, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent MGATHER(TileDst &dst, GlobalData &src, TileInd &indexes, WaitEvents &... events); +``` + +## Constraints + +- **Supported data types**: + - `dst`/`src` element type must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `bfloat16_t`, `float`. + - On AICore targets, `float8_e4m3_t` and `float8_e5m2_t` are also supported. + - `indexes` element type must be `int32_t` or `uint32_t`. +- **Tile and memory types**: + - `dst` must be a vector tile (`TileType::Vec`). + - `indexes` must be a vector tile (`TileType::Vec`). + - `dst` and `indexes` must use row-major layout. + - `src` must be a `GlobalTensor` in GM memory. + - `src` must use `ND` layout. +- **Shape constraints**: + - `dst.Rows == indexes.Rows`. + - `indexes` must be shaped as `[N, 1]` for row-indexed gather or `[N, M]` for element-indexed gather. + - `dst` row width must be 32-byte aligned, that is, `dst.Cols * sizeof(DType)` must be a multiple of 32. + - `src` static shape must satisfy `Shape<1, 1, 1, TableRows, RowWidth>`. +- **Index interpretation**: + - Index interpretation is target-defined. The CPU simulator treats indices as linear element indices into `src.data()`. + - The CPU simulator does not enforce bounds checks on `indexes`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.mgather %mem, %idx : (!pto.partition_tensor_view, pto.tile<...>) +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.mgather %mem, %idx : (!pto.partition_tensor_view, pto.tile<...>) +``` + +### PTO Assembly Form + +```text +%dst = mgather %mem, %idx : !pto.memref<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.mgather ins(%mem, %idx : !pto.partition_tensor_view, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/MGATHER_zh.md b/designs/outerCube/PTOISA/MGATHER_zh.md new file mode 100644 index 00000000..1b0ba2a3 --- /dev/null +++ b/designs/outerCube/PTOISA/MGATHER_zh.md @@ -0,0 +1,100 @@ +# MGATHER + +## 指令示意图 + +![MGATHER tile operation](../figures/isa/MGATHER.svg) + +## 简介 + +使用逐元素索引从全局内存收集加载元素到 Tile 中。 + +## 数学语义 + +对目标有效区域中的每个元素 `(i, j)`: + +$$ \mathrm{dst}_{i,j} = \mathrm{mem}[\mathrm{idx}_{i,j}] $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = mgather %mem, %idx : !pto.memref<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.mgather %mem, %idx : (!pto.partition_tensor_view, pto.tile<...>) +-> !pto.tile +``` + +### AS Level 2(DPS) + +```text +pto.mgather ins(%mem, %idx : !pto.partition_tensor_view, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent MGATHER(TileDst &dst, GlobalData &src, TileInd &indexes, WaitEvents &... events); +``` + +## 约束 + +- **支持的数据类型**: + - `dst`/`src` 的元素类型必须是以下之一:`uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t`、`int32_t`、`half`、`bfloat16_t`、`float`。 + - 在 AICore 目标上,还支持 `float8_e4m3_t` 和 `float8_e5m2_t`。 + - `indexes` 的元素类型必须是 `int32_t` 或 `uint32_t`。 +- **Tile 与内存类型约束**: + - `dst` 必须是向量 Tile(`TileType::Vec`)。 + - `indexes` 必须是向量 Tile(`TileType::Vec`)。 + - `dst` 和 `indexes` 必须使用行主序布局。 + - `src` 必须是位于 GM 内存中的 `GlobalTensor`。 + - `src` 必须使用 `ND` 布局。 +- **形状约束**: + - `dst.Rows == indexes.Rows`。 + - `indexes` 的形状必须为 `[N, 1]`(按行 gather)或 `[N, M]`(按元素 gather)。 + - `dst` 的行宽必须满足 32 字节对齐,即 `dst.Cols * sizeof(DType)` 必须是 32 的倍数。 + - `src` 的静态 shape 必须满足 `Shape<1, 1, 1, TableRows, RowWidth>`。 +- **索引解释**: + - 索引解释由目标定义。CPU 模拟器将索引视为 `src.data()` 中的线性元素索引。 + - CPU 模拟器不对 `indexes` 强制执行边界检查。 + +## 示例 + +参见 `docs/isa/` 和 `docs/coding/tutorials/` 中的相关示例。 + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.mgather %mem, %idx : (!pto.partition_tensor_view, pto.tile<...>) +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.mgather %mem, %idx : (!pto.partition_tensor_view, pto.tile<...>) +``` + +### PTO 汇编形式 + +```text +%dst = mgather %mem, %idx : !pto.memref<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.mgather ins(%mem, %idx : !pto.partition_tensor_view, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/MSCATTER.md b/designs/outerCube/PTOISA/MSCATTER.md new file mode 100644 index 00000000..f7b34223 --- /dev/null +++ b/designs/outerCube/PTOISA/MSCATTER.md @@ -0,0 +1,105 @@ +# MSCATTER + + +## Tile Operation Diagram + +![MSCATTER tile operation](../figures/isa/MSCATTER.svg) + +## Introduction + +Scatter-store elements from a tile into global memory using per-element indices. + +## Math Interpretation + +For each element `(i, j)` in the source valid region: + +$$ \mathrm{mem}[\mathrm{idx}_{i,j}] = \mathrm{src}_{i,j} $$ + +If multiple elements map to the same destination location, the final value is implementation-defined (CPU simulator: last writer wins in row-major iteration order). + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +mscatter %src, %mem, %idx : !pto.memref<...>, !pto.tile<...>, !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +pto.mscatter %src, %idx, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### AS Level 2 (DPS) + +```text +pto.mscatter ins(%src, %idx : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent MSCATTER(GlobalData &dst, TileSrc &src, TileInd &indexes, WaitEvents &... events); +``` + +## Constraints + +- **Supported data types**: + - `src`/`dst` element type must be one of: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `half`, `bfloat16_t`, `float`. + - On AICore targets, `float8_e4m3_t` and `float8_e5m2_t` are also supported. + - `indexes` element type must be `int32_t` or `uint32_t`. +- **Tile and memory types**: + - `src` must be a vector tile (`TileType::Vec`). + - `indexes` must be a vector tile (`TileType::Vec`). + - `src` and `indexes` must use row-major layout. + - `dst` must be a `GlobalTensor` in GM memory. + - `dst` must use `ND` layout. +- **Atomic operation constraints**: + - Non-atomic scatter is supported for all supported element types. + - `Add` atomic mode requires `int32_t`, `uint32_t`, `float`, or `half`. + - `Max`/`Min` atomic mode requires `int32_t` or `float`. +- **Shape constraints**: + - `src.Rows == indexes.Rows`. + - `indexes` must be shaped as `[N, 1]` for row-indexed scatter or `[N, M]` for element-indexed scatter. + - `src` row width must be 32-byte aligned, that is, `src.Cols * sizeof(DType)` must be a multiple of 32. + - `dst` static shape must satisfy `Shape<1, 1, 1, TableRows, RowWidth>`. +- **Index interpretation**: + - Index interpretation is target-defined. The CPU simulator treats indices as linear element indices into `dst.data()`. + - The CPU simulator does not enforce bounds checks on `indexes`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +pto.mscatter %src, %idx, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +pto.mscatter %src, %idx, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### PTO Assembly Form + +```text +mscatter %src, %mem, %idx : !pto.memref<...>, !pto.tile<...>, !pto.tile<...> +# AS Level 2 (DPS) +pto.mscatter ins(%src, %idx : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` + diff --git a/designs/outerCube/PTOISA/MSCATTER_zh.md b/designs/outerCube/PTOISA/MSCATTER_zh.md new file mode 100644 index 00000000..c0d9efdf --- /dev/null +++ b/designs/outerCube/PTOISA/MSCATTER_zh.md @@ -0,0 +1,105 @@ +# MSCATTER + +## 指令示意图 + +![MSCATTER tile operation](../figures/isa/MSCATTER.svg) + +## 简介 + +使用逐元素索引将 Tile 中的元素散播存储到全局内存。 + +## 数学语义 + +对源有效区域中的每个元素 `(i, j)`: + +$$ \mathrm{mem}[\mathrm{idx}_{i,j}] = \mathrm{src}_{i,j} $$ + +如果多个元素映射到同一目标位置,最终值由实现定义(CPU 模拟器:按行主序迭代顺序,最后写入者获胜)。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +mscatter %src, %mem, %idx : !pto.memref<...>, !pto.tile<...>, !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +pto.mscatter %src, %idx, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### AS Level 2(DPS) + +```text +pto.mscatter ins(%src, %idx : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent MSCATTER(GlobalData &dst, TileSrc &src, TileInd &indexes, WaitEvents &... events); +``` + +## 约束 + +- **支持的数据类型**: + - `src`/`dst` 的元素类型必须是以下之一:`int8_t`、`uint8_t`、`int16_t`、`uint16_t`、`int32_t`、`uint32_t`、`half`、`bfloat16_t`、`float`。 + - 在 AICore 目标上,还支持 `float8_e4m3_t` 和 `float8_e5m2_t`。 + - `indexes` 的元素类型必须是 `int32_t` 或 `uint32_t`。 +- **Tile 与内存类型约束**: + - `src` 必须是向量 Tile(`TileType::Vec`)。 + - `indexes` 必须是向量 Tile(`TileType::Vec`)。 + - `src` 和 `indexes` 必须使用行主序布局。 + - `dst` 必须是位于 GM 内存中的 `GlobalTensor`。 + - `dst` 必须使用 `ND` 布局。 +- **原子操作约束**: + - 非原子 scatter 对所有受支持元素类型都可用。 + - `Add` 原子模式要求元素类型为 `int32_t`、`uint32_t`、`float` 或 `half`。 + - `Max`/`Min` 原子模式要求元素类型为 `int32_t` 或 `float`。 +- **形状约束**: + - `src.Rows == indexes.Rows`。 + - `indexes` 的形状必须为 `[N, 1]`(按行 scatter)或 `[N, M]`(按元素 scatter)。 + - `src` 的行宽必须满足 32 字节对齐,即 `src.Cols * sizeof(DType)` 必须是 32 的倍数。 + - `dst` 的静态 shape 必须满足 `Shape<1, 1, 1, TableRows, RowWidth>`。 +- **索引解释**: + - 索引解释由目标定义。CPU 模拟器将索引视为 `dst.data()` 中的线性元素索引。 + - CPU 模拟器不对 `indexes` 强制执行边界检查。 + +## 示例 + +参见 `docs/isa/` 和 `docs/coding/tutorials/` 中的相关示例。 + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +pto.mscatter %src, %idx, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +pto.mscatter %src, %idx, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### PTO 汇编形式 + +```text +mscatter %src, %mem, %idx : !pto.memref<...>, !pto.tile<...>, !pto.tile<...> +# AS Level 2 (DPS) +pto.mscatter ins(%src, %idx : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` + diff --git a/designs/outerCube/PTOISA/PTOISA.md b/designs/outerCube/PTOISA/PTOISA.md new file mode 100644 index 00000000..75fd1802 --- /dev/null +++ b/designs/outerCube/PTOISA/PTOISA.md @@ -0,0 +1,152 @@ +# PTO ISA Overview + +This page is the source-synchronized ISA index generated from `docs/isa/manifest.yaml`. + +## Docs Contents + +| Area | Page | Description | +|---|---|---| +| Overview | [`docs/README.md`](README.md) | PTO ISA guide entry point and navigation. | +| Overview | [`docs/PTOISA.md`](PTOISA.md) | This page (overview + full instruction index). | +| ISA reference | [`docs/isa/README.md`](isa/README.md) | Per-instruction reference directory index. | +| ISA reference | [`docs/isa/conventions.md`](isa/conventions.md) | Shared notation, operands, events, and modifiers. | +| Assembly (PTO-AS) | [`docs/assembly/PTO-AS.md`](assembly/PTO-AS.md) | PTO-AS syntax reference. | +| Source of truth | [`include/pto/common/pto_instr.hpp`](reference/pto-intrinsics-header.md) | C++ intrinsic API (authoritative). | +| PTO Auto Mode | [`docs/auto_mode/README.md`](README.md) | PTO auto mode guide entry point. | + +## Instruction Index (All PTO Instructions) + +| Category | Instruction | Description | +|---|---|---| +| Synchronization | [`TSYNC`](isa/TSYNC.md) | Synchronize PTO execution (wait on events or insert a per-op pipeline barrier). | +| Manual / Resource Binding | [`TASSIGN`](isa/TASSIGN.md) | Bind a Tile object to an implementation-defined on-chip address (manual placement). | +| Manual / Resource Binding | [`TSETFMATRIX`](isa/TSETFMATRIX.md) | Set FMATRIX register(s) for IMG2COL-like ops. | +| Manual / Resource Binding | [`TSET_IMG2COL_RPT`](isa/TSET_IMG2COL_RPT.md) | Set IMG2COL repeat metadata from an IMG2COL configuration tile. | +| Manual / Resource Binding | [`TSET_IMG2COL_PADDING`](isa/TSET_IMG2COL_PADDING.md) | Set IMG2COL padding metadata from an IMG2COL configuration tile. | +| Elementwise (Tile-Tile) | [`TADD`](isa/TADD.md) | Elementwise add of two tiles. | +| Elementwise (Tile-Tile) | [`TABS`](isa/TABS.md) | Elementwise absolute value of a tile. | +| Elementwise (Tile-Tile) | [`TAND`](isa/TAND.md) | Elementwise bitwise AND of two tiles. | +| Elementwise (Tile-Tile) | [`TOR`](isa/TOR.md) | Elementwise bitwise OR of two tiles. | +| Elementwise (Tile-Tile) | [`TSUB`](isa/TSUB.md) | Elementwise subtract of two tiles. | +| Elementwise (Tile-Tile) | [`TMUL`](isa/TMUL.md) | Elementwise multiply of two tiles. | +| Elementwise (Tile-Tile) | [`TMIN`](isa/TMIN.md) | Elementwise minimum of two tiles. | +| Elementwise (Tile-Tile) | [`TMAX`](isa/TMAX.md) | Elementwise maximum of two tiles. | +| Elementwise (Tile-Tile) | [`TCMP`](isa/TCMP.md) | Compare two tiles and write a packed predicate mask. | +| Elementwise (Tile-Tile) | [`TDIV`](isa/TDIV.md) | Elementwise division of two tiles. | +| Elementwise (Tile-Tile) | [`TSHL`](isa/TSHL.md) | Elementwise shift-left of two tiles. | +| Elementwise (Tile-Tile) | [`TSHR`](isa/TSHR.md) | Elementwise shift-right of two tiles. | +| Elementwise (Tile-Tile) | [`TXOR`](isa/TXOR.md) | Elementwise bitwise XOR of two tiles. | +| Elementwise (Tile-Tile) | [`TLOG`](isa/TLOG.md) | Elementwise natural logarithm of a tile. | +| Elementwise (Tile-Tile) | [`TRECIP`](isa/TRECIP.md) | Elementwise reciprocal of a tile. | +| Elementwise (Tile-Tile) | [`TPRELU`](isa/TPRELU.md) | Elementwise PReLU (parametric ReLU) with a per-element slope tile. | +| Elementwise (Tile-Tile) | [`TADDC`](isa/TADDC.md) | Elementwise ternary add: `src0 + src1 + src2`. | +| Elementwise (Tile-Tile) | [`TSUBC`](isa/TSUBC.md) | Elementwise ternary op: `src0 - src1 + src2`. | +| Elementwise (Tile-Tile) | [`TCVT`](isa/TCVT.md) | Elementwise type conversion with a specified rounding mode. | +| Elementwise (Tile-Tile) | [`TSEL`](isa/TSEL.md) | Select between two tiles using a mask tile (per-element selection). | +| Elementwise (Tile-Tile) | [`TRSQRT`](isa/TRSQRT.md) | Elementwise reciprocal square root. | +| Elementwise (Tile-Tile) | [`TSQRT`](isa/TSQRT.md) | Elementwise square root. | +| Elementwise (Tile-Tile) | [`TEXP`](isa/TEXP.md) | Elementwise exponential. | +| Elementwise (Tile-Tile) | [`TNOT`](isa/TNOT.md) | Elementwise bitwise NOT of a tile. | +| Elementwise (Tile-Tile) | [`TRELU`](isa/TRELU.md) | Elementwise ReLU of a tile. | +| Elementwise (Tile-Tile) | [`TNEG`](isa/TNEG.md) | Elementwise negation of a tile. | +| Elementwise (Tile-Tile) | [`TREM`](isa/TREM.md) | Elementwise remainder of two tiles. | +| Elementwise (Tile-Tile) | [`TFMOD`](isa/TFMOD.md) | Elementwise fmod of two tiles. | +| Tile-Scalar / Tile-Immediate | [`TEXPANDS`](isa/TEXPANDS.md) | Broadcast a scalar into a destination tile. | +| Tile-Scalar / Tile-Immediate | [`TCMPS`](isa/TCMPS.md) | Compare a tile against a scalar and write per-element comparison results. | +| Tile-Scalar / Tile-Immediate | [`TSELS`](isa/TSELS.md) | Select between source tile and scalar using a mask tile (per-element selection for source tile). | +| Tile-Scalar / Tile-Immediate | [`TMINS`](isa/TMINS.md) | Elementwise minimum of a tile and a scalar. | +| Tile-Scalar / Tile-Immediate | [`TADDS`](isa/TADDS.md) | Elementwise add a scalar to a tile. | +| Tile-Scalar / Tile-Immediate | [`TSUBS`](isa/TSUBS.md) | Elementwise subtract a scalar from a tile. | +| Tile-Scalar / Tile-Immediate | [`TDIVS`](isa/TDIVS.md) | Elementwise division with a scalar (tile/scalar or scalar/tile). | +| Tile-Scalar / Tile-Immediate | [`TMULS`](isa/TMULS.md) | Elementwise multiply a tile by a scalar. | +| Tile-Scalar / Tile-Immediate | [`TFMODS`](isa/TFMODS.md) | Elementwise remainder with a scalar: `fmod(src, scalar)`. | +| Tile-Scalar / Tile-Immediate | [`TREMS`](isa/TREMS.md) | Elementwise remainder with a scalar: `remainder(src, scalar)`. | +| Tile-Scalar / Tile-Immediate | [`TMAXS`](isa/TMAXS.md) | Elementwise max of a tile and a scalar: `max(src, scalar)`. | +| Tile-Scalar / Tile-Immediate | [`TANDS`](isa/TANDS.md) | Elementwise bitwise AND of a tile and a scalar. | +| Tile-Scalar / Tile-Immediate | [`TORS`](isa/TORS.md) | Elementwise bitwise OR of a tile and a scalar. | +| Tile-Scalar / Tile-Immediate | [`TSHLS`](isa/TSHLS.md) | Elementwise shift-left a tile by a scalar. | +| Tile-Scalar / Tile-Immediate | [`TSHRS`](isa/TSHRS.md) | Elementwise shift-right a tile by a scalar. | +| Tile-Scalar / Tile-Immediate | [`TXORS`](isa/TXORS.md) | Elementwise bitwise XOR of a tile and a scalar. | +| Tile-Scalar / Tile-Immediate | [`TLRELU`](isa/TLRELU.md) | Leaky ReLU with a scalar slope. | +| Tile-Scalar / Tile-Immediate | [`TADDSC`](isa/TADDSC.md) | Elementwise fused add with scalar and a second tile: `src0 + scalar + src1`. | +| Tile-Scalar / Tile-Immediate | [`TSUBSC`](isa/TSUBSC.md) | Elementwise fused op: `src0 - scalar + src1`. | +| Axis Reduce / Expand | [`TROWSUM`](isa/TROWSUM.md) | Reduce each row by summing across columns. | +| Axis Reduce / Expand | [`TROWPROD`](isa/TROWPROD.md) | Reduce each row by multiplying across columns. | +| Axis Reduce / Expand | [`TCOLSUM`](isa/TCOLSUM.md) | Reduce each column by summing across rows. | +| Axis Reduce / Expand | [`TCOLPROD`](isa/TCOLPROD.md) | Reduce each column by multiplying across rows. | +| Axis Reduce / Expand | [`TCOLMAX`](isa/TCOLMAX.md) | Reduce each column by taking the maximum across rows. | +| Axis Reduce / Expand | [`TROWMAX`](isa/TROWMAX.md) | Reduce each row by taking the maximum across columns. | +| Axis Reduce / Expand | [`TROWMIN`](isa/TROWMIN.md) | Reduce each row by taking the minimum across columns. | +| Axis Reduce / Expand | [`TROWARGMAX`](isa/TROWARGMAX.md) | Get the column index of the maximum element for each row. | +| Axis Reduce / Expand | [`TROWARGMIN`](isa/TROWARGMIN.md) | Get the column index of the minimum element for each row. | +| Axis Reduce / Expand | [`TCOLARGMAX`](isa/TCOLARGMAX.md) | Get the row index of the maximum element for each column. | +| Axis Reduce / Expand | [`TCOLARGMIN`](isa/TCOLARGMIN.md) | Get the row index of the minimum element for each column. | +| Axis Reduce / Expand | [`TROWEXPAND`](isa/TROWEXPAND.md) | Broadcast the first element of each source row across the destination row. | +| Axis Reduce / Expand | [`TROWEXPANDDIV`](isa/TROWEXPANDDIV.md) | Row-wise broadcast divide: divide each row of `src0` by a per-row scalar vector `src1`. | +| Axis Reduce / Expand | [`TROWEXPANDMUL`](isa/TROWEXPANDMUL.md) | Row-wise broadcast multiply: multiply each row of `src0` by a per-row scalar vector `src1`. | +| Axis Reduce / Expand | [`TROWEXPANDSUB`](isa/TROWEXPANDSUB.md) | Row-wise broadcast subtract: subtract a per-row scalar vector `src1` from each row of `src0`. | +| Axis Reduce / Expand | [`TROWEXPANDADD`](isa/TROWEXPANDADD.md) | Row-wise broadcast add: add a per-row scalar vector. | +| Axis Reduce / Expand | [`TROWEXPANDMAX`](isa/TROWEXPANDMAX.md) | Row-wise broadcast max with a per-row scalar vector. | +| Axis Reduce / Expand | [`TROWEXPANDMIN`](isa/TROWEXPANDMIN.md) | Row-wise broadcast min with a per-row scalar vector. | +| Axis Reduce / Expand | [`TROWEXPANDEXPDIF`](isa/TROWEXPANDEXPDIF.md) | Row-wise exp-diff: compute exp(src0 - src1) with per-row scalars. | +| Axis Reduce / Expand | [`TCOLMIN`](isa/TCOLMIN.md) | Reduce each column by taking the minimum across rows. | +| Axis Reduce / Expand | [`TCOLEXPAND`](isa/TCOLEXPAND.md) | Broadcast the first element of each source column across the destination column. | +| Axis Reduce / Expand | [`TCOLEXPANDDIV`](isa/TCOLEXPANDDIV.md) | Column-wise broadcast divide: divide each column by a per-column scalar vector. | +| Axis Reduce / Expand | [`TCOLEXPANDMUL`](isa/TCOLEXPANDMUL.md) | Column-wise broadcast multiply: multiply each column by a per-column scalar vector. | +| Axis Reduce / Expand | [`TCOLEXPANDADD`](isa/TCOLEXPANDADD.md) | Column-wise broadcast add with per-column scalar vector. | +| Axis Reduce / Expand | [`TCOLEXPANDMAX`](isa/TCOLEXPANDMAX.md) | Column-wise broadcast max with per-column scalar vector. | +| Axis Reduce / Expand | [`TCOLEXPANDMIN`](isa/TCOLEXPANDMIN.md) | Column-wise broadcast min with per-column scalar vector. | +| Axis Reduce / Expand | [`TCOLEXPANDSUB`](isa/TCOLEXPANDSUB.md) | Column-wise broadcast subtract: subtract a per-column scalar vector from each column. | +| Axis Reduce / Expand | [`TCOLEXPANDEXPDIF`](isa/TCOLEXPANDEXPDIF.md) | Column-wise exp-diff: compute exp(src0 - src1) with per-column scalars. | +| Memory (GM <-> Tile) | [`TLOAD`](isa/TLOAD.md) | Load data from a GlobalTensor (GM) into a Tile. | +| Memory (GM <-> Tile) | [`TPREFETCH`](isa/TPREFETCH.md) | Prefetch data from global memory into a tile-local cache/buffer (hint). | +| Memory (GM <-> Tile) | [`TSTORE`](isa/TSTORE.md) | Store data from a Tile into a GlobalTensor (GM), optionally using atomic write or quantization parameters. | +| Memory (GM <-> Tile) | [`TSTORE_FP`](isa/TSTORE_FP.md) | Store an accumulator tile into global memory using a scaling (`fp`) tile for vector quantization parameters. | +| Memory (GM <-> Tile) | [`MGATHER`](isa/MGATHER.md) | Gather-load elements from global memory into a tile using per-element indices. | +| Memory (GM <-> Tile) | [`MSCATTER`](isa/MSCATTER.md) | Scatter-store elements from a tile into global memory using per-element indices. | +| Matrix Multiply | [`TGEMV_MX`](isa/TGEMV_MX.md) | GEMV with additional scaling tiles for mixed-precision / quantized matrix-vector compute. | +| Matrix Multiply | [`TMATMUL_MX`](isa/TMATMUL_MX.md) | Matrix multiply (GEMM) with additional scaling tiles for mixed-precision / quantized matmul on supported targets. | +| Matrix Multiply | [`TMATMUL`](isa/TMATMUL.md) | Matrix multiply (GEMM) producing an accumulator/output tile. | +| Matrix Multiply | [`TMATMUL_ACC`](isa/TMATMUL_ACC.md) | Matrix multiply with accumulator input (fused accumulate). | +| Matrix Multiply | [`TMATMUL_BIAS`](isa/TMATMUL_BIAS.md) | Matrix multiply with bias add. | +| Matrix Multiply | [`TGEMV`](isa/TGEMV.md) | General Matrix-Vector multiplication producing an accumulator/output tile. | +| Matrix Multiply | [`TGEMV_ACC`](isa/TGEMV_ACC.md) | GEMV with explicit accumulator input/output tiles. | +| Matrix Multiply | [`TGEMV_BIAS`](isa/TGEMV_BIAS.md) | GEMV with bias add. | +| Data Movement / Layout | [`TEXTRACT`](isa/TEXTRACT.md) | Extract a sub-tile from a source tile. | +| Data Movement / Layout | [`TEXTRACT_FP`](isa/TEXTRACT_FP.md) | Extract with fp/scaling tile (vector-quantization parameters). | +| Data Movement / Layout | [`TIMG2COL`](isa/TIMG2COL.md) | Image-to-column transform for convolution-like workloads. | +| Data Movement / Layout | [`TINSERT`](isa/TINSERT.md) | Insert a sub-tile into a destination tile at an (indexRow, indexCol) offset. | +| Data Movement / Layout | [`TINSERT_FP`](isa/TINSERT_FP.md) | Insert with fp/scaling tile (vector-quantization parameters). | +| Data Movement / Layout | [`TFILLPAD`](isa/TFILLPAD.md) | Copy+pad a tile outside the valid region with a compile-time pad value. | +| Data Movement / Layout | [`TFILLPAD_INPLACE`](isa/TFILLPAD_INPLACE.md) | In-place fill/pad variant. | +| Data Movement / Layout | [`TFILLPAD_EXPAND`](isa/TFILLPAD_EXPAND.md) | Fill/pad while allowing dst to be larger than src. | +| Data Movement / Layout | [`TMOV`](isa/TMOV.md) | Move/copy between tiles, optionally applying implementation-defined conversion modes. | +| Data Movement / Layout | [`TMOV_FP`](isa/TMOV_FP.md) | Move/convert from an accumulator tile into a destination tile, using a scaling (`fp`) tile for vector quantization parameters. | +| Data Movement / Layout | [`TRESHAPE`](isa/TRESHAPE.md) | Reinterpret a tile as another tile type/shape while preserving the underlying bytes. | +| Data Movement / Layout | [`TTRANS`](isa/TTRANS.md) | Transpose with an implementation-defined temporary tile. | +| Data Movement / Layout | [`TSUBVIEW`](isa/TSUBVIEW.md) | Reinterpret a tile as a subtile of another tile. | +| Data Movement / Layout | [`TGET_SCALE_ADDR`](isa/TGET_SCALE_ADDR.md) | Bind the on-chip address of output tile to a scaled factor of that of input tile. | +| Complex | [`TPRINT`](isa/TPRINT.md) | Debug/print elements from a tile (implementation-defined). | +| Complex | [`TMRGSORT`](isa/TMRGSORT.md) | Merge sort for multiple sorted lists (implementation-defined element format and layout). | +| Complex | [`TSORT32`](isa/TSORT32.md) | Sort 32-element blocks of `src` with accompanying `idx` entries and output sorted value-index pairs. | +| Complex | [`TGATHER`](isa/TGATHER.md) | Gather/select elements using either an index tile or a compile-time mask pattern. | +| Complex | [`TCI`](isa/TCI.md) | Generate a contiguous integer sequence into a destination tile. | +| Complex | [`TTRI`](isa/TTRI.md) | Generate a triangular (lower/upper) mask tile. | +| Complex | [`TPARTADD`](isa/TPARTADD.md) | Partial elementwise add with implementation-defined handling of mismatched valid regions. | +| Complex | [`TPARTMUL`](isa/TPARTMUL.md) | Partial elementwise multiply with implementation-defined handling of mismatched valid regions. | +| Complex | [`TPARTMAX`](isa/TPARTMAX.md) | Partial elementwise max with implementation-defined handling of mismatched valid regions. | +| Complex | [`TPARTMIN`](isa/TPARTMIN.md) | Partial elementwise min with implementation-defined handling of mismatched valid regions. | +| Complex | [`TGATHERB`](isa/TGATHERB.md) | Gather elements using byte offsets. | +| Complex | [`TSCATTER`](isa/TSCATTER.md) | Scatter rows of a source tile into a destination tile using per-element row indices. | +| Complex | [`TQUANT`](isa/TQUANT.md) | Quantize a tile (e.g. FP32 to FP8) producing exponent/scaling/max outputs. | +| Communication | [`TPUT`](isa/comm/TPUT.md) | Remote write: transfer local data to remote NPU memory (GM → UB → GM). | +| Communication | [`TGET`](isa/comm/TGET.md) | Remote read: read remote NPU data to local memory (GM → UB → GM). | +| Communication | [`TPUT_ASYNC`](isa/comm/TPUT_ASYNC.md) | Asynchronous remote write (local GM → DMA engine → remote GM). | +| Communication | [`TGET_ASYNC`](isa/comm/TGET_ASYNC.md) | Asynchronous remote read (remote GM → DMA engine → local GM). | +| Communication | [`TNOTIFY`](isa/comm/TNOTIFY.md) | Send flag notification to remote NPU. | +| Communication | [`TWAIT`](isa/comm/TWAIT.md) | Blocking wait until signal(s) meet comparison condition. | +| Communication | [`TTEST`](isa/comm/TTEST.md) | Non-blocking test if signal(s) meet comparison condition. | +| Communication | [`TGATHER`](isa/comm/TGATHER.md) | Gather data from all ranks and concatenate along DIM_3. | +| Communication | [`TSCATTER`](isa/comm/TSCATTER.md) | Scatter data to all ranks by splitting along DIM_3. | +| Communication | [`TREDUCE`](isa/comm/TREDUCE.md) | Gather and reduce data from all ranks element-wise to local. | +| Communication | [`TBROADCAST`](isa/comm/TBROADCAST.md) | Broadcast data from current NPU to all ranks. | diff --git a/designs/outerCube/PTOISA/README.md b/designs/outerCube/PTOISA/README.md new file mode 100644 index 00000000..a637245d --- /dev/null +++ b/designs/outerCube/PTOISA/README.md @@ -0,0 +1,153 @@ +

+ PTO Tile Lib +

+ +# PTO ISA Reference + +This directory contains the per-instruction reference for the PTO Tile Lib ISA. + +- Source of truth (C++ intrinsics): `include/pto/common/pto_instr.hpp` +- [Common conventions (operands, events, modifiers)](conventions.md) + +## Synchronization +- [TSYNC](TSYNC.md) - Synchronize PTO execution (wait on events or insert a per-op pipeline barrier). + +## Manual / Resource Binding +- [TASSIGN](TASSIGN.md) - Bind a Tile object to an implementation-defined on-chip address (manual placement). +- [TSETFMATRIX](TSETFMATRIX.md) - Set FMATRIX register(s) for IMG2COL-like ops. +- [TSET_IMG2COL_RPT](TSET_IMG2COL_RPT.md) - Set IMG2COL repeat metadata from an IMG2COL configuration tile. +- [TSET_IMG2COL_PADDING](TSET_IMG2COL_PADDING.md) - Set IMG2COL padding metadata from an IMG2COL configuration tile. + +## Elementwise (Tile-Tile) +- [TADD](TADD.md) - Elementwise add of two tiles. +- [TABS](TABS.md) - Elementwise absolute value of a tile. +- [TAND](TAND.md) - Elementwise bitwise AND of two tiles. +- [TOR](TOR.md) - Elementwise bitwise OR of two tiles. +- [TSUB](TSUB.md) - Elementwise subtract of two tiles. +- [TMUL](TMUL.md) - Elementwise multiply of two tiles. +- [TMIN](TMIN.md) - Elementwise minimum of two tiles. +- [TMAX](TMAX.md) - Elementwise maximum of two tiles. +- [TCMP](TCMP.md) - Compare two tiles and write a packed predicate mask. +- [TDIV](TDIV.md) - Elementwise division of two tiles. +- [TSHL](TSHL.md) - Elementwise shift-left of two tiles. +- [TSHR](TSHR.md) - Elementwise shift-right of two tiles. +- [TXOR](TXOR.md) - Elementwise bitwise XOR of two tiles. +- [TLOG](TLOG.md) - Elementwise natural logarithm of a tile. +- [TRECIP](TRECIP.md) - Elementwise reciprocal of a tile. +- [TPRELU](TPRELU.md) - Elementwise PReLU (parametric ReLU) with a per-element slope tile. +- [TADDC](TADDC.md) - Elementwise ternary add: `src0 + src1 + src2`. +- [TSUBC](TSUBC.md) - Elementwise ternary op: `src0 - src1 + src2`. +- [TCVT](TCVT.md) - Elementwise type conversion with a specified rounding mode. +- [TSEL](TSEL.md) - Select between two tiles using a mask tile (per-element selection). +- [TRSQRT](TRSQRT.md) - Elementwise reciprocal square root. +- [TSQRT](TSQRT.md) - Elementwise square root. +- [TEXP](TEXP.md) - Elementwise exponential. +- [TNOT](TNOT.md) - Elementwise bitwise NOT of a tile. +- [TRELU](TRELU.md) - Elementwise ReLU of a tile. +- [TNEG](TNEG.md) - Elementwise negation of a tile. +- [TREM](TREM.md) - Elementwise remainder of two tiles. +- [TFMOD](TFMOD.md) - Elementwise fmod of two tiles. + +## Tile-Scalar / Tile-Immediate +- [TEXPANDS](TEXPANDS.md) - Broadcast a scalar into a destination tile. +- [TCMPS](TCMPS.md) - Compare a tile against a scalar and write per-element comparison results. +- [TSELS](TSELS.md) - Select between source tile and scalar using a mask tile (per-element selection for source tile). +- [TMINS](TMINS.md) - Elementwise minimum of a tile and a scalar. +- [TADDS](TADDS.md) - Elementwise add a scalar to a tile. +- [TSUBS](TSUBS.md) - Elementwise subtract a scalar from a tile. +- [TDIVS](TDIVS.md) - Elementwise division with a scalar (tile/scalar or scalar/tile). +- [TMULS](TMULS.md) - Elementwise multiply a tile by a scalar. +- [TFMODS](TFMODS.md) - Elementwise remainder with a scalar: `fmod(src, scalar)`. +- [TREMS](TREMS.md) - Elementwise remainder with a scalar: `remainder(src, scalar)`. +- [TMAXS](TMAXS.md) - Elementwise max of a tile and a scalar: `max(src, scalar)`. +- [TANDS](TANDS.md) - Elementwise bitwise AND of a tile and a scalar. +- [TORS](TORS.md) - Elementwise bitwise OR of a tile and a scalar. +- [TSHLS](TSHLS.md) - Elementwise shift-left a tile by a scalar. +- [TSHRS](TSHRS.md) - Elementwise shift-right a tile by a scalar. +- [TXORS](TXORS.md) - Elementwise bitwise XOR of a tile and a scalar. +- [TLRELU](TLRELU.md) - Leaky ReLU with a scalar slope. +- [TADDSC](TADDSC.md) - Elementwise fused add with scalar and a second tile: `src0 + scalar + src1`. +- [TSUBSC](TSUBSC.md) - Elementwise fused op: `src0 - scalar + src1`. + +## Axis Reduce / Expand +- [TROWSUM](TROWSUM.md) - Reduce each row by summing across columns. +- [TROWPROD](TROWPROD.md) - Reduce each row by multiplying across columns. +- [TCOLSUM](TCOLSUM.md) - Reduce each column by summing across rows. +- [TCOLPROD](TCOLPROD.md) - Reduce each column by multiplying across rows. +- [TCOLMAX](TCOLMAX.md) - Reduce each column by taking the maximum across rows. +- [TROWMAX](TROWMAX.md) - Reduce each row by taking the maximum across columns. +- [TROWMIN](TROWMIN.md) - Reduce each row by taking the minimum across columns. +- [TROWARGMAX](TROWARGMAX.md) - Get the column index of the maximum element for each row. +- [TROWARGMIN](TROWARGMIN.md) - Get the column index of the minimum element for each row. +- [TCOLARGMAX](TCOLARGMAX.md) - Get the row index of the maximum element for each column. +- [TCOLARGMIN](TCOLARGMIN.md) - Get the row index of the minimum element for each column. +- [TROWEXPAND](TROWEXPAND.md) - Broadcast the first element of each source row across the destination row. +- [TROWEXPANDDIV](TROWEXPANDDIV.md) - Row-wise broadcast divide: divide each row of `src0` by a per-row scalar vector `src1`. +- [TROWEXPANDMUL](TROWEXPANDMUL.md) - Row-wise broadcast multiply: multiply each row of `src0` by a per-row scalar vector `src1`. +- [TROWEXPANDSUB](TROWEXPANDSUB.md) - Row-wise broadcast subtract: subtract a per-row scalar vector `src1` from each row of `src0`. +- [TROWEXPANDADD](TROWEXPANDADD.md) - Row-wise broadcast add: add a per-row scalar vector. +- [TROWEXPANDMAX](TROWEXPANDMAX.md) - Row-wise broadcast max with a per-row scalar vector. +- [TROWEXPANDMIN](TROWEXPANDMIN.md) - Row-wise broadcast min with a per-row scalar vector. +- [TROWEXPANDEXPDIF](TROWEXPANDEXPDIF.md) - Row-wise exp-diff: compute exp(src0 - src1) with per-row scalars. +- [TCOLMIN](TCOLMIN.md) - Reduce each column by taking the minimum across rows. +- [TCOLEXPAND](TCOLEXPAND.md) - Broadcast the first element of each source column across the destination column. +- [TCOLEXPANDDIV](TCOLEXPANDDIV.md) - Column-wise broadcast divide: divide each column by a per-column scalar vector. +- [TCOLEXPANDMUL](TCOLEXPANDMUL.md) - Column-wise broadcast multiply: multiply each column by a per-column scalar vector. +- [TCOLEXPANDADD](TCOLEXPANDADD.md) - Column-wise broadcast add with per-column scalar vector. +- [TCOLEXPANDMAX](TCOLEXPANDMAX.md) - Column-wise broadcast max with per-column scalar vector. +- [TCOLEXPANDMIN](TCOLEXPANDMIN.md) - Column-wise broadcast min with per-column scalar vector. +- [TCOLEXPANDSUB](TCOLEXPANDSUB.md) - Column-wise broadcast subtract: subtract a per-column scalar vector from each column. +- [TCOLEXPANDEXPDIF](TCOLEXPANDEXPDIF.md) - Column-wise exp-diff: compute exp(src0 - src1) with per-column scalars. + +## Memory (GM <-> Tile) +- [TLOAD](TLOAD.md) - Load data from a GlobalTensor (GM) into a Tile. +- [TPREFETCH](TPREFETCH.md) - Prefetch data from global memory into a tile-local cache/buffer (hint). +- [TSTORE](TSTORE.md) - Store data from a Tile into a GlobalTensor (GM), optionally using atomic write or quantization parameters. +- [TSTORE_FP](TSTORE_FP.md) - Store an accumulator tile into global memory using a scaling (`fp`) tile for vector quantization parameters. +- [MGATHER](MGATHER.md) - Gather-load elements from global memory into a tile using per-element indices. +- [MSCATTER](MSCATTER.md) - Scatter-store elements from a tile into global memory using per-element indices. + +## Matrix Multiply +- [TGEMV_MX](TGEMV_MX.md) - GEMV with additional scaling tiles for mixed-precision / quantized matrix-vector compute. +- [TMATMUL_MX](TMATMUL_MX.md) - Matrix multiply (GEMM) with additional scaling tiles for mixed-precision / quantized matmul on supported targets. +- [TMATMUL](TMATMUL.md) - Matrix multiply (GEMM) producing an accumulator/output tile. +- [TMATMUL_ACC](TMATMUL_ACC.md) - Matrix multiply with accumulator input (fused accumulate). +- [TMATMUL_BIAS](TMATMUL_BIAS.md) - Matrix multiply with bias add. +- [TGEMV](TGEMV.md) - General Matrix-Vector multiplication producing an accumulator/output tile. +- [TGEMV_ACC](TGEMV_ACC.md) - GEMV with explicit accumulator input/output tiles. +- [TGEMV_BIAS](TGEMV_BIAS.md) - GEMV with bias add. + +## Data Movement / Layout +- [TEXTRACT](TEXTRACT.md) - Extract a sub-tile from a source tile. +- [TEXTRACT_FP](TEXTRACT_FP.md) - Extract with fp/scaling tile (vector-quantization parameters). +- [TIMG2COL](TIMG2COL.md) - Image-to-column transform for convolution-like workloads. +- [TINSERT](TINSERT.md) - Insert a sub-tile into a destination tile at an (indexRow, indexCol) offset. +- [TINSERT_FP](TINSERT_FP.md) - Insert with fp/scaling tile (vector-quantization parameters). +- [TFILLPAD](TFILLPAD.md) - Copy+pad a tile outside the valid region with a compile-time pad value. +- [TFILLPAD_INPLACE](TFILLPAD_INPLACE.md) - In-place fill/pad variant. +- [TFILLPAD_EXPAND](TFILLPAD_EXPAND.md) - Fill/pad while allowing dst to be larger than src. +- [TMOV](TMOV.md) - Move/copy between tiles, optionally applying implementation-defined conversion modes. +- [TMOV_FP](TMOV_FP.md) - Move/convert from an accumulator tile into a destination tile, using a scaling (`fp`) tile for vector quantization parameters. +- [TRESHAPE](TRESHAPE.md) - Reinterpret a tile as another tile type/shape while preserving the underlying bytes. +- [TTRANS](TTRANS.md) - Transpose with an implementation-defined temporary tile. +- [TSUBVIEW](TSUBVIEW.md) - Reinterpret a tile as a subtile of another tile. +- [TGET_SCALE_ADDR](TGET_SCALE_ADDR.md) - Bind the on-chip address of output tile to a scaled factor of that of input tile. + +## Complex +- [TPRINT](TPRINT.md) - Debug/print elements from a tile (implementation-defined). +- [TMRGSORT](TMRGSORT.md) - Merge sort for multiple sorted lists (implementation-defined element format and layout). +- [TSORT32](TSORT32.md) - Sort each 32-element block of `src` together with the corresponding indices from `idx`, and write the sorted value-index pairs into `dst`. +- [TGATHER](TGATHER.md) - Gather/select elements using either an index tile or a compile-time mask pattern. +- [TCI](TCI.md) - Generate a contiguous integer sequence into a destination tile. +- [TTRI](TTRI.md) - Generate a triangular (lower/upper) mask tile. +- [TPARTADD](TPARTADD.md) - Partial elementwise add with implementation-defined handling of mismatched valid regions. +- [TPARTMUL](TPARTMUL.md) - Partial elementwise multiply with implementation-defined handling of mismatched valid regions. +- [TPARTMAX](TPARTMAX.md) - Partial elementwise max with implementation-defined handling of mismatched valid regions. +- [TPARTMIN](TPARTMIN.md) - Partial elementwise min with implementation-defined handling of mismatched valid regions. +- [TGATHERB](TGATHERB.md) - Gather elements using byte offsets. +- [TSCATTER](TSCATTER.md) - Scatter rows of a source tile into a destination tile using per-element row indices. +- [TQUANT](TQUANT.md) - Quantize a tile (e.g. FP32 to FP8) producing exponent/scaling/max outputs. + +## Communication + +See [comm/README.md](comm/README.md) for the full per-instruction communication ISA reference (point-to-point, async, synchronization, and collective operations). diff --git a/designs/outerCube/PTOISA/README_zh.md b/designs/outerCube/PTOISA/README_zh.md new file mode 100644 index 00000000..00d087e3 --- /dev/null +++ b/designs/outerCube/PTOISA/README_zh.md @@ -0,0 +1,153 @@ +

+ PTO Tile Lib +

+ +# PTO ISA 参考 + +本目录是 PTO Tile Lib ISA 的指令参考(每条指令一页)。 + +- 权威来源(C++ 内建函数):`include/pto/common/pto_instr.hpp` +- [通用约定(操作数、事件、修饰符)](conventions_zh.md) + +## 同步 +- [TSYNC](TSYNC_zh.md) - 同步 PTO 执行(等待事件或插入每操作流水线屏障)。 + +## 手动 / 资源绑定 +- [TASSIGN](TASSIGN_zh.md) - 将 Tile 对象绑定到实现定义的片上地址(手动放置)。 +- [TSETFMATRIX](TSETFMATRIX_zh.md) - 为类 IMG2COL 操作设置 FMATRIX 寄存器。 +- [TSET_IMG2COL_RPT](TSET_IMG2COL_RPT_zh.md) - 从 IMG2COL 配置 Tile 设置 IMG2COL 重复次数元数据。 +- [TSET_IMG2COL_PADDING](TSET_IMG2COL_PADDING_zh.md) - 从 IMG2COL 配置 Tile 设置 IMG2COL 填充元数据。 + +## 逐元素(Tile-Tile) +- [TADD](TADD_zh.md) - 两个 Tile 的逐元素加法。 +- [TABS](TABS_zh.md) - Tile 的逐元素绝对值。 +- [TAND](TAND_zh.md) - 两个 Tile 的逐元素按位与。 +- [TOR](TOR_zh.md) - 两个 Tile 的逐元素按位或。 +- [TSUB](TSUB_zh.md) - 两个 Tile 的逐元素减法。 +- [TMUL](TMUL_zh.md) - 两个 Tile 的逐元素乘法。 +- [TMIN](TMIN_zh.md) - 两个 Tile 的逐元素最小值。 +- [TMAX](TMAX_zh.md) - 两个 Tile 的逐元素最大值。 +- [TCMP](TCMP_zh.md) - 比较两个 Tile 并写入一个打包的谓词掩码。 +- [TDIV](TDIV_zh.md) - 两个 Tile 的逐元素除法。 +- [TSHL](TSHL_zh.md) - 两个 Tile 的逐元素左移。 +- [TSHR](TSHR_zh.md) - 两个 Tile 的逐元素右移。 +- [TXOR](TXOR_zh.md) - 两个 Tile 的逐元素按位异或。 +- [TLOG](TLOG_zh.md) - Tile 的逐元素自然对数。 +- [TRECIP](TRECIP_zh.md) - Tile 的逐元素倒数。 +- [TPRELU](TPRELU_zh.md) - 带逐元素斜率 Tile 的逐元素参数化 ReLU (PReLU)。 +- [TADDC](TADDC_zh.md) - 三元逐元素加法:`src0 + src1 + src2`。 +- [TSUBC](TSUBC_zh.md) - 三元逐元素运算:`src0 - src1 + src2`。 +- [TCVT](TCVT_zh.md) - 带指定舍入模式的逐元素类型转换。 +- [TSEL](TSEL_zh.md) - 使用掩码 Tile 在两个 Tile 之间进行选择(逐元素选择)。 +- [TRSQRT](TRSQRT_zh.md) - 逐元素倒数平方根。 +- [TSQRT](TSQRT_zh.md) - 逐元素平方根。 +- [TEXP](TEXP_zh.md) - 逐元素指数运算。 +- [TNOT](TNOT_zh.md) - Tile 的逐元素按位取反。 +- [TRELU](TRELU_zh.md) - Tile 的逐元素 ReLU。 +- [TNEG](TNEG_zh.md) - Tile 的逐元素取负。 +- [TREM](TREM_zh.md) - 两个 Tile 的逐元素余数,余数符号与除数相同。 +- [TFMOD](TFMOD_zh.md) - 两个 Tile 的逐元素余数,余数符号与被除数相同。 + +## Tile-标量 / Tile-立即数 +- [TEXPANDS](TEXPANDS_zh.md) - 将标量广播到目标 Tile 中。 +- [TCMPS](TCMPS_zh.md) - 将 Tile 与标量比较并写入逐元素比较结果。 +- [TSELS](TSELS_zh.md) - 使用掩码 Tile 在源 Tile 和标量之间进行选择(源 Tile 逐元素选择)。 +- [TMINS](TMINS_zh.md) - Tile 与标量的逐元素最小值。 +- [TADDS](TADDS_zh.md) - Tile 与标量的逐元素加法。 +- [TSUBS](TSUBS_zh.md) - 从 Tile 中逐元素减去一个标量。 +- [TDIVS](TDIVS_zh.md) - 与标量的逐元素除法(Tile/标量 或 标量/Tile)。 +- [TMULS](TMULS_zh.md) - Tile 与标量的逐元素乘法。 +- [TFMODS](TFMODS_zh.md) - 与标量的逐元素余数:`fmod(src, scalar)`。 +- [TREMS](TREMS_zh.md) - 与标量的逐元素余数:`remainder(src, scalar)`。 +- [TMAXS](TMAXS_zh.md) - Tile 与标量的逐元素最大值:`max(src, scalar)`。 +- [TANDS](TANDS_zh.md) - Tile 与标量的逐元素按位与。 +- [TORS](TORS_zh.md) - Tile 与标量的逐元素按位或。 +- [TSHLS](TSHLS_zh.md) - Tile 按标量逐元素左移。 +- [TSHRS](TSHRS_zh.md) - Tile 按标量逐元素右移。 +- [TXORS](TXORS_zh.md) - Tile 与标量的逐元素按位异或。 +- [TLRELU](TLRELU_zh.md) - 带标量斜率的 Leaky ReLU。 +- [TADDSC](TADDSC_zh.md) - 与标量和第二个 Tile 的融合逐元素加法:`src0 + scalar + src1`。 +- [TSUBSC](TSUBSC_zh.md) - 融合逐元素运算:`src0 - scalar + src1`。 + +## 轴归约 / 扩展 +- [TROWSUM](TROWSUM_zh.md) - 通过对列求和来归约每一行。 +- [TROWPROD](TROWPROD_zh.md) - 通过跨列乘积来归约每一行。 +- [TCOLSUM](TCOLSUM_zh.md) - 通过对行求和来归约每一列。 +- [TCOLPROD](TCOLPROD_zh.md) - 通过跨行乘积来归约每一列。 +- [TCOLMAX](TCOLMAX_zh.md) - 通过取行间最大值来归约每一列。 +- [TROWMAX](TROWMAX_zh.md) - 通过取列间最大值来归约每一行。 +- [TROWMIN](TROWMIN_zh.md) - 通过取列间最小值来归约每一行。 +- [TROWARGMAX](TROWARGMAX_zh.md) - 获取每行最大值对应列索引。 +- [TROWARGMIN](TROWARGMIN_zh.md) - 获取每行最小值对应列索引。 +- [TCOLARGMAX](TCOLARGMAX_zh.md) - 获取每列最大值对应行索引。 +- [TCOLARGMIN](TCOLARGMIN_zh.md) - 获取每列最小值对应行索引。 +- [TROWEXPAND](TROWEXPAND_zh.md) - 将每个源行的第一个元素广播到目标行中。 +- [TROWEXPANDDIV](TROWEXPANDDIV_zh.md) - 行广播除法:将 `src0` 的每一行除以一个每行标量向量 `src1`。 +- [TROWEXPANDMUL](TROWEXPANDMUL_zh.md) - 行广播乘法:将 `src0` 的每一行乘以一个每行标量向量 `src1`。 +- [TROWEXPANDSUB](TROWEXPANDSUB_zh.md) - 行广播减法:从 `src0` 的每一行中减去一个每行标量向量 `src1`。 +- [TROWEXPANDADD](TROWEXPANDADD_zh.md) - 行广播加法:加上一个每行标量向量。 +- [TROWEXPANDMAX](TROWEXPANDMAX_zh.md) - 行广播最大值:与每行标量向量取最大值。 +- [TROWEXPANDMIN](TROWEXPANDMIN_zh.md) - 行广播最小值:与每行标量向量取最小值。 +- [TROWEXPANDEXPDIF](TROWEXPANDEXPDIF_zh.md) - 行指数差运算:计算 exp(src0 - src1),其中 src1 为每行标量。 +- [TCOLMIN](TCOLMIN_zh.md) - 通过取行间最小值来归约每一列。 +- [TCOLEXPAND](TCOLEXPAND_zh.md) - 将每个源列的第一个元素广播到目标列中。 +- [TCOLEXPANDDIV](TCOLEXPANDDIV_zh.md) - 列广播除法:将每一列除以一个每列标量向量。 +- [TCOLEXPANDMUL](TCOLEXPANDMUL_zh.md) - 列广播乘法:将每一列乘以一个每列标量向量。 +- [TCOLEXPANDADD](TCOLEXPANDADD_zh.md) - 列广播加法:对每一列加上每列标量向量。 +- [TCOLEXPANDMAX](TCOLEXPANDMAX_zh.md) - 列广播最大值:与每列标量向量取最大值。 +- [TCOLEXPANDMIN](TCOLEXPANDMIN_zh.md) - 列广播最小值:与每列标量向量取最小值。 +- [TCOLEXPANDSUB](TCOLEXPANDSUB_zh.md) - 列广播减法:从每一列中减去一个每列标量向量。 +- [TCOLEXPANDEXPDIF](TCOLEXPANDEXPDIF_zh.md) - 列指数差运算:计算 exp(src0 - src1),其中 src1 为每列标量。 + +## 内存(GM <-> Tile) +- [TLOAD](TLOAD_zh.md) - 从 GlobalTensor (GM) 加载数据到 Tile。 +- [TPREFETCH](TPREFETCH_zh.md) - 将数据从全局内存预取到 Tile 本地缓存/缓冲区(提示)。 +- [TSTORE](TSTORE_zh.md) - 将 Tile 中的数据存储到 GlobalTensor (GM),可选使用原子写入或量化参数。 +- [TSTORE_FP](TSTORE_FP_zh.md) - 使用缩放 (`fp`) Tile 作为向量量化参数,将累加器 Tile 存储到全局内存。 +- [MGATHER](MGATHER_zh.md) - 使用逐元素索引从全局内存收集加载元素到 Tile 中。 +- [MSCATTER](MSCATTER_zh.md) - 使用逐元素索引将 Tile 中的元素散播存储到全局内存。 + +## 矩阵乘 +- [TGEMV_MX](TGEMV_MX_zh.md) - 带缩放 Tile 的 GEMV 变体,支持混合精度/量化矩阵向量计算。 +- [TMATMUL_MX](TMATMUL_MX_zh.md) - 带额外缩放 Tile 的矩阵乘法 (GEMM),用于支持目标上的混合精度/量化矩阵乘法。 +- [TMATMUL](TMATMUL_zh.md) - 矩阵乘法 (GEMM),生成累加器/输出 Tile。 +- [TMATMUL_ACC](TMATMUL_ACC_zh.md) - 带累加器输入的矩阵乘法(融合累加)。 +- [TMATMUL_BIAS](TMATMUL_BIAS_zh.md) - 带偏置加法的矩阵乘法。 +- [TGEMV](TGEMV_zh.md) - 通用矩阵-向量乘法,生成累加器/输出 Tile。 +- [TGEMV_ACC](TGEMV_ACC_zh.md) - 带显式累加器输入/输出 Tile 的 GEMV。 +- [TGEMV_BIAS](TGEMV_BIAS_zh.md) - 带偏置加法的 GEMV。 + +## 数据搬运 / 布局 +- [TEXTRACT](TEXTRACT_zh.md) - 从源 Tile 中提取子 Tile。 +- [TEXTRACT_FP](TEXTRACT_FP_zh.md) - 带 fp/缩放 Tile 的提取(向量量化参数)。 +- [TIMG2COL](TIMG2COL_zh.md) - 用于类卷积工作负载的图像到列变换。 +- [TINSERT](TINSERT_zh.md) - 在 (indexRow, indexCol) 偏移处将子 Tile 插入到目标 Tile 中。 +- [TINSERT_FP](TINSERT_FP_zh.md) - 带 fp/缩放 Tile 的插入(向量量化参数)。 +- [TFILLPAD](TFILLPAD_zh.md) - 复制 Tile 并在有效区域外使用编译时填充值进行填充。 +- [TFILLPAD_INPLACE](TFILLPAD_INPLACE_zh.md) - 原地填充/填充变体。 +- [TFILLPAD_EXPAND](TFILLPAD_EXPAND_zh.md) - 填充/填充时允许目标大于源。 +- [TMOV](TMOV_zh.md) - 在 Tile 之间移动/复制,可选应用实现定义的转换模式。 +- [TMOV_FP](TMOV_FP_zh.md) - 使用缩放 (`fp`) Tile 作为向量量化参数,将累加器 Tile 移动/转换到目标 Tile。 +- [TRESHAPE](TRESHAPE_zh.md) - 将 Tile 重新解释为另一种 Tile 类型/形状,同时保留底层字节。 +- [TTRANS](TTRANS_zh.md) - 使用实现定义的临时 Tile 进行转置。 +- [TSUBVIEW](TSUBVIEW_zh.md) - 表达一个tile是另一个tile的subview。 +- [TGET_SCALE_ADDR](TGET_SCALE_ADDR_zh.md) - 将输出tile的片上内存值绑定为扩展后的输入tile内存的值。 + +## 复杂指令 +- [TPRINT](TPRINT_zh.md) - 调试/打印 Tile 中的元素(实现定义)。 +- [TMRGSORT](TMRGSORT_zh.md) - 用于多个已排序列表的归并排序(实现定义的元素格式和布局)。 +- [TSORT32](TSORT32_zh.md) - 对 `src` 的每个 32 元素块,与 `idx` 中对应的索引一起进行排序,并将排序后的值-索引对写入 `dst`。 +- [TGATHER](TGATHER_zh.md) - 使用索引 Tile 或编译时掩码模式来收集/选择元素。 +- [TCI](TCI_zh.md) - 生成连续整数序列到目标 Tile 中。 +- [TTRI](TTRI_zh.md) - 生成三角(下/上)掩码 Tile。 +- [TPARTADD](TPARTADD_zh.md) - 部分逐元素加法,对不匹配的有效区域具有实现定义的处理方式。 +- [TPARTMUL](TPARTMUL_zh.md) - 部分逐元素乘法,对有效区域不一致的处理为实现定义。 +- [TPARTMAX](TPARTMAX_zh.md) - 部分逐元素最大值,对不匹配的有效区域具有实现定义的处理方式。 +- [TPARTMIN](TPARTMIN_zh.md) - 部分逐元素最小值,对不匹配的有效区域具有实现定义的处理方式。 +- [TGATHERB](TGATHERB_zh.md) - 使用字节偏移量收集元素。 +- [TSCATTER](TSCATTER_zh.md) - 使用逐元素行索引将源 Tile 的行散播到目标 Tile 中。 +- [TQUANT](TQUANT_zh.md) - 量化 Tile(例如 FP32 到 FP8),生成指数/缩放/最大值输出。 + +## 通信 + +完整的通信 ISA 指令参考(点对点、异步、同步原语及集合通信)见 [comm/README_zh.md](comm/README_zh.md)。 diff --git a/designs/outerCube/PTOISA/TABS.md b/designs/outerCube/PTOISA/TABS.md new file mode 100644 index 00000000..3931b44e --- /dev/null +++ b/designs/outerCube/PTOISA/TABS.md @@ -0,0 +1,133 @@ +# TABS + + +## Tile Operation Diagram + +![TABS tile operation](../figures/isa/TABS.svg) + +## Introduction + +Elementwise absolute value of a tile. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \left|\mathrm{src}_{i,j}\right| $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tabs %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tabs %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tabs ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tabs %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tabs ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TABS(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (CPU sim)**: + - `TileData::DType` must be one of: `int32_t`, `int`, `int16_t`, `half`, `float`. + - The implementation iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. +- **Implementation checks (Costmodel)**: + - `TileData::DType` must be one of: `int32_t`、`int16_t`、`int8_t`、`uint8_t`、`half`、`float`. +- **Implementation checks (NPU)**: + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TABS(dst, src); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TABS(dst, src); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tabs %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tabs %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tabs %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tabs ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TABS_zh.md b/designs/outerCube/PTOISA/TABS_zh.md new file mode 100644 index 00000000..3b7f23ce --- /dev/null +++ b/designs/outerCube/PTOISA/TABS_zh.md @@ -0,0 +1,106 @@ +# TABS + +## 指令示意图 + +![TABS tile operation](../figures/isa/TABS.svg) + +## 简介 + +Tile 的逐元素绝对值。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \left|\mathrm{src}_{i,j}\right| $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tabs %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tabs %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tabs ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tabs %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tabs ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TABS(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (CPU sim)**: + - `TileData::DType` must be one of: `int32_t`, `int`, `int16_t`, `half`, `float`. + - The implementation iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. +- **实现检查 (Costmodel)**: + - `TileData::DType` must be one of: `int32_t`、`int16_t`、`int8_t`、`uint8_t`、`half`、`float`. +- **实现检查 (NPU)**: + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TABS(dst, src); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TABS(dst, src); +} +``` diff --git a/designs/outerCube/PTOISA/TADD.md b/designs/outerCube/PTOISA/TADD.md new file mode 100644 index 00000000..d6b4a5ad --- /dev/null +++ b/designs/outerCube/PTOISA/TADD.md @@ -0,0 +1,129 @@ +# TADD + + +## Tile Operation Diagram + +![TADD tile operation](../figures/isa/TADD.svg) + +## Introduction + +Elementwise add of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} + \mathrm{src1}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tadd %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TADD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `int32_t`, `uint32_t`, `float`, `int16_t`, `uint16_t`, `half`, `bfloat16_t`, `uint8_t`, `int8_t`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src0/src1` are assumed to be compatible (not validated by explicit runtime checks in this op). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TADD(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TADD(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tadd %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TADDC.md b/designs/outerCube/PTOISA/TADDC.md new file mode 100644 index 00000000..f91ad263 --- /dev/null +++ b/designs/outerCube/PTOISA/TADDC.md @@ -0,0 +1,103 @@ +# TADDC + + +## Tile Operation Diagram + +![TADDC tile operation](../figures/isa/TADDC.svg) + +## Introduction + +Elementwise ternary add: `src0 + src1 + src2`. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} + \mathrm{src1}_{i,j} + \mathrm{src2}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = taddc %src0, %src1, %src2 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.taddc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.taddc ins(%src0, %src1, %src2 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.taddc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.taddc ins(%src0, %src1, %src2 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TADDC(TileData &dst, TileData &src0, TileData &src1, TileData &src2, WaitEvents &... events); +``` + +## Constraints + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, c, out; + TADDC(out, a, b, c); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.taddc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.taddc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = taddc %src0, %src1, %src2 : !pto.tile<...> +# AS Level 2 (DPS) +pto.taddc ins(%src0, %src1, %src2 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TADDC_zh.md b/designs/outerCube/PTOISA/TADDC_zh.md new file mode 100644 index 00000000..1a9fec65 --- /dev/null +++ b/designs/outerCube/PTOISA/TADDC_zh.md @@ -0,0 +1,76 @@ +# TADDC + +## 指令示意图 + +![TADDC tile operation](../figures/isa/TADDC.svg) + +## 简介 + +三元逐元素加法:`src0 + src1 + src2`。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} + \mathrm{src1}_{i,j} + \mathrm{src2}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = taddc %src0, %src1, %src2 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.taddc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.taddc ins(%src0, %src1, %src2 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.taddc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.taddc ins(%src0, %src1, %src2 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TADDC(TileData &dst, TileData &src0, TileData &src1, TileData &src2, WaitEvents &... events); +``` + +## 约束 + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, c, out; + TADDC(out, a, b, c); +} +``` diff --git a/designs/outerCube/PTOISA/TADDS.md b/designs/outerCube/PTOISA/TADDS.md new file mode 100644 index 00000000..c30212cc --- /dev/null +++ b/designs/outerCube/PTOISA/TADDS.md @@ -0,0 +1,134 @@ +# TADDS + + +## Tile Operation Diagram + +![TADDS tile operation](../figures/isa/TADDS.svg) + +## Introduction + +Elementwise add a scalar to a tile. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} + \mathrm{scalar} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tadds %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tadds %src, %scalar : (!pto.tile<...>,dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tadds ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tadds %src, %scalar : (!pto.tile<...>,dtype) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tadds ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TADDS(TileDataDst &dst, TileDataSrc &src0, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int`, `int16_t`, `half`, `float16_t`, `float`, `float32_t`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `float`, `bfloat16_t`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidCol() == dst.GetValidCol()`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TADDS(dst, src, 1.0f); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TADDS(dst, src, 1.0f); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tadds %src, %scalar : (!pto.tile<...>,dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tadds %src, %scalar : (!pto.tile<...>,dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tadds %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tadds ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TADDSC.md b/designs/outerCube/PTOISA/TADDSC.md new file mode 100644 index 00000000..697dcef1 --- /dev/null +++ b/designs/outerCube/PTOISA/TADDSC.md @@ -0,0 +1,116 @@ +# TADDSC + + +## Tile Operation Diagram + +![TADDSC tile operation](../figures/isa/TADDSC.svg) + +## Introduction + +Elementwise fused add with scalar and a second tile: `src0 + scalar + src1`. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} + \mathrm{scalar} + \mathrm{src1}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = taddsc %src0, %scalar, %src1 : !pto.tile<...>, f32, !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.taddsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.taddsc ins(%src0, %scalar, %src1 : !pto.tile_buf<...>, dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.taddsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.taddsc ins(%src0, %scalar, %src1 : !pto.tile_buf<...>, dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TADDSC(TileData& dst, TileData& src0, typename TileData::DType scalar, TileData& src1, + WaitEvents&... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Common constraints**: + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `dst`, `src0` and `src1` must have the same valid row/col. + - Scalar type must match the Tile data type. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, out; + TADDSC(out, a, 2.0f, b); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.taddsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.taddsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = taddsc %src0, %scalar, %src1 : !pto.tile<...>, f32, !pto.tile<...> +# AS Level 2 (DPS) +pto.taddsc ins(%src0, %scalar, %src1 : !pto.tile_buf<...>, dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TADDSC_zh.md b/designs/outerCube/PTOISA/TADDSC_zh.md new file mode 100644 index 00000000..b3920061 --- /dev/null +++ b/designs/outerCube/PTOISA/TADDSC_zh.md @@ -0,0 +1,89 @@ +# TADDSC + +## 指令示意图 + +![TADDSC tile operation](../figures/isa/TADDSC.svg) + +## 简介 + +与标量和第二个 Tile 的融合逐元素加法:`src0 + scalar + src1`。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} + \mathrm{scalar} + \mathrm{src1}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = taddsc %src0, %scalar, %src1 : !pto.tile<...>, f32, !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.taddsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.taddsc ins(%src0, %scalar, %src1 : !pto.tile_buf<...>, dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.taddsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.taddsc ins(%src0, %scalar, %src1 : !pto.tile_buf<...>, dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TADDSC(TileData& dst, TileData& src0, typename TileData::DType scalar, TileData& src1, + WaitEvents&... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **Common constraints**: + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `dst`, `src0` and `src1` must have the same valid row/col. + - Scalar type must match the Tile data type. +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, out; + TADDSC(out, a, 2.0f, b); +} +``` diff --git a/designs/outerCube/PTOISA/TADDS_zh.md b/designs/outerCube/PTOISA/TADDS_zh.md new file mode 100644 index 00000000..1ab2a003 --- /dev/null +++ b/designs/outerCube/PTOISA/TADDS_zh.md @@ -0,0 +1,107 @@ +# TADDS + +## 指令示意图 + +![TADDS tile operation](../figures/isa/TADDS.svg) + +## 简介 + +Tile 与标量的逐元素加法。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} + \mathrm{scalar} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tadds %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tadds %src, %scalar : (!pto.tile<...>,dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tadds ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tadds %src, %scalar : (!pto.tile<...>,dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tadds ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TADDS(TileDataDst &dst, TileDataSrc &src0, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int`, `int16_t`, `half`, `float16_t`, `float`, `float32_t`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `float`, `bfloat16_t`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidCol() == dst.GetValidCol()`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TADDS(dst, src, 1.0f); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TADDS(dst, src, 1.0f); +} +``` diff --git a/designs/outerCube/PTOISA/TADD_zh.md b/designs/outerCube/PTOISA/TADD_zh.md new file mode 100644 index 00000000..8041e0fe --- /dev/null +++ b/designs/outerCube/PTOISA/TADD_zh.md @@ -0,0 +1,102 @@ +# TADD + +## 指令示意图 + +![TADD tile operation](../figures/isa/TADD.svg) + +## 简介 + +两个 Tile 的逐元素加法。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} + \mathrm{src1}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tadd %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TADD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `int32_t`, `uint32_t`, `float`, `int16_t`, `uint16_t`, `half`, `bfloat16_t`, `uint8_t`, `int8_t`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src0/src1` are assumed to be compatible (not validated by explicit runtime checks in this op). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TADD(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TADD(dst, src0, src1); +} +``` diff --git a/designs/outerCube/PTOISA/TALIAS.md b/designs/outerCube/PTOISA/TALIAS.md new file mode 100644 index 00000000..a827762d --- /dev/null +++ b/designs/outerCube/PTOISA/TALIAS.md @@ -0,0 +1,40 @@ +# TALIAS + +## Tile Operation Diagram + +![TALIAS tile operation](../figures/isa/TALIAS.svg) + +## Introduction + +Create an alias tile view that shares the original tile storage. + +## Math Interpretation + +Semantics are instruction-specific. Unless stated otherwise, behavior is defined over the destination valid region. + +## Assembly Syntax + +PTO-AS form: see `docs/assembly/PTO-AS.md`. + +### IR Level 1 (SSA) + +```text +%dst = pto.talias ... +``` + +### IR Level 2 (DPS) + +```text +pto.talias ins(...) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`. + +## Constraints + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## Examples + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TALIAS_zh.md b/designs/outerCube/PTOISA/TALIAS_zh.md new file mode 100644 index 00000000..d182462c --- /dev/null +++ b/designs/outerCube/PTOISA/TALIAS_zh.md @@ -0,0 +1,41 @@ +# TALIAS + +## 指令示意图 + +![TALIAS tile operation](../figures/isa/TALIAS.svg) + +## 简介 + +创建一个与原始 Tile 共享底层存储的别名视图。 + +## 数学语义 + +语义随指令而变化。 Unless stated otherwise, behavior is defined over the destination valid region. + +## 汇编语法 + +PTO-AS 形式:参见 `docs/assembly/PTO-AS.md`. + +### AS Level 1(SSA) + +```text +%dst = pto.talias ... +``` + +### AS Level 2(DPS) + +```text +pto.talias ins(...) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`. + +## 约束 + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## 示例 + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TAND.md b/designs/outerCube/PTOISA/TAND.md new file mode 100644 index 00000000..cb660442 --- /dev/null +++ b/designs/outerCube/PTOISA/TAND.md @@ -0,0 +1,103 @@ +# TAND + + +## Tile Operation Diagram + +![TAND tile operation](../figures/isa/TAND.svg) + +## Introduction + +Elementwise bitwise AND of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \;\&\; \mathrm{src1}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tand %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tand %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tand ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TAND(TileData &dst, TileData &src0, TileData &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Supported element types are 1-byte or 2-byte integral types. + - `dst`, `src0`, and `src1` must use the same element type. + - `dst`, `src0`, and `src1` must be row-major. + - Runtime: `src0.GetValidRow()/GetValidCol()` and `src1.GetValidRow()/GetValidCol()` must match `dst`. +- **Implementation checks (A5)**: + - Supported element types are 1-byte, 2-byte, or 4-byte integral types. + - `dst`, `src0`, and `src1` must use the same element type. + - `dst`, `src0`, and `src1` must be row-major. + - Runtime: `src0.GetValidRow()/GetValidCol()` and `src1.GetValidRow()/GetValidCol()` must match `dst`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, out; + TAND(out, a, b); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tand %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tand %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tand %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tand ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TANDS.md b/designs/outerCube/PTOISA/TANDS.md new file mode 100644 index 00000000..e829518b --- /dev/null +++ b/designs/outerCube/PTOISA/TANDS.md @@ -0,0 +1,106 @@ +# TANDS + + +## Tile Operation Diagram + +![TANDS tile operation](../figures/isa/TANDS.svg) + +## Introduction + +Elementwise bitwise AND of a tile and a scalar. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \;\&\; \mathrm{scalar} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tands %src, %scalar : !pto.tile<...>, i32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tands %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tands ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TANDS(TileDataDst &dst, TileDataSrc &src, typename TileDataDst::DType scalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Intended for integral element types. + - `dst` and `src` must use the same element type. + - `dst` and `src` must be vector tiles. + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`. + - In manual mode, setting the source tile and destination tile to the same memory is unsupported. +- **Implementation checks (A5)**: + - Intended for integral element types supported by `TEXPANDS` and `TAND`. + - `dst` and `src` must use the same element type. + - `dst` and `src` must be vector tiles. + - In manual mode, setting the source tile and destination tile to the same memory is unsupported. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc = Tile; + TileDst dst; + TileSrc src; + TANDS(dst, src, 0xffu); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tands %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tands %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tands %src, %scalar : !pto.tile<...>, i32 +# AS Level 2 (DPS) +pto.tands ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TANDS_zh.md b/designs/outerCube/PTOISA/TANDS_zh.md new file mode 100644 index 00000000..0968ae50 --- /dev/null +++ b/designs/outerCube/PTOISA/TANDS_zh.md @@ -0,0 +1,106 @@ +# TANDS + +## 指令示意图 + +![TANDS tile operation](../figures/isa/TANDS.svg) + +## 简介 + +Tile 与标量的逐元素按位与。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \;\&\; \mathrm{scalar} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tands %src, %scalar : !pto.tile<...>, i32 +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tands %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tands ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TANDS(TileDataDst &dst, TileDataSrc &src, typename TileDataDst::DType scalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - 适用于整数元素类型。 + - `dst` 和 `src` 必须使用相同的元素类型。 + - `dst` 和 `src` 必须是向量 Tile。 + - 运行时:`src.GetValidRow() == dst.GetValidRow()` 且 `src.GetValidCol() == dst.GetValidCol()`。 + - 在手动模式下,不支持将源 Tile 和目标 Tile 设置为相同的内存。 +- **实现检查 (A5)**: + - 适用于 `TEXPANDS` 和 `TAND` 支持的整数元素类型。 + - `dst` 和 `src` 必须使用相同的元素类型。 + - `dst` 和 `src` 必须是向量 Tile。 + - 在手动模式下,不支持将源 Tile 和目标 Tile 设置为相同的内存。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc = Tile; + TileDst dst; + TileSrc src; + TANDS(dst, src, 0xffu); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tands %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tands %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tands %src, %scalar : !pto.tile<...>, i32 +# AS Level 2 (DPS) +pto.tands ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TAND_zh.md b/designs/outerCube/PTOISA/TAND_zh.md new file mode 100644 index 00000000..e91e08bd --- /dev/null +++ b/designs/outerCube/PTOISA/TAND_zh.md @@ -0,0 +1,103 @@ +# TAND + +## 指令示意图 + +![TAND tile operation](../figures/isa/TAND.svg) + +## 简介 + +两个 Tile 的逐元素按位与。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \;\&\; \mathrm{src1}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tand %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tand %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tand ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TAND(TileData &dst, TileData &src0, TileData &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - 支持的元素类型为 1 字节或 2 字节整数类型。 + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - 运行时:`src0.GetValidRow()/GetValidCol()` 和 `src1.GetValidRow()/GetValidCol()` 必须与 `dst` 一致。 +- **实现检查 (A5)**: + - 支持的元素类型为 1 字节、2 字节或 4 字节整数类型。 + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - 运行时:`src0.GetValidRow()/GetValidCol()` 和 `src1.GetValidRow()/GetValidCol()` 必须与 `dst` 一致。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, out; + TAND(out, a, b); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tand %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tand %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tand %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tand ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TASSIGN.md b/designs/outerCube/PTOISA/TASSIGN.md new file mode 100644 index 00000000..92ad1c2c --- /dev/null +++ b/designs/outerCube/PTOISA/TASSIGN.md @@ -0,0 +1,219 @@ +# TASSIGN + + +## Tile Operation Diagram + +![TASSIGN tile operation](../figures/isa/TASSIGN.svg) + +## Introduction + +Bind a Tile object to an implementation-defined on-chip address (manual placement). + +## Math Interpretation + +Not applicable. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +`TASSIGN` is typically introduced by bufferization/lowering when mapping SSA tiles to physical storage. + +Synchronous form: + +```text +tassign %tile, %addr : !pto.tile<...>, index +``` + +### AS Level 1 (SSA) + +```text +pto.tassign %tile, %addr : !pto.tile<...>, dtype +``` + +### AS Level 2 (DPS) + +```text +pto.tassign ins(%tile, %addr : !pto.tile_buf<...>, dtype) +``` + +### IR Level 1 (SSA) + +```text +pto.tassign %tile, %addr : !pto.tile<...>, dtype +``` + +### IR Level 2 (DPS) + +```text +pto.tassign ins(%tile, %addr : !pto.tile_buf<...>, dtype) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`. + +### Form 1: Runtime address + +```cpp +template +PTO_INST void TASSIGN(T& obj, AddrType addr); +``` + +Binds `obj` to the on-chip address `addr`. No compile-time bounds checking is +performed (the address value is not available at compile time). + +### Form 2: Compile-time address (with static bounds check) + +```cpp +template +PTO_INST void TASSIGN(T& obj); +``` + +Binds `obj` to the on-chip address `Addr`. Because `Addr` is a non-type +template parameter, the compiler performs the following **compile-time** checks +via `static_assert`: + +| Check | Condition | Assertion ID | Error message | +|-------|-----------|--------------|---------------| +| Memory space exists | `capacity > 0` | SA-0351 | Memory space is not available on this architecture. | +| Tile fits in memory | `tile_size <= capacity` | SA-0352 | Tile storage size exceeds memory space capacity. | +| Address in bounds | `Addr + tile_size <= capacity` | SA-0353 | addr + tile_size exceeds memory space capacity (out of bounds). | +| Address aligned | `Addr % alignment == 0` | SA-0354 | addr is not properly aligned for the target memory space. | + +See `docs/coding/debug.md` (fix recipe `FIX-A12`) for suggested remedies. + +The memory space, capacity, and alignment are determined automatically from the +Tile's `TileType` (i.e. `Loc` template parameter): + +| TileType | Memory | Capacity (A2A3) | Capacity (A5) | Capacity (Kirin9030) | Capacity (KirinX90) | Alignment | +|----------|--------|-----------------|---------------|----------------------|---------------------|-----------| +| Vec | UB | 192 KB | 256 KB | 128 KB | 128 KB | 32 B | +| Mat | L1 | 512 KB | 512 KB | 512 KB | 1024 KB | 32 B | +| Left | L0A | 64 KB | 64 KB | 32 KB | 64 KB | 32 B | +| Right | L0B | 64 KB | 64 KB | 32 KB | 64 KB | 32 B | +| Acc | L0C | 128 KB | 256 KB | 64 KB | 128 KB | 32 B | +| Bias | Bias | 1 KB | 4 KB | 1 KB | 1 KB | 32 B | +| Scaling | FBuffer | 2 KB | 4 KB | 7 KB | 6 KB | 32 B | +| ScaleLeft | L0A | N/A | 4 KB | N/A | N/A | 32 B | +| ScaleRight | L0B | N/A | 4 KB | N/A | N/A | 32 B | + +Capacities can be overridden at build time via `-D` flags (e.g. +`-DPTO_UBUF_SIZE_BYTES=262144`). See `include/pto/common/buffer_limits.hpp`. + +**Note:** This overload is only available for `Tile` and `ConvTile` types. For +`GlobalTensor`, use `TASSIGN(obj, pointer)` (Form 1). + +## Constraints + +- **Implementation checks**: + - If `obj` is a Tile: + - In manual mode (when `__PTO_AUTO__` is not defined), `addr` must be an integral type and is reinterpreted as the tile's storage address. + - In auto mode (when `__PTO_AUTO__` is defined), `TASSIGN(tile, addr)` is a no-op. + - If `obj` is a `GlobalTensor`: + - `addr` must be a pointer type. + - The pointed-to element type must match `GlobalTensor::DType`. + +## Examples + +### Runtime address (no compile-time check) + +```cpp +#include + +using namespace pto; + +void example_runtime() { + using TileT = Tile; + TileT a, b, c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c, 0x3000); + TADD(c, a, b); +} +``` + +### Compile-time address (with static bounds check) + +```cpp +#include + +using namespace pto; + +void example_checked() { + using TileT = Tile; + TileT a, b, c; + + TASSIGN<0x0000>(a); // OK: 0x0000 + 1024 <= 192KB + TASSIGN<0x0400>(b); // OK: 0x0400 + 1024 <= 192KB + TASSIGN<0x0800>(c); // OK: 0x0800 + 1024 <= 192KB + TADD(c, a, b); +} +``` + +The following triggers a compile error: + +```cpp +void example_oob() { + // Tile occupies 256*256*4 = 256KB + using BigTile = Tile; + BigTile t; + + // static_assert fires: tile_size (256KB) > UB capacity (192KB on A2A3) + TASSIGN<0x0>(t); +} +``` + +```cpp +void example_oob_addr() { + using TileT = Tile; // 64KB + TileT t; + + // static_assert fires: 0x20000 (128KB) + 64KB = 192KB, + // but 0x20001 + 64KB > 192KB + TASSIGN<0x20001>(t); +} +``` + +### Ping-pong L0 buffer allocation + +```cpp +void example_pingpong() { + using L0ATile = TileLeft; // L0A tile + using L0BTile = TileRight; // L0B tile + + L0ATile a0, a1; + L0BTile b0, b1; + + TASSIGN<0x0000>(a0); // L0A ping + TASSIGN<0x8000>(a1); // L0A pong + TASSIGN<0x0000>(b0); // L0B ping (separate physical memory from L0A) + TASSIGN<0x8000>(b1); // L0B pong +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +pto.tassign %tile, %addr : !pto.tile<...>, dtype +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +pto.tassign %tile, %addr : !pto.tile<...>, dtype +``` + +### PTO Assembly Form + +```text +tassign %tile, %addr : !pto.tile<...>, index +# AS Level 2 (DPS) +pto.tassign ins(%tile, %addr : !pto.tile_buf<...>, dtype) +``` diff --git a/designs/outerCube/PTOISA/TASSIGN_zh.md b/designs/outerCube/PTOISA/TASSIGN_zh.md new file mode 100644 index 00000000..cdf0ce03 --- /dev/null +++ b/designs/outerCube/PTOISA/TASSIGN_zh.md @@ -0,0 +1,192 @@ +# TASSIGN + +## 指令示意图 + +![TASSIGN tile operation](../figures/isa/TASSIGN.svg) + +## 简介 + +将 Tile 对象绑定到实现定义的片上地址(手动放置)。 + +## 数学语义 + +Not applicable. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +`TASSIGN` is typically introduced by bufferization/lowering when mapping SSA tiles to physical storage. + +同步形式: + +```text +tassign %tile, %addr : !pto.tile<...>, index +``` + +### AS Level 1 (SSA) + +```text +pto.tassign %tile, %addr : !pto.tile<...>, dtype +``` + +### AS Level 2 (DPS) + +```text +pto.tassign ins(%tile, %addr : !pto.tile_buf<...>, dtype) +``` + +### AS Level 1(SSA) + +```text +pto.tassign %tile, %addr : !pto.tile<...>, dtype +``` + +### AS Level 2(DPS) + +```text +pto.tassign ins(%tile, %addr : !pto.tile_buf<...>, dtype) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`. + +### Form 1: Runtime address + +```cpp +template +PTO_INST void TASSIGN(T& obj, AddrType addr); +``` + +Binds `obj` to the on-chip address `addr`. No compile-time bounds checking is +performed (the address value is not available at compile time). + +### Form 2: Compile-time address (with static bounds check) + +```cpp +template +PTO_INST void TASSIGN(T& obj); +``` + +Binds `obj` to the on-chip address `Addr`. Because `Addr` is a non-type +template parameter, the compiler performs the following **compile-time** checks +via `static_assert`: + +| Check | Condition | Assertion ID | Error message | +|-------|-----------|--------------|---------------| +| Memory space exists | `capacity > 0` | SA-0351 | Memory space is not available on this architecture. | +| Tile fits in memory | `tile_size <= capacity` | SA-0352 | Tile storage size exceeds memory space capacity. | +| Address in bounds | `Addr + tile_size <= capacity` | SA-0353 | addr + tile_size exceeds memory space capacity (out of bounds). | +| Address aligned | `Addr % alignment == 0` | SA-0354 | addr is not properly aligned for the target memory space. | + +See `docs/coding/debug.md` (fix recipe `FIX-A12`) for suggested remedies. + +The memory space, capacity, and alignment are determined automatically from the +Tile's `TileType` (i.e. `Loc` template parameter): + +| TileType | Memory | Capacity (A2A3) | Capacity (A5) | Capacity (Kirin9030) | Capacity (KirinX90) | Alignment | +|----------|--------|-----------------|---------------|----------------------|---------------------|-----------| +| Vec | UB | 192 KB | 256 KB | 128 KB | 128 KB | 32 B | +| Mat | L1 | 512 KB | 512 KB | 512 KB | 1024 KB | 32 B | +| Left | L0A | 64 KB | 64 KB | 32 KB | 64 KB | 32 B | +| Right | L0B | 64 KB | 64 KB | 32 KB | 64 KB | 32 B | +| Acc | L0C | 128 KB | 256 KB | 64 KB | 128 KB | 32 B | +| Bias | Bias | 1 KB | 4 KB | 1 KB | 1 KB | 32 B | +| Scaling | FBuffer | 2 KB | 4 KB | 7 KB | 6 KB | 32 B | +| ScaleLeft | L0A | N/A | 4 KB | N/A | N/A | 32 B | +| ScaleRight | L0B | N/A | 4 KB | N/A | N/A | 32 B | + +Capacities can be overridden at build time via `-D` flags (e.g. +`-DPTO_UBUF_SIZE_BYTES=262144`). See `include/pto/common/buffer_limits.hpp`. + +**Note:** This overload is only available for `Tile` and `ConvTile` types. For +`GlobalTensor`, use `TASSIGN(obj, pointer)` (Form 1). + +## 约束 + +- **实现检查**: + - If `obj` is a Tile: + - In manual mode (when `__PTO_AUTO__` is not defined), `addr` must be an integral type and is reinterpreted as the tile's storage address. + - In auto mode (when `__PTO_AUTO__` is defined), `TASSIGN(tile, addr)` is a no-op. + - If `obj` is a `GlobalTensor`: + - `addr` must be a pointer type. + - The pointed-to element type must match `GlobalTensor::DType`. + +## 示例 + +### Runtime address (no compile-time check) + +```cpp +#include + +using namespace pto; + +void example_runtime() { + using TileT = Tile; + TileT a, b, c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c, 0x3000); + TADD(c, a, b); +} +``` + +### Compile-time address (with static bounds check) + +```cpp +#include + +using namespace pto; + +void example_checked() { + using TileT = Tile; + TileT a, b, c; + + TASSIGN<0x0000>(a); // OK: 0x0000 + 1024 <= 192KB + TASSIGN<0x0400>(b); // OK: 0x0400 + 1024 <= 192KB + TASSIGN<0x0800>(c); // OK: 0x0800 + 1024 <= 192KB + TADD(c, a, b); +} +``` + +The following triggers a compile error: + +```cpp +void example_oob() { + // Tile occupies 256*256*4 = 256KB + using BigTile = Tile; + BigTile t; + + // static_assert fires: tile_size (256KB) > UB capacity (192KB on A2A3) + TASSIGN<0x0>(t); +} +``` + +```cpp +void example_oob_addr() { + using TileT = Tile; // 64KB + TileT t; + + // static_assert fires: 0x20000 (128KB) + 64KB = 192KB, + // but 0x20001 + 64KB > 192KB + TASSIGN<0x20001>(t); +} +``` + +### Ping-pong L0 buffer allocation + +```cpp +void example_pingpong() { + using L0ATile = TileLeft; // L0A tile + using L0BTile = TileRight; // L0B tile + + L0ATile a0, a1; + L0BTile b0, b1; + + TASSIGN<0x0000>(a0); // L0A ping + TASSIGN<0x8000>(a1); // L0A pong + TASSIGN<0x0000>(b0); // L0B ping (separate physical memory from L0A) + TASSIGN<0x8000>(b1); // L0B pong +} +``` diff --git a/designs/outerCube/PTOISA/TAXPY.md b/designs/outerCube/PTOISA/TAXPY.md new file mode 100644 index 00000000..ffe6e549 --- /dev/null +++ b/designs/outerCube/PTOISA/TAXPY.md @@ -0,0 +1,40 @@ +# TAXPY + +## Tile Operation Diagram + +![TAXPY tile operation](../figures/isa/TAXPY.svg) + +## Introduction + +AXPY-style fused update: multiply a tile by a scalar and accumulate into the destination tile. + +## Math Interpretation + +Semantics are instruction-specific. Unless stated otherwise, behavior is defined over the destination valid region. + +## Assembly Syntax + +PTO-AS form: see `docs/assembly/PTO-AS.md`. + +### IR Level 1 (SSA) + +```text +%dst = pto.taxpy ... +``` + +### IR Level 2 (DPS) + +```text +pto.taxpy ins(...) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`. + +## Constraints + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## Examples + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TAXPY_zh.md b/designs/outerCube/PTOISA/TAXPY_zh.md new file mode 100644 index 00000000..df5907e8 --- /dev/null +++ b/designs/outerCube/PTOISA/TAXPY_zh.md @@ -0,0 +1,41 @@ +# TAXPY + +## 指令示意图 + +![TAXPY tile operation](../figures/isa/TAXPY.svg) + +## 简介 + +AXPY 风格融合更新:将 Tile 乘以标量并累加到目标 Tile。 + +## 数学语义 + +语义随指令而变化。 Unless stated otherwise, behavior is defined over the destination valid region. + +## 汇编语法 + +PTO-AS 形式:参见 `docs/assembly/PTO-AS.md`. + +### AS Level 1(SSA) + +```text +%dst = pto.taxpy ... +``` + +### AS Level 2(DPS) + +```text +pto.taxpy ins(...) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`. + +## 约束 + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## 示例 + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TCI.md b/designs/outerCube/PTOISA/TCI.md new file mode 100644 index 00000000..ee4ab171 --- /dev/null +++ b/designs/outerCube/PTOISA/TCI.md @@ -0,0 +1,133 @@ +# TCI + + +## Tile Operation Diagram + +![TCI tile operation](../figures/isa/TCI.svg) + +## Introduction + +Generate a contiguous integer sequence into a destination tile. + +## Math Interpretation + +For a linearized index `k` over the valid elements: + +- Ascending: + + $$ \mathrm{dst}_{k} = S + k $$ + +- Descending: + + $$ \mathrm{dst}_{k} = S - k $$ + +The linearization order depends on the tile layout (implementation-defined). + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tci %S {descending = false} : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tci %scalar {descending = false} : dtype -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tci ins(%scalar {descending = false} : dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tci %scalar {descending = false} : dtype -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tci ins(%scalar {descending = false} : dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCI(TileData &dst, T start, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3/A5)**: + - `TileData::DType` must be exactly the same type as the scalar template parameter `T`. + - `dst/scalar` element types must be identical, and must be one of: `int32_t`, `uint32_t`, `int16_t`, `uint16_t`. + - `TileData::Cols != 1` (this is the condition enforced by the implementation). +- **Valid region**: + - The implementation uses `dst.GetValidCol()` as the sequence length and does not consult `dst.GetValidRow()`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT dst; + TCI(dst, /*S=*/0); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT dst; + TASSIGN(dst, 0x1000); + TCI(dst, /*S=*/100); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tci %scalar {descending = false} : dtype -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tci %scalar {descending = false} : dtype -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tci %S {descending = false} : !pto.tile<...> +# AS Level 2 (DPS) +pto.tci ins(%scalar {descending = false} : dtype) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TCI_zh.md b/designs/outerCube/PTOISA/TCI_zh.md new file mode 100644 index 00000000..ba405600 --- /dev/null +++ b/designs/outerCube/PTOISA/TCI_zh.md @@ -0,0 +1,106 @@ +# TCI + +## 指令示意图 + +![TCI tile operation](../figures/isa/TCI.svg) + +## 简介 + +生成连续整数序列到目标 Tile 中。 + +## 数学语义 + +For a linearized index `k` over the valid elements: + +- Ascending: + + $$ \mathrm{dst}_{k} = S + k $$ + +- Descending: + + $$ \mathrm{dst}_{k} = S - k $$ + +The linearization order depends on the tile layout (implementation-defined). + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tci %S {descending = false} : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tci %scalar {descending = false} : dtype -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tci ins(%scalar {descending = false} : dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tci %scalar {descending = false} : dtype -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tci ins(%scalar {descending = false} : dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCI(TileData &dst, T start, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3/A5)**: + - `TileData::DType` must be exactly the same type as the scalar template parameter `T`. + - `dst/scalar` element types must be identical, and must be one of: `int32_t`, `uint32_t`, `int16_t`, `uint16_t`. + - `TileData::Cols != 1` (this is the condition enforced by the implementation). +- **有效区域**: + - The implementation uses `dst.GetValidCol()` as the sequence length and does not consult `dst.GetValidRow()`. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT dst; + TCI(dst, /*S=*/0); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT dst; + TASSIGN(dst, 0x1000); + TCI(dst, /*S=*/100); +} +``` diff --git a/designs/outerCube/PTOISA/TCMP.md b/designs/outerCube/PTOISA/TCMP.md new file mode 100644 index 00000000..8e6db2f0 --- /dev/null +++ b/designs/outerCube/PTOISA/TCMP.md @@ -0,0 +1,142 @@ +# TCMP + + +## Tile Operation Diagram + +![TCMP tile operation](../figures/isa/TCMP.svg) + +## Introduction + +Compare two tiles and write a packed predicate mask. + +## Math Interpretation + +Conceptually, for each element `(i, j)` in the valid region, define a predicate: + +$$ p_{i,j} = \left(\mathrm{src0}_{i,j}\ \mathrm{cmpMode}\ \mathrm{src1}_{i,j}\right) $$ + +The predicate mask is stored in `dst` using an implementation-defined packed layout. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcmp %src0, %src1 {cmpMode = #pto.cmp} : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcmp %src0, %src1{cmpMode = #pto}: (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcmp ins(%src0, %src1{cmpMode = #pto}: !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tcmp %src0, %src1{cmpMode = #pto}: (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tcmp ins(%src0, %src1{cmpMode = #pto}: !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp` and `include/pto/common/type.hpp`: + +```cpp +template +PTO_INST RecordEvent TCMP(TileDataDst &dst, TileDataSrc &src0, TileDataSrc &src1, CmpMode cmpMode, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Input type must be one of: `int32_t`, `half`, `float`. + - Output type must be `uint8_t`. + - `src0/src1/dst` tile location must be `TileType::Vec`. + - Static valid bounds: `TileDataSrc::ValidRow <= TileDataSrc::Rows` and `TileDataSrc::ValidCol <= TileDataSrc::Cols`. + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. + - Note: `src1` shape/valid is not validated by explicit runtime assertions in this implementation. + - For `TileDataSrc::DType == int32_t`, the implementation uses the `EQ` compare path regardless of `cmpMode`. +- **Implementation checks (A5)**: + - Input type must be one of: `uint32_t`, `int32_t`, `uint16_t`, `int16_t`, `uint8_t`, `int8_t`, `float`, `half`. + - Output type must be `uint32_t`. + - Implemented (see `include/pto/npu/a5/TCmp.hpp`). + - The A5 implementation uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain and writes a packed predicate mask into `dst` (target-defined packing). +- **Mask encoding**: + - The mask tile is interpreted as packed predicate bits in a target-defined layout. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using MaskT = Tile; + SrcT src0, src1; + MaskT mask(16, 2); + TCMP(mask, src0, src1, CmpMode::GT); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using MaskT = Tile; + SrcT src0, src1; + MaskT mask(16, 2); + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(mask, 0x3000); + TCMP(mask, src0, src1, CmpMode::GT); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcmp %src0, %src1{cmpMode = #pto}: (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcmp %src0, %src1{cmpMode = #pto}: (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcmp %src0, %src1 {cmpMode = #pto.cmp} : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcmp ins(%src0, %src1{cmpMode = #pto}: !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TCMPS.md b/designs/outerCube/PTOISA/TCMPS.md new file mode 100644 index 00000000..d3d199f6 --- /dev/null +++ b/designs/outerCube/PTOISA/TCMPS.md @@ -0,0 +1,140 @@ +# TCMPS + + +## Tile Operation Diagram + +![TCMPS tile operation](../figures/isa/TCMPS.svg) + +## Introduction + +Compare a tile against a scalar and write per-element comparison results. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \left(\mathrm{src}_{i,j}\ \mathrm{cmpMode}\ \mathrm{scalar}\right) $$ + +The encoding/type of `dst` is implementation-defined (often a mask-like tile). + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcmps %src, %scalar {cmpMode = #pto.cmp} : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcmps %src, %scalar {cmpMode = #pto} : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcmps ins(%src, %scalar{cmpMode = #pto}: !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tcmps %src, %scalar {cmpMode = #pto} : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tcmps ins(%src, %scalar{cmpMode = #pto}: !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp` and `include/pto/common/type.hpp`: + +```cpp +template +PTO_INST RecordEvent TCMPS(TileDataDst& dst, TileDataSrc0& src0, T src1, CmpMode cmpMode, WaitEvents&... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `float`, `half`, `uint16_t`, `int16_t`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `int32_t`, `float`, `half`, `uint16_t`, `int16_t`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Common constraints**: + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0` and `dst` must have the same valid row/col. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Comparison modes**: + - Supports `CmpMode::EQ`, `CmpMode::NE`, `CmpMode::LT`, `CmpMode::GT`, `CmpMode::LE`, `CmpMode::GE`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst(16, 2); + TCMPS(dst, src, 0.0f, CmpMode::GT); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst(16, 2); + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TCMPS(dst, src, 0.0f, CmpMode::GT); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcmps %src, %scalar {cmpMode = #pto} : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcmps %src, %scalar {cmpMode = #pto} : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcmps %src, %scalar {cmpMode = #pto.cmp} : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcmps ins(%src, %scalar{cmpMode = #pto}: !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TCMPS_zh.md b/designs/outerCube/PTOISA/TCMPS_zh.md new file mode 100644 index 00000000..a697b3e0 --- /dev/null +++ b/designs/outerCube/PTOISA/TCMPS_zh.md @@ -0,0 +1,113 @@ +# TCMPS + +## 指令示意图 + +![TCMPS tile operation](../figures/isa/TCMPS.svg) + +## 简介 + +将 Tile 与标量比较并写入逐元素比较结果。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \left(\mathrm{src}_{i,j}\ \mathrm{cmpMode}\ \mathrm{scalar}\right) $$ + +The encoding/type of `dst` is implementation-defined (often a mask-like tile). + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tcmps %src, %scalar {cmpMode = #pto.cmp} : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcmps %src, %scalar {cmpMode = #pto} : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcmps ins(%src, %scalar{cmpMode = #pto}: !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcmps %src, %scalar {cmpMode = #pto} : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcmps ins(%src, %scalar{cmpMode = #pto}: !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp` and `include/pto/common/type.hpp`: + +```cpp +template +PTO_INST RecordEvent TCMPS(TileDataDst& dst, TileDataSrc0& src0, T src1, CmpMode cmpMode, WaitEvents&... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `float`, `half`, `uint16_t`, `int16_t`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `int32_t`, `float`, `half`, `uint16_t`, `int16_t`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **Common constraints**: + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0` and `dst` must have the same valid row/col. +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Comparison modes**: + - Supports `CmpMode::EQ`, `CmpMode::NE`, `CmpMode::LT`, `CmpMode::GT`, `CmpMode::LE`, `CmpMode::GE`. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst(16, 2); + TCMPS(dst, src, 0.0f, CmpMode::GT); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst(16, 2); + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TCMPS(dst, src, 0.0f, CmpMode::GT); +} +``` diff --git a/designs/outerCube/PTOISA/TCMP_zh.md b/designs/outerCube/PTOISA/TCMP_zh.md new file mode 100644 index 00000000..1d7b93dd --- /dev/null +++ b/designs/outerCube/PTOISA/TCMP_zh.md @@ -0,0 +1,115 @@ +# TCMP + +## 指令示意图 + +![TCMP tile operation](../figures/isa/TCMP.svg) + +## 简介 + +比较两个 Tile 并写入一个打包的谓词掩码。 + +## 数学语义 + +Conceptually, for each element `(i, j)` in the valid region, define a predicate: + +$$ p_{i,j} = \left(\mathrm{src0}_{i,j}\ \mathrm{cmpMode}\ \mathrm{src1}_{i,j}\right) $$ + +The predicate mask is stored in `dst` using an implementation-defined packed layout. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tcmp %src0, %src1 {cmpMode = #pto.cmp} : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcmp %src0, %src1{cmpMode = #pto}: (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcmp ins(%src0, %src1{cmpMode = #pto}: !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcmp %src0, %src1{cmpMode = #pto}: (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcmp ins(%src0, %src1{cmpMode = #pto}: !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp` and `include/pto/common/type.hpp`: + +```cpp +template +PTO_INST RecordEvent TCMP(TileDataDst &dst, TileDataSrc &src0, TileDataSrc &src1, CmpMode cmpMode, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - Input type must be one of: `int32_t`, `half`, `float`. + - Output type must be `uint8_t`. + - `src0/src1/dst` tile location must be `TileType::Vec`. + - Static valid bounds: `TileDataSrc::ValidRow <= TileDataSrc::Rows` and `TileDataSrc::ValidCol <= TileDataSrc::Cols`. + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. + - Note: `src1` shape/valid is not validated by explicit runtime assertions in this implementation. + - For `TileDataSrc::DType == int32_t`, the implementation uses the `EQ` compare path regardless of `cmpMode`. +- **实现检查 (A5)**: + - Input type must be one of: `uint32_t`, `int32_t`, `uint16_t`, `int16_t`, `uint8_t`, `int8_t`, `float`, `half`. + - Output type must be `uint32_t`. + - Implemented (see `include/pto/npu/a5/TCmp.hpp`). + - The A5 implementation uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain and writes a packed predicate mask into `dst` (target-defined packing). +- **Mask encoding**: + - The mask tile is interpreted as packed predicate bits in a target-defined layout. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using MaskT = Tile; + SrcT src0, src1; + MaskT mask(16, 2); + TCMP(mask, src0, src1, CmpMode::GT); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using MaskT = Tile; + SrcT src0, src1; + MaskT mask(16, 2); + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(mask, 0x3000); + TCMP(mask, src0, src1, CmpMode::GT); +} +``` diff --git a/designs/outerCube/PTOISA/TCOLARGMAX.md b/designs/outerCube/PTOISA/TCOLARGMAX.md new file mode 100644 index 00000000..a69a8a3c --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLARGMAX.md @@ -0,0 +1,178 @@ +# TCOLARGMAX + + +## Tile Operation Diagram + +![TCOLARGMAX tile operation](../figures/isa/TCOLARGMAX.svg) + +## Introduction + +Get the row index of the maximum element for each column. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \underset{0 \le i < R}{\operatorname{argmax}} \; \mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see `docs/grammar/PTO-AS.md`. + +Synchronous form: + +```text +%dst = tcolargmax %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### IR Level 1 (SSA) + +```text +%dst = pto.tcolargmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tcolargmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLARGMAX(TileDataOut& dst, TileDataIn& src, TileDataTmp& tmp, WaitEvents&... events); +``` + +## Constraints + +Implementation checks (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile layout of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile layout of `dst`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Source data types: `half`, `float`, `uint16_t`, `uint32_t`. + - Destination data types: `uint32_t` or `int32_t`. + - `tmp` data type must be consistent with `src` data type. + - Compile-time check: `src.ValidCol` must be `1` or `-1` (dynamic). + - Runtime valid checks: + - `srcValidCol != 0` and `srcValidRow != 0`. + - `dstValidRow == 1`. + - `srcValidCol == dstValidCol`. +- A5: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile layout of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile layout of `dst`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Source data types: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `half`, `float`. + - Destination data types: `uint32_t` or `int32_t`. + - Compile-time check: `src.ValidCol` must be `1` or `-1` (dynamic). + - Runtime valid checks: + - `srcValidCol != 0` and `srcValidRow != 0`. + - `dstValidRow == 1`. + - `srcValidCol == dstValidCol`. + - `tmp` temporary tile is not used, only for compatibility. + +### About temporary tile `tmp` for A2A3 + +* `tmp` is always used in the A2A3 implementation as scratch space for intermediate results (current index, argmax index, and current max elements). +* `tmp` tile's data type must be the same as `src`'s data type. +* `tmp` tile is organized into three regions within a single row: + - Region 0 (`[0, tmpGapEles)`): current row index counter (incremented per row). + - Region 1 (`[tmpGapEles, 2 * tmpGapEles)`): current maximum elements for comparison. + - Region 2 (`[2 * tmpGapEles, 3 * tmpGapEles)`): argmax index result (before final conversion to `dst`). +* `tmpGapEles` is determined as follows: + - When `srcValidCol >= elemPerRpt`: `tmpGapEles = elemPerRpt`. + - When `srcValidCol < elemPerRpt`: `tmpGapEles = ceil(srcValidCol / elemPerBlock) * elemPerBlock`. +* Simply set `tmp` tile size the same as `src` when `src` is small, or calculate the required stride based on `src`'s `validCol` using the following formula: + +```text +repeats = ceil(validCol / elementPerRepeat) +stride = ceil(repeats * 2 / elementPerBlock) * elementPerBlock + ceil(repeats / elementPerBlock) * elementPerBlock +``` + +### About temporary tile `tmp` for A5 + +* `tmp` temporary tile is **not used** in the A5 implementation. The A5 uses vector register-based computation (`__VEC_SCOPE__`) and does not require scratch tile storage. +* `tmp` is retained in the C++ intrinsic signature solely for API compatibility with A2A3. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src(16, 255); + DstT dst(1, 255); + TmpT tmp(1, 32); + TCOLARGMAX(dst, src, tmp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src(16, 255); + DstT dst(1, 255); + TmpT tmp(1, 32); + TASSIGN(src, 0x0); + TASSIGN(dst, 0x1000); + TASSIGN(tmp, 0x2000); + TCOLARGMAX(dst, src, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolargmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolargmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolargmax %src : !pto.tile<...> -> !pto.tile<...> +# IR Level 2 (DPS) +pto.tcolargmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +- [x] Explore existing docs/isa for documentation style and format +- [x] Read tcolargmax and tcolargmin A2A3 implementation in include/ +- [x] Read tcolargmax and tcolargmin A5 implementation in include/ +- [x] Read test cases for tcolargmax and tcolargmin +- [x] Understand A2A3 vs A5 differences and tmp handling +- [x] Write tcolargmax English documentation (docs/isa/TCOLARGMAX.md) +- [ ] Write tcolargmax Chinese documentation (docs/isa/TCOLARGMAX_zh.md) +- [ ] Verify documentation completeness and accuracy + + \ No newline at end of file diff --git a/designs/outerCube/PTOISA/TCOLARGMAX_zh.md b/designs/outerCube/PTOISA/TCOLARGMAX_zh.md new file mode 100644 index 00000000..91268287 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLARGMAX_zh.md @@ -0,0 +1,178 @@ +# TCOLARGMAX + +## 指令示意图 + +![TCOLARGMAX tile operation](../figures/isa/TCOLARGMAX.svg) + +## 简介 + +获取每列最大值对应行索引。 + +## 数学语义 + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \underset{0 \le i < R}{\operatorname{argmax}} \; \mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 `docs/grammar/PTO-AS.md`. + +同步形式: + +```text +%dst = tcolargmax %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### IR Level 1(SSA) + +```text +%dst = pto.tcolargmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2(DPS) + +```text +pto.tcolargmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLARGMAX(TileDataOut& dst, TileDataIn& src, TileDataTmp& tmp, WaitEvents&... events); +``` + +## 约束 + +实现检查 (NPU): + +- A2A3: + - Tile location: `dst` 和 `src` 必须为 `TileType::Vec`。 + - Tile 布局 of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`)。 + - Tile 布局 of `dst`: ND fractal (`isRowMajor` and `SLayout::NoneBox`)。 + - 源数据类型: `half`、`float`、`uint16_t`、`uint32_t`。 + - 目标数据类型: `uint32_t` 或 `int32_t`。 + - `tmp` 数据类型必须与 `src` 数据类型一致。 + - 编译期检查: `src.ValidCol` 必须为 `1` 或 `-1`(动态)。 + - 运行期有效区域检查: + - `srcValidCol != 0` 且 `srcValidRow != 0`。 + - `dstValidRow == 1`。 + - `srcValidCol == dstValidCol`。 +- A5: + - Tile location: `dst` 和 `src` 必须为 `TileType::Vec`。 + - Tile 布局 of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`)。 + - Tile 布局 of `dst`: ND fractal (`isRowMajor` and `SLayout::NoneBox`)。 + - 源数据类型: `int8_t`、`uint8_t`、`int16_t`、`uint16_t`、`int32_t`、`uint32_t`、`half`、`float`。 + - 目标数据类型: `uint32_t` 或 `int32_t`。 + - 编译期检查: `src.ValidCol` 必须为 `1` 或 `-1`(动态)。 + - 运行期有效区域检查: + - `srcValidCol != 0` 且 `srcValidRow != 0`。 + - `dstValidRow == 1`。 + - `srcValidCol == dstValidCol`。 + - `tmp` 临时 Tile 不使用,仅做兼容。 + +### A2A3 `tmp` 临时 Tile 相关说明 + +* A2A3 实现中 `tmp` **始终被使用**,作为中间结果的临时存储空间(当前行索引、argmax 索引、当前最大值元素)。 +* `tmp` Tile 的数据类型必须与 `src` 的数据类型一致。 +* `tmp` Tile 在单行内被划分为三个区域: + - 区域 0(`[0, tmpGapEles)`):当前行索引计数器(每行递增)。 + - 区域 1(`[tmpGapEles, 2 * tmpGapEles)`):当前最大值元素,用于比较。 + - 区域 2(`[2 * tmpGapEles, 3 * tmpGapEles)`):argmax 索引结果(最终转换后写入 `dst`)。 +* `tmpGapEles` 的确定方式: + - 当 `srcValidCol >= elemPerRpt` 时:`tmpGapEles = elemPerRpt`。 + - 当 `srcValidCol < elemPerRpt` 时:`tmpGapEles = ceil(srcValidCol / elemPerBlock) * elemPerBlock`。 +* 当 `src` 较小时,可直接将 `tmp` Tile 大小设为与 `src` 相同;也可按以下公式根据 `src` 的 `validCol` 算出 `tmp` Tile 所需 stride: + +```text +repeats = ceil(validCol / elementPerRepeat) +stride = ceil(repeats * 2 / elementPerBlock) * elementPerBlock + ceil(repeats / elementPerBlock) * elementPerBlock +``` + +### A5 `tmp` 临时 Tile 相关说明 + +* A5 实现中 `tmp` 临时 Tile **不使用**。A5 使用基于向量寄存器的计算方式(`__VEC_SCOPE__`),不需要临时 Tile 存储。 +* `tmp` 在 C++ 内建接口签名中保留,仅为了与 A2A3 的 API 兼容。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src(16, 255); + DstT dst(1, 255); + TmpT tmp(1, 32); + TCOLARGMAX(dst, src, tmp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src(16, 255); + DstT dst(1, 255); + TmpT tmp(1, 32); + TASSIGN(src, 0x0); + TASSIGN(dst, 0x1000); + TASSIGN(tmp, 0x2000); + TCOLARGMAX(dst, src, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tcolargmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolargmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tcolargmax %src : !pto.tile<...> -> !pto.tile<...> +# IR Level 2 (DPS) +pto.tcolargmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +- [x] Explore existing docs/isa for documentation style and format +- [x] Read tcolargmax and tcolargmin A2A3 implementation in include/ +- [x] Read tcolargmax and tcolargmin A5 implementation in include/ +- [x] Read test cases for tcolargmax and tcolargmin +- [x] Understand A2A3 vs A5 differences and tmp handling +- [x] Write tcolargmax English documentation (docs/isa/TCOLARGMAX.md) +- [x] Write tcolargmax Chinese documentation (docs/isa/TCOLARGMAX_zh.md) +- [ ] Verify documentation completeness and accuracy + + \ No newline at end of file diff --git a/designs/outerCube/PTOISA/TCOLARGMIN.md b/designs/outerCube/PTOISA/TCOLARGMIN.md new file mode 100644 index 00000000..63df789f --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLARGMIN.md @@ -0,0 +1,172 @@ +# TCOLARGMIN + + +## Tile Operation Diagram + +![TCOLARGMIN tile operation](../figures/isa/TCOLARGMIN.svg) + +## Introduction + +Get the row index of the minimum element for each column. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \underset{0 \le i < R}{\operatorname{argmin}} \; \mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see `docs/grammar/PTO-AS.md`. + +Synchronous form: + +```text +%dst = tcolargmin %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### IR Level 1 (SSA) + +```text +%dst = pto.tcolargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tcolargmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLARGMIN(TileDataOut& dst, TileDataIn& src, TileDataTmp& tmp, WaitEvents&... events); +``` + +## Constraints + +Implementation checks (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile layout of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile layout of `dst`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Source data types: `half`, `float`, `uint16_t`, `uint32_t`. + - Destination data types: `uint32_t` or `int32_t`. + - `tmp` data type must be consistent with `src` data type. + - Compile-time check: `src.ValidCol` must be `1` or `-1` (dynamic). + - Runtime valid checks: + - `srcValidCol != 0` and `srcValidRow != 0`. + - `dstValidRow == 1`. + - `srcValidCol == dstValidCol`. +- A5: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile layout of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile layout of `dst`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Source data types: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `half`, `float`. + - Destination data types: `uint32_t` or `int32_t`. + - Compile-time check: `src.ValidCol` must be `1` or `-1` (dynamic). + - Runtime valid checks: + - `srcValidCol != 0` and `srcValidRow != 0`. + - `dstValidRow == 1`. + - `srcValidCol == dstValidCol`. + - `tmp` temporary tile is not used, only for compatibility. + +### About temporary tile `tmp` for A2A3 + +* `tmp` is always used in the A2A3 implementation as scratch space for intermediate results (current index, argmin index, and current min elements). +* `tmp` tile's data type must be the same as `src`'s data type. +* `tmp` tile is organized into three regions within a single row: + - Region 0 (`[0, tmpGapEles)`): current row index counter (incremented per row). + - Region 1 (`[tmpGapEles, 2 * tmpGapEles)`): current minimum elements for comparison. + - Region 2 (`[2 * tmpGapEles, 3 * tmpGapEles)`): argmin index result (before final conversion to `dst`). +* `tmpGapEles` is determined as follows: + - When `srcValidCol >= elemPerRpt`: `tmpGapEles = elemPerRpt`. + - When `srcValidCol < elemPerRpt`: `tmpGapEles = ceil(srcValidCol / elemPerBlock) * elemPerBlock`. +* Simply set `tmp` tile size the same as `src` when `src` is small, or calculate the required stride based on `src`'s `validCol` using the following formula: + +```text +repeats = ceil(validCol / elementPerRepeat) +stride = ceil(repeats * 2 / elementPerBlock) * elementPerBlock + ceil(repeats / elementPerBlock) * elementPerBlock +``` + +### About temporary tile `tmp` for A5 + +* `tmp` temporary tile is **not used** in the A5 implementation. The A5 uses vector register-based computation (`__VEC_SCOPE__`) and does not require scratch tile storage. +* `tmp` is retained in the C++ intrinsic signature solely for API compatibility with A2A3. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src(16, 255); + DstT dst(1, 255); + TmpT tmp(1, 32); + TCOLARGMIN(dst, src, tmp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src(16, 255); + DstT dst(1, 255); + TmpT tmp(1, 32); + TASSIGN(src, 0x0); + TASSIGN(dst, 0x1000); + TASSIGN(tmp, 0x2000); + TCOLARGMIN(dst, src, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolargmin %src : !pto.tile<...> -> !pto.tile<...> +# IR Level 2 (DPS) +pto.tcolargmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +- [x] Write tcolargmin English documentation (docs/isa/TCOLARGMIN.md) +- [ ] Write tcolargmin Chinese documentation (docs/isa/TCOLARGMIN_zh.md) + + \ No newline at end of file diff --git a/designs/outerCube/PTOISA/TCOLARGMIN_zh.md b/designs/outerCube/PTOISA/TCOLARGMIN_zh.md new file mode 100644 index 00000000..33fe2a24 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLARGMIN_zh.md @@ -0,0 +1,172 @@ +# TCOLARGMIN + +## 指令示意图 + +![TCOLARGMIN tile operation](../figures/isa/TCOLARGMIN.svg) + +## 简介 + +获取每列最小值对应行索引。 + +## 数学语义 + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \underset{0 \le i < R}{\operatorname{argmin}} \; \mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 `docs/grammar/PTO-AS.md`. + +同步形式: + +```text +%dst = tcolargmin %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### IR Level 1(SSA) + +```text +%dst = pto.tcolargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2(DPS) + +```text +pto.tcolargmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLARGMIN(TileDataOut& dst, TileDataIn& src, TileDataTmp& tmp, WaitEvents&... events); +``` + +## 约束 + +实现检查 (NPU): + +- A2A3: + - Tile location: `dst` 和 `src` 必须为 `TileType::Vec`。 + - Tile 布局 of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`)。 + - Tile 布局 of `dst`: ND fractal (`isRowMajor` and `SLayout::NoneBox`)。 + - 源数据类型: `half`、`float`、`uint16_t`、`uint32_t`。 + - 目标数据类型: `uint32_t` 或 `int32_t`。 + - `tmp` 数据类型必须与 `src` 数据类型一致。 + - 编译期检查: `src.ValidCol` 必须为 `1` 或 `-1`(动态)。 + - 运行期有效区域检查: + - `srcValidCol != 0` 且 `srcValidRow != 0`。 + - `dstValidRow == 1`。 + - `srcValidCol == dstValidCol`。 +- A5: + - Tile location: `dst` 和 `src` 必须为 `TileType::Vec`。 + - Tile 布局 of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`)。 + - Tile 布局 of `dst`: ND fractal (`isRowMajor` and `SLayout::NoneBox`)。 + - 源数据类型: `int8_t`、`uint8_t`、`int16_t`、`uint16_t`、`int32_t`、`uint32_t`、`half`、`float`。 + - 目标数据类型: `uint32_t` 或 `int32_t`。 + - 编译期检查: `src.ValidCol` 必须为 `1` 或 `-1`(动态)。 + - 运行期有效区域检查: + - `srcValidCol != 0` 且 `srcValidRow != 0`。 + - `dstValidRow == 1`。 + - `srcValidCol == dstValidCol`。 + - `tmp` 临时 Tile 不使用,仅做兼容。 + +### A2A3 `tmp` 临时 Tile 相关说明 + +* A2A3 实现中 `tmp` **始终被使用**,作为中间结果的临时存储空间(当前行索引、argmin 索引、当前最小值元素)。 +* `tmp` Tile 的数据类型必须与 `src` 的数据类型一致。 +* `tmp` Tile 在单行内被划分为三个区域: + - 区域 0(`[0, tmpGapEles)`):当前行索引计数器(每行递增)。 + - 区域 1(`[tmpGapEles, 2 * tmpGapEles)`):当前最小值元素,用于比较。 + - 区域 2(`[2 * tmpGapEles, 3 * tmpGapEles)`):argmin 索引结果(最终转换后写入 `dst`)。 +* `tmpGapEles` 的确定方式: + - 当 `srcValidCol >= elemPerRpt` 时:`tmpGapEles = elemPerRpt`。 + - 当 `srcValidCol < elemPerRpt` 时:`tmpGapEles = ceil(srcValidCol / elemPerBlock) * elemPerBlock`。 +* 当 `src` 较小时,可直接将 `tmp` Tile 大小设为与 `src` 相同;也可按以下公式根据 `src` 的 `validCol` 算出 `tmp` Tile 所需 stride: + +```text +repeats = ceil(validCol / elementPerRepeat) +stride = ceil(repeats * 2 / elementPerBlock) * elementPerBlock + ceil(repeats / elementPerBlock) * elementPerBlock +``` + +### A5 `tmp` 临时 Tile 相关说明 + +* A5 实现中 `tmp` 临时 Tile **不使用**。A5 使用基于向量寄存器的计算方式(`__VEC_SCOPE__`),不需要临时 Tile 存储。 +* `tmp` 在 C++ 内建接口签名中保留,仅为了与 A2A3 的 API 兼容。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src(16, 255); + DstT dst(1, 255); + TmpT tmp(1, 32); + TCOLARGMIN(dst, src, tmp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src(16, 255); + DstT dst(1, 255); + TmpT tmp(1, 32); + TASSIGN(src, 0x0); + TASSIGN(dst, 0x1000); + TASSIGN(tmp, 0x2000); + TCOLARGMIN(dst, src, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tcolargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tcolargmin %src : !pto.tile<...> -> !pto.tile<...> +# IR Level 2 (DPS) +pto.tcolargmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +- [x] Write tcolargmin English documentation (docs/isa/TCOLARGMIN.md) +- [x] Write tcolargmin Chinese documentation (docs/isa/TCOLARGMIN_zh.md) + + \ No newline at end of file diff --git a/designs/outerCube/PTOISA/TCOLEXPAND.md b/designs/outerCube/PTOISA/TCOLEXPAND.md new file mode 100644 index 00000000..6ccf4b10 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPAND.md @@ -0,0 +1,103 @@ +# TCOLEXPAND + + +## Tile Operation Diagram + +![TCOLEXPAND tile operation](../figures/isa/TCOLEXPAND.svg) + +## Introduction + +Broadcast the first element of each source column across the destination column. + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. For `0 <= i < R` and `0 <= j < C`: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{0,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolexpand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tcolexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tcolexpand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPAND(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## Constraints + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT src, dst; + TCOLEXPAND(dst, src); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolexpand %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TCOLEXPANDADD.md b/designs/outerCube/PTOISA/TCOLEXPANDADD.md new file mode 100644 index 00000000..49b22407 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDADD.md @@ -0,0 +1,89 @@ +# TCOLEXPANDADD + + +## Tile Operation Diagram + +![TCOLEXPANDADD tile operation](../figures/isa/TCOLEXPANDADD.svg) + +## Introduction + +Column-wise broadcast add: add each element of `src0` by a per-column scalar vector `src1`. + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_j` be the per-column scalar taken from `src1` (one value per column). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} + s_j +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolexpandadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDADD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. +- `src1` is expected to provide **one scalar per column** (i.e., its valid shape must cover `C` values). +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TColExpand*.hpp`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDADD_zh.md b/designs/outerCube/PTOISA/TCOLEXPANDADD_zh.md new file mode 100644 index 00000000..a4983a95 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDADD_zh.md @@ -0,0 +1,89 @@ +# TCOLEXPANDADD + +## 指令示意图 + +![TCOLEXPANDADD tile operation](../figures/isa/TCOLEXPANDADD.svg) + +## 简介 + +列广播加法:对每一列加上每列标量向量。 + +## 数学语义 + +设 `R = dst.GetValidRow()` 和 `C = dst.GetValidCol()`。设 `s_j` 为从 `src1` 中获取的每列标量(每列一个值)。 + +对于 `0 <= i < R` 和 `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} + s_j +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tcolexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolexpandadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDADD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::DType`、`TileDataSrc1::DType` 必须是以下之一:`half`、`float`。 +- Tile 形状/布局约束(编译时):`TileDataDst::isRowMajor`。 +- `src1` 预期提供**每列一个标量**(即,其有效形状必须覆盖 `C` 个值)。 +- 确切的布局/分形约束是目标特定的;参见 `include/pto/npu/*/TColExpand*.hpp` 下的后端头文件。 + +## 示例 + +参见 `docs/isa/` 和 `docs/coding/tutorials/` 中的相关示例。 + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tcolexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tcolexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDDIV.md b/designs/outerCube/PTOISA/TCOLEXPANDDIV.md new file mode 100644 index 00000000..6cc63a92 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDDIV.md @@ -0,0 +1,89 @@ +# TCOLEXPANDDIV + + +## Tile Operation Diagram + +![TCOLEXPANDDIV tile operation](../figures/isa/TCOLEXPANDDIV.svg) + +## Introduction + +Column-wise broadcast divide: divide each element of `src0` by a per-column scalar vector `src1`. + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_j` be the per-column scalar taken from `src1` (one value per column). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} / s_j +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolexpanddiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDDIV(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. +- `src1` is expected to provide **one scalar per column** (i.e., its valid shape must cover `C` values). +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TColExpand*.hpp`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpanddiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDDIV_zh.md b/designs/outerCube/PTOISA/TCOLEXPANDDIV_zh.md new file mode 100644 index 00000000..8816edbd --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDDIV_zh.md @@ -0,0 +1,89 @@ +# TCOLEXPANDDIV + +## 指令示意图 + +![TCOLEXPANDDIV tile operation](../figures/isa/TCOLEXPANDDIV.svg) + +## 简介 + +列广播除法:将每一列除以一个每列标量向量。 + +## 数学语义 + +设 `R = dst.GetValidRow()` 和 `C = dst.GetValidCol()`。设 `s_j` 为从 `src1` 中获取的每列标量(每列一个值)。 + +对于 `0 <= i < R` 和 `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} / s_j +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tcolexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolexpanddiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDDIV(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::DType`、`TileDataSrc1::DType` 必须是以下之一:`half`、`float`。 +- Tile 形状/布局约束(编译时):`TileDataDst::isRowMajor`。 +- `src1` 预期提供**每列一个标量**(即,其有效形状必须覆盖 `C` 个值)。 +- 确切的布局/分形约束是目标特定的;参见 `include/pto/npu/*/TColExpand*.hpp` 下的后端头文件。 + +## 示例 + +参见 `docs/isa/` 和 `docs/coding/tutorials/` 中的相关示例。 + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tcolexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tcolexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpanddiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDEXPDIF.md b/designs/outerCube/PTOISA/TCOLEXPANDEXPDIF.md new file mode 100644 index 00000000..ea7d54b0 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDEXPDIF.md @@ -0,0 +1,89 @@ +# TCOLEXPANDEXPDIF + + +## Tile Operation Diagram + +![TCOLEXPANDEXPDIF tile operation](../figures/isa/TCOLEXPANDEXPDIF.svg) + +## Introduction + +Column-wise exp-diff: compute `exp(src0 - src1)` using a per-column scalar vector `src1`. + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_j` be the per-column scalar taken from `src1` (one value per column). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \exp(\mathrm{src0}_{i,j} - s_j) +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolexpandexpdif ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDEXPDIF(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. +- `src1` is expected to provide **one scalar per column** (i.e., its valid shape must cover `C` values). +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TColExpand*.hpp`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandexpdif ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDEXPDIF_zh.md b/designs/outerCube/PTOISA/TCOLEXPANDEXPDIF_zh.md new file mode 100644 index 00000000..b2fdc7ed --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDEXPDIF_zh.md @@ -0,0 +1,89 @@ +# TCOLEXPANDEXPDIF + +## 指令示意图 + +![TCOLEXPANDEXPDIF tile operation](../figures/isa/TCOLEXPANDEXPDIF.svg) + +## 简介 + +列指数差运算:计算 exp(src0 - src1),其中 src1 为每列标量。 + +## 数学语义 + +设 `R = dst.GetValidRow()` 和 `C = dst.GetValidCol()`。设 `s_j` 为从 `src1` 中获取的每列标量(每列一个值)。 + +对于 `0 <= i < R` 和 `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \exp(\mathrm{src0}_{i,j} - s_j) +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tcolexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolexpandexpdif ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDEXPDIF(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::DType`、`TileDataSrc1::DType` 必须是以下之一:`half`、`float`。 +- Tile 形状/布局约束(编译时):`TileDataDst::isRowMajor`。 +- `src1` 预期提供**每列一个标量**(即,其有效形状必须覆盖 `C` 个值)。 +- 确切的布局/分形约束是目标特定的;参见 `include/pto/npu/*/TColExpand*.hpp` 下的后端头文件。 + +## 示例 + +参见 `docs/isa/` 和 `docs/coding/tutorials/` 中的相关示例。 + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tcolexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tcolexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandexpdif ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDMAX.md b/designs/outerCube/PTOISA/TCOLEXPANDMAX.md new file mode 100644 index 00000000..85337c45 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDMAX.md @@ -0,0 +1,90 @@ +# TCOLEXPANDMAX + + +## Tile Operation Diagram + +![TCOLEXPANDMAX tile operation](../figures/isa/TCOLEXPANDMAX.svg) + +## Introduction + +Column-wise broadcast max: take `max(src0, src1)` where `src1` provides one scalar per column. + + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_j` be the per-column scalar taken from `src1` (one value per column). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \max(\mathrm{src0}_{i,j}, s_j) +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolexpandmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDMAX(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. +- `src1` is expected to provide **one scalar per column** (i.e., its valid shape must cover `C` values). +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TColExpand*.hpp`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDMAX_zh.md b/designs/outerCube/PTOISA/TCOLEXPANDMAX_zh.md new file mode 100644 index 00000000..ddd493b4 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDMAX_zh.md @@ -0,0 +1,89 @@ +# TCOLEXPANDMAX + +## 指令示意图 + +![TCOLEXPANDMAX tile operation](../figures/isa/TCOLEXPANDMAX.svg) + +## 简介 + +列广播最大值:与每列标量向量取最大值。 + +## 数学语义 + +设 `R = dst.GetValidRow()` 和 `C = dst.GetValidCol()`。设 `s_j` 为从 `src1` 中获取的每列标量(每列一个值)。 + +对于 `0 <= i < R` 和 `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \max(\mathrm{src0}_{i,j}, s_j) +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tcolexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolexpandmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDMAX(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::DType`、`TileDataSrc1::DType` 必须是以下之一:`half`、`float`。 +- Tile 形状/布局约束(编译时):`TileDataDst::isRowMajor`。 +- `src1` 预期提供**每列一个标量**(即,其有效形状必须覆盖 `C` 个值)。 +- 确切的布局/分形约束是目标特定的;参见 `include/pto/npu/*/TColExpand*.hpp` 下的后端头文件。 + +## 示例 + +参见 `docs/isa/` 和 `docs/coding/tutorials/` 中的相关示例。 + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tcolexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tcolexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDMIN.md b/designs/outerCube/PTOISA/TCOLEXPANDMIN.md new file mode 100644 index 00000000..0cd2eec5 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDMIN.md @@ -0,0 +1,90 @@ +# TCOLEXPANDMIN + + +## Tile Operation Diagram + +![TCOLEXPANDMIN tile operation](../figures/isa/TCOLEXPANDMIN.svg) + +## Introduction + +Column-wise broadcast min: take `min(src0, src1)` where `src1` provides one scalar per column. + + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_j` be the per-column scalar taken from `src1` (one value per column). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \min(\mathrm{src0}_{i,j}, s_j) +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolexpandmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDMIN(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. +- `src1` is expected to provide **one scalar per column** (i.e., its valid shape must cover `C` values). +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TColExpand*.hpp`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDMIN_zh.md b/designs/outerCube/PTOISA/TCOLEXPANDMIN_zh.md new file mode 100644 index 00000000..11c7bae7 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDMIN_zh.md @@ -0,0 +1,89 @@ +# TCOLEXPANDMIN + +## 指令示意图 + +![TCOLEXPANDMIN tile operation](../figures/isa/TCOLEXPANDMIN.svg) + +## 简介 + +列广播最小值:与每列标量向量取最小值。 + +## 数学语义 + +设 `R = dst.GetValidRow()` 和 `C = dst.GetValidCol()`。设 `s_j` 为从 `src1` 中获取的每列标量(每列一个值)。 + +对于 `0 <= i < R` 和 `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \min(\mathrm{src0}_{i,j}, s_j) +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tcolexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolexpandmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDMIN(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::DType`、`TileDataSrc1::DType` 必须是以下之一:`half`、`float`。 +- Tile 形状/布局约束(编译时):`TileDataDst::isRowMajor`。 +- `src1` 预期提供**每列一个标量**(即,其有效形状必须覆盖 `C` 个值)。 +- 确切的布局/分形约束是目标特定的;参见 `include/pto/npu/*/TColExpand*.hpp` 下的后端头文件。 + +## 示例 + +参见 `docs/isa/` 和 `docs/coding/tutorials/` 中的相关示例。 + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tcolexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tcolexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDMUL.md b/designs/outerCube/PTOISA/TCOLEXPANDMUL.md new file mode 100644 index 00000000..c1bcc7d6 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDMUL.md @@ -0,0 +1,89 @@ +# TCOLEXPANDMUL + + +## Tile Operation Diagram + +![TCOLEXPANDMUL tile operation](../figures/isa/TCOLEXPANDMUL.svg) + +## Introduction + +Column-wise broadcast multiply: multiply each element of `src0` by a per-column scalar vector `src1`. + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_j` be the per-column scalar taken from `src1` (one value per column). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \cdot s_j +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolexpandmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDMUL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. +- `src1` is expected to provide **one scalar per column** (i.e., its valid shape must cover `C` values). +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TColExpand*.hpp`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDMUL_zh.md b/designs/outerCube/PTOISA/TCOLEXPANDMUL_zh.md new file mode 100644 index 00000000..4bd2711b --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDMUL_zh.md @@ -0,0 +1,89 @@ +# TCOLEXPANDMUL + +## 指令示意图 + +![TCOLEXPANDMUL tile operation](../figures/isa/TCOLEXPANDMUL.svg) + +## 简介 + +列广播乘法:将每一列乘以一个每列标量向量。 + +## 数学语义 + +设 `R = dst.GetValidRow()` 和 `C = dst.GetValidCol()`。设 `s_j` 为从 `src1` 中获取的每列标量(每列一个值)。 + +对于 `0 <= i < R` 和 `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \cdot s_j +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tcolexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolexpandmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDMUL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::DType`、`TileDataSrc1::DType` 必须是以下之一:`half`、`float`。 +- Tile 形状/布局约束(编译时):`TileDataDst::isRowMajor`。 +- `src1` 预期提供**每列一个标量**(即,其有效形状必须覆盖 `C` 个值)。 +- 确切的布局/分形约束是目标特定的;参见 `include/pto/npu/*/TColExpand*.hpp` 下的后端头文件。 + +## 示例 + +参见 `docs/isa/` 和 `docs/coding/tutorials/` 中的相关示例。 + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tcolexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tcolexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDSUB.md b/designs/outerCube/PTOISA/TCOLEXPANDSUB.md new file mode 100644 index 00000000..6330e1a1 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDSUB.md @@ -0,0 +1,89 @@ +# TCOLEXPANDSUB + + +## Tile Operation Diagram + +![TCOLEXPANDSUB tile operation](../figures/isa/TCOLEXPANDSUB.svg) + +## Introduction + +Column-wise broadcast subtract: subtract a per-column scalar vector `src1` from `src0`. + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_j` be the per-column scalar taken from `src1` (one value per column). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} - s_j +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolexpandsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDSUB(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. +- `src1` is expected to provide **one scalar per column** (i.e., its valid shape must cover `C` values). +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TColExpand*.hpp`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPANDSUB_zh.md b/designs/outerCube/PTOISA/TCOLEXPANDSUB_zh.md new file mode 100644 index 00000000..5a28c2d1 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPANDSUB_zh.md @@ -0,0 +1,89 @@ +# TCOLEXPANDSUB + +## 指令示意图 + +![TCOLEXPANDSUB tile operation](../figures/isa/TCOLEXPANDSUB.svg) + +## 简介 + +列广播减法:从每一列中减去一个每列标量向量。 + +## 数学语义 + +设 `R = dst.GetValidRow()` 和 `C = dst.GetValidCol()`。设 `s_j` 为从 `src1` 中获取的每列标量(每列一个值)。 + +对于 `0 <= i < R` 和 `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} - s_j +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tcolexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolexpandsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPANDSUB(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::DType`、`TileDataSrc1::DType` 必须是以下之一:`half`、`float`。 +- Tile 形状/布局约束(编译时):`TileDataDst::isRowMajor`。 +- `src1` 预期提供**每列一个标量**(即,其有效形状必须覆盖 `C` 个值)。 +- 确切的布局/分形约束是目标特定的;参见 `include/pto/npu/*/TColExpand*.hpp` 下的后端头文件。 + +## 示例 + +参见 `docs/isa/` 和 `docs/coding/tutorials/` 中的相关示例。 + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tcolexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tcolexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolexpandsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCOLEXPAND_zh.md b/designs/outerCube/PTOISA/TCOLEXPAND_zh.md new file mode 100644 index 00000000..6d966964 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLEXPAND_zh.md @@ -0,0 +1,76 @@ +# TCOLEXPAND + +## 指令示意图 + +![TCOLEXPAND tile operation](../figures/isa/TCOLEXPAND.svg) + +## 简介 + +将每个源列的第一个元素广播到目标列中。 + +## 数学语义 + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. For `0 <= i < R` and `0 <= j < C`: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{0,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tcolexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolexpand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolexpand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLEXPAND(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## 约束 + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT src, dst; + TCOLEXPAND(dst, src); +} +``` diff --git a/designs/outerCube/PTOISA/TCOLMAX.md b/designs/outerCube/PTOISA/TCOLMAX.md new file mode 100644 index 00000000..fdfed987 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLMAX.md @@ -0,0 +1,135 @@ +# TCOLMAX + + +## Tile Operation Diagram + +![TCOLMAX tile operation](../figures/isa/TCOLMAX.svg) + +## Introduction + +Reduce each column by taking the maximum across rows. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \max_{0 \le i < R} \mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolmax %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolmax %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolmax ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tcolmax %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tcolmax ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLMAX(TileDataOut &dst, TileDataIn &src, WaitEvents &... events); +``` + +## Constraints + +Implementation checks (NPU): + +- Tile location: `dst` and `src` must be `TileType::Vec`. +- Tile layout: both tiles must be ND fractal (`isRowMajor` and `SLayout::NoneBox`). +- Data types: + - A2A3: `half`, `float`, `int16_t`, `int32_t`. + - A5: `half`, `float`, `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `bfloat16_t`. +- DType consistency: `dst.DType == src.DType`. +- Runtime valid checks: + - `src.GetValidCol() == dst.GetValidCol()`. + - If `src.GetValidRow() == 0` or `src.GetValidCol() == 0`, the implementation returns early. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TCOLMAX(dst, src); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TCOLMAX(dst, src); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolmax %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolmax %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolmax %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolmax ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TCOLMAX_zh.md b/designs/outerCube/PTOISA/TCOLMAX_zh.md new file mode 100644 index 00000000..a958f1c4 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLMAX_zh.md @@ -0,0 +1,108 @@ +# TCOLMAX + +## 指令示意图 + +![TCOLMAX tile operation](../figures/isa/TCOLMAX.svg) + +## 简介 + +通过取行间最大值来归约每一列。 + +## 数学语义 + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \max_{0 \le i < R} \mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tcolmax %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolmax %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolmax ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolmax %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolmax ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLMAX(TileDataOut &dst, TileDataIn &src, WaitEvents &... events); +``` + +## 约束 + +实现检查 (NPU): + +- Tile location: `dst` and `src` must be `TileType::Vec`. +- Tile 布局: both tiles must be ND fractal (`isRowMajor` and `SLayout::NoneBox`). +- 数据类型: + - A2A3: `half`, `float`, `int16_t`, `int32_t`. + - A5: `half`, `float`, `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `bfloat16_t`. +- 数据类型一致性: `dst.DType == src.DType`. +- 运行期有效区域检查: + - `src.GetValidCol() == dst.GetValidCol()`. + - If `src.GetValidRow() == 0` or `src.GetValidCol() == 0`, the implementation returns early. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TCOLMAX(dst, src); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TCOLMAX(dst, src); +} +``` diff --git a/designs/outerCube/PTOISA/TCOLMIN.md b/designs/outerCube/PTOISA/TCOLMIN.md new file mode 100644 index 00000000..1cfdcd35 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLMIN.md @@ -0,0 +1,135 @@ +# TCOLMIN + + +## Tile Operation Diagram + +![TCOLMIN tile operation](../figures/isa/TCOLMIN.svg) + +## Introduction + +Reduce each column by taking the minimum across rows. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \min_{0 \le i < R} \mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolmin %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolmin %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolmin ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tcolmin %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tcolmin ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLMIN(TileDataOut &dst, TileDataIn &src, WaitEvents &... events); +``` + +## Constraints + +Implementation checks (NPU): + +- Tile location: `dst` and `src` must be `TileType::Vec`. +- Tile layout: both tiles must be ND fractal (`isRowMajor` and `SLayout::NoneBox`). +- Data types: + - A2A3: `half`, `float`, `int16_t`, `int32_t`. + - A5: `half`, `float`, `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `bfloat16_t`. +- DType consistency: `dst.DType == src.DType`. +- Runtime valid checks: + - `src.GetValidCol() == dst.GetValidCol()`. + - If `src.GetValidRow() == 0` or `src.GetValidCol() == 0`, the implementation returns early. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TCOLMIN(dst, src); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TCOLMIN(dst, src); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolmin %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolmin %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolmin %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolmin ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TCOLMIN_zh.md b/designs/outerCube/PTOISA/TCOLMIN_zh.md new file mode 100644 index 00000000..9a6fcadf --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLMIN_zh.md @@ -0,0 +1,108 @@ +# TCOLMIN + +## 指令示意图 + +![TCOLMIN tile operation](../figures/isa/TCOLMIN.svg) + +## 简介 + +通过取行间最小值来归约每一列。 + +## 数学语义 + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \min_{0 \le i < R} \mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tcolmin %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolmin %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolmin ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolmin %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolmin ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLMIN(TileDataOut &dst, TileDataIn &src, WaitEvents &... events); +``` + +## 约束 + +实现检查 (NPU): + +- Tile location: `dst` and `src` must be `TileType::Vec`. +- Tile 布局: both tiles must be ND fractal (`isRowMajor` and `SLayout::NoneBox`). +- 数据类型: + - A2A3: `half`, `float`, `int16_t`, `int32_t`. + - A5: `half`, `float`, `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `bfloat16_t`. +- 数据类型一致性: `dst.DType == src.DType`. +- 运行期有效区域检查: + - `src.GetValidCol() == dst.GetValidCol()`. + - If `src.GetValidRow() == 0` or `src.GetValidCol() == 0`, the implementation returns early. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TCOLMIN(dst, src); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TCOLMIN(dst, src); +} +``` diff --git a/designs/outerCube/PTOISA/TCOLPROD.md b/designs/outerCube/PTOISA/TCOLPROD.md new file mode 100644 index 00000000..d0d050b6 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLPROD.md @@ -0,0 +1,135 @@ +# TCOLPROD + + +## Tile Operation Diagram + +![TCOLPROD tile operation](../figures/isa/TCOLPROD.svg) + +## Introduction + +Reduce each column by multiplying across rows. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \prod_{i=0}^{R-1} \mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolprod %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolprod %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolprod ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tcolprod %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tcolprod ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLPROD(TileDataOut &dst, TileDataIn &src, WaitEvents &... events); +``` + +## Constraints + +Implementation checks (NPU): + +- Tile location: `dst` and `src` must be `TileType::Vec`. +- Tile layout: both tiles must be ND fractal (`isRowMajor` and `SLayout::NoneBox`). +- DType consistency: `dst.DType == src.DType`. +- Supported `src.DType`: + - A2A3: `half`, `float`, `int16_t`, `int32_t`. + - A5: `half`, `float`, `bfloat16`, `int16_t`, `int32_t`, `uint16_t`, `uint32_t`. +- Runtime valid checks: + - `src.GetValidCol() == dst.GetValidCol()`. + - If `src.GetValidRow() == 0` or `src.GetValidCol() == 0`, the implementation returns early. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TCOLPROD(dst, src); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TCOLPROD(dst, src); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolprod %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolprod %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolprod %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolprod ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TCOLPROD_zh.md b/designs/outerCube/PTOISA/TCOLPROD_zh.md new file mode 100644 index 00000000..36ef0001 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLPROD_zh.md @@ -0,0 +1,108 @@ +# TCOLPROD + +## 指令示意图 + +![TCOLPROD tile operation](../figures/isa/TCOLPROD.svg) + +## 简介 + +通过跨行乘积来归约每一列。 + +## 数学语义 + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \prod_{i=0}^{R-1} \mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tcolprod %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolprod %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolprod ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolprod %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolprod ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLPROD(TileDataOut &dst, TileDataIn &src, WaitEvents &... events); +``` + +## 约束 + +实现检查 (NPU): + +- Tile location: `dst` and `src` must be `TileType::Vec`. +- Tile 布局: both tiles must be ND fractal (`isRowMajor` and `SLayout::NoneBox`). +- 数据类型一致性: `dst.DType == src.DType`. +- Supported `src.DType`: + - A2A3: `half`, `float`, `int16_t`, `int32_t`. + - A5: `half`, `float`, `bfloat16`, `int16_t`, `int32_t`, `uint16_t`, `uint32_t`. +- 运行期有效区域检查: + - `src.GetValidCol() == dst.GetValidCol()`. + - If `src.GetValidRow() == 0` or `src.GetValidCol() == 0`, the implementation returns early. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TCOLPROD(dst, src); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TCOLPROD(dst, src); +} +``` diff --git a/designs/outerCube/PTOISA/TCOLSUM.md b/designs/outerCube/PTOISA/TCOLSUM.md new file mode 100644 index 00000000..ef4ec250 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLSUM.md @@ -0,0 +1,149 @@ +# TCOLSUM + + +## Tile Operation Diagram + +![TCOLSUM tile operation](../figures/isa/TCOLSUM.svg) + +## Introduction + +Reduce each column by summing across rows. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \sum_{i=0}^{R-1} \mathrm{src}_{i,j} $$ + +`isBinary` selects the implementation path (binary-tree accumulation vs. sequential accumulation). + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcolsum %src {isBinary = false} : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolsum %src : !pto.tile<...> -> !pto.tile<...> +%dst = pto.tcolsum %src, %tmp {isBinary = false} : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolsum ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +pto.tcolsum ins(%src, %tmp {isBinary = false} : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tcolsum %src : !pto.tile<...> -> !pto.tile<...> +%dst = pto.tcolsum %src, %tmp {isBinary = false} : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tcolsum ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +pto.tcolsum ins(%src, %tmp {isBinary = false} : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLSUM(TileDataOut &dst, TileDataIn &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TCOLSUM(TileDataOut &dst, TileDataIn &src, TileDataTmp &tmp, bool isBinary, WaitEvents &... events); +``` + +## Constraints + +Implementation checks (NPU): + +- Tile location: `dst`, `src`, `tmp` must be `TileType::Vec`. +- Tile layout: all tiles must be ND fractal (`isRowMajor` and `SLayout::NoneBox`). +- DType consistency: + - A2A3: `src.DType` must be one of `half`, `float`, `int16_t`, `int32_t`, and `dst.DType == tmp.DType == src.DType`. + - A5: `dst.DType == src.DType` is required by `TColReduceCheck`; the exact supported `src.DType` set is target-defined (see `include/pto/npu/a5/TColReduceOps.hpp`). +- Runtime valid checks: + - A2A3: `src.GetValidCol() == dst.GetValidCol()`; returns early if `src.GetValidRow() == 0` or `src.GetValidCol() == 0`. + - A5: `srcValidRow` and `srcValidCol` must be non-zero; `srcValidCol == dstValidCol` is asserted by `TColReduceCheck`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TCOLSUM(dst, src, tmp, /*isBinary=*/false); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TCOLSUM(dst, src, tmp, /*isBinary=*/false); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcolsum %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcolsum %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcolsum %src {isBinary = false} : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcolsum ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TCOLSUM_zh.md b/designs/outerCube/PTOISA/TCOLSUM_zh.md new file mode 100644 index 00000000..b15c5ad6 --- /dev/null +++ b/designs/outerCube/PTOISA/TCOLSUM_zh.md @@ -0,0 +1,122 @@ +# TCOLSUM + +## 指令示意图 + +![TCOLSUM tile operation](../figures/isa/TCOLSUM.svg) + +## 简介 + +通过对行求和来归约每一列。 + +## 数学语义 + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= j < C`: + +$$ \mathrm{dst}_{0,j} = \sum_{i=0}^{R-1} \mathrm{src}_{i,j} $$ + +`isBinary` selects the implementation path (binary-tree accumulation vs. sequential accumulation). + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tcolsum %src {isBinary = false} : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### AS Level 1 (SSA) + +```text +%dst = pto.tcolsum %src : !pto.tile<...> -> !pto.tile<...> +%dst = pto.tcolsum %src, %tmp {isBinary = false} : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcolsum ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +pto.tcolsum ins(%src, %tmp {isBinary = false} : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcolsum %src : !pto.tile<...> -> !pto.tile<...> +%dst = pto.tcolsum %src, %tmp {isBinary = false} : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcolsum ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +pto.tcolsum ins(%src, %tmp {isBinary = false} : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TCOLSUM(TileDataOut &dst, TileDataIn &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TCOLSUM(TileDataOut &dst, TileDataIn &src, TileDataTmp &tmp, bool isBinary, WaitEvents &... events); +``` + +## 约束 + +实现检查 (NPU): + +- Tile location: `dst`, `src`, `tmp` must be `TileType::Vec`. +- Tile 布局: all tiles must be ND fractal (`isRowMajor` and `SLayout::NoneBox`). +- 数据类型一致性: + - A2A3: `src.DType` must be one of `half`, `float`, `int16_t`, `int32_t`, and `dst.DType == tmp.DType == src.DType`. + - A5: `dst.DType == src.DType` is required by `TColReduceCheck`; the exact supported `src.DType` set is target-defined (see `include/pto/npu/a5/TColReduceOps.hpp`). +- 运行期有效区域检查: + - A2A3: `src.GetValidCol() == dst.GetValidCol()`; returns early if `src.GetValidRow() == 0` or `src.GetValidCol() == 0`. + - A5: `srcValidRow` and `srcValidCol` must be non-zero; `srcValidCol == dstValidCol` is asserted by `TColReduceCheck`. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TCOLSUM(dst, src, tmp, /*isBinary=*/false); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TCOLSUM(dst, src, tmp, /*isBinary=*/false); +} +``` diff --git a/designs/outerCube/PTOISA/TCONCAT.md b/designs/outerCube/PTOISA/TCONCAT.md new file mode 100644 index 00000000..e24be1e1 --- /dev/null +++ b/designs/outerCube/PTOISA/TCONCAT.md @@ -0,0 +1,40 @@ +# TCONCAT + +## Tile Operation Diagram + +![TCONCAT tile operation](../figures/isa/TCONCAT.svg) + +## Introduction + +Concatenate two source tiles along the column dimension into a destination tile. + +## Math Interpretation + +Semantics are instruction-specific. Unless stated otherwise, behavior is defined over the destination valid region. + +## Assembly Syntax + +PTO-AS form: see `docs/assembly/PTO-AS.md`. + +### IR Level 1 (SSA) + +```text +%dst = pto.tconcat ... +``` + +### IR Level 2 (DPS) + +```text +pto.tconcat ins(...) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`. + +## Constraints + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## Examples + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TCONCAT_zh.md b/designs/outerCube/PTOISA/TCONCAT_zh.md new file mode 100644 index 00000000..3a050b7e --- /dev/null +++ b/designs/outerCube/PTOISA/TCONCAT_zh.md @@ -0,0 +1,41 @@ +# TCONCAT + +## 指令示意图 + +![TCONCAT tile operation](../figures/isa/TCONCAT.svg) + +## 简介 + +沿列维将两个源 Tile 拼接到目标 Tile。 + +## 数学语义 + +语义随指令而变化。 Unless stated otherwise, behavior is defined over the destination valid region. + +## 汇编语法 + +PTO-AS 形式:参见 `docs/assembly/PTO-AS.md`. + +### AS Level 1(SSA) + +```text +%dst = pto.tconcat ... +``` + +### AS Level 2(DPS) + +```text +pto.tconcat ins(...) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`. + +## 约束 + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## 示例 + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TCVT.md b/designs/outerCube/PTOISA/TCVT.md new file mode 100644 index 00000000..72bdccf0 --- /dev/null +++ b/designs/outerCube/PTOISA/TCVT.md @@ -0,0 +1,124 @@ +# TCVT + + +## Tile Operation Diagram + +![TCVT tile operation](../figures/isa/TCVT.svg) + +## Introduction + +Elementwise type conversion with a specified rounding mode. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{cast}_{\mathrm{rmode}}\!\left(\mathrm{src}_{i,j}\right) $$ + +where `rmode` is a rounding policy (see `pto::RoundMode`). + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tcvt %src {rmode = #pto.round_mode} : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tcvt %src{rmode = #pto}: !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tcvt ins(%src{rmode = #pto}: !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp` and `include/pto/common/constants.hpp`: + +```cpp +template +PTO_INST RecordEvent TCVT(TileDataD &dst, TileDataS &src, RoundMode mode, SaturationMode satMode, WaitEvents &... events); + +template +PTO_INST RecordEvent TCVT(TileDataD &dst, TileDataS &src, RoundMode mode, WaitEvents &... events); +``` + +## Constraints + +- `dst` and `src` must be compatible in shape/valid region as required by the implementation. +- The conversion `(src element type) -> (dst element type)` must be supported by the target for the given `RoundMode`. +- **Implementation notes (A2A3/A5)**: + - One form accepts an explicit `SaturationMode`, and the specified saturation behavior is forwarded directly to the implementation. + - The other form omits `SaturationMode`; in that case, the implementation chooses a target-defined default saturation behavior for the specific type pair. + - On CPU, only the form without explicit `SaturationMode` is currently implemented. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TCVT(dst, src, RoundMode::CAST_RINT); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TCVT(dst, src, RoundMode::CAST_RINT); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tcvt %src{rmode = #pto}: !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcvt %src{rmode = #pto}: !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tcvt %src {rmode = #pto.round_mode} : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcvt ins(%src{rmode = #pto}: !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TCVT_zh.md b/designs/outerCube/PTOISA/TCVT_zh.md new file mode 100644 index 00000000..fddd187a --- /dev/null +++ b/designs/outerCube/PTOISA/TCVT_zh.md @@ -0,0 +1,124 @@ +# TCVT + +## 指令示意图 + +![TCVT tile operation](../figures/isa/TCVT.svg) + +## 简介 + +带指定舍入模式的逐元素类型转换。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{cast}_{\mathrm{rmode}}\!\left(\mathrm{src}_{i,j}\right) $$ + +其中 `rmode` 是舍入策略(参见 `pto::RoundMode`)。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tcvt %src {rmode = #pto.round_mode} : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tcvt %src{rmode = #pto}: !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tcvt ins(%src{rmode = #pto}: !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp` 和 `include/pto/common/constants.hpp`: + +```cpp +template +PTO_INST RecordEvent TCVT(TileDataD &dst, TileDataS &src, RoundMode mode, SaturationMode satMode, WaitEvents &... events); + +template +PTO_INST RecordEvent TCVT(TileDataD &dst, TileDataS &src, RoundMode mode, WaitEvents &... events); +``` + +## 约束 + +- `dst` 和 `src` 必须在形状/有效区域方面兼容,如实现所要求的。 +- 对于给定的 `RoundMode`,转换 `(src 元素类型) -> (dst 元素类型)` 必须被目标支持。 +- **实现说明 (A2A3/A5)**: + - 一种形式接受显式的 `SaturationMode`,指定的饱和行为会直接传递给实现。 + - 另一种形式不显式给出 `SaturationMode`;此时实现会针对具体类型对选择目标定义的默认饱和行为。 + - 在 CPU 实现中,目前仅实现了不显式传入 `SaturationMode` 的形式。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TCVT(dst, src, RoundMode::CAST_RINT); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TCVT(dst, src, RoundMode::CAST_RINT); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tcvt %src{rmode = #pto}: !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tcvt %src{rmode = #pto}: !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tcvt %src {rmode = #pto.round_mode} : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tcvt ins(%src{rmode = #pto}: !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TDEQUANT.md b/designs/outerCube/PTOISA/TDEQUANT.md new file mode 100644 index 00000000..8204ce03 --- /dev/null +++ b/designs/outerCube/PTOISA/TDEQUANT.md @@ -0,0 +1,40 @@ +# TDEQUANT + +## Tile Operation Diagram + +![TDEQUANT tile operation](../figures/isa/TDEQUANT.svg) + +## Introduction + +Dequantize an integer tile into a floating-point tile using scale and offset tiles. + +## Math Interpretation + +Semantics are instruction-specific. Unless stated otherwise, behavior is defined over the destination valid region. + +## Assembly Syntax + +PTO-AS form: see `docs/assembly/PTO-AS.md`. + +### IR Level 1 (SSA) + +```text +%dst = pto.tdequant ... +``` + +### IR Level 2 (DPS) + +```text +pto.tdequant ins(...) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`. + +## Constraints + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## Examples + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TDEQUANT_zh.md b/designs/outerCube/PTOISA/TDEQUANT_zh.md new file mode 100644 index 00000000..9f07b6c4 --- /dev/null +++ b/designs/outerCube/PTOISA/TDEQUANT_zh.md @@ -0,0 +1,41 @@ +# TDEQUANT + +## 指令示意图 + +![TDEQUANT tile operation](../figures/isa/TDEQUANT.svg) + +## 简介 + +使用 scale 与 offset Tile 将整数量化 Tile 反量化为浮点 Tile。 + +## 数学语义 + +语义随指令而变化。 Unless stated otherwise, behavior is defined over the destination valid region. + +## 汇编语法 + +PTO-AS 形式:参见 `docs/assembly/PTO-AS.md`. + +### AS Level 1(SSA) + +```text +%dst = pto.tdequant ... +``` + +### AS Level 2(DPS) + +```text +pto.tdequant ins(...) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`. + +## 约束 + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## 示例 + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TDIV.md b/designs/outerCube/PTOISA/TDIV.md new file mode 100644 index 00000000..7c10a875 --- /dev/null +++ b/designs/outerCube/PTOISA/TDIV.md @@ -0,0 +1,137 @@ +# TDIV + + +## Tile Operation Diagram + +![TDIV tile operation](../figures/isa/TDIV.svg) + +## Introduction + +Elementwise division of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \frac{\mathrm{src0}_{i,j}}{\mathrm{src1}_{i,j}} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tdiv %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tdiv %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tdiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tdiv %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tdiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TDIV(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `half`, `float`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `int32_t`, `uint32_t`, `float`, `int16_t`, `uint16_t`, `half`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain;. +- **Division-by-zero**: + - Behavior is target-defined. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TDIV(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TDIV(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tdiv %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tdiv %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tdiv %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tdiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TDIVS.md b/designs/outerCube/PTOISA/TDIVS.md new file mode 100644 index 00000000..23905d8d --- /dev/null +++ b/designs/outerCube/PTOISA/TDIVS.md @@ -0,0 +1,155 @@ +# TDIVS + + +## Tile Operation Diagram + +![TDIVS tile operation](../figures/isa/TDIVS.svg) + +## Introduction + +Elementwise division with a scalar (tile/scalar or scalar/tile). + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +- Tile/scalar: + + $$ \mathrm{dst}_{i,j} = \frac{\mathrm{src}_{i,j}}{\mathrm{scalar}} $$ + +- Scalar/tile: + + $$ \mathrm{dst}_{i,j} = \frac{\mathrm{scalar}}{\mathrm{src}_{i,j}} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Tile/scalar form: + +```text +%dst = tdivs %src, %scalar : !pto.tile<...>, f32 +``` + +Scalar/tile form: + +```text +%dst = tdivs %scalar, %src : f32, !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tdivs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +%dst = pto.tdivs %scalar, %src : (dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tdivs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +pto.tdivs ins(%scalar, %src : dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tdivs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +%dst = pto.tdivs %scalar, %src : (dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tdivs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +pto.tdivs ins(%scalar, %src : dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TDIVS(TileDataDst &dst, TileDataSrc &src0, typename TileDataSrc::DType scalar, WaitEvents &... events); + +template +PTO_INST RecordEvent TDIVS(TileDataDst &dst, typename TileDataDst::DType scalar, TileDataSrc &src0, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)** (both overloads): + - `TileData::DType` must be one of: `int32_t`, `int`, `int16_t`, `half`, `float16_t`, `float`, `float32_t`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Implementation checks (A5)** (both overloads): + - `TileData::DType` must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `float`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Division-by-zero**: + - Behavior is target-defined; on A5 the tile/scalar form maps to multiply-by-reciprocal and uses `1/0 -> +inf` for `scalar == 0`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TDIVS(dst, src, 2.0f); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TDIVS(dst, 2.0f, src); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tdivs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tdivs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.tdivs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tdivs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TDIVS_zh.md b/designs/outerCube/PTOISA/TDIVS_zh.md new file mode 100644 index 00000000..060f5c64 --- /dev/null +++ b/designs/outerCube/PTOISA/TDIVS_zh.md @@ -0,0 +1,128 @@ +# TDIVS + +## 指令示意图 + +![TDIVS tile operation](../figures/isa/TDIVS.svg) + +## 简介 + +与标量的逐元素除法(Tile/标量 或 标量/Tile)。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +- Tile/scalar: + + $$ \mathrm{dst}_{i,j} = \frac{\mathrm{src}_{i,j}}{\mathrm{scalar}} $$ + +- Scalar/tile: + + $$ \mathrm{dst}_{i,j} = \frac{\mathrm{scalar}}{\mathrm{src}_{i,j}} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +Tile/scalar form: + +```text +%dst = tdivs %src, %scalar : !pto.tile<...>, f32 +``` + +Scalar/tile form: + +```text +%dst = tdivs %scalar, %src : f32, !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tdivs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +%dst = pto.tdivs %scalar, %src : (dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tdivs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +pto.tdivs ins(%scalar, %src : dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tdivs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +%dst = pto.tdivs %scalar, %src : (dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tdivs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +pto.tdivs ins(%scalar, %src : dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TDIVS(TileDataDst &dst, TileDataSrc &src0, typename TileDataSrc::DType scalar, WaitEvents &... events); + +template +PTO_INST RecordEvent TDIVS(TileDataDst &dst, typename TileDataDst::DType scalar, TileDataSrc &src0, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)** (both overloads): + - `TileData::DType` must be one of: `int32_t`, `int`, `int16_t`, `half`, `float16_t`, `float`, `float32_t`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **实现检查 (A5)** (both overloads): + - `TileData::DType` must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `float`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Division-by-zero**: + - Behavior is target-defined; on A5 the tile/scalar form maps to multiply-by-reciprocal and uses `1/0 -> +inf` for `scalar == 0`. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TDIVS(dst, src, 2.0f); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TDIVS(dst, 2.0f, src); +} +``` diff --git a/designs/outerCube/PTOISA/TDIV_zh.md b/designs/outerCube/PTOISA/TDIV_zh.md new file mode 100644 index 00000000..c9d13a40 --- /dev/null +++ b/designs/outerCube/PTOISA/TDIV_zh.md @@ -0,0 +1,110 @@ +# TDIV + +## 指令示意图 + +![TDIV tile operation](../figures/isa/TDIV.svg) + +## 简介 + +两个 Tile 的逐元素除法。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \frac{\mathrm{src0}_{i,j}}{\mathrm{src1}_{i,j}} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tdiv %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tdiv %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tdiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tdiv %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tdiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TDIV(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `half`, `float`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `int32_t`, `uint32_t`, `float`, `int16_t`, `uint16_t`, `half`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain;. +- **Division-by-zero**: + - Behavior is target-defined. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TDIV(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TDIV(dst, src0, src1); +} +``` diff --git a/designs/outerCube/PTOISA/TEXP.md b/designs/outerCube/PTOISA/TEXP.md new file mode 100644 index 00000000..f4abc35a --- /dev/null +++ b/designs/outerCube/PTOISA/TEXP.md @@ -0,0 +1,128 @@ +# TEXP + + +## Tile Operation Diagram + +![TEXP tile operation](../figures/isa/TEXP.svg) + +## Introduction + +Elementwise exponential. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \exp(\mathrm{src}_{i,j}) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = texp %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.texp %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.texp ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.texp %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.texp ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TEXP(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (NPU)**: + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TEXP(dst, src); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TEXP(dst, src); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.texp %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.texp %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = texp %src : !pto.tile<...> +# AS Level 2 (DPS) +pto.texp ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TEXPANDS.md b/designs/outerCube/PTOISA/TEXPANDS.md new file mode 100644 index 00000000..2fa9271d --- /dev/null +++ b/designs/outerCube/PTOISA/TEXPANDS.md @@ -0,0 +1,146 @@ +# TEXPANDS + +## Tile Operation Diagram + +![TEXPANDS tile operation](../figures/isa/TEXPANDS.svg) + +## Introduction + +Broadcast a scalar into a destination tile. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{scalar} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = texpands %scalar : f32, !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.texpands %scalar : dtype -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.texpands ins(%scalar : dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.texpands %scalar : dtype -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.texpands ins(%scalar : dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TEXPANDS(TileData &dst, typename TileData::DType scalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - For `TileType::Vec`: + - `TileData::DType` must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `bfloat16_t`, `float`. + - Row-major and col-major vector tiles are both supported. + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - For `TileType::Mat`: + - `TileData::DType` must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `bfloat16_t`, `float`. + - Static valid bounds: `TileData::Rows * TileData::Cols * sizeof(T) / 32` must be in `[1, 32767]`. +- **Implementation checks (A5)**: + - For `TileType::Vec`: + - `TileData::DType` must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `bfloat16_t`, `float`. + - Row-major and col-major vector tiles are both supported. + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - For `TileType::Mat`: + - `TileData::DType` must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `bfloat16_t`, `float`. + - For `TileData::layout == pto::Layout::NC1HWC0 || TileData::layout == pto::Layout::FRACTAL_Z`: + - `shape0 * shape1 * shape2 * shape3` must be in `[1, 32767]`. + - For `TileData::layout == pto::Layout::NDC1HWC0 || TileData::layout == pto::Layout::FRACTAL_Z_3D`: + - `shape0 * shape1 * shape2 * shape3 * shape4` must be in `[1, 32767]`. +- **Valid region**: + - For `TileType::Vec`: + - The op fills `dst` over `dst.GetValidRow()` / `dst.GetValidCol()`. + - For `TileType::Mat`: + - For tile operands, the op fills `dst` over `TileData::Rows` / `TileData::Cols`. + - For conv tiles, the op fills `dst` over the conv-tile shape. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() +{ + using TileT = Tile; + TileT dst; + TEXPANDS(dst, 0.0f); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() +{ + using TileT = Tile; + TileT dst; + TASSIGN(dst, 0x1000); + TEXPANDS(dst, 0.0f); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.texpands %scalar : dtype -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.texpands %scalar : dtype -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = texpands %scalar : f32, !pto.tile<...> +# AS Level 2 (DPS) +pto.texpands ins(%scalar : dtype) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TEXPANDS_zh.md b/designs/outerCube/PTOISA/TEXPANDS_zh.md new file mode 100644 index 00000000..63753d2f --- /dev/null +++ b/designs/outerCube/PTOISA/TEXPANDS_zh.md @@ -0,0 +1,134 @@ +# TEXPANDS + +## 指令示意图 + +![TEXPANDS tile operation](../figures/isa/TEXPANDS.svg) + +## 简介 + +将标量广播到目标 Tile 中。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{scalar} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = texpands %scalar : f32, !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.texpands %scalar : dtype -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.texpands ins(%scalar : dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TEXPANDS(TileData &dst, typename TileData::DType scalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查(A2A3)**: + - 对于 `TileType::Vec`: + - `TileData::DType` 必须是以下之一:`uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t`、`int32_t`、`half`、`bfloat16_t`、`float`。 + - 支持行优先和列优先向量 Tile。 + - 静态有效边界:`TileData::ValidRow <= TileData::Rows` 且 `TileData::ValidCol <= TileData::Cols`。 + - 对于 `TileType::Mat`: + - `TileData::DType` 必须是以下之一:`uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t`、`int32_t`、`half`、`bfloat16_t`、`float`。 + - 静态有效边界:`TileData::Rows * TileData::Cols * sizeof(T) / 32` 必须在 `[1, 32767]` 范围内。 +- **实现检查(A5)**: + - 对于 `TileType::Vec`: + - `TileData::DType` 必须是以下之一:`uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t`、`int32_t`、`half`、`bfloat16_t`、`float`。 + - 支持行优先和列优先向量 Tile。 + - 静态有效边界:`TileData::ValidRow <= TileData::Rows` 且 `TileData::ValidCol <= TileData::Cols`。 + - 对于 `TileType::Mat`: + - `TileData::DType` 必须是以下之一:`uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t`、`int32_t`、`half`、`bfloat16_t`、`float`。 + - 对于 `TileData::layout == pto::Layout::NC1HWC0 || TileData::layout == pto::Layout::FRACTAL_Z`: + - `shape0 * shape1 * shape2 * shape3` 必须在 `[1, 32767]` 范围内。 + - 对于 `TileData::layout == pto::Layout::NDC1HWC0 || TileData::layout == pto::Layout::FRACTAL_Z_3D`: + - `shape0 * shape1 * shape2 * shape3 * shape4` 必须在 `[1, 32767]` 范围内。 +- **有效区域**: + - 对于 `TileType::Vec`: + - 该操作在 `dst.GetValidRow()` / `dst.GetValidCol()` 上填充 `dst`。 + - 对于 `TileType::Mat`: + - 对于普通 Tile,该操作在 `TileData::Rows` / `TileData::Cols` 上填充 `dst`。 + - 对于 ConvTile,该操作在 conv-tile 的 shape 范围内填充 `dst`。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() +{ + using TileT = Tile; + TileT dst; + TEXPANDS(dst, 0.0f); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() +{ + using TileT = Tile; + TileT dst; + TASSIGN(dst, 0x1000); + TEXPANDS(dst, 0.0f); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.texpands %scalar : dtype -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.texpands %scalar : dtype -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = texpands %scalar : f32, !pto.tile<...> +# AS Level 2 (DPS) +pto.texpands ins(%scalar : dtype) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TEXP_zh.md b/designs/outerCube/PTOISA/TEXP_zh.md new file mode 100644 index 00000000..d871a692 --- /dev/null +++ b/designs/outerCube/PTOISA/TEXP_zh.md @@ -0,0 +1,101 @@ +# TEXP + +## 指令示意图 + +![TEXP tile operation](../figures/isa/TEXP.svg) + +## 简介 + +逐元素指数运算。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \exp(\mathrm{src}_{i,j}) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = texp %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.texp %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.texp ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.texp %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.texp ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TEXP(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (NPU)**: + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TEXP(dst, src); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TEXP(dst, src); +} +``` diff --git a/designs/outerCube/PTOISA/TEXTRACT.md b/designs/outerCube/PTOISA/TEXTRACT.md new file mode 100644 index 00000000..957e2e68 --- /dev/null +++ b/designs/outerCube/PTOISA/TEXTRACT.md @@ -0,0 +1,138 @@ +# TEXTRACT + + +## Tile Operation Diagram + +![TEXTRACT tile operation](../figures/isa/TEXTRACT.svg) + +## Introduction + +Extract a sub-tile from a source tile. + +## Math Interpretation + +Conceptually copies a window starting at `(indexRow, indexCol)` from `src` into `dst`. Exact mapping depends on layouts. + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. For `0 <= i < R` and `0 <= j < C`: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{\mathrm{indexRow}+i,\; \mathrm{indexCol}+j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = textract %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.textract %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.textract ins(%src, %idxrow, %idxcol : !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TEXTRACT(DstTileData &dst, SrcTileData &src, uint16_t indexRow = 0, uint16_t indexCol = 0, WaitEvents &... events); + +template +PTO_INST RecordEvent TEXTRACT(DstTileData &dst, SrcTileData &src, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +template +PTO_INST RecordEvent TEXTRACT(DstTileData &dst, SrcTileData &src, uint64_t preQuantScalar, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +template +PTO_INST RecordEvent TEXTRACT_FP(DstTileData &dst, SrcTileData &src, FpTileData &fp, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `DstTileData::DType` must equal `SrcTileData::DType` and must be one of: `int8_t`, `half`, `bfloat16_t`, `float`. + - Source fractal must satisfy: `(SFractal == ColMajor && isRowMajor)` or `(SFractal == RowMajor && !isRowMajor)`. In GEMV scenarios, the source fractal satisfies `(SrcTileData::Rows == 1 && SrcTileData::isRowMajor)` for Left. + - Runtime bounds checks: + - `indexRow + DstTileData::Rows <= SrcTileData::Rows` + - `indexCol + DstTileData::Cols <= SrcTileData::Cols` + - Destination must be `TileType::Left` or `TileType::Right` with a target-supported fractal configuration. +- **Implementation checks (A5)**: + - `DstTileData::DType` must equal `SrcTileData::DType` and must be one of: `int8_t`, `hifloat8_t`, `float8_e5m2_t`, `float8_e4m3_t`, `half`, `bfloat16_t`, `float`, `float4_e2m1x2_t`, `float4_e1m2x2_t`, `float8_e8m0_t`. + - Source fractal must satisfy: `(SFractal == ColMajor && isRowMajor)` or `(SFractal == RowMajor && !isRowMajor)` for Left/Right, In GEMV scenarios, the source fractal satisfies `(SrcTileData::Rows == 1 && SrcTileData::isRowMajor)` for Left. `(SFractal == RowMajor && isRowMajor)` for ScaleLeft, `(SFractal == ColMajor && !isRowMajor)` for ScaleRight. + - Destination supports `Mat -> Left/Right/Scale`, `Acc -> Mat` (including relu/scalar-quant/vector-quant forms), and also supports specific `Vec -> Mat` extraction paths. + - The vector-quantized form additionally requires an `FpTileData` scaling operand, matching the `TEXTRACT_FP(...)` interface shown above. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = TileLeft; + SrcT src; + DstT dst; + TEXTRACT(dst, src, /*indexRow=*/0, /*indexCol=*/0); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = TileLeft; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TEXTRACT(dst, src, /*indexRow=*/0, /*indexCol=*/0); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.textract %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.textract %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = textract %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.textract ins(%src, %idxrow, %idxcol : !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TEXTRACT_FP.md b/designs/outerCube/PTOISA/TEXTRACT_FP.md new file mode 100644 index 00000000..757e4a5b --- /dev/null +++ b/designs/outerCube/PTOISA/TEXTRACT_FP.md @@ -0,0 +1,90 @@ +# TEXTRACT_FP + + +## Tile Operation Diagram + +![TEXTRACT_FP tile operation](../figures/isa/TEXTRACT_FP.svg) + +## Introduction + +Extract a sub-tile from a source tile, while also providing an `fp` (scaling) tile used for vector quantization parameters (target/implementation-defined). + +## See also + +- TEXTRACT base instruction: `docs/isa/TEXTRACT.md`. + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TEXTRACT_FP(DstTileData &dst, SrcTileData &src, FpTileData &fp, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); +``` + +## Math Interpretation + +Unless otherwise specified, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.textract_fp %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.textract_fp ins(%src, %idxrow, %idxcol : !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.textract_fp %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.textract_fp ins(%src, %idxrow, %idxcol : !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## Constraints + +Type/layout/location/shape legality is backend-dependent; treat implementation-specific notes as normative for that backend. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.textract_fp %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.textract_fp %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.textract_fp %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.textract_fp ins(%src, %idxrow, %idxcol : !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TEXTRACT_FP_zh.md b/designs/outerCube/PTOISA/TEXTRACT_FP_zh.md new file mode 100644 index 00000000..9f18d61c --- /dev/null +++ b/designs/outerCube/PTOISA/TEXTRACT_FP_zh.md @@ -0,0 +1,59 @@ +# TEXTRACT_FP + +## 指令示意图 + +![TEXTRACT_FP tile operation](../figures/isa/TEXTRACT_FP.svg) + +## 简介 + +带 fp/缩放 Tile 的提取(向量量化参数)。 + +## 数学语义 + +除非另有说明, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.textract_fp %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.textract_fp ins(%src, %idxrow, %idxcol : !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.textract_fp %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.textract_fp ins(%src, %idxrow, %idxcol : !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TEXTRACT_FP(DstTileData &dst, SrcTileData &src, FpTileData &fp, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); +``` + +## 约束 + +Type/layout/location/shape legality is backend-dependent; treat implementation-specific notes as normative for that backend. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TEXTRACT_zh.md b/designs/outerCube/PTOISA/TEXTRACT_zh.md new file mode 100644 index 00000000..827a6046 --- /dev/null +++ b/designs/outerCube/PTOISA/TEXTRACT_zh.md @@ -0,0 +1,138 @@ +# TEXTRACT + +## 指令示意图 + +![TEXTRACT tile operation](../figures/isa/TEXTRACT.svg) + +## 简介 + +从源 Tile 中提取子 Tile。 + +## 数学语义 + +概念上从 `src` 中复制从 `(indexRow, indexCol)` 开始的窗口到 `dst`。确切的映射取决于布局。 + +设 `R = dst.GetValidRow()` 和 `C = dst.GetValidCol()`。对于 `0 <= i < R` 和 `0 <= j < C`: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{\mathrm{indexRow}+i,\; \mathrm{indexCol}+j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = textract %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.textract %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.textract ins(%src, %idxrow, %idxcol : !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TEXTRACT(DstTileData &dst, SrcTileData &src, uint16_t indexRow = 0, uint16_t indexCol = 0, WaitEvents &... events); + +template +PTO_INST RecordEvent TEXTRACT(DstTileData &dst, SrcTileData &src, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +template +PTO_INST RecordEvent TEXTRACT(DstTileData &dst, SrcTileData &src, uint64_t preQuantScalar, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +template +PTO_INST RecordEvent TEXTRACT_FP(DstTileData &dst, SrcTileData &src, FpTileData &fp, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `DstTileData::DType` 必须等于 `SrcTileData::DType` 且必须是以下之一:`int8_t`、`half`、`bfloat16_t`、`float`。 + - 源分形必须满足:`(SFractal == ColMajor && isRowMajor)` 或 `(SFractal == RowMajor && !isRowMajor)`,GEMV场景中,目标为Left时,源分形满足`(SrcTileData::Rows == 1 && SrcTileData::isRowMajor)` + - 运行时边界检查: + - `indexRow + DstTileData::Rows <= SrcTileData::Rows` + - `indexCol + DstTileData::Cols <= SrcTileData::Cols` + - 目标必须是 `TileType::Left` 或 `TileType::Right`,具有目标支持的分形配置。 +- **实现检查 (A5)**: + - `DstTileData::DType` 必须等于 `SrcTileData::DType` 且必须是以下之一:`int8_t`、`hifloat8_t`、`float8_e5m2_t`、`float8_e4m3_t`、`half`、`bfloat16_t`、`float`、`float4_e2m1x2_t`、`float4_e1m2x2_t`、`float8_e8m0_t`。 + - 源分形必须满足:对于 Left/Right 为 `(SFractal == ColMajor && isRowMajor)` 或 `(SFractal == RowMajor && !isRowMajor)`,GEMV场景中,目标为Left时,源分形满足`(SrcTileData::Rows == 1 && SrcTileData::isRowMajor)`;对于 ScaleLeft 为 `(SFractal == RowMajor && isRowMajor)`,对于 ScaleRight 为 `(SFractal == ColMajor && !isRowMajor)`。 + - 目标支持 `Mat -> Left/Right/Scale`、`Acc -> Mat`(含 relu / 标量量化 / 向量量化形式),也支持特定 tile 位置的 `Vec -> Mat` 提取路径。 + - 向量量化形式额外要求提供 `FpTileData` 缩放操作数,对应上文展示的 `TEXTRACT_FP(...)` 接口。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = TileLeft; + SrcT src; + DstT dst; + TEXTRACT(dst, src, /*indexRow=*/0, /*indexCol=*/0); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = TileLeft; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TEXTRACT(dst, src, /*indexRow=*/0, /*indexCol=*/0); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.textract %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.textract %src, %idxrow, %idxcol : (!pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = textract %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.textract ins(%src, %idxrow, %idxcol : !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TFILLPAD.md b/designs/outerCube/PTOISA/TFILLPAD.md new file mode 100644 index 00000000..b670bb02 --- /dev/null +++ b/designs/outerCube/PTOISA/TFILLPAD.md @@ -0,0 +1,133 @@ +# TFILLPAD + + +## Tile Operation Diagram + +![TFILLPAD tile operation](../figures/isa/TFILLPAD.svg) + +## Introduction + +Copy a source tile into a destination tile and fill the remaining (padded) elements with a compile-time pad value +selected by `TileDataDst::PadVal` (e.g., `PadValue::Min`/`PadValue::Max`). + +This is commonly used to materialize deterministic values outside the runtime valid region so that subsequent ops can +operate on a full static tile shape. + +## Math Interpretation + +Let `VR = src.GetValidRow()` and `VC = src.GetValidCol()`. For each destination element `(i, j)`: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\mathrm{src}_{i,j} & \text{if } i < VR \text{ and } j < VC \\ +\mathrm{pad} & \text{otherwise} +\end{cases} +$$ + +`pad` is determined by `TileDataDst::PadVal` and the element type (e.g., `+inf/-inf` for floating types when available, +otherwise `std::numeric_limits::max()/min()`). + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form (conceptual): + +```text +%dst = tfillpad %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tfillpad %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tfillpad ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tfillpad %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tfillpad ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Implemented in the backend headers pulled in by `include/pto/common/pto_instr_impl.hpp`: + +```cpp +template +PTO_INST RecordEvent TFILLPAD(TileData &dst, TileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TFILLPAD(DstTileData &dst, SrcTileData &src, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::PadVal != PadValue::Null`. +- `sizeof(TileDataDst::DType) == sizeof(TileDataSrc::DType)` and element size must be `1`, `2`, or `4` bytes. +- `TFILLPAD`: `TileDataDst::Rows/Cols` must match `TileDataSrc::Rows/Cols`. +- `TFILLPAD_EXPAND`: `TileDataDst::Rows >= TileDataSrc::Rows` and `TileDataDst::Cols >= TileDataSrc::Cols`. +- `TFILLPAD(TileData &dst, TileData &src)`:`if TileData::TileType is Mat, layout only support (!TileData::isRowMajor && TileData::Slayout::RowMajor), and PadVal only support PadValue::Zero` + +## Examples + +```cpp +#include + +using namespace pto; + +void example1() { + using SrcT = Tile; + using DstT = Tile; + + SrcT src; + DstT dst; + TFILLPAD(dst, src); +} + +void example2() { + using TileMatData = Tile; + + TileMatData matTile; + TFILLPAD(matTile, matTile); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tfillpad %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tfillpad %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.tfillpad %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tfillpad ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TFILLPAD_EXPAND.md b/designs/outerCube/PTOISA/TFILLPAD_EXPAND.md new file mode 100644 index 00000000..e8d59a45 --- /dev/null +++ b/designs/outerCube/PTOISA/TFILLPAD_EXPAND.md @@ -0,0 +1,89 @@ +# TFILLPAD_EXPAND + + +## Tile Operation Diagram + +![TFILLPAD_EXPAND tile operation](../figures/isa/TFILLPAD_EXPAND.svg) + +## Introduction + +Expand fill/pad variant of TFILLPAD (allows dst to be larger than src; implementation-defined). + +## See also + +- TFILLPAD overview and constraints: `docs/isa/TFILLPAD.md`. + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TFILLPAD_EXPAND(DstTileData &dst, SrcTileData &src, WaitEvents &... events); +``` + +## Math Interpretation + +Unless otherwise specified, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.tfillpad_expand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tfillpad_expand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tfillpad_expand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tfillpad_expand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## Constraints + +Type/layout/location/shape legality is backend-dependent; treat implementation-specific notes as normative for that backend. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tfillpad_expand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tfillpad_expand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.tfillpad_expand %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tfillpad_expand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TFILLPAD_EXPAND_zh.md b/designs/outerCube/PTOISA/TFILLPAD_EXPAND_zh.md new file mode 100644 index 00000000..1711c218 --- /dev/null +++ b/designs/outerCube/PTOISA/TFILLPAD_EXPAND_zh.md @@ -0,0 +1,58 @@ +# TFILLPAD_EXPAND + +## 指令示意图 + +![TFILLPAD_EXPAND tile operation](../figures/isa/TFILLPAD_EXPAND.svg) + +## 简介 + +填充/填充时允许目标大于源。 + +## 数学语义 + +除非另有说明, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.tfillpad_expand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tfillpad_expand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tfillpad_expand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tfillpad_expand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TFILLPAD_EXPAND(DstTileData &dst, SrcTileData &src, WaitEvents &... events); +``` + +## 约束 + +Type/layout/location/shape legality is backend-dependent; treat implementation-specific notes as normative for that backend. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TFILLPAD_INPLACE.md b/designs/outerCube/PTOISA/TFILLPAD_INPLACE.md new file mode 100644 index 00000000..29134773 --- /dev/null +++ b/designs/outerCube/PTOISA/TFILLPAD_INPLACE.md @@ -0,0 +1,89 @@ +# TFILLPAD_INPLACE + + +## Tile Operation Diagram + +![TFILLPAD_INPLACE tile operation](../figures/isa/TFILLPAD_INPLACE.svg) + +## Introduction + +In-place fill/pad variant of TFILLPAD (implementation-defined). + +## See also + +- TFILLPAD overview and constraints: `docs/isa/TFILLPAD.md`. + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TFILLPAD_INPLACE(DstTileData &dst, SrcTileData &src, WaitEvents &... events); +``` + +## Math Interpretation + +Unless otherwise specified, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.tfillpad_inplace %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tfillpad_inplace ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tfillpad_inplace %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tfillpad_inplace ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## Constraints + +Type/layout/location/shape legality is backend-dependent; treat implementation-specific notes as normative for that backend. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tfillpad_inplace %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tfillpad_inplace %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.tfillpad_inplace %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tfillpad_inplace ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TFILLPAD_INPLACE_zh.md b/designs/outerCube/PTOISA/TFILLPAD_INPLACE_zh.md new file mode 100644 index 00000000..9900b46f --- /dev/null +++ b/designs/outerCube/PTOISA/TFILLPAD_INPLACE_zh.md @@ -0,0 +1,58 @@ +# TFILLPAD_INPLACE + +## 指令示意图 + +![TFILLPAD_INPLACE tile operation](../figures/isa/TFILLPAD_INPLACE.svg) + +## 简介 + +原地填充/填充变体。 + +## 数学语义 + +除非另有说明, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.tfillpad_inplace %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tfillpad_inplace ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tfillpad_inplace %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tfillpad_inplace ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TFILLPAD_INPLACE(DstTileData &dst, SrcTileData &src, WaitEvents &... events); +``` + +## 约束 + +Type/layout/location/shape legality is backend-dependent; treat implementation-specific notes as normative for that backend. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TFILLPAD_zh.md b/designs/outerCube/PTOISA/TFILLPAD_zh.md new file mode 100644 index 00000000..d6975b6f --- /dev/null +++ b/designs/outerCube/PTOISA/TFILLPAD_zh.md @@ -0,0 +1,108 @@ +# TFILLPAD + +## 指令示意图 + +![TFILLPAD tile operation](../figures/isa/TFILLPAD.svg) + +## 简介 + +复制 Tile 并在有效区域外使用编译时填充值进行填充。 + +Copy a source tile into a destination tile and fill the remaining (padded) elements with a compile-time pad value +selected by `TileDataDst::PadVal` (e.g., `PadValue::Min`/`PadValue::Max`). + +This is commonly used to materialize deterministic values outside the runtime valid region so that subsequent ops can +operate on a full static tile shape. + +## 数学语义 + +Let `VR = src.GetValidRow()` and `VC = src.GetValidCol()`. 对每个 destination element `(i, j)`: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\mathrm{src}_{i,j} & \text{if } i < VR \text{ and } j < VC \\ +\mathrm{pad} & \text{otherwise} +\end{cases} +$$ + +`pad` is determined by `TileDataDst::PadVal` and the element type (e.g., `+inf/-inf` for floating types when available, +otherwise `std::numeric_limits::max()/min()`). + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form (conceptual): + +```text +%dst = tfillpad %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tfillpad %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tfillpad ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tfillpad %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tfillpad ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +Implemented in the backend headers pulled in by `include/pto/common/pto_instr_impl.hpp`: + +```cpp +template +PTO_INST RecordEvent TFILLPAD(TileData &dst, TileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TFILLPAD(DstTileData &dst, SrcTileData &src, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::PadVal != PadValue::Null`. +- `sizeof(TileDataDst::DType) == sizeof(TileDataSrc::DType)` and element size must be `1`, `2`, or `4` bytes. +- `TFILLPAD`: `TileDataDst::Rows/Cols` must match `TileDataSrc::Rows/Cols`. +- `TFILLPAD_EXPAND`: `TileDataDst::Rows >= TileDataSrc::Rows` and `TileDataDst::Cols >= TileDataSrc::Cols`. +- `TFILLPAD(TileData &dst, TileData &src)`:`if TileData::TileType is Mat, layout only support (!TileData::isRowMajor && TileData::Slayout::RowMajor), and PadVal only support PadValue::Zero` + +## 示例 + +```cpp +#include + +using namespace pto; + +void example1() { + using SrcT = Tile; + using DstT = Tile; + + SrcT src; + DstT dst; + TFILLPAD(dst, src); +} + +void example2() { + using TileMatData = Tile; + + TileMatData matTile; + TFILLPAD(matTile, matTile); +} +``` diff --git a/designs/outerCube/PTOISA/TFMOD.md b/designs/outerCube/PTOISA/TFMOD.md new file mode 100644 index 00000000..bf647996 --- /dev/null +++ b/designs/outerCube/PTOISA/TFMOD.md @@ -0,0 +1,104 @@ +# TFMOD + + +## Tile Operation Diagram + +![TFMOD tile operation](../figures/isa/TFMOD.svg) + +## Introduction + +Elementwise floor of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$\mathrm{dst}_{i,j} = \mathrm{fmod}(\mathrm{src0}_{i,j}, \mathrm{src1}_{i,j})$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tfmod %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tfmod %src0, %src1 : !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tfmod ins(%src0, %src1 : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tfmod %src0, %src1 : !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tfmod ins(%src0, %src1 : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TFMOD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. +- Division-by-zero behavior is target-defined; the CPU simulator asserts in debug builds. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT out, a, b; + TFMOD(out, a, b); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tfmod %src0, %src1 : !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tfmod %src0, %src1 : !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tfmod %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tfmod ins(%src0, %src1 : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TFMODS.md b/designs/outerCube/PTOISA/TFMODS.md new file mode 100644 index 00000000..41034bb0 --- /dev/null +++ b/designs/outerCube/PTOISA/TFMODS.md @@ -0,0 +1,107 @@ +# TFMODS + + +## Tile Operation Diagram + +![TFMODS tile operation](../figures/isa/TFMODS.svg) + +## Introduction + +Elementwise floor with a scalar: `fmod(src, scalar)`. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$\mathrm{dst}_{i,j} = \mathrm{fmod}(\mathrm{src}_{i,j}, \mathrm{scalar})$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tfmods %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tfmods %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 2 (DPS) + +```text +pto.tfmods ins(%src, %scalar : !pto.tile_buf<...>, f32) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TFMODS(TileDataDst &dst, TileDataSrc &src, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `dst` and `src` must use the same element type. + - Supported element types are `float` and `float32_t`. + - `dst` and `src` must be vector tiles. + - `dst` and `src` must be row-major. + - Runtime: `dst.GetValidRow() == src.GetValidRow() > 0` and `dst.GetValidCol() == src.GetValidCol() > 0`. +- **Implementation checks (A5)**: + - `dst` and `src` must use the same element type. + - Supported element types are 2-byte or 4-byte types supported by the target implementation (including `half` and `float`). + - `dst` and `src` must be vector tiles. + - Static valid bounds must satisfy `ValidRow <= Rows` and `ValidCol <= Cols` for both tiles. + - Runtime: `dst.GetValidRow() == src.GetValidRow()` and `dst.GetValidCol() == src.GetValidCol()`. +- **Division-by-zero**: + - Behavior is target-defined; the CPU simulator asserts in debug builds. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TFMODS(out, x, 3.0f); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tfmods %src, %scalar : !pto.tile<...>, f32 +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tfmods %src, %scalar : !pto.tile<...>, f32 +``` + +### PTO Assembly Form + +```text +%dst = tfmods %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tfmods ins(%src, %scalar : !pto.tile_buf<...>, f32) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TFMODS_zh.md b/designs/outerCube/PTOISA/TFMODS_zh.md new file mode 100644 index 00000000..4726099f --- /dev/null +++ b/designs/outerCube/PTOISA/TFMODS_zh.md @@ -0,0 +1,107 @@ +# TFMODS + +## 指令示意图 + +![TFMODS tile operation](../figures/isa/TFMODS.svg) + +## 简介 + +与标量的逐元素余数:`fmod(src, scalar)`。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$\mathrm{dst}_{i,j} = \mathrm{fmod}(\mathrm{src}_{i,j}, \mathrm{scalar})$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tfmods %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tfmods %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 2(DPS) + +```text +pto.tfmods ins(%src, %scalar : !pto.tile_buf<...>, f32) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TFMODS(TileDataDst &dst, TileDataSrc &src, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `dst` 和 `src` 必须使用相同的元素类型。 + - 支持的元素类型为 `float` 和 `float32_t`。 + - `dst` 和 `src` 必须是向量 Tile。 + - `dst` 和 `src` 必须是行主序。 + - 运行时:`dst.GetValidRow() == src.GetValidRow() > 0` 且 `dst.GetValidCol() == src.GetValidCol() > 0`。 +- **实现检查 (A5)**: + - `dst` 和 `src` 必须使用相同的元素类型。 + - 支持的元素类型为目标实现支持的 2 字节或 4 字节类型(包括 `half` 和 `float`)。 + - `dst` 和 `src` 必须是向量 Tile。 + - 两个 Tile 的静态有效边界都必须满足 `ValidRow <= Rows` 且 `ValidCol <= Cols`。 + - 运行时:`dst.GetValidRow() == src.GetValidRow()` 且 `dst.GetValidCol() == src.GetValidCol()`。 +- **除零**: + - 行为由目标定义;CPU 模拟器在调试构建中会断言。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TFMODS(out, x, 3.0f); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tfmods %src, %scalar : !pto.tile<...>, f32 +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tfmods %src, %scalar : !pto.tile<...>, f32 +``` + +### PTO 汇编形式 + +```text +%dst = tfmods %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tfmods ins(%src, %scalar : !pto.tile_buf<...>, f32) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TFMOD_zh.md b/designs/outerCube/PTOISA/TFMOD_zh.md new file mode 100644 index 00000000..1dfe85fa --- /dev/null +++ b/designs/outerCube/PTOISA/TFMOD_zh.md @@ -0,0 +1,77 @@ +# TFMOD + +## 指令示意图 + +![TFMOD tile operation](../figures/isa/TFMOD.svg) + +## 简介 + +两个 Tile 的逐元素余数,余数符号与被除数相同。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$\mathrm{dst}_{i,j} = \mathrm{fmod}(\mathrm{src0}_{i,j}, \mathrm{src1}_{i,j})$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tfmod %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tfmod %src0, %src1 : !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tfmod ins(%src0, %src1 : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tfmod %src0, %src1 : !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tfmod ins(%src0, %src1 : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TFMOD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. +- Division-by-zero behavior is target-defined; the CPU simulator asserts in debug builds. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT out, a, b; + TFMOD(out, a, b); +} +``` diff --git a/designs/outerCube/PTOISA/TFREE.md b/designs/outerCube/PTOISA/TFREE.md new file mode 100644 index 00000000..f13f9709 --- /dev/null +++ b/designs/outerCube/PTOISA/TFREE.md @@ -0,0 +1,40 @@ +# TFREE + +## Tile Operation Diagram + +![TFREE tile operation](../figures/isa/TFREE.svg) + +## Introduction + +Release the currently held pipe or FIFO slot back to the producer. + +## Math Interpretation + +Semantics are instruction-specific. Unless stated otherwise, behavior is defined over the destination valid region. + +## Assembly Syntax + +PTO-AS form: see `docs/assembly/PTO-AS.md`. + +### IR Level 1 (SSA) + +```text +%dst = pto.tfree ... +``` + +### IR Level 2 (DPS) + +```text +pto.tfree ins(...) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`. + +## Constraints + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## Examples + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TFREE_zh.md b/designs/outerCube/PTOISA/TFREE_zh.md new file mode 100644 index 00000000..3e7be0d2 --- /dev/null +++ b/designs/outerCube/PTOISA/TFREE_zh.md @@ -0,0 +1,41 @@ +# TFREE + +## 指令示意图 + +![TFREE tile operation](../figures/isa/TFREE.svg) + +## 简介 + +将当前占用的 pipe 或 FIFO 槽位释放回生产者。 + +## 数学语义 + +语义随指令而变化。 Unless stated otherwise, behavior is defined over the destination valid region. + +## 汇编语法 + +PTO-AS 形式:参见 `docs/assembly/PTO-AS.md`. + +### AS Level 1(SSA) + +```text +%dst = pto.tfree ... +``` + +### AS Level 2(DPS) + +```text +pto.tfree ins(...) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`. + +## 约束 + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## 示例 + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TGATHER.md b/designs/outerCube/PTOISA/TGATHER.md new file mode 100644 index 00000000..911dfba7 --- /dev/null +++ b/designs/outerCube/PTOISA/TGATHER.md @@ -0,0 +1,156 @@ +# TGATHER + + +## Tile Operation Diagram + +![TGATHER tile operation](../figures/isa/TGATHER.svg) + +## Introduction + +Gather/select elements using either an index tile or a compile-time mask pattern. + +## Math Interpretation + +Index-based gather (conceptual): + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. For `0 <= i < R` and `0 <= j < C`: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}\!\left[\mathrm{indices}_{i,j}\right] $$ + +Exact index interpretation and bounds behavior are implementation-defined. + +Mask-pattern gather is an implementation-defined selection/reduction controlled by `pto::MaskPattern`. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Index-based gather: + +```text +%dst = tgather %src0, %indices : !pto.tile<...> -> !pto.tile<...> +``` + +Mask-pattern gather: + +```text +%dst = tgather %src {maskPattern = #pto.mask_pattern} : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tgather %src, %indices : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%dst = pto.tgather %src {maskPattern = #pto.mask_pattern}: !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tgather ins(%src, %indices : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +pto.tgather ins(%src, {maskPattern = #pto.mask_pattern} : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGATHER(TileDataD &dst, TileDataS0 &src0, TileDataS1 &src1, TileDataTmp &tmp, WaitEvents &... events); + +template +PTO_INST RecordEvent TGATHER(DstTileData &dst, SrcTileData &src, WaitEvents &... events); +``` + +## Constraints + +- **Index-based gather: implementation checks (A2A3)**: + - `sizeof(DstTileData::DType)` must be must be `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `half`, `float`. + - `sizeof(Src1TileData::DType)` must be must be `int32_t`, `uint32_t`. + - `DstTileData::DType` must be the same type as `Src0TileData::DType`. + - `src1.GetValidCol() == Src1TileData::Cols` and `dst.GetValidCol() == DstTileData::Cols`. +- **Index-based gather: implementation checks (A5)**: + - `sizeof(DstTileData::DType)` must be must be `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `half`, `float`. + - `sizeof(Src1TileData::DType)` must be must be `int16_t`, `uint16_t`, `int32_t`, `uint32_t`. + - `DstTileData::DType` must be the same type as `Src0TileData::DType`. + - `src1.GetValidCol() == Src1TileData::Cols` and `dst.GetValidCol() == DstTileData::Cols`. +- **Mask-pattern gather: implementation checks (A2A3)**: + - Source element size must be `2` or `4` bytes. + - `SrcTileData::DType`/`DstTileData::DType` must be `int16_t` or `uint16_t` or `int32_t` or `uint32_t` + or `half` or `bfloat16_t` or `float`. + - `dst` and `src` must both be `TileType::Vec` and row-major. + - `sizeof(dst element) == sizeof(src element)` and `dst.GetValidCol() == DstTileData::Cols` (continuous dst storage). +- **Mask-pattern gather: implementation checks (A5)**: + - Source element size must be `1` or `2` or `4` bytes. + - `dst` and `src` must both be `TileType::Vec` and row-major. + - `SrcTileData::DType`/`DstTileData::DType` must be `int8_t` or `uint8_t` or `int16_t` or `uint16_t` or `int32_t` or `uint32_t` + or `half` or `bfloat16_t` or `float` or `float8_e4m3_t`or `float8_e5m2_t` or `hifloat8_t`. + - Supported dtypes are restricted to a target-defined set (checked via `static_assert` in the implementation), and `sizeof(dst element) == sizeof(src element)`, `dst.GetValidCol() == DstTileData::Cols` (continuous dst storage). +- **Bounds / validity**: + - Index bounds are not validated by explicit runtime assertions; out-of-range indices are target-defined. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using IdxT = Tile; + using DstT = Tile; + SrcT src0; + IdxT idx; + DstT dst; + TGATHER(dst, src0, idx); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TGATHER(dst, src); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tgather %src, %indices : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tgather %src, %indices : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.tgather %src, %indices : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tgather ins(%src, %indices : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TGATHERB.md b/designs/outerCube/PTOISA/TGATHERB.md new file mode 100644 index 00000000..3ee1b4ef --- /dev/null +++ b/designs/outerCube/PTOISA/TGATHERB.md @@ -0,0 +1,141 @@ +# TGATHERB + + +## Tile Operation Diagram + +![TGATHERB tile operation](../figures/isa/TGATHERB.svg) + +## Introduction + +Gather elements using byte offsets. + +## Math Interpretation + +For each element in the valid region: + +$$ \mathrm{dst}_{i,j} = *\left(\mathrm{srcBase} + \mathrm{offset}_{i,j}\right) $$ + +Exact bounds behavior is implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tgatherb %src, %offsets : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tgatherb %src, %offsets : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tgatherb ins(%src, %offsets : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tgatherb %src, %offsets : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tgatherb ins(%src, %offsets : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGATHERB(TileDataDst &dst, TileDataSrc &src, TileDataOffset &offset, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Destination layout must be row-major (`TileDataDst::isRowMajor`). + - Destination element size must be `1`, `2`, or `4` bytes (enforced via `static_assert` in the helper). + - `SrcTileData::DType`/`DstTileData::DType` must be `int8_t` or `uint8_t` or `int16_t` or `uint16_t` or `int32_t` or `uint32_t` or `half` or `bfloat16_t` or `float`. +- **Implementation checks (A5)**: + - Destination element size must be `1`, `2`, or `4` bytes. + - `SrcTileData::DType`/`DstTileData::DType` must be `int8_t` or `uint8_t` or `int16_t` or `uint16_t` or `int32_t` or `uint32_t` or `half` or `bfloat16_t` or `float`. +- **Offset interpretation**: + - Offsets are interpreted as `uint32_t` values (byte offsets) by the implementation. + - Offset bounds are not validated by explicit runtime assertions; out-of-range offsets are target-defined. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using OffT = Tile; + using DstT = Tile; + SrcT src; + OffT off; + DstT dst; + TGATHERB(dst, src, off); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using OffT = Tile; + using DstT = Tile; + SrcT src; + OffT off; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(off, 0x2000); + TASSIGN(dst, 0x3000); + TGATHERB(dst, src, off); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tgatherb %src, %offsets : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tgatherb %src, %offsets : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tgatherb %src, %offsets : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tgatherb ins(%src, %offsets : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TGATHERB_zh.md b/designs/outerCube/PTOISA/TGATHERB_zh.md new file mode 100644 index 00000000..64652ef2 --- /dev/null +++ b/designs/outerCube/PTOISA/TGATHERB_zh.md @@ -0,0 +1,114 @@ +# TGATHERB + +## 指令示意图 + +![TGATHERB tile operation](../figures/isa/TGATHERB.svg) + +## 简介 + +使用字节偏移量收集元素。 + +## 数学语义 + +对每个元素 在有效区域内: + +$$ \mathrm{dst}_{i,j} = *\left(\mathrm{srcBase} + \mathrm{offset}_{i,j}\right) $$ + +Exact bounds behavior is implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tgatherb %src, %offsets : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tgatherb %src, %offsets : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tgatherb ins(%src, %offsets : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tgatherb %src, %offsets : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tgatherb ins(%src, %offsets : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGATHERB(TileDataDst &dst, TileDataSrc &src, TileDataOffset &offset, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - Destination layout must be row-major (`TileDataDst::isRowMajor`). + - Destination element size must be `1`, `2`, or `4` bytes (enforced via `static_assert` in the helper). + - `SrcTileData::DType`/`DstTileData::DType` must be `int8_t` or `uint8_t` or `int16_t` or `uint16_t` or `int32_t` or `uint32_t` or `half` or `bfloat16_t` or `float`. +- **实现检查 (A5)**: + - Destination element size must be `1`, `2`, or `4` bytes. + - `SrcTileData::DType`/`DstTileData::DType` must be `int8_t` or `uint8_t` or `int16_t` or `uint16_t` or `int32_t` or `uint32_t` or `half` or `bfloat16_t` or `float`. +- **Offset interpretation**: + - Offsets are interpreted as `uint32_t` values (byte offsets) by the implementation. + - Offset bounds are not validated by explicit runtime assertions; out-of-range offsets are target-defined. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using OffT = Tile; + using DstT = Tile; + SrcT src; + OffT off; + DstT dst; + TGATHERB(dst, src, off); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using OffT = Tile; + using DstT = Tile; + SrcT src; + OffT off; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(off, 0x2000); + TASSIGN(dst, 0x3000); + TGATHERB(dst, src, off); +} +``` diff --git a/designs/outerCube/PTOISA/TGATHER_zh.md b/designs/outerCube/PTOISA/TGATHER_zh.md new file mode 100644 index 00000000..731801d2 --- /dev/null +++ b/designs/outerCube/PTOISA/TGATHER_zh.md @@ -0,0 +1,154 @@ +# TGATHER + +## 指令示意图 + +![TGATHER tile operation](../figures/isa/TGATHER.svg) + +## 简介 + +使用索引 Tile 或编译时掩码模式来收集/选择元素。 + +## 数学语义 + +基于索引的 gather(概念性定义): + +设 `R = dst.GetValidRow()`,`C = dst.GetValidCol()`。对于 `0 <= i < R` 且 `0 <= j < C`: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}\!\left[\mathrm{indices}_{i,j}\right] $$ + +确切的索引解释和边界行为由实现定义。 + +基于掩码模式的 gather 是由 `pto::MaskPattern` 控制的实现定义的选择/归约操作。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +基于索引的 gather: + +```text +%dst = tgather %src0, %indices : !pto.tile<...> -> !pto.tile<...> +``` + +基于掩码模式的 gather: + +```text +%dst = tgather %src {maskPattern = #pto.mask_pattern} : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tgather %src, %indices : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%dst = pto.tgather %src {maskPattern = #pto.mask_pattern}: !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tgather ins(%src, %indices : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +pto.tgather ins(%src, {maskPattern = #pto.mask_pattern} : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGATHER(TileDataD &dst, TileDataS0 &src0, TileDataS1 &src1, TileDataTmp &tmp, WaitEvents &... events); + +template +PTO_INST RecordEvent TGATHER(DstTileData &dst, SrcTileData &src, WaitEvents &... events); +``` + +## 约束 + +- **基于索引的 gather:实现检查 (A2A3)**: + - `sizeof(DstTileData::DType)` 对应类型必须是 `int16_t`、`uint16_t`、`int32_t`、`uint32_t`、`half`、`float` 之一。 + - `sizeof(Src1TileData::DType)` 对应类型必须是 `int32_t`、`uint32_t` 之一。 + - `DstTileData::DType` 必须与 `Src0TileData::DType` 类型相同。 + - `src1.GetValidCol() == Src1TileData::Cols` 且 `dst.GetValidCol() == DstTileData::Cols`。 +- **基于索引的 gather:实现检查 (A5)**: + - `sizeof(DstTileData::DType)` 对应类型必须是 `int16_t`、`uint16_t`、`int32_t`、`uint32_t`、`half`、`float` 之一。 + - `sizeof(Src1TileData::DType)` 对应类型必须是 `int16_t`、`uint16_t`、`int32_t`、`uint32_t` 之一。 + - `DstTileData::DType` 必须与 `Src0TileData::DType` 类型相同。 + - `src1.GetValidCol() == Src1TileData::Cols` 且 `dst.GetValidCol() == DstTileData::Cols`。 +- **基于掩码模式的 gather:实现检查 (A2A3)**: + - 源元素大小必须是 `2` 或 `4` 字节。 + - `SrcTileData::DType`/`DstTileData::DType` 必须是 `int16_t`、`uint16_t`、`int32_t`、`uint32_t`、`half`、`bfloat16_t` 或 `float` 之一。 + - `dst` 和 `src` 必须都是 `TileType::Vec` 且行主序。 + - `sizeof(dst element) == sizeof(src element)` 且 `dst.GetValidCol() == DstTileData::Cols`(连续的目标存储)。 +- **基于掩码模式的 gather:实现检查 (A5)**: + - 源元素大小必须是 `1`、`2` 或 `4` 字节。 + - `dst` 和 `src` 必须都是 `TileType::Vec` 且行主序。 + - `SrcTileData::DType`/`DstTileData::DType` 必须是 `int8_t`、`uint8_t`、`int16_t`、`uint16_t`、`int32_t`、`uint32_t`、`half`、`bfloat16_t`、`float`、`float8_e4m3_t`、`float8_e5m2_t` 或 `hifloat8_t` 之一。 + - 支持的数据类型限制为目标定义的集合(通过实现中的 `static_assert` 强制执行),且 `sizeof(dst element) == sizeof(src element)`,`dst.GetValidCol() == DstTileData::Cols`(连续的目标存储)。 +- **边界 / 有效性**: + - 索引边界不通过显式运行时断言进行验证;超出范围的索引行为由目标定义。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using IdxT = Tile; + using DstT = Tile; + SrcT src0; + IdxT idx; + DstT dst; + TGATHER(dst, src0, idx); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TGATHER(dst, src); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tgather %src, %indices : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tgather %src, %indices : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = pto.tgather %src, %indices : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tgather ins(%src, %indices : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TGEMV.md b/designs/outerCube/PTOISA/TGEMV.md new file mode 100644 index 00000000..1a8253ee --- /dev/null +++ b/designs/outerCube/PTOISA/TGEMV.md @@ -0,0 +1,282 @@ +# TGEMV + + +## Tile Operation Diagram + +![TGEMV tile operation](../figures/isa/TGEMV.svg) + +## Introduction + +General Matrix-Vector multiplication (GEMV) producing an accumulator/output tile. + +## Math Interpretation + +Let: + +- `M = 1` +- `K = bMatrix.GetValidRow()` +- `N = bMatrix.GetValidCol()` + +### 1. TGEMV (Tile-based GEMV) + +For `0 <= j < N` (output elements in the effective matmul domain): + +$$ \mathrm{C}_{0,j} = \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} $$ + +### 2. TGEMV_ACC (Tile-based GEMV with Accumulation) + +For `0 <= j < N` (accumulates into existing tile): + +$$ \mathrm{C}_{0,j} \gets \mathrm{C}_{0,j} + \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} $$ + +### 3. TGEMV_BIAS (Tile-based GEMV with Bias) + +For `0 <= j < N` (adds bias term to matrix product): + +$$ \mathrm{C}_{0,j} = \mathrm{Bias}_{0,j} + \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} $$ + +**Note:** Exact accumulator behavior and datatype promotion are target/implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%acc = tgemv %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> + +%acc1 = tgemv.acc %acc0, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> + +%acc = tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%c = pto.tgemv %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c_out = pto.tgemv.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c = pto.tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tgemv ins(%a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +pto.tgemv.acc ins(%c_in, %a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +pto.tgemv.bias ins(%a, %b, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGEMV(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents&... events); + +template +PTO_INST RecordEvent TGEMV_ACC(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents&... events); + +template +PTO_INST RecordEvent TGEMV_BIAS(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, TileBias &biasData, WaitEvents&... events); +``` + +## Constraints + +### Common shape and location constraints + +These constraints apply to `TGEMV`, `TGEMV_ACC`, and `TGEMV_BIAS` unless otherwise noted. + +- Static shape constraints: + - `TileLeft::Rows == TileRes::Rows` + - `TileLeft::Cols == TileRight::Rows` + - `TileRight::Cols == TileRes::Cols` +- Tile locations: + - `TileLeft::Loc == Left` + - `TileRight::Loc == Right` + - `TileRes::Loc == Acc` +- Runtime valid-size constraints: + - `m` must be `1` + - `k` and `n` (taken from `bMatrix.GetValidRow()` and `bMatrix.GetValidCol()`) must be in `[1, 4095]` + +### TGEMV / TGEMV_ACC datatype constraints + +- **Implementation checks (A2A3)**: + - Supported `(CType, AType, BType)` triples: + - `(int32_t, int8_t, int8_t)` + - `(float, half, half)` + - `(float, float, float)` + - `(float, bfloat16_t, bfloat16_t)` +- **Implementation checks (A5)**: + - Accumulator type must be `int32_t` or `float`. + - If `int32_t`: `AType == int8_t` and `BType == int8_t`. + - If `float`: supports `half`, `bfloat16_t`, `float`, and selected fp8 pairs (target-defined). + - Fractal/layout constraints are enforced: + - Left: `Loc == Left`, `!isRowMajor`, `SFractal == RowMajor` + - Right: `Loc == Right`, `isRowMajor`, `SFractal == ColMajor` + - Acc: `Loc == Acc`, `!isRowMajor`, `SFractal == RowMajor` + +### TGEMV_BIAS additional constraints + +- Bias tile datatype must exactly match `TileRes::DType`. +- Bias tile must be configured as a single row. +- Bias tile location must be `TileType::Bias`. +- **Additional A5 note**: + - No separate explicit `m/k/n` runtime assertions are enforced in the underlying A5 matmul implementation beyond the GEMV contract described above. + +## Examples + +### Auto + +#### 1. TGEMV + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c; + TGEMV(c, a, b); +} +``` + +#### 2. TGEMV_ACC + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TGEMV_ACC(c1, c0, a, b); +} +``` + +#### 3. TGEMV_BIAS + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TGEMV_BIAS(c, a, b, bias); +} +``` + +### Manual + +#### 1. TGEMV + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c, 0x3000); + TGEMV(c, a, b); +} +``` + +#### 2. TGEMV_ACC + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c0, 0x3000); + TASSIGN(c1, 0x4000); + TGEMV_ACC(c1, c0, a, b); +} +``` + +#### 3. TGEMV_BIAS + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(bias, 0x3000); + TASSIGN(c, 0x4000); + TGEMV_BIAS(c, a, b, bias); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%c = pto.tgemv %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%c = pto.tgemv %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%acc = tgemv %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tgemv ins(%a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TGEMV_ACC.md b/designs/outerCube/PTOISA/TGEMV_ACC.md new file mode 100644 index 00000000..7bad9382 --- /dev/null +++ b/designs/outerCube/PTOISA/TGEMV_ACC.md @@ -0,0 +1,166 @@ +# TGEMV_ACC + + +## Tile Operation Diagram + +![TGEMV_ACC tile operation](../figures/isa/TGEMV_ACC.svg) + +## Introduction + +Tile-based GEMV with explicit accumulator input tile (`cInMatrix`) and output tile (`cOutMatrix`). + +## See also + +- Base GEMV instruction: `docs/isa/TGEMV.md`. +- Bias variant: `docs/isa/TGEMV_BIAS.md`. + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGEMV_ACC(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_ACC(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); +``` + +## Math Interpretation + +Let: + +- `M = 1` +- `K = bMatrix.GetValidRow()` +- `N = bMatrix.GetValidCol()` + +For `0 <= j < N` (accumulates into the existing output tile): + +$$ \mathrm{C}_{0,j} \gets \mathrm{C}_{0,j} + \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} $$ + +**Note:** Exact accumulator behavior and datatype promotion are target/implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%acc1 = tgemv.acc %acc0, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%c_out = pto.tgemv.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tgemv.acc ins(%c_in, %a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +``` + +## Constraints + +### Common shape and location constraints + +- Static shape constraints: + - `TileLeft::Rows == TileRes::Rows` + - `TileLeft::Cols == TileRight::Rows` + - `TileRight::Cols == TileRes::Cols` +- Tile locations: + - `TileLeft::Loc == Left` + - `TileRight::Loc == Right` + - `TileRes::Loc == Acc` +- Runtime valid-size constraints: + - `m` must be `1` + - `k` and `n` (taken from `bMatrix.GetValidRow()` and `bMatrix.GetValidCol()`) must be in `[1, 4095]` + +### Datatype constraints + +- **Implementation checks (A2A3)**: + - Supported `(CType, AType, BType)` triples: + - `(int32_t, int8_t, int8_t)` + - `(float, half, half)` + - `(float, float, float)` + - `(float, bfloat16_t, bfloat16_t)` +- **Implementation checks (A5)**: + - Accumulator type must be `int32_t` or `float`. + - If `int32_t`: `AType == int8_t` and `BType == int8_t`. + - If `float`: supports `half`, `bfloat16_t`, `float`, and selected fp8 pairs (target-defined). + - Fractal/layout constraints are enforced: + - Left: `Loc == Left`, `!isRowMajor`, `SFractal == RowMajor` + - Right: `Loc == Right`, `isRowMajor`, `SFractal == ColMajor` + - Acc: `Loc == Acc`, `!isRowMajor`, `SFractal == RowMajor` + - No separate explicit `m/k/n` runtime assertions are enforced in the underlying A5 matmul implementation beyond the GEMV contract described above. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TGEMV_ACC(c1, c0, a, b); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c0, 0x3000); + TASSIGN(c1, 0x4000); + TGEMV_ACC(c1, c0, a, b); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%c_out = pto.tgemv.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%c_out = pto.tgemv.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%acc1 = tgemv.acc %acc0, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tgemv.acc ins(%c_in, %a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TGEMV_ACC_zh.md b/designs/outerCube/PTOISA/TGEMV_ACC_zh.md new file mode 100644 index 00000000..7a79476d --- /dev/null +++ b/designs/outerCube/PTOISA/TGEMV_ACC_zh.md @@ -0,0 +1,165 @@ +# TGEMV_ACC + +## 指令示意图 + +![TGEMV_ACC tile operation](../figures/isa/TGEMV_ACC.svg) + +## 简介 + +带显式累加器输入 Tile(`cInMatrix`)和输出 Tile(`cOutMatrix`)的 GEMV。 + +## 另请参见 + +- 基础 GEMV 指令:`docs/isa/TGEMV.md`。 +- 偏置变体:`docs/isa/TGEMV_BIAS.md`。 + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGEMV_ACC(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_ACC(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); +``` + +## 数学语义 + +设: + +- `M = 1` +- `K = bMatrix.GetValidRow()` +- `N = bMatrix.GetValidCol()` + +对于 `0 <= j < N`(累加到已有输出 Tile): + +$$ \mathrm{C}_{0,j} \gets \mathrm{C}_{0,j} + \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} $$ + +**注意:** 精确的累加器行为和数据类型提升由目标/实现定义。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%acc1 = tgemv.acc %acc0, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%c_out = pto.tgemv.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tgemv.acc ins(%c_in, %a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +``` + +## 约束 + +### 通用形状与位置约束 + +- 静态形状约束: + - `TileLeft::Rows == TileRes::Rows` + - `TileLeft::Cols == TileRight::Rows` + - `TileRight::Cols == TileRes::Cols` +- Tile 位置约束: + - `TileLeft::Loc == Left` + - `TileRight::Loc == Right` + - `TileRes::Loc == Acc` +- 运行时有效尺寸约束: + - `m` 必须为 `1` + - `k` 和 `n`(取自 `bMatrix.GetValidRow()` 与 `bMatrix.GetValidCol()`)必须位于 `[1, 4095]` + +### 数据类型约束 + +- **实现检查 (A2A3)**: + - 支持的 `(CType, AType, BType)` 三元组: + - `(int32_t, int8_t, int8_t)` + - `(float, half, half)` + - `(float, float, float)` + - `(float, bfloat16_t, bfloat16_t)` +- **实现检查 (A5)**: + - 累加器类型必须是 `int32_t` 或 `float`。 + - 如果为 `int32_t`:`AType == int8_t` 且 `BType == int8_t`。 + - 如果为 `float`:支持 `half`、`bfloat16_t`、`float` 以及选定的 fp8 组合(目标定义)。 + - 会强制执行以下分形/布局约束: + - Left:`Loc == Left`、`!isRowMajor`、`SFractal == RowMajor` + - Right:`Loc == Right`、`isRowMajor`、`SFractal == ColMajor` + - Acc:`Loc == Acc`、`!isRowMajor`、`SFractal == RowMajor` + - 除上述 GEMV 约定外,底层 A5 matmul 实现不会再单独补充一组显式的 `m/k/n` 运行时断言。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TGEMV_ACC(c1, c0, a, b); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c0, 0x3000); + TASSIGN(c1, 0x4000); + TGEMV_ACC(c1, c0, a, b); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%c_out = pto.tgemv.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%c_out = pto.tgemv.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%acc1 = tgemv.acc %acc0, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tgemv.acc ins(%c_in, %a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TGEMV_BIAS.md b/designs/outerCube/PTOISA/TGEMV_BIAS.md new file mode 100644 index 00000000..08a40fcf --- /dev/null +++ b/designs/outerCube/PTOISA/TGEMV_BIAS.md @@ -0,0 +1,178 @@ +# TGEMV_BIAS + + +## Tile Operation Diagram + +![TGEMV_BIAS tile operation](../figures/isa/TGEMV_BIAS.svg) + +## Introduction + +Tile-based GEMV with bias add. + +## See also + +- Base GEMV instruction: `docs/isa/TGEMV.md`. +- Accumulation variant: `docs/isa/TGEMV_ACC.md`. + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGEMV_BIAS(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, TileBias &biasData, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_BIAS(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, TileBias &biasData, WaitEvents &... events); +``` + +## Math Interpretation + +Let: + +- `M = 1` +- `K = bMatrix.GetValidRow()` +- `N = bMatrix.GetValidCol()` + +For `0 <= j < N` (adds a bias term to the matrix product): + +$$ \mathrm{C}_{0,j} = \mathrm{Bias}_{0,j} + \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} $$ + +**Note:** Exact accumulator behavior and datatype promotion are target/implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%acc = tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%c = pto.tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tgemv.bias ins(%a, %b, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +## Constraints + +### Common shape and location constraints + +- Static shape constraints: + - `TileLeft::Rows == TileRes::Rows` + - `TileLeft::Cols == TileRight::Rows` + - `TileRight::Cols == TileRes::Cols` +- Tile locations: + - `TileLeft::Loc == Left` + - `TileRight::Loc == Right` + - `TileRes::Loc == Acc` +- Runtime valid-size constraints: + - `m` must be `1` + - `k` and `n` (taken from `bMatrix.GetValidRow()` and `bMatrix.GetValidCol()`) must be in `[1, 4095]` + +### Datatype constraints + +- **Implementation checks (A2A3)**: + - Supported `(CType, AType, BType)` triples: + - `(int32_t, int8_t, int8_t)` + - `(float, half, half)` + - `(float, float, float)` + - `(float, bfloat16_t, bfloat16_t)` +- **Implementation checks (A5)**: + - Accumulator type must be `int32_t` or `float`. + - If `int32_t`: `AType == int8_t` and `BType == int8_t`. + - If `float`: supports `half`, `bfloat16_t`, `float`, and selected fp8 pairs (target-defined). + - Fractal/layout constraints are enforced: + - Left: `Loc == Left`, `!isRowMajor`, `SFractal == RowMajor` + - Right: `Loc == Right`, `isRowMajor`, `SFractal == ColMajor` + - Acc: `Loc == Acc`, `!isRowMajor`, `SFractal == RowMajor` + +### Bias-specific constraints + +- Bias tile datatype must exactly match `TileRes::DType`. +- Bias tile must be configured as a single row. +- Bias tile location must be `TileType::Bias`. +- **Additional A5 note**: + - No separate explicit `m/k/n` runtime assertions are enforced in the underlying A5 matmul implementation beyond the GEMV contract described above. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TGEMV_BIAS(c, a, b, bias); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(bias, 0x3000); + TASSIGN(c, 0x4000); + TGEMV_BIAS(c, a, b, bias); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%c = pto.tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%c = pto.tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%acc = tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tgemv.bias ins(%a, %b, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TGEMV_BIAS_zh.md b/designs/outerCube/PTOISA/TGEMV_BIAS_zh.md new file mode 100644 index 00000000..5e78f56a --- /dev/null +++ b/designs/outerCube/PTOISA/TGEMV_BIAS_zh.md @@ -0,0 +1,177 @@ +# TGEMV_BIAS + +## 指令示意图 + +![TGEMV_BIAS tile operation](../figures/isa/TGEMV_BIAS.svg) + +## 简介 + +带偏置加法的 GEMV。 + +## 另请参见 + +- 基础 GEMV 指令:`docs/isa/TGEMV.md`。 +- 累加变体:`docs/isa/TGEMV_ACC.md`。 + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGEMV_BIAS(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, TileBias &biasData, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_BIAS(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, TileBias &biasData, WaitEvents &... events); +``` + +## 数学语义 + +设: + +- `M = 1` +- `K = bMatrix.GetValidRow()` +- `N = bMatrix.GetValidCol()` + +对于 `0 <= j < N`(将偏置项加入矩阵乘积): + +$$ \mathrm{C}_{0,j} = \mathrm{Bias}_{0,j} + \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} $$ + +**注意:** 精确的累加器行为和数据类型提升由目标/实现定义。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%acc = tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%c = pto.tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tgemv.bias ins(%a, %b, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +## 约束 + +### 通用形状与位置约束 + +- 静态形状约束: + - `TileLeft::Rows == TileRes::Rows` + - `TileLeft::Cols == TileRight::Rows` + - `TileRight::Cols == TileRes::Cols` +- Tile 位置约束: + - `TileLeft::Loc == Left` + - `TileRight::Loc == Right` + - `TileRes::Loc == Acc` +- 运行时有效尺寸约束: + - `m` 必须为 `1` + - `k` 和 `n`(取自 `bMatrix.GetValidRow()` 与 `bMatrix.GetValidCol()`)必须位于 `[1, 4095]` + +### 数据类型约束 + +- **实现检查 (A2A3)**: + - 支持的 `(CType, AType, BType)` 三元组: + - `(int32_t, int8_t, int8_t)` + - `(float, half, half)` + - `(float, float, float)` + - `(float, bfloat16_t, bfloat16_t)` +- **实现检查 (A5)**: + - 累加器类型必须是 `int32_t` 或 `float`。 + - 如果为 `int32_t`:`AType == int8_t` 且 `BType == int8_t`。 + - 如果为 `float`:支持 `half`、`bfloat16_t`、`float` 以及选定的 fp8 组合(目标定义)。 + - 会强制执行以下分形/布局约束: + - Left:`Loc == Left`、`!isRowMajor`、`SFractal == RowMajor` + - Right:`Loc == Right`、`isRowMajor`、`SFractal == ColMajor` + - Acc:`Loc == Acc`、`!isRowMajor`、`SFractal == RowMajor` + +### 偏置专属约束 + +- 偏置 tile 的数据类型必须与 `TileRes::DType` 完全一致。 +- 偏置 tile 必须配置为单行。 +- 偏置 tile 的位置必须为 `TileType::Bias`。 +- **A5 附加说明**: + - 除上述 GEMV 约定外,底层 A5 matmul 实现不会再单独补充一组显式的 `m/k/n` 运行时断言。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TGEMV_BIAS(c, a, b, bias); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(bias, 0x3000); + TASSIGN(c, 0x4000); + TGEMV_BIAS(c, a, b, bias); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%c = pto.tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%c = pto.tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%acc = tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tgemv.bias ins(%a, %b, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TGEMV_MX.md b/designs/outerCube/PTOISA/TGEMV_MX.md new file mode 100644 index 00000000..7d117ba7 --- /dev/null +++ b/designs/outerCube/PTOISA/TGEMV_MX.md @@ -0,0 +1,127 @@ +# TGEMV_MX + + +## Tile Operation Diagram + +![TGEMV_MX tile operation](../figures/isa/TGEMV_MX.svg) + +## Introduction + +GEMV with scaling tiles for mixed-precision / quantized matrix-vector compute on supported targets. + +This instruction family extends `TGEMV` with additional scale operands (mx path). Accumulator and scale handling are target-dependent. + +## Math Interpretation + +Conceptually (base GEMV path): + +$$ +\mathrm{C}_{0,j} = \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} +$$ + +For `TGEMV_MX`, scale tiles participate in implementation-defined mixed-precision reconstruction / scaling. The architectural contract is that output corresponds to the target-defined mx GEMV semantics. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Schematic form: + +```text +%acc = tgemv.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%acc = pto.tgemv.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tgemv.mx ins(%a, %a_scale, %b, %b_scale : (!pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>)) outs(%acc : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%acc = pto.tgemv.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tgemv.mx ins(%a, %a_scale, %b, %b_scale : (!pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>)) outs(%acc : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, TileBias &biasData, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, TileBias &biasData, WaitEvents &... events); +``` + +Additional overloads support accumulation/bias variants and `AccPhase` selection. + +## Constraints + +- Uses backend-specific mx legality checks for data types, tile locations, fractal/layout combinations, and scaling formats. +- Scale tile compatibility and accumulator promotion are implementation-defined by target backend. +- For portability, validate the exact `(A, B, scaleA, scaleB, C)` type tuple and tile layout against target implementation constraints. + +## Examples + +For practical usage patterns, see: + +- `docs/isa/TMATMUL_MX.md` +- `docs/isa/TGEMV.md` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%acc = pto.tgemv.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%acc = pto.tgemv.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%acc = pto.tgemv.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tgemv.mx ins(%a, %a_scale, %b, %b_scale : (!pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>)) outs(%acc : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TGEMV_MX_zh.md b/designs/outerCube/PTOISA/TGEMV_MX_zh.md new file mode 100644 index 00000000..1b7e9d31 --- /dev/null +++ b/designs/outerCube/PTOISA/TGEMV_MX_zh.md @@ -0,0 +1,98 @@ +# TGEMV_MX + +## 指令示意图 + +![TGEMV_MX tile operation](../figures/isa/TGEMV_MX.svg) + +## 简介 + +带缩放 Tile 的 GEMV 变体,支持混合精度/量化矩阵向量计算。 + +## 数学语义 + +Conceptually (base GEMV path): + +$$ +\mathrm{C}_{0,j} = \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} +$$ + +For `TGEMV_MX`, scale tiles participate in implementation-defined mixed-precision reconstruction / scaling. The architectural contract is that output corresponds to the target-defined mx GEMV semantics. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +Schematic form: + +```text +%acc = tgemv.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%acc = pto.tgemv.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tgemv.mx ins(%a, %a_scale, %b, %b_scale : (!pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>)) outs(%acc : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%acc = pto.tgemv.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tgemv.mx ins(%a, %a_scale, %b, %b_scale : (!pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>)) outs(%acc : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, TileBias &biasData, WaitEvents &... events); + +template +PTO_INST RecordEvent TGEMV_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, TileBias &biasData, WaitEvents &... events); +``` + +Additional overloads support accumulation/bias variants and `AccPhase` selection. + +## 约束 + +- Uses backend-specific mx legality checks for data types, tile locations, fractal/layout combinations, and scaling formats. +- Scale tile compatibility and accumulator promotion are implementation-defined by target backend. +- For portability, validate the exact `(A, B, scaleA, scaleB, C)` type tuple and tile layout against target implementation constraints. + +## 示例 + +For practical usage patterns, see: + +- `docs/isa/TMATMUL_MX.md` +- `docs/isa/TGEMV.md` diff --git a/designs/outerCube/PTOISA/TGEMV_zh.md b/designs/outerCube/PTOISA/TGEMV_zh.md new file mode 100644 index 00000000..f17168fe --- /dev/null +++ b/designs/outerCube/PTOISA/TGEMV_zh.md @@ -0,0 +1,282 @@ +# TGEMV + +## 指令示意图 + +![TGEMV tile operation](../figures/isa/TGEMV.svg) + +## 简介 + +通用矩阵-向量乘法,生成累加器/输出 Tile。 + +## 数学语义 + +设: + +- `M = 1` +- `K = bMatrix.GetValidRow()` +- `N = bMatrix.GetValidCol()` + +### 1. TGEMV(基于 Tile 的 GEMV) + +对于 `0 <= j < N`(有效矩阵乘法域中的输出元素): + +$$ \mathrm{C}_{0,j} = \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} $$ + +### 2. TGEMV_ACC(带累加的基于 Tile 的 GEMV) + +对于 `0 <= j < N`(累加到现有 tile): + +$$ \mathrm{C}_{0,j} \gets \mathrm{C}_{0,j} + \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} $$ + +### 3. TGEMV_BIAS(带偏置的基于 Tile 的 GEMV) + +对于 `0 <= j < N`(将偏置项添加到矩阵乘积): + +$$ \mathrm{C}_{0,j} = \mathrm{Bias}_{0,j} + \sum_{k=0}^{K-1} \mathrm{A}_{0,k} \cdot \mathrm{B}_{k,j} $$ + +**注意:** 精确的累加器行为和数据类型提升由目标/实现定义。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%acc = tgemv %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> + +%acc1 = tgemv.acc %acc0, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> + +%acc = tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%c = pto.tgemv %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c_out = pto.tgemv.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c = pto.tgemv.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tgemv ins(%a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +pto.tgemv.acc ins(%c_in, %a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +pto.tgemv.bias ins(%a, %b, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGEMV(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents&... events); + +template +PTO_INST RecordEvent TGEMV_ACC(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents&... events); + +template +PTO_INST RecordEvent TGEMV_BIAS(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, TileBias &biasData, WaitEvents&... events); +``` + +## 约束 + +### 通用形状与位置约束 + +以下约束在未特别说明时同时适用于 `TGEMV`、`TGEMV_ACC` 和 `TGEMV_BIAS`。 + +- 静态形状约束: + - `TileLeft::Rows == TileRes::Rows` + - `TileLeft::Cols == TileRight::Rows` + - `TileRight::Cols == TileRes::Cols` +- Tile 位置约束: + - `TileLeft::Loc == Left` + - `TileRight::Loc == Right` + - `TileRes::Loc == Acc` +- 运行时有效尺寸约束: + - `m` 必须为 `1` + - `k` 和 `n`(取自 `bMatrix.GetValidRow()` 与 `bMatrix.GetValidCol()`)必须位于 `[1, 4095]` + +### TGEMV / TGEMV_ACC 数据类型约束 + +- **实现检查 (A2A3)**: + - 支持的 `(CType, AType, BType)` 三元组: + - `(int32_t, int8_t, int8_t)` + - `(float, half, half)` + - `(float, float, float)` + - `(float, bfloat16_t, bfloat16_t)` +- **实现检查 (A5)**: + - 累加器类型必须是 `int32_t` 或 `float`。 + - 如果为 `int32_t`:`AType == int8_t` 且 `BType == int8_t`。 + - 如果为 `float`:支持 `half`、`bfloat16_t`、`float` 以及选定的 fp8 组合(目标定义)。 + - 会强制执行以下分形/布局约束: + - Left:`Loc == Left`、`!isRowMajor`、`SFractal == RowMajor` + - Right:`Loc == Right`、`isRowMajor`、`SFractal == ColMajor` + - Acc:`Loc == Acc`、`!isRowMajor`、`SFractal == RowMajor` + +### TGEMV_BIAS 的附加约束 + +- 偏置 tile 的数据类型必须与 `TileRes::DType` 完全一致。 +- 偏置 tile 必须配置为单行。 +- 偏置 tile 的位置必须为 `TileType::Bias`。 +- **A5 附加说明**: + - 除上述 GEMV 约定外,底层 A5 matmul 实现不会再单独补充一组显式的 `m/k/n` 运行时断言。 + +## 示例 + +### 自动(Auto) + +#### 1. TGEMV + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c; + TGEMV(c, a, b); +} +``` + +#### 2. TGEMV_ACC + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TGEMV_ACC(c1, c0, a, b); +} +``` + +#### 3. TGEMV_BIAS + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TGEMV_BIAS(c, a, b, bias); +} +``` + +### 手动(Manual) + +#### 1. TGEMV + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c, 0x3000); + TGEMV(c, a, b); +} +``` + +#### 2. TGEMV_ACC + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c0, 0x3000); + TASSIGN(c1, 0x4000); + TGEMV_ACC(c1, c0, a, b); +} +``` + +#### 3. TGEMV_BIAS + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(bias, 0x3000); + TASSIGN(c, 0x4000); + TGEMV_BIAS(c, a, b, bias); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%c = pto.tgemv %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%c = pto.tgemv %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%acc = tgemv %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tgemv ins(%a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TGET_SCALE_ADDR.md b/designs/outerCube/PTOISA/TGET_SCALE_ADDR.md new file mode 100644 index 00000000..458b05ba --- /dev/null +++ b/designs/outerCube/PTOISA/TGET_SCALE_ADDR.md @@ -0,0 +1,83 @@ +# TGET_SCALE_ADDR + +## Tile Operation Diagram + +![TGET_SCALE_ADDR tile operation](../figures/isa/TGET_SCALE_ADDR.svg) + +## Introduction + +Bind the on-chip address of output tile as a scaled address of the input tile. + +The scaling factor is defined by a right-shift amount `SHIFT_MX_ADDR` in `include/pto/npu/a5/utils.hpp`. + +## Math Interpretation + +Address(`dst`) = Address(`src`) >> `SHIFT_MX_ADDR` + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +### IR Level 1 (SSA) + +TODO + +### IR Level 2 (DPS) + +TODO + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGET_SCALE_ADDR(TileDataDst &dst, TileDataSrc &src, aitEvents&... events); +``` + +## Constraints + +Enforced by `TGET_SCALE_ADDR_IMPL`: + +- **Both `src` and `dst` must be Tile instances** +- **Currently only work in auto mode** (will support manual mode in the future) + +## Examples + +```cpp +#include + +> wa +using namespace pto; + +template +void example() { + using LeftTile = TileLeft; + using RightTile = TileRight; + + using LeftScaleTile = TileLeftScale; + using RightScaleTile = TileRightScale; + + LeftTile aTile; + RightTile bTile; + LeftScaleTile aScaleTile; + RightScaleTile bScaleTile; + + TGET_SCALE_ADDR(aScaleTile, aTile); + TGET_SCALE_ADDR(bScaleTile, bTile); +} +``` + +## asm form examples + +### Auto Mode + +TODO + +### Manual Mode + +TODO + +### PTO Assembly Form + +TODO diff --git a/designs/outerCube/PTOISA/TGET_SCALE_ADDR_zh.md b/designs/outerCube/PTOISA/TGET_SCALE_ADDR_zh.md new file mode 100644 index 00000000..0dad8946 --- /dev/null +++ b/designs/outerCube/PTOISA/TGET_SCALE_ADDR_zh.md @@ -0,0 +1,81 @@ +# TGET_SCALE_ADDR + +## Tile Operation Diagram + +![TGET_SCALE_ADDR tile operation](../figures/isa/TGET_SCALE_ADDR.svg) + +## Introduction + +将输入Tile的片上地址数值按比例扩展,将其结果数值绑定为输出Tile的片上地址。 + +这个扩展因子是由`include/pto/npu/a5/utils.hpp`中的右移值`SHIFT_MX_ADDR`定义的。 + +## 数学语义 + +Address(`dst`) = Address(`src`) >> `SHIFT_MX_ADDR` + +## 汇编语法 + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +### IR Level 1 (SSA) + +TODO + +### IR Level 2 (DPS) + +TODO + +## C++ 内建接口 + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TGET_SCALE_ADDR(TileDataDst &dst, TileDataSrc &src, aitEvents&... events); +``` + +## 约束 + +- **输入和输出都必须为Tile对象** +- **目前只能用在auto模式下**(以后会将支持manual模式下的实现) + +## 示例 + +```cpp +#include + +> wa +using namespace pto; + +template +void example() { + using LeftTile = TileLeft; + using RightTile = TileRight; + + using LeftScaleTile = TileLeftScale; + using RightScaleTile = TileRightScale; + + LeftTile aTile; + RightTile bTile; + LeftScaleTile aScaleTile; + RightScaleTile bScaleTile; + + TGET_SCALE_ADDR(aScaleTile, aTile); + TGET_SCALE_ADDR(bScaleTile, bTile); +} +``` + +## asm form examples + +### Auto Mode + +TODO + +### Manual Mode + +TODO + +### PTO Assembly Form + +TODO diff --git a/designs/outerCube/PTOISA/THISTOGRAM.md b/designs/outerCube/PTOISA/THISTOGRAM.md new file mode 100644 index 00000000..62186da0 --- /dev/null +++ b/designs/outerCube/PTOISA/THISTOGRAM.md @@ -0,0 +1,40 @@ +# THISTOGRAM + +## Tile Operation Diagram + +![THISTOGRAM tile operation](../figures/isa/THISTOGRAM.svg) + +## Introduction + +Accumulate histogram bin counts from source values using an index tile. + +## Math Interpretation + +Semantics are instruction-specific. Unless stated otherwise, behavior is defined over the destination valid region. + +## Assembly Syntax + +PTO-AS form: see `docs/assembly/PTO-AS.md`. + +### IR Level 1 (SSA) + +```text +%dst = pto.thistogram ... +``` + +### IR Level 2 (DPS) + +```text +pto.thistogram ins(...) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`. + +## Constraints + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## Examples + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/THISTOGRAM_zh.md b/designs/outerCube/PTOISA/THISTOGRAM_zh.md new file mode 100644 index 00000000..1f352be5 --- /dev/null +++ b/designs/outerCube/PTOISA/THISTOGRAM_zh.md @@ -0,0 +1,41 @@ +# THISTOGRAM + +## 指令示意图 + +![THISTOGRAM tile operation](../figures/isa/THISTOGRAM.svg) + +## 简介 + +使用索引 Tile 从源值中累计直方图 bin 计数。 + +## 数学语义 + +语义随指令而变化。 Unless stated otherwise, behavior is defined over the destination valid region. + +## 汇编语法 + +PTO-AS 形式:参见 `docs/assembly/PTO-AS.md`. + +### AS Level 1(SSA) + +```text +%dst = pto.thistogram ... +``` + +### AS Level 2(DPS) + +```text +pto.thistogram ins(...) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`. + +## 约束 + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## 示例 + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TIMG2COL.md b/designs/outerCube/PTOISA/TIMG2COL.md new file mode 100644 index 00000000..5cba652d --- /dev/null +++ b/designs/outerCube/PTOISA/TIMG2COL.md @@ -0,0 +1,85 @@ +# TIMG2COL + + +## Tile Operation Diagram + +![TIMG2COL tile operation](../figures/isa/TIMG2COL.svg) + +## Introduction + +Transform an input feature-map tile (e.g. NC1HWC0 layout) into an im2col-style matrix tile for convolution-like workloads. Parameters are provided via `Img2colTileConfig` and `(posM, posK)` offsets. + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +PTO_INST RecordEvent TIMG2COL(TileData &dst, ConvTileData &src, uint16_t posM = 0, uint16_t posK = 0, + WaitEvents&... events); +``` + +## Constraints + +- This instruction is target/implementation-specific. See `include/pto/npu/*/TImg2col.hpp` for the supported tile types/layouts and config fields. + +## Math Interpretation + +Unless otherwise specified, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.timg2col %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.timg2col ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.timg2col %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.timg2col ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.timg2col %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.timg2col %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.timg2col %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.timg2col ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TIMG2COL_zh.md b/designs/outerCube/PTOISA/TIMG2COL_zh.md new file mode 100644 index 00000000..c13dacf8 --- /dev/null +++ b/designs/outerCube/PTOISA/TIMG2COL_zh.md @@ -0,0 +1,58 @@ +# TIMG2COL + +## 指令示意图 + +![TIMG2COL tile operation](../figures/isa/TIMG2COL.svg) + +## 简介 + +用于类卷积工作负载的图像到列变换。 + +## 数学语义 + +除非另有说明, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.timg2col %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.timg2col ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.timg2col %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.timg2col ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +PTO_INST RecordEvent TIMG2COL(TileData &dst, ConvTileData &src, uint16_t posM = 0, uint16_t posK = 0, + WaitEvents&... events); +``` + +## 约束 + +- This instruction is target/implementation-specific. See `include/pto/npu/*/TImg2col.hpp` for the supported tile types/layouts and config fields. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TINSERT.md b/designs/outerCube/PTOISA/TINSERT.md new file mode 100644 index 00000000..c5f02509 --- /dev/null +++ b/designs/outerCube/PTOISA/TINSERT.md @@ -0,0 +1,107 @@ +# TINSERT + + +## Tile Operation Diagram + +![TINSERT tile operation](../figures/isa/TINSERT.svg) + +## Introduction + +Insert a source sub-tile into a destination tile at `(indexRow, indexCol)`. This is conceptually the inverse of `TEXTRACT` for many layouts. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. Conceptually, for `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{\mathrm{indexRow}+i,\;\mathrm{indexCol}+j} = \mathrm{src}_{i,j} +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tinsert %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tinsert %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tinsert ins(%src[%r0, %r1] : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TINSERT(DstTileData &dst, SrcTileData &src, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +template +PTO_INST RecordEvent TINSERT(DstTileData &dst, SrcTileData &src, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +template +PTO_INST RecordEvent TINSERT(DstTileData &dst, SrcTileData &src, uint64_t preQuantScalar, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +template +PTO_INST RecordEvent TINSERT_FP(DstTileData &dst, SrcTileData &src, FpTileData &fp, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +#ifdef PTO_NPU_ARCH_A5 +template +PTO_INST RecordEvent TINSERT(DstTileData &dst, SrcTileData &src, uint32_t indexRow = 0, uint32_t indexCol = 0, WaitEvents &... events); +#endif +``` + +## Constraints + +- **A2/A3**: + - The documented overloads map to `Acc -> Mat` insertion paths, including plain, `reluMode`, scalar pre-quant, and vector pre-quant (`TINSERT_FP`) forms. + - Runtime bounds must satisfy `indexRow + src.Rows <= dst.Rows` and `indexCol + src.Cols <= dst.Cols`. +- **A5**: + - In addition to the `Acc -> Mat` insertion paths above, A5 also exposes `template TINSERT(...)` for `Vec -> Mat` and `Vec -> Vec` insertion variants. + - `mode == TInsertMode::ND` requires a row-major source vector tile and inserts into a matrix tile in ND layout. + - `mode == TInsertMode::ND_VEC` requires both source and destination to be row-major vector tiles. + - NZ-family modes (`NZ`, `NZ_PLUS_1`, `SPLIT2_NZ_PLUS_1`, `SPLIT4_NZ_PLUS_1`) require an NZ-format source vector tile and a matrix destination tile. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tinsert %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tinsert %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tinsert %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tinsert ins(%src[%r0, %r1] : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TINSERT_FP.md b/designs/outerCube/PTOISA/TINSERT_FP.md new file mode 100644 index 00000000..ce12ec57 --- /dev/null +++ b/designs/outerCube/PTOISA/TINSERT_FP.md @@ -0,0 +1,90 @@ +# TINSERT_FP + + +## Tile Operation Diagram + +![TINSERT_FP tile operation](../figures/isa/TINSERT_FP.svg) + +## Introduction + +Vector-quantization variant of `TINSERT` that also takes an `fp` (scaling) tile. + +## See also + +- TINSERT base instruction: `docs/isa/TINSERT.md`. + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TINSERT_FP(DstTileData &dst, SrcTileData &src, FpTileData &fp, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); +``` + +## Math Interpretation + +Unless otherwise specified, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.tinsert_fp %src, %fp, %idxrow, %idxcol : (!pto.tile<...>, !pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tinsert_fp ins(%src, %fp, %idxrow, %idxcol : !pto.tile_buf<...>, !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tinsert_fp %src, %fp, %idxrow, %idxcol : (!pto.tile<...>, !pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tinsert_fp ins(%src, %fp, %idxrow, %idxcol : !pto.tile_buf<...>, !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## Constraints + +Type/layout/location/shape legality is backend-dependent; treat implementation-specific notes as normative for that backend. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tinsert_fp %src, %fp, %idxrow, %idxcol : (!pto.tile<...>, !pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tinsert_fp %src, %fp, %idxrow, %idxcol : (!pto.tile<...>, !pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.tinsert_fp %src, %fp, %idxrow, %idxcol : (!pto.tile<...>, !pto.tile<...>, dtype, dtype) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tinsert_fp ins(%src, %fp, %idxrow, %idxcol : !pto.tile_buf<...>, !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TINSERT_FP_zh.md b/designs/outerCube/PTOISA/TINSERT_FP_zh.md new file mode 100644 index 00000000..5dc56ee3 --- /dev/null +++ b/designs/outerCube/PTOISA/TINSERT_FP_zh.md @@ -0,0 +1,59 @@ +# TINSERT_FP + +## 指令示意图 + +![TINSERT_FP tile operation](../figures/isa/TINSERT_FP.svg) + +## 简介 + +带 fp/缩放 Tile 的插入(向量量化参数)。 + +## 数学语义 + +除非另有说明, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.tinsert_fp %src, %fp, %idxrow, %idxcol : (!pto.tile<...>, !pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tinsert_fp ins(%src, %fp, %idxrow, %idxcol : !pto.tile_buf<...>, !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tinsert_fp %src, %fp, %idxrow, %idxcol : (!pto.tile<...>, !pto.tile<...>, dtype, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tinsert_fp ins(%src, %fp, %idxrow, %idxcol : !pto.tile_buf<...>, !pto.tile_buf<...>, dtype, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TINSERT_FP(DstTileData &dst, SrcTileData &src, FpTileData &fp, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); +``` + +## 约束 + +Type/layout/location/shape legality is backend-dependent; treat implementation-specific notes as normative for that backend. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TINSERT_zh.md b/designs/outerCube/PTOISA/TINSERT_zh.md new file mode 100644 index 00000000..80448b6d --- /dev/null +++ b/designs/outerCube/PTOISA/TINSERT_zh.md @@ -0,0 +1,107 @@ +# TINSERT + +## 指令示意图 + +![TINSERT tile operation](../figures/isa/TINSERT.svg) + +## 简介 + +在 (indexRow, indexCol) 偏移处将子 Tile 插入到目标 Tile 中。 + +## 数学语义 + +设 `R = src.GetValidRow()` 和 `C = src.GetValidCol()`。概念上,对于 `0 <= i < R` 和 `0 <= j < C`: + +$$ +\mathrm{dst}_{\mathrm{indexRow}+i,\;\mathrm{indexCol}+j} = \mathrm{src}_{i,j} +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tinsert %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tinsert %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tinsert ins(%src[%r0, %r1] : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TINSERT(DstTileData &dst, SrcTileData &src, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +template +PTO_INST RecordEvent TINSERT(DstTileData &dst, SrcTileData &src, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +template +PTO_INST RecordEvent TINSERT(DstTileData &dst, SrcTileData &src, uint64_t preQuantScalar, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +template +PTO_INST RecordEvent TINSERT_FP(DstTileData &dst, SrcTileData &src, FpTileData &fp, uint16_t indexRow, uint16_t indexCol, WaitEvents &... events); + +#ifdef PTO_NPU_ARCH_A5 +template +PTO_INST RecordEvent TINSERT(DstTileData &dst, SrcTileData &src, uint32_t indexRow = 0, uint32_t indexCol = 0, WaitEvents &... events); +#endif +``` + +## 约束 + +- **A2/A3**: + - 文档中列出的这些重载对应 `Acc -> Mat` 插入路径,包括普通形式、`reluMode` 形式、标量预量化形式以及向量预量化(`TINSERT_FP`)形式。 + - 运行时边界必须满足 `indexRow + src.Rows <= dst.Rows` 且 `indexCol + src.Cols <= dst.Cols`。 +- **A5**: + - 除了上面的 `Acc -> Mat` 插入路径外,A5 还额外提供 `template TINSERT(...)`,用于 `Vec -> Mat` 与 `Vec -> Vec` 插入变体。 + - `mode == TInsertMode::ND` 要求源向量 tile 为行优先,并以 ND 布局插入到矩阵 tile。 + - `mode == TInsertMode::ND_VEC` 要求源和目的都为行优先向量 tile。 + - NZ 系列模式(`NZ`、`NZ_PLUS_1`、`SPLIT2_NZ_PLUS_1`、`SPLIT4_NZ_PLUS_1`)要求源向量 tile 为 NZ 格式,目的为矩阵 tile。 + +## 示例 + +参见 `docs/isa/` 和 `docs/coding/tutorials/` 中的相关示例。 + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tinsert %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tinsert %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tinsert %src[%r0, %r1] : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tinsert ins(%src[%r0, %r1] : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TLOAD.md b/designs/outerCube/PTOISA/TLOAD.md new file mode 100644 index 00000000..3f36e1ee --- /dev/null +++ b/designs/outerCube/PTOISA/TLOAD.md @@ -0,0 +1,159 @@ +# TLOAD + + +## Tile Operation Diagram + +![TLOAD tile operation](../figures/isa/TLOAD.svg) + +## Introduction + +Load data from a GlobalTensor (GM) into a Tile. + +## Math Interpretation + +Notation depends on the `GlobalTensor` shape/stride and the `Tile` layout. Conceptually (2D view, with a base offset): + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{r_0 + i,\; c_0 + j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%t0 = tload %sv[%c0, %c0] : (!pto.memref<...>, index, index) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tload %mem : !pto.partition_tensor_view -> +!pto.tile +``` + +### AS Level 2 (DPS) + +```text +pto.tload ins(%mem : !pto.partition_tensor_view) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tload %mem : !pto.partition_tensor_view -> +!pto.tile +``` + +### IR Level 2 (DPS) + +```text +pto.tload ins(%mem : !pto.partition_tensor_view) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TLOAD(TileData &dst, GlobalData &src, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `int64_t`, `uint64_t`, `half`, `bfloat16_t`, `float`. + - Destination tile location must be `TileType::Vec` or `TileType::Mat`. + - `sizeof(TileData::DType) == sizeof(GlobalData::DType)`. + - Runtime: all `src.GetShape(dim)` values and `dst.GetValidRow()/GetValidCol()` must be `> 0`. + - `TileType::Vec` loads only support matching layouts: ND->ND, DN->DN, NZ->NZ. + - `TileType::Mat` loads support: ND->ND, DN->DN, NZ->NZ, plus ND->NZ and DN->ZN. + - For ND->NZ or DN->ZN: `GlobalData::staticShape[0..2] == 1` and `TileData::SFractalSize == 512`. + - For `int64_t/uint64_t`, only ND->ND or DN->DN are supported. +- **Implementation checks (A5)**: + - `sizeof(TileData::DType)` must be `1`, `2`, `4`, or `8` bytes, and must match `sizeof(GlobalData::DType)`. + - For `int64_t/uint64_t`, `TileData::PadVal` must be `PadValue::Null` or `PadValue::Zero`. + - `TileType::Vec` loads require one of the following layout pairs: + - ND with row-major + `SLayout::NoneBox` (ND->ND), + - DN with col-major + `SLayout::NoneBox` (DN->DN), + - NZ with `SLayout::RowMajor` (NZ->NZ). + - For row-major ND->ND with compile-time-known shapes, `TileData::ValidCol` must equal `GlobalData::staticShape[4]`, and `TileData::ValidRow` must equal the product of `GlobalData::staticShape[0..3]`. + - `TileType::Mat` loads are additionally constrained by `TLoadCubeCheck` (e.g., only specific ND/DN/NZ conversions and L1-size limits). + - `TileType::Mat` loads also handle loads for mx format, which include `MX_A_ZZ/MX_A_ND/MX_A_DN` to ZZ for scalarA and `MX_B_NN/MX_B_ND/MX_B_DN` to NN for scalarB. + - for `MX_A_ZZ/MX_B_NN`: `GlobalData::staticShape[3] == 16` and `GlobalData::staticShape[4] == 2`. + - for `MX_A_ND/MX_ADN/MX_B_ND/MX_B_DN`: `GlobalData::staticShape[0] == 1` and `GlobalData::staticShape[1] == 1` and `GlobalData::staticShape[4] == 2`. + - for scaleA, `dst.GetValidCol() % 2 == 0`. + - for scaleB, `dst.GetValidRow() % 2 == 0` + +- **Valid region**: + - The implementation uses `dst.GetValidRow()` / `dst.GetValidCol()` as the transfer size. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +template +void example_auto(__gm__ T* in) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GTensor = GlobalTensor; + + GTensor gin(in); + TileT t; + TLOAD(t, gin); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +template +void example_manual(__gm__ T* in) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GTensor = GlobalTensor; + + GTensor gin(in); + TileT t; + TASSIGN(t, 0x1000); + TLOAD(t, gin); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tload %mem : !pto.partition_tensor_view -> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tload %mem : !pto.partition_tensor_view -> +``` + +### PTO Assembly Form + +```text +%t0 = tload %sv[%c0, %c0] : (!pto.memref<...>, index, index) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tload ins(%mem : !pto.partition_tensor_view) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TLOAD_zh.md b/designs/outerCube/PTOISA/TLOAD_zh.md new file mode 100644 index 00000000..47b25f99 --- /dev/null +++ b/designs/outerCube/PTOISA/TLOAD_zh.md @@ -0,0 +1,132 @@ +# TLOAD + +## 指令示意图 + +![TLOAD tile operation](../figures/isa/TLOAD.svg) + +## 简介 + +从 GlobalTensor (GM) 加载数据到 Tile。 + +## 数学语义 + +Notation depends on the `GlobalTensor` shape/stride and the `Tile` layout. Conceptually (2D view, with a base offset): + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{r_0 + i,\; c_0 + j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%t0 = tload %sv[%c0, %c0] : (!pto.memref<...>, index, index) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tload %mem : !pto.partition_tensor_view -> +!pto.tile +``` + +### AS Level 2 (DPS) + +```text +pto.tload ins(%mem : !pto.partition_tensor_view) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tload %mem : !pto.partition_tensor_view -> +!pto.tile +``` + +### AS Level 2(DPS) + +```text +pto.tload ins(%mem : !pto.partition_tensor_view) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TLOAD(TileData &dst, GlobalData &src, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `int64_t`, `uint64_t`, `half`, `bfloat16_t`, `float`. + - Destination tile location must be `TileType::Vec` or `TileType::Mat`. + - `sizeof(TileData::DType) == sizeof(GlobalData::DType)`. + - Runtime: all `src.GetShape(dim)` values and `dst.GetValidRow()/GetValidCol()` must be `> 0`. + - `TileType::Vec` loads only support matching layouts: ND->ND, DN->DN, NZ->NZ. + - `TileType::Mat` loads support: ND->ND, DN->DN, NZ->NZ, plus ND->NZ and DN->ZN. + - For ND->NZ or DN->ZN: `GlobalData::staticShape[0..2] == 1` and `TileData::SFractalSize == 512`. + - For `int64_t/uint64_t`, only ND->ND or DN->DN are supported. +- **实现检查 (A5)**: + - `sizeof(TileData::DType)` must be `1`, `2`, `4`, or `8` bytes, and must match `sizeof(GlobalData::DType)`. + - For `int64_t/uint64_t`, `TileData::PadVal` must be `PadValue::Null` or `PadValue::Zero`. + - `TileType::Vec` loads require one of the following layout pairs: + - ND with row-major + `SLayout::NoneBox` (ND->ND), + - DN with col-major + `SLayout::NoneBox` (DN->DN), + - NZ with `SLayout::RowMajor` (NZ->NZ). + - For row-major ND->ND with compile-time-known shapes, `TileData::ValidCol` must equal `GlobalData::staticShape[4]`, and `TileData::ValidRow` must equal the product of `GlobalData::staticShape[0..3]`. + - `TileType::Mat` loads are additionally constrained by `TLoadCubeCheck` (e.g., only specific ND/DN/NZ conversions and L1-size limits). + - `TileType::Mat` loads also handle loads for mx format, which include `MX_A_ZZ/MX_A_ND/MX_A_DN` to ZZ for scalarA and `MX_B_NN/MX_B_ND/MX_B_DN` to NN for scalarB. + - for `MX_A_ZZ/MX_B_NN`: `GlobalData::staticShape[3] == 16` and `GlobalData::staticShape[4] == 2`. + - for `MX_A_ND/MX_ADN/MX_B_ND/MX_B_DN`: `GlobalData::staticShape[0] == 1` and `GlobalData::staticShape[1] == 1` and `GlobalData::staticShape[4] == 2`. + - for scaleA, `dst.GetValidCol() % 2 == 0`. + - for scaleB, `dst.GetValidRow() % 2 == 0` + +- **有效区域**: + - The implementation uses `dst.GetValidRow()` / `dst.GetValidCol()` as the transfer size. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +template +void example_auto(__gm__ T* in) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GTensor = GlobalTensor; + + GTensor gin(in); + TileT t; + TLOAD(t, gin); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +template +void example_manual(__gm__ T* in) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GTensor = GlobalTensor; + + GTensor gin(in); + TileT t; + TASSIGN(t, 0x1000); + TLOAD(t, gin); +} +``` diff --git a/designs/outerCube/PTOISA/TLOG.md b/designs/outerCube/PTOISA/TLOG.md new file mode 100644 index 00000000..46148513 --- /dev/null +++ b/designs/outerCube/PTOISA/TLOG.md @@ -0,0 +1,114 @@ +# TLOG + + +## Tile Operation Diagram + +![TLOG tile operation](../figures/isa/TLOG.svg) + +## Introduction + +Elementwise natural logarithm of a tile. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \log(\mathrm{src}_{i,j}) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tlog %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tlog %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tlog ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tlog %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tlog ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TLOG(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (NPU)**: + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Domain / NaN**: + - Domain behavior (e.g., `log(<=0)`) is target-defined. + + + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TLOG(out, x); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tlog %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tlog %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tlog %src : !pto.tile<...> +# AS Level 2 (DPS) +pto.tlog ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TLOG_zh.md b/designs/outerCube/PTOISA/TLOG_zh.md new file mode 100644 index 00000000..67f5924b --- /dev/null +++ b/designs/outerCube/PTOISA/TLOG_zh.md @@ -0,0 +1,85 @@ +# TLOG + +## 指令示意图 + +![TLOG tile operation](../figures/isa/TLOG.svg) + +## 简介 + +Tile 的逐元素自然对数。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \log(\mathrm{src}_{i,j}) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tlog %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tlog %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tlog ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tlog %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tlog ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TLOG(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (NPU)**: + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Domain / NaN**: + - Domain behavior (e.g., `log(<=0)`) is target-defined. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TLOG(out, x); +} +``` diff --git a/designs/outerCube/PTOISA/TLRELU.md b/designs/outerCube/PTOISA/TLRELU.md new file mode 100644 index 00000000..3f66bc86 --- /dev/null +++ b/designs/outerCube/PTOISA/TLRELU.md @@ -0,0 +1,104 @@ +# TLRELU + + +## Tile Operation Diagram + +![TLRELU tile operation](../figures/isa/TLRELU.svg) + +## Introduction + +Leaky ReLU with a scalar slope. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = (\mathrm{src}_{i,j} > 0) ? \mathrm{src}_{i,j} : (\mathrm{src}_{i,j} \cdot \mathrm{slope}) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tlrelu %src, %slope : !pto.tile<...>, f32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tlrelu %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tlrelu ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TLRELU(TileDataDst& dst, TileDataSrc& src, typename TileDataSrc::DType scalar, WaitEvents&... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `half`, `float16_t`, `float`, `float32_t` (floating-point types only). + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `half`, `float` (floating-point types only). + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Common constraints**: + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `dst` and `src` must have the same valid row/col. + - Slope scalar type must match the Tile data type. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TLRELU(out, x, 0.1f); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tlrelu %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tlrelu %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tlrelu %src, %slope : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tlrelu ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TLRELU_zh.md b/designs/outerCube/PTOISA/TLRELU_zh.md new file mode 100644 index 00000000..7fc11ff7 --- /dev/null +++ b/designs/outerCube/PTOISA/TLRELU_zh.md @@ -0,0 +1,104 @@ +# TLRELU + +## 指令示意图 + +![TLRELU tile operation](../figures/isa/TLRELU.svg) + +## 简介 + +带标量斜率的 Leaky ReLU。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = (\mathrm{src}_{i,j} > 0) ? \mathrm{src}_{i,j} : (\mathrm{src}_{i,j} \cdot \mathrm{slope}) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tlrelu %src, %slope : !pto.tile<...>, f32 +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tlrelu %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tlrelu ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TLRELU(TileDataDst& dst, TileDataSrc& src, typename TileDataSrc::DType scalar, WaitEvents&... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` 必须是以下之一:`half`、`float16_t`、`float`、`float32_t`(仅浮点类型)。 + - Tile 布局必须是行主序(`TileData::isRowMajor`)。 +- **实现检查 (A5)**: + - `TileData::DType` 必须是以下之一:`half`、`float`(仅浮点类型)。 + - Tile 布局必须是行主序(`TileData::isRowMajor`)。 +- **通用约束**: + - Tile 位置必须是向量(`TileData::Loc == TileType::Vec`)。 + - 静态有效边界:`TileData::ValidRow <= TileData::Rows` 且 `TileData::ValidCol <= TileData::Cols`。 + - 运行时:`dst` 和 `src` 的有效行列数必须相同。 + - 斜率标量类型必须与 Tile 数据类型一致。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TLRELU(out, x, 0.1f); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tlrelu %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tlrelu %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tlrelu %src, %slope : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tlrelu ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TMATMUL.md b/designs/outerCube/PTOISA/TMATMUL.md new file mode 100644 index 00000000..3d79405b --- /dev/null +++ b/designs/outerCube/PTOISA/TMATMUL.md @@ -0,0 +1,159 @@ +# TMATMUL + + +## Tile Operation Diagram + +![TMATMUL tile operation](../figures/isa/TMATMUL.svg) + +## Introduction + +Matrix multiply (GEMM) producing an accumulator/output tile. + +## Math Interpretation + +Let: + +- `M = aMatrix.GetValidRow()` +- `K = aMatrix.GetValidCol()` +- `N = bMatrix.GetValidCol()` + +For `0 <= i < M` and `0 <= j < N` (output elements in the effective matmul domain): + +$$ \mathrm{C}_{i,j} = \sum_{k=0}^{K-1} \mathrm{A}_{i,k} \cdot \mathrm{B}_{k,j} $$ + +Exact accumulator behavior and datatype promotion are target/implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%acc = tmatmul %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%c = pto.tmatmul %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmatmul ins(%a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%c = pto.tmatmul %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tmatmul ins(%a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMATMUL(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Supported `(CType, AType, BType)` triples: + - `(int32_t, int8_t, int8_t)` + - `(float, half, half)` + - `(float, float, float)` + - `(float, bfloat16_t, bfloat16_t)` + - Static shape constraints: `TileLeft::Rows == TileRes::Rows`, `TileLeft::Cols == TileRight::Rows`, `TileRight::Cols == TileRes::Cols`. + - Tile locations: `TileLeft::Loc == Left`, `TileRight::Loc == Right`, `TileRes::Loc == Acc`. + - Runtime: `m/k/n` (taken from `aMatrix.GetValidRow()`, `aMatrix.GetValidCol()`, `bMatrix.GetValidCol()`) must be in `[1, 4095]`. +- **Implementation checks (A5)**: + - Accumulator type must be `int32_t` or `float`. + - If `int32_t`: `AType == int8_t` and `BType == int8_t`. + - If `float`: supports `half/bfloat16_t/float` and selected fp8 pairs (target-defined). + - Static shape constraints: `TileLeft::Rows == TileRes::Rows`, `TileLeft::Cols == TileRight::Rows`, `TileRight::Cols == TileRes::Cols`. + - Fractal/layout constraints are enforced: + - Left: `Loc == Left`, `!isRowMajor`, `SFractal == RowMajor` + - Right: `Loc == Right`, `isRowMajor`, `SFractal == ColMajor` + - Acc: `Loc == Acc`, `!isRowMajor`, `SFractal == RowMajor` + - Runtime: `m/k/n` (taken from `aMatrix.GetValidRow()`, `aMatrix.GetValidCol()`, `bMatrix.GetValidCol()`) must be in `[1, 4095]`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c; + TMATMUL(c, a, b); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c, 0x3000); + TMATMUL(c, a, b); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%c = pto.tmatmul %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%c = pto.tmatmul %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%acc = tmatmul %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tmatmul ins(%a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TMATMUL_ACC.md b/designs/outerCube/PTOISA/TMATMUL_ACC.md new file mode 100644 index 00000000..e16b45c6 --- /dev/null +++ b/designs/outerCube/PTOISA/TMATMUL_ACC.md @@ -0,0 +1,147 @@ +# TMATMUL_ACC + + +## Tile Operation Diagram + +![TMATMUL_ACC tile operation](../figures/isa/TMATMUL_ACC.svg) + +## Introduction + +Matrix multiply with accumulator input (fused accumulate). + +## Math Interpretation + +Let: + +- `M = aMatrix.GetValidRow()` +- `K = aMatrix.GetValidCol()` +- `N = bMatrix.GetValidCol()` + +For `0 <= i < M` and `0 <= j < N`: + +$$ \mathrm{C1}_{i,j} = \mathrm{C0}_{i,j} + \sum_{k=0}^{K-1} \mathrm{A}_{i,k} \cdot \mathrm{B}_{k,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%acc1 = tmatmul.acc %acc0, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%c_out = pto.tmatmul.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmatmul.acc ins(%c_in, %a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%c_out = pto.tmatmul.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tmatmul.acc ins(%c_in, %a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMATMUL_ACC(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_ACC(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_ACC(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); +``` + +## Constraints + +- All constraints from `TMATMUL` apply to the `(cOutMatrix, aMatrix, bMatrix)` triple. +- **Implementation notes (A2A3/A5)**: + - `TMATMUL_ACC_IMPL` uses `aMatrix.GetValidRow()`, `aMatrix.GetValidCol()`, and `bMatrix.GetValidCol()` for `m/k/n`. + - `cInMatrix` is not validated by explicit assertions in the current implementations (target-defined behavior). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TMATMUL_ACC(c1, c0, a, b); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c0, 0x3000); + TASSIGN(c1, 0x4000); + TMATMUL_ACC(c1, c0, a, b); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%c_out = pto.tmatmul.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%c_out = pto.tmatmul.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%acc1 = tmatmul.acc %acc0, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tmatmul.acc ins(%c_in, %a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TMATMUL_ACC_zh.md b/designs/outerCube/PTOISA/TMATMUL_ACC_zh.md new file mode 100644 index 00000000..a7868230 --- /dev/null +++ b/designs/outerCube/PTOISA/TMATMUL_ACC_zh.md @@ -0,0 +1,120 @@ +# TMATMUL_ACC + +## 指令示意图 + +![TMATMUL_ACC tile operation](../figures/isa/TMATMUL_ACC.svg) + +## 简介 + +带累加器输入的矩阵乘法(融合累加)。 + +## 数学语义 + +Let: + +- `M = aMatrix.GetValidRow()` +- `K = aMatrix.GetValidCol()` +- `N = bMatrix.GetValidCol()` + +For `0 <= i < M` and `0 <= j < N`: + +$$ \mathrm{C1}_{i,j} = \mathrm{C0}_{i,j} + \sum_{k=0}^{K-1} \mathrm{A}_{i,k} \cdot \mathrm{B}_{k,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%acc1 = tmatmul.acc %acc0, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%c_out = pto.tmatmul.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmatmul.acc ins(%c_in, %a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%c_out = pto.tmatmul.acc %c_in, %a, %b : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmatmul.acc ins(%c_in, %a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMATMUL_ACC(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_ACC(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_ACC(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); +``` + +## 约束 + +- All constraints from `TMATMUL` apply to the `(cOutMatrix, aMatrix, bMatrix)` triple. +- **Implementation notes (A2A3/A5)**: + - `TMATMUL_ACC_IMPL` uses `aMatrix.GetValidRow()`, `aMatrix.GetValidCol()`, and `bMatrix.GetValidCol()` for `m/k/n`. + - `cInMatrix` is not validated by explicit assertions in the current implementations (target-defined behavior). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TMATMUL_ACC(c1, c0, a, b); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c0, c1; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c0, 0x3000); + TASSIGN(c1, 0x4000); + TMATMUL_ACC(c1, c0, a, b); +} +``` diff --git a/designs/outerCube/PTOISA/TMATMUL_BIAS.md b/designs/outerCube/PTOISA/TMATMUL_BIAS.md new file mode 100644 index 00000000..e3326e46 --- /dev/null +++ b/designs/outerCube/PTOISA/TMATMUL_BIAS.md @@ -0,0 +1,153 @@ +# TMATMUL_BIAS + + +## Tile Operation Diagram + +![TMATMUL_BIAS tile operation](../figures/isa/TMATMUL_BIAS.svg) + +## Introduction + +Matrix multiply with bias add. + +## Math Interpretation + +Let: + +- `M = aMatrix.GetValidRow()` +- `K = aMatrix.GetValidCol()` +- `N = bMatrix.GetValidCol()` + +For `0 <= i < M` and `0 <= j < N`: + +$$ \mathrm{C}_{i,j} = \sum_{k=0}^{K-1} \mathrm{A}_{i,k} \cdot \mathrm{B}_{k,j} + \mathrm{Bias}_{0,j} $$ + +Bias broadcasting behavior is implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%acc = tmatmul.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%c = pto.tmatmul.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmatmul.bias ins(%a, %b, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%c = pto.tmatmul.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tmatmul.bias ins(%a, %b, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMATMUL_BIAS(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, TileBias &biasData, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_BIAS(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, TileBias &biasData, WaitEvents &... events); +``` + +## Constraints + +- All constraints from `TMATMUL` apply to the `(cMatrix, aMatrix, bMatrix)` triple. +- **Bias constraints (A2A3)**: + - `TileBias::DType` must match `TileRes::DType`. + - `TileBias::Loc == TileType::Bias` and `TileBias::Rows == 1`. +- **Bias constraints (A5)**: + - `TileBias::DType` must match `TileRes::DType`. + - `TileBias::Loc == TileType::Bias`, `TileBias::Rows == 1`, and `TileBias::isRowMajor`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TMATMUL_BIAS(c, a, b, bias); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(bias, 0x3000); + TASSIGN(c, 0x4000); + TMATMUL_BIAS(c, a, b, bias); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%c = pto.tmatmul.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%c = pto.tmatmul.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%acc = tmatmul.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tmatmul.bias ins(%a, %b, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TMATMUL_BIAS_zh.md b/designs/outerCube/PTOISA/TMATMUL_BIAS_zh.md new file mode 100644 index 00000000..daa5bad0 --- /dev/null +++ b/designs/outerCube/PTOISA/TMATMUL_BIAS_zh.md @@ -0,0 +1,126 @@ +# TMATMUL_BIAS + +## 指令示意图 + +![TMATMUL_BIAS tile operation](../figures/isa/TMATMUL_BIAS.svg) + +## 简介 + +带偏置加法的矩阵乘法。 + +## 数学语义 + +Let: + +- `M = aMatrix.GetValidRow()` +- `K = aMatrix.GetValidCol()` +- `N = bMatrix.GetValidCol()` + +For `0 <= i < M` and `0 <= j < N`: + +$$ \mathrm{C}_{i,j} = \sum_{k=0}^{K-1} \mathrm{A}_{i,k} \cdot \mathrm{B}_{k,j} + \mathrm{Bias}_{0,j} $$ + +Bias broadcasting behavior is implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%acc = tmatmul.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%c = pto.tmatmul.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmatmul.bias ins(%a, %b, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%c = pto.tmatmul.bias %a, %b, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmatmul.bias ins(%a, %b, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMATMUL_BIAS(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, TileBias &biasData, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_BIAS(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, TileBias &biasData, WaitEvents &... events); +``` + +## 约束 + +- All constraints from `TMATMUL` apply to the `(cMatrix, aMatrix, bMatrix)` triple. +- **Bias constraints (A2A3)**: + - `TileBias::DType` must match `TileRes::DType`. + - `TileBias::Loc == TileType::Bias` and `TileBias::Rows == 1`. +- **Bias constraints (A5)**: + - `TileBias::DType` must match `TileRes::DType`. + - `TileBias::Loc == TileType::Bias`, `TileBias::Rows == 1`, and `TileBias::isRowMajor`. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TMATMUL_BIAS(c, a, b, bias); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + Bias bias; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(bias, 0x3000); + TASSIGN(c, 0x4000); + TMATMUL_BIAS(c, a, b, bias); +} +``` diff --git a/designs/outerCube/PTOISA/TMATMUL_MX.md b/designs/outerCube/PTOISA/TMATMUL_MX.md new file mode 100644 index 00000000..935d4c1e --- /dev/null +++ b/designs/outerCube/PTOISA/TMATMUL_MX.md @@ -0,0 +1,202 @@ +# TMATMUL_MX + + +## Tile Operation Diagram + +![TMATMUL_MX tile operation](../figures/isa/TMATMUL_MX.svg) + +## Introduction + +Matrix multiply (GEMM) with additional scaling tiles for mixed-precision / quantized matmul on supported targets. + +This instruction is currently implemented on A5 (see `include/pto/npu/a5/TMatmul.hpp`). + +## Math Interpretation + +Let: + +- `M = aMatrix.GetValidRow()` +- `K = aMatrix.GetValidCol()` +- `N = bMatrix.GetValidCol()` + +Conceptually, the result corresponds to a matrix multiply over the effective matmul domain (`0 <= i < M`, `0 <= j < N`), with the scaling tiles `aScaleMatrix` / `bScaleMatrix` configuring implementation-defined mixed-precision behavior: + +$$ \mathrm{C}_{i,j} = \sum_{k=0}^{K-1} \mathrm{A}_{i,k} \cdot \mathrm{B}_{k,j} $$ + +The exact role of `aScaleMatrix` / `bScaleMatrix` (and any dequant/quant semantics) is target-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous forms (conceptual): + +```text +%c = tmatmul.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c_out = tmatmul.mx.acc %c_in, %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c = tmatmul.mx.bias %a, %a_scale, %b, %b_scale, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%c = pto.tmatmul.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) +-> !pto.tile<...> +%c_out = pto.tmatmul.mx.acc %c_in, %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, +!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c = pto.tmatmul.mx.bias %a, %a_scale, %b, %b_scale, %bias : (!pto.tile<...>, !pto.tile<...>, +!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmatmul.mx ins(%a, %a_scale, %b, %b_scale : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) +outs(%c : !pto.tile_buf<...>) +pto.tmatmul.mx.acc ins(%c_in, %a, %a_scale, %b, %b_scale : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, +!pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +pto.tmatmul.mx.bias ins(%a, %a_scale, %b, %b_scale, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, +!pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%c = pto.tmatmul.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) +-> !pto.tile<...> +%c_out = pto.tmatmul.mx.acc %c_in, %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, +!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c = pto.tmatmul.mx.bias %a, %a_scale, %b, %b_scale, %bias : (!pto.tile<...>, !pto.tile<...>, +!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tmatmul.mx ins(%a, %a_scale, %b, %b_scale : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) +outs(%c : !pto.tile_buf<...>) +pto.tmatmul.mx.acc ins(%c_in, %a, %a_scale, %b, %b_scale : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, +!pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +pto.tmatmul.mx.bias ins(%a, %a_scale, %b, %b_scale, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, +!pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, TileBias &biasData, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, TileBias &biasData, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A5)**: + - `m/k/n` are taken from `aMatrix.GetValidRow()`, `aMatrix.GetValidCol()`, `bMatrix.GetValidCol()`. + - Static legality checks are enforced via `CheckMadMxValid<...>()` (types, shapes, fractals, and scaling tile legality). +- **Bias form**: + - `TileBias::DType` must be `float` and `TileBias::Loc == TileType::Bias` with `TileBias::Rows == 1` (A5 checks via `static_assert`). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using ScaleA = TileLeftScale; + using ScaleB = TileRightScale; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + ScaleA scaleA; + ScaleB scaleB; + Bias bias; + C c; + TMATMUL_MX(c, a, scaleA, b, scaleB, bias); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using ScaleA = TileLeftScale; + using ScaleB = TileRightScale; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + ScaleA scaleA; + ScaleB scaleB; + Bias bias; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(scaleA, GetScaleAddr(a.data())); + TASSIGN(scaleB, GetScaleAddr(b.data())); + TASSIGN(bias, 0x3000); + TASSIGN(c, 0x4000); + TMATMUL_MX(c, a, scaleA, b, scaleB, bias); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%c = pto.tmatmul.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%c = pto.tmatmul.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) +``` + +### PTO Assembly Form + +```text +%c = pto.tmatmul.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) +# AS Level 2 (DPS) +pto.tmatmul.mx ins(%a, %a_scale, %b, %b_scale : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TMATMUL_MX_zh.md b/designs/outerCube/PTOISA/TMATMUL_MX_zh.md new file mode 100644 index 00000000..738035e6 --- /dev/null +++ b/designs/outerCube/PTOISA/TMATMUL_MX_zh.md @@ -0,0 +1,173 @@ +# TMATMUL_MX + +## 指令示意图 + +![TMATMUL_MX tile operation](../figures/isa/TMATMUL_MX.svg) + +## 简介 + +带额外缩放 Tile 的矩阵乘法 (GEMM),用于支持目标上的混合精度/量化矩阵乘法。 + +## 数学语义 + +Let: + +- `M = aMatrix.GetValidRow()` +- `K = aMatrix.GetValidCol()` +- `N = bMatrix.GetValidCol()` + +Conceptually, the result corresponds to a matrix multiply over the effective matmul domain (`0 <= i < M`, `0 <= j < N`), with the scaling tiles `aScaleMatrix` / `bScaleMatrix` configuring implementation-defined mixed-precision behavior: + +$$ \mathrm{C}_{i,j} = \sum_{k=0}^{K-1} \mathrm{A}_{i,k} \cdot \mathrm{B}_{k,j} $$ + +The exact role of `aScaleMatrix` / `bScaleMatrix` (and any dequant/quant semantics) is target-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous forms (conceptual): + +```text +%c = tmatmul.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c_out = tmatmul.mx.acc %c_in, %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c = tmatmul.mx.bias %a, %a_scale, %b, %b_scale, %bias : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%c = pto.tmatmul.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) +-> !pto.tile<...> +%c_out = pto.tmatmul.mx.acc %c_in, %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, +!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c = pto.tmatmul.mx.bias %a, %a_scale, %b, %b_scale, %bias : (!pto.tile<...>, !pto.tile<...>, +!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmatmul.mx ins(%a, %a_scale, %b, %b_scale : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) +outs(%c : !pto.tile_buf<...>) +pto.tmatmul.mx.acc ins(%c_in, %a, %a_scale, %b, %b_scale : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, +!pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +pto.tmatmul.mx.bias ins(%a, %a_scale, %b, %b_scale, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, +!pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%c = pto.tmatmul.mx %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) +-> !pto.tile<...> +%c_out = pto.tmatmul.mx.acc %c_in, %a, %a_scale, %b, %b_scale : (!pto.tile<...>, !pto.tile<...>, +!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +%c = pto.tmatmul.mx.bias %a, %a_scale, %b, %b_scale, %bias : (!pto.tile<...>, !pto.tile<...>, +!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmatmul.mx ins(%a, %a_scale, %b, %b_scale : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) +outs(%c : !pto.tile_buf<...>) +pto.tmatmul.mx.acc ins(%c_in, %a, %a_scale, %b, %b_scale : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, +!pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c_out : !pto.tile_buf<...>) +pto.tmatmul.mx.bias ins(%a, %a_scale, %b, %b_scale, %bias : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, +!pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cOutMatrix, TileRes &cInMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, TileBias &biasData, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL_MX(TileRes &cMatrix, TileLeft &aMatrix, TileLeftScale &aScaleMatrix, TileRight &bMatrix, TileRightScale &bScaleMatrix, TileBias &biasData, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A5)**: + - `m/k/n` are taken from `aMatrix.GetValidRow()`, `aMatrix.GetValidCol()`, `bMatrix.GetValidCol()`. + - Static legality checks are enforced via `CheckMadMxValid<...>()` (types, shapes, fractals, and scaling tile legality). +- **Bias form**: + - `TileBias::DType` must be `float` and `TileBias::Loc == TileType::Bias` with `TileBias::Rows == 1` (A5 checks via `static_assert`). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using ScaleA = TileLeftScale; + using ScaleB = TileRightScale; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + ScaleA scaleA; + ScaleB scaleB; + Bias bias; + C c; + TMATMUL_MX(c, a, scaleA, b, scaleB, bias); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using ScaleA = TileLeftScale; + using ScaleB = TileRightScale; + using Bias = Tile; + using C = TileAcc; + A a; + B b; + ScaleA scaleA; + ScaleB scaleB; + Bias bias; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(scaleA, GetScaleAddr(a.data())); + TASSIGN(scaleB, GetScaleAddr(b.data())); + TASSIGN(bias, 0x3000); + TASSIGN(c, 0x4000); + TMATMUL_MX(c, a, scaleA, b, scaleB, bias); +} +``` diff --git a/designs/outerCube/PTOISA/TMATMUL_zh.md b/designs/outerCube/PTOISA/TMATMUL_zh.md new file mode 100644 index 00000000..dbe425a8 --- /dev/null +++ b/designs/outerCube/PTOISA/TMATMUL_zh.md @@ -0,0 +1,132 @@ +# TMATMUL + +## 指令示意图 + +![TMATMUL tile operation](../figures/isa/TMATMUL.svg) + +## 简介 + +矩阵乘法 (GEMM),生成累加器/输出 Tile。 + +## 数学语义 + +Let: + +- `M = aMatrix.GetValidRow()` +- `K = aMatrix.GetValidCol()` +- `N = bMatrix.GetValidCol()` + +For `0 <= i < M` and `0 <= j < N` (output elements in the effective matmul domain): + +$$ \mathrm{C}_{i,j} = \sum_{k=0}^{K-1} \mathrm{A}_{i,k} \cdot \mathrm{B}_{k,j} $$ + +Exact accumulator behavior and datatype promotion are target/implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%acc = tmatmul %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%c = pto.tmatmul %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmatmul ins(%a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%c = pto.tmatmul %a, %b : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmatmul ins(%a, %b : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%c : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMATMUL(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); + +template +PTO_INST RecordEvent TMATMUL(TileRes &cMatrix, TileLeft &aMatrix, TileRight &bMatrix, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - Supported `(CType, AType, BType)` triples: + - `(int32_t, int8_t, int8_t)` + - `(float, half, half)` + - `(float, float, float)` + - `(float, bfloat16_t, bfloat16_t)` + - Static shape constraints: `TileLeft::Rows == TileRes::Rows`, `TileLeft::Cols == TileRight::Rows`, `TileRight::Cols == TileRes::Cols`. + - Tile locations: `TileLeft::Loc == Left`, `TileRight::Loc == Right`, `TileRes::Loc == Acc`. + - Runtime: `m/k/n` (taken from `aMatrix.GetValidRow()`, `aMatrix.GetValidCol()`, `bMatrix.GetValidCol()`) must be in `[1, 4095]`. +- **实现检查 (A5)**: + - Accumulator type must be `int32_t` or `float`. + - If `int32_t`: `AType == int8_t` and `BType == int8_t`. + - If `float`: supports `half/bfloat16_t/float` and selected fp8 pairs (target-defined). + - Static shape constraints: `TileLeft::Rows == TileRes::Rows`, `TileLeft::Cols == TileRight::Rows`, `TileRight::Cols == TileRes::Cols`. + - Fractal/layout constraints are enforced: + - Left: `Loc == Left`, `!isRowMajor`, `SFractal == RowMajor` + - Right: `Loc == Right`, `isRowMajor`, `SFractal == ColMajor` + - Acc: `Loc == Acc`, `!isRowMajor`, `SFractal == RowMajor` + - Runtime: `m/k/n` (taken from `aMatrix.GetValidRow()`, `aMatrix.GetValidCol()`, `bMatrix.GetValidCol()`) must be in `[1, 4095]`. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c; + TMATMUL(c, a, b); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using A = TileLeft; + using B = TileRight; + using C = TileAcc; + A a; + B b; + C c; + TASSIGN(a, 0x1000); + TASSIGN(b, 0x2000); + TASSIGN(c, 0x3000); + TMATMUL(c, a, b); +} +``` diff --git a/designs/outerCube/PTOISA/TMAX.md b/designs/outerCube/PTOISA/TMAX.md new file mode 100644 index 00000000..b46691b1 --- /dev/null +++ b/designs/outerCube/PTOISA/TMAX.md @@ -0,0 +1,135 @@ +# TMAX + + +## Tile Operation Diagram + +![TMAX tile operation](../figures/isa/TMAX.svg) + +## Introduction + +Elementwise maximum of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \max(\mathrm{src0}_{i,j}, \mathrm{src1}_{i,j}) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tmax %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMAX(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `uint32_t`, `int32_t`, `uint16_t`, `int16_t`, `uint8_t`, `int8_t`, `float`, `half`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src0/src1` are assumed to be compatible (not validated by explicit runtime checks in this op). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TMAX(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TMAX(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tmax %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TMAXS.md b/designs/outerCube/PTOISA/TMAXS.md new file mode 100644 index 00000000..268aa2ff --- /dev/null +++ b/designs/outerCube/PTOISA/TMAXS.md @@ -0,0 +1,104 @@ +# TMAXS + + +## Tile Operation Diagram + +![TMAXS tile operation](../figures/isa/TMAXS.svg) + +## Introduction + +Elementwise max of a tile and a scalar: `max(src, scalar)`. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \max(\mathrm{src}_{i,j}, \mathrm{scalar}) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tmaxs %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmaxs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmaxs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMAXS(TileDataDst& dst, TileDataSrc& src, typename TileDataSrc::DType scalar, WaitEvents&... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `int32_t`, `uint32_t`, `float`, `int16_t`, `uint16_t`, `half`, `bfloat16_t`, `uint8_t`, `int8_t`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Common constraints**: + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `dst` and `src` must have the same valid row/col. + - Scalar type must match the Tile data type. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TMAXS(out, x, 0.0f); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tmaxs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmaxs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tmaxs %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tmaxs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TMAXS_zh.md b/designs/outerCube/PTOISA/TMAXS_zh.md new file mode 100644 index 00000000..a77d73c8 --- /dev/null +++ b/designs/outerCube/PTOISA/TMAXS_zh.md @@ -0,0 +1,104 @@ +# TMAXS + +## 指令示意图 + +![TMAXS tile operation](../figures/isa/TMAXS.svg) + +## 简介 + +Tile 与标量的逐元素最大值:`max(src, scalar)`。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \max(\mathrm{src}_{i,j}, \mathrm{scalar}) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tmaxs %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tmaxs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmaxs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMAXS(TileDataDst& dst, TileDataSrc& src, typename TileDataSrc::DType scalar, WaitEvents&... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` 必须是以下之一:`int32_t`、`int16_t`、`half`、`float`。 + - Tile 布局必须是行主序(`TileData::isRowMajor`)。 +- **实现检查 (A5)**: + - `TileData::DType` 必须是以下之一:`int32_t`、`uint32_t`、`float`、`int16_t`、`uint16_t`、`half`、`bfloat16_t`、`uint8_t`、`int8_t`。 + - Tile 布局必须是行主序(`TileData::isRowMajor`)。 +- **通用约束**: + - Tile 位置必须是向量(`TileData::Loc == TileType::Vec`)。 + - 静态有效边界:`TileData::ValidRow <= TileData::Rows` 且 `TileData::ValidCol <= TileData::Cols`。 + - 运行时:`dst` 和 `src` 的有效行列数必须相同。 + - 标量类型必须与 Tile 数据类型一致。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TMAXS(out, x, 0.0f); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tmaxs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmaxs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tmaxs %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tmaxs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TMAX_zh.md b/designs/outerCube/PTOISA/TMAX_zh.md new file mode 100644 index 00000000..ee11f117 --- /dev/null +++ b/designs/outerCube/PTOISA/TMAX_zh.md @@ -0,0 +1,108 @@ +# TMAX + +## 指令示意图 + +![TMAX tile operation](../figures/isa/TMAX.svg) + +## 简介 + +两个 Tile 的逐元素最大值。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \max(\mathrm{src0}_{i,j}, \mathrm{src1}_{i,j}) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tmax %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMAX(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `uint32_t`, `int32_t`, `uint16_t`, `int16_t`, `uint8_t`, `int8_t`, `float`, `half`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src0/src1` are assumed to be compatible (not validated by explicit runtime checks in this op). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TMAX(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TMAX(dst, src0, src1); +} +``` diff --git a/designs/outerCube/PTOISA/TMIN.md b/designs/outerCube/PTOISA/TMIN.md new file mode 100644 index 00000000..5feca244 --- /dev/null +++ b/designs/outerCube/PTOISA/TMIN.md @@ -0,0 +1,135 @@ +# TMIN + + +## Tile Operation Diagram + +![TMIN tile operation](../figures/isa/TMIN.svg) + +## Introduction + +Elementwise minimum of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \min(\mathrm{src0}_{i,j}, \mathrm{src1}_{i,j}) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tmin %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMIN(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `uint32_t`, `int32_t`, `uint16_t`, `int16_t`, `uint8_t`, `int8_t`, `float`, `half`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src0/src1` are assumed to be compatible (not validated by explicit runtime checks in this op). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TMIN(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TMIN(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tmin %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TMINS.md b/designs/outerCube/PTOISA/TMINS.md new file mode 100644 index 00000000..0ded1cc5 --- /dev/null +++ b/designs/outerCube/PTOISA/TMINS.md @@ -0,0 +1,121 @@ +# TMINS + + +## Tile Operation Diagram + +![TMINS tile operation](../figures/isa/TMINS.svg) + +## Introduction + +Elementwise minimum of a tile and a scalar. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \min(\mathrm{src}_{i,j}, \mathrm{scalar}) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tmins %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmins %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmins ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMINS(TileDataDst &dst, TileDataSrc &src, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int`, `int16_t`, `half`, `float16_t`, `float`, `float32_t`. + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`. +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `float`, `bfloat16_t`. + - Runtime: `src.GetValidCol() == dst.GetValidCol()`. +- **Common constraints**: + - `dst` and `src` must use the same element type. + - Scalar type must match the tile data type. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TMINS(dst, src, 0.0f); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TMINS(dst, src, 0.0f); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tmins %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmins %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tmins %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tmins ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TMINS_zh.md b/designs/outerCube/PTOISA/TMINS_zh.md new file mode 100644 index 00000000..c53c5ddb --- /dev/null +++ b/designs/outerCube/PTOISA/TMINS_zh.md @@ -0,0 +1,121 @@ +# TMINS + +## 指令示意图 + +![TMINS tile operation](../figures/isa/TMINS.svg) + +## 简介 + +Tile 与标量的逐元素最小值。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \min(\mathrm{src}_{i,j}, \mathrm{scalar}) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tmins %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tmins %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmins ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMINS(TileDataDst &dst, TileDataSrc &src, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` 必须是以下之一:`int32_t`、`int`、`int16_t`、`half`、`float16_t`、`float`、`float32_t`。 + - 运行时:`src.GetValidRow() == dst.GetValidRow()` 且 `src.GetValidCol() == dst.GetValidCol()`。 +- **实现检查 (A5)**: + - `TileData::DType` 必须是以下之一:`uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t`、`int32_t`、`half`、`float`、`bfloat16_t`。 + - 运行时:`src.GetValidCol() == dst.GetValidCol()`。 +- **通用约束**: + - `dst` 和 `src` 必须使用相同的元素类型。 + - 标量类型必须与 Tile 数据类型一致。 + - Tile 位置必须是向量(`TileData::Loc == TileType::Vec`)。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TMINS(dst, src, 0.0f); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TMINS(dst, src, 0.0f); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tmins %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmins %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tmins %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tmins ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TMIN_zh.md b/designs/outerCube/PTOISA/TMIN_zh.md new file mode 100644 index 00000000..33ecee2e --- /dev/null +++ b/designs/outerCube/PTOISA/TMIN_zh.md @@ -0,0 +1,108 @@ +# TMIN + +## 指令示意图 + +![TMIN tile operation](../figures/isa/TMIN.svg) + +## 简介 + +两个 Tile 的逐元素最小值。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \min(\mathrm{src0}_{i,j}, \mathrm{src1}_{i,j}) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tmin %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMIN(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `uint32_t`, `int32_t`, `uint16_t`, `int16_t`, `uint8_t`, `int8_t`, `float`, `half`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src0/src1` are assumed to be compatible (not validated by explicit runtime checks in this op). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TMIN(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TMIN(dst, src0, src1); +} +``` diff --git a/designs/outerCube/PTOISA/TMOV.md b/designs/outerCube/PTOISA/TMOV.md new file mode 100644 index 00000000..6fa16879 --- /dev/null +++ b/designs/outerCube/PTOISA/TMOV.md @@ -0,0 +1,161 @@ +# TMOV + + +## Tile Operation Diagram + +![TMOV tile operation](../figures/isa/TMOV.svg) + +## Introduction + +Move/copy between tiles, optionally applying implementation-defined conversion modes selected by template parameters and overloads. + +`TMOV` is used for: + +- Vec -> Vec moves +- Mat -> Left/Right/Bias/Scaling/Scale(Microscaling) moves (target-dependent) +- Acc -> Vec moves (target-dependent) + +## Math Interpretation + +Conceptually copies or transforms elements from `src` into `dst` over the valid region. Exact transformation depends on the selected mode and target. + +For the pure copy case: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +The PTO AS design recommends splitting `TMOV` into a family of ops: + +```text +%left = tmov.m2l %mat : !pto.tile<...> -> !pto.tile<...> +%right = tmov.m2r %mat : !pto.tile<...> -> !pto.tile<...> +%bias = tmov.m2b %mat : !pto.tile<...> -> !pto.tile<...> +%scale = tmov.m2s %mat : !pto.tile<...> -> !pto.tile<...> +%vec = tmov.a2v %acc : !pto.tile<...> -> !pto.tile<...> +%v1 = tmov.v2v %v0 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmov.s2d %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmov ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp` and `include/pto/common/constants.hpp`: + +```cpp +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, FpTileData &fp, WaitEvents &... events); + +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, uint64_t preQuantScalar, WaitEvents &... events); + +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, uint64_t preQuantScalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Shape rules: + - Shapes must match: `SrcTileData::Rows == DstTileData::Rows` and `SrcTileData::Cols == DstTileData::Cols`. + - Supported location pairs (compile-time checked): + - `Mat -> Left/Right/Bias/Scaling` + - `Vec -> Vec` + - `Acc -> Mat` + - Additional checks by path: + - `Acc -> Mat`: additional fractal and dtype constraints are enforced (for example, `Acc` uses an NZ-like fractal, `Mat` uses a 512B fractal, and only specific dtype conversions are allowed). +- **Implementation checks (A5)**: + - Shape rules: + - For `Mat -> Left/Right/Bias/Scaling/Scale`, shapes must match. + - For `Vec -> Vec` and `Vec -> Mat`, the effective copy region may be determined by the valid rows/cols of source and destination. + - Supported location pairs include (target-dependent): + - `Mat -> Left/Right/Bias/Scaling/Scale` + - `Vec -> Vec/Mat` + - `Acc -> Vec/Mat` + - `Acc -> Vec` supports additional `AccToVecMode` forms; some forms also take `FpTileData` or `preQuantScalar`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TMOV(dst, src); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = TileLeft; + SrcT mat; + DstT left; + TASSIGN(mat, 0x1000); + TASSIGN(left, 0x2000); + TMOV(left, mat); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tmov.s2d %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmov.s2d %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.tmov.s2d %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tmov ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TMOV_FP.md b/designs/outerCube/PTOISA/TMOV_FP.md new file mode 100644 index 00000000..620431ff --- /dev/null +++ b/designs/outerCube/PTOISA/TMOV_FP.md @@ -0,0 +1,141 @@ +# TMOV_FP + + +## Tile Operation Diagram + +![TMOV_FP tile operation](../figures/isa/TMOV_FP.svg) + +## Introduction + +Move/convert from an accumulator tile into a destination tile, using a scaling (`fp`) tile for vector quantization parameters. + +`TMOV_FP` is a named wrapper around the `TMOV_IMPL(..., fp)` path and is part of the `TMOV` family (see `docs/isa/TMOV.md`). + +## Math Interpretation + +Conceptually converts each element using an implementation-defined quantization/dequantization configuration derived from `fp`: + +$$ \mathrm{dst}_{i,j} = \mathrm{Convert}\!\left(\mathrm{src}_{i,j};\ \mathrm{fp}\right) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tmov.fp %src, %fp : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmov.fp %src, %fp : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmov.fp ins(%src, %fp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tmov.fp %src, %fp : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tmov.fp ins(%src, %fp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp` and `include/pto/common/constants.hpp`: + +```cpp +template +PTO_INST RecordEvent TMOV_FP(DstTileData &dst, SrcTileData &src, FpTileData &fp, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - The fp path is only supported for accumulator conversion and is validated by internal compile-time checks in `TMOV_IMPL(dst, src, fp)`. + - `FpTileData::Loc` must be `TileType::Scaling` (`static_assert`). +- **Implementation checks (A5)**: + - Validated by `CheckTMovAccValid(...)` and related compile-time checks in `TMOV_IMPL(dst, src, fp)`. + - `FpTileData::Loc` must be `TileType::Scaling` (`static_assert`). + - Destination location is target-dependent (`Vec` or `Mat` are supported in the fp path). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using AccT = TileAcc; + using DstT = Tile; + using FpT = Tile; + + AccT acc; + DstT dst; + FpT fp; + TMOV_FP(dst, acc, fp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using AccT = TileAcc; + using DstT = Tile; + using FpT = Tile; + + AccT acc; + DstT dst; + FpT fp; + TASSIGN(acc, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(fp, 0x3000); + TMOV_FP(dst, acc, fp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tmov.fp %src, %fp : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmov.fp %src, %fp : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tmov.fp %src, %fp : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tmov.fp ins(%src, %fp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TMOV_FP_zh.md b/designs/outerCube/PTOISA/TMOV_FP_zh.md new file mode 100644 index 00000000..28ed5751 --- /dev/null +++ b/designs/outerCube/PTOISA/TMOV_FP_zh.md @@ -0,0 +1,112 @@ +# TMOV_FP + +## 指令示意图 + +![TMOV_FP tile operation](../figures/isa/TMOV_FP.svg) + +## 简介 + +使用缩放 (`fp`) Tile 作为向量量化参数,将累加器 Tile 移动/转换到目标 Tile。 + +## 数学语义 + +Conceptually converts each element using an implementation-defined quantization/dequantization configuration derived from `fp`: + +$$ \mathrm{dst}_{i,j} = \mathrm{Convert}\!\left(\mathrm{src}_{i,j};\ \mathrm{fp}\right) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tmov.fp %src, %fp : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmov.fp %src, %fp : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmov.fp ins(%src, %fp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tmov.fp %src, %fp : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmov.fp ins(%src, %fp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp` and `include/pto/common/constants.hpp`: + +```cpp +template +PTO_INST RecordEvent TMOV_FP(DstTileData &dst, SrcTileData &src, FpTileData &fp, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - The fp path is only supported for accumulator conversion and is validated by internal compile-time checks in `TMOV_IMPL(dst, src, fp)`. + - `FpTileData::Loc` must be `TileType::Scaling` (`static_assert`). +- **实现检查 (A5)**: + - Validated by `CheckTMovAccValid(...)` and related compile-time checks in `TMOV_IMPL(dst, src, fp)`. + - `FpTileData::Loc` must be `TileType::Scaling` (`static_assert`). + - Destination location is target-dependent (`Vec` or `Mat` are supported in the fp path). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using AccT = TileAcc; + using DstT = Tile; + using FpT = Tile; + + AccT acc; + DstT dst; + FpT fp; + TMOV_FP(dst, acc, fp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using AccT = TileAcc; + using DstT = Tile; + using FpT = Tile; + + AccT acc; + DstT dst; + FpT fp; + TASSIGN(acc, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(fp, 0x3000); + TMOV_FP(dst, acc, fp); +} +``` diff --git a/designs/outerCube/PTOISA/TMOV_zh.md b/designs/outerCube/PTOISA/TMOV_zh.md new file mode 100644 index 00000000..92f4edd2 --- /dev/null +++ b/designs/outerCube/PTOISA/TMOV_zh.md @@ -0,0 +1,161 @@ +# TMOV + +## 指令示意图 + +![TMOV tile operation](../figures/isa/TMOV.svg) + +## 简介 + +在 Tile 之间移动/复制,可选通过模板参数和重载选择实现定义的转换模式。 + +`TMOV` 用于: + +- Vec -> Vec 移动 +- Mat -> Left/Right/Bias/Scaling/Scale(微缩放)移动(取决于目标) +- Acc -> Vec 移动(取决于目标) + +## 数学语义 + +概念上在有效区域上将元素从 `src` 复制或转换到 `dst`。确切的转换取决于所选模式和目标。 + +对于纯复制情况: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +PTO AS 设计建议将 `TMOV` 拆分为一系列操作: + +```text +%left = tmov.m2l %mat : !pto.tile<...> -> !pto.tile<...> +%right = tmov.m2r %mat : !pto.tile<...> -> !pto.tile<...> +%bias = tmov.m2b %mat : !pto.tile<...> -> !pto.tile<...> +%scale = tmov.m2s %mat : !pto.tile<...> -> !pto.tile<...> +%vec = tmov.a2v %acc : !pto.tile<...> -> !pto.tile<...> +%v1 = tmov.v2v %v0 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tmov.s2d %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmov ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp` 和 `include/pto/common/constants.hpp`: + +```cpp +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, FpTileData &fp, WaitEvents &... events); + +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, uint64_t preQuantScalar, WaitEvents &... events); + +template +PTO_INST RecordEvent TMOV(DstTileData &dst, SrcTileData &src, uint64_t preQuantScalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - 形状规则: + - 形状必须匹配:`SrcTileData::Rows == DstTileData::Rows` 且 `SrcTileData::Cols == DstTileData::Cols`。 + - 支持的位置对(编译时检查): + - `Mat -> Left/Right/Bias/Scaling` + - `Vec -> Vec` + - `Acc -> Mat` + - 按路径附加检查如下: + - `Acc -> Mat`:会额外检查分形与数据类型约束(例如 `Acc` 使用类 NZ 分形,`Mat` 使用 512B 分形,且仅允许特定的数据类型转换)。 +- **实现检查 (A5)**: + - 形状规则: + - 对于 `Mat -> Left/Right/Bias/Scaling/Scale`,形状必须匹配。 + - 对于 `Vec -> Vec` 和 `Vec -> Mat`,实际复制区域可能由源和目的的有效行/列共同决定。 + - 支持的位置对包括(取决于目标): + - `Mat -> Left/Right/Bias/Scaling/Scale` + - `Vec -> Vec/Mat` + - `Acc -> Vec/Mat` + - `Acc -> Vec` 还支持额外的 `AccToVecMode` 形式;其中部分形式还会结合 `FpTileData` 或 `preQuantScalar` 使用。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TMOV(dst, src); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = TileLeft; + SrcT mat; + DstT left; + TASSIGN(mat, 0x1000); + TASSIGN(left, 0x2000); + TMOV(left, mat); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tmov.s2d %src : !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmov.s2d %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = pto.tmov.s2d %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tmov ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TMRGSORT.md b/designs/outerCube/PTOISA/TMRGSORT.md new file mode 100644 index 00000000..f76f1889 --- /dev/null +++ b/designs/outerCube/PTOISA/TMRGSORT.md @@ -0,0 +1,155 @@ +# TMRGSORT + + +## Tile Operation Diagram + +![TMRGSORT tile operation](../figures/isa/TMRGSORT.svg) + +## Introduction + +Merge sort for multiple sorted lists (implementation-defined element format and layout). + +## Math Interpretation + +Merges sorted input lists into `dst`. Ordering, element format (e.g., value/index pairs), and the meaning of executed counts depend on the implementation. + +$$ \mathrm{dst} = \mathrm{merge}(\mathrm{src}_0, \mathrm{src}_1, \ldots) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form (conceptual): + +```text +%dst, %executed = tmrgsort %src0, %src1 {exhausted = false} + : !pto.tile<...>, !pto.tile<...> -> (!pto.tile<...>, vector<4xi16>) +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmrgsort %src, %blockLen : (!pto.tile<...>, dtype) -> !pto.tile<...> +%dst, %executed = pto.tmrgsort %src0, %src1, %src2, %src3 {exhausted = false} + : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> (!pto.tile<...>, vector<4xi16>) +``` + +### AS Level 2 (DPS) + +```text +pto.tmrgsort ins(%src, %blockLen : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +pto.tmrgsort ins(%src0, %src1, %src2, %src3 {exhausted = false} : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) +outs(%dst, %executed : !pto.tile_buf<...>, vector<4xi16>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tmrgsort %src, %blockLen : (!pto.tile<...>, dtype) -> !pto.tile<...> +%dst, %executed = pto.tmrgsort %src0, %src1, %src2, %src3 {exhausted = false} + : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> (!pto.tile<...>, vector<4xi16>) +``` + +### IR Level 2 (DPS) + +```text +pto.tmrgsort ins(%src, %blockLen : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +pto.tmrgsort ins(%src0, %src1, %src2, %src3 {exhausted = false} : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) +outs(%dst, %executed : !pto.tile_buf<...>, vector<4xi16>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMRGSORT(DstTileData &dst, MrgSortExecutedNumList &executedNumList, TmpTileData &tmp, Src0TileData &src0, Src1TileData &src1, Src2TileData &src2, Src3TileData &src3, WaitEvents &... events); + +template +PTO_INST RecordEvent TMRGSORT(DstTileData &dst, MrgSortExecutedNumList &executedNumList, TmpTileData &tmp, Src0TileData &src0, Src1TileData &src1, Src2TileData &src2, WaitEvents &... events); + +template +PTO_INST RecordEvent TMRGSORT(DstTileData &dst, MrgSortExecutedNumList &executedNumList, TmpTileData &tmp, Src0TileData &src0, Src1TileData &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TMRGSORT(DstTileData &dst, SrcTileData &src, uint32_t blockLen, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3/A5)**: + - Element type must be `half` or `float` and must match across `dst/tmp/src*` tiles. + - All tiles must be `TileType::Vec`, row-major, and have `Rows == 1` (list stored in a single row). + - UB memory usage is checked (compile-time and runtime) against target limits (single `Cols` across inputs plus `tmp`/`dst`). +- **Single-list variant (`TMRGSORT(dst, src, blockLen)`)**: + - `blockLen` must be a multiple of 64 (as checked by the implementation). + - `src.GetValidCol()` must be an integer multiple of `blockLen * 4`. + - `repeatTimes = src.GetValidCol() / (blockLen * 4)` must be in `[1, 255]`. +- **Multi-list variants**: + - `tmp` is required and `executedNumList` is written by the implementation; supported list counts and exact semantics are target-defined. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TMRGSORT(dst, src, /*blockLen=*/64); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TMRGSORT(dst, src, /*blockLen=*/64); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tmrgsort %src, %blockLen : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmrgsort %src, %blockLen : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.tmrgsort %src, %blockLen : (!pto.tile<...>, dtype) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tmrgsort ins(%src, %blockLen : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TMRGSORT_zh.md b/designs/outerCube/PTOISA/TMRGSORT_zh.md new file mode 100644 index 00000000..1b3dcc36 --- /dev/null +++ b/designs/outerCube/PTOISA/TMRGSORT_zh.md @@ -0,0 +1,128 @@ +# TMRGSORT + +## 指令示意图 + +![TMRGSORT tile operation](../figures/isa/TMRGSORT.svg) + +## 简介 + +用于多个已排序列表的归并排序(实现定义的元素格式和布局)。 + +## 数学语义 + +Merges sorted input lists into `dst`. Ordering, element format (e.g., value/index pairs), and the meaning of executed counts depend on the implementation. + +$$ \mathrm{dst} = \mathrm{merge}(\mathrm{src}_0, \mathrm{src}_1, \ldots) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form (conceptual): + +```text +%dst, %executed = tmrgsort %src0, %src1 {exhausted = false} + : !pto.tile<...>, !pto.tile<...> -> (!pto.tile<...>, vector<4xi16>) +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmrgsort %src, %blockLen : (!pto.tile<...>, dtype) -> !pto.tile<...> +%dst, %executed = pto.tmrgsort %src0, %src1, %src2, %src3 {exhausted = false} + : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> (!pto.tile<...>, vector<4xi16>) +``` + +### AS Level 2 (DPS) + +```text +pto.tmrgsort ins(%src, %blockLen : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +pto.tmrgsort ins(%src0, %src1, %src2, %src3 {exhausted = false} : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) +outs(%dst, %executed : !pto.tile_buf<...>, vector<4xi16>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tmrgsort %src, %blockLen : (!pto.tile<...>, dtype) -> !pto.tile<...> +%dst, %executed = pto.tmrgsort %src0, %src1, %src2, %src3 {exhausted = false} + : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> (!pto.tile<...>, vector<4xi16>) +``` + +### AS Level 2(DPS) + +```text +pto.tmrgsort ins(%src, %blockLen : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +pto.tmrgsort ins(%src0, %src1, %src2, %src3 {exhausted = false} : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) +outs(%dst, %executed : !pto.tile_buf<...>, vector<4xi16>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMRGSORT(DstTileData &dst, MrgSortExecutedNumList &executedNumList, TmpTileData &tmp, Src0TileData &src0, Src1TileData &src1, Src2TileData &src2, Src3TileData &src3, WaitEvents &... events); + +template +PTO_INST RecordEvent TMRGSORT(DstTileData &dst, MrgSortExecutedNumList &executedNumList, TmpTileData &tmp, Src0TileData &src0, Src1TileData &src1, Src2TileData &src2, WaitEvents &... events); + +template +PTO_INST RecordEvent TMRGSORT(DstTileData &dst, MrgSortExecutedNumList &executedNumList, TmpTileData &tmp, Src0TileData &src0, Src1TileData &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TMRGSORT(DstTileData &dst, SrcTileData &src, uint32_t blockLen, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3/A5)**: + - Element type must be `half` or `float` and must match across `dst/tmp/src*` tiles. + - All tiles must be `TileType::Vec`, row-major, and have `Rows == 1` (list stored in a single row). + - UB memory usage is checked (compile-time and runtime) against target limits (single `Cols` across inputs plus `tmp`/`dst`). +- **Single-list variant (`TMRGSORT(dst, src, blockLen)`)**: + - `blockLen` must be a multiple of 64 (as checked by the implementation). + - `src.GetValidCol()` must be an integer multiple of `blockLen * 4`. + - `repeatTimes = src.GetValidCol() / (blockLen * 4)` must be in `[1, 255]`. +- **Multi-list variants**: + - `tmp` is required and `executedNumList` is written by the implementation; supported list counts and exact semantics are target-defined. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TMRGSORT(dst, src, /*blockLen=*/64); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TMRGSORT(dst, src, /*blockLen=*/64); +} +``` diff --git a/designs/outerCube/PTOISA/TMUL.md b/designs/outerCube/PTOISA/TMUL.md new file mode 100644 index 00000000..b4cfc637 --- /dev/null +++ b/designs/outerCube/PTOISA/TMUL.md @@ -0,0 +1,135 @@ +# TMUL + + +## Tile Operation Diagram + +![TMUL tile operation](../figures/isa/TMUL.svg) + +## Introduction + +Elementwise multiply of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \cdot \mathrm{src1}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tmul %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmul %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tmul %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMUL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `int32_t`, `uint32_t`, `float`, `int16_t`, `uint16_t`, `half`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; . + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TMUL(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TMUL(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tmul %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmul %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tmul %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TMULS.md b/designs/outerCube/PTOISA/TMULS.md new file mode 100644 index 00000000..c163afb3 --- /dev/null +++ b/designs/outerCube/PTOISA/TMULS.md @@ -0,0 +1,134 @@ +# TMULS + + +## Tile Operation Diagram + +![TMULS tile operation](../figures/isa/TMULS.svg) + +## Introduction + +Elementwise multiply a tile by a scalar. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \cdot \mathrm{scalar} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tmuls %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmuls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmuls ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tmuls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tmuls ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMULS(TileDataDst &dst, TileDataSrc &src0, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int`, `int16_t`, `half`, `float16_t`, `float`, `float32_t`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `float`, `bfloat16_t`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidCol() == dst.GetValidCol()`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TMULS(dst, src, 2.0f); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TMULS(dst, src, 2.0f); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tmuls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tmuls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tmuls %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tmuls ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TMULS_zh.md b/designs/outerCube/PTOISA/TMULS_zh.md new file mode 100644 index 00000000..20425d19 --- /dev/null +++ b/designs/outerCube/PTOISA/TMULS_zh.md @@ -0,0 +1,107 @@ +# TMULS + +## 指令示意图 + +![TMULS tile operation](../figures/isa/TMULS.svg) + +## 简介 + +Tile 与标量的逐元素乘法。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \cdot \mathrm{scalar} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tmuls %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmuls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmuls ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tmuls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmuls ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMULS(TileDataDst &dst, TileDataSrc &src0, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int`, `int16_t`, `half`, `float16_t`, `float`, `float32_t`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `float`, `bfloat16_t`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0.GetValidCol() == dst.GetValidCol()`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TMULS(dst, src, 2.0f); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TMULS(dst, src, 2.0f); +} +``` diff --git a/designs/outerCube/PTOISA/TMUL_zh.md b/designs/outerCube/PTOISA/TMUL_zh.md new file mode 100644 index 00000000..14b6351a --- /dev/null +++ b/designs/outerCube/PTOISA/TMUL_zh.md @@ -0,0 +1,108 @@ +# TMUL + +## 指令示意图 + +![TMUL tile operation](../figures/isa/TMUL.svg) + +## 简介 + +两个 Tile 的逐元素乘法。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \cdot \mathrm{src1}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tmul %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tmul %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tmul %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TMUL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `int32_t`, `uint32_t`, `float`, `int16_t`, `uint16_t`, `half`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; . + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TMUL(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TMUL(dst, src0, src1); +} +``` diff --git a/designs/outerCube/PTOISA/TNEG.md b/designs/outerCube/PTOISA/TNEG.md new file mode 100644 index 00000000..c29ae21b --- /dev/null +++ b/designs/outerCube/PTOISA/TNEG.md @@ -0,0 +1,103 @@ +# TNEG + + +## Tile Operation Diagram + +![TNEG tile operation](../figures/isa/TNEG.svg) + +## Introduction + +Elementwise negation of a tile. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = -\mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tneg %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tneg %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tneg ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tneg %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tneg ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TNEG(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## Constraints + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TNEG(out, x); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tneg %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tneg %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tneg %src : !pto.tile<...> +# AS Level 2 (DPS) +pto.tneg ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TNEG_zh.md b/designs/outerCube/PTOISA/TNEG_zh.md new file mode 100644 index 00000000..ae533466 --- /dev/null +++ b/designs/outerCube/PTOISA/TNEG_zh.md @@ -0,0 +1,76 @@ +# TNEG + +## 指令示意图 + +![TNEG tile operation](../figures/isa/TNEG.svg) + +## 简介 + +Tile 的逐元素取负。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = -\mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tneg %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tneg %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tneg ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tneg %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tneg ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TNEG(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## 约束 + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TNEG(out, x); +} +``` diff --git a/designs/outerCube/PTOISA/TNOT.md b/designs/outerCube/PTOISA/TNOT.md new file mode 100644 index 00000000..84c439fd --- /dev/null +++ b/designs/outerCube/PTOISA/TNOT.md @@ -0,0 +1,119 @@ +# TNOT + + +## Tile Operation Diagram + +![TNOT tile operation](../figures/isa/TNOT.svg) + +## Introduction + +Elementwise bitwise NOT of a tile. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \sim\mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tnot %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tnot %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tnot ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tnot %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tnot ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TNOT(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## Constraints + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int16_t`, `uint16_t`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src` and `dst` tiles should have the same `validRow/validCol`. +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `uint32_t`, `int32_t`, `uint16_t`, `int16_t`, `uint8_t`, `int8_t`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src` and `dst` tiles should have the same `validRow/validCol`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src/dst` are assumed to be compatible (not validated by explicit runtime checks in this op). + + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TNOT(out, x); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tnot %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tnot %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tnot %src : !pto.tile<...> +# AS Level 2 (DPS) +pto.tnot ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TNOT_zh.md b/designs/outerCube/PTOISA/TNOT_zh.md new file mode 100644 index 00000000..3c88eabb --- /dev/null +++ b/designs/outerCube/PTOISA/TNOT_zh.md @@ -0,0 +1,89 @@ +# TNOT + +## 指令示意图 + +![TNOT tile operation](../figures/isa/TNOT.svg) + +## 简介 + +Tile 的逐元素按位取反。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \sim\mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tnot %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tnot %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tnot ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tnot %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tnot ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TNOT(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int16_t`, `uint16_t`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src` and `dst` tiles should have the same `validRow/validCol`. +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `uint32_t`, `int32_t`, `uint16_t`, `int16_t`, `uint8_t`, `int8_t`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src` and `dst` tiles should have the same `validRow/validCol`. +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src/dst` are assumed to be compatible (not validated by explicit runtime checks in this op). + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TNOT(out, x); +} +``` diff --git a/designs/outerCube/PTOISA/TOR.md b/designs/outerCube/PTOISA/TOR.md new file mode 100644 index 00000000..ece1123c --- /dev/null +++ b/designs/outerCube/PTOISA/TOR.md @@ -0,0 +1,103 @@ +# TOR + + +## Tile Operation Diagram + +![TOR tile operation](../figures/isa/TOR.svg) + +## Introduction + +Elementwise bitwise OR of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \;|\; \mathrm{src1}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tor %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tor ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TOR(TileData &dst, TileData &src0, TileData &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Supported element types are 1-byte or 2-byte integral types. + - `dst`, `src0`, and `src1` must use the same element type. + - `dst`, `src0`, and `src1` must be row-major. + - Runtime: `src0.GetValidRow()/GetValidCol()` and `src1.GetValidRow()/GetValidCol()` must match `dst`. +- **Implementation checks (A5)**: + - Supported element types are `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, and `int32_t`. + - `dst`, `src0`, and `src1` must use the same element type. + - `dst`, `src0`, and `src1` must be row-major. + - Runtime: `src0.GetValidRow()/GetValidCol()` and `src1.GetValidRow()/GetValidCol()` must match `dst`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, out; + TOR(out, a, b); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tor %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tor ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TORS.md b/designs/outerCube/PTOISA/TORS.md new file mode 100644 index 00000000..8bd53389 --- /dev/null +++ b/designs/outerCube/PTOISA/TORS.md @@ -0,0 +1,106 @@ +# TORS + + +## Tile Operation Diagram + +![TORS tile operation](../figures/isa/TORS.svg) + +## Introduction + +Elementwise bitwise OR of a tile and a scalar. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \;|\; \mathrm{scalar} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tors %src, %scalar : !pto.tile<...>, i32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tors ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TORS(TileDataDst &dst, TileDataSrc &src, typename TileDataDst::DType scalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Intended for integral element types. + - `dst` and `src` must use the same element type. + - `dst` and `src` must be vector tiles. + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`. + - In manual mode, setting the source tile and destination tile to the same memory is unsupported. +- **Implementation checks (A5)**: + - Intended for integral element types supported by `TEXPANDS` and `TOR`. + - `dst` and `src` must use the same element type. + - `dst` and `src` must be vector tiles. + - In manual mode, setting the source tile and destination tile to the same memory is unsupported. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc = Tile; + TileDst dst; + TileSrc src; + TORS(dst, src, 0xffu); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tors %src, %scalar : !pto.tile<...>, i32 +# AS Level 2 (DPS) +pto.tors ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TORS_zh.md b/designs/outerCube/PTOISA/TORS_zh.md new file mode 100644 index 00000000..65c938fb --- /dev/null +++ b/designs/outerCube/PTOISA/TORS_zh.md @@ -0,0 +1,106 @@ +# TORS + +## 指令示意图 + +![TORS tile operation](../figures/isa/TORS.svg) + +## 简介 + +Tile 与标量的逐元素按位或。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \;|\; \mathrm{scalar} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tors %src, %scalar : !pto.tile<...>, i32 +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tors ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TORS(TileDataDst &dst, TileDataSrc &src, typename TileDataDst::DType scalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - 适用于整数元素类型。 + - `dst` 和 `src` 必须使用相同的元素类型。 + - `dst` 和 `src` 必须是向量 Tile。 + - 运行时:`src.GetValidRow() == dst.GetValidRow()` 且 `src.GetValidCol() == dst.GetValidCol()`。 + - 在手动模式下,不支持将源 Tile 和目标 Tile 设置为相同的内存。 +- **实现检查 (A5)**: + - 适用于 `TEXPANDS` 和 `TOR` 支持的整数元素类型。 + - `dst` 和 `src` 必须使用相同的元素类型。 + - `dst` 和 `src` 必须是向量 Tile。 + - 在手动模式下,不支持将源 Tile 和目标 Tile 设置为相同的内存。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc = Tile; + TileDst dst; + TileSrc src; + TORS(dst, src, 0xffu); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tors %src, %scalar : !pto.tile<...>, i32 +# AS Level 2 (DPS) +pto.tors ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TOR_zh.md b/designs/outerCube/PTOISA/TOR_zh.md new file mode 100644 index 00000000..8ad38d80 --- /dev/null +++ b/designs/outerCube/PTOISA/TOR_zh.md @@ -0,0 +1,103 @@ +# TOR + +## 指令示意图 + +![TOR tile operation](../figures/isa/TOR.svg) + +## 简介 + +两个 Tile 的逐元素按位或。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \;|\; \mathrm{src1}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tor %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tor ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TOR(TileData &dst, TileData &src0, TileData &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - 支持的元素类型为 1 字节或 2 字节整数类型。 + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - 运行时:`src0.GetValidRow()/GetValidCol()` 和 `src1.GetValidRow()/GetValidCol()` 必须与 `dst` 一致。 +- **实现检查 (A5)**: + - 支持的元素类型为 `uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t` 和 `int32_t`。 + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - 运行时:`src0.GetValidRow()/GetValidCol()` 和 `src1.GetValidRow()/GetValidCol()` 必须与 `dst` 一致。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, out; + TOR(out, a, b); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tor %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tor ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TPACK.md b/designs/outerCube/PTOISA/TPACK.md new file mode 100644 index 00000000..b96033de --- /dev/null +++ b/designs/outerCube/PTOISA/TPACK.md @@ -0,0 +1,40 @@ +# TPACK + +## Tile Operation Diagram + +![TPACK tile operation](../figures/isa/TPACK.svg) + +## Introduction + +Pack or convert tile elements into a narrower destination representation. + +## Math Interpretation + +Semantics are instruction-specific. Unless stated otherwise, behavior is defined over the destination valid region. + +## Assembly Syntax + +PTO-AS form: see `docs/assembly/PTO-AS.md`. + +### IR Level 1 (SSA) + +```text +%dst = pto.tpack ... +``` + +### IR Level 2 (DPS) + +```text +pto.tpack ins(...) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`. + +## Constraints + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## Examples + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TPACK_zh.md b/designs/outerCube/PTOISA/TPACK_zh.md new file mode 100644 index 00000000..71a90d2c --- /dev/null +++ b/designs/outerCube/PTOISA/TPACK_zh.md @@ -0,0 +1,41 @@ +# TPACK + +## 指令示意图 + +![TPACK tile operation](../figures/isa/TPACK.svg) + +## 简介 + +将 Tile 元素打包或转换为更窄的目标表示。 + +## 数学语义 + +语义随指令而变化。 Unless stated otherwise, behavior is defined over the destination valid region. + +## 汇编语法 + +PTO-AS 形式:参见 `docs/assembly/PTO-AS.md`. + +### AS Level 1(SSA) + +```text +%dst = pto.tpack ... +``` + +### AS Level 2(DPS) + +```text +pto.tpack ins(...) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`. + +## 约束 + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## 示例 + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TPARTADD.md b/designs/outerCube/PTOISA/TPARTADD.md new file mode 100644 index 00000000..8409953f --- /dev/null +++ b/designs/outerCube/PTOISA/TPARTADD.md @@ -0,0 +1,137 @@ +# TPARTADD + + +## Tile Operation Diagram + +![TPARTADD tile operation](../figures/isa/TPARTADD.svg) + +## Introduction + +Partial elementwise add with implementation-defined handling of mismatched valid regions. + +## Math Interpretation + +For each element `(i, j)` in the destination valid region: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\mathrm{src0}_{i,j} + \mathrm{src1}_{i,j} & \text{if both inputs are defined at } (i,j) \\ +\mathrm{src0}_{i,j} & \text{if only src0 is defined at } (i,j) \\ +\mathrm{src1}_{i,j} & \text{if only src1 is defined at } (i,j) +\end{cases} +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tpartadd %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tpartadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tpartadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tpartadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tpartadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPARTADD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `dst/src0/src1` element types must be identical, and must be one of: `int32_t`, `int16_t`, `half`, `float`. + - All three tiles must be row-major (`isRowMajor`). + - Runtime: if `dst.GetValidRow() == 0` or `dst.GetValidCol() == 0`, the op returns early. + - Runtime: the implementation requires at least one input's valid region to match `dst`'s valid region, and the other's valid region not greater than `dst`'s valid region (otherwise it asserts). +- **Implementation checks (A5)**: + - `dst/src0/src1` element types must be identical, and must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `float`, `bfloat16_t`. + - Runtime: if `dst` has a zero valid region, the op returns early. + - Only certain partial-validity patterns are handled (e.g., one source equal to `dst` while the other is smaller by valid-rows or valid-cols); other patterns are not supported (target-defined behavior). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TPARTADD(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TPARTADD(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tpartadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tpartadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tpartadd %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tpartadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TPARTADD_zh.md b/designs/outerCube/PTOISA/TPARTADD_zh.md new file mode 100644 index 00000000..dc0569b5 --- /dev/null +++ b/designs/outerCube/PTOISA/TPARTADD_zh.md @@ -0,0 +1,110 @@ +# TPARTADD + +## 指令示意图 + +![TPARTADD tile operation](../figures/isa/TPARTADD.svg) + +## 简介 + +部分逐元素加法,对不匹配的有效区域具有实现定义的处理方式。 + +## 数学语义 + +对每个元素 `(i, j)` in the destination valid region: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\mathrm{src0}_{i,j} + \mathrm{src1}_{i,j} & \text{if both inputs are defined at } (i,j) \\ +\mathrm{src0}_{i,j} & \text{if only src0 is defined at } (i,j) \\ +\mathrm{src1}_{i,j} & \text{if only src1 is defined at } (i,j) +\end{cases} +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tpartadd %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tpartadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tpartadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tpartadd %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tpartadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPARTADD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `dst/src0/src1` element types must be identical, and must be one of: `int32_t`, `int16_t`, `half`, `float`. + - All three tiles must be row-major (`isRowMajor`). + - Runtime: if `dst.GetValidRow() == 0` or `dst.GetValidCol() == 0`, the op returns early. + - Runtime: the implementation requires at least one input's valid region to match `dst`'s valid region, and the other's valid region not greater than `dst`'s valid region (otherwise it asserts). +- **实现检查 (A5)**: + - `dst/src0/src1` element types must be identical, and must be one of: `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, `int32_t`, `half`, `float`, `bfloat16_t`. + - Runtime: if `dst` has a zero valid region, the op returns early. + - Only certain partial-validity patterns are handled (e.g., one source equal to `dst` while the other is smaller by valid-rows or valid-cols); other patterns are not supported (target-defined behavior). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TPARTADD(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TPARTADD(dst, src0, src1); +} +``` diff --git a/designs/outerCube/PTOISA/TPARTMAX.md b/designs/outerCube/PTOISA/TPARTMAX.md new file mode 100644 index 00000000..a0762900 --- /dev/null +++ b/designs/outerCube/PTOISA/TPARTMAX.md @@ -0,0 +1,137 @@ +# TPARTMAX + + +## Tile Operation Diagram + +![TPARTMAX tile operation](../figures/isa/TPARTMAX.svg) + +## Introduction + +Partial elementwise max with implementation-defined handling of mismatched valid regions. + +## Math Interpretation + +For each element `(i, j)` in the destination valid region: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\max(\mathrm{src0}_{i,j}, \mathrm{src1}_{i,j}) & \text{if both inputs are defined at } (i,j) \\ +\mathrm{src0}_{i,j} & \text{if only src0 is defined at } (i,j) \\ +\mathrm{src1}_{i,j} & \text{if only src1 is defined at } (i,j) +\end{cases} +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tpartmax %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tpartmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tpartmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tpartmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tpartmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPARTMAX(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `dst/src0/src1` element types must be identical, and must be one of: `int32_t`, `int16_t`, `half`, `float`. + - All three tiles must be row-major (`isRowMajor`). + - Runtime: if `dst.GetValidRow() == 0` or `dst.GetValidCol() == 0`, the op returns early. + - Runtime: the implementation requires at least one input's valid region to match `dst`'s valid region, and the onther's valid region not greater than `dst`'s valid region (otherwise it asserts). +- **Implementation checks (A5)**: + - `dst/src0/src1` element types must be identical and must be one of: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `half`, `bfloat16_t`, `float`. + - Runtime: if any of `src0/src1/dst` has a zero valid region, the op returns early. + - Requires `src0` and `src1` valid region to be `<= dst` valid region in both dimensions; other patterns are not supported (target-defined behavior). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TPARTMAX(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TPARTMAX(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tpartmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tpartmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tpartmax %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tpartmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TPARTMAX_zh.md b/designs/outerCube/PTOISA/TPARTMAX_zh.md new file mode 100644 index 00000000..a2916a9f --- /dev/null +++ b/designs/outerCube/PTOISA/TPARTMAX_zh.md @@ -0,0 +1,110 @@ +# TPARTMAX + +## 指令示意图 + +![TPARTMAX tile operation](../figures/isa/TPARTMAX.svg) + +## 简介 + +部分逐元素最大值,对不匹配的有效区域具有实现定义的处理方式。 + +## 数学语义 + +对每个元素 `(i, j)` in the destination valid region: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\max(\mathrm{src0}_{i,j}, \mathrm{src1}_{i,j}) & \text{if both inputs are defined at } (i,j) \\ +\mathrm{src0}_{i,j} & \text{if only src0 is defined at } (i,j) \\ +\mathrm{src1}_{i,j} & \text{if only src1 is defined at } (i,j) +\end{cases} +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tpartmax %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tpartmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tpartmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tpartmax %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tpartmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPARTMAX(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `dst/src0/src1` element types must be identical, and must be one of: `int32_t`, `int16_t`, `half`, `float`. + - All three tiles must be row-major (`isRowMajor`). + - Runtime: if `dst.GetValidRow() == 0` or `dst.GetValidCol() == 0`, the op returns early. + - Runtime: the implementation requires at least one input's valid region to match `dst`'s valid region, and the onther's valid region not greater than `dst`'s valid region (otherwise it asserts). +- **实现检查 (A5)**: + - `dst/src0/src1` element types must be identical and must be one of: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `half`, `bfloat16_t`, `float`. + - Runtime: if any of `src0/src1/dst` has a zero valid region, the op returns early. + - Requires `src0` and `src1` valid region to be `<= dst` valid region in both dimensions; other patterns are not supported (target-defined behavior). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TPARTMAX(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TPARTMAX(dst, src0, src1); +} +``` diff --git a/designs/outerCube/PTOISA/TPARTMIN.md b/designs/outerCube/PTOISA/TPARTMIN.md new file mode 100644 index 00000000..fd9a8502 --- /dev/null +++ b/designs/outerCube/PTOISA/TPARTMIN.md @@ -0,0 +1,137 @@ +# TPARTMIN + + +## Tile Operation Diagram + +![TPARTMIN tile operation](../figures/isa/TPARTMIN.svg) + +## Introduction + +Partial elementwise min with implementation-defined handling of mismatched valid regions. + +## Math Interpretation + +For each element `(i, j)` in the destination valid region: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\min(\mathrm{src0}_{i,j}, \mathrm{src1}_{i,j}) & \text{if both inputs are defined at } (i,j) \\ +\mathrm{src0}_{i,j} & \text{if only src0 is defined at } (i,j) \\ +\mathrm{src1}_{i,j} & \text{if only src1 is defined at } (i,j) +\end{cases} +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tpartmin %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tpartmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tpartmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tpartmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tpartmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPARTMIN(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `dst/src0/src1` element types must be identical, and must be one of: `int32_t`, `int16_t`, `half`, `float`. + - All three tiles must be row-major (`isRowMajor`). + - Runtime: if `dst.GetValidRow() == 0` or `dst.GetValidCol() == 0`, the op returns early. + - Runtime: the implementation requires at least one input's valid region to match `dst`'s valid region, and the onther's valid region not greater than `dst`'s valid region (otherwise it asserts). +- **Implementation checks (A5)**: + - `dst/src0/src1` element types must be identical and must be one of: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `half`, `bfloat16_t`, `float`. + - Runtime: if any of `src0/src1/dst` has a zero valid region, the op returns early. + - Requires `src0` and `src1` valid region to be `<= dst` valid region in both dimensions; other patterns are not supported (target-defined behavior). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TPARTMIN(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TPARTMIN(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tpartmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tpartmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tpartmin %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tpartmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TPARTMIN_zh.md b/designs/outerCube/PTOISA/TPARTMIN_zh.md new file mode 100644 index 00000000..89b0603a --- /dev/null +++ b/designs/outerCube/PTOISA/TPARTMIN_zh.md @@ -0,0 +1,110 @@ +# TPARTMIN + +## 指令示意图 + +![TPARTMIN tile operation](../figures/isa/TPARTMIN.svg) + +## 简介 + +部分逐元素最小值,对不匹配的有效区域具有实现定义的处理方式。 + +## 数学语义 + +对每个元素 `(i, j)` in the destination valid region: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\min(\mathrm{src0}_{i,j}, \mathrm{src1}_{i,j}) & \text{if both inputs are defined at } (i,j) \\ +\mathrm{src0}_{i,j} & \text{if only src0 is defined at } (i,j) \\ +\mathrm{src1}_{i,j} & \text{if only src1 is defined at } (i,j) +\end{cases} +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tpartmin %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tpartmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tpartmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tpartmin %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tpartmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPARTMIN(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `dst/src0/src1` element types must be identical, and must be one of: `int32_t`, `int16_t`, `half`, `float`. + - All three tiles must be row-major (`isRowMajor`). + - Runtime: if `dst.GetValidRow() == 0` or `dst.GetValidCol() == 0`, the op returns early. + - Runtime: the implementation requires at least one input's valid region to match `dst`'s valid region, and the onther's valid region not greater than `dst`'s valid region (otherwise it asserts). +- **实现检查 (A5)**: + - `dst/src0/src1` element types must be identical and must be one of: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `half`, `bfloat16_t`, `float`. + - Runtime: if any of `src0/src1/dst` has a zero valid region, the op returns early. + - Requires `src0` and `src1` valid region to be `<= dst` valid region in both dimensions; other patterns are not supported (target-defined behavior). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TPARTMIN(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TPARTMIN(dst, src0, src1); +} +``` diff --git a/designs/outerCube/PTOISA/TPARTMUL.md b/designs/outerCube/PTOISA/TPARTMUL.md new file mode 100644 index 00000000..d0da3546 --- /dev/null +++ b/designs/outerCube/PTOISA/TPARTMUL.md @@ -0,0 +1,129 @@ +# TPARTMUL + + +## Tile Operation Diagram + +![TPARTMUL tile operation](../figures/isa/TPARTMUL.svg) + +## Introduction + +Partial elementwise multiply with implementation-defined handling of mismatched valid regions. + +## Math Interpretation + +For each element `(i, j)` in the destination valid region: + +$$ +\mathrm{dst}_{i,j} = +egin{cases} +\mathrm{src0}_{i,j} \cdot \mathrm{src1}_{i,j} & ext{if both inputs are defined at } (i,j) \ +\mathrm{src0}_{i,j} & ext{if only src0 is defined at } (i,j) \ +\mathrm{src1}_{i,j} & ext{if only src1 is defined at } (i,j) +\end{cases} +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tpartmul %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tpartmul %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tpartmul ins(%src0, %src1 : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tpartmul %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tpartmul ins(%src0, %src1 : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPARTMUL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- Element type/layout legality follows backend checks and is analogous to `TPARTADD` / `TPARTMAX` / `TPARTMIN`. +- Destination valid region defines the result domain. +- Partial-validity handling is implementation-defined for unsupported shape combinations. + +## Examples + +### Auto + +```cpp +#include +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TPARTMUL(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TPARTMUL(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tpartmul %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tpartmul %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tpartmul %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tpartmul ins(%src0, %src1 : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TPARTMUL_zh.md b/designs/outerCube/PTOISA/TPARTMUL_zh.md new file mode 100644 index 00000000..ca6c30c5 --- /dev/null +++ b/designs/outerCube/PTOISA/TPARTMUL_zh.md @@ -0,0 +1,102 @@ +# TPARTMUL + +## 指令示意图 + +![TPARTMUL tile operation](../figures/isa/TPARTMUL.svg) + +## 简介 + +部分逐元素乘法,对有效区域不一致的处理为实现定义。 + +## 数学语义 + +对每个元素 `(i, j)` in the destination valid region: + +$$ +\mathrm{dst}_{i,j} = +egin{cases} +\mathrm{src0}_{i,j} \cdot \mathrm{src1}_{i,j} & ext{if both inputs are defined at } (i,j) \ +\mathrm{src0}_{i,j} & ext{if only src0 is defined at } (i,j) \ +\mathrm{src1}_{i,j} & ext{if only src1 is defined at } (i,j) +\end{cases} +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tpartmul %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tpartmul %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tpartmul ins(%src0, %src1 : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tpartmul %src0, %src1 : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tpartmul ins(%src0, %src1 : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPARTMUL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- Element type/layout legality follows backend checks and is analogous to `TPARTADD` / `TPARTMAX` / `TPARTMIN`. +- Destination valid region defines the result domain. +- Partial-validity handling is implementation-defined for unsupported shape combinations. + +## 示例 + +### 自动(Auto) + +```cpp +#include +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TPARTMUL(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TPARTMUL(dst, src0, src1); +} +``` diff --git a/designs/outerCube/PTOISA/TPOP.md b/designs/outerCube/PTOISA/TPOP.md new file mode 100644 index 00000000..1d49b220 --- /dev/null +++ b/designs/outerCube/PTOISA/TPOP.md @@ -0,0 +1,40 @@ +# TPOP + +## Tile Operation Diagram + +![TPOP tile operation](../figures/isa/TPOP.svg) + +## Introduction + +Pop a tile from a pipe or FIFO consumer endpoint. + +## Math Interpretation + +Semantics are instruction-specific. Unless stated otherwise, behavior is defined over the destination valid region. + +## Assembly Syntax + +PTO-AS form: see `docs/assembly/PTO-AS.md`. + +### IR Level 1 (SSA) + +```text +%dst = pto.tpop ... +``` + +### IR Level 2 (DPS) + +```text +pto.tpop ins(...) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`. + +## Constraints + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## Examples + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TPOP_zh.md b/designs/outerCube/PTOISA/TPOP_zh.md new file mode 100644 index 00000000..1c6909d2 --- /dev/null +++ b/designs/outerCube/PTOISA/TPOP_zh.md @@ -0,0 +1,41 @@ +# TPOP + +## 指令示意图 + +![TPOP tile operation](../figures/isa/TPOP.svg) + +## 简介 + +从 pipe 或 FIFO 的消费者端弹出一个 Tile。 + +## 数学语义 + +语义随指令而变化。 Unless stated otherwise, behavior is defined over the destination valid region. + +## 汇编语法 + +PTO-AS 形式:参见 `docs/assembly/PTO-AS.md`. + +### AS Level 1(SSA) + +```text +%dst = pto.tpop ... +``` + +### AS Level 2(DPS) + +```text +pto.tpop ins(...) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`. + +## 约束 + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## 示例 + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TPREFETCH.md b/designs/outerCube/PTOISA/TPREFETCH.md new file mode 100644 index 00000000..ab7fc128 --- /dev/null +++ b/designs/outerCube/PTOISA/TPREFETCH.md @@ -0,0 +1,94 @@ +# TPREFETCH + + +## Tile Operation Diagram + +![TPREFETCH tile operation](../figures/isa/TPREFETCH.svg) + +## Introduction + +Prefetch data from global memory into a tile-local cache/buffer (implementation-defined). This is typically used to reduce latency before a subsequent `TLOAD`. + +Note: unlike most PTO instructions, `TPREFETCH` does **not** implicitly call `TSYNC(events...)` in the C++ wrapper. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tprefetch %src : !pto.global<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tprefetch %src : !pto.global<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tprefetch ins(%src : !pto.global<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tprefetch %src : !pto.global<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tprefetch ins(%src : !pto.global<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPREFETCH(TileData &dst, GlobalData &src); +``` + +## Constraints + +- Semantics and caching behavior are target/implementation-defined. +- Some targets may ignore prefetches or treat them as hints. + +## Math Interpretation + +Unless otherwise specified, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tprefetch %src : !pto.global<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tprefetch %src : !pto.global<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tprefetch %src : !pto.global<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tprefetch ins(%src : !pto.global<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TPREFETCH_zh.md b/designs/outerCube/PTOISA/TPREFETCH_zh.md new file mode 100644 index 00000000..2e22e6ff --- /dev/null +++ b/designs/outerCube/PTOISA/TPREFETCH_zh.md @@ -0,0 +1,65 @@ +# TPREFETCH + +## 指令示意图 + +![TPREFETCH tile operation](../figures/isa/TPREFETCH.svg) + +## 简介 + +将数据从全局内存预取到 Tile 本地缓存/缓冲区(提示)。 + +## 数学语义 + +除非另有说明, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tprefetch %src : !pto.global<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tprefetch %src : !pto.global<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tprefetch ins(%src : !pto.global<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tprefetch %src : !pto.global<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tprefetch ins(%src : !pto.global<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPREFETCH(TileData &dst, GlobalData &src); +``` + +## 约束 + +- Semantics and caching behavior are target/implementation-defined. +- Some targets may ignore prefetches or treat them as hints. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TPRELU.md b/designs/outerCube/PTOISA/TPRELU.md new file mode 100644 index 00000000..74c5ef05 --- /dev/null +++ b/designs/outerCube/PTOISA/TPRELU.md @@ -0,0 +1,106 @@ +# TPRELU + + +## Tile Operation Diagram + +![TPRELU tile operation](../figures/isa/TPRELU.svg) + +## Introduction + +Elementwise PReLU (parametric ReLU) with a per-element slope tile. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = (\mathrm{src0}_{i,j} > 0) ? \mathrm{src0}_{i,j} : (\mathrm{src0}_{i,j} \cdot \mathrm{src1}_{i,j}) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tprelu %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tprelu %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tprelu ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tprelu %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tprelu ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPRELU(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. +- Temporary space is required by A3 for calculation, while not used by A5. +- For A3, 2 source Tile, destination Tile, temporary space must in different memory range without overlapping. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, slope, out, tmp; + TPRELU(out, x, slope, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tprelu %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tprelu %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tprelu %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tprelu ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TPRELU_zh.md b/designs/outerCube/PTOISA/TPRELU_zh.md new file mode 100644 index 00000000..1f5b2a8a --- /dev/null +++ b/designs/outerCube/PTOISA/TPRELU_zh.md @@ -0,0 +1,79 @@ +# TPRELU + +## 指令示意图 + +![TPRELU tile operation](../figures/isa/TPRELU.svg) + +## 简介 + +带逐元素斜率 Tile 的逐元素参数化 ReLU (PReLU)。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = (\mathrm{src0}_{i,j} > 0) ? \mathrm{src0}_{i,j} : (\mathrm{src0}_{i,j} \cdot \mathrm{src1}_{i,j}) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tprelu %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tprelu %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tprelu ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tprelu %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tprelu ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TPRELU(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. +- Temporary space is required by A3 for calculation, while not used by A5. +- For A3, 2 source Tile, destination Tile, temporary space must in different memory range without overlapping. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, slope, out, tmp; + TPRELU(out, x, slope, tmp); +} +``` diff --git a/designs/outerCube/PTOISA/TPRINT.md b/designs/outerCube/PTOISA/TPRINT.md new file mode 100644 index 00000000..b92db8fe --- /dev/null +++ b/designs/outerCube/PTOISA/TPRINT.md @@ -0,0 +1,157 @@ +# TPRINT + + +## Tile Operation Diagram + +![TPRINT tile operation](../figures/isa/TPRINT.svg) + +## Introduction + +Print the contents of a Tile or GlobalTensor for debugging purposes directly from device code. + +The `TPRINT` instruction outputs the logical view of data stored in a Tile or GlobalTensor. It supports common data types (e.g., `float`, `half`, `int8`, `uint32`) and multiple memory layouts (`ND`, `DN`, `NZ` for GlobalTensor; vector tiles for on-chip buffers). + +> **Important**: +> - This instruction is **for development and debugging ONLY**. +> - It incurs **significant runtime overhead** and **must not be used in production kernels**. +> - Output may be **truncated** if it exceeds the internal print buffer. +> - **Requires CCE compilation option `-D_DEBUG --cce-enable-print`** (see [Behavior](#behavior)). + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +```text +tprint %src : !pto.tile<...> | !pto.global<...> +``` + +### AS Level 1 (SSA) + +```text +pto.tprint %src : !pto.tile<...> | !pto.partition_tensor_view -> () +``` + +### AS Level 2 (DPS) + +```text +pto.tprint ins(%src : !pto.tile_buf<...> | !pto.partition_tensor_view) +``` + +### IR Level 1 (SSA) + +```text +pto.tprint %src : !pto.tile<...> | !pto.partition_tensor_view -> () +``` + +### IR Level 2 (DPS) + +```text +pto.tprint ins(%src : !pto.tile_buf<...> | !pto.partition_tensor_view) +``` +## C++ Intrinsic +Declared in `include/pto/common/pto_instr.hpp`: +```cpp +template +PTO_INST RecordEvent TPRINT(TileData &src, WaitEvents &... events); +``` + +### Supported Types for T +- **Tile**: Must be a vector tile (`TileType::Vec`) with supported element type. +- **GlobalTensor**: Must use layout `ND`, `DN`, or `NZ`, and have a supported element type. + +## Constraints + +- **Supported element type**: + - Floating-point: `float`, `half` + - Signed integers: `int8_t`, `int16_t`, `int32_t` + - Unsigned integers: `uint8_t`, `uint16_t`, `uint32_t` +- **For Tiles**: `TileData::Loc == TileType::Vec` (only vector tiles are printable). +- **For GlobalTensor**: Layout must be one of `Layout::ND`, `Layout::DN`, or `Layout::NZ`. + +## Behavior +- **Mandatory Compilation Flag**: + + On A2/A3/A5 devices, `TPRINT` uses `cce::printf` to emit output via the device-to-host debug channel. **You must enable the CCE option `-D_DEBUG --cce-enable-print`**. + +- **Buffer Limitation:** + + The internal print buffer of `cce::printf` is limited in size. If the output exceeds this buffer, a warning message such as `"Warning: out of bound! try best to print"` may appear, and **only partial data will be printed**. + +- **Synchronization**: + + Automatically inserts a `pipe_barrier(PIPE_ALL)` before printing to ensure all prior operations complete and data is consistent. + +- **Formatting**: + + - Floating-point values: printed as `%6.2f` + - Integer values: printed as `%6d` + - For `GlobalTensor`, due to data size and buffer limitations, only elements within its logical shape (defined by `Shape`) are printed. + - For `Tile`, invalid regions (beyond `validRows`/`validCols`) are still printed but marked with a `|` separator when partial validity is specified. + +## Examples + +### Print a Tile + +```cpp +#include + +PTO_INTERNAL void DebugTile(__gm__ float *src) { + using ValidSrcShape = TileShape2D; + using NDSrcShape = BaseShape2D; + using GlobalDataSrc = GlobalTensor; + GlobalDataSrc srcGlobal(src); + + using srcTileData = Tile; + srcTileData srcTile; + TASSIGN(srcTile, 0x0); + + TLOAD(srcTile, srcGlobal); + TPRINT(srcTile); +} +``` + +### Print a GlobalTensor + +```cpp +#include + +PTO_INTERNAL void DebugGlobalTensor(__gm__ float *src) { + using ValidSrcShape = TileShape2D; + using NDSrcShape = BaseShape2D; + using GlobalDataSrc = GlobalTensor; + GlobalDataSrc srcGlobal(src); + + TPRINT(srcGlobal); +} +``` + +## Math Interpretation + +Unless otherwise specified, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +pto.tprint %src : !pto.tile<...> | !pto.partition_tensor_view -> () +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +pto.tprint %src : !pto.tile<...> | !pto.partition_tensor_view -> () +``` + +### PTO Assembly Form + +```text +pto.tprint %src : !pto.tile<...> | !pto.partition_tensor_view -> () +# AS Level 2 (DPS) +pto.tprint ins(%src : !pto.tile_buf<...> | !pto.partition_tensor_view) +``` diff --git a/designs/outerCube/PTOISA/TPRINT_zh.md b/designs/outerCube/PTOISA/TPRINT_zh.md new file mode 100644 index 00000000..1dbacc50 --- /dev/null +++ b/designs/outerCube/PTOISA/TPRINT_zh.md @@ -0,0 +1,113 @@ +# TPRINT + +## 指令示意图 + +![TPRINT tile operation](../figures/isa/TPRINT.svg) + +## 简介 + +调试/打印 Tile 中的元素(实现定义)。 + +Print the contents of a Tile or GlobalTensor for debugging purposes directly from device code. + +The `TPRINT` instruction outputs the logical view of data stored in a Tile or GlobalTensor. It supports common data types (e.g., `float`, `half`, `int8`, `uint32`) and multiple memory layouts (`ND`, `DN`, `NZ` for GlobalTensor; vector tiles for on-chip buffers). + +> **Important**: +> - This instruction is **for development and debugging ONLY**. +> - It incurs **significant runtime overhead** and **must not be used in production kernels**. +> - Output may be **truncated** if it exceeds the internal print buffer. +> - **Requires CCE compilation option `-D_DEBUG --cce-enable-print`** (see [Behavior](#behavior)). + +## 数学语义 + +除非另有说明, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +```text +tprint %src : !pto.tile<...> | !pto.global<...> +``` + +### AS Level 1 (SSA) + +```text +pto.tprint %src : !pto.tile<...> | !pto.partition_tensor_view -> () +``` + +### AS Level 2 (DPS) + +```text +pto.tprint ins(%src : !pto.tile_buf<...> | !pto.partition_tensor_view) +``` + +### AS Level 1(SSA) + +```text +pto.tprint %src : !pto.tile<...> | !pto.partition_tensor_view -> () +``` + +### AS Level 2(DPS) + +```text +pto.tprint ins(%src : !pto.tile_buf<...> | !pto.partition_tensor_view) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: +```cpp +template +PTO_INST RecordEvent TPRINT(TileData &src, WaitEvents &... events); +``` + +### Supported Types for T +- **Tile**: Must be a vector tile (`TileType::Vec`) with supported element type. +- **GlobalTensor**: Must use layout `ND`, `DN`, or `NZ`, and have a supported element type. + +## 约束 + +- **Supported element type**: + - Floating-point: `float`, `half` + - Signed integers: `int8_t`, `int16_t`, `int32_t` + - Unsigned integers: `uint8_t`, `uint16_t`, `uint32_t` +- **For Tiles**: `TileData::Loc == TileType::Vec` (only vector tiles are printable). +- **For GlobalTensor**: Layout must be one of `Layout::ND`, `Layout::DN`, or `Layout::NZ`. + +## 示例 + +### Print a Tile + +```cpp +#include + +PTO_INTERNAL void DebugTile(__gm__ float *src) { + using ValidSrcShape = TileShape2D; + using NDSrcShape = BaseShape2D; + using GlobalDataSrc = GlobalTensor; + GlobalDataSrc srcGlobal(src); + + using srcTileData = Tile; + srcTileData srcTile; + TASSIGN(srcTile, 0x0); + + TLOAD(srcTile, srcGlobal); + TPRINT(srcTile); +} +``` + +### Print a GlobalTensor + +```cpp +#include + +PTO_INTERNAL void DebugGlobalTensor(__gm__ float *src) { + using ValidSrcShape = TileShape2D; + using NDSrcShape = BaseShape2D; + using GlobalDataSrc = GlobalTensor; + GlobalDataSrc srcGlobal(src); + + TPRINT(srcGlobal); +} +``` diff --git a/designs/outerCube/PTOISA/TPUSH.md b/designs/outerCube/PTOISA/TPUSH.md new file mode 100644 index 00000000..1a1b8c4c --- /dev/null +++ b/designs/outerCube/PTOISA/TPUSH.md @@ -0,0 +1,40 @@ +# TPUSH + +## Tile Operation Diagram + +![TPUSH tile operation](../figures/isa/TPUSH.svg) + +## Introduction + +Push a tile into a pipe or FIFO producer endpoint. + +## Math Interpretation + +Semantics are instruction-specific. Unless stated otherwise, behavior is defined over the destination valid region. + +## Assembly Syntax + +PTO-AS form: see `docs/assembly/PTO-AS.md`. + +### IR Level 1 (SSA) + +```text +%dst = pto.tpush ... +``` + +### IR Level 2 (DPS) + +```text +pto.tpush ins(...) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`. + +## Constraints + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## Examples + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TPUSH_zh.md b/designs/outerCube/PTOISA/TPUSH_zh.md new file mode 100644 index 00000000..f6ebfadc --- /dev/null +++ b/designs/outerCube/PTOISA/TPUSH_zh.md @@ -0,0 +1,41 @@ +# TPUSH + +## 指令示意图 + +![TPUSH tile operation](../figures/isa/TPUSH.svg) + +## 简介 + +将 Tile 推入 pipe 或 FIFO 的生产者端。 + +## 数学语义 + +语义随指令而变化。 Unless stated otherwise, behavior is defined over the destination valid region. + +## 汇编语法 + +PTO-AS 形式:参见 `docs/assembly/PTO-AS.md`. + +### AS Level 1(SSA) + +```text +%dst = pto.tpush ... +``` + +### AS Level 2(DPS) + +```text +pto.tpush ins(...) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`. + +## 约束 + +Refer to backend-specific legality checks for data type/layout/location/shape constraints. + +## 示例 + +See related instruction pages in `docs/isa/` for concrete Auto/Manual usage patterns. diff --git a/designs/outerCube/PTOISA/TQUANT.md b/designs/outerCube/PTOISA/TQUANT.md new file mode 100644 index 00000000..945abb5e --- /dev/null +++ b/designs/outerCube/PTOISA/TQUANT.md @@ -0,0 +1,94 @@ +# TQUANT + + +## Tile Operation Diagram + +![TQUANT tile operation](../figures/isa/TQUANT.svg) + +## Introduction + +Quantize an FP32 tile into a lower-precision format (e.g. FP8), producing auxiliary exponent/scaling/max tiles. The quantization mode is a compile-time template parameter (`mode`). + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TQUANT(TileDataOut &dst, TileDataSrc &src, TileDataExp *exp, TileDataMax *max, TileDataSrc *scaling, WaitEvents &... events); + +template +PTO_INST RecordEvent TQUANT(TileDataOut &dst, TileDataSrc &src, TileDataExp *exp, TileDataMax *max, TileDataSrc *scaling, TileDataExp *exp_zz, TileDataIdx *vgather_idx, WaitEvents &... events); + +template +PTO_INST RecordEvent TQUANT(TileDataOut &dst, TileDataSrc &src, TileDataPara &scale, TileDataPara *offset = nullptr, WaitEvents &... events); +``` + +## Constraints + +- This instruction is currently implemented for specific targets (see `include/pto/npu/*/TQuant.hpp`). +- Input type requirements and output tile types are mode/target-dependent. + +## Math Interpretation + +Unless otherwise specified, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.tquant %src, %qp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tquant ins(%src, %qp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tquant %src, %qp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tquant ins(%src, %qp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tquant %src, %qp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tquant %src, %qp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.tquant %src, %qp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tquant ins(%src, %qp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TQUANT_zh.md b/designs/outerCube/PTOISA/TQUANT_zh.md new file mode 100644 index 00000000..85d6d359 --- /dev/null +++ b/designs/outerCube/PTOISA/TQUANT_zh.md @@ -0,0 +1,67 @@ +# TQUANT + +## 指令示意图 + +![TQUANT tile operation](../figures/isa/TQUANT.svg) + +## 简介 + +量化 Tile(例如 FP32 到 FP8),生成指数/缩放/最大值输出。 + +## 数学语义 + +除非另有说明, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.tquant %src, %qp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tquant ins(%src, %qp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tquant %src, %qp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tquant ins(%src, %qp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TQUANT(TileDataOut &dst, TileDataSrc &src, TileDataExp *exp, TileDataMax *max, TileDataSrc *scaling, WaitEvents &... events); + +template +PTO_INST RecordEvent TQUANT(TileDataOut &dst, TileDataSrc &src, TileDataExp *exp, TileDataMax *max, TileDataSrc *scaling, TileDataExp *exp_zz, TileDataIdx *vgather_idx, WaitEvents &... events); + +template +PTO_INST RecordEvent TQUANT(TileDataOut &dst, TileDataSrc &src, TileDataPara &scale, TileDataPara *offset = nullptr, WaitEvents &... events); +``` + +## 约束 + +- This instruction is currently implemented for specific targets (see `include/pto/npu/*/TQuant.hpp`). +- Input type requirements and output tile types are mode/target-dependent. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TRANDOM.md b/designs/outerCube/PTOISA/TRANDOM.md new file mode 100644 index 00000000..f7aa18a6 --- /dev/null +++ b/designs/outerCube/PTOISA/TRANDOM.md @@ -0,0 +1,122 @@ +# TRANDOM + +## Tile Operation Diagram + +![TRANDOM tile operation](../figures/isa/TRANDOM.svg) + +## Introduction + +Generates random numbers in the destination tile using a counter-based cipher algorithm. + +## Math Interpretation + +This instruction implements a counter-based random number generator. For each element in the valid region, it generates +pseudo-random values based on a key and counter state using a cipher-like transformation with configurable rounds. + +The algorithm uses: + +- 128-bit state (4 × 32-bit counters) +- 64-bit key (2 × 32-bit words) +- ChaCha-like quarter-round operations with vector instructions + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +trandom %dst, %key, %counter : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trandom %key, %counter : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trandom ins(%key, %counter : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ Intrinsic + +Declared in `include/pto/npu/a5/TRandom.hpp`: + +```cpp +template +PTO_INST void TRANDOM_IMPL(DstTile &dst, TRandomKey &key, TRandomCounter &counter); +``` + +## Constraints + +- **Implementation checks (A5)**: + - `DstTile::DType` must be one of: `int32_t`, `uint32_t`. + - Tile layout must be row-major (`DstTile::isRowMajor`). + - `Rounds` must be either 7 or 10 (default: 10). + - `key` and `counter` must not be null. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT dst; + TRandomKey key = {0x01234, 0x56789}; + TRandomCounter counter = {0, 0, 0, 0}; + TRANDOM_IMPL(dst, key, counter); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT dst; + TRandomKey key = {0x01234, 0x56789}; + TRandomCounter counter = {0, 0, 0, 0}; + TASSIGN(dst, 0x0); + TRANDOM_IMPL<10>(dst, key, counter); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trandom %key, %counter : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x3000) +%dst = pto.trandom %key, %counter : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +trandom %dst, %key, %counter : !pto.tile<...> +# AS Level 2 (DPS) +pto.trandom ins(%key, %counter : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TRANDOM_zh.md b/designs/outerCube/PTOISA/TRANDOM_zh.md new file mode 100644 index 00000000..d6dfc933 --- /dev/null +++ b/designs/outerCube/PTOISA/TRANDOM_zh.md @@ -0,0 +1,122 @@ +# TRANDOM + +## Tile Operation Diagram + +![TRANDOM tile operation](../figures/isa/TRANDOM.svg) + +## 简介 + +使用基于计数器的密码算法在目标 Tile 中生成随机数。 + +## 数学解释 + +该指令实现了一个基于计数器的随机数生成器。对于有效区域中的每个元素,它基于密钥和计数器状态,使用可配置轮数的密码类变换生成 +伪随机值。 + +该算法使用: + +- 128 位状态(4 × 32 位计数器) +- 64 位密钥(2 × 32 位字) +- 类似 ChaCha 的四分之一轮操作,使用向量指令 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS.md)。 + +同步形式: + +```text +trandom %dst, %key, %counter : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trandom %key, %counter : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trandom ins(%key, %counter : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内置函数 + +声明于 `include/pto/npu/a5/TRandom.hpp`: + +```cpp +template +PTO_INST void TRANDOM_IMPL(DstTile &dst, TRandomKey &key, TRandomCounter &counter); +``` + +## 约束条件 + +- **实现检查(A5)**: + - `DstTile::DType` 必须为以下类型之一:`int32_t`、`uint32_t`。 + - Tile 布局必须为行主序(`DstTile::isRowMajor`)。 + - `Rounds` 必须为 7 或 10(默认为 10)。 + - `key` 和 `counter` 不能为空。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +### Auto 模式 + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT dst; + TRandomKey key = {0x01234, 0x56789}; + TRandomCounter counter = {0, 0, 0, 0}; + TRANDOM_IMPL(dst, key, counter); +} +``` + +### Manual 模式 + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT dst; + TRandomKey key = {0x01234, 0x56789}; + TRandomCounter counter = {0, 0, 0, 0}; + TASSIGN(dst, 0x0); + TRANDOM_IMPL<10>(dst, key, counter); +} +``` + +## 汇编形式示例 + +### Auto 模式 + +```text +# Auto 模式:编译器/运行时管理的布局和调度。 +%dst = pto.trandom %key, %counter : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual 模式 + +```text +# Manual 模式:在发出指令之前显式绑定资源。 +# Tile 操作数可选: +# pto.tassign %arg0, @tile(0x3000) +%dst = pto.trandom %key, %counter : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +trandom %dst, %key, %counter : !pto.tile<...> +# AS Level 2 (DPS) +pto.trandom ins(%key, %counter : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TRECIP.md b/designs/outerCube/PTOISA/TRECIP.md new file mode 100644 index 00000000..4b592b63 --- /dev/null +++ b/designs/outerCube/PTOISA/TRECIP.md @@ -0,0 +1,113 @@ +# TRECIP + + +## Tile Operation Diagram + +![TRECIP tile operation](../figures/isa/TRECIP.svg) + +## Introduction + +Elementwise reciprocal of a tile. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \frac{1}{\mathrm{src}_{i,j}} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trecip %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trecip %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trecip ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.trecip %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trecip ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TRECIP(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (NPU)**: + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile layout must be row-major (`TileData::isRowMajor`). + - A3's TRECIP instruction does not support setting the source Tile and destination Tile to the same memory. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Domain / NaN**: + - Division-by-zero behavior is target-defined; the CPU simulator asserts in debug builds. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TRECIP(out, x); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trecip %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trecip %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trecip %src : !pto.tile<...> +# AS Level 2 (DPS) +pto.trecip ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TRECIP_zh.md b/designs/outerCube/PTOISA/TRECIP_zh.md new file mode 100644 index 00000000..f93f9086 --- /dev/null +++ b/designs/outerCube/PTOISA/TRECIP_zh.md @@ -0,0 +1,86 @@ +# TRECIP + +## 指令示意图 + +![TRECIP tile operation](../figures/isa/TRECIP.svg) + +## 简介 + +Tile 的逐元素倒数。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \frac{1}{\mathrm{src}_{i,j}} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = trecip %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trecip %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trecip ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trecip %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trecip ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TRECIP(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (NPU)**: + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - A3's TRECIP instruction does not support setting the source Tile and destination Tile to the same memory. +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Domain / NaN**: + - Division-by-zero behavior is target-defined; the CPU simulator asserts in debug builds. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TRECIP(out, x); +} +``` diff --git a/designs/outerCube/PTOISA/TRELU.md b/designs/outerCube/PTOISA/TRELU.md new file mode 100644 index 00000000..0a85211e --- /dev/null +++ b/designs/outerCube/PTOISA/TRELU.md @@ -0,0 +1,116 @@ +# TRELU + + +## Tile Operation Diagram + +![TRELU tile operation](../figures/isa/TRELU.svg) + +## Introduction + +Elementwise ReLU of a tile. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \max(\mathrm{src}_{i,j}, 0) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trelu %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trelu %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trelu ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.trelu %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trelu ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TRELU(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `half`, `float`, `int32_t`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src` and `dst` tiles should have the same `validRow/validCol`. +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `half`, `float`, `int32_t`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src` and `dst` tiles should have the same `validRow/validCol`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src/dst` are assumed to be compatible (not validated by explicit runtime checks in this op). + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TRELU(out, x); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trelu %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trelu %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trelu %src : !pto.tile<...> +# AS Level 2 (DPS) +pto.trelu ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TRELU_zh.md b/designs/outerCube/PTOISA/TRELU_zh.md new file mode 100644 index 00000000..7024ffd7 --- /dev/null +++ b/designs/outerCube/PTOISA/TRELU_zh.md @@ -0,0 +1,89 @@ +# TRELU + +## 指令示意图 + +![TRELU tile operation](../figures/isa/TRELU.svg) + +## 简介 + +Tile 的逐元素 ReLU。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \max(\mathrm{src}_{i,j}, 0) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = trelu %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trelu %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trelu ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trelu %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trelu ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TRELU(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `half`, `float`, `int32_t`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src` and `dst` tiles should have the same `validRow/validCol`. +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `half`, `float`, `int32_t`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src` and `dst` tiles should have the same `validRow/validCol`. +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src/dst` are assumed to be compatible (not validated by explicit runtime checks in this op). + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TRELU(out, x); +} +``` diff --git a/designs/outerCube/PTOISA/TREM.md b/designs/outerCube/PTOISA/TREM.md new file mode 100644 index 00000000..88968044 --- /dev/null +++ b/designs/outerCube/PTOISA/TREM.md @@ -0,0 +1,115 @@ +# TREM + + +## Tile Operation Diagram + +![TREM tile operation](../figures/isa/TREM.svg) + +## Introduction + +Elementwise remainder of two tiles. The result has the same sign as the divider. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \bmod \mathrm{src1}_{i,j}$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trem %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trem %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trem ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TREM(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- **Implementation Checks (A2A3)**: + - `dst`, `src0`, and `src1` must use the same element type. + - Supported element types: `float` and `int32_t`. + - `dst`, `src0`, and `src1` must be vector tiles. + - `dst`, `src0`, and `src1` must be row-major. + - Runtime: `dst.GetValidRow() == src0.GetValidRow() == src1.GetValidRow() > 0` and `dst.GetValidCol() == src0.GetValidCol() == src1.GetValidCol() > 0`. + - **tmp Buffer Requirements**: + - `tmp.GetValidCol() >= dst.GetValidCol()` (at least as many columns as dst) + - `tmp.GetValidRow() >= 1` (at least 1 row) + - Data type must match `TileDataDst::DType`. +- **Implementation Checks (A5)**: + - `dst`, `src0`, and `src1` must use the same element type. + - Supported element types: `float`, `int32_t`, `uint32_t`, `half`, `int16_t`, and `uint16_t`. + - `dst`, `src0`, and `src1` must be vector tiles. + - Static valid bounds: `ValidRow <= Rows` and `ValidCol <= Cols` for all tiles. + - Runtime: `dst.GetValidRow() == src0.GetValidRow() == src1.GetValidRow()` and `dst.GetValidCol() == src0.GetValidCol() == src1.GetValidCol()`. + - Note: tmp parameter is accepted but not validated or used on A5. +- **Division by Zero**: + - Behavior is target-defined; the CPU simulator asserts in debug builds. +- **Valid Region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **For `int32_t` Inputs (A2A3 Only)**: Both `src0` and `src1` elements must be in the range `[-2^24, 2^24]` (i.e., `[-16777216, 16777216]`) to ensure exact conversion to float32 during computation. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT out, a, b; + Tile tmp; + TREM(out, a, b, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trem %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trem %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trem %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.trem ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TREMS.md b/designs/outerCube/PTOISA/TREMS.md new file mode 100644 index 00000000..03c323be --- /dev/null +++ b/designs/outerCube/PTOISA/TREMS.md @@ -0,0 +1,115 @@ +# TREMS + + +## Tile Operation Diagram + +![TREMS tile operation](../figures/isa/TREMS.svg) + +## Introduction + +Elementwise remainder with a scalar: `%`. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$\mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \bmod \mathrm{scalar}$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trems %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trems %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trems ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TREMS(TileDataDst &dst, TileDataSrc &src, typename TileDataSrc::DType scalar, + TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- **Implementation Checks (A2A3)**: + - `dst` and `src` must use the same element type. + - Supported element types: `float` and `int32_t`. + - `dst` and `src` must be vector tiles. + - `dst` and `src` must be row-major. + - Runtime: `dst.GetValidRow() == src.GetValidRow() > 0` and `dst.GetValidCol() == src.GetValidCol() > 0`. + - **tmp Buffer Requirements**: + - `tmp.GetValidCol() >= dst.GetValidCol()` (at least as many columns as dst) + - `tmp.GetValidRow() >= 1` (at least 1 row) + - Data type must match `TileDataDst::DType`. +- **Implementation Checks (A5)**: + - `dst` and `src` must use the same element type. + - Supported element types: `float`, `int32_t`, `uint32_t`, `half`, `int16_t`, and `uint16_t`. + - `dst` and `src` must be vector tiles. + - Static valid bounds: `ValidRow <= Rows` and `ValidCol <= Cols` for both tiles. + - Runtime: `dst.GetValidRow() == src.GetValidRow()` and `dst.GetValidCol() == src.GetValidCol()`. + - Note: tmp parameter is accepted but not validated or used on A5. +- **Division by Zero**: + - Behavior is target-defined; the CPU simulator asserts in debug builds. +- **Valid Region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **For `int32_t` Inputs (A2A3 Only)**: Both `src` elements and `scalar` must be in the range `[-2^24, 2^24]` (i.e., `[-16777216, 16777216]`) to ensure exact conversion to float32 during computation. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + Tile tmp; + TREMS(out, x, 3.0f, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trems %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trems %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trems %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.trems ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TREMS_zh.md b/designs/outerCube/PTOISA/TREMS_zh.md new file mode 100644 index 00000000..b3e3bb51 --- /dev/null +++ b/designs/outerCube/PTOISA/TREMS_zh.md @@ -0,0 +1,115 @@ +# TREMS + +## 指令示意图 + +![TREMS tile operation](../figures/isa/TREMS.svg) + +## 简介 + +与标量的逐元素余数:`remainder(src, scalar)`。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$\mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \bmod \mathrm{scalar}$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = trems %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trems %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trems ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TREMS(TileDataDst &dst, TileDataSrc &src, typename TileDataSrc::DType scalar, + TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `dst` 和 `src` 必须使用相同的元素类型。 + - 支持的元素类型:`float` 和 `int32_t`。 + - `dst` 和 `src` 必须是向量 Tile。 + - `dst` 和 `src` 必须是行主序。 + - 运行时:`dst.GetValidRow() == src.GetValidRow() > 0` 且 `dst.GetValidCol() == src.GetValidCol() > 0`。 + - **tmp 缓冲区要求**: + - `tmp.GetValidCol() >= dst.GetValidCol()`(至少与 dst 相同的列数) + - `tmp.GetValidRow() >= 1`(至少 1 行) + - 数据类型必须与 `TileDataDst::DType` 匹配。 +- **实现检查 (A5)**: + - `dst` 和 `src` 必须使用相同的元素类型。 + - 支持的元素类型:`float`、`int32_t`、`uint32_t`、`half`、`int16_t` 和 `uint16_t`。 + - `dst` 和 `src` 必须是向量 Tile。 + - 两个 Tile 的静态有效边界都必须满足 `ValidRow <= Rows` 且 `ValidCol <= Cols`。 + - 运行时:`dst.GetValidRow() == src.GetValidRow()` 且 `dst.GetValidCol() == src.GetValidCol()`。 + - 注意:tmp 参数在 A5 上被接受但不进行验证或使用。 +- **除零**: + - 行为由目标定义;CPU 模拟器在调试构建中会断言。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 +- **对于 `int32_t` 输入(仅 A2A3)**:`src` 的元素和 `scalar` 必须在 `[-2^24, 2^24]` 范围内(即 `[-16777216, 16777216]`),以确保在计算过程中能精确转换为 float32。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + Tile tmp; + TREMS(out, x, 3.0f, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.trems %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trems %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = trems %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.trems ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TREM_zh.md b/designs/outerCube/PTOISA/TREM_zh.md new file mode 100644 index 00000000..5011131c --- /dev/null +++ b/designs/outerCube/PTOISA/TREM_zh.md @@ -0,0 +1,113 @@ +# TREM + +## 指令示意图 + +![TREM tile operation](../figures/isa/TREM.svg) + +## 简介 + +两个 Tile 的逐元素余数运算。结果符号与除数相同。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \bmod \mathrm{src1}_{i,j}$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = trem %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trem %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trem ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TREM(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - 支持的元素类型:`float` 和 `int32_t`。 + - `dst`、`src0` 和 `src1` 必须是向量 Tile。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - 运行时:`dst.GetValidRow() == src0.GetValidRow() == src1.GetValidRow() > 0` 且 `dst.GetValidCol() == src0.GetValidCol() == src1.GetValidCol() > 0`。 + - **tmp 缓冲区要求**: + - `tmp.GetValidCol() >= dst.GetValidCol()`(至少与 dst 相同的列数) + - `tmp.GetValidRow() >= 1`(至少 1 行) + - 数据类型必须与 `TileDataDst::DType` 匹配。 +- **实现检查 (A5)**: + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - 支持的元素类型:`float`、`int32_t`、`uint32_t`、`half`、`int16_t` 和 `uint16_t`。 + - `dst`、`src0` 和 `src1` 必须是向量 Tile。 + - 静态有效边界:所有 Tile 都必须满足 `ValidRow <= Rows` 且 `ValidCol <= Cols`。 + - 运行时:`dst.GetValidRow() == src0.GetValidRow() == src1.GetValidRow()` 且 `dst.GetValidCol() == src0.GetValidCol() == src1.GetValidCol()`。 + - 注意:tmp 参数在 A5 上被接受但不进行验证或使用。 +- **除零**: + - 行为由目标定义;CPU 模拟器在调试构建中会断言。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 +- **对于 `int32_t` 输入(仅 A2A3)**:`src0` 和 `src1` 的所有元素必须在 `[-2^24, 2^24]` 范围内(即 `[-16777216, 16777216]`),以确保在计算过程中能精确转换为 float32。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT out, a, b; + Tile tmp; + TREM(out, a, b, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.trem %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trem %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = trem %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.trem ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TRESHAPE.md b/designs/outerCube/PTOISA/TRESHAPE.md new file mode 100644 index 00000000..ef5ca45d --- /dev/null +++ b/designs/outerCube/PTOISA/TRESHAPE.md @@ -0,0 +1,115 @@ +# TRESHAPE + + +## Tile Operation Diagram + +![TRESHAPE tile operation](../figures/isa/TRESHAPE.svg) + +## Introduction + +Reinterpret a tile as another tile type/shape while preserving the underlying bytes. + +This is a *bitwise* reshape: it does not change values, it only changes how the same byte buffer is viewed. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +```text +%dst = treshape %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.treshape %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.treshape ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.treshape %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.treshape ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TRESHAPE(TileDataOut &dst, TileDataIn &src, WaitEvents &... events); +``` + +## Constraints + +Enforced by `TRESHAPE_IMPL`: + +- **Tile type must match**: `TileDataIn::Loc == TileDataOut::Loc`. +- **Total byte size must match**: `sizeof(InElem) * InNumel == sizeof(OutElem) * OutNumel`. +- **No boxed/non-boxed conversion**: + - cannot reshape between `SLayout::NoneBox` and boxed layouts. + +## Notes + +- **CPU simulation**: implemented as a byte-for-byte copy into `dst`. +- **A2/A3**: implemented as an alias (`TASSIGN_IMPL(dst, src.data())`), so `dst` and `src` refer to the same underlying storage. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using Src = Tile; + using Dst = Tile; + static_assert(Src::Numel == Dst::Numel); + + Src src; + Dst dst; + TRESHAPE(dst, src); +} +``` + +## Math Interpretation + +Unless otherwise specified, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.treshape %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.treshape %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.treshape %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.treshape ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TRESHAPE_zh.md b/designs/outerCube/PTOISA/TRESHAPE_zh.md new file mode 100644 index 00000000..616c08b8 --- /dev/null +++ b/designs/outerCube/PTOISA/TRESHAPE_zh.md @@ -0,0 +1,81 @@ +# TRESHAPE + +## 指令示意图 + +![TRESHAPE tile operation](../figures/isa/TRESHAPE.svg) + +## 简介 + +将 Tile 重新解释为另一种 Tile 类型/形状,同时保留底层字节。 + +## 数学语义 + +除非另有说明, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +```text +%dst = treshape %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.treshape %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.treshape ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.treshape %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.treshape ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TRESHAPE(TileDataOut &dst, TileDataIn &src, WaitEvents &... events); +``` + +## 约束 + +Enforced by `TRESHAPE_IMPL`: + +- **Tile type must match**: `TileDataIn::Loc == TileDataOut::Loc`. +- **Total byte size must match**: `sizeof(InElem) * InNumel == sizeof(OutElem) * OutNumel`. +- **No boxed/non-boxed conversion**: + - cannot reshape between `SLayout::NoneBox` and boxed layouts. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using Src = Tile; + using Dst = Tile; + static_assert(Src::Numel == Dst::Numel); + + Src src; + Dst dst; + TRESHAPE(dst, src); +} +``` diff --git a/designs/outerCube/PTOISA/TROWARGMAX.md b/designs/outerCube/PTOISA/TROWARGMAX.md new file mode 100644 index 00000000..470e0bda --- /dev/null +++ b/designs/outerCube/PTOISA/TROWARGMAX.md @@ -0,0 +1,148 @@ +# TROWARGMAX + + +## Tile Operation Diagram + +![TROWARGMAX tile operation](../figures/isa/TROWARGMAX.svg) + +## Introduction + +Get the column index of the maximum element for each row. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \max_{0 \le j < C} j_{i} $$ + +## Assembly Syntax + +PTO-AS form: see `docs/grammar/PTO-AS.md`. + +Synchronous form: + +```text +%dst = trowargmax %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### IR Level 1 (SSA) + +```text +%dst = pto.trowargmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trowargmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWARGMAX(TileDataOut& dst, TileDataIn& src, TileDataTmp& tmp, WaitEvents&... events); +``` + +## Constraints + +Implementation checks (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile layout of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile layout of `dst`: + - **Compact Mode**: DN layout Tile of 1D, e.g., `Tile`, ROWS must be 32b aligned. + - **Traditional Mode**: ND layout Tile of 2D, e.g., `Tile`. + - Source data types: `half` or `float`. + - Destination data types: `uint32_t` or `int32_t`. + - Runtime valid checks: + - `srcValidCol != 0` and `srcValidRow != 0`. +- A5: + - Source data types: `half` or `float`. + - Destination data types: `uint32_t` or `int32_t`. + - No explicit runtime assertions on `validRow/validCol` in the implementation; the loops use `src.GetValidRow()` and `src.GetValidCol()`. + - `tmp` temporary tile is not used, only for compatibility use. + +### About temporary tile `tmp` for A3 + +* Temporary tile is not used when `srcValidCol <= ElementPerRepeat`, used when `srcValidCol > ElementPerRepeat`. +* `tmp` tile's rows is the same as `src`. +* Simply set `tmp` tile size the same as `src` when `src` is small. +* `tmp` tile's stride can be calculated out based on `src`'s `validCol` using the following formula: + +```text +repeats = ceil(validCol / elementPerRepeat) +stride = ceil(repeats * 2 / elementPerBlock) * elementPerBlock + ceil(repeats / elementPerBlock) * elementPerBlock +``` + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWARGMAX(dst, src, tmp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWARGMAX(dst, src, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowmax %src : !pto.tile<...> -> !pto.tile<...> +# IR Level 2 (DPS) +pto.trowmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TROWARGMAX_zh.md b/designs/outerCube/PTOISA/TROWARGMAX_zh.md new file mode 100644 index 00000000..01da87a4 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWARGMAX_zh.md @@ -0,0 +1,147 @@ +# TROWARGMAX + +## 指令示意图 + +![TROWARGMAX tile operation](../figures/isa/TROWARGMAX.svg) + +## 简介 + +获取每行最大值对应列索引。 + +## 数学语义 + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \max_{0 \le j < C} j_{i} $$ + +## 汇编语法 + +PTO-AS 形式:参见 `docs/grammar/PTO-AS.md`. + +同步形式: + +```text +%dst = trowargmax %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### IR Level 1(SSA) + +```text +%dst = pto.trowargmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2(DPS) + +```text +pto.trowargmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWARGMAX(TileDataOut& dst, TileDataIn& src, TileDataTmp& tmp, WaitEvents&... events); +``` + +## 约束 + +实现检查 (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile 布局 of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile 布局 of `dst`: + - **紧凑模式**:DN 布局的一维 Tile,例如 `Tile`,此时ROWS要做到32b对齐。 + - **传统模式**:ND 布局的二维 Tile,例如 `Tile`。 + - 源数据类型: `half` or `float`. + - 目标数据类型:`uint32_t` or `int32_t`. + - 运行期有效区域检查: + - `srcValidCol != 0` and `srcValidRow != 0`. +- A5: + - 源数据类型: `half` or `float`. + - 目标数据类型:`uint32_t` or `int32_t`. + - No explicit runtime assertions on `validRow/validCol` in the implementation; the loops use `src.GetValidRow()` and `src.GetValidCol()`. + - `tmp`临时Tile不使用,仅做兼容。 + +### A3 `tmp`临时Tile相关说明 + +* `tmp`临时Tile在`srcValidCol <= ElementPerRepeat`时不使用,`srcValidCol > ElementPerRepeat`时需要使用。 +* `tmp` tile的行数和`src` tile的行数相同。 +* 按以下公式根据`src` tile的`validCol`算出`tmp` tile所需stride: + +```text +repeats = ceil(validCol / elementPerRepeat) +stride = ceil(repeats * 2 / elementPerBlock) * elementPerBlock + ceil(repeats / elementPerBlock) * elementPerBlock +``` + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWARGMAX(dst, src, tmp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWARGMAX(dst, src, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.trowargmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowargmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = trowargmax %src : !pto.tile<...> -> !pto.tile<...> +# IR Level 2 (DPS) +pto.trowargmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TROWARGMIN.md b/designs/outerCube/PTOISA/TROWARGMIN.md new file mode 100644 index 00000000..e1d99930 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWARGMIN.md @@ -0,0 +1,148 @@ +# TROWARGMIN + + +## Tile Operation Diagram + +![TROWARGMIN tile operation](../figures/isa/TROWARGMIN.svg) + +## Introduction + +Get the column index of the minimum element for each row. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \min_{0 \le j < C} j_{i} $$ + +## Assembly Syntax + +PTO-AS form: see `docs/grammar/PTO-AS.md`. + +Synchronous form: + +```text +%dst = trowargmin %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### IR Level 1 (SSA) + +```text +%dst = pto.trowargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trowargmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWARGMIN(TileDataOut& dst, TileDataIn& src, TileDataTmp& tmp, WaitEvents&... events); +``` + +## Constraints + +Implementation checks (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile layout of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile layout of `dst`: + - **Compact Mode**: DN layout Tile of 1D, e.g., `Tile`, ROWS must be 32b aligned. + - **Traditional Mode**: ND layout Tile of 2D, e.g., `Tile`. + - Source data types: `half` or `float`. + - Destination data types: `uint32_t` or `int32_t`. + - Runtime valid checks: + - `srcValidCol != 0` and `srcValidRow != 0`. +- A5: + - Source data types: `half` or `float`. + - Destination data types: `uint32_t` or `int32_t`. + - No explicit runtime assertions on `validRow/validCol` in the implementation; the loops use `src.GetValidRow()` and `src.GetValidCol()`. + - `tmp` temporary tile is not used, only for compatibility use. + +### About temporary tile `tmp` for A3 + +* Temporary tile is not used when `srcValidCol <= ElementPerRepeat`, used when `srcValidCol > ElementPerRepeat`. +* `tmp` tile's rows is the same as `src`. +* Simply set `tmp` tile size the same as `src` when `src` is small. +* `tmp` tile's stride can be calculated out based on `src`'s `validCol` using the following formula: + +```text +repeats = ceil(validCol / elementPerRepeat) +stride = ceil(repeats * 2 / elementPerBlock) * elementPerBlock + ceil(repeats / elementPerBlock) * elementPerBlock +``` + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWARGMIN(dst, src, tmp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWARGMIN(dst, src, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowargmin %src : !pto.tile<...> -> !pto.tile<...> +# IR Level 2 (DPS) +pto.trowargmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TROWARGMIN_zh.md b/designs/outerCube/PTOISA/TROWARGMIN_zh.md new file mode 100644 index 00000000..4dae67f1 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWARGMIN_zh.md @@ -0,0 +1,147 @@ +# TROWARGMIN + +## 指令示意图 + +![TROWARGMIN tile operation](../figures/isa/TROWARGMIN.svg) + +## 简介 + +获取每行最小值对应列索引。 + +## 数学语义 + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \min_{0 \le j < C} j_{i} $$ + +## 汇编语法 + +PTO-AS 形式:参见 `docs/grammar/PTO-AS.md`. + +同步形式: + +```text +%dst = trowargmin %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### IR Level 1(SSA) + +```text +%dst = pto.trowargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2(DPS) + +```text +pto.trowargmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWARGMIN(TileDataOut& dst, TileDataIn& src, TileDataTmp& tmp, WaitEvents&... events); +``` + +## 约束 + +实现检查 (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile 布局 of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile 布局 of `dst`: + - **紧凑模式**:DN 布局的一维 Tile,例如 `Tile`,此时ROWS要做到32b对齐。 + - **传统模式**:ND 布局的二维 Tile,例如 `Tile`。 + - 源数据类型: `half` or `float`. + - 目标数据类型:`uint32_t` or `int32_t`. + - 运行期有效区域检查: + - `srcValidCol != 0` and `srcValidRow != 0`. +- A5: + - 源数据类型: `half` or `float`. + - 目标数据类型:`uint32_t` or `int32_t`. + - No explicit runtime assertions on `validRow/validCol` in the implementation; the loops use `src.GetValidRow()` and `src.GetValidCol()`. + - `tmp`临时Tile不使用,仅做兼容。 + +### A3 `tmp`临时Tile相关说明 + +* `tmp`临时Tile在`srcValidCol <= ElementPerRepeat`时不使用,`srcValidCol > ElementPerRepeat`时需要使用。 +* `tmp` tile的行数和`src` tile的行数相同。 +* 按以下公式根据`src` tile的`validCol`算出`tmp` tile所需stride: + +```text +repeats = ceil(validCol / elementPerRepeat) +stride = ceil(repeats * 2 / elementPerBlock) * elementPerBlock + ceil(repeats / elementPerBlock) * elementPerBlock +``` + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWARGMIN(dst, src, tmp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWARGMIN(dst, src, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.trowargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowargmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = trowargmin %src : !pto.tile<...> -> !pto.tile<...> +# IR Level 2 (DPS) +pto.trowargmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TROWEXPAND.md b/designs/outerCube/PTOISA/TROWEXPAND.md new file mode 100644 index 00000000..55d385d1 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPAND.md @@ -0,0 +1,132 @@ +# TROWEXPAND + + +## Tile Operation Diagram + +![TROWEXPAND tile operation](../figures/isa/TROWEXPAND.svg) + +## Introduction + +Broadcast the first element of each source row across the destination row. + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. For `0 <= i < R` and `0 <= j < C`: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,0} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.trowexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trowexpand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPAND(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## Constraints + +Implementation checks (NPU): + +- Tile Type: `dst` and `src` must be `TileType::Vec`. +- Tile layout: ND fractal (`isRowMajor` and `SLayout::NoneBox`) for both `src` and `dst`. +- Data type: A2A3/A5 element types must be one of: `int8_t` or `uint8_t` or `int16_t` or `uint16_t` or `int32_t` or `uint32_t` or `half` or `bfloat16_t` or `float`. +- Runtime valid checks: + - A2A3: returns early if any of `dstValidRow`, `dstValidCol`, `srcValidRow`, `srcValidCol` is zero. + - A5: asserts `srcValidRow == dstValidRow` and asserts `srcValidRow != 0 && srcValidCol != 0`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TROWEXPAND(dst, src); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TROWEXPAND(dst, src); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowexpand %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowexpand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TROWEXPANDADD.md b/designs/outerCube/PTOISA/TROWEXPANDADD.md new file mode 100644 index 00000000..8f8b334f --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDADD.md @@ -0,0 +1,106 @@ +# TROWEXPANDADD + + +## Tile Operation Diagram + +![TROWEXPANDADD tile operation](../figures/isa/TROWEXPANDADD.svg) + +## Introduction + +Row-wise broadcast add: add a per-row scalar vector `src1` to each row of `src0`. + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_i` be the per-row scalar taken from `src1` (one value per row). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} + s_i +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpandadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.trowexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trowexpandadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDADD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDADD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType` +- `TileDataDst::DType`, `TileDataSrc0::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. +- Mode 1: `src1` is expected to provide **one scalar per row** (i.e., its valid shape must cover `R` values). +- Mode 2: `src1` is expected to provide **32 bytes data per row**. +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TRowExpand*.hpp`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowexpandadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TROWEXPANDADD_zh.md b/designs/outerCube/PTOISA/TROWEXPANDADD_zh.md new file mode 100644 index 00000000..6170b26b --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDADD_zh.md @@ -0,0 +1,79 @@ +# TROWEXPANDADD + +## 指令示意图 + +![TROWEXPANDADD tile operation](../figures/isa/TROWEXPANDADD.svg) + +## 简介 + +行广播加法:加上一个每行标量向量。 + +## 数学语义 + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_i` be the per-row scalar taken from `src1` (one value per row). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} + s_i +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = trowexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpandadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trowexpandadd %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowexpandadd ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDADD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDADD(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType` +- `TileDataDst::DType`, `TileDataSrc0::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile 形状/布局约束 (compile-time): `TileDataDst::isRowMajor`. +- Mode 1: `src1` is expected to provide **one scalar per row** (i.e., its valid shape must cover `R` values). +- Mode 2: `src1` is expected to provide **32 bytes data per row**. +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TRowExpand*.hpp`. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TROWEXPANDDIV.md b/designs/outerCube/PTOISA/TROWEXPANDDIV.md new file mode 100644 index 00000000..5a116155 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDDIV.md @@ -0,0 +1,126 @@ +# TROWEXPANDDIV + + +## Tile Operation Diagram + +![TROWEXPANDDIV tile operation](../figures/isa/TROWEXPANDDIV.svg) + +## Introduction + +Row-wise broadcast divide: divide each row of `src0` by a per-row scalar vector `src1`. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \frac{\mathrm{src0}_{i,j}}{\mathrm{src1}_{0,i}} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpanddiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDDIV(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDDIV(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks**: + - `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType` (compile-time). + - `TileDataDst::DType`, `TileDataSrc0::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. + - Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. + - Mode 1: `src1` is expected to provide **one scalar per row** (i.e., its valid shape must cover `R` values). + - Mode 2: `src1` is expected to provide **32 bytes data per row**. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TROWEXPANDDIV(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TASSIGN(src0, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(src1, 0x3000); + TROWEXPANDDIV(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowexpanddiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TROWEXPANDDIV_zh.md b/designs/outerCube/PTOISA/TROWEXPANDDIV_zh.md new file mode 100644 index 00000000..0f5523ea --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDDIV_zh.md @@ -0,0 +1,126 @@ +# TROWEXPANDDIV + +## 指令示意图 + +![TROWEXPANDDIV tile operation](../figures/isa/TROWEXPANDDIV.svg) + +## 简介 + +行广播除法:将 `src0` 的每一行除以一个每行标量向量 `src1`。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \frac{\mathrm{src0}_{i,j}}{\mathrm{src1}_{0,i}} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = trowexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trowexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowexpanddiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDDIV(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDDIV(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- **实现检查**: + - `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType`(编译时)。 + - `TileDataDst::DType`、`TileDataSrc0::DType`、`TileDataSrc1::DType` 必须是以下之一:`half`、`float`。 + - Tile 形状/布局约束(编译时):`TileDataDst::isRowMajor`。 + - 模式 1:`src1` 预期提供**每行一个标量**(即,其有效形状必须覆盖 `R` 个值)。 + - 模式 2:`src1` 预期提供**每行 32 字节数据**。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TROWEXPANDDIV(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TASSIGN(src0, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(src1, 0x3000); + TROWEXPANDDIV(dst, src0, src1); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.trowexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = trowexpanddiv %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowexpanddiv ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TROWEXPANDEXPDIF.md b/designs/outerCube/PTOISA/TROWEXPANDEXPDIF.md new file mode 100644 index 00000000..d5551a18 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDEXPDIF.md @@ -0,0 +1,106 @@ +# TROWEXPANDEXPDIF + + +## Tile Operation Diagram + +![TROWEXPANDEXPDIF tile operation](../figures/isa/TROWEXPANDEXPDIF.svg) + +## Introduction + +Row-wise exp-diff: compute `exp(src0 - src1)` where `src1` provides one scalar per row. + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_i` be the per-row scalar taken from `src1` (one value per row). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \exp(\mathrm{src0}_{i,j} - s_i) +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpandexpdif ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.trowexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trowexpandexpdif ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDEXPDIF(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDEXPDIF(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType` +- `TileDataDst::DType`, `TileDataSrc0::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. +- Mode 1: `src1` is expected to provide **one scalar per row** (i.e., its valid shape must cover `R` values). +- Mode 2: `src1` is expected to provide **32 bytes data per row**. +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TRowExpand*.hpp`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowexpandexpdif ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TROWEXPANDEXPDIF_zh.md b/designs/outerCube/PTOISA/TROWEXPANDEXPDIF_zh.md new file mode 100644 index 00000000..7a98cd6f --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDEXPDIF_zh.md @@ -0,0 +1,79 @@ +# TROWEXPANDEXPDIF + +## 指令示意图 + +![TROWEXPANDEXPDIF tile operation](../figures/isa/TROWEXPANDEXPDIF.svg) + +## 简介 + +行指数差运算:计算 exp(src0 - src1),其中 src1 为每行标量。 + +## 数学语义 + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_i` be the per-row scalar taken from `src1` (one value per row). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \exp(\mathrm{src0}_{i,j} - s_i) +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = trowexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpandexpdif ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trowexpandexpdif %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowexpandexpdif ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDEXPDIF(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDEXPDIF(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType` +- `TileDataDst::DType`, `TileDataSrc0::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile 形状/布局约束 (compile-time): `TileDataDst::isRowMajor`. +- Mode 1: `src1` is expected to provide **one scalar per row** (i.e., its valid shape must cover `R` values). +- Mode 2: `src1` is expected to provide **32 bytes data per row**. +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TRowExpand*.hpp`. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TROWEXPANDMAX.md b/designs/outerCube/PTOISA/TROWEXPANDMAX.md new file mode 100644 index 00000000..88ddf649 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDMAX.md @@ -0,0 +1,106 @@ +# TROWEXPANDMAX + + +## Tile Operation Diagram + +![TROWEXPANDMAX tile operation](../figures/isa/TROWEXPANDMAX.svg) + +## Introduction + +Row-wise broadcast max: take `max(src0, src1)` where `src1` provides one scalar per row. + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_i` be the per-row scalar taken from `src1` (one value per row). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \max(\mathrm{src0}_{i,j}, s_i) +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpandmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.trowexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trowexpandmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDMAX(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDMAX(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType` +- `TileDataDst::DType`, `TileDataSrc0::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. +- Mode 1: `src1` is expected to provide **one scalar per row** (i.e., its valid shape must cover `R` values). +- Mode 2: `src1` is expected to provide **32 bytes data per row**. +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TRowExpand*.hpp`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowexpandmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TROWEXPANDMAX_zh.md b/designs/outerCube/PTOISA/TROWEXPANDMAX_zh.md new file mode 100644 index 00000000..f763dfa3 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDMAX_zh.md @@ -0,0 +1,79 @@ +# TROWEXPANDMAX + +## 指令示意图 + +![TROWEXPANDMAX tile operation](../figures/isa/TROWEXPANDMAX.svg) + +## 简介 + +行广播最大值:与每行标量向量取最大值。 + +## 数学语义 + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_i` be the per-row scalar taken from `src1` (one value per row). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \max(\mathrm{src0}_{i,j}, s_i) +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = trowexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpandmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trowexpandmax %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowexpandmax ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDMAX(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDMAX(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType` +- `TileDataDst::DType`, `TileDataSrc0::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile 形状/布局约束 (compile-time): `TileDataDst::isRowMajor`. +- Mode 1: `src1` is expected to provide **one scalar per row** (i.e., its valid shape must cover `R` values). +- Mode 2: `src1` is expected to provide **32 bytes data per row**. +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TRowExpand*.hpp`. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TROWEXPANDMIN.md b/designs/outerCube/PTOISA/TROWEXPANDMIN.md new file mode 100644 index 00000000..e981807c --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDMIN.md @@ -0,0 +1,106 @@ +# TROWEXPANDMIN + + +## Tile Operation Diagram + +![TROWEXPANDMIN tile operation](../figures/isa/TROWEXPANDMIN.svg) + +## Introduction + +Row-wise broadcast min: take `min(src0, src1)` where `src1` provides one scalar per row. + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_i` be the per-row scalar taken from `src1` (one value per row). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \min(\mathrm{src0}_{i,j}, s_i) +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpandmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.trowexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trowexpandmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDMIN(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDMIN(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType` +- `TileDataDst::DType`, `TileDataSrc0::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. +- Mode 1: `src1` is expected to provide **one scalar per row** (i.e., its valid shape must cover `R` values). +- Mode 2: `src1` is expected to provide **32 bytes data per row**. +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TRowExpand*.hpp`. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowexpandmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TROWEXPANDMIN_zh.md b/designs/outerCube/PTOISA/TROWEXPANDMIN_zh.md new file mode 100644 index 00000000..e3895fd9 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDMIN_zh.md @@ -0,0 +1,79 @@ +# TROWEXPANDMIN + +## 指令示意图 + +![TROWEXPANDMIN tile operation](../figures/isa/TROWEXPANDMIN.svg) + +## 简介 + +行广播最小值:与每行标量向量取最小值。 + +## 数学语义 + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `s_i` be the per-row scalar taken from `src1` (one value per row). + +For `0 <= i < R` and `0 <= j < C`: + +$$ +\mathrm{dst}_{i,j} = \min(\mathrm{src0}_{i,j}, s_i) +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = trowexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpandmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trowexpandmin %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowexpandmin ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDMIN(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDMIN(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType` +- `TileDataDst::DType`, `TileDataSrc0::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. +- Tile 形状/布局约束 (compile-time): `TileDataDst::isRowMajor`. +- Mode 1: `src1` is expected to provide **one scalar per row** (i.e., its valid shape must cover `R` values). +- Mode 2: `src1` is expected to provide **32 bytes data per row**. +- Exact layout/fractal constraints are target-specific; see backend headers under `include/pto/npu/*/TRowExpand*.hpp`. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TROWEXPANDMUL.md b/designs/outerCube/PTOISA/TROWEXPANDMUL.md new file mode 100644 index 00000000..76a11fb4 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDMUL.md @@ -0,0 +1,126 @@ +# TROWEXPANDMUL + + +## Tile Operation Diagram + +![TROWEXPANDMUL tile operation](../figures/isa/TROWEXPANDMUL.svg) + +## Introduction + +Row-wise broadcast multiply: multiply each row of `src0` by a per-row scalar vector `src1`. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \cdot \mathrm{src1}_{0,i} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpandmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDMUL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDMUL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks**: + - `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType` (compile-time). + - `TileDataDst::DType`, `TileDataSrc0::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. + - Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. + - Mode 1: `src1` is expected to provide **one scalar per row** (i.e., its valid shape must cover `R` values). + - Mode 2: `src1` is expected to provide **32 bytes data per row**. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TROWEXPANDMUL(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TASSIGN(src0, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(src1, 0x3000); + TROWEXPANDMUL(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowexpandmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TROWEXPANDMUL_zh.md b/designs/outerCube/PTOISA/TROWEXPANDMUL_zh.md new file mode 100644 index 00000000..f27d717f --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDMUL_zh.md @@ -0,0 +1,126 @@ +# TROWEXPANDMUL + +## 指令示意图 + +![TROWEXPANDMUL tile operation](../figures/isa/TROWEXPANDMUL.svg) + +## 简介 + +行广播乘法:将 `src0` 的每一行乘以一个每行标量向量 `src1`。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \cdot \mathrm{src1}_{0,i} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = trowexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trowexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowexpandmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDMUL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDMUL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- **实现检查**: + - `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType`(编译时)。 + - `TileDataDst::DType`、`TileDataSrc0::DType`、`TileDataSrc1::DType` 必须是以下之一:`half`、`float`。 + - Tile 形状/布局约束(编译时):`TileDataDst::isRowMajor`。 + - 模式 1:`src1` 预期提供**每行一个标量**(即,其有效形状必须覆盖 `R` 个值)。 + - 模式 2:`src1` 预期提供**每行 32 字节数据**。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TROWEXPANDMUL(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TASSIGN(src0, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(src1, 0x3000); + TROWEXPANDMUL(dst, src0, src1); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.trowexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = trowexpandmul %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowexpandmul ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TROWEXPANDSUB.md b/designs/outerCube/PTOISA/TROWEXPANDSUB.md new file mode 100644 index 00000000..15f38477 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDSUB.md @@ -0,0 +1,126 @@ +# TROWEXPANDSUB + + +## Tile Operation Diagram + +![TROWEXPANDSUB tile operation](../figures/isa/TROWEXPANDSUB.svg) + +## Introduction + +Row-wise broadcast subtract: subtract a per-row scalar vector `src1` from each row of `src0`. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} - \mathrm{src1}_{0,i} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpandsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDSUB(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDSUB(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks**: + - `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType` (compile-time). + - `TileDataDst::DType`, `TileDataSrc0::DType`, `TileDataSrc1::DType` must be one of: `half`, `float`. + - Tile shape/layout constraint (compile-time): `TileDataDst::isRowMajor`. + - Mode 1: `src1` is expected to provide **one scalar per row** (i.e., its valid shape must cover `R` values). + - Mode 2: `src1` is expected to provide **32 bytes data per row**. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TROWEXPANDSUB(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TASSIGN(src0, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(src1, 0x3000); + TROWEXPANDSUB(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowexpandsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TROWEXPANDSUB_zh.md b/designs/outerCube/PTOISA/TROWEXPANDSUB_zh.md new file mode 100644 index 00000000..d1dad849 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPANDSUB_zh.md @@ -0,0 +1,126 @@ +# TROWEXPANDSUB + +## 指令示意图 + +![TROWEXPANDSUB tile operation](../figures/isa/TROWEXPANDSUB.svg) + +## 简介 + +行广播减法:从 `src0` 的每一行中减去一个每行标量向量 `src1`。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} - \mathrm{src1}_{0,i} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = trowexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trowexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowexpandsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPANDSUB(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); + +template +PTO_INST RecordEvent TROWEXPANDSUB(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- **实现检查**: + - `TileDataDst::DType == TileDataSrc0::DType == TileDataSrc1::DType`(编译时)。 + - `TileDataDst::DType`、`TileDataSrc0::DType`、`TileDataSrc1::DType` 必须是以下之一:`half`、`float`。 + - Tile 形状/布局约束(编译时):`TileDataDst::isRowMajor`。 + - 模式 1:`src1` 预期提供**每行一个标量**(即,其有效形状必须覆盖 `R` 个值)。 + - 模式 2:`src1` 预期提供**每行 32 字节数据**。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TROWEXPANDSUB(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + using RowVecT = Tile; + + TileT src0, dst; + RowVecT src1(16); + TASSIGN(src0, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(src1, 0x3000); + TROWEXPANDSUB(dst, src0, src1); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.trowexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = trowexpandsub %src0, %src1 : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowexpandsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TROWEXPAND_zh.md b/designs/outerCube/PTOISA/TROWEXPAND_zh.md new file mode 100644 index 00000000..d0dd216d --- /dev/null +++ b/designs/outerCube/PTOISA/TROWEXPAND_zh.md @@ -0,0 +1,105 @@ +# TROWEXPAND + +## 指令示意图 + +![TROWEXPAND tile operation](../figures/isa/TROWEXPAND.svg) + +## 简介 + +将每个源行的第一个元素广播到目标行中。 + +## 数学语义 + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. For `0 <= i < R` and `0 <= j < C`: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,0} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = trowexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trowexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowexpand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trowexpand %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowexpand ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWEXPAND(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## 约束 + +实现检查 (NPU): + +- Tile Type: `dst` and `src` must be `TileType::Vec`. +- Tile 布局: ND fractal (`isRowMajor` and `SLayout::NoneBox`) for both `src` and `dst`. +- Data type: A2A3/A5 element types must be one of: `int8_t` or `uint8_t` or `int16_t` or `uint16_t` or `int32_t` or `uint32_t` or `half` or `bfloat16_t` or `float`. +- 运行期有效区域检查: + - A2A3: returns early if any of `dstValidRow`, `dstValidCol`, `srcValidRow`, `srcValidCol` is zero. + - A5: asserts `srcValidRow == dstValidRow` and asserts `srcValidRow != 0 && srcValidCol != 0`. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TROWEXPAND(dst, src); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + SrcT src; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TROWEXPAND(dst, src); +} +``` diff --git a/designs/outerCube/PTOISA/TROWMAX.md b/designs/outerCube/PTOISA/TROWMAX.md new file mode 100644 index 00000000..67c2254c --- /dev/null +++ b/designs/outerCube/PTOISA/TROWMAX.md @@ -0,0 +1,135 @@ +# TROWMAX + + +## Tile Operation Diagram + +![TROWMAX tile operation](../figures/isa/TROWMAX.svg) + +## Introduction + +Reduce each row by taking the maximum across columns. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \max_{0 \le j < C} \mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowmax %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### AS Level 1 (SSA) + +```text +%dst = pto.trowmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWMAX(TileDataOut &dst, TileDataIn &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +Implementation checks (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile layout of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile layout of `dst`: + - **Compact Mode**: DN layout Tile of 1D, e.g., `Tile`, ROWS must be 32b aligned. + - **Traditional Mode**: ND layout Tile of 2D, e.g., `Tile`. + - Data types: `half` or `float`. + - DType consistency: `dst.DType == src.DType`. + - Runtime valid checks: + - `srcValidCol != 0` and `srcValidRow != 0`. + - `srcValidRow == dstValidRow` (the output valid row must match the input valid row). +- A5: + - Data types: `half` or `float`. + - DType consistency: `dst.DType == src.DType`. + - No explicit runtime assertions on `validRow/validCol` in the implementation; the loops use `src.GetValidRow()` and `src.GetValidCol()`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWMAX(dst, src, tmp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWMAX(dst, src, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowmax %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TROWMAX_zh.md b/designs/outerCube/PTOISA/TROWMAX_zh.md new file mode 100644 index 00000000..1795309c --- /dev/null +++ b/designs/outerCube/PTOISA/TROWMAX_zh.md @@ -0,0 +1,135 @@ +# TROWMAX + +## 指令示意图 + +![TROWMAX tile operation](../figures/isa/TROWMAX.svg) + +## 简介 + +通过取列间最大值来归约每一行。 + +## 数学语义 + +设 `R = src.GetValidRow()`,`C = src.GetValidCol()`。对 `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \max_{0 \le j < C} \mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = trowmax %src : !pto.tile<...> -> !pto.tile<...> +``` +降低时可能引入内部临时 Tile;C++ 内建接口需要显式传入 `tmp` 操作数。 + +### AS Level 1(SSA) + +```text +%dst = pto.trowmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWMAX(TileDataOut &dst, TileDataIn &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +实现检查 (NPU): + +- A2A3: + - Tile 位置:`dst` 和 `src` 必须是 `TileType::Vec`。 + - `src` 的 Tile 布局:ND 分形(`isRowMajor` 且 `SLayout::NoneBox`)。 + - `dst` 的 Tile 布局: + - **紧凑模式**:DN 布局的一维 Tile,例如 `Tile`,此时ROWS要做到32b对齐。 + - **传统模式**:ND 布局的二维 Tile,例如 `Tile`。 + - 数据类型:`half` 或 `float`。 + - 数据类型一致性:`dst.DType == src.DType`。 + - 运行期有效区域检查: + - `srcValidCol != 0` 且 `srcValidRow != 0`。 + - `srcValidRow == dstValidRow`(输出有效行数必须与输入有效行数匹配)。 +- A5: + - 数据类型:`half` 或 `float`。 + - 数据类型一致性:`dst.DType == src.DType`。 + - 实现中对 `validRow/validCol` 无显式运行时断言;循环使用 `src.GetValidRow()` 和 `src.GetValidCol()`。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWMAX(dst, src, tmp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWMAX(dst, src, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.trowmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowmax %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = trowmax %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowmax ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TROWMIN.md b/designs/outerCube/PTOISA/TROWMIN.md new file mode 100644 index 00000000..ab60bbe7 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWMIN.md @@ -0,0 +1,135 @@ +# TROWMIN + + +## Tile Operation Diagram + +![TROWMIN tile operation](../figures/isa/TROWMIN.svg) + +## Introduction + +Reduce each row by taking the minimum across columns. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \min_{0 \le j < C} \mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowmin %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### AS Level 1 (SSA) + +```text +%dst = pto.trowmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWMIN(TileDataOut &dst, TileDataIn &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +Implementation checks (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile layout of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile layout of `dst`: + - **Compact Mode**: DN layout Tile of 1D, e.g., `Tile`, ROWS must be 32b aligned. + - **Traditional Mode**: ND layout Tile of 2D, e.g., `Tile`. + - Data types: `half` or `float`. + - DType consistency: `dst.DType == src.DType`. + - Runtime valid checks: + - `srcValidCol != 0` and `srcValidRow != 0`. + - `srcValidRow == dstValidRow` (the output valid row must match the input valid row). +- A5: + - Data types: `half` or `float`. + - DType consistency: `dst.DType == src.DType`. + - No explicit runtime assertions on `validRow/validCol` in the implementation; the loops use `src.GetValidRow()` and `src.GetValidCol()`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWMIN(dst, src, tmp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWMIN(dst, src, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowmin %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TROWMIN_zh.md b/designs/outerCube/PTOISA/TROWMIN_zh.md new file mode 100644 index 00000000..8cd11fc2 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWMIN_zh.md @@ -0,0 +1,135 @@ +# TROWMIN + +## 指令示意图 + +![TROWMIN tile operation](../figures/isa/TROWMIN.svg) + +## 简介 + +通过取列间最小值来归约每一行。 + +## 数学语义 + +设 `R = src.GetValidRow()`,`C = src.GetValidCol()`。对 `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \min_{0 \le j < C} \mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = trowmin %src : !pto.tile<...> -> !pto.tile<...> +``` +降低时可能引入内部临时 Tile;C++ 内建接口需要显式传入 `tmp` 操作数。 + +### AS Level 1(SSA) + +```text +%dst = pto.trowmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWMIN(TileDataOut &dst, TileDataIn &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +实现检查 (NPU): + +- A2A3: + - Tile 位置:`dst` 和 `src` 必须是 `TileType::Vec`。 + - `src` 的 Tile 布局:ND 分形(`isRowMajor` 且 `SLayout::NoneBox`)。 + - `dst` 的 Tile 布局: + - **紧凑模式**:DN 布局的一维 Tile,例如 `Tile`,此时ROWS要做到32b对齐。 + - **传统模式**:ND 布局的二维 Tile,例如 `Tile`。 + - 数据类型:`half` 或 `float`。 + - 数据类型一致性:`dst.DType == src.DType`。 + - 运行期有效区域检查: + - `srcValidCol != 0` 且 `srcValidRow != 0`。 + - `srcValidRow == dstValidRow`(输出有效行数必须与输入有效行数匹配)。 +- A5: + - 数据类型:`half` 或 `float`。 + - 数据类型一致性:`dst.DType == src.DType`。 + - 实现中对 `validRow/validCol` 无显式运行时断言;循环使用 `src.GetValidRow()` 和 `src.GetValidCol()`。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWMIN(dst, src, tmp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWMIN(dst, src, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.trowmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowmin %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = trowmin %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowmin ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TROWPROD.md b/designs/outerCube/PTOISA/TROWPROD.md new file mode 100644 index 00000000..dfd49a18 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWPROD.md @@ -0,0 +1,150 @@ +# TROWPROD + + +## Tile Operation Diagram + +![TROWPROD tile operation](../figures/isa/TROWPROD.svg) + +## Introduction + +Reduce each row by multiplying across columns. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \prod_{j=0}^{C-1} \mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowprod %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### AS Level 1 (SSA) + +```text +%dst = pto.trowprod %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowprod ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.trowprod %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trowprod ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWPROD(TileDataOut &dst, TileDataIn &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +Implementation checks (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile layout of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile layout of `dst`: DN layout Tile of 1D, e.g., `Tile` + - Data types: `half`, `float`. + - DType consistency: `dst.DType == src.DType`. + - Runtime valid checks: + - `srcValidCol != 0` and `srcValidRow != 0`. + - `srcValidRow == dstValidRow` (the output valid row must match the input valid row). + - `tmp` must have the same shape as `src`. + +## Implementation Notes + +Unlike TROWSUM which uses `vcadd`/`vcgadd` instructions, TROWPROD uses binary reduction with `vmul` since there is no `vcmul` instruction available on A2A3. The implementation: + +1. Multiplies adjacent repeat pairs and stores results in `tmp` +2. Iteratively performs binary multiplication reduction on `tmp` +3. Continues until each row has only one element + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWPROD(dst, src, tmp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWPROD(dst, src, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowprod %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowprod %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowprod %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowprod ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TROWPROD_zh.md b/designs/outerCube/PTOISA/TROWPROD_zh.md new file mode 100644 index 00000000..7863b14d --- /dev/null +++ b/designs/outerCube/PTOISA/TROWPROD_zh.md @@ -0,0 +1,115 @@ +# TROWPROD + +## 指令示意图 + +![TROWPROD tile operation](../figures/isa/TROWPROD.svg) + +## 简介 + +对每一行沿列方向连乘归约。 + +## 数学语义 + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \prod_{j=0}^{C-1} \mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = trowprod %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### AS Level 1 (SSA) + +```text +%dst = pto.trowprod %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowprod ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trowprod %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowprod ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWPROD(TileDataOut &dst, TileDataIn &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +实现检查 (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile 布局 of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile 布局 of `dst`: DN layout Tile of 1D, e.g., `Tile` + - 数据类型: `half`, `float`. + - 数据类型一致性: `dst.DType == src.DType`. + - 运行期有效区域检查: + - `srcValidCol != 0` and `srcValidRow != 0`. + - `srcValidRow == dstValidRow` (the output valid row must match the input valid row). + - `tmp` must have the same shape as `src`. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWPROD(dst, src, tmp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWPROD(dst, src, tmp); +} +``` diff --git a/designs/outerCube/PTOISA/TROWSUM.md b/designs/outerCube/PTOISA/TROWSUM.md new file mode 100644 index 00000000..44944d24 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWSUM.md @@ -0,0 +1,147 @@ +# TROWSUM + + +## Tile Operation Diagram + +![TROWSUM tile operation](../figures/isa/TROWSUM.svg) + +## Introduction + +Reduce each row by summing across columns. + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \sum_{j=0}^{C-1} \mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trowsum %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### AS Level 1 (SSA) + +```text +%dst = pto.trowsum %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowsum ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.trowsum %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trowsum ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWSUM(TileDataOut &dst, TileDataIn &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +Implementation checks (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile layout of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile layout of `dst`: + - **Recommended**: DN layout Tile of 1D, e.g., `Tile` + - **To be removed**: ND layout Tile of 2D, e.g., `Tile` + - Data types: `half` or `float`. + - DType consistency: `dst.DType == src.DType`. + - Runtime valid checks: + - `srcValidCol != 0` and `srcValidRow != 0`. + - `srcValidRow == dstValidRow` (the output valid row must match the input valid row). +- A5: + - Data types: `half` or `float`. + - DType consistency: `dst.DType == src.DType`. + - No explicit runtime assertions on `validRow/validCol` in the implementation; the loops use `src.GetValidRow()` and `src.GetValidCol()`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWSUM(dst, src, tmp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWSUM(dst, src, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trowsum %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trowsum %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trowsum %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.trowsum ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TROWSUM_zh.md b/designs/outerCube/PTOISA/TROWSUM_zh.md new file mode 100644 index 00000000..527f4e02 --- /dev/null +++ b/designs/outerCube/PTOISA/TROWSUM_zh.md @@ -0,0 +1,120 @@ +# TROWSUM + +## 指令示意图 + +![TROWSUM tile operation](../figures/isa/TROWSUM.svg) + +## 简介 + +通过对列求和来归约每一行。 + +## 数学语义 + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. For `0 <= i < R`: + +$$ \mathrm{dst}_{i,0} = \sum_{j=0}^{C-1} \mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = trowsum %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### AS Level 1 (SSA) + +```text +%dst = pto.trowsum %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trowsum ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trowsum %src, %tmp : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trowsum ins(%src, %tmp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TROWSUM(TileDataOut &dst, TileDataIn &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +实现检查 (NPU): + +- A2A3: + - Tile location: `dst` and `src` must be `TileType::Vec`. + - Tile 布局 of `src`: ND fractal (`isRowMajor` and `SLayout::NoneBox`). + - Tile 布局 of `dst`: + - **推荐**: DN layout Tile of 1D, e.g., `Tile` + - **将移除**: ND layout Tile of 2D, e.g., `Tile` + - 数据类型: `half` or `float`. + - 数据类型一致性: `dst.DType == src.DType`. + - 运行期有效区域检查: + - `srcValidCol != 0` and `srcValidRow != 0`. + - `srcValidRow == dstValidRow` (the output valid row must match the input valid row). +- A5: + - 数据类型: `half` or `float`. + - 数据类型一致性: `dst.DType == src.DType`. + - No explicit runtime assertions on `validRow/validCol` in the implementation; the loops use `src.GetValidRow()` and `src.GetValidCol()`. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TROWSUM(dst, src, tmp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TROWSUM(dst, src, tmp); +} +``` diff --git a/designs/outerCube/PTOISA/TRSQRT.md b/designs/outerCube/PTOISA/TRSQRT.md new file mode 100644 index 00000000..10e58013 --- /dev/null +++ b/designs/outerCube/PTOISA/TRSQRT.md @@ -0,0 +1,134 @@ +# TRSQRT + + +## Tile Operation Diagram + +![TRSQRT tile operation](../figures/isa/TRSQRT.svg) + +## Introduction + +Elementwise reciprocal square root. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \frac{1}{\sqrt{\mathrm{src}_{i,j}}} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = trsqrt %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trsqrt ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.trsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.trsqrt ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TRSQRT(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TRSQRT(TileDataDst &dst, TileDataSrc &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (NPU)**: + - The `tmp` buffer must be at least 32 bytes. When tmp is provided, the high-precision version is executed. + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Domain / NaN**: + - Behavior is target-defined (e.g., for `src == 0` or negative inputs). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TRSQRT(dst, src); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TRSQRT(dst, src); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.trsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.trsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = trsqrt %src : !pto.tile<...> +# AS Level 2 (DPS) +pto.trsqrt ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TRSQRT_zh.md b/designs/outerCube/PTOISA/TRSQRT_zh.md new file mode 100644 index 00000000..9b942dee --- /dev/null +++ b/designs/outerCube/PTOISA/TRSQRT_zh.md @@ -0,0 +1,107 @@ +# TRSQRT + +## 指令示意图 + +![TRSQRT tile operation](../figures/isa/TRSQRT.svg) + +## 简介 + +逐元素倒数平方根。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \frac{1}{\sqrt{\mathrm{src}_{i,j}}} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = trsqrt %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.trsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.trsqrt ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.trsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.trsqrt ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TRSQRT(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TRSQRT(TileDataDst &dst, TileDataSrc &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (NPU)**: + - The `tmp` buffer must be at least 32 bytes. When tmp is provided, the high-precision version is executed. + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Domain / NaN**: + - Behavior is target-defined (e.g., for `src == 0` or negative inputs). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TRSQRT(dst, src); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TRSQRT(dst, src); +} +``` diff --git a/designs/outerCube/PTOISA/TSCATTER.md b/designs/outerCube/PTOISA/TSCATTER.md new file mode 100644 index 00000000..05d0dd23 --- /dev/null +++ b/designs/outerCube/PTOISA/TSCATTER.md @@ -0,0 +1,139 @@ +# TSCATTER + + +## Tile Operation Diagram + +![TSCATTER tile operation](../figures/isa/TSCATTER.svg) + +## Introduction + +Scatter source elements into a destination tile using per-element flattened destination offsets. + +## Math Interpretation + +For each source element `(i, j)`, let `k = idx[i,j]` and write: + +$$ \mathrm{dst\_flat}_{k} = \mathrm{src}_{i,j} $$ + +Here `dst_flat` denotes the destination tile viewed as a single linear storage sequence. `TSCATTER` does **not** interpret `idx[i,j]` as a destination row selector. On the standard row-major tile layout, this is equivalent to writing the `k`-th flattened destination element. + +If multiple elements map to the same destination location, the final value is implementation-defined (last writer wins in the current implementation). + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tscatter %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tscatter %src, %idx : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tscatter ins(%src, %idx : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSCATTER(TileDataD &dst, TileDataS &src, TileDataI &indexes, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileDataD::Loc`, `TileDataS::Loc`, `TileDataI::Loc` must be `TileType::Vec`. + - `TileDataD::DType`, `TileDataS::DType` must be one of: `int32_t`, `int16_t`, `int8_t`, `half`, `float32_t`, `uint32_t`, `uint16_t`, `uint8_t`, `bfloat16_t`. + - `TileDataI::DType` must be one of: `int16_t`, `int32_t`, `uint16_t` or `uint32_t`. + - `indexes` values are interpreted as flattened destination element offsets in destination tile storage order. + - No bounds checks are enforced on `indexes` values. + - Static valid bounds: `TileDataD::ValidRow <= TileDataD::Rows`, `TileDataD::ValidCol <= TileDataD::Cols`, `TileDataS::ValidRow <= TileDataS::Rows`, `TileDataS::ValidCol <= TileDataS::Cols`, `TileDataI::ValidRow <= TileDataI::Rows`, `TileDataI::ValidCol <= TileDataI::Cols`. + - `TileDataD::DType` and `TileDataS::DType` must be the same. + - When size of `TileDataD::DType` is 4 bytes, the size of `TileDataI::DType` must be 4 bytes. + - When size of `TileDataD::DType` is 2 bytes, the size of `TileDataI::DType` must be 2 bytes. + - When size of `TileDataD::DType` is 1 bytes, the size of `TileDataI::DType` must be 2 bytes. +- **Implementation checks (A5)**: + - `TileDataD::Loc`, `TileDataS::Loc`, `TileDataI::Loc` must be `TileType::Vec`. + - `TileDataD::DType`, `TileDataS::DType` must be one of: `int32_t`, `int16_t`, `int8_t`, `half`, `float32_t`, `uint32_t`, `uint16_t`, `uint8_t`, `bfloat16_t`. + - `TileDataI::DType` must be one of: `int16_t`, `int32_t`, `uint16_t` or `uint32_t`. + - `indexes` values are interpreted as flattened destination element offsets in destination tile storage order. + - No bounds checks are enforced on `indexes` values. + - Static valid bounds: `TileDataD::ValidRow <= TileDataD::Rows`, `TileDataD::ValidCol <= TileDataD::Cols`, `TileDataS::ValidRow <= TileDataS::Rows`, `TileDataS::ValidCol <= TileDataS::Cols`, `TileDataI::ValidRow <= TileDataI::Rows`, `TileDataI::ValidCol <= TileDataI::Cols`. + - `TileDataD::DType` and `TileDataS::DType` must be the same. + - When size of `TileDataD::DType` is 4 bytes, the size of `TileDataI::DType` must be 4 bytes. + - When size of `TileDataD::DType` is 2 bytes, the size of `TileDataI::DType` must be 2 bytes. + - When size of `TileDataD::DType` is 1 bytes, the size of `TileDataI::DType` must be 2 bytes. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + using IdxT = Tile; + TileT src, dst; + IdxT idx; + TSCATTER(dst, src, idx); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + using IdxT = Tile; + TileT src, dst; + IdxT idx; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(idx, 0x3000); + TSCATTER(dst, src, idx); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tscatter %src, %idx : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tscatter %src, %idx : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tscatter %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# IR Level 2 (DPS) +pto.tscatter ins(%src, %idx : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TSCATTER_zh.md b/designs/outerCube/PTOISA/TSCATTER_zh.md new file mode 100644 index 00000000..30b828ac --- /dev/null +++ b/designs/outerCube/PTOISA/TSCATTER_zh.md @@ -0,0 +1,112 @@ +# TSCATTER + +## 指令示意图 + +![TSCATTER tile operation](../figures/isa/TSCATTER.svg) + +## 简介 + +使用逐元素行索引将源 Tile 的行散播到目标 Tile 中。 + +## 数学语义 + +对每个源元素 `(i, j)`, let `k = idx[i,j]` and write: + +$$ \mathrm{dst\_flat}_{k} = \mathrm{src}_{i,j} $$ + +Here `dst_flat` denotes the destination tile viewed as a single linear storage sequence. `TSCATTER` does **not** interpret `idx[i,j]` as a destination row selector. On the standard row-major tile layout, this is equivalent to writing the `k`-th flattened destination element. + +If multiple elements map to the same destination location, the final value is implementation-defined (last writer wins in the current implementation). + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tscatter %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tscatter %src, %idx : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tscatter ins(%src, %idx : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSCATTER(TileDataD &dst, TileDataS &src, TileDataI &indexes, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileDataD::Loc`, `TileDataS::Loc`, `TileDataI::Loc` must be `TileType::Vec`. + - `TileDataD::DType`, `TileDataS::DType` must be one of: `int32_t`, `int16_t`, `int8_t`, `half`, `float32_t`, `uint32_t`, `uint16_t`, `uint8_t`, `bfloat16_t`. + - `TileDataI::DType` must be one of: `int16_t`, `int32_t`, `uint16_t` or `uint32_t`. + - `indexes` values are interpreted as flattened destination element offsets in destination tile storage order. + - No bounds checks are enforced on `indexes` values. + - Static valid bounds: `TileDataD::ValidRow <= TileDataD::Rows`, `TileDataD::ValidCol <= TileDataD::Cols`, `TileDataS::ValidRow <= TileDataS::Rows`, `TileDataS::ValidCol <= TileDataS::Cols`, `TileDataI::ValidRow <= TileDataI::Rows`, `TileDataI::ValidCol <= TileDataI::Cols`. + - `TileDataD::DType` and `TileDataS::DType` must be the same. + - When size of `TileDataD::DType` is 4 bytes, the size of `TileDataI::DType` must be 4 bytes. + - When size of `TileDataD::DType` is 2 bytes, the size of `TileDataI::DType` must be 2 bytes. + - When size of `TileDataD::DType` is 1 bytes, the size of `TileDataI::DType` must be 2 bytes. +- **实现检查 (A5)**: + - `TileDataD::Loc`, `TileDataS::Loc`, `TileDataI::Loc` must be `TileType::Vec`. + - `TileDataD::DType`, `TileDataS::DType` must be one of: `int32_t`, `int16_t`, `int8_t`, `half`, `float32_t`, `uint32_t`, `uint16_t`, `uint8_t`, `bfloat16_t`. + - `TileDataI::DType` must be one of: `int16_t`, `int32_t`, `uint16_t` or `uint32_t`. + - `indexes` values are interpreted as flattened destination element offsets in destination tile storage order. + - No bounds checks are enforced on `indexes` values. + - Static valid bounds: `TileDataD::ValidRow <= TileDataD::Rows`, `TileDataD::ValidCol <= TileDataD::Cols`, `TileDataS::ValidRow <= TileDataS::Rows`, `TileDataS::ValidCol <= TileDataS::Cols`, `TileDataI::ValidRow <= TileDataI::Rows`, `TileDataI::ValidCol <= TileDataI::Cols`. + - `TileDataD::DType` and `TileDataS::DType` must be the same. + - When size of `TileDataD::DType` is 4 bytes, the size of `TileDataI::DType` must be 4 bytes. + - When size of `TileDataD::DType` is 2 bytes, the size of `TileDataI::DType` must be 2 bytes. + - When size of `TileDataD::DType` is 1 bytes, the size of `TileDataI::DType` must be 2 bytes. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + using IdxT = Tile; + TileT src, dst; + IdxT idx; + TSCATTER(dst, src, idx); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + using IdxT = Tile; + TileT src, dst; + IdxT idx; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(idx, 0x3000); + TSCATTER(dst, src, idx); +} +``` diff --git a/designs/outerCube/PTOISA/TSEL.md b/designs/outerCube/PTOISA/TSEL.md new file mode 100644 index 00000000..c1090b2a --- /dev/null +++ b/designs/outerCube/PTOISA/TSEL.md @@ -0,0 +1,140 @@ +# TSEL + + +## Tile Operation Diagram + +![TSEL tile operation](../figures/isa/TSEL.svg) + +## Introduction + +Select between two tiles using a mask tile (per-element selection). + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\mathrm{src0}_{i,j} & \text{if } \mathrm{mask}_{i,j}\ \text{is true} \\ +\mathrm{src1}_{i,j} & \text{otherwise} +\end{cases} +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tsel %mask, %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsel %mask, %src0, %src1 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsel ins(%mask, %src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSEL(TileData &dst, MaskTile &selMask, TileData &src0, TileData &src1, TmpTile &tmp, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `sizeof(TileData::DType)` must be `2` or `4` bytes. + - `TileData::DType` must be `int16_t` or `uint16_t` or `int32_t` or `uint32_t` or `half` or `bfloat16_t` or `float`. + - `dst`, `src0`, and `src1` must use the same element type. + - `dst`, `src0`, and `src1` must be row-major. + - The selection domain is `dst.GetValidRow()` / `dst.GetValidCol()`. +- **Implementation checks (A5)**: + - `sizeof(TileData::DType)` must be `2` or `4` bytes. + - `TileData::DType` must be `int16_t` or `uint16_t` or `int32_t` or `uint32_t` or `half` or `bfloat16_t` or `float`. + - `dst`, `src0`, and `src1` must use the same element type. + - `dst`, `src0`, and `src1` must be row-major. + - The selection domain is `dst.GetValidRow()` / `dst.GetValidCol()`. +- **Mask encoding**: + - The mask tile is interpreted as packed predicate bits in a target-defined layout. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + using MaskT = Tile; + using TmpT = Tile; + TileT src0, src1, dst; + MaskT mask(16, 2); + TmpT tmp; + TSEL(dst, mask, src0, src1, tmp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + using MaskT = Tile; + using TmpT = Tile; + TileT src0, src1, dst; + MaskT mask(16, 2); + TmpT tmp; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TASSIGN(mask, 0x4000); + TASSIGN(tmp, 0x5000); + TSEL(dst, mask, src0, src1, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tsel %mask, %src0, %src1 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tsel %mask, %src0, %src1 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tsel %mask, %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tsel ins(%mask, %src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSELS.md b/designs/outerCube/PTOISA/TSELS.md new file mode 100644 index 00000000..a68226f3 --- /dev/null +++ b/designs/outerCube/PTOISA/TSELS.md @@ -0,0 +1,157 @@ +# TSELS + +## Tile Operation Diagram + +![TSELS tile operation](../figures/isa/TSELS.svg) + +## Introduction + +Select between source tile and scalar using a mask tile (per-element selection for source tile). + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\mathrm{src}_{i,j} & \text{if } \mathrm{mask}_{i,j}\ \text{is true} \\ +\mathrm{scalar} & \text{otherwise} +\end{cases} +$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tsels %mask, %src, %scalar : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsels %mask, %src, %scalar : (!pto.tile<...>, !pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsels ins(%mask, %src, %scalar : !pto.tile_buf<...>, !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tsels %src0, %src1, %scalar : (!pto.tile<...>, !pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tsels ins(%src0, %src1, %scalar : !pto.tile_buf<...>, !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSELS(TileDataDst &dst, TileDataMask &mask, TileDataSrc &src, TileDataTmp &tmp, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `sizeof(TileDataDst::DType)` must be `2` or `4` bytes. + - Supported data types are `half`, `float16_t`, `float`, and `float32_t`. + - `dst` and `src` must use the same element type. + - `dst` and `src` must be row-major. + - Runtime: `src.GetValidRow()/GetValidCol()` must match `dst.GetValidRow()/GetValidCol()`. +- **Implementation checks (A5)**: + - `sizeof(TileDataDst::DType)` may be `1`, `2`, or `4` bytes. + - Supported data types are `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `half`, and `float`. + - `dst` and `src` must use the same element type. + - `dst`, `mask`, and `src` must be row-major. + - Runtime: `src.GetValidRow()/GetValidCol()` must match `dst.GetValidRow()/GetValidCol()`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Mask encoding**: + - The mask tile is interpreted as packed predicate bits in a target-defined layout. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileDst = Tile; + using TileSrc = Tile; + using TileTmp = Tile; + using TileMask = Tile; + TileDst dst; + TileSrc src; + TileTmp tmp; + TileMask mask(16, 2); + float scalar = 0.0f; + TSELS(dst, mask, src, tmp, scalar); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileDst = Tile; + using TileSrc = Tile; + using TileTmp = Tile; + using TileMask = Tile; + TileDst dst; + TileSrc src; + TileTmp tmp; + TileMask mask(16, 2); + float scalar = 0.0f; + TASSIGN(src, 0x1000); + TASSIGN(tmp, 0x2000); + TASSIGN(dst, 0x3000); + TASSIGN(mask, 0x4000); + TSELS(dst, mask, src, tmp, scalar); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tsels %mask, %src, %scalar : (!pto.tile<...>, !pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tsels %mask, %src, %scalar : (!pto.tile<...>, !pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tsels %mask, %src, %scalar : !pto.tile<...> +# AS Level 2 (DPS) +pto.tsels ins(%mask, %src, %scalar : !pto.tile_buf<...>, !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TSELS_zh.md b/designs/outerCube/PTOISA/TSELS_zh.md new file mode 100644 index 00000000..31d6d7e2 --- /dev/null +++ b/designs/outerCube/PTOISA/TSELS_zh.md @@ -0,0 +1,147 @@ +# TSELS + +## 指令示意图 + +![TSELS tile operation](../figures/isa/TSELS.svg) + +## 简介 + +使用 mask tile 在源 Tile 和标量之间进行逐元素选择。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\mathrm{src}_{i,j} & \text{if } \mathrm{mask}_{i,j}\ \text{为真} \\ +\mathrm{scalar} & \text{否则} +\end{cases} +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tsels %mask, %src, %scalar : !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tsels %mask, %src, %scalar : (!pto.tile<...>, !pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tsels ins(%mask, %src, %scalar : !pto.tile_buf<...>, !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSELS(TileDataDst &dst, TileDataMask &mask, TileDataSrc &src, TileDataTmp &tmp, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `sizeof(TileDataDst::DType)` 必须是 `2` 或 `4` 字节。 + - 支持的数据类型为 `half`、`float16_t`、`float` 和 `float32_t`。 + - `dst` 和 `src` 必须使用相同的元素类型。 + - `dst` 和 `src` 必须是行主序。 + - 运行时:`src.GetValidRow()/GetValidCol()` 必须与 `dst.GetValidRow()/GetValidCol()` 一致。 +- **实现检查 (A5)**: + - `sizeof(TileDataDst::DType)` 可以是 `1`、`2` 或 `4` 字节。 + - 支持的数据类型为 `int8_t`、`uint8_t`、`int16_t`、`uint16_t`、`int32_t`、`uint32_t`、`half` 和 `float`。 + - `dst` 和 `src` 必须使用相同的元素类型。 + - `dst`、`mask` 和 `src` 必须是行主序。 + - 运行时:`src.GetValidRow()/GetValidCol()` 必须与 `dst.GetValidRow()/GetValidCol()` 一致。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 +- **掩码编码**: + - 掩码 Tile 被解释为目标定义布局中的打包谓词位。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileDst = Tile; + using TileSrc = Tile; + using TileTmp = Tile; + using TileMask = Tile; + TileDst dst; + TileSrc src; + TileTmp tmp; + TileMask mask(16, 2); + float scalar = 0.0f; + TSELS(dst, mask, src, tmp, scalar); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileDst = Tile; + using TileSrc = Tile; + using TileTmp = Tile; + using TileMask = Tile; + TileDst dst; + TileSrc src; + TileTmp tmp; + TileMask mask(16, 2); + float scalar = 0.0f; + TASSIGN(src, 0x1000); + TASSIGN(tmp, 0x2000); + TASSIGN(dst, 0x3000); + TASSIGN(mask, 0x4000); + TSELS(dst, mask, src, tmp, scalar); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tsels %mask, %src, %scalar : (!pto.tile<...>, !pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tsels %mask, %src, %scalar : (!pto.tile<...>, !pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tsels %mask, %src, %scalar : !pto.tile<...> +# AS Level 2 (DPS) +pto.tsels ins(%mask, %src, %scalar : !pto.tile_buf<...>, !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSEL_zh.md b/designs/outerCube/PTOISA/TSEL_zh.md new file mode 100644 index 00000000..fac4e34b --- /dev/null +++ b/designs/outerCube/PTOISA/TSEL_zh.md @@ -0,0 +1,140 @@ +# TSEL + +## 指令示意图 + +![TSEL tile operation](../figures/isa/TSEL.svg) + +## 简介 + +使用掩码 Tile 在两个 Tile 之间进行选择(逐元素选择)。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ +\mathrm{dst}_{i,j} = +\begin{cases} +\mathrm{src0}_{i,j} & \text{if } \mathrm{mask}_{i,j}\ \text{is true} \\ +\mathrm{src1}_{i,j} & \text{otherwise} +\end{cases} +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tsel %mask, %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tsel %mask, %src0, %src1 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tsel ins(%mask, %src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSEL(TileData &dst, MaskTile &selMask, TileData &src0, TileData &src1, TmpTile &tmp, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `sizeof(TileData::DType)` 必须是 `2` 或 `4` 字节。 + - `TileData::DType` 必须是 `int16_t` 或 `uint16_t` 或 `int32_t` 或 `uint32_t` 或 `half` 或 `bfloat16_t` 或 `float`。 + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - 选择域由 `dst.GetValidRow()` / `dst.GetValidCol()` 决定。 +- **实现检查 (A5)**: + - `sizeof(TileData::DType)` 必须是 `2` 或 `4` 字节。 + - `TileData::DType` 必须是 `int16_t` 或 `uint16_t` 或 `int32_t` 或 `uint32_t` 或 `half` 或 `bfloat16_t` 或 `float`。 + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - 选择域由 `dst.GetValidRow()` / `dst.GetValidCol()` 决定。 +- **掩码编码**: + - 掩码 tile 被解释为目标定义布局中的打包谓词位。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + using MaskT = Tile; + using TmpT = Tile; + TileT src0, src1, dst; + MaskT mask(16, 2); + TmpT tmp; + TSEL(dst, mask, src0, src1, tmp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + using MaskT = Tile; + using TmpT = Tile; + TileT src0, src1, dst; + MaskT mask(16, 2); + TmpT tmp; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TASSIGN(mask, 0x4000); + TASSIGN(tmp, 0x5000); + TSEL(dst, mask, src0, src1, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tsel %mask, %src0, %src1 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tsel %mask, %src0, %src1 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tsel %mask, %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tsel ins(%mask, %src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSETFMATRIX.md b/designs/outerCube/PTOISA/TSETFMATRIX.md new file mode 100644 index 00000000..a7679c4e --- /dev/null +++ b/designs/outerCube/PTOISA/TSETFMATRIX.md @@ -0,0 +1,89 @@ +# TSETFMATRIX + + +## Tile Operation Diagram + +![TSETFMATRIX tile operation](../figures/isa/TSETFMATRIX.svg) + +## Introduction + +Set the FMATRIX register(s) used by IMG2COL-like operations from an `Img2colTileConfig` (target/implementation-defined). + +## See also + +- IMG2COL instruction: `docs/isa/TIMG2COL.md`. + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSETFMATRIX(ConvTileData &src, WaitEvents&... events); +``` + +## Math Interpretation + +Unless otherwise specified, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +pto.tsetfmatrix %cfg : !pto.fmatrix_config -> () +``` + +### AS Level 2 (DPS) + +```text +pto.tsetfmatrix ins(%cfg : !pto.fmatrix_config) outs() +``` + +### IR Level 1 (SSA) + +```text +pto.tsetfmatrix %cfg : !pto.fmatrix_config -> () +``` + +### IR Level 2 (DPS) + +```text +pto.tsetfmatrix ins(%cfg : !pto.fmatrix_config) outs() +``` +## Constraints + +Type/layout/location/shape legality is backend-dependent; treat implementation-specific notes as normative for that backend. + +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +pto.tsetfmatrix %cfg : !pto.fmatrix_config -> () +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +pto.tsetfmatrix %cfg : !pto.fmatrix_config -> () +``` + +### PTO Assembly Form + +```text +pto.tsetfmatrix %cfg : !pto.fmatrix_config -> () +# AS Level 2 (DPS) +pto.tsetfmatrix ins(%cfg : !pto.fmatrix_config) outs() +``` diff --git a/designs/outerCube/PTOISA/TSETFMATRIX_zh.md b/designs/outerCube/PTOISA/TSETFMATRIX_zh.md new file mode 100644 index 00000000..ee314082 --- /dev/null +++ b/designs/outerCube/PTOISA/TSETFMATRIX_zh.md @@ -0,0 +1,58 @@ +# TSETFMATRIX + +## 指令示意图 + +![TSETFMATRIX tile operation](../figures/isa/TSETFMATRIX.svg) + +## 简介 + +为类 IMG2COL 操作设置 FMATRIX 寄存器。 + +## 数学语义 + +除非另有说明, semantics are defined over the valid region and target-dependent behavior is marked as implementation-defined. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +pto.tsetfmatrix %cfg : !pto.fmatrix_config -> () +``` + +### AS Level 2 (DPS) + +```text +pto.tsetfmatrix ins(%cfg : !pto.fmatrix_config) outs() +``` + +### AS Level 1(SSA) + +```text +pto.tsetfmatrix %cfg : !pto.fmatrix_config -> () +``` + +### AS Level 2(DPS) + +```text +pto.tsetfmatrix ins(%cfg : !pto.fmatrix_config) outs() +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSETFMATRIX(ConvTileData &src, WaitEvents&... events); +``` + +## 约束 + +Type/layout/location/shape legality is backend-dependent; treat implementation-specific notes as normative for that backend. + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TSETHF32MODE.md b/designs/outerCube/PTOISA/TSETHF32MODE.md new file mode 100644 index 00000000..9d27bd40 --- /dev/null +++ b/designs/outerCube/PTOISA/TSETHF32MODE.md @@ -0,0 +1,62 @@ +# TSETHF32MODE + +## Tile Operation Diagram + +![TSETHF32MODE tile operation](../figures/isa/TSETHF32MODE.svg) + +## Introduction + +Configure HF32 transform mode (implementation-defined). + +This instruction controls backend-specific HF32 transformation behavior used by supported compute paths. + +## Math Interpretation + +No direct tensor arithmetic is produced by this instruction. It updates target mode state used by subsequent instructions. + +## Assembly Syntax + +PTO-AS form: see `docs/assembly/PTO-AS.md`. + +Schematic form: + +```text +tsethf32mode {enable = true, mode = ...} +``` + +### IR Level 1 (SSA) + +```text +pto.tsethf32mode {enable = true, mode = ...} +``` + +### IR Level 2 (DPS) + +```text +pto.tsethf32mode ins({enable = true, mode = ...}) outs() +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSETHF32MODE(WaitEvents &... events); +``` + +## Constraints + +- Available only when the corresponding backend capability macro is enabled. +- Exact mode values and hardware behavior are target-defined. +- This instruction has control-state side effects and should be ordered appropriately relative to dependent compute instructions. + +## Examples + +```cpp +#include +using namespace pto; + +void example_enable_hf32() { + TSETHF32MODE(); +} +``` diff --git a/designs/outerCube/PTOISA/TSETHF32MODE_zh.md b/designs/outerCube/PTOISA/TSETHF32MODE_zh.md new file mode 100644 index 00000000..674bbedb --- /dev/null +++ b/designs/outerCube/PTOISA/TSETHF32MODE_zh.md @@ -0,0 +1,61 @@ +# TSETHF32MODE + +## 指令示意图 + +![TSETHF32MODE tile operation](../figures/isa/TSETHF32MODE.svg) + +## 简介 + +设置 HF32 变换模式(实现定义)。 + +## 数学语义 + +No direct tensor arithmetic is produced by this instruction. It updates target mode state used by subsequent instructions. + +## 汇编语法 + +PTO-AS 形式:参见 `docs/assembly/PTO-AS.md`. + +Schematic form: + +```text +tsethf32mode {enable = true, mode = ...} +``` + +### AS Level 1(SSA) + +```text +pto.tsethf32mode {enable = true, mode = ...} +``` + +### AS Level 2(DPS) + +```text +pto.tsethf32mode ins({enable = true, mode = ...}) outs() +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSETHF32MODE(WaitEvents &... events); +``` + +## 约束 + +- Available only when the corresponding backend capability macro is enabled. +- Exact mode values and hardware behavior are target-defined. +- This instruction has control-state side effects and should be ordered appropriately relative to dependent compute instructions. + +## 示例 + +```cpp +#include +using namespace pto; + +void example_enable_hf32() { + TSETHF32MODE(); +} +``` diff --git a/designs/outerCube/PTOISA/TSETTF32MODE.md b/designs/outerCube/PTOISA/TSETTF32MODE.md new file mode 100644 index 00000000..b366e0d2 --- /dev/null +++ b/designs/outerCube/PTOISA/TSETTF32MODE.md @@ -0,0 +1,62 @@ +# TSETTF32MODE + +## Tile Operation Diagram + +![TSETTF32MODE tile operation](../figures/isa/TSETTF32MODE.svg) + +## Introduction + +Configure TF32 transform mode (implementation-defined). + +This instruction controls backend-specific TF32 transformation behavior used by supported compute paths. + +## Math Interpretation + +No direct tensor arithmetic is produced by this instruction. It updates target mode state used by subsequent instructions. + +## Assembly Syntax + +PTO-AS form: see `docs/assembly/PTO-AS.md`. + +Schematic form: + +```text +tsettf32mode {enable = true, mode = ...} +``` + +### IR Level 1 (SSA) + +```text +pto.tsettf32mode {enable = true, mode = ...} +``` + +### IR Level 2 (DPS) + +```text +pto.tsettf32mode ins({enable = true, mode = ...}) outs() +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSETTF32MODE(WaitEvents &... events); +``` + +## Constraints + +- Available only when the corresponding backend capability macro is enabled. +- Exact mode values and hardware behavior are target-defined. +- This instruction has control-state side effects and should be ordered appropriately relative to dependent compute instructions. + +## Examples + +```cpp +#include +using namespace pto; + +void example_enable_tf32() { + TSETTF32MODE(); +} +``` diff --git a/designs/outerCube/PTOISA/TSETTF32MODE_zh.md b/designs/outerCube/PTOISA/TSETTF32MODE_zh.md new file mode 100644 index 00000000..3bb8aaac --- /dev/null +++ b/designs/outerCube/PTOISA/TSETTF32MODE_zh.md @@ -0,0 +1,61 @@ +# TSETTF32MODE + +## 指令示意图 + +![TSETTF32MODE tile operation](../figures/isa/TSETTF32MODE.svg) + +## 简介 + +设置 TF32 变换模式(实现定义)。 + +## 数学语义 + +No direct tensor arithmetic is produced by this instruction. It updates target mode state used by subsequent instructions. + +## 汇编语法 + +PTO-AS 形式:参见 `docs/assembly/PTO-AS.md`. + +Schematic form: + +```text +tsettf32mode {enable = true, mode = ...} +``` + +### AS Level 1(SSA) + +```text +pto.tsettf32mode {enable = true, mode = ...} +``` + +### AS Level 2(DPS) + +```text +pto.tsettf32mode ins({enable = true, mode = ...}) outs() +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSETTF32MODE(WaitEvents &... events); +``` + +## 约束 + +- Available only when the corresponding backend capability macro is enabled. +- Exact mode values and hardware behavior are target-defined. +- This instruction has control-state side effects and should be ordered appropriately relative to dependent compute instructions. + +## 示例 + +```cpp +#include +using namespace pto; + +void example_enable_tf32() { + TSETTF32MODE(); +} +``` diff --git a/designs/outerCube/PTOISA/TSET_IMG2COL_PADDING.md b/designs/outerCube/PTOISA/TSET_IMG2COL_PADDING.md new file mode 100644 index 00000000..7f3b513a --- /dev/null +++ b/designs/outerCube/PTOISA/TSET_IMG2COL_PADDING.md @@ -0,0 +1,106 @@ +# TSET_IMG2COL_PADDING + +## Tile Operation Diagram + +![TSET_IMG2COL_PADDING tile operation](../figures/isa/TSET_IMG2COL_PADDING.svg) + +## Introduction + +Set IMG2COL padding metadata from an IMG2COL configuration tile (implementation-defined). + +## Math Interpretation + +No direct tensor arithmetic is produced by this instruction. It updates IMG2COL padding control state consumed by subsequent data-movement operations. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Schematic form: + +```text +tset_img2col_padding %cfg +``` + +### AS Level 1 (SSA) + +```text +pto.tset_img2col_padding %cfg : !pto.fmatrix_config -> () +``` + +### AS Level 2 (DPS) + +```text +pto.tset_img2col_padding ins(%cfg : !pto.fmatrix_config) outs() +``` + +### IR Level 1 (SSA) + +```text +pto.tset_img2col_padding %cfg +``` + +### IR Level 2 (DPS) + +```text +pto.tset_img2col_padding ins(%cfg) outs() +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSET_IMG2COL_PADDING(ConvTileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TSET_IMG2COL_PADDING(ConvTileData &src, WaitEvents &... events); +``` + +For `MEMORY_BASE` targets, an overload without `SetFmatrixMode` is also provided. + +## Constraints + +- This instruction is backend-specific and available only for backends that expose IMG2COL configuration state. +- `src` must be a valid IMG2COL configuration tile type accepted by the backend implementation. +- The exact padding fields updated by this instruction are implementation-defined. +- Use this instruction before dependent `TIMG2COL` operations in the same execution stream. + +## Examples + +```cpp +#include + +using namespace pto; + +void example_set_img2col_padding(Img2colTileConfig& cfg) { + TSET_IMG2COL_PADDING(cfg); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +pto.tset_img2col_padding %cfg : !pto.fmatrix_config -> () +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +pto.tset_img2col_padding %cfg : !pto.fmatrix_config -> () +``` + +### PTO Assembly Form + +```text +pto.tset_img2col_padding %cfg : !pto.fmatrix_config -> () +# AS Level 2 (DPS) +pto.tset_img2col_padding ins(%cfg : !pto.fmatrix_config) outs() +``` diff --git a/designs/outerCube/PTOISA/TSET_IMG2COL_PADDING_zh.md b/designs/outerCube/PTOISA/TSET_IMG2COL_PADDING_zh.md new file mode 100644 index 00000000..5d4c2e1d --- /dev/null +++ b/designs/outerCube/PTOISA/TSET_IMG2COL_PADDING_zh.md @@ -0,0 +1,80 @@ +# TSET_IMG2COL_PADDING + +## 指令示意图 + +![TSET_IMG2COL_PADDING tile operation](../figures/isa/TSET_IMG2COL_PADDING.svg) + +## 简介 + +从 IMG2COL 配置 Tile 设置 IMG2COL 填充元数据。 + +## 数学语义 + +No direct tensor arithmetic is produced by this instruction. It updates IMG2COL padding control state consumed by subsequent data-movement operations. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +Schematic form: + +```text +tset_img2col_padding %cfg +``` + +### AS Level 1 (SSA) + +```text +pto.tset_img2col_padding %cfg : !pto.fmatrix_config -> () +``` + +### AS Level 2 (DPS) + +```text +pto.tset_img2col_padding ins(%cfg : !pto.fmatrix_config) outs() +``` + +### AS Level 1(SSA) + +```text +pto.tset_img2col_padding %cfg +``` + +### AS Level 2(DPS) + +```text +pto.tset_img2col_padding ins(%cfg) outs() +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSET_IMG2COL_PADDING(ConvTileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TSET_IMG2COL_PADDING(ConvTileData &src, WaitEvents &... events); +``` + +For `MEMORY_BASE` targets, an overload without `SetFmatrixMode` is also provided. + +## 约束 + +- This instruction is backend-specific and available only for backends that expose IMG2COL configuration state. +- `src` must be a valid IMG2COL configuration tile type accepted by the backend implementation. +- The exact padding fields updated by this instruction are implementation-defined. +- Use this instruction before dependent `TIMG2COL` operations in the same execution stream. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example_set_img2col_padding(Img2colTileConfig& cfg) { + TSET_IMG2COL_PADDING(cfg); +} +``` diff --git a/designs/outerCube/PTOISA/TSET_IMG2COL_RPT.md b/designs/outerCube/PTOISA/TSET_IMG2COL_RPT.md new file mode 100644 index 00000000..6911e358 --- /dev/null +++ b/designs/outerCube/PTOISA/TSET_IMG2COL_RPT.md @@ -0,0 +1,106 @@ +# TSET_IMG2COL_RPT + +## Tile Operation Diagram + +![TSET_IMG2COL_RPT tile operation](../figures/isa/TSET_IMG2COL_RPT.svg) + +## Introduction + +Set IMG2COL repeat metadata from an IMG2COL configuration tile (implementation-defined). + +## Math Interpretation + +No direct tensor arithmetic is produced by this instruction. It updates IMG2COL control state used by subsequent data-movement operations. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Schematic form: + +```text +tset_img2col_rpt %cfg +``` + +### AS Level 1 (SSA) + +```text +pto.tset_img2col_rpt %cfg : !pto.fmatrix_config -> () +``` + +### AS Level 2 (DPS) + +```text +pto.tset_img2col_rpt ins(%cfg : !pto.fmatrix_config) outs() +``` + +### IR Level 1 (SSA) + +```text +pto.tset_img2col_rpt %cfg +``` + +### IR Level 2 (DPS) + +```text +pto.tset_img2col_rpt ins(%cfg) outs() +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSET_IMG2COL_RPT(ConvTileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TSET_IMG2COL_RPT(ConvTileData &src, WaitEvents &... events); +``` + +For `MEMORY_BASE` targets, an overload without `SetFmatrixMode` is also provided. + +## Constraints + +- This instruction is backend-specific and available only for backends that expose IMG2COL configuration state. +- `src` must be a valid IMG2COL configuration tile type accepted by the backend implementation. +- The exact register/metadata fields updated by this instruction are implementation-defined. +- Use this instruction before dependent `TIMG2COL` operations in the same execution stream. + +## Examples + +```cpp +#include + +using namespace pto; + +void example_set_img2col_rpt(Img2colTileConfig& cfg) { + TSET_IMG2COL_RPT(cfg); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +pto.tset_img2col_rpt %cfg : !pto.fmatrix_config -> () +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +pto.tset_img2col_rpt %cfg : !pto.fmatrix_config -> () +``` + +### PTO Assembly Form + +```text +pto.tset_img2col_rpt %cfg : !pto.fmatrix_config -> () +# AS Level 2 (DPS) +pto.tset_img2col_rpt ins(%cfg : !pto.fmatrix_config) outs() +``` diff --git a/designs/outerCube/PTOISA/TSET_IMG2COL_RPT_zh.md b/designs/outerCube/PTOISA/TSET_IMG2COL_RPT_zh.md new file mode 100644 index 00000000..059534d0 --- /dev/null +++ b/designs/outerCube/PTOISA/TSET_IMG2COL_RPT_zh.md @@ -0,0 +1,80 @@ +# TSET_IMG2COL_RPT + +## 指令示意图 + +![TSET_IMG2COL_RPT tile operation](../figures/isa/TSET_IMG2COL_RPT.svg) + +## 简介 + +从 IMG2COL 配置 Tile 设置 IMG2COL 重复次数元数据。 + +## 数学语义 + +No direct tensor arithmetic is produced by this instruction. It updates IMG2COL control state used by subsequent data-movement operations. + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +Schematic form: + +```text +tset_img2col_rpt %cfg +``` + +### AS Level 1 (SSA) + +```text +pto.tset_img2col_rpt %cfg : !pto.fmatrix_config -> () +``` + +### AS Level 2 (DPS) + +```text +pto.tset_img2col_rpt ins(%cfg : !pto.fmatrix_config) outs() +``` + +### AS Level 1(SSA) + +```text +pto.tset_img2col_rpt %cfg +``` + +### AS Level 2(DPS) + +```text +pto.tset_img2col_rpt ins(%cfg) outs() +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSET_IMG2COL_RPT(ConvTileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TSET_IMG2COL_RPT(ConvTileData &src, WaitEvents &... events); +``` + +For `MEMORY_BASE` targets, an overload without `SetFmatrixMode` is also provided. + +## 约束 + +- This instruction is backend-specific and available only for backends that expose IMG2COL configuration state. +- `src` must be a valid IMG2COL configuration tile type accepted by the backend implementation. +- The exact register/metadata fields updated by this instruction are implementation-defined. +- Use this instruction before dependent `TIMG2COL` operations in the same execution stream. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example_set_img2col_rpt(Img2colTileConfig& cfg) { + TSET_IMG2COL_RPT(cfg); +} +``` diff --git a/designs/outerCube/PTOISA/TSHL.md b/designs/outerCube/PTOISA/TSHL.md new file mode 100644 index 00000000..8f3cb121 --- /dev/null +++ b/designs/outerCube/PTOISA/TSHL.md @@ -0,0 +1,103 @@ +# TSHL + + +## Tile Operation Diagram + +![TSHL tile operation](../figures/isa/TSHL.svg) + +## Introduction + +Elementwise shift-left of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \ll \mathrm{src1}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tshl %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tshl %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tshl ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSHL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Supported element types are `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, and `int32_t`. + - `dst`, `src0`, and `src1` must use the same element type. + - `dst`, `src0`, and `src1` must be row-major. + - Runtime: `src0.GetValidRow()/GetValidCol()` and `src1.GetValidRow()/GetValidCol()` must match `dst`. +- **Implementation checks (A5)**: + - Supported element types are `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, and `int32_t`. + - `dst`, `src0`, and `src1` must use the same element type. + - `dst`, `src0`, and `src1` must be row-major. + - Runtime: `src0.GetValidRow()/GetValidCol()` and `src1.GetValidRow()/GetValidCol()` must match `dst`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, sh, out; + TSHL(out, x, sh); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tshl %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tshl %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tshl %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tshl ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSHLS.md b/designs/outerCube/PTOISA/TSHLS.md new file mode 100644 index 00000000..d792f7cc --- /dev/null +++ b/designs/outerCube/PTOISA/TSHLS.md @@ -0,0 +1,108 @@ +# TSHLS + + +## Tile Operation Diagram + +![TSHLS tile operation](../figures/isa/TSHLS.svg) + +## Introduction + +Elementwise shift-left of a tile, shift bits given by scalar. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \ll \mathrm{scalar} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tshls %src, %scalar : !pto.tile<...>, i32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tshls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tshls ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSHLS(TileDataDst &dst, TileDataSrc &src, typename TileDataDst::DType scalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Supported element types are `int32_t`, `int`, `int16_t`, `uint32_t`, `unsigned int`, and `uint16_t`. + - `dst` and `src` must use the same element type. + - `dst` and `src` must be vector tiles. + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`. + - Scalar only supports zero and positive values. +- **Implementation checks (A5)**: + - Supported element types are `int32_t`, `int16_t`, `int8_t`, `uint32_t`, `uint16_t`, and `uint8_t`. + - `dst` and `src` must use the same element type. + - `dst` and `src` must be vector tiles. + - Static valid bounds must satisfy `ValidRow <= Rows` and `ValidCol <= Cols` for both tiles. + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`. + - Scalar only supports zero and positive values. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc = Tile; + TileDst dst; + TileSrc src; + TSHLS(dst, src, 0x2); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tshls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tshls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tshls %src, %scalar : !pto.tile<...>, i32 +# AS Level 2 (DPS) +pto.tshls ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSHLS_zh.md b/designs/outerCube/PTOISA/TSHLS_zh.md new file mode 100644 index 00000000..12e456e7 --- /dev/null +++ b/designs/outerCube/PTOISA/TSHLS_zh.md @@ -0,0 +1,108 @@ +# TSHLS + +## 指令示意图 + +![TSHLS tile operation](../figures/isa/TSHLS.svg) + +## 简介 + +Tile 按标量逐元素左移。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \ll \mathrm{scalar} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tshls %src, %scalar : !pto.tile<...>, i32 +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tshls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tshls ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSHLS(TileDataDst &dst, TileDataSrc &src, typename TileDataDst::DType scalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - 支持的元素类型为 `int32_t`、`int`、`int16_t`、`uint32_t`、`unsigned int` 和 `uint16_t`。 + - `dst` 和 `src` 必须使用相同的元素类型。 + - `dst` 和 `src` 必须是向量 Tile。 + - 运行时:`src.GetValidRow() == dst.GetValidRow()` 且 `src.GetValidCol() == dst.GetValidCol()`。 + - 标量仅支持零和正值。 +- **实现检查 (A5)**: + - 支持的元素类型为 `int32_t`、`int16_t`、`int8_t`、`uint32_t`、`uint16_t` 和 `uint8_t`。 + - `dst` 和 `src` 必须使用相同的元素类型。 + - `dst` 和 `src` 必须是向量 Tile。 + - 两个 Tile 的静态有效边界都必须满足 `ValidRow <= Rows` 且 `ValidCol <= Cols`。 + - 运行时:`src.GetValidRow() == dst.GetValidRow()` 且 `src.GetValidCol() == dst.GetValidCol()`。 + - 标量仅支持零和正值。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc = Tile; + TileDst dst; + TileSrc src; + TSHLS(dst, src, 0x2); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tshls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tshls %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tshls %src, %scalar : !pto.tile<...>, i32 +# AS Level 2 (DPS) +pto.tshls ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSHL_zh.md b/designs/outerCube/PTOISA/TSHL_zh.md new file mode 100644 index 00000000..02fb6bc5 --- /dev/null +++ b/designs/outerCube/PTOISA/TSHL_zh.md @@ -0,0 +1,103 @@ +# TSHL + +## 指令示意图 + +![TSHL tile operation](../figures/isa/TSHL.svg) + +## 简介 + +两个 Tile 的逐元素左移。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \ll \mathrm{src1}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tshl %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tshl %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tshl ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSHL(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - 支持的元素类型为 `uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t` 和 `int32_t`。 + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - 运行时:`src0.GetValidRow()/GetValidCol()` 和 `src1.GetValidRow()/GetValidCol()` 必须与 `dst` 一致。 +- **实现检查 (A5)**: + - 支持的元素类型为 `uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t` 和 `int32_t`。 + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - 运行时:`src0.GetValidRow()/GetValidCol()` 和 `src1.GetValidRow()/GetValidCol()` 必须与 `dst` 一致。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, sh, out; + TSHL(out, x, sh); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tshl %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tshl %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tshl %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tshl ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSHR.md b/designs/outerCube/PTOISA/TSHR.md new file mode 100644 index 00000000..0ddd9b4f --- /dev/null +++ b/designs/outerCube/PTOISA/TSHR.md @@ -0,0 +1,103 @@ +# TSHR + + +## Tile Operation Diagram + +![TSHR tile operation](../figures/isa/TSHR.svg) + +## Introduction + +Elementwise shift-right of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \gg \mathrm{src1}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tshr %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tshr %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tshr ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSHR(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Supported element types are `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, and `int32_t`. + - `dst`, `src0`, and `src1` must use the same element type. + - `dst`, `src0`, and `src1` must be row-major. + - Runtime: `src0.GetValidRow()/GetValidCol()` and `src1.GetValidRow()/GetValidCol()` must match `dst`. +- **Implementation checks (A5)**: + - Supported element types are `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, and `int32_t`. + - `dst`, `src0`, and `src1` must use the same element type. + - `dst`, `src0`, and `src1` must be row-major. + - Runtime: `src0.GetValidRow()/GetValidCol()` and `src1.GetValidRow()/GetValidCol()` must match `dst`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, sh, out; + TSHR(out, x, sh); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tshr %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tshr %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tshr %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tshr ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSHRS.md b/designs/outerCube/PTOISA/TSHRS.md new file mode 100644 index 00000000..bf8fa11b --- /dev/null +++ b/designs/outerCube/PTOISA/TSHRS.md @@ -0,0 +1,108 @@ +# TSHRS + + +## Tile Operation Diagram + +![TSHRS tile operation](../figures/isa/TSHRS.svg) + +## Introduction + +Elementwise shift-right of a tile, shift bits given by scalar. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \gg \mathrm{scalar} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tshrs %src, %scalar : !pto.tile<...>, i32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tshrs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tshrs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSHRS(TileDataDst &dst, TileDataSrc &src, typename TileDataDst::DType scalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Supported element types are `int32_t`, `int`, `int16_t`, `uint32_t`, `unsigned int`, and `uint16_t`. + - `dst` and `src` must use the same element type. + - `dst` and `src` must be vector tiles. + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`. + - Scalar only supports zero and positive values. +- **Implementation checks (A5)**: + - Supported element types are `int32_t`, `int16_t`, `int8_t`, `uint32_t`, `uint16_t`, and `uint8_t`. + - `dst` and `src` must use the same element type. + - `dst` and `src` must be vector tiles. + - Static valid bounds must satisfy `ValidRow <= Rows` and `ValidCol <= Cols` for both tiles. + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`. + - Scalar only supports zero and positive values. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc = Tile; + TileDst dst; + TileSrc src; + TSHRS(dst, src, 0x2); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tshrs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tshrs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tshrs %src, %scalar : !pto.tile<...>, i32 +# AS Level 2 (DPS) +pto.tshrs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSHRS_zh.md b/designs/outerCube/PTOISA/TSHRS_zh.md new file mode 100644 index 00000000..12330812 --- /dev/null +++ b/designs/outerCube/PTOISA/TSHRS_zh.md @@ -0,0 +1,108 @@ +# TSHRS + +## 指令示意图 + +![TSHRS tile operation](../figures/isa/TSHRS.svg) + +## 简介 + +Tile 按标量逐元素右移。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \gg \mathrm{scalar} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tshrs %src, %scalar : !pto.tile<...>, i32 +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tshrs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tshrs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSHRS(TileDataDst &dst, TileDataSrc &src, typename TileDataDst::DType scalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - 支持的元素类型为 `int32_t`、`int`、`int16_t`、`uint32_t`、`unsigned int` 和 `uint16_t`。 + - `dst` 和 `src` 必须使用相同的元素类型。 + - `dst` 和 `src` 必须是向量 Tile。 + - 运行时:`src.GetValidRow() == dst.GetValidRow()` 且 `src.GetValidCol() == dst.GetValidCol()`。 + - 标量仅支持零和正值。 +- **实现检查 (A5)**: + - 支持的元素类型为 `int32_t`、`int16_t`、`int8_t`、`uint32_t`、`uint16_t` 和 `uint8_t`。 + - `dst` 和 `src` 必须使用相同的元素类型。 + - `dst` 和 `src` 必须是向量 Tile。 + - 两个 Tile 的静态有效边界都必须满足 `ValidRow <= Rows` 且 `ValidCol <= Cols`。 + - 运行时:`src.GetValidRow() == dst.GetValidRow()` 且 `src.GetValidCol() == dst.GetValidCol()`。 + - 标量仅支持零和正值。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc = Tile; + TileDst dst; + TileSrc src; + TSHRS(dst, src, 0x2); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tshrs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tshrs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tshrs %src, %scalar : !pto.tile<...>, i32 +# AS Level 2 (DPS) +pto.tshrs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSHR_zh.md b/designs/outerCube/PTOISA/TSHR_zh.md new file mode 100644 index 00000000..2a170903 --- /dev/null +++ b/designs/outerCube/PTOISA/TSHR_zh.md @@ -0,0 +1,103 @@ +# TSHR + +## 指令示意图 + +![TSHR tile operation](../figures/isa/TSHR.svg) + +## 简介 + +两个 Tile 的逐元素右移。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \gg \mathrm{src1}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tshr %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tshr %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tshr ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSHR(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - 支持的元素类型为 `uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t` 和 `int32_t`。 + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - 运行时:`src0.GetValidRow()/GetValidCol()` 和 `src1.GetValidRow()/GetValidCol()` 必须与 `dst` 一致。 +- **实现检查 (A5)**: + - 支持的元素类型为 `uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t` 和 `int32_t`。 + - `dst`、`src0` 和 `src1` 必须使用相同的元素类型。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - 运行时:`src0.GetValidRow()/GetValidCol()` 和 `src1.GetValidRow()/GetValidCol()` 必须与 `dst` 一致。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, sh, out; + TSHR(out, x, sh); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tshr %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tshr %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tshr %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tshr ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSORT32.md b/designs/outerCube/PTOISA/TSORT32.md new file mode 100644 index 00000000..08948410 --- /dev/null +++ b/designs/outerCube/PTOISA/TSORT32.md @@ -0,0 +1,150 @@ +# TSORT32 + +## Tile Operation Diagram + +![TSORT32 tile operation](../figures/isa/TSORT32.svg) + +## Introduction + +Sort each 32-element block of `src` together with the corresponding indices from `idx`, and write the sorted value-index pairs into `dst`. + +## Math Interpretation + +For each row, `TSORT32` processes `src` in independent 32-element blocks. Let block `b` cover columns `32b ... 32b+31`, and let `n_b = min(32, C - 32b)` be the valid element count of that block. + +For each valid element in the block, form a pair + +$$ +(v_k, i_k) = (\mathrm{src}_{r,32b+k}, \mathrm{idx}_{r,32b+k}), \quad 0 \le k < n_b +$$ + +Then sort the pairs by value and write the sorted value-index pairs to `dst`. The exact packing layout in `dst` is target-defined, but semantically the output of each block is the reordered sequence + +$$ +[(v_{\pi(0)}, i_{\pi(0)}), (v_{\pi(1)}, i_{\pi(1)}), \ldots, (v_{\pi(n_b-1)}, i_{\pi(n_b-1)})] +$$ + +where `π` is the permutation produced by the implementation for that 32-element block. + +Notes: + +- `idx` is an input tile, not an output tile. +- `dst` stores sorted value-index pairs, not just sorted values. +- The CPU simulation sorts in descending order by value, and for equal values keeps smaller indices first. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tsort32 %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsort32 %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsort32 ins(%src, %idx : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSORT32(DstTileData &dst, SrcTileData &src, IdxTileData &idx); + +template +PTO_INST RecordEvent TSORT32(DstTileData &dst, SrcTileData &src, IdxTileData &idx, TmpTileData &tmp); +``` + +## Constraints + +- `TSORT32` does not take `WaitEvents&...` and does not call `TSYNC(...)` internally; synchronize explicitly if needed. +- `idx` is a required input operand in both overloads; it provides the indices that are permuted together with `src`. +- **Implementation checks (A2A3/A5)**: + - `DstTileData::DType` must be `half` or `float`. + - `SrcTileData::DType` must match `DstTileData::DType`. + - `IdxTileData::DType` must be `uint32_t`. + - `dst/src/idx` tile location must be `TileType::Vec`, and all must be row-major (`isRowMajor`). +- **Valid region**: + - The implementation uses `dst.GetValidRow()` as the row count. + - The implementation uses `src.GetValidCol()` to determine how many elements participate in sorting in each row. + - Sorting is performed independently per 32-element block; the 4-argument overload additionally supports non-32-aligned tails with `tmp`. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using IdxT = Tile; + using DstT = Tile; + SrcT src; + IdxT idx; + DstT dst; + TSORT32(dst, src, idx); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using IdxT = Tile; + using DstT = Tile; + SrcT src; + IdxT idx; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(idx, 0x2000); + TASSIGN(dst, 0x3000); + TSORT32(dst, src, idx); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tsort32 %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +# pto.tassign %arg2, @tile(0x3000) +%dst = pto.tsort32 %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tsort32 %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tsort32 ins(%src, %idx : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSORT32_zh.md b/designs/outerCube/PTOISA/TSORT32_zh.md new file mode 100644 index 00000000..2229f183 --- /dev/null +++ b/designs/outerCube/PTOISA/TSORT32_zh.md @@ -0,0 +1,150 @@ +# TSORT32 + +## 指令示意图 + +![TSORT32 tile operation](../figures/isa/TSORT32.svg) + +## 简介 + +对 `src` 的每个 32 元素块,与 `idx` 中对应的索引一起进行排序,并将排序后的值-索引对写入 `dst`。 + +## 数学语义 + +对每一行,`TSORT32` 会按独立的 32 元素块处理 `src`。设第 `b` 个块覆盖列 `32b ... 32b+31`,该块的有效元素数为 `n_b = min(32, C - 32b)`。 + +对于块中的每个有效元素,先构造一个二元组: + +$$ +(v_k, i_k) = (\mathrm{src}_{r,32b+k}, \mathrm{idx}_{r,32b+k}), \quad 0 \le k < n_b +$$ + +然后按值对这些二元组排序,并将排序后的值-索引对写入 `dst`。`dst` 中的具体打包布局由目标实现定义,但从语义上看,每个块的输出可表示为: + +$$ +[(v_{\pi(0)}, i_{\pi(0)}), (v_{\pi(1)}, i_{\pi(1)}), \ldots, (v_{\pi(n_b-1)}, i_{\pi(n_b-1)})] +$$ + +其中 `π` 是该 32 元素块对应的排序置换。 + +说明: + +- `idx` 是输入 Tile,不是输出 Tile。 +- `dst` 保存的是排序后的值-索引对,而不只是排序后的值。 +- 在 CPU 仿真实现中,按值降序排序;当值相同时,索引较小者优先。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tsort32 %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tsort32 %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tsort32 ins(%src, %idx : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSORT32(DstTileData &dst, SrcTileData &src, IdxTileData &idx); + +template +PTO_INST RecordEvent TSORT32(DstTileData &dst, SrcTileData &src, IdxTileData &idx, TmpTileData &tmp); +``` + +## 约束 + +- `TSORT32` 不接受 `WaitEvents&...` 参数,也不在内部调用 `TSYNC(...)`;如有需要请显式同步。 +- `idx` 在两个重载中都是必需的输入操作数;它提供与 `src` 一起参与重排的索引。 +- **实现检查 (A2A3/A5)**: + - `DstTileData::DType` 必须是 `half` 或 `float`。 + - `SrcTileData::DType` 必须与 `DstTileData::DType` 匹配。 + - `IdxTileData::DType` 必须是 `uint32_t`。 + - `dst`/`src`/`idx` Tile 位置必须是 `TileType::Vec`,且都必须是行主序(`isRowMajor`)。 +- **有效区域**: + - 实现使用 `dst.GetValidRow()` 作为行数。 + - 实现使用 `src.GetValidCol()` 确定每行参与排序的元素数量。 + - 排序按独立的 32 元素块进行;4 参数重载额外通过 `tmp` 支持非 32 对齐尾块。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using IdxT = Tile; + using DstT = Tile; + SrcT src; + IdxT idx; + DstT dst; + TSORT32(dst, src, idx); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using IdxT = Tile; + using DstT = Tile; + SrcT src; + IdxT idx; + DstT dst; + TASSIGN(src, 0x1000); + TASSIGN(idx, 0x2000); + TASSIGN(dst, 0x3000); + TSORT32(dst, src, idx); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tsort32 %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +# pto.tassign %arg2, @tile(0x3000) +%dst = pto.tsort32 %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tsort32 %src, %idx : !pto.tile<...>, !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.tsort32 ins(%src, %idx : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TSQRT.md b/designs/outerCube/PTOISA/TSQRT.md new file mode 100644 index 00000000..881fb3ec --- /dev/null +++ b/designs/outerCube/PTOISA/TSQRT.md @@ -0,0 +1,130 @@ +# TSQRT + + +## Tile Operation Diagram + +![TSQRT tile operation](../figures/isa/TSQRT.svg) + +## Introduction + +Elementwise square root. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \sqrt{\mathrm{src}_{i,j}} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tsqrt %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsqrt ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tsqrt ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSQRT(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (NPU)**: + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Domain / NaN**: + - Behavior is target-defined (e.g., for negative inputs). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TSQRT(dst, src); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TSQRT(dst, src); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tsqrt %src : !pto.tile<...> +# AS Level 2 (DPS) +pto.tsqrt ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TSQRT_zh.md b/designs/outerCube/PTOISA/TSQRT_zh.md new file mode 100644 index 00000000..555c0308 --- /dev/null +++ b/designs/outerCube/PTOISA/TSQRT_zh.md @@ -0,0 +1,103 @@ +# TSQRT + +## 指令示意图 + +![TSQRT tile operation](../figures/isa/TSQRT.svg) + +## 简介 + +逐元素平方根。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \sqrt{\mathrm{src}_{i,j}} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tsqrt %src : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsqrt ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tsqrt %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tsqrt ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSQRT(TileDataDst &dst, TileDataSrc &src, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (NPU)**: + - `TileData::DType` must be one of: `float` or `half`; + - Tile location must be vector (`TileData::Loc == TileType::Vec`); + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`; + - Runtime: `src.GetValidRow() == dst.GetValidRow()` and `src.GetValidCol() == dst.GetValidCol()`; + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. +- **Domain / NaN**: + - Behavior is target-defined (e.g., for negative inputs). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src, dst; + TSQRT(dst, src); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src, dst; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TSQRT(dst, src); +} +``` diff --git a/designs/outerCube/PTOISA/TSTORE.md b/designs/outerCube/PTOISA/TSTORE.md new file mode 100644 index 00000000..e6e017c7 --- /dev/null +++ b/designs/outerCube/PTOISA/TSTORE.md @@ -0,0 +1,158 @@ +# TSTORE + + +## Tile Operation Diagram + +![TSTORE tile operation](../figures/isa/TSTORE.svg) + +## Introduction + +Store data from a Tile into a GlobalTensor (GM), optionally using atomic write or quantization parameters. + +## Math Interpretation + +Notation depends on the `GlobalTensor` shape/stride and the `Tile` layout. Conceptually (2D view, with a base offset): + +$$ \mathrm{dst}_{r_0 + i,\; c_0 + j} = \mathrm{src}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +tstore %t1, %sv_out[%c0, %c0] +``` + +### IR Level 1 (SSA) + +```text +pto.tstore %src, %mem : (!pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### IR Level 2 (DPS) + +```text +pto.tstore ins(%src : !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp` and `include/pto/common/constants.hpp`: + +```cpp +template +PTO_INST RecordEvent TSTORE(GlobalData &dst, TileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TSTORE(GlobalData &dst, TileData &src, uint64_t preQuantScalar, WaitEvents &... events); + +template +PTO_INST RecordEvent TSTORE_FP(GlobalData &dst, TileData &src, FpTileData &fp, WaitEvents &... events); +``` + +The `preQuantScalar` and `TSTORE_FP` quantized-store overloads are only legal for `TileType::Acc` on current A2/A3 and A5 backends. They do not provide a native vec-tile quantized store contract. + +## Constraints + +- **Implementation checks (A2A3)**: + - Source tile location must be one of: `TileType::Vec`, `TileType::Mat`, `TileType::Acc`. + - Runtime: all `dst.GetShape(dim)` values and `src.GetValidRow()/GetValidCol()` must be `> 0`. + - For `TileType::Vec` / `TileType::Mat`: + - `TileData::DType` must be one of: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `int64_t`, `uint64_t`, `half`, `bfloat16_t`, `float`. + - `sizeof(TileData::DType) == sizeof(GlobalData::DType)`. + - Layouts must match ND/DN/NZ (or a special case where `TileData::Rows == 1` or `TileData::Cols == 1`). + - For `int64_t/uint64_t`, only ND->ND or DN->DN are supported. + - A2/A3 does not expose a native vec quantized-store path. Frontends that need `vec -> GM` dtype conversion or quantization MUST first materialize the converted vec tile (for example via `TCVT`) and then issue a same-dtype `TSTORE`. + - For `TileType::Acc` (including quantized/atomic variants): + - Destination layout must be ND or NZ. + - Source dtype must be `int32_t` or `float`. + - When not using quantization, destination dtype must be `__gm__ int32_t/float/half/bfloat16_t`. + - Static shape constraints: `1 <= TileData::Cols <= 4095`; if ND then `1 <= TileData::Rows <= 8192`; if NZ then `1 <= TileData::Rows <= 65535` and `TileData::Cols % 16 == 0`. + - Runtime: `1 <= src.GetValidCol() <= 4095`. +- **Implementation checks (A5)**: + - Source tile location must be `TileType::Vec` or `TileType::Acc` (no `Mat` store on this target). + - For `TileType::Vec`: + - `sizeof(TileData::DType) == sizeof(GlobalData::DType)`. + - `TileData::DType` must be one of: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `int64_t`, `uint64_t`, `half`, `bfloat16_t`, `float`, `float8_e4m3_t`, `float8_e5m2_t`, `hifloat8_t`, `float4_e1m2x2_t`, `float4_e2m1x2_t`. + - Layouts must match ND/DN/NZ (or a special case where `TileData::Rows == 1` or `TileData::Cols == 1`). + - Additional alignment constraints are enforced (e.g., for ND the row-major width in bytes must be a multiple of 32; for DN the column-major height in bytes must be a multiple of 32, with special-case exceptions). + - For `TileType::Acc`: + - Destination layout must be ND or NZ; source dtype must be `int32_t` or `float`. + - When not using quantization, destination dtype must be `__gm__ int32_t/float/half/bfloat16_t`. + - Static shape constraints match A2A3 for rows/cols; `AtomicAdd` additionally restricts destination dtype to supported atomic types. +- **Valid region**: + - The implementation uses `src.GetValidRow()` / `src.GetValidCol()` as the transfer size. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +template +void example_auto(__gm__ T* out) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GTensor = GlobalTensor; + + GTensor gout(out); + TileT t; + TSTORE(gout, t); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +template +void example_manual(__gm__ T* out) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GTensor = GlobalTensor; + + GTensor gout(out); + TileT t; + TASSIGN(t, 0x1000); + TSTORE(gout, t); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +pto.tstore %src, %mem : (!pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +pto.tstore %src, %mem : (!pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### PTO Assembly Form + +```text +tstore %t1, %sv_out[%c0, %c0] +# IR Level 2 (DPS) +pto.tstore ins(%src : !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` diff --git a/designs/outerCube/PTOISA/TSTORE_FP.md b/designs/outerCube/PTOISA/TSTORE_FP.md new file mode 100644 index 00000000..942e1abe --- /dev/null +++ b/designs/outerCube/PTOISA/TSTORE_FP.md @@ -0,0 +1,147 @@ +# TSTORE_FP + + +## Tile Operation Diagram + +![TSTORE_FP tile operation](../figures/isa/TSTORE_FP.svg) + +## Introduction + +Store an accumulator tile into global memory using a scaling (`fp`) tile for vector quantization parameters. + +`TSTORE_FP` is the fp-quantization overload of `TSTORE` (see `docs/isa/TSTORE.md`). + +## Math Interpretation + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. Conceptually (2D view, with a base offset), for `0 <= i < R` and `0 <= j < C`: + +$$ \mathrm{dst}_{r_0 + i,\; c_0 + j} = \mathrm{Convert}\!\left(\mathrm{src}_{i,j};\ \mathrm{fp}\right) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +tstore.fp %src, %fp, %sv_out[%c0, %c0] +``` + +### AS Level 1 (SSA) + +```text +pto.tstore.fp %src, %fp, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### AS Level 2 (DPS) + +```text +pto.tstore.fp ins(%src, %fp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` + +### IR Level 1 (SSA) + +```text +pto.tstore.fp %src, %fp, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### IR Level 2 (DPS) + +```text +pto.tstore.fp ins(%src, %fp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp` and `include/pto/common/constants.hpp`: + +```cpp +template +PTO_INST RecordEvent TSTORE_FP(GlobalData &dst, TileData &src, FpTileData &fp, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - The fp store path is implemented via `TSTORE_IMPL(dst, src, fp)` and uses the same accumulator-to-GM legality checks as quantized accumulator stores: + - Destination layout must be ND or NZ. + - Source dtype must be `int32_t` or `float`. + - Static shape constraints: `1 <= TileData::Cols <= 4095`; if ND then `1 <= TileData::Rows <= 8192`; if NZ then `1 <= TileData::Rows <= 65535` and `TileData::Cols % 16 == 0`. + - Runtime: `1 <= src.GetValidCol() <= 4095`. + - No explicit `static_assert` is enforced on `FpTileData` (the implementation uses `fp` to set FPC state). +- **Implementation checks (A5)**: + - Implemented via `TSTORE_IMPL(dst, src, fp)` and validated by `CheckStaticAcc<..., true>()` for the accumulator path (ND/NZ only, `int32_t/float` source dtype, rows/cols ranges). + - No explicit `static_assert` is enforced on `FpTileData` (the implementation uses `fp` to set FPC state). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto(__gm__ int8_t* out) { + using AccT = TileAcc; + using FpT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GT = GlobalTensor; + + GT gout(out); + AccT acc; + FpT fp(16); + TSTORE_FP(gout, acc, fp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual(__gm__ int8_t* out) { + using AccT = TileAcc; + using FpT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GT = GlobalTensor; + + GT gout(out); + AccT acc; + FpT fp(16); + TASSIGN(acc, 0x1000); + TASSIGN(fp, 0x2000); + TSTORE_FP(gout, acc, fp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +pto.tstore.fp %src, %fp, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +pto.tstore.fp %src, %fp, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### PTO Assembly Form + +```text +tstore.fp %src, %fp, %sv_out[%c0, %c0] +# AS Level 2 (DPS) +pto.tstore.fp ins(%src, %fp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` diff --git a/designs/outerCube/PTOISA/TSTORE_FP_zh.md b/designs/outerCube/PTOISA/TSTORE_FP_zh.md new file mode 100644 index 00000000..53e9a64e --- /dev/null +++ b/designs/outerCube/PTOISA/TSTORE_FP_zh.md @@ -0,0 +1,118 @@ +# TSTORE_FP + +## 指令示意图 + +![TSTORE_FP tile operation](../figures/isa/TSTORE_FP.svg) + +## 简介 + +使用缩放 (`fp`) Tile 作为向量量化参数,将累加器 Tile 存储到全局内存。 + +## 数学语义 + +Let `R = src.GetValidRow()` and `C = src.GetValidCol()`. Conceptually (2D view, with a base offset), for `0 <= i < R` and `0 <= j < C`: + +$$ \mathrm{dst}_{r_0 + i,\; c_0 + j} = \mathrm{Convert}\!\left(\mathrm{src}_{i,j};\ \mathrm{fp}\right) $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +tstore.fp %src, %fp, %sv_out[%c0, %c0] +``` + +### AS Level 1 (SSA) + +```text +pto.tstore.fp %src, %fp, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### AS Level 2 (DPS) + +```text +pto.tstore.fp ins(%src, %fp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` + +### AS Level 1(SSA) + +```text +pto.tstore.fp %src, %fp, %mem : (!pto.tile<...>, !pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### AS Level 2(DPS) + +```text +pto.tstore.fp ins(%src, %fp : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp` and `include/pto/common/constants.hpp`: + +```cpp +template +PTO_INST RecordEvent TSTORE_FP(GlobalData &dst, TileData &src, FpTileData &fp, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - The fp store path is implemented via `TSTORE_IMPL(dst, src, fp)` and uses the same accumulator-to-GM legality checks as quantized accumulator stores: + - Destination layout must be ND or NZ. + - Source dtype must be `int32_t` or `float`. + - Static shape constraints: `1 <= TileData::Cols <= 4095`; if ND then `1 <= TileData::Rows <= 8192`; if NZ then `1 <= TileData::Rows <= 65535` and `TileData::Cols % 16 == 0`. + - Runtime: `1 <= src.GetValidCol() <= 4095`. + - No explicit `static_assert` is enforced on `FpTileData` (the implementation uses `fp` to set FPC state). +- **实现检查 (A5)**: + - Implemented via `TSTORE_IMPL(dst, src, fp)` and validated by `CheckStaticAcc<..., true>()` for the accumulator path (ND/NZ only, `int32_t/float` source dtype, rows/cols ranges). + - No explicit `static_assert` is enforced on `FpTileData` (the implementation uses `fp` to set FPC state). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto(__gm__ int8_t* out) { + using AccT = TileAcc; + using FpT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GT = GlobalTensor; + + GT gout(out); + AccT acc; + FpT fp(16); + TSTORE_FP(gout, acc, fp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual(__gm__ int8_t* out) { + using AccT = TileAcc; + using FpT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GT = GlobalTensor; + + GT gout(out); + AccT acc; + FpT fp(16); + TASSIGN(acc, 0x1000); + TASSIGN(fp, 0x2000); + TSTORE_FP(gout, acc, fp); +} +``` diff --git a/designs/outerCube/PTOISA/TSTORE_zh.md b/designs/outerCube/PTOISA/TSTORE_zh.md new file mode 100644 index 00000000..1ae44856 --- /dev/null +++ b/designs/outerCube/PTOISA/TSTORE_zh.md @@ -0,0 +1,131 @@ +# TSTORE + +## 指令示意图 + +![TSTORE tile operation](../figures/isa/TSTORE.svg) + +## 简介 + +将 Tile 中的数据存储到 GlobalTensor (GM),可选使用原子写入或量化参数。 + +## 数学语义 + +Notation depends on the `GlobalTensor` shape/stride and the `Tile` layout. Conceptually (2D view, with a base offset): + +$$ \mathrm{dst}_{r_0 + i,\; c_0 + j} = \mathrm{src}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +tstore %t1, %sv_out[%c0, %c0] +``` + +### AS Level 1(SSA) + +```text +pto.tstore %src, %mem : (!pto.tile<...>, !pto.partition_tensor_view) -> () +``` + +### AS Level 2(DPS) + +```text +pto.tstore ins(%src : !pto.tile_buf<...>) outs(%mem : !pto.partition_tensor_view) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp` and `include/pto/common/constants.hpp`: + +```cpp +template +PTO_INST RecordEvent TSTORE(GlobalData &dst, TileData &src, WaitEvents &... events); + +template +PTO_INST RecordEvent TSTORE(GlobalData &dst, TileData &src, uint64_t preQuantScalar, WaitEvents &... events); + +template +PTO_INST RecordEvent TSTORE_FP(GlobalData &dst, TileData &src, FpTileData &fp, WaitEvents &... events); +``` + +The `preQuantScalar` and `TSTORE_FP` quantized-store overloads are only legal for `TileType::Acc` on current A2/A3 and A5 backends. They do not provide a native vec-tile quantized store contract. + +## 约束 + +- **实现检查 (A2A3)**: + - Source tile location must be one of: `TileType::Vec`, `TileType::Mat`, `TileType::Acc`. + - Runtime: all `dst.GetShape(dim)` values and `src.GetValidRow()/GetValidCol()` must be `> 0`. + - For `TileType::Vec` / `TileType::Mat`: + - `TileData::DType` must be one of: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `int64_t`, `uint64_t`, `half`, `bfloat16_t`, `float`. + - `sizeof(TileData::DType) == sizeof(GlobalData::DType)`. + - Layouts must match ND/DN/NZ (or a special case where `TileData::Rows == 1` or `TileData::Cols == 1`). + - For `int64_t/uint64_t`, only ND->ND or DN->DN are supported. + - A2/A3 does not expose a native vec quantized-store path. Frontends that need `vec -> GM` dtype conversion or quantization MUST first materialize the converted vec tile (for example via `TCVT`) and then issue a same-dtype `TSTORE`. + - For `TileType::Acc` (including quantized/atomic variants): + - Destination layout must be ND or NZ. + - Source dtype must be `int32_t` or `float`. + - When not using quantization, destination dtype must be `__gm__ int32_t/float/half/bfloat16_t`. + - Static shape constraints: `1 <= TileData::Cols <= 4095`; if ND then `1 <= TileData::Rows <= 8192`; if NZ then `1 <= TileData::Rows <= 65535` and `TileData::Cols % 16 == 0`. + - Runtime: `1 <= src.GetValidCol() <= 4095`. +- **实现检查 (A5)**: + - Source tile location must be `TileType::Vec` or `TileType::Acc` (no `Mat` store on this target). + - For `TileType::Vec`: + - `sizeof(TileData::DType) == sizeof(GlobalData::DType)`. + - `TileData::DType` must be one of: `int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `int64_t`, `uint64_t`, `half`, `bfloat16_t`, `float`, `float8_e4m3_t`, `float8_e5m2_t`, `hifloat8_t`, `float4_e1m2x2_t`, `float4_e2m1x2_t`. + - Layouts must match ND/DN/NZ (or a special case where `TileData::Rows == 1` or `TileData::Cols == 1`). + - Additional alignment constraints are enforced (e.g., for ND the row-major width in bytes must be a multiple of 32; for DN the column-major height in bytes must be a multiple of 32, with special-case exceptions). + - For `TileType::Acc`: + - Destination layout must be ND or NZ; source dtype must be `int32_t` or `float`. + - When not using quantization, destination dtype must be `__gm__ int32_t/float/half/bfloat16_t`. + - Static shape constraints match A2A3 for rows/cols; `AtomicAdd` additionally restricts destination dtype to supported atomic types. +- **有效区域**: + - The implementation uses `src.GetValidRow()` / `src.GetValidCol()` as the transfer size. + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +template +void example_auto(__gm__ T* out) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GTensor = GlobalTensor; + + GTensor gout(out); + TileT t; + TSTORE(gout, t); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +template +void example_manual(__gm__ T* out) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GTensor = GlobalTensor; + + GTensor gout(out); + TileT t; + TASSIGN(t, 0x1000); + TSTORE(gout, t); +} +``` diff --git a/designs/outerCube/PTOISA/TSUB.md b/designs/outerCube/PTOISA/TSUB.md new file mode 100644 index 00000000..5f20459b --- /dev/null +++ b/designs/outerCube/PTOISA/TSUB.md @@ -0,0 +1,135 @@ +# TSUB + + +## Tile Operation Diagram + +![TSUB tile operation](../figures/isa/TSUB.svg) + +## Introduction + +Elementwise subtract of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} - \mathrm{src1}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tsub %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsub %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tsub %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSUB(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `uint32_t`, `int32_t`, `uint16_t`, `int16_t`, `uint8_t`, `int8_t`, `float`, `half`. + - Tile layout must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src0/src1` are assumed to be compatible (not validated by explicit runtime checks in this op). + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TSUB(dst, src0, src1); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TSUB(dst, src0, src1); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tsub %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tsub %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tsub %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TSUBC.md b/designs/outerCube/PTOISA/TSUBC.md new file mode 100644 index 00000000..aa0ef697 --- /dev/null +++ b/designs/outerCube/PTOISA/TSUBC.md @@ -0,0 +1,103 @@ +# TSUBC + + +## Tile Operation Diagram + +![TSUBC tile operation](../figures/isa/TSUBC.svg) + +## Introduction + +Elementwise ternary op: `src0 - src1 + src2`. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} - \mathrm{src1}_{i,j} + \mathrm{src2}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tsubc %src0, %src1, %src2 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsubc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsubc ins(%src0, %src1, %src2 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tsubc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tsubc ins(%src0, %src1, %src2 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSUBC(TileData &dst, TileData &src0, TileData &src1, TileData &src2, WaitEvents &... events); +``` + +## Constraints + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, c, out; + TSUBC(out, a, b, c); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tsubc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tsubc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tsubc %src0, %src1, %src2 : !pto.tile<...> +# AS Level 2 (DPS) +pto.tsubc ins(%src0, %src1, %src2 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TSUBC_zh.md b/designs/outerCube/PTOISA/TSUBC_zh.md new file mode 100644 index 00000000..100e1832 --- /dev/null +++ b/designs/outerCube/PTOISA/TSUBC_zh.md @@ -0,0 +1,76 @@ +# TSUBC + +## 指令示意图 + +![TSUBC tile operation](../figures/isa/TSUBC.svg) + +## 简介 + +三元逐元素运算:`src0 - src1 + src2`。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} - \mathrm{src1}_{i,j} + \mathrm{src2}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tsubc %src0, %src1, %src2 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsubc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsubc ins(%src0, %src1, %src2 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tsubc %src0, %src1, %src2 : (!pto.tile<...>, !pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tsubc ins(%src0, %src1, %src2 : !pto.tile_buf<...>, !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSUBC(TileData &dst, TileData &src0, TileData &src1, TileData &src2, WaitEvents &... events); +``` + +## 约束 + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, c, out; + TSUBC(out, a, b, c); +} +``` diff --git a/designs/outerCube/PTOISA/TSUBS.md b/designs/outerCube/PTOISA/TSUBS.md new file mode 100644 index 00000000..4ce309b9 --- /dev/null +++ b/designs/outerCube/PTOISA/TSUBS.md @@ -0,0 +1,105 @@ +# TSUBS + + +## Tile Operation Diagram + +![TSUBS tile operation](../figures/isa/TSUBS.svg) + +## Introduction + +Elementwise subtract a scalar from a tile. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} - \mathrm{scalar} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tsubs %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsubs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsubs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSUBS(TileDataDst &dst, TileDataSrc &src0, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int`, `int16_t`, `half`, `float16_t`, `float`, `float32_t`. + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `int32_t`, `int`, `int16_t`, `half`, `float16_t`, `float`, `float32_t`. + - Tile location must be vector (`TileDataDst::Loc == TileType::Vec` and `TileDataSrc::Loc == TileType::Vec`). + - Static valid bounds: `TileDataDst::ValidRow <= TileDataDst::Rows`, `TileDataDst::ValidCol <= TileDataDst::Cols`, `TileDataSrc::ValidRow <= TileDataSrc::Rows`, and `TileDataSrc::ValidCol <= TileDataSrc::Cols`. + - Runtime: `src0.GetValidRow() == dst.GetValidRow()` and `src0.GetValidCol() == dst.GetValidCol()`. +- **Common constraints**: + - `dst` and `src0` must use the same element type. + - Scalar type must match `TileDataSrc::DType`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TSUBS(out, x, 1.0f); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tsubs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tsubs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tsubs %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tsubs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSUBSC.md b/designs/outerCube/PTOISA/TSUBSC.md new file mode 100644 index 00000000..c04cf390 --- /dev/null +++ b/designs/outerCube/PTOISA/TSUBSC.md @@ -0,0 +1,116 @@ +# TSUBSC + + +## Tile Operation Diagram + +![TSUBSC tile operation](../figures/isa/TSUBSC.svg) + +## Introduction + +Elementwise fused op: `src0 - scalar + src1`. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} - \mathrm{scalar} + \mathrm{src1}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = tsubsc %src0, %scalar, %src1 : !pto.tile<...>, f32, !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsubsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsubsc ins(%src0, %scalar, %src1 : !pto.tile_buf<...>, dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.tsubsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.tsubsc ins(%src0, %scalar, %src1 : !pto.tile_buf<...>, dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSUBSC(TileData& dst, TileData& src0, typename TileData::DType scalar, TileData& src1, + WaitEvents&... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Implementation checks (A5)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile layout must be row-major (`TileData::isRowMajor`). +- **Common constraints**: + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `dst`, `src0` and `src1` must have the same valid row/col. + - Scalar type must match the Tile data type. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, out; + TSUBSC(out, a, 2.0f, b); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.tsubsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tsubsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = tsubsc %src0, %scalar, %src1 : !pto.tile<...>, f32, !pto.tile<...> +# AS Level 2 (DPS) +pto.tsubsc ins(%src0, %scalar, %src1 : !pto.tile_buf<...>, dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TSUBSC_zh.md b/designs/outerCube/PTOISA/TSUBSC_zh.md new file mode 100644 index 00000000..3b34e713 --- /dev/null +++ b/designs/outerCube/PTOISA/TSUBSC_zh.md @@ -0,0 +1,89 @@ +# TSUBSC + +## 指令示意图 + +![TSUBSC tile operation](../figures/isa/TSUBSC.svg) + +## 简介 + +融合逐元素运算:`src0 - scalar + src1`。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} - \mathrm{scalar} + \mathrm{src1}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tsubsc %src0, %scalar, %src1 : !pto.tile<...>, f32, !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsubsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsubsc ins(%src0, %scalar, %src1 : !pto.tile_buf<...>, dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tsubsc %src0, %scalar, %src1 : (!pto.tile<...>, dtype, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tsubsc ins(%src0, %scalar, %src1 : !pto.tile_buf<...>, dtype, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSUBSC(TileData& dst, TileData& src0, typename TileData::DType scalar, TileData& src1, + WaitEvents&... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). +- **Common constraints**: + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `dst`, `src0` and `src1` must have the same valid row/col. + - Scalar type must match the Tile data type. +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT a, b, out; + TSUBSC(out, a, 2.0f, b); +} +``` diff --git a/designs/outerCube/PTOISA/TSUBS_zh.md b/designs/outerCube/PTOISA/TSUBS_zh.md new file mode 100644 index 00000000..5441db2b --- /dev/null +++ b/designs/outerCube/PTOISA/TSUBS_zh.md @@ -0,0 +1,105 @@ +# TSUBS + +## 指令示意图 + +![TSUBS tile operation](../figures/isa/TSUBS.svg) + +## 简介 + +从 Tile 中逐元素减去一个标量。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} - \mathrm{scalar} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = tsubs %src, %scalar : !pto.tile<...>, f32 +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tsubs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tsubs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSUBS(TileDataDst &dst, TileDataSrc &src0, typename TileDataSrc::DType scalar, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` 必须是以下之一:`int32_t`、`int`、`int16_t`、`half`、`float16_t`、`float`、`float32_t`。 + - Tile 位置必须是向量(`TileData::Loc == TileType::Vec`)。 + - 运行时:`src0.GetValidRow() == dst.GetValidRow()` 且 `src0.GetValidCol() == dst.GetValidCol()`。 +- **实现检查 (A5)**: + - `TileData::DType` 必须是以下之一:`int32_t`、`int`、`int16_t`、`half`、`float16_t`、`float`、`float32_t`。 + - Tile 位置必须是向量(`TileDataDst::Loc == TileType::Vec` 且 `TileDataSrc::Loc == TileType::Vec`)。 + - 静态有效边界:`TileDataDst::ValidRow <= TileDataDst::Rows`、`TileDataDst::ValidCol <= TileDataDst::Cols`、`TileDataSrc::ValidRow <= TileDataSrc::Rows`,且 `TileDataSrc::ValidCol <= TileDataSrc::Cols`。 + - 运行时:`src0.GetValidRow() == dst.GetValidRow()` 且 `src0.GetValidCol() == dst.GetValidCol()`。 +- **通用约束**: + - `dst` 和 `src0` 必须使用相同的元素类型。 + - 标量类型必须与 `TileDataSrc::DType` 一致。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileT = Tile; + TileT x, out; + TSUBS(out, x, 1.0f); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.tsubs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.tsubs %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = tsubs %src, %scalar : !pto.tile<...>, f32 +# AS Level 2 (DPS) +pto.tsubs ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TSUBVIEW.md b/designs/outerCube/PTOISA/TSUBVIEW.md new file mode 100644 index 00000000..c423eb63 --- /dev/null +++ b/designs/outerCube/PTOISA/TSUBVIEW.md @@ -0,0 +1,89 @@ +# TSUBVIEW + + +## Tile Operation Diagram + +![TSUBVIEW tile operation](../figures/isa/TSUBVIEW.svg) + +## Introduction + +Reinterpret a tile as a subtile of another tile. + +## Math Interpretation + +- `rowIdx`: in the valid region of `src`, the starting row index of the `dst` subtile. +- `colIdx`: in the valid region of `src`, the starting column index of the `dst` subtile. + +For each element `(i, j)` in the valid region of `dst`: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{\mathrm{rowIdx} + i,\mathrm{colIdx} + j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + + +### IR Level 1 (SSA) +TODO + +### IR Level 2 (DPS) +TODO + + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSUBVIEW(TileDataDst &dst, TileDataSrc &src, uint16_t rowIdx, uint16_t colIdx, WaitEvents&... events); +``` + +## Constraints + +Enforced by `TSUBVIEW_IMPL`: + +- **Tile type must match**: `TileDataSrc::Loc == TileDataDst::Loc`. +- **Both tiles must have the same static capacity**: `TileDataSrc::Rows == TileDataDst::Rows` and `TileDataSrc::Cols == TileDataDst::Cols`. +- **Both tiles must have the same BLayout**: `TileDataSrc::BFractal == TileDataDst::BFractal`. +- **The source tile's validRow (validCol) is at least as big as the destination tile's validRow (validCol)** + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using Src = Tile; + using Dst = Tile; + + Src src; + Dst dst0; + Dst dst1; + Dst dst2; + Dst dst3; + + // e.g. split into four 2x32 subtiles + TSUBVIEW(dst0, src, 0, 0); + TSUBVIEW(dst1, src, 0, 32); + TSUBVIEW(dst2, src, 2, 0); + TSUBVIEW(dst3, src, 2, 32); +} +``` + +## ASM Form Examples + +### Auto Mode + +TODO + +### Manual Mode + +TODO + +### PTO Assembly Form + +TODO + diff --git a/designs/outerCube/PTOISA/TSUBVIEW_zh.md b/designs/outerCube/PTOISA/TSUBVIEW_zh.md new file mode 100644 index 00000000..42f56e89 --- /dev/null +++ b/designs/outerCube/PTOISA/TSUBVIEW_zh.md @@ -0,0 +1,87 @@ +# TSUBVIEW + +## Tile操作图例 + +![TSUBVIEW tile operation](../figures/isa/TSUBVIEW.svg) + +## 简介 + +表达一个Tile是另一个Tile的subview。 + +## 数学表达 + +- `rowIdx`: 在`src`的有效区域内的起始行的索引。 +- `colIdx`: 在`src`的有效区域内的起始列的索引。 + +对于`dst`中有效区域内的每一个元素`(i, j)`: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{\mathrm{rowIdx} + i,\mathrm{colIdx} + j} $$ + +## 汇编语法 + +PTO-AS form: 详见 [PTO-AS Specification](../assembly/PTO-AS.md). + +### IR Level 1 (SSA) + +TODO + +### IR Level 2 (DPS) + +TODO + +## C++ Intrinsic + +定义在 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSUBVIEW(TileDataDst &dst, TileDataSrc &src, uint16_t rowIdx, uint16_t colIdx, WaitEvents&... events); +``` + +## 限制 + +规定在`TSUBVIEW_IMPL`中: + +- **Tile类型必须相同**: `TileDataSrc::Loc == TileDataDst::Loc`. +- **输入和输出Tile的静态shape必须相同**: `TileDataSrc::Rows == TileDataDst::Rows` and `TileDataSrc::Cols == TileDataDst::Cols`. +- **输入和输出Tile的BLayout必须相同**: `TileDataSrc::BFractal == TileDataDst::BFractal`. +- **src的validRow和validCol必须大于等于dst的validRow和validCol** + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using Src = Tile; + using Dst = Tile; + + Src src; + Dst dst0; + Dst dst1; + Dst dst2; + Dst dst3; + + // e.g. split into four 2x32 subtiles + TSUBVIEW(dst0, src, 0, 0); + TSUBVIEW(dst1, src, 0, 32); + TSUBVIEW(dst2, src, 2, 0); + TSUBVIEW(dst3, src, 2, 32); +} +``` + +## ASM示例 + +### Auto模式 + +TODO + +### Manual模式 + +TODO + +### PTO汇编格式 + +TODO diff --git a/designs/outerCube/PTOISA/TSUB_zh.md b/designs/outerCube/PTOISA/TSUB_zh.md new file mode 100644 index 00000000..4a69783c --- /dev/null +++ b/designs/outerCube/PTOISA/TSUB_zh.md @@ -0,0 +1,108 @@ +# TSUB + +## 指令示意图 + +![TSUB tile operation](../figures/isa/TSUB.svg) + +## 简介 + +两个 Tile 的逐元素减法。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} - \mathrm{src1}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +同步形式: + +```text +%dst = tsub %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.tsub %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.tsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.tsub %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.tsub ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TSUB(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `TileData::DType` must be one of: `int32_t`, `int16_t`, `half`, `float`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **实现检查 (A5)**: + - `TileData::DType` must be one of: `uint32_t`, `int32_t`, `uint16_t`, `int16_t`, `uint8_t`, `int8_t`, `float`, `half`. + - Tile 布局 must be row-major (`TileData::isRowMajor`). + - Tile location must be vector (`TileData::Loc == TileType::Vec`). + - Static valid bounds: `TileData::ValidRow <= TileData::Rows` and `TileData::ValidCol <= TileData::Cols`. + - Runtime: `src0`, `src1` and `dst` tiles should have the same `validRow/validCol`. +- **有效区域**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain; `src0/src1` are assumed to be compatible (not validated by explicit runtime checks in this op). + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using TileT = Tile; + TileT src0, src1, dst; + TSUB(dst, src0, src1); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT src0, src1, dst; + TASSIGN(src0, 0x1000); + TASSIGN(src1, 0x2000); + TASSIGN(dst, 0x3000); + TSUB(dst, src0, src1); +} +``` diff --git a/designs/outerCube/PTOISA/TSYNC.md b/designs/outerCube/PTOISA/TSYNC.md new file mode 100644 index 00000000..7764c600 --- /dev/null +++ b/designs/outerCube/PTOISA/TSYNC.md @@ -0,0 +1,143 @@ +# TSYNC + + +## Tile Operation Diagram + +![TSYNC tile operation](../figures/isa/TSYNC.svg) + +## Introduction + +Synchronize PTO execution: + +- `TSYNC(events...)` waits on a set of explicit event tokens. +- `TSYNC()` inserts a pipeline barrier for a single vector op class. + +Many intrinsics in `include/pto/common/pto_instr.hpp` call `TSYNC(events...)` internally before issuing the instruction. + +## Math Interpretation + +Not applicable. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Event operand form: + +```text +tsync %e0, %e1 : !pto.event<...>, !pto.event<...> +``` + +Single-op barrier form: + +```text +tsync.op #pto.op +``` + +### AS Level 1 (SSA) + +```text +// Level 1 (SSA) does not support explicit synchronization primitives. +``` + +### AS Level 2 (DPS) + +```text +pto.record_event[src_op, dst_op, eventID] +// 支持的op:TLOAD,TSTORE_ACC,TSTORE_VEC,TMOV_M2L,TMOV_M2S,TMOV_M2B,TMOV_M2V,TMOV_V2M,TMATMUL,TVEC +pto.wait_event[src_op, dst_op, eventID] +// 支持的op:TLOAD,TSTORE_ACC,TSTORE_VEC,TMOV_M2L,TMOV_M2S,TMOV_M2B,TMOV_M2V,TMOV_V2M,TMATMUL,TVEC +pto.barrier(op) +// 支持的op:TVEC,TMATMUL +``` + +In the current PTO-DSL front-end flow, `record_event` and `wait_event` should +be treated as low-level TSYNC forms. Front-end kernels SHOULD normally stay free +of explicit event wiring and rely on `ptoas --enable-insert-sync`. + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST void TSYNC(); + +template +PTO_INST void TSYNC(WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (`TSYNC()`)**: + - `TSYNC_IMPL()` only supports vector-pipeline ops (`static_assert(pipe == PIPE_V)` in `include/pto/common/event.hpp`). +- **`TSYNC(events...)` semantics**: + - `TSYNC(events...)` calls `WaitAllEvents(events...)`, which invokes `events.Wait()` on each event token. In auto mode, this is no-op. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto(__gm__ float* in) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GT = GlobalTensor; + + GT gin(in); + TileT t; + Event e; + e = TLOAD(t, gin); + TSYNC(e); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT a, b, c; + Event e; + e = TADD(c, a, b); + TSYNC(); + TSYNC(e); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%result = pto.tsync ... +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%result = pto.tsync ... +``` + +### PTO Assembly Form + +```text +tsync %e0, %e1 : !pto.event<...>, !pto.event<...> +# AS Level 2 (DPS) +pto.record_event[src_op, dst_op, eventID] +``` + diff --git a/designs/outerCube/PTOISA/TSYNC_zh.md b/designs/outerCube/PTOISA/TSYNC_zh.md new file mode 100644 index 00000000..2d686d55 --- /dev/null +++ b/designs/outerCube/PTOISA/TSYNC_zh.md @@ -0,0 +1,141 @@ +# TSYNC + +## 指令示意图 + +![TSYNC tile operation](../figures/isa/TSYNC.svg) + +## 简介 + +同步 PTO 执行(等待事件或插入每操作流水线屏障)。 + +- `TSYNC(events...)` 等待一组显式事件令牌。 +- `TSYNC()` 为单个向量操作类插入流水线屏障。 + +`include/pto/common/pto_instr.hpp` 中的许多内建函数在发射指令前会在内部调用 `TSYNC(events...)`。 + +## 数学语义 + +不适用。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +Event operand form: + +```text +tsync %e0, %e1 : !pto.event<...>, !pto.event<...> +``` + +Single-op barrier form: + +```text +tsync.op #pto.op +``` + +### AS Level 1(SSA) + +```text +// Level 1 (SSA) does not support explicit synchronization primitives. +``` + +### AS Level 2(DPS) + +```text +pto.record_event[src_op, dst_op, eventID] +// 支持的op:TLOAD, TSTORE_ACC,TSTORE_VEC,TMOV_M2L,TMOV_M2S,TMOV_M2B,TMOV_M2V,TMOV_V2M,TMATMUL,TVEC +pto.wait_event[src_op, dst_op, eventID] +// 支持的op:TLOAD, TSTORE_ACC,TSTORE_VEC,TMOV_M2L,TMOV_M2S,TMOV_M2B,TMOV_M2V,TMOV_V2M,TMATMUL,TVEC +pto.barrier(op) +// 支持的op:TVEC,TMATMUL +``` + +在当前 PTO-DSL 前端流程中,`record_event` 和 `wait_event` 应视为 TSYNC 的低层形式。 +前端 kernel 通常不应手工编写事件连线,而应依赖 `ptoas --enable-insert-sync` +自动插入同步。 + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST void TSYNC(); + +template +PTO_INST void TSYNC(WaitEvents &... events); +``` + +## 约束 + +- **实现检查(`TSYNC()`)**: + - `TSYNC_IMPL()` 仅支持向量流水线操作(`include/pto/common/event.hpp` 中通过 `static_assert(pipe == PIPE_V)` 强制执行)。 +- **`TSYNC(events...)` 语义**: + - `TSYNC(events...)` 调用 `WaitAllEvents(events...)`,后者对每个事件令牌调用 `events.Wait()`。在auto模式下是no-op。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto(__gm__ float* in) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GT = GlobalTensor; + + GT gin(in); + TileT t; + Event e; + e = TLOAD(t, gin); + TSYNC(e); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using TileT = Tile; + TileT a, b, c; + Event e; + e = TADD(c, a, b); + TSYNC(); + TSYNC(e); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%result = pto.tsync ... +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%result = pto.tsync ... +``` + +### PTO 汇编形式 + +```text +tsync %e0, %e1 : !pto.event<...>, !pto.event<...> +# AS Level 2 (DPS) +pto.record_event[src_op, dst_op, eventID] +``` diff --git a/designs/outerCube/PTOISA/TTRANS.md b/designs/outerCube/PTOISA/TTRANS.md new file mode 100644 index 00000000..f2738063 --- /dev/null +++ b/designs/outerCube/PTOISA/TTRANS.md @@ -0,0 +1,144 @@ +# TTRANS + + +## Tile Operation Diagram + +![TTRANS tile operation](../figures/isa/TTRANS.svg) + +## Introduction + +Transpose with an implementation-defined temporary tile. + +## Math Interpretation + +For a 2D tile, over the effective transpose domain: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{j,i} $$ + +Exact shape/layout and the transpose domain depend on the target (see Constraints). + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = ttrans %src : !pto.tile<...> -> !pto.tile<...> +``` +Lowering may introduce internal scratch tiles; the C++ intrinsic requires an explicit `tmp` operand. + +### AS Level 1 (SSA) + +```text +%dst = pto.ttrans %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.ttrans ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TTRANS(TileDataDst &dst, TileDataSrc &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - `sizeof(TileDataSrc::DType) == sizeof(TileDataDst::DType)`. + - Source layout must be row-major (`TileDataSrc::isRowMajor`). + - Element size must be `1`, `2`, or `4` bytes. + - Supported element types are restricted per element width: + - 4 bytes: `uint32_t`, `int32_t`, `float` + - 2 bytes: `uint16_t`, `int16_t`, `half`, `bfloat16_t` + - 1 byte: `uint8_t`, `int8_t` + - The transpose size is taken from `src.GetValidRow()` / `src.GetValidCol()`. +- **Implementation checks (A5)**: + - `sizeof(TileDataSrc::DType) == sizeof(TileDataDst::DType)`. + - 32-byte alignment constraints are enforced on the major dimension of both input and output (row-major checks `Cols * sizeof(T) % 32 == 0`, col-major checks `Rows * sizeof(T) % 32 == 0`). + - Supported element types are restricted per element width: + - 4 bytes: `uint32_t`, `int32_t`, `float` + - 2 bytes: `uint16_t`, `int16_t`, `half`, `bfloat16_t` + - 1 byte: `uint8_t`, `int8_t` + - The implementation operates over the static tile shape (`TileDataSrc::Rows/Cols`) and does not consult `GetValidRow/GetValidCol`. +- **Temporary tile**: + - The C++ API requires `tmp`, but some implementations may not use it. +- **ConvTile**: + - Transpose of ConvTile for `TileType::Vec` is supported。 Element size must be `1`、`2` or `4` bytes. Supported element types are `uint32_t`、`int32_t`、`float`、`uint16_t`、`int16_t`、`half`、`bfloat16_t`、`uint8_t`、`int8_t`. + - Format transformation from `NCHW` to `NC1HWC0` is supported, while `C1 == (C + C0 - 1)/C0`,HW matches alignment constraint,which means `H*W*sizeof(T)==0`. C0 means `c0_size`, which `C0 * sizeof(T) == 32`。C0 can also be 4. + - Format transformation from `NC1HWC0` to `FRACTAL_Z` is supported, while `N1 == (N + N0 - 1)/N0`。N0 should be 16. + +## Examples + +### Auto + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TTRANS(dst, src, tmp); +} +``` + +### Manual + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TTRANS(dst, src, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.ttrans %src : !pto.tile<...> -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.ttrans %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = ttrans %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.ttrans ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TTRANS_zh.md b/designs/outerCube/PTOISA/TTRANS_zh.md new file mode 100644 index 00000000..370e6082 --- /dev/null +++ b/designs/outerCube/PTOISA/TTRANS_zh.md @@ -0,0 +1,144 @@ +# TTRANS + +## 指令示意图 + +![TTRANS tile operation](../figures/isa/TTRANS.svg) + +## 简介 + +使用实现定义的临时 Tile 进行转置。 + +## 数学语义 + +对于二维 Tile,在有效转置域上: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{j,i} $$ + +确切的形状/布局及转置域取决于目标硬件(参见约束)。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = ttrans %src : !pto.tile<...> -> !pto.tile<...> +``` +降低时可能引入内部临时 Tile;C++ 内建接口需要显式传入 `tmp` 操作数。 + +### AS Level 1(SSA) + +```text +%dst = pto.ttrans %src : !pto.tile<...> -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.ttrans ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TTRANS(TileDataDst &dst, TileDataSrc &src, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - `sizeof(TileDataSrc::DType) == sizeof(TileDataDst::DType)`。 + - 源布局必须是行主序(`TileDataSrc::isRowMajor`)。 + - 元素大小必须是 `1`、`2` 或 `4` 字节。 + - 支持的元素类型按元素宽度限制如下: + - 4 字节:`uint32_t`、`int32_t`、`float` + - 2 字节:`uint16_t`、`int16_t`、`half`、`bfloat16_t` + - 1 字节:`uint8_t`、`int8_t` + - 转置大小取自 `src.GetValidRow()` / `src.GetValidCol()`。 +- **实现检查 (A5)**: + - `sizeof(TileDataSrc::DType) == sizeof(TileDataDst::DType)`。 + - 对输入和输出的主维度强制执行 32 字节对齐约束(行主序检查 `Cols * sizeof(T) % 32 == 0`,列主序检查 `Rows * sizeof(T) % 32 == 0`)。 + - 支持的元素类型按元素宽度限制如下: + - 4 字节:`uint32_t`、`int32_t`、`float` + - 2 字节:`uint16_t`、`int16_t`、`half`、`bfloat16_t` + - 1 字节:`uint8_t`、`int8_t` + - 实现在静态 Tile 形状(`TileDataSrc::Rows/Cols`)上运算,不参考 `GetValidRow/GetValidCol`。 +- **临时 Tile**: + - C++ API 需要 `tmp`,但某些实现可能不使用它。 +- **ConvTile**: + - 支持在`TileType::Vec`上的ConvTile的格式转换。其元素大小必须是 `1`、`2` 或 `4` 字节。元素类型限制为`uint32_t`、`int32_t`、`float`、`uint16_t`、`int16_t`、`half`、`bfloat16_t`、`uint8_t`、`int8_t`。 + - 支持ConvTile从`NCHW`到`NC1HWC0`的变换,其中`C1 == (C + C0 - 1)/C0`,HW满足对齐要求,即`H*W*sizeof(T)==0`. C0对应`c0_size`, 即`C0 * sizeof(T) == 32`。C0也可以为4。 + - 支持ConvTile从`NC1HWC0`到`FRACTAL_Z`的变换, 其中`N1 == (N + N0 - 1)/N0`。N0为16。 + +## 示例 + +### 自动(Auto) + +```cpp +#include + +using namespace pto; + +void example_auto() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TTRANS(dst, src, tmp); +} +``` + +### 手动(Manual) + +```cpp +#include + +using namespace pto; + +void example_manual() { + using SrcT = Tile; + using DstT = Tile; + using TmpT = Tile; + SrcT src; + DstT dst; + TmpT tmp; + TASSIGN(src, 0x1000); + TASSIGN(dst, 0x2000); + TASSIGN(tmp, 0x3000); + TTRANS(dst, src, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.ttrans %src : !pto.tile<...> -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.ttrans %src : !pto.tile<...> -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = ttrans %src : !pto.tile<...> -> !pto.tile<...> +# AS Level 2 (DPS) +pto.ttrans ins(%src : !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TTRI.md b/designs/outerCube/PTOISA/TTRI.md new file mode 100644 index 00000000..d1b01b3e --- /dev/null +++ b/designs/outerCube/PTOISA/TTRI.md @@ -0,0 +1,98 @@ +# TTRI + + +## Tile Operation Diagram + +![TTRI tile operation](../figures/isa/TTRI.svg) + +## Introduction + +Generate a (lower/upper) triangular mask tile with ones and zeros. The triangular orientation is controlled by the compile-time template parameter `isUpperOrLower` (0 = lower, 1 = upper). + +## Math Interpretation + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `d = diagonal`. + +Lower-triangular (`isUpperOrLower=0`) conceptually produces: + +$$ +\mathrm{dst}_{i,j} = \begin{cases}1 & j \le i + d \\\\ 0 & \text{otherwise}\end{cases} +$$ + +Upper-triangular (`isUpperOrLower=1`) conceptually produces: + +$$ +\mathrm{dst}_{i,j} = \begin{cases}0 & j < i + d \\\\ 1 & \text{otherwise}\end{cases} +$$ + +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TTRI(TileData &dst, int diagonal, WaitEvents &... events); +``` + +## Constraints + +- `isUpperOrLower` must be `0` (lower) or `1` (upper). +- Destination tile must be row-major on some targets (see `include/pto/npu/*/TTri.hpp`). + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.ttri %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.ttri ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### IR Level 1 (SSA) + +```text +%dst = pto.ttri %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### IR Level 2 (DPS) + +```text +pto.ttri ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## Examples + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.ttri %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.ttri %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = pto.ttri %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +# AS Level 2 (DPS) +pto.ttri ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` diff --git a/designs/outerCube/PTOISA/TTRI_zh.md b/designs/outerCube/PTOISA/TTRI_zh.md new file mode 100644 index 00000000..cf82b414 --- /dev/null +++ b/designs/outerCube/PTOISA/TTRI_zh.md @@ -0,0 +1,71 @@ +# TTRI + +## 指令示意图 + +![TTRI tile operation](../figures/isa/TTRI.svg) + +## 简介 + +生成三角(下/上)掩码 Tile。 + +## 数学语义 + +Let `R = dst.GetValidRow()` and `C = dst.GetValidCol()`. Let `d = diagonal`. + +Lower-triangular (`isUpperOrLower=0`) conceptually produces: + +$$ +\mathrm{dst}_{i,j} = \begin{cases}1 & j \le i + d \\\\ 0 & \text{otherwise}\end{cases} +$$ + +Upper-triangular (`isUpperOrLower=1`) conceptually produces: + +$$ +\mathrm{dst}_{i,j} = \begin{cases}0 & j < i + d \\\\ 1 & \text{otherwise}\end{cases} +$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS Specification](../assembly/PTO-AS.md). + +### AS Level 1 (SSA) + +```text +%dst = pto.ttri %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.ttri ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +### AS Level 1(SSA) + +```text +%dst = pto.ttri %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.ttri ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TTRI(TileData &dst, int diagonal, WaitEvents &... events); +``` + +## 约束 + +- `isUpperOrLower` must be `0` (lower) or `1` (upper). +- Destination tile must be row-major on some targets (see `include/pto/npu/*/TTri.hpp`). + +## 示例 + +See related examples in `docs/isa/` and `docs/coding/tutorials/`. diff --git a/designs/outerCube/PTOISA/TXOR.md b/designs/outerCube/PTOISA/TXOR.md new file mode 100644 index 00000000..ab22c828 --- /dev/null +++ b/designs/outerCube/PTOISA/TXOR.md @@ -0,0 +1,110 @@ +# TXOR + + +## Tile Operation Diagram + +![TXOR tile operation](../figures/isa/TXOR.svg) + +## Introduction + +Elementwise bitwise XOR of two tiles. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \oplus \mathrm{src1}_{i,j} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = txor %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.txor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.txor ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TXOR(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- The op iterates over `dst.GetValidRow()` / `dst.GetValidCol()`. +- **Implementation checks (A5)**: + - `dst`, `src0`, and `src1` element types must match. + - Supported element types are `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, and `int32_t`. + - `dst`, `src0`, and `src1` must be row-major. + - `src0.GetValidRow()/GetValidCol()` and `src1.GetValidRow()/GetValidCol()` must match `dst`. +- **Implementation checks (A2A3)**: + - `dst`, `src0`, `src1`, and `tmp` element types must match. + - Supported element types are `uint8_t`, `int8_t`, `uint16_t`, and `int16_t`. + - `dst`, `src0`, `src1`, and `tmp` must be row-major. + - `src0`, `src1`, and `tmp` valid shapes must match `dst`. + - In manual mode, `dst`, `src0`, `src1`, and `tmp` must not overlap in memory. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc0 = Tile; + using TileSrc1 = Tile; + using TileTmp = Tile; + TileDst dst; + TileSrc0 src0; + TileSrc1 src1; + TileTmp tmp; + TXOR(dst, src0, src1, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.txor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.txor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = txor %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.txor ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TXORS.md b/designs/outerCube/PTOISA/TXORS.md new file mode 100644 index 00000000..9caa91d4 --- /dev/null +++ b/designs/outerCube/PTOISA/TXORS.md @@ -0,0 +1,105 @@ +# TXORS + + +## Tile Operation Diagram + +![TXORS tile operation](../figures/isa/TXORS.svg) + +## Introduction + +Elementwise bitwise XOR of a tile and a scalar. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \oplus \mathrm{scalar} $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../assembly/PTO-AS.md). + +Synchronous form: + +```text +%dst = txors %src, %scalar : !pto.tile<...>, i32 +``` + +### AS Level 1 (SSA) + +```text +%dst = pto.txors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2 (DPS) + +```text +pto.txors ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` +## C++ Intrinsic + +Declared in `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TXORS(TileDataDst &dst, TileDataSrc &src0, typename TileDataSrc::DType scalar, TileDataTmp &tmp, WaitEvents &... events); +``` + +## Constraints + +- **Implementation checks (A2A3)**: + - Supported element types are `uint8_t`, `int8_t`, `uint16_t`, and `int16_t`. + - `dst`, `src`, and `tmp` must use the same element type. + - In manual mode, source, destination, and temporary storage must not overlap in memory. +- **Implementation checks (A5)**: + - Supported element types are `uint8_t`, `int8_t`, `uint16_t`, `int16_t`, `uint32_t`, and `int32_t`. + - `dst` and `src` element types must match. + - `src.GetValidRow()/GetValidCol()` must match `dst`. +- **Valid region**: + - The op uses `dst.GetValidRow()` / `dst.GetValidCol()` as the iteration domain. + +## Examples + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc = Tile; + using TileTmp = Tile; + TileDst dst; + TileSrc src; + TileTmp tmp; + TXORS(dst, src, 0x1u, tmp); +} +``` + +## ASM Form Examples + +### Auto Mode + +```text +# Auto mode: compiler/runtime-managed placement and scheduling. +%dst = pto.txors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### Manual Mode + +```text +# Manual mode: bind resources explicitly before issuing the instruction. +# Optional for tile operands: +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.txors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO Assembly Form + +```text +%dst = txors %src, %scalar : !pto.tile<...>, i32 +# AS Level 2 (DPS) +pto.txors ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TXORS_zh.md b/designs/outerCube/PTOISA/TXORS_zh.md new file mode 100644 index 00000000..ebc0ed69 --- /dev/null +++ b/designs/outerCube/PTOISA/TXORS_zh.md @@ -0,0 +1,105 @@ +# TXORS + +## 指令示意图 + +![TXORS tile operation](../figures/isa/TXORS.svg) + +## 简介 + +Tile 与标量的逐元素按位异或。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src}_{i,j} \oplus \mathrm{scalar} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = txors %src, %scalar : !pto.tile<...>, i32 +``` + +### AS Level 1(SSA) + +```text +%dst = pto.txors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.txors ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TXORS(TileDataDst &dst, TileDataSrc &src0, typename TileDataSrc::DType scalar, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- **实现检查 (A2A3)**: + - 支持的元素类型为 `uint8_t`、`int8_t`、`uint16_t` 和 `int16_t`。 + - `dst`、`src` 和 `tmp` 必须使用相同的元素类型。 + - 在手动模式下,源、目标和临时存储的内存区域不得重叠。 +- **实现检查 (A5)**: + - 支持的元素类型为 `uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t` 和 `int32_t`。 + - `dst` 和 `src` 的元素类型必须一致。 + - `src.GetValidRow()/GetValidCol()` 必须与 `dst` 一致。 +- **有效区域**: + - 该操作使用 `dst.GetValidRow()` / `dst.GetValidCol()` 作为迭代域。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc = Tile; + using TileTmp = Tile; + TileDst dst; + TileSrc src; + TileTmp tmp; + TXORS(dst, src, 0x1u, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.txors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.txors %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = txors %src, %scalar : !pto.tile<...>, i32 +# AS Level 2 (DPS) +pto.txors ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/TXOR_zh.md b/designs/outerCube/PTOISA/TXOR_zh.md new file mode 100644 index 00000000..ee3126fc --- /dev/null +++ b/designs/outerCube/PTOISA/TXOR_zh.md @@ -0,0 +1,110 @@ +# TXOR + +## 指令示意图 + +![TXOR tile operation](../figures/isa/TXOR.svg) + +## 简介 + +两个 Tile 的逐元素按位异或。 + +## 数学语义 + +对每个元素 `(i, j)` 在有效区域内: + +$$ \mathrm{dst}_{i,j} = \mathrm{src0}_{i,j} \oplus \mathrm{src1}_{i,j} $$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +%dst = txor %src0, %src1 : !pto.tile<...> +``` + +### AS Level 1(SSA) + +```text +%dst = pto.txor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### AS Level 2(DPS) + +```text +pto.txor ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + +## C++ 内建接口 + +声明于 `include/pto/common/pto_instr.hpp`: + +```cpp +template +PTO_INST RecordEvent TXOR(TileDataDst &dst, TileDataSrc0 &src0, TileDataSrc1 &src1, TileDataTmp &tmp, WaitEvents &... events); +``` + +## 约束 + +- 该操作在 `dst.GetValidRow()` / `dst.GetValidCol()` 上迭代。 +- **实现检查 (A5)**: + - `dst`、`src0` 和 `src1` 的元素类型必须一致。 + - 支持的元素类型为 `uint8_t`、`int8_t`、`uint16_t`、`int16_t`、`uint32_t` 和 `int32_t`。 + - `dst`、`src0` 和 `src1` 必须是行主序。 + - `src0.GetValidRow()/GetValidCol()` 和 `src1.GetValidRow()/GetValidCol()` 必须与 `dst` 一致。 +- **实现检查 (A2A3)**: + - `dst`、`src0`、`src1` 和 `tmp` 的元素类型必须一致。 + - 支持的元素类型为 `uint8_t`、`int8_t`、`uint16_t` 和 `int16_t`。 + - `dst`、`src0`、`src1` 和 `tmp` 必须是行主序。 + - `src0`、`src1` 和 `tmp` 的有效形状必须与 `dst` 一致。 + - 在手动模式下,`dst`、`src0`、`src1` 和 `tmp` 的内存区域不得重叠。 + +## 示例 + +```cpp +#include + +using namespace pto; + +void example() { + using TileDst = Tile; + using TileSrc0 = Tile; + using TileSrc1 = Tile; + using TileTmp = Tile; + TileDst dst; + TileSrc0 src0; + TileSrc1 src1; + TileTmp tmp; + TXOR(dst, src0, src1, tmp); +} +``` + +## 汇编示例(ASM) + +### 自动模式 + +```text +# 自动模式:由编译器/运行时负责资源放置与调度。 +%dst = pto.txor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### 手动模式 + +```text +# 手动模式:先显式绑定资源,再发射指令。 +# 可选(当该指令包含 tile 操作数时): +# pto.tassign %arg0, @tile(0x1000) +# pto.tassign %arg1, @tile(0x2000) +%dst = pto.txor %src0, %src1 : (!pto.tile<...>, !pto.tile<...>) -> !pto.tile<...> +``` + +### PTO 汇编形式 + +```text +%dst = txor %src0, %src1 : !pto.tile<...> +# AS Level 2 (DPS) +pto.txor ins(%src0, %src1 : !pto.tile_buf<...>, !pto.tile_buf<...>) outs(%dst : !pto.tile_buf<...>) +``` + diff --git a/designs/outerCube/PTOISA/comm/README.md b/designs/outerCube/PTOISA/comm/README.md new file mode 100644 index 00000000..01bba656 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/README.md @@ -0,0 +1,130 @@ +# PTO Communication ISA Reference + +This directory contains the per-instruction reference for the PTO Communication ISA. + +- Source of truth (C++ intrinsics): `include/pto/comm/pto_comm_inst.hpp` +- Type definitions: `include/pto/comm/comm_types.hpp` + +## Point-to-Point Communication (Synchronous) +- [**TPUT**](TPUT.md): Remote write (GM → UB → GM) +- [**TGET**](TGET.md): Remote read (GM → UB → GM) + +## Point-to-Point Communication (Asynchronous) +- [**TPUT_ASYNC**](TPUT_ASYNC.md): Asynchronous remote write (GM → DMA engine → GM) +- [**TGET_ASYNC**](TGET_ASYNC.md): Asynchronous remote read (GM → DMA engine → GM) + +## Signal-Based Synchronization +- [**TNOTIFY**](TNOTIFY.md): Send notification to remote NPU +- [**TWAIT**](TWAIT.md): Blocking wait for signal condition +- [**TTEST**](TTEST.md): Non-blocking test signal condition + +## Collective Communication + +- [**TGATHER**](TGATHER.md): Gather data from all ranks +- [**TSCATTER**](TSCATTER.md): Scatter data to all ranks +- [**TREDUCE**](TREDUCE.md): Reduce data from all ranks to local +- [**TBROADCAST**](TBROADCAST.md): Broadcast from current NPU to all ranks + +## Type Definitions + +### NotifyOp + +Operation type for `TNOTIFY`: + +| Value | Description | +|-------|-------------| +| `NotifyOp::Set` | Direct set (`signal = value`) | +| `NotifyOp::AtomicAdd` | Atomic add (`signal += value`) | + +### WaitCmp + +Comparison operators for `TWAIT` and `TTEST`: + +| Value | Description | +|-------|-------------| +| `WaitCmp::EQ` | Equal (`==`) | +| `WaitCmp::NE` | Not equal (`!=`) | +| `WaitCmp::GT` | Greater than (`>`) | +| `WaitCmp::GE` | Greater or equal (`>=`) | +| `WaitCmp::LT` | Less than (`<`) | +| `WaitCmp::LE` | Less or equal (`<=`) | + +```cpp +// Usage (unified runtime parameter style): +comm::TNOTIFY(signal, 1, comm::NotifyOp::Set); +comm::TWAIT(signal, 1, comm::WaitCmp::EQ); +comm::TTEST(signal, 1, comm::WaitCmp::GE); +``` + +### ReduceOp + +Reduction operators for `TREDUCE`: + +| Value | Description | +|-------|-------------| +| `ReduceOp::Sum` | Element-wise sum | +| `ReduceOp::Max` | Element-wise maximum | +| `ReduceOp::Min` | Element-wise minimum | + +### AtomicType + +Atomic operation type for `TPUT` (defined in `include/pto/common/constants.hpp`): + +| Value | Description | +|-------|-------------| +| `AtomicType::AtomicNone` | No atomic operation (default) | +| `AtomicType::AtomicAdd` | Atomic add operation | + +### DmaEngine + +DMA backend selection for `TPUT_ASYNC` and `TGET_ASYNC`: + +| Value | Description | +|-------|-------------| +| `DmaEngine::SDMA` | SDMA engine (supports 2D transfer) | +| `DmaEngine::URMA` | URMA engine (supports 1D transfer, todo) | + +### AsyncEvent + +Returned by `TPUT_ASYNC` / `TGET_ASYNC`. Use to synchronize completion: + +```cpp +struct AsyncEvent { + uint64_t handle; + DmaEngine engine; + + bool valid() const; // true if handle != 0 + bool Wait(const AsyncSession &session) const; // block until transfer completes + bool Test(const AsyncSession &session) const; // non-blocking completion check +}; +``` + +### AsyncSession + +Engine-agnostic session for async DMA operations. Build once, pass to all async calls: + +```cpp +comm::AsyncSession session; +comm::BuildAsyncSession(scratchTile, workspace, session); +``` + +Defined in `include/pto/comm/async/async_types.hpp`. See [TPUT_ASYNC](TPUT_ASYNC.md) for construction details and parameters. + +### ParallelGroup + +Wrapper for collective communication across multiple NPUs: + +```cpp +template +struct ParallelGroup { + // Pointer to an array of `GlobalData` objects (each wraps a GM address). + // The array itself is local metadata; the wrapped addresses may refer to local or remote GM, + // depending on the collective instruction. + GlobalData *tensors; + int nranks; // Number of ranks + int rootIdx; // Root NPU's rank index + + // Factory function (recommended): build from an existing tensor array. + static ParallelGroup Create(GlobalData *tensorArray, int size, int rank_id); +}; +``` diff --git a/designs/outerCube/PTOISA/comm/README_zh.md b/designs/outerCube/PTOISA/comm/README_zh.md new file mode 100644 index 00000000..ee826e32 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/README_zh.md @@ -0,0 +1,131 @@ +# PTO 通信 ISA 参考手册 + +本目录包含 PTO 通信 ISA 的逐指令参考文档。 + +- 权威来源(C++ 内建接口):`include/pto/comm/pto_comm_inst.hpp` +- 类型定义:`include/pto/comm/comm_types.hpp` + +## 点对点通信(同步) +- [**TPUT**](TPUT_zh.md):远程写(GM → UB → GM) +- [**TGET**](TGET_zh.md):远程读(GM → UB → GM) + +## 点对点通信(异步) +- [**TPUT_ASYNC**](TPUT_ASYNC_zh.md):异步远程写(GM → DMA 引擎 → GM) +- [**TGET_ASYNC**](TGET_ASYNC_zh.md):异步远程读(GM → DMA 引擎 → GM) + +## 基于信号的同步 +- [**TNOTIFY**](TNOTIFY_zh.md):向远端 NPU 发送通知 +- [**TWAIT**](TWAIT_zh.md):阻塞等待信号条件满足 +- [**TTEST**](TTEST_zh.md):非阻塞检测信号条件 + +## 集合通信 + +- [**TGATHER**](TGATHER_zh.md):从所有 rank 收集数据 +- [**TSCATTER**](TSCATTER_zh.md):向所有 rank 分发数据 +- [**TREDUCE**](TREDUCE_zh.md):从所有 rank 归约数据到本地 +- [**TBROADCAST**](TBROADCAST_zh.md):从当前 NPU 广播数据到所有 rank + +## 类型定义 + +### NotifyOp + +`TNOTIFY` 的操作类型: + +| 值 | 说明 | +|-------|-------------| +| `NotifyOp::Set` | 直接赋值(`signal = value`)| +| `NotifyOp::AtomicAdd` | 原子加(`signal += value`)| + +### WaitCmp + +`TWAIT` 和 `TTEST` 的比较运算符: + +| 值 | 说明 | +|-------|-------------| +| `WaitCmp::EQ` | 等于(`==`)| +| `WaitCmp::NE` | 不等于(`!=`)| +| `WaitCmp::GT` | 大于(`>`)| +| `WaitCmp::GE` | 大于等于(`>=`)| +| `WaitCmp::LT` | 小于(`<`)| +| `WaitCmp::LE` | 小于等于(`<=`)| + +```cpp +// 用法示例(统一运行时参数风格): +comm::TNOTIFY(signal, 1, comm::NotifyOp::Set); +comm::TWAIT(signal, 1, comm::WaitCmp::EQ); +comm::TTEST(signal, 1, comm::WaitCmp::GE); +``` + +### ReduceOp + +`TREDUCE` 的归约运算符: + +| 值 | 说明 | +|-------|-------------| +| `ReduceOp::Sum` | 逐元素求和 | +| `ReduceOp::Max` | 逐元素取最大值 | +| `ReduceOp::Min` | 逐元素取最小值 | + +### AtomicType + +`TPUT` 的原子操作类型(定义于 `include/pto/common/constants.hpp`): + +| 值 | 说明 | +|-------|-------------| +| `AtomicType::AtomicNone` | 无原子操作(默认)| +| `AtomicType::AtomicAdd` | 原子加操作 | + +### DmaEngine + +`TPUT_ASYNC` 和 `TGET_ASYNC` 的 DMA 后端选择: + +| 值 | 说明 | +|-------|-------------| +| `DmaEngine::SDMA` | SDMA 引擎(支持二维传输)| +| `DmaEngine::URMA` | URMA 引擎(支持一维传输,待实现)| + +### AsyncEvent + +由 `TPUT_ASYNC` / `TGET_ASYNC` 返回,用于同步传输完成状态: + +```cpp +struct AsyncEvent { + uint64_t handle; + DmaEngine engine; + + bool valid() const; // handle != 0 时返回 true + bool Wait(const AsyncSession &session) const; // 阻塞直到传输完成 + bool Test(const AsyncSession &session) const; // 非阻塞完成检测 +}; +``` + +### AsyncSession + +用于异步 DMA 操作的引擎无关会话对象,构建一次后传递给所有异步调用: + +```cpp +comm::AsyncSession session; +comm::BuildAsyncSession(scratchTile, workspace, session); +``` + +定义于 `include/pto/comm/async/async_types.hpp`。构建参数详见 [TPUT_ASYNC](TPUT_ASYNC_zh.md)。 + +### ParallelGroup + +用于多 NPU 集合通信的包装器: + +```cpp +template +struct ParallelGroup { + // 指向 `GlobalData` 对象数组的指针(每个对象封装一个 GM 地址)。 + // 数组本身是本地元数据;封装的地址可以指向本地或远端 GM, + // 具体取决于集合通信指令的语义。 + GlobalData *tensors; + int nranks; // rank 总数 + int rootIdx; // 根 NPU 的 rank 索引 + + // 工厂函数(推荐):从已有 tensor 数组构建。 + static ParallelGroup Create(GlobalData *tensorArray, int size, int rank_id); +}; +``` + diff --git a/designs/outerCube/PTOISA/comm/TBROADCAST.md b/designs/outerCube/PTOISA/comm/TBROADCAST.md new file mode 100644 index 00000000..1f0f613d --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TBROADCAST.md @@ -0,0 +1,122 @@ +# TBROADCAST + +## Introduction + +Broadcast data from current NPU to all ranks in the parallel group. The calling NPU is the root and its data is copied to all other NPUs. + +Only the root needs to execute `TBROADCAST`. Non-root ranks only need to ensure their destination buffers are allocated and writable for the duration of the operation. Calling `TBROADCAST` on non-root ranks is undefined behavior. + +**Large Tile Support**: When the GlobalTensor exceeds the UB (Unified Buffer) tile capacity in rows and/or columns, the transfer is automatically chunked via 2D sliding. + +## Math Interpretation + +After the operation: + +$$ \mathrm{dst}^{(k)}_{i,j} = \mathrm{src}^{(\text{root})}_{i,j} \quad \forall k \in [0, N) $$ + +where $N$ is the number of ranks and `root` is the calling NPU. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../../assembly/PTO-AS.md). + +Synchronous form: + +```text +tbroadcast %group, %src : (!pto.group<...>, !pto.memref<...>) +``` +Lowering introduces UB staging tile(s) for the GM→UB→GM data path; the C++ intrinsic requires explicit `stagingTileData` (or `pingTile` / `pongTile`) operand(s). + +## C++ Intrinsic + +Declared in `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +// Basic broadcast (single staging tile) +template +PTO_INST RecordEvent TBROADCAST(ParallelGroupType ¶llelGroup, GlobalSrcData &srcGlobalData, + TileData &stagingTileData, WaitEvents&... events); + +// Ping-pong broadcast (double buffering with two staging tiles) +template +PTO_INST RecordEvent TBROADCAST(ParallelGroupType ¶llelGroup, GlobalSrcData &srcGlobalData, + TileData &pingTile, TileData &pongTile, WaitEvents&... events); +``` + +## Constraints + +- **Type constraints**: + - `ParallelGroup::value_type::RawDType` must equal `GlobalSrcData::RawDType`. + - `TileData::DType` must equal `GlobalSrcData::RawDType`. +- **Memory constraints**: + - `srcGlobalData` must point to local memory (current NPU). + - `stagingTileData` (or `pingTile` / `pongTile`) must be pre-allocated in UB. +- **ParallelGroup constraints**: + - `parallelGroup.tensors[k]` must refer to rank `k`'s destination buffer (remote GM as seen by the root). + - `parallelGroup.GetRootIdx()` identifies the calling NPU as the broadcast root. + - All destination tensors are assumed to have the same shape and strides. +- **Chunked mode constraints** (when data exceeds a single UB tile): + - If `TileData` has static `ValidRow`, `GetShape(DIM_3)` must be divisible by `ValidRow`. Use a Tile with `DYNAMIC` ValidRow for partial row support. + - If `TileData` has static `ValidCol`, `GetShape(DIM_4)` must be divisible by `ValidCol`. Use a Tile with `DYNAMIC` ValidCol for partial column support. + +## Examples + +### Basic Broadcast + +```cpp +#include + +using namespace pto; + +template +void broadcast(__gm__ T* group_addrs[NRANKS], __gm__ T* my_data, int my_rank) { + // Tile dimensions can differ from tensor dimensions. + // The 2D sliding chunked path automatically tiles both row and column. + using TileT = Tile; + using GTensor = GlobalTensor, + BaseShape2D, Layout::ND>; + + GTensor tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) { + tensors[i] = GTensor(group_addrs[i]); + } + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GTensor srcG(my_data); + TileT stagingTile(TILE_ROWS, TILE_COLS); + + // Current NPU broadcasts its data to all others + comm::TBROADCAST(group, srcG, stagingTile); +} +``` + +### Ping-Pong Broadcast (Double Buffering) + +Uses two UB tiles to overlap TLOAD of the next chunk with TSTORE of the current chunk. + +```cpp +#include + +using namespace pto; + +template +void broadcast_pingpong(__gm__ T* group_addrs[NRANKS], __gm__ T* my_data, int my_rank) { + + using TileT = Tile; + using GPerRank = GlobalTensor, + BaseShape2D, Layout::ND>; + + GPerRank tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) { + tensors[i] = GPerRank(group_addrs[i]); + } + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GPerRank srcG(my_data); + TileT pingTile(TILE_ROWS, TILE_COLS); + TileT pongTile(TILE_ROWS, TILE_COLS); + + // Ping-pong: overlaps TLOAD and TSTORE for better throughput + comm::TBROADCAST(group, srcG, pingTile, pongTile); +} +``` diff --git a/designs/outerCube/PTOISA/comm/TBROADCAST_zh.md b/designs/outerCube/PTOISA/comm/TBROADCAST_zh.md new file mode 100644 index 00000000..a82808a5 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TBROADCAST_zh.md @@ -0,0 +1,123 @@ +# TBROADCAST + +## 简介 + +将当前 NPU 的数据广播到并行组中所有 rank。调用方 NPU 为根节点,其数据将被复制到所有其他 NPU。 + +只有根节点需要执行 `TBROADCAST`。非根节点只需确保在操作期间其目标缓冲区已分配且可写。在非根节点上调用 `TBROADCAST` 属于未定义行为。 + +**大 Tile 支持**:当 GlobalTensor 在行和/或列方向超出 UB(统一缓冲区)Tile 容量时,传输将通过二维滑动自动分块。 + +## 数学语义 + +操作完成后: + +$$ \mathrm{dst}^{(k)}_{i,j} = \mathrm{src}^{(\text{root})}_{i,j} \quad \forall k \in [0, N) $$ + +其中 $N$ 为 rank 总数,`root` 为调用方 NPU。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +tbroadcast %group, %src : (!pto.group<...>, !pto.memref<...>) +``` + +降级时会为 GM→UB→GM 数据路径引入 UB 暂存 Tile;C++ 内建接口需要显式传入 `stagingTileData`(或 `pingTile` / `pongTile`)操作数。 + +## C++ 内建接口 + +声明于 `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +// 基础广播(单暂存 Tile) +template +PTO_INST RecordEvent TBROADCAST(ParallelGroupType ¶llelGroup, GlobalSrcData &srcGlobalData, + TileData &stagingTileData, WaitEvents&... events); + +// 乒乓广播(使用两个暂存 Tile 实现双缓冲) +template +PTO_INST RecordEvent TBROADCAST(ParallelGroupType ¶llelGroup, GlobalSrcData &srcGlobalData, + TileData &pingTile, TileData &pongTile, WaitEvents&... events); +``` + +## 约束 + +- **类型约束**: + - `ParallelGroup::value_type::RawDType` 必须等于 `GlobalSrcData::RawDType`。 + - `TileData::DType` 必须等于 `GlobalSrcData::RawDType`。 +- **内存约束**: + - `srcGlobalData` 必须指向本地内存(当前 NPU)。 + - `stagingTileData`(或 `pingTile` / `pongTile`)必须预先在 UB 中分配。 +- **ParallelGroup 约束**: + - `parallelGroup.tensors[k]` 必须指向 rank `k` 的目标缓冲区(从根节点视角看到的远端 GM)。 + - `parallelGroup.GetRootIdx()` 标识调用方 NPU 为广播根节点。 + - 所有目标 tensor 假定具有相同的形状和步幅。 +- **分块模式约束**(数据超出单个 UB Tile 时): + - 若 `TileData` 具有静态 `ValidRow`,则 `GetShape(DIM_3)` 必须能被 `ValidRow` 整除。如需支持不足一行的情况,请使用 `DYNAMIC` ValidRow 的 Tile。 + - 若 `TileData` 具有静态 `ValidCol`,则 `GetShape(DIM_4)` 必须能被 `ValidCol` 整除。如需支持不足一列的情况,请使用 `DYNAMIC` ValidCol 的 Tile。 + +## 示例 + +### 基础广播 + +```cpp +#include + +using namespace pto; + +template +void broadcast(__gm__ T* group_addrs[NRANKS], __gm__ T* my_data, int my_rank) { + // Tile 维度可以与 tensor 维度不同。 + // 二维滑动分块路径会自动在行和列两个方向进行分块。 + using TileT = Tile; + using GTensor = GlobalTensor, + BaseShape2D, Layout::ND>; + + GTensor tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) { + tensors[i] = GTensor(group_addrs[i]); + } + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GTensor srcG(my_data); + TileT stagingTile(TILE_ROWS, TILE_COLS); + + // 当前 NPU 将自身数据广播到所有其他 NPU + comm::TBROADCAST(group, srcG, stagingTile); +} +``` + +### 乒乓广播(双缓冲) + +使用两个 UB Tile,将下一块的 TLOAD 与当前块的 TSTORE 重叠执行。 + +```cpp +#include + +using namespace pto; + +template +void broadcast_pingpong(__gm__ T* group_addrs[NRANKS], __gm__ T* my_data, int my_rank) { + + using TileT = Tile; + using GPerRank = GlobalTensor, + BaseShape2D, Layout::ND>; + + GPerRank tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) { + tensors[i] = GPerRank(group_addrs[i]); + } + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GPerRank srcG(my_data); + TileT pingTile(TILE_ROWS, TILE_COLS); + TileT pongTile(TILE_ROWS, TILE_COLS); + + // 乒乓模式:将 TLOAD 与 TSTORE 重叠执行以提升吞吐量 + comm::TBROADCAST(group, srcG, pingTile, pongTile); +} +``` diff --git a/designs/outerCube/PTOISA/comm/TGATHER.md b/designs/outerCube/PTOISA/comm/TGATHER.md new file mode 100644 index 00000000..a5d3b35e --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TGATHER.md @@ -0,0 +1,128 @@ +# TGATHER + +## Introduction + +Gather operation: the calling NPU (root) collects data from all ranks in the parallel group and concatenates the results along **DIM_3** (row dimension) into a local output buffer. + + +Only the root needs to execute `TGATHER`. Non-root ranks only need to ensure their source buffers are ready and remain valid for the duration of the operation. Calling `TGATHER` on non-root ranks is undefined behavior. + +**Large Tile Support**: When the GlobalTensor exceeds the UB tile capacity in rows and/or columns, the transfer is automatically chunked via 2D sliding — the same mechanism used by other PTO-COMM instructions. + +## Math Interpretation + +Each rank $r$ has source data of shape $(D_0, D_1, D_2, H, W)$. The gather concatenates all $N$ ranks along DIM_3: + +$$\mathrm{dst}_{d_0, d_1, d_2,\; r \cdot H + i,\; j} = \mathrm{src}^{(r)}_{d_0, d_1, d_2,\; i,\; j} \quad \forall\, r \in [0, N),\; i \in [0, H),\; j \in [0, W)$$ + +The destination tensor has shape $(D_0, D_1, D_2, N \times H, W)$. + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../../assembly/PTO-AS.md). + +Synchronous form: + +```text +tgather %group, %dst : (!pto.group<...>, !pto.memref<...>) +``` +Lowering introduces UB staging tile(s) for the GM→UB→GM data path; the C++ intrinsic requires explicit `stagingTileData` (or `pingTile` / `pongTile`) operand(s). + +## C++ Intrinsic + +Declared in `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +// Basic gather (single staging tile) +template +PTO_INST RecordEvent TGATHER(ParallelGroupType ¶llelGroup, GlobalDstData &dstGlobalData, + TileData &stagingTileData, WaitEvents&... events); + +// Ping-pong gather (double buffering with two staging tiles) +template +PTO_INST RecordEvent TGATHER(ParallelGroupType ¶llelGroup, GlobalDstData &dstGlobalData, + TileData &pingTile, TileData &pongTile, WaitEvents&... events); +``` + +## Constraints + +- **Type constraints**: + - `ParallelGroup::value_type::RawDType` must equal `GlobalDstData::RawDType`. + - `TileData::DType` must equal `GlobalDstData::RawDType`. +- **Memory constraints**: + - `dstGlobalData` must point to local memory (current NPU) and be large enough to hold the concatenated result from all ranks. Specifically, `dstGlobalData.GetShape(DIM_3)` must be $\geq N \times H$ where $H$ is each rank's `GetShape(DIM_3)`. + - If `dstGlobalData.GetShape(DIM_3) > N × H`, only the first `N × H` rows are written; remaining rows are left unchanged. + - `stagingTileData` (or `pingTile` / `pongTile`) must be pre-allocated in UB. +- **ParallelGroup constraints**: + - `parallelGroup.tensors[r]` must refer to rank `r`'s source buffer (remote GM as seen by the root). + - `parallelGroup.GetRootIdx()` identifies the calling NPU as the gather root. + - All source tensors are assumed to have the same shape and strides; behavior is undefined if they differ. +- **Chunked mode constraints** (when source data exceeds a single UB tile): + - If `TileData` has static `ValidRow`, `GetShape(DIM_3)` of each rank's source must be divisible by `ValidRow`. Use a Tile with `DYNAMIC` ValidRow for partial row support. + - If `TileData` has static `ValidCol`, `GetShape(DIM_4)` must be divisible by `ValidCol`. Use a Tile with `DYNAMIC` ValidCol for partial column support. + +## Examples + +### Basic Gather (Single Staging Tile) + +Each rank contributes `ROWS × COLS` data. The root collects them into `NRANKS * ROWS` rows. +The tile size (`TILE_ROWS × TILE_COLS`) can be smaller than the per-rank data — when it is, the implementation automatically chunks the transfer along both DIM_3 and DIM_4 via 2D sliding. + +```cpp +#include + +using namespace pto; + +template +void gather(__gm__ T* group_addrs[NRANKS], __gm__ T* result, int my_rank) { + using TileT = Tile; + using GPerRank = GlobalTensor, + BaseShape2D, Layout::ND>; + using GResult = GlobalTensor, + BaseShape2D, Layout::ND>; + + GPerRank tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) { + tensors[i] = GPerRank(group_addrs[i]); + } + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GResult dstG(result); + TileT stagingTile(TILE_ROWS, TILE_COLS); + + comm::TGATHER(group, dstG, stagingTile); +} +``` + +### Ping-Pong Gather (Double Buffering) + +Uses two UB tiles to overlap TLOAD of the next chunk (MTE2) with TSTORE of the current chunk (MTE3). + +```cpp +#include + +using namespace pto; + +template +void gather_pingpong(__gm__ T* group_addrs[NRANKS], __gm__ T* result, int my_rank) { + // Tile can be smaller than the data in both dimensions + using TileT = Tile; + using GPerRank = GlobalTensor, + BaseShape2D, Layout::ND>; + using GResult = GlobalTensor, + BaseShape2D, Layout::ND>; + + GPerRank tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) { + tensors[i] = GPerRank(group_addrs[i]); + } + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GResult dstG(result); + TileT pingTile(TILE_ROWS, TILE_COLS); + TileT pongTile(TILE_ROWS, TILE_COLS); + + // Ping-pong: overlaps TLOAD and TSTORE for better throughput + comm::TGATHER(group, dstG, pingTile, pongTile); +} +``` diff --git a/designs/outerCube/PTOISA/comm/TGATHER_zh.md b/designs/outerCube/PTOISA/comm/TGATHER_zh.md new file mode 100644 index 00000000..699e50b1 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TGATHER_zh.md @@ -0,0 +1,121 @@ +# TGATHER + +## 简介 + +Gather 操作:调用方 NPU(根节点)从并行组中所有 rank 收集数据,并沿 **DIM_3**(行维度)拼接到本地输出缓冲区。 + +只有根节点需要执行 `TGATHER`。非根节点只需确保在操作期间其源缓冲区已就绪且保持有效。在非根节点上调用 `TGATHER` 属于未定义行为。 + +**大 Tile 支持**:当 GlobalTensor 在行和/或列方向超出 UB Tile 容量时,传输将通过二维滑动自动分块——与其他 PTO-COMM 指令采用相同机制。 + +## 数学语义 + +每个 rank $r$ 的源数据形状为 $(D_0, D_1, D_2, H, W)$。gather 沿 DIM_3 拼接所有 $N$ 个 rank 的数据: + +$$\mathrm{dst}_{d_0, d_1, d_2,\; r \cdot H + i,\; j} = \mathrm{src}^{(r)}_{d_0, d_1, d_2,\; i,\; j} \quad \forall\, r \in [0, N),\; i \in [0, H),\; j \in [0, W)$$ + +目标 tensor 的形状为 $(D_0, D_1, D_2, N \times H, W)$。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +tgather %group, %dst : (!pto.group<...>, !pto.memref<...>) +``` + +降级时会为 GM→UB→GM 数据路径引入 UB 暂存 Tile;C++ 内建接口需要显式传入 `stagingTileData`(或 `pingTile` / `pongTile`)操作数。 + +## C++ 内建接口 + +声明于 `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +// 基础 gather(单暂存 Tile) +template +PTO_INST RecordEvent TGATHER(ParallelGroupType ¶llelGroup, GlobalDstData &dstGlobalData, + TileData &stagingTileData, WaitEvents&... events); + +// 乒乓 gather(使用两个暂存 Tile 实现双缓冲) +template +PTO_INST RecordEvent TGATHER(ParallelGroupType ¶llelGroup, GlobalDstData &dstGlobalData, + TileData &pingTile, TileData &pongTile, WaitEvents&... events); +``` + +## 约束 + +- **类型约束**: + - `ParallelGroup::value_type::RawDType` 必须等于 `GlobalDstData::RawDType`。 + - `TileData::DType` 必须等于 `GlobalDstData::RawDType`。 +- **内存约束**: + - `dstGlobalData` 必须指向本地内存(当前 NPU),且足够容纳所有 rank 拼接后的结果。具体要求:`dstGlobalData.GetShape(DIM_3)` 必须 $\geq N \times H$,其中 $H$ 为每个 rank 的 `GetShape(DIM_3)`。 + - 若 `dstGlobalData.GetShape(DIM_3) > N × H`,则只写入前 `N × H` 行,其余行保持不变。 + - `stagingTileData`(或 `pingTile` / `pongTile`)必须预先在 UB 中分配。 +- **ParallelGroup 约束**: + - `parallelGroup.tensors[r]` 必须指向 rank `r` 的源缓冲区(从根节点视角看到的远端 GM)。 + - `parallelGroup.GetRootIdx()` 标识调用方 NPU 为 gather 根节点。 + - 所有源 tensor 假定具有相同的形状和步幅;否则行为未定义。 +- **分块模式约束**(源数据超出单个 UB Tile 时): + - 若 `TileData` 具有静态 `ValidRow`,则每个 rank 源数据的 `GetShape(DIM_3)` 必须能被 `ValidRow` 整除。如需支持不足一行的情况,请使用 `DYNAMIC` ValidRow 的 Tile。 + - 若 `TileData` 具有静态 `ValidCol`,则 `GetShape(DIM_4)` 必须能被 `ValidCol` 整除。如需支持不足一列的情况,请使用 `DYNAMIC` ValidCol 的 Tile。 + +## 示例 + +### 基础 Gather(单暂存 Tile) + +每个 rank 提供 `ROWS × COLS` 的数据,根节点将其收集到 `NRANKS * ROWS` 行中。 +Tile 大小(`TILE_ROWS × TILE_COLS`)可小于每 rank 的数据——此时实现会自动沿 DIM_3 和 DIM_4 通过二维滑动进行分块传输。 + +```cpp +#include + +using namespace pto; + +template +void gather(__gm__ T* group_addrs[NRANKS], __gm__ T* result, int my_rank) { + using TileT = Tile; + using GPerRank = GlobalTensor, + BaseShape2D, Layout::ND>; + using GResult = GlobalTensor, + BaseShape2D, Layout::ND>; + + GPerRank tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) tensors[i] = GPerRank(group_addrs[i]); + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GResult dstG(result); + TileT stagingTile(TILE_ROWS, TILE_COLS); + comm::TGATHER(group, dstG, stagingTile); +} +``` + +### 乒乓 Gather(双缓冲) + +使用两个 UB Tile,将下一块的 TLOAD(MTE2)与当前块的 TSTORE(MTE3)重叠执行。 + +```cpp +#include + +using namespace pto; + +template +void gather_pingpong(__gm__ T* group_addrs[NRANKS], __gm__ T* result, int my_rank) { + using TileT = Tile; + using GPerRank = GlobalTensor, + BaseShape2D, Layout::ND>; + using GResult = GlobalTensor, + BaseShape2D, Layout::ND>; + + GPerRank tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) tensors[i] = GPerRank(group_addrs[i]); + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GResult dstG(result); + TileT pingTile(TILE_ROWS, TILE_COLS); + TileT pongTile(TILE_ROWS, TILE_COLS); + // 乒乓模式:将 TLOAD 与 TSTORE 重叠执行以提升吞吐量 + comm::TGATHER(group, dstG, pingTile, pongTile); +} +``` diff --git a/designs/outerCube/PTOISA/comm/TGET.md b/designs/outerCube/PTOISA/comm/TGET.md new file mode 100644 index 00000000..0e000561 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TGET.md @@ -0,0 +1,109 @@ +# TGET + +## Introduction + +Remote read operation: read remote NPU's data to local memory. Data is transferred via a UB tile as intermediate staging buffer. + +When the GlobalTensor exceeds the UB tile capacity, TGET automatically performs **2D sliding** �?chunking rows (DIM_3) and columns (DIM_4) to fit each chunk into the tile, iterating over all outer dimensions (DIM_0, DIM_1, DIM_2). + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}^{\mathrm{local}}_{i,j} = \mathrm{src}^{\mathrm{remote}}_{i,j} $$ + +Data flow: `srcGlobalData (remote GM)` �?`stagingTileData (UB)` �?`dstGlobalData (local GM)` + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../../assembly/PTO-AS.md). + +Synchronous form: + +```text +tget %dst_local, %src_remote : (!pto.memref<...>, !pto.memref<...>) +``` +Lowering introduces UB staging tile(s) for the GM→UB→GM data path; the C++ intrinsic requires explicit `stagingTileData` (or `pingTile` / `pongTile`) operand(s). + +## C++ Intrinsic + +Declared in `include/pto/comm/pto_comm_inst.hpp` + +### Single-tile (auto-chunking) + +```cpp +template +PTO_INST RecordEvent TGET(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + TileData &stagingTileData, WaitEvents&... events); +``` + +### Ping-pong double buffering + +Uses two staging tiles to overlap TLOAD and TSTORE for adjacent chunks, hiding one DMA transfer behind the other. + +```cpp +template +PTO_INST RecordEvent TGET(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + TileData &pingTile, TileData &pongTile, WaitEvents&... events); +``` + +## Constraints + +- **Type constraints**: + - `GlobalSrcData::RawDType` must equal `GlobalDstData::RawDType`. + - `TileData::DType` must equal `GlobalSrcData::RawDType`. + - `GlobalSrcData::layout` must equal `GlobalDstData::layout`. +- **Memory constraints**: + - `srcGlobalData` must point to remote address (on source NPU). + - `dstGlobalData` must point to local address (on current NPU). + - `stagingTileData` / `pingTile` / `pongTile` must be pre-allocated in Unified Buffer. +- **Valid region**: + - Transfer size is determined by `GlobalTensor` shape (auto-chunked to fit tile). +- **Ping-pong**: + - `pingTile` and `pongTile` must have the same type and dimensions. + - Must reside at non-overlapping UB offsets. + +## Examples + +### Basic Usage + +```cpp +#include +#include + +using namespace pto; + +template +void example_tget(__gm__ T* local_data, __gm__ T* remote_addr) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + /* + If the globalTensor is larger than UB Tile, TGET will perform 2D sliding automatically. + using GShape = Shape<1, 1, 1, 4096, 4096>; + using GStride = BaseShape2D; + */ + using GTensor = GlobalTensor; + + GTensor srcG(remote_addr); + GTensor dstG(local_data); + TileT stagingTile; + TASSIGN(stagingTile, 0); + + // Basic remote read + comm::TGET(dstG, srcG, stagingTile); +} +``` + +### Ping-pong Double Buffering + +```cpp +constexpr size_t tileUBBytes = ((64 * 64 * sizeof(float) + 1023) / 1024) * 1024; +TileT pingTile(64, 64); +TileT pongTile(64, 64); +TASSIGN(pingTile, 0); +TASSIGN(pongTile, tileUBBytes); // Non-overlapping UB region + +// Overlaps TLOAD[i+1] with TSTORE[i] for better pipeline utilization +comm::TGET(dstG, srcG, pingTile, pongTile); +``` diff --git a/designs/outerCube/PTOISA/comm/TGET_ASYNC.md b/designs/outerCube/PTOISA/comm/TGET_ASYNC.md new file mode 100644 index 00000000..9a576051 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TGET_ASYNC.md @@ -0,0 +1,130 @@ +# TGET_ASYNC + +## Introduction + +`TGET_ASYNC` is an asynchronous remote read primitive. It starts a transfer from remote GM to local GM and returns an `AsyncEvent` immediately. + +Data flow: + +`srcGlobalData (remote GM) -> DMA engine -> dstGlobalData (local GM)` + +## Template Parameter + +- `engine`: + - `DmaEngine::SDMA` (default) + - `DmaEngine::URMA` (todo) + +> **Important (SDMA path)** +> `TGET_ASYNC` with `DmaEngine::SDMA` currently supports **only flat contiguous logical 1D tensors**. +> Non-1D or non-contiguous layouts are not supported by the current SDMA async implementation. + +## C++ Intrinsic + +Declared in `include/pto/comm/pto_comm_inst.hpp`. + +```cpp +template +PTO_INST AsyncEvent TGET_ASYNC(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + const AsyncSession &session, WaitEvents &... events); +``` + +`AsyncSession` is an engine-agnostic session object. Build once with +`BuildAsyncSession()`, then pass to all async calls and event waits. +The template `engine` parameter selects the DMA backend at compile time, making the +code forward-compatible with future engines (URMA, CCU, etc.). + +## AsyncSession Construction + +Use `BuildAsyncSession` from `include/pto/comm/async/async_event_impl.hpp`: + +```cpp +template +PTO_INTERNAL bool BuildAsyncSession(ScratchTile &scratchTile, + __gm__ uint8_t *workspace, + AsyncSession &session, + uint32_t syncId = 0, + const sdma::SdmaBaseConfig &baseConfig = {32 * 1024, 0, 1}, + uint32_t channelGroupIdx = sdma::kAutoChannelGroupIdx); +``` + +The engine template parameter selects the backend (currently only SDMA). + +Parameters with defaults: + +| Parameter | Default | Description | +|---|---|---| +| `syncId` | `0` | MTE3/MTE2 pipe sync event id (0-7). Override if kernel uses other pipe barriers on the same id. | +| `baseConfig` | `{32*1024, 0, 1}` | `{block_bytes, comm_block_offset, queue_num}`. Suitable for most single-queue transfers. | +| `channelGroupIdx` | `kAutoChannelGroupIdx` | SDMA channel group index. Default uses `get_block_idx()` internally, mapping to current AI core. Override for multi-block or custom channel mapping scenarios. | + +## Constraints + +- `GlobalSrcData::RawDType == GlobalDstData::RawDType` +- `GlobalSrcData::layout == GlobalDstData::layout` +- SDMA path requires source tensor to be **flat contiguous logical 1D only** +- workspace must be a valid GM pointer allocated by host-side `SdmaWorkspaceManager` + +If the 1D contiguous requirement is not met, current implementation returns an invalid async event (`handle == 0`). + +## scratchTile Role + +`scratchTile` is **not** used to hold transferred payload data. +It is converted to `TmpBuffer` and used as temporary UB workspace for: + +- writing/reading SDMA control words (flag, sq_tail, channel_info) +- polling event completion flags +- committing queue tail during completion + +The real payload path remains remote GM -> DMA engine -> local GM; `scratchTile` is only for control/synchronization metadata. + +## scratchTile Type and Size Constraints + +- must be a `pto::Tile` type +- must be UB/Vec tile (`ScratchTile::Loc == TileType::Vec`) +- available bytes must be at least `sizeof(uint64_t)` (8 bytes) + +Recommended: `Tile` (256B). + +## Completion Semantics + +Use `AsyncEvent` to synchronize: + +- `event.Wait(session)` — blocks until the transfer is complete + +After wait succeeds, reads into `dstGlobalData` are complete. + +## Example + +```cpp +#include +#include + +using namespace pto; + +template +__global__ AICORE void SimpleGet(__gm__ T *localDst, __gm__ T *remoteSrc, + __gm__ uint8_t *sdmaWorkspace) +{ + using ShapeDyn = Shape; + using StrideDyn = Stride; + using GT = GlobalTensor; + using ScratchTile = Tile; + + ShapeDyn shape(1, 1, 1, 1, 1024); + StrideDyn stride(1024, 1024, 1024, 1024, 1); + GT dstG(localDst, shape, stride); + GT srcG(remoteSrc, shape, stride); + + ScratchTile scratchTile; + TASSIGN(scratchTile, 0x0); + + comm::AsyncSession session; + if (!comm::BuildAsyncSession(scratchTile, sdmaWorkspace, session)) { + return; + } + + auto event = comm::TGET_ASYNC(dstG, srcG, session); + (void)event.Wait(session); +} +``` diff --git a/designs/outerCube/PTOISA/comm/TGET_ASYNC_zh.md b/designs/outerCube/PTOISA/comm/TGET_ASYNC_zh.md new file mode 100644 index 00000000..8f9a6383 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TGET_ASYNC_zh.md @@ -0,0 +1,126 @@ +# TGET_ASYNC + +## 简介 + +`TGET_ASYNC` 是异步远程读原语。它启动一次从远端 GM 到本地 GM 的传输,并立即返回 `AsyncEvent`。 + +数据流: + +`srcGlobalData(远端 GM)` → DMA 引擎 → `dstGlobalData(本地 GM)` + +## 模板参数 + +- `engine`: + - `DmaEngine::SDMA`(默认) + - `DmaEngine::URMA`(待实现) + +> **注意(SDMA 路径)** +> `TGET_ASYNC` 配合 `DmaEngine::SDMA` 目前**仅支持扁平连续的逻辑一维 tensor**。 +> 当前 SDMA 异步实现不支持非一维或非连续布局。 + +## C++ 内建接口 + +声明于 `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +template +PTO_INST AsyncEvent TGET_ASYNC(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + const AsyncSession &session, WaitEvents &... events); +``` + +`AsyncSession` 是引擎无关的会话对象。使用 `BuildAsyncSession()` 构建一次后,传递给所有异步调用和事件等待。模板参数 `engine` 在编译期选择 DMA 后端,使代码对未来引擎(URMA、CCU 等)保持前向兼容。 + +## AsyncSession 构建 + +使用 `include/pto/comm/async/async_event_impl.hpp` 中的 `BuildAsyncSession`: + +```cpp +template +PTO_INTERNAL bool BuildAsyncSession(ScratchTile &scratchTile, + __gm__ uint8_t *workspace, + AsyncSession &session, + uint32_t syncId = 0, + const sdma::SdmaBaseConfig &baseConfig = {32 * 1024, 0, 1}, + uint32_t channelGroupIdx = sdma::kAutoChannelGroupIdx); +``` + +带默认值的参数说明: + +| 参数 | 默认值 | 说明 | +|---|---|---| +| `syncId` | `0` | MTE3/MTE2 管道同步事件 ID(0-7)。若 kernel 在相同 ID 上使用了其他管道屏障,则需覆盖此值。| +| `baseConfig` | `{32*1024, 0, 1}` | `{block_bytes, comm_block_offset, queue_num}`。适用于大多数单队列传输场景。| +| `channelGroupIdx` | `kAutoChannelGroupIdx` | SDMA 通道组索引。默认内部使用 `get_block_idx()` 映射到当前 AI Core。多 block 或自定义通道映射场景下需覆盖此值。| + +## 约束 + +- `GlobalSrcData::RawDType == GlobalDstData::RawDType` +- `GlobalSrcData::layout == GlobalDstData::layout` +- SDMA 路径要求源 tensor 为**扁平连续的逻辑一维** +- workspace 必须是由主机侧 `SdmaWorkspaceManager` 分配的有效 GM 指针 + +若不满足一维连续要求,当前实现返回无效 async event(`handle == 0`)。 + +## scratchTile 的作用 + +`scratchTile` **不是**用于传输数据负载的暂存缓冲区。 +它被转换为 `TmpBuffer`,用作临时 UB 工作区,用于: + +- 写入/读取 SDMA 控制字(flag、sq_tail、channel_info) +- 轮询事件完成标志 +- 完成时提交队列尾部 + +实际数据路径为远端 GM → DMA 引擎 → 本地 GM;`scratchTile` 仅用于控制和同步元数据。 + +## scratchTile 类型与大小约束 + +- 必须是 `pto::Tile` 类型 +- 必须是 UB/Vec tile(`ScratchTile::Loc == TileType::Vec`) +- 可用字节数至少为 `sizeof(uint64_t)`(8 字节) + +推荐使用:`Tile`(256B)。 + +## 完成语义 + +使用 `AsyncEvent` 同步: + +- `event.Wait(session)` — 阻塞直到传输完成 + +wait 成功后,读入 `dstGlobalData` 的数据已全部就绪。 + +## 示例 + +```cpp +#include +#include + +using namespace pto; + +template +__global__ AICORE void SimpleGet(__gm__ T *localDst, __gm__ T *remoteSrc, + __gm__ uint8_t *sdmaWorkspace) +{ + using ShapeDyn = Shape; + using StrideDyn = Stride; + using GT = GlobalTensor; + using ScratchTile = Tile; + + ShapeDyn shape(1, 1, 1, 1, 1024); + StrideDyn stride(1024, 1024, 1024, 1024, 1); + GT dstG(localDst, shape, stride); + GT srcG(remoteSrc, shape, stride); + + ScratchTile scratchTile; + TASSIGN(scratchTile, 0x0); + + comm::AsyncSession session; + if (!comm::BuildAsyncSession(scratchTile, sdmaWorkspace, session)) { + return; + } + + auto event = comm::TGET_ASYNC(dstG, srcG, session); + (void)event.Wait(session); +} +``` + diff --git a/designs/outerCube/PTOISA/comm/TGET_zh.md b/designs/outerCube/PTOISA/comm/TGET_zh.md new file mode 100644 index 00000000..2c39826f --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TGET_zh.md @@ -0,0 +1,106 @@ +# TGET + +## 简介 + +远程读操作:将远端 NPU 的数据读取到本地内存。数据通过 UB Tile 作为中间暂存缓冲区进行传输。 + +当 GlobalTensor 超出 UB Tile 容量时,TGET 将自动执行**二维滑动**——沿行(DIM_3)和列(DIM_4)分块以适配 Tile,并遍历所有外层维度(DIM_0、DIM_1、DIM_2)。 + +## 数学语义 + +对有效区域内每个元素 `(i, j)`: + +$$\mathrm{dst}^{\mathrm{local}}_{i,j} = \mathrm{src}^{\mathrm{remote}}_{i,j}$$ + +数据流:`srcGlobalData(远端 GM)` → `stagingTileData(UB)` → `dstGlobalData(本地 GM)` + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +tget %dst_local, %src_remote : (!pto.memref<...>, !pto.memref<...>) +``` + +降级时会为 GM→UB→GM 数据路径引入 UB 暂存 Tile;C++ 内建接口需要显式传入 `stagingTileData`(或 `pingTile` / `pongTile`)操作数。 + +## C++ 内建接口 + +声明于 `include/pto/comm/pto_comm_inst.hpp` + +### 单 Tile(自动分块) + +```cpp +template +PTO_INST RecordEvent TGET(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + TileData &stagingTileData, WaitEvents&... events); +``` + +### 乒乓双缓冲 + +使用两个暂存 Tile,将相邻块的 TLOAD 与 TSTORE 重叠执行,隐藏 DMA 传输延迟。 + +```cpp +template +PTO_INST RecordEvent TGET(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + TileData &pingTile, TileData &pongTile, WaitEvents&... events); +``` + +## 约束 + +- **类型约束**: + - `GlobalSrcData::RawDType` 必须等于 `GlobalDstData::RawDType`。 + - `TileData::DType` 必须等于 `GlobalSrcData::RawDType`。 + - `GlobalSrcData::layout` 必须等于 `GlobalDstData::layout`。 +- **内存约束**: + - `srcGlobalData` 必须指向远端地址(源 NPU)。 + - `dstGlobalData` 必须指向本地地址(当前 NPU)。 + - `stagingTileData` / `pingTile` / `pongTile` 必须预先在统一缓冲区中分配。 +- **有效区域**: + - 传输大小由 `GlobalTensor` 的形状决定(自动分块以适配 Tile)。 +- **乒乓约束**: + - `pingTile` 和 `pongTile` 必须具有相同的类型和维度。 + - 必须位于不重叠的 UB 偏移处。 + +## 示例 + +### 基础用法 + +```cpp +#include +#include + +using namespace pto; + +template +void example_tget(__gm__ T* local_data, __gm__ T* remote_addr) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GTensor = GlobalTensor; + + GTensor srcG(remote_addr); + GTensor dstG(local_data); + TileT stagingTile; + TASSIGN(stagingTile, 0); + + // 基础远程读 + comm::TGET(dstG, srcG, stagingTile); +} +``` + +### 乒乓双缓冲 + +```cpp +constexpr size_t tileUBBytes = ((64 * 64 * sizeof(float) + 1023) / 1024) * 1024; +TileT pingTile(64, 64); +TileT pongTile(64, 64); +TASSIGN(pingTile, 0); +TASSIGN(pongTile, tileUBBytes); // 不重叠的 UB 区域 + +// 将 TLOAD[i+1] 与 TSTORE[i] 重叠执行以提升流水线利用率 +comm::TGET(dstG, srcG, pingTile, pongTile); +``` + diff --git a/designs/outerCube/PTOISA/comm/TNOTIFY.md b/designs/outerCube/PTOISA/comm/TNOTIFY.md new file mode 100644 index 00000000..6bb80aa4 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TNOTIFY.md @@ -0,0 +1,100 @@ +# TNOTIFY + +## Introduction + +Send flag notification to remote NPU. Used for lightweight synchronization between NPUs without transferring bulk data. + +## Math Interpretation + +For `NotifyOp::Set`: + +$$ \mathrm{signal}^{\mathrm{remote}} = \mathrm{value} $$ + +For `NotifyOp::AtomicAdd`: + +$$ \mathrm{signal}^{\mathrm{remote}} \mathrel{+}= \mathrm{value} \quad (\text{atomic}) $$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../../assembly/PTO-AS.md). + +```text +tnotify %signal_remote, %value {op = #pto.notify_op} : (!pto.memref, i32) +tnotify %signal_remote, %value {op = #pto.notify_op} : (!pto.memref, i32) +``` + +## C++ Intrinsic + +Declared in `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +template +PTO_INST void TNOTIFY(GlobalSignalData &dstSignalData, int32_t value, NotifyOp op, WaitEvents&... events); +``` + +## Constraints + +- **Type constraints**: + - `GlobalSignalData::DType` must be `int32_t` (32-bit signal). +- **Memory constraints**: + - `dstSignalData` must point to remote address (on target NPU). + - `dstSignalData` should be 4-byte aligned. +- **Operation semantics**: + - `NotifyOp::Set`: Direct store to remote memory. + - `NotifyOp::AtomicAdd`: Hardware atomic add using `st_atomic` instruction. + +## Examples + +### Basic Set Notification + +```cpp +#include + +using namespace pto; + +void notify_set(__gm__ int32_t* remote_signal) { + comm::Signal sig(remote_signal); + + // Set remote signal to 1 + comm::TNOTIFY(sig, 1, comm::NotifyOp::Set); +} +``` + +### Atomic Counter Increment + +```cpp +#include + +using namespace pto; + +void atomic_increment(__gm__ int32_t* remote_counter) { + comm::Signal counter(remote_counter); + + // Atomically add 1 to remote counter + comm::TNOTIFY(counter, 1, comm::NotifyOp::AtomicAdd); +} +``` + +### Producer-Consumer Pattern + +```cpp +#include + +using namespace pto; + +// Producer: notify when data is ready +void producer(__gm__ int32_t* remote_flag) { + // ... produce data ... + + comm::Signal flag(remote_flag); + comm::TNOTIFY(flag, 1, comm::NotifyOp::Set); +} + +// Consumer: wait for data +void consumer(__gm__ int32_t* local_flag) { + comm::Signal flag(local_flag); + comm::TWAIT(flag, 1, comm::WaitCmp::EQ); + + // ... consume data ... +} +``` diff --git a/designs/outerCube/PTOISA/comm/TNOTIFY_zh.md b/designs/outerCube/PTOISA/comm/TNOTIFY_zh.md new file mode 100644 index 00000000..d484bcfe --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TNOTIFY_zh.md @@ -0,0 +1,101 @@ +# TNOTIFY + +## 简介 + +向远端 NPU 发送标志通知。用于 NPU 之间的轻量级同步,无需传输大量数据。 + +## 数学语义 + +`NotifyOp::Set` 时: + +$$\mathrm{signal}^{\mathrm{remote}} = \mathrm{value}$$ + +`NotifyOp::AtomicAdd` 时: + +$$\mathrm{signal}^{\mathrm{remote}} \mathrel{+}= \mathrm{value} \quad (\text{原子操作})$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../../assembly/PTO-AS_zh.md)。 + +```text +tnotify %signal_remote, %value {op = #pto.notify_op} : (!pto.memref, i32) +tnotify %signal_remote, %value {op = #pto.notify_op} : (!pto.memref, i32) +``` + +## C++ 内建接口 + +声明于 `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +template +PTO_INST void TNOTIFY(GlobalSignalData &dstSignalData, int32_t value, NotifyOp op, WaitEvents&... events); +``` + +## 约束 + +- **类型约束**: + - `GlobalSignalData::DType` 必须为 `int32_t`(32 位信号)。 +- **内存约束**: + - `dstSignalData` 必须指向远端地址(目标 NPU)。 + - `dstSignalData` 应 4 字节对齐。 +- **操作语义**: + - `NotifyOp::Set`:直接存储到远端内存。 + - `NotifyOp::AtomicAdd`:使用 `st_atomic` 指令执行硬件原子加。 + +## 示例 + +### 基础 Set 通知 + +```cpp +#include + +using namespace pto; + +void notify_set(__gm__ int32_t* remote_signal) { + comm::Signal sig(remote_signal); + + // 将远端信号置为 1 + comm::TNOTIFY(sig, 1, comm::NotifyOp::Set); +} +``` + +### 原子计数器自增 + +```cpp +#include + +using namespace pto; + +void atomic_increment(__gm__ int32_t* remote_counter) { + comm::Signal counter(remote_counter); + + // 对远端计数器原子加 1 + comm::TNOTIFY(counter, 1, comm::NotifyOp::AtomicAdd); +} +``` + +### 生产者-消费者模式 + +```cpp +#include + +using namespace pto; + +// 生产者:数据就绪后发送通知 +void producer(__gm__ int32_t* remote_flag) { + // ... 生产数据 ... + + comm::Signal flag(remote_flag); + comm::TNOTIFY(flag, 1, comm::NotifyOp::Set); +} + +// 消费者:等待数据就绪 +void consumer(__gm__ int32_t* local_flag) { + comm::Signal flag(local_flag); + comm::TWAIT(flag, 1, comm::WaitCmp::EQ); + + // ... 消费数据 ... +} +``` + diff --git a/designs/outerCube/PTOISA/comm/TPUT.md b/designs/outerCube/PTOISA/comm/TPUT.md new file mode 100644 index 00000000..0c16a735 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TPUT.md @@ -0,0 +1,131 @@ +# TPUT + +## Introduction + +Remote write operation: write local data to remote NPU's memory. Data is transferred via a UB tile as intermediate staging buffer. + +When the GlobalTensor exceeds the UB tile capacity, TPUT automatically performs **2D sliding** — chunking rows (DIM_3) and columns (DIM_4) to fit each chunk into the tile, iterating over all outer dimensions (DIM_0, DIM_1, DIM_2). + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}^{\mathrm{remote}}_{i,j} = \mathrm{src}^{\mathrm{local}}_{i,j} $$ + +Data flow: `srcGlobalData (local GM)` → `stagingTileData (UB)` → `dstGlobalData (remote GM)` + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../../assembly/PTO-AS.md). + +Synchronous form: + +```text +tput %dst_remote, %src_local : (!pto.memref<...>, !pto.memref<...>) +``` +Lowering introduces UB staging tile(s) for the GM→UB→GM data path; the C++ intrinsic requires explicit `stagingTileData` (or `pingTile` / `pongTile`) operand(s). + +## C++ Intrinsic + +Declared in `include/pto/comm/pto_comm_inst.hpp` + +### Single-tile (auto-chunking) + +```cpp +template +PTO_INST RecordEvent TPUT(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + TileData &stagingTileData, WaitEvents&... events); +``` + +### Ping-pong double buffering + +Uses two staging tiles to overlap TLOAD and TSTORE for adjacent chunks, hiding one DMA transfer behind the other. + +```cpp +template +PTO_INST RecordEvent TPUT(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + TileData &pingTile, TileData &pongTile, WaitEvents&... events); +``` + +### Runtime atomic type + +```cpp +template +PTO_INST RecordEvent TPUT(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + TileData &stagingTileData, AtomicType atomicType, WaitEvents&... events); +``` + +## Constraints + +- **Type constraints**: + - `GlobalSrcData::RawDType` must equal `GlobalDstData::RawDType`. + - `TileData::DType` must equal `GlobalSrcData::RawDType`. + - `GlobalSrcData::layout` must equal `GlobalDstData::layout`. +- **Memory constraints**: + - `dstGlobalData` must point to remote address (on target NPU). + - `srcGlobalData` must point to local address (on current NPU). + - `stagingTileData` / `pingTile` / `pongTile` must be pre-allocated in Unified Buffer. +- **Valid region**: + - Transfer size is determined by `GlobalTensor` shape (auto-chunked to fit tile). +- **Atomic operation**: + - `atomicType` supports `AtomicNone` and `AtomicAdd`. +- **Ping-pong**: + - `pingTile` and `pongTile` must have the same type and dimensions. + - Must reside at non-overlapping UB offsets. + +## Examples + +### Basic Usage + +```cpp +#include +#include + +using namespace pto; + +template +void example_tput(__gm__ T* local_data, __gm__ T* remote_addr) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + /* + If the globalTensor is larger than UB Tile, TPUT will perform 2D sliding automatically. + using GShape = Shape<1, 1, 1, 4096, 4096>; + using GStride = BaseShape2D; + */ + using GTensor = GlobalTensor; + + GTensor srcG(local_data); + GTensor dstG(remote_addr); + TileT stagingTile; + TASSIGN(stagingTile, 0); + + // Basic remote write + comm::TPUT(dstG, srcG, stagingTile); + + // Remote write with atomic add + comm::TPUT(dstG, srcG, stagingTile); +} +``` + +### Ping-pong Double Buffering + +```cpp +constexpr size_t tileUBBytes = ((64 * 64 * sizeof(float) + 1023) / 1024) * 1024; +TileT pingTile(64, 64); +TileT pongTile(64, 64); +TASSIGN(pingTile, 0); +TASSIGN(pongTile, tileUBBytes); // Non-overlapping UB region + +// Overlaps TLOAD[i+1] with TSTORE[i] for better pipeline utilization +comm::TPUT(dstG, srcG, pingTile, pongTile); +``` + +### Runtime Atomic Type + +```cpp +// Select atomic type at runtime instead of compile-time template parameter +comm::TPUT(dstG, srcG, stagingTile, AtomicType::AtomicAdd); +``` \ No newline at end of file diff --git a/designs/outerCube/PTOISA/comm/TPUT_ASYNC.md b/designs/outerCube/PTOISA/comm/TPUT_ASYNC.md new file mode 100644 index 00000000..3d8a16aa --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TPUT_ASYNC.md @@ -0,0 +1,132 @@ +# TPUT_ASYNC + +## Introduction + +`TPUT_ASYNC` is an asynchronous remote write primitive. It starts a transfer from local GM to remote GM and returns an `AsyncEvent` immediately. + +Data flow: + +`srcGlobalData (local GM) -> DMA engine -> dstGlobalData (remote GM)` + + +## Template Parameter + +- `engine`: + - `DmaEngine::SDMA` (default) + - `DmaEngine::URMA` (todo) + +> **Important (SDMA path)** +> `TPUT_ASYNC` with `DmaEngine::SDMA` currently supports **only flat contiguous logical 1D tensors**. +> Non-1D or non-contiguous layouts are not supported by the current SDMA async implementation. + + +## C++ Intrinsic + +Declared in `include/pto/comm/pto_comm_inst.hpp`. + +```cpp +template +PTO_INST AsyncEvent TPUT_ASYNC(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + const AsyncSession &session, WaitEvents &... events); +``` + +`AsyncSession` is an engine-agnostic session object. Build once with +`BuildAsyncSession()`, then pass to all async calls and event waits. +The template `engine` parameter selects the DMA backend at compile time, making the +code forward-compatible with future engines (URMA, CCU, etc.). + +## AsyncSession Construction + +Use `BuildAsyncSession` from `include/pto/comm/async/async_event_impl.hpp`: + +```cpp +template +PTO_INTERNAL bool BuildAsyncSession(ScratchTile &scratchTile, + __gm__ uint8_t *workspace, + AsyncSession &session, + uint32_t syncId = 0, + const sdma::SdmaBaseConfig &baseConfig = {32 * 1024, 0, 1}, + uint32_t channelGroupIdx = sdma::kAutoChannelGroupIdx); +``` + +The engine template parameter selects the backend (currently only SDMA). + +Parameters with defaults: + +| Parameter | Default | Description | +|---|---|---| +| `syncId` | `0` | MTE3/MTE2 pipe sync event id (0-7). Override if kernel uses other pipe barriers on the same id. | +| `baseConfig` | `{32*1024, 0, 1}` | `{block_bytes, comm_block_offset, queue_num}`. Suitable for most single-queue transfers. | +| `channelGroupIdx` | `kAutoChannelGroupIdx` | SDMA channel group index. Default uses `get_block_idx()` internally, mapping to current AI core. Override for multi-block or custom channel mapping scenarios. | + +## Constraints + +- `GlobalSrcData::RawDType == GlobalDstData::RawDType` +- `GlobalSrcData::layout == GlobalDstData::layout` +- SDMA path requires source tensor to be **flat contiguous logical 1D only** +- workspace must be a valid GM pointer allocated by host-side `SdmaWorkspaceManager` + +If the 1D contiguous requirement is not met, current implementation returns an invalid async event (`handle == 0`). + +## scratchTile Role + +`scratchTile` is **not** the payload staging buffer for user data. +It is converted to `TmpBuffer` and used as temporary UB workspace for: + +- writing/reading SDMA control words (flag, sq_tail, channel_info) +- polling event completion flags +- committing queue tail during completion + +Data payload moves between GM buffers directly; `scratchTile` only supports control and synchronization metadata. + +## scratchTile Type and Size Constraints + +- must be a `pto::Tile` type +- must be UB/Vec tile (`ScratchTile::Loc == TileType::Vec`) +- available bytes must be at least `sizeof(uint64_t)` (8 bytes) + +Recommended: `Tile` (256B). + +## Completion Semantics + +Use `AsyncEvent` to synchronize: + +- `event.Wait(session)` — blocks until the transfer is complete + +After wait succeeds, writes to `dstGlobalData` are complete. + +## Example + +```cpp +#include +#include + +using namespace pto; + +template +__global__ AICORE void SimplePut(__gm__ T *remoteDst, __gm__ T *localSrc, + __gm__ uint8_t *sdmaWorkspace) +{ + using ShapeDyn = Shape; + using StrideDyn = Stride; + using GT = GlobalTensor; + using ScratchTile = Tile; + + ShapeDyn shape(1, 1, 1, 1, 1024); + StrideDyn stride(1024, 1024, 1024, 1024, 1); + GT dstG(remoteDst, shape, stride); + GT srcG(localSrc, shape, stride); + + ScratchTile scratchTile; + TASSIGN(scratchTile, 0x0); + + comm::AsyncSession session; + if (!comm::BuildAsyncSession(scratchTile, sdmaWorkspace, session)) { + return; + } + + auto event = comm::TPUT_ASYNC(dstG, srcG, session); + (void)event.Wait(session); +} +``` diff --git a/designs/outerCube/PTOISA/comm/TPUT_ASYNC_zh.md b/designs/outerCube/PTOISA/comm/TPUT_ASYNC_zh.md new file mode 100644 index 00000000..ceb83763 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TPUT_ASYNC_zh.md @@ -0,0 +1,126 @@ +# TPUT_ASYNC + +## 简介 + +`TPUT_ASYNC` 是异步远程写原语。它启动一次从本地 GM 到远端 GM 的传输,并立即返回 `AsyncEvent`。 + +数据流: + +`srcGlobalData(本地 GM)` → DMA 引擎 → `dstGlobalData(远端 GM)` + +## 模板参数 + +- `engine`: + - `DmaEngine::SDMA`(默认) + - `DmaEngine::URMA`(待实现) + +> **注意(SDMA 路径)** +> `TPUT_ASYNC` 配合 `DmaEngine::SDMA` 目前**仅支持扁平连续的逻辑一维 tensor**。 +> 当前 SDMA 异步实现不支持非一维或非连续布局。 + +## C++ 内建接口 + +声明于 `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +template +PTO_INST AsyncEvent TPUT_ASYNC(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + const AsyncSession &session, WaitEvents &... events); +``` + +`AsyncSession` 是引擎无关的会话对象。使用 `BuildAsyncSession()` 构建一次后,传递给所有异步调用和事件等待。模板参数 `engine` 在编译期选择 DMA 后端,使代码对未来引擎(URMA、CCU 等)保持前向兼容。 + +## AsyncSession 构建 + +使用 `include/pto/comm/async/async_event_impl.hpp` 中的 `BuildAsyncSession`: + +```cpp +template +PTO_INTERNAL bool BuildAsyncSession(ScratchTile &scratchTile, + __gm__ uint8_t *workspace, + AsyncSession &session, + uint32_t syncId = 0, + const sdma::SdmaBaseConfig &baseConfig = {32 * 1024, 0, 1}, + uint32_t channelGroupIdx = sdma::kAutoChannelGroupIdx); +``` + +带默认值的参数说明: + +| 参数 | 默认值 | 说明 | +|---|---|---| +| `syncId` | `0` | MTE3/MTE2 管道同步事件 ID(0-7)。若 kernel 在相同 ID 上使用了其他管道屏障,则需覆盖此值。| +| `baseConfig` | `{32*1024, 0, 1}` | `{block_bytes, comm_block_offset, queue_num}`。适用于大多数单队列传输场景。| +| `channelGroupIdx` | `kAutoChannelGroupIdx` | SDMA 通道组索引。默认内部使用 `get_block_idx()` 映射到当前 AI Core。多 block 或自定义通道映射场景下需覆盖此值。| + +## 约束 + +- `GlobalSrcData::RawDType == GlobalDstData::RawDType` +- `GlobalSrcData::layout == GlobalDstData::layout` +- SDMA 路径要求源 tensor 为**扁平连续的逻辑一维** +- workspace 必须是由主机侧 `SdmaWorkspaceManager` 分配的有效 GM 指针 + +若不满足一维连续要求,当前实现返回无效 async event(`handle == 0`)。 + +## scratchTile 的作用 + +`scratchTile` **不是**用于存放用户数据负载的暂存缓冲区。 +它被转换为 `TmpBuffer`,用作临时 UB 工作区,用于: + +- 写入/读取 SDMA 控制字(flag、sq_tail、channel_info) +- 轮询事件完成标志 +- 完成时提交队列尾部 + +实际数据负载直接在 GM 缓冲区之间传输;`scratchTile` 仅用于控制和同步元数据。 + +## scratchTile 类型与大小约束 + +- 必须是 `pto::Tile` 类型 +- 必须是 UB/Vec tile(`ScratchTile::Loc == TileType::Vec`) +- 可用字节数至少为 `sizeof(uint64_t)`(8 字节) + +推荐使用:`Tile`(256B)。 + +## 完成语义 + +使用 `AsyncEvent` 同步: + +- `event.Wait(session)` — 阻塞直到传输完成 + +wait 成功后,对 `dstGlobalData` 的写入已全部完成。 + +## 示例 + +```cpp +#include +#include + +using namespace pto; + +template +__global__ AICORE void SimplePut(__gm__ T *remoteDst, __gm__ T *localSrc, + __gm__ uint8_t *sdmaWorkspace) +{ + using ShapeDyn = Shape; + using StrideDyn = Stride; + using GT = GlobalTensor; + using ScratchTile = Tile; + + ShapeDyn shape(1, 1, 1, 1, 1024); + StrideDyn stride(1024, 1024, 1024, 1024, 1); + GT dstG(remoteDst, shape, stride); + GT srcG(localSrc, shape, stride); + + ScratchTile scratchTile; + TASSIGN(scratchTile, 0x0); + + comm::AsyncSession session; + if (!comm::BuildAsyncSession(scratchTile, sdmaWorkspace, session)) { + return; + } + + auto event = comm::TPUT_ASYNC(dstG, srcG, session); + (void)event.Wait(session); +} +``` + diff --git a/designs/outerCube/PTOISA/comm/TPUT_zh.md b/designs/outerCube/PTOISA/comm/TPUT_zh.md new file mode 100644 index 00000000..ad4a2da7 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TPUT_zh.md @@ -0,0 +1,128 @@ +# TPUT + +## 简介 + +远程写操作:将本地数据写入远端 NPU 的内存。数据通过 UB Tile 作为中间暂存缓冲区进行传输。 + +当 GlobalTensor 超出 UB Tile 容量时,TPUT 将自动执行**二维滑动**——沿行(DIM_3)和列(DIM_4)分块以适配 Tile,并遍历所有外层维度(DIM_0、DIM_1、DIM_2)。 + +## 数学语义 + +对有效区域内每个元素 `(i, j)`: + +$$\mathrm{dst}^{\mathrm{remote}}_{i,j} = \mathrm{src}^{\mathrm{local}}_{i,j}$$ + +数据流:`srcGlobalData(本地 GM)` → `stagingTileData(UB)` → `dstGlobalData(远端 GM)` + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +tput %dst_remote, %src_local : (!pto.memref<...>, !pto.memref<...>) +``` + +降级时会为 GM→UB→GM 数据路径引入 UB 暂存 Tile;C++ 内建接口需要显式传入 `stagingTileData`(或 `pingTile` / `pongTile`)操作数。 + +## C++ 内建接口 + +声明于 `include/pto/comm/pto_comm_inst.hpp` + +### 单 Tile(自动分块) + +```cpp +template +PTO_INST RecordEvent TPUT(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + TileData &stagingTileData, WaitEvents&... events); +``` + +### 乒乓双缓冲 + +使用两个暂存 Tile,将相邻块的 TLOAD 与 TSTORE 重叠执行,隐藏 DMA 传输延迟。 + +```cpp +template +PTO_INST RecordEvent TPUT(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + TileData &pingTile, TileData &pongTile, WaitEvents&... events); +``` + +### 运行时原子类型 + +```cpp +template +PTO_INST RecordEvent TPUT(GlobalDstData &dstGlobalData, GlobalSrcData &srcGlobalData, + TileData &stagingTileData, AtomicType atomicType, WaitEvents&... events); +``` + +## 约束 + +- **类型约束**: + - `GlobalSrcData::RawDType` 必须等于 `GlobalDstData::RawDType`。 + - `TileData::DType` 必须等于 `GlobalSrcData::RawDType`。 + - `GlobalSrcData::layout` 必须等于 `GlobalDstData::layout`。 +- **内存约束**: + - `dstGlobalData` 必须指向远端地址(目标 NPU)。 + - `srcGlobalData` 必须指向本地地址(当前 NPU)。 + - `stagingTileData` / `pingTile` / `pongTile` 必须预先在统一缓冲区中分配。 +- **有效区域**: + - 传输大小由 `GlobalTensor` 的形状决定(自动分块以适配 Tile)。 +- **原子操作**: + - `atomicType` 支持 `AtomicNone` 和 `AtomicAdd`。 +- **乒乓约束**: + - `pingTile` 和 `pongTile` 必须具有相同的类型和维度。 + - 必须位于不重叠的 UB 偏移处。 + +## 示例 + +### 基础用法 + +```cpp +#include +#include + +using namespace pto; + +template +void example_tput(__gm__ T* local_data, __gm__ T* remote_addr) { + using TileT = Tile; + using GShape = Shape<1, 1, 1, 16, 16>; + using GStride = BaseShape2D; + using GTensor = GlobalTensor; + + GTensor srcG(local_data); + GTensor dstG(remote_addr); + TileT stagingTile; + TASSIGN(stagingTile, 0); + + // 基础远程写 + comm::TPUT(dstG, srcG, stagingTile); + + // 带原子加的远程写 + comm::TPUT(dstG, srcG, stagingTile); +} +``` + +### 乒乓双缓冲 + +```cpp +constexpr size_t tileUBBytes = ((64 * 64 * sizeof(float) + 1023) / 1024) * 1024; +TileT pingTile(64, 64); +TileT pongTile(64, 64); +TASSIGN(pingTile, 0); +TASSIGN(pongTile, tileUBBytes); // 不重叠的 UB 区域 + +// 将 TLOAD[i+1] 与 TSTORE[i] 重叠执行以提升流水线利用率 +comm::TPUT(dstG, srcG, pingTile, pongTile); +``` + +### 运行时原子类型 + +```cpp +// 在运行时而非编译期模板参数中选择原子类型 +comm::TPUT(dstG, srcG, stagingTile, AtomicType::AtomicAdd); +``` + diff --git a/designs/outerCube/PTOISA/comm/TREDUCE.md b/designs/outerCube/PTOISA/comm/TREDUCE.md new file mode 100644 index 00000000..1a2e2c7e --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TREDUCE.md @@ -0,0 +1,118 @@ +# TREDUCE + +## Introduction + +Reduce operation: gather data from multiple remote NPUs and perform element-wise reduction locally. + + +Only the root needs to execute `TREDUCE`. Non-root ranks only need to ensure their source buffers are ready and remain valid for the duration of the operation. Calling `TREDUCE` on non-root ranks is undefined behavior. + +**Large Tile Support**: When the GlobalTensor exceeds the UB tile capacity in rows and/or columns, the reduction is automatically chunked via 2D sliding. + +## Math Interpretation + +For each element `(i, j)` in the valid region: + +$$ \mathrm{dst}^{\mathrm{local}}_{i,j} = \bigoplus_{r=0}^{N-1} \mathrm{src}^{(r)}_{i,j} $$ + +where $N$ is the number of ranks and $\oplus$ is the reduction operation (sum, max, min, etc.). + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../../assembly/PTO-AS.md). + +Synchronous form: + +```text +treduce %group, %dst {op = #pto.reduce_op} : (!pto.group<...>, !pto.memref<...>) +treduce %group, %dst {op = #pto.reduce_op} : (!pto.group<...>, !pto.memref<...>) +``` +Lowering introduces internal accumulator and receive tiles for the reduce pipeline; the C++ intrinsic requires explicit `accTileData`, `recvTileData` (or `accTileData`, `pingTileData`, `pongTileData`) operand(s). + +## C++ Intrinsic + +Declared in `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +// Basic reduce (accumulator + receive tile) +template +PTO_INST RecordEvent TREDUCE(ParallelGroupType ¶llelGroup, GlobalDstData &dstGlobalData, + TileData &accTileData, TileData &recvTileData, ReduceOp op, WaitEvents&... events); + +// Ping-pong reduce (accumulator + ping + pong tiles for double buffering) +template +PTO_INST RecordEvent TREDUCE(ParallelGroupType ¶llelGroup, GlobalDstData &dstGlobalData, + TileData &accTileData, TileData &pingTileData, TileData &pongTileData, + ReduceOp op, WaitEvents&... events); +``` + +## Constraints + +- **Type constraints**: + - `ParallelGroup::value_type::RawDType` must equal `GlobalDstData::RawDType`. + - `TileData::DType` must equal `GlobalDstData::RawDType`. +- **Memory constraints**: + - `dstGlobalData` must point to local address (on current NPU). + - `accTileData`, `recvTileData` (or `accTileData`, `pingTileData`, `pongTileData`) must be pre-allocated UB tiles. +- **ParallelGroup constraints**: + - `parallelGroup.tensors[r]` must refer to rank `r`'s source buffer (remote GM as seen by the root). + - `parallelGroup.GetRootIdx()` identifies the calling NPU as the reduce root. + - All source tensors are assumed to have the same shape and strides. +- **Chunked mode constraints** (when data exceeds a single UB tile): + - If `TileData` has static `ValidRow`, `GetShape(DIM_3)` must be divisible by `ValidRow`. Use a Tile with `DYNAMIC` ValidRow for partial row support. + - If `TileData` has static `ValidCol`, `GetShape(DIM_4)` must be divisible by `ValidCol`. Use a Tile with `DYNAMIC` ValidCol for partial column support. + +## Examples + +### Basic Reduce Sum + +```cpp +#include + +using namespace pto; + +template +void reduce_sum(__gm__ T* group_addrs[NRANKS], __gm__ T* result, int my_rank) { + using TileT = Tile; + using GTensor = GlobalTensor, + BaseShape2D, Layout::ND>; + + // Stack-allocated tensors + GTensor tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) { + tensors[i] = GTensor(group_addrs[i]); + } + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GTensor dstG(result); + TileT accTile, recvTile; + + comm::TREDUCE(group, dstG, accTile, recvTile, comm::ReduceOp::Sum); +} +``` + +### Max Reduce + +```cpp +#include + +using namespace pto; + +template +void reduce_max(__gm__ T* group_addrs[NRANKS], __gm__ T* result, int my_rank) { + using TileT = Tile; + using GTensor = GlobalTensor, + BaseShape2D, Layout::ND>; + + GTensor tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) { + tensors[i] = GTensor(group_addrs[i]); + } + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GTensor dstG(result); + TileT accTile, recvTile; + + comm::TREDUCE(group, dstG, accTile, recvTile, comm::ReduceOp::Max); +} +``` diff --git a/designs/outerCube/PTOISA/comm/TREDUCE_zh.md b/designs/outerCube/PTOISA/comm/TREDUCE_zh.md new file mode 100644 index 00000000..5f165efe --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TREDUCE_zh.md @@ -0,0 +1,112 @@ +# TREDUCE + +## 简介 + +Reduce 操作:从多个远端 NPU 收集数据并在本地执行逐元素归约。 + +只有根节点需要执行 `TREDUCE`。非根节点只需确保在操作期间其源缓冲区已就绪且保持有效。在非根节点上调用 `TREDUCE` 属于未定义行为。 + +**大 Tile 支持**:当 GlobalTensor 在行和/或列方向超出 UB Tile 容量时,归约操作将通过二维滑动自动分块。 + +## 数学语义 + +对有效区域内每个元素 `(i, j)`: + +$$\mathrm{dst}^{\mathrm{local}}_{i,j} = \bigoplus_{r=0}^{N-1} \mathrm{src}^{(r)}_{i,j}$$ + +其中 $N$ 为 rank 总数,$\oplus$ 为归约运算(求和、取最大值、取最小值等)。 + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +treduce %group, %dst {op = #pto.reduce_op} : (!pto.group<...>, !pto.memref<...>) +treduce %group, %dst {op = #pto.reduce_op} : (!pto.group<...>, !pto.memref<...>) +``` + +降级时会为 reduce 流水线引入内部累加 Tile 和接收 Tile;C++ 内建接口需要显式传入 `accTileData`、`recvTileData`(或 `accTileData`、`pingTileData`、`pongTileData`)操作数。 + +## C++ 内建接口 + +声明于 `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +// 基础 reduce(累加 Tile + 接收 Tile) +template +PTO_INST RecordEvent TREDUCE(ParallelGroupType ¶llelGroup, GlobalDstData &dstGlobalData, + TileData &accTileData, TileData &recvTileData, ReduceOp op, WaitEvents&... events); + +// 乒乓 reduce(累加 Tile + ping/pong Tile 实现双缓冲) +template +PTO_INST RecordEvent TREDUCE(ParallelGroupType ¶llelGroup, GlobalDstData &dstGlobalData, + TileData &accTileData, TileData &pingTileData, TileData &pongTileData, + ReduceOp op, WaitEvents&... events); +``` + +## 约束 + +- **类型约束**: + - `ParallelGroup::value_type::RawDType` 必须等于 `GlobalDstData::RawDType`。 + - `TileData::DType` 必须等于 `GlobalDstData::RawDType`。 +- **内存约束**: + - `dstGlobalData` 必须指向本地内存(当前 NPU)。 + - `accTileData`、`recvTileData`(或 `accTileData`、`pingTileData`、`pongTileData`)必须为预先分配的 UB Tile。 +- **ParallelGroup 约束**: + - `parallelGroup.tensors[r]` 必须指向 rank `r` 的源缓冲区(从根节点视角看到的远端 GM)。 + - `parallelGroup.GetRootIdx()` 标识调用方 NPU 为 reduce 根节点。 + - 所有源 tensor 假定具有相同的形状和步幅。 +- **分块模式约束**(数据超出单个 UB Tile 时): + - 若 `TileData` 具有静态 `ValidRow`,则 `GetShape(DIM_3)` 必须能被 `ValidRow` 整除。如需支持不足一行的情况,请使用 `DYNAMIC` ValidRow 的 Tile。 + - 若 `TileData` 具有静态 `ValidCol`,则 `GetShape(DIM_4)` 必须能被 `ValidCol` 整除。如需支持不足一列的情况,请使用 `DYNAMIC` ValidCol 的 Tile。 + +## 示例 + +### 基础求和归约 + +```cpp +#include + +using namespace pto; + +template +void reduce_sum(__gm__ T* group_addrs[NRANKS], __gm__ T* result, int my_rank) { + using TileT = Tile; + using GTensor = GlobalTensor, + BaseShape2D, Layout::ND>; + + GTensor tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) tensors[i] = GTensor(group_addrs[i]); + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GTensor dstG(result); + TileT accTile, recvTile; + comm::TREDUCE(group, dstG, accTile, recvTile, comm::ReduceOp::Sum); +} +``` + +### 最大值归约 + +```cpp +#include + +using namespace pto; + +template +void reduce_max(__gm__ T* group_addrs[NRANKS], __gm__ T* result, int my_rank) { + using TileT = Tile; + using GTensor = GlobalTensor, + BaseShape2D, Layout::ND>; + + GTensor tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) tensors[i] = GTensor(group_addrs[i]); + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GTensor dstG(result); + TileT accTile, recvTile; + comm::TREDUCE(group, dstG, accTile, recvTile, comm::ReduceOp::Max); +} +``` + diff --git a/designs/outerCube/PTOISA/comm/TSCATTER.md b/designs/outerCube/PTOISA/comm/TSCATTER.md new file mode 100644 index 00000000..66e00ca3 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TSCATTER.md @@ -0,0 +1,126 @@ +# TSCATTER + +## Introduction + +Scatter operation: the calling NPU (root) distributes data to all ranks in the parallel group by splitting the local source tensor along **DIM_3** (row dimension). This is the inverse of `TGATHER`. + + +Only the root needs to execute `TSCATTER`. Non-root ranks only need to ensure their destination buffers are allocated and writable for the duration of the operation. Calling `TSCATTER` on non-root ranks is undefined behavior. + +**Large Tile Support**: When the per-rank data exceeds the UB tile capacity in rows and/or columns, the transfer is automatically chunked via 2D sliding. + +## Math Interpretation + +The local source tensor has shape $(D_0, D_1, D_2, N \times H, W)$, where $N$ is the number of ranks and each rank receives $H$ rows. After the operation: + +$$\mathrm{dst}^{(r)}_{d_0, d_1, d_2,\; i,\; j} = \mathrm{src}^{\mathrm{local}}_{d_0, d_1, d_2,\; r \cdot H + i,\; j} \quad \forall\, r \in [0, N),\; i \in [0, H),\; j \in [0, W)$$ + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../../assembly/PTO-AS.md). + +Synchronous form: + +```text +tscatter %group, %src : (!pto.group<...>, !pto.memref<...>) +``` +Lowering introduces UB staging tile(s) for the GM→UB→GM data path; the C++ intrinsic requires explicit `stagingTileData` (or `pingTile` / `pongTile`) operand(s). + +## C++ Intrinsic + +Declared in `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +// Basic scatter (single staging tile) +template +PTO_INST RecordEvent TSCATTER(ParallelGroupType ¶llelGroup, GlobalSrcData &srcGlobalData, + TileData &stagingTileData, WaitEvents&... events); + +// Ping-pong scatter (double buffering with two staging tiles) +template +PTO_INST RecordEvent TSCATTER(ParallelGroupType ¶llelGroup, GlobalSrcData &srcGlobalData, + TileData &pingTile, TileData &pongTile, WaitEvents&... events); +``` + +## Constraints + +- **Type constraints**: + - `ParallelGroup::value_type::RawDType` must equal `GlobalSrcData::RawDType`. + - `TileData::DType` must equal `GlobalSrcData::RawDType`. +- **Memory constraints**: + - `srcGlobalData` must point to local memory (current NPU) and be large enough to hold data for all ranks. Specifically, `srcGlobalData.GetShape(DIM_3)` must be $\geq N \times H$ where $H$ is each rank's `GetShape(DIM_3)`. + - If `srcGlobalData.GetShape(DIM_3) > N × H`, only the first `N × H` rows are read; remaining rows are ignored. + - `stagingTileData` (or `pingTile` / `pongTile`) must be pre-allocated in UB. +- **ParallelGroup constraints**: + - `parallelGroup.tensors[r]` must refer to rank `r`'s destination buffer (remote GM as seen by the root). + - `parallelGroup.GetRootIdx()` identifies the calling NPU as the scatter root. + - All destination tensors are assumed to have the same shape and strides; behavior is undefined if they differ. +- **Chunked mode constraints** (when per-rank data exceeds a single UB tile): + - If `TileData` has static `ValidRow`, `GetShape(DIM_3)` of each rank's destination must be divisible by `ValidRow`. Use a Tile with `DYNAMIC` ValidRow for partial row support. + - If `TileData` has static `ValidCol`, `GetShape(DIM_4)` must be divisible by `ValidCol`. Use a Tile with `DYNAMIC` ValidCol for partial column support. + +## Examples + +### Basic Scatter (Single Staging Tile) + +Root has `NRANKS * ROWS` rows of width `COLS`. Each rank receives `ROWS × COLS`, split along DIM_3. +The tile size (`TILE_ROWS × TILE_COLS`) can be smaller than the per-rank data — when it is, the implementation automatically chunks the transfer along both DIM_3 and DIM_4 via 2D sliding. + +```cpp +#include + +using namespace pto; + +template +void scatter(__gm__ T* local_data, __gm__ T* group_addrs[NRANKS], int my_rank) { + using TileT = Tile; + using GPerRank = GlobalTensor, + BaseShape2D, Layout::ND>; + using GSource = GlobalTensor, + BaseShape2D, Layout::ND>; + + GPerRank tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) { + tensors[i] = GPerRank(group_addrs[i]); + } + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GSource srcG(local_data); + TileT stagingTile(TILE_ROWS, TILE_COLS); + + comm::TSCATTER(group, srcG, stagingTile); +} +``` + +### Ping-Pong Scatter (Double Buffering) + +Uses two UB tiles to overlap TLOAD of the next chunk (MTE2) with TSTORE of the current chunk (MTE3). + +```cpp +#include + +using namespace pto; + +template +void scatter_pingpong(__gm__ T* local_data, __gm__ T* group_addrs[NRANKS], int my_rank) { + // Tile can be smaller than the data in both dimensions + using TileT = Tile; + using GPerRank = GlobalTensor, + BaseShape2D, Layout::ND>; + using GSource = GlobalTensor, + BaseShape2D, Layout::ND>; + + GPerRank tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) { + tensors[i] = GPerRank(group_addrs[i]); + } + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GSource srcG(local_data); + TileT pingTile(TILE_ROWS, TILE_COLS); + TileT pongTile(TILE_ROWS, TILE_COLS); + + // Ping-pong: overlaps TLOAD and TSTORE for better throughput + comm::TSCATTER(group, srcG, pingTile, pongTile); +} +``` diff --git a/designs/outerCube/PTOISA/comm/TSCATTER_zh.md b/designs/outerCube/PTOISA/comm/TSCATTER_zh.md new file mode 100644 index 00000000..c353beab --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TSCATTER_zh.md @@ -0,0 +1,120 @@ +# TSCATTER + +## 简介 + +Scatter 操作:调用方 NPU(根节点)将本地源 tensor 沿 **DIM_3**(行维度)拆分后分发到并行组中所有 rank。该操作是 `TGATHER` 的逆操作。 + +只有根节点需要执行 `TSCATTER`。非根节点只需确保在操作期间其目标缓冲区已分配且可写。在非根节点上调用 `TSCATTER` 属于未定义行为。 + +**大 Tile 支持**:当每 rank 的数据在行和/或列方向超出 UB Tile 容量时,传输将通过二维滑动自动分块。 + +## 数学语义 + +本地源 tensor 的形状为 $(D_0, D_1, D_2, N \times H, W)$,其中 $N$ 为 rank 总数,每个 rank 接收 $H$ 行。操作完成后: + +$$\mathrm{dst}^{(r)}_{d_0, d_1, d_2,\; i,\; j} = \mathrm{src}^{\mathrm{local}}_{d_0, d_1, d_2,\; r \cdot H + i,\; j} \quad \forall\, r \in [0, N),\; i \in [0, H),\; j \in [0, W)$$ + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../../assembly/PTO-AS_zh.md)。 + +同步形式: + +```text +tscatter %group, %src : (!pto.group<...>, !pto.memref<...>) +``` + +降级时会为 GM→UB→GM 数据路径引入 UB 暂存 Tile;C++ 内建接口需要显式传入 `stagingTileData`(或 `pingTile` / `pongTile`)操作数。 + +## C++ 内建接口 + +声明于 `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +// 基础 scatter(单暂存 Tile) +template +PTO_INST RecordEvent TSCATTER(ParallelGroupType ¶llelGroup, GlobalSrcData &srcGlobalData, + TileData &stagingTileData, WaitEvents&... events); + +// 乒乓 scatter(使用两个暂存 Tile 实现双缓冲) +template +PTO_INST RecordEvent TSCATTER(ParallelGroupType ¶llelGroup, GlobalSrcData &srcGlobalData, + TileData &pingTile, TileData &pongTile, WaitEvents&... events); +``` + +## 约束 + +- **类型约束**: + - `ParallelGroup::value_type::RawDType` 必须等于 `GlobalSrcData::RawDType`。 + - `TileData::DType` 必须等于 `GlobalSrcData::RawDType`。 +- **内存约束**: + - `srcGlobalData` 必须指向本地内存(当前 NPU),且足够容纳所有 rank 的数据。具体要求:`srcGlobalData.GetShape(DIM_3)` 必须 $\geq N \times H$,其中 $H$ 为每个 rank 的 `GetShape(DIM_3)`。 + - 若 `srcGlobalData.GetShape(DIM_3) > N × H`,则只读取前 `N × H` 行,其余行被忽略。 + - `stagingTileData`(或 `pingTile` / `pongTile`)必须预先在 UB 中分配。 +- **ParallelGroup 约束**: + - `parallelGroup.tensors[r]` 必须指向 rank `r` 的目标缓冲区(从根节点视角看到的远端 GM)。 + - `parallelGroup.GetRootIdx()` 标识调用方 NPU 为 scatter 根节点。 + - 所有目标 tensor 假定具有相同的形状和步幅;否则行为未定义。 +- **分块模式约束**(每 rank 数据超出单个 UB Tile 时): + - 若 `TileData` 具有静态 `ValidRow`,则每个 rank 目标数据的 `GetShape(DIM_3)` 必须能被 `ValidRow` 整除。如需支持不足一行的情况,请使用 `DYNAMIC` ValidRow 的 Tile。 + - 若 `TileData` 具有静态 `ValidCol`,则 `GetShape(DIM_4)` 必须能被 `ValidCol` 整除。如需支持不足一列的情况,请使用 `DYNAMIC` ValidCol 的 Tile。 + +## 示例 + +### 基础 Scatter(单暂存 Tile) + +根节点拥有 `NRANKS * ROWS` 行、宽度为 `COLS` 的数据,每个 rank 接收 `ROWS × COLS`,沿 DIM_3 拆分。 +Tile 大小可小于每 rank 的数据——此时实现会自动通过二维滑动进行分块传输。 + +```cpp +#include + +using namespace pto; + +template +void scatter(__gm__ T* local_data, __gm__ T* group_addrs[NRANKS], int my_rank) { + using TileT = Tile; + using GPerRank = GlobalTensor, + BaseShape2D, Layout::ND>; + using GSource = GlobalTensor, + BaseShape2D, Layout::ND>; + + GPerRank tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) tensors[i] = GPerRank(group_addrs[i]); + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GSource srcG(local_data); + TileT stagingTile(TILE_ROWS, TILE_COLS); + comm::TSCATTER(group, srcG, stagingTile); +} +``` + +### 乒乓 Scatter(双缓冲) + +使用两个 UB Tile,将下一块的 TLOAD(MTE2)与当前块的 TSTORE(MTE3)重叠执行。 + +```cpp +#include + +using namespace pto; + +template +void scatter_pingpong(__gm__ T* local_data, __gm__ T* group_addrs[NRANKS], int my_rank) { + using TileT = Tile; + using GPerRank = GlobalTensor, + BaseShape2D, Layout::ND>; + using GSource = GlobalTensor, + BaseShape2D, Layout::ND>; + + GPerRank tensors[NRANKS]; + for (int i = 0; i < NRANKS; ++i) tensors[i] = GPerRank(group_addrs[i]); + + comm::ParallelGroup group(tensors, NRANKS, my_rank); + GSource srcG(local_data); + TileT pingTile(TILE_ROWS, TILE_COLS); + TileT pongTile(TILE_ROWS, TILE_COLS); + // 乒乓模式:将 TLOAD 与 TSTORE 重叠执行以提升吞吐量 + comm::TSCATTER(group, srcG, pingTile, pongTile); +} +``` + diff --git a/designs/outerCube/PTOISA/comm/TTEST.md b/designs/outerCube/PTOISA/comm/TTEST.md new file mode 100644 index 00000000..184f1f43 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TTEST.md @@ -0,0 +1,150 @@ +# TTEST + +## Introduction + +Non-blocking test if signal(s) meet comparison condition. Returns `true` if condition is satisfied, `false` otherwise. Used for polling-based synchronization with timeout or interleaved work. + +Supports single signal or multi-dimensional signal tensor (up to 5-D, shape derived from GlobalTensor). For tensor, returns `true` only if ALL signals meet the condition. + +## Math Interpretation + +Test and return result: + +Single signal: + +$$ \mathrm{result} = (\mathrm{signal} \;\mathtt{cmp}\; \mathrm{cmpValue}) $$ + +Signal tensor (all must satisfy): + +$$ \mathrm{result} = \bigwedge_{d_0, d_1, d_2, d_3, d_4} (\mathrm{signal}_{d_0, d_1, d_2, d_3, d_4} \;\mathtt{cmp}\; \mathrm{cmpValue}) $$ + +where `cmp` ∈ {`EQ`, `NE`, `GT`, `GE`, `LT`, `LE`} + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../../assembly/PTO-AS.md). + +```text +%result = ttest %signal, %cmp_value {cmp = #pto.cmp} : (!pto.memref, i32) -> i1 +%result = ttest %signal_matrix, %cmp_value {cmp = #pto.cmp} : (!pto.memref, i32) -> i1 +``` + +## C++ Intrinsic + +Declared in `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +template +PTO_INST bool TTEST(GlobalSignalData &signalData, int32_t cmpValue, WaitCmp cmp, WaitEvents&... events); +``` + +## Constraints + +- **Type constraints**: + - `GlobalSignalData::DType` must be `int32_t` (32-bit signal). +- **Memory constraints**: + - `signalData` must point to local address (on current NPU). +- **Return value**: + - Returns `true` if condition is satisfied, `false` otherwise. + - For signal tensor, returns `true` only if ALL signals satisfy the condition. +- **Shape semantics**: + - For single signal: Shape is `<1,1,1,1,1>`. + - For signal tensor: Shape determines the multi-dimensional region (up to 5-D) to test. +- **Comparison operators** (WaitCmp): + | Value | Condition | + |-------|-----------| + | `EQ` | `signal == cmpValue` | + | `NE` | `signal != cmpValue` | + | `GT` | `signal > cmpValue` | + | `GE` | `signal >= cmpValue` | + | `LT` | `signal < cmpValue` | + | `LE` | `signal <= cmpValue` | + +## Examples + +### Basic Test + +```cpp +#include + +using namespace pto; + +bool check_ready(__gm__ int32_t* local_signal) { + comm::Signal sig(local_signal); + + // Check if signal == 1 + return comm::TTEST(sig, 1, comm::WaitCmp::EQ); +} +``` + +### Test Signal Matrix + +```cpp +#include + +using namespace pto; + +// Test if all signals from a 4x8 dense grid of workers are ready +bool check_worker_grid(__gm__ int32_t* signal_matrix) { + comm::Signal2D<4, 8> grid(signal_matrix); + + // Returns true only if all 32 signals == 1 + return comm::TTEST(grid, 1, comm::WaitCmp::EQ); +} +``` + +### Polling with Timeout + +```cpp +#include + +using namespace pto; + +bool poll_with_timeout(__gm__ int32_t* local_signal, int max_iterations) { + comm::Signal sig(local_signal); + + for (int i = 0; i < max_iterations; ++i) { + if (comm::TTEST(sig, 1, comm::WaitCmp::EQ)) { + return true; // Signal received + } + // Could do other work here between polls + } + return false; // Timeout +} +``` + +### Progress-Based Polling + +```cpp +#include + +using namespace pto; + +void process_with_progress(__gm__ int32_t* local_counter, int expected_count) { + comm::Signal counter(local_counter); + + while (!comm::TTEST(counter, expected_count, comm::WaitCmp::GE)) { + // Do some useful work while waiting + // ... + } + // All expected signals received +} +``` + +### Compare TWAIT vs TTEST + +```cpp +#include + +using namespace pto; + +void compare_wait_test(__gm__ int32_t* local_signal) { + comm::Signal sig(local_signal); + + // Blocking: spins until signal == 1 + comm::TWAIT(sig, 1, comm::WaitCmp::EQ); + + // Non-blocking: returns immediately with result + bool ready = comm::TTEST(sig, 1, comm::WaitCmp::EQ); +} +``` diff --git a/designs/outerCube/PTOISA/comm/TTEST_zh.md b/designs/outerCube/PTOISA/comm/TTEST_zh.md new file mode 100644 index 00000000..1b71740a --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TTEST_zh.md @@ -0,0 +1,151 @@ +# TTEST + +## 简介 + +非阻塞检测信号是否满足比较条件。满足则返回 `true`,否则返回 `false`。适用于基于轮询的同步(含超时)或与其他工作交错执行的场景。 + +支持单个信号或多维信号 tensor(最高 5 维,形状由 GlobalTensor 决定)。对于 tensor,仅当**所有**信号均满足条件时才返回 `true`。 + +## 数学语义 + +检测并返回结果: + +单个信号: + +$$\mathrm{result} = (\mathrm{signal} \;\mathtt{cmp}\; \mathrm{cmpValue})$$ + +信号 tensor(所有元素均须满足): + +$$\mathrm{result} = \bigwedge_{d_0, d_1, d_2, d_3, d_4} (\mathrm{signal}_{d_0, d_1, d_2, d_3, d_4} \;\mathtt{cmp}\; \mathrm{cmpValue})$$ + +其中 `cmp` ∈ {`EQ`, `NE`, `GT`, `GE`, `LT`, `LE`} + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../../assembly/PTO-AS_zh.md)。 + +```text +%result = ttest %signal, %cmp_value {cmp = #pto.cmp} : (!pto.memref, i32) -> i1 +%result = ttest %signal_matrix, %cmp_value {cmp = #pto.cmp} : (!pto.memref, i32) -> i1 +``` + +## C++ 内建接口 + +声明于 `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +template +PTO_INST bool TTEST(GlobalSignalData &signalData, int32_t cmpValue, WaitCmp cmp, WaitEvents&... events); +``` + +## 约束 + +- **类型约束**: + - `GlobalSignalData::DType` 必须为 `int32_t`(32 位信号)。 +- **内存约束**: + - `signalData` 必须指向本地地址(当前 NPU)。 +- **返回值**: + - 条件满足时返回 `true`,否则返回 `false`。 + - 对于信号 tensor,仅当所有信号均满足条件时才返回 `true`。 +- **形状语义**: + - 单个信号:形状为 `<1,1,1,1,1>`。 + - 信号 tensor:形状决定要检测的多维区域(最高 5 维)。 +- **比较运算符**(WaitCmp): + | 值 | 条件 | + |-------|--------| + | `EQ` | `signal == cmpValue` | + | `NE` | `signal != cmpValue` | + | `GT` | `signal > cmpValue` | + | `GE` | `signal >= cmpValue` | + | `LT` | `signal < cmpValue` | + | `LE` | `signal <= cmpValue` | + +## 示例 + +### 基础检测 + +```cpp +#include + +using namespace pto; + +bool check_ready(__gm__ int32_t* local_signal) { + comm::Signal sig(local_signal); + + // 检测 signal == 1 + return comm::TTEST(sig, 1, comm::WaitCmp::EQ); +} +``` + +### 检测信号矩阵 + +```cpp +#include + +using namespace pto; + +// 检测 4x8 网格中所有 worker 的信号是否就绪 +bool check_worker_grid(__gm__ int32_t* signal_matrix) { + comm::Signal2D<4, 8> grid(signal_matrix); + + // 仅当所有 32 个信号均为 1 时返回 true + return comm::TTEST(grid, 1, comm::WaitCmp::EQ); +} +``` + +### 带超时的轮询 + +```cpp +#include + +using namespace pto; + +bool poll_with_timeout(__gm__ int32_t* local_signal, int max_iterations) { + comm::Signal sig(local_signal); + + for (int i = 0; i < max_iterations; ++i) { + if (comm::TTEST(sig, 1, comm::WaitCmp::EQ)) { + return true; // 收到信号 + } + // 两次轮询之间可执行其他工作 + } + return false; // 超时 +} +``` + +### 基于进度的轮询 + +```cpp +#include + +using namespace pto; + +void process_with_progress(__gm__ int32_t* local_counter, int expected_count) { + comm::Signal counter(local_counter); + + while (!comm::TTEST(counter, expected_count, comm::WaitCmp::GE)) { + // 等待期间执行其他有用工作 + // ... + } + // 所有预期信号均已收到 +} +``` + +### TWAIT 与 TTEST 对比 + +```cpp +#include + +using namespace pto; + +void compare_wait_test(__gm__ int32_t* local_signal) { + comm::Signal sig(local_signal); + + // 阻塞:自旋直到 signal == 1 + comm::TWAIT(sig, 1, comm::WaitCmp::EQ); + + // 非阻塞:立即返回结果 + bool ready = comm::TTEST(sig, 1, comm::WaitCmp::EQ); +} +``` + diff --git a/designs/outerCube/PTOISA/comm/TWAIT.md b/designs/outerCube/PTOISA/comm/TWAIT.md new file mode 100644 index 00000000..4d9af8d8 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TWAIT.md @@ -0,0 +1,131 @@ +# TWAIT + +## Introduction + +Blocking wait until signal(s) meet comparison condition. Used in conjunction with `TNOTIFY` for flag-based synchronization. + +Supports single signal or multi-dimensional signal tensor (up to 5-D, shape derived from GlobalTensor). + + +## Math Interpretation + +Wait (spin) until the following condition is satisfied: + +Single signal: + +$$ \mathrm{signal} \;\mathtt{cmp}\; \mathrm{cmpValue} $$ + +Signal tensor (all elements must satisfy): + +$$ \forall d_0, d_1, d_2, d_3, d_4: \mathrm{signal}_{d_0, d_1, d_2, d_3, d_4} \;\mathtt{cmp}\; \mathrm{cmpValue} $$ + +where `cmp` ∈ {`EQ`, `NE`, `GT`, `GE`, `LT`, `LE`} + +## Assembly Syntax + +PTO-AS form: see [PTO-AS Specification](../../assembly/PTO-AS.md). + +```text +twait %signal, %cmp_value {cmp = #pto.cmp} : (!pto.memref, i32) +twait %signal_matrix, %cmp_value {cmp = #pto.cmp} : (!pto.memref, i32) +``` + +## C++ Intrinsic + +Declared in `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +template +PTO_INST void TWAIT(GlobalSignalData &signalData, int32_t cmpValue, WaitCmp cmp, WaitEvents&... events); +``` + +## Constraints + +- **Type constraints**: + - `GlobalSignalData::DType` must be `int32_t` (32-bit signal). +- **Memory constraints**: + - `signalData` must point to local address (on current NPU). +- **Shape semantics**: + - For single signal: Shape is `<1,1,1,1,1>`. + - For signal tensor: Shape determines the multi-dimensional region (up to 5-D) to wait on. All signals in the tensor must satisfy the condition. +- **Comparison operators** (WaitCmp): + | Value | Condition | + |-------|-----------| + | `EQ` | `signal == cmpValue` | + | `NE` | `signal != cmpValue` | + | `GT` | `signal > cmpValue` | + | `GE` | `signal >= cmpValue` | + | `LT` | `signal < cmpValue` | + | `LE` | `signal <= cmpValue` | + +## Examples + +### Wait for Single Signal + +```cpp +#include + +using namespace pto; + +void wait_for_ready(__gm__ int32_t* local_signal) { + comm::Signal sig(local_signal); + + // Wait until signal == 1 + comm::TWAIT(sig, 1, comm::WaitCmp::EQ); +} +``` + +### Wait for Signal Matrix + +```cpp +#include + +using namespace pto; + +// Wait for signals from a 4x8 dense grid of workers +void wait_worker_grid(__gm__ int32_t* signal_matrix) { + comm::Signal2D<4, 8> grid(signal_matrix); + + // Wait until all 32 signals == 1 + comm::TWAIT(grid, 1, comm::WaitCmp::EQ); +} +``` + +### Wait for Counter Threshold + +```cpp +#include + +using namespace pto; + +void wait_for_count(__gm__ int32_t* local_counter, int expected_count) { + comm::Signal counter(local_counter); + + // Wait until counter >= expected_count + comm::TWAIT(counter, expected_count, comm::WaitCmp::GE); +} +``` + +### Producer-Consumer Pattern + +```cpp +#include + +using namespace pto; + +// Producer: notify when data is ready +void producer(__gm__ int32_t* remote_flag) { + // ... produce data ... + + comm::Signal flag(remote_flag); + comm::TNOTIFY(flag, 1, comm::NotifyOp::Set); +} + +// Consumer: wait for data +void consumer(__gm__ int32_t* local_flag) { + comm::Signal flag(local_flag); + comm::TWAIT(flag, 1, comm::WaitCmp::EQ); + + // ... consume data ... +} +``` \ No newline at end of file diff --git a/designs/outerCube/PTOISA/comm/TWAIT_zh.md b/designs/outerCube/PTOISA/comm/TWAIT_zh.md new file mode 100644 index 00000000..0ed76431 --- /dev/null +++ b/designs/outerCube/PTOISA/comm/TWAIT_zh.md @@ -0,0 +1,131 @@ +# TWAIT + +## 简介 + +阻塞等待,直到信号满足比较条件。与 `TNOTIFY` 配合使用,实现基于标志的同步。 + +支持单个信号或多维信号 tensor(最高 5 维,形状由 GlobalTensor 决定)。 + +## 数学语义 + +自旋等待,直到以下条件满足: + +单个信号: + +$$\mathrm{signal} \;\mathtt{cmp}\; \mathrm{cmpValue}$$ + +信号 tensor(所有元素均须满足): + +$$\forall d_0, d_1, d_2, d_3, d_4: \mathrm{signal}_{d_0, d_1, d_2, d_3, d_4} \;\mathtt{cmp}\; \mathrm{cmpValue}$$ + +其中 `cmp` ∈ {`EQ`, `NE`, `GT`, `GE`, `LT`, `LE`} + +## 汇编语法 + +PTO-AS 形式:参见 [PTO-AS 规范](../../assembly/PTO-AS_zh.md)。 + +```text +twait %signal, %cmp_value {cmp = #pto.cmp} : (!pto.memref, i32) +twait %signal_matrix, %cmp_value {cmp = #pto.cmp} : (!pto.memref, i32) +``` + +## C++ 内建接口 + +声明于 `include/pto/comm/pto_comm_inst.hpp`: + +```cpp +template +PTO_INST void TWAIT(GlobalSignalData &signalData, int32_t cmpValue, WaitCmp cmp, WaitEvents&... events); +``` + +## 约束 + +- **类型约束**: + - `GlobalSignalData::DType` 必须为 `int32_t`(32 位信号)。 +- **内存约束**: + - `signalData` 必须指向本地地址(当前 NPU)。 +- **形状语义**: + - 单个信号:形状为 `<1,1,1,1,1>`。 + - 信号 tensor:形状决定要等待的多维区域(最高 5 维)。tensor 中所有信号必须满足条件。 +- **比较运算符**(WaitCmp): + | 值 | 条件 | + |-------|--------| + | `EQ` | `signal == cmpValue` | + | `NE` | `signal != cmpValue` | + | `GT` | `signal > cmpValue` | + | `GE` | `signal >= cmpValue` | + | `LT` | `signal < cmpValue` | + | `LE` | `signal <= cmpValue` | + +## 示例 + +### 等待单个信号 + +```cpp +#include + +using namespace pto; + +void wait_for_ready(__gm__ int32_t* local_signal) { + comm::Signal sig(local_signal); + + // 等待 signal == 1 + comm::TWAIT(sig, 1, comm::WaitCmp::EQ); +} +``` + +### 等待信号矩阵 + +```cpp +#include + +using namespace pto; + +// 等待 4x8 网格中所有 worker 的信号就绪 +void wait_worker_grid(__gm__ int32_t* signal_matrix) { + comm::Signal2D<4, 8> grid(signal_matrix); + + // 等待所有 32 个信号均为 1 + comm::TWAIT(grid, 1, comm::WaitCmp::EQ); +} +``` + +### 等待计数器阈值 + +```cpp +#include + +using namespace pto; + +void wait_for_count(__gm__ int32_t* local_counter, int expected_count) { + comm::Signal counter(local_counter); + + // 等待 counter >= expected_count + comm::TWAIT(counter, expected_count, comm::WaitCmp::GE); +} +``` + +### 生产者-消费者模式 + +```cpp +#include + +using namespace pto; + +// 生产者:数据就绪后发送通知 +void producer(__gm__ int32_t* remote_flag) { + // ... 生产数据 ... + + comm::Signal flag(remote_flag); + comm::TNOTIFY(flag, 1, comm::NotifyOp::Set); +} + +// 消费者:等待数据就绪 +void consumer(__gm__ int32_t* local_flag) { + comm::Signal flag(local_flag); + comm::TWAIT(flag, 1, comm::WaitCmp::EQ); + + // ... 消费数据 ... +} +``` + diff --git a/designs/outerCube/PTOISA/conventions.md b/designs/outerCube/PTOISA/conventions.md new file mode 100644 index 00000000..c1d9dd82 --- /dev/null +++ b/designs/outerCube/PTOISA/conventions.md @@ -0,0 +1,41 @@ +# PTO ISA Conventions + +This page defines shared conventions used by the per-instruction ISA reference pages in `docs/isa/` and the corresponding C++ intrinsics in `include/pto/common/pto_instr.hpp`. + +## Notation + +- **Tile**: A fixed-size on-chip tile object (e.g., `pto::Tile<...>`). Many instructions operate on tiles and use the tile’s valid region (`GetValidRow()`, `GetValidCol()`). +- **GM (global memory)**: Off-chip memory accessed via `pto::GlobalTensor<...>`. +- **Scalar / immediate**: A host-side scalar value or an encoded immediate used by `*S` / `*C` variants. + +For the detailed C++ programming model behind these terms, see: + +- Tiles: `docs/coding/Tile.md` +- GlobalTensor: `docs/coding/GlobalTensor.md` +- Scalars and enums: `docs/coding/Scalar.md` + +## Shapes and layouts + +- **Row-major vs. column-major**: Unless stated otherwise, CPU simulator kernels assume row-major tiles. Instructions that support multiple layouts will state supported layouts explicitly. +- **Valid region**: The runtime compute region of a tile, expressed as `(valid_row, valid_col)` and queried via `GetValidRow()` / `GetValidCol()`. + +### Valid Region Semantics + +For instruction pages, when we say “for each element `(i, j)` in the valid region”, we mean: + +- `valid_row = dst.GetValidRow()` and `valid_col = dst.GetValidCol()` unless the instruction explicitly defines a different domain (e.g., some ops may use the source tile’s valid region). +- The math interpretation defines `dst[i, j]` only for indices where `0 <= i < valid_row` and `0 <= j < valid_col`. +- Elements outside the valid region are **unspecified** unless the instruction explicitly states otherwise (do not assume they are zeroed or preserved). + +For multi-operand instructions (e.g., `src0`, `src1`), the docs assume the input tiles are compatible with the iteration domain unless the constraints section states stricter requirements. + +## Types + +- The instruction page lists supported data types (e.g., `fp16`, `fp32`, `int8`, `int16`, `int32`, `uint8`, `uint16`, `uint32`). CPU simulator support may be a subset and is documented in `include/README.md`. + +## Events and synchronization + +- Instructions may require ordering between memory and vector pipelines. When examples show events (e.g., `set_flag(...)` / `wait_flag(...)`), they indicate the required ordering constraints on the target backend. +- `TSYNC` is used for explicit synchronization when needed by a sequence of instructions. + +See `docs/coding/Event.md` for the event model used by PTO Tile Lib. diff --git a/designs/outerCube/PTOISA/conventions_zh.md b/designs/outerCube/PTOISA/conventions_zh.md new file mode 100644 index 00000000..e7a292e4 --- /dev/null +++ b/designs/outerCube/PTOISA/conventions_zh.md @@ -0,0 +1,42 @@ +# PTO ISA 通用约定 + +本页定义 `docs/isa/` 指令参考文档中使用的通用术语与写法,并与 `include/pto/common/pto_instr.hpp` 中的 C++ 内建接口保持一致。 + +## 记号 + +- **Tile**:片上二维操作数对象(例如 `pto::Tile<...>`)。大量指令以 Tile 作为输入/输出,并通过 `GetValidRow()` / `GetValidCol()` 使用 Tile 的有效区域(valid region)。 +- **GM(全局内存)**:通过 `pto::GlobalTensor<...>` 访问的片外内存视图。 +- **标量 / 立即数**:主机侧标量值,或在 `*S` / `*C` 等变体中编码的立即数参数。 + +关于这些对象的 C++ 编程模型(类型、布局、枚举、约束等),可参考: + +- Tile:`docs/coding/Tile_zh.md` +- GlobalTensor:`docs/coding/GlobalTensor_zh.md` +- 标量与枚举:`docs/coding/Scalar_zh.md` + +## 形状与布局 + +- **行主序 / 列主序**:除非指令页明确声明支持多种布局,否则示例与参考实现默认假设为行主序 Tile。支持多布局的指令会在约束小节中列出具体要求。 +- **有效区域(valid region)**:Tile 运行时计算域,通常写作 `(valid_row, valid_col)`,并通过 `GetValidRow()` / `GetValidCol()` 查询。 + +### 有效区域语义 + +在指令页中,当我们写“对有效区域内的每个元素 `(i, j)`”,含义为: + +- 除非指令显式定义不同的迭代域,否则默认使用 `valid_row = dst.GetValidRow()`、`valid_col = dst.GetValidCol()`。 +- 数学语义仅对 `0 <= i < valid_row` 且 `0 <= j < valid_col` 的 `dst[i, j]` 做出定义。 +- 有效区域之外元素的值为**未指定**,除非指令页明确说明(不要假设一定清零或保持不变)。 + +对多输入指令(例如 `src0`、`src1`),除非约束小节有更严格的要求,文档默认输入 Tile 与迭代域在形状/有效区域上是兼容的。 + +## 数据类型 + +每条指令页会列出支持的数据类型(例如 `fp16`、`fp32`、`int8`、`int16`、`int32`、`uint8`、`uint16`、`uint32` 等)。 +不同后端/目标对数据类型与布局支持可能不同,具体以对应实现与编译期检查为准。 + +## 事件与同步 + +- 某些指令序列需要建立内存与向量流水线之间的顺序关系。示例中出现的事件(例如 `set_flag(...)` / `wait_flag(...)`)用于表达后端需要满足的顺序约束。 +- 在需要显式同步的场景,使用 `TSYNC` 建立阶段间的顺序关系。 + +事件模型可参考:`docs/coding/Event_zh.md`。 diff --git a/designs/outerCube/vector4k.md b/designs/outerCube/vector4k.md new file mode 100644 index 00000000..6b3fb228 --- /dev/null +++ b/designs/outerCube/vector4k.md @@ -0,0 +1,962 @@ +# VEC-4K: Vector Unit for 4 KB PTO Tiles (PTO ISA Subset) + +## 1. Purpose and Scope + +This document specifies a **vector execution unit (VEC-4K)** that implements a **software-visible subset** of the PTO Tile Lib ISA ([`PTOISA/README.md`](PTOISA/README.md)): elementwise tile–tile ops, tile–scalar ops, axis reduce/expand, and selected **complex** instructions (e.g. **TMRGSORT**, **TSORT32**, **TGATHER**, **TCI**). The unit is paired with a **tile register file (TRegFile)** holding **4 KB** tiles. + +**Non-goals (this document):** matrix multiply (**TMATMUL** / **TGEMV** family), global-memory **TLOAD/TSTORE** (treated as separate DMA-like paths), and **comm** collective ISA—only the on-tile vector datapath is analyzed here. + +--- + +## 2. Tile and Format Model + +### 2.1 Storage Invariant + +Each logical tile occupies exactly **4096 bytes** in the TRegFile. The logical shape is **R × C** with: + +- **R** and **C** are powers of two (implementation may also require R·C to match the element count implied by the format). +- **Row-major** layout: address increases along columns within a row, then along rows. + +Let **E** be the **storage bytes per logical element** in the chosen encoding: + +| Logical format | Typical storage `E` (bytes / element) | Elements per 4 KB tile (N = 4096 / E) | +|----------------|----------------------------------------|----------------------------------------| +| FP32 | 4 | 1024 | +| FP16 / BF16 | 2 | 2048 | +| FP8 (E4M3/E5M2)| 1 | 4096 | +| MXFP4 / HiFP4 | ½ (packed nibble pair in byte) | 8192 | + +For **sub-byte** types, hardware views memory as **byte lanes**; unpack/pack stages map nibbles to wider internal operands (e.g. FP16/FP32) for ALU operations, then pack back on write. + +**Valid shape examples** (illustrative): + +- FP32: 32×32, 16×64, 64×16, … (R·C = 1024). +- FP16: 64×32, 32×64, 128×16, … (R·C = 2048). +- FP8: 64×64, 128×32, … (R·C = 4096). +- FP4: 128×64, 256×32, … (R·C = 8192). + +### 2.2 Metadata + +Each issued vector op carries **format**, **R**, **C**, and **opcode**. Microcode (or a small FSM per op) derives: + +- `strip_count = 4096 / 512 = 8` **physical strips** per tile (fixed by §3). +- `elem_per_strip = 512 / E` (must be integer; hardware asserts this from format). + +--- + +## 3. TRegFile Interface and Striping + +### 3.1 Ports (Design Assumption) + +| Direction | Width | Count | Aggregate | +|-----------|-------|-------|-----------| +| Read | 512 B | 2 | **1024 B/cycle** | +| Write | 512 B | 2 | **1024 B/cycle** | + +Ports may target **independent tile bases** (e.g. `src0` and `src1`) or **the same tile** at different offsets (double-pump one operand into two buffers). + +When VEC is attached to [`tregfile4k.md`](tregfile4k.md), a concrete dual-read binding is **Rd0 → R0** (**Port A**, phase **0**) and **Rd1 → R4** (**Port B**, phase **4**) so **same `tile_idx`** yields strip pair **`(G,e)`** and **`(G+4,e)`** at epoch phase **e** (**§4.4**). + +**Read semantics:** TRegFile read ports present **only** full **512 B** bank-group strips per [`tregfile4k.md`](tregfile4k.md) — **no gather** (no sub-strip element indexing inside the RF). **`TCOL*`** therefore **replays** the same `reg_idx` over **multiple cycles** / **epochs** as needed; **column extraction** is done in **VEC** from **strip buffers A/B** after each read (**§5.3.2**, **§4.4 Example H**). + +### 3.2 Physical Strip + +A **strip** is a contiguous **512-byte** chunk at offset `s·512` for `s ∈ {0,…,7}` within the 4 KB tile. This matches one port transaction per strip. + +**Minimum streaming latency (full tile), ignoring bank conflicts:** + +- **Unary** (both read ports read **the same logical tile** at consecutive strip offsets): **2 strips/cycle** × **512 B** = **1024 B/cycle** → **4096 / 1024 = 4 cycles** to read one full tile. +- **Binary elementwise** (typical: **Rd0 → src0** strip `s`, **Rd1 → src1** strip `s`): **1 strip per operand per cycle** → **8 strips/operand** → **8 cycles** to ingest **both** full tiles. Software/hardware can **reuse** a buffered operand (e.g. **op reuse buffer**) to hide half of the reads on back-to-back dependent ops. + +The micro-architecture below assumes **8 strip indices** per 4 KB tile and schedules **cross-strip** work where reductions, expands, or gathers require it. + +### 3.3 On-Chip Buffers + +- **A strip buffer** and **B strip buffer** (512 B each, **Rd0/Rd1**), optionally **double-buffered** to overlap TRegFile read with compute; **§4.1** **crossbar** ingests **1024 B/cycle** from the two ports. +- **Acc** — **256 × 32 b × 2** ping-pong (**`N_run = 512`**, **§4.1**, **§9.3.2**); optional **strip / staging** for multi-pass sorts; **§4.3** schedules **Rd0/Rd1** vs **`fiber_id`** / **Acc** updates. +- **Scalar broadcast register** (tile–scalar immediates or a single-element tile). + +--- + +## 4. Vector Datapath Overview + +### 4.1 Block Diagram (dataflow level) + +**Reference micro-architecture** (counts for **scheduling** / **§5.3.2** **`N_tree`**): + +```text + ┌────────────────────────────────────────────────────────────────────┐ + │ TRegFile (4 KB tiles) │ + │ Rd0 (512 B) Rd1 (512 B) │ + └──────────────────┬──────────────────────────┬──────────────────────┘ + │ │ + └──────────┬───────────────┘ + │ 1024 B aggregate / cycle (2×512 B) + ▼ + ┌────────────────────────────────────────────────────────────────────┐ + │ Instruction opcode + shape (format, R, C, …) ──► CONTROL │ + └────────────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────┐ + │ CROSSBAR │ + │ 1024 B in → distribute to compute │ + └─────────────────┬───────────────────┘ + │ + ┌─────────────────▼───────────────────────────────────────────┐ + │ (A) ALIGN / UNPACK / PERMUTE (control-selected) │ + │ IN: 1024 B / cycle from crossbar │ + │ OUT: 128 slices (slice i width = W_prep,i bits) │ + └─────────────────┬───────────────────────────────────────────┘ + │ 128 parallel slice buses + ▼ + ┌─────────────────▼───────────────────────────────────────────┐ + │ (B) N_group = 128 INDEPENDENT COMPUTE GROUPS i = 0…127 │ + │ ┌─────────────────────────────────────────────────────┐ │ + │ │ Group i (representative): │ │ + │ │ IN: W_prep,i (from A) │ │ + │ │ ┌──────────────────┐ ┌────────────────────┐ │ │ + │ │ │ Elementwise ALU │ ───► │ Reduction tree │ │ │ + │ │ │ OUT: W_ALU,i │ │ OUT: W_tree,i │ │ │ + │ │ └──────────────────┘ └─────────┬──────────┘ │ │ + │ │ Constraint: W_ALU,i ≥ W_tree,i (allowed; tree │ │ + │ │ narrows / retires partials; ALU may be wide SIMD) │ │ + │ │ Elementwise bypass: ALU OUT can skip deep tree │ │ + │ │ (W_tree,i = W_ALU,i, or tree depth 0) when opcode │ │ + │ │ does not need cross-lane combine on that group. │ │ + │ └─────────────────────────────────────────────────────┘ │ + │ Typical: W_tree,i = 32 b (FP32-shaped partial → Acc) │ + └─────────────────┬───────────────────────────────────────────┘ + │ 128 × W_tree,i (subset to Acc per beat) + ▼ + ┌─────────────────────────────────────┐ + │ ACCUMULATOR (DFF, ping-pong) │ + │ 256 × 32 b × 2 halves ≈ 2048 B │ + │ per slot: DFF + optional combine │ + │ • RMW: adder (new ⊕ feedback DFF) │ + │ • BYPASS: new → DFF (no combine) │ + │ N_run = 512 logical slots (§9.3.2) │ + └─────────────────┬───────────────────┘ + │ mux: one 256-word half + ▼ + ┌─────────────────────────────────────┐ + │ Pack (wide → FP8/FP4 / narrow dst) │ + └─────────────────┬───────────────────┘ + │ + ┌───────────▼───────────┐ + │ Wr0 (512 B) Wr1 (512 B) │ + │ = 1024 B retire / phase │ + └───────────────────────────┘ +``` + +**Flow:** **Rd0+Rd1** → **crossbar** (**1024 B/cycle**). **Control** drives **crossbar** routing, **(A)** unpack/permute **masks**, **(B)** per-group **ALU** opcode and **tree** depth (or **bypass**), and **Acc** addressing. **(A)** outputs **128** parallel **slices** of width **`W_prep,i`** (depends on **format** / **opcode** / **implementation**). **(B)** is **`N_group = 128`** identical **structures**: each slice feeds an **elementwise ALU** whose output has width **`W_ALU,i`**, then a **reduction tree** that may **narrow** to **`W_tree,i`**. **Allow `W_ALU,i ≥ W_tree,i`** (wide **SIMD** **ALU** **before** a **smaller** **tree** **footprint**). **Typical** partial to **Acc:** **`W_tree,i = 32` b** (FP32-shaped). **Pure elementwise** may use **tree depth 0** or **bypass** so **`W_tree,i = W_ALU,i`** for the path toward **Pack**. **Accumulator:** besides **RMW** (**partial ⊕** **stored** via **adder** **+** **DFF** **feedback**), **control** may **bypass** the **combine** **adder** and **write** **tree/ALU** **data** **straight** **into** the **Acc** **DFF** (**load** / **overwrite** / **non-accumulating** **retire**). **Acc** ping-pong, **Wr** half-select, **`fiber_id` / `ρ` / Acc waves:** **§5.3.2**, **§9.3.2**; **§4.3** calendars. + +### 4.2 “Lanes” vs “Strips” + +- **SIMD lane**: one parallel datapath processing **one logical element** after unpack (width depends on op; internal **FP32** is a reasonable unified width for expensive ops). +- **Strip**: 512 B of **spatially contiguous** storage; SIMD width = `elem_per_strip`. +- **Cross-lane** (within strip): reductions along a dimension that fits in one strip (partial row/col). +- **Cross-strip**: **control-programmed** **crossbar** + **trees** (**§4.1**) combine strip `s` and `s′` contributions, or **multi-cycle accumulation** into **Acc** ping-pong (**§9.3.2**) / **staging**. + +### 4.3 Fiber ID and strip read calendar + +**`fiber_id`** is the **logical index along the axis** that **reduce** and **expand/broadcast** class ops treat as a **fiber**—one **output slot** per fiber after a reduce, or one **scalar source** per fiber when expanding along that axis: + +| Opcode family | `fiber_id` | Range | +|---------------|------------|--------| +| **`TROW*`** (row reduce) | row index **`r`** | `0 … R−1` | +| **`TCOL*`** (column reduce) | column index **`c`** | `0 … C−1` | +| **`TROWEXPAND*`** | **`r`** (splat target row) | `0 … R−1` | +| **`TCOLEXPAND*`** | **`c`** (splat target column) | `0 … C−1` | + +**Elementwise** tile–tile ops do **not** use a single global **`fiber_id`**; they are scheduled **strip-by-strip** only. **Gather/sort/merge** use their own index streams; where they write **per-row/col** state, that state can still be keyed like **`fiber_id`** for buffer allocation. + +**From strip bytes to `(r, c)` and `fiber_id`:** For strip index **`s ∈ {0,…,7}`** and lane/byte offset inside the **512 B** chunk, **row-major** layout fixes a linear element order; decode **`(r, c)`** from **`(R, C, E)`**. Then **`fiber_id = r`** or **`c`** according to the opcode’s **axis**. **Control** drives the **crossbar** (**§4.1**) so **unpack**, **permutation**, and **tree/ALU** inputs see the correct segment for each **`fiber_id`** touched in that strip. + +**Strip read calendar:** A **calendar** is the **cycle-by-cycle** schedule that binds **what arrives on Rd0/Rd1** to **what the datapath does**—in particular, **which operands feed each lane** and **which `fiber_id`(s)** touch **Acc** or **per-fiber `v` buffers** that cycle. + +- **Port row (per cycle `t`):** specifies **`s(t)`** (which **512 B** chunk), **which logical tile** each port reads (**`src0`**, **`src1`**, narrow **`v`** tile, scalar tile, **ping-pong scratch** for merges/sorts, or **idle**), and optional **second-pass** phases. **`TCOL*`** does **not** use a **transpose scratchpad** — only **normal** `reg_idx` tiles in **row-major** strip order (**§5.3.2**). TRegFile ports **cannot gather** (**§3.1**); **`TCOL*`** may **repeat** the **same** `reg_idx` over **multiple TRegFile epochs** when **`#W = max(⌈C/N_acc⌉, ⌈C/(N_tree·f)⌉, ⌈C/N_run⌉) > 1`** (**§5.3.2**, **§4.4 Example H**; **`#W`** **reduces** to **two** **terms** **when** **`N_acc ≤ N_run`**). +- **Operand sources (`TROW*` reduce):** **Tile elements** arrive strip-serially from **read ports**; after **unpack → within-strip tree → cross-strip combine**, the reducer performs **RMW** on **Acc** at **`fiber_id = r`**. Physical slot **`ρ`** is **`fiber_id` remapped** into **`[0, N_run)`** for the current **Acc wave** (§9.3.2: **`bank = ρ mod 8`**, **`word = ρ >> 3`**, **0…63**). One strip can touch **many** distinct **`fiber_id`**s when **`row_B < 512`** or **`col_B < 512`** (many thin fibers per strip). +- **Operand sources (`TCOL*` reduce):** Ports still deliver **full rows** inside each **512 B** strip; **VEC** **selects** **`(r,c)`** for the scheduled **column band** from **strip buffers** (**no RF gather**). **Acc[`c`]** **+=** partial sums across strip-beats and, if needed, across **re-scans** of the tile. +- **Operand sources (expand):** **`v[fiber_id]`** is supplied from a **narrow per-fiber vector** streamed on a read port, from **Acc / staging** after an in-place reduce, or from a **small buffer** filled in a **prefetch** phase; **`src`** elements still arrive **strip-major** like §5.1. The calendar interleaves **`v`** strip reads with **`src`** strips so each cycle’s SIMD sees a consistent **`(fiber_id, lane)`** map. + +**Templates:** Opcode decode picks a **calendar template** from **`(format, R, C, opcode)`**. The §9 metrics **`rS`, `rW`, `rK`, …** / **`cS`, `cW`, …** fix how many **within-strip** rounds and how **8-strip** walks align with **cross-strip** merge. The **47** distinct scheduling recipes in **§9.5.1** are **calendar families** over the same datapath, not separate RTL blocks. **Epoch-aligned worked tables** vs [`tregfile4k.md`](tregfile4k.md): **§4.4**. + +**Illustrative calendar row** (one cycle of a row-reduce pass; details vary by shape): + +| Field | Content | +|-------|---------| +| **`t`** | Cycle index in the micro-sequence | +| **Rd0 / Rd1** | e.g. **`src` strip `s`**, second operand or **`idle`** | +| **`s`** | Current **strip index** `0…7` (or remapped pass) | +| **Lane → `(r,c)`** | From **§2.1** row-major map | +| **`fiber_id`s updated** | Subset of **`r`** appearing in this strip’s segment | +| **Acc** | **RMW** at decoded **bank/word** for each retiring partial | + +### 4.4 Epoch-aligned fiber calendars vs `tregfile4k.md` (four worked examples) + +Full **(format, shape)** enumeration would need **76** row-axis templates alone (§9); this subsection fixes **VEC ↔ TRegFile-4K** timing and shows **eight** representative **`fiber_id`** calendars (**Examples A–H**; **E–H** emphasize **FP8** and **MXFP4**). See [`tregfile4k.md`](tregfile4k.md): global **`e = cy[2:0]`** (phase within an **8-cycle** epoch); read port **Rp** presents bank-group **`G = (p + e) mod 8`** (**512 B** = one **strip** **Gs**). + +**Port binding for the tables:** + +| Logical name | TRegFile read port | Phase `p` | Strip delivered at phase `e` | +|--------------|-------------------|-----------|--------------------------------| +| **Port A** | **R0** | 0 | **`G_A = e`** | +| **Port B** | **R4** | 4 | **`G_B = (4 + e) mod 8`** | + +**Epoch start:** **`t = 0`** is an **epoch boundary** (**e = 0**): the **`reg_idx`** for each port is **active** for the next 8 cycles (pending→active promotion, `tregfile4k.md` §4). + +**Dual-port same `tile_idx` on A+B:** in cycles **`t = 0…3`** (**e = 0…3**), the pair **`(G_A, G_B)`** visits **`(0,4), (1,5), (2,6), (3,7)`** — every strip **Gs ∈ {0,…,7}** appears **exactly once** as **one** of the two 512 B beats. Cycles **`t = 4…7`** repeat the **same** strip schedule (second lap with the **same** latched tile on both ports); microcode **suppresses duplicate Acc** or reuses beats for **another operand** / **write path**. **Benefit:** **4 cycles** to see all **8** strips **once** with **two** 512 B reads/cycle vs **8 cycles** with **Port A only** (Port B **idle**). + +**Row-major element index** in a strip: byte offset **`512·Gs + δ`** with **`δ`** increasing along **columns** within the row segment; **`fiber_id = r`** for **`TROW*`** labels which **row**’s **C** elements are being reduced or expanded. + +**Column shorthand in tables:** + +| Column | Meaning | +|--------|---------| +| **`t`**, **`e`** | Core cycle from op **epoch start**; **`e = t mod 8`**. | +| **Port A / B** | **`T@Gs`** = tile **`reg_idx` T** and physical strip **Gs** (512 B). **`—`** = port unused this cycle. | +| **Fibers (this beat)** | **`fiber_id`** values whose **row data** (reduce) or **row op** (expand) is anchored on **that port’s** strip in this cycle. | +| **First elem @ port** | Logical **element** at **byte 0** of that port’s 512 B chunk for the listed fiber (start of the row segment in that strip). | +| **`#elem`** | **Logical elements along the row** (along **C**) taken from **that port** this cycle toward **`TROW*`** / expand **`src`**. | +| **Reduce / expand** | **`TROWSUM`:** cross-lane tree over **`#elem`** → **one** partial per **`fiber_id = r`** → **Acc RMW**. **`TCOL*`** (**§5.3.2**): **`fiber_id = c`**; **strip replay** + **VEC column mux** + **`Acc[c]`** **RMW**; **`P_beat = min(N_tree, N_acc)`**; **`#W = max(⌈C/N_acc⌉, ⌈C/(N_tree·f)⌉, ⌈C/N_run⌉)`** (**`f`**, **`N_run`**, **§5.3.2**; **`#W`** ≡ **`#waves`** **when** **`⌈C/N_run⌉ ≤ ⌈C/N_acc⌉`**) — **§4.4 Example H**. **`TROWEXPANDADD`:** for each **`fiber_id`**, combine **`#elem`** **`src`** lanes with **`v[fiber_id]`** (from **`v`** tile or latch). | + +*End-to-end latency* may add **tree pipeline stages** after the last ingest cycle; tables list **operand arrival** and **per-fiber arithmetic scope** per cycle. + +--- + +#### Example A — `TROWSUM`, **FP32**, **8×128** (`C = 128`, **one row = one strip**) + +**Geometry:** **`row_B = 512 B`**, strip **Gs** holds **exactly row `r = Gs`**. **Dual-port** same **`src`** tile on **A+B** gives **two rows/cycle** for **`t = 0…3`**; **`t = 4…7`** are duplicate strip delivery (masked). + +| `t` | `e` | Port A | Port B | Fibers (A) | First elem @ A | `#elem` | Fibers (B) | First elem @ B | `#elem` | Reduce note | +|----:|----:|--------|--------|------------|----------------|--------:|------------|----------------|--------:|-------------| +| 0 | 0 | `src@G0` | `src@G4` | `r=0` | elem `(0,0)` | 128 | `r=4` | elem `(4,0)` | 128 | 2× `TROWSUM` lane-tree → **Acc** `r=0`, `r=4` | +| 1 | 1 | `src@G1` | `src@G5` | `r=1` | `(1,0)` | 128 | `r=5` | `(5,0)` | 128 | **Acc** `r=1`, `r=5` | +| 2 | 2 | `src@G2` | `src@G6` | `r=2` | `(2,0)` | 128 | `r=6` | `(6,0)` | 128 | **Acc** `r=2`, `r=6` | +| 3 | 3 | `src@G3` | `src@G7` | `r=3` | `(3,0)` | 128 | `r=7` | `(7,0)` | 128 | **Acc** `r=3`, `r=7` | + +**Unique ingest complete at `t = 3`** (8 fibers, each **128** elements). **Single-port (A only):** stretch to **`t = 0…7`**, one row/cycle, **no** dual-port gain. + +--- + +#### Example B — `TROWSUM`, **FP32**, **32×32** (`C = 32`, **4 rows / strip**) + +**Geometry:** **`row_B = 128 B`**, **`512 / 128 = 4`** rows per strip. **Single Port A** is enough (dual port does **not** shorten **unique** row coverage unless compute is strip-bound and overlapped differently); **Port B idle**. + +| `t` | `e` | Port A | Port B | Fibers (A) | First elem @ A (each fiber) | `#elem` each | Reduce note | +|----:|----:|--------|--------|------------|-----------------------------|----------------|-------------| +| 0 | 0 | `src@G0` | — | `0,1,2,3` | `(0,0)`, `(1,0)`, `(2,0)`, `(3,0)` | 32 | 4× lane-tree (`K=32`) → **Acc** `r=0…3` | +| 1 | 1 | `src@G1` | — | `4,5,6,7` | `(4,0)`…`(7,0)` | 32 | **Acc** `r=4…7` | +| 2 | 2 | `src@G2` | — | `8,9,10,11` | … | 32 | **Acc** | +| 3 | 3 | `src@G3` | — | `12…15` | … | 32 | **Acc** | +| 4 | 4 | `src@G4` | — | `16…19` | … | 32 | **Acc** | +| 5 | 5 | `src@G5` | — | `20…23` | … | 32 | **Acc** | +| 6 | 6 | `src@G6` | — | `24…27` | … | 32 | **Acc** | +| 7 | 7 | `src@G7` | — | `28…31` | … | 32 | **Acc** | + +**32** fibers, each **`#elem = C = 32`** from **Port A** only; **8 cycles** = one epoch, **one** `reg_idx` on **R0**. + +--- + +#### Example C — `TROWEXPANDADD`, **FP32**, **8×128** (`v[r]` + `src`) + +**`v` tile:** **8** row scalars as **FP32** = **32 B** at **byte offset 0** of strip **G0** (tile **`v`**; remaining bytes **don’t-care**). + +**Why prefetch `v`:** If **`v@G0`** were on **Port B** while **`src`** streams on **Port A** only, **dual-port** cannot also deliver **`src@G4`** in the **same** cycle. **High bandwidth schedule:** (1) **Pre-epoch** or **`t_pre`:** **Port B** reads **`v@G0`** once; latch **`v[0]…v[7]`** (bytes **0–3**, **4–7**, …, **28–31**). (2) **`t = 0…3`:** **Port A** and **Port B** both carry **`src`** with **same** `reg_idx` as Example **A** — **two rows/cycle**. + +| `t` | `e` | Port A | Port B | Fibers (`src`) | First elem @ A | First elem @ B | `#elem` / fiber | **`v[fiber_id]`** | Expand | +|----:|----:|--------|--------|----------------|----------------|----------------|-----------------|-------------------|--------| +| 0 | 0 | `src@G0` | `src@G4` | `r=0`, `r=4` | `(0,0)` | `(4,0)` | 128 | **latched** **`v[0]`**, **`v[4]`** | **128** lanes/fiber: **`src` + v** | +| 1 | 1 | `src@G1` | `src@G5` | `r=1`, `r=5` | `(1,0)` | `(5,0)` | 128 | **latched** **`v[1]`**, **`v[5]`** | … | +| 2 | 2 | `src@G2` | `src@G6` | `r=2`, `r=6` | … | … | 128 | **latched** **`v[2]`**, **`v[6]`** | … | +| 3 | 3 | `src@G3` | `src@G7` | `r=3`, `r=7` | … | … | 128 | **latched** **`v[3]`**, **`v[7]`** | … | + +**`t_pre` (one beat, e.g. previous epoch):** **Port B** = **`v@G0`**, **Port A** = **—** or next **`src`** prefetch; **first element of `v[0]`** = **byte 0** of **B**’s 512 B chunk. + +--- + +#### Example D — `TROWSUM`, **HiFP4**, **256×32** (`C = 32`, **`E = ½`**, **32 rows / strip**) + +**Geometry:** **`row_B = 16 B`** = **32** logical elements/row; **`512 / 16 = 32`** distinct **`fiber_id`** values per strip. **Dual-port** same **`src`**: **`t = 0…3`** covers **256** rows (all fibers) with **64** partial trees/cycle (32 fibers × 2 ports). + +| `t` | `e` | Port A | Port B | Fiber IDs (A) | First elem @ A | `#elem` | Fiber IDs (B) | First elem @ B | `#elem` | Reduce note | +|----:|----:|--------|--------|---------------|----------------|--------:|---------------|----------------|--------:|-------------| +| 0 | 0 | `src@G0` | `src@G4` | `r=0…31` | row `r` starts at byte **`16r`** mod strip | 32 each | `r=128…159` | byte **`16(r−128)`** in **G4** strip | 32 each | **64** lane-trees → **64** **Acc** RMW (**watch bank** = `r mod 8`) | +| 1 | 1 | `src@G1` | `src@G5` | `32…63` | … | 32 | `160…191` | … | 32 | **64** **Acc** | +| 2 | 2 | `src@G2` | `src@G6` | `64…95` | … | 32 | `192…223` | … | 32 | **64** **Acc** | +| 3 | 3 | `src@G3` | `src@G7` | `96…127` | … | 32 | `224…255` | … | 32 | **64** **Acc** | + +**`t = 4…7`:** duplicate **`src`** strips (suppress **Acc** idempotent re-reduce) unless **`reg_idx`** advances to a **second** tile / phase. + +--- + +#### Example E — `TROWSUM`, **FP8** (`E = 1`), **64×64** (`C = 64`, **8 rows / strip**) + +**Geometry:** **`row_B = 64 B`** = **64** **`UINT8`/FP8** elements along the row; **`512 / 64 = 8`** distinct **`fiber_id`** values per strip. **Dual-port** same **`src`**: **`t = 0…3`** ingests all **64** rows (**16** partial trees/cycle). + +| `t` | `e` | Port A | Port B | Fiber IDs (A) | First elem @ A | `#elem` | Fiber IDs (B) | First elem @ B | `#elem` | Reduce note | +|----:|----:|--------|--------|---------------|----------------|--------:|---------------|----------------|--------:|-------------| +| 0 | 0 | `src@G0` | `src@G4` | `r=0…7` | row `r` at byte **`64r`** in **G0** | 64 | `r=32…39` | byte **`64(r−32)`** in **G4** *(base row **32**)* | 64 | **16** lane-trees → **Acc** (unpack **FP8→FP32** internal) | +| 1 | 1 | `src@G1` | `src@G5` | `8…15` | … | 64 | `40…47` | … | 64 | **16** **Acc** | +| 2 | 2 | `src@G2` | `src@G6` | `16…23` | … | 64 | `48…55` | … | 64 | **16** **Acc** | +| 3 | 3 | `src@G3` | `src@G7` | `24…31` | … | 64 | `56…63` | … | 64 | **16** **Acc** | + +--- + +#### Example F — `TROWSUM`, **FP8** (`E = 1`), **16×256** (`C = 256`, **2 rows / strip**) + +**Geometry:** **`row_B = 256 B`**; **`512 / 256 = 2`** rows per strip. **Dual-port** same **`src`**: **4** cycles × **4** fibers/cycle = **16** rows. + +| `t` | `e` | Port A | Port B | Fibers (A) | First elem @ A | `#elem` | Fibers (B) | First elem @ B | `#elem` | Reduce note | +|----:|----:|--------|--------|------------|----------------|--------:|-----------|----------------|--------:|-------------| +| 0 | 0 | `src@G0` | `src@G4` | `r=0`, `1` | `(0,0)`, `(1,0)` | 256 each | `r=8`, `9` | strip **G4** row **8** at byte **0**, row **9** at byte **256** | 256 | **4** trees (`K=256`, **`D_lane = 8`**) | +| 1 | 1 | `src@G1` | `src@G5` | `2`, `3` | … | 256 | `10`, `11` | … | 256 | **4** **Acc** | +| 2 | 2 | `src@G2` | `src@G6` | `4`, `5` | … | 256 | `12`, `13` | … | 256 | **4** **Acc** | +| 3 | 3 | `src@G3` | `src@G7` | `6`, `7` | … | 256 | `14`, `15` | … | 256 | **4** **Acc** | + +*(Row **8** in **G4:** byte offset **`512·4 = 2048`** = **`8 × 256`**.)* + +--- + +#### Example G — `TROWSUM`, **MXFP4** (`E = ½`, packed nibble), **512×16** (`C = 16`, **64 rows / strip**) + +**Geometry:** **`row_B = 8 B`** = **16** logical FP4 elements/row; **`512 / 8 = 64`** **`fiber_id`**s per strip. **Dual-port** same **`src`**: **`t = 0…3`** covers **512** rows (**128** lane-trees/cycle). **Unpack** maps **nibble lanes** to **wide** compare/add operands before **Acc**. + +| `t` | `e` | Port A | Port B | Fiber IDs (A) | First elem @ A | `#elem` | Fiber IDs (B) | First elem @ B | `#elem` | Reduce note | +|----:|----:|--------|--------|---------------|----------------|--------:|---------------|----------------|--------:|-------------| +| 0 | 0 | `src@G0` | `src@G4` | `r=0…63` | row `r` at byte **`8r`** in **G0** | 16 | `r=256…319` | byte **`8(r−256)`** in **G4** | 16 | **128** trees → **128** **Acc** RMW | +| 1 | 1 | `src@G1` | `src@G5` | `64…127` | … | 16 | `320…383` | … | 16 | **128** **Acc** | +| 2 | 2 | `src@G2` | `src@G6` | `128…191` | … | 16 | `384…447` | … | 16 | **128** **Acc** | +| 3 | 3 | `src@G3` | `src@G7` | `192…255` | … | 16 | `448…511` | … | 16 | **128** **Acc** | + +--- + +#### Example H — `TCOLSUM`, **MXFP4** (`E = ½`), **32×256** (`R = 32`, **`C = 256`**, **native row-major**) + +**TRegFile:** Read ports emit **only** full **512 B** strips (**[`tregfile4k.md`](tregfile4k.md)**); **there is no gather** inside the tile RF. **`TCOLSUM`** cannot request “column **`c`** only” from the file — it must **accept whole strips** on **Port A / Port B**, then **select** the needed **`(r,c)`** in **VEC** (strip buffers → unpack → **column mux** / shifter network). + +**Policy:** **No transpose scratchpad** (**§5.3.2**). Operand remains **one** `reg_idx`, **§2.1** row-major. **Acc[`c`]**: **read–modify–write** associative **add** so partials from each strip-beat **accumulate** until all **R = 32** row contributions for column **`c`** are seen. + +**Hardware parallelism (§5.3.2):** Let **`N_tree`** = parallel **adder / reduce trees** per beat; **`N_acc`** = parallel **Acc** **RMW** slots per cycle (**`N_acc ≤ N_run`**, **§9.3.2**); **`f`** = **effective `Acc[c]` commits per tree per full tile scan** (one **dual-ingest** pass over **all** strips — includes **sub-cycles** / **pipeline**). **Same-cycle** combine+retire is capped by **`P_beat = min(N_tree, N_acc)`**. + +**Wave count (both limits):** + +**`#W = max(⌈C / N_acc⌉, ⌈C / (N_tree · f)⌉, ⌈C / N_run⌉)`** (**`#W`** **tile-epoch** **count**; **synonym** **`#waves`** **below** **when** **`N_acc ≤ N_run`** **⇒** **`⌈C/N_run⌉ ≤ ⌈C/N_acc⌉`**) + +- **`#waves_acc = ⌈C / N_acc⌉`**: **Acc-band** partitioning (**≤ `N_acc`** columns **finished** per **wave** if **trees** keep up). +- **`#waves_tree = ⌈C / (N_tree · f)⌉`**: when **`N_tree ≪ N_acc`**, **tree throughput** may require **more** **full scans** than **`#waves_acc`** predicts. +- **`#waves_Nrun = ⌈C / N_run⌉`**: **DFF** **capacity** (**§5.3.2**); **redundant** **vs** **`#waves_acc`** **when** **`N_acc ≤ N_run`**. + +When **`N_acc > N_tree`**, **trees** are **time-multiplexed** over **sub-cycles**; **`f`** must be **measured** from **RTL**/**micro-arch** (Example: **`S = 4`** strip-pair beats × **`f_micro`** commits per beat per tree). + +**Illustrative numbers:** **`C = 256`**, **`N_acc = 64`**, **`N_run = 512`** → **`#waves_acc = 4`**, **`⌈C/N_run⌉ = 1`**. With **`N_tree = 8`**, **`f = 8`** (e.g. **8** **`Acc[c]`** commits per tree over one **4-beat** scan × **2** **micro** rounds): **`N_tree · f = 64`**, **`#waves_tree = 4`**, **`#W = max(4, 4, 1) = 4`**. With **`N_tree = 4`**, **`f = 8`**: **`N_tree · f = 32`**, **`#waves_tree = 8`**, **`#W = max(4, 8, 1) = 8`** — **tree**-limited; **strip calendar** is unchanged, but **microcode** runs **more** **tile** **epochs** and may **shrink** the **column band** per wave below **`N_acc`**. + +**Acc-limited schedule (`#W = ⌈C / N_acc⌉`):** **`c_base = N_acc · k`** as in the tables below. **`N_tree = 32`**, **`N_acc = 64`** may still need **two** **tree** **phases** per **`t`** — **micro-cycles** expand **`f`**; re-check **`#waves_tree`**. + +**Geometry:** **`row_B = 128 B`** (**256** FP4 elements/row); **4** rows per **512 B** strip. **Dual** read (**R0+R4**, **§3.1**): **`t = 0…3`** (**`e = 0…3`**) delivers **unique** strip pairs **`(0,4)…(3,7)`** and visits **all 32** rows **once per full scan**. Cycles **`t = 4…7`** of the **same** TRegFile **epoch** repeat the **same** strips (second lap with identical `reg_idx`); **disable Acc updates** on **`t = 4…7`** unless **overlapped** with another **column band** (implementation choice). + +**If `N_acc ≥ C`:** **one** wave (**one** scan, **`t = 0…3`**) suffices for all columns. + +**Per-strip-beat (one wave):** From **A**/**B** buffers, **column mux** pulls **`(r,c)`** for **`c`** in the active band only; **8** row samples per **`c`** per **`t`** (4 rows × 2 ports) → **`Acc[c] +=`** partial (after **`K = 8`** interim reduce via **`N_tree`** lanes). After **`t = 3`** for that wave, each **`c`** in the band has **32** row terms accumulated → **done** for **`TCOLSUM`** (modulo tree **pipeline**). + +**Strip calendar (identical each wave; `c_base = N_acc · k`):** + +| `t` | `e` | Port A | Port B | Rows in A / B | **`fiber_id` band** | Per-**`c`** row samples this `t` | **Acc** | +|----:|----:|--------|--------|---------------|---------------------|----------------------------------|---------| +| 0 | 0 | `src@G0` | `src@G4` | **0–3** / **16–19** | **`c ∈ [c_base, c_base + N_acc − 1]`** | **8** | **`Acc[c] +=`** partial from **8** **`(r,c)`** (mux from **A/B**) | +| 1 | 1 | `src@G1` | `src@G5` | **4–7** / **20–23** | same band | **8** | **+=** … | +| 2 | 2 | `src@G2` | `src@G6` | **8–11** / **24–27** | same band | **8** | **+=** … | +| 3 | 3 | `src@G3` | `src@G7` | **12–15** / **28–31** | same band | **8** | **32** terms integrated per **`c`** in band | + +**`(r,c)` byte** in a strip with base row **`r₀`**: **`(r − r₀)·row_B + ⌊c·E⌋`** plus **nibble** select (**`E = ½`**). **Multi-epoch summary** (**Acc-limited** **`#W = 4`**, i.e. **`#waves_tree ≤ #waves_acc`**; **`N_acc = 64`**, **`N_run = 512`**): + +| Wave `k` | Tile read policy | Active columns | Strip beats used | +|----------|------------------|----------------|------------------| +| 0 | Same `reg_idx`, epoch **E0** | **`c = 0…63`** | **`t = 0…3`** as above | +| 1 | **Re-read** same tile, epoch **E1** | **`c = 64…127`** | repeat calendar | +| 2 | epoch **E2** | **`c = 128…191`** | repeat | +| 3 | epoch **E3** | **`c = 192…255`** | repeat | + +If **`#waves_tree > #waves_acc`**, add **waves** **`k = 4 … #W − 1`** with **overlapping** or **narrower** **column** **bands** — **implementation**-specific. + +**Cycle lower bound (illustrative):** **`#W × 4`** strip-pair cycles (**`#W`** from **§5.3.2** **`max(⌈C/N_acc⌉, ⌈C/(N_tree·f)⌉, ⌈C/N_run⌉)`**), **plus** **epoch** turnarounds — **implementation-dependent**. **Single-port A only** doubles strip cycles per scan (**`t = 0…7`** unique). + +**§9 `c*`** metrics still bound **tree / strip** complexity; they **exclude** **transpose scratch**, **`#W`** **tile replays**, **`f`**, **`N_run`**, **TRegFile** **non-gather** replay — add explicitly in schedules (**§5.3.2**). + +--- + +## 5. Instruction Categories and Cycle Sketches + +The following uses **R**/**C** notation, **S = 8** strips, and **read_pair** = one cycle with both 512 B read ports used. **Write_pair** = both write ports used (same or different tiles depending on retire policy). + +### 5.1 Elementwise (Tile–Tile) + +**Representative:** `TADD`, `TMUL`, `TAND`, `TCMP`, `TCVT` (unary ops such as `TABS`/`TRELU` omit one read port per strip). + +**Dataflow (non-pipelined sketch):** + +1. For each strip index `s = 0…7`: `read_pair` loads `src0[s]` and `src1[s]` → unpack → SIMD op → pack into `dst[s]` staging. +2. Retire `dst[s]` with `write_pair` (same cycle as next strip’s read if the pipeline supports **read–compute–write** overlap). + +**Latency:** **8 cycles** minimum to **read** both operand tiles strip-by-strip; with **pipelining** (overlap read `s+1` with write `s−1`), end-to-end often **~10–12 cycles** for a full `dst` tile (implementation-dependent buffering). + +**Cross-lane:** **none** for pure elementwise; SIMD lanes are independent within the strip. + +**Special:** `TCVT` may widen/narrow; internal path uses **wider SIMD** or **two-pass** if pack/unpack asymmetry exceeds one cycle. + +```mermaid +flowchart LR + subgraph per_strip [Per strip s] + R0[Read src0 512B] --> U0[Unpack] + R1[Read src1 512B] --> U1[Unpack] + U0 --> ALU[Elementwise ALU] + U1 --> ALU + ALU --> P[Pack] + P --> W[Write dst 512B] + end +``` + +--- + +### 5.2 Tile–Scalar / Tile–Immediate + +**Representative:** `TADDS`, `TMULS`, `TCMPS`, `TEXPANDS`, `TADDSC`. + +**Scalar path:** + +- Immediate or **single-element tile** loaded once into **scalar broadcast reg** (optional **1-cycle** read of a dedicated scalar slot). +- Each strip: SIMD op **lane_i = f(tile[s]_i, scalar)**. + +**Cycles:** same order of magnitude as §5.1; **one fewer** long-latency operand read if scalar is in a control register. + +**Cross-lane:** none (unless scalar differs per row/col via side table—then becomes expand-like). + +--- + +### 5.3 Axis Reduce + +**Row-reduce examples:** `TROWSUM`, `TROWMAX`, `TROWARGMAX` (reduce across **columns** within each row). +**Column-reduce examples:** `TCOLSUM`, `TCOLMAX`, `TCOLARGMAX`. + +**Key geometric fact:** a **512 B strip** spans a **contiguous run** of row-major storage; for **large C**, one row may span **multiple strips**; for **small C**, one strip may hold **multiple partial rows**. The control FSM computes `(row, col)` range per strip from `(R, C, E)`. + +**Scheduling:** Reduce passes are driven by a **strip read calendar** keyed by **`fiber_id = r`** or **`c`** (**§4.3**): each cycle’s **Rd0/Rd1** transactions determine **operand sources**; partials retire to **Acc** on the matching **`fiber_id`**. **`TCOL*`** obeys **§5.3.2** (**no transpose scratchpad**; TRegFile **no gather** — **strip replay** + **VEC column select** + **Acc RMW**). + +#### 5.3.1 Row-wise reduce (e.g. `TROWSUM`) + +For each **row r**, compute `acc[r] = reduce_{c} M[r,c]`. + +**Phase A – partial reduce within strip:** + +- For strips that contain **multiple columns of the same row segment**, use **horizontal SIMD tree** within the strip (cross-lane inside SIMD). + +**Phase B – cross-strip combine for rows spanning strips:** + +- Strips contributing to the same row feed a **segmented reduction network** or write **partial sums** to **Acc** (logical index `r`, physical slot **`ρ`** when **`N_run < R`**, **§9.3.2**), then **second pass** reads back when row complete. **Logical** depth (**rAccB** / **rStgUB**) is in **§9.3.1**; **legal shapes** in **§9.7**; **running** silicon = **`N_run`** DFF entries. + +**Cycle sketch (conceptual):** + +| Phase | Action | +|-------|--------| +| 1 | For each strip `s` in 0…7: read tile strip, compute **strip-partial** per affected row segment → **Acc** RMW (atomic add/max/…) | +| 2 | If all columns of row seen, **finalize** row result to narrow format | +| 3 | For `TROWEXPAND`-style output of scalar-per-row, stream writes; for true reduce producing **R×1** or packed vector tile, **write compact tile** over multiple cycles | + +**`TROWARGMAX` / `TROWARGMIN`:** each strip produces **(value, col_index)** pairs; cross-strip compare selects winner; **cross-lane compare tree** + **index mux**. + +```text + Strip0 partial ──┐ + Strip1 partial ──┼──▶ Per-row combine (max/sum) ──▶ Row result tile + ... │ + Strip7 partial ──┘ + ▲ + └── updates keyed by row id (cross-strip) +``` + +#### 5.3.2 Column-wise reduce (e.g. `TCOLSUM`, `TCOLMAX`) + +**Architectural rule (VEC-4K):** **`TCOL*`** **must** be implemented on the **operand tile’s native row-major** layout as seen through **normal TRegFile** `reg_idx` / **512 B strip** reads. **No transpose scratchpad tile** — software and microcode **shall not** rely on materializing a **C×R** row-major **copy** of the operand in a separate tile **for the sole purpose of column reduction**. + +**Parallelism — `N_tree` vs `N_acc` (hardware):** + +| Symbol | Meaning | +|--------|---------| +| **`N_tree`** | Parallel **adder / reduce** (or **elementwise**) **paths** after the **crossbar** (**§4.1**). **Reference implementation:** **`N_tree = 128`**. Scheduling **examples** below may use **smaller** **illustrative** values. | +| **`N_acc`** | **Distinct** **`Acc[·]`** **RMW** (or **commit**) slots **in the same cycle** — limited by **`N_tree`**, **adder feedback** **ports**, and **`N_run`** (**§9.3.2**): **`N_acc ≤ N_run`** (**512**); **one ping-pong half** holds **256** slots (**1024 B**). | + +**Coupling:** On a **single** beat, **at most `min(N_tree, N_acc)`** columns can **both** **combine** fresh partials **and** **retire** to **Acc** in the **same** cycle if **tree output** must **pair** **1:1** with an **accumulator write**. + +**When `N_acc > N_tree`:** **Trees** are the **scarce** resource; microcode may **time-multiplex** **`N_tree`** trees across **sub-cycles** while **keeping `N_acc`** **accumulators** **live** (pipeline registers + **Acc**). Once **pipelined**, the **schedule** can still **advance up to `N_acc` column accumulations per cycle** by **feeding** accumulators from **staggered** tree outputs. + +**Accumulator-centric wave count** (column **bands** sized by **Acc** retire slots): + +**`#waves_acc = ⌈C / N_acc⌉`** + +(i.e. **`num_col / num_acc`**, rounded up): each **wave** is one **complete** **row-major** scan of the operand tile, updating a **disjoint band** of **at most `N_acc`** columns’ **`Acc[c]`** toward the final **R**-way reduce. + +**Tree-limited wave count (`N_tree ≪ N_acc`):** Define **`f`** as the **effective number of distinct `Acc[c]` commits** each **single** **adder tree** can **sustain per full operand tile scan** (one **dual-port** ingest of **all** strips, e.g. **`t = 0…3`** in Example H), **after** **pipelining** and **sub-cycle** multiplex (**`f`** counts **tree→Acc** **throughput** over the **whole** scan — product of **commits per strip-beat** × **number of beats**, **bypass**, and **stage depth**). If **trees** cannot **produce** partials fast enough to **match** **`#waves_acc`**, **more** **tile replays** are needed: + +**`#waves_tree = ⌈C / (N_tree · f)⌉`** + +**DFF capacity wave count (`N_run`):** + +**`#waves_Nrun = ⌈C / N_run⌉`** + +**Combined (conservative, full-tile replays for `TCOL*`):** + +**`#W = max(⌈C / N_acc⌉, ⌈C / (N_tree · f)⌉, ⌈C / N_run⌉)`** + +Treat **`#W`** as the **tile-scan / epoch** **count** **lower bound** for **correct** **`TCOL*`** (**`#waves`** is an **alias** when **unambiguous**). **`K_outer`** (**§8**, **write staging**) counts **outer** **fiber-offset** **campaigns**; it **need not** equal **`#W`** — **avoid** **double-counting** **tile** **replays** **unless** the **micro-op** **nest** (**outer** × **inner** strip loop) is **specified**. + +**Redundancy:** **`N_acc ≤ N_run`** (**§9.3.2**) ⇒ **`C / N_acc ≥ C / N_run`** ⇒ **`⌈C / N_acc⌉ ≥ ⌈C / N_run⌉`** for **`C > 0`**, so **`#waves_Nrun`** **never exceeds** **`#waves_acc`** **in that regime** — the **third** **`max`** **term** is **for** **generality** (alternate **schedules**, **tooling**, or **future** **caps**) and **matches** **`#waves_acc`** **when** **`N_acc = N_run`**. + +When **`N_tree · f ≥ N_acc`**, **`#waves_tree ≤ #waves_acc`** and **`#W = max(⌈C / N_acc⌉, ⌈C / N_run⌉) = ⌈C / N_acc⌉`** (still **Acc-band** **long pole** if **`N_acc ≤ N_run`**). + +**Fiber-capacity rounds (`N_run`):** if **more output fibers** are **live** than **`N_run`** DFF slots allow in one **in-core** pass, or an **extreme `(R,C)`** forces **narrow bands** per **outer** iteration, **opcode decode** expands **one** architectural **`TROW*` / `TCOL*`** into **`K_outer` hardware loops** (nested **outside** the **strip** walk). Each **outer** iteration carries a **fiber base / offset** (`c_base`, `r_base`, …) so **only** **`≤ N_run`** **logical** fibers map to **physical `ρ`** in **Acc** at a time; the **inner** loop is still the **usual** **row-major** strip calendar (**§4.3**). **Correctness** is preserved by **trading latency for capacity**. + +**Write-side staging across outer loops:** **Wr0 / Wr1** (and **Pack** ahead of them) may hold **wide staging registers** — **per-strip** or **per-output-slice** — where **partial result tiles** or **completed fiber bands** from **earlier** outer iterations **accumulate** or **merge** until the **full** **`R`×`C`** (or **per-fiber vector**) output is **complete**. For **associative** reduces (**sum** / **max** / **min**), **merge** order must match **ISA numerics** (e.g. **sum** associativity vs **rounding**); **arg** ops need **value∥index** staging consistent across rounds (**§9.3.2**). This is **invisible** at **single-instruction retire** if the **VEC** does **not** commit **`dst`** until the **last** outer loop. + +**When `N_acc ≤ N_tree`:** **Acc** is the **bottleneck** for **parallel column retires**; **`#waves_acc`** **still** applies; **`min(N_tree, N_acc) = N_acc`** caps **simultaneous** combine+retire per beat unless **pipelining** exposes extra **tree** throughput. **Also** evaluate **`#waves_tree`** if **`f`** is **small**. + +**Design shorthand:** **`P_beat = min(N_tree, N_acc)`** for **strict** same-cycle **tree→Acc** pairing; **tile-replay** uses **`#W`** above — see **§4.4 Example H**. + +**`TROW*` mirror (row-axis output fibers, `fiber_id = r`):** same **symbols** **`N_tree`**, **`N_acc`**, **`N_run`**, **`f`** (**`f`** = **effective `Acc[r]` commits per tree per full operand scan** on the **row-reduce** calendar). **Replace `C` → `R`:** + +**`#W_trow = max(⌈R / N_acc⌉, ⌈R / (N_tree · f)⌉, ⌈R / N_run⌉)`** + +**§5.3.1** **Phase B** **strip** **walk** **replays** apply. **`⌈R / N_run⌉`** is **redundant** **vs** **`⌈R / N_acc⌉`** **when** **`N_acc ≤ N_run`** — **same** **as** **`TCOL*`**. + +For each **column c**, reduce across **rows** (**R** elements). In **row-major** storage, a column is **not** one contiguous byte range unless **`C = 1`**. **Implementation:** TRegFile delivers **only** full **512 B** strips (**§3.1** — **no gather**). Hardware **re-reads** the operand tile’s strips in **Gs** order (possibly **many TRegFile epochs** with the **same** `reg_idx`); after each read, **strip buffers A/B** hold **row-contiguous** data; **unpack** + **column mux** (VEC-side, **not** in the RF) extracts **`(r,c)`** for the **column band** scheduled that beat; **Acc[`c`] read–modify–write** accumulates partial sums until all **R** rows are covered. If **`N_acc < C`**, **repeat** the **full strip walk** for the next column band (**§4.4 Example H**). **Blocked partials** and **multi-cycle** calendars (**§4.3**) apply. + +**Metrics parity (§9):** Closed-form **`c*`** symbols match the **mathematical** **`TROW*`-on-`C×R`** substitution (**transpose-equivalent indices only**). That **algebra** does **not** imply a **physical** transpose buffer — it sizes **trees**, **`cS`**, **`cW`**, and **SRAM**; **wall-clock** must add **strip replay** and **`#W = max(⌈C / N_acc⌉, ⌈C / (N_tree · f)⌉, ⌈C / N_run⌉)`** **full-tile** **epochs** (**`f`**, **`N_run`** per **§5.3.2** / **§9.3.2**). + +**Cross-strip / cross-lane:** still heavy when **`col_B`** is small (many columns’ samples packed per strip); see **`c*`** in **§9.3** and **§9.6** (§9.7 lists **shapes** only). + +--- + +### 5.4 Axis Expand / Broadcast + +**Representative:** `TROWEXPAND*`, `TCOLEXPAND*`. + +**Scheduling:** Expands use the same **`fiber_id`** convention (**§4.3**): **`r`** or **`c`** selects **`v[fiber_id]`**; the **calendar** interleaves reads of the **narrow `v` tile** (or **Acc**-backed **`v`**) with **`src`** strips so each **512 B** write sees correct **splat** metadata per lane. + +**Row expand** (broadcast scalar along row): after computing or loading **per-row scalar** `v[r]`, for each strip determine row segments and **broadcast** `v[r]` across lane positions (SIMD **splat**). + +**Column expand:** same **no-transpose-scratch** rule as **`TCOL*`** (**§5.3.2**): **row-major** strip walk + **multi-pass splat** / **lane scatter** with **column address generation**; **no** **`C×R`** scratch tile **only** for expand. + +**Cycles:** often **1× read** of narrow **per-row/col vector tile** + **1× read** of `src` + **streamed write** of `dst` → similar to **8–16** cycles depending on whether `src` and `v` fit strip schedule without extra passes. + +```mermaid +flowchart TB + subgraph row_expand [TROWEXPANDADD sketch] + V[Per-row scalars v[r] in buffer] + S[src tile strips] + V --> SPLAT[Splat per row segment] + S --> ADD[Add/mul/max...] + SPLAT --> ADD + ADD --> D[dst strips] + end +``` + +--- + +### 5.5 Complex Instructions + +#### 5.5.1 `TSORT32` + +Spec: sort **each 32-element block** of `src` with paired indices from `idx`. + +- **Within-strip:** if `32·E ≤ 512 B`, multiple blocks per strip; process blocks **in parallel SIMD sort networks** (bitonic / odd-even) of depth O(log² 32) comparators **pipelined**. +- **Cross-block:** independent per block → **minimal cross-strip** except when a 32-block spans strip boundary → **microcode** stitches **tail/head** in a **staging register**. + +**Cycles:** **many** (tens), dominated by comparator stages × number of blocks `N/32`. + +#### 5.5.2 `TMRGSORT` (merge sort of multiple sorted lists) + +Typically **multi-list merge** with **k-way** comparator tree: + +1. **Load** list headers / pointers (implementation-defined in ISA). +2. **Stream** strips from each list into **merge front buffers** (read ports time-multiplexed across lists). +3. **Repeat:** compare **k** front elements, pick winner, push to **output strip**, refill from corresponding list. +4. **Write** output strips via write ports. + +**Cross-lane / cross-strip:** **heavy**; merge **global** across lists, not SIMD-embarrassingly parallel. Expect **O(4096 / 1024) × (merge depth)** plus **compare tree** cycles—**hundreds** of cycles acceptable for a “complex” op. + +```text + List0 strips ──▶ ┐ + List1 strips ──▶ ├──▶ k-way merge tree ──▶ out strip buffer ──▶ Wr ports + ... │ + List(k-1) ────▶ ┘ +``` + +#### 5.5.3 `TGATHER` / `TGATHERB` / `TSCATTER` + +- **Index-driven** access: per element, **addr = base + f(index)`**; within VEC-4K, **on-tile** gather means **cross-strip byte mux** driven by **index SIMD** (indices may come from second tile). +- Realistic schedule: **batch** indices into **coalesced** groups that fall into **same or adjacent strips** to limit mux fanout. + +**Cycles:** **large variance**; worst case approaches **per-element** serialization if indices are random. + +#### 5.5.4 `TCI`, `TTRI`, `TPART*` + +- **`TCI`:** **strip-parallel** index generation `base + stride` → **no cross-lane** dependency beyond broadcast of parameters. +- **`TTRI`:** row/col counters compared to generate mask; **cross-lane** for diagonal boundary within strip only. +- **`TPART*`:** valid-region mask intersects elementwise regions; same as §5.1 with **predicate gating**. + +#### 5.5.5 `TQUANT` / `TDEQUANT` + +Often **two-phase**: compute **scale/exp** per tile or per row (reduce), then **elementwise** scale. Combines **§5.3** + **§5.1**. + +--- + +## 6. Cross-Lane and Cross-Strip Summary + +| Category | Cross-lane (within 512 B strip) | Cross-strip (among 8 strips) | +|----------|----------------------------------|------------------------------| +| Elementwise tile–tile | Independent lanes | None (strip order arbitrary) | +| Tile–scalar | Independent | None | +| Row reduce | Horizontal tree for row segment in strip | Combine partials for rows spanning strips | +| Column reduce | **Strip read** → **VEC column mux** from row data in buffer (RF **no gather**) | **Heavy** strip **replay** / **`Acc` RMW** / multi-epoch (**no transpose scratch**) | +| Row expand | Splat scalar across row segment | Repeat/broadcast metadata per strip | +| Column expand | Partial splat (**column-major intent**, row-major storage) | **Heavy** multi-pass (**no transpose scratch**) | +| `TSORT32` | Sort network per 32-block | Block spanning strip boundary | +| `TMRGSORT` | Per-element compare in tree | **Global** merge across streams | +| `TGATHER` | Mux selected elements | Arbitrary strip sources | + +--- + +## 7. Datapath Diagram — Row Reduce with Cross-Strip Combine + +**Read sequencing** follows a **strip calendar** (**§4.3**). **§4.1** **dataflow:** **Rd0+Rd1** → **crossbar** (**1024 B**) → **(A) align/unpack/permute** → **128 slices `W_prep,i`** → **(B) 128×(ALU `W_ALU,i` → tree `W_tree,i`)** → **Acc** ping-pong → **half-select** → **Wr0+Wr1** (**`W_ALU,i` may exceed `W_tree,i`**). + +```mermaid +flowchart TB + subgraph rf [TRegFile] + RD0[Rd0 512B] + RD1[Rd1 512B] + end + subgraph ctl [Control] + C[Opcode + shape] + end + subgraph xb [Crossbar] + XB[1024 B in] + end + subgraph modA ["(A) Align unpack permute"] + A["IN 1024 B OUT 128 x W_prep,i"] + end + subgraph modB ["(B) 128 groups N_group"] + G["Per i: slice W_prep,i to ALU to tree W_ALU,i then W_tree,i 32b typical"] + end + subgraph acc [Acc ping-pong] + ACCMEM["256x32bx2 RMW or bypass to DFF"] + end + subgraph out [Retire] + PACK[Pack] + WR[Wr0 Wr1 512B] + end + RD0 --> XB + RD1 --> XB + C --> XB + C --> A + C --> G + C --> ACCMEM + XB --> A + A --> G + G -->|W_tree to Acc rho| ACCMEM + ACCMEM -->|256 words| PACK --> WR +``` + +--- + +## 8. Implementation Notes + +1. **Opcode decode** produces **control** for the **§4.1** **crossbar**, **(A)** **align/unpack/permute** (**per-slice** **`W_prep,i`**), **(B)** **128** **groups** (**ALU** **`W_ALU,i`**, **tree** **`W_tree,i`**, **`W_ALU,i ≥ W_tree,i`** **allowed**), **Acc** ping-pong **addresses**, **per-slot** **RMW** **vs** **bypass-to-DFF** (**§9.3.2**), **Wr half-select**, and a **strip read calendar** (**§4.3**): per-cycle **Rd0/Rd1** targets, **strip index** phase, **`fiber_id`** / **Acc** side effects. Parameters include strip loop count, **`TCOL*`** **wave** / **`N_acc`** / **`N_tree`** / **`f`** (§5.3.2), **`N_run`** / **`ρ` remap**, **`K_outer`**, **write-side staging**, **splat** / merge **k**, §9 **`r*`** / **`c*`** template id (**47** families). **`TCOL*`** **replays** over **`#W = max(⌈C/N_acc⌉, ⌈C/(N_tree·f)⌉, ⌈C/N_run⌉)`** when **`#W > 1`**; **no transpose-scratch**; **no RF gather** (**§3.1**). +2. **Determinism:** PTO ops are expected to be **deterministic** at the tile level; multi-cycle internal scheduling is **invisible** if the instruction **retires atomically** from the programmer’s view (barriers via **`TSYNC`** as needed). +3. **Resource conflicts:** with only **two** read ports, **TMRGSORT** and **column-reduce** should **stall** other TRegFile clients or use **dedicated tiles** for **algorithmic** ping-pong (e.g. sort lists) — **not** for a **transpose scratchpad** forbidden by **§5.3.2**. +4. **Numerics:** FP4/FP8 ops may specify **internal FP16/FP32** evaluation; document **rounding** per `TCVT` / ISA rules. + +--- + +## 9. Legal `(format, R×C)` enumeration and axis-reduce complexity (`TROW*` / `TCOL*`) + +This section **enumerates every** combination of **logical format** and **tile shape** from §2.1 and, for each, gives **paired** metrics for **row-axis** reductions (`TROWSUM`, `TROWMAX`, `TROWARGMAX`, …) and **column-axis** reductions (`TCOLSUM`, `TCOLMAX`, `TCOLARGMAX`, …). It then explains how a **single reconfigurable reduction tree** morphs with those parameters, and counts how many distinct **control shapes** appear in the table. + +### 9.1 Enumeration rules + +- Tile storage: **4096 bytes**, row-major, **R** and **C** powers of two. +- **N = R·C = 4096 / E** with **E** bytes per logical element: + - **FP32:** `E = 4`, `N = 1024`, **11** shapes. + - **FP16** and **BF16:** `E = 2`, `N = 2048`, **12** shapes each (**24** table rows). + - **FP8:** `E = 1`, `N = 4096`, **13** shapes. + - **MXFP4** and **HiFP4:** `E = ½`, `N = 8192`, **14** shapes each (**28** table rows). + +**Master table rows:** **76**. **Unique `(E, R, C)` geometries:** **50**. + +`elem_per_strip = 512/E` (for `E = ½`, **1024** elements per 512 B). + +### 9.2 Row-axis metrics (`TROW*`) + +For each **row** fiber, reduce **C** elements. **Bytes per row** `row_B = 4096/R`. + +| Sym | Definition | +|-----|------------| +| **rS** | Strips per row `= ⌈row_B / 512⌉`. | +| **rK** | Elements in one cross-lane segment: `C` if `rS = 1`, else `512/E`. | +| **rDl** | Cross-lane depth `= max(0, ⌈log₂ rK⌉)`. | +| **rDc** | Cross-strip depth `= max(0, ⌈log₂ rS⌉)`. | +| **rW** | Per-strip serial work: `rDl` if `row_B ≥ 512`, else `(512/row_B)·rDl`. | +| **rLB** | `4 + rDl + rDc` (optimistic; §9.4). | +| **rUB** | `4 + 8·rW + R·rDc` (conservative serial tree; §9.4). | +| **rAccB** / **rStgUB** | Partial state (bytes): §9.3.1 — **`4·R`** logical running; **`4·R·rS`** staged upper bound; **physical running** **`N_run`** (**§9.3.2**). | + +### 9.3 Column-axis metrics (`TCOL*`) + +For each **column** fiber, reduce **R** elements. **Logical bytes per column** (if packed contiguously) `col_B = 4096/C = R·E`. + +**Formal substitution (metrics only — not a scratch layout):** Algebraically, `TCOL*` on **R×C** matches `TROW*` on a **fictitious C×R** row-major tile with the **same** **4096 B** element multiset. **VEC-4K does not allocate a physical C×R transpose scratchpad** for **`TCOL*`** (**§5.3.2**); the **`c*`** formulas still size **trees**, strip pressure, and **SRAM** for **strip replay + VEC column mux** (**TRegFile has no gather**, **§3.1**). The symbols are the **same** as §9.2 with **`(R,C) → (C,R)`** and **`C` ↔ `R`**: + +| Sym | Definition | +|-----|------------| +| **cS** | `⌈col_B / 512⌉` (= strips per column fiber in the **substitution-equivalent** striping model). | +| **cK** | `R` if `cS = 1`, else `512/E`. | +| **cDl** | `max(0, ⌈log₂ cK⌉)`. | +| **cDc** | `max(0, ⌈log₂ cS⌉)`. | +| **cW** | `cDl` if `col_B ≥ 512`, else `(512/col_B)·cDl`. | +| **cLB** | `4 + cDl + cDc`. | +| **cUB** | `4 + 8·cW + C·cDc` (note **`C`** column outputs, not `R`). | +| **cAccB** / **cStgUB** | Partial state (bytes): §9.3.1 — **`4·C`** logical running; **`4·C·cS`** staged upper bound; **physical running** **`N_run`** (**§9.3.2**). | + +**Row-major hardware path:** scheduling uses the **same numeric** `(cS, cK, cDl, cDc, cW)` for **strip-sequential reads**, **partial state**, **VEC column extraction**, and **cross-strip merge** on the **operand tile**; **cLB/cUB** **exclude** **TTRANS** / **transpose-scratch** (**§5.3.2**) and **exclude** **`#W = max(⌈C/N_acc⌉, ⌈C/(N_tree·f)⌉, ⌈C/N_run⌉)`** **tile replays**, **`f`**, and **`N_run`** (**§5.3.2**). **Multi-epoch** **`reg_idx`** replay (**§4.4 Example H**) may dominate wall-clock. + +### 9.3.1 Partial accumulator state (`TROW*` / `TCOL*`) + +Strips that contribute to the **same** row (or column) either **update a running partial** or **buffer strip-level partials** until the cross-strip merge completes. **§9.3.1** gives **logical** byte formulas; **§9.7** lists **(format, R×C)** rows only; **§9.3.2** caps **live** running entries in silicon. + +**Assumption A — associative reduce (max / min / sum):** each output fiber keeps **one** **FP32-shaped** running partial (widen narrow formats in the reducer). Index **r** for rows, **c** for columns. + +| Symbol | Formula | Meaning | +|--------|---------|---------| +| **rAccB** | **`4·R`** | **Logical** per-row state (bytes): `R` rows × **4 B** (FP32 partial / compare operand width). **Physical** running file holds **`min(R, N_run)`** slots at a time when **`N_run < R`** (**wave** remap, **§9.3.2**). | +| **cAccB** | **`4·C`** | **Logical** per-column state (bytes). **Physical** **`min(C, N_run)`** at a time when **`N_run < C`**. | + +**Implementation cap — `N_run = 512`:** the **VEC-4K** running-partial file is **512 × 32b DFF** (**2048 B**), **16×** smaller than the **§2.1** worst-case **8192** logical fibers (**8192 / 512 = 16**). When **`R > N_run`** or **`C > N_run`**, **decode** drives **`K_outer > 1` hardware loops** (**§5.3.2**): each **outer** step maps **at most `N_run`** fibers to **`ρ`** and runs the **full** inner **strip** schedule (or a **defined** subset); **completed** bands **retire** through **Pack → Wr0/Wr1**, often into **write-path staging registers** that **hold** or **combine** **partial `dst`** **slices** until **all** offsets are **done** — **functionally** equivalent to an unbounded Acc file, at **higher** cycle cost. + +**Same mechanism for `N_acc` / tree limits:** **outer loops** are **not** only for **`N_run`**; **banded `TCOL*`** (**`#W`** in **§5.3.2**) is the **same** pattern — **time** for **capacity**. **Write staging** is optional when each **wave** writes **disjoint** **`dst`** fibers directly; it is **required** when **outer** rounds must **merge** into **shared** output **strip** words or **one** **fiber** is **finished** only after **multiple** passes. + +**Assumption B — staged strip partials (upper bound):** microarchitecture retains **up to one FP32 partial per strip slot per fiber** before the **`⌈log₂ S⌉`** cross-strip tree drains them (worst case over all fibers simultaneously). + +| Symbol | Formula | Meaning | +|--------|---------|---------| +| **rStgUB** | **`4·R·rS`** | **Row-axis staging upper bound** (bytes). Never exceeds **32 768 B (32 KiB)** for any legal `(E,R,C)` in §2.1 (same peak as **rAccB** when `rS = 1`, or **4·1024·8** when `R = 1`, `rS = 8`, etc.). | +| **cStgUB** | **`4·C·cS`** | **Column-axis** analogue; peak **32 KiB**. | + +**`TROWARGMAX` / `TROWARGMIN` / `TCOLARG*`:** plan for **value + index** per fiber (e.g. **8 B** aligned entries). A simple scaling rule: **≈ `2 × rAccB`** / **`2 × cAccB`** (and **×2** on **rStgUB** / **cStgUB** if indices are kept per staged partial). + +**Dual-axis overlap:** a single physical **Acc** file can be **time-multiplexed** between row and column passes; **simultaneous** row+column reductions need enough **live** slots for both (**≤ `N_run`** each if **serialized** bands) **or** **serialized** op issue. **Transpose scratch** is **not** used to fold axes (**§5.3.2**). + +#### 9.3.2 Accumulator organization (ping-pong DFF, `N_run = 512`, §4.1) + +The **running partial** store matches **§4.1**: **two** **ping-pong** **halves** of **256 × 32 bit** each (**1024 B** / **half**, **2048 B** total). **Each** **slot** is a **DFF** **word** with **two** **write** **modes** selected by **control**: + +- **RMW accumulate:** **new** **partial** from **(B)** **feeds** an **adder**; **second** **operand** is **feedback** from the **same** **slot’s** **DFF** **output**; **sum** (or **max/min** **compare**) **writes** **back** **to** **that** **DFF** (**associative** **reduce** **path**). +- **Bypass combine (write-through):** **new** **data** **from** **(B)** **is** **muxed** **directly** **into** the **DFF**, **skipping** the **accumulate** **adder** — **overwrite** / **initialise** / **move** **style** **updates** **without** **old** **+** **new** **arithmetic**. + +**One** **half** **accepts** **writes** **while** **control** may **select** the **other** **half** for **Pack → Wr0+Wr1** (**512 B + 512 B** = **1024 B** = **256** words **per** **retire** **phase**). + +**Logical indexing:** **`ρ ∈ [0, N_run)`** with **`N_run = 512`**; e.g. **`ρ = h·256 + σ`** with **half** **`h ∈ {0,1}`** and **`σ ∈ [0,255]`**. **`fiber_id`** (**§4.3**) **remaps** to **`ρ`** across **Acc waves** when **`R` or `C > 512`**. + +**Capacity:** + +- **`N_run = 512`** **FP32-shaped** partials **across** **both** **halves**. **`N_acc ≤ N_run`** (**§5.3.2**); **at most 256** **distinct** **RMW** targets **per** **half** **per** **cycle** if **all** **writes** **land** **in** **one** **active** **accumulation** **half**. +- **Logical** **`max(R)`** / **`max(C)`** over §2.1 remains **8192** — **rAccB** / **cAccB** (**§9.3.1**, **§9.6**) are **algorithmic**. + +**Conflict decode (optional 8-way view):** **`bank_id = ρ mod 8`**, **`word = ρ >> 3`** still **useful** for **port** **scheduling** (same as **32** **words**/bank × **8** **banks** **within** **each** **256-word** **half**). + +**`TROWARG*` / `TCOLARG*` variant:** + +- **64-bit** **value∥index** **per** **slot** → **double** **width** **or** **sidecar** **index** **RAM** (**§4.1** **unchanged** **topology**). + +**Staged partials (`rStgUB` / `cStgUB`):** + +- **Separate** **small** **buffer** **or** **stretched** **schedule** (**§9.3.1** **Assumption B**) — **orthogonal** to **Acc** ping-pong. + +**Summary:** **§4.1** **256 × 32 b × 2** **ping-pong** **Acc**; **`N_run = 512`**; **per-slot** **RMW** **or** **bypass-to-DFF**; **Wr0+Wr1** **drain** **one** **half** (**256** **words**) **per** **selected** **phase**. **Acc waves** **remap** **`fiber_id → ρ`** when **logical** **fibers** **exceed** **512**. + +### 9.4 Cycle model (both axes) + +Both axes assume **§3.2** unary ingest: **4 cycles** minimum to read the full tile with **two** 512 B read ports. + +- **Lower bound (*LB*):** ideal overlap of read, **one** wide pipelined `⌈log₂ K⌉`-stage tree, and cross-strip merge; **ignores** time-multiplexing many thin fibers on **one** physical tree. +- **Upper bound (*UB*):** **8** strips each pay **W** tree-stage units on **one** shared tree, plus **one cross-strip phase per output fiber** (`R` outputs for `TROW*`, `C` outputs for `TCOL*`). + +### 9.5 Reconfigurable reduction tree — how the hardware “shape” follows the table + +The datapath is **one logical pipeline** reused by all table rows; its **effective shape** is selected by microcode from the **`r*`** or **`c*`** fields. + +1. **Unpack** maps a 512 B strip to up to **1024** logical lanes (FP4) / **512** (FP8) / … — **physical SIMD** may be narrower; the **logical** tree depth is still **⌈log₂ K⌉**. + +2. **Cross-lane tree (variable fan-in K):** implement as **`D_lane = ⌈log₂ K⌉`** stages of **pairwise** reduce ops. **K** jumps with `(format, R, C)`: + - **`rK`** (row) depends primarily on **`C`** when `rS=1`, else fixed **`512/E`**. + - **`cK`** (column) depends primarily on **`R`** when `cS=1`, else **`512/E`**. + - For **rectangular** tiles, **`rK ≠ cK`** in general → row and column ops need **different programmed depths** for the same stored tile. + +3. **Cross-strip merger (variable S):** after each strip contributes a **partial**, a **balanced tree** of depth **`⌈log₂ S⌉`** combines partials for the same fiber ID (`rS` or `cS`). **S ∈ {1,2,4,8}** in this enumeration → at most **3** compare stages after lane tree. + +4. **Temporal “stretch” (W):** when **`row_B < 512`** (or **`col_B < 512`**), **multiple complete fibers** land in one strip. A **single** lane tree of depth **`D_lane`** must run **`512/row_B`** (or **`512/col_B`**) times **per strip** unless duplicated in silicon → **`W`** scales **linearly** with packed fiber count. + +```mermaid +flowchart LR + subgraph strip [Per 512B strip] + U[Unpack] --> MUX[Mux K active lanes] + MUX --> LT[log2 K-stage lane tree] + LT --> P[Partial per fiber ID] + end + P --> CS[Depth log2 S cross-strip tree] + CS --> ACC[Acc ping-pong 256×32b×2 RMW §4.1] + ACC --> OUT[Reduced fiber value] +``` + +#### 9.5.1 How many distinct “shapes” are needed? + +| Counting notion | Value | Meaning | +|-----------------|------:|---------| +| **Physical datapaths** | **1** | One reducer suffices if it supports **max K = 1024**, **max `D_lane` = 10**, **max `S` = 8** (`D_cross ≤ 3`), with **per-stage bypass** and **programmable lane mask**. | +| **Unique `(D_lane, D_cross, W_strip)` tuples** | **47** | Distinct **time-scheduling recipes** for **either** axis, over all **50** geometries (same 47-set for row **or** column as multiset over shapes). | +| **Unique `(S, K, D_lane, D_cross)` quartets** | **23** | Coarser strip + tree fingerprint (per axis). | +| **Unique paired `(row tuple, column tuple)`** | **50** | One pair per **`(E,R,C)`**; **square** shapes have **identical** row and column metrics. | + +So: **one** parameterized **tree + cross-strip** unit covers the whole table; firmware/microcode must hold **47** **scheduling templates** (or equivalent parameterized loops) per axis, not **76** different RTL blocks. + +### 9.6 Summary by format (extrema over all legal shapes) + +Maxima over **both** axes are **identical** for each format family (swap **R↔C** maps extreme row cases to extreme column cases). + +| Format | N | # shapes | max **K** (either axis) | max **D_lane** | max **S** | max **D_cross** | min *LB* | max *LB* | max *UB* (r or c) | max **rAccB** / **cAccB** | max **rStgUB** / **cStgUB** | +|--------|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| FP32 | 1024 | 11 | 128 | 7 | 8 | 3 | 4 | 14 | 516 | 4096 | 32768 | +| FP16 / BF16 | 2048 | 12 | 256 | 8 | 8 | 3 | 4 | 15 | 1028 | 8192 | 32768 | +| FP8 | 4096 | 13 | 512 | 9 | 8 | 3 | 4 | 16 | 2052 | 16384 | 32768 | +| MXFP4 / HiFP4 | 8192 | 14 | 1024 | 10 | 8 | 3 | 4 | 17 | 4100 | **32768** | **32768** | + +**Logical** peak **rAccB** / **cAccB** in the table is still **`4·R` / `4·C`** (up to **32 KiB** at **`R` or `C = 8192`**). **VEC-4K silicon** (**§9.3.2**): **running partials** = **`N_run = 512`** entries × **4 B** = **2048 B DFF**; **`R` or `C > 512`** uses **Acc waves** (**§9.3.1**). **rStgUB** / **cStgUB** remain **staging upper bounds** (still **≤ 32 KiB** per §2.1); **physical staging** may be a **small separate buffer** or **longer** microcode schedule. + +### 9.7 Legal `(format, R×C)` enumeration + +**76** rows — same **master-table** row count as **§9.1** (**50** distinct **`(E, R, C)`** geometries; **FP16** vs **BF16** duplicate shapes). **Per-axis metrics** (`r*`, `c*`, **LB/UB**, **rAccB**, **cAccB**, **rStgUB**, **cStgUB**) are defined in **§9.2–9.3**; **format extrema** in **§9.6**. + +| Format | E (B/elem) | N | R×C | +|--------|------------|---|-----| +| FP32 | 4 | 1024 | 1×1024 | +| FP32 | 4 | 1024 | 2×512 | +| FP32 | 4 | 1024 | 4×256 | +| FP32 | 4 | 1024 | 8×128 | +| FP32 | 4 | 1024 | 16×64 | +| FP32 | 4 | 1024 | 32×32 | +| FP32 | 4 | 1024 | 64×16 | +| FP32 | 4 | 1024 | 128×8 | +| FP32 | 4 | 1024 | 256×4 | +| FP32 | 4 | 1024 | 512×2 | +| FP32 | 4 | 1024 | 1024×1 | +| FP16 | 2 | 2048 | 1×2048 | +| FP16 | 2 | 2048 | 2×1024 | +| FP16 | 2 | 2048 | 4×512 | +| FP16 | 2 | 2048 | 8×256 | +| FP16 | 2 | 2048 | 16×128 | +| FP16 | 2 | 2048 | 32×64 | +| FP16 | 2 | 2048 | 64×32 | +| FP16 | 2 | 2048 | 128×16 | +| FP16 | 2 | 2048 | 256×8 | +| FP16 | 2 | 2048 | 512×4 | +| FP16 | 2 | 2048 | 1024×2 | +| FP16 | 2 | 2048 | 2048×1 | +| BF16 | 2 | 2048 | 1×2048 | +| BF16 | 2 | 2048 | 2×1024 | +| BF16 | 2 | 2048 | 4×512 | +| BF16 | 2 | 2048 | 8×256 | +| BF16 | 2 | 2048 | 16×128 | +| BF16 | 2 | 2048 | 32×64 | +| BF16 | 2 | 2048 | 64×32 | +| BF16 | 2 | 2048 | 128×16 | +| BF16 | 2 | 2048 | 256×8 | +| BF16 | 2 | 2048 | 512×4 | +| BF16 | 2 | 2048 | 1024×2 | +| BF16 | 2 | 2048 | 2048×1 | +| FP8 | 1 | 4096 | 1×4096 | +| FP8 | 1 | 4096 | 2×2048 | +| FP8 | 1 | 4096 | 4×1024 | +| FP8 | 1 | 4096 | 8×512 | +| FP8 | 1 | 4096 | 16×256 | +| FP8 | 1 | 4096 | 32×128 | +| FP8 | 1 | 4096 | 64×64 | +| FP8 | 1 | 4096 | 128×32 | +| FP8 | 1 | 4096 | 256×16 | +| FP8 | 1 | 4096 | 512×8 | +| FP8 | 1 | 4096 | 1024×4 | +| FP8 | 1 | 4096 | 2048×2 | +| FP8 | 1 | 4096 | 4096×1 | +| MXFP4 | 0.5 | 8192 | 1×8192 | +| MXFP4 | 0.5 | 8192 | 2×4096 | +| MXFP4 | 0.5 | 8192 | 4×2048 | +| MXFP4 | 0.5 | 8192 | 8×1024 | +| MXFP4 | 0.5 | 8192 | 16×512 | +| MXFP4 | 0.5 | 8192 | 32×256 | +| MXFP4 | 0.5 | 8192 | 64×128 | +| MXFP4 | 0.5 | 8192 | 128×64 | +| MXFP4 | 0.5 | 8192 | 256×32 | +| MXFP4 | 0.5 | 8192 | 512×16 | +| MXFP4 | 0.5 | 8192 | 1024×8 | +| MXFP4 | 0.5 | 8192 | 2048×4 | +| MXFP4 | 0.5 | 8192 | 4096×2 | +| MXFP4 | 0.5 | 8192 | 8192×1 | +| HiFP4 | 0.5 | 8192 | 1×8192 | +| HiFP4 | 0.5 | 8192 | 2×4096 | +| HiFP4 | 0.5 | 8192 | 4×2048 | +| HiFP4 | 0.5 | 8192 | 8×1024 | +| HiFP4 | 0.5 | 8192 | 16×512 | +| HiFP4 | 0.5 | 8192 | 32×256 | +| HiFP4 | 0.5 | 8192 | 64×128 | +| HiFP4 | 0.5 | 8192 | 128×64 | +| HiFP4 | 0.5 | 8192 | 256×32 | +| HiFP4 | 0.5 | 8192 | 512×16 | +| HiFP4 | 0.5 | 8192 | 1024×8 | +| HiFP4 | 0.5 | 8192 | 2048×4 | +| HiFP4 | 0.5 | 8192 | 4096×2 | +| HiFP4 | 0.5 | 8192 | 8192×1 | + +--- + +## 10. Related Documents + +- [`tregfile4k.md`](tregfile4k.md) — **8R/8W** tile RF, **8-cycle epoch**, **`G = (p+e) mod 8`** calendar; **§4.4** binds **R0/R4** to VEC **Port A/B**. +- [`outerCube.md`](outerCube.md) — MXU / outer product engine (different port count; **not** identical to VEC-4K’s 2R+2W model). +- [`PTOISA/README.md`](PTOISA/README.md) — authoritative ISA list and per-op references. + +--- + +## Document History + +| Version | Date | Notes | +|---------|------|-------| +| 0.1 | 2026-04-07 | Initial VEC-4K architecture sketch for 4 KB tiles, 2×512B R/W ports, PTO ISA vector subset | +| 0.2 | 2026-04-07 | §9: full `(format, R×C)` enumeration, `TROWMAX` tree complexity, `C_lb`/`C_ub` cycle models, master table | +| 0.3 | 2026-04-07 | Remove §5.3.1a examples; §9 unified row+column table; §9.5 tree morphing + shape counts (1 datapath, 47 recipes, 23 quartets, 50 pairs) | +| 0.4 | 2026-04-07 | §9.3.1 + table columns **rAccB**/**cAccB**/**rStgUB**/**cStgUB** (per-fiber partial SRAM sizing) | +| 0.5 | 2026-04-07 | §9.7: Markdown pipe table → compact **HTML** table (`font-size: 0.52em`, scroll wrapper) for preview rendering | +| 0.6 | 2026-04-07 | §9.3.2 multi-bank accumulator SRAM: **8×1024×32b** default, address map, ports, arg/staging variants | +| 0.7 | 2026-04-07 | §4.1 / §7 / §9.5 diagrams: **Acc 8-bank** in logical block diagram, row-reduce mermaid, reconfigurable-tree flow | +| 0.8 | 2026-04-07 | §4.3 **fiber_id** + **strip read calendar** (Rd0/Rd1 per cycle, operand sources, Acc RMW); ties to §5.3–5.4, §8, §9.3.2 / §9.5.1 templates | +| 0.9 | 2026-04-07 | §4.4 four **epoch-aligned** fiber calendars vs `tregfile4k.md` (**R0/R4**), dual/single-port ingest, **TROWSUM**/**TROWEXPANDADD** examples | +| 0.10 | 2026-04-07 | §4.4 **+4** examples (**E–H**): **FP8** `TROWSUM` **64×64**, **16×256**; **MXFP4** `TROWSUM` **512×16**; **MXFP4** **32×256** (superseded for **H** in **v0.11**) | +| 0.11 | 2026-04-07 | §4.4 Example **H** → **`TCOLSUM` MXFP4 32×256** (**`fiber_id = c`**); §4.3 legend **`TCOL*`** cross-ref | +| 0.12 | 2026-04-07 | **`TCOL*` / `TCOLEXPAND*`**: **no transpose scratchpad** — native row-major + gather/splat; §5.3.2, §6, §8–9, **Example H** rewritten; **`c*`** = metrics-only substitution | +| 0.13 | 2026-04-07 | **§3.1** TRegFile **no gather**; **`TCOL*`** = strip **replay** + VEC **column mux** + **Acc RMW**; **Example H** multi-epoch / **`P`** bands; §5.3.2, §6, §8–9 aligned | +| 0.14 | 2026-04-07 | **§5.3.2** **`N_tree`** / **`N_acc`**, **`P_beat=min`**, **`#waves=⌈C/N_acc⌉`**; **Example H** + §4.3/§8/§9 use **`N_acc`** (not lone **`P`**) | +| 0.15 | 2026-04-07 | **`f`** (commits/tree/full-scan); **`#waves=max(⌈C/N_acc⌉,⌈C/(N_tree·f)⌉)`**; §5.3.2, Example H, §4.3/§8/§9 | +| 0.16 | 2026-04-07 | §4.4 Example **H**: **illustrative** **`#waves_acc`/`#waves_tree`** split; **Acc-limited** multi-epoch table caption; **tree-limited** extra waves note | +| 0.17 | 2026-04-07 | **`N_run = 512`**: running partials **8×64×32b DFF** (~**2 KiB**), **16×** vs **8192** peak; **Acc waves** / **`ρ`** remap; §4.1/§4.3/§5.3.2/§8/§9.3–9.7 + mermaid | +| 0.18 | 2026-04-07 | **§5.3.2** / **§9.3.1** / **§8**: **`K_outer`** hardware loops, **fiber offset**, **Wr staging** to **merge** partial **`dst`** when Acc capacity **<** extreme shape | +| 0.19 | 2026-04-07 | **§5.3.2**: **`#waves_Nrun`**, **`#W`** **max** **formula**, **`#W_trow`** **`TROW*`**; **`K_outer`** **vs** **`#W`**; **§4.3** / **§4.4 H** / **§8** / **§9.3** **`#W`** **cross-refs**; **§5.3.1** **Acc** **wording** | +| 0.20 | 2026-04-07 | **§9.7**: drop wide **HTML** **r\*/c\*** **master** table → **Markdown** **4-column** **(Format, E, N, R×C)** only; **cross-refs** **§5.3.2** / **§9.3.1** | +| 0.21 | 2026-04-07 | **§4.1** **dataflow** diagram: **opcode+shape→control→crossbar** (**1024 B**), **`N_tree=128`**, **Acc** **256×32b×2** **ping-pong** **adder+fb**, **Wr half** → **Wr0+Wr1**; **§3.3** / **§4.2–4.3** / **§7–8** / **§9.3.2** / **§9.5** mermaid | +| 0.22 | 2026-04-07 | **§4.1** / **§7**: split **(A)** **align/unpack/permute** vs **(B)** **128** **groups**; **W_prep,i** / **W_ALU,i** / **W_tree,i**; **W_ALU ≥ W_tree** | +| 0.23 | 2026-04-07 | **Acc** **bypass**: **write-through** **to** **DFF** **without** **combine** **adder** (**§4.1**, **§9.3.2**, **§7–8**) | From c069c6fe3bc6048f01b9d4364cff1016ab189573 Mon Sep 17 00:00:00 2001 From: Mac Date: Mon, 20 Apr 2026 16:46:42 +0800 Subject: [PATCH 02/12] Add outerCube tile16 datapath plots and NVIDIA shuffle montage Made-with: Cursor --- designs/outerCube/nv_shuffle.py | 301 +++++++ .../outerCube/plot_tile16_vector_datapath.py | 756 ++++++++++++++++++ .../tile16_figures/elementwise_all.png | Bin 0 -> 480089 bytes .../outerCube/tile16_figures/expand_all.png | Bin 0 -> 342947 bytes .../tile16_figures/mergesort_all.png | Bin 0 -> 609277 bytes .../tile16_figures/nv_shuffle_all.png | Bin 0 -> 467588 bytes .../outerCube/tile16_figures/reduce_all.png | Bin 0 -> 292627 bytes 7 files changed, 1057 insertions(+) create mode 100644 designs/outerCube/nv_shuffle.py create mode 100644 designs/outerCube/plot_tile16_vector_datapath.py create mode 100644 designs/outerCube/tile16_figures/elementwise_all.png create mode 100644 designs/outerCube/tile16_figures/expand_all.png create mode 100644 designs/outerCube/tile16_figures/mergesort_all.png create mode 100644 designs/outerCube/tile16_figures/nv_shuffle_all.png create mode 100644 designs/outerCube/tile16_figures/reduce_all.png diff --git a/designs/outerCube/nv_shuffle.py b/designs/outerCube/nv_shuffle.py new file mode 100644 index 00000000..3e738d01 --- /dev/null +++ b/designs/outerCube/nv_shuffle.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +""" +NVIDIA warp shuffle schematic (16 B register tile). + +One PNG montage (4×4): rows = __shfl_sync (per-lane indexed read; here a fixed permutation), +__shfl_up_sync, +__shfl_down_sync, __shfl_xor_sync; columns = FP32 / FP16 / FP8 / FP4 (E = 4/2/1/0.5 B). + +Each pane: input lanes (▭ width ∝ E) on top, output lanes on bottom, curved wires +lane i ← lane src(i). Not cycle-accururate; illustration only. +""" + +from __future__ import annotations + +import argparse +import math +import os +from dataclasses import dataclass +from typing import List, Sequence, Tuple + +from matplotlib.axes import Axes +from matplotlib.patches import Circle, FancyArrowPatch, Rectangle + +import matplotlib.pyplot as plt + +TILE_BYTES = 16 +E_REF = 4.0 + + +@dataclass(frozen=True) +class FloatForm: + E: float # bytes per element + label: str + + @property + def n(self) -> int: + return int(round(TILE_BYTES / self.E)) + + +def formats() -> List[FloatForm]: + return [ + FloatForm(4.0, "FP32"), + FloatForm(2.0, "FP16"), + FloatForm(1.0, "FP8"), + FloatForm(0.5, "FP4"), + ] + + +def _rect(ax: Axes, cx: float, cy: float, w: float, h: float, **kwargs) -> Rectangle: + kwargs.setdefault("zorder", 2) + r = Rectangle((cx - w / 2, cy - h / 2), w, h, fill=True, **kwargs) + ax.add_patch(r) + return r + + +def _rect_top(cy: float, h: float) -> float: + return cy + h / 2 + + +def _rect_bottom(cy: float, h: float) -> float: + return cy - h / 2 + + +def _lane_layout(E: float, n: int, x0: float, x1: float) -> Tuple[float, float, List[float]]: + """Single contiguous row of n elements; rectangle width ∝ E (same spirit as tile16 script).""" + e = max(float(E), 0.5) + span = x1 - x0 + g_in = min(0.028, 0.11 * span / max(n, 2)) + gaps_inside = max(n - 1, 0) * g_in + slack = span - gaps_inside + if slack <= 0: + g_in *= 0.5 + gaps_inside = max(n - 1, 0) * g_in + slack = span - gaps_inside + K = slack / (n * E_REF) + w = max(0.0028, K * e) + if n * w + gaps_inside > span: + w = max(0.0028, (span - gaps_inside) / max(n, 1)) + h = min(0.038, 0.48 / max(n, 1)) + xs: List[float] = [] + x = x0 + for i in range(n): + xs.append(x + w / 2) + x += w + if i < n - 1: + x += g_in + return w, h, xs + + +def _shfl_linear_permutation_params(n: int) -> Tuple[int, int]: + """ + Coefficients (a, b) for src(dst) = (a * dst + b) % n with gcd(a, n) == 1 so each + destination reads from a distinct source (full permutation on lanes). + """ + if n <= 1: + return 1, 0 + a = 1 + for cand in range(2, n): + if math.gcd(cand, n) == 1: + a = cand + break + b = 1 if n > 1 else 0 + return a, b + + +def _shuffle_src( + op: str, n: int, dst: int, *, delta: int = 1, xor_mask: int = 4 +) -> int: + """Map destination lane -> source lane for schematic (CUDA-ish).""" + if n <= 0: + return 0 + if op == "shfl": + a, b = _shfl_linear_permutation_params(n) + return (a * dst + b) % n + if op == "shfl_up": + j = dst - delta + return j if j >= 0 else 0 + if op == "shfl_down": + j = dst + delta + return j if j < n else n - 1 + if op == "shfl_xor": + m = xor_mask % n + if m == 0: + m = 1 if n > 1 else 0 + return dst ^ m + return dst + + +def render_shuffle_pane( + ax: Axes, + op: str, + op_title: str, + cuda_name: str, + spec: FloatForm, + *, + title_fs: float = 8.0, + lane_fs: float = 5.0, +) -> None: + n = spec.n + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) + ax.axis("off") + + E = float(spec.E) + x0, x1 = 0.05, 0.95 + w, h, xs = _lane_layout(E, n, x0, x1) + + y_in = 0.74 + y_out = 0.26 + ax.set_title( + f"{op_title}\n{cuda_name} — {spec.label} — E={E:g} B, N={n} ({TILE_BYTES} B tile)", + fontsize=title_fs, + pad=4, + ) + + for i in range(n): + _rect(ax, xs[i], y_in, w, h, facecolor="#cfe8ff", edgecolor="#1a4f8c", linewidth=0.9) + if n <= 16: + ax.text( + xs[i], + y_in + h / 2 + 0.022, + f"L{i}", + ha="center", + va="bottom", + fontsize=max(4.0, lane_fs), + color="#123", + ) + + for i in range(n): + _rect(ax, xs[i], y_out, w, h, facecolor="#dff5df", edgecolor="#1a6c2e", linewidth=0.9) + if n <= 16: + ax.text( + xs[i], + y_out - h / 2 - 0.018, + f"O{i}", + ha="center", + va="top", + fontsize=max(4.0, lane_fs), + color="#123", + ) + + # Legend for op-specific params (tiny) + if op == "shfl": + pa, pb = _shfl_linear_permutation_params(n) + note = f"per-lane srcLane: ( {pa}·i + {pb} ) mod {n} (each Oi reads a different Li)" + elif op == "shfl_up": + note = "delta=1 (from lower lane id)" + elif op == "shfl_down": + note = "delta=1 (from higher lane id)" + else: + m = 4 % n if n else 0 + if m == 0: + m = 1 if n > 1 else 0 + note = f"maskLane={m} (XOR index)" + + ax.text(0.5, 0.06, note, ha="center", va="center", fontsize=max(3.8, lane_fs - 0.8), color="#444") + + xor_mask = 4 + for dst in range(n): + src = _shuffle_src(op, n, dst, delta=1, xor_mask=xor_mask) + x0a, y0a = xs[src], _rect_bottom(y_in, h) + x1a, y1a = xs[dst], _rect_top(y_out, h) + dx = x1a - x0a + rad = 0.22 * (1.0 if dx >= 0 else -1.0) * (0.35 + min(abs(dx) * 1.8, 0.9)) + style = "arc3,rad=" + str(rad) + a = FancyArrowPatch( + (x0a, y0a), + (x1a, y1a), + arrowstyle="-|>", + linestyle="-", + linewidth=0.75, + color="#333", + connectionstyle=style, + mutation_scale=7, + zorder=3, + clip_on=False, + ) + ax.add_patch(a) + + # Optional small node at merge (purely decorative for dense N) + if n <= 8: + cr = min(0.014, 0.5 * h) + for dst in range(n): + xm = 0.5 * (xs[_shuffle_src(op, n, dst, xor_mask=xor_mask)] + xs[dst]) + ym = 0.5 * (y_in + y_out) + c = Circle((xm, ym), cr, facecolor="#f6f0ff", edgecolor="#555", linewidth=0.6, zorder=1) + ax.add_patch(c) + + +def _save_montage_4x4(out_path: str, *, dpi: int = 140) -> None: + forms = formats() + ops: List[Tuple[str, str, str]] = [ + ("shfl", "shfl (per-lane indexed)", "__shfl_sync"), + ("shfl_up", "shfl_up", "__shfl_up_sync"), + ("shfl_down", "shfl_down", "__shfl_down_sync"), + ("shfl_xor", "shfl_xor", "__shfl_xor_sync"), + ] + + fig, axes = plt.subplots(4, 4, figsize=(22, 22), dpi=dpi) + fig.suptitle( + f"NVIDIA warp shuffle — {TILE_BYTES} B tile, ▭ width ∝ element size (FP32/FP16/FP8/FP4)", + fontsize=13, + y=0.995, + ) + + for r, (op, short, cuda) in enumerate(ops): + for c, spec in enumerate(forms): + render_shuffle_pane(axes[r, c], op, short, cuda, spec, title_fs=7.6, lane_fs=5.0) + + for c, spec in enumerate(forms): + axes[0, c].text( + 0.5, + 1.02, + spec.label, + transform=axes[0, c].transAxes, + ha="center", + va="bottom", + fontsize=10, + fontweight="600", + color="#111", + ) + + fig.text( + 0.5, + 0.012, + ( + "Each column fixes E (bytes/element) with R×C×E = 16 B; rows are shuffle kinds. " + "Top row: each Oi uses its own srcLane(i) (here a linear permutation on lanes; " + "broadcast is the special case srcLane(i)=const). " + "Arrows: value at Oi from Lsrc (schematic; mask/warp participation omitted)." + ), + ha="center", + fontsize=8.5, + color="#222", + ) + fig.subplots_adjust(left=0.03, right=0.97, top=0.94, bottom=0.04, hspace=0.42, wspace=0.20) + fig.savefig(out_path, bbox_inches="tight") + plt.close(fig) + + +def main(argv: Sequence[str] | None = None) -> int: + p = argparse.ArgumentParser(description="Plot NVIDIA warp shuffle 4×4 montage (one PNG).") + p.add_argument( + "-o", + "--out", + default=os.path.join(os.path.dirname(__file__), "tile16_figures", "nv_shuffle_all.png"), + help="Output PNG path (directories created if missing).", + ) + p.add_argument("--dpi", type=int, default=140, help="Figure DPI.") + args = p.parse_args(list(argv) if argv is not None else None) + + out_dir = os.path.dirname(os.path.abspath(args.out)) + if out_dir: + os.makedirs(out_dir, exist_ok=True) + _save_montage_4x4(args.out, dpi=args.dpi) + print(f"Wrote {args.out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/designs/outerCube/plot_tile16_vector_datapath.py b/designs/outerCube/plot_tile16_vector_datapath.py new file mode 100644 index 00000000..d6158af1 --- /dev/null +++ b/designs/outerCube/plot_tile16_vector_datapath.py @@ -0,0 +1,756 @@ +#!/usr/bin/env python3 +""" +Generate small-tile (16 B) vector datapath illustrations inspired by +`vector4k.md`: A and B holding registers as contiguous blocks (all A, then all +B), ▭ width ∝ E, circles = 2-input execution units, crossed wires. + +Writes 4 PNGs (one montage per operation type: 8 subplots each) to an output +directory (default: ./tile16_figures next to this script). Types 1–4: elementwise, +reduce, expand, mergesort (compare–swap levels). +""" + +from __future__ import annotations + +import argparse +import os +from dataclasses import dataclass +from typing import Callable, List, Sequence, Tuple + +from matplotlib.axes import Axes + +import matplotlib.pyplot as plt +from matplotlib.patches import Circle, FancyArrowPatch, Rectangle + +TILE_BYTES = 16 +# Reference element size (bytes) for rectangle width scaling in figures. +E_REF = 4.0 + + +@dataclass(frozen=True) +class TileShape: + idx: int + R: int + C: int + E: float # bytes per logical element (0.5 allowed for packed nibble) + label: str + + @property + def N(self) -> int: + assert abs(self.R * self.C * self.E - TILE_BYTES) < 1e-6, self + return int(round(TILE_BYTES / self.E)) + + def fmt_desc(self) -> str: + return f"R×C = {self.R}×{self.C}, E = {self.E} B/elem → N = {self.N}" + + +def canonical_shapes() -> List[TileShape]: + # Eight distinct (R, C, E) with R*C*E = 16 B; mix wide/narrow + formats. + return [ + TileShape(1, 1, 4, 4.0, "1×4 FP32 (wide row)"), + TileShape(2, 4, 1, 4.0, "4×1 FP32 (tall col)"), + TileShape(3, 2, 2, 4.0, "2×2 FP32"), + TileShape(4, 1, 8, 2.0, "1×8 FP16"), + TileShape(5, 8, 1, 2.0, "8×1 FP16"), + TileShape(6, 2, 4, 2.0, "2×4 FP16"), + TileShape(7, 4, 4, 1.0, "4×4 FP8"), + TileShape(8, 4, 8, 0.5, "4×8 MXFP4 (½ B/elem)"), + ] + + +def _rect(ax: Axes, cx: float, cy: float, w: float, h: float, **kwargs) -> Rectangle: + """Axis-aligned rectangle centered at (cx, cy).""" + kwargs.setdefault("zorder", 2) + r = Rectangle((cx - w / 2, cy - h / 2), w, h, fill=True, **kwargs) + ax.add_patch(r) + return r + + +def _rect_top(cy: float, h: float) -> float: + return cy + h / 2 + + +def _rect_bottom(cy: float, h: float) -> float: + return cy - h / 2 + + +def _circ_top(cy: float, r: float) -> float: + return cy + r + + +def _circ_bottom(cy: float, r: float) -> float: + return cy - r + + +def _ab_block_layout( + E: float, + n_a: int, + n_b: int, + x0: float, + x1: float, +) -> Tuple[float, float, float, float, List[float], List[float]]: + """ + All A operands contiguous, then a gap, then all B operands contiguous. + Same width w for every element rectangle (w ∝ E). Returns: + (w, h, gap_inside_block, gap_between_ab, xa_centers, xb_centers). + """ + e = max(float(E), 0.5) + span = x1 - x0 + gap_block = min(0.032, 0.09 * span) + g_in = min(0.009, 0.14 * span / max(n_a + n_b, 2)) + gaps_inside = max(n_a - 1, 0) * g_in + max(n_b - 1, 0) * g_in + coef = n_a + n_b + slack = span - gap_block - gaps_inside + if slack <= 0: + slack = 0.01 * span + gap_block *= 0.5 + g_in *= 0.5 + gaps_inside = max(n_a - 1, 0) * g_in + max(n_b - 1, 0) * g_in + slack = span - gap_block - gaps_inside + K = slack / (coef * E_REF) + w = max(0.0025, K * e) + if coef * w + gaps_inside + gap_block > span: + w = max(0.0025, (span - gap_block - gaps_inside) / max(coef, 1)) + h = min(0.032, 0.52 / max(n_a + n_b, 1)) + + xa: List[float] = [] + x = x0 + for i in range(n_a): + xa.append(x + w / 2) + x += w + if i < n_a - 1: + x += g_in + x += gap_block + xb: List[float] = [] + for i in range(n_b): + xb.append(x + w / 2) + x += w + if i < n_b - 1: + x += g_in + return w, h, g_in, gap_block, xa, xb + + +def _circle(ax, cx: float, cy: float, r: float, **kwargs) -> Circle: + kwargs.setdefault("zorder", 2) + c = Circle((cx, cy), r, fill=True, **kwargs) + ax.add_patch(c) + return c + + +def _wire_straight( + ax: Axes, + p0: Tuple[float, float], + p1: Tuple[float, float], + *, + color: str = "#444", + lw: float = 0.9, + zorder: float = 4, +) -> None: + """Straight segment (exact endpoints; draws above operand patches).""" + ax.plot( + [p0[0], p1[0]], + [p0[1], p1[1]], + color=color, + linewidth=lw, + solid_capstyle="round", + zorder=zorder, + clip_on=False, + ) + + +def _wire( + ax, + p0: Tuple[float, float], + p1: Tuple[float, float], + rad: float = 0.0, + color: str = "#444", + lw: float = 0.9, + zorder: float = 4, +) -> None: + if abs(rad) < 1e-9: + _wire_straight(ax, p0, p1, color=color, lw=lw, zorder=zorder) + return + style = "arc3,rad=" + str(rad) + a = FancyArrowPatch( + p0, + p1, + arrowstyle="-", + linestyle="-", + linewidth=lw, + color=color, + connectionstyle=style, + mutation_scale=1, + zorder=zorder, + clip_on=False, + ) + ax.add_patch(a) + + +def _odd_even_transposition_stages(n: int, max_stages: int) -> List[List[Tuple[int, int]]]: + """Disjoint compare–swap pairs per stage (odd / even offset), straight multistage network.""" + out: List[List[Tuple[int, int]]] = [] + for p in range(max_stages): + pairs: List[Tuple[int, int]] = [] + start = p % 2 + for i in range(start, n - 1, 2): + pairs.append((i, i + 1)) + out.append(pairs) + return out + + +def _title_block(ax: Axes, kind: str, spec: TileShape, *, fontsize: float = 11) -> None: + ax.set_title( + f"{kind} — {TILE_BYTES} B tile\n{spec.label} — {spec.fmt_desc()}", + fontsize=fontsize, + pad=6, + ) + + +def render_elementwise( + ax: Axes, + spec: TileShape, + *, + title_fontsize: float = 9, + label_fs: float = 6, + show_footer: bool = False, +) -> None: + N = spec.N + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) + ax.axis("off") + _title_block(ax, "Element-wise (2-input / lane)", spec, fontsize=title_fontsize) + + # One row: all A contiguous, gap, all B contiguous; width ∝ E; ALU below each (A[i],B[i]) pair. + x0, x1 = 0.04, 0.96 + y_reg, y_u, y_o = 0.86, 0.50, 0.30 + w, h, _g_in, _gap_ab, xa_list, xb_list = _ab_block_layout(float(spec.E), N, N, x0, x1) + cr = min(0.022, 0.04 / max(N / 8, 1), 0.55 * h) + + ax.text(0.02, y_reg + h / 2 + 0.045, "A block | B block (width ∝ E)", fontsize=max(5.0, label_fs)) + + for i in range(N): + xa_c, xb_c = xa_list[i], xb_list[i] + x_mid = 0.5 * (xa_c + xb_c) + _rect(ax, xa_c, y_reg, w, h, facecolor="#cfe8ff", edgecolor="#1a4f8c", linewidth=1.0) + ax.text(xa_c, y_reg + h / 2 + 0.028, f"A{i}", ha="center", va="bottom", fontsize=label_fs) + _rect(ax, xb_c, y_reg, w, h, facecolor="#ffe8cf", edgecolor="#8c4f1a", linewidth=1.0) + ax.text(xb_c, y_reg + h / 2 + 0.028, f"B{i}", ha="center", va="bottom", fontsize=label_fs) + + _circle(ax, x_mid, y_u, cr, facecolor="#e8e8ff", edgecolor="#333", linewidth=1.0) + ax.text(x_mid, y_u, "⊕", ha="center", va="center", fontsize=max(5.0, label_fs), color="#222") + + rad_a = 0.18 * ((-1) ** i) + rad_b = -rad_a + _wire( + ax, + (xa_c, _rect_bottom(y_reg, h)), + (x_mid, _circ_top(y_u, cr)), + rad=rad_a, + ) + _wire( + ax, + (xb_c, _rect_bottom(y_reg, h)), + (x_mid, _circ_top(y_u, cr)), + rad=rad_b, + ) + + wo, ho = w * 0.92, h * 0.92 + _rect(ax, x_mid, y_o, wo, ho, facecolor="#dff5df", edgecolor="#1a6c2e", linewidth=0.9) + _wire_straight( + ax, + (x_mid, _circ_bottom(y_u, cr)), + (x_mid, _rect_top(y_o, ho)), + lw=0.7, + color="#555", + ) + + if show_footer: + ax.text( + 0.5, + 0.02, + "▭ = element (width ∝ E); ○ = 2-input ALU; opposite bends = crossed operand paths.", + ha="center", + va="bottom", + fontsize=6, + color="#333", + ) + + +def render_reduce( + ax: Axes, + spec: TileShape, + *, + title_fontsize: float = 9, + label_fs: float = 5.5, + show_footer: bool = False, +) -> None: + """Row-style reduce along C for fiber row 0; then binary reduction tree.""" + C = spec.C + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) + ax.axis("off") + _title_block(ax, "Reduce (row 0, axis C)", spec, fontsize=title_fontsize) + + N = C + x0, x1 = 0.06, 0.94 + y_reg = 0.12 + y_leaf_circ = 0.30 + w, h, _g_in, _gap_ab, xa_list, xb_list = _ab_block_layout(float(spec.E), N, N, x0, x1) + cr = min(0.024, 0.05 / max(N / 8, 1), 0.55 * h) + + ax.text(0.02, 0.94, f"fiber row 0; R={spec.R}", fontsize=max(5.0, label_fs)) + ax.text(0.02, y_reg + h / 2 + 0.05, "A block | B block (width ∝ E)", fontsize=max(5.0, label_fs)) + + for i in range(N): + xa_c, xb_c = xa_list[i], xb_list[i] + x_mid = 0.5 * (xa_c + xb_c) + _rect(ax, xa_c, y_reg, w, h, facecolor="#cfe8ff", edgecolor="#1a4f8c", linewidth=1.0) + ax.text(xa_c, y_reg + h / 2 + 0.028, f"A0,{i}", ha="center", va="bottom", fontsize=label_fs) + _rect(ax, xb_c, y_reg, w, h, facecolor="#ffe8cf", edgecolor="#8c4f1a", linewidth=1.0) + ax.text(xb_c, y_reg + h / 2 + 0.028, f"B0,{i}", ha="center", va="bottom", fontsize=label_fs) + + leaf_out_x: List[float] = [] + for i in range(N): + xa_c, xb_c = xa_list[i], xb_list[i] + x_mid = 0.5 * (xa_c + xb_c) + _circle(ax, x_mid, y_leaf_circ, cr, facecolor="#e8e8ff", edgecolor="#333", linewidth=1.0) + rad_a = 0.12 * ((-1) ** i) + _wire( + ax, + (xa_c, _rect_top(y_reg, h)), + (x_mid, _circ_bottom(y_leaf_circ, cr)), + rad=rad_a, + ) + _wire( + ax, + (xb_c, _rect_top(y_reg, h)), + (x_mid, _circ_bottom(y_leaf_circ, cr)), + rad=-rad_a, + ) + leaf_out_x.append(x_mid) + + leaves = [(x, y_leaf_circ) for x in leaf_out_x] + cur = leaves + dy = 0.09 + r_tree = cr * 1.05 + y = y_leaf_circ + cr + dy * 0.55 + depth = 0 + while len(cur) > 1: + r_child = cr if depth == 0 else r_tree + nxt: List[Tuple[float, float]] = [] + for j in range(0, len(cur), 2): + if j + 1 < len(cur): + xm = 0.5 * (cur[j][0] + cur[j + 1][0]) + xL, xR = cur[j][0], cur[j + 1][0] + else: + xm = cur[j][0] + xL = xR = cur[j][0] + _circle(ax, xm, y, r_tree, facecolor="#f3e8ff", edgecolor="#333", linewidth=1.0) + _wire( + ax, + (xL, cur[j][1] + r_child), + (xm, _circ_bottom(y, r_tree)), + rad=0.05, + ) + if j + 1 < len(cur): + _wire( + ax, + (xR, cur[j + 1][1] + r_child), + (xm, _circ_bottom(y, r_tree)), + rad=-0.05, + ) + nxt.append((xm, y)) + cur = nxt + y += dy + depth += 1 + + root = cur[0] + w_acc = max(w * 1.25, 0.014) + y_acc_c = root[1] + 0.08 + _rect( + ax, + root[0], + y_acc_c, + w_acc, + h, + facecolor="#ffd7d7", + edgecolor="#8c1a1a", + linewidth=1.1, + ) + _wire_straight( + ax, + (root[0], _circ_top(root[1], r_tree)), + (root[0], _rect_bottom(y_acc_c, h)), + lw=0.85, + color="#333", + ) + ax.text(root[0], y_acc_c + h / 2 + 0.045, "Acc", ha="center", va="bottom", fontsize=max(5.0, label_fs + 1)) + + if show_footer: + ax.text( + 0.5, + 0.01, + "○ = 2-input combine; upper = reduction tree.", + ha="center", + fontsize=6, + ) + + +def _fanout_levels_top_down( + C: int, x0: float, x1: float, y_top: float, y_bottom: float +) -> List[List[Tuple[float, float]]]: + """Balanced pairing tree: index 0 is root, last level has C leaves (left-to-right x).""" + xs = [x0 + (x1 - x0) * i / max(C - 1, 1) for i in range(C)] + layers_bottom_up: List[List[Tuple[float, float]]] = [[(x, 0.0) for x in xs]] + while len(layers_bottom_up[-1]) > 1: + prev = layers_bottom_up[-1] + nxt: List[Tuple[float, float]] = [] + for j in range(0, len(prev), 2): + if j + 1 < len(prev): + xm = 0.5 * (prev[j][0] + prev[j + 1][0]) + else: + xm = prev[j][0] + nxt.append((xm, 0.0)) + layers_bottom_up.append(nxt) + layers = list(reversed(layers_bottom_up)) + nh = len(layers) + for i, lev in enumerate(layers): + if nh == 1: + yi = y_top + else: + yi = y_top - i * ((y_top - y_bottom) / (nh - 1)) + layers[i] = [(x, yi) for (x, _) in lev] + return layers + + +def _fanout_levels_from_leaf_xs( + leaf_xs: List[float], y_top: float, y_bottom: float +) -> List[List[Tuple[float, float]]]: + """Same pairing tree as `_fanout_levels_top_down`, but leaf x positions are explicit.""" + if not leaf_xs: + return [] + layers_bottom_up: List[List[Tuple[float, float]]] = [[(x, 0.0) for x in leaf_xs]] + while len(layers_bottom_up[-1]) > 1: + prev = layers_bottom_up[-1] + nxt: List[Tuple[float, float]] = [] + for j in range(0, len(prev), 2): + if j + 1 < len(prev): + xm = 0.5 * (prev[j][0] + prev[j + 1][0]) + else: + xm = prev[j][0] + nxt.append((xm, 0.0)) + layers_bottom_up.append(nxt) + layers = list(reversed(layers_bottom_up)) + nh = len(layers) + for i, lev in enumerate(layers): + if nh == 1: + yi = y_top + else: + yi = y_top - i * ((y_top - y_bottom) / (nh - 1)) + layers[i] = [(x, yi) for (x, _) in lev] + return layers + + +def render_expand( + ax: Axes, + spec: TileShape, + *, + title_fontsize: float = 9, + label_fs: float = 5.5, + show_footer: bool = False, +) -> None: + """Expand along C: v[r] in A fans out; B holds src row; leaf 2-input combines.""" + C = spec.C + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) + ax.axis("off") + _title_block(ax, "Expand (row 0, fanout C)", spec, fontsize=title_fontsize) + + y_fan_top = 0.82 + y_fan_bot = 0.52 + y_leaf_circ = 0.34 + y_row = 0.12 + + x0, x1 = 0.05, 0.95 + w, h, _g_in, _gap_ab, xa_list, xb_list = _ab_block_layout(float(spec.E), 1, C, x0, x1) + cr = min(0.026, 0.05 / max(C / 8, 1), 0.55 * h) + xv = xa_list[0] + leaves_x = list(xb_list) + + levels = _fanout_levels_from_leaf_xs(leaves_x, y_top=y_fan_top, y_bottom=y_fan_bot) + root_fan_x, root_fan_y = levels[0][0] + + ax.text(0.02, 0.94, f"row 0; R={spec.R}", fontsize=max(5.0, label_fs)) + ax.text(0.02, y_row + h / 2 + 0.05, "A block (v) | B block (width ∝ E)", fontsize=max(5.0, label_fs)) + + _rect(ax, xv, y_row, w, h, facecolor="#cfe8ff", edgecolor="#1a4f8c", linewidth=1.0) + ax.text(xv, y_row + h / 2 + 0.03, "v[r]", ha="center", va="bottom", fontsize=max(5.0, label_fs + 0.5)) + for i, xb in enumerate(xb_list): + _rect(ax, xb, y_row, w, h, facecolor="#ffe8cf", edgecolor="#8c4f1a", linewidth=1.0) + ax.text(xb, y_row + h / 2 + 0.03, f"B0,{i}", ha="center", va="bottom", fontsize=label_fs) + + for lev in levels: + for (x, y) in lev: + _circle(ax, x, y, cr, facecolor="#eef8ff", edgecolor="#333", linewidth=1.0) + + _wire_straight( + ax, + (xv, _rect_top(y_row, h)), + (root_fan_x, _circ_bottom(root_fan_y, cr)), + lw=0.85, + color="#333", + ) + + for li in range(len(levels) - 1): + parents = levels[li] + children = levels[li + 1] + for pi, (xp, yp) in enumerate(parents): + for ci in (2 * pi, 2 * pi + 1): + if ci < len(children): + xc, yc = children[ci] + _wire( + ax, + (xp, _circ_bottom(yp, cr)), + (xc, _circ_top(yc, cr)), + rad=0.035 * ((-1) ** (ci + pi)), + ) + + y_fan_leaves = levels[-1][0][1] + for i, x_lane in enumerate(leaves_x): + xb = xb_list[i] + y_out_c = y_leaf_circ + 0.09 + wo, ho = w * 0.9, h * 0.9 + _circle(ax, x_lane, y_leaf_circ, cr, facecolor="#e8ffe8", edgecolor="#333", linewidth=1.0) + _wire_straight( + ax, + (x_lane, _circ_bottom(y_fan_leaves, cr)), + (x_lane, _circ_top(y_leaf_circ, cr)), + lw=0.9, + color="#444", + ) + _wire( + ax, + (xb, _rect_top(y_row, h)), + (x_lane, _circ_bottom(y_leaf_circ, cr)), + rad=0.14 * ((-1) ** i), + color="#8c4f1a", + lw=1.0, + ) + _rect(ax, x_lane, y_out_c, wo, ho, facecolor="#dff5df", edgecolor="#1a6c2e", linewidth=0.9) + _wire_straight( + ax, + (x_lane, _circ_bottom(y_leaf_circ, cr)), + (x_lane, _rect_top(y_out_c, ho)), + lw=0.75, + color="#555", + ) + + if show_footer: + ax.text( + 0.5, + 0.01, + "Fanout tree + leaf ○ with B lanes.", + ha="center", + fontsize=6, + ) + + +def render_mergesort( + ax: Axes, + spec: TileShape, + *, + title_fontsize: float = 9, + label_fs: float = 5.5, + show_footer: bool = False, +) -> None: + """ + Multi-level compare–swap (2-in / 2-out) only: straight wires from data to each ○, + straight wires between levels on fixed tracks; no shuffle bus / no horizontal rails. + """ + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) + ax.axis("off") + _title_block(ax, "Mergesort (compare–swap)", spec, fontsize=title_fontsize) + + Ntile = spec.N + n_vis = min(Ntile, 8) + if n_vis < 2: + n_vis = 2 + if n_vis % 2 == 1: + n_vis -= 1 + m = n_vis // 2 + E = float(spec.E) + x0, x1 = 0.06, 0.94 + w, h, _g, _ggap, xa_list, xb_list = _ab_block_layout(E, m, m, x0, x1) + + y_reg = 0.84 + for i in range(m): + _rect(ax, xa_list[i], y_reg, w, h, facecolor="#cfe8ff", edgecolor="#1a4f8c", linewidth=0.9) + ax.text( + xa_list[i], + y_reg + h / 2 + 0.02, + f"A{i}", + ha="center", + va="bottom", + fontsize=max(4.5, label_fs - 0.5), + ) + for i in range(m): + _rect(ax, xb_list[i], y_reg, w, h, facecolor="#ffe8cf", edgecolor="#8c4f1a", linewidth=0.9) + ax.text( + xb_list[i], + y_reg + h / 2 + 0.02, + f"B{i}", + ha="center", + va="bottom", + fontsize=max(4.5, label_fs - 0.5), + ) + + note = f"n={n_vis} lanes" + (f" (tile N={Ntile})" if n_vis < Ntile else "") + ax.text(0.02, 0.97, note, fontsize=max(4.5, label_fs - 0.5)) + + n = 2 * m + xs_lane: List[float] = [] + for i in range(m): + xs_lane.append(xa_list[i]) + xs_lane.append(xb_list[i]) + y_rect_bot = _rect_bottom(y_reg, h) + cr = min(0.017, 0.32 / n, 0.45 * h) + leg = max(0.028, 0.05 - 0.002 * n) + max_stages = max(3, min(2 * n - 1, 10)) + stages = _odd_even_transposition_stages(n, max_stages) + + y_track = [y_rect_bot] * n + for pairs in stages: + y_in = min(y_track) + y_cs = y_in - leg - cr + y_next = y_cs - cr - 0.012 + paired: set[int] = set() + for (i, j) in pairs: + paired.add(i) + paired.add(j) + xm = 0.5 * (xs_lane[i] + xs_lane[j]) + _circle(ax, xm, y_cs, cr, facecolor="#f0e8ff", edgecolor="#333", linewidth=0.85) + ax.text( + xm, + y_cs, + "C&S", + ha="center", + va="center", + fontsize=max(4.0, label_fs - 1.0), + color="#222", + ) + _wire_straight(ax, (xs_lane[i], y_track[i]), (xm, _circ_top(y_cs, cr)), lw=0.75, color="#444") + _wire_straight(ax, (xs_lane[j], y_track[j]), (xm, _circ_top(y_cs, cr)), lw=0.75, color="#444") + dx = cr * 0.55 + _wire_straight(ax, (xm - dx, _circ_bottom(y_cs, cr)), (xs_lane[i], y_next), lw=0.75, color="#444") + _wire_straight(ax, (xm + dx, _circ_bottom(y_cs, cr)), (xs_lane[j], y_next), lw=0.75, color="#444") + y_track[i] = y_next + y_track[j] = y_next + for k in range(n): + if k not in paired: + _wire_straight(ax, (xs_lane[k], y_track[k]), (xs_lane[k], y_next), lw=0.65, color="#888") + y_track[k] = y_next + + y_cur = min(y_track) + y_out = max(0.07, y_cur - 0.025) + wo, ho = w * 0.82, h * 0.82 + for k in range(n): + _wire_straight( + ax, + (xs_lane[k], y_track[k]), + (xs_lane[k], _rect_top(y_out, ho)), + lw=0.55, + color="#666", + ) + for k in range(n): + _rect(ax, xs_lane[k], y_out, wo, ho, facecolor="#dff5df", edgecolor="#1a6c2e", linewidth=0.8) + ax.text(0.5, y_out - ho / 2 - 0.018, "merged lane values", ha="center", va="top", fontsize=5) + + if show_footer: + ax.text( + 0.5, + 0.015, + "▭ = key; ○ = compare–swap (2 in / 2 out); only straight segments, no shuffle / bus lines.", + ha="center", + fontsize=5, + color="#333", + ) + + +def _save_montage( + out_path: str, + shapes: List[TileShape], + render: Callable[..., None], + suptitle: str, + footnote: str, + *, + figsize: Tuple[float, float] = (28, 16), + dpi: int = 140, +) -> None: + fig, axes = plt.subplots(2, 4, figsize=figsize, dpi=dpi) + fig.suptitle(suptitle, fontsize=14, y=0.995) + for ax, spec in zip(axes.ravel(), shapes): + render(ax, spec, title_fontsize=8.5, label_fs=5.5, show_footer=False) + fig.text(0.5, 0.012, footnote, ha="center", fontsize=9, color="#222") + fig.subplots_adjust(left=0.03, right=0.97, top=0.93, bottom=0.06, hspace=0.38, wspace=0.22) + fig.savefig(out_path, bbox_inches="tight") + plt.close(fig) + + +def main(argv: Sequence[str] | None = None) -> int: + p = argparse.ArgumentParser(description="Plot 16 B tile vector datapath montages (4 PNGs).") + p.add_argument( + "-o", + "--out-dir", + default=os.path.join(os.path.dirname(__file__), "tile16_figures"), + help="Directory for PNG output (created if missing).", + ) + args = p.parse_args(list(argv) if argv is not None else None) + + os.makedirs(args.out_dir, exist_ok=True) + shapes = canonical_shapes() + + _save_montage( + os.path.join(args.out_dir, "elementwise_all.png"), + shapes, + render_elementwise, + suptitle=f"Element-wise — 8 tile shapes ({TILE_BYTES} B holding register each)", + footnote=( + "▭ = element (width ∝ E); all A contiguous, then all B; ○ = 2-input unit; " + "crossed bends on operand paths (cf. vector4k dual-port strip buffers)." + ), + ) + _save_montage( + os.path.join(args.out_dir, "reduce_all.png"), + shapes, + render_reduce, + suptitle=f"Reduce (representative row / fiber) — 8 tile shapes ({TILE_BYTES} B tile)", + footnote=( + "Per subplot: A block then B block (▭ width ∝ E); lane i pairing into ○, then reduction tree to Acc." + ), + ) + _save_montage( + os.path.join(args.out_dir, "expand_all.png"), + shapes, + render_expand, + suptitle=f"Expand / fanout — 8 tile shapes ({TILE_BYTES} B tile)", + footnote=( + "A block (v) then B block (▭ width ∝ E); v fans out; leaf ○ combines fanout with each B lane." + ), + ) + _save_montage( + os.path.join(args.out_dir, "mergesort_all.png"), + shapes, + render_mergesort, + suptitle=f"Mergesort (compare–swap) — 8 tile shapes ({TILE_BYTES} B tile)", + footnote=( + "Type 4: A|B keys (▭ ∝ E); multi-stage compare–swap (2-in/2-out ○); straight wires only, " + "odd–even transposition layers (schematic multistage C&S, not a full minimal merge depth proof)." + ), + ) + + print(f"Wrote 4 PNG montages to {args.out_dir}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/designs/outerCube/tile16_figures/elementwise_all.png b/designs/outerCube/tile16_figures/elementwise_all.png new file mode 100644 index 0000000000000000000000000000000000000000..2ad21101dcd4aaa76d9770554025619de6e3e6b8 GIT binary patch literal 480089 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

`xQNLoWgHdM0|EjgkXL zZFBW;lQ|apKL#@MJ%{37c;PkL#uZ86Lxy@|3X zk&qj(Pxz{Y;@Hm22Jo|gW5|h194Lk2woDmwv5oV%J%_3h>xSxv6~}B*W+RQBR*IfI zj5n2GRKLr{JBCP>o(+uL&myUymXX`#iv4Ty%I!R>Z*&A+W)99}y({rn!&E6XU zAFWV-1iN034G1DwnN^77%i8x--9pdFH-Y>+l(&)=KRVo4wczpEk>V-N<&q-rC!hPK zX!mg;l~+R+5#eEuW&>GxN(GQ}Ou#mM!0HYMh<1Q;m*6l5kphd}tVBjr=ivBbf;aS9 zlY&~=p4ZHthYNahKSmA@XGr9pdopC!wZ3{U@*z?Ti2uNxQyaUVV@0I-I>Sw^xV83{ z`7WT+9c!1NMzy~!cj^^tvDY`bbTtsTT>D;cCK1`=zLo;Q7C&Op+6`AXGwI54=|+n$ zh0a9z>csBRZVcM9q;CGEXF)6L)to=%k2iBtoJ3ai{y{+W3$A3)jwgC$N_R|SzcRT_ zwn`qaKYZ}WyR$*L{(?vn4xmimdnJTuF+6Xm}5?K}%-kLiwo(ajwi|2eZ(o-h#f zfER0-D_!EM&bCUI`2tPOVo1Igdy=|F1m>x+ zMe{T;W~{hJ>Qf`(!1^aH;6eNiMB;g!d8)w(fWSnlo!?9##L$WZ(xmHgZGk%cwu1Bh zzaK<}k5reqe>hL!d8{y1du(sJt2X1x;_h``Ock?2d#Ol?0uhZ(JtI~}*Qhc)!HcE$ z;}t0WR+PKJ?P$SKRQDb$9P-7nbXXGl*_sebJ(f9e>=IL&*8!^Naiz5;w8Qb%H{Sxe#(uRqzHea4CI zIB0}tzo1K*p*RLLBV^xagl36^wK3+A0Ud$uQPNhJI|YiLF@j1YF5acn;ARr6yVa)8 znIyAokEe|<9$V&t~)NcbBlSz zS@Gxby)6p*liw!CRz-G_%CCR}YLPZuvU1}3mtD36z02J#A&juW(76{S#Ff_qcE8Hr zEu0(>`XtF!@Io;@U74Zw_Qj!l8A?)3`-3H`oWzXN0gK8eY;iermky?BH^0bR(0pOEim0Ll@fq!QbDv4Rue__JB>J*%oIq3lyMQl@;tlWhdO;UDy zW|`_ot%#ZuNC7%DW8(Ge^~O9fPt_9{3KungfnzHy>a#0)_6o{4ey`0<3K=T|V2}NN z642SwT!>`UCoacU3nEW}J4VHXisz2zDT@@3O~p(eAj>JGW=6!_>^=z>5TIacIL|(!V@BpO!opl*QZI!eCW=hAy z6kLX85??XuAs0z#@AI5=dkSR+WQD|>>%wYVHG0Z$0Ncmy`|EO;Ksm8R{`iuRoYY(o zkKK^9_M8G%9cM96aZNBBGxsTvGL416wSs8vY@$KR%(RbC7(O^;$b02+ao18q-c-b0 zG*Z0sTG!E=TA_$!i7DTrGT1=4d(DnYLGO{kyUh;gyols&k!R{@mi9~VnEa~75_6hv z?*UDDRd>nas6D6MNEv*8Vs4K+&FKS-Hz2STTj0)Di=n-Ef(tk*`Ta=$0a1IEtE`v? zSAL~eh8XbX2z}RYHiJ;+T3mLgXD0L+0*f};b<4mM_QyDc@#W7i0B*1&G-``_vLbhH z4zX0xM-=ChA`KV1E~N4%n3I=VxQTICx=qmJMfha`+|#&0Z2lf4+LYtm3=rJ*=4Rt3(lVGOdT8MFEG8#aMYPU4dxV^UFrJ!pWQk` ziJdx{n2M7YOh-!YltrmBUF|-POey1BjPMq z!IFmy=Q-b@dtU7_by=5`yK+}wT}?$E&ff2^*>@T|`?;mz7Qpsia27nm#VcAI;H7_h zDSi^;vK7;%B4e{YnUOrU*sDV)fjY|_xbnG279q#3wYMYJX!q{k~u! z>grF{jD&2vC>F~*VjW9=&YYL~!t+CJSZ1E&eC#N8?LLa>@1%0qnMJ+iNOY;rg{8k2~Yn zGGglcPnQ`*#A^lHjGTubC58stw`^t&u>1j1h7Fr(Ya9Uy{JJ=;hr3b!+ANWVVpqy%!gniBjZL z*C?hZ(V})k@HGPgFbmx6!LQm6Q)2>zu6)|L3KG3}0hDz^q^i(}Wps^$Z`3Z~Qxm^! zSnG89j+#_|X0q4GY#se@RY+yeHazn0IJ?DsdFTw4xwYccmOmQY(W<78tm4*u=S97J}d?>cpKl|M@i zs}1T+L)D}ecXv+X;9dOZFe2$nR)yOSR<+D49b2aTueXUTkyR92#3j};0@8R#NX{

NX@YIsvAI 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 literal 0 HcmV?d00001 diff --git a/designs/outerCube/tile16_figures/mergesort_all.png b/designs/outerCube/tile16_figures/mergesort_all.png new file mode 100644 index 0000000000000000000000000000000000000000..33497a79490c2226bd217124a1950dc20b496957 GIT binary patch literal 609277 zcmeFZhg*|b*Ds7^^wCinWgJBWMx`i%i1cnnLRET4ML@cM^b*UcgHn_(RflGRfb^bN z5Tr)HfYhjzXef~qLI?@p+L@X6{oeBzoO7<@bvdI5N$&gJd#ztvJL-bb`R!ZxZRO+R z+m6vaYr@C3<0c>9e`^1;3BGcx$Akp`Q@e4_>V~P0%Z;GRe$IS`mv3D6^ts{bcIEd# zXFq>8A8+|%a>tJ;N&kNB#*OR#YO=Ck|MeZmeEeKx3t!xQ3$L=}x~{c9AK&gj(eHIl z{U@LDt>fdvoIPb0oH0GJInl$Mr9FG5WPMNKU%#6_a`-9teZP_AuNOA%JhD}H=enOZ z?*8@rm38Y*oz1g4>Gt6FbBUv2n@^?b?mQLBohrvpb!aOqKWXjAtW5}`hV*R>(lv_j zEvJ-|>M2L|#pvX$hY#@Izn;1ps1C~i_utWoxOdCb|M@+dI_nSrkFT7J zd(4M!-GBf3;Pk)c%e>-?Tq@5tH#LwuM{$}+vYe$wv$`^xu%5|NPc$8!$Y?|CWsqs29UsjYm=DI|qY%9Fo7ly!(aQ(jcodGBC$HD_rk zh16E6u+SlI`B1t{x3XJD$o#xW$W=!6+N!H=cG%}zn`3*q9+3u`jF05~{v8*KN}KnQ zPi+=9)8s7nlAGcq>WB<;lG7ws%(IegF|oiL4WsX5h4Y?VsvU;!C-kSAr|M?wrtm{P z#E4hlCEhjdkuW-aHmCbJVKGlO3CkRC&5mDOzip(N$eEd^6WFu195!XGSVFmaG}NPV zJYL@dZq)gJ0q2yY+|qtWY@^uSQQsC}@?49k<0i@Fm42euIQ^QD&7CMO=17n%CJW;d z*`huwH#&7+B=lM(+|%Umk&Hq|bLOzWF>AZ25zLVsO?BQSA8&p1&&s`Xyj{9H5o@eEkr-J}KUG|>ceiDdd2*(r*Qn6D=F2^I z0&B96u>4Loc4_*Rn9odQe=(Q7#w`h58gMP7hf~$b<6=>6Q$^JdxCbg^GgI^W=`#HQ z_%;h;O@C${OuA0TP5r`?+cUH2c4Qf)bjAN6iz z^qs-4vf7T8B#s@yo2eSINO1^tV|;RoOKPH-^=PjCcw0D;;uvWa|BR%)wo1FZ z$KhL;#$1yCImyz&nxIU{rVn3#yIs;<{6lxyk2gQ3%q$Ihlo(zpQ{!EBwBoLlP<8nD zF8!j#z!@iDv2za{6Qztcn6KG+RgwoS%C1c7#aq}H9XCH&kR7?woh64y~Z*Q81kx43hZ0|v-I(A^p$31!o{T_ zxBMshD;zr3F*EpUl=!XitdL3EWU5oF``z6ZhsoOVx^K(ckEy?IS9-fm-b|jP;ZHf# z&KzYDs>A8U^|Lnln6E3w*p*)QT<6S*8sSd8XN-`QAR^ly=edn{Beog*Q_8lznN0U_ z*tH~rU7e%7`B~Oe-Eh|#cyb@-nN%%iZIs6S#k!x5)jTzu(RVJDsUG&LaNR5+B)an9 zzMdbsU6yDEWyF^}{8QxLDD2ZgT$Nt7e-U2z>-KjM6Vv0n7t%)BA94fL2{hn7i z;F4a{piM3^**QhWu7$NQ^Wx3pw=BG;lr+ri1uTELZ&_Ge=Z60$+Fp(+mI6<}~u8?U^p{N^6N&@<<3sy($p1p{4~2fA>VlH2ZxvfiJ^!&3^+V2*?@ zbc&yvv8RVx4(yq4RG)r%hS4Nod}FmZRFg`nS)moS`2l)%Q9rFV--MaRxoMtLVp>Whsw|e(PVgF-4Q{J2 zHP#I1@6EW;gBfdB>d}`?;z&_b4Y5rZRg|NlS)u5;7zatoxVab$C&TOYLMY{b$ z_MWos{&u2plgQ}eBl`^}<)~RItnmc>y!zHt{z;#Y6ig2{+7gwZuJY3sru?ti-<7i= z1$0v11h&jvXn>|gK2nfj;`Q?NrimIH=^`#R`2B(Vi3?qSyywotE+l3g^^d+v?4W-< z_)yv;;>R7)7pR&gFHD^*uA|}ojM97vtTqi7hw^N3U*D%qBdg8M!i}66A6e@C>V^p! z@-BVpi(ar3$gqQY=H=4p;Tb>Klh?{(4=Nm>>HwEuz?P!Q4aC2hz-I9>tf`KCp^(x_m#9 zk}{zVAYiB~sCw$?kjGP&`sbS)_T8DAZZp-6XK_9YE~Qcq+Fw=XF?RP@ zoc8EaJ&lopg=(4|I9xD4;F{s9GeoL^BC~1OA|xF8^-K*RoypQ({lXt32>;r^R2HQd zWccZ|j6IKpYWSOTI)4fgx<`z)%M<5U6RQi^Ys1W}7Ll+Qbe7}l*F9qqlh&@8id|uw z(tPh0@s?f*zs&K6*Y-B9yp59O<-RQRuPn99jE?zl)I zJ(C{pI@V|F5T)93pQO_TK4-JwD@1|+L>Y>IB=7Fx!}B( zayF&)dWO&x<&JUWd;+ISKQcuvqPYainQB((HIV;2KO9DHjCmH^Uq5O-N?hnt=U-`L zsL@J_dR~lDN7hAvj3Ul4|0$oq!OOfAdUDO|U#zA#@zQL47;7S>u-8~SBBMVG>LRMkvxOBO zW6Y}9fVUj*&+_~}XLA<|OeW}*HEs~@FKf%4h5>KQ!ly~7QH})k+mqLA-hTm~zypfg zm4`oAo|?`In>}K_-qcNMce=^<6T8JsOTR;%(#ItxhX_8m`T9qfKmK%n&n2m&`8~E$ z&!AhHI%~}bN!`vn=!U;h376Q2&t4suoS>sd7#=j5zSRXcn?wt;CP2w*Fsl-xt<*tB z!S8Vhe&#b+5;5a3v6e1sz4^{#J--2;gXXg<3o$<7IfK|Wj(%ksTAyY+4BwGmvm>g^ zu5(7{k(NuPW_}(?4^p5ui?&g4`DuEY)>RE_FW>Gsx_j7U-J+8y+sc~D9WS5Hw5D3O z4CtVhuFt6Mu$9uzr_E3Sc$mz|u`lMgYUG1Ha+& zpXgO;uXM+)B+Ikc;F(x*c!&#trcZ{{W7zS=xcNPM9K+{=vu(9gXn$@nOlZ4Rlr(li zRyC`@&x2mfw?6wJdBXhJqV~^i45;tYRyN|P3>&GoWqqez)86Rxeh3xDx zC3>f7abIY$ht?jW=hGz~MU@r^xtaTf4GrOQV%6{&SpU^==3#1Dh9h?_4r?aL`F6fN z^0<{Mkwdd27ie?7&kKEYO!~f`_~ibyzG{iHqt@3y z54Fw3)2dZBR}YC6q`BiwVwM5=9idV@OTvSs+_;m<#3Mwx5JxRyaf9YAYIBD2ybx!~ zajnj5X4olCQ%}5GTEDLa&<@+=wF*(672QYSR=D|jqk^p$(3+u*uC=Uao(u4y%{ODET_GT>HSegR(Rhssog$SNLa&=D9ts8q+?+lMo7@c{N#`^G?BNYtbMII?j+iy=i{8Mm@zRGw zTK&mQwm4?p>P^{&T{BegfyMUnh z7!;O$K51r8UKF&t76CVmV278Rz3x!xHB`1AgN;WDE>X4=u!a6OWaJNDOk(y=IUP3P z$kQEzzuX-nI9TemJ3T)4o0|qKeBq-{JE6L6u&`Nu%^&%J8G^d2)9DevcA1Fmlm{7i z9=C-CoO@?sO%0iO2;JmnZ}^v=9rrGVYXg^#;i2C(wA*i*PcK2Gt;#md_FvFdY~o5o z2XwC6uJq}tTx~FLaUoG)mg*KG?LJ-o(5ZW(qfXhVXPkZcMI-Tn` zQ^<6RRc*Tg@R0p9MSOSc!l-@FNWcer(b1cerGUUIGABQr;h#yL1Oj5}beM+EV99v6 zs&vIb!}AQ0*{vZ{L7L&*XbfXC82hX@+^S-){BO_esR?30t!>rPK=a0wcBMM;ugq4G0$5uwuf;4JVGnhql&vO0o-a?e;613BK@P02!lEj? zg?#{dE+6x*v!hqH%iq7U!puZC+H83htGd-37nu6MzR2VjzW8d2SFdA0CAY6%SNC1L zu6Ywzij?Q)o-A)Z840!5OukdWw8hP~Ft?9S3i#;DEFKPYrca zj#3}CGg3Rt^8a$cnw**|@`>;m>Mx6gqWaE+bz%sYkQq3LGwXPW2n+d-HZ>8NZ-Uou z-1AQj(>Twx2f?`b8cHNPX=_1deiW4Gpbra~A%^5}-O5pJ?uvsHr7FSQANQLe?%q)L zx!W{i3HJ>tkvZ=2bAQGwSSHD5LvC{1x*(^G6y0UY11& zsM2rbaEn)s^4|Q=ZIkP<3y~)qnW`$A8<_Ud4pa&+w(c9}h_@V|ExmIK zeE`#9OT=5B84-|{FzQ>!Y@lj-32^UuWF=T3cDW?JYjr{vd!#pT48Sa1KlHng$W-pz zn(4Ckdldwo)0r7bSBtq~N6L+c!>H;M)#r5GT^Gc@7n|;Kno`5}d#Iy!Go(eyc$t7r z2p)2bKc8vv3A4*Wd;W3Ujc`r^;E~|=Pc+bCN~(ta+GzkHf)tVT^G8~TtH?l!2W(=? z7J6$1=9A#HHOH`-%J#XBZfm5{Quyxe!5x7|+s!1mW54S=;t@9}rqp+-O)ZM=v4`ao zL85+o2(k=8OuVFO&|SFTyX?BfD9NnnJJh~CEi*!u*1hg~{*)~}~HV?8H;J5WEfe8YP+B zR47&`UW!#&s($R(eQri~V*z;MS-PDEtc)fWuL-Xj1m!}=$zk_zC zX>`!3dR~a~eSez&vYme?xq7WSVL}7+!9qlRe*LNO+{@DAsRB;pUz1U;PHl*Htlt=A^);>q?$fs+M;E9o0{ZJtn$l zRplQ~@gH68aMd!k2PMKev)?PxK;1+na{0>%ek^;4f_F$>8v9wc;R3fSbjruE?ep)9 z6#|ga%^JqkAO-3P-l}_A?tA}_zspr!vKUD$JYUT^KAT3%E__j@hw0m=yQ>L_s04i4 z2J=zR;_(3tc6UXZz_m?j#D-D3fg3ec)ubcJ16LE`7fmeV4Fbv(`>b-rek!Wy(c>ID zlX>X_CSD?eZy7dyftYTdL!7Pmc;BIMXck^$>K z(j%Gm^LwYc*;i9;;W=cJi&HznKEPe+g(vGlN@g}DZ+r(zT@|QUT4r5)Fv-K7<(+Le zH)h7Woi~kG&Hd_F&%qI$t{taNz1^YyV!x2JD66PdjzeHJmY8Wf0Q74kZZQ8oOK7$48qgIQQzO8N7XQDbDC_4&>cW_9c)y?9z++O6cU zrtAKtU77|1wB#0T48(9DybCZ61`o=eJFPzBWKFpREt6uy7m! zv8mlpLhuNAp35R(legZ%Y&E>?A|=87!jHYC&L0r(8vmF!bHQ@O(}+*_TAB+;654u< z*VEdV49(@wcl5>UGX%=BXaSCv>B7&qj{PycA#%9d3Bzd%PF^nOjsK1 zE0Ki>|51CkTbj-G+Ivj(yrSi=#f3H1y%|3SR)^&F`x)td4aDD@C!4eO zRdEs1;Rn znaorm@dhu55FshEuKWx=m{e!Q9^jFo3|JdvA`%t(GvffUS2XopGtW!~ixH22qU zIq52Plh8EcgRR#l8OqmB`Jd}~&E3JTGYKGk`7}$NcoFGE;~NCuK-8&iQ`H=T|&F^U^vFHuS)gc2E` zbP`tv`w15I(6`87?O5(|{#sAr%&N3H;coUD`t=@))96X5=x!L!@tUor9$S+LDIuPH zSPcf>1U=om&J9RyZoQhX^V5KjtD&+la?Dz$O13&kg;u3grV9g-N|AveOu@FxRBvk! zKi2T$jX=ekiAFZeq?#S>zAe7ZKgh&xDU3 z5iQChL4z2;GpIac-J9Z>9Fbq?{&|~x z$Ax&!Lm1l-1TU4T#~>)Phw6$=1((l&SM;dgiIbknsnha4-y^9t7e!dWNTme! z*mWvUiH{R$b2Czv%pTwLK(oj*_xv64)j>5A z9aDX%)65HEJ`TC0d+C{+#>T6te9Ub(8?^ChWVBI0e{EA~J>A*50TqZpyZog*kVHab zYIg3=|#uw0I^Uf2RoAlbGX8mlP#}LylfNW0QBAIc4Ycy}gb6I++lwbL)<@T3_Ru%0!Vz- z5i&_eQ;Zt8=)l!z&$zmSS0Wk=+v11KWzTncNoL+K8TyVItp$>++bKwrXz}Xnto5$x z3Yw8Fdw-COJe&ETu3nIJ9od;$TM{|q?CON^73}Jm=$Vxm!5t$R2_`b~Nt);9hxY%zwErs`Z&!A>pe9*4CIeH2nG$f?4!a!s88j5UH zE84ux3Sgt&8To;&j0DWIC$ak)qrl``b@s;C2)EWJ0qAm?6)XCMc_{q;|K_8%|J z(?(W1W{3W~&8{_&Y(&jaA7X%3R$m8DRzIN1EY{QLlHhCz0q&rlc^Oi=#dk75D3`%J zLyk5^&#>o8<}Z)OJK-Z1&>QuS)n2V+&(<@=E5;cB=3h2wZ}EE)hfsFM`Kov5=eHC~ zS3<#A6raB;EM9wfN;ZxwC0bQZ!xt`@ADfmPaE50q+j`_N-41-U!USWjTCu^a17)2i z+viXd!TjQtEH+-(W*u*Z3=EUIaziR~ZE)t=t4OPom>-Zqh$J6vxhQqJksd)mN_I{V z%g{?6xQB7kSNvq=qzO7uO9%xy&xEVh(sU#B8M9r%U?@MKK`+OHI*{UiUpVlvdGoz9 zgc74Hlxc8?z5n9vbU&-BBWUS+hkks_BnW9$t$lZr++SLs$DIfVf@X#YBa0wr=)#r| zWZeGw*x}yEu%XO{G)7{C$^t97x*^YJtvJtp5>YK>!+No98-iq+Y{ayaXkjGy;fZIP zI!q_!*kSQE{k*4}>qi;I@qB4BNm4{>!fJXGT@9z+YGv!w7|=qhfoQ}^Z2OiX?$FWr zRU6BLU3HLuAei*&cm1SVJvsaXIo}zZ50&tX3zmd9z7Kp~WpLXjf*^DZY07$cdq7CR zI%jvtg&}#85-V*g)$HPE)Z?^k-<1F5>N3itvh)U#@zuwaoK?bA&iL>nMlBQvhInq3 z$OZxTyd(>nep5#z8)@?UH>+-@Hmp4%h3Bu{3k);bN2`^KznSS&F4AxSONILjiX*jM z$!M4N;3nKJ!#3MjKz$2gH5$!3j;645O~N95dM!R(y0g8lR<_LU4x=lGP`;lz0*G^` zVy}WWZR2WSNu|)WgHh3{js^CQwm!AS3hw`XNMdRn93Z-Ff$Dt`lsYgoH8eN;bDD~K zZ>JsqKAn-mcrxCSQBL#si7eTC)azxa2tQc7^Xl16eP6V^iHstjBzhYw02z2gY^2O# z)H(@?jFbRYr%zq+Luj zg3A%u;ufnqnc56?bw5)xQv?N!c=9K;5jp3SDOT9;$f4MW9GVuAHUM@ZTdzuU)*5)p z!JnwLA(8B`tBsnlm_g&4Dj|-7$tS!LL5KeJL4{}ZF)#I7=0EXkJ2DhW0;A@`-WSM1 zX$S&Eb~>mI#L#kIFzo|ZXIDxg6coc81ueI<6ja+KHUUU~qc3EOcGU3RT9_)wUOdST z9CgH6XjKzQDv%oyGBs2*A7X*@*{0v$@JD-@-i z)Vlg->*=Gu4*6N3wB|UdsgeC&3tZpTxfW%u<=6ays}17GR)5Vw;Du426$t*qi5b{h zRe*c*fw=76v$BbzO-(%1)V$2&In zCh2s#+}i4bvUlx7^4w@PN1?=(IAe_Mg-(I>07cqBn8;p(JkP#8cGpi4iRa$>dPnak z0#Wj^oVp4%LXg_XikX^>KzLD=xa}RZuHK-?Z}ile!1^5(^mj=1M&@7dT%+3|&euJ} zYun7Lr-~w_LF4rrQXg3X#zC1YPshI9ypk#O+zTElNdbvr#mR1v!BfEfw%I$t;3n5;$1iIzkae35(+6aFWk4JtueT($x^ns;Z(QZK)l?t zQXFm}^()+zs=Ts(h%MRT_Z35;BrmNOp(T?%DVNNEzRTHEgLGOZ?o~3)65i^%A{dtD z@Y@i#^ki!??oATj3zzjtDyi7yz#`~xC0d;C#wmJ!s6XPSd1gE<2&a>_HRtVtg;bWI zVsD4zr2pyMc$-{zr#?eT+=b+TFyt`=WV-tT^WORw4xfg%#trrr-*&Bfjl7uoWniuj zoq*(>)m_8#)2?YDg?gs8&2~Vt+}`xH!j#%VMu@R^soBEn#{w<#QUtClXe>rKS?{*B z@_Z0`3%3NnU?nMF{HB1Hx6jw+*91^aPvWkeLpA^ zkZO;Ff@Fz+8qP0$BIEKs!8Stnm&d|$ObFq)p$o^GhM@<-grB6O;~bCQROpb`&qMQ3F_aT-oe9`jm&uPs>oz!7Vi8} ztWSBTl7JnjI>i$_gFKoC9+Pk4v_#MF@t^HrF~Il^S1lMHR{f;+vD3sl80_ehWY}bW zM#8v&)Ce*5d4eo;Zq;SHA<4PT2UE4yrB1g4d=!$mb>f1SI@=(&5qY6$&dJtot_5{o zUEj~|_dSbidlKOxHXd}0co}Dr?r*g;6CM#w)` z+70vu)dSR<+yMF?-3Jkg+LWQ6&Rk_ZYzp5^91uIvUZA?(m$vb`cyjtCPz5r~9pvuf7oDhzTlU z#G_>}e?=ezo~s2gvXB%p>wkv(LhLW(Ly4EpLZah5rsfWYY^A20U-Y=?jhJ^ffl@!nQKQtF>j?3k|ed$Y`F2-$^HxVn}lZp{cF7o)BUKY83* zSFxwAt$5rx^+qnu6W^KtU=AV$a)ae>&#D*~sMU@e|Gk-GH~zh=_d0RmCxgOgzyOkI zHHCbxm=nrL2n38aKQIpeDnJHN!djENJVu5%iTYsqhJ*$)y@utl4|!>>6%?d|FK#=% zDg6_G=JuQaK^YXCu)w_sm=r8FpzTP38QR(bpa91$P5*rd#m@ZW^+e0@@-?qF37HjS z2HMmLu0ho<5Sx)++GOxY7}Kt<1ga|r&@oi)8z}ziHM1vNue+(xQvDi&R!3Xj+1w345y$vvx zLRbuSh$iWTDe*`ayhd}yk$tr}f2$I_mxTY^%#xe9h7#ca|6gPA|7S#=|LZgV-<0G2 ze;4(CO5*4m{Oq@+nn0oGyP zp54bE$xr=jWq=f^gkOMxf<IZuY4`lH_!-gYU1NN{(UEZoc-4? zUk-_Esyndm*XEn6%LO|hwScXzJUWH=gCc0nDgC`SpWx6zakw#Q%y3Fur|49)%@O$sXe4+jcKC z_(xOIw&YYRgKc~#eRc`(XQigT-X-ubY8~G<&97fx3-HTZeZ4$){J1ZM@2=cc0H}p% zq#=g63PSb;P2eJApbPR+3*^FE9m+&j=c%7gK!0KuB48H^>2T%07If%scW)MCh2qu5 zqUCkGM2Z{c+i1{baa;h9W+?qg?A3y#CmBWs>^J}=N(dlFS|YBGC&8eLc+K}dq`S~Q zni|WTTHT>OeZd|QJY*0yxHOb$y?M(Zwlwfo2U6_TgU!0v=*rjGs3^g$a;5O7ZjOpe z%)(_o`{nP;GW_~wHtN3M*14C#dsBn+v;L&~6kQevI!4|tlpjyIDCv%XL?!s)X0S>) zFC#2@jo6(I?2zMy*&7{UHDs^bVHa;=Z{bubSQp z7->k0{KmgQ;Lg^k$L}EoMuzy91!BDsB$Q#`LDqR2br>vJ!y{h|V8z`wU6r4uU!G(j zF(j13=x67*UXwlI9QpC$d2{`FnsQk z;oE$I1co+Ag%SXuJ@KM-*@(P0yx&m!sWcMsb!4(zT7#BSrn zkoOI_Uosgf-6>ZPY>)Zw@`T=Dxo}NNFwYrfl*1^OQskzt0oRmazlXQ52fxXI4DyQ7 z=xY9fY^N98tSFfw?FE%b$Ofu|v6jLOJ5-)}W>25#Y++nL6I(~us%(`z*B)YhHTDaLHjjD%&8(yYJlf!nfD z7zj&+C`(ocG+jSx9_{AnKz{(>2ng%CZ3V2eXfD(TN7{94PG zV5Gt|MnL82)hsj#6)l~xDJ+=3Hv!GV;thK)OOH?Mxlv`JW)a%2W z*m-q)d;g338rd`cAT1|8Maf5*TSnyxMrt?(8`CRSo1T z{OG-;0ko!_XbEw#;8WN3C0J`i&10_U9Qf`M7ASxE!tK6|zoRKKMaW+#M$ln3$mCRt zUFTKCt|jA}sR55?Kqa;`L!svO1#}^WdC4~T2G_&wslQ9yT0U@pzljzT<$zz0A$`d= z-xiJ1l=_29lpFzX45{%6%Eo?G*{kUW5Uz6;%aAs$cdE!lQzRK@$~v~}7pQQ>4qwZO zQ6orZXL6LNNp=eFxu%=*M(RKZ2hsUmlHH%W$5+}Nxn(Qg&THF%RD#9xhQg*u>-jEu zqPlqo7SRq=dz@lmLG6b+}C z6;KMOxv+#_Kk(xu&` z@bkU;bO7qkeOMgB27o1AS^Y16{T({f{a`DNVFOqwwEX=u8_h0#)vIMi^UZ@pRhsBY zx+Ws%mYaz_ne%vD^ynPSsW3HZI}e^v(=U&1h<~h;cHX!3nxJ6b!9#aHLLl++iNUkw z(Pw+EDYR8|Qj*-L;M)`EX|OR@@=5OnXELD4I{%S6Ilm-WGzL-HD-2kg$vf54@;pOz zUZ?(Iox(VLyb~f!l%$?n!I1)UO_R1u)F4=u#rat)YqFmkU_mAaRD*G}q?z*A@q`*i z=Z0HSKt_N~`+Jzk66ECfX|#2qp}DGaZA~p;P8}b0orR}!W-x05?tCWnME|zuN;6j# z5<+v+-XE5ST@YFcl6wmA(4jsJfNBxFJDdek5M++4-JW{UG>yzYrB#yR{A-pKBzFpt z|LUy3n*l|WTkTpY-M8y};i;r7==Pn_SI=+)fd5u7$!ueN$YDtMre3wiP^4i4xs^}|Umk@?Ekm7ZRA0Lu0Y7A!u z@7*i2wkCVvxJ2&j1~kcfPhXGFpVvNA3+1;E^(`k1uUd_*t|6<(m%9eg6%Q$fwWMZ` z!anM}{T2fxTSO9%#`G%Gn5@^FXb04s7~Q6X&w>?yF#?i##j$F%=Vcsa@M&l$XU5>US+%BJTDMR{ z)Rf!Fee~2BtW24yr#t-8HavNK%xNmLaBc0`eG&J1(F$*pg|WumS;b!mQq+VK?p)mL zSG4B@YAlJrgYeF_Mf1zsmsJNO7(d;!LVoqTI5b8h{z!BSV0TArlIrI1?(bjUM1!1k z5N(!ReO{a9_Y!>9G=q9`YT};&<*s~vCXwXYqz-+%qJGK^qMu09*RA@t_{iIWsUMuACPZr4=Y#vc*%yu zjYqefL@GY*zV_;6WSAy?R$`xXumucL$f9Or-?x7%QVZ_*^BBiX#PHhjT@{s^jL%%e zVs&iXx8IbHH>v-avwkTo9uKR5@@>mM}kYbXBkEj64(|fo5~Fd(Y;@cu1KCXhWHG^f77sGMwJo78>T zL`N&fJGV4zQ=pE0>o+;Kmokt8;Y8}(&O;L#3IsHHd7=)})=?lA3M7SLm_owjpgGc` z+n*uKzfpjto|ABOV}dM_Zd z&J|hFT7J;YlIZF%Lf5V$icAA|i)P>4nipy#H$WKM2}T+4p)xjpt~`>ZOF zeBH5-0xO#aM!}J(uV5xWM;!RXgEqien$ipEpDv#4c}@Z6rhP@7_B=<=n%&`a)!s#L z2b@ZA;$SPxLR+HTT*RS@8#zNWs*lhvAtSfxHd6N+rnMouC9Ah#Ss;7)Eak?QKX*MX zSU0j7Ueylpx`!F$1z`KJ$F8Sn6j=JuSO$Pi=j`J1=C}!~*1$|a6!`8(9L8qt#w!vEFc z-~RldsfmLazim|1kbuAjzT>!^{7hq52j1c$-o_f+qSH1VA5aJWEpK;z49)Kc)gfEE z6hMC5!Z1ilJu1krM|z7BJS49UFn?pv;M^R{juyDg5E-E_dAqII`F_1dQ38#bm(~n{TEl(!lwT>)A03)o-q$3Vi z`r-7?iuo`-cfvlOPC8aU-4hJRO>=ZXmau$Rw1B^rZqeY*(^^$cqp>HL41|v%#yZRf0EP72YffOS&0JC!<(6 zY-7d10_-CKCVy%~fn2c6AN?5-vz!_gBX&WlOWFS|WNLccvbF?e35cmN(e zcys;6m)(D9tD&`Aq9v>l*$IFwRiZnYHWZ}o)23B|KL&+x=y~mh3Mt@qR)N3 z%y&u5p!=G_zC-h^|K%_6{;;hz@l4p?p6!4A!=ZEk7@q%rg?Cy}0uH8Wa?lAec<8U; zx3xWB2>&!WZ4Pqx=W8UmxP+Q7(dp@VMQKVN zXiCpIDoGTcrhCO;2V@#Oc_0~$HA@U?@Qt*y4p5^qWZ}&(iN!QmO2Hq#{Zz#C=QGd~ zyLkeS&Kq$DrjHv1@i#?ir~hLX6cpi7dOe`eLO6|~ESv=WvY&C69vFuxzE9mGb%6x| zUTh_Vdn}ZPub-hPfOR|w+Y=9gfP6&c@IdKm(ZE|F%qZrE!b56ije-VSGYAnJJTE%{ zRj3dhWWiJQC9{J6nsVm6la0KXPYODr;>ev(DFi*LRt-vm4yTzo$#DnU{)H0E&(L6g zKxm211^{mx0lfMc1)UO*9I=cDLI9Fv1bWffk{3j6V06Q;1a{t`l?YK#G1{2Z@WW@g z(Hqxib_rN#=S;vfpD_bM2aO#q01ZYM>_|9XAUXXrI&`K?I1;c%0D}-nZAWQs@_ntv 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

cnk|mY=n5hnljh2_n-tRc$8sF8%Y5icge} z%gWk4nmxh`z8y^;M$8kKi)bri!ps8f>4f98eZ0L}T%vHK#KbNkegu0{VafP6Z{Ft0 z?hTod9hQ3f=Zo4Zpj8Be#a6EmIhR#xr6<7MI-G z7f{h8O(^uFM!+PGbFx^!;^0o*I7Y$Rr@Jax81K zH{J$f1BoHGmx#j#=fh$~iffl5XHaPW1~r~e=n{T|P+{PpkKK4oQDVE)E@fTThv^;K z08^n;Tjdyg=1@69w>pB@5#%S>8lLRW7T}sr1I`-GnbrN(JE%TFYB~q&dJOs0gKV{Y z#t%#?VfwSOk%^|q3~(`pfiOMQgzZ!piz?FhUFBsXt0O;=8dS{lAgnpT=T9?A6KLVrqx9s+{Ivm9TW` zfr4H2tM8REOwQcg>uu%$hDO=1N|@&QH`3#UFwdjV7PU2HFAmn4KA zhVo&jR_qd7X{5WbFr{E&cBwRZ17N1f0?<(>v>oiQ@@R>ARTwY9>TGd z`g*eQ)*z+14N=!SRX$R&Zg2fPFHcnr!Gq!Q1tyH$o0h^m=cOURgqD+MNI0W1zvK76 zt0oqB*Z3>phfkVCI;wlSukTnn@fOjEzLViFXi;Bp+wD-D4eR_U@eOe6pFL|3caaz0 zY3@5atK<4sDxL>zpSvnQLv_EO@>aUVSzk8nCFQ2l+Or52%~a?(%8}hc-Vjm}hBB1? z?azwlK%r1tuTp73QOhsIBncgh;MU;ASJ#b3nOzeU=6_2~SL8_5*%hVW*MD)9h8?eB zCNN@`mxACzg4@P$rK}~7sss1c%a@hj(}Zbvr%9XM<0Gs9roViT&cjf;x(k88<{XC8 zdCSW^RJ+ZFUf%k+iW&ZX!AbPPUU}6D-Laes5lU`0z1GA>s!X-0+UlLiRl0x(U?}UI zhjC3mL;m}feQ-HUfu&k#jm!GC;LrMv%zNFHNr!T)JG6&GL0Ctp_^vZmSa|E


4pX?sY)!zCmh43+X5>C44FPS~L*N(AIN-B-){= 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

iSu$5rr@^9n@@&d?nUIXJY*?0SZuZN+N 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%0DK?j_5g+a15zL@Y4}+^fu<2c5vs+Lp#7;RY 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

N8-Z?z?x+qSI)` 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 O=lT`fKg%zB#Qs028Q8@D literal 0 HcmV?d00001 diff --git a/designs/outerCube/tile16_figures/expand_all.png b/designs/outerCube/tile16_figures/expand_all.png new file mode 100644 index 0000000000000000000000000000000000000000..5fef8ff4a289c722e0e39d4c61f45b19de5f6dfb GIT binary patch literal 342947 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�P?#-ek#fXB7Y2+ab^osV`rvRmPOOE6Z~6Fm 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