# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

ImportAll(paradigms.stream);

# gets/replaces the module names, copies generated files into genpath
setModule := function(filename, searchString, moduleName, genpath, bb)
	local cmdString;
	# replace the module name
	cmdString := ConcatenationString(paradigms.stream._hardwarePath, "dram_scripts/_getModuleName.sh ", filename," ",searchString," ", moduleName);
	Exec(cmdString);
	if(bb = 1) then
		cmdString := ConcatenationString(paradigms.stream._hardwarePath, "dram_scripts/_blackBoxifyMems.sh ", filename);
		Exec(cmdString);
	fi;
	# Copy the file into genpath
	cmdString := ConcatenationString("cp ",filename," ",genpath);
	Exec(cmdString);
	return moduleName;
end;

# gets the latency of permMems, writes them into a tmp file
getLMLatency := function(filename, RD_WR, memInst, tmpFile )
	local cmdString;
	# Puts lm latency define statement into tmpFile
	cmdString := ConcatenationString(paradigms.stream._hardwarePath, "dram_scripts/_permMemLatencies.sh ",filename," ",RD_WR," ",String(memInst)," ",tmpFile);
	Exec(cmdString);
end;

# prints the latencies into config file, determines the max latency etc., then deletes the tmp file
putLMLatency := function(tmpFile, readFile)
	local cmdString;
	cmdString := ConcatenationString(paradigms.stream._hardwarePath, "dram_scripts/_putPermMemLatencies.sh ",tmpFile," ",readFile);
	Exec(cmdString);
	Read(readFile);
	Exec(ConcatenationString("rm ", readFile));
	Exec(ConcatenationString("rm ", tmpFile));
end;

# adds prefix to module names to avoid overwrites
prefixModules := function(filename, prefix)
	local cmdString;
	cmdString := ConcatenationString(paradigms.stream._hardwarePath, "dram_scripts/_prefixModName.sh ",filename," ",prefix);
	Exec(cmdString);
end;

#genConfigFile := function(srt, prec, genpath)
genConfigFile3 := function(srt, opts, genpath)
	local 	stages, fft_size, i,j,diff_prms,a,b,c,d,e,stream,path,format,cmdString,module,locmodname,bb,memfences,prmObjs,prmObj,
			cube_width,
			mem_wr_prms,
			mem_rd_prms,
			loc_wr_prms,
			loc_rd_prms,
			sym_mem_wr,
			sym_mem_rd,
			sym_loc_wr,
			sym_loc_rd;

	stages := Length(Collect(srt, MemFence));
	fft_size := Collect(srt, DFT)[1].params[1];
	memfences := Collect(srt, MemFence);

	mem_wr_prms := [];
	mem_rd_prms := [];
	loc_wr_prms := [];
	loc_rd_prms := [];

	for i in [1..stages] do
		mem_wr_prms[i] := Collect(memfences[i], MemWrPrm);
		mem_rd_prms[i] := Collect(memfences[i], MemRdPrm);
		loc_wr_prms[i] := Collect(memfences[i], LocalWrPrm);
		loc_rd_prms[i] := Collect(memfences[i], LocalRdPrm);
	od;

	cube_width := mem_rd_prms[1][1].func._children[Length(mem_rd_prms[1][1].func._children)].params[1];
	stream := opts.dram_datawidth/opts.precision/2;
	format := When (opts.precision = 64, 2, 1);
	bb := opts.bb;

	PrintLine("//=========================");
	PrintLine("// DO NOT MODIFY THIS FILE!");
	PrintLine("//=========================\n");

	# Print the define statements into config
	PrintLine("`define CONFIG_FILE");

	# Streamig width
	if(stream >= 4) then
		PrintLine("`define SW_4");
	fi;
	if(stream >= 8) then
		PrintLine("`define SW_8");
	fi;
	if(stream = 16) then
		PrintLine("`define SW_16");
	fi;
	if(stream > 16 or stream < 2) then
		Error("\n***ERROR: Streaming width = ",stream," is not supported for now!\n");
	fi;

	PrintLine("// Asymmetric algorithm...");
	diff_prms := stages;
	PrintLine("`define ASYMMETRIC_ALGO");

	PrintLine("`define APPDATA_WIDTH ", opts.dram_datawidth);
	PrintLine("`define DDR_ADDR_WIDTH ", opts.dram_addrwidth);
	PrintLine("`define LOG_FFT_SIZE ", LogInt(fft_size,2));
	PrintLine("`define PACKET_SIZE ", cube_width/stream);
	PrintLine("`define PRECISION ", opts.precision);
	PrintLine("`define NUM_OF_STAGES ", stages);

	PrintLine("// ODCM parameters");
	PrintLine("//");
	PrintLine("// MemWrPrm:");

	for i in [1..stages] do
		for j in [1..Length(mem_wr_prms[i])] do
			if(mem_wr_prms[i][j].func.numChildren() = 3) then # IxLxI
				a := mem_wr_prms[i][j].func._children[1].params[1];
				b := mem_wr_prms[i][j].func._children[2].params[1];
				c := mem_wr_prms[i][j].func._children[2].params[2];
				d := mem_wr_prms[i][j].func._children[3].params[1];
				PrintLine("// IxLxI: I(",a,")xL(",b,",",c,")xI(",d,") --> need to transpose for write address generation --> IxLxI: I(",a,")xL(",b,",",b/c,")xI(",d,")");
				PrintLine("`define MEM_WR_PERM_MODULE_NAME_",i,"_",Length(mem_wr_prms[i])-j+1," permIL #(.loga(",LogInt(a,2),"), .logb(",LogInt(b,2),"), .logc(",LogInt(b/c,2),"))");
			fi;
			if(mem_wr_prms[i][j].func.numChildren() = 2) then # LxI
				b := mem_wr_prms[i][j].func._children[1].params[1];
				c := mem_wr_prms[i][j].func._children[1].params[2];
				d := mem_wr_prms[i][j].func._children[2].params[1];
				PrintLine("// LxI: L(",b,",",c,")xI(",d,") --> need to transpose for write address generation --> LxI: L(",b,",",b/c,")xI(",d,")");
				PrintLine("`define MEM_WR_PERM_MODULE_NAME_",i,"_",Length(mem_wr_prms[i])-j+1," permL #(.logb(",LogInt(b,2),"), .logc(",LogInt(b/c,2),"))");
			fi;
			if(mem_wr_prms[i][j].func.numChildren() = 4) then # IxLxIxI
				a := mem_wr_prms[i][j].func._children[1].params[1];
				b := mem_wr_prms[i][j].func._children[2].params[1];
				c := mem_wr_prms[i][j].func._children[2].params[2];
				d := mem_wr_prms[i][j].func._children[3].params[1];
				e := mem_wr_prms[i][j].func._children[4].params[1];
				PrintLine("// IxLxIxI: I(",a,")xL(",b,",",c,")xI(",d,")xI(",e,") --> need to transpose for write address generation --> IxLxIxI: I(",a,")xL(",b,",",b/c,")xI(",d,")xI(",e,")");
				PrintLine("`define MEM_WR_PERM_MODULE_NAME_",i,"_",Length(mem_wr_prms[i])-j+1," permILI #(.loga(",LogInt(a,2),"), .logb(",LogInt(b,2),"), .logc(",LogInt(b/c,2),"), .logd(",LogInt(d,2),"))");
			fi;
		od;
	od;

	PrintLine("//");
	PrintLine("// MemRdPrm:");

	for i in [1..stages] do
		for j in [1..Length(mem_rd_prms[i])] do
			if(mem_rd_prms[i][j].func.numChildren() = 3) then # IxLxI
				a := mem_rd_prms[i][j].func._children[1].params[1];
				b := mem_rd_prms[i][j].func._children[2].params[1];
				c := mem_rd_prms[i][j].func._children[2].params[2];
				d := mem_rd_prms[i][j].func._children[3].params[1];
				PrintLine("// IxLxI: I(",a,")xL(",b,",",c,")xI(",d,")");
				PrintLine("`define MEM_RD_PERM_MODULE_NAME_",i,"_",j," permIL #(.loga(",LogInt(a,2),"), .logb(",LogInt(b,2),"), .logc(",LogInt(c,2),"))");
			fi;
			if(mem_rd_prms[i][j].func.numChildren() = 2) then # LxI
				b := mem_rd_prms[i][j].func._children[1].params[1];
				c := mem_rd_prms[i][j].func._children[1].params[2];
				d := mem_rd_prms[i][j].func._children[2].params[1];
				PrintLine("// LxI: L(",b,",",c,")xI(",d,")");
				PrintLine("`define MEM_RD_PERM_MODULE_NAME_",i,"_",j," permL #(.logb(",LogInt(b,2),"), .logc(",LogInt(c,2),"))");
			fi;
			if(mem_rd_prms[i][j].func.numChildren() = 4) then # IxLxIxI
				a := mem_rd_prms[i][j].func._children[1].params[1];
				b := mem_rd_prms[i][j].func._children[2].params[1];
				c := mem_rd_prms[i][j].func._children[2].params[2];
				d := mem_rd_prms[i][j].func._children[3].params[1];
				e := mem_rd_prms[i][j].func._children[4].params[1];
				PrintLine("// IxLxIxI: I(",a,")xL(",b,",",c,")xI(",d,")xI(",e,")");
				PrintLine("`define MEM_RD_PERM_MODULE_NAME_",i,"_",j," permILI #(.loga(",LogInt(a,2),"), .logb(",LogInt(b,2),"), .logc(",LogInt(c,2),"), .logd(",LogInt(d,2),"))");
			fi;
		od;
	od;

	PrintLine("// LM parameters");
	PrintLine("//");
	PrintLine("// LocalWrPrm:");
	for i in [1..stages] do
		prmObjs := [];
		locmodname := ConcatenationString("LocWrPrm_",String(i));
		PrintLine("/*** ");
		for j in [1..Length(loc_wr_prms[i])] do
			if(loc_wr_prms[i][j].func.numChildren() = 3) then # IxLxI
				a := loc_wr_prms[i][j].func._children[1].params[1];
				b := loc_wr_prms[i][j].func._children[2].params[1];
				c := loc_wr_prms[i][j].func._children[2].params[2];
				d := loc_wr_prms[i][j].func._children[3].params[1];
				PrintLine("// IxLxI: I(",a,")xL(",b,",",c,")xI(",d,")");
				prmObjs[j] := TL(b,c,a,d);
				#path := genBRAMPermMem(TRC(TPrm(TL(b,c,a,d))), stream*2, format, opts.precision, locmodname);
			fi;
			if(loc_wr_prms[i][j].func.numChildren() = 2) then
				if(Length(loc_wr_prms[i][j].func._children[1].params) = 2) then # LxI
					b := loc_wr_prms[i][j].func._children[1].params[1];
					c := loc_wr_prms[i][j].func._children[1].params[2];
					d := loc_wr_prms[i][j].func._children[2].params[1];
					PrintLine("// LxI: L(",b,",",c,")xI(",d,")");
					prmObjs[j] := TL(b,c,1,d);
					#path := genBRAMPermMem(TRC(TPrm(TL(b,c,1,d))), stream*2, format, opts.precision, locmodname);
				fi;
				if(Length(loc_wr_prms[i][j].func._children[1].params) = 1) then # IxL
					a := loc_wr_prms[i][j].func._children[1].params[1];
					b := loc_wr_prms[i][j].func._children[2].params[1];
					c := loc_wr_prms[i][j].func._children[2].params[2];
					PrintLine("// IxL: I(",a,")xL(",b,",",c,")");
					prmObjs[j] := TL(b,c,a,1);
					#path := genBRAMPermMem(TRC(TPrm(TL(b,c,a,1))), stream*2, format, opts.precision, locmodname);
				fi;
			fi;
			if(loc_wr_prms[i][j].func.numChildren() = 0) then # L
				b := loc_wr_prms[i][j].func.params[1];
				c := loc_wr_prms[i][j].func.params[2];
				PrintLine("// L: L(",b,",",c,")");
				prmObjs[j] := TL(b,c,1,1);
				#path := genBRAMPermMem(TRC(TPrm(TL(b,c,1,1))), stream*2, format, opts.precision, locmodname);
			fi;
		od;

		prmObj := prmObjs[1];
		for j in [2..Length(loc_wr_prms[i])] do
			prmObj := prmObj * prmObjs[j];
		od;
		path := genBRAMPermMem(TRC(TPrm(prmObj)), stream*2, format, opts.precision, locmodname);

		prefixModules(ConcatenationString(path, locmodname,".v"), ConcatenationString("lw",String(i)));
		module := setModule(ConcatenationString(path, locmodname,".v"), "module_name_is", ConcatenationString("perm_mem_locwr_",String(i)), genpath, bb);
		PrintLine("***/ ");
		PrintLine("`define LOC_WR_MODULE_NAME_",i," ",module);
		getLMLatency(ConcatenationString(path, locmodname,".v"), "WR", i, "/tmp/_spiral.tmp");
	od;

	#genBRAMPermMem(perm, w, format, bits, name)
	#genBRAMPermMem(TRC(TPrm(TL(32,2,1,1))), 4, 2, 16, "LocRdPrm");
	PrintLine("//");
	PrintLine("// LocalRdPrm:");
	for i in [1..stages] do
		prmObjs := [];
		locmodname := ConcatenationString("LocRdPrm_",String(i));
		PrintLine("/*** ");
		for j in [1..Length(loc_rd_prms[i])] do
			if(loc_rd_prms[i][j].func.numChildren() = 3) then # IxLxI
				a := loc_rd_prms[i][j].func._children[1].params[1];
				b := loc_rd_prms[i][j].func._children[2].params[1];
				c := loc_rd_prms[i][j].func._children[2].params[2];
				d := loc_rd_prms[i][j].func._children[3].params[1];
				PrintLine("// IxLxI: I(",a,")xL(",b,",",c,")xI(",d,")");
				prmObjs[j] := TL(b,c,a,d);
				#path := genBRAMPermMem(TRC(TPrm(TL(b,c,a,d))), stream*2, format, opts.precision, locmodname);
			fi;
			if(loc_rd_prms[i][j].func.numChildren() = 2) then
				if(Length(loc_rd_prms[i][j].func._children[1].params) = 2) then # LxI
					b := loc_rd_prms[i][j].func._children[1].params[1];
					c := loc_rd_prms[i][j].func._children[1].params[2];
					d := loc_rd_prms[i][j].func._children[2].params[1];
					PrintLine("// LxI: L(",b,",",c,")xI(",d,")");
					prmObjs[j] := TL(b,c,1,d);
					#path := genBRAMPermMem(TRC(TPrm(TL(b,c,1,d))), stream*2, format, opts.precision, locmodname);
				fi;
				if(Length(loc_rd_prms[i][j].func._children[1].params) = 1) then # IxL
					a := loc_rd_prms[i][j].func._children[1].params[1];
					b := loc_rd_prms[i][j].func._children[2].params[1];
					c := loc_rd_prms[i][j].func._children[2].params[2];
					PrintLine("// IxL: I(",a,")xL(",b,",",c,")");
					prmObjs[j] := TL(b,c,a,1);
					#path := genBRAMPermMem(TRC(TPrm(TL(b,c,a,1))), stream*2, format, opts.precision, locmodname);
				fi;
			fi;
			if(loc_rd_prms[i][j].func.numChildren() = 0) then # L
				b := loc_rd_prms[i][j].func.params[1];
				c := loc_rd_prms[i][j].func.params[2];
				PrintLine("// L: L(",b,",",c,")");
				prmObjs[j] := TL(b,c,1,1);
				#path := genBRAMPermMem(TRC(TPrm(TL(b,c,1,1))), stream*2, format, opts.precision, locmodname);
			fi;
		od;

		prmObj := prmObjs[1];
		for j in [2..Length(loc_rd_prms[i])] do
			prmObj := prmObj * prmObjs[j];
		od;
		path := genBRAMPermMem(TRC(TPrm(prmObj)), stream*2, format, opts.precision, locmodname);

		prefixModules(ConcatenationString(path, locmodname,".v"), ConcatenationString("lr",String(i)));
		module := setModule(ConcatenationString(path, locmodname,".v"), "module_name_is", ConcatenationString("perm_mem_locrd_",String(i)), genpath, bb);
		PrintLine("***/ ");
		PrintLine("`define LOC_RD_MODULE_NAME_",i," ",module);
		getLMLatency(ConcatenationString(path, locmodname,".v"), "RD", i, "/tmp/_spiral.tmp");
	od;

	PrintLine("// LM Additive latency ");
	putLMLatency("/tmp/_spiral.tmp", "/tmp/_spiral_readFile.tmp");

	PrintLine("/*** ");
	path := HDLGen(streamDFTUnroll(fft_size,2,stream*2), 1, format, 0, 0, 0, "dftgen");
	prefixModules(ConcatenationString(path, "dftgen.v"), "f");
	module := setModule(ConcatenationString(path, "dftgen.v"), "module_name_is", "dftcore", genpath, bb);
	PrintLine("***/ ");

	PrintLine("//");
	PrintLine("// FFT Core");
	PrintLine("`define FFT_CORE_MODULE_NAME ", module);

	if(opts.throttle = 1) then
		PrintLine("`define THROTTLE_EN");
	fi;

	PrintLine("// Summary");
	PrintLine("//");
	PrintLine("// Cube Width:",cube_width/stream," accesses");
	PrintLine("// Cube Width:",cube_width," words");
	PrintLine("// Stages:",stages);
	PrintLine("// Streaming Width:",opts.dram_datawidth/opts.precision/2);
	PrintLine("// DFT Size:",fft_size);
	PrintLine("// Format: ",format," (2:double, 1:single)");

end;

#genConfigFile := function(srt, prec, genpath)
genConfigFile2 := function(srt, opts, genpath)
	local 	stages, fft_size, i,j,diff_prms,a,b,c,d,stream,path,format,cmdString,module,locmodname,bb,twiddles,twiddle,
			mem_wr_prms,
			mem_rd_prms,
			loc_wr_prms,
			loc_rd_prms,
			tile_width,
			sym_mem_wr,
			sym_mem_rd,
			sym_loc_wr,
			sym_loc_rd;

	stages := Length(Collect(srt, MemFence));
	fft_size := Collect(srt, DFT)[1].params[1];
	mem_wr_prms := Collect(srt, MemWrPrm);
	mem_rd_prms := Collect(srt, MemRdPrm);
	loc_wr_prms := Collect(srt, LocalWrPrm);
	loc_rd_prms := Collect(srt, LocalRdPrm);
	twiddles := Collect(srt, TwiddleROM);
	tile_width := mem_rd_prms[1].func._children[Length(mem_rd_prms[1].func._children)].params[1];
	stream := opts.dram_datawidth/opts.precision/2;
	format := When (opts.precision = 64, 2, 1);
	bb := opts.bb;

	sym_mem_wr := true;
	sym_mem_rd := true;
	sym_loc_wr := true;
	sym_loc_rd := true;

	for i in [1..stages] do
		for j in [1..stages] do
			sym_mem_wr := sym_mem_wr and (mem_wr_prms[i] = mem_wr_prms[j]);
			sym_mem_rd := sym_mem_rd and (mem_rd_prms[i] = mem_rd_prms[j]);
			sym_loc_wr := sym_loc_wr and (loc_wr_prms[i] = loc_wr_prms[j]);
			sym_loc_rd := sym_loc_rd and (loc_rd_prms[i] = loc_rd_prms[j]);
		od;
	od;

	PrintLine("//=========================");
	PrintLine("// DO NOT MODIFY THIS FILE!");
	PrintLine("//=========================\n");

	# Print the define statements into config
	PrintLine("`define CONFIG_FILE");

	# Streamig width
	if(stream >= 4) then
		PrintLine("`define SW_4");
	fi;
	if(stream >= 8) then
		PrintLine("`define SW_8");
	fi;
	if(stream = 16) then
		PrintLine("`define SW_16");
	fi;
	if(stream > 16 or stream < 2) then
		Error("\n***ERROR: Streaming width = ",stream," is not supported for now!\n");
	fi;

	# determine symmetry of the algorithm
	if(sym_mem_wr and sym_mem_rd and sym_loc_wr and sym_loc_rd) then
		PrintLine("// All symmetric algorithm...");
		diff_prms := 1;
	else
		PrintLine("// Asymmetric algorithm...");
		diff_prms := stages;
		PrintLine("`define ASYMMETRIC_ALGO");
	fi;

	PrintLine("`define APPDATA_WIDTH ", opts.dram_datawidth);
	PrintLine("`define DDR_ADDR_WIDTH ", opts.dram_addrwidth);
	PrintLine("`define LOG_FFT_SIZE ", LogInt(fft_size,2));
	PrintLine("`define PACKET_SIZE ", tile_width/stream);
	PrintLine("`define PRECISION ", opts.precision);
	PrintLine("`define NUM_OF_STAGES ", stages);

	PrintLine("// ODCM parameters");
	PrintLine("//");
	PrintLine("// MemWrPrm:");
	for i in [1..diff_prms] do
		if(mem_wr_prms[i].func.numChildren() = 3) then # IxLxI
			a := mem_wr_prms[i].func._children[1].params[1];
			b := mem_wr_prms[i].func._children[2].params[1];
			c := mem_wr_prms[i].func._children[2].params[2];
			d := mem_wr_prms[i].func._children[3].params[1];
			PrintLine("// IxLxI: I(",a,")xL(",b,",",c,")xI(",d,") --> need to transpose for write address generation --> IxLxI: I(",a,")xL(",b,",",b/c,")xI(",d,")");
			PrintLine("`define MEM_WR_PERM_MODULE_NAME_",i," permIL #(.loga(",LogInt(a,2),"), .logb(",LogInt(b,2),"), .logc(",LogInt(b/c,2),"))");
			#PrintLine("`define MEM_WR_PERM_MODULE_NAME permIL #(.loga(",LogInt(a/stream,2),"), .logb(",LogInt(b/stream,2),"), .logc(",LogInt(c/stream,2),"))");
		fi;
		if(mem_wr_prms[i].func.numChildren() = 2) then # LxI
			b := mem_wr_prms[i].func._children[1].params[1];
			c := mem_wr_prms[i].func._children[1].params[2];
			d := mem_wr_prms[i].func._children[2].params[1];
			PrintLine("// LxI: L(",b,",",c,")xI(",d,") --> need to transpose for write address generation --> LxI: L(",b,",",b/c,")xI(",d,")");
			PrintLine("`define MEM_WR_PERM_MODULE_NAME_",i," permL #(.logb(",LogInt(b,2),"), .logc(",LogInt(b/c,2),"))");
			#PrintLine("`define MEM_WR_PERM_MODULE_NAME permL #(.logb(",LogInt(b/stream,2),"), .logc(",LogInt(c/stream,2),"))");
		fi;
	od;

	PrintLine("//");
	PrintLine("// MemRdPrm:");
	for i in [1..diff_prms] do
		if(mem_rd_prms[i].func.numChildren() = 3) then # IxLxI
			a := mem_rd_prms[i].func._children[1].params[1];
			b := mem_rd_prms[i].func._children[2].params[1];
			c := mem_rd_prms[i].func._children[2].params[2];
			d := mem_rd_prms[i].func._children[3].params[1];
			PrintLine("// IxLxI: I(",a,")xL(",b,",",c,")xI(",d,")");
			PrintLine("`define MEM_RD_PERM_MODULE_NAME_",i," permIL #(.loga(",LogInt(a,2),"), .logb(",LogInt(b,2),"), .logc(",LogInt(c,2),"))");
			#PrintLine("`define MEM_RD_PERM_MODULE_NAME permIL #(.loga(",LogInt(a/stream,2),"), .logb(",LogInt(b/stream,2),"), .logc(",LogInt(c/stream,2),"))");
		fi;
		if(mem_rd_prms[i].func.numChildren() = 2) then # LxI
			b := mem_rd_prms[i].func._children[1].params[1];
			c := mem_rd_prms[i].func._children[1].params[2];
			d := mem_rd_prms[i].func._children[2].params[1];
			PrintLine("// LxI: L(",b,",",c,")xI(",d,")");
			PrintLine("`define MEM_RD_PERM_MODULE_NAME_",i," permL #(.logb(",LogInt(b,2),"), .logc(",LogInt(c,2),"))");
			#PrintLine("`define MEM_RD_PERM_MODULE_NAME permL #(.logb(",LogInt(b/stream,2),"), .logc(",LogInt(c/stream,2),"))");
		fi;
	od;

	PrintLine("// LM parameters");
	PrintLine("//");
	PrintLine("// LocalWrPrm:");
	for i in [1..diff_prms] do
		locmodname := ConcatenationString("LocWrPrm_",String(i));
		PrintLine("/*** ");
		if(loc_wr_prms[i].func.numChildren() = 3) then # IxLxI
			a := loc_wr_prms[i].func._children[1].params[1];
			b := loc_wr_prms[i].func._children[2].params[1];
			c := loc_wr_prms[i].func._children[2].params[2];
			d := loc_wr_prms[i].func._children[3].params[1];
			PrintLine("// IxLxI: I(",a,")xL(",b,",",c,")xI(",d,")");
			path := genBRAMPermMem(TRC(TPrm(TL(b,c,a,d))), stream*2, format, opts.precision, locmodname);

		fi;
		if(loc_wr_prms[i].func.numChildren() = 2) then
			if(Length(loc_wr_prms[i].func._children[1].params) = 2) then # LxI
				b := loc_wr_prms[i].func._children[1].params[1];
				c := loc_wr_prms[i].func._children[1].params[2];
				d := loc_wr_prms[i].func._children[2].params[1];
				PrintLine("// LxI: L(",b,",",c,")xI(",d,")");
				path := genBRAMPermMem(TRC(TPrm(TL(b,c,1,d))), stream*2, format, opts.precision, locmodname);
			fi;
			if(Length(loc_wr_prms[i].func._children[1].params) = 1) then # IxL
				a := loc_wr_prms[i].func._children[1].params[1];
				b := loc_wr_prms[i].func._children[2].params[1];
				c := loc_wr_prms[i].func._children[2].params[2];
				PrintLine("// IxL: I(",a,")xL(",b,",",c,")");
				path := genBRAMPermMem(TRC(TPrm(TL(b,c,a,1))), stream*2, format, opts.precision, locmodname);
			fi;
		fi;
		if(loc_wr_prms[i].func.numChildren() = 0) then # L
			b := loc_wr_prms[i].func.params[1];
			c := loc_wr_prms[i].func.params[2];
			PrintLine("// L: L(",b,",",c,")");
			path := genBRAMPermMem(TRC(TPrm(TL(b,c,1,1))), stream*2, format, opts.precision, locmodname);
		fi;

		prefixModules(ConcatenationString(path, locmodname,".v"), ConcatenationString("lw",String(i)));
		module := setModule(ConcatenationString(path, locmodname,".v"), "module_name_is", ConcatenationString("perm_mem_locwr_",String(i)), genpath, bb);
		PrintLine("***/ ");
		PrintLine("`define LOC_WR_MODULE_NAME_",i," ",module);
		getLMLatency(ConcatenationString(path, locmodname,".v"), "WR", i, "/tmp/_spiral.tmp");
	od;

	#genBRAMPermMem(perm, w, format, bits, name)
	#genBRAMPermMem(TRC(TPrm(TL(32,2,1,1))), 4, 2, 16, "LocRdPrm");
	PrintLine("//");
	PrintLine("// LocalRdPrm:");
	for i in [1..diff_prms] do
		locmodname := ConcatenationString("LocRdPrm_",String(i));
		PrintLine("/*** ");
		if(loc_rd_prms[i].func.numChildren() = 3) then # IxLxI
			a := loc_rd_prms[i].func._children[1].params[1];
			b := loc_rd_prms[i].func._children[2].params[1];
			c := loc_rd_prms[i].func._children[2].params[2];
			d := loc_rd_prms[i].func._children[3].params[1];
			PrintLine("// IxLxI: I(",a,")xL(",b,",",c,")xI(",d,")");
			path := genBRAMPermMem(TRC(TPrm(TL(b,c,a,d))), stream*2, format, opts.precision, locmodname);
		fi;
		if(loc_rd_prms[i].func.numChildren() = 2) then
			if(Length(loc_rd_prms[i].func._children[1].params) = 2) then # LxI
				b := loc_rd_prms[i].func._children[1].params[1];
				c := loc_rd_prms[i].func._children[1].params[2];
				d := loc_rd_prms[i].func._children[2].params[1];
				PrintLine("// LxI: L(",b,",",c,")xI(",d,")");
				path := genBRAMPermMem(TRC(TPrm(TL(b,c,1,d))), stream*2, format, opts.precision, locmodname);
			fi;
			if(Length(loc_rd_prms[i].func._children[1].params) = 1) then # IxL
				a := loc_rd_prms[i].func._children[1].params[1];
				b := loc_rd_prms[i].func._children[2].params[1];
				c := loc_rd_prms[i].func._children[2].params[2];
				PrintLine("// IxL: I(",a,")xL(",b,",",c,")");
				path := genBRAMPermMem(TRC(TPrm(TL(b,c,a,1))), stream*2, format, opts.precision, locmodname);
			fi;
		fi;
		if(loc_rd_prms[i].func.numChildren() = 0) then # L
			b := loc_rd_prms[i].func.params[1];
			c := loc_rd_prms[i].func.params[2];
			PrintLine("// L: L(",b,",",c,")");
			path := genBRAMPermMem(TRC(TPrm(TL(b,c,1,1))), stream*2, format, opts.precision, locmodname);
		fi;

		prefixModules(ConcatenationString(path, locmodname,".v"), ConcatenationString("lr",String(i)));
		module := setModule(ConcatenationString(path, locmodname,".v"), "module_name_is", ConcatenationString("perm_mem_locrd_",String(i)), genpath, bb);
		PrintLine("***/ ");
		PrintLine("`define LOC_RD_MODULE_NAME_",i," ",module);
		getLMLatency(ConcatenationString(path, locmodname,".v"), "RD", i, "/tmp/_spiral.tmp");
	od;

	PrintLine("// LM Additive latency ");
	putLMLatency("/tmp/_spiral.tmp", "/tmp/_spiral_readFile.tmp");

	PrintLine("/*** ");
	path := HDLGen(streamDFTUnroll(fft_size,2,stream*2), 1, format, 0, 0, 0, "dftgen");
	prefixModules(ConcatenationString(path, "dftgen.v"), "f");
	module := setModule(ConcatenationString(path, "dftgen.v"), "module_name_is", "dftcore", genpath, bb);
	PrintLine("***/ ");

	PrintLine("//");
	PrintLine("// FFT Core");
	PrintLine("`define FFT_CORE_MODULE_NAME ", module);

	if(opts.throttle = 1) then
		PrintLine("`define THROTTLE_EN");
	fi;

	# Twiddle ROM
	if(Length(twiddles) > 0) then
		if(Length(twiddles) > 1) then
			Error("Unexpected number of twiddles!");
		fi;
		twiddle := twiddles[1];
		a := twiddle.params[1];
		b := twiddle.params[2];
		c := twiddle.params[3];

		PrintLine("/*** ");
		path := HDLGen(streamGen(TRC(TDiag(fPrecompute(Tw1(a, b, c)))).withTags([AStream(stream*2)]), InitStreamHw()), 1, format, 0, 0, 0, "twiddleUnit");
		cmdString := ConcatenationString(paradigms.stream._hardwarePath, "dram_scripts/_twidModuleName.sh ", path, "twiddleUnit.v ", "twiddleUnit ", "iter_in");
		Exec(cmdString);
		cmdString := ConcatenationString("cp ", path, "twiddleUnit.v ", genpath);
		Exec(cmdString);
		PrintLine("***/ ");
		PrintLine("//");
		PrintLine("// Twiddle Unit");
		PrintLine("`define TWIDDLE_UNIT_v2");
		PrintLine("`define TWIDDLE_UNIT_NAME tw_twiddleUnit");
		PrintLine("`define CODEBLOCK_IT iter_in");
	fi;

	PrintLine(" ");
	PrintLine("// Summary");
	PrintLine("//");
	PrintLine("// Tile Width:",tile_width/stream," accesses");
	PrintLine("// Tile Width:",tile_width," words");
	PrintLine("// Stages:",stages);
	PrintLine("// Streaming Width:",opts.dram_datawidth/opts.precision/2);
	PrintLine("// DFT Size:",fft_size);
	PrintLine("// Format: ",format," (2:double, 1:single)");

end;

genVerifFile := function(srt, opts, genpath)
	local mem_rd_prms, tile_width;

	mem_rd_prms := Collect(srt, MemRdPrm);
	tile_width := mem_rd_prms[1].func._children[Length(mem_rd_prms[1].func._children)].params[1];

	PrintLine("SIZE=",Collect(srt, DFT)[1].params[1]);
	PrintLine("SW=",opts.dram_datawidth/opts.precision/2);
	PrintLine("TILE=",tile_width);

	PrintLine("matlab -nodisplay -nosplash -r \"genRefInputOutput($SIZE,$SW,$TILE); exit;\"");
	PrintLine("./run.sh sim");
	PrintLine("matlab -nodisplay -nosplash -r \"compareOutputs($SIZE); exit;\" > matlab.log");
	PrintLine("echo \"\"");
	PrintLine("grep \"SNR (dB)\" matlab.log");

end;

DRAMSystemGen := function(srt, opts)
	local path,conf,cmd,s,verif;

	path := Concat("/tmp/spiral/dramSys", String(GetPid()), "/");
    MakeDir(path);

	# generate conf file & permMem & dft & put into genpath
	conf := ConcatenationString(path,"_configFile.vh");

	# 8/6/2014 - put the verification scripts in the genpath too
	verif := ConcatenationString(path,"verify.sh");

	# 2D or 3D?
	s := Length(Collect(srt, MemFence));
	if( s = 2 ) then
		PrintTo(conf, genConfigFile2(srt, opts, path));
		PrintTo(verif, genVerifFile(srt, opts, path));
		# copy src files into genpath
		cmd := ConcatenationString("cp -rf ", paradigms.stream._hardwarePath,"dram_src/* ", path);
	Exec(cmd);
	else if ( s = 3) then
		PrintTo(conf, genConfigFile3(srt, opts, path));
		# copy src files into genpath
		cmd := ConcatenationString("cp -rf ", paradigms.stream._hardwarePath, "dram_src_3d/* ", path);
	Exec(cmd);
	else
		PrintLine("SPLRuleTree not recognized! s=",s);
	fi;fi;

	return path;
end;


# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

LocalConfig.bench := rec();

if IsBound(platforms.sse) then         LocalConfig.bench.SSE     := platforms.sse.benchSSE; fi;
if IsBound(platforms.intel) then       LocalConfig.bench.SMP_SSE := platforms.intel.benchCore2; fi;
if IsBound(platforms.benchScalar) then LocalConfig.bench.scalar  := platforms.benchScalar; fi;
if IsBound(platforms.avx) then         LocalConfig.bench.AVX     := platforms.avx.benchAVX; fi;

_dumpBench := function(obj, objPath, masterList, level)
	local newObj, newObjPath, fieldNames, name, size;

	if IsClass(obj) then
		if obj.__name__ = "DPBench" then
			if (level > 0) and IsBound(obj.sizes) and EndsWith(objPath, ")") then
				newObjPath := DropLast(Copy(objPath), 1);
				for size in obj.sizes do
					Append(masterList, [newObjPath::"["::String(size)::"])"]);
				od;
			else
				Append(masterList, [Copy(objPath)]);
			fi;
		fi;
		return;
	elif IsRec(obj) then
		fieldNames := UserRecFields(obj);
		for name in fieldNames do
			newObj := obj.(name);
			newObjPath := Copy(objPath)::"."::Copy(name);
			_dumpBench(newObj, newObjPath, masterList, level);
		od;
	elif IsFunc(obj) then
		newObj := obj();
		newObjPath := Copy(objPath)::"()";
		_dumpBench(newObj, newObjPath, masterList, level);
	fi;
end;

#F
#F DumpBenches(platform, level)
#F     platform : string, name of platform, eg., "SSE"
#F     level    : integer, level of detail
#F                0 : build benches that test a list of sizes
#F                1 : build an individual bench for each size
#F
#F Returns a list of all bench constructors for the specified platform
#F

DumpBenches := function(platform, level)
	local benchlist;

	benchlist := [];

	if IsBound(LocalConfig.bench.(platform)) and IsFunc(LocalConfig.bench.(platform)) then
		_dumpBench(LocalConfig.bench.(platform), "LocalConfig.bench."::platform, benchlist, level);
	fi;

	return Copy(benchlist);
end;

#F
#F DumpAllBenches(level = 0)
#F     level    : integer, optional (see DumpBenches)
#F
#F Returns a list of bench constructors for all supported platforms.
#F

DumpAllBenches := function(arg)
	local level, platform, platforms, benchlist;

	level := Cond(IsBound(arg[1]) and IsInt(arg[1]), arg[1], 0);

	##platforms := UserRecFields(LocalConfig.bench);
	platforms := ["SSE", "AVX"];

	benchlist := [];

	for platform in platforms do
		Append(benchlist, DumpBenches(platform, level));
	od;

	return Copy(benchlist);
end;


%options package=$PACKAGE_NAME$
%options template=$TEMPLATE$F.gi
$KEYWORD_FILTER$
--
-- This is just a sample lexer and not a real lexer for $LANG_NAME$
--

%Globals
    /.

    ./
%End

%Define
    $additional_interfaces /., ILexer./
    $kw_lexer_class /.$KEYWORD_LEXER$./
%End

%Include
    $LEXER_MAP$F.gi
%End

%Export
    --
    -- List all the token types the lexer will directly process
    -- and export to the parser. If a keyword lexer is used as
    -- a filter for this lexer, it may export a set of keywords
    -- that will also be passed along to the parser.
    --
    -- For example:
    --
        SINGLE_LINE_COMMENT
        IDENTIFIER
        NUMBER
        DoubleLiteral
        COMMA
        SEMICOLON
        PLUS
        MINUS
        TIMES
        DIVIDE
        GREATER
        LESS
        EQUAL
        NOTEQUAL
        ASSIGN
        LEFTPAREN
        RIGHTPAREN
        LEFTBRACE
        RIGHTBRACE
%End

%Terminals
    CtlCharNotWS

    LF   CR   HT   FF

    a    b    c    d    e    f    g    h    i    j    k    l    m
    n    o    p    q    r    s    t    u    v    w    x    y    z
    _

    A    B    C    D    E    F    G    H    I    J    K    L    M
    N    O    P    Q    R    S    T    U    V    W    X    Y    Z

    0    1    2    3    4    5    6    7    8    9

    AfterASCII   ::= '\u0080..\ufffe'
    Space        ::= ' '
    LF           ::= NewLine
    CR           ::= Return
    HT           ::= HorizontalTab
    FF           ::= FormFeed
    DoubleQuote  ::= '"'
    SingleQuote  ::= "'"
    Percent      ::= '%'
    VerticalBar  ::= '|'
    Exclamation  ::= '!'
    AtSign       ::= '@'
    BackQuote    ::= '`'
    Tilde        ::= '~'
    Sharp        ::= '#'
    DollarSign   ::= '$'
    Ampersand    ::= '&'
    Caret        ::= '^'
    Colon        ::= ':'
    SemiColon    ::= ';'
    BackSlash    ::= '\'
    LeftBrace    ::= '{'
    RightBrace   ::= '}'
    LeftBracket  ::= '['
    RightBracket ::= ']'
    QuestionMark ::= '?'
    Comma        ::= ','
    Dot          ::= '.'
    LessThan     ::= '<'
    GreaterThan  ::= '>'
    Plus         ::= '+'
    Minus        ::= '-'
    Slash        ::= '/'
    Star         ::= '*'
    LeftParen    ::= '('
    RightParen   ::= ')'
    Equal        ::= '='
%End

%Start
    Token
%End

%Rules
    Token ::= identifier    /.    checkForKeyWord();./
            | number        /.    makeToken($_NUMBER);./
            | DoubleLiteral /.    makeToken($_DoubleLiteral);./
            | white         /.    skipToken();./
            | slc           /.    makeComment($_SINGLE_LINE_COMMENT);./
            | ';'           /.    makeToken($_SEMICOLON);./
            | ','           /.    makeToken($_COMMA);./
            | '+'           /.    makeToken($_PLUS);./
            | '-'           /.    makeToken($_MINUS);./
            | '='           /.    makeToken($_ASSIGN);./
            | '('           /.    makeToken($_LEFTPAREN);./
            | ')'           /.    makeToken($_RIGHTPAREN);./
            | '{'           /.    makeToken($_LEFTBRACE);./
            | '}'           /.    makeToken($_RIGHTBRACE);./
            | '*'           /.    makeToken($_TIMES);./
            | '/'           /.    makeToken($_DIVIDE);./
            | '>'           /.    makeToken($_GREATER);./
            | '<'           /.    makeToken($_LESS);./
            | '=' '='       /.    makeToken($_EQUAL);./
            | '!' '='       /.    makeToken($_NOTEQUAL);./

    identifier -> letter
                | identifier letter
                | identifier digit

    number ::= digit
             | number digit

    DoubleLiteral ::= Decimal
                    | Decimal Exponent
                    | number Exponent

    Exponent ::= LetterEe number
               | LetterEe '-' number
               | LetterEe '+' number

    LetterEe ::= 'e'
               | 'E'

    Decimal ::= '.' number
              | number '.'
              | number '.' number

    white ::= whiteChar
            | white whiteChar

    slc ::= '/' '/'
          | slc notEOL

    digit ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

    aA ::= a | A
    bB ::= b | B
    cC ::= c | C
    dD ::= d | D
    eE ::= e | E
    fF ::= f | F
    gG ::= g | G
    hH ::= h | H
    iI ::= i | I
    jJ ::= j | J
    kK ::= k | K
    lL ::= l | L
    mM ::= m | M
    nN ::= n | N
    oO ::= o | O
    pP ::= p | P
    qQ ::= q | Q
    rR ::= r | R
    sS ::= s | S
    tT ::= t | T
    uU ::= u | U
    vV ::= v | V
    wW ::= w | W
    xX ::= x | X
    yY ::= y | Y
    zZ ::= z | Z

    letter ::= aA | bB | cC | dD | eE | fF | gG | hH | iI | jJ | kK | lL | mM | nN | oO | pP | qQ | rR | sS | tT | uU | vV | wW | xX | yY | zZ

    -- any ::= letter | digit | special | white

    whiteChar ::= Space | LF | CR | HT | FF

    special ::= '+' | '-' | '(' | ')' | '"' | '!' | '@' | '`' | '~' | '.' |
                '%' | '&' | '^' | ':' | ';' | "'" | '\' | '|' | '{' | '}' |
                '[' | ']' | '?' | ',' | '<' | '>' | '=' | '#' | '*' | '_' |
                '/' | '$'

    notEOL ::= letter | digit | special | Space | HT | FF
%End


# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

Import(paradigms.vector.rewrite);
Import(paradigms.smp);
Import(paradigms.distributed);
Import(paradigms.multibuffer);

StandardVecRules := MergedRuleSet(RulesSplitComplex, StandardSumsRules, RulesSMP, RulesVec, RulesPropagate, RulesVDiag, RulesKickout, RulesTermGrp, TerminateSymSPL);
HfuncVecRules    := MergedRuleSet(StandardVecRules, RulesHfunc);

PartialVecTermRules := MergedRuleSet(RulesVRC, RulesRC, RulesTermGrp, RulesVRCTermDiag);
FullVecTermRules    := MergedRuleSet(RulesVRC, RulesRC, RulesTermGrp, RulesVRCTerm, RulesTerm, TerminateSymSPL);

ErrorOut := function(sums, opts)
    PrintLine("------------- Begin ------------------");
    PrintLine(sums);
    PrintLine("------------- End ------------------");
    Error("BP: postprocess");
    return(sums);
end;

ErrorOut1 := function(sums, opts)
   Print(".");
   return(sums);
end;

VecLibStrategiesCell := rec(
#    sigmaSpl := [ StandardSumsRules, StandardVecRules ],
   sigmaSpl := [
               #(s, opts) -> ErrorOut(s, opts),
               RemoveNoPull_Dist,
               StandardSumsRules ],

    postProcess := [
    #(s, opts) -> ErrorOut(s, opts),
    MergedRuleSet(PTensorRules, StandardSumsRules, RulesTermGrp, RemoveBuf),
    #(s, opts) -> ErrorOut(s, opts),
    MergedRuleSet(PTensorConvertRules, StandardSumsRules),
    #(s, opts) -> ErrorOut(s, opts),
    StandardSumsRules,
    #(s, opts) -> ErrorOut(s, opts),
    StandardVecRules,
    #(s, opts) -> ErrorOut(s, opts),
        MergedRuleSet(HfuncVecRules, PartialVecTermRules),
    #(s, opts) -> ErrorOut(s, opts),
       MergedRuleSet(HfuncVecRules, PartialVecTermRules),
    #(s, opts) -> ErrorOut(s, opts),
       #(s, opts) -> BlockSums(opts.globalUnrolling, s), #Doing this later (is this a problem?)
       #(s, opts) -> Process_fPrecompute(s, opts), # Doing this in the codegen so we can verify
       RecursStepTerm,
    #(s, opts) -> ErrorOut(s, opts),
       MergedRuleSet(StandardVecRules, FullVecTermRules),
    #(s, opts) -> ErrorOut(s, opts),
       MergedRuleSet(RulesHfunc, RulesFuncSimp, RulesStrengthReduce),
    #(s, opts) -> ErrorOut(s, opts),
        CellVRCTerm,
        RemoveBuf,
      DistMultiBuf,
        #RulesSums, # So composes are flattened before RulesComposeDists runs
       RulesComposeDists,
        #RulesDistMerge, # Not required because for the DMP algorithm, we now do this at a high level
    #(s, opts) -> ErrorOut(s, opts),
      (s, opts) -> applyCellRules(s, opts),
    #(s, opts) -> ErrorOut(s, opts),
        MultiBufDist, #NOTE: Need to do this after RC/VRC has been dealt with, (but before marking BBs). Why not move this to where DistMBuf is?
    #(s, opts) -> ErrorOut(s, opts), #Uncomment line to see state after VRC rules apply.
#      DistMultiBuf, # Why is this here? Why not move it down?
    #(s, opts) -> ErrorOut(s, opts),
        FixBorder,    #Border is used by MultiBufDist, and hence cannot precede it, since FixBorder removes the border
    #(s, opts) -> ErrorOut(s, opts),
        (s, opts) -> BlockSums(opts.globalUnrolling, s),
    #(s, opts) -> ErrorOut(s, opts),
#       (s, opts) -> CellDFTBlockCyclicLayoutHackWrap(s, opts),   # This does nothing if there are no GathRecv/ScatSend that still have functions
    #(s, opts) -> ErrorOut(s, opts),
        (s, opts) -> applyCellInplace(s, opts),
        RulesComposeStreams
    ],

    rc := [],
    preRC := [],
);

VecLibStrategies := rec(
#    sigmaSpl := [ StandardSumsRules, StandardVecRules ],
    sigmaSpl := [ StandardSumsRules ],

    postProcess := [
    #MergedRuleSet(PTensorRules, StandardSumsRules),
    #MergedRuleSet(PTensorConvertRules, StandardSumsRules),
    StandardSumsRules,
    StandardVecRules,
        MergedRuleSet(HfuncVecRules, PartialVecTermRules),
        MultiBufDist,
        MergedRuleSet(HfuncVecRules, PartialVecTermRules),
        (s, opts) -> BlockSums(opts.globalUnrolling, s),
        (s, opts) -> Process_fPrecompute(s, opts), # Doing this in the codegen so we can verify
        RecursStepTerm,
        MergedRuleSet(StandardVecRules, FullVecTermRules),
        MergedRuleSet(RulesHfunc, RulesFuncSimp, RulesStrengthReduce),
        CellVRCTerm,
        (s, opts) -> applyCellRules(s, opts),
        CellDFTBlockCyclicLayoutHack,
        (s, opts) -> applyCellInplace(s, opts),
        RulesComposeDists
    ],

    rc := [],
    preRC := [],
);

VecLibTerminate := [
    MergedRuleSet(StandardVecRules, FullVecTermRules),
    MergedRuleSet(RulesHfunc, RulesFuncSimp, RulesStrengthReduce),
];

#
# NOTE: implement layering
#
#Class(VecRecCodegen, RecCodegen, VectorCodegen);
Class(VecRecCodegen, RecCodegen, VectorCodegen, rec(

#	vRC_Compose := Rule([vRC, @(1, Compose)], e -> Compose(List(@(1).val.children(), vRC))),
#	vRC_SUM := Rule([vRC, @(1, SUM)], e -> SUM(List(@(1).val.children(), vRC))),
#	vRC_SUMAcc := Rule([vRC, @(1, SUMAcc)], e -> SUMAcc(List(@(1).val.children(), vRC))),

#	vRC_Container := Rule([vRC, @(1, [BB,Buf,Inplace,Grp,NoPull,NoPullLeft,NoPullRight, NoDiagPullin, NoDiagPullinLeft, NoDiagPullinRight ])],
#		e -> ObjId(@(1).val)(vRC(@(1).val.child(1)))),

#	vRC_Data := Rule([vRC, @(1, Data)], e -> Data(@(1).val.var, @(1).val.value, vRC(@(1).val.child(1)))),

#	vRC_RStep := Rule([vRC, @(1, RecursStep)], e -> RecursStep(2*@(1).val.yofs, 2*@(1).val.xofs,vRC(@(1).val.child(1)))),

#	vRC_ISum := Rule([vRC, @(1, ISum)], e -> ISum(@(1).val.var, @(1).val.domain, vRC(@(1).val.child(1)))),
#	vRC_ICompose := Rule([vRC, @(1, ICompose)], e -> ICompose(@(1).val.var, @(1).val.domain, vRC(@(1).val.child(1)))),

#	vRC_Grp := Rule([vRC, @(1, Grp)], e -> Grp(vRC(@(1).val.child(1)))),

#	vRC_Scale := Rule([vRC, @(1, Scale)], e ->
#		vRC(Diag(fConst(Rows(@(1).val), @(1).val.scalar))) * vRC(@(1).val.child(1))),

#	vRC_CR := Rule([vRC, @(1, CR)], e -> @(1).val.child(1)),

#	VTensor_VScale := Rule([@(1, VTensor), [VScale, @(2), @(3), @(4)]], e -> VScale(VTensor(@(2).val, @(1).val.vlen), @(3).val, @(4).val*@(1).val.vlen)),
#	VTensor_VTensor := Rule([@(1, VTensor), @(2, VTensor)], e -> VTensor(@(2).val.child(1), @(1).val.vlen*@(2).val.vlen)),

#	vRC_SMP := Rule([@(1, vRC), @(2, [SMPSum, SMPBarrier])],
#		e -> let(s := @(2).val, CopyFields(s, rec(_children := List(s.children(), c->ObjId(@(1).val)(c)), dimensions := @(1).val.dimensions)))),

# should RC and VRC be in some other place?
#	vRC_TCvt := Rule( [@(0, [vRC, RC, VRC]), @(1, TCvt)],
#		e -> let( t := @(1).val, TCvt( 2*t.n(), t.isa_to(), t.isa_from(), t.props()).withTags(t.getTags()).takeAobj(t) )),

));

Class(OpenMP_SSEUnparser, paradigms.smp.OpenMP_UnparseMixin, platforms.sse.SSEUnparser);
Class(OpenMP_SSEUnparser_ParFor, paradigms.smp.OpenMP_UnparseMixin_ParFor, platforms.sse.SSEUnparser);
Class(SMP_SSEUnparser,    paradigms.smp.SMP_UnparseMixin,    platforms.sse.SSEUnparser);
Class(SMP_NEONUnparser,    paradigms.smp.SMP_UnparseMixin,    platforms.neon.NEONUnparser);

Class(OpenMP_AVXUnparser, paradigms.smp.OpenMP_UnparseMixin, platforms.avx.AVXUnparser);
Class(OpenMP_AVXUnparser_ParFor, paradigms.smp.OpenMP_UnparseMixin_ParFor, platforms.avx.AVXUnparser);

SMP_NEONUnparser.preprocess := x -> FixAssign0(x);
SMP_SSEUnparser.preprocess := x -> FixAssign0(x);
OpenMP_SSEUnparser.preprocess := x -> FixAssign0(x);

Declare(_InitVecLibgen);
Declare(_InitVecParLibgenNEON);
Declare(_InitVecLibgenNEON);

InitVecLibgenCell := (isa, use_functions, use_openmp, use_buffering, simdopts) ->
    _InitVecLibgen(
        InitLibgen(CopyFields(LibgenDefaults, SIMDGlobals.getOpts(isa, simdopts),
        rec(generateComplexCode:=false))),
        use_functions, use_openmp, use_buffering, false);

InitVecLibgenNEON := (opt) ->
    _InitVecLibgenNEON(opt);

InitVecParLibgenNEON := (opt, use_functions, use_openmp, use_buffering) ->
    _InitVecParLibgenNEON(
			InitLibgen(CopyFields(LibgenDefaults, _InitVecLibgenNEON(opt), rec(generateComplexCode:=false))),
        use_functions, use_openmp, use_buffering, false);

InitVecParLibgenNEONCx := (opt, use_functions, use_openmp, use_buffering) ->
    _InitVecParLibgenNEON(
			InitLibgen(CopyFields(CplxLibgenDefaults, _InitVecLibgenNEON(opt), rec(generateComplexCode:=true))),
        use_functions, use_openmp, use_buffering, false);

InitVecLibgen := (isa, use_functions, use_openmp, use_buffering) ->
    _InitVecLibgen(
        InitLibgen(CopyFields(LibgenDefaults, SIMDGlobals.getOpts(isa,
                     rec(svct:=true, splitL:=true, oddSizes:=false)), rec(generateComplexCode:=false))),
        use_functions, use_openmp, use_buffering, false);

InitParLibgen := (use_functions, use_openmp, use_buffering) ->
    _InitVecLibgen(InitLibgen(LibgenDefaults), use_functions, use_openmp, use_buffering, false);

InitParLibgenCplx := (use_functions, use_openmp, use_buffering, use_inplace) -> CopyFields(
    _InitVecLibgen(InitLibgen(CplxLibgenDefaults), use_functions, use_openmp, use_buffering, use_inplace),
    rec(unparser := CMacroUnparserProg));

_InitVecLibgenNEON := function(opt)
#	local opts;
#	opts := platforms.neon.benchNEON().half.1d.dft_ic.small.cmplx().getOpts();
#	opts.profile := default_profiles.linux_arm;
	opt.globalUnrolling := 64;
	opt.breakdownRules.TL := [L_cx_real, SIMD_ISA_Bases1, SIMD_ISA_Bases2, IxLxI_kmn_n, IxLxI_kmn_km, L_mn_m_vec, IxLxI_vtensor];
	return opt;
end;

_InitVecParLibgenNEON := function(opt, use_functions, use_openmp, use_buffering, use_inplace)
    local opts, m, clet_size;
#		opt := platforms.neon.benchNEON().half.1d.dft_ic.small.cmplx().getOpts();
		#	opts.profile := default_profiles.linux_arm_pthread;
		opt.globalUnrolling := 64;
		opt.breakdownRules.TL := [L_cx_real, SIMD_ISA_Bases1, SIMD_ISA_Bases2, IxLxI_kmn_n, IxLxI_kmn_km, L_mn_m_vec, IxLxI_vtensor];
    m := When(not use_functions, 64, 32);
    clet_size := m;

    opts := CopyFields(opt, rec(
#        formulaStrategies := Copy(VecLibStrategies),
        breakdownRules := CopyFields(opt.breakdownRules, rec(
            GT := [
                CopyFields(GT_Base, rec(maxSize:=false)),
                GT_NthLoop,
                CopyFields(GT_DFT_CT, rec(
                        minRank := 1,
                        maxRank := When(use_buffering, 1, 0),
                        minSize := When(use_functions, m+1, 1),
                        forTransposition := false,   # What is the right value? beta.anl needs false.
                        codeletSize := When(use_inplace, clet_size, false),
                        inplace := use_inplace)),
                CopyFields(GT_Par, rec(parEntireLoop := false, splitLoop := true)),
                GT_Vec_AxI,
                GT_Vec_IxA, GT_Vec_IxA_L, GT_Vec_L_IxA,
                GT_Vec_SplitL ],
#						DFT := opts.breakdownRules.DFT :: [
#                CopyFields(DFT_GT_CT, rec(
#                        codeletSize := When(use_inplace, clet_size, false),
#                        inplace := use_inplace,
#                        minSize := When(use_functions, m+1, 1)))
#						],
            DFT := [
                DFT_Rader, DFT_GoodThomas, DFT_PD, DFT_Base,
                CopyFields(DFT_CT, rec(maxSize := m)),
                CopyFields(DFT_GT_CT, rec(
                        codeletSize := When(use_inplace, clet_size, false),
                        inplace := use_inplace,
            # YSV: Please don't uncomment without talking to YSV
                        #requiredFirstTag := [AVecReg, AVecRegCx, AParSMP, ParCell],
                        minSize := When(use_functions, m+1, 1)))
            ],
            DFT3     := [ DFT3_Base, DFT3_CT ],
            MDDFT    := [ MDDFT_Base, MDDFT_tSPL_RowCol ],
            # Below is used for MDDFT. MDDFT with inplaceness excludes DFT with inplaceness
            # Thus setting use_inplace=false will disable DFT inplaceness, and enable it in MDDFT
            # NOTE: above is ugly! the only way to fix it is to figure out storage schemes automatically..
            TTensor  := [ CopyFields(AxI_IxB,rec(inplace:=not use_inplace)),
                          CopyFields(IxB_AxI,rec(inplace:=not use_inplace)) ],
            TTensorI := [ TTensorI_toGT ],
            TCompose := [ TCompose_tag ],
#D            TTag     := [TTag_down],
            InfoNt   := [Info_Base]
        )),

        codegen := VecRecCodegen,
        libgen := CopyFields(opt.libgen, rec(terminateStrategy := VecLibTerminate)),
        compileStrategy := IndicesCS,
        useDeref := true
    ));

    if use_buffering then Add(opts.breakdownRules.GT,
            CopyFields(GT_BufReshape, rec(bufIters := [2,4,8,16], u := [2,4]))); fi;

    if not use_functions then
        opts.baseHashes := DropLast(opts.baseHashes, 1);
        Append(opts.formulaStrategies.postProcess, opts.libgen.terminateStrategy);
    fi;

    if use_openmp then
        opts.unparser := OpenMP_SSEUnparser;
    else
        # should not be needed in latest version
        opts.subParams := [var("num_threads", TInt), var("tid", TInt)];
        opts.unparser := SMP_NEONUnparser;
        opts.profile := When(LocalConfig.osinfo.isWindows(), # or LocalConfig.osinfo.isCygwin(),
            LocalConfig.cpuinfo.profile.threads(),
            profiler.default_profiles.linux_x86_threads
        );
    fi;

    return opts;
end;

_InitVecLibgen := function(opts, use_functions, use_openmp, use_buffering, use_inplace)
    local opts, m, clet_size;
    m := When(not use_functions, 64, 32);
    clet_size := m;

    opts := CopyFields(opts, rec(
        formulaStrategies := Copy(VecLibStrategies),
        breakdownRules := CopyFields(opts.breakdownRules, rec(
            GT := [
                CopyFields(GT_Base, rec(maxSize:=false)),
                GT_NthLoop,
                CopyFields(GT_DFT_CT, rec(
                        minRank := 1,
                        maxRank := When(use_buffering, 1, 0),
                        minSize := When(use_functions, m+1, 1),
                        forTransposition := false,   # What is the right value? beta.anl needs false.
                        codeletSize := When(use_inplace, clet_size, false),
                        inplace := use_inplace)),
                CopyFields(GT_Par, rec(parEntireLoop := false, splitLoop := true)),
                GT_Vec_AxI,
                GT_Vec_IxA, GT_Vec_IxA_L, GT_Vec_L_IxA,
                GT_Vec_SplitL ],
            DFT := [
                DFT_Rader, DFT_GoodThomas, DFT_PD, DFT_Base,
                CopyFields(DFT_CT, rec(maxSize := m)),
                CopyFields(DFT_GT_CT, rec(
                        codeletSize := When(use_inplace, clet_size, false),
                        inplace := use_inplace,
            # YSV: Please don't uncomment without talking to YSV
                        #requiredFirstTag := [AVecReg, AVecRegCx, AParSMP, ParCell],
                        minSize := When(use_functions, m+1, 1)))
            ],
            DFT3     := [ DFT3_Base, DFT3_CT ],
            MDDFT    := [ MDDFT_Base, MDDFT_tSPL_RowCol ],
            # Below is used for MDDFT. MDDFT with inplaceness excludes DFT with inplaceness
            # Thus setting use_inplace=false will disable DFT inplaceness, and enable it in MDDFT
            # NOTE: above is ugly! the only way to fix it is to figure out storage schemes automatically..
            TTensor  := [ CopyFields(AxI_IxB,rec(inplace:=not use_inplace)),
                          CopyFields(IxB_AxI,rec(inplace:=not use_inplace)) ],
            TTensorI := [ TTensorI_toGT ],
            TCompose := [ TCompose_tag ],
#D            TTag     := [TTag_down],
            InfoNt   := [Info_Base]
        )),

        codegen := VecRecCodegen,
        libgen := CopyFields(opts.libgen, rec(terminateStrategy := VecLibTerminate)),
        compileStrategy := IndicesCS,
        useDeref := true
    ));

    if use_buffering then Add(opts.breakdownRules.GT,
            CopyFields(GT_BufReshape, rec(bufIters := [2,4,8,16], u := [2,4]))); fi;

    if not use_functions then
        opts.baseHashes := DropLast(opts.baseHashes, 1);
        Append(opts.formulaStrategies.postProcess, opts.libgen.terminateStrategy);
    fi;

    if use_openmp then
        opts.unparser := OpenMP_SSEUnparser;
    else
        # should not be needed in latest version
        opts.subParams := [var("num_threads", TInt), var("tid", TInt)];
        opts.unparser := SMP_SSEUnparser;
        opts.profile := When(LocalConfig.osinfo.isWindows(), # or LocalConfig.osinfo.isCygwin(),
            LocalConfig.cpuinfo.profile.threads(),
            profiler.default_profiles.linux_x86_threads
        );
    fi;

    return opts;
end;

# Example: doParSimdDft(1, 8, false, false, false); # no threads
# Example: doParSimdDft(4, 8, false, false, false); # 4 threads, SPMD
# Example: doParSimdDft(4, 8, false, true, false);  # 4 threads, OpenMP
# Example: doParSimdDft(4, 8, true, true, false);   # 4 threads, OpenMP, codelet reuse
#
doParSimdDft := function(arg)
    local sizes, opts, dpbench, tags, name, isa, p, logn, use_functions, use_openmp,
        use_buffering, interleavedComplex, argrec,simd_opts;

    isa := arg[1];
    p := arg[2];
    logn := arg[3];
    if Length(arg) = 4 and IsRec(arg[4]) then
        argrec := CopyFields(rec(
            use_functions := false,
            use_openmp := true,
            use_buffering := false,
            interleavedComplex := true,
            simd_opts := rec()
        ), arg[4]);
        use_functions := argrec.use_functions;
        use_openmp := argrec.use_openmp;
        use_buffering := argrec.use_buffering;
        interleavedComplex := argrec.interleavedComplex;
        simd_opts := argrec.simd_opts;
    else
        use_functions := arg[4];
        use_openmp := arg[5];
        use_buffering := arg[6];
        interleavedComplex := arg[7];
        simd_opts := rec();
    fi;

    opts := InitVecLibgen(isa, use_functions, use_openmp, use_buffering);
    if use_openmp then opts.language := "c.icl.openmp"; fi;

    if IsList(logn) then sizes := logn;
    else sizes := List([2 * isa.v * p^2 .. logn], d -> 2^d); fi;

    tags := When(p=1, [AVecReg(opts.vector.isa)], [AParSMP(p), AVecReg(opts.vector.isa)]);
    opts.benchTransforms := List(sizes, d -> When(interleavedComplex, InterleavedComplexT, SplitComplexT)(DFT(d)).withTags(tags));
    if p = 1 then
        PrintLine("ISA: ", isa, ", sizes: ", sizes);
        name := isa.name;
    else
        PrintLine("ISA: ", isa, ", threads: ",p,", sizes: ", sizes);
        name := Concat(StringInt(p), "p_", isa.name);
    fi;
    dpbench := DPBench(rec((name) := opts),
                    rec(timeBaseCases := false, verbosity:=0));
    return dpbench;
end;

doParSimdMddft := function(isa, p, logn, use_functions, use_openmp, use_buffering)
    local sizes, opts, dpbench, tags;
    opts := InitVecLibgen(isa, use_functions, use_openmp, use_buffering);

    if IsList(logn) then sizes := logn;
    else sizes := List(Cartesian([4..logn], [4..logn]), d -> [2^d[1], 2^d[2]]); fi;

    tags := When(p=1, [AVecReg(opts.vector.isa)], [AParSMP(p), AVecReg(opts.vector.isa)]);
    PrintLine("ISA: ", isa, ", threads: ",p,", sizes: ", sizes);

    opts.benchTransforms := List(sizes, d -> TRC(MDDFT(d)).withTags(tags));
    dpbench := DPBench(rec((Concat(StringInt(p), "p_", isa.name)) := opts),
                       rec(timeBaseCases := false, verbosity:=0));
    return dpbench;
end;

doParSimdWht := function(isa, p, logn, use_functions, use_openmp, use_buffering)
    local sizes, opts, dpbench, tags;
    opts := InitVecLibgen(isa, use_functions, use_openmp, use_buffering);

    if use_openmp then opts.language := "c.icl.openmp"; else opts.language := "c.icl.opt.core2"; fi;

    if IsList(logn) then sizes := logn;
    else sizes := [Log2Int(isa.v^2 * p^2) .. logn]; fi;

    tags := When(p=1, [AVecReg(opts.vector.isa)], [AParSMP(p), AVecReg(opts.vector.isa)]);
    opts.benchTransforms := List(sizes, d -> WHT(d).withTags(tags));
    PrintLine("ISA: ", isa, ", threads: ",p,", sizes: ", sizes);
    dpbench := DPBench(rec((Concat(StringInt(p), "p_", isa.name)) := opts),
                       rec(timeBaseCases := false, verbosity:=0));
    return dpbench;
end;

Class(cellopts_tmp, rec(
  sc := rec(tsplRader:=false, tsplBluestein:=false, tsplPFA:=false, oddSizes:=false, interleavedComplex := false),
  ic := rec(tsplRader:=false, tsplBluestein:=false, tsplPFA:=false, oddSizes:=false, interleavedComplex := true),
));

#F doMBufIndeParSimdDftCell_old([sizes], isa, p, mbuf_its)
doMBufIndeParSimdDftCell_old := function(arg)
#doMBufIndeParSimdWhtCell := function(arg)
    local sizes, opts, tags, dpbench, mbuf_its, name, isa, logn, use_functions, use_openmp,
        use_buffering, interleavedComplex, argrec, simd_opts, transform, p, extra_its;

    sizes := arg[1];
    isa   := arg[2];
    p     := arg[3];
    mbuf_its := arg[4];

    use_functions := false;
    use_openmp := true;
    use_buffering := false;
    interleavedComplex := true; #NOTE: change this?
    simd_opts := rec();

    opts := InitVecLibgenCell(isa, use_functions, use_openmp, use_buffering, cellopts_tmp.sc);
    opts.compileStrategy := IndicesCS2_FMA;
    opts.spus     := p;
    opts.multibuffer_its := mbuf_its;
    opts.codegen  := MBufCodegen;
    opts.unparser := isa.unparser;
    opts.profile  := isa.backendConfig.profile;

    #opts.globalUnrolling := opts.globalUnrolling*p;
    opts.globalUnrolling := 520;

    opts.measSteadyState := true;

    #opts.breakdownRules.GT := [ GT_Base, GT_NthLoop, GT_DFT_CT, GT_CellDMP_base, GT_CellDMP_gen, GT_Cell, GT_Vec_AxI, GT_Vec_IxA_Push, GT_Vec_IxA_L, GT_Vec_L_IxA, GT_MBufCell_spec ];
    opts.breakdownRules.GT := [ GT_Base, GT_NthLoop, GT_DFT_CT, GT_CellDMP_base, GT_CellDMP_gen, GT_Cell, GT_Vec_AxI, GT_Vec_IxA_Push, GT_Vec_IxA_L, GT_Vec_L_IxA ];
    opts.breakdownRules.TL := Concatenation(opts.breakdownRules.TL, [  TL_CellDMP ]);

    # Determine best packet size here. Packet size is simply size of kernel

    # Determine the correct # of extra loops here
    # For block size of 16384 bytes, we need (16384/(opts.vector.bits/8))/

    # WHT:
    #transform := k -> GT(WHT(LogInt(k,2)), GTPar, GTPar, [mbuf_its*p*((16384/(opts.vector.isa.bits/8))/k)]).withTags(
    #            Concatenation([ParCell(p, k*((16384/(opts.vector.isa.bits/8))/k)), MBufCell(mbuf_its)], opts.tags));

    # DFT_ic:
    transform := k -> GT( TRC(GT(DFT(k,1,false), GTPar, GTPar, [((16384/(opts.vector.isa.bits/8))/(2*k))])), GTPar, GTPar, [mbuf_its*p]).withTags(
                        Concatenation([ParCell(p, 2*k*((16384/(opts.vector.isa.bits/8))/(2*k))), MBufCell(mbuf_its)], opts.tags) );

    # DFT_sc: (untested)
    #transform := k -> GT( SplitComplexT(DFT(k,1,false)), GTPar, GTPar, [mbuf_its*p*((16384/(opts.vector.isa.bits/8))/(2*k))]).withTags(
    #                     Concatenation([ParCell(p, 2*k*((16384/(opts.vector.isa.bits/8))/(2*k))), MBufCell(mbuf_its)], opts.tags) );

    #Error("BP");
    opts.benchTransforms := List(sizes, transform);

    PrintLine("ISA: ", isa, ", Multibuf_its: ",mbuf_its,", sizes: ", sizes);
    name := Concat(StringInt(p), "p_", StringInt(mbuf_its), "mbuf_", isa.name, "_ic");

    dpbench := DPBench(rec((name) := opts),
                    rec(timeBaseCases := false, verbosity:=0));
    return dpbench;
end;

#F doMBufIndeParSimdDftCell([sizes], isa, p, mbuf_its)
#F Does a multibffered, parallel DFT, but not the IxDFT.
doMBufIndeParSimdDftCell_single := function(arg)
#doMBufIndeParSimdWhtCell := function(arg)
    local sizes, opts, tags, dpbench, mbuf_its, name, isa, logn, use_functions, use_openmp,
        use_buffering, interleavedComplex, argrec, simd_opts, transform, p, extra_its;

    sizes := arg[1];
    isa   := arg[2];
    p     := arg[3];
    mbuf_its := arg[4];

    use_functions := false;
    use_openmp := true;
    use_buffering := false;
    interleavedComplex := true; #NOTE: change this?
    simd_opts := rec();

    opts := InitVecLibgenCell(isa, use_functions, use_openmp, use_buffering, cellopts_tmp.sc);
    opts.compileStrategy := IndicesCS2_FMA;
    opts.spus     := p;
    opts.multibuffer_its := mbuf_its;
    opts.codegen  := MBufCodegen;
    opts.unparser := isa.unparser;
    opts.profile  := isa.backendConfig.profile;

    #opts.globalUnrolling := opts.globalUnrolling*p;
    opts.globalUnrolling := 520;

    opts.measSteadyState := true;

    #opts.breakdownRules.GT := [ GT_Base, GT_NthLoop, GT_DFT_CT, GT_CellDMP_base, GT_CellDMP_gen, GT_Cell, GT_Vec_AxI, GT_Vec_IxA_Push, GT_Vec_IxA_L, GT_Vec_L_IxA, GT_MBufCell_spec ];
    opts.breakdownRules.GT := [ GT_Base, GT_NthLoop, GT_DFT_CT, GT_CellDMP_base, GT_CellDMP_gen, GT_Cell, GT_Vec_AxI, GT_Vec_IxA_Push, GT_Vec_IxA_L, GT_Vec_L_IxA];
    opts.breakdownRules.TL := Concatenation(opts.breakdownRules.TL, [  TL_CellDMP ]);

    # Determine best packet size here. Packet size is simply size of kernel

    # Determine the correct # of extra loops here
    # For block size of 16384 bytes, we need (16384/(opts.vector.bits/8))/

    # WHT:
    #transform := k -> GT(WHT(LogInt(k,2)), GTPar, GTPar, [mbuf_its*p*((16384/(opts.vector.isa.bits/8))/k)]).withTags(
    #            Concatenation([ParCell(p, k*((16384/(opts.vector.isa.bits/8))/k)), MBufCell(mbuf_its)], opts.tags));

    # DFT_ic:
    #transform := k -> GT( TRC(DFT(k,1,false)), GTPar, GTPar, [mbuf_its*p]).withTags( Concatenation([ParCell(p, (2*k)), MBufCell(mbuf_its)], opts.tags) );

    # DFT_sc:
    transform := k -> GT( SplitComplexT(DFT(k,1,false)), GTPar, GTPar, [mbuf_its*p]).withTags( Concatenation([ParCell(p, (2*k)), MBufCell(mbuf_its)], opts.tags) );

    opts.benchTransforms := List(sizes, transform);

    PrintLine("ISA: ", isa, ", Multibuf_its: ",mbuf_its,", sizes: ", sizes);
    name := Concat(StringInt(mbuf_its), "mbuf_", StringInt(p), "p_", isa.name, "_sc");

    dpbench := DPBench(rec((name) := opts),
                    rec(timeBaseCases := false, verbosity:=0));
    return dpbench;
end;

#F doMBufIndeParSimdDftCell([sizes], isa, p, mbuf_its)
#F Does just the IxBase (no parallelization, no multibuffering)
doMBufIndeParSimdDftCell_IxDFTbase := function(arg)
#doMBufIndeParSimdWhtCell := function(arg)
    local sizes, opts, tags, dpbench, mbuf_its, name, isa, logn, use_functions, use_openmp,
        use_buffering, interleavedComplex, argrec, simd_opts, transform, p, extra_its;

    sizes := arg[1];
    isa   := arg[2];
    p     := arg[3];
    mbuf_its := arg[4];

    use_functions := false;
    use_openmp := true;
    use_buffering := false;
    interleavedComplex := true; #NOTE: change this?
    simd_opts := rec();

    opts := InitVecLibgenCell(isa, use_functions, use_openmp, use_buffering, cellopts_tmp.sc);
    opts.compileStrategy := IndicesCS2_FMA;
    opts.spus     := p;
    opts.multibuffer_its := mbuf_its;
    opts.codegen  := MBufCodegen;
    opts.unparser := isa.unparser;
    opts.profile  := isa.backendConfig.profile;

    #opts.globalUnrolling := opts.globalUnrolling*p;
    opts.globalUnrolling := 520;

    opts.measSteadyState := true;

    #opts.breakdownRules.GT := [ GT_Base, GT_NthLoop, GT_DFT_CT, GT_CellDMP_base, GT_CellDMP_gen, GT_Cell, GT_Vec_AxI, GT_Vec_IxA_Push, GT_Vec_IxA_L, GT_Vec_L_IxA, GT_MBufCell_spec ];
    opts.breakdownRules.GT := [ GT_Base, GT_NthLoop, GT_DFT_CT, GT_CellDMP_base, GT_CellDMP_gen, GT_Cell, GT_Vec_AxI, GT_Vec_IxA_Push, GT_Vec_IxA_L, GT_Vec_L_IxA ];
    opts.breakdownRules.TL := Concatenation(opts.breakdownRules.TL, [  TL_CellDMP ]);

    # Determine best packet size here. Packet size is simply size of kernel

    # Determine the correct # of extra loops here
    # For block size of 16384 bytes, we need (16384/(opts.vector.bits/8))/

    # WHT:
    #transform := k -> GT(WHT(LogInt(k,2)), GTPar, GTPar, [mbuf_its*p*((16384/(opts.vector.isa.bits/8))/k)]).withTags(
    #            Concatenation([ParCell(p, k*((16384/(opts.vector.isa.bits/8))/k)), MBufCell(mbuf_its)], opts.tags));

    # DFT_ic:
    #transform := k -> TRC(GT(DFT(k,1,false), GTPar, GTPar, [((16384/(opts.vector.isa.bits/8))/(2*k))])).withTags(opts.tags);

    # DFT_sc:
    transform := k -> GT( SplitComplexT(DFT(k,1,false)), GTPar, GTPar, [((16384/(opts.vector.isa.bits/8))/(2*k))] ).withTags(opts.tags);

    opts.benchTransforms := List(sizes, transform);

    PrintLine("ISA: ", isa, ", Multibuf_its: ",mbuf_its,", sizes: ", sizes);
    name := Concat("Ix_", isa.name, "_sc");

    dpbench := DPBench(rec((name) := opts),
                    rec(timeBaseCases := false, verbosity:=0));
    return dpbench;
end;

RecursStep.needInterleavedLeft := self >> self.child(1).needInterleavedLeft();
RecursStep.needInterleavedRight := self >> self.child(1).needInterleavedRight();

RTWrap.needInterleavedRight := self >> false;
RTWrap.needInterleavedLeft := self >> false;

VGath.mkCodelet    := self >> ObjId(self)(self.func.mkCodelet(), self.v);
VGath_sv.mkCodelet := self >> ObjId(self)(self.func.mkCodelet(), self.v, self.sv);
VScat.mkCodelet    := self >> ObjId(self)(self.func.mkCodelet(), self.v);
VScat_sv.mkCodelet := self >> ObjId(self)(self.func.mkCodelet(), self.v, self.sv);
VTensor.mkCodelet  := self >> ObjId(self)(self.child(1).mkCodelet(), self.vlen);

VRC.mkCodelet   := self >> ObjId(self)(self.child(1).mkCodelet(), self.v);
VRCL.mkCodelet  := self >> ObjId(self)(self.child(1).mkCodelet(), self.v);
VRCR.mkCodelet  := self >> ObjId(self)(self.child(1).mkCodelet(), self.v);
VRCLR.mkCodelet := self >> ObjId(self)(self.child(1).mkCodelet(), self.v);

BlockVPerm.mkCodelet := self >> self;
VPerm.mkCodelet      := self >> self;
VPrm_x_I.mkCodelet   := self >> self;

VDiag.mkCodelet     := self >> ObjId(self)(self.element.mkCodelet(), self.v);
VDiag_x_I.mkCodelet := self >> ObjId(self)(self.element.mkCodelet(), self.v);
VRCDiag.mkCodelet   := self >> ObjId(self)(self.element.mkCodelet(), self.v);

VData.signature     := self >> CodeletSignature(self.func);
VData.codeletParams := self >> CodeletParams(self.func);
VData.mkCodelet     := self >> ObjId(self)(MkCodelet(self.func), self.v);
VData.codeletShape  := self >> [ObjId(self), CodeletShape(self.func), self.v];

VDup.signature     := self >> CodeletSignature(self.func);
VDup.codeletParams := self >> CodeletParams(self.func);
VDup.mkCodelet     := self >> ObjId(self)(MkCodelet(self.func), self.v);
VDup.codeletShape  := self >> [ObjId(self), CodeletShape(self.func), self.v];

# NOTE:

# Also children/rChildren is invalid throughout
# children/rChildren inconsistent with constructor

# cutoff_size is eg. 8, then vector length 4 results in VTensor(DFT(2), 4),
# which is size 16, and will be terminated to a Blk(...). How do we prevent this?

# niterate := function(clet_set)
#    s := vrec4(7, 8)[2]; s := prep(s);
#    UniteSet(clet_set, List(clets(s), CodeletName));
#    return clet_set;
# end;
#
# iterate := function(clet_set)
#    s := vrec4(7, 8)[2]; s := prep(s);
#    UniteSet(clet_set, clets(s));
#    return clet_set;
# end;


#
# usage: (!!!Achtung: This will overwrite all manual modifications)
#
# Read("~/Workspace/Chevalley.gap/init.gi"); Read(Filename(home_dir,"load.gi"));
# Read(Filename(e8_dir_char3,"init_char3.gi")); Read(Filename(e8_dir_char3,"gen_files.gi"));
#

for l in labels do
    f:=Filename(e8_dir_char3,Concatenation(l,".gi"));
    #PrintTo(f,l);
    PrintTo(f,"#\n");
    AppendTo(f,"# usage:\n");
    AppendTo(f,"# Read(\"~/Workspace/Chevalley.gap/init.gi\"); Read(Filename(home_dir,\"load.gi\")); ");
    AppendTo(f,"Read(Filename(e8_dir_char3,\"init_char3.gi\")); Read(Filename(e8_dir_char3,\"");
    AppendTo(f,Concatenation(l,".gi"));
    AppendTo(f,"\"));\n");
    AppendTo(f,"#\n");
    AppendTo(f,"# Read(Filename(e8_dir_char3,\"init_char3.gi\")); Read(Filename(e8_dir_char3,\"");
    AppendTo(f,Concatenation(l,".gi"));
    AppendTo(f,"\"));\n\n");

    AppendTo(f,"label:=\"");
    AppendTo(f,l);
    AppendTo(f,"\";\n");

    AppendTo(f,"orb_nr:=Position(labels,label);\n\n");
    AppendTo(f,"orb:=AllClasses(orbs)[orb_nr];\n");
    AppendTo(f,"info:=handleClassShort(orb);\n\n");

    AppendTo(f,"Print(\"Consider the class \",Label(orb),\" in characteristic \",Characteristic(ring(sys)),\":\\n\");\n");
    AppendTo(f,"Print(\"\\tBorel representative \\n\\t\",coefficients(BorelRep(orb)),\"\\n\");\n");
    AppendTo(f,"Print(\"\\tconnected C_U(u) \\n\\t\",coefficients(info[1]),\"\\n\");\n");
    AppendTo(f,"Print(\"\\tconnected C_U(u) in Levi \\n\\t\",coefficients(info[2]),\"\\n\");\n");
    AppendTo(f,"Print(\"\\tconnected double C_U(u) \\n\\t\",coefficients(info[3]),\"\\n\");\n");
od;


Concatenation(ListWithIdenticalEntries(10, "BOB "));
"BOB BOB BOB BOB BOB BOB BOB BOB BOB BOB "