绪
这是自己第一次写文章,可能存在一定的错误,还望大家海涵。并且由于时间精力问题,没有补充完整。完整的代码已经上传到github中,链接:https://github.com/awdsn/LDPC_MS_decode。
一、译码算法
算法打算采用MS译码算法,更好的话可以改进算法如NMS算法,由于在数据位宽降低的情况下NMS算法译码效果反而不如MS算法译码效果,故自己没有选用。
二、C++代码
1.初始
下面代码将测试模块与译码模块放在了一起;并且按照译码算法中的步骤分别设置了以下子函数:校验节点更新,变量节点更新,校验节点和值计算,校验。
///初始化
void initialize()//计算qji_0
{
int a, b;
bool c_in[M];
///读取文本到数组
ifstream infile; //定义读取文件流,相对于程序来说是in
infile.open("接收到信号y-test.txt");//打开文件//接收到信号y//编码后向量x
for (b = 0; b < M; b++) {
infile >> c_in[b];
for (a = 0; a < N; a++) {
if (c_in[b])
q[0][a][b] = 1;//ci=1
else
q[0][a][b] = -1;//ci=0
}
}
infile.close();//读取完成之后关闭文件
for (b = 0; b < M; b++) {//直接将读取的文件做输出校验
c[b] = c_in[b];
}
}
void check_matrix_read()//将校验矩阵读入数组
{
int a, b;
ifstream infile; //定义读取文件流,相对于程序来说是in
infile.open("校验矩阵H.txt");//打开文件
for (a = 0; a < N; a++) {
for (b = 0; b < M; b++) {
infile >> H[a][b];//a行b列
}
}
infile.close();//读取完成之后关闭文件
}
void check_note_update(int diedai)//校验节点更新;计算出rji的值
{
int i, a, b, a0, b0, i_temp;
double temp0 = 1;
double min_temp = 40;//将最小值缓存
for (b = 0; b < M; b++) {
for (a = 0; a < N; a++) {
if (H[a][b]) {
a0 = a;//行数------j
b0 = b;//列数-------i
for (i = 0; i < M; i++) {
if (H[a0][i]) {
i_temp = i;
if (i_temp != b0) {//i_temp=R(j)\i; 变量节点集合
min_temp = min(min_temp, fabs(q[diedai - 1][a0][i_temp]));
temp0 = temp0 * sign(q[diedai - 1][a0][i_temp]);///
}
}
}
r[diedai][a0][b] = temp0 * min_temp;///?????????????
temp0 = 1;//计算完依次的rji之后将temp_0值恢复,准备计算下一次
}
}
}
}
void variable_note_update(int diedai) {//变量节点更新
int j, a, b, a0, b0, j_temp;
double temp1 = 0;//rki和值缓存
for (a = 0; a < N; a++) {
for (b = 0; b < M; b++) {
if (H[a][b]) {
a0 = a;//行数------j
b0 = b;//列数-------i
for (j = 0; j < N; j++) {
if (H[j][b0]) {//j_temp=C(i)\j
j_temp = j;
if (j_temp != a0) {
temp1 = temp1 + r[diedai][j_temp][b0];
}
}
}
q[diedai ][a][b] = q[0][0][b] + temp1;
temp1 = 0;//恢复原样
}
}
}
}
void variable_note_panjue(int diedai) {//变量节点后验概率以及判决
int i, j;
double temp2 = 0;
for (i = 0; i < M; i++) {
for (j = 0; j < N; j++) {
if (H[j][i]) {
temp2 = temp2 + r[diedai][j][i];
}
}
qi[diedai][i] = q[0][0][i] + temp2;
temp2 = 0;
if (qi[diedai][i] > 0)
c[i] = 1.;
else if (qi[diedai][i] < 0)
c[i] = 0;
else
c[i] = 0;//
}
}
int check_formula(int diedai) {//根据校验矩阵计算是否译码完成
int i, j;
int temp = 0;
int s_sum = 0;//s_sum负责记录满足条件,即s[i]=0的个数
for (j = 0; j < N; j++) {//N个校验方程
for (i = 0; i < M; i++) {
temp = temp + c[i] * H[j][i];
}
s[j] = temp;
if (s[j]%2 == 0) {
s_sum = s_sum + 1;
}
temp = 0;
}
cout << "第" << diedai << "次: 满足条件的s数目为" << s_sum << endl;//负责调试,确实功能是否存在错误
/// 验证
int fanhui;//暂存值,负责确定返回值是什么
if (s_sum == N)
fanhui = 1;//满足校验矩阵,结束迭代
else
fanhui = 0;
return fanhui;
s_sum = 0;
}
int main() {
int diedai = 0;//迭代次数,即第几次迭代
int result, b, i;
float error_num = 0;
float error_rate ;
//读入x
ifstream infile; //定义读取文件流,相对于程序来说是in
infile.open("编码后向量x.txt");
for (b = 0;b < M; b++) {
infile >> x[b];
//cout << x[M] << endl;
}
infile.close();//读取完成之后关闭文件
///*************************************译码******************************************//
initialize();///初始化,计算q0_ij,即pi
check_matrix_read();//将校验矩阵读入数组
while (diedai <= diedai_max) {
result = check_formula(diedai);
if (result) {
//cout << "最终迭代次数为: " << diedai << endl;//确定第几次迭代时结束运行
diedai = diedai_max + 1;
}
else {
diedai = diedai + 1;
}
check_note_update(diedai);
variable_note_update(diedai);
variable_note_panjue(diedai);
}
//****************************************测试模块:验证输出******************************************//
cout << setw(5) << "M" << setw(10) << "x[M]" << setw(10) << "c[N]" << endl;
for (i = 0; i < M; i++) {
//cout << setw(5) << i << setw(10) << x[i] << setw(10) << c[i] << endl;
if (x[i] != c[i]) {
error_num = error_num + 1;
}
}
error_rate = error_num / M;
//cout.setf(ios::fixed);
cout << "最终的误比特率为" << error_num << endl;
return 0;
}
2.改进后
将上述代码进行初步改进,如改变读取数据的方式;优化存储中间变量的数组;为了减少整个译码模块执行所需的时间,将一些可以并行执行的for循环放在一个函数中,而不是按照执行的功能放在不同的函数中;此外,对顶层函数中调用各子函数的顺序进行一定的改变,进一步减少译码时间。最终各部分的代码如下:
void check_note_update(int j ,bool H[N][M], data_zj q[M],data_zj r[N][M],data_zj r_copy[M])//校验节点更新;计算出rji的值
{
int i, b;
int i1_temp = 0;
ap_int<2> sign1_ji=1;
ap_int<2> sign1[M] ; ///变量节点矩阵中数值的符号
data_zj min1_temp = fabs(q[0]);//将最小值缓存
data_zj min2_temp = fabs(q[0]);//将次小值缓存
for (i = 0; i < M; i++) {
i1_temp = 0;*/
if (H[j][i]) {
if (min2_temp >= fabs(q[i])) {
min2_temp = fabs(q[i]);
if (min2_temp <= min1_temp) {
data_zj temp = min2_temp;
min2_temp = min1_temp;
min1_temp = temp;
i1_temp = i;
}
}
}
}
for (i = 0; i < M; i++) {//计算符号
if (H[j][i]) {
sign1_ji = sign1_ji * sign(q[i]);
}
}
for (i = 0; i < M; i++) {//计算符号
if (H[j][i]) {
if (sign(q[i]) == sign1_ji)
sign1[i] = 1;
else
sign1[i] = -1;
}
}
LOOP_check_note_update:
//#pragma HLS dependence variable=r inter false
for (i = 0; i < M; i++) {
#pragma HLS pipeline rewind
r_copy[i]=r[j+1][i];
if (H[j][i]) {
if (i != i1_temp)//i_temp=R(j)\i; 变量节点集合
r[j][i] = sign1[i] * (min1_temp);
else
r[j][i] = sign1[i] * (min2_temp);
}
}
}
void variable_note_pos(data_zj P[M],bool H[N][M],data_zj r[N][M],ap_fixed <10,8,AP_TRN,AP_SAT> qi[M]) {
//变量节点后验概率
int i,j;
ap_fixed <10,8,AP_TRN,AP_SAT> temp2 = 0;
LOOP_qi_1:
for (i = 0; i < M; i++) {
LOOP_qi_2:
for (j = 0; j < N; j++){
#pragma HLS UNROLL factor=2
#pragma HLS pipeline rewind
if (H[j][i]) { //j=C(i);
temp2 = temp2 + r[j][i];
}
if(j==N-1){
qi[i] = P[i] + temp2;
temp2 = 0;
}
}
}
}
void panjue(bool c[M],ap_fixed <10,8,AP_TRN,AP_SAT> qi[M]){
int i;
//****************************************判决*****************************************
LOOP_panjue:
for (i = 0; i < M; i++) {
#pragma HLS pipeline rewind
if (qi[i] > 0)
c[i] = 0;
else
c[i] = 1;//
}
}
void variable_note_update(int j,bool H[N][M],data_zj r_copy[M],data_zj q[M], ap_fixed<10,8, AP_TRN, AP_SAT> qi[M]) {
int i,b;
j表示计算第几行
//*****************************************变量节点更新*****************************************
LOOP_v_update_1:
for (i = 0; i < M; i++) {
#pragma HLS UNROLL factor=2
#pragma HLS pipeline rewind
q[i]=qi[i]-r_copy[i];
}
}
ap_int<1> check_formula(int diedai,bool c[M],bool H[N][M]) {
//根据校验矩阵计算是否译码完成
int i, j;
ap_uint<11> s[N];//矩阵相乘后的结果
ap_int<11> temp = 0;
ap_int<11> s_sum = 0;//s_sum负责记录满足条件,即s[i]=0的个数
LOOP_formula1:
for (j = 0; j < N; j++) {//N个校验方程
LOOP_formula2:
for (i = 0; i < M; i++) {
#pragma HLS pipeline rewind
temp = temp + c[i] * H[j][i];
if(i==M-1){
if (temp%2 == 0) s_sum = s_sum + 1;
temp = 0;
}
}
}
if (s_sum == N)
return 1;//满足校验矩阵,结束迭代
else
return 0;
s_sum = 0;
}
void update(bool H[N][M],data_zj r[N][M],ap_fixed<10, 8, AP_TRN, AP_SAT> qi[M]){
LOOP_N_hang:
for(int j=0;j<N;j++){
if(j==0)
LOOP_r_copy:
for (int i = 0; i < M; i++)
#pragma HLS pipeline II=1
r_copy[i] = r[0][i];
variable_note_update(j,H,r_copy,q,qi);
check_note_update(j,H,q,r,r_copy);
}
}
void decode(bool c[M]) {
ap_uint<4> diedai_max=5;
ap_uint<4> diedai;//迭代次数,即第几次迭代
ap_uint<1> result=0;
bool H[N][M] = {
#include"H.h"
};
data_zj P[M] = {
#include"y.h"
};
///*************************************译码******************************************
int i,j;
LOOP_initialize:
for (i = 0; i < M; i++) {
#pragma HLS pipeline
c[i]=BPSK(P[i]);
}
LOOP_diedai:
for(diedai=0;diedai<diedai_max;diedai=diedai+1){
result = check_formula(diedai,c,H);
if (result) {
break;
}
else
{
update(H,r,qi);
variable_note_pos(P, H, r, qi);
panjue(c, qi);
}
}
}
3.C++代码在HLS中的综合结果
将上述代码放到HLS中综合,相关的约束大部分已写在代码,其余约束如下:
set_directive_resource -core RAM_2P_LUTRAM "check_note_update" r
set_directive_resource -core RAM_2P_LUTRAM "variable_note_pos" r
set_directive_resource -core RAM_2P_LUTRAM "decode" r
set_directive_resource -core RAM_2P_LUTRAM "check_note_update" q
set_directive_resource -core RAM_2P_LUTRAM "check_note_update" r_copy
set_directive_resource -core RAM_2P_LUTRAM "variable_note_update" q
set_directive_resource -core RAM_2P_LUTRAM "variable_note_update" r_copy
set_directive_resource -core RAM_2P_LUTRAM "variable_note_update" qi
set_directive_resource -core RAM_2P_LUTRAM "decode" q
set_directive_resource -core RAM_2P_LUTRAM "decode" r_copy
set_directive_resource -core RAM_2P_LUTRAM "decode" qi
set_directive_resource -core RAM_2P_LUTRAM "variable_note_pos" qi
set_directive_resource -core RAM_2P_LUTRAM "panjue" qi
set_directive_resource -core ROM_2P_LUTRAM "decode" P
set_directive_dependence -variable r -class array -type intra -direction WAR -dependent false "check_note_update/LOOP_check_note_update1"
综合后的结果如下图:
进行联合仿真,验证综合后的代码功能是否正确,波形如下:
三、Verilog代码
HLS综合后生成的IP放到vivado中,与其他模块一起仿真,负责将译码后的输出通过串口发送出去。整体需要调用时钟生成IP与串口发送模块。
之后,将电路烧写到开发板中,并连回PC。(下图中的数据是不断重复的)
四、MATLAB对译码算法仿真
具体的MATLAB译码算法代码,主体使用了github别人开源的代码,自己对其进行了一定的修改,不在写出。
设置仿真帧数为10^6,仿真得到FER与迭代次数、加入噪声信噪比之间的关系如下图:
五、整个过程中出现的部分问题和解决方法
1.HLS中生成IP失败
官网中已有相关的解决方法:https://blog.csdn.net/qq_41873311/article/details/122596244
其中需要在Xilinx文件夹目录下,执行脚本;但自己电脑上安装路径没有该文件夹,后发现官方默认的Xilinx文件夹对应自己电脑中vivado安装的文件夹:E:\learn\software\vivado\rote。运行对应的脚本后成功出现下图:
2.HLS优化时将2个数组合并成1个数组失败
一个数组被resource指定成ROM,而合并后的数组被指定为RAM,发生冲突,导致优化失败
3.Vivado中没有自己需要的开发板和芯片型号
首先需要对Vivado更新,安装第二个版本,成功使vivado支持所有k7系列的芯片,从而使vivado中找到了板子的芯片xc7k325t-ffg900;(此时没有板子的信息)
之后,按照官方教程在digilent的GitHub库中将boards的new文件夹的信息全部拷下来到安装路径下面相应的文件夹。
4Vivado中无法调用HLS生成的IP
HLS中选择的芯片和vivado中选择的芯片要相同。