当前位置: 首页 > news >正文

ZYNQ笔记(十四):基于 BRAM 的 PS、PL 数据交互

版本:Vivado2020.2(Vitis)

实验任务:

        PS 将字符串数据写入BRAM,再将数据读取出来;PL 从 BRAM 中读取数据,bing。通过 ILA 来观察读出的数据,与前面串口打印的数据进行对照(检查是否正确读出)。

目录

一、介绍

1. BRAM(Block RAM)

2. AXI BRAM Controller         

二、硬件设计

1. 整体系统框图

2. 配置 BRAM

3. 配置 AXI BRAM Controller

4. 自定义IP核(pl_bram_rd)

5. 最终 bd 设计

三、软件设计

四、效果


一、介绍

1. BRAM(Block RAM)

        BRAM(Block RAM) 是 Xilinx Zynq SoC 内部的嵌入式高速静态存储器(SRAM),分布在 FPGA 可编程逻辑(PL)部分,但也可被处理器系统(PS)直接访问。主要特点:

特性说明
高速访问1~2 个时钟周期完成读写,远快于外部 DDR(通常需数十周期)
低功耗低功耗,片上存储无需外部总线交互
双端口架构支持双端口(可独立读写),可配置为单端口或真双端口模式
存储容量每块 36Kb(可拆分为 2×18Kb),器件集成数量:几十至数百块
初始化方式支持 COE 文件预加载或运行时写入
典型用途高速缓存、查找表、PS-PL 数据共享、跨时钟域同步
ECC 支持可选校验功能

2. AXI BRAM Controller         

        AXI BRAM Controller 是 Xilinx FPGA 和 Zynq SoC 中常用的 IP 核,用于通过 AXI 总线协议 访问 BRAM (Block RAM)。它允许 PS 端或 DMA 控制器通过标准 AXI 接口高效读写 BRAM,同时支持 PL(FPGA 逻辑)和 PS(处理器系统)之间的数据共享。

特性说明
支持的 AXI 协议AXI4(高性能)、AXI4-Lite(简化版)
数据位宽32/64/128/256/512 位(需匹配 BRAM 配置)
突发传输AXI4 支持突发(Burst)读写,提升吞吐量
ECC 支持可选,增加数据校验功能
低延迟配置可配置访问延迟
多主设备支持多个 Master(如 CPU + DMA)可共享同一 BRAM

二、硬件设计

1. 整体系统框图

        PS端:除了 基本的 DDR 和 UART 还使用到了 AXI 接口,因此如时钟、复位、AXI接口 都需要保留和配置。 

        PL 端:存储数据的 BRAM 的 IP 核、实现对 BRAM 写入数据的 AXI BRAM 控制器 IP 核、读取 BRAM IP 核数据的自定义的 IP 核(pl_bram_rd)。

2. 配置 BRAM

        即添加 Block Memory Generator IP 核,配置如下:

        BRAM IP 核支持两种模式,一种是独立模式(Stand Alone),在此模式下,可以自由配置 RAM 的数 据深度和宽度;另一种是 BRAM 控制器模式(BRAM Controller),在此模式下,地址和数据默认为 32 位, 由于本次实验添加了 BRAM 控制器 IP 核,因此 BRAM 模式选择 BRAM 控制器模式。

        存储类型(Memory Type)设置为“True Dual Port RAM” 真双端口 RAM。一端连接 PL 读 BRAM IP 核,另一端连接 BRAM 控制器。

        来到其他选项:取消使能安全电路(如果勾选使能安全电路,BRAM 端口会增加 rst_busy 端口,用于表示何时可以访问 BRAM。)

        在 AXI BRAM Controller 模式下,BRAM 的 we写使能信号为 4 位,这是为了支持 按字节写入(Byte-Write)功能,允许对 32 位数据(4 字节)中的特定字节进行单独写入,而无需覆盖整个字。以下是详细说明:

  • AXI 总线特性:AXI 协议支持字节级写入(通过 WSTRB 信号),因此 BRAM Controller 需将 AXI 的字节使能信号转换为 BRAM 的 we[3:0]

  • 数据对齐:32 位 BRAM 数据(4 字节)中,每个 we 位对应一个字节的写使能:

    • we[0]:控制 字节 0(数据位 [7:0]

    • we[1]:控制 字节 1(数据位 [15:8]

    • we[2]:控制 字节 2(数据位 [23:16]

    • we[3]:控制 字节 3(数据位 [31:24]

3. 配置 AXI BRAM Controller

        AXI Protocol(AXI 协议)选择的是 AXI4_Lite,本实验只需发送一段开源电子网网址的字符串,因此选择适用于低吞吐率存储映射的 AXI4_Lite 接口即可满足传输需求。

        Data Width(数据位宽)固定为默认的 32 位,由于 AXI4 总线为字节寻址,因此在映射到 BRAM 地址时,需要按 4 字节寻址。

        BRAM 接口数 1,本次 BRAM 控制器只需要读写 BRAM 的一个端口,

        此外 Memory Depth(存储深度)不可以设置,寻址 BRAM 的存储深度是在 Address Editor 里设置。读取延时默认1个时钟周期。ECC 用于数据错误纠正与检查,不使能。所有配置如下:

4. 自定义IP核(pl_bram_rd)

        自定义 IP 核的设计和封装可参考:ZYNQ笔记(六):自定义IP核-LED呼吸灯 。可以直接点击菜单栏的 “Tools”,选择 “Creat and Package New IP…” 在当前工程目录快速进行自定义 IP 核的创建,如下图所示:

        需要注意创建的是 带 AXI 接口的 IP 

        IP 默认在工程目录的上一级目录创建目录ip_repo (/../ip_repo) ,这里去掉一个点让他创建在当前工程目录下(/./ip_repo) :

        AXI 接口类型设置为 Lite 和前面保持一致,接口模式为从。Next、Finish。

        在IP Catolog 内找到 整个自定义 IP pl_bram_rd,右键进行编辑。pl_bram_rd 里面所添加的自定义verilog模块是直接使用的正点原子的 bram_rd 模块,模块引出了 bram 接口并实现了对 bram 的读操作,控制信号在例化时相依分配寄存器即可,这样 PS 端通过读相应寄存器实现对 PL 端 自定义IP核 pl_bram_rd 的控制。

bram_rd 模块

/*BRAM读数据 模块例化bram_rd bram_rd(.clk            (S_AXI_ACLK ), //时钟信号.rst_n          (S_AXI_ARESETN), //复位信号//PS端输入的控制信号(分配三个寄存器进行控制).start_rd       (slv_reg0[0]), //读开始信号(上升沿有效).start_addr     (slv_reg1   ), //读开始地址  .rd_len         (slv_reg2   ), //读数据的长度//BRAM端口     .ram_clk        (ram_clk    ), //RAM时钟.ram_rst        (ram_rst    ), //RAM复位信号,高有效.ram_en         (ram_en     ), //RAM工作使能信号.ram_we         (ram_we     ), //RAM写使能信号.ram_addr       (ram_addr   ), //RAM地址.ram_wr_data    (ram_wr_data), //RAM要写入的数据.ram_rd_data    (ram_rd_data)  //RAM中读出的数据);
*///模块实现对BRAM的读取操作(BRAM为控制模式为 BRAM Contrulor 控制)
module bram_rd(input                clk        , //时钟信号input                rst_n      , //复位信号//控制信号input                start_rd   , //读开始信号(上升沿有效)input        [31:0]  start_addr , //读开始地址  input        [31:0]  rd_len     , //读数据的长度//BRAM端口output               ram_clk    , //RAM时钟output               ram_rst    , //RAM复位信号,高有效output  reg          ram_en     , //RAM工作使能信号output  reg  [3:0]   ram_we     , //RAM写使能信号output  reg  [31:0]  ram_addr   , //RAM地址output  reg  [31:0]  ram_wr_data, //RAM要写入的数据input        [31:0]  ram_rd_data  //RAM中读出的数据
);
reg  [1:0]   flow_cnt;//流程计数
reg          start_rd_d0;
reg          start_rd_d1;assign ram_rst = 1'b0;
assign ram_clk = clk ;//读开始信号上升沿标志信号
wire   pos_start_rd; 
assign pos_start_rd = ~start_rd_d1 & start_rd_d0;//延时两拍,采start_rd信号的上升沿
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginstart_rd_d0 <= 1'b0;   start_rd_d1 <= 1'b0; endelse beginstart_rd_d0 <= start_rd;   start_rd_d1 <= start_rd_d0;     end
end//判断读开始信号,从RAM中读出数据
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginflow_cnt    <= 2'd0;ram_en      <= 1'b0;ram_we      <= 4'd0;ram_addr    <= 32'd0;end//通过计数实现的简单状态机else begincase(flow_cnt)2'd0 : beginif(pos_start_rd) beginram_en   <= 1'b1;ram_addr <= start_addr;flow_cnt <= flow_cnt + 2'd1;endend2'd1 : beginif(ram_addr - start_addr == rd_len - 4) begin  //数据读完(读完差值为rd_len-4)ram_en   <= 1'b0;flow_cnt <= flow_cnt + 2'd1;endelseram_addr <= ram_addr + 32'd4; //地址累加4(,一次读32位4个字节,一个字节对应一个地址)end2'd2 : beginram_addr <= 32'd0; flow_cnt <= 2'd0;endendcaseend
endendmodule

        接着就是把 bram_rd 模块例化到 官方AXI 接口模块中并添加端口定义:

AXI接口模块例化、添加端口定义:

AXI 接口顶层模块添加端口定义:

        接着保存后综合,并打包IP核(参考:ZYNQ笔记(六):自定义IP核-LED呼吸灯 )。注意再PORT 栏部分,为了使 IP 核最后在 bd 视图简洁规范,这里需要对自定义的端口封装成一个BRAM的总线接口并匹配端口类型(也可以不设置,不过结果就是db视图中IP核自定义端口全部罗列在视图上不能缩略为接口,同时运行自动连线时不能自动连接BRAM,需要手动连线)

        找到端口,可能有端口被纳入到其他组里面,选中右键 Remove Interface 把它移出来即可:

        选中所有自定义端口右键添加总线接口,接口定义设置为官方的bram接口,自己进行命名,模式选“Master”主模式(因为是要读写BRAM模块)

        为自定义的端口添加接口映射(左右对应选中点击Map Ports):

        最后添加自动计算参数,这里直接全部添加过去(Vivado 根据连接的总线或关联 IP 自动推导,这里没有进行特定的设置,直接选“自动”即可)

       完成后PORT栏可以查看接口情况,GUI视图栏也可以看到这些端口被封装为一个接口了:

        最后打包IP即可。注意 : 在Vitis 开发环境下,需要修改自定义IP核的 Makefile 文件,如果不修改,当包含该 IP 的硬件(xsa)文件导出 到 vitis 后,对 vitis 工程进行编译就会报错,报错信息为“xxx.h: No such file or directory”。因此需要在使用该 IP 前完成修改: Makefile 修改方法

5. 最终 bd 设计

        添加完自定义IP核后可以运行自动连接,为了保险起见手动设置要自定义IP核要连接的 BRAM 避免出错。

        接着需要设置地址映射,设置地址范围也会间接定义 BRAM 的大小。这里读/写同一 bram 就都设置为 4K(单位字节),也定义 BRAM 深度大小为 1K (BRAM 数据位宽32位 4字节)。BRAM 控制器 和 自定义 IP(BRAM读控制器) 的地址范围不能重合。

        除了定义 BRAM 大小,两个 bram 控制器需要设置映射地址和范围是为了定义可访问的 BRAM 地址,确保模块能够准确读写数据。尽管已经通过连接明确了物理关系(接口连接),地址映射仍然是必要的,实际设置的是 BRAM 的映射地址,而非 BRAM 控制器本身,以确保控制器能正确进行数据访问。      

        最后整体 bd 设计部分如图所示:设计检查、Generate Output Products、 Create HDL Wrapper、管脚约束(没有PL管脚,忽略这一步)、Gnerate Bitstream、Export Hardware(包含比特流文件)、启动Vitis

三、软件设计

#include "xil_printf.h"
#include "xparameters.h"
#include "pl_bram_rd.h"  	//自定义IP头文件(包含对IP寄存器进行读写的函数)
#include "xbram.h"			//AXI BRAM Controller IP头文件(包含使用该IP进行BRAM读写的函数)
#include "stdio.h"
#include "sleep.h"//===================用户自定义宏===================//
#define BRAM_BASEADDR       XPAR_BRAM_0_BASEADDR	//BRAM器件(BRAM IP核) 基地址#define BRAM_RD_IP			XPAR_PL_BRAM_RD_0_S00_AXI_BASEADDR 	//自定义IP核(pl_bram_rd)寄存器基地址
#define BRAM_RD_START_RD	PL_BRAM_RD_S00_AXI_SLV_REG0_OFFSET 	//start_rd  对应寄存器(slv_reg0)地址偏移量(pl_bram_rd.h中查看)
#define BRAM_RD_STRAT_ADDR	PL_BRAM_RD_S00_AXI_SLV_REG1_OFFSET 	//start_addr对应寄存器(slv_reg1)地址偏移量
#define BRAM_RD_RD_LEN		PL_BRAM_RD_S00_AXI_SLV_REG2_OFFSET 	//rd_len    对应寄存器(slv_reg2)地址偏移量#define STRAT_ADDR 			0	//读起始地址(从0开始)
#define BRAM_DATA_BYTE 		4 	//BRAM 数据字节数(32位宽 4字节)//===================函数变量声明===================//
void BRAM_Ctrl_Wr();	//BRAM控制器写数据
void BRAM_Ctrl_Rd();	//BRAM控制器读数据
void PL_BRAM_RD_Rd();	//自定义IP核(pl_bram_rd)读数据int  wr_len;				//写入数据长度
char wr_buff[50] = "Hello"; //写入数据(字符数组)
char rd_buff[50] = "";		//读出数据//======================主函数======================//
int main()
{while(1){xil_printf("Write Char Data : %s\r\n", wr_buff);  // 打印待写入数据数据wr_len = strlen(wr_buff);	//获取字符串长度BRAM_Ctrl_Wr();				//BRAM控制器写数据BRAM_Ctrl_Rd();				//BRAM控制器读数据PL_BRAM_RD_Rd();			//自定义IP核(pl_bram_rd)读数据sleep(3);					//每隔3秒执行一次}return 0;
}//================BRAM控制器写数据================//
void BRAM_Ctrl_Wr()
{//用XBram_WriteReg一次写入一个字符,用for循环遍历写入所有字符for(int i=0; i<wr_len; i++){//XBram_WriteReg(BRAM基地址,偏移量,写入数据),这里一次偏移4字节是因为BRAM数据位宽32位4字节,不过每次只写1个字符XBram_WriteReg(BRAM_BASEADDR, i*BRAM_DATA_BYTE, wr_buff[i]);}xil_printf("BRAM Write done! \r\n");
}//================BRAM控制器读数据================//
void BRAM_Ctrl_Rd()
{xil_printf("Read Char Data : ");//用XBram_WriteReg一次读取一个字符,用for循环遍历读取所有字符for(int i=0; i<wr_len; i++){//XBram_ReadReg(BRAM基地址,偏移量)rd_buff[i] = XBram_ReadReg(BRAM_BASEADDR, i*BRAM_DATA_BYTE);xil_printf("%c",rd_buff[i]);}xil_printf("\r\n");
}//===========自定义IP核(pl_bram_rd)读数据===========//
void PL_BRAM_RD_Rd()
{PL_BRAM_RD_mWriteReg(BRAM_RD_IP, BRAM_RD_RD_LEN, wr_len*BRAM_DATA_BYTE); 			//设置读数据长度PL_BRAM_RD_mWriteReg(BRAM_RD_IP, BRAM_RD_STRAT_ADDR, STRAT_ADDR*BRAM_DATA_BYTE); 	//设置读起始地址//产生读开始信号有效上升沿PL_BRAM_RD_mWriteReg(BRAM_RD_IP, BRAM_RD_START_RD, 1); 			//拉高读开始信号PL_BRAM_RD_mWriteReg(BRAM_RD_IP, BRAM_RD_START_RD, 0); 			//拉低读开始信号
}

四、效果

        PS 端:先打印待写入数据,写入后将数据读取并打印。可看到前后一致说明写入数据无误

        PL 端:要用到 ILA 查看 自定义IP 核读取的数据是否正确,手动添加一个 ILA IP 核并在system_warper 中进行例化(修改保存后需要重新走一遍导出xac文件之前的流程)。

        不过这样到最后并没有实现PL到PS的数据传输,可以考虑自定义IP核加一个中断输出,并重新修改功能,实现读取数据后再接着最后一个地址开始,再把数据写到BRAM上,然后产生一个高脉冲信号(中断信号)时PS端得到中断开读数据检查两端数据是否一致:参考文章
ZYNQ—BRAM全双工PS_PL数据交互(开源) 

        关于自定义IP核的中断(BD设计里面需要给ZYNQ添加中断端口),我这里也有一个可参考的模板,中断类型位上升沿中断:

#include "xil_printf.h"
#include "xparameters.h"
#include "pl_bram_rd.h"  	//自定义IP头文件(包含对IP寄存器进行读写的函数)
#include "xbram.h"			//AXI BRAM Controller IP头文件(包含使用该IP进行BRAM读写的函数)
#include "xscugic.h"
#include "stdio.h"//===================用户自定义宏===================//
#define BRAM_BASEADDR       XPAR_BRAM_0_BASEADDR	//BRAM器件(BRAM IP核) 基地址#define BRAM_RD_IP			XPAR_PL_BRAM_RD_0_S00_AXI_BASEADDR 	//自定义IP核(pl_bram_rd)寄存器基地址
#define BRAM_RD_START_RD	PL_BRAM_RD_S00_AXI_SLV_REG0_OFFSET 	//start_rd  对应寄存器(slv_reg0)地址偏移量(pl_bram_rd.h中查看)
#define BRAM_RD_STRAT_ADDR	PL_BRAM_RD_S00_AXI_SLV_REG1_OFFSET 	//start_addr对应寄存器(slv_reg1)地址偏移量
#define BRAM_RD_RD_LEN		PL_BRAM_RD_S00_AXI_SLV_REG2_OFFSET 	//rd_len    对应寄存器(slv_reg2)地址偏移量#define STRAT_ADDR 			0	//读起始地址(从0开始)
#define BRAM_DATA_BYTE 		4 	//BRAM 数据字节数(32位宽 4字节)#define INTC_DEVICE_ID		XPAR_SCUGIC_SINGLE_DEVICE_ID	  	//宏定义中断控制器(GIC)ID
#define BRAM_RD_IP_ID  		XPAR_PL_BRAM_RD_0_DEVICE_ID       	//自定义IP核(pl_bram_rd)器件ID
#define BRAM_RD_IP_INTR_ID	XPAR_FABRIC_PL_BRAM_RD_0_INTR_INTR 	//宏定义GPIO中断号(每个器件都有中断号,xparameters.h查询)//===================函数变量声明===================//
XScuGic Intc;			//中断控制器驱动实例void BRAM_Ctrl_Wr ();	//BRAM控制器写数据
void BRAM_Ctrl_Rd ();	//BRAM控制器读数据
void PL_BRAM_RD_Rd();	//自定义IP核(pl_bram_rd)读数据
void Set_Intr_Sys(XScuGic *GicInstancePtr, u16 DeviceIntrID);    //建立中断系统
void IntrHandler();		//中断处理函数int  wr_len;		      //写入数据长度
char wr_buff[] = "ABC"; //写入数据(字符数组)//======================主函数======================//
int main()
{//建立中断系统Set_Intr_Sys(&Intc, BRAM_RD_IP_INTR_ID);wr_len = strlen(wr_buff);	//获取字符串长度BRAM_Ctrl_Wr ();			//BRAM控制器写数据BRAM_Ctrl_Rd ();			//BRAM控制器读数据PL_BRAM_RD_Rd();			//自定义IP核(pl_bram_rd)读写数据while(1)return 0;
}//================BRAM控制器写数据================//
void BRAM_Ctrl_Wr()
{//用XBram_WriteReg一次写入一个字符,用for循环遍历写入所有字符for(int i=0; i<wr_len; i++){//XBram_WriteReg(BRAM基地址,偏移量,写入数据),这里一次偏移4字节是因为BRAM数据位宽32位4字节,不过每次只写1个字符//读写地址=基地址+偏移量XBram_WriteReg(BRAM_BASEADDR, i*BRAM_DATA_BYTE, wr_buff[i]);}xil_printf("BRAM Write Finish! \r\n");
}//================BRAM控制器读数据================//
void BRAM_Ctrl_Rd()
{int r_data;xil_printf("Read the Written Data : ");//用XBram_WriteReg一次读取一个字符,用for循环遍历读取所有字符for(int i=0; i<wr_len; i++){//XBram_ReadReg(BRAM基地址,偏移量)//读写地址=基地址+偏移量r_data = XBram_ReadReg(BRAM_BASEADDR, i*BRAM_DATA_BYTE);xil_printf("%c",r_data);}xil_printf("\r\n");
}//===========自定义IP核(pl_bram_rd)读数据===========//
void PL_BRAM_RD_Rd(u32 addr)
{xil_printf("START the custom IP: \r\n");PL_BRAM_RD_mWriteReg(BRAM_RD_IP, BRAM_RD_RD_LEN, wr_len*BRAM_DATA_BYTE); 		 //设置读数据长度PL_BRAM_RD_mWriteReg(BRAM_RD_IP, BRAM_RD_STRAT_ADDR, STRAT_ADDR*BRAM_DATA_BYTE); //设置读起始地址//产生读开始信号有效上升沿PL_BRAM_RD_mWriteReg(BRAM_RD_IP, BRAM_RD_START_RD, 1); 	//拉高读开始信号PL_BRAM_RD_mWriteReg(BRAM_RD_IP, BRAM_RD_START_RD, 0);	//拉低读开始信号
}//===========================中断处理函数===========================//
void IntrHandler(void *CallbackRef)
{int r_data;xil_printf("Interrupt Detected , Start Read BRAM :\r\n");//clear interrupt status//PL_BRAM_RD_mWriteReg(PL_RAM_BASE, PL_RAM_CTRL , INTRCLR_MASK) ;//BRAM控制器读数据for(int i=0; i<wr_len*2; i++){//XBram_ReadReg(BRAM基地址,偏移量)//读写地址=基地址+偏移量r_data = XBram_ReadReg(BRAM_BASEADDR, i*BRAM_DATA_BYTE);xil_printf("Address:%d	Data:%c \r\n", i*BRAM_DATA_BYTE, r_data);}xil_printf("\r\n");
}//===========================建立中断系统===========================//
/* 建立中断系统,使能自定义IP核终端输出信号的上升沿产生中断* @param GicInstancePtr 是指向 XScuGic 驱动实例的指针* @param DeviceIntrID   是器件中断 ID*/
void Set_Intr_Sys(XScuGic *GicInstancePtr, u16 DeviceIntrID)
{//定义中断控制器配置信息(指针类型)XScuGic_Config * IntcConfig;//根据中断控制器ID,查找GIC配置信息(Generic Interrupt Controller(通用)中断控制器)IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);//初始化中断控制器驱动XScuGic_CfgInitialize(GicInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress);//设置并打开中断异常处理功能Xil_ExceptionInit();Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler,GicInstancePtr);//为GPIO中断设置中断处理函数(IntrHandler为自己编写的中断函数)XScuGic_Connect(GicInstancePtr, DeviceIntrID,(Xil_ExceptionHandler)IntrHandler, (void *) NULL); //自定义IP核无实例NULL//使能处理器中断Xil_ExceptionEnable();//设置中断优先级、触发类型(优先级从最高0开始步长8最大为248,0xA0=160优先级中等;触发类型:上升沿)XScuGic_SetPriorityTriggerType(GicInstancePtr, DeviceIntrID, 0xA0 , 0x3);//使能来自于器件的中断XScuGic_Enable(GicInstancePtr, DeviceIntrID);
}

相关文章:

  • 【Token系列】01 | Token不是词:GPT如何切分语言的最小单元
  • 云服务器 —— 公有 IP 与 私有 IP
  • 【计算机视觉】CV项目实战- 深度解析TorchVision_Maskrcnn:基于PyTorch的实例分割实战指南
  • 深入解析Spring Boot配置处理器:机制、架构与实践
  • 【计算机网络】信息时代的数字神经系统
  • NVLink、UALink 崛起,PCIe Gen6 如何用 PAM4 迎战未来?
  • 基于QT的仿QQ音乐播放器
  • 极简桌面app官网版下载 极简桌面最新版 安装包下载
  • 栈相关算法题解题思路与代码实现分享
  • 深入解析NuttX:为何它是嵌入式RTOS领域的标杆?​​
  • 思科路由器重分发(RIP动态路由+静态路由)
  • RAG技术与应用---0426
  • 8.学习笔记-Maven进阶(P82-P89)
  • 23种设计模式-行为型模式之观察者模式(Java版本)
  • 零基础上手Python数据分析 (24):Scikit-learn 机器学习初步 - 让数据预测未来!
  • stm32L4R5ZI Nucleo-144 GPIO点灯及按键中断
  • Log4j Properties 配置项详细说明
  • linux socket编程之tcp(实现客户端和服务端消息的发送和接收)
  • C盘爆红如何解决
  • 如何使用WebRTC
  • 酒店保洁员调包住客港币,海南官方通报:成立调查组赴属地调查
  • 伊朗外长: 美伊谈判进展良好,讨论了很多技术细节
  • 拉卡拉一季度净利约1亿降超五成,去年净利3.5亿降逾23%
  • 朝中社发表评论文章,谴责美军部署B1-B轰炸机至日本
  • 专访倪军:人要有终身学习能力,一张文凭无法像以往支撑那么多年
  • “70后”女博士张姿卸任国家国防科技工业局副局长