刘航宇 发布的文章 - 我的学记|刘航宇的博客
首页
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🎵音乐
🏞️壁纸
搜 索
1
【NPN/PNP三极管】放大电路饱和失真和截止失真的区别
13,195 阅读
2
论文写作中如何把word里面所有数字和字母替换为新罗马字体
7,414 阅读
3
【高数】形心计算公式讲解大全
6,843 阅读
4
如何判断运放是工作在线性区还是非线性区
5,417 阅读
5
【1】基于STM32CubeMX-STM32GPIO端口开发
5,321 阅读
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
嵌入式&系统
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
登录
搜 索
标签搜索
嵌入式
ASIC/FPGA
VLSI
SOC设计
机器学习
天线设计
C/C++
EDA&虚拟机
软件算法
小实验
信号处理
电子线路
通信&射频
随笔
笔试面试
硬件算法
Verilog
软件无线电
Python
DL/ML
刘航宇
嵌入式系统&数字IC爱好者博客
累计撰写
303
篇文章
累计收到
529
条评论
首页
栏目
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
嵌入式&系统
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
页面
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🎵音乐
🏞️壁纸
用户登录
登录
刘航宇(共303篇)
找到
303
篇与
刘航宇
相关的结果
2025-01-09
【FPGA】AXI DMA详解
<DMA简介DMA是一种内存访问技术,允许某些计算机内部的硬件子系统可以独立的直接读写内存,而不需要CPU介入处理,从而不需要CPU的大量中断负载,否则,CPU需要从来源把每一片段的数据复制到寄存器,然后在把他们再次写回到新的地方,在这个时间里,CPU就无法执行其他的任务。DMA是一种快速数据传送方式,通常用来传送数据量较多的数据块。使用DMA时,CPU向DMA控制器发送一个存储器传输请求,这样当DMA控制器在传输的时候,CPU执行其他的操作,传输完成时DMA以中断的方式通知CPU。DMA传输过程的示意图为:DMA的传输过程为:1、为了配置用DMA传输数据到存储器,处理器(Cortex-A9)发出一条指令。2、DMA控制器把数据从外设传输到存储器或者从存储器传输到存储器,从而较少CPU处理的事务量。3、输出传输完成后,向CPU发出一个中断通知DMA传输可以关闭。为了发起传输事务,DMA控制器必须得到以下信息:(1)、源地址——数据被读出的地址(2)、目的地址——数据被写入的地址(3)、传输长度——应传输的字节数ZYNQ DMA简介ZYNQ提供了两种DMA,一种是集成在PS中的硬核DMA,另一种是PL中使用的软核AXI DMA IP。在ARM APU(Application Processor Unit,应用处理单元)设计过程中,已经考虑到大量数据搬移的情况,因此在APU中自带了一个DMA控制器DAMC,这个DMAC驻留在PS内,而且必须通过驻留在内存中的DMA指令编程,这些程序往往需要CPU准备,因此需要部分的CPU参与。DMAC支持多达8个通道,所以多个DMA结构的核可以挂载在单个DMAC上。DMAC与PL的连接是通过AXI-GP接口,这个接口最高支持到32位宽,这也限制了这种模式下的传输速率,理论上最大为600MB/s,这种模式不占用PL资源,但需要对DMA指令编程,会增加软件的复杂性。为了获取更高的速率,可以空间换时间,在PL中添加AXI DMA IP core,并利用AXI_HP接口完成高速的数据传输,各种接口的传输比较为:ZYNQ中ACI_HP接口的分布为:通过PL的DMA和AXI_HP接口传输方式的拓扑图为:DMA的数据传输经过S_AXI_HP接口,每一个HP接口都含有控制和数据fifo,这些fifo为大数据量突发传输提供缓冲,使得HP成为理想的高速数据接口。对DMA的控制或配置通过M_AXI_GP接口(M代表master为PS),传输状态通过中断传达到PS的中断控制器。对M_AXI_GP0理解是:在ZYNQ7处理器系统IP core中,在PS-PL Configuration下的AXI Non Secure Enablement下有一个GP Master AXI Interface选项,可选一个M_AXI_GP0接口。该接口的作用是对PL侧的IP core通过AXI-Lite总线进行配置,如果不仅需要的话直接不使能即可。三、AXI DMA IP简介ZYNQ提供了两种DMA,一种是集成在PS中的硬核DMA,另一种是PL中使用的软核AXI DMA IP。AXI DMA IP核在AXI4-Stream IP接口之间提供高带宽直接存储访问。其可选的scatter gather(SG,链式相关)功能还可以从基于处理器的系统中的中央处理单元(CPU)卸载数据搬运任务。初始化、状态和管理寄存器通过AXI-Lite从接口访问(即数据发出方为PL,PS为Slave),核心功能组成为(这张图很有助于理解DMA中断以及SDK代码,下面会解释):原图位于AXI_DMA数据手册的第五页。AXI DMA使用了三种总线,分别是:(1)、AXI Memory Map,用于内存交互,AXI4 Memory Map Read用于从DMA读取,AXI4 Memory Map用于向DMA写入。(2)、AXI4-Lite同于对寄存器的配置。(3)、AXI4-Stream接口用于对外设的读写,S2MM(Stream to Memory Mapped,数据流向内存映射)用于对外设读取。AXI_MM2S和AXI_S2MM是AXI_Stream总线,可以发送和接收连续的数据流,无需地址。AXI DMA提供3种模式:(1)、Direct Register模式:用于在MM2S和S2MM通道上执行简单的DMA传输,小的FPGA资源少。有两个通道:一个从Device到DMA,另一个从DMA到Device。应用程序必须设置缓冲区地址和长度字段以启动相应通道中的传输。(2)、Scatter/Gather模式:允许在单个DMA事务中将数据传输到多个存储区域传输数据。(3)、Cyclic DMA模式:四、AXI DMA参数与接口分析1、接口分析:(1)、M_AXI_MM2S:DMA的读通道,从DDR中读取数据。受Enable Read Channel控制,表现为M_AXI_MM2S。(2)、M_AXI_S2MM:DMA的写通道,将数据写入DDR中。受Enable Write Channel控制,表现为M_AXI_S2MM。(3)、M_AXIS_MM2S:DMA将数据发送到具有stream接口IP。(4)、S_AXIS_S2MM:DMA将数据从具有Stream接口的IP中将数据读入。(5)、mm2s_introut:DMA将数据从DDR的映射单元中读出,然后将数据发送到具有Stream接口的IP完成信号。(6)、s2mm_introut:DMA将数据从具有stream接口的IP中读入,并写入到内存映射单元的完成中断信号。2、参数分析(1)、Enable Scatter Gatter Engine链式DMA操作,取消选中该选项可启用directregister模式操作。(2)、Enable Micro DMA改选项会生成高度优化的DMA,资源数量较少,用于传输极少量数据的应用程序。(3)、Width of Buffer Length Register根据IP手册pg021,在direct register模式下,此整数值用于指定控制字段缓冲区长度的有效位数,字节数等于2^(width),即字节读取和字节写入的有效长度都是2^(width)。比如宽度设置为26,可传输的字节数为2^(26)字节。(pg021,78页)。(4)、Address Width指定地址空间的宽度,默认32。(5)、Enable Read ChannelMemory Map Data Width:AXI MM2S存储映射读取总线的数据位宽,可为32、64、128、256、512、1024。Stream Data Width:AXI MM2S AXI-Stream数据总线的位宽,该值必须小于等于Memory Map Data Width,可以为8、16、32、64、128、512、1024。Max Burst Size:最大突发长度设置,指定的是MM2S的AXI4-Memory Map侧的突发周期的最大值,可为2、4、8、16、32、64、128、256。(6)、Enable Write channel:同Read channel。3、关于中断的理解(1)、M_AXI_MM2S:DMA的读通道,从DDR中读取数据。受Enable Read Channel控制,表现为M_AXI_MM2S。在AXI_DMA ip core的输出信号中,有两个中断信号,分别是s2mm_introut和mm2s_introut,mm指的是Memory Mapped,S指的是Stream。Memory Map指的是什么?根据AXI DMA的介绍,AXI DMA提供一个介于AXI4 Memory Mapped 与AXI4 Stream IP之间的高带宽DMA:原话位于IP参考的page5:The AXI DirectMemory Access (AXI DMA) IP core provides high-bandwidth direct memory accessbetween the AXI4 memory mapped and AXI4-Stream IP interfaces.所以,对于DMA来说,S2MM,就是Stream形式的数据到达DDR映射空间,具体的实现方式是Stream数据流先进入DMA,之后再从DMA到Memeory Mapped。MM2S是Memory Mapped将数据送入具有AXI Stream接口的IP。从这里分析mm2s_introut与s2mm_introut信号的区别是分析不出来的,因为数据都是先到DMA,再从DMA发送出去。在第5页还有一张图,讲述AXI DMA的架构:分析这张图,DDR内存映射空间的读写都是通过AXI4Memory Map完成的,也就是说s2mm与mm2s的重点不在PS DDR侧,重点在PL侧,当Stream接口的数据将输出传到DMA时候,这个过程叫做DMA的接收,DMA将映射单元的数据写到stream接口的IP,这个过程叫做DMA的发送。所以!也就可以理解在SDK中将s2mm_introut定义为DMA接收中断,将mm2s_introut定义为发送中断了!所以以下语句就很容易理解了:// DMA接收通道的中断IDdefine RX_INTR_ID XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_IDdefine TX_INTR_ID XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_IDXilinx FPGA里面的AXI DMA IP核的简单用法在FPGA里面,AXI DMA这个IP核的主要作用,就是在Verilog语言和C语言之间传输大批量的数据,使用的通信协议为AXI4-Stream。Xilinx很多IP核都是基于AXI4-Stream协议的,例如浮点数Floating-point IP核,以及以太网Tri Mode Ethernet MAC IP核。要想将Verilog层面的数据搬运到C语言里面处理,就要使用DMA IP核。本文以浮点数Floating-point IP核将定点数转换为浮点数为例,详细讲解AXI DMA IP核的使用方法。浮点数IP核的输入输出数据都是32位,协议均为AXI4-Stream。C语言程序首先将要转换的定点数数据通过DMA发送给浮点数IP核,浮点数IP核转换完成后再通过DMA将单精度浮点数结果发回C语言程序,再通过printf打印出来。定点数的数据类型为int,小数点定在第四位上,即:XXXXXXX.X。整数部分占28位,小数部分占4位。转换后浮点数的数据类型为float,可以用printf的%f直接打印出来。工程下载地址:https://pan.baidu.com/s/1SXppHMdhroFT8vGCIysYTQ(提取码:u7wf)MicroBlaze C语言工程的建法不再赘述,请参阅:https://blog.csdn.net/ZLK1214/article/details/111824576以读写Floating-point IP核数据为例首先添加Floating-point IP核,作为DMA的外设端:(主存端为BRAM)这里要注意一下,一定要勾选上TLAST,否则DMA接收端会出现DMA Internal Error的错误:下面是Xilinx DMA手册里面对DMA Internal Error错误的描述:添加AXI DMA IP核:IP核添加好了,但还没有连线:点击Run Connection Automation,自动连接DMA的S_AXI_LITE接口:自动连接浮点数IP核的时钟引脚:添加BRAM控制器:最终的连线结果:修改新建的BRAM的容量为64KB:最终的地址分配方式:保存Block Design,然后生成Bitstream:Bitstream生成后,导出xsa文件:Vitis Platform工程重新导入xsa文件:修改C程序(helloworld.c)的代码:(这里面XPAR_BRAM_2_BASEADDR最好改成0xc0000000,因为生成的xparameters.h配置文件里面BRAM号可能有变化)/* * helloworld.c: simple test application * * This application configures UART 16550 to baud rate 9600. * PS7 UART (Zynq) is not initialized by this application, since * bootrom/bsp configures it to baud rate 115200 * * ------------------------------------------------ * | UART TYPE BAUD RATE | * ------------------------------------------------ * uartns550 9600 * uartlite Configurable only in HW design * ps7_uart 115200 (configured by bootrom/bsp) */ #include <stdio.h> #include <xaxidma.h> #include "platform.h" // DMA无法通过AXI Interconnect访问Microblaze本身的BRAM内存 // 只能访问挂接在AXI Interconnect上的内存 #define _countof(arr) (sizeof(arr) / sizeof(*(arr))) typedef struct { int numbers_in[40]; float numbers_out[40]; } BRAM2_Data; static BRAM2_Data *bram2_data = (BRAM2_Data *)XPAR_BRAM_2_BASEADDR; static XAxiDma xaxidma; int main(void) { int i, ret = 0; XAxiDma_Config *xaxidma_cfg; init_platform(); printf("Hello World\n"); printf("Successfully ran Hello World application\n"); // 初始化DMA xaxidma_cfg = XAxiDma_LookupConfig(XPAR_AXIDMA_0_DEVICE_ID); XAxiDma_CfgInitialize(&xaxidma, xaxidma_cfg); ret = XAxiDma_Selftest(&xaxidma); if (ret != XST_SUCCESS) { printf("XAxiDma_Selftest() failed! ret=%d\n", ret); goto err; } // 初始化DMA的输入数据 printf("numbers_in=%p, numbers_out=%p\n", bram2_data->numbers_in, bram2_data->numbers_out); for (i = 0; i < _countof(bram2_data->numbers_in); i++) { bram2_data->numbers_in[i] = 314 * (i + 1); if (i & 1) bram2_data->numbers_in[i] = -bram2_data->numbers_in[i]; } // DMA开始发送数据 (Length参数的单位为字节) ret = XAxiDma_SimpleTransfer(&xaxidma, (uintptr_t)bram2_data->numbers_in, sizeof(bram2_data->numbers_in), XAXIDMA_DMA_TO_DEVICE); if (ret != XST_SUCCESS) { printf("XAxiDma_SimpleTransfer(XAXIDMA_DMA_TO_DEVICE) failed! ret=%d\n", ret); goto err; } // DMA开始接收数据 ret = XAxiDma_SimpleTransfer(&xaxidma, (uintptr_t)bram2_data->numbers_out, sizeof(bram2_data->numbers_out), XAXIDMA_DEVICE_TO_DMA); if (ret != XST_SUCCESS) { printf("XAxiDma_SimpleTransfer(XAXIDMA_DEVICE_TO_DMA) failed! ret=%d\n", ret); goto err; } // 等待DMA发送完毕 i = 0; while (XAxiDma_Busy(&xaxidma, XAXIDMA_DMA_TO_DEVICE)) { i++; if (i == 200000) { // 必须确保DMA访问的内存是直接挂接在AXI Interconnect上的 // 否则这里会报DMA Decode Error的错误 (the address request points to an invalid address) printf("DMA Tx timeout! DMASR=0x%08lx\n", XAxiDma_ReadReg(xaxidma.RegBase + XAXIDMA_TX_OFFSET, XAXIDMA_SR_OFFSET)); goto err; } } printf("DMA Tx complete!\n"); // 等待DMA接收完毕 i = 0; while (XAxiDma_Busy(&xaxidma, XAXIDMA_DEVICE_TO_DMA)) { i++; if (i == 200000) { // floating-point IP核的配置里面一定要把A通道的tlast复选框勾选上, 使输入端和输出端都有tlast信号 // 否则s_axis_s2mm_tlast一直为0, DMA以为数据还没接收完, 就会报DMA Internal Error的错误 // (the incoming packet is bigger than what is specified in the DMA length register) printf("DMA Rx timeout! DMASR=0x%08lx\n", XAxiDma_ReadReg(xaxidma.RegBase + XAXIDMA_RX_OFFSET, XAXIDMA_SR_OFFSET)); goto err; } } printf("DMA Rx complete!\n"); err: for (i = 0; i < _countof(bram2_data->numbers_out); i++) printf("numbers_out[%d]=%f\n", i, bram2_data->numbers_out[i]); cleanup_platform(); return 0; }C程序的运行结果:接下来讲一下我们刚才禁用掉的Scatter Gather接口的用法。取消禁用后,之前的C代码就不能运行了。之前没有启用Scatter Gather的时候,我们一次只能提交一个DMA请求,等这个DMA请求的数据传输完毕后,我们才能提交下一个DMA传输请求。有了Scatter Gather接口,我们就可以一次性提交很多很多DMA请求,然后CPU去干其他的事情。这可以大大提高传输效率。除此以外,Scatter Gather还可以将多个位于不同内存地址的缓冲区合并成一个AXI4-Stream数据包传输。下面的示例演示了如何利用Scatter Gather功能批量收发3组数据包。启用了Scatter Gather后,DMA里面多出了一个M_AXI_SG接口,点击Run Connection Automation,连接到AXI Interconnect上:Vivado工程Generate Bitstream,然后导出xsa文件。回到Vitis后,必须把Platform工程删了重建,不然XPAR_AXI_DMA_0_INCLUDE_SG的值得不到更新。原有的C程序不再可用,修改一下程序代码: /* * helloworld.c: simple test application * * This application configures UART 16550 to baud rate 9600. * PS7 UART (Zynq) is not initialized by this application, since * bootrom/bsp configures it to baud rate 115200 * * ------------------------------------------------ * | UART TYPE BAUD RATE | * ------------------------------------------------ * uartns550 9600 * uartlite Configurable only in HW design * ps7_uart 115200 (configured by bootrom/bsp) */ #include <stdio.h> #include <xaxidma.h> #include "platform.h" /* Xilinx的官方例程:C:\Xilinx\Vitis\2020.1\data\embeddedsw\XilinxProcessorIPLib\drivers\axidma_v9_11\examples\xaxidma_example_sg_poll.c */ // DMA无法通过AXI Interconnect访问Microblaze本身的BRAM内存 // 只能访问挂接在AXI Interconnect上的内存 #define _countof(arr) (sizeof(arr) / sizeof(*(arr))) typedef struct { int numbers_in[40]; float numbers_out[40]; } BRAM2_Data; typedef struct { uint8_t txbuf[640]; uint8_t rxbuf[640]; } BRAM2_BdRingBuffer; static BRAM2_Data *bram2_data = (BRAM2_Data *)0xc0000000; static BRAM2_BdRingBuffer *bram2_bdringbuf = (BRAM2_BdRingBuffer *)0xc0008000; static XAxiDma xaxidma; int main(void) { int i, n, ret = 0; XAxiDma_Bd *bd, *p; XAxiDma_BdRing *txring, *rxring; XAxiDma_Config *cfg; init_platform(); printf("Hello World\n"); printf("Successfully ran Hello World application\n"); // 初始化DMA cfg = XAxiDma_LookupConfig(XPAR_AXIDMA_0_DEVICE_ID); XAxiDma_CfgInitialize(&xaxidma, cfg); ret = XAxiDma_Selftest(&xaxidma); if (ret != XST_SUCCESS) { printf("XAxiDma_Selftest() failed! ret=%d\n", ret); goto err; } if (!XAxiDma_HasSg(&xaxidma)) { printf("XPAR_AXI_DMA_0_INCLUDE_SG=%d\n", XPAR_AXI_DMA_0_INCLUDE_SG); printf("Please recreate and build Vitis platform project!\n"); goto err; } // 初始化DMA的输入数据 printf("[0] numbers_in=%p, numbers_out=%p\n", bram2_data[0].numbers_in, bram2_data[0].numbers_out); printf("[1] numbers_in=%p, numbers_out=%p\n", bram2_data[1].numbers_in, bram2_data[1].numbers_out); printf("[2] numbers_in=%p, numbers_out=%p\n", bram2_data[2].numbers_in, bram2_data[2].numbers_out); for (i = 0; i < _countof(bram2_data[0].numbers_in); i++) { bram2_data[0].numbers_in[i] = 314 * (i + 1); bram2_data[1].numbers_in[i] = -141 * (i + 1); bram2_data[2].numbers_in[i] = -2718 * (i + 1); if (i & 1) { bram2_data[0].numbers_in[i] = -bram2_data[0].numbers_in[i]; bram2_data[1].numbers_in[i] = -bram2_data[1].numbers_in[i]; bram2_data[2].numbers_in[i] = -bram2_data[2].numbers_in[i]; } } // 配置DMA发送描述符 txring = XAxiDma_GetTxRing(&xaxidma); n = XAxiDma_BdRingCntCalc(XAXIDMA_BD_MINIMUM_ALIGNMENT, sizeof(bram2_bdringbuf->txbuf)); ret = XAxiDma_BdRingCreate(txring, (uintptr_t)bram2_bdringbuf->txbuf, (uintptr_t)bram2_bdringbuf->txbuf, XAXIDMA_BD_MINIMUM_ALIGNMENT, n); if (ret != XST_SUCCESS) { printf("XAxiDma_BdRingCreate(txring) failed! ret=%d\n", ret); goto err; } printf("BdRing Tx count: %d\n", n); ret = XAxiDma_BdRingAlloc(txring, 3, &bd); if (ret != XST_SUCCESS) { printf("XAxiDma_BdRingAlloc(txring) failed! ret=%d\n", ret); goto err; } p = bd; for (i = 0; i < 3; i++) { XAxiDma_BdSetBufAddr(p, (uintptr_t)bram2_data[i].numbers_in); XAxiDma_BdSetLength(p, sizeof(bram2_data[i].numbers_in), txring->MaxTransferLen); XAxiDma_BdSetCtrl(p, XAXIDMA_BD_CTRL_TXSOF_MASK | XAXIDMA_BD_CTRL_TXEOF_MASK); XAxiDma_BdSetId(p, i); p = (XAxiDma_Bd *)XAxiDma_BdRingNext(txring, p); } ret = XAxiDma_BdRingToHw(txring, 3, bd); if (ret != XST_SUCCESS) { printf("XAxiDma_BdRingToHw(txring) failed! ret=%d\n", ret); goto err; } // 配置DMA接收描述符 rxring = XAxiDma_GetRxRing(&xaxidma); n = XAxiDma_BdRingCntCalc(XAXIDMA_BD_MINIMUM_ALIGNMENT, sizeof(bram2_bdringbuf->rxbuf)); ret = XAxiDma_BdRingCreate(rxring, (uintptr_t)bram2_bdringbuf->rxbuf, (uintptr_t)bram2_bdringbuf->rxbuf, XAXIDMA_BD_MINIMUM_ALIGNMENT, n); if (ret != XST_SUCCESS) { printf("XAxiDma_BdRingCreate(rxring) failed! ret=%d\n", ret); goto err; } printf("BdRing Rx count: %d\n", n); ret = XAxiDma_BdRingAlloc(rxring, 3, &bd); if (ret != XST_SUCCESS) { printf("XAxiDma_BdRingAlloc(rxring) failed! ret=%d\n", ret); goto err; } p = bd; for (i = 0; i < 3; i++) { XAxiDma_BdSetBufAddr(p, (uintptr_t)bram2_data[i].numbers_out); XAxiDma_BdSetLength(p, sizeof(bram2_data[i].numbers_out), rxring->MaxTransferLen); XAxiDma_BdSetCtrl(p, 0); XAxiDma_BdSetId(p, i); p = (XAxiDma_Bd *)XAxiDma_BdRingNext(rxring, p); } ret = XAxiDma_BdRingToHw(rxring, 3, bd); if (ret != XST_SUCCESS) { printf("XAxiDma_BdRingToHw(rxring) failed! ret=%d\n", ret); goto err; } // 开始发送数据 ret = XAxiDma_BdRingStart(txring); if (ret != XST_SUCCESS) { printf("XAxiDma_BdRingStart(txring) failed! ret=%d\n", ret); goto err; } // 开始接收数据 ret = XAxiDma_BdRingStart(rxring); if (ret != XST_SUCCESS) { printf("XAxiDma_BdRingStart(rxring) failed! ret=%d\n", ret); goto err; } // 等待收发结束 n = 0; while (n < 6) { // 检查发送是否结束 ret = XAxiDma_BdRingFromHw(txring, XAXIDMA_ALL_BDS, &bd); if (ret != 0) { n += ret; p = bd; for (i = 0; i < ret; i++) { printf("DMA Tx%lu Complete!\n", XAxiDma_BdGetId(p)); p = (XAxiDma_Bd *)XAxiDma_BdRingNext(txring, p); } ret = XAxiDma_BdRingFree(txring, ret, bd); if (ret != XST_SUCCESS) printf("XAxiDma_BdRingFree(txring) failed! ret=%d\n", ret); } // 检查接收是否结束 ret = XAxiDma_BdRingFromHw(rxring, XAXIDMA_ALL_BDS, &bd); if (ret != 0) { n += ret; p = bd; for (i = 0; i < ret; i++) { printf("DMA Rx%lu Complete!\n", XAxiDma_BdGetId(p)); p = (XAxiDma_Bd *)XAxiDma_BdRingNext(rxring, p); } ret = XAxiDma_BdRingFree(rxring, ret, bd); if (ret != XST_SUCCESS) printf("XAxiDma_BdRingFree(rxring) failed! ret=%d\n", ret); } } err: for (i = 0; i < _countof(bram2_data[0].numbers_out); i++) printf("numbers_out[%d]=%f,%f,%f\n", i, bram2_data[0].numbers_out[i], bram2_data[1].numbers_out[i], bram2_data[2].numbers_out[i]); cleanup_platform(); return 0; }
2025年01月09日
110 阅读
0 评论
1 点赞
SoC架构、通信举例、AHB、APB接口
典型的SoC包括以下部分• 一个或多个处理器内核,可以是MCU、MPU、数字信号处理器或专用指 令集处理器内核;• 存储器:可以是RAM、ROM、EEPROM或闪存;• 用于提供时间脉冲信号的振荡器和锁相环电路;• 由计数器和计时器、电源电路组成的外设;• 不同标准的连线接口,如USB、火线、以太网、通用异步收发;• 用于在数字信号和模拟信号之间转换的ADC/DAC;• 电压调理电路及稳压器。在外设内部,各组件通过芯片上的互联总线相互连接。ARM公司推出的 AMBA片上总线主要包括高性能系统总线AHB、通用系统总线ASB、外围互联总线APB、可拓展接口AXI。AHB主要针对高效率、高频宽及快速系统模块;ASB可用于某些高速且不必要使用AHB 总线的场合作为系统总线;APB 主要用于低速、低功率的外围,AXI在AMBA3.0协议中增加,可以用于ARM 和FPGA的高速数据交互。基于ARM的SoCCPU与SRAM通信CPU通过AHB对SRAM进行读写地址生成:CPU生成要访问的SRAM地址。信号发送:CPU通过AHB总线发送地址信息和读写请求信号。数据传输:读操作:CPU发送读请求后,SRAM将数据放在数据线上,CPU接收数据。写操作:CPU将数据放在数据线上,发送写请求,SRAM接收并存储数据。等待确认:操作完成后,SRAM可能通过AHB的握手信号通知CPU操作已完成。CPU与I2C接口通信CPU通过APB读取I2C的数据I2C是一种串行通信协议,通常用于低速外围设备与主控制器之间的通信。APB是一种用于连接低速外设的总线。以下是CPU通过APB读取I2C数据的一般步骤:初始化I2C外设:CPU通过APB发送指令初始化I2C外设,设置通信参数如时钟速率、地址等。发送启动条件:CPU通过I2C外设发送启动条件,开始通信。地址和读写位:CPU发送目标设备的I2C地址以及读写位(读操作为1,写操作为0)。数据传输:写操作:如果读写位为0,CPU通过APB发送数据到I2C外设,然后发送停止条件结束通信。读操作:如果读写位为1,CPU接收来自I2C外设的数据。数据接收:在读取操作中,CPU通过APB从I2C外设接收数据。发送停止条件:操作完成后,CPU发送停止条件,结束I2C通信。AHB从接口AHB主接口APB桥APB桥为AHB的一个从设备,但它在APB中是唯一的主设备,而APB中其它低速和低功率消耗的外围皆为APB桥的从设备。下图是 APB 桥的信号接口:APB桥将系统总线传送转换成APB方式的传送,它具备一些这些功能: 锁存地址,在传送过程中保持地址有效。锁存读写控制信号 对锁存的地址进行译码并产生选择信号PSELx,在传送过程中只有一个选择信号可以被激活。也就是选择出唯一一个APB从设备以进行读写动作. 写操作时: 负责将AHB送来的数据送上APB总线。 读操作时: 负责将APB的数据送上AHB系统总线。 产生一时序选通信号PENABLE作为数据传递时的启动信号APB Slave框图如前面所提及,APB 总线中除了 APB bridge 为 M 外,其它的外围皆为 S。因此,APB 从设备比 AHB从设备接口较为简单且非常具弹性: 例如 a. APB 少了仲裁器及复杂的译码电路,APB 进行写操作时,从设备可以决定: : 在 PCLK 上升沿触发, 且 PSEL 为高时锁存数据 :或在 PENABLE 上升沿, 且 PSEL 为高时锁存数据 b. PSELx,PADDR和PWRITE信号的组合可以决定哪个寄存器会被写操作更新。 c. 在读操作的时候,数据可以 PWRITE 在=0,PSELx 和 PENABLE=1 的时候被送到总线上,而PADDR 用于决定哪个寄存器会被读。AXI总线AXI的三种类型 AXI(AXI4-Full):用于高性能的存储器映射需求;(占用资源较多,传输大量数据) AXI4-Lite:简化版的AXI4接口;(占用资源较少,可用于低吞吐率的存储器的通信) AXI4-Stream:用于高速的流数据通信(不使用地址,适用于视频流)AXI的工作方式AXI4和AXI4-Lite包含5个独立的通道 读地址通道 读数据通道 写地址通道 写数据通道 写响应通道 AXI4:由于读写地址通道是分离的,所以支持双向同时传输;突发长度最大为256; AXI4-Lite:和AXI4比较类似,但是不支持突发传输; AXI4-Stream:和AXI4的写数据通道比较类似,只有一个单一的数据通道,突发长度不受限制;AXI传输对比Burst可以减少地址通道的交互,提升单笔传输的效率。Outstanding可以较少多笔传输之间的等待,提升多笔传输的效率。AMBA中AXI AHP APB比较
2024年08月23日
222 阅读
0 评论
1 点赞
2024-08-23
FPGA/数字IC之FIFO深度计算
FIFO的深度计算问题FIFO的最小深度问题,可以理解为两个模块之间的数据传输问题;只有在读取速度慢于写入速度的情况下,我们才需要一个FIFO,来暂时的寄存这些没有被读出去的数据;一个最主要的逻辑思想是:确定FIFO的大小,就是要找到在写入过程中没有被读取的数据的个数;即FIFO的深度等于未被读取的数据的数量。现在考虑一种实例,A时钟域数据发往B时钟域,将会出现以下几种情况:1情况1:fa>fb,且在读和写中都没有空闲周期;例如:写入频率fa = 80MHz,读取频率fb = 50MHz。突发长度即要写入的数据数目为120个。计算如下:写入一个数据需要的时间 = 1 / 80MHz = 12.5ns;写入突发事件中所有数据需要时间 = 120 * 12.5 = 1500ns;读取一个数所需时间 = 1 / 50MHz = 20ns;所有数据写入完成后使用1500ns,1500ns可以读出数据为 = 1500 / 20 = 75个。所以,要在FIFO中存储的剩余数据量为 120 - 75 = 45;故:设计的FIFO的最小深度为45!2情况2:fa>fb,两个连续的读写之间有一个时钟周期延迟;这种情况和情况1一样,仅仅是人为的制造了某种混乱;3️情况3:fa>fb,在读和写中都有空闲周期;例如:写入频率fa = 80MHz,读取频率fb = 50MHz。突发长度即要写入的数据数目为120个。两个连续的写之间空闲周期为1,两个连续的读之间空闲周期为3;计算如下:可以理解为每两个时钟周期写入一次数据,每四个时钟周期读出一个数据。写入一个数据需要的时间 = 2 (1 / 80MHz) = 2 12.5 = 25ns;写入突发事件中所有数据需要时间 = 120 * 25 = 3000ns;读取一个数所需时间 = 4 (1 / 50MHz) = 4 20 = 80ns;所有数据写入完成后使用3000ns,可以读出数据为 = 3000 / 80 = 37.5 ≈ 37个。所以,要在FIFO中存储的剩余数据量为120 - 37 = 83;故:设计的FIFO的最小深度为83!4️情况4:fa>fb,读写使能的占空比给定;例如:写入频率fa = 80MHz,读取频率fb = 50MHz。突发长度即要写入的数据数目为120个。写使能占空比为50%,读使能占空比为25%。这种情况和情况3一样,没有什么区别;【注】这里的情况2️和情况4仅仅是为了说明同一个问题可以通过不同的方式来提问。5️情况5:fa<fb,在读和写中都没有空闲周期;例如:写入频率fa = 30MHz,读取频率fb = 50MHz。突发长度即要写入的数据数目为120个。计算如下:在这种情况下,深度为1的FIFO就足够了,因为不会有任何数据的丢失,因为读比写快。6情况6:fa<fb,在读和写中都有空闲周期例如:写入频率fa = 30MHz,读取频率fb = 50MHz。突发长度即要写入的数据数目为120个。两个连续的写之间空闲周期为1,两个连续的读之间空闲周期为3;计算如下:可以理解为每两个时钟周期写入一次数据,每四个时钟周期读出一个数据。写入一个数据需要的时间 = 2 (1 / 30MHz) = 2 33.33 = 66.667ns;写入突发事件中所有数据需要时间 = 120 * 66.667 = 8000ns;读取一个数所需时间 = 4 (1 / 50MHz) = 4 20 = 80ns;所有数据写入完成后使用8000ns,可以读出数据为 8000 / 80 = 100个。所以,要在FIFO中存储的剩余数据量为120 - 100 = 20;故:设计的FIFO的最小深度为207情况7:fa=fb,在读和写中都没有空闲周期。例如:写入/读取频率fa = fb = 30MHz。突发长度即要写入的数据数目为120个。计算如下:如果clka和clkb之间没有相位差,则不需要FIFO;如果clka和clkb之间有一定的相位差,一个深度为“1”的FIFO就够了。8️情况8:fa=fb,在写和读中都有空闲周期例如:写入频率fa = 50MHz,读取频率fb = 50MHz。突发长度即要写入的数据数目为120个。两个连续的写之间空闲周期为1,两个连续的读之间空闲周期为3;计算如下:可以理解为每两个时钟周期写入一次数据,每四个时钟周期读出一个数据。写入一个数据需要的时间 = 2 (1 / 50MHz) = 2 20 = 40ns;写入突发事件中所有数据需要时间 = 120 * 40 = 4800ns;读取一个数所需时间 = 4 (1 / 50MHz) = 4 20 = 80ns;所有数据写入完成后使用4800ns,可以读出的数据为4800 / 80 = 60个。所以,要在FIFO中存储的剩余数据量为120 - 60 = 60;故:设计的FIFO的最小深度为60!情况9:数据速率如下所示;例如:写入80个数据,需要100个时钟周期,(写入的时钟随机);读出8个数据,需要10个时钟周期,(读取的时钟随机)。计算如下:①上述表明,书写频率等于读出频率②读和写都可以在任何随机的时刻发生,以下是一些可能性。在上述情况中,完成写入所需的周期如下:在FIFO设计中,为了更稳妥的考虑,我们选择最坏的情况下进行数据传输,以此来设计FIFO的深度,以避免数据丢失;最坏的情况即:写和读之间的数据率差异应该最大,因此对于写操作,考虑最大的数据速率,对于读操作,考虑最小的数据速率。故:考虑上述可能性中的第四种情况(即所谓的“背靠背”情况):计算如下:160个时钟周期内,写入160个数据;数据的读取率为8个数据/10个时钟周期;在160个时钟周期内可以读取的数据量为:160 * 8 / 10 = 128。因此,需要存储在FIFO中的剩余字节数为160 - 128 = 32。故:设计的FIFO的最小深度为32!1️0情况10:以不同的形式给出写入和读取的规则。例如:fa = fb / 4; Tenb = Ta * 100; enb的占空比为25%。计算如下:在这种情况下,就需要假设一些数值: 设:fb = 100MHz。则,fa = 1 / 4 * 100 = 25MHz。 则:Ta = 40ns,Tenb = 4000ns; 又因为写使能占空比25%。 则:写使能时间为4000ns / 4 = 1000ns。【注】此处认为fb为写时钟,因为只有这样,才能是写入的比读出的快,当然了,把fa当做写时钟,下面就没法算了,哈哈。突发长度 = 1000ns / 10ns = 100个; 在写入的1000ns内,可以读出 1000ns / 40ns = 25个; 所以,要在FIFO中存储的剩余数据量为100 - 25 = 75; 故:设计的FIFO的最小深度为75!例如假设两个异步时钟clk_a和clk_b,clk_a = 148.5M,clk_b = 140M。如图所示,clk_a时钟域中连续1920个16bit的数据通过data_valid标记,有效数据之后,紧接着720个无效数据时钟周期。请问,该数据通过异步fifo同步到clk_b时钟域,异步fifo的最小深度是多少?请写出计算过程。【解析】考虑情况9️中case-4案例,即所谓的“背靠背”情况:在a时钟域通过1920 * 2 = 3840个Ta传输3840个数据;Ta = 1 / 148.5MHz ≈ 6.734ns;Tb = 1 / 140MHz ≈ 7.143ns。3840 * 6.734 = 25858.56ns;在25858.56ns内,通过clk_b读取的个数为:25858.56 / 7.143 ≈ 3620个。 那么,剩余未读完的个数为3840 - 3620 = 220; 即FIFO的深度最小为220。【注】在此情况下,理解清楚规则非常之重要!对于读写同时进行的FIFO,有一个简便计算公式FIFO_Depth >= Burst_length -Burst_length (rd_clk/ wr_clk)(rd_rate)最后,需要注意的是,我们在本文通篇计算的都是最小FIFO深度,但是在实际应用中,尤其是异步FIFO的应用中,需要使用格雷码计数,这就要求FIFO的深度为2的整数次幂,否则格雷码计数到最大值跳变为0时,将出现多位变化的情况,不符合设计。异步FIFO深度不是2的整数次幂情况下,则可能需要特殊处理,需要使用别的编码方式了。
2024年08月23日
207 阅读
0 评论
0 点赞
通讯等不确定性条件下设计无人机之间的安全距离
引用文章:Q. Quan, R. Fu and K. -Y. Cai, "How Far Two UAVs Should Be subject to Communication Uncertainties," in IEEE Transactions on Intelligent Transportation Systems, doi: 10.1109/TITS.2022.3213555.简介近年来,无人机技术的快速发展使得低空空域中无人机的数量爆炸增长。碰撞避免作为无人机应用的关键技术,近年来人们已经进行了大量研究,设计出各种方法使得无人机在飞行过程中与障碍物保持一定安全距离(本文将该距离称为无人机的 安全半径)。然而, 在碰撞避免相关研究中,不确定性往往是难以处理的问题。针对不确定性,主流的方法包括对不确定性进行预测,以及在闭环控制中给予补偿。然而,以上方法很依赖于预测及补偿的精准设计,设计不当将有可能导致控制器失效。本研究受地面交通中车辆间安全距离启发,基于对通信不确定性和无人机控制器性能的假设,提出了 安全半径设计和控制器设计的原则 (本文称为 分离原理 )。进一步,安全半径具体通常通过经验预先设计,如果设计得过大或过小将分别影响无人机飞行的高效性及安全性。 如何根据无人机模型及通信不确定性确定其安全半径下界仍然是悬而未决的问题 。利用分离原理,本文研究了 设计阶段(无不确定性)和飞行阶段(受不确定性影响)的无人机安全半径设计 。最后,通过仿真和实验表明了所提方法的有效性。1. 研究问题本研究中,无人机与障碍物的运动模型均建模为多旋翼模型,换句话说,我们考虑的障碍物可以等效为另一架多旋翼飞行器。对于多旋翼飞行器而言,其可以获得自身以及障碍物的实时位置和速度。为简单描述起见,本文建模均为二维,类似的建模及分析方法可以扩展到三维情况。我们首先建立多旋翼的具体控制模型如下。其中多旋翼的位置和速度分别表示为 ,速度控制模型建立为一阶惯性环节,其时间常数 与无人机的机动性能相关,控制输入 为期望速度。在此基础上,本文定义一种新的滤波位置模型如下。多旋翼滤波位置的物理意义是根据多旋翼的当前位置、速度及机动能力,对其运动趋势预测。在此基础上,定义多旋翼的安全区域为以滤波位置为圆心的圆形区域,其半径称为安全半径 。对于障碍物,我们类似定义其滤波位置,以及以障碍物滤波位置为圆心的圆形障碍物区域,半径称为障碍物半径 。进一步,我们针对不确定性做了以下2点假设:(1)无人机和障碍物对自身的三维位置及三维速度估计均存在噪声;(2)无人机在获取障碍物状态信息时,存在通信延迟及丢包。该通信网络模型如下图所示。本研究中,丢包模型进一步建模为均值模型,且假设估计噪声、通信延迟、丢包均值模型误差均存在上界且上界已知。其数学定义如下:进一步,本文对无人机的控制器性能做出如下假设:(1)在无不确定性的理想状态下,无人机的控制器可以使无人机和障碍物之间的 真实距离 始终大于给定安全距离;(2)在存在以上不确定性的实际情况中,无人机的控制器可以使无人机和障碍物之间的 估计距离 始终保持给定安全距离。本研究的具体目标是在不确定性上界已知的条件下计算出无人机安全半径的下界。2. 分离原理介绍无人机在理想状态下设计的控制器中给定的安全半径值 ,在实际含有不确定性的环境中将转化为估计安全半径 ;直观上理解,将含有不确定性的无人机与障碍物之间的估计位置误差代替真实位置误差作为反馈时,控制器将难以维持给定的安全半径。本研究中, 分离原理具体研究的是不确定性在满足什么条件时无人机的控制器设计和安全半径设计过程可分离 ,也就是估计安全半径和安全半径相等, 成立。分离原理具体数学描述如下:分离原理中提出的三个条件包括一个充分必要条件(i)以及两个充分条件(ii)和(iii)。其中,条件(i)为理论推导,在实际中难以得到验证,通常可通过条件(ii)和(iii)进行验证。条件(ii)对无人机速度的限制较宽,该式在障碍物主动躲避无人机的条件下容易达成。反之,条件(iii)对无人机速度的限制较苛刻,要求无人机速度严格大于障碍物速度,适用于在障碍物不主动躲避无人机的情况下。以上两种情况的区别如下图所示。3. 安全半径设计根据上述分离原理的提出,在分离原理满足的前提下,基于无人机的安全半径模型和通信网络模型,可以进一步设计 合适大小的无人机安全半径 。如前文所述,安全半径设计过大会增加环境的冗余度,设计过小则无法保证飞行过程的安全。在本研究中,给出了在理想情况下以及实际情况下安全半径的下界,分别如下:4. 仿真及实验验证仿真及实验验证在仿真中,我们针对单障碍物、多非合作障碍物、多合作障碍物设计了三种场景来验证我们方法的有效性。在实验中,我们使用DJI tello无人机对以上情况进行测试,结果与我们的理论一致。实验视频:https://youtu.be/LkSDPFGa_1E论文地址https://rfly.buaa.edu.cn/pdfs/2022/How_far_two_UAVs_should_be_subject_to_communication_uncertainties.pdf
2024年07月16日
181 阅读
0 评论
1 点赞
2024-07-05
Linux之不使用命令删除文件中的第N行
1.问题描述设计一个程序,通过命令行参数接收一个文件名 filename.txt (纯文本文件)和一个整型数字 n,实现从 filename.txt 中删除第 n 行数据。2. 解题思路: (1) 借助临时文件: 将文件逐行读取,跳过要删除的行,并将其写入临时文件,然后删除源文件,重命名临时文件为源文件,完成删除指定行数据。 (2) 不借助临时文件: 将文件以读写方式打开,读取到要删除行后,通过移动文件指针将文件后面所有行前移一行,但是最后一行会重复,可以通过截断文件操作完成在源文件上删除指定行数据。 (3) 通过sed 或 awk 删除文件指定行3. 代码实现:(1) 通过 fopen 打开文件借助临时文件删除指定行数据#filename:ques_15a.c #include <stdio.h> // 包含标准输入输出库 #include <stdlib.h> // 包含标准库函数,比如exit int main(int argc, char *argv[]) { // 检查命令行参数的数量是否正确 if (argc != 3) { printf("Usage: ./a.out filename num\n"); exit(EXIT_FAILURE); // 如果不正确,打印用法信息并退出 } char buf[4096]; // 定义一个足够大的字符数组用于读取文件行 int linenum = atoi(argv[2]); // 将命令行参数中的行号转换为整数 FILE *fp = fopen(argv[1], "r"); // 尝试以只读模式打开源文件 FILE *fpt = fopen("temp.txt", "w"); // 创建一个临时文件用于写入 // 如果源文件无法打开,打印错误信息并退出 if (!fp) { printf("File %s not exist!\n", argv[1]); exit(EXIT_FAILURE); } // 检查是否有权限修改源文件 char str[100]; sprintf(str, "%s%s", "test -w ", argv[1]); if (system(str)) { // 如果没有写权限 printf("Can't modify file %s, permission denied!\n", argv[1]); exit(EXIT_FAILURE); // 打印错误信息并退出 } int total_line = 0; // 初始化文件总行数计数器 while (fgets(buf, sizeof(buf), fp)) { // 读取文件的每一行 total_line++; } fseek(fp, 0, SEEK_SET); // 将文件指针重置到文件的开头 // 如果要删除的行数大于文件的总行数,打印错误信息并退出 if (linenum > total_line) { printf("%d is greater than total_line!\n", linenum); exit(EXIT_FAILURE); } int i = 0; // 初始化当前行计数器 while (fgets(buf, sizeof(buf), fp)) { // 再次读取文件的每一行 i++; // 当前行数加1 if (i != linenum) { // 如果当前行不是要删除的行 fputs(buf, fpt); // 将当前行写入临时文件 } } remove(argv[1]); // 删除原始文件 rename("temp.txt", argv[1]); // 将临时文件重命名为原始文件名 // 关闭文件指针 fclose(fp); fclose(fpt); return 0; // 程序正常退出 }(2) 通过 Linux 系统调用 open 打开文件,需要自定义读取一行的函数,不借助临时文件删除指定行数据# filename:ques_15b.c #include <stdio.h> // 包含标准输入输出库 #include <stdlib.h> // 包含标准库函数,比如atoi和exit #include <string.h> // 包含字符串操作函数,比如strlen #include <fcntl.h> // 包含文件控制的定义 #include <sys/stat.h> // 包含文件状态的定义 #include <sys/types.h>// 包含各种数据类型 #include <unistd.h> // 包含UNIX标准函数定义 // 函数声明:读取一行文件内容到缓冲区 int readline(int fd, char *buf) { int t = 0; // 用于记录读取的字符数 // 循环读取直到遇到换行符 for (; ;) { read(fd, &buf[t], 1); // 从文件描述符fd读取一个字符到buf t++; // 增加读取的字符数 if (buf[t-1] == '\n') { // 如果读取到换行符 break; // 退出循环 } } return t; // 返回读取的字符数 } // 函数声明:获取文件的大小和行数 int get_file_info(int fd, int *size) { int num = 0; // 记录行数 char ch; // 临时变量用于存储读取的字符 // 循环读取直到文件结束 while (read(fd, &ch, 1) > 0) { (*size)++; // 文件大小加1 if (ch == '\n') { // 如果读取到换行符 num++; // 行数加1 } } return num; // 返回行数 } int main(int argc, char *argv[]) { // 检查命令行参数数量 if (argc != 3) { printf("Usage: ./a.out filename num\n"); exit(EXIT_FAILURE); // 参数不正确时退出 } int fd; // 文件描述符 char buf[4096]; // 缓冲区 int linenum = atoi(argv[2]); // 将命令行参数转换为整数 // 尝试以读写模式打开文件 fd = open(argv[1], O_RDWR); if (fd < 0) { printf("Can't open file %s, file not exist or permission denied!\n", argv[1]); exit(EXIT_FAILURE); // 打开失败时退出 } int size = 0; // 文件大小 // 获取文件的行数和大小 int total_line = get_file_info(fd, &size); // 如果要删除的行数大于文件总行数,退出 if (linenum > total_line) { printf("%d is greater than total_line!\n", linenum); exit(EXIT_FAILURE); } int s = 0; // 要删除行的大小 int t = 0; // 当前行的大小 int i = 0; // 当前行数 lseek(fd, 0, SEEK_SET); // 将文件指针移到文件头 // 循环读取文件,直到文件结束 while (read(fd, &buf[0], 1) > 0) { lseek(fd, -1, SEEK_CUR); // 回退一个字符 memset(buf, 0, sizeof(buf)); // 清空缓冲区 readline(fd, buf); // 读取一行到缓冲区 i++; // 行数加1 t = strlen(buf); // 当前行的大小 // 如果当前行是目标行,记录其大小 if (i == linenum) { s = t; } // 如果当前行在目标行之后,将该行前移 if (i > linenum) { lseek(fd, -(s+t), SEEK_CUR); // 移动文件指针 write(fd, buf, strlen(buf)); // 写入当前行 lseek(fd, s, SEEK_CUR); // 移动文件指针 } } ftruncate(fd, size-s); // 截断文件,删除指定行 close(fd); // 关闭文件描述符 return 0; // 正常退出 }(3) 通过 fopen 打开文件,不借助临时文件删除指定行数据# filename:ques_15b.c #include <stdio.h> // 包含标准输入输出库 #include <stdlib.h> // 包含标准库函数,如atoi和exit #include <string.h> // 包含字符串处理函数,如strlen #include <math.h> // 包含数学函数,虽然在这个程序中没有使用 #include <unistd.h> // 包含UNIX标准函数,如truncate int main(int argc, char *argv[]) { // 检查命令行参数个数是否正确 if (argc != 3) { printf("Usage: ./a.out filename num\n"); exit(EXIT_FAILURE); // 参数不正确时退出程序 } int linenum = atoi(argv[2]); // 将命令行中指定的行号转换为整数 char buf[4096]; // 定义缓冲区,用于读取文件内容 // 尝试以读写模式打开文件 FILE *fp = fopen(argv[1], "r+"); // 如果文件无法打开,打印错误信息并退出程序 if (!fp) { printf("Can't open file %s, file not exist or permission denied!\n", argv[1]); exit(EXIT_FAILURE); } int total_line = 0; // 记录文件的总行数 int size = 0; // 记录文件的总大小 // 循环读取文件直到文件末尾,计算总行数和总大小 while (fgets(buf, sizeof(buf), fp)) { size += strlen(buf); // 累加每行的长度 total_line++; // 行数加1 } // 如果要删除的行数大于文件的总行数,打印错误信息并退出程序 if (linenum > total_line) { printf("%d is greater than total_line!\n", linenum); exit(EXIT_FAILURE); } int s = 0; // 记录要删除的行的大小 int t = 0; // 记录当前读取行的大小 int i = 0; // 记录当前行数 fseek(fp, 0L, SEEK_SET); // 将文件指针重置到文件开头 // 再次循环读取文件,准备删除指定行 while (fgets(buf, sizeof(buf), fp)) { i++; // 当前行数加1 t = strlen(buf); // 当前行的长度 // 如果当前行是要删除的行,记录其大小 if (i == linenum) { s = t; } // 如果当前行在要删除的行之后,将其前移 if (i > linenum) { fseek(fp, -(s+t), SEEK_CUR); // 将文件指针移动到正确的位置 fputs(buf, fp); // 写入当前行 fseek(fp, s, SEEK_CUR); // 将文件指针向前移动s个字节 } } // 截断文件,删除指定行 truncate(argv[1], size-s); // 关闭文件指针 fclose(fp); return 0; // 正常退出程序 }(4) 这三个删除文件指定行的函数都需借助临时文件完成
2024年07月05日
113 阅读
0 评论
1 点赞
2024-06-27
FPGA/数字IC-常考八股
各种时间概念建立时间(setup time)是指在触发器的时钟信号上升沿到来以前,数据稳定不变的时间,如果建立时间不够,数据将不能在这个时钟上升沿被打入触发器。保持时间(hold time)是指在触发器的时钟信号上升沿到来以后,数据稳定不变的时间,如果保持时间不够,数据同样不能被打出触发器。传输延时(transmission delay)数据相对于时钟上升沿tc-q后从触发器输出至Q端,则tc-q称为寄存器的传输延时恢复时间(recovery time)原本有效的复位信号释放后,与紧跟其后的第一个时钟上升沿之间的最小时间。清除时间(removal time)时钟信号的上升沿,与紧跟其后异步复位信号从有效到无效的最小时间。亚稳态的产生在FPGA系统中,如果数据传输中不满足触发器的 Tsu 和 Th 不满足,或者复位过程中复位信号的释放相对于有效时钟沿的恢复时间(recovery time)不满足,就可能产生亚稳态,此时触发器输出端Q在有效时钟沿之后比较长的一段时间处于不确定的状态,在这段时间里Q端在0和1之间处于振荡状态,而不是等于数据输入端D的值。这段时间称为决断时间(resolution time)。经过resolution time之后Q端将稳定到0或1上,但是稳定到0或者1,是随机的,与输入没有必然的关系。如何防止亚稳态?亚稳态是指触发器无法在某个规定时间段内达到一个可确认的状态。当一个触发器进入亚稳态时,既无法预测该单元的输出电平,也无法预测何时输出才能稳定在某个正确的电平上。在这个稳定期间,触发器输出一些中间级电平,或者可能处于振荡状态,并且这种无用的输出电平可以沿信号通道上的各个触发器级联式传播下去。解决方法:1 降低系统时钟频率2 用反应更快的FF3 引入同步机制,防止亚稳态传播(加两级D触发器)。4 改善时钟质量,用边沿变化快速的时钟信号多时域设计中,如何处理信号跨时域?不同的时钟域之间信号通信时需要进行同步处理,这样可以防止新时钟域中第一级触发器的亚稳态信号对下级逻辑造成影响。解决方法:1 信号跨时钟域同步:当单个信号跨时钟域时,可以采用两级触发器来同步;2 数据或地址总线跨时钟域: 可以采用异步 FIFO 来实现时钟同步;3 第三种方法就是采用握手信号。什么是竞争与冒险现象?怎样判断?如何消除?在组合电路中, 门电路两个输入信号同时向相反的逻辑电平跳变称为竞争;由于竞争而在电路的输出端可能产生尖峰脉冲的现象称为竞争冒险。 (也就是由于竞争产生的毛刺叫做冒险)。判断方法:•代数法: 如果布尔式中有相反的信号则可能产生竞争和冒险现象,即逻辑函数在一定条件下可以化简成 Y=A+A’或 Y=AA’则可以判断存在竞争冒险现象(只是一个变量变化的情况) ;•卡诺图:有两个相切的卡诺圈并且相切处没有被其他卡诺圈包围,就有可能出现竞争冒险;•实验法+观察法:示波器观测;解决方法:1:加滤波电容,消除毛刺的影响;2:加选通信号,避开毛刺;3:增加冗余项消除逻辑冒险。锁存器(Latch)和寄存器(Flip-Flop)概念和区别电平敏感的存储器件称为锁存器。可分为高电平锁存器和低电平锁存器,用于不同时钟之间的信号同步。有交叉耦合的门构成的双稳态的存储原件称为触发器(寄存器)。分为上升沿触发和下降沿触发。可以认为是两个不同电平敏感的锁存器串连而成。前一个锁存器决定了触发器的建立时间,后一个锁存器则决定了保持时间。latch 是电平触发, register 是边沿触发。 register 在同一时钟边沿触发下动作,符合同步电路的设计思想,而 latch 则属于异步电路设计,往往会导致时序分析困难,不适当的应用 latch 则会大量浪费芯片资源。`timescale`timescale 1ns / 1ps,含义为:时延单位为1ns,时延精度为1ps在仿真文件里面需要写,例如`timescale 10 ns / 1 ns //单位10ns,精度1ns module test; reg set; initial begin #1 set = 0; //1*10 ns = 10ns #10 set = 1; //10*10ns = 100ns end endmodule阻塞赋值&非阻塞赋值阻塞赋值“=”属于顺序执行,即下一条语句执行前,当前语句一定会执行完毕非阻塞赋值“<=”属于并行执行语句,即下一条语句的执行和当前语句的执行是同时进行的在设计电路时,always 时序逻辑块中多用非阻塞赋值,always 组合逻辑块中多用阻塞赋值;在仿真电路时,initial 块中一般多用阻塞赋值不要在一个过程结构中混合使用阻塞赋值与非阻塞赋值流水线流水线设计是verilog设计中基本功之一,是对组合逻辑系统的分割,并在各个部分之间插入寄存器,并暂存中间数据的方法。流水线操作的目的是把一个大操作分解为若干小操作,因为每一步操作变小了,所以时间更短,频率更快(面积换时间)。流水线在理各个阶段都需要增加寄存器保存中间计算状态,而且多条指令并行执行会导致功耗增加,硬件复杂度增加函数&任务函数返回一个值,而任务则不返回值函数至少要有一个输入变量,而任务可以没有或有多个任何类型的变量。函数只能与主模块共用同一个仿真时间单位,而任务可以定义自己的仿真时间单位。函数不能启动任务,而任务能够启动其他任务和函数。不可综合verilog语句(1)initial:只能在test bench中使用,不能综合。(2)events:event在同步test bench时更有用,不能综合。(3)real:不支持real数据类型的综合。(4)time:不支持time数据类型的综合。(5)force 和release:不支持force和release的综合。(6)assign 和deassign:不支持对reg 数据类型的assign或deassign进行综合,支持对wire数据类型的assign或deassign进行综合。(7) fork join:不可综合,可以使用非块语句达到同样的效果。(8) primitives:支持门级原语的综合,不支持非门级原语的综合。(9) table:不支持UDP 和table的综合。(10) 敏感列表里同时带有posedge和negedge如:always @(posedge clk or negedge clk) begin...end这个always块不可综合。(11) 同一个reg变量被多个always块驱动(12) 延时以#开头的延时不可综合成硬件电路延时,综合工具会忽略所有延时代码,但不会报错。如:a=#10 b;这里的#10是用于仿真时的延时,在综合的时候综合工具会忽略它。也就是说,在综合的时候上式等同于a=b;(13) 与X、Z的比较可能会有人喜欢在条件表达式中把数据和X(或Z)进行比较,殊不知这是不可综合的,综合工具同样会忽略。所以要确保信号只有两个状态:0或1。功耗(Power)等于处理器上的电流值与电压值的乘积,可以反应处理器最大负荷运行、满载工作时电流热效应造成热量释放的大小指标,单位为瓦(W)。总功耗由静态和动态两部分组成PGA芯片内有两种存储器资源:一种叫block ram,另一种是由LUT配置成的内部存储器(也就是分布式ram)。block ram由一定数量固定大小的存储块构成的,使用block ram,不占用额外的逻辑资源,并且速度快。但是使用的时钟消耗block ram的资源是其大小的整数倍。FPGA时钟结构:全局时钟、局部时钟和I/O时钟MMCM、PLL即时钟管理模块,用来消除时钟的延迟、抖动以及产生各种不同频率的时钟。MMCM相对PLL的优势就是相位可动态调整,但PLL占用的面积更小。一个属于单片机STM32(顺序处理) ,一个属于可编程阵列FPGA(并行处理)RAM随机读取存储器,掉电丢失 ROM只读存储器 Flash memory 是“闪存” EEPROM可以一次只擦除一个字节单端口RAM:只有一组地址线数据线 伪双口RAM,一个端口只读,一个端口只写 同步复位&异步复位同步复位:只有在时钟沿到来时复位信号才起作用,复位信号持续的时间应该超过一个时钟周期才能保证系统复位异步复位:复位信号容易受到毛刺的影响。复位结束时刻恰在亚稳态窗口内时异步复位同步释放最好当异步复位有效时,复位信号立刻复位信号释放时,会在时钟上升沿来临时,才可以恢复一些小计算总结%0表示用最少位数表示进行取模运算%时,结果值的符号位采用模运算式里第一个操作数的符号位对于有符号数来说:若符号位为1,使用>>>,高位补1;若符号位为0,使用>>>,高位补0;对于无符号数来说,无论最高位是什么,使用>>>,高位都补0。标识符可以是任意一组数字、字母、$符合和下划线的组合,但第一个字符必须是字母或者下划线
2024年06月27日
333 阅读
0 评论
2 点赞
2024-06-27
基础概念:中断、任务、进程、线程、RTOS、Linux
中断、任务、进程和线程是计算机科学和操作系统中的基本概念,它们在多任务操作和资源管理中扮演着重要的角色。下面是这些概念的简要解释以及它们之间的区别:中断中断是硬件或软件发出的信号,用来通知CPU暂停当前的工作,转而去执行一个特殊的程序(中断处理程序)。中断可以是外部的,比如来自硬件设备的信号,或者是内部的,比如软件生成的信号。中断机制允许操作系统响应外部事件,如用户输入或硬件状态变化。任务在某些操作系统中,任务是一个抽象概念,用来表示一个执行单元,它可以是一个进程或者线程。任务通常指的是需要操作系统调度和资源管理的执行流。进程进程是操作系统分配资源和调度的基本单位。每个进程都有自己的地址空间、数据栈以及其他用于跟踪进程状态和执行的资源。进程可以包含一个或多个线程。线程线程是进程中的一个实体,是CPU调度和执行的单位。线程共享所属进程的资源,但拥有自己的堆栈和程序计数器。线程比进程更轻量级,创建和切换的开销更小。它们之间的区别资源分配:进程是资源分配的最小单位,线程则不是。执行:进程是执行程序的实例,线程是进程中的实际执行流。地址空间:进程有独立的地址空间,线程共享进程的地址空间。创建开销:进程的创建开销通常大于线程。通信:线程间可以通过共享内存进行通信,进程间通信需要使用IPC(进程间通信)机制。RTOS和Linux的区别RTOS(实时操作系统)和Linux是两种不同类型的操作系统,它们在设计目标和特性上有所区别:设计目标:RTOS:设计用于需要快速、可预测响应的系统,如嵌入式系统、工业控制等。Linux:是一个通用操作系统,主要用于桌面、服务器、移动设备等。实时性:RTOS:提供确定的响应时间,可以保证任务在指定的时间内得到处理。Linux:虽然可以配置为实时系统,但通常不具备RTOS的严格实时性。调度策略:RTOS:通常使用基于优先级的抢占式调度。Linux:使用完全公平调度器(CFS)进行调度,可以配置为实时调度。内存管理:RTOS:通常有简单的内存管理机制,适合资源受限的环境。Linux:具有复杂的内存管理机制,支持虚拟内存和内存共享。应用场景:RTOS:适用于对实时性要求高、资源受限的场合。Linux:适用于需要高度灵活性和扩展性的场合。开源和社区支持:RTOS:有些RTOS是开源的,但社区规模通常小于Linux。Linux:是一个开源项目,拥有庞大的社区和开发者支持。总的来说,RTOS和Linux各有优势,选择哪个系统取决于应用的具体需求。RTOS适合对实时性要求极高的场景,而Linux适合需要高度灵活性和功能丰富的环境。
2024年06月27日
174 阅读
0 评论
1 点赞
Verilog语言实现读写txt文件方法
随着项目难度递增,例如AI芯片设计,不免需要验证Verilog的功能性,那么输入可以是测试集.txt,输出为预测结果/精确率.txt。下面讲解一下Verilog如何实现读写txt文件一、读txt文件1、准备一个txt文件 MATLAB上生成的txt文件的格式为每行一个数据,与coe文件不同的是,数据之间没有逗号。由于verilog的读txt的系统函数仅仅能识别十六进制和二进制数据,一般情况下,将数据转换为十六进制数比较方便。当数据为有符号数时,可以采用下面的方式生成txt文件:% 生成20个随机的int16类型数据 data_signed = int16(randi([-32768, 32767], 1, 20)); % 打开文件准备写入 fid = fopen('data_signed.txt', 'w'); % 将data_signed数组中的数据转换为无符号整数并以16进制格式写入文件 fprintf(fid, '%04x\r\n', typecast(data_signed, 'uint16')); % 关闭文件 fclose(fid);其中,typecast(int16(data_signed),‘uint16’) 可以将数据转变为补码形式。同时,需要注意的是,在换行中,需要采用’\r\n‘,不能仅仅使用’\r’ 2、在testbench上读写 读txt文件时,一般选择先将txt中的数据置入寄存器memory中,然后按照地址读出memory中的数据。reg [15:0] mem [0:1023]; reg [9:0] addr ; reg [11:0]data_out ; initial begin $readmemh("E:/self-study/VIVADO_workspace/prj/data_signed.txt",mem); addr = 10'd0; end always #10 begin data_out = mem[addr][11:0]; addr = addr + 10'd1; end需要注意的是,txt文件的地址不能出错,另外,注意地址分隔符需要用’/‘,windos系统中的’\‘需要对应改过来才能使用。二、写txt文件integer handle; initial begin handle = $fopen("E:/self-study/VIVADO_workspace/prj/data_out.txt"); end always@(posedge sys_clk) begin if(data_in_valid) begin $fdisplay(handle,"%d",data_out); end end利用initial函数初始化handle的值,另外,地址分隔符同样要改回来。如果仿真之后,txt文件中并没有内容,可以尝试刷新一下txt文件。如果还没有内容出现,则就是txt文件没有关闭造成的,解决方法有两种:①直接关闭xsim仿真程序,此时txt文件自动关闭,内容出现。②使用$fclose函数关闭文件,如果使能变量aagc_in_valid是一段高电平信号,则可以检测该信号的下降沿,在此时关闭文件即可: reg [2:0] valid_reg; always@(posedge sys_clk) begin valid_reg <= ; if(valid_reg == 3'b110) begin $fclose(handle); end end甚至于,直接写一个计数器,也是可以的。$display语句的使用在功能仿真阶段调试程序时,$display是很有用的一段程序,它和java,c语言中的打印语句使用方法是相似的,可以把变量和语句运行状况打在TCL log的窗口上,供调试者知晓代码运行情况。$display("here is a debugging point."); $display("%d", data_in);demo案例module wr_txt_tb; reg [15:0] mem [0:1023]; reg [9:0] addr; reg [15:0] data_out; initial begin $readmemh("E:/Downloads/data_signed.txt", mem); addr = 10'd0; end always #10 begin if (addr < 10'd20) begin // 假设您只想读取前20个数据 data_out = mem[addr][15:0]; addr = addr + 10'd1; $display("here is a debugging point."); $display("%h", data_out); // 以十六进制形式打印 end else begin $stop; // 结束仿真 end end endmodule
2024年04月14日
442 阅读
0 评论
1 点赞
2024-03-12
C语言编译的四个步骤
编译一个C语言程序是一个多阶段的过程。从总体上看,这个过程可以分成四个独立的阶段。预处理、编译、汇编和连接。在这篇文章中,我将逐一介绍编译下列C程序的四个阶段。/* * "Hello, World!": A classic. */ #include <stdio.h> int main(void) { puts("Hello, World!"); return 0; }预处理编译的第一个阶段称为预处理。在这个阶段,以#字符开头的行被预处理器解释为预处理器命令。这些命令形成一种简单的宏语言,有自己的语法和语义。这种语言通过提供内联文件、定义宏和有条件地省略代码的功能,来减少源代码的重复性。在解释命令之前,预处理器会做一些初始处理。这包括连接续行(以 \ 结尾的行)和剥离注释。要打印预处理阶段的结果,请向gcc传递-E选项。 gcc -E hello_world.c 考虑到上面的 "Hello, World!"的例子,预处理器将产生stdio.h头文件的内容和hello_world.c文件的内容,并将其前面的注释剥离出来。编译编译的第二个阶段被称为编译,令人困惑。在这个阶段,预处理过的代码被翻译成目标处理器架构特有的汇编指令。这些形成了一种中间的人类可读语言。这一步骤的存在允许C代码包含内联汇编指令,并允许使用不同的汇编器。一些编译器也支持使用集成汇编器,在这种情况下,编译阶段直接生成机器代码,避免了生成中间汇编指令和调用汇编器的开销。要保存编译阶段的结果,可以向gcc传递-S选项。 gcc -S hello_world.c 这将创建一个名为hello_world.s的文件,包含生成的汇编指令。汇编在这个阶段,汇编器被用来将汇编指令翻译成目标代码。输出包括目标处理器要运行的实际指令。要保存汇编阶段的结果,请向gcc传递-c选项。 gcc -c hello_world.c运行上述命令将创建一个名为hello_world.o的文件,包含程序的目标代码。这个文件的内容是二进制格式,可以用运行命令hexdump或od来检查。hexdump hello_world.o od -c hello_world.oLinux中的od(octal dump)命令用于转换输入内容为八进制。Hexdump是一个命令行工具,用于以各种方式显示文件的原始内容,包括十六进制,可用于Linux、FreeBDS、OS X和其他平台。Hexdump不是传统Unix系统或GNU命令的一部分。链接汇编阶段产生的目标代码是由处理器能够理解的机器指令组成的,但程序的某些部分是不符合顺序的或缺失的。为了产生一个可执行的程序,现有的部分必须被重新排列,并把缺失的部分补上。这个过程被称为链接。链接器将安排目标代码的各个部分,使某些部分的功能能够成功地调用其他部分的功能。它还将添加包含程序所使用的库函数指令的片段。在 "Hello,world!"程序的例子中,链接器将添加puts函数的对象代码。这一阶段的结果是最终的可执行程序。当不使用选项运行时,gcc 将把这个文件命名为 a.out。如果要给文件命名,请向 gcc 传递 -o 选项。gcc -o hello_world hello_world.c
2024年03月12日
147 阅读
0 评论
0 点赞
Socket通信-Linux系统中C语言实现TCP/UDP图片和文件传输
TCP实现传输控制协议(TCP,Transmission Control Protocol) 是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK,并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接,TCP使用的流量控制协议是可变大小的滑动窗口协议。1.服务端基于TCP协议的socket的server端程序编程步骤:1、建立socket ,使用socket()2、绑定socket ,使用bind()3、打开listening socket,使用listen()4、等待client连接请求,使用accept()5、收到连接请求,确定连接成功后,使用输入,输出函数recv(),send()与client端互传信息6、关闭socket,使用close()服务端代码server.c/*server.c*/ #include<netinet/in.h> #include<sys/types.h> #include<sys/socket.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #define SERVER_PORT 5678 //端口号 #define LENGTH_OF_LISTEN_QUEUE 20 #define BUFFER_SIZE 1024 #define FILE_NAME_MAX_SIZE 512 int main(int argc, char **argv) { // 设置一个socket地址结构server_addr,代表服务器ip地址和端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htons(INADDR_ANY); server_addr.sin_port = htons(SERVER_PORT); // 创建用于流协议(TCP)socket,用server_socket代表服务器向客户端提供服务的接口 int server_socket = socket(PF_INET, SOCK_STREAM, 0); if (server_socket < 0) { printf("Create Socket Failed!\n"); exit(1); } else printf("Create Socket Success.\n"); // 把socket和socket地址结构绑定 if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr))) { printf("Server Bind Port: %d Failed!\n", SERVER_PORT); exit(1); } else printf("Client Bind Port Success.\n"); // server_socket用于监听 if (listen(server_socket, LENGTH_OF_LISTEN_QUEUE)) { printf("Server Listen Failed!\n"); exit(1); } else printf("Listening....\n"); // 服务器始终监听 while(1) { // 定义客户端的socket地址结构client_addr,当收到来自客户端的请求后,调用accept // 接受此请求,同时将client端的地址和端口等信息写入client_addr中 struct sockaddr_in client_addr; socklen_t length = sizeof(client_addr); // 接受一个从client端到达server端的连接请求,将客户端的信息保存在client_addr中 // 如果没有连接请求,则一直等待直到有连接请求为止,这是accept函数的特性 // accpet返回一个新的socket,这个socket用来与此次连接到server的client进行通信 // 这里的new_server_socket代表了这个通信通道 int new_server_socket = accept(server_socket, (struct sockaddr*)&client_addr, &length); if (new_server_socket < 0) { printf("Server Accept Failed!\n"); break; } else printf("Server Accept Success.\n"); char buffer[BUFFER_SIZE]; bzero(buffer, sizeof(buffer)); length = recv(new_server_socket, buffer, BUFFER_SIZE, 0); if (length < 0) { printf("Server Recieve Data Failed!\n"); break; } else printf("Server Recieve Data Success.\n"); char file_name[FILE_NAME_MAX_SIZE + 1]; bzero(file_name, sizeof(file_name)); strncpy(file_name, buffer, strlen(buffer) > FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE : strlen(buffer)); FILE *fp = fopen(file_name, "r"); //获取文件操作符 if (fp == NULL) { printf("File:\t%s Not Found!\n", file_name); } else { bzero(buffer, BUFFER_SIZE); int file_block_length = 0; while( (file_block_length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0) { // 发送buffer中的字符串到new_server_socket,实际上就是发送给客户端 if (send(new_server_socket, buffer, file_block_length, 0) < 0) { printf("Send File:\t%s Failed!\n", file_name); break; } bzero(buffer, sizeof(buffer)); } fclose(fp); printf("File:\t%s Transfer Finished!\n", file_name); } close(new_server_socket); } close(server_socket); return 0; } 2.客户端基于TCP协议的socket的Client程序编程步骤:1、建立socket,使用socket()2、通知server请求连接,使用connect()3、若连接成功,就使用输入输出函数recv(),send()与server互传信息4、关闭socket,使用close()客户端代码client.c/*client.c*/ #include<netinet/in.h> // for sockaddr_in #include<sys/types.h> // for socket #include<sys/socket.h> // for socket #include<stdio.h> // for printf #include<stdlib.h> // for exit #include<string.h> // for bzero #define SERVER_PORT 5678 #define BUFFER_SIZE 1024 #define FILE_NAME_MAX_SIZE 512 int main(int argc, char **argv) { if (argc != 2) //判断有没有输入服务器ip { printf("Usage: ./%s ServerIPAddress\n", argv[0]); exit(1); } // 设置一个socket地址结构client_addr, 代表客户机的ip地址和端口 struct sockaddr_in client_addr; bzero(&client_addr, sizeof(client_addr)); client_addr.sin_family = AF_INET; // internet协议族IPv4 client_addr.sin_addr.s_addr = htons(INADDR_ANY); // INADDR_ANY表示自动获取本机地址 client_addr.sin_port = htons(0); // auto allocated, 让系统自动分配一个空闲端口 // 创建用于internet的流协议(TCP)类型socket,用client_socket代表客户端socket int client_socket = socket(AF_INET, SOCK_STREAM, 0); if (client_socket < 0) { printf("Create Socket Failed!\n"); exit(1); } else printf("Create Socket Success.\n"); // 把客户端的socket和客户端的socket地址结构绑定 if (bind(client_socket, (struct sockaddr*)&client_addr, sizeof(client_addr))) { printf("Client Bind Port Failed!\n"); exit(1); } else printf("Client Bind Port Success.\n"); // 设置一个socket地址结构server_addr,代表服务器的internet地址和端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; // 服务器的IP地址来自程序的参数 if (inet_aton(argv[1], &server_addr.sin_addr) == 0) { printf("Server IP Address Error!\n"); exit(1); } server_addr.sin_port = htons(SERVER_PORT); int server_addr_length = sizeof(server_addr); // 向服务器发起连接请求,连接成功后client_socket代表客户端和服务器端的一个socket连接 if (connect(client_socket, (struct sockaddr*)&server_addr, server_addr_length) < 0) { printf("Can Not Connect To %s!\n", argv[1]); exit(1); } else printf("Alreadly Connect To %s.\n", argv[1]); char file_name[FILE_NAME_MAX_SIZE + 1]; bzero(file_name, sizeof(file_name)); printf("Please Input File Name On Server: "); scanf("%s", file_name); char buffer[BUFFER_SIZE];//缓存区 bzero(buffer, sizeof(buffer)); strncpy(buffer, file_name, strlen(file_name) > BUFFER_SIZE ? BUFFER_SIZE : strlen(file_name)); // 向服务器发送buffer中的数据,此时buffer中存放的是客户端需要接收的文件的名字 send(client_socket, buffer, BUFFER_SIZE, 0); // send , sendto(), recv(),recvfrom() FILE *fp = fopen(file_name, "w"); if (fp == NULL) { printf("File: %s Can Not Open To Write!\n", file_name); exit(1); } // 从服务器端接收数据到buffer中 bzero(buffer, sizeof(buffer)); int length = 0; while(length = recv(client_socket, buffer, BUFFER_SIZE, 0)) { if (length < 0) { printf("Recieve Data From Server %s Failed!\n", argv[1]); break; } int write_length = fwrite(buffer, sizeof(char), length, fp); if (write_length < length) { printf("File:\t%s Write Failed!\n", file_name); break; } bzero(buffer, BUFFER_SIZE); } printf("Recieve File: %s From Server[%s] Finished!\n", file_name, argv[1]); // 传输完毕,关闭socket fclose(fp); close(client_socket); return 0; } 如图,客户端(左)从服务端(右)下载文件/图片:二、UDP实现UDP(User Datagram Protocol) 全称是用户数据报协议,是一种非面向连接的协议,这种协议并不能保证我们的网络程序的连接是可靠的。1.服务端基于UDP协议的socket的server编程步骤:1、建立socket,使用socket()2、绑定socket,使用bind()3、以recvfrom()函数接收发送端传来的数据(使用recvfrom函数 时需设置非阻塞,以免程序卡在此处)4、关闭socket,使用close()/*server.c*/ #include<netinet/in.h> #include<sys/types.h> #include<sys/socket.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #define SERVER_PORT 5678 //端口号 #define LENGTH_OF_LISTEN_QUEUE 20 #define BUFFER_SIZE 1024 #define FILE_NAME_MAX_SIZE 512 int main(int argc, char **argv) { // 设置一个socket地址结构server_addr,代表服务器internet的地址和端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htons(INADDR_ANY); server_addr.sin_port = htons(SERVER_PORT); // create a stream socket // 创建用于internet的流协议(UDP)socket,用server_socket代表服务器向客户端提供服务的接口 int server_socket = socket(PF_INET, SOCK_DGRAM, 0); if (server_socket < 0) { printf("Create Socket Failed!\n"); exit(1); } else printf("Create Socket Success.\n"); // 把socket和socket地址结构绑定 if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr))) { printf("Server Bind Port: %d Failed!\n", SERVER_PORT); exit(1); } else printf("Server Bind Port Success.\n"); printf("Waiting......\n"); // 服务器端一直运行用以持续为客户端提供服务 while(1) { // 接受此请求,同时将client端的地址和端口等信息写入client_addr中 struct sockaddr_in client_addr; int length = 0; int addrlen = sizeof(client_addr); char buffer[BUFFER_SIZE]; bzero(buffer, sizeof(buffer)); length = recvfrom(server_socket, buffer, BUFFER_SIZE, 0,(struct sockaddr *)&client_addr,&addrlen); if (length < 0) { printf("Server Recieve Data Failed!\n"); break; } char file_name[FILE_NAME_MAX_SIZE + 1]; bzero(file_name, sizeof(file_name)); strncpy(file_name, buffer, strlen(buffer) > FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE : strlen(buffer)); FILE *fp = fopen(file_name, "r"); if (fp == NULL) { printf("File:\t%s Not Found!\n", file_name); } else { bzero(buffer, BUFFER_SIZE); int file_block_length = 0; while( (file_block_length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0) { // 发送buffer中的字符串到server_socket,实际上就是发送给客户端 if (sendto(server_socket, buffer, BUFFER_SIZE, 0,(struct sockaddr *)&client_addr,addrlen) < 0) { printf("Send File:\t%s Failed!\n", file_name); break; } bzero(buffer, sizeof(buffer)); } fclose(fp); printf("File:\t%s Transfer Finished!\n", file_name); } } close(server_socket); return 0; }2.客户端基于UDP协议的socket的client端编程步骤:1、建立Socket,使socket()2、用sendto()函数向接收端发送数据。3、关闭socket,使用close()函数/*client.c*/ #include<netinet/in.h> // for sockaddr_in #include<sys/types.h> // for socket #include<sys/socket.h> // for socket #include<stdio.h> // for printf #include<stdlib.h> // for exit #include<string.h> // for bzero #include <fcntl.h> #include <unistd.h> #include <sys/time.h> #define SERVER_PORT 5678 #define BUFFER_SIZE 1024 #define FILE_NAME_MAX_SIZE 512 int main(int argc, char **argv) { if (argc != 2) { printf("Usage: ./%s ServerIPAddress\n", argv[0]); exit(1); } // 设置一个socket地址结构client_addr, 代表客户机的internet地址和端口 struct sockaddr_in client_addr; bzero(&client_addr, sizeof(client_addr)); client_addr.sin_family = AF_INET; // internet协议族 client_addr.sin_addr.s_addr = htons(INADDR_ANY); // INADDR_ANY表示自动获取本机地址 client_addr.sin_port = htons(0); // auto allocated, 让系统自动分配一个空闲端口 // 创建用于internet的流协议(TCP)类型socket,用client_socket代表客户端socket int client_socket = socket(AF_INET, SOCK_DGRAM, 0); if (client_socket < 0) { printf("Create Socket Failed!\n"); exit(1); } // 设置超时,防止recvfrom()函数阻塞 struct timeval timeout; timeout.tv_sec = 1;//秒 timeout.tv_usec = 0;//微秒 if (setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == -1) { perror("setsockopt failed:"); } // 把客户端的socket和客户端的socket地址结构绑定 if (bind(client_socket, (struct sockaddr*)&client_addr, sizeof(client_addr))) { printf("Client Bind Port Failed!\n"); exit(1); } // 设置一个socket地址结构server_addr,代表服务器的internet地址和端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; // 服务器的IP地址来自程序的参数 if (inet_aton(argv[1], &server_addr.sin_addr) == 0) { printf("Server IP Address Error!\n"); exit(1); } server_addr.sin_port = htons(SERVER_PORT); int server_addr_length = sizeof(server_addr); char file_name[FILE_NAME_MAX_SIZE + 1]; bzero(file_name, sizeof(file_name)); printf("Please Input File Name On Server: "); scanf("%s", file_name); char buffer[BUFFER_SIZE]; bzero(buffer, sizeof(buffer)); strncpy(buffer, file_name, strlen(file_name) > BUFFER_SIZE ? BUFFER_SIZE : strlen(file_name)); // 向服务器发送buffer中的数据,此时buffer中存放的是客户端需要接收的文件的名字 if( sendto(client_socket, buffer, BUFFER_SIZE, 0,(struct sockaddr *)&server_addr,server_addr_length) ) printf("Waiting receive %s from server....\n",file_name); FILE *fp = fopen(file_name, "w"); if (fp == NULL) { printf("File:\t%s Can Not Open To Write!\n", file_name); exit(1); } // 从服务器端接收数据到buffer中 bzero(buffer, sizeof(buffer)); int length = 0; //length = recvfrom(client_socket, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&server_addr, &server_addr_length)//非阻塞 while( length = recvfrom(client_socket, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&server_addr, &server_addr_length)) { if (length < 0) { //printf("Recieve Data From Server %s Failed!\n", argv[1]); break; } int write_length = fwrite(buffer, sizeof(char), length, fp); if (write_length < length) { printf("File: %s Write Failed!\n", file_name); break; } bzero(buffer, BUFFER_SIZE); } printf("Receive File: %s From Server[%s] Finished!\n", file_name, argv[1]); // 传输完毕,关闭socket fclose(fp); close(client_socket); return 0; } 如图,客户端(左)从服务端(右)下载文件/图片:注:使用recvfrom函数 时需设置非阻塞,以免程序卡住。
2024年02月29日
175 阅读
0 评论
0 点赞
2024-01-20
机器学习/深度学习-训练过程讲解acc/loss/val_acc/val_loss分析
计算loss是会把所有loss层的loss相加。从验证集误差是和测试集误差的角度分析其实你这个问题就是个伪命题,如果我们刻意的去在训练集上拟合模型,使其准确率达到很高的高度,或者说尽量在验证集合上表现的好,都是相悖的。因为我们不能为了某一特定数据集来刻意拟合,因为模型预测数据是不一定就在这个训练或者验证集合的空间中。还有,如果这个model预测集合acc20% 训练集合acc19% (即训练集精度低于测试集精度),那么这个模型肯定是不好的。还有一点需要注意,就是千万不能为了拟合测试集合而去更改模型,测试集合应该每次都有不同。那么如何选取一个较为理想的medel?首先,要有一个期望的准确率,通过不同模型的实验,找到最能接近的;然后,选定模型后进行参数调优;那么我们要尽可能的提高model的准确率,同时提高其泛化的能力,不能单一看某一指标,此时可参考 准确率、召回率、虚警率、F1Score等指标综合评判。或者采用多重验证随机划分训练、预测、验证集合,多次随机后找到最优参数。有时候训练集合误差很低,但是泛化能力极差,产生过拟合,有时候验证集合误差很低,但是可能验证集合无法代表所有的样本,有特殊性或者其他异常点较多。所以模型问题不能单一从你这两点来评判。一般而言,训练集loss < 验证集loss <测试集loss因为网络 [已见过] 所有训练集samples,故最低。而网络用验证集作为反馈来调节参数,相当于参考了验证集samples中的信息(间接 [已见过])。又因为网络没有任何测试集的信息,所以测试结果一般而言最差。不过这都不是绝对的,有不符合这个一般现象的task,而我们不可以说哪种情况更“好”。多数情况验证集上错误率更低一点。因为是选择在验证集上准确率最高的模型来进行测试。考虑到数据的随机性,在验证集上准确率最高的模型在测试集上不一定是最高的,所以算出来的指标通常验证集会比测试集上好一点。但是实际情况下都有可能,特别是数据量不太大的时候。样本集合的数据也只是近似整体的分布,肯定会有波动。一个好的网络,二者的差距应该是很低的。但一般情况下因为网络不可避免地存在一定程度上的过拟合,所以肯定是train_loss低于test_lost,但如果低太多,就得考虑是过拟合的问题还是因为样本的特征空间不统一的问题。一般训练集不大时,最终训练的网络及容易过拟合,也就是说train-loss一定会收敛,但是test-loss不会收敛;训练时的loss会低于test的loss大概1~2个数量级,通常是10倍左右。单独观察训练集loss曲线 1)如果你的 learning_rate_policy 是 step 或者其他变化类型的话, loss 曲线可以帮助你选择一个比较合适的 stepsize; 2)如果loss曲线表现出线性(下降缓慢)表明学习率太低; 3)如果loss不再下降,表明学习率太高陷入局部最小值; 4)曲线的宽度和batch size有关,如果宽度太宽,说明相邻batch间的变化太大,应该减小batch size。可能导致不收敛的问题(如loss为87.3365,loss居高不下等)的解决方案 1)在caffe中可以在solver里面设置:debug_info: true,看看各个层的data和diff是什么值,一般这个时候那些值不是NAN(无效数字)就是INF(无穷大); 2)检查数据标签是否从0开始并且连续; 3)把学习率base_lr调低; 4)数据问题,lmdb生成有误; 5)中间层没有归一化,导致经过几层后,输出的值已经很小了,这个时候再计算梯度就比较尴尬了,可以尝试在各个卷积层后加入BN层和SCALE层; 5)把base_lr调低,然后batchsize也调高; 6)把data层的输入图片进行归一化,就是从0-255归一化到0-1,使用的参数是: transform_param { scale: 0.00390625//像素归一化,1/255 } 7)网络参数太多,网络太深,删掉几层看看,可能因为数据少,需要减少中间层的num_output; 8)记得要shuffle数据,否则数据不够随机,几个batch之间的数据差异很小。loss出现NAN 1)观察loss值的趋势,如果迭代几次以后一直在增大,最后变成nan,那就是发散了,需要考虑减小训练速率,或者是调整其他参数。 2)数据不能太少,如果太少的话很容易发散" 3)Gradient Clipp 处理gradient之后往后传,一定程度上解决梯度爆炸问题。(但由于有了batch normalization,此方法用的不多) 4)原因可能是训练的时间不够长。 5)可以看一下训练时,网络参数的L2或L1联合观察loss曲线和acc 1)单独的 loss 曲线能提供的信息很少的,一般会结合测试机上的 accuracy 曲线来判断是否过拟合; 2)关键是要看你在测试集上的acc如何; 3)可以把accuracy和loss的曲线画出来,方便设定stepsize,一般在accuracy和loss都趋于平缓的时候就可以减小lr了; 4)看第一次test时(即iteration 0),loss和精度,如果太差,说明初始点的设置有问题 5)使用caffe时,train时,看见的loss是训练集的;accuracy才是测试集的 6)所谓的过拟合是:loss下降,accuracy也下降TensorFlow中loss与val_loss、accuracy和val_accuracy含义loss:训练集损失值accuracy:训练集准确率val_loss:测试集损失值val_accruacy:测试集准确率以下5种情况可供参考:train loss 不断下降,test loss不断下降,说明网络仍在学习;(最好的)train loss 不断下降,test loss趋于不变,说明网络过拟合;(max pool或者正则化)train loss 趋于不变,test loss不断下降,说明数据集100%有问题;(检查dataset)train loss 趋于不变,test loss趋于不变,说明学习遇到瓶颈,需要减小学习率或批量数目;(减少学习率)train loss 不断上升,test loss不断上升,说明网络结构设计不当,训练超参数设置不当,数据集经过清洗等问题。(最不好的情况)震荡修复验证集曲线震荡分析原因:训练的batch_size太小目前batch_size = 64,改成128:改成200:可见,增大batch_size 变大,震荡逐渐消失,同时在测试集的acc也提高了。batch_size为200时,训练集acc小于测试集,模型欠拟合,需要继续增大epoch。总结增大batchsize的好处有三点:1)内存的利用率提高了,大矩阵乘法的并行化效率提高。2)跑完一次epoch(全数据集)所需迭代次数减少,对于相同的数据量的处理速度进一步加快,但是达到相同精度所需要的epoch数量也越来越多。由于这两种因素的矛盾, batch_Size 增大到某个时候,达到时间上的最优。3)一定范围内,batchsize越大,其确定的下降方向就越准,引起训练震荡越小。盲目增大的坏处:1)当数据集太大时,内存撑不住。2)过大的batchsize的结果是网络很容易收敛到一些不好的局部最优点。3)batchsize增大到一定的程度,其确定的下降方向已经基本不再变化。4)太小的batch也存在一些问题,比如训练速度很慢,训练不容易收敛等。5)具体的batch size的选取和训练集的样本数目相关。
2024年01月20日
1,480 阅读
0 评论
2 点赞
泛化能力,过拟合,欠拟合,不收敛,奥卡姆剃刀
泛化能力欠拟合过拟合与不收敛用比较直白的话来讲,就是通过数据训练学习的模型,拿到真实场景去试,这个模型到底行不行,如果达到了一定的要求和标准,它就是行,说明泛化能力好,如果表现很差,说明泛化能力就差。为了更好的理解泛化能力,这里引入三种现象,欠拟合、过拟合以及不收敛。举个例子来说明下,好比高考数学考试,为了在高考能有个好成绩,高一到高三,好多人会采用“题海战术”来训练自己的做题能力,但高考试卷上的题,都是新题,几乎没有一模一样的题,学生们为了掌握解题规律就不停的刷题,希望最后自己碰到类似的题,能够举一反三,能学以致用,这种规律掌握的适用性,就是泛化能力。有的人对相似题型的解题规律掌握的很好,并且解题效果也很好,这种就是泛化能力强,这种同学往往数学成绩就好。有的同学成绩不好,就是泛化能力差,可能有三种情况为了更直观展示,引用了几张图来说明,如下图所示,真实曲线是正弦曲线,蓝色的点是训练数据,红色的线为拟合曲线。解决手段在深度学习的模型建立过程中,一般都是用已经产生的数据训练,然后使用训练得到的模型去拟合未来的数据,借此来预测一些东西。在机器学习和深度学习的训练过程中,经常会出现欠拟合和过拟合的现象。训练一开始,模型通常会欠拟合,所以会对模型进行优化,等训练到一定程度后,就需要解决过拟合的问题了。一、模型训练拟合的分类和表现如何判断过拟合呢?我们在训练的时候会定义训练误差,验证集误差,测试集误差(即泛化误差)。训练误差总是减少的,而泛化误差一开始会减少,到了一定程度后不减少反而开始增加,这时候便出现了过拟合的现象。如下图,直观理解,欠拟合就是还没有学习到数据的特征,还有待继续学习,所以此时判断的不准确;而过拟合则是学习的太彻底,以至于把数据的一些不需要的局部特征或者噪声所带来的特征都给学习到了,所以在测试的时候泛化误差也不佳。从方差和偏差的角度来说,欠拟合就是在训练集上高方差,高偏差,过拟合就是高方差,低偏差。为了更加直观,我们看下面的图对比上图,图一的拟合并没有把大体的规律给拟合出来,拟合效果不好,这个就是欠拟合,还需要继续学习规律,此时模型简单;图三的拟合过于复杂,拟合的过于细致,以至于拟合了一些没有必要的东西,这样在训练集上效果很好,但放到测试集和验证集就会不好。图二是最好的,把数据的规律拟合出来了,也没有更复杂,更换数据集后也不会效果很差。在上面的拟合函数中,可以想到,图三过拟合的拟合函数肯定是一个高次函数,其参数个数肯定比图二多,可以说图三的拟合函数比图二要大,模型更加复杂。这也是过拟合的一个判断经验,模型是否过于复杂。另外针对图三,我们把一些高次变量对应的参数值变小,也就相当于把模型变简单了。从这个角度上讲,可以减少参数值,也就是一般模型过拟合,参数值整体比较大。从模型复杂性上讲,可以是: ——模型的参数个数; ——模型的参数值的大小。 个数越多,参数值越大,模型越复杂。二、欠拟合欠拟合的表现有什么方法来判断模型是否欠拟合呢?其实一般都是依靠模型在训练集和验证集上的表现,有一个大概的判断就行了。如果要有一个具体的方法,可以参考机器学中,学习曲线来判断模型是否过拟合,如下图:欠拟合的解决方案(1)增加数据特征:欠拟合是由于学习不足导致的,可以考虑添加特征,从数据中挖掘更多的特征,有时候嗨需要对特征进行变换,使用组合特征和高次特征;(2)使用更高级的模型:模型简单也会导致欠拟合,即模型参数过少,结构过于简单,例如线性模型只能拟合一次函数的数据。尝试使用更高级的模型有助于解决欠拟合,增加神经网络的层数,增加参数个数,或者使用更高级的方法;(3)减少正则化参数:正则化参数是用来防止过拟合的,出现欠拟合的情况就要考虑减少正则化参数。三、过拟合过拟合的定义模型在训练集上表现好,但在测试集和验证集上表现很差,这就是过拟合过拟合的原因(1)数据量太小这是很容易产生过拟合的原因。设想我们有一组数据很好的满足了三次函数的规律,但我们只取了一小部分数据进行训练,那么得到的模型很可能是一个线性函数,把这个线性函数用于测试集上,可想而知肯定效果很差。(此时训练集上效果好,测试集效果差)(2)训练集和验证集分布不一致这也是很大一个原因。训练集上训练出来的模型适合训练集,当把模型应用到一个不一样分布的数据集上,效果肯定大打折扣,这个是显而易见的。(3)网络模型过于复杂选择模型算法时,选择了一个复杂度很高的模型,然而数据的规律是很简单的,复杂的模型反而不适用了。(4)数据质量很差数据有很多噪声,模型在学习的时候,肯定也会把噪声规律学习到,从而减少了一般性的规律。这个时候模型预测效果也不好。(5)过度训练这是同第四个相联系的,只要模型训练时间足够长,那么模型肯定会把一些噪声隐含的规律学习到,这时候降低模型的性能也是显而易见的。解决方法(1)降低模型复杂度处理过拟合的第一步就是降低模型复杂度。为了降低复杂度,我们可以简单地移除层或者减少神经元的数量使得网络规模变小。与此同时,计算神经网络中不同层的输入和输出维度也十分重要。虽然移除层的数量或神经网络的规模并无通用的规定,但如果你的神经网络发生了过拟合,就尝试缩小它的规模。(2)数据集扩增在数据挖掘领域流行着这样的一句话,“有时候往往拥有更多的数据胜过一个好的模型”。因为我们在使用训练数据训练模型,通过这个模型对将来的数据进行拟合,而在这之间又一个假设便是,训练数据与将来的数据是独立同分布的。即使用当前的训练数据来对将来的数据进行估计与模拟,而更多的数据往往估计与模拟地更准确。因此,更多的数据有时候更优秀。但是往往条件有限,如人力物力财力的不足,而不能收集到更多的数据,如在进行分类的任务中,需要对数据进行打标,并且很多情况下都是人工得进行打标,因此一旦需要打标的数据量过多,就会导致效率低下以及可能出错的情况。所以,往往在这时候,需要采取一些计算的方式与策略在已有的数据集上进行手脚,以得到更多的数据。通俗的讲,数据机扩增即需要得到更多的符合要求的数据,即和已有的数据是独立同分布的,或者近似独立同分布的。一般有以下方法: ——从数据源头采集更多数据 ——复制原有数据加上噪声 ——重采样 ——根据当前数据集估计数据分布参数,利用该分布产生更多数据(3)数据增强使用数据增强可以生成多幅相似图像。这可以帮助我们增加数据集从而减少过拟合。因为随着数据量的增加,模型无法过拟合所有样本,因此不得不进行泛化。计算机视觉领域通常的做法有:翻转,平移,旋转,缩放,改变亮度,添加噪声等。(4)正则化正则化是指在进行目标函数或者代价函数优化时,在目标函数或者代价函数后面加上一个正则项,一般有L1正则和L2正则等。L1惩罚项的目的是使权重绝对值最小化,公式如下:L1惩罚项的目的是使权重的平方最小化,公式如下:下面对两种正则化方法进行了比较:如果数据过于复杂以致没有办法进行准确的建模,那么L2是更好的选择,因为它能够学习数据中呈现的内在模式。而当数据足够简单,可以精确建模的话,L1更合适,对于我遇到的大多数计算机视觉问题,L2正则化几乎总是可以给出最好的结果。然而L1不容易受到离群值的影响。所以正确的正则化选项取决于我们想要解决的问题。总结:正则项是为了降低模型的复杂度,从而避免模型过分拟合训练数据,包括噪声与异常点。从另一个角度讲,正则化即是假设模型参数服从先验概率,即为模型参数添加先验,只是不同的正则化方式的先验分布是不一样的。这样就规定了参数的分布,使得模型的复杂度降低(试想一下,限定条件多了,是不是模型的复杂度就降低了呢),这样模型对于噪声和异常点的抗干扰性的能力增强,从而提高模型的泛化能力。还有个解释,从贝叶斯学派来看,加了先验,在数据少的时候,先验知识可以防止过拟合;从频率学派来看,正则项限定了参数的取值,从而提高了模型的稳定性,而稳定性强的模型不会过拟合,即控制模型空间。另外一个角度,过拟合从直观上理解便是,在对训练数据进行拟合时,需要照顾到每个点,从而使得拟合函数波动性非常大,即方差大。在某些小区间里,函数值的变化很剧烈,意味着函数在某些小区间的导数值的绝对值非常大,由于自变量的值在给定的训练数据集中是一定的,因为只有系数足够大,才能保证导数的绝对值足够大,如下图:另一个解释,规则化项的引入,在训练(最小化cost)的过程中,当某一维的特征所对应的权重过大时,而此时模型的预测和真实数据之间的距离很小,通过规则化项就可以使整体的cost取较大的值,从而,在训练的过程中避免了去选择了那些某一维(或几维)特征权重过大的情况,即过分依赖某一维(或几维)的特征。L1和L2的区别是,L1正则是拉普拉斯先验,L2正则则是高斯先验。它们都是服从均值为0,协方差为1/λ。当λ=0,即没有先验,没有正则项,则相当于先验分布具有无穷大的协方差,那么这个先验约束则会非常弱,模型为了拟合拟合所有的训练集数据,参数可以变得任意大从而使得模型不稳定,即方差大而偏差小。λ越大,表明先验分布协方差越小,偏差越大,模型越稳定。即,加入正则项是在偏差bias与方差variance之间做平衡tradeoff。下图即为L2与L1正则的区别:上图中的模型是线性回归,有两个特征,要优化的参数分别是w1和w2,左图的正则化是L2,右图是L1。蓝色线就是优化过程中遇到的等高线,一圈代表一个目标函数值,圆心就是样本观测值(假设一个样本),半径就是误差值,受限条件就是红色边界(就是正则化那部分),二者相交处,才是最优参数。可见右边的最优参数只可能在坐标轴上,所以就会出现0权重参数,使得模型稀疏。其实拉普拉斯分布和高斯分布是数学家从试验中误差服从什么分布研究得出的。一般直观上的认识是服从均值为0的对称分布,并且误差大的概率低,误差小的概率高,因为拉普拉斯使用拉普拉斯分布对误差的分布进行拟合,如下图:而拉普拉斯在最高点,即自变量为0处不可导,因为不便于计算,于是高斯在这基础上使用高斯分布对其进行拟合,如下图:(5)dropout正则时通过再代价函数后面加上正则项来防止过拟合的。而在神经网络中,有一种方法时通过修改神经网络本身结构实现的,其名为dropout。该方法是对网络进行训练时用的一种技巧,对于如下的三层人工神经网络:对于上图所示的网络,在训练开始时,随即删除一些(可自己设定概率)隐藏层神经元,即认为这些神经元不存在,同时保持输入层和输出层的个数不变,这样便得到如下的ANN:然后按照BP学习算法对ANN中的参数进行学习更新(虚线链接的单元不更新,因为认为这些连接元被临时删除了)。这样一次迭代更新便完成了,下一次迭代中,同样随机删除一些神经元,与上次不一样,做随机选择,这样一直进行,直至训练结束。这种技术被证明可以减少很多问题的过拟合,这些问题包括图像分类,图像切割,词嵌入,语义匹配等问题。(6)早停对模型的训练即是对模型的参数进行更新的过程,这个参数学习的过程往往会用到一些迭代方法,如梯度下降(Gradient descent)学习算法。Early stopping一种迭代次数截断的方法来防止过拟合的方法,即在模型对训练数据集迭代收敛之前停止迭代来防止过拟合。Early stopping方法的具体做法是,在每一个Epoch结束时(一个Epoch集为对所有的训练数据的一轮遍历)计算验证集的正确率,当正确率不再提高时,就停止训练。这种做法很符合直观感受,因为正确率都不在提高了,再继续训练也是无益的,只会提高训练的时间。如下图,在几次迭代后,即使训练误差仍然在减少,但测验误差已经开始增加了。那么该做法的一个重点便是怎样才认为验证准确率validation accurary不再提高了呢?并不是说验证准确率validation accurary一降下来便认为不再提高了,因为可能经过这个Epoch后,正确率降低了,但是随后的Epoch又让正确率又上去了,所以不能根据一两次的连续降低就判断不再提高。一般的做法是,在训练的过程中,记录到目前为止最好的验证准确率validation accurary,当连续10次没达到最佳正确率时,认为不再提高了,此时便可以停止迭代。这种策略也称为“No-improvement-in-n”,n即Epoch的次数,可以根据实际情况取,如10、20、30……(7)重新清洗数据把明显异常的数据剔除。(8)使用集成学习方法把多个模型集成在一起,降低单个模型的过拟合风险。
2024年01月19日
330 阅读
0 评论
0 点赞
1
2
...
26