#include "../lib/lib.h"
#include <cstdint>
#include <iterator>

#define BysIdx(VarA, BaseB) VarA[BaseB*64]
#define VecIdx(VarA, BaseB) VarA[BaseB*16]

void FrodoDecaps_Hard(string cin_file, string skin_file, string ssout_file)
{

#pragma comment (user, "parameter definations")

	uint32_t SHAKE_128 = 0;
	uint32_t SHAKE_256 = 1;

	// FrodoKEM-640
	uint32_t D = 15;
	uint32_t q = 32768;
	uint32_t n = 640;
	uint32_t n_bar = 8;
	uint32_t B = 2;
	uint32_t len_A = 128/8; 	// in bytes
	uint32_t len_sec = 128/8;
	uint32_t SHAKE = SHAKE_128;

	uint32_t len_SE = 256/8;
	uint32_t len_salt = 256/8;
	// if eFrodoKEM, use these
	uint32_t elen_SE = 128/8;
	uint32_t elen_salt = 0;

	uint32_t A_tmp_len = 0;

	// for VectorIR use
	uint32_t len_matrix_n_nbar = n*n_bar;
	uint32_t len_matrix_nbar_nbar = n_bar*n_bar;

	// for ByteStringIR use, all len* are in bytes
	//uint32_t len_u1 = B*n_bar*n_bar/8;
	//uint32_t len_seed_SE1_k1 = len_sec + len_u1 + len_salt;
	// below takes up more than one line
	//uint32_t len_b = D*n*n_bar/8;	// 150 lines
	//uint32_t len_c1 = D*n*n_bar/8;	// 150 lines
	//uint32_t len_c2 = D*n_bar*n_bar/8;	// 2 lines
	//uint32_t len_S_T_string = len_matrix_n_nbar*16/8;	// 640*8*16/512 = 160 lines
	//uint32_t len_sk = len_sec+len_A+len_b+len_S_T_string+len_sec;	// 311 lines
	
	uint32_t lines_sk = 311;
	uint32_t lines_c = 153;
	uint32_t lines_c1 = 150;
	uint32_t lines_c2 = 2;


#pragma message ("offline execution: prepare consts")

#pragma message ("online execution")

	Config_ALUon();
	Config_ALUconst(1,1);
	Config_ALU2_ADD();

	// 0. extract from ciphertext c and sk

	MByteStringIR Msk;
#pragma comment(user, "Msk:0")
	Msk.Initial_FromFile(skin_file, lines_sk);

	ByteStringIR sk;
#pragma comment(user, "sk:693")
	sk.Initial_FromMByteStringIR(&Msk, lines_sk);

	ByteStringIR seed_A;
#pragma comment(user, "seed_A:1017")
	VectorIR seed_A_vec;
#pragma comment(user, "seed_A_vec:1017")
	// now seed_A has s||seed_A
	seed_A = Hard_ByteStringIRFromByteStringIR(&sk, 1);

	ByteStringIR s;
#pragma comment(user, "s:1018")
	VectorIR s_vec;
#pragma comment(user, "s_vec:1018")
	s = Hard_ByteStringIRFromByteStringIR(&sk, 1);

	// extract seed_A
	seed_A_vec = Hard_CoeffShiftCoeffs(&seed_A_vec, 1, 0, 1, 1, 128);

	// clean s, leave least 128 bits only
	s_vec = Hard_CoeffShiftCoeffs(&s_vec, 1, 0, 1, 0, 512-128);
	s_vec = Hard_CoeffShiftCoeffs(&s_vec, 1, 0, 1, 1, 512-128);


	ByteStringIR b;
#pragma comment(user, "b:693")
	VectorIR b_vec;
#pragma comment(user, "b_vec:693")

	ByteStringIR b_string_part_0_0;
#pragma comment(user, "b_string_part_0_0:693")
	ByteStringIR b_string_part_0_1;
#pragma comment(user, "b_string_part_0_1:723")
	ByteStringIR b_string_part_1_0;
#pragma comment(user, "b_string_part_1_0:730")
	ByteStringIR b_string_part_1_1;
#pragma comment(user, "b_string_part_1_1:760")
	ByteStringIR b_string_part_2_0;
#pragma comment(user, "b_string_part_2_0:768")
	ByteStringIR b_string_part_2_1;
#pragma comment(user, "b_string_part_2_1:798")
	ByteStringIR b_string_part_3_0;
#pragma comment(user, "b_string_part_3_0:805")
	ByteStringIR b_string_part_3_1;
#pragma comment(user, "b_string_part_3_1:835")


	VectorIR b_vec_part_0_0;
#pragma comment(user, "b_vec_part_0_0:693")
	VectorIR b_vec_part_0_1;
#pragma comment(user, "b_vec_part_0_1:723")
	VectorIR b_vec_part_1_0;
#pragma comment(user, "b_vec_part_1_0:730")
	VectorIR b_vec_part_1_1;
#pragma comment(user, "b_vec_part_1_1:760")
	VectorIR b_vec_part_2_0;
#pragma comment(user, "b_vec_part_2_0:768")
	VectorIR b_vec_part_2_1;
#pragma comment(user, "b_vec_part_2_1:798")
	VectorIR b_vec_part_3_0;
#pragma comment(user, "b_vec_part_3_0:805")
	VectorIR b_vec_part_3_1;
#pragma comment(user, "b_vec_part_3_1:835")


	// shift b and ST_string
	b_vec = Hard_CoeffShiftCoeffs(&b_vec, lines_sk, 0, 1, 1, 256);

	ByteStringIR pkh;
#pragma comment(user, "pkh:1019")
	ByteStringIR pkh_sk;
#pragma comment(user, "pkh_sk:1003")
	// copy pkh from sk
	pkh = Hard_ByteStringIRFromByteStringIR(&pkh_sk, 1);


	MByteStringIR Mc;
#pragma comment(user, "Mc:400")
	Mc.Initial_FromFile(cin_file, lines_c);

	ByteStringIR c;
#pragma comment(user, "c:512")
	c.Initial_FromMByteStringIR(&Mc, lines_c);

	ByteStringIR salt;
#pragma comment(user, "salt:1020")
	VectorIR salt_vec;
#pragma comment(user, "salt_vec:1020")
	// salt in string c
	ByteStringIR salt_c;
#pragma comment(user, "salt_c:663")
	// copy salt from c
	salt = Hard_ByteStringIRFromByteStringIR(&salt_c, 2);
	// clean salt
	salt_vec = Hard_CoeffShiftCoeffs(&salt_vec, 2, 0, 1, 1, 512-64);


	// Bp = Unpack(c1)

	VectorIR c1_vec;	// first 150 lines of c
#pragma comment(user, "c1_vec:512")

	// the first step of Unpack
	c1_vec = Hard_CoeffReverse(&c1_vec, lines_c1, 0, 0, 1, 1);

	// c1 is divided into 2 blocks: 75+75 lines
	ByteStringIR c1_string_block_0;
#pragma comment(user, "c1_string_block_0:512")
	ByteStringIR c1_string_block_1;
#pragma comment(user, "c1_string_block_1:587")
	VectorIR Bp_vec;
#pragma comment(user, "Bp_vec:0")

	// ST_string is divided into 2 blocks: 80+80 lines
	ByteStringIR ST_string_block_0;
#pragma comment(user, "ST_string_block_0:843")
	ByteStringIR ST_string_block_1;
#pragma comment(user, "ST_string_block_1:923")
	VectorIR ST_vec;
#pragma comment(user, "ST_vec:256")


	// calculate Bp*S
	
	VectorIR BpS_vec;
#pragma comment(user, "BpS_vec:192")

	ControlScalarIR k_cs;
#pragma comment(user, "k_cs:2")
	ControlScalarIR k_low_cs;
#pragma comment(user, "k_low_cs:3")
	ControlScalarIR k_high_cs;
#pragma comment(user, "k_high_cs:4")
	ControlScalarIR Bp_vec_index_cs;
#pragma comment(user, "Bp_vec_index_cs:5")
	ControlScalarIR ST_vec_index_cs;
#pragma comment(user, "ST_vec_index_cs:6")
	ControlScalarIR BpS_vec_index_cs;
#pragma comment(user, "BpS_vec_index_cs:7")
	IVectorIR no_use;
#pragma comment(user, "no_use:0")

	// the first loop

	// remaining steps of Ubpack
#pragma comment(user, "CLUSTER:0")
	Bp_vec = Hard_VectorIRFromByteStringIR_Dir(&c1_string_block_0, 75, 160, 15, 32, 0, 512*75, 512*160);
	Bp_vec = Hard_CoeffReverse(&Bp_vec, 160, 0, 0, 0, 1);
#pragma comment(user, "CLUSTER:0")
	Bp_vec = Hard_Vec_Shift(&Bp_vec, 160, 17, 1);

	// parallel execution to Unpack
#pragma comment(user, "CLUSTER:1")
	ST_vec = Hard_VectorIRFromByteStringIR_Dir(&ST_string_block_0, 80, 160, 16, 32, 0, 512*80, 512*160);

	for (k_cs.scalar_data = 0; k_cs.scalar_data < 160; k_cs.scalar_data = k_cs.scalar_data + 1) {

		k_low_cs.scalar_data = k_cs.scalar_data & 3;
		k_high_cs.scalar_data = k_cs.scalar_data >> 2;
		Bp_vec_index_cs.scalar_data = k_low_cs.scalar_data * 20;
		ST_vec_index_cs.scalar_data = k_high_cs.scalar_data * 20;
		BpS_vec_index_cs.scalar_data = k_high_cs.scalar_data << 3;
		BpS_vec_index_cs.scalar_data = BpS_vec_index_cs.scalar_data ^ k_low_cs.scalar_data;

		VecIdx(BpS_vec,BpS_vec_index_cs.scalar_data) = Hard_VecMulVec(VecIdx(Bp_vec,Bp_vec_index_cs.scalar_data), VecIdx(ST_vec,ST_vec_index_cs.scalar_data), 160, 0,0,0,0,0,0,0,no_use,0,0,0,0);

	}

	// the second loop

	// remaining steps of Ubpack
#pragma comment(user, "CLUSTER:0")
	Bp_vec = Hard_VectorIRFromByteStringIR_Dir(&c1_string_block_1, 75, 160, 15, 32, 0, 512*75, 512*160);
	Bp_vec = Hard_CoeffReverse(&Bp_vec, 160, 0, 0, 0, 1);
#pragma comment(user, "CLUSTER:0")
	Bp_vec = Hard_Vec_Shift(&Bp_vec, 160, 17, 1);

	// parallel execution to Unpack
#pragma comment(user, "CLUSTER:1")
	ST_vec = Hard_VectorIRFromByteStringIR_Dir(&ST_string_block_1, 80, 160, 16, 32, 0, 512*80, 512*160);

	for (k_cs.scalar_data = 0; k_cs.scalar_data < 160; k_cs.scalar_data = k_cs.scalar_data + 1) {

		k_low_cs.scalar_data = k_cs.scalar_data & 3;
		k_high_cs.scalar_data = k_cs.scalar_data >> 2;
		Bp_vec_index_cs.scalar_data = k_low_cs.scalar_data * 20;
		ST_vec_index_cs.scalar_data = k_high_cs.scalar_data * 20;
		BpS_vec_index_cs.scalar_data = k_high_cs.scalar_data << 3;
		BpS_vec_index_cs.scalar_data = BpS_vec_index_cs.scalar_data ^ k_low_cs.scalar_data;
		BpS_vec_index_cs.scalar_data = BpS_vec_index_cs.scalar_data ^ 4;

		VecIdx(BpS_vec,BpS_vec_index_cs.scalar_data) = Hard_VecMulVec(VecIdx(Bp_vec,Bp_vec_index_cs.scalar_data), VecIdx(ST_vec,ST_vec_index_cs.scalar_data), 160, 0,0,0,0,0,0,0,no_use,0,0,0,0);

	}


	// form vector
#pragma comment(user, "CLUSTER:1")
	BpS_vec = Hard_VectorIRFromVectorIR_Dir(&BpS_vec, 64, 4, 32, 32, 0, 32, 4*512);


	// 2. C = Unpack(c2)

	// no need to form matrix, since BpS is vector now

	ByteStringIR c2;
#pragma comment(user, "c2:662")
	VectorIR C_vec;
#pragma comment(user, "C_vec:0")

#pragma comment(user, "CLUSTER:0")
	C_vec = Hard_VectorIRFromByteStringIR_Dir(&c2, 2, 4, 15, 32, 0, 960, 4*512);


	// 3. M = C-Bp*S
	VectorIR M;
#pragma comment(user, "M:0")

#pragma comment(user, "CLUSTER:0")
	M = Hard_Vec_Sub(&C_vec, &BpS_vec, 4);



	// 4. u_p = Decode(M)

	ByteStringIR u_p;
#pragma comment(user, "u_p:1021")

#pragma comment(user, "CLUSTER:0")
	M = Hard_Vec_AndConst(&M, 4, 0, 32767);
#pragma comment(user, "CLUSTER:0")
	M = Hard_Vec_AddConst(&M, 4, 12);	// D-B-1 = 12
#pragma comment(user, "CLUSTER:0")
	M = Hard_Vec_Shift(&M, 4, D-B, 1);
#pragma comment(user, "CLUSTER:0")
	u_p = Hard_ByteStringIRFromVectorIR_Dir(&M, 4, 1, B, 8, 0, 128, 128);


	//////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////


	// 5. gen seed_SE_p and k_p

	salt_vec = Hard_CoeffShiftCoeffs(&salt_vec, 1, 0, 1, 0, 256);

	VectorIR u_p_vec;	
#pragma comment(user, "u_p_vec:1021")

	u_p_vec = Hard_CoeffShiftCoeffs(&u_p_vec, 1, 0, 1, 1, 128);

	VectorIR seed5_vec;
#pragma comment(user, "seed5_vec:1019")
	ByteStringIR seed5;
#pragma comment(user, "seed5:1019")

#pragma comment(user, "CLUSTER:0")
	seed5_vec = Hard_Vec_Xor(&seed5_vec, &salt_vec, 1, 0);
#pragma comment(user, "CLUSTER:0")
	seed5_vec = Hard_Vec_Xor(&seed5_vec, &u_p_vec, 1, 0);

	// now seed5 has pkh||u'||salt

	// seedSE'||k'
	ByteStringIR seed_SE_p_k_p;
#pragma comment(user, "seed_SE_p_k_p:1022")
	seed_SE_p_k_p = Hard_Shake3_Static(&seed5, 1, 1, SHAKE, 0,0,0,0,48);


	// 6. gen seed_r; use DGW to gen Sp, Ep, Epp later

	// shift seedSE'||k' to from 0x96||seedSE'||k'
	VectorIR seed_SE_p_k_p_vec;
#pragma comment(user, "seed_SE_p_k_p_vec:1022")
	seed_SE_p_k_p_vec = Hard_CoeffShiftCoeffs(&seed_SE_p_k_p_vec, 1, 0, 1, 0, 8);
#pragma comment(user, "CLUSTER:0")
	seed_SE_p_k_p_vec = Hard_Vec_XorConst(&seed_SE_p_k_p_vec, 1, 0, 150);	// 0x96 = 150(dec)


	ByteStringIR seed_r;		// seed_r is 0x96||seedSE'
#pragma comment(user, "seed_r:1022")


	// 7. sample Sp

	// type VectorIR, for Gauss Sample use only
	VectorIR Sp_string_initial_vec;
#pragma comment(user, "Sp_string_initial_vec:856")
	// TODO out_coeff
#pragma comment(user, "CLUSTER:0")
	Sp_string_initial_vec = Hard_PRNG_Gauss_Sample(&seed_r, 160, 1, 0, 0, 16, 0, 1, 13, 0, 0, SHAKE, len_SE+1, 0);

	ByteStringIR Sp_string_block_0_0;
#pragma comment(user, "Sp_string_block_0_0:856")
	ByteStringIR Sp_string_block_0_1;
#pragma comment(user, "Sp_string_block_0_1:861")
	ByteStringIR Sp_string_block_0_2;
#pragma comment(user, "Sp_string_block_0_2:866")
	ByteStringIR Sp_string_block_0_3;
#pragma comment(user, "Sp_string_block_0_3:871")
	ByteStringIR Sp_string_block_0_4;
#pragma comment(user, "Sp_string_block_0_4:876")
	ByteStringIR Sp_string_block_0_5;
#pragma comment(user, "Sp_string_block_0_5:881")
	ByteStringIR Sp_string_block_0_6;
#pragma comment(user, "Sp_string_block_0_6:886")
	ByteStringIR Sp_string_block_0_7;
#pragma comment(user, "Sp_string_block_0_7:891")

	ByteStringIR Sp_string_block_1_0;
#pragma comment(user, "Sp_string_block_1_0:896")
	ByteStringIR Sp_string_block_1_1;
#pragma comment(user, "Sp_string_block_1_1:901")
	ByteStringIR Sp_string_block_1_2;
#pragma comment(user, "Sp_string_block_1_2:906")
	ByteStringIR Sp_string_block_1_3;
#pragma comment(user, "Sp_string_block_1_3:911")
	ByteStringIR Sp_string_block_1_4;
#pragma comment(user, "Sp_string_block_1_4:916")
	ByteStringIR Sp_string_block_1_5;
#pragma comment(user, "Sp_string_block_1_5:921")
	ByteStringIR Sp_string_block_1_6;
#pragma comment(user, "Sp_string_block_1_6:926")
	ByteStringIR Sp_string_block_1_7;
#pragma comment(user, "Sp_string_block_1_7:931")

	ByteStringIR Sp_string_block_2_0;
#pragma comment(user, "Sp_string_block_2_0:936")
	ByteStringIR Sp_string_block_2_1;
#pragma comment(user, "Sp_string_block_2_1:941")
	ByteStringIR Sp_string_block_2_2;
#pragma comment(user, "Sp_string_block_2_2:946")
	ByteStringIR Sp_string_block_2_3;
#pragma comment(user, "Sp_string_block_2_3:951")
	ByteStringIR Sp_string_block_2_4;
#pragma comment(user, "Sp_string_block_2_4:956")
	ByteStringIR Sp_string_block_2_5;
#pragma comment(user, "Sp_string_block_2_5:961")
	ByteStringIR Sp_string_block_2_6;
#pragma comment(user, "Sp_string_block_2_6:966")
	ByteStringIR Sp_string_block_2_7;
#pragma comment(user, "Sp_string_block_2_7:971")

	ByteStringIR Sp_string_block_3_0;
#pragma comment(user, "Sp_string_block_3_0:976")
	ByteStringIR Sp_string_block_3_1;
#pragma comment(user, "Sp_string_block_3_1:981")
	ByteStringIR Sp_string_block_3_2;
#pragma comment(user, "Sp_string_block_3_2:986")
	ByteStringIR Sp_string_block_3_3;
#pragma comment(user, "Sp_string_block_3_3:991")
	ByteStringIR Sp_string_block_3_4;
#pragma comment(user, "Sp_string_block_3_4:996")
	ByteStringIR Sp_string_block_3_5;
#pragma comment(user, "Sp_string_block_3_5:1001")
	ByteStringIR Sp_string_block_3_6;
#pragma comment(user, "Sp_string_block_3_6:1006")
	ByteStringIR Sp_string_block_3_7;
#pragma comment(user, "Sp_string_block_3_7:1011")

	// 12-13: Unpack(b) and calculate Sp*B on-the-fly 

	// first step of Unpack
	b_vec = Hard_CoeffReverse(&b_vec, 150, 0, 0, 1, 1);
	
	
	// Sp_col is a 8*160 block of Sp
	// here is the 8 lines of the block
	VectorIR Sp_col_0_vec;
#pragma comment(user, "Sp_col_0_vec:0")
	VectorIR Sp_col_1_vec;
#pragma comment(user, "Sp_col_1_vec:10")
	VectorIR Sp_col_2_vec;
#pragma comment(user, "Sp_col_2_vec:20")
	VectorIR Sp_col_3_vec;
#pragma comment(user, "Sp_col_3_vec:30")
	VectorIR Sp_col_4_vec;
#pragma comment(user, "Sp_col_4_vec:40")
	VectorIR Sp_col_5_vec;
#pragma comment(user, "Sp_col_5_vec:50")
	VectorIR Sp_col_6_vec;
#pragma comment(user, "Sp_col_6_vec:60")
	VectorIR Sp_col_7_vec;
#pragma comment(user, "Sp_col_7_vec:70")

	// indicate the whole 8*160 Sp_col block, used in matmat-mul
	VectorIR Sp_col_vec;
#pragma comment(user, "Sp_col_vec:0")
	
	// matrix B has 2 parts: 128+32 lines
	VectorIR B_row_vec_part_0_tmp;
#pragma comment(user, "B_row_vec_part_0_tmp:384")
	VectorIR B_row_vec_part_1_tmp;
#pragma comment(user, "B_row_vec_part_1_tmp:623")
	VectorIR B_row_vec_part_0;
#pragma comment(user, "B_row_vec_part_0:256")
	VectorIR B_row_vec_part_1;
#pragma comment(user, "B_row_vec_part_1:512")

	// intermediate results for matmat-mul
	VectorIR SpB_mat_tmp;
#pragma comment(user, "SpB_mat_tmp:120")
	// results for matmat-mul
	VectorIR SpB_mat;
#pragma comment(user, "SpB_mat:545")
	
	//////////////////////////////////////////////////////
	// matmat-mul:
	// Sp = [Sp0, Sp1, Sp2, Sp3]
	// B = [B0, B1, B2, B3]^T
	// thus Sp*B = Sp0*B0 + Sp2*B2 + Sp1*B1 + Sp3*B3
	//////////////////////////////////////////////////////

	//////////////////////////////////////////////////////
	// Sp0*B0
	//////////////////////////////////////////////////////

	// extract Sp
	// 2 parallel reads
#pragma comment(user, "CLUSTER:0")
	Sp_col_0_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_0_0, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:1")
	Sp_col_2_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_1_0, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:0")
	Sp_col_1_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_0_4, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:1")
	Sp_col_3_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_1_4, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_4_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_2_0, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_5_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_2_4, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_6_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_3_0, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_7_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_3_4, 5, 10, 16, 32, 0, 256, 512);

	// form matrix Sp, use CLUSTER3 to allow parallel execution with Unpack(b)
#pragma comment(user, "CLUSTER:3")
	Sp_col_vec = Hard_BankTransform(&Sp_col_vec, 80, 0, 1, 0, 0, 0);

	// Unpack(b) and from matrix B
	// CLUSTER0 - Unpack part 0: 30 rows of b -> 128 rows of B_row
	// CLUSTER1 - Unpack part 1: 7.5 rows of b -> 32 rows of B_row, put in bank4 to form matrix

	// TODO parallel write (?)
#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_VectorIRFromByteStringIR_Dir(&b_string_part_0_1, 8, 16, 15, 32, 0, 15*64, 32*64); 
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_VectorIRFromByteStringIR_Dir(&b_string_part_0_0, 30, 64, 15, 32, 0, 15*64, 32*64); 
	// reverse/shift in vector, then form matrix B, to half the cycles cost
//#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_CoeffReverse(&B_row_vec_part_0_tmp, 64, 0, 0, 0, 1);
//#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_CoeffReverse(&B_row_vec_part_1_tmp, 16, 0, 0, 0, 1);
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_Vec_Shift(&B_row_vec_part_0_tmp, 32, 17, 1);
#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_Vec_Shift(&B_row_vec_part_1_tmp, 16, 17, 1);
	// form matrix B from VectorIR
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0 = Hard_VectorIRFromVectorIR_Dir(&B_row_vec_part_0_tmp, 64, 128, 32, 32, 0, 32*16, 16*16);
#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1 = Hard_VectorIRFromVectorIR_Dir(&B_row_vec_part_1_tmp, 16, 32, 32, 32, 0, 32*16, 16*16);


	// matmat-mul
	SpB_mat = Hard_MatmatMul(&Sp_col_vec, &B_row_vec_part_0, 160, 0, 0, 16, 8, 160, 160, 8, 0);


	//////////////////////////////////////////////////////
	// Sp1*B1
	//////////////////////////////////////////////////////

	// extract Sp
#pragma comment(user, "CLUSTER:0")
	Sp_col_0_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_0_1, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:1")
	Sp_col_2_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_1_1, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:0")
	Sp_col_1_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_0_5, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:1")
	Sp_col_3_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_1_5, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_4_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_2_1, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_5_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_2_5, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_6_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_3_1, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_7_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_3_5, 5, 10, 16, 32, 0, 256, 512);

	// form matrix Sp
#pragma comment(user, "CLUSTER:3")
	Sp_col_vec = Hard_BankTransform(&Sp_col_vec, 80, 0, 1, 0, 0, 0);

	// Unpack(b) and from matrix B

	// shift 0.5+37 lines of b
	b_vec_part_1_0 = Hard_CoeffShiftCoeffs(&b_vec_part_1_0, 38, 0, 1, 1, 256);

#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_VectorIRFromByteStringIR_Dir(&b_string_part_1_1, 8, 16, 15, 32, 0, 15*64, 32*64); 
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_VectorIRFromByteStringIR_Dir(&b_string_part_1_0, 30, 64, 15, 32, 0, 15*64, 32*64); 
	// reverse/shift in vector, then form matrix B
//#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_CoeffReverse(&B_row_vec_part_0_tmp, 64, 0, 0, 0, 1);
//#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_CoeffReverse(&B_row_vec_part_1_tmp, 16, 0, 0, 0, 1);
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_Vec_Shift(&B_row_vec_part_0_tmp, 32, 17, 1);
#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_Vec_Shift(&B_row_vec_part_1_tmp, 16, 17, 1);
	// form matrix B from VectorIR
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0 = Hard_VectorIRFromVectorIR_Dir(&B_row_vec_part_0_tmp, 64, 128, 32, 32, 0, 32*16, 16*16);
#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1 = Hard_VectorIRFromVectorIR_Dir(&B_row_vec_part_1_tmp, 16, 32, 32, 32, 0, 32*16, 16*16);

	// matmat-mul
	SpB_mat_tmp = Hard_MatmatMul(&Sp_col_vec, &B_row_vec_part_0, 160, 0, 0, 16, 8, 160, 160, 8, 0);
	
	// add tmp result to SpB_mat
#pragma comment(user, "CLUSTER:3")
	SpB_mat = Hard_Vec_Add(&SpB_mat, &SpB_mat_tmp, 8);


	//////////////////////////////////////////////////////
	// Sp2*B2
	//////////////////////////////////////////////////////

	// extract Sp
#pragma comment(user, "CLUSTER:0")
	Sp_col_0_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_0_2, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:1")
	Sp_col_2_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_1_2, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:0")
	Sp_col_1_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_0_6, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:1")
	Sp_col_3_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_1_6, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_4_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_2_2, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_5_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_2_6, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_6_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_3_2, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_7_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_3_6, 5, 10, 16, 32, 0, 256, 512);

	// form matrix
#pragma comment(user, "CLUSTER:3")
	Sp_col_vec = Hard_BankTransform(&Sp_col_vec, 80, 0, 1, 0, 0, 0);

	// Unpack(b) and from matrix B

#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_VectorIRFromByteStringIR_Dir(&b_string_part_2_1, 8, 16, 15, 32, 0, 15*64, 32*64); 
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_VectorIRFromByteStringIR_Dir(&b_string_part_2_0, 30, 64, 15, 32, 0, 15*64, 32*64); 
	// reverse/shift in vector, then form matrix B
//#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_CoeffReverse(&B_row_vec_part_0_tmp, 64, 0, 0, 0, 1);
//#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_CoeffReverse(&B_row_vec_part_1_tmp, 16, 0, 0, 0, 1);
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_Vec_Shift(&B_row_vec_part_0_tmp, 32, 17, 1);
#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_Vec_Shift(&B_row_vec_part_1_tmp, 16, 17, 1);
	// form matrix B from VectorIR
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0 = Hard_VectorIRFromVectorIR_Dir(&B_row_vec_part_0_tmp, 64, 128, 32, 32, 0, 32*16, 16*16);
#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1 = Hard_VectorIRFromVectorIR_Dir(&B_row_vec_part_1_tmp, 16, 32, 32, 32, 0, 32*16, 16*16);

	// matmat-mul
	SpB_mat_tmp = Hard_MatmatMul(&Sp_col_vec, &B_row_vec_part_0, 160, 0, 0, 16, 8, 160, 160, 8, 0);
	
	// add tmp result to SpB_mat
#pragma comment(user, "CLUSTER:3")
	SpB_mat = Hard_Vec_Add(&SpB_mat, &SpB_mat_tmp, 8);


	//////////////////////////////////////////////////////
	// Sp3*B3
	//////////////////////////////////////////////////////

	// extract Sp
#pragma comment(user, "CLUSTER:0")
	Sp_col_0_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_0_3, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:1")
	Sp_col_2_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_1_3, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:0")
	Sp_col_1_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_0_7, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:1")
	Sp_col_3_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_1_7, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_4_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_2_3, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_5_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_2_7, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_6_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_3_3, 5, 10, 16, 32, 0, 256, 512);
#pragma comment(user, "CLUSTER:2")
	Sp_col_7_vec = Hard_VectorIRFromByteStringIR_Dir(&Sp_string_block_3_7, 5, 10, 16, 32, 0, 256, 512);

	// form matrix
#pragma comment(user, "CLUSTER:3")
	Sp_col_vec = Hard_BankTransform(&Sp_col_vec, 80, 0, 1, 0, 0, 0);

	// Unpack(b) and from matrix B

	// shift 0.5+37 lines of b
	b_vec_part_3_0 = Hard_CoeffShiftCoeffs(&b_vec_part_3_0, 38, 0, 1, 1, 256);

#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_VectorIRFromByteStringIR_Dir(&b_string_part_3_1, 8, 16, 15, 32, 0, 15*64, 32*64); 
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_VectorIRFromByteStringIR_Dir(&b_string_part_3_0, 30, 64, 15, 32, 0, 15*64, 32*64); 
	// reverse/shift in vector, then form matrix B
//#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_CoeffReverse(&B_row_vec_part_0_tmp, 64, 0, 0, 0, 1);
//#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_CoeffReverse(&B_row_vec_part_1_tmp, 16, 0, 0, 0, 1);
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0_tmp = Hard_Vec_Shift(&B_row_vec_part_0_tmp, 32, 17, 1);
#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1_tmp = Hard_Vec_Shift(&B_row_vec_part_1_tmp, 16, 17, 1);
	// form matrix B from VectorIR
#pragma comment(user, "CLUSTER:0")
	B_row_vec_part_0 = Hard_VectorIRFromVectorIR_Dir(&B_row_vec_part_0_tmp, 64, 128, 32, 32, 0, 32*16, 16*16);
#pragma comment(user, "CLUSTER:1")
	B_row_vec_part_1 = Hard_VectorIRFromVectorIR_Dir(&B_row_vec_part_1_tmp, 16, 32, 32, 32, 0, 32*16, 16*16);

	// matmat-mul
	SpB_mat_tmp = Hard_MatmatMul(&Sp_col_vec, &B_row_vec_part_0, 160, 0, 0, 16, 8, 160, 160, 8, 0);
	
	// add tmp result to SpB_mat
#pragma comment(user, "CLUSTER:3")
	SpB_mat = Hard_Vec_Add(&SpB_mat, &SpB_mat_tmp, 8);


	// transfer SpB_mat to VectorIR
	VectorIR SpB_vec;
#pragma comment(user, "SpB_vec:508")
#pragma comment(user, "CLUSTER:2")
	SpB_vec = Hard_VectorIRFromVectorIR_Dir(&SpB_mat, 8, 4, 32, 32, 0, 32*8, 32*16);




	// 9-10. Gen(A) on-the-fly, and calculate Sp*A

	// move Sp_string_block_1_* to bank 6 to make room for Sp*A
	ByteStringIR Sp_string_block_1_0_new;
#pragma comment(user, "Sp_string_block_1_0_new:816")
	ByteStringIR Sp_string_block_1_4_new;
#pragma comment(user, "Sp_string_block_1_4_new:836")

	Sp_string_block_1_0_new = Hard_ByteStringIRFromByteStringIR(&Sp_string_block_3_0, 40);

	// type VectorIR, for shift op use only
	VectorIR Sp_string_block_0_0_vec;
	VectorIR Sp_string_block_0_4_vec;
	VectorIR Sp_string_block_1_0_new_vec;
	VectorIR Sp_string_block_1_4_new_vec;
	VectorIR Sp_string_block_2_0_vec;
	VectorIR Sp_string_block_2_4_vec;
	VectorIR Sp_string_block_3_0_vec;
	VectorIR Sp_string_block_3_4_vec;
#pragma comment(user, "Sp_string_block_0_0_vec:856")
#pragma comment(user, "Sp_string_block_0_4_vec:876")
#pragma comment(user, "Sp_string_block_1_0_new_vec:816")
#pragma comment(user, "Sp_string_block_1_4_new_vec:836")
#pragma comment(user, "Sp_string_block_2_0_vec:936")
#pragma comment(user, "Sp_string_block_2_4_vec:956")
#pragma comment(user, "Sp_string_block_3_0_vec:976")
#pragma comment(user, "Sp_string_block_3_4_vec:996")

	// seedA is in bank7;
	// now prepare i||seedA in 4 different banks (i=0,1,2,3) for the first round of loop

	VectorIR seed_A_0_vec;
#pragma comment(user, "seed_A_0_vec:379")
	VectorIR seed_A_1_vec;
#pragma comment(user, "seed_A_1_vec:507")
	VectorIR seed_A_2_vec;
#pragma comment(user, "seed_A_2_vec:815")
	VectorIR seed_A_3_vec;
#pragma comment(user, "seed_A_3_vec:1017")

//#pragma comment(user, "CLUSTER:0")
	seed_A_0_vec = Hard_CoeffShiftCoeffs(&seed_A_3_vec, 1, 0, 1, 0, 16);
#pragma comment(user, "CLUSTER:0")
	seed_A_1_vec = Hard_Vec_AddConst(&seed_A_0_vec, 1, 1);
#pragma comment(user, "CLUSTER:0")
	seed_A_2_vec = Hard_Vec_AddConst(&seed_A_0_vec, 1, 2);
#pragma comment(user, "CLUSTER:1")
	seed_A_3_vec = Hard_Vec_AddConst(&seed_A_1_vec, 1, 2);

	ByteStringIR seed_A_0_string;
#pragma comment(user, "seed_A_0_string:379")
	ByteStringIR seed_A_1_string;
#pragma comment(user, "seed_A_1_string:507")
	ByteStringIR seed_A_2_string;
#pragma comment(user, "seed_A_2_string:815")
	ByteStringIR seed_A_3_string;
#pragma comment(user, "seed_A_3_string:1017")

	// output data A_row_string_* from SHAKE are put in 4 different banks

	ByteStringIR A_row_string_0;
#pragma comment(user, "A_row_string_0:288")
	ByteStringIR A_row_string_1;
#pragma comment(user, "A_row_string_1:416")
	ByteStringIR A_row_string_2;
#pragma comment(user, "A_row_string_2:512")
	ByteStringIR A_row_string_3;
#pragma comment(user, "A_row_string_3:640")

	// A_row_vec_* from A_row_string_* are also put in 4 different banks

	VectorIR A_row_vec_0;
#pragma comment(user, "A_row_vec_0:532")
	VectorIR A_row_vec_1;
#pragma comment(user, "A_row_vec_1:660")
	VectorIR A_row_vec_2;
#pragma comment(user, "A_row_vec_2:768")
	VectorIR A_row_vec_3;
#pragma comment(user, "A_row_vec_3:896")

	// matrix A_row

	VectorIR A_row_mat_0;
#pragma comment(user, "A_row_mat_0:288")
	VectorIR A_row_mat_1;
#pragma comment(user, "A_row_mat_1:308")
	VectorIR A_row_mat_2;
#pragma comment(user, "A_row_mat_2:328")
	VectorIR A_row_mat_3;
#pragma comment(user, "A_row_mat_3:348")

	// matrix Sp_col_new (append _new because the name Sp_col has been used)
	// a 8*4 block of Sp
	VectorIR Sp_col_mat_new;
#pragma comment(user, "Sp_col_mat_new:800")
	// 8 lines of Sp_col_mat_new;
	VectorIR Sp_col_0_vec_new;
	VectorIR Sp_col_1_vec_new;
	VectorIR Sp_col_2_vec_new;
	VectorIR Sp_col_3_vec_new;
	VectorIR Sp_col_4_vec_new;
	VectorIR Sp_col_5_vec_new;
	VectorIR Sp_col_6_vec_new;
	VectorIR Sp_col_7_vec_new;
#pragma comment(user, "Sp_col_0_vec_new:800")
#pragma comment(user, "Sp_col_1_vec_new:801")
#pragma comment(user, "Sp_col_2_vec_new:802")
#pragma comment(user, "Sp_col_3_vec_new:803")
#pragma comment(user, "Sp_col_4_vec_new:804")
#pragma comment(user, "Sp_col_5_vec_new:805")
#pragma comment(user, "Sp_col_6_vec_new:806")
#pragma comment(user, "Sp_col_7_vec_new:807")


	// result of Sp*A
	VectorIR SpA_mat;
#pragma comment(user, "SpA_mat:0")
	// store intermediate results of Sp*A
	VectorIR SpA_mat_tmp;
#pragma comment(user, "SpA_mat_tmp:512")
	// to add Sp*A intermediate results
	VectorIR SpA_mat_0_0;
	VectorIR SpA_mat_0_1;
	VectorIR SpA_mat_1_0;
	VectorIR SpA_mat_1_1;
#pragma comment(user, "SpA_mat_0_0:0")
#pragma comment(user, "SpA_mat_0_1:128")
#pragma comment(user, "SpA_mat_1_0:256")
#pragma comment(user, "SpA_mat_1_1:384")
	VectorIR SpA_mat_tmp_0_0;
	VectorIR SpA_mat_tmp_0_1;
	VectorIR SpA_mat_tmp_1_0;
	VectorIR SpA_mat_tmp_1_1;
#pragma comment(user, "SpA_mat_tmp_0_0:512")
#pragma comment(user, "SpA_mat_tmp_0_1:640")
#pragma comment(user, "SpA_mat_tmp_1_0:768")
#pragma comment(user, "SpA_mat_tmp_1_1:896")

	// before loop, clean SpA_mat
#pragma comment(user, "CLUSTER:0")
	SpA_mat_0_0 = Hard_Vec_AndConst(&SpA_mat_0_0, 128, 0, 0);
#pragma comment(user, "CLUSTER:1")
	SpA_mat_0_1 = Hard_Vec_AndConst(&SpA_mat_0_0, 128, 0, 0);
#pragma comment(user, "CLUSTER:2")
	SpA_mat_1_0 = Hard_Vec_AndConst(&SpA_mat_0_0, 32, 0, 0);
#pragma comment(user, "CLUSTER:3")
	SpA_mat_1_1 = Hard_Vec_AndConst(&SpA_mat_0_0, 32, 0, 0);

	// for loop
	ControlScalarIR i_cs;
#pragma comment(user, "i_cs:0")

	// line offset of Sp_string_block_*
	ControlScalarIR j_cs;
#pragma comment(user, "j_cs:1")

	for (i_cs.scalar_data = 0; i_cs.scalar_data < 160; i_cs.scalar_data = i_cs.scalar_data + 1) {

		// input i||seedA has 34 bytes; output 1280 bytes
		A_row_string_0 = Hard_Shake3_Static(&seed_A_0_string, 20, 1, SHAKE, 0,0,0,0, 34);
		A_row_string_1 = Hard_Shake3_Static(&seed_A_1_string, 20, 1, SHAKE, 0,0,0,0, 34);
		A_row_string_2 = Hard_Shake3_Static(&seed_A_2_string, 20, 1, SHAKE, 0,0,0,0, 34);
		A_row_string_3 = Hard_Shake3_Static(&seed_A_3_string, 20, 1, SHAKE, 0,0,0,0, 34);

		// refresh i||seedA for the next round
		// parallel execution of LOW
#pragma comment(user, "CLUSTER:0")
		seed_A_0_vec = Hard_Vec_AddConst(&seed_A_0_vec, 1, 4);
#pragma comment(user, "CLUSTER:1")
		seed_A_1_vec = Hard_Vec_AddConst(&seed_A_1_vec, 1, 4);
#pragma comment(user, "CLUSTER:2")
		seed_A_2_vec = Hard_Vec_AddConst(&seed_A_2_vec, 1, 4);
#pragma comment(user, "CLUSTER:3")
		seed_A_3_vec = Hard_Vec_AddConst(&seed_A_3_vec, 1, 4);

		// parallel execution of ADW
#pragma comment(user, "CLUSTER:0")
		A_row_vec_0 = Hard_VectorIRFromByteStringIR_Dir(&A_row_string_0, 20, 40, 16, 32, 0, 20*512, 40*512);
#pragma comment(user, "CLUSTER:1")
		A_row_vec_1 = Hard_VectorIRFromByteStringIR_Dir(&A_row_string_1, 20, 40, 16, 32, 0, 20*512, 40*512);
#pragma comment(user, "CLUSTER:2")
		A_row_vec_2 = Hard_VectorIRFromByteStringIR_Dir(&A_row_string_2, 20, 40, 16, 32, 0, 20*512, 40*512);
#pragma comment(user, "CLUSTER:3")
		A_row_vec_3 = Hard_VectorIRFromByteStringIR_Dir(&A_row_string_3, 20, 40, 16, 32, 0, 20*512, 40*512);

		// form matrix A_row
#pragma comment(user, "CLUSTER:0")
		A_row_mat_0 = Hard_BankTransform(&A_row_vec_0, 40, 0, 1, 0, 0, 0);
#pragma comment(user, "CLUSTER:1")
		A_row_mat_1 = Hard_BankTransform(&A_row_vec_1, 40, 0, 1, 0, 0, 0);
#pragma comment(user, "CLUSTER:2")
		A_row_mat_2 = Hard_BankTransform(&A_row_vec_2, 40, 0, 1, 0, 0, 0);
#pragma comment(user, "CLUSTER:3")
		A_row_mat_3 = Hard_BankTransform(&A_row_vec_3, 40, 0, 1, 0, 0, 0);


		// i_cs = [0,7]: 	j_cs = 0 
		// i_cs = [8,15]:	j_cs = 1 
		// ...
		j_cs.scalar_data = i_cs.scalar_data >> 3;

		// form matrix Sp_col_mat_new
		// parallel read
#pragma comment(user, "CLUSTER:0")
		Sp_col_0_vec_new = Hard_VectorIRFromByteStringIR_Dir(BysIdx(Sp_string_block_0_0,j_cs.scalar_data), 1, 1, 16, 32, 0, 16*4, 32*4);
#pragma comment(user, "CLUSTER:1")
		Sp_col_4_vec_new = Hard_VectorIRFromByteStringIR_Dir(BysIdx(Sp_string_block_2_0,j_cs.scalar_data), 1, 1, 16, 32, 0, 16*4, 32*4);
#pragma comment(user, "CLUSTER:0")
		Sp_col_1_vec_new = Hard_VectorIRFromByteStringIR_Dir(BysIdx(Sp_string_block_0_4,j_cs.scalar_data), 1, 1, 16, 32, 0, 16*4, 32*4);
#pragma comment(user, "CLUSTER:1")
		Sp_col_5_vec_new = Hard_VectorIRFromByteStringIR_Dir(BysIdx(Sp_string_block_2_4,j_cs.scalar_data), 1, 1, 16, 32, 0, 16*4, 32*4);
#pragma comment(user, "CLUSTER:0")
		Sp_col_2_vec_new = Hard_VectorIRFromByteStringIR_Dir(BysIdx(Sp_string_block_1_0_new,j_cs.scalar_data), 1, 1, 16, 32, 0, 16*4, 32*4);
#pragma comment(user, "CLUSTER:1")
		Sp_col_6_vec_new = Hard_VectorIRFromByteStringIR_Dir(BysIdx(Sp_string_block_3_0,j_cs.scalar_data), 1, 1, 16, 32, 0, 16*4, 32*4);
#pragma comment(user, "CLUSTER:0")
		Sp_col_3_vec_new = Hard_VectorIRFromByteStringIR_Dir(BysIdx(Sp_string_block_1_4_new,j_cs.scalar_data), 1, 1, 16, 32, 0, 16*4, 32*4);
#pragma comment(user, "CLUSTER:1")
		Sp_col_7_vec_new = Hard_VectorIRFromByteStringIR_Dir(BysIdx(Sp_string_block_3_4,j_cs.scalar_data), 1, 1, 16, 32, 0, 16*4, 32*4);

		// mat-mat mul
		SpA_mat_tmp = Hard_MatmatMul(&Sp_col_mat_new, &A_row_mat_0, 80, 0, 0, 16, 8, 4, 4, 640, 0);

		// the chosen line right shift 4 bits
		// do not allocate buffer address
		Sp_string_block_0_0_vec     = Hard_CoeffShiftCoeffs(BysIdx(Sp_string_block_0_0,j_cs.scalar_data), 1, 0, 1, 1, 16*4);
		Sp_string_block_0_4_vec     = Hard_CoeffShiftCoeffs(BysIdx(Sp_string_block_0_4,j_cs.scalar_data), 1, 0, 1, 1, 16*4);
		Sp_string_block_1_0_new_vec = Hard_CoeffShiftCoeffs(BysIdx(Sp_string_block_1_0_new,j_cs.scalar_data), 1, 0, 1, 1, 16*4);
		Sp_string_block_1_4_new_vec = Hard_CoeffShiftCoeffs(BysIdx(Sp_string_block_1_4_new,j_cs.scalar_data), 1, 0, 1, 1, 16*4);
		Sp_string_block_2_0_vec     = Hard_CoeffShiftCoeffs(BysIdx(Sp_string_block_2_0,j_cs.scalar_data), 1, 0, 1, 1, 16*4);
		Sp_string_block_2_4_vec     = Hard_CoeffShiftCoeffs(BysIdx(Sp_string_block_2_4,j_cs.scalar_data), 1, 0, 1, 1, 16*4);
		Sp_string_block_3_0_vec     = Hard_CoeffShiftCoeffs(BysIdx(Sp_string_block_3_0,j_cs.scalar_data), 1, 0, 1, 1, 16*4);
		Sp_string_block_3_4_vec     = Hard_CoeffShiftCoeffs(BysIdx(Sp_string_block_3_4,j_cs.scalar_data), 1, 0, 1, 1, 16*4);


		// parallel execution
#pragma comment(user, "CLUSTER:0")
		SpA_mat_0_0 = Hard_Vec_Add(&SpA_mat_0_0, &SpA_mat_tmp_0_0, 128);
#pragma comment(user, "CLUSTER:1")
		SpA_mat_0_1 = Hard_Vec_Add(&SpA_mat_0_1, &SpA_mat_tmp_0_1, 128);
#pragma comment(user, "CLUSTER:0")
		SpA_mat_1_0 = Hard_Vec_Add(&SpA_mat_1_0, &SpA_mat_tmp_1_0, 32);
#pragma comment(user, "CLUSTER:1")
		SpA_mat_1_1 = Hard_Vec_Add(&SpA_mat_1_1, &SpA_mat_tmp_1_1, 32);


	}

	// matrix Sp*A -> vector

	// DVectorIR refers to SpA_mat_tmp
	DVectorIR SpA_mat_Dvec;
#pragma comment(user, "SpA_mat_Dvec:0")
	VectorIR SpA_vec;
#pragma comment(user, "SpA_vec:512")

#pragma comment(user, "CLUSTER:0")
	SpA_vec.Initial_FromDVectorIR(&SpA_mat_Dvec, 160);


	// 8,11. Sample again to generate Ep and Epp

	// Ep_string is divided into 64+64+32 lines
	ByteStringIR Ep_string_block_0;
#pragma comment(user, "Ep_string_block_0:320")
	ByteStringIR Ep_string_block_1;
#pragma comment(user, "Ep_string_block_1:384")
	ByteStringIR Ep_string_block_2;
#pragma comment(user, "Ep_string_block_2:448")

	ByteStringIR Epp_string;
#pragma comment(user, "Epp_string:450")
	
	// type VectorIR, for Gauss Sample use only
	VectorIR Ep_string_initial_vec;
#pragma comment(user, "Ep_string_initial_vec:320")
	
	// set seed_flag = 1 to continue the previous output
	// TODO out_coeff?
#pragma comment(user, "CLUSTER:0")
	Ep_string_initial_vec = Hard_PRNG_Gauss_Sample(&seed_r, 162, 1, 0, 1, 16, 0, 1, 13, 0, 0, SHAKE, 33, 0);



	// 10. Bpp = Sp*A + Ep
	// 13. V = Sp*B + Epp

	VectorIR Ep_vec_0;
#pragma comment(user, "Ep_vec_0:0")
	VectorIR Ep_vec_1;
#pragma comment(user, "Ep_vec_1:128")
	VectorIR Ep_vec_2;
#pragma comment(user, "Ep_vec_2:896")

	VectorIR Epp_vec;
#pragma comment(user, "Epp_vec:256")

	// for vecadd use only
	VectorIR SpA_vec_0;
#pragma comment(user, "SpA_vec_0:512")
	VectorIR SpA_vec_1;
#pragma comment(user, "SpA_vec_1:640")
	VectorIR SpA_vec_2;
#pragma comment(user, "SpA_vec_2:768")

	// for vecadd use only
	VectorIR Bpp_vec_0;
#pragma comment(user, "Bpp_vec_0:512")
	VectorIR Bpp_vec_1;
#pragma comment(user, "Bpp_vec_1:640")
	VectorIR Bpp_vec_2;
#pragma comment(user, "Bpp_vec_2:768")

	VectorIR V_vec;
#pragma comment(user, "V_vec:508")


	// parallel execution
#pragma comment(user, "CLUSTER:0")
	Ep_vec_0 = Hard_VectorIRFromByteStringIR_Dir(&Ep_string_block_0, 64, 128, 16, 32, 0, 512*64, 512*128);
#pragma comment(user, "CLUSTER:1")
	Ep_vec_1 = Hard_VectorIRFromByteStringIR_Dir(&Ep_string_block_1, 64, 128, 16, 32, 0, 512*64, 512*128);

	// parallel execution
#pragma comment(user, "CLUSTER:0")
	SpA_vec_0 = Hard_Vec_Add(&SpA_vec_0, &Ep_vec_0, 128);
#pragma comment(user, "CLUSTER:1")
	SpA_vec_1 = Hard_Vec_Add(&SpA_vec_1, &Ep_vec_1, 128);

#pragma comment(user, "CLUSTER:2")
	Ep_vec_2 = Hard_VectorIRFromByteStringIR_Dir(&Ep_string_block_2, 32, 64, 16, 32, 0, 512*32, 512*64);

#pragma comment(user, "CLUSTER:2")
	SpA_vec_2 = Hard_Vec_Add(&SpA_vec_2, &Ep_vec_2, 64);


#pragma comment(user, "CLUSTER:3")
	Epp_vec = Hard_VectorIRFromByteStringIR_Dir(&Epp_string, 2, 4, 16, 32, 0, 512*2, 512*4);
#pragma comment(user, "CLUSTER:3")
	V_vec = Hard_Vec_Add(&SpB_vec, &Epp_vec, 4);


	// 14. Cp = V+Encode(u_p)
	VectorIR u_p_encoded_vec;
#pragma comment(user, "u_p_encoded_vec:1000")
	VectorIR Cp_vec;
#pragma comment(user, "Cp_vec:508")

	// u_p_vec has 128'b0||u
	// do not allocate CLUSTER
	u_p_vec = Hard_CoeffShiftCoeffs(&u_p_vec, 1, 0, 1, 1, 128);

#pragma comment(user, "CLUSTER:3")
	u_p_encoded_vec = Hard_VectorIRFromByteStringIR_Dir(&u_p_vec, 1, 4, 2, 32, 0, 128, 4*512);

#pragma comment(user, "CLUSTER:3")
	u_p_encoded_vec = Hard_Vec_Shift(&u_p_encoded_vec, 4, D-B, 1);

#pragma comment(user, "CLUSTER:3")
	Cp_vec = Hard_Vec_Add(&u_p_encoded_vec, &V_vec, 4);


	//////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////

	// 15. constant time comparison

	// read c again from XMEM

	ByteStringIR c1_new;
#pragma comment(user, "c1_new:328")
	VectorIR c1_vec_new;
#pragma comment(user, "c1_vec_new:328")
	ByteStringIR c2_new;
#pragma comment(user, "c2_new:478")
	VectorIR c2_vec_new;
#pragma comment(user, "c2_vec_new:478")

	c1_new.Initial_FromMByteStringIR(&Mc, lines_c-1);	// no need to read salt

	// B' = Unpack(c1)
	// C = Unpack(c2)
	
	// Bp is divided into 3 blocks for parallel execution
	// _new indicates this is the second time that c is loaded into buffer
	VectorIR Bp_vec_new_0;
#pragma comment(user, "Bp_vec_new_0:0")
	VectorIR Bp_vec_new_1;
#pragma comment(user, "Bp_vec_new_1:128")
	VectorIR Bp_vec_new_2;
#pragma comment(user, "Bp_vec_new_2:256")
	VectorIR C_vec_new;
#pragma comment(user, "C_vec_new:320")

	c1_vec_new = Hard_CoeffReverse(&c1_vec_new, lines_c1+lines_c2, 0, 0, 1, 1);
#pragma comment(user, "CLUSTER:0")
	Bp_vec_new_0 = Hard_VectorIRFromByteStringIR_Dir(&c1_new, lines_c1+lines_c2, 168, 15, 32, 0, 77760, 512*324);
	Bp_vec_new_0 = Hard_CoeffReverse(&Bp_vec_new_0, 324, 0, 0, 0, 1);
	// parallel execution
#pragma comment(user, "CLUSTER:0")
	Bp_vec_new_0 = Hard_Vec_Shift(&Bp_vec_new_0, 128, 17, 1);
#pragma comment(user, "CLUSTER:1")
	Bp_vec_new_1 = Hard_Vec_Shift(&Bp_vec_new_1, 128, 17, 1);
#pragma comment(user, "CLUSTER:2")
	Bp_vec_new_2 = Hard_Vec_Shift(&Bp_vec_new_2, 72, 17, 1);

	// compare results
	VectorIR B_cmp_0;
#pragma comment(user, "B_cmp_0:0")
	VectorIR B_cmp_1;
#pragma comment(user, "B_cmp_1:128")
	VectorIR B_cmp_2;
#pragma comment(user, "B_cmp_2:256")
	VectorIR C_cmp;
#pragma comment(user, "C_cmp:320")

	// Bpp and Cp should mod q before comparison

	// parallel execution
#pragma comment(user, "CLUSTER:0")
	Bpp_vec_0 = Hard_Vec_AndConst(&Bpp_vec_0, 128, 0, 32767);
#pragma comment(user, "CLUSTER:1")
	Bpp_vec_1 = Hard_Vec_AndConst(&Bpp_vec_1, 128, 0, 32767);
#pragma comment(user, "CLUSTER:2")
	Bpp_vec_2 = Hard_Vec_AndConst(&Bpp_vec_2, 64, 0, 32767);
#pragma comment(user, "CLUSTER:3")
	Cp_vec = Hard_Vec_AndConst(&Cp_vec, 4, 0, 32767);

	// parallel execution
#pragma comment(user, "CLUSTER:0")
	B_cmp_0 = Hard_Vec_Comp(&Bp_vec_new_0, &Bpp_vec_0, 128, 2);
#pragma comment(user, "CLUSTER:1")
	B_cmp_1 = Hard_Vec_Comp(&Bp_vec_new_1, &Bpp_vec_1, 128, 2);
#pragma comment(user, "CLUSTER:2")
	B_cmp_2 = Hard_Vec_Comp(&Bp_vec_new_2, &Bpp_vec_2, 64, 2);
#pragma comment(user, "CLUSTER:2")
	C_cmp = Hard_Vec_Comp(&C_vec_new, &Cp_vec, 4, 2);

	// FIXME
	VectorIR selector;
#pragma comment(user, "selector:896")
	selector = Hard_EleVecAccumulate(&B_cmp_0, 324, 0, 1, 0);
	selector = Hard_EleVecExpand(&selector, 1, 0, 1);



	// 16. compute ss

	// read c again from XMEM
	c1_new.Initial_FromMByteStringIR(&Mc, lines_c);

	VectorIR selector_kp;
#pragma comment(user, "selector_kp:897")
	VectorIR selector_s;
#pragma comment(user, "selector_s:898")
	ByteStringIR k_bar;
#pragma comment(user, "k_bar:899")
	VectorIR k_bar_vec;
#pragma comment(user, "k_bar_vec:899")

	// shift 0x96||seedSE'||k' to obtain k'
	seed_SE_p_k_p_vec = Hard_CoeffShiftCoeffs(&seed_SE_p_k_p_vec, 1, 0, 1, 1, 264);

	// k_bar = (selector & k_bar) | (~selector & s)
#pragma comment(user, "CLUSTER:0")
	selector_kp = Hard_Vec_And(&selector, &seed_SE_p_k_p_vec, 1, 0);
#pragma comment(user, "CLUSTER:0")
	selector = Hard_Vec_Not(&selector, 1, 0);
#pragma comment(user, "CLUSTER:0")
	selector_s = Hard_Vec_And(&selector, &s_vec, 1, 0);
#pragma comment(user, "CLUSTER:0")
	k_bar_vec = Hard_Vec_Or(&selector_kp, &selector_s, 1, 0);

	k_bar_vec = Hard_CoeffShiftCoeffs(&k_bar_vec, 1, 0, 1, 0, 192);

	// append k_bar to c
	VectorIR c_end;
#pragma comment(user, "c_end:481")
#pragma comment(user, "CLUSTER:0")
	c_end = Hard_Vec_Xor(&c_end, &k_bar_vec, 1, 0);

	ByteStringIR ss;
#pragma comment(user, "ss:900")
	ss = Hard_Shake3_Static(&c1_new, 1, 153, SHAKE, 0, 0, 0, 0, 40);
	
	MByteStringIR Mss;
#pragma comment(user, "Mss:1000")
	Mss.Initial_FromByteStringIR(&ss, 1);


}
快速解释这段代码，先进行整体粗略概括功能，然后逐行详细解释