text: #pragma once
  
  #include <hls_stream.h>
  #include "ap_int.h"
  #include <iostream>
  #include <string>
  
  #define CASSERT_DATAFLOW(x) ;
  
  using namespace hls;
  using namespace std;
  
  // templated struct to carry multi-channel pixel data
  template <unsigned int NumChannels, unsigned int DataWidth>
  struct MultiChanData {
  	ap_uint<DataWidth> data[NumChannels];
  };
  
  // essentially a data cast; convert MultiChanData into a wide ap_uint
  template <unsigned int NumChannels, unsigned int DataWidth>
  void FlattenMultiChanData(
  	stream<MultiChanData<NumChannels, DataWidth> > & in,
  	stream<ap_uint<NumChannels*DataWidth> > & out,
  	const unsigned int numReps
  ) {
  	for(unsigned int r = 0; r < numReps; r++) {
  #pragma HLS PIPELINE II=1
  		MultiChanData<NumChannels, DataWidth> e = in.read();
  		ap_uint<NumChannels*DataWidth> o = 0;
  		for(unsigned int v = 0; v < NumChannels; v++) {
  #pragma HLS UNROLL
  			o(DataWidth*(v+1)-1, DataWidth*v) = e.data[v];
  		}
  		out.write(o);
  	}
  }
  
  // reverse of FlattenMultiChanData: convert wide ap_uint to MultiChanData
  template <unsigned int NumChannels, unsigned int DataWidth>
  void PackMultiChanData(
  	stream<ap_uint<NumChannels*DataWidth> > & in,
  	stream<MultiChanData<NumChannels, DataWidth> > & out,
  	const unsigned int numReps
  ) {
  	for(unsigned int r = 0; r < numReps; r++) {
  #pragma HLS PIPELINE II=1
  		ap_uint<NumChannels*DataWidth> e = in.read();
  		MultiChanData<NumChannels, DataWidth> o;
  		for(unsigned int v = 0; v < NumChannels; v++) {
  #pragma HLS UNROLL
  			o.data[v] = e(DataWidth*(v+1)-1, DataWidth*v);
  		}
  		out.write(o);
  	}
  }
  
  // datawidth conversion for several parallel channels carried over a single
  // channel
  template<unsigned int InWidth,		// width of input stream
  		unsigned int OutWidth,		// width of output stream
  		unsigned int NumInWords,		// number of input words to process
  		unsigned int NumVecs
  >
  void MultiChanDataWidthConverter_Batch(
  	stream<MultiChanData<NumVecs, InWidth> > & in,
  	stream<MultiChanData<NumVecs, OutWidth> > & out,
  	const unsigned int numReps) {
  	if (InWidth > OutWidth) {
  		// emit multiple output words per input word read
  		CASSERT_DATAFLOW(InWidth % OutWidth == 0);
  		const unsigned int outPerIn = InWidth / OutWidth;
  		const unsigned int totalIters = NumInWords * outPerIn * numReps;
  		unsigned int o = 0;
  		MultiChanData<NumVecs, InWidth> ei;
  		for (unsigned int t = 0; t < totalIters; t++) {
  #pragma HLS PIPELINE II=1
  			// read new input word if current out count is zero
  			if (o == 0)
  				ei = in.read();
  			// pick output word from the rightmost position
  			MultiChanData<NumVecs, OutWidth> eo;
  			for(unsigned int v = 0; v < NumVecs; v++) {
  #pragma HLS UNROLL
  				eo.data[v] = (ei.data[v])(OutWidth - 1, 0);
  				// shift input to get new output word for next iteration
  				ei.data[v] = ei.data[v] >> OutWidth;
  			}
  			out.write(eo);
  			// increment written output count
  			o++;
  			// wraparound indices to recreate the nested loop structure
  			if (o == outPerIn) {
  				o = 0;
  			}
  		}
  	} else if (InWidth == OutWidth) {
  		// straight-through copy
  		for (unsigned int i = 0; i < NumInWords * numReps; i++) {
  #pragma HLS PIPELINE II=1
  			MultiChanData<NumVecs, InWidth> e = in.read();
  			MultiChanData<NumVecs, OutWidth> eo;
  			// we don't support typecasting between templated types, so explicitly
  			// transfer vector-by-vector here
  			for(unsigned int v=0; v < NumVecs; v++) {
  #pragma HLS UNROLL
  				eo.data[v] = e.data[v];
  			}
  			out.write(eo);
  		}
  	} else { // InWidth < OutWidth
  		// read multiple input words per output word emitted
  		CASSERT_DATAFLOW(OutWidth % InWidth == 0);
  		const unsigned int inPerOut = OutWidth / InWidth;
  		const unsigned int totalIters = NumInWords * numReps;
  		unsigned int i = 0;
  		MultiChanData<NumVecs, OutWidth> eo;
  		for (unsigned int t = 0; t < totalIters; t++) {
  #pragma HLS PIPELINE II=1
  			// read input and shift into output buffer
  			MultiChanData<NumVecs, InWidth> ei = in.read();
  			for(unsigned int v = 0; v < NumVecs; v++) {
  #pragma HLS UNROLL
  				eo.data[v] = eo.data[v] >> InWidth;
  				(eo.data[v])(OutWidth - 1, OutWidth - InWidth) = ei.data[v];
  			}
  			// increment read input count
  			i++;
  			// wraparound logic to recreate nested loop functionality
  			if (i == inPerOut) {
  				i = 0;
  				out.write(eo);
  			}
  		}
  	}
  }