# RPC(大点数脉冲压缩算法)

## 1. 概述
本文将以基于 FT04-DSP 的 rpc 算法的移植为例，介绍 AI+DSP 应用开发的开发流程。

算法简介：

大点数脉冲压缩（Pulse Compression）是雷达信号处理中的关键技术，该算法通过匹配滤波将宽脉冲信号压缩成窄脉冲，能够提高雷达的距离分辨率和检测能力，广泛用于气象雷达、合成孔径雷达、医学成像、地震勘探等需要处理超大点数信号的领域。


## 2. FT04 c实现
主要代码如下
```
void main(void)
{
	int ID = getcoreid();
	int iCntI, iCntJ;			/**循环计数*/
	int mask = 15;				// 核掩码，低四位有效,0表示核0执行，3表示核0、核1执行，mask表示四个核执行
	int iHeaderCPLX = 2048 / 8; /**回波协议*/
	int iReadLenPerCore, iReadStPerCore, iWriteLenPerCore, iWriteStPerCore;
	int iFillZeroLen;
	int iPingPongFlag = 0;

	int iChannelNum = 5;				   /**总波束个数,也就是通道个数*/
	int iImageValidRLen = 43456;		   /**距离向有效点数*/
	int iRSampleLen = 43456;			   /**距离向采样长度*/
	int iASampleLen = 17;				   /**方位向收数点数*/
	int iRpcLength = 65536;				   /**脉压结果长度*/
	int iTpADNum;						   /**脉冲宽度Tp内的采样点数*/
	float fFs = 25;						   /**采样率MHz*/
	float fTp = 210;					   /**脉冲宽度us*/
	float fKr = 20.2775 / 210;			   /**调频斜率*/
	float fLosVel = 1.485044537103641e+03; /**平台和目标沿雷达视线的速度分量，单位m/s*/
	float fPRT = 2.38092e+03;			   /**发射脉冲重复间隔，单位us*/
	float fDr = 5.99584916;				   /**距离单元宽度*/
	float fTemp0;
	float r, theta;

	iTpADNum = (int)(fFs * fTp + 0.5); /**脉冲宽度Tp内的采样点数*/
	iReadLenPerCore = iRSampleLen >> 2;
	iReadStPerCore = iReadLenPerCore * ID;
	iWriteLenPerCore = iImageValidRLen >> 2;
	iWriteStPerCore = iWriteLenPerCore * ID;
	iFillZeroLen = iRpcLength - iRSampleLen;

	CPLX *pcDDRfftTwid = (CPLX *)0x140000000;  /**旋转因子 需要多少地址空间？*/
	CPLX *pcDDRifftTwid = (CPLX *)0x180000000; /**旋转因子 需要多少地址空间？*/
	float *pfGSMAngle = (float *)0x70320000;
	CPLX *pcGSMData1 = (CPLX *)(0x70330000);
	CPLX *pcGSMData2 = (CPLX *)(0x703b0000);
	CPLX *pcGSMfftBuf = (CPLX *)(0x70200000); /**算子缓存地址，所需空间为数据量的2倍，计算后释放*/
	CPLX *pcGSMBuf = (CPLX *)(0x703a0000);	  /**缓存地址*/
	CPLX *pcGSMRefBuf = (CPLX *)(0x70760000); /**运动补偿系数（方位线）缓存地址*/
	CPLX *pcGSMPcRef = (CPLX *)(0x70430000);  /**参考函数*/
	CPLX *pcGSMRcRef = (CPLX *)(0x704b0000);  /**运动补偿系数*/
	CPLX *pcGSMRef = (CPLX *)(0x70730000);	  /**运动补偿系数*/
	CPLX *pcGSMDataIn[2];
	CPLX *pcGSMDataOut[2];
	CPLX *pcDDRDataIn = (CPLX *)(0x80000000);	/**原始回波存储地址*/
	CPLX *pcDDRDataOut = (CPLX *)(0x100000000); /**结果存储地址*/
	int *piGSMBuf = (int *)(0x70780000);
	pcGSMDataIn[0] = (CPLX *)(0x70530000);
	pcGSMDataIn[1] = (CPLX *)(0x705b0000);
	pcGSMDataOut[0] = (CPLX *)(0x70630000);
	pcGSMDataOut[1] = (CPLX *)(0x706b0000);

	unsigned long long cnt, cnt1, Use_Time, cnt2, cnt3, Use_Time1, cnt4, cnt5, Use_Time2, cnt6, cnt7, cnt8, Use_Time3, Use_Time4, Use_Time5, Use_Time6, Use_Time7;
	setTimerPeriod(0xffffffff); // 配置定时器周期0xffffffff
	configTimer(0x80);			// 持续性计数，定时器当计数等于周期后一个时钟周期，计数重置为0再重新计数
	asm("smfence\t");
	timerStart(0);
	asm("smfence\t");

	sys_bar(0, 4);
	cnt = getTimerCount();
	int i;
	float *p;

	/**旋转因子初始化*/
	gk_getw_core((CPLX *)pcDDRfftTwid, iRpcLength, 0, mask);
	sys_bar(0, 4);
	gk_getw_core((CPLX *)pcDDRifftTwid, iRpcLength, 1, mask);
	sys_bar(0, 4);
	/**计算脉压系数*/
	get_angle_f_core(fFs, fKr, iTpADNum, pfGSMAngle, mask);
	sys_bar(0, 4);
	gk_cos_sin_f_core(pfGSMAngle, iTpADNum, pcGSMData1, mask);
	sys_bar(0, 4);
	gk_cvmemfill_f_core(0, 0, iRpcLength - iTpADNum, pcGSMData1 + iTpADNum, mask);
	sys_bar(1, 4);
	gk_getw_core((CPLX *)pcDDRfftTwid, iRpcLength, 0, mask);
	sys_bar(0, 4);
	gk_FFT_core((CPLX *)pcGSMData1, (CPLX *)pcDDRfftTwid, (CPLX *)pcGSMData2, iRpcLength, (CPLX *)pcGSMfftBuf, mask); /**FFT*/
	sys_bar(12, 4);
	gk_bitrev_core((float *)pcGSMData2, iRpcLength, (float *)pcGSMPcRef, (float *)pcGSMfftBuf, mask); /**反序 输出为pcGSMRef*/
	sys_bar(3, 4);
	gk_cvconj_f_core((float *)pcGSMPcRef, iRpcLength, (float *)pcGSMPcRef, mask);
	sys_bar(4, 4);
	gk_cvmemfill_f_core(1, 0, iRpcLength, pcGSMRcRef, mask);
	sys_bar(0, 4);

	/**运动补偿初始化*/
	gk_cvmemfill_f_core(0, 1, iASampleLen, pcGSMRefBuf, mask);
	sys_bar(0, 4);
	fTemp0 = -fLosVel * fPRT * 1e-6 / fDr / iRpcLength;									/**(velLos*prt*(noPulse-1)+rangeComp/2)/widthRange/numFFT*/
	gk_tmuls_f_4core(-(iASampleLen - 1) / 2.0, fTemp0, iASampleLen, (float *)pcGSMBuf); /**rcCoef=exp(1j * 2 * pi * (-losVel*pri*((0:(prNum-1))-(prNum-1)/2)/disWidth)/range_fftNum);*/
	sys_bar(0, 4);
	gk_vej_f_4core((float *)pcGSMBuf, pcGSMBuf, iASampleLen, pcGSMRefBuf);
	sys_bar(0, 4);

	/**这一块因为没有直接求int型向量的函数，所以用了两步*/
	gk_tmuls_f_4core(-iRpcLength / 2, 1, iRpcLength, pcGSMRcRef); /**生成距离向向量*/
	sys_bar(0, 4);
	gk_vmuls_i_core(pcGSMRcRef, 1, iRpcLength, piGSMBuf, 15); /**需要int型(做转换)*/
	sys_bar(0, 4);
	/**读取单条脉冲单个通道的数据*/
	pcDDRDataIn = pcDDRDataIn + iHeaderCPLX;
	DMA_Config(6, pcDDRDataIn + iReadStPerCore, pcGSMDataIn[iPingPongFlag] + iReadStPerCore, 0, 0, iReadLenPerCore, 8);
	DMA_start(0x40);
	DMA_check(0x40);
	sys_bar(0, 4);

	pcDDRDataIn = pcDDRDataIn + iRpcLength; // 源数据通道间隔为65536
	cnt1 = getTimerCount();
	Use_Time = (cnt1 - cnt) * 20;
	for (iCntJ = 0; iCntJ < iASampleLen; iCntJ++) /***/
	{
		Use_Time1 = 0;
		Use_Time2 = 0;
		sys_bar(0, 4);
		cnt2 = getTimerCount();

		r = sqrt(pcGSMRefBuf[iCntJ].re * pcGSMRefBuf[iCntJ].re + pcGSMRefBuf[iCntJ].im * pcGSMRefBuf[iCntJ].im);
		theta = atan(pcGSMRefBuf[iCntJ].im / pcGSMRefBuf[iCntJ].re);
		gk_cvpown_f_core(r, theta, iRpcLength, piGSMBuf, pcGSMData2, mask);
		sys_bar(0, 4);

		DMA_Config(0, pcGSMData2 + iRpcLength / 8 * ID, pcGSMRcRef + iRpcLength / 2 + iRpcLength / 8 * ID, 0, 0, 0x8000, 2); /**做一下fftshift*/
		DMA_Config(1, pcGSMData2 + iRpcLength / 2 + iRpcLength / 8 * ID, pcGSMRcRef + iRpcLength / 8 * ID, 0, 0, 0x8000, 2);
		DMA_start(3);
		DMA_check(3);
		sys_bar(0, 4);

		cnt3 = getTimerCount();
		Use_Time1 = (cnt3 - cnt2) * 20 + Use_Time1;
		for (iCntI = 0; iCntI < iChannelNum; iCntI++) /***/
		{
			if (iCntI == (iChannelNum - 1))
			{
				pcDDRDataIn = pcDDRDataIn + iHeaderCPLX;
			}
			sys_bar(0, 4);
			iPingPongFlag = ((iCntJ * iChannelNum) + iCntI + 1) % 2;
			/**读取单条脉冲单个通道的数据*/
			DMA_Config(6, pcDDRDataIn + iReadStPerCore, pcGSMDataIn[iPingPongFlag] + iReadStPerCore, 0, 0, iReadLenPerCore, 8);
			DMA_start(0x40);
			iPingPongFlag = ((iCntJ * iChannelNum) + iCntI) % 2;
			gk_cvmemfill_f_core(0, 0, iFillZeroLen, pcGSMDataIn[iPingPongFlag] + iRSampleLen, mask);
			sys_bar(0, 4);
			gk_FFT_core((CPLX *)pcGSMDataIn[iPingPongFlag], (CPLX *)pcDDRfftTwid, (CPLX *)pcGSMData2, iRpcLength, (CPLX *)pcGSMfftBuf, mask); /**FFT*/
			sys_bar(0, 4);
			gk_bitrev_core((float *)pcGSMData2, iRpcLength, (float *)pcGSMData1, (float *)pcGSMfftBuf, mask); /**反序 输出为pcGSMRef*/
			sys_bar(0, 4);

			gk_cvmulcv_f_core(pcGSMData1, pcGSMRcRef, iRpcLength, pcGSMData1, mask); /**乘运动补偿*/
			sys_bar(0, 4);
			gk_cvmulcv_f_core(pcGSMData1, pcGSMPcRef, iRpcLength, pcGSMData1, mask); /**乘脉压系数*/
			sys_bar(0, 4);

			gk_IFFT_core((CPLX *)pcGSMData1, (CPLX *)pcDDRfftTwid, (CPLX *)pcGSMData2, iRpcLength, (CPLX *)pcGSMfftBuf, mask); /**FFT*/
			sys_bar(0, 4);
			gk_bitrev_core((float *)pcGSMData2, iRpcLength, (float *)pcGSMDataOut[iPingPongFlag], (float *)pcGSMfftBuf, mask); /**反序 输出为pcGSMRef*/
			sys_bar(0, 4);
			/**存储单条脉冲单个通道的数据*/
			if (((iCntJ * iChannelNum) + iCntI) > 0)
			{
				DMA_check(0x80);
			}
			DMA_Config(7, pcGSMDataOut[iPingPongFlag] + iWriteStPerCore, pcDDRDataOut + iWriteStPerCore, 0, 0, iWriteLenPerCore, 8);
			DMA_start(0x80);
			sys_bar(0, 4);
			pcDDRDataIn = pcDDRDataIn + iRpcLength; // 源数据通道间隔为65536
			pcDDRDataOut = pcDDRDataOut + iImageValidRLen;
			DMA_check(0x40);
			cnt4 = getTimerCount();
			Use_Time2 = (cnt4 - cnt3) * 20 + Use_Time2;
		}
	}
	DMA_check(0x80);
	if (ID == 0)
	{
		printf("ok\n\n");
	}
}
```

本代码用 FT-IDE 编译，能在ft04 板卡上加载、运行。

部分接口介绍：
- `sys_bar` 为栅栏接口，用于多核同步：只有当所有核都完成当前计算任务后，才会开启下一个计算任务。
- `getTimerCount` 为时钟接口，它返回当前时钟的节拍数：用于性能统计。
- `DMA_Config` 为DMA传输接口，用于提升数据的搬移的速度。
- `gk_` 开头的函数为具体的 dsp 算子，算子的功能可通过查阅相关文档、头文件、代码可知，本文就不逐一介绍。

## 3. 开发流程

### 3.1 数据导入
c实现中，数据读取是通过IDE导入的。在python中使用 `open` 函数打开并读取原始的二进制数据文件，并把其转成`complex64`类型的复数tensor。

示例代码：
```python
with open("data_43456_17_rpcdata_chnum5_RPCDataIn.bin", 'rb') as f:
    raw_data = f.read()
echo = np.frombuffer(raw_data, dtype=np.complex64)
echo = Tensor(echo)
```

### 3.2 常量处理
移植时的主要工作是：将 c 算子的计算逻辑转换为 MindSpore Signal+ 的 API 调用。

整体分析可知，从数据导入到内层循环的fft之前的步骤，主要是模型常量的生成，另外乘法算子
```
gk_cvmulcv_f_core(pcGSMData1, pcGSMRcRef, iRpcLength, pcGSMData1, mask); /**乘运动补偿*/
sys_bar(0, 4);
gk_cvmulcv_f_core(pcGSMData1, pcGSMPcRef, iRpcLength, pcGSMData1, mask); /**乘脉压系数*/
```
中的 `pcGSMRcRef`、`pcGSMPcRef` 均为长度为 65536 的一维数据，其生成只依赖于给定的参数，不依赖于原始输入，为模型中的常量。
为避免额外的开销，可将其相乘后放在`__init__`中。

示例代码：
```python
class rpc(nn.Cell):
    def __init__(self):
        super(rpc, self).__init__()
        fFs = 25
        fTp = 210
        fKr = 20.2775 / 210
        iTpADNum = (int)(fFs * fTp + 0.5)
        iRpcLength = 65536
        ...
```

### 3.3 核心计算
循环部分是算法的核心，其主要流程是在循环中对一维的数据做fft、乘法和ifft运算。

可使用下列优化方法来提升性能：
- 维度提升：因为框架中算子：fft、乘法和ifft均支持二维输入，所以把核心中的一维算子、数据改写成二维的，这样可节省框架的循环调用的开销。
```
gk_FFT_core((CPLX *)pcGSMDataIn[iPingPongFlag], (CPLX *)pcDDRfftTwid, (CPLX *)pcGSMData2, iRpcLength, (CPLX *)pcGSMfftBuf, mask); /**FFT*/
```
中的 `pcGSMDataIn` 为长度为 65536 的一维输入数据，可把其当成一个二维输入 tensor 中的一行。
```
gk_bitrev_core((float *)pcGSMData2, iRpcLength, (float *)pcGSMDataOut[iPingPongFlag], (float *)pcGSMfftBuf, mask); /**反序 输出为pcGSMRef*/
```
中的 `pcGSMDataOut` 为长度为 65536 的一维输出数据，可把其当成一个二维输出 tensor 中的一行。

- 空间分配：在运行前为框架算子缓存区分配空间，并将存储类型设置为 `smc`，这样能提升算子的访存速度，对于此应用，使用`smc`前后运行时间分别为：88ms、70ms，性能提升了 18 ms。

示例代码：
```python
    def construct(self, t):
        t = t.reshape(self.iASampleLen, t.size//self.iASampleLen)                 
        t = self.slice(t, [0, self.iHeaderCPLX], [self.iASampleLen, self.iRpcLength*self.iChannelNum])
        t = t.reshape(self.line, self.iRpcLength)
        t = self.slice(t, [0,0], [self.line, self.iRSampleLen])
        t = ops.cat((t, self.zero), 1)
        t = self.fft(t)
        t = self.mul(t, self.pcGSMRcRef)
        t = self.ifft(t)    
        t = self.slice(t, [0,0], [self.line, self.iRSampleLen])
        t = t.reshape(t.size)
        return t  
```


### python完整代码示例：
```python
import numpy as np
import mindspore as ms
import mindradar as mr
from mindspore import Tensor, ops, nn
import data_compare as compare

class rpc(nn.Cell):
    def __init__(self):
        super(rpc, self).__init__()
        fFs = 25
        fTp = 210
        fKr = 20.2775 / 210
        iTpADNum = (int)(fFs * fTp + 0.5)
        iRpcLength = 65536
        iRSampleLen = 43456
        iASampleLen = 17
        iChannelNum = 5        
        f_n = iRpcLength - iTpADNum
        pfGSMAngle = np.zeros(iTpADNum, dtype=np.float32)    
        for i in range(iTpADNum):
            tr = (i - iTpADNum / 2.0) / fFs
            pfGSMAngle[i] = np.pi * fKr * tr * tr
        pcGSMData1 = pfGSMAngle.astype(np.complex64)
        pcGSMData1.real = np.cos(pfGSMAngle)
        pcGSMData1.imag = np.sin(pfGSMAngle)
        zero = np.zeros(f_n, dtype=np.complex64)    
        pcGSMData1 = np.append(pcGSMData1, zero, axis=0)
        pcGSMPcRef = np.fft.fft(pcGSMData1).astype(np.complex64)
        pcGSMPcRef = np.conj(pcGSMPcRef)

        tmuls_0 = -(iASampleLen - 1) / 2.0
        fLosVel = 1.485044537103641e+03
        fPRT = 2.38092e+03
        fDr = 5.99584916
        fTemp0 = -fLosVel * fPRT * 1e-6 / fDr / iRpcLength
        pcGSMBuf = np.zeros(iASampleLen, dtype=np.float32)    
        for i in range(iASampleLen):
            pcGSMBuf[i] = (tmuls_0 + i) * fTemp0
        pcGSMRefBuf = pcGSMBuf.astype(np.complex64)
        pcGSMRefBuf.real = np.cos(pcGSMBuf * 2 * np.pi)
        pcGSMRefBuf.imag = np.sin(pcGSMBuf * 2 * np.pi)
        piGSMBuf = np.linspace(-iRpcLength / 2, iRpcLength / 2 - 1, iRpcLength).astype(np.int32)
        line = iASampleLen * iChannelNum
        pcGSMData2 = np.zeros(line * iRpcLength, dtype=np.complex64).reshape(line, iRpcLength)
        for j in range(iASampleLen):
            for i in range(iChannelNum):
                pcGSMData2[j * iChannelNum + i] = np.power(pcGSMRefBuf[j], piGSMBuf)            
        pcGSMRcRef = np.fft.fftshift(pcGSMData2, 1)
        pcGSMRcRef = pcGSMRcRef * pcGSMPcRef
        
        self.pcGSMRcRef = Tensor(pcGSMRcRef)

        self.iASampleLen = iASampleLen
        zero = np.zeros(line*(iRpcLength-iRSampleLen), dtype=np.complex64).reshape(line, iRpcLength-iRSampleLen)
        self.zero = Tensor(zero)
        self.iRpcLength = iRpcLength
        self.iRSampleLen = iRSampleLen
        self.iChannelNum = iChannelNum
        self.line = line
        self.iHeaderCPLX = 256
        self.fft = mr.FFT()
        self.ifft = mr.IFFT()
        self.mul = ops.Mul()
        self.slice = ops.Slice()

    def construct(self, t):
        t = t.reshape(self.iASampleLen, t.size//self.iASampleLen)                 
        t = self.slice(t, [0, self.iHeaderCPLX], [self.iASampleLen, self.iRpcLength*self.iChannelNum])
        t = t.reshape(self.line, self.iRpcLength)
        t = self.slice(t, [0,0], [self.line, self.iRSampleLen])
        t = ops.cat((t, self.zero), 1)
        t = self.fft(t)
        t = self.mul(t, self.pcGSMRcRef)
        t = self.ifft(t)    
        t = self.slice(t, [0,0], [self.line, self.iRSampleLen])
        t = t.reshape(t.size)
        return t  


with open("data_43456_17_rpcdata_chnum5_RPCDataIn.bin", 'rb') as f:
    raw_data = f.read()
echo = np.frombuffer(raw_data, dtype=np.complex64)
echo = Tensor(echo)
fun = rpc()
out = fun(echo)
ms.export(fun, echo, file_name="rpc", file_format="MINDIR")
print('out', out.shape)
print('out', out)

out = out.asnumpy()
# compare.data_compare(out, "out_43456_5_17.bin")
compare.data_compare(out, "rpc_model_out.bin")
```


## 4. 转换模型
在上述代码中，我们已经使用了 `export` 方法导出了`rpc.mindir`模型。 要将该模型部署到 FT04 平台，需要将 `mindir` 格式转换成 mindspore lite 的 `ms` 格式。
现使用 MindSpore 的转换工具：`converter_lite` 进行转换。

转换命令如下：
```
./converter_lite --fmk=MINDIR --modelFile=/mnt/hgfs/D/work/mind-radar/examples/rpc/rpc.mindir --outputFile=/mnt/hgfs/D/work/mind-radar/examples/rpc/rpc
```

转换完成后，生成模型`rpc.ms` 后，可使用可视化工具：`netron` 打开模型，查看模型结构。 

模型结构如图所示：
 <div align="center">
   <img src="../../_static/rpc_model.png" width="50%">
 </div>

## 5. 端侧部署、运行
打开 YHFT-IDE ，新建工程。输入工程名、路径，工程类型选择 `Heterogeneous` ， 输入交叉编译工具路径，然后点确定。会生成一个异构模板工程。

通过修改 `data_handler.cc` 文件中的函数来调整模型的输入数据，输入改成原始数据文件路径。
在 `main` 函数设置运行后端；修改 `CMakeLists.txt` 文件， 最后编译该工程，编译成功后将 build 文件夹下的可执行文件和模型 `rpc.ms` 一起拷贝到 FT04 中。

在FT04上运行可执行文件，观察输出结果。与预期结果对比，验证模型是否正确。

执行命令如下：
```
./mindspore_rpc rpc.ms 
```

程序在执行时会打印部分输入输出数据，以及各部分的运行时间，并生成模型输出的数据文件`rpc_model_out.bin`，用于后续的验证、分析
。实测该模型的运行时间为 70 ms。 

## 6. 测试
结果比较、误差分析。

将数据文件`rpc_model_out.bin` 拷出来, 同标准结果`out_43456_5_17.bin` 进行比较。
运行结果比较的 python 代码。

输出结果如下：
 <div align="center">
   <img src="../../_static/rpc_result.png" width="50%">
 </div>





