首页 > 其他分享 >实现PS端YOLO网络前向计算函数

实现PS端YOLO网络前向计算函数

时间:2023-07-24 23:22:40浏览次数:29  
标签:PS cnt addr tx DMA YOLO batch 前向 u32

实现PS端YOLO网络前向计算函数

目的:在PS端控制PL端完成YOLO网络的前向计算

前提:已经实现了YOLO网络参数导入到DDR3的功能

创建新文件

  • 在Vitis软件中新建两个文件:yolo_accel_ctrl.cyolo_accel_ctrl.h
    • yolo_accel_ctrl.c用于编写前向计算函数的主体代码
    • yolo_accel_ctrl.h用于声明前向计算函数所需的变量和函数

状态跳转图 & 流程图

graph TD A[开始] --> B[初始化状态为S_IDLE] B --> C{判断状态} C -->|S_IDLE| D[发送参数命令] D --> E[状态跳转为S_FEATURE_CONV] C -->|S_FEATURE_CONV| F[发送数据命令] F --> G[卷积计算命令] G --> H[状态跳转为S_DMA_RX] C -->|S_DMA_RX| I[接收数据命令] I --> J{判断是否完成当前8个输出通道} J -->|否| K[更新TX_CNT和TX_ADDR] K --> E J -->|是| L{判断是否完成全部16个输出通道} L -->|否| M[更新BATCH_CNT和TX_ADDR] M --> E L -->|是| N[状态跳转为S_FINISH] C -->|S_FINISH| O[结束]

image

定义状态机

  • yolo_accel_ctrl.c中定义一个状态机,用于控制前向计算的流程
    • 参考仿真时使用的Verilog代码中的状态机
    • 状态机有四个状态:S_IDLE, S_FEATURE_CONV, S_DMA_RX, S_FINISH
      • S_IDLE:初始化状态,发送参数给PL端
      • S_FEATURE_CONV:数据发送和卷积计算状态,发送输入数据给PL端,并控制PL端进行卷积运算
      • S_DMA_RX:数据接收状态,通过DMA从PL端读取输出数据
      • S_FINISH:结束状态,跳出状态机循环
    • 状态机使用switch-case语句实现
    • 状态机使用两个计数器控制循环次数:tx_cnt和batch_cnt
      • tx_cnt:每8个输出通道重复数据发送、卷积计算、数据接收的次数计数器
      • batch_cnt:每完成8个输出通道计算的次数计数器
// 定义状态机
enum state {S_IDLE, S_FEATURE_CONV, S_DMA_RX, S_FINISH};
enum state state = S_IDLE; // 初始状态为S_IDLE

// 定义计数器
u8 tx_cnt; // 每8个输出通道重复数据发送、卷积计算、数据接收的次数计数器
u8 batch_cnt; // 每完成8个输出通道计算的次数计数器

// 定义状态机循环条件
while (state != S_FINISH) {
  switch (state) {
    case S_IDLE:
      // 初始化状态代码
      break;
    case S_FEATURE_CONV:
      // 数据发送和卷积计算状态代码
      break;
    case S_DMA_RX:
      // 数据接收状态代码
      break;
    case S_FINISH:
      // 结束状态代码
      break;
    default:
      // 默认状态代码
      break;
  }
}

初始化状态

  • 在S_IDLE状态中,完成以下操作:
    • 定义并初始化一些变量,如命令、地址、数据长度等
    • 发送参数给PL端,包括BIAS、ACT、WEIGHT等
    • 跳转到S_FEATURE_CONV状态
case S_IDLE:
  // 定义并初始化变量
  u32 cmd; // 命令变量
  u32 tx_addr; // 发送数据地址变量
  u32 tx_len; // 发送数据长度变量
  u32 rx_addr; // 接收数据地址变量
  u32 rx_len; // 接收数据长度变量

  tx_cnt = 0; // 初始化tx_cnt为0
  batch_cnt = 0; // 初始化batch_cnt为0

  // 发送参数给PL端
  cmd = (u32)0x00000001; // BIAS参数命令
  Xil_Out32(CMD_BASEADDR, cmd); // 将命令写入CMD_BASEADDR寄存器
  XAxiDma_SimpleTransfer(&axiDma, (u32)BIAS_DDR_BASEADDR, BIAS_LENGTH, XAXIDMA_DMA_TO_DEVICE); // 将BIAS参数从BIAS_DDR_BASEADDR地址发送给PL端,长度为BIAS_LENGTH

  cmd = (u32)0x00000002; // ACT参数命令
  Xil_Out32(CMD_BASEADDR, cmd); // 将命令写入CMD_BASEADDR寄存器
  XAxiDma_SimpleTransfer(&axiDma, (u32)ACT_DDR_BASEADDR, ACT_LENGTH, XAXIDMA_DMA_TO_DEVICE); // 将ACT参数从ACT_DDR_BASEADDR地址发送给PL端,长度为ACT_LENGTH

  cmd = (u32)0x00000003; // WEIGHT参数命令
  Xil_Out32(CMD_BASEADDR, cmd); // 将命令写入CMD_BASEADDR寄存器
  XAxiDma_SimpleTransfer(&axiDma, (u32)WEIGHT_DDR_BASEADDR, WEIGHT_LENGTH, XAXIDMA_DMA_TO_DEVICE); // 将WEIGHT参数从WEIGHT_DDR_BASEADDR地址发送给PL端,长度为WEIGHT_LENGTH

  // 跳转到S_FEATURE_CONV状态
  state = S_FEATURE_CONV;
  break;

数据发送和卷积计算状态

  • 在S_FEATURE_CONV状态中,完成以下操作:

    • 根据tx_cnt和batch_cnt的值,判断是否是第一次或最后一次发送数据,生成相应的命令

      • 第一次发送数据:cmd = (u32)0x00000001 | (u32)0x00010000;
      • 最后一次发送数据:cmd = (u32)0x00000001 | (u32)0x00100000;
      • 其他情况:cmd = (u32)0x00000001 | (u32)0x00110000;
    • 根据tx_cnt的值,判断是否是最后一次发送数据,生成相应的数据长度

      • 最后一次发送数据:tx_len = (u32)9984;
      • 其他情况:tx_len = (u32)29952;
    • 根据batch_cnt的值,判断是否是第一次或第二次发送数据,生成相应的数据地址

      • 第一次发送数据:tx_addr = (u32)0x10000000;
      • 第二次发送数据:tx_addr = (u32)0x10000000 + (u32)(7 * IN_WIDTH * IN_CH * sizeof(float));
      • 其他情况:不改变tx_addr的值;
    • 将命令写入CMD_BASEADDR寄存器,将输入数据从tx_addr地址发送给PL端,长度为tx_len

    • 将命令的最后4位改为4,表示开始卷积计算,并写入CMD_BASEADDR寄存器

    • 等待PL端完成卷积计算,并检查是否有错误发生

    • 根据tx_cnt的值,判断是否需要跳转到S_DMA_RX状态,或者增加tx_cnt的值

      • 如果tx_cnt等于7,表示已经发送了8个输出通道的数据,需要跳转到S_DMA_RX状态,并将tx_cnt重置为0

      • 否则,将tx_cnt加1,继续发送下一个输出通道的数据

    • 根据batch_cnt的值,判断是否需要增加batch_cnt的值

      • 如果batch_cnt等于0,并且tx_cnt等于0,表示已经完成了第一次发送8个输出通道的数据,需要将batch_cnt加1
      • 否则,不改变batch_cnt的值
case S_FEATURE_CONV:
  // 根据tx_cnt和batch_cnt的值,判断是否是第一次或最后一次发送数据,生成相应的命令
  if (tx_cnt == 0 && batch_cnt == 0) {
    // 第一次发送数据
    cmd = (u32)0x00000001 | (u32)0x00010000;
  } else if (tx_cnt == 7 && batch_cnt == 1) {
    // 最后一次发送数据
    cmd = (u32)0x00000001 | (u32)0x00100000;
  } else {
    // 其他情况
    cmd = (u32)0x00000001 | (u32)0x00110000;
  }

  // 根据tx_cnt的值,判断是否是最后一次发送数据,生成相应的数据长度
  if (tx_cnt == 7 && batch_cnt == 1) {
    // 最后一次发送数据
    tx_len = (u32)9984;
  } else {
    // 其他情况
    tx_len = (u32)29952;
  }

  // 根据batch_cnt的值,判断是否是第一次或第二次发送数据,生成相应的数据地址
  if (batch_cnt == 0 && tx_cnt == 0) {
    // 第一次发送数据
    tx_addr = (u32)0x10000000;
  } else if (batch_cnt == 1 && tx_cnt == 0) {
    // 第二次发送数据
    tx_addr = (u32)0x10000000 + (u32)(7 * IN_WIDTH * IN_CH * sizeof(float));
  } else {
    // 其他情况
    // 不改变tx_addr的值
  }

  // 将命令写入CMD_BASEADDR寄存器,将输入数据从tx_addr地址发送给PL端,长度为tx_len
  Xil_Out32(CMD_BASEADDR, cmd);
  XAxiDma_SimpleTransfer(&axiDma, tx_addr, tx_len, XAXIDMA_DMA_TO_DEVICE);

  // 将命令的最后4位改为4,表示开始卷积计算,并写入CMD_BASEADDR寄存器
  cmd = cmd & (u32)0xFFFFFFF0 | (u32)0x00000004;
  Xil_Out32(CMD_BASEADDR, cmd);

  // 等待PL端完成卷积计算,并检查是否有错误发生
  while ((Xil_In32(CMD_BASEADDR + CMD_DONE_OFFSET) & CMD_DONE_MASK) != CMD_DONE_MASK);
  if ((Xil_In32(CMD_BASEADDR + CMD_ERROR_OFFSET) & CMD_ERROR_MASK) != CMD_ERROR_MASK) {
    printf("Error: Convolution error\n");
    return XST_FAILURE;
  }

  // 根据tx_cnt的值,判断是否需要跳转到S_DMA_RX状态,或者增加tx_cnt的值
  if (tx_cnt == 7) {
    // 如果tx_cnt等于7,表示已经发送了8个输出通道的数据,需要跳转到S_DMA_RX状态,并将tx_cnt重置为0
    state = S_DMA_RX;
    tx_cnt = 0;
  } else {
    // 否则,将tx_cnt加1,继续发送下一个输出通道的数据
    tx_cnt++;
    state = S_FEATURE_CONV;
  }

  // 根据batch_cnt的值,判断是否需要增加batch_cnt的值
  if (batch_cnt == 0 && tx_cnt == 0) {
    // 如果batch_cnt等于0,并且tx_cnt等于0,表示已经完成了第一次发送8个输出通道的数据,需要将batch_cnt加1
    batch_cnt++;
  } else {
    // 否则,不改变batch_cnt的值
  }
  break;
  • 在S_DMA_RX状态中,完成以下操作:
    • 根据tx_cnt和batch_cnt的值,判断是否是第一次或最后一次接收数据,生成相应的数据地址和数据长度
      • 如果tx_cnt等于0,并且batch_cnt等于0,表示第一次接收数据,数据地址为0x20000000,数据长度为65536
      • 如果tx_cnt等于7,并且batch_cnt等于1,表示最后一次接收数据,数据地址为0x20000000 + (u32)(7 * OUT_WIDTH * OUT_CH * sizeof(float)),数据长度为9984
      • 其他情况下,数据地址为上一次接收数据的地址加上上一次接收数据的长度,数据长度为49920或65536交替出现
    • 从PL端接收输出数据到相应的数据地址,长度为相应的数据长度
    • 根据tx_cnt和batch_cnt的值,判断是否需要跳转到S_FINISH状态,或者跳转回S_FEATURE_CONV状态
      • 如果tx_cnt等于7,并且batch_cnt等于1,表示已经完成了所有输出通道的接收,需要跳转到S_FINISH状态
      • 否则,跳转回S_FEATURE_CONV状态,并根据情况增加tx_cnt或batch_cnt的值
case S_DMA_RX:
  // 根据tx_cnt和batch_cnt的值,判断是否是第一次或最后一次接收数据,生成相应的数据地址和数据长度
  if (tx_cnt == 0 && batch_cnt == 0) {
    // 第一次接收数据
    rx_addr = (u32)0x20000000;
    rx_len = (u32)65536;
  } else if (tx_cnt == 7 && batch_cnt == 1) {
    // 最后一次接收数据
    rx_addr = (u32)0x20000000 + (u32)(7 * OUT_WIDTH * OUT_CH * sizeof(float));
    rx_len = (u32)9984;
  } else {
    // 其他情况
    // 数据地址为上一次接收数据的地址加上上一次接收数据的长度
    rx_addr = rx_addr + rx_len;
    // 数据长度为49920或65536交替出现
    if (rx_len == (u32)49920) {
      rx_len = (u32)65536;
    } else {
      rx_len = (u32)49920;
    }
  }

  // 从PL端接收输出数据到相应的数据地址,长度为相应的数据长度
  XAxiDma_SimpleTransfer(&axiDma, rx_addr, rx_len, XAXIDMA_DEVICE_TO_DMA);

  // 等待DMA传输完成,并检查是否有错误发生
  while ((XAxiDma_Busy(&axiDma, XAXIDMA_DEVICE_TO_DMA)));
  if ((XAxiDma_ReadReg(axiDma.RegBase, XAXIDMA_RX_OFFSET + XAXIDMA_SR_OFFSET) & XAXIDMA_IRQ_ERROR_MASK)) {
    printf("Error: DMA receive error\n");
    return XST_FAILURE;
  }

  // 根据tx_cnt和batch_cnt的值,判断是否需要跳转到S_FINISH状态,或者跳转回S_FEATURE_CONV状态
  if (tx_cnt == 7 && batch_cnt == 1) {
    // 如果tx_cnt等于7,并且batch_cnt等于1,表示已经完成了所有输出通道的接收,需要跳转到S_FINISH状态
    state = S_FINISH;
  } else {
    // 否则,跳转回S_FEATURE_CONV状态,并根据情况增加tx_cnt或batch_cnt的值
    state = S_FEATURE_CONV;
    if (tx_cnt == 7) {
      // 如果tx_cnt等于7,表示已经完成了8个输出通道的接收,需要将tx_cnt重置为0,并将batch_cnt加1
      tx_cnt = 0;
      batch_cnt++;
    } else {
      // 否则,将tx_cnt加1
      tx_cnt++;
    }
  }
  break;
  • 在S_FINISH状态中,完成以下操作:
    • 打印输出数据的前16个元素,用于检查结果是否正确
    • 跳出状态机的循环,结束前向计算函数
case S_FINISH:
  // 打印输出数据的前16个元素,用于检查结果是否正确
  printf("Output data:\n");
  for (int i = 0; i < 16; i++) {
    printf("%f ", ((float *)0x20000000)[i]);
  }
  printf("\n");

  // 跳出状态机的循环,结束前向计算函数
  break;
  • 在S_IDLE状态中,完成以下操作:
    • 根据layer_id的值,判断是哪一层的前向计算,生成相应的参数地址和数据地址
      • 如果layer_id等于0,表示是第0层的前向计算,参数地址为0x10000000,数据地址为0x30000000
      • 如果layer_id等于1,表示是第1层的前向计算,参数地址为0x10080000,数据地址为0x20000000
      • 如果layer_id等于2,表示是第2层的前向计算,参数地址为0x10100000,数据地址为0x20000000
      • 其他情况下,打印错误信息,并返回
    • 从参数地址开始,发送BIAS、ACT、WEIGHT三种参数到PL端
    • 跳转到S_FEATURE_CONV状态
case S_IDLE:
  // 根据layer_id的值,判断是哪一层的前向计算,生成相应的参数地址和数据地址
  switch (layer_id) {
    case 0:
      // 如果layer_id等于0,表示是第0层的前向计算,参数地址为0x10000000,数据地址为0x30000000
      param_addr = (u32)0x10000000;
      data_addr = (u32)0x30000000;
      break;
    case 1:
      // 如果layer_id等于1,表示是第1层的前向计算,参数地址为0x10080000,数据地址为0x20000000
      param_addr = (u32)0x10080000;
      data_addr = (u32)0x20000000;
      break;
    case 2:
      // 如果layer_id等于2,表示是第2层的前向计算,参数地址为0x10100000,数据地址为0x20000000
      param_addr = (u32)0x10100000;
      data_addr = (u32)0x20000000;
      break;
    default:
      // 其他情况下,打印错误信息,并返回
      printf("Error: Invalid layer id\n");
      return XST_FAILURE;
  }

  // 从参数地址开始,发送BIAS、ACT、WEIGHT三种参数到PL端
  // 发送BIAS参数
  Xil_Out32(ADDR_REG_1, param_addr);
  Xil_Out32(ADDR_REG_2, BIAS_DATA);
  Xil_Out32(ADDR_REG_3, BIAS_LEN);
  Xil_Out32(ADDR_REG_4, CMD_BIAS);

  // 发送ACT参数
  Xil_Out32(ADDR_REG_1, param_addr + BIAS_LEN);
  Xil_Out32(ADDR_REG_2, ACT_DATA);
  Xil_Out32(ADDR_REG_3, ACT_LEN);
  Xil_Out32(ADDR_REG_4, CMD_ACT);

  // 发送WEIGHT参数
  Xil_Out32(ADDR_REG_1, param_addr + BIAS_LEN + ACT_LEN);
  Xil_Out32(ADDR_REG_2, WEIGHT_DATA);
  Xil_Out32(ADDR_REG_3, WEIGHT_LEN);
  Xil_Out32(ADDR_REG_4, CMD_WEIGHT);

  // 跳转到S_FEATURE_CONV状态
  state = S_FEATURE_CONV;
  break;
  • 在S_FEATURE_CONV状态中,完成以下操作:
    • 根据tx_cnt和batch_cnt的值,判断是否是第一次或最后一次发送数据,生成相应的命令和数据长度
      • 如果tx_cnt等于0,表示是第一次发送数据,命令为CMD_TX_FIRST,数据长度为TX_LEN_FIRST
      • 如果tx_cnt等于TX_CNT_END,并且batch_cnt等于BATCH_CNT_END,表示是最后一次发送数据,命令为CMD_TX_LAST,数据长度为TX_LEN_LAST
      • 其他情况下,表示是中间的发送数据,命令为CMD_TX_OTHER,数据长度为TX_LEN_OTHER
    • 发送数据到PL端,并启动卷积计算
    • 跳转到S_DMA_RX状态
case S_FEATURE_CONV:
  // 根据tx_cnt和batch_cnt的值,判断是否是第一次或最后一次发送数据,生成相应的命令和数据长度
  if (tx_cnt == 0) {
    // 如果tx_cnt等于0,表示是第一次发送数据,命令为CMD_TX_FIRST,数据长度为TX_LEN_FIRST
    cmd = CMD_TX_FIRST;
    tx_len = TX_LEN_FIRST;
  } else if (tx_cnt == TX_CNT_END && batch_cnt == BATCH_CNT_END) {
    // 如果tx_cnt等于TX_CNT_END,并且batch_cnt等于BATCH_CNT_END,表示是最后一次发送数据,命令为CMD_TX_LAST,数据长度为TX_LEN_LAST
    cmd = CMD_TX_LAST;
    tx_len = TX_LEN_LAST;
  } else {
    // 其他情况下,表示是中间的发送数据,命令为CMD_TX_OTHER,数据长度为TX_LEN_OTHER
    cmd = CMD_TX_OTHER;
    tx_len = TX_LEN_OTHER;
  }

  // 发送数据到PL端,并启动卷积计算
  Xil_Out32(ADDR_REG_1, data_addr);
  Xil_Out32(ADDR_REG_2, FEATURE_DATA);
  Xil_Out32(ADDR_REG_3, tx_len);
  Xil_Out32(ADDR_REG_4, cmd | CMD_CONV);

  // 跳转到S_DMA_RX状态
  state = S_DMA_RX;
  break;
  • 在S_DMA_RX状态中,完成以下操作:
    • 根据tx_cnt和batch_cnt的值,判断是否是第一次或最后一次接收数据,生成相应的数据长度
      • 如果tx_cnt等于0,表示是第一次接收数据,数据长度为RX_LEN_FIRST
      • 如果tx_cnt等于TX_CNT_END,并且batch_cnt等于BATCH_CNT_END,表示是最后一次接收数据,数据长度为RX_LEN_LAST
      • 其他情况下,表示是中间的接收数据,数据长度为RX_LEN_OTHER
    • 从PL端接收数据,并存储到rx_addr指定的地址
    • 更新tx_cnt、batch_cnt、data_addr和rx_addr的值
    • 判断是否完成了当前层的所有计算,如果是,则跳转到S_FINISH状态,否则跳转到S_FEATURE_CONV状态
case S_DMA_RX:
  // 根据tx_cnt和batch_cnt的值,判断是否是第一次或最后一次接收数据,生成相应的数据长度
  if (tx_cnt == 0) {
    // 如果tx_cnt等于0,表示是第一次接收数据,数据长度为RX_LEN_FIRST
    rx_len = RX_LEN_FIRST;
  } else if (tx_cnt == TX_CNT_END && batch_cnt == BATCH_CNT_END) {
    // 如果tx_cnt等于TX_CNT_END,并且batch_cnt等于BATCH_CNT_END,表示是最后一次接收数据,数据长度为RX_LEN_LAST
    rx_len = RX_LEN_LAST;
  } else {
    // 其他情况下,表示是中间的接收数据,数据长度为RX_LEN_OTHER
    rx_len = RX_LEN_OTHER;
  }

  // 从PL端接收数据,并存储到rx_addr指定的地址
  dma_rx(rx_addr, rx_len);

  // 更新tx_cnt、batch_cnt、data_addr和rx_addr的值
  tx_cnt++; // 增加发送计数器的值
  if (tx_cnt > TX_CNT_END) {
    // 如果发送计数器超过了最大值,说明已经完成了8个输出通道的计算
    tx_cnt = 0; // 重置发送计数器为0
    batch_cnt++; // 增加批次计数器的值
    if (batch_cnt > BATCH_CNT_END) {
      // 如果批次计数器超过了最大值,说明已经完成了所有输出通道的计算
      batch_cnt = 0; // 重置批次计数器为0
      data_addr = DATA_ADDR_INIT; // 恢复数据地址为初始值
      rx_addr = RX_ADDR_INIT; // 恢复接收地址为初始值
    } else {
      // 否则,说明还有8个输出通道需要计算[^1^][1]
      data_addr += DATA_ADDR_OFFSET; // 增加数据地址的偏移量,指向下一个8个输入通道的数据
      rx_addr += RX_ADDR_OFFSET; // 增加接收地址的偏移量,指向下一个8个输出通道的存储位置
    }
  } else {
    // 否则,说明还在同一个8个输出通道内进行计算
    data_addr += DATA_ADDR_INC; // 增加数据地址的增量,指向下一个输入块的数据
    rx_addr += RX_ADDR_INC; // 增加接收地址的增量,指向下一个输出块的存储位置
  }

  // 判断是否完成了当前层的所有计算,如果是,则跳转到S_FINISH状态,否则跳转到S_FEATURE_CONV状态
  if (tx_cnt == TX_CNT_END && batch_cnt == BATCH_CNT_END) {
    // 如果发送计数器和批次计数器都达到了最大值,说明当前层的所有计算都完成了
    state = S_FINISH; // 跳转到S_FINISH状态
  } else {
    // 否则,说明还有更多的计算需要进行
    state = S_FEATURE_CONV; // 跳转到S_FEATURE_CONV状态
  }
  break;
  • 在S_FINISH状态中,完成以下操作:
    • 输出当前层的计算结果,可以使用print_data函数打印rx_addr指向的数据
    • 跳出状态机的循环,结束前向计算函数
case S_FINISH:
  // 输出当前层的计算结果,可以使用print_data函数打印rx_addr指向的数据
  print_data(rx_addr, RX_LEN_LAST);

  // 跳出状态机的循环,结束前向计算函数
  break;

注意:

  • 在 PS 端编写代码时要注意寄存器地址、参数地址、数据地址、数据长度等值是否正确
  • 在 PS 端调用 DMA 函数时,要注意以下几点:
    • DMA 函数的参数要与发送或接收的数据地址、长度、方向一致
    • DMA 函数要在发送或接收数据之前调用,以初始化 DMA 通道
    • DMA 函数要在发送或接收命令之后调用,以启动 DMA 传输
    • DMA 函数要等待传输完成后返回,以确保数据完整
  • 在 PS 端进行 YOLO 网络的前向计算时,需要注意输出结果的正确性和精度。可以通过与软件模拟或硬件仿真的结果进行比对来验证输出结果是否正确,并通过调整参数、数据和命令的位宽、位数和格式来优化输出结果的精度。
  • 在 PS 端实现前向计算函数时,需要注意每一层的参数、数据和命令的变化,以及循环次数和条件判断的正确性。可以通过参考仿真代码和表格来确定每一层的具体需求和控制流程,并在编写代码时进行注释和调试。

标签:PS,cnt,addr,tx,DMA,YOLO,batch,前向,u32
From: https://www.cnblogs.com/LiamJacob/p/17578631.html

相关文章

  • hadoop-eclipse开发环境搭建及error: failure to login错误
    对于Hadoop开发者来讲,通过JAVAAPI编程是进入Map-Reduce分布式开发的第一步。由于Eclipse本身并没有提供对MapReduce编程模式的支持,所以需要一些简单的步骤来实现。1.安装Hadoop。本文的Hadoop是部署在虚拟机上的伪分布模式。相关软件环境如下:JDK:sunjdk1.6.0_30Hadoop:hadoop-0......
  • 使用 Eclipse Dali 开发 OpenJPA 应用
    简介:JPA(JavaPersistenceAPI)是EJB3.0新引入的数据持久化编程模型。JPA充分利用了注释(Annotation)和对象/关系映射,为数据持久化提供了更简单、易用的编程方式。OpenJPA是Apache组织提供的JPA标准实现。本文是使用ApacheOpenJPA开发EJB3.0应用系列的第七部分,主要......
  • Eclipse远程调试Tomcat
    最近,一直在研究Tomcat的工作内幕,主要的方法就是参考《HowTomcatWorks》这本书和Tomcat5.5.26的源代码。 Tomcat的代码结构还是比较清晰的,注释也比较全。但是代码毕竟是静态的,难以彻底弄清类与类之间的协作关系,以及运行时对象的交互关系。 如果能对Tomcat的启动、处理请求和停......
  • 忽略证书的HTTPS请求实现
    publicstaticvoidhttpsTest(StringhttpUrl){//忽略证书的https请求try{SSLContextsslContext=newSSLContextBuilder().loadTrustMaterial(null,newTrustStrategy(){@OverridepublicbooleanisTrusted(X509Certificate[......
  • 网关改造正当时,跟学 HigressOps 夏季营
    从全局看,软件改造过程中产生的成本,是为了减少单点治理和运维过程中所带来的更高的成本。HigressOps旨在通过云原生网关Higress对网关架构进行升级,将流量网关、微服务网关、安全网关合三为一,以Higress+Nacos的组合提供了最小集运行环境,同时满足用户服务注册、配置管理和微......
  • SpringBoot基于Spring Security的HTTP跳转HTTPS
    简单说说之所以采用SpringSecurity来做这件事,一是SpringSecurity可以根据不同的URL来进行判断是否需要跳转(不推荐),二是不需要新建一个TomcatServletWebServerFactoryBean,新建这个Bean可能会导致SpringBoot关于Server的配置失效。三是网上大部分流传的通过实现WebServerFactor......
  • m基于OFDM+QPSK和LDPC编译码通信链路matlab性能仿真,包括Costas载波同步和gardner定时
    1.算法仿真效果matlab2013b仿真结果如下:      2.算法涉及理论知识概要        基于OFDM+QPSK和LDPC编码的通信链路是一种常用的数字通信系统,用于实现高速、可靠的数据传输。该系统结合了正交频分复用(OFDM)、四相移键控(QPSK)调制和低密度奇偶校验(LDPC)编码......
  • PS三联封面制作
    PS三联封面制作新建2160乘以960文件拖入图片,打勾CTRL+T激活自由变换工具,在不变形的情况下将图片铺满背景将拖入的图片转换为智能对象使用切片工具,进行垂直等分添加标题,设置文字字体、大小、颜色、间距标题不明显,设置描边将标题移动好位置添加序号,设置文字字体、大小、颜......
  • vue 父向子通过props 传递一个function报未定义
    解决方法:参考资料:https://cloud.tencent.com/developer/ask/sof/523570来自为知笔记(Wiz)......
  • ES数据备份之snapshot和elasticdump
    https://blog.csdn.net/m0_46435788/article/details/114291491?spm=1001.2101.3001.6650.9&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-9-114291491-blog-129881702.235%5Ev38%5Epc_relevant_sort&depth_1-utm_so......