🌍首页
📁归档
⏳时光机
🚩友链
📫留言
📧订阅
🌟推荐
📕考研课程
🏜️ 免费壁纸
❤ 捐助本站
💰资助名单
🎵音乐实验
Search
1
【NPN/PNP三极管】放大电路饱和失真和截止失真的区别
20,145 阅读
2
论文写作中如何把word/wps里面所有数字和字母替换为新罗马字体
10,704 阅读
3
【高数】形心计算公式讲解大全
9,080 阅读
4
Vivado-FPGA Verilog烧写固化教程
8,072 阅读
5
【概论】一阶矩、二阶矩原点矩,中心矩区别与概念
7,854 阅读
🪶微语&随笔
励志美文
我的随笔
工作办公
📡电子&通信
嵌入式&系统
通信&信息处理
编程&脚本笔记
⌨️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
登录
/
注册
18(共108篇)
找到
108
篇与
18
相关的结果
航空电子 FPGA 开发中的需求层级:从 PRD 到 HDD 一张图看懂
摘要本文介绍了航空电子FPGA开发中的需求层级,从PRD到HDD,通过一张图和通俗比喻,帮助读者理解这些文档在DO-254/ARP4754体系中的位置和作用,以及FPGA工程师在需求追溯中的角色和职责。关键词:航空电子, FPGA, DO-254, 需求层级, 硬件设计描述目录 一、为什么要搞懂这些文档?(立住规矩) 二、需求层级全景图(自上而下) 三、各层级详解(FPGA 工程师视角)1. SRD — System Requirements Document(系统需求文档) 2. ERD — Equipment Requirements Document(设备需求文档) 3. HRD — Hardware Requirements Document(硬件需求文档) 4. HDD — Hardware Design Description(硬件设计描述) 四、那 PRD 是什么? 五、FPGA 工程师的日常:需求追溯怎么落地?实际工作流示例 六、新手容易踩的坑 七、一句话总结 参考标准 写给 FPGA 新人的话:刚入行航空电子时,相比新人会被一堆缩写搞懵了——SRD、ERD、HRD、HDD、PRD…… 本文试图用一张图和几个通俗比喻,帮你理清这些文档在 DO-254/ARP4754 体系中的位置和作用。一、为什么要搞懂这些文档?(立住规矩) 在航空领域,FPGA 不是"写完了能跑就行"。适航标准(主要是 DO-254)要求你必须证明:每一个硬件功能都有明确的需求来源,每一个需求都被设计实现,每一个设计都被测试验证。 这个过程叫做 需求追溯(Traceability)。而追溯的链条,就是靠一层层的需求文档串起来的。 pmzShTJ.png图片 二、需求层级全景图(自上而下) 如果把一架飞机比作一个人,需求分解的过程就像是从"这个人要做什么"逐步细化到"每个细胞怎么工作"。 flowchart TD classDef aircraft fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#000 classDef system fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000 classDef equipment fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#000 classDef hardware fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px,color:#000 classDef design fill:#fce4ec,stroke:#880e4f,stroke-width:2px,color:#000 classDef fpgaRole fill:#fffde7,stroke:#f57f17,stroke-width:1px,stroke-dasharray: 5 5,color:#000 A["🛫 飞机级需求<br/>Aircraft Level Requirements<br/>『飞机要能自动着陆』"]:::aircraft B["📋 SRD<br/>System Requirements Document<br/>系统需求文档<br/>ARP4754A 管辖<br/>『自动着陆系统接收 GPS/ILS 信号』"]:::system C["📦 ERD<br/>Equipment Requirements Document<br/>设备需求文档<br/>ARP4754A / DO-254 交界<br/>『导航计算机输出位置数据,20Hz』"]:::equipment D["🔧 HRD<br/>Hardware Requirements Document<br/>硬件需求文档<br/>DO-254 核心管辖<br/>『FPGA 实现 UART 115200 8N1』"]:::hardware E["💻 HDD<br/>Hardware Design Description<br/>硬件设计描述<br/>DO-254 实现层<br/>Verilog/VHDL、原理图、约束文件"]:::design P["📑 PRD<br/>Product/Project Requirements Document<br/>产品/项目需求文档<br/>公司内部定义(非 DO-254 标准术语)"]:::system R3["👤 FPGA 工程师<br/>✍️ 核心编写工作"]:::fpgaRole R4["👤 FPGA 工程师<br/>✍️ 核心设计实现"]:::fpgaRole A -->|"分解为"| B B -->|"分配到设备"| C C -->|"细化到硬件"| D D -->|"设计实现为"| E P -.->|"可能分解为"| C D -.-> R3 E -.-> R4 E -.->|"向上追溯"| D D -.->|"向上追溯"| C C -.->|"向上追溯"| B B -.->|"向上追溯"| A三、各层级详解(FPGA 工程师视角) 1. SRD — System Requirements Document(系统需求文档) 这是谁写的? 系统工程师。 FPGA 工程师需要做什么? 读它,理解系统要做什么。 SRD 描述的是系统层面的功能和性能。比如"飞行管理系统(FMS)要能在 5 秒内完成航路计算"。这时候 FPGA 还只是系统框图里的一个"黑盒子",可能标注为"信号处理模块"。 关键认知:SRD 通常不会直接提到 FPGA 内部的寄存器或时序,它关心的是系统输入输出。 2. ERD — Equipment Requirements Document(设备需求文档) 这是谁写的? 设备/模块级工程师。 FPGA 工程师需要做什么? 参与评审,确认分配给 FPGA 的功能边界。 ERD 把 SRD 的系统功能分解到具体的设备(LRU,Line Replaceable Unit)。比如"导航计算机"作为一个设备,它要包含电源板、接口板、FPGA 处理板。 ERD 会定义: 设备的物理接口(连接器型号、引脚定义) 设备的性能指标(延迟、吞吐量) 设备的环境要求(温度、振动) 对 FPGA 的意义:ERD 是你 FPGA 所在"盒子"的顶层约束。如果 ERD 说"接口延迟必须小于 1ms",你的 FPGA 设计就必须满足这个。 3. HRD — Hardware Requirements Document(硬件需求文档) 这是谁写的? 硬件工程师(就是你!)。 这是 DO-254 的 C 级审查核心。 HRD 把 ERD 中分配给 FPGA/硬件的功能进一步细化,变成可验证的硬件需求。HRD 里的每一条需求,最终都要在测试用例里被覆盖。 HRD 的典型内容: 需求 ID需求描述来源(追溯)HRD_001FPGA 应实现 1 路 RS422 接收,波特率 115200,8N1ERD_042HRD_002数据解析模块应在收到帧头后 10μs 内输出有效标志ERD_055HRD_003内部 FIFO 深度应不小于 1024 字系统安全性分析关键认知: HRD 必须用可验证的语言写。避免"高性能"、"快速"这种模糊词。 每一条 HRD 都必须能向上追溯到 ERD/SRD。 DO-254 审查官会随机抽 HRD 条目,检查它是否有设计实现和测试覆盖。 4. HDD — Hardware Design Description(硬件设计描述) pasted_1787295560129_x2nj6o.png图片 这是谁写的? 还是你,FPGA 工程师。 这是需求的"实现层"。 HDD 不是一份单独的"文档",而是所有设计产物的集合: HDL 源代码(Verilog/VHDL) 原理图和 PCB 布局 时序约束文件(SDC/XDC) 设计说明文档(模块划分、状态机描述、算法流程) HDD 与 HRD 的关系: HRD 说"要什么"(What) HDD 说"怎么做"(How) DO-254 要求 HDD 能向下覆盖 HRD。简单说:HRD 里的每一条需求,你都要在 HDD 里找到对应的设计实现。 四、那 PRD 是什么? 在 DO-254 和 ARP4754A 的标准术语里,并没有 PRD 这个缩写。 但在实际项目中,你看到的 PRD 通常指以下几种之一: 可能的含义解释在层级中的位置Product Requirements Document(产品需求文档)公司某款"产品"(如某型飞行控制计算机)的顶层需求,向下分解为多个 ERDERD 之上Project Requirements Document(项目需求文档)某个具体项目/合同的交付要求,包含进度、合规、配置管理要求与 ERD 平行或之上Process Requirements Document(过程需求文档)定义开发过程、工具链、编码规范等过程域,非功能需求建议:如果你在公司文档里看到 PRD,先查一下公司的《术语表》或《需求管理计划》,确认具体定义。不同公司用法不同,不要假设。 五、FPGA 工程师的日常:需求追溯怎么落地? 作为 FPGA 工程师,你不需要写 SRD 和 ERD,但你要深刻理解它们。你的核心工作是在 HRD ↔ HDD ↔ 测试 之间建立闭环。 实际工作流示例 flowchart TD %% ========== 样式定义 ========== classDef read fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#000 classDef write fill:#fff8e1,stroke:#f9a825,stroke-width:2px,color:#000 classDef design fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000 classDef test fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#000 classDef review fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px,color:#000 classDef trace fill:#e0f2f1,stroke:#00695c,stroke-width:1px,stroke-dasharray: 5 5,color:#000 classDef milestone fill:#ffebee,stroke:#b71c1c,stroke-width:3px,color:#000 %% ========== 步骤 1:读需求 ========== S1["📖 Step 1:读 SRD / ERD<br/>理解系统要求"]:::read S1_Detail["每秒处理 1000 个雷达回波<br/>(系统级指标)"]:::read %% ========== 步骤 2:写 HRD ========== S2["✍️ Step 2:写 HRD<br/>硬件需求文档"]:::write S2_Detail["FPGA 雷达接口模块<br/>以 1MHz 速率接收 ADC 采样数据"]:::write T1["🔗 追溯至 ERD_xxx"]:::trace %% ========== 步骤 3:写 HDD(设计)========== S3["💻 Step 3:写 HDD<br/>硬件设计描述"]:::design S3_Detail["module adc_interface<br/>SPI 接收 + FIFO 缓冲 + 状态机"]:::design T2["🔗 追溯:HRD_xxx 由<br/>adc_interface 实现"]:::trace %% ========== 步骤 4:写测试 ========== S4["🧪 Step 4:写 Test Cases<br/>测试验证"]:::test S4_Detail["Testbench 注入 1MHz 数据<br/>检查 FIFO 不溢出"]:::test T3["🔗 追溯:覆盖 HRD_xxx<br/>测试通过 ✓"]:::trace %% ========== 步骤 5:审查 ========== S5["🔍 Step 5:适航审查"]:::review S5_Q["审查官:HRD_xxx 这条需求<br/>设计在哪?测了吗?"]:::review S5_A["出示 HDD 代码 + 测试报告<br/>链路闭合 ✓"]:::review DONE["🎉 DO-254 需求追溯闭环"]:::milestone %% ========== 流程连接 ========== S1 --> S1_Detail S1_Detail -->|"分解"| S2 S2 --> S2_Detail S2_Detail --> T1 T1 -->|"驱动"| S3 S3 --> S3_Detail S3_Detail --> T2 T2 -->|"验证"| S4 S4 --> S4_Detail S4_Detail --> T3 T3 -->|"审查"| S5 S5 --> S5_Q S5_Q --> S5_A S5_A --> DONE %% ========== 双向追溯示意 ========== DONE -.->|"向上追溯<br/>Trace Up"| T3 T3 -.-> T2 T2 -.-> T1 T1 -.->|"追溯到源头"| S1_Detail六、新手容易踩的坑 pasted_1787295645472_ckx8zi.png图片 坑为什么危险怎么办HRD 写得太像设计把"用什么算法"写进需求,导致设计变更时必须改需求HRD 只写"要什么",不写"怎么做"需求没有唯一 ID无法追溯,审查时找不到对应关系所有需求必须有全局唯一编号(如 HRD_FPGA_001)需求不可验证"处理速度尽量快"——怎么算通过?用数字说话:"延迟 ≤ 5μs"忽略 ERD 的接口定义FPGA 引脚分配错误,导致板子改线早期就冻结接口需求,与硬件/结构对齐七、一句话总结 SRD 定义系统做什么,ERD 定义设备做什么,HRD 定义 FPGA 做什么,HDD 定义 FPGA 怎么做。PRD 可能是你们公司的"产品需求",先查内部定义。DO-254 的核心就是:每一层都能向上追溯、向下覆盖、中间有验证。参考标准 DO-254:《Design Assurance Guidance for Airborne Electronic Hardware》 ARP4754A:《Guidelines for Development of Civil Aircraft and Systems》 如有错误,欢迎指正交流。
工作办公
# 工作记录
刘航宇
8月21日
0
63
4
Python与Verilog实现卡尔曼滤波器 — 机器人位置估计
摘要本文介绍了卡尔曼滤波在机器人位置估计中的应用,分别用Python和Verilog实现了卡尔曼滤波器,并对比了两种实现方式的优缺点。关键词:Python, Verilog, 卡尔曼滤波器, 机器人位置估计, 硬件加速目录 一、引言:为什么机器人需要卡尔曼滤波? 二、卡尔曼滤波的五大核心公式 三、以机器人为例卡尔曼公式实现和说明设计运动 传感器的观测值 卡尔曼滤波 四、Verilog实现:定点运算与硬件加速4.1 系统架构 4.2 核心代码解析(1)Kalman_1:状态预测(公式1) (2)Kalman_2_3:协方差预测与增益计算(公式2、3) (3)Kalman_4_5:状态与协方差更新(公式4、5) (4)Kalman_ctrl:数据源控制 4.3 10级流水线时序 五、Python vs Verilog:对比分析 六、总结 工程参考代码清单Python仿真代码 Verilog完整工程文件 一、引言:为什么机器人需要卡尔曼滤波? 想象你开发了一个可以在树林里自主导航的机器人。为了知道它的实时位置,你在机器人上安装了GPS传感器,但GPS的精度大约为10米——在布满沟壑和悬崖的树林里,10米的误差足以让机器人坠入深渊。 此时,你还可以获取一些额外的运动信息:里程计可以记录机器人的行走距离,惯性测量单元(IMU)可以感知姿态变化。但这些传感器同样存在噪声和漂移。GPS告诉了你"大概在哪",里程计告诉了你"走了多远",但两者都不完美。 卡尔曼滤波(Kalman Filter) 的核心思想正是:综合利用所有可用的信息,根据其本身的噪声特性分配权重,得到一个比任何单一估计都更准确的结果。 本文将从原理出发,分别用 Python(浮点运算,算法验证) 和 Verilog(定点运算,硬件加速) 实现一维卡尔曼滤波器,用于机器人位置估计。 二、卡尔曼滤波的五大核心公式 卡尔曼滤波是一种最优线性递归估计算法,由预测(Prediction)和更新(Update)两个阶段组成,共五个核心公式: 对于线性离散系统,卡尔曼滤波通过以下五个公式递归运行: 阶段公式含义预测$\hat{X}_k^- = A\hat{X}_{k-1} + BU_{k-1}$先验状态估计:用上一时刻的最优估计预测当前状态预测$P_k^- = AP_{k-1}A^T + Q$先验误差协方差:预测的不确定性会累积更新$K_k = \frac{P_k^-H^T}{HP_k^-H^T + R}$卡尔曼增益:决定"更信预测还是更信测量"更新$\hat{X}_k = \hat{X}_k^- + K_k(Z_k - H\hat{X}_k^-)$后验状态估计:用测量残差修正预测更新$P_k = (I - K_kH)P_k^-$后验误差协方差:更新后的不确定性其中: $A$ 为状态转移矩阵,$B$ 为控制矩阵,$H$ 为观测矩阵 $Q$ 为过程噪声协方差,$R$ 为测量噪声协方差 $K_k$ 是卡尔曼增益,它是整个算法的"灵魂":$P_k^-$ 越大(预测越不靠谱),$K_k$ 越大,越重视测量反馈。 三、以机器人为例卡尔曼公式实现和说明 下面我们用一个例子来说明上述卡尔曼滤波公式的内在逻辑。 假如我们有一台轮式机器人 Robo 如图所示,它只能够进行一维直线运动。 pmqvRRH.png图片 我们用状态向量 $\vec{x}$ 来表示机器人的当前状态: $\vec{x}=\left[ \begin{array}{c}{p} \\ {v}\end{array}\right] \\$ 其中 $p$ 和 $v$ 分别为相对于绝对坐标系 $O_1$ 的位移。 那么我们要做的是,已知某时刻的最佳状态估计 $\hat{\mathbf{x}}_{k}$ ,预测下一时刻的最佳估计 $\hat{\mathbf{x}}_{k+1}$ 。 设计运动 我们为 Robo 设计一个匀加速直线运动。设初始状态向量为 $\vec{x_0}=\left[ \begin{array}{c}{0} &{1}\end{array}\right]^T $ ,并有一个加速指令性,使其以 $a = 0.1$ 的加速度做匀加速直线运动。那么根据匀加速直线运动的位移计算公式,任意时刻 $k$ 的位移 $p$ 与速度 $v$ 都可以由位移、速度、加速度递推得到: $\begin{aligned} p_{k} &=p_{k-1}+v_{k-1} \times \Delta k+a\times \frac{\Delta k^{2}}{2} \\ v_{k} &=v_{k-1}+a \times \Delta k \end{aligned} \tag{3} $ 将其转化为矩阵形式: $\left[ \begin{array}{c}{p_{k}} \\ {v_{k}}\end{array}\right]=\left[ \begin{array}{cc}{1} & {\Delta k} \\ {0} & {1}\end{array}\right] \left[ \begin{array}{c}{p_{k-1}} \\ {v_{k-1}}\end{array}\right]+\left[ \begin{array}{c}{\frac{\Delta k^{2}}{2}} \\ {\Delta k}\end{array}\right] a \tag{4}$ $x_{t}=F_{t} x_{t-1}+B_{t} a \\$ 我们取时间间隔 $\Delta k = 1$ ,由上式编程计算得到机器人的真实状态向量轨迹: import numpy as np import math import matplotlib.pyplot as plt if __name__=="__main__": ## 1.设计一个匀加速直线运动,以观测此运动 X_real = np.mat(np.zeros((2, 100))) # 空矩阵,用于存放真实状态向量 X_real[:, 0] = np.mat([[0.0], # 初始状态向量 [1.0]]) a_real = 0.1# 真实加速度 F = np.mat([[1.0, 1.0], # 状态转移矩阵 [0.0, 1.0]]) Q = np.mat([[0.0001, 0.0], # 状态转移协方差矩阵,我们假设外部干扰很小 [0.0, 0.0001]]) B = np.mat([[0.5], # 控制矩阵 [1.0]]) for i in range(99): X_real[:, i + 1] = F * X_real[:, i] + B * a_real # 计算真实状态向量 X_real = np.array(X_real) fig = plt.figure(1) plt.grid() plt.title('real displacement') plt.xlabel('k (s)') plt.ylabel('x (m)') plt.plot(X_real[0, :]) plt.show() fig = plt.figure(2) plt.grid() plt.title('real velocity') plt.xlabel('k (s)') plt.ylabel('v (m/s)') plt.plot(X_real[1, :]) plt.show() X_real = np.mat(X_real)pasted_1786518907327_xixy8e.png图片 位移真实值 pasted_1786518920293_nxitzh.png图片 速度真实值 传感器的观测值 我们有一台传感器 $O_2$ 放置于机器人的前方,它可以测得机器人位移 $s$ 和速度 $w$ 。当然,传感器自身本身有一定的噪声 $\xi$ ,其协方差为 $R$ 。由于传感器的信息是相对于自身坐标系 $O_2$ 的,而我们的状态向量 $\vec{x}$ 是相对于绝对坐标系 $O_1$ 的,二者的方向刚好相反,我们使用观测矩阵 $\mathbf{H}$ 来转换二者的关系。因此,相对于真实状态向量,传感器的观测向量可以表示如下: $\left[ \begin{array}{c}{s} \\ {w}\end{array}\right]=\left[ \begin{array}{cc}{-1} & 0\\ {0} & {-1}\end{array}\right] \left[ \begin{array}{c}{p} \\ {v}\end{array}\right]+\left[ \begin{array}{c}{\xi_p} \\ {\xi_v}\end{array}\right] \tag{5}$ $z_{t}=H x_{t}+\xi \\$ 我们设传感器噪声的协方差 $R$ 为单位阵(噪声方差为1)。根据上式编写程序,得到的传感器的观测值如下: ## 2.建立传感器观测值 z_t = np.mat(np.zeros((2, 100))) # 空矩阵,用于存放传感器观测值 H = np.mat(np.zeros((2, 2))) H[0, 0], H[1, 1] = -1.0, -1.0 noise = np.mat(np.random.randn(2,100)) # 加入位移方差为1,速度方差为1的传感器噪声 R = np.mat([[1.0, 0.0], # 观测噪声的协方差矩阵 [0.0, 1.0]]) for i in range(100): z_t[:, i] = H * X_real[:, i] + noise[:, i] z_t = np.array(z_t) fig = plt.figure(3) plt.grid() plt.title('sensor displacement') plt.xlabel('k (s)') plt.ylabel('x (m)') plt.plot(z_t[0, :]) plt.show() fig = plt.figure(4) plt.grid() plt.title('sensor velocity') plt.xlabel('k (s)') plt.ylabel('v (m/s)') plt.plot(z_t[1, :]) plt.show() z_t = np.mat(z_t)pasted_1786518974937_94intu.png图片 传感器观测到的位移,与真实值相反且有噪声 pasted_1786519009388_5uhe6m.png图片 传感器观测到的速度,与真实值相反且有噪声 卡尔曼滤波 前两步的操作主要是为了获取数据,现在我们利用预测和更新公式对以上数据进行卡尔曼滤波: ## 3.执行线性卡尔曼滤波 Q = np.mat([[1.0, 0.0], # 状态转移协方差矩阵,我们假设外部干扰很小, [0.0, 1.0]])# 转移矩阵可信度很高 # 建立一系列空序列用于储存结果 X_update = np.mat(np.zeros((2, 100))) P_update = np.zeros((100, 2, 2)) X_predict = np.mat(np.zeros((2, 100))) P_predict = np.zeros((100, 2, 2)) P_update[0, :, :] = np.mat([[1.0, 0.0], # 状态向量协方差矩阵初值 [0.0, 1.0]]) P_predict[0, :, :] = np.mat([[1.0, 0.0], # 状态向量协方差矩阵初值 [0.0, 1.0]]) for i in range(99): # 预测 X_predict[:, i + 1] = F * X_update[:, i] + B * a_real P_p = F * np.mat(P_update[i, :, :]) * F.T + Q P_predict[i + 1, :, :] = P_p # 更新 K = P_p * H.T * np.linalg.inv(H * P_p * H.T + R) # 卡尔曼增益 P_u = P_p - K * H * P_p P_update[i + 1, :, :] = P_u X_update[:, i + 1] = X_predict[:, i + 1] + K * (z_t[:, i + 1] - H * X_predict[:, i + 1]) X_update = np.array(X_update) X_real = np.array(X_real) fig = plt.figure(5) plt.grid() plt.title('Kalman predict displacement') plt.xlabel('k (s)') plt.ylabel('x (m)') plt.plot(X_real[0, :], label='real', color='b') plt.plot(X_update[0, :], label='predict', color='r') plt.legend() plt.show() fig = plt.figure(6) plt.grid() plt.title('Kalman predict velocity') plt.xlabel('k (s)') plt.ylabel('v (m/s)') plt.plot(X_real[1, :], label='real', color='b') plt.plot(X_update[1, :], label='predict', color='r') plt.legend() plt.show() X_update = np.mat(X_update) X_real = np.mat(X_real)pasted_1786519044132_v2numq.png图片 位移卡尔曼滤波结果 pasted_1786519056398_qpvdv1.png图片 速度卡尔曼滤波结果 可以看出,卡尔曼滤波将存在误差的传感器信号和状态向量很好的融合,较好地预测出了真实地机器人运动状态。 四、Verilog实现:定点运算与硬件加速 当我们需要将卡尔曼滤波部署到FPGA或ASIC上时,Python的浮点运算就不再适用。硬件实现需要考虑:定点数表示、流水线设计、时序约束和资源复用。附件中的Verilog代码展示了一个轻量级、10级流水线的卡尔曼滤波器实现。 4.1 系统架构 pasted_1786519272107_29m7o0.png图片 整个系统分为5个模块: 模块功能对应公式Kalman顶层模块,实例化子模块并连接信号—Kalman_1预测状态 X^-公式1Kalman_2_3预测协方差 P^- 并计算增益 Kg公式2、3Kalman_4_5更新状态 X 和协方差 P公式4、5Kalman_ctrl数据控制器,从DPRAM读取测量值—以及3个共享运算器:add(加减法)、mux(乘法)、div(除法)。 4.2 核心代码解析 (1)Kalman_1:状态预测(公式1) module Kalman_1( input clk_50M, input Rst_n, input [15:0] X_last, // t-1时刻的后验估计 input [15:0] B_X_last, // 控制输入(预留,当前为0) output reg [15:0] X_ // t时刻的先验估计 ); // 流水线计数 0~9 always@(posedge clk_50M or negedge Rst_n) begin if(!Rst_n) X_ <= 16'd0; else begin case (Count) 4'd1: begin X_ <= X_last; // X^- = X_last(简化模型) end // ... 其他时钟周期可用于更复杂的预测 endcase end end endmodule设计要点:当前实现为简化模型,直接令 X^- = X_last。B_X_last 预留了控制输入接口,可扩展为带速度项的运动模型。 (2)Kalman_2_3:协方差预测与增益计算(公式2、3) module Kalman_2_3( input clk_50M, input Rst_n, input [15:0] P_last, output reg [15:0] P_, output reg [15:0] Kg ); reg [15:0] Q_error = 0; // 系统过程协方差 reg [15:0] R_error = 3; // 测量噪声协方差 always@(posedge clk_50M or negedge Rst_n) begin case (Count) 4'd1: begin ADD_sub <= 1'd1; // 加法 ADD_dataa <= P_last * 'd100; // P_last 放大100倍 ADD_datab <= Q_error; // + Q end 4'd2: begin P_ <= ADD_result; // P^-(仍放大100倍) ADD_dataa <= ADD_result / 'd10; // 缩小10倍 ADD_datab <= R_error * 'd10; // R 放大10倍 end 4'd3: begin DIV_dataa <= P_; // 被除数 = P^-(×100) DIV_datab <= ADD_result; // 除数 = P^-/10 + R×10 end 4'd4: begin Kg <= DIV_result; // Kg = P^- / (P^- + R) P_ <= P_ / 'd100; // P^- 恢复原始量级 end endcase end endmodule定点数与缩放处理: 这是硬件实现中最关键的部分。由于16位定点数无法直接表示小数,代码采用了缩放因子(Scaling Factor)策略: P_last * 100:将协方差放大100倍,避免小数被截断 R_error * 10:测量噪声放大10倍 最终 Kg 的计算结果也需要在后续模块中相应缩放 // 在Kalman_4_5中处理缩放还原 MULT_datab <= ADD_result / 'd10; // 乘法结果缩小10倍 ADD_datab <= Kg / 10; // Kg缩小10倍用于1-Kg计算(3)Kalman_4_5:状态与协方差更新(公式4、5) module Kalman_4_5( input clk_50M, input Rst_n, input [15:0] in_data, // 测量值 Z input [15:0] X_, // 先验估计 input [15:0] P_, // 先验协方差 input [15:0] Kg, // 卡尔曼增益 output reg [15:0] X, // 后验估计 output reg [15:0] P // 后验协方差 ); always@(posedge clk_50M or negedge Rst_n) begin case (Count) 4'd2: begin // 计算 |Z - X^-|,通过比较大小确定减法顺序 if(in_data > X_) begin ADD_dataa <= in_data; ADD_datab <= X_; end else begin ADD_dataa <= X_; ADD_datab <= in_data; end end 4'd5: begin MULT_dataa <= Kg; MULT_datab <= ADD_result; // Kg * |Z - X^-| end 4'd6: begin // 根据in_data和X_的大小关系,决定加或减 if(in_data > X_) ADD_sub <= 1'd1; // 加法:X = X^- + Kg*(Z-X^-) else ADD_sub <= 1'd0; // 减法:X = X^- - Kg*(X^- - Z) ADD_dataa <= X_; ADD_datab <= MULT_result / 'd10; end 4'd7: begin X <= ADD_result; // 输出更新后的状态 ADD_dataa <= 16'd1; ADD_datab <= Kg / 10; // 1 - Kg/10 end 4'd8: begin MULT_dataa <= P_; MULT_datab <= ADD_result; // P = P^- * (1 - Kg) end 4'd9: begin P <= MULT_result; // 输出更新后的协方差 end endcase end endmodule有符号数处理技巧:由于Verilog中 [15:0] 默认是无符号数,代码巧妙地通过先取绝对值做减法,再根据大小关系决定加减,避免了有符号数运算的复杂性。 (4)Kalman_ctrl:数据源控制 该模块模拟了传感器数据源。实际应用中,这里可以替换为ADC接口、串口接收或传感器总线(如I2C/SPI)。 4.3 10级流水线时序 pasted_1786519515333_7gcyvi.png图片 每个滤波周期 = 10个时钟周期 @ 50MHz = 200ns,即每秒可处理500万次滤波迭代,这是硬件加速相比软件的巨大优势。 五、Python vs Verilog:对比分析 对比维度Python实现Verilog实现数据表示IEEE 754 双精度浮点(64位)16位定点整数 + 缩放因子运算精度高(~15位有效数字)有限(受限于位宽和缩放策略)执行速度受限于CPU,~μs级50MHz时钟,200ns/次迭代资源消耗内存(MB级)LUT/FF/DSP(硬件资源)开发难度低,代码简洁高,需处理时序、缩放、位宽适用场景算法验证、仿真、离线分析实时嵌入式、FPGA加速、ASIC可移植性跨平台依赖具体FPGA/ASIC工艺关键差异——定点数缩放: Python中可以直接写 Kg = P_pred / (P_pred + R),而Verilog中必须手动管理数值范围: # Python:自然优雅 Kg = P_pred / (P_pred + R) # 0.09... X = X_pred + Kg * (z - X_pred)// Verilog:需要精心设计的缩放 // P_last * 100 -> ADD -> /100 -> DIV -> Kg // Kg / 10 -> MULT -> /10 -> ADD -> X这种"不优雅"是硬件实现的代价,但也是达到实时性能的必要手段。 六、总结 卡尔曼滤波是机器人状态估计的基石算法。本文从机器人位置估计这一经典场景出发: 原理层面:梳理了卡尔曼滤波的五大核心公式,阐明了预测与更新的闭环逻辑。 Python层面:利用浮点运算实现了算法原型,验证了滤波器对噪声的抑制效果和增益收敛特性。 Verilog层面:设计了10级流水线的定点数实现,通过缩放因子策略在16位位宽下完成了滤波运算,达到了200ns/次迭代的实时性能。 两种实现方式各有其用:Python用于"想清楚",在算法层面验证正确性;Verilog用于"跑得快",在硬件层面实现实时处理。在实际工程中,通常先用Python/MATLAB进行算法仿真和参数整定,再将验证通过的算法移植到Verilog/VHDL进行硬件部署——这正是从算法到芯片的完整开发流程。 工程参考代码清单 Python仿真代码 见上文第三节完整代码。 Verilog完整工程文件
FPGA&ASIC
通信&信息处理
VLSI&IC验证
软硬件算法
# ASIC/FPGA
# 信号处理
# Verilog
# 软件算法
刘航宇
8月12日
1
59
2
2026-08-11
谈谈电子信息就业与在国企从事FPGA工程师工作一年的感受
摘要本文讲述了一位电子系硕士毕业生在国企从事FPGA工程师工作一年的感受。作者分享了工作体验、FPGA行业特点、国企的“稳”与“慢”,并给在读生提出建议,强调早定位就业方向,坚持学习与总结。关键词:电子信息就业, FPGA工程师, 国企工作, FPGA设计, 职业规划1、前期概括 2025年从西北大学电子系硕士毕业,在西安三环内一家国企上班,位置还不错,并工作于自己喜欢的岗位。读研三年我找到了我喜欢从事的行业。在本科时候就很喜欢嵌入式、FPGA领域,因此大学课余之时,常常自学STM32、Linux、Verilog语法。 blob图片 随着年龄的增加、认知能力的提升,读研期间发现自己对FPGA、数字电路的学习与领悟能力比嵌入式更高,我硕士期间项目也比较吻合从事LoRa通信物联网研究、无人机避障软硬协同研究,因此我硕士阶段主要是以FPGA学习为主、嵌入式和算法学习为辅,在2022~2024年期间,我的博客https://ee.ac.cn也产出了大量优秀的技术文章,阅读量不乏有几年甚至上万,所以做为一名工科生来说,非常建议大家可以写技术博客或者是找个平台记录起来。在我研三找工作时候发现个人简历中出现博客在很多企业都会特别关注。 pasted_1786428918785_27cs6m.png图片 好在功夫不负有心人,最终也收获到了FPGA、嵌入式和IC的offer,我最终选择了本地国企FPGA工程师,目前上了一年班了,和心里预期一样,总体还是不错的。六险二金,足额缴纳,多了一笔企业年金,吃饭什么的食堂做的挺好,国家和公司补贴每顿18元,实际付款4元,这4元也是用每月补贴600元的饭卡支付,600元花不完可以买生活用品或者去华润消费,住宿话有员工宿舍是两人间,不过我没有住宿舍自己租房住。 2、工作体验 刚入职时候,公司对每个新人有完整的培养体系,安排导师,刚开始就像换个大学去上课,有操场、文体中心、上课的教室,大概培养1~2个月开始,就开始慢慢接触项目,直到现在成为项目组不可缺少一员。 工作状态和私企还是有明显区别,做为FPGA工程师我个人感觉是工作紧张和松弛都是有度,大多数情况下当天的活当天可以干完,到点下班还是可以的,法定节假日如果不是项目特别着急也可以正常休假,一年有三个大假期在春节、国企和8月份时候,和之前在私企实习相比感觉自己还可以活的像个人,而不是动物。 2.1、谈谈FPGA pasted_1786432958554_utsafb.png图片 FPGA这个行业,本质是比硬件偏一点一点软件,比软件非常偏硬件,也可以理解为嵌入式的最底层设计或者面向基础芯片的数字电路式驱动开发。根据自己实习和工作的所见所闻来说,越偏软件越容易出现加班,无论私企还是国庆,越偏硬件“卷”的程度越低,加班压力排名:纯软>嵌入式>FPGA/IC>硬件工程师。某种程度来说,软件方向似乎在一直内卷,高考录取分“卷”、考研竞争“卷”、工作加班“卷”,电子信息专业从事硬件的你,有时候转方向到软件方向未必是一件好事。 2.2、要做好从"写代码"到"做产品"的思维转变 工作时候不要认为是学校里的那一套:算法验证通过、仿真没问题、上板跑起来,就算交差了。但在这里,FPGA不是实验室里的Demo,而是流程与规矩。 一份FPGA设计从需求分析到最终交付,中间要经过需求追溯、架构评审、代码审查、仿真验证、综合实现、时序收敛、硬件在环测试……每一个环节都有文档、有签字、有基线。流程多不是为了烦人,而是立住流程规矩,好比短剧、小说中中常有“不守规矩流程”的人酿成大祸或者用户流失,规矩流程其实是一种内部的保证。 2.3、国企的"稳"与"慢" 稳,体现在资源和平台上。 项目周期以年为单位,不会今天立项明天砍。你有足够的时间去打磨一个设计,去反复迭代优化。培训体系相对完善。更重要的是,你参与的项目往往有国家战略背景,这种"做实事"的成就感,是单纯追求技术时髦无法替代的。 慢,体现在决策和响应上。 一个工具链的升级,可能需要层层审批;引入一个新的IP核,可能要经过漫长的合规审查;想尝试敏捷开发?在这里,"瀑布模型"依然是主流。对于习惯了互联网"小步快跑"的人来说,这种节奏确实需要适应。 在国企,"推事"比"做事"更需要技巧。需要学会写工作文档要点、跨部门协调。技术能力决定了你的下限,但沟通能力和对规则的理解,决定了你的上限 3、写在最后的对各位在读生的建议 无论是本科还是硕士,要趁早定位出“合适”自己的就业方向,无论是软件、硬件还是其他行业,朝着这个方向学习、总结、记录、复盘,坚持一两年将会对你就业有很大帮助。大环境不好,要先收割offer,再看offer的这个工资、地点合不合适,是追求央国企的相对稳定还是大厂的高薪,对于我们人生这条路要走的还长,值得每个人都静下来心来思考思考。
我的随笔
工作办公
# 随笔
刘航宇
8月11日
0
104
3
2026-08-05
西北大学电子信息类硕士就业情况与去向分析-详细版
摘要西北大学电子信息类硕士就业率高,达96.7%,专业对口率94.8%。毕业生主要去向为信息技术、制造业、科研设计等,就业单位包括华为、腾讯、比亚迪等知名企业。薪资竞争力强,年薪区间15W-60W。关键词:西北大学, 电子信息类硕士, 就业率, 行业分布, 单位性质目录 一、总体概览:就业率与规模 二、行业分布:硬科技引领,多领域协同 三、单位性质:国企龙头与科技巨头并重1. 国有企业(45.6%) 2. 科技龙头企业(36.4%) 3. 科研设计单位(10.3%) 四、地域分布:聚焦产业高地,本地深耕与全国辐射并重 五、专业对口率:人岗精准匹配 六、电子信息类(843)考研与就业联动 七、总结 一、总体概览:就业率与规模 西北大学肇始于1902年的陕西大学堂和京师大学堂速成科仕学馆。1912年始称西北大学,为国家"211工程"重点建设高校和"双一流"学科建设高校,目前2022~2025届三届硕士毕业生共计997人,总体就业率达到96.7%,专业对口率高达94.8%。 西北大学的电子信息学院与计算机学院的整体数据表明,毕业生继续攻读学位占比超40%,就业率稳定在95%左右。大量入职国家级研究院所、大型银行、政府机关;华为、阿里、京东、腾讯、字节跳动、百度等大型IT企业,市场认可度高。 pmTmrI1.png图片 二、行业分布:硬科技引领,多领域协同 从行业流向来看,西北大学电子信息类硕士毕业生的就业呈现"硬科技引领、多领域协同"的高质量格局,核心领域就业占比突出: 行业领域占比主要去向单位信息传输、软件和信息技术服务业38.4%华为、腾讯、百度、阿里、字节跳动制造业(含高端电子制造)33.0%比亚迪、北方华创、半导体/集成电路企业科学研究和技术服务业12.5%中国电科38所/39所、航天科技集团金融业8.8%中国银行、工商银行、中信银行(技术岗)电力、能源、交通业4.35%国家电网、中国电力科学研究院、大秦铁路公共管理、国防军工3.9%中国人民银行、太原卫星发射中心、陕西省委组织部教育业3.2%山东大学、火箭军工程大学、北京理工大学值得关注的是,83.9%的毕业生进入了信息技术和先进制造两大核心产业,100%覆盖"十四五"战略性新兴产业。特别是在半导体、集成电路、量子技术(芯动微电子)、智能驾驶(上海引望智能)等前沿领域,就业占比显著提升,既体现了行业发展方向,也彰显了我校人才培养对高端岗位的前瞻性适配。 三、单位性质:国企龙头与科技巨头并重 从就业单位性质来看,西北大学电子信息类硕士毕业生的去向呈现"三足鼎立"格局: 1. 国有企业(45.6%) 涵盖中国航天科技集团、国家电网、中国移动等重点国企,为毕业生提供稳定编制、完善福利及清晰的职业晋升通道。 2. 科技龙头企业(36.4%) 以华为、比亚迪、腾讯、阿里、字节跳动、百度、京东等为核心,这类企业技术研发投入大、成长速度快、薪资竞争力突出。据考研经验分享平台的信息,毕业生年薪范围大致在15W至60W之间,高的可达50-60W,低的约18W。 3. 科研设计单位(10.3%) 包括中国电科38所/39所、航天771所、中煤科工研究院等权威科研机构,8.7%的毕业生直接参与国家级科研项目。 整体来看,82.0%的毕业生入职优质国企或行业龙头企业,形成了"稳定保障与成长空间兼具"的高质量就业格局。 四、地域分布:聚焦产业高地,本地深耕与全国辐射并重 就业地域呈现"本地深耕、全国辐射、聚焦高地"的三维布局: 就业地域占比产业聚集区陕西省西安市53.2%高新区(半导体)、雁塔区(IT产业)北京市15.0%海淀区(互联网)、朝阳区(金融)广东省(深圳、广州)11.0%南山区(腾讯)、坪山区(比亚迪)上海市4.35%浦东新区(芯片设计)其他新一线城市3.52%成都、南京、杭州、苏州87.1%的毕业生选择在西安、北京、深圳、上海等经济发达城市就业。西安作为西部科技核心城市,高新区和雁塔区集聚了大量优质企业,为毕业生提供了"产业绑定+生活便利"的独特优势。而长三角、珠三角地区的就业占比也在显著提升。 五、专业对口率:人岗精准匹配 专业对口率高达94.8%,绝大多数毕业生从事与所学专业直接相关的技术类、科研类岗位,具体表现为: 工程技术人员占比78.4%,学院课程设置与产业实际需求高度匹配; 电子信息类专业93%的毕业生任职于信息技术与制造业技术岗(如研发工程师、系统架构师、算法工程师等),头部企业覆盖率达78.9%; 87.9%的毕业生从事工程技术或科研岗位。 六、电子信息类(843)考研与就业联动 对于有意报考西北大学电子信息类硕士的同学,以下信息值得参考: 招生规模:新一代电子信息技术(专硕)每年约120人,电子科学与技术(学硕)约20人,信息与通信工程(学硕)约20人; 复试分数线:近年来基本为国家线或略高于国家线,报考性价比突出; 就业认可度:西北大学在西安及全国大厂就业认可度较高,"学长学姐去华为、中兴、各大银行的都有"。 更多电子信息考研与就业资讯,可关注微信公众号:843电子信息站 图片 七、总结 综合来看,西北大学电子信息类专业硕士就业呈现以下鲜明特征: 就业率高:总体就业率96.7%,处于较高水平; 行业聚焦:信息技术与先进制造两大核心产业吸纳超八成毕业生; 单位优质:超八成入职国企或行业龙头,科研单位占比可观; 地域集中:近九成在经济发达城市就业,西安本地优势明显; 对口精准:94.8%的专业对口率确保了人才培养与市场需求的高效衔接; 薪资竞争力强:年薪区间15W-60W,技术岗与科研岗价值凸显。 对于电子信息领域的考研学子而言,西北大学凭借其211平台、扎实的学科基础、较高的就业质量以及相对友好的报考门槛,无疑是一个性价比突出的选择。而对于即将毕业的硕士生来说,无论是选择深耕技术、投身科研,还是进入金融、国防等交叉领域,西北大学都提供了坚实的跳板与广阔的舞台。 本文数据主要来源于西北大学2023-2025届硕士就业质量报告及互联网相关公开信息,仅供参考。
我的随笔
# 随笔
刘航宇
8月5日
0
80
4
在FPGA上部署轻量BP神经网络-Python与Verilog:信号检测实战
摘要本文介绍了在FPGA上部署轻量BP神经网络进行信号检测的实战,包括系统架构、神经网络模型设计、FPGA硬件实现详解以及关键优化技巧,实验结果表明该方案功耗低、性能好。关键词:FPGA, BP神经网络, 信号检测, 低功耗, 实时处理目录 储备知识: 一、背景:为什么要在FPGA上跑神经网络? 二、系统整体架构 三、神经网络模型设计:极致轻量 四、FPGA硬件实现详解4.1 串并转换模块(Serial-to-Parallel 4.2 数据归一化模块 4.3 隐藏层神经元模块(核心计算单元) 4.4 ReLU激活函数模块 4.5 输出层神经元模块 4.6 阈值判断模块 五、关键优化技巧5.1 归一化的"逆向"处理 5.2 权值二值化(BWN)降低功耗 5.3 亚稳态消除 六、实验结果:功耗与性能 七、总结与思考 python和verilog源程序下载 储备知识: 我们在前文讲了BP神经网络算法和激活函数 BP神经网络算法 https://ee.ac.cn/index.php/archives/612.html 激活函数讲解 https://ee.ac.cn/index.php/archives/617.html 一、背景:为什么要在FPGA上跑神经网络? 在无源物联网(Battery-free IoT)和反向散射通信(Backscatter)场景中,传统的信号检测方法(如能量检测)虽然电路简单,但无法区分同频段的多种信号,且受噪声影响大;而基于PC或GPU的深度学习方案虽然精度高,但功耗动辄数十瓦,显然无法部署在依赖能量采集的微瓦级标签上。 核心矛盾在于:缺乏一种检测精度高、功耗极低(毫瓦级)、且能实时处理的解决方案。 答案是将轻量级的BP神经网络部署到低功耗FPGA上,在边缘端完成信号推理。 二、系统整体架构 整个检测系统的链路如下: graph LR subgraph 射频前端 A[目标信号] --> B[天线] B --> C[阻抗匹配] C --> D[LNA放大] end subgraph 信号处理 D --> E["包络检波(ENV)"] E --> F["ADC采样<br>1MSPS, 12bit"] end subgraph 智能决策 F --> G["FPGA<br>BP神经网络推理"] G --> H[调制控制信号] end在FPGA内部,BP神经网络负责识别当前输入的512点采样数据是否为目标信号的前导码(Preamble)。一旦识别成功,输出控制信号,开启后端的Backscatter调制模块。 FPGA选型:Actel IGLOO/e系列 AGLE600V5-FG484。选择它的原因是: Flash架构:上电即运行,无需外部配置芯片,适合无源系统; 超低功耗:静态功耗仅0.046mW,适合能量采集场景; 资源适中:13824个逻辑单元,足以容纳轻量级网络。 三、神经网络模型设计:极致轻量 为了在FPGA上高效部署,网络结构必须足够"轻"。本文设计的网络结构如下: pasted_1785558315326_lc6zyp.png图片 层级神经元数说明输入层512对应ADC采集的一条信号的512个采样点隐藏层2仅2个神经元,大幅降低计算量输出层1二分类:是目标信号(1) / 非目标信号(0)激活函数:隐藏层和输出层均采用 ReLU。 $$f(x) = \max(0, x)$$ 选择ReLU的原因不仅是训练效果好,更重要的是硬件实现极其简单——只需一个比较器判断正负,无需计算指数或乘法,这对FPGA非常友好。 pasted_1785561816152_bsny25.png图片 训练环境:TensorFlow/Keras,使用Adam优化器和MSE损失函数。在真实环境数据集上训练后,测试集识别准确率可达 95.76%。 pasted_1785561967835_yy5zgm.png图片 四、FPGA硬件实现详解 FPGA内部的神经网络是一个纯推理引擎。本节按数据流方向,逐个拆解每个模块的设计思路和Verilog实现要点。 pasted_1785562098406_gf64mu.png图片 整体RTL结构如下: pasted_1785563631508_f74j4w.png图片 4.1 串并转换模块(Serial-to-Parallel pasted_1785563649652_chqrj8.png图片 ADC(ADS7042)在片选信号CS拉低后,先输出2个前导0,再输出12位有效数据(D11→D0),每个数据位占用一个CLK周期。 设计要点: CS和CLK由FPGA内部产生,CS每16个CLK周期拉低一次; 在CS下降沿开始,用计数器对串行数据移位寄存; 凑齐12位有效数据后,输出一组并行数据 adc_data[11:0]。 波形图 pmhbofe.png图片 module serial_p(input data0_in,input cs,input clk,output[12:0] data0_out,input rst); reg[11:0] temp; reg[12:0] save; integer count; integer cnt; always @(negedge clk or negedge rst) begin if(!rst) begin//reset temp<=12'b000000000000; count<=-1; save<=12'b111111111111; cnt<=-1; end else if(cs&&count%16==0) begin save<={1'b0,temp}; count<=1; cnt<=cnt+1; if(cnt==512) begin cnt<=1; end end else if(cs) begin temp<=12'b0; count<=1; end else if(count<3) count<=count+1; else begin temp<={temp,data0_in}; count<=count+1; end end assign data0_out=save; endmodule 4.2 数据归一化模块 问题:训练时,输入数据被归一化到 [0, 1.8](对应ADC参考电压1.8V)。如果在FPGA中直接用除法做 Vin = adc_data / 4095 * 1.8,会消耗大量逻辑资源(FPGA做除法器代价极高)。 解决方案:在软件端完成归一化,FPGA端只做整数运算。 具体做法: 在Python/Keras训练时,将输入数据 x 归一化为 x_norm = x / 4095 * 1.8; 在提取权值时,将第一层权值 w 除以 1.8 并放大 1024 倍取整; 这样FPGA输入的原始ADC值 adc_data(范围0~4095)直接与预处理后的权值相乘,等效于完成了归一化+加权。 # 创建一个自定义的回调对象 cb = MyCallback() # 以只读方式打开训练数据文件 with open('/content/work/data/data.txt') as traindata: # 读取文件内容 data = traindata.read() # 将文件内容转换为numpy数组,并将数据类型转换为float,并将数组形状调整为(47100, 2) data = np.array(list(data.split()), dtype=float).reshape((47100, 2)) # 创建一个最大最小归一化对象 min_max_scaler = preprocessing.MinMaxScaler() # 归一化 data = data * (1/65535) # 以只读方式打开训练标签文件 with open('/content/work/data/label.txt') as labda: # 读取文件内容 labels = labda.read() # 将文件内容转换为numpy数组,并将数据类型转换为float,并将数组形状调整为(47100, 2) labels = np.array(list(labels.split()), dtype=float).reshape((47100, 2)) # 对data和labels进行乱序,保持它们的对应关系 # 为了保持对应关系,需要先将data和labels沿着第二个维度拼接起来,形成一个(100, 4)的数组 # 然后对这个数组进行乱序,再将它们沿着第二个维度分开,还原成data和labels data_labels = np.concatenate((data, labels), axis=1) np.random.shuffle(data_labels) data = data_labels[:, :2] labels = data_labels[:, 2:]FPGA端实现: 实际上,归一化模块在FPGA中被合并到了权值预处理阶段,硬件上不需要额外的归一化电路。但为了模块化清晰,也可以显式设计一个"电压映射"模块,将12bit ADC码值转换为定点数格式: 工程建议:永远不要在FPGA里做除法。所有线性变换(归一化、缩放)都应在软件端合并到权值中,FPGA只做整数乘加。 4.3 隐藏层神经元模块(核心计算单元) 隐藏层的数学表达式: $$y = \text{ReLU}\left( \sum_{i=1}^{512} x_i \cdot w_i + b \right)$$ , i = 1..512 工程难点: 512次乘累加如果在一个时钟周期完成,需要512个乘法器,资源爆炸; 如果纯串行完成,需要512个时钟周期,一条chirp要512×512=262144个周期,太慢。 解决方案:采用 串行MAC流水线 + 双缓冲FIFO 架构。 设计思路: 权值存储:512个权值和1个偏置在编译时以 reg [31:0] w[0:511] 形式固化在FPGA中; FIFO缓存:ADC每输出一个采样点,立即写入FIFO。FIFO深度设为16即可(只要保证不溢出),因为读出速度(MAC运算)和写入速度(ADC采样)基本匹配; 串行MAC:每个时钟周期从FIFO读出一个数据 xi,与对应的 wi 相乘,累加到累加器 acc; 512个周期后:累加器结果加上偏置 b,送入ReLU模块。 pasted_1785563798085_32317i.png图片 FIFO波形图 pmhqDBt.png图片 关键设计细节: 位宽设计:输入22bit × 权值32bit = 乘法结果54bit,累加512次需要额外9bit,因此累加器至少需要 63bit。如果位宽不够,累加会溢出,导致结果完全错误。 时序约束:乘法器和加法器要满足16MHz时钟的建立保持时间。如果组合逻辑延迟太大,需要插入流水线寄存器(将乘法器和加法器分两级完成)。 双神经元并行:隐藏层有2个神经元,需要例化两个上述模块,各自独立的权值存储和累加器,但共享同一个FIFO输入。 4.4 ReLU激活函数模块 ReLU的硬件实现是整个网络中最简单的部分: pmhqWcj.png图片 module relu ( input wire clk, input wire rst_n, input wire [31:0] x_in, // MAC+bias结果 input wire x_valid, output reg [31:0] y_out, output reg y_valid ); always @(posedge clk or negedge rst_n) begin if (!rst_n) begin y_out <= 32'd0; y_valid <= 1'b0; end else begin y_valid <= x_valid; if ($signed(x_in) >= 0) y_out <= x_in; else y_out <= 32'd0; end end endmodule4.5 输出层神经元模块 输出层只有1个神经元,输入是隐藏层2个神经元的输出: $$\text{output} = \text{ReLU}\left( h_1 \cdot w'_1 + h_2 \cdot w'_2 + b' \right)$$ 这个模块与隐藏层类似,但逻辑资源消耗极小。 pmhq7NT.png图片 module nerual_out( clk, rst,x1,x2,data_out ); input clk, rst; input signed[0:49] x1,x2; output signed[79:0] data_out; //<statements> //reg signed[0:28] weight[0:1]; reg signed[0:7] save,save1,save2; localparam bais=-50'd214979034352654; //localparam bais = -26'd6406874; localparam weight0 = 29'd22506522; localparam weight1 =-29'd341584992; //integer bais; integer count; always @(negedge clk or negedge rst) begin if(!rst) //reset,set values and bias begin // bais<=-50'd214979034352654; // weight[0]<=29'd22506522; // weight[1]<=-29'd341584992; save<=0; save1<=0; save2<=0; count<=0; end else begin save1<=x1*weight0; save2<=x2*weight1; count<=count+1; if(count>518) save<=save1+save2+bais;//Multiply the input by the weights and add up the results end end assign data_out=save; endmodule4.6 阈值判断模块 为什么需要阈值判断? 前面提到,我们在保存权值时将所有参数扩大了 1024 倍(即 $2^{10}$)。这意味着: 隐藏层的输出值也被放大了约1024倍; 输出层的计算结果同样被放大; 原本训练时,输出层ReLU后大于0即判定为目标信号; 现在由于数值被放大,不能简单以0为界,需要设定一个等效的阈值。 阈值推导: 原始判定边界:输出 > 0.5(二分类常用阈值); 放大后等效边界:threshold = 0.5 × 1024 × 1024 = 524288; 实际上,由于隐藏层和输出层都放大了1024倍,总放大倍数为 $1024^2 = 1048576$,因此: threshold = 0.5 × 1048576 = 524288。 波形图 pmhqXv9.png图片 五、关键优化技巧 5.1 归一化的"逆向"处理 再次强调这个最重要的优化: 处理方式FPGA资源消耗推荐度FPGA内做除法(adc/4095)极高,需除法器IP❌FPGA内用移位近似除法中等,精度损失大△权值预处理(本文方法)零额外消耗✅具体做法: 训练时归一化:$V_{\text{in}} = \frac{\text{adc_data}}{4095} \times 1.8$; 提取权值时:$w' = \frac{w}{1.8} \times 1024$,取整; FPGA输入:直接使用原始ADC值 $x$(0~4095); 等效运算。 5.2 权值二值化(BWN)降低功耗 为了进一步压缩模型、降低功耗,本文采用了 Binary-Weight-Networks (BWN) 对权值进行二值化: 权值只取 +1 或 -1; 引入缩放因子 α 保证量化后的权值尽可能接近原始浮点权值。 二值化后,乘法运算退化为加减法或符号位判断,FPGA资源占用和动态功耗大幅下降。虽然精度略有损失,但在本场景中仍能保持97%以上的识别率。 二值化乘法器的Verilog实现: // 二值化权值乘法:只需判断符号 wire signed [31:0] product; assign product = bin_weight ? x_in : -x_in; // bin_weight=1为正,0为负相比32bit×32bit的硬件乘法器,这个逻辑几乎不消耗DSP资源。 5.3 亚稳态消除 ADC数据输入和CS信号是异步信号(相对于16MHz系统时钟),必须做双级触发器同步,防止亚稳态传播: reg adc_dat_d1, adc_dat_d2; always @(posedge clk) begin adc_dat_d1 <= adc_dat; // 第一级 adc_dat_d2 <= adc_dat_d1; // 第二级(稳定后使用) end wire adc_dat_sync = adc_dat_d2;六、实验结果:功耗与性能 通过 Actel Libero SOC 对FPGA各模块进行功耗仿真,结果如下: 工作模式静态功耗动态功耗总功耗空闲态0.046 mW00.046 mW正常工作0.046 mW2.79 mW2.835 mWFlash*Freeze模式0.115 mW00.115 mW2.835 mW 的总功耗,对于低功耗设备来说是完全可以接受的。 各子模块动态功耗分解: 串并转换:0.01 mW 隐藏层神经元(含FIFO):0.083 mW ReLU激活:0.067 mW 输出层神经元:0.067 mW 七、总结与思考 本文介绍了一套完整的从算法到硬件的轻量化神经网络部署方案: 算法层:针对低功耗约束,设计了512-2-1极简BP网络,避免使用CNN/RNN等重算力模型; 数据层:在真实环境中采集目标信号,建立含噪样本库,并通过数据增强提升泛化能力; 硬件层:基于Actel低功耗FPGA,用FIFO流水、整数运算、ReLU硬判决、权值二值化等手段,实现了毫瓦级推理; 系统层:与射频前端(LNA、包络检波、ADC)紧密配合,形成完整的标签信号检测链路。 可扩展性:该FPGA神经网络框架不仅可用于目标信号检测,只需更换训练数据集和权值参数,即可用于FMCW、RFID等其他射频信号的识别,具备良好的通用性。 python和verilog源程序下载
FPGA&ASIC
通信&信息处理
机器学习
软硬件算法
# ASIC/FPGA
# 信号处理
# 机器学习
# 软件算法
# 物联网
# Python
刘航宇
8月1日
0
62
6
数字IC设计项目高性能FFT芯片设计(前后端全流程)
摘要本项目基于SMIC 0.18 μm工艺,设计并实现了16点基-4 FFT算法芯片,工作频率135 MHz,面积4.6953 mm²,单次FFT能耗5.25×10⁻⁶ mJ。项目涵盖RTL建模、功能仿真、逻辑综合与物理实现,并提供了源码与项目文件。关键词:数字IC设计, FFT芯片, 高性能, 16点基-4 FFT算法, 定点数表示目录 一、设计规范简介1.1 功能描述 1.2 性能指标能耗计算 单位面积功耗时间操作次数 二、电路性能分析与结构设计2.1 性能估算面积开销 运算性能 吞吐率 带宽 工作频率 2.2 硬件结构图 2.3 流水线时序设计 2.4 时序过程 三、RTL 模型与仿真验证3.1 验证方法 3.2 验证平台搭建 3.3 仿真验证结果3.3.1 功能点验证 3.3.2 第一组数据验证对比 3.3.3 测试向量 3.3.4 仿真性能统计 四、逻辑综合策略与结果4.1 逻辑综合流程参数设置 4.2 逻辑综合结果时序结果 面积结果 功耗结果 资源使用 设计违例 五、物理实现与结果分析5.1 ICC 设计步骤5.1.1 数据设定 (Data Setup) 5.1.2 布局规划 (Floorplanning) 5.1.3 布局 (Placement) 5.1.4 时钟树综合 (CTS) 5.1.5 布线 (Routing) 5.2 ICC 设计结果5.2.1 芯片概貌 5.2.2 时序结果 5.2.3 面积结果 5.2.4 功耗结果 5.2.5 芯片压降 5.2.6 拥塞分析 5.2.7 资源使用 5.2.8 设计违例 六、设计总结6.1 遇到的主要问题 6.2 相应的解决思路 6.3 关键优化点乘法器优化(频率提升核心) 七、参考资料 八、项目工程下载(源码、文档和参考文献) 一、设计规范简介 1.1 功能描述 本次课程设计的核心目标是实现一个 高能效的快速傅里叶变换(FFT)电路,具体规格如下: 算法:16 点基-4 FFT 算法 输入数据:实部与虚部均为 17 bit(1 bit 符号位 + 8 bit 整数位 + 8 bit 小数位) 旋转因子:实部与虚部均为 8 bit(1 bit 符号位 + 7 bit 数据位) 数据格式:定点数表示,如图 1 所示 图 1 16 点基-4 FFT 蝶形运算原理图 pasted_1785032408236_t4qsdl.png图片 图 2 数据表示方式(17 bit 输入 / 8 bit 旋转因子) 运算数据的实部虚部均为17 bit 而旋转因子的实部与虚部为8 bit pm2xSTf.png图片1.2 性能指标 评价指标具体数值完成一次 16 点 FFT 运算的能耗5.25118208 × 10⁻⁶ mJ单位面积功耗时间 FFT 操作次数4.1987688 × 10⁴ 次/(mm²·mW·s)工作频率135.135135 MHz面积开销4.695300 mm²能耗计算 单次 FFT 能耗 = 每秒能耗 / 每秒 FFT 运算次数 = 44.3512 mW/s / (1 / (16 × 7.4 ns)) = 5.251182 × 10⁻⁶ mJ单位面积功耗时间操作次数 单位面积功耗时间 FFT 操作次数 = 每秒 16 点 FFT 运算次数 / (面积 × 功耗 × 时间) = (1 / (16 × 7.4 ns)) / (4.535456 mm² × 44.3512 mW × 1 s) = 4.1987688 × 10⁴ 次/(mm²·mW·s)二、电路性能分析与结构设计 2.1 性能估算 面积开销 芯片共有 90 个管脚,布局尺寸计算如下: 长:76 μm × 23 + 210 μm × 2 = 2168 μm = 2.168 mm 宽:76 μm × 22 + 210 μm × 2 = 2092 μm = 2.092 mm 总面积:2.168 mm × 2.092 mm = 4.535456 mm² pasted_1785034131111_9ws1vf.png图片 运算性能 每秒可完成 FFT 运算次数 = 1 s / (16 × 7.4 ns) = 8.445946 × 10⁶ 次吞吐率 芯片采用 串行输入输出 方式,每 16 个周期完成一次信号输入,从输入第 13 个信号时开始蝶形运算,经过 8 个周期完成运算,再经过 16 个周期输出: 单次运算周期:13 + 8 + 16 = 37 个周期 单次运算吞吐率:1 / (37 × 7.4 ns) = 3.65 × 10⁶ 次/秒 由于采用了 三级流水线 架构,在连续运算足够多的情况下: 流水运算周期:16 个周期(每周期输出一个结果) 流水吞吐率:1 / (16 × 7.4 ns) = 8.45 × 10⁶ 次/秒 带宽 存储器时钟频率和数据输入速度保持恒定,峰值带宽与平均带宽相同: 带宽 = 17 bit × 2(实部+虚部) × 135.135135 MHz = 4.595 Gbps工作频率 工作频率 = 1 / 时钟周期 = 1 / 7.4 ns = 135.135135 MHz2.2 硬件结构图 图 3 电路硬件结构图 pm2x9k8.png图片 整个电路分为以下模块:模块名称文件名模块类型功能描述控制模块ctrl时序逻辑控制电路中各个模块的运行时序串转并模块s_p时序逻辑将串行输入的数据整合为并行并改变顺序复选器mux时序逻辑按照 FFT 级数选择进入运算模块的数据中转存储模块reg1时序逻辑存储并转发两级 FFT 运算间的运算数据运算模块(蝶形运算)butterfly组合逻辑进行四输入蝶形运算乘法器multi16组合逻辑计算数据与旋转因子间的乘法并转串模块p_s时序逻辑将并行输出的数据分解为串行并倒位序2.3 流水线时序设计 时钟周期1-1617-2122-2930-37input第 N 组输入数据到 REG1第 N+1 组输入数据到 REG1——calculate—两级蝶形运算两级蝶形运算—output——第 N-1 组从 REG3 输出第 N 组从 REG3 输出2.4 时序过程 pasted_1785033901140_9815cd.png图片 三、RTL 模型与仿真验证 3.1 验证方法 FFT 芯片的验证包括两部分: 输出结果验证:通过 testbench 输入数据,将仿真输出与理论正确结果对比 流水线验证:一次输入多组数据,观察电路是否能流水式执行 3.2 验证平台搭建 在 ModelSim SE-64 10.1c 中,将所有模块的 Verilog 代码通过 top 文件连接,撰写 testbench 从指定文本文件读入输入数据并进行计算。 3.3 仿真验证结果 3.3.1 功能点验证 取三组输入作为测试向量,验证输出结果正确性与流水线功能。 图 4 测试波形(流水线时序验证) pmR29yj.png图片 从波形可见,p_s 模块数据按预期工作:每周期进入 4 个数据,逐个输出,上一组数据输出完毕后下一周期立即输出下一组,流水式执行衔接良好。3.3.2 第一组数据验证对比 X[k]理论输出(实部)实际输出(实部)理论输出(虚部)实际输出(虚部)X[0]0_00010010_000000000_00010001_110010000_00010010_000000000_00010001_10111000X[1]0_00000101_011011100_00000101_010111101_11110000_101110101_11110000_11100101X[2]1_11110110_000000001_11110110_001001100_00000100_001111100_00000100_00101101X[3]0_00000010_000011010_00000010_000000110_00001010_010111000_00001010_00111100X[4]0_00001000_000000000_00000111_111011001_11110110_000000001_11110110_00101000X[5]1_11110110_001100001_11110111_010011010_00000011_100100000_00000011_10000100X[6]0_00000011_001010110_00000011_000111000_00000010_111000000_00000001_01100101X[7]0_00000000_011011010_00000000_011101000_00000010_111000000_00000010_11100001X[8]0_00000010_000000000_00000010_000010001_11111110_000000001_11111110_00001000X[9]0_00000101_111000010_00000101_110100101_11111111_111101011_11111111_11110111X[10]1_11110110_000000001_11110110_001001101_11111011_110000011_11111011_11010001X[11]0_00001001_010000100_00001001_001001110_00000100_111100110_00000100_11100010X[12]1_11111100_000000001_11111100_000111001_11111010_000000001_11111010_00011000X[13]1_11111101_011111111_11111101_100010110_00000011_101111110_00000011_10110000X[14]0_00001000_110101000_00001000_101100001_11111110_100101011_11111110_10011101X[15]1_11110100_010000101_11110100_011010101_11110101_110011111_11110101_11110001图 5 Transcript 输出 pasted_1785032904139_lcns0m.png图片 通过对比可知,实际输出与理论输出的实部与虚部均十分接近,在误差范围内可认为相等(存在数据位宽限制等不可避免的截断误差)。第二组、第三组数据经同样验证,结果一致。3.3.3 测试向量 第一组测试向量: 0000000010000000000000000000000000 0000000000000000000000000000000000 0000000010000000000000000000000000 0000000000000000000000000000000000 0000000110000000000000000000000000 0000000100000000000000010000000000 0000000010000000000000100000000000 0000000100000000000000001000000000 0000000010000000000000000000000000 0000000100000000000000010000000000 0000000010000000000000000000000000 0000000000000000000000000000000000 0000000010000000000000000000000000 0000000010000000000000000000000000 0000000010000000000000000000000000 0000000010000000000000000000000000第二组测试向量: 全 1 序列(16 个相同数据) 第三组测试向量: 仅第一个数据为 1,其余为 0 3.3.4 仿真性能统计 ✅ 成功实现 16 点 FFT 算法,输出结果与理论值一致 ✅ 每周期输入一个数据,每周期输出一个数据 ✅ 成功实现流水式运行 ✅ 多次测试输出结果稳定,未出现错误 四、逻辑综合策略与结果 4.1 逻辑综合流程 使用 Synopsys Design Compiler (DC) 进行逻辑综合,主要流程: 指定库文件 → 读入设计(转GTECH) → 定义环境 → 设计约束 → 编译策略 → 综合优化 → 存储网表pasted_1785034037920_i4ehar.png图片 参数设置 参数数值工艺SMIC 0.18 μm时钟周期7.4 ns输入输出延迟3.7 ns工作电压1.62 V运行温度125 ℃面积约束0(无约束)互连模型balanced_tree时钟延时4.3 ns不确定时钟0.5 ns过渡时钟0.2 ns端口最大过渡3 ns4.2 逻辑综合结果 时序结果 项目时间 (ns)clock clk (rise edge)7.40clock network delay (ideal)4.30clock uncertainty-0.50clk slack (MET)0.007.4 ns 时钟符合设计要求,换算工作频率约为 135 MHz。 面积结果 项目数量项目面积 (μm²)Ports70Combinational area1,483,546.41Nets140Non-combinational area153,599.85Cells71Net Interconnect area2,089,790.57References3Total Area3,726,936.83功耗结果 项目数值Global Operating Voltage1.62 VCell Internal Power756.0848 mWNet Switching Power375.6310 mWTotal Dynamic Power1.1317 WCell Leakage Power28.5281 μW资源使用 项目数量Hierarchical Cell85 个Hierarchical Port5,373 个Leaf Cell16,473 个Buf/Inv Cell2,815 个Total Number of Nets18,037 个设计违例 ✅ 电路逻辑综合结果中无设计违例 五、物理实现与结果分析 5.1 ICC 设计步骤 使用 Synopsys IC Compiler (ICC) 进行物理实现,分为四个阶段: pasted_1785034197720_i2g0so.png图片 5.1.1 数据设定 (Data Setup) 读入逻辑和时序库文件、SDC 约束文件、门级网表,以及 IO 物理库文件、TF 工艺文件、TLU+ 文件等物理数据。 5.1.2 布局规划 (Floorplanning) 定义所有管脚(输入输出、时钟、初始信号)及电源组 添加电源环,生成基本布局规划 尝试标准单元虚拟布局(create_fp_placement -timing_driven) 分析拥塞(面积利用率设置不高,拥塞问题较少) 形成电源网络,分析时序,调整电源管脚位置使电压降 < 10% 5.1.3 布局 (Placement) 遵循 NDR 规则,特殊信号线(如 clock)加宽、加大间距 执行 place_opt 进行布局 布局优化(解决建立时间违例) 拥塞分析与优化 5.1.4 时钟树综合 (CTS) 生成时钟网络,加入 buffer 解决负载和 slew time 问题 执行 clock_opt 单时钟同步设计,CTS 工作较简单 5.1.5 布线 (Routing) 全局布线 → 轨道分配 → DRC 修复 先对时钟布线(route_group -all_clock_nets) 解决 verify_lvs 报错(查阅资料后确认无实质影响) 解决虚拟机内存不足导致的闪退问题(清理空间后完成) 5.2 ICC 设计结果 5.2.1 芯片概貌 图 6 芯片概貌 pasted_1785032971553_0fu3op.png图片 顶部和底部各 22 个管脚 左边和右边各 23 个管脚 共 10 组电源管脚:4 组为管脚供电(分列四周),6 组为内核供电(上下各 1 组,左右各 2 组) 5.2.2 时序结果 阶段input (ns)output (ns)clk (ns)data_setup——0.48data_setup_zic1.430.481.19floorplan0.95-1.02-1.39placement1.010.590.19cts_only_psyn1.260.820.62cts_only_cts1.260.830.36route_initial1.350.880.65route_final1.350.850.015.2.3 面积结果 图 7 芯片面积测量 pasted_1785033659880_dlug93.png图片 | 项目 | 面积或长度 |Combinational Area1,477,888.21 μm²Non-combinational Area154,248.50 μm²Net Area0.00 μm²Net X Length620,954.62 nmNet Y Length666,869.44 nmCell Area1,632,136.71 μm²Design Area1,632,136.71 μm²Net Length1,287,824.00 nmTotal Area4,695,300.72 μm²芯片尺寸:2092.38 μm × 2244.00 μm 5.2.4 功耗结果 项目数值Global Operating Voltage1.62 VCell Internal Power33.6445 mWNet Switching Power12.7067 mWTotal Dynamic Power44.3512 mWCell Leakage Power26.0446 μW5.2.5 芯片压降 图 8 芯片压降分析 pasted_1785033682302_ytnfda.png图片 工作电压:1.62 V 最大压降:129.6 mV 压降比例:129.6 / 1620 = 8% < 10%,符合设计要求 5.2.6 拥塞分析 图 9 拥塞分析(Placement / Detail Route / Track Assignment / Global Route) pasted_1785033720429_zl0o70.png图片 由于芯片面积较大,面积利用率设置不高,拥塞问题较少,顺利解决。5.2.7 资源使用 项目数量或利用率Module Cells16,240 个Pins91,614 个IO Pad Cells90 个IO Pins70 个Nets18,151 个Average Pins Per Net3.0535 个Total Std Cell Area514,936.70 μm²Total Pad Cell Area1,612,800.00 μm²Core Size1612.38 × 1764.00 = 2,844,238.32 μm²Pad Core Size1672.38 × 1824.00 = 3,050,421.12 μm²Chip Size2092.38 × 2244.00 = 4,695,300.72 μm²Std cells utilization18.10%Cell/Core Ratio18.10%Cell/Chip Ratio45.32%Number of Cell Rows350图 10 单元密度分布 pasted_1785033741857_8nbcxl.png图片5.2.8 设计违例 ✅ 最终设计无设计违例 六、设计总结 6.1 遇到的主要问题 管脚数量过多,芯片面积过大 电路计算结果不正确 时序模块数据传输不同步,拖慢运算速度 芯片工作频率过低 芯片电压降问题 6.2 相应的解决思路 问题解决方案管脚过多、面积过大采用串并转换减少管脚;改用基-4 FFT 算法并复用蝶形运算模块减小面积计算结果不正确手动计算关键中间数据,逐一比对各模块输入输出,逐个修复 bug数据传输不同步在时序电路传输标志信号的同时,用组合逻辑电路传输数据,避免同步问题工作频率过低重写乘法器:将乘法运算改为组合逻辑状态机判断旋转因子,采用移位方法完成乘法,频率从 82 MHz 提升至 135 MHz电压降问题增加供电管脚数量并调整位置,最大压降控制在 8%(何卫锋老师指导)6.3 关键优化点 乘法器优化(频率提升核心) 优化前: assign mul = in_17bit[16:0] * in_8bit[7:0]; // 直接乘法,延迟大优化后: always @ (in_17bit or in_8bit) begin case (in_8bit) 8'b00000000: neg_mul = 25'b0; 8'b01111111: neg_mul = (in_17bit << 7); // ... 其他旋转因子用移位实现 endcase end通过 set_flatten true 和 compile –map high 策略,将工作频率从 82 MHz 大幅提升至 135 MHz。 七、参考资料 [1] FFT 算法相关教材与文献 [2] Synopsys Design Compiler 用户手册 [3] Synopsys IC Compiler 用户手册 [4] SMIC 0.18 μm 工艺设计套件文档 项目成果总结: 本项目完整实践了数字 IC 设计的前后端全流程,从 RTL 编码、功能仿真、逻辑综合到物理实现,成功将 16 点基-4 FFT 算法芯片化。通过架构优化(串并转换、流水线)、算法优化(基-4 复用)、电路优化(组合逻辑移位乘法器),在 SMIC 0.18 μm 工艺下实现了 135 MHz 工作频率、4.70 mm² 面积、5.25×10⁻⁶ mJ 单次运算能耗的优异性能指标。八、项目工程下载(源码、文档和参考文献) 包含:前端RTL源码、中端DC源码脚本、后端ICC源码脚本、项目报告、参考文件等
FPGA&ASIC
IP&SOC设计
VLSI&IC验证
# VLSI
# ASIC/FPGA
# TCL脚本
# SOC设计
刘航宇
7月26日
1
116
6
基于51/52单片机电容、电感、电阻参数测量系统设计与Proteus仿真
摘要本文介绍了基于51/52单片机的电容、电感、电阻参数测量系统的设计与Proteus仿真。系统采用频率转换法,通过单片机读取频率并计算得到参数值,具有高精度和便于数字化显示的特点。文章详细阐述了系统设计、单元电路设计、程序设计以及仿真调试过程。关键词:51单片机, 电容测量, 电感测量, 电阻测量, Proteus仿真目录 一、设计任务和要求 二、方案设计与论证2.1 系统总设计框图 2.2 方案一:电桥平衡原理① 电阻的测量:直流电桥平衡原理 ② 电容的测量:交流电桥平衡原理 ③ 电感的测量:交流电桥平衡原理 2.3 方案二:频率转换法(本设计采用)① 电阻的测量:555 构成单稳态 ② 电容的测量:555 构成单稳态 ③ 电感的测量:电容三点式正弦波振荡 2.4 方案对比与选择 三、单元电路设计和参数计算3.1 电阻的检测单元电路设计 3.2 电容的检测单元电路设计 3.3 电感的检测单元电路设计 四、总原理图及元器件清单4.1 总原理图 4.2 元器件清单 五、程序设计5.1 程序流程图 5.2 核心代码中断函数(频率测量) 主函数 显示函数 键盘检测函数 5.3 完整程序清单及protues仿真文件(附录) 六、安装与调试6.1 仿真调试(1)电阻测量电路仿真 (2)电容测量电路仿真 (3)电感测量电路仿真 调试要点 液晶显示电路调试 仿真整体显示 6.2 实物调试电阻检测 电容检测 电感检测 八、参考文献 九、附录-完整软件程序与protues仿真文件 完整程序与仿真文件见文章末尾 一、设计任务和要求 设计并制作一个元器件参数测量仪 电阻阻值测量,范围:100Ω ~ 1MΩ 电容容值测量,范围:100pF ~ 10000pF 测量精度:±5% 电感参数的测量 扩大量程 提高测量精度 二、方案设计与论证 2.1 系统总设计框图 pasted_1784990858481_sxzptf.png图片 图1 系统总设计框图在测量各元件参数时,采用首先把电感、电容、电阻的参数转化为频率[1],再送至单片机检测,检测结束后,送至 LCD1602 进行显示。 2.2 方案一:电桥平衡原理 ① 电阻的测量:直流电桥平衡原理 pasted_1784990980814_5yavx3.png图片 图2 直流电桥平衡电路图根据电路平衡原理,不断调节电位器 R3,使得电表指针指向正中间,再测量电位器电阻值。 ② 电容的测量:交流电桥平衡原理 pasted_1784991076614_jito75.png图片 图3 交流电桥平衡电路图通过调节 Z1、Z2 使电桥平衡。这时电表的读数为零。通过读取 Z1、Z2、Zn 的值,即可得到被测电容的值。 ③ 电感的测量:交流电桥平衡原理 pasted_1784991402619_84r7tl.png图片 图4 交流电桥平衡电路图2.3 方案二:频率转换法(本设计采用) ① 电阻的测量:555 构成单稳态 pasted_1784991953296_lg44ir.png图片 图5 555 定时器构成单稳态电路图根据 555 定时器构成单稳态,产生脉冲波形,通过单片机读取高低电平得出频率,通过公式换算得到电阻阻值。 ② 电容的测量:555 构成单稳态 pasted_1785027612555_za97df.png图片 图6 555 定时器构成单稳态电路图** 根据 555 定时器构成单稳态,产生脉冲波形,通过单片机读取高低电平得出频率,通过公式换算得到电容值。 ③ 电感的测量:电容三点式正弦波振荡 pasted_1785027682239_4u3sw9.png图片 图7 电容三点式正弦波振荡电路图由电容三点式正弦波振荡电路得出频率,通过单片机读取高低电平得出频率,通过公式换算得到电感值。 2.4 方案对比与选择 对比项方案一(电桥法)方案二(频率法)测量精度受人为调节影响大单片机数字化,精度高操作难度调节麻烦,不易操作自动测量,一键显示电感测量不易测得准确值振荡电路,相对稳定数字化不易数字化便于单片机处理结论: 方案二通过单片机读取转化,精确度会明显提高,且便于数字化显示。故本设计选择方案二。 三、单元电路设计和参数计算 3.1 电阻的检测单元电路设计 pasted_1785027698225_mjicut.png图片 图8 555 定时器构成单稳态电路图由 555 单稳态触发公式: $$f=\frac{1}{\ln2(R_{1}+2R_x)C}$$ 得: $$R_x=\frac{1}{2}(\frac{1}{\ln2fC}-R_{1})$$ 从而,可以计算得出电阻值的大小。 3.2 电容的检测单元电路设计 pasted_1785028035648_x380cp.png图片 图9 555 定时器构成单稳态电路图根据 555 定时器构成单稳态,产生脉冲波形,通过单片机读取高低电平得出频率,通过公式换算得到电容值[2]。 由: $$f=\frac{1}{\ln2^*(R_1+2R_2)^*C_x}$$ 若 R1 = R2,得: $$C_x=\frac{1}{3\ln2^*f^*R_1}$$ 从而,可以计算得出电容值的大小。 3.3 电感的检测单元电路设计 pasted_1785028091408_lymeeb.png图片 图10 电容三点式正弦波振荡电路图由 LC 振荡频率公式: $$f=\frac{1}{2\pi\sqrt{\frac{C_1*C_2}{C_1+C_2}*L_x}}$$ 得: $$L_x=\frac{1}{\left(2\pi f\right)^2\frac{C_1*C_2}{C_1+C_2}}$$ 从而,可以计算得出电感值的大小。 四、总原理图及元器件清单 4.1 总原理图 pasted_1785028273962_o8rzuv.png图片 图11 电阻、电容、电感参数测试总原理图 pasted_1785028294514_u0wku4.png图片 图12 测量电阻、电容部分的原理图 pasted_1785028310991_mmmde2.png图片 图13 测量电感以及 ±5V 发生原理图4.2 元器件清单 元件序号型号数量备注U1STC89C52RC1单片机R3A103J1排阻R2, R3, R17, R1810KΩ4电位器X111.0592M1晶振C325V, 10uF1电解电容C1, C2, C11103 (0.01uF)3瓷片电容R1, R13, R1810KΩ3电阻J1, J2, J3, J4—4按键U216021液晶A1, A2NE5552555 定时器R4300Ω1电阻R5, R6510KΩ2电阻C4~C9104 (0.1uF)6瓷片电容R7, R9100KΩ2电阻C1050V, 22uF1电解电容Q1~Q3S9108 (NPN)3三极管R102KΩ1电阻R8, R11, R141KΩ3电阻C12, C1350V, 47uF2电解电容R12, R1939KΩ2电阻R15, R1651Ω2电阻C1425V, 470uF1电解电容U3NE555321运放五、程序设计 5.1 程序流程图 图14 中断程序流程图 pasted_1785028347463_kh1ui9.png图片 图15 主程序流程图 pasted_1785028417008_m02be6.png图片 初始化 (led_init, t_init, keyscan) ↓ 清屏 ↓ 循环: ├── 显示频率 display_f(f) └── 根据 a 值分支: ├── case 1: 计算电阻 R,显示 display_R(R) ├── case 2: 计算电容 C,显示 display_C(C) └── case 3: 计算电感 L,显示 display_L(L)5.2 核心代码 中断函数(频率测量) void T0_count() interrupt 1 { switch(a) { case 1: // 电阻测量 while(R_out); while(!R_out); TH0 = 0; TL0 = 0; while(R_out); while(!R_out); th0 = TH0; tl0 = TL0; TR0 = 0; break; case 2: // 电容测量 while(C_out); while(!C_out); TH0 = 0; TL0 = 0; while(C_out); while(!C_out); th0 = TH0; tl0 = TL0; TR0 = 0; break; case 3: // 电感测量 while(L_out); while(!L_out); TH0 = 0; TL0 = 0; while(L_out); while(!L_out); th0 = TH0; tl0 = TL0; TR0 = 0; break; } f = 1000000.0 / 1.085069 / (th0 * 256 + tl0) + 0.5; }主函数 void main() { led_init(); // 液晶初始化 t_init(); // 定时器初始化 keyscan(); // 按键检测 write_com(0x01); // 清屏 while(1) { display_f(f); switch(a) { case 1: // 电阻: R = (T/0.693 - 150) 换算 R = (ulong)(5000000.0 / 0.6931472 / f - 150 + 0.5); display_R(R); break; case 2: // 电容: C = 换算公式 C = (int)(100000000.0 / 153 / 0.6931472 / f + 0.5); display_C(C); break; case 3: // 电感: L = 1/(4π²f²C) 换算 L = (int)(1000000000000.0 / 0.1 / PI / PI / f / f + 0.5); display_L(L); break; } } }显示函数 // 频率显示 void display_f(ulong f) { uchar count = 0; ulong f0 = f; while(f) { f = f / 10; count++; } for(num = 5 + count; num > 5; num--) { table2[num] = f0 % 10 + 48; f0 = f0 / 10; } write_com(0x80); for(num = 0; num < 6 + count; num++) { write_data(table2[num]); delayms(5); } } // 电阻显示 void display_R(ulong R) { uchar count = 0; ulong R0 = R; while(R) { R = R / 10; count++; } for(num = 6 + count; num > 6; num--) { table3[num] = R0 % 10 + 48; R0 = R0 / 10; } write_com(0x80 + 0x40); for(num = 0; num < 7 + count; num++) { write_data(table3[num]); delayms(5); } } // 电容显示 void display_C(uint C) { uchar count = 0; uint C0 = C; while(C) { C = C / 10; count++; } for(num = 5 + count; num > 5; num--) { table4[num] = C0 % 10 + 48; C0 = C0 / 10; } write_com(0x80 + 0x40); for(num = 0; num < 6 + count; num++) { write_data(table4[num]); delayms(5); } } // 电感显示 void display_L(uint L) { uchar count = 0; uint L0 = L; while(L) { L = L / 10; count++; } for(num = 5 + count; num > 5; num--) { table5[num] = L0 % 10 + 48; L0 = L0 / 10; } write_com(0x80 + 0x40); for(num = 0; num < 6 + count; num++) { write_data(table5[num]); delayms(5); } }键盘检测函数 void keyscan() { if(key_R == 0) // 检测电阻按键 { delayms(10); // 消抖 if(key_R == 0) a = 1; } else if(key_C == 0) // 检测电容按键 { delayms(10); if(key_C == 0) a = 2; } else if(key_L == 0) // 检测电感按键 { delayms(10); if(key_L == 0) a = 3; } else while(key_R && key_C && key_L); // 等待按键按下 }5.3 完整程序清单及protues仿真文件(附录) 见文末附录部分。 六、安装与调试 6.1 仿真调试 (1)电阻测量电路仿真 图16 电阻测量的仿真电路图 pasted_1785028525830_e8xagf.png图片 图17 电阻测量的仿真输出波形 pasted_1785028553380_77yjdf.png图片(2)电容测量电路仿真 图18 电容测量的仿真电路图 pasted_1785028566386_213kgj.png图片 图19 电容测量的仿真输出波形 pasted_1785028577785_fn2fqc.png图片(3)电感测量电路仿真 图20 电感测量的仿真电路图 pasted_1785028588732_9bvoyx.png图片 图21 电感测量的仿真输出波形 pasted_1785028599631_39ilf2.png图片调试要点 接通电源,用示波器观察输出波形,若为方波,则电路焊接无误,否则检查电路[3] 在调试过程中发现,若改变电源电压,输出方波的频率会发生变化,计算出的数值存在一定误差 当 VCC 为 3.25V 左右时误差较小 液晶显示电路调试 将测量电路的输出分别与单片机的 P1^5、P1^6、P1^7 相接,观察液晶是否显示测量结果 在调试过程中发现,电阻、电容的测量误差较小 由于电容三点式震荡电路的频率不稳定,电感测量的误差较大 仿真整体显示 图22 电阻测量显示 pasted_1785028620741_10mwwy.png图片 图23 电容测量显示 pasted_1785028631102_motyub.png图片 图24 电感测量显示 pasted_1785028644198_twwyn4.png图片 图25 电源生成模块显示 pm2vEdK.png图片6.2 实物调试 实物按照仿真电路搭建,通电显示 "Welcome! RCL detector"。 图26 实物开机界面 pm2vmJe.png图片电阻检测 插入 2.7KΩ 电阻 若显示不准确,调节仿真中 R2 对应的电位器即可改变输出频率,使得检测精准 检测显示 2735Ω,可见调试准确 图27 电阻检测显示 pm2vnRH.png图片电容检测 插入 0.1uF 电容 若显示不准确,调节仿真中 R3 对应的电位器即可改变输出频率,使得检测精准 检测显示 101pF,可见调试准确[4] 图28 电容检测显示 pm2vuzd.png图片电感检测 插入 65mH 电感 若显示不准确,调节程序计算公式5,即可改显示结果,使得检测精准 检测显示 65535uH,可见调试准确 图29 电感检测显示 pm2vMQA.png图片八、参考文献 [1]杨沁佳.浅谈电感传感器微位移检测仪的原理与设计[J].数字通信世界,2019(09):143+159. [2]张璐雅,阮景.一种基于555时基电路的振荡器设计[J].集成电路应用,2019,36(12):12-13. [3]谢琪林.浅谈STC89S51单片机时序[J].电子测试,2020(24):139-140. [4]黄江.基于STM32的高精度电容测量仪设计[J].信息技术与信息化,2020(11):140-141. [5]王晓辉,王智永,韩智伟,胡帅可,谢印庆.基于三极管放大原理的电容三点式LC振荡器的设计[J].电子世界,2020(03):206. [6] E. L. Pankratov. An approach to model manufacturing of an enhanced swing differential Colpitts oscillator based on heterostructures to increase density of their elements with account mismatch-induced stress: on optimization of annealing. 2020, :1-19. 九、附录-完整软件程序与protues仿真文件 // ============================================ // RCL 测量仪程序 // 基于 STC89C52RC + NE555 + LCD1602 // ============================================ #include <reg52.h> #define uint unsigned int #define uchar unsigned char #define ulong unsigned long #define PI 3.1415926 // ========== 字符串常量 ========== uchar code table1[8] = "Welcome! RCL detector"; uchar table2[16] = "f(Hz)="; uchar table3[16] = "R(Ohm)="; uchar table4[16] = "C(pF)="; uchar table5[16] = "L(uH)=";
嵌入式&系统
通信&信息处理
软硬件算法
# 嵌入式
# 信号处理
# 软件算法
# 物联网
刘航宇
7月26日
0
43
5
2026-07-24
利用图像处理技术mfc设计细胞识别程序-C++
摘要本文介绍了利用图像处理技术设计细胞识别程序的过程,包括图像标注、细胞判别、细胞收缩、中心点信息获取、错误信息删除等步骤,并详细阐述了每个步骤的实现方法和代码示例。关键词:细胞识别, 图像处理, MFC, C++, 细胞计数目录 一、图像处理设计目标1.1 课题与技术指标 1.2 任务点 二、设计思路 三、创建工程及算法分析3.1 预备工作及工程创建 3.2 打开 BMP 图片与显示 HSI 值代码分析 主要代码 3.3 Mark - 细胞标记代码分析 主要代码 3.4 OnTwoValue() - 二值化代码分析 状态标记说明 主要代码 3.5 OnFillHoles() - 填洞代码分析 主要代码 3.6 shrink() - 收缩代码分析 主要代码 3.7 findcenter() - 中心点获取代码分析 合并中心点 去除多余圆或者错误圆 主要代码 3.8 count() - 统计代码分析 3.9 OnCellprgAll() - 按序执行全部 四、错误情况及分析4.1 未声明全局变量信息头 4.2 错把指针当作 int 型 4.3 软件菜单栏突然丢失 4.4 矩形框在鼠标未按下就出现了 五、总结 参考文献 一、图像处理设计目标 1.1 课题与技术指标 课题: 利用图像处理技术设计细胞识别程序。 设计指标: 实验 VS2019 MFC 开发平台 待识别图像为 24bit 的真彩色细胞图像进行处理 要求识别出细胞,并且保证准确度情况下,统计出细胞的个数和大小 1.2 任务点 完成图像标注:添加 cdib.h、cdib.cpp、添加函数、添加消息响应函数、添加菜单、打开图像 完成细胞的判别:持续判断 Maybe 点邻域有无 Mark 点、Sobel 计算边缘、删除孤立边缘、生成黑白图像 细胞收缩:利用 stack、vector 获取孔洞坐标、填充孔洞、Edge 处置 0、4 方向、8 方向交替生成边缘 细胞中心点信息获取:递归算法、判别局部是否全是边缘、储存全部中心位置 错误信息的删除:计算中心点均值、半径、去除半径过小的点、包含圆的剔除、相交过大圆的剔除、信息统计与显示 二、设计思路 图像处理之细胞识别的主要工作就是识别细胞数目以及选择出指定大小细胞。设计思路如下图所示。同时我们要去除不符合要求的中心点,来确定最终的细胞数目,去除太小和太大的细胞1,并结合实际来修改各种参数,设计合理。 pasted_1784860654942_5veaow.png图片 图1 系统设计思路 (流程图:打开图像 → HSI 转换 → 细胞标记 → 二值化 → 填洞 → 收缩 → 中心点获取 → 错误删除 → 统计输出)三、创建工程及算法分析 3.1 预备工作及工程创建 (1)VS2019 安装 MFC 与 C++ 开发环境 (2)创建 MFC 工程,配置 MFC 应用程序向导 pasted_1784860679771_dmm4ha.png图片 pasted_1784860708914_t33nmc.png图片 图2 创建工程 / 图3 MFC 配置3.2 打开 BMP 图片与显示 HSI 值 代码分析 (1)在 OnDraw 函数实现显示图片;在 messages 中添加 display() 实现打开一幅图像的功能。在 LoadBmp() 函数中利用 fopen 实现自动打开图片,利用 fread 读取图片信息。 (2)通过:查看 → 建立类向导 → 添加 OnMouseMove() 函数,添加代码实现获取所要信息;实现鼠标移动显示 HSI。 (3)通过函数 RgbtoHsi 实现 RGB 向 HSI 的转化。 RGB → HSI 转换原理: RGB 向 HSI 模型的转换是由一个基于笛卡尔直角坐标系的单位立方体向基于圆柱极坐标的双锥体的转换。基本要求是将 RGB 中的亮度因素分离,将色度分解为色调和饱和度,并用角向量表示色调2。如果直接对 R、G、B 处理,其处理过程中很可能会引起三个量不同程度的变化,这样就会产生色差问题,甚至带来颜色上的失真。HSI 模型的出现,使得在保持色彩无失真的情况下实现图像处理成为可能。 pasted_1784860731276_p9qcks.png图片 图4 HSI 显示效果主要代码 // HSI 主要代码示意 void RgbtoHsi(RGB* rgb, HSI* hsi) { // 将 RGB 转换为 HSI 色彩空间 // H: 色调, S: 饱和度, I: 亮度 // 分离亮度因素,保持色彩无失真 }pasted_1784860760614_roywnx.png图片 图5 HSI 主要代码3.3 Mark - 细胞标记 代码分析 确定 Mark 与 mbMark 是通过半径来的,通过计算图像上的某个像素点与细胞内部像素点的欧几里得距离与归一化门限值分别为 0.09 和 0.15 比较: r < 0.09 → Mark(红色,细胞内部) 0.09 < r < 0.15 → mbMark(蓝色,可能边界) OnMouseMove 函数里调用 RgbtoHsi(&rgb, &Hsi) 函数,可以在屏幕上显示鼠标所指点的坐标以及 RGB、HSI 和灰度值,通过 HSI 的可以选取合适的阈值来找到细胞以及边界3。 边缘提取的方法: 先滤波,去除噪声的影响,防止细胞内的噪声的影响 之后用 Sobel 算子 求出边界 之后画一个 5×5 的矩形,如果没有遇到边界,是噪声,去除 边界点是绿色。细胞边界分别用红色、暗红、蓝色和绿色标记出来 主要代码 // Mark 主要代码示意 // 根据 HSI 距离判断像素属性 // Sobel 边缘检测 + 噪声去除 // 多色标记不同区域pasted_1784860785613_yfx4ad.png图片 pasted_1784860803522_x9441d.png图片 pasted_1784860810461_y7dtuy.png图片 图6 Mark 主要代码 / 图7 标记后情况3.4 OnTwoValue() - 二值化 代码分析 背景灰度值为 128,细胞灰度值为 240,边缘为 255。二值化便于轮廓提取。 均值滤波的基本原理是用均值代替原图像中的各个像素值,即对待处理的当前像素点 (x, y),选择一个模板,该模板由其近邻的若干像素组成,求模板中所有像素的均值,再把该均值赋予当前像素点 (x, y),作为处理后图像在该点上的灰度 g(x, y),即 g(x, y) = 1/m ∑f(x, y);m 为该模板中包含当前像素在内的像素总个数。 状态标记说明 标记值含义0x7Xedge 边界(表示边界没 Mark,存在为四边)0x8XMark -- not edge(是细胞)0xfXMark -- edge0xX1visited(已访问)0x2CENTERED(中心点) m_vCenterPoints.at(j) —— 存储最后中心点的地方 主要代码 // 二值化主要代码示意 // 灰度阈值分割 + 均值滤波 // 状态标记管理pasted_1784860833275_8scd1m.png图片 pasted_1784860839706_6y6bwf.png图片 图8 二值化主要代码 / 图9 二值化现象3.5 OnFillHoles() - 填洞 代码分析 在阈值处理时,如果像素在阈值范围内,则像素将被标志。孔洞填充将先统计所有连通的非标志区域面积,总会有一个或者几个面积特别大的区域,其它的都是面积相对较小的区域。较小或者很小的往往就是系统所要填充的孔洞了。 填洞方法: 用栈(stack)、向量(vector)处理 从四个方向遍历,没访问过的黑点进栈,直到边和走完 之后读值到 xt, yt 并去值,v 放的是点的位置 没有碰到边界和小于 100,则填洞,即令洞的各点值为 Mark 值 将细胞中灰度值为 128 的部分的灰度值设置为 2404 填完细胞中的洞(即为 0 的值)后,处理非 Mark 的边界值(目的是打开边缘),设为 0。 主要代码 // 填洞主要代码示意 // stack + vector 获取孔洞坐标 // 连通区域面积统计 // 小区域填充为 Mark 值pasted_1784860863112_8iao9n.png图片 pasted_1784860868652_0m8z7m.png图片 图10 填洞主要代码 / 图11 填洞现象3.6 shrink() - 收缩 代码分析 收缩的目的是为了方便计数。扫描图像,对图像进行预先的 3 次腐蚀,判断所生成边界点,然后根据原理判定是否标注该点,存放所标志的中心点,便于统计细胞个数及计算细胞半径。 算法: 由 Mark 生成边界,我们有四邻域生成边界和八邻域生成边界 判断该点是否为 Mark 点,如果是 Mark 点的话,判断 i, j 是否是我们选取图片的边界,如果是的话,将该点变成边缘点 否则判断它的上下左右(周围八个点)是否有非 Mark 点,如果有,则将这边变成边缘点,反之不变 最后去掉边界则完成收缩 主要代码 // 收缩主要代码示意 // 3次腐蚀预处理 // 4/8邻域边界生成 // 边界点判定与标记pasted_1784860892612_9svgpz.png图片 图12 收缩主要代码及现象3.7 findcenter() - 中心点获取 代码分析 通过判断 MARK 点上下左右四个方向或者八个方向是否有非 Mark 点,如果有的话即认为是边界,将该点加上边界标志。 在收缩的过程(直到没有边界可去)中: 清除所有标志点 在不超过边下,没有被访问过的边缘,四个方向全是非 Mark 的话(收缩 k < 2 时)则是孤立点 k >= 2,是中心点,保存 x, y, 半径 还有除了边界就是背景,这也要保存(就是消失前保存) Mark(i, j): 八个方向,没有访问过的 Mark,且是边界点,则保存 SaveIT(i, j): 这点不是中心点(相当于没有访问过)则设为中心点(并保存);已经是中心点则去除标志,八方向寻找中心点 值得注意的是我们也要去除半径不大于 2 的孤立点,因为我们认为它的半径太小,是噪声。如果是半径大于 2 的孤立点,我们对他进行标记成中心点,对半径做一点补偿(pt.radius = k + pre_shrink_count + 4,4 为补偿),然后入队。合并中心点 本点是中心点,则八个方向看都不是中心点,则保存。有相连的话,多点求中心与半径(用到了 CalcCenterArea(i, j))。 CalcCenterArea(i, j): 八方向求中心点后去除标志,循环统计。半径取最大,而 x, y 则是取平均。 去除多余圆或者错误圆 相近圆: 圆心距离小于 10,则取半径小的中心点为中心点,取半径最大值 + 2 去除潜在的错误: 即半径小于 9 的去掉。但四个方向,边部补偿,防止边部的细胞被去除 多圆相交时,不相交部分小于则去除: 首先,相交部分先取出来保存 之后,单个圆内的交上了被标志(画一个方框然后统计之内的点数) 之后单个圆不相交的进行统计 不相交的占 50% 以下则清除本圆 主要代码 // 中心点获取主要代码示意 // 递归算法判别局部边缘 // 孤立点过滤与半径补偿 // 中心点合并与错误圆剔除pasted_1784860913700_o2gfhm.png图片 pasted_1784860921869_zuiuiy.png图片 图13 中心点获取主要代码及现象3.8 count() - 统计 代码分析 首先图像重新打开,然后获取细胞内部的 HSI 的最大值和最小值,并且计算细胞、平均半径、平均面积等值。 Count() 函数中首先调用了重载函数 reload(),将图片还原,随后将之前细胞识别所保留下来的信息(细胞个数),从 view.h 中提取信息,将细胞数、半径、面积显示在对话框中。 利用前面修正的结果,在 Count 模块进行总的计数,计算出平均半径和平均面积并输出 文件重新加载模块: 如果 noclick 置为 true,则进行重新加载图片 pasted_1784860943157_r86ld7.png图片 pasted_1784860947860_2i9gn5.png图片 pasted_1784860960332_53obi4.png图片 图14 重新加载图片 / 图15 最终标记现象 3.9 OnCellprgAll() - 按序执行全部 void OnCellprgAll() { // 依次调用前面各个函数 // 实现细胞识别的所有操作步骤 }pasted_1784860975641_sjtz6y.png图片 pasted_1784860983894_4m8nzp.png图片 图16 OnCellprgAll()代码分析: 不难看出调用了前面各个函数可以依次实现细胞识别的所有操作步骤。 四、错误情况及分析 4.1 未声明全局变量信息头 pasted_1784861010264_hf1vnq.png图片 图17 错误案例1原因: 缺少必要的头文件包含或全局变量声明 修正: 在适当位置添加全局变量声明 4.2 错把指针当作 int 型 pasted_1784861026701_fl51zf.png图片 图18 错误案例2修正: 应将 int m_pImage; 改为 BYTE* m_pImage; 4.3 软件菜单栏突然丢失 pasted_1784861045886_ob0u8q.png图片 图19 错误案例3原因: 误碰软件,把菜单移出去了 修正: 可以移动菜单栏使其恢复正常 pasted_1784861066297_b3vczn.png图片 图20 修正错误4.4 矩形框在鼠标未按下就出现了 原因: OnMouseMove 中判断条件不对 修正: 检查鼠标按下状态标志位的逻辑判断 五、总结 本项目基于 VS2019 MFC 平台,利用图像处理技术实现了细胞识别程序。主要技术路线包括: HSI 色彩空间转换 —— 分离亮度与色度,保持色彩无失真处理 Sobel 边缘检测 + 噪声过滤 —— 提取细胞边界 二值化与形态学处理 —— 填洞、腐蚀收缩,简化细胞轮廓 递归中心点检测 —— 通过迭代收缩获取细胞中心 后处理优化 —— 去除噪声点、合并相近圆、剔除错误检测 统计输出 —— 细胞个数、平均半径、平均面积 参考文献 1-2 ↩ 朱会平, 陈志远. 基于细胞显微图像的数量统计应用[J]. 实验室科学, 2011, 14(05): 73-75. ↩ 蔡朋杞. 红细胞识别系统的设计与实现[D]. 电子科技大学, 2011. ↩ 赵秋影. 人体细胞识别技术研究[D]. 长春理工大学, 2007. ↩
通信&信息处理
软硬件算法
# 信号处理
# 图像处理
# 通信&射频
刘航宇
7月24日
0
35
2
国家线已出,26复试与27届考研西北大学843电子信息该如何准备?
摘要西北大学843电子信息考研,26届复试需联系导师,关注时间节点,准备专业课笔试、面试和实验操作;27届初试需刷真题,关注招生简章变动。西大认可度高,不歧视双非,建议早规划,执行力比计划更重要。关键词:西北大学, 843电子信息, 考研复试, 初试准备, 专业课学习🔥 26届复试党(迫在眉睫版) 🌱 27届考研党(早鸟规划版) 27考研概况 843专业招生人数 考试科目 初试题型与分值 下面针对新生常见提问的一些问答: 国家线终于出了是264分!考西北大学843电子信息的宝子们看过来,这篇一次性说清26届复试和27届初试该怎么准备! 图片 peSlNL9.png图片 🔥 26届复试党(迫在眉睫版) 先联系导师,选导师!!提前占据自己喜欢的研究方向 ⏰ 时间节点要记牢 国家线公布后1-2周内,西大自划线+复试名单就会出 复试一般安排在3月底-4月初 📋 西北大学复试考什么?843电子信息复试通常包含: 专业课笔试 综合面试 实验操作 💡 现阶段必做3件事: 速查官网 → 按研究生网站或提供的资料看以往复试细则 联系导师 → 邮件模板准备好,附上简历+初试成绩 狂补专业课 → 重点看初试没考但复试常考的内容 💬 西大复试很公平,不歧视双非!本科没项目也别慌,把毕业设计吃透也能聊! 推荐的复试指导班和资料 ✅26 届 843 复试指导课仅需31(资料整理服务费68) 不到一顿饭钱轻松助你考研一臂之力,报名链接👉 https://mooc1.chaoxing.com/course/portal/f4Jg9QrC9ticE_Jwt1BcCA peSuvvj.png图片 peSlNL9.png图片 🌱 27届考研党(早鸟规划版) 📌 843电子信息考情速览 初试科目:政治、英语一、数学一、843电子基础综合 参考书:模电、数电和电路 🗓️ 全年时间轴(建议版) 图片 全科真题模拟;专业课刷西大历年真题(重点!) ⚠️ 西大843避坑提醒: 真题重复率不低!务必搞到近10年真题(报名27课程或者考研Q群519462257获取) 电路计算题要动手算,别只看不做 关注电子学院和计算机学院官网,9月看招生简章有无变动 推荐的初试系统全程班和资料(报名特惠488,该班级会送复试) ✅27 届 843 电子通信考研系统班:从基础划重点到真题逐题讲,还有复试全程指导,赠送 07-26 年 843/849 真题 + 笔记课件 + 复试资料包,报名链接👉https://mooc1.chaoxing.com/course/260044360.html peSlIW8.png图片 peSlNL9.png图片 请记住: 初试分数是硬道理,复试表现是加分项 别被"本科出身"焦虑绑架,西大真的很公平 现在准备完全来得及,执行力比完美计划更重要 27考研概况 西北大学在大厂如华为、中兴、腾讯和阿里的认可度一向不错,西北大学电子信息类专业在211中报名人数一直较少,21~25年,这几年来看,复试分数线一直都是国家线或者比国家线高几分,因此从报考性价比和上岸概率来说无疑是个不错的选择! 843专业招生人数 新一代电子信息专硕专硕专硕120人左右,电子科学与技术(学硕)20人左右,信息与通信工程(学硕)20人左右 考试科目 模电、数电、电路----(三门课不少章节不考,考的不算多) 初试题型与分值 初试:三门,每门2~3题,每科目占50分 考察基础理解程度 下面针对新生常见提问的一些问答: 问:西北大学843就业情况如何? 答:就业不错,高的有年薪50~60W的,也低的也有18W、15W的,每年有不少去大厂、银行的。 问:西北大学843三个专业初试是一样的吗? 答:完全一样,卷子也一样,复试略有不同 问:我本科不是电子信息类专业可以报名吗? 答:可以,不歧视,根据以往843考研班统计结果来看,是有不少跨专业上岸的,跨幅最大是本科土木 问:我本科是二本,专升本可以吗? 答:可以报名,考试流程都是正常的,对于这类学生可能存在自身学习能力低下,但是只要努力就会有结果,根据以往843考研班统计结果:每年都有二本上岸的,2024年有两位是专升本过来的 问:有没有843考研交流群? 答:有,推荐下面这个843群有500多人QQ群号:519462257QQ群号:519462257QQ群号:519462257 点击快速加入,西大843QQ交流大群 问:843初试都有考哪些东西? 答:模电,数电,电路分析三本书,不过不少地方不考,不用太担心!如果不知道重点可以进一步问学长 问:是否有推荐的靠谱843课程班或资料? 答:下面两个链接是推荐的843班 https://edu.ee.ac.cn/ https://mooc1.chaoxing.com/course/260044360.html peS1nSO.png图片 peSlNL9.png图片 如果只是需要考研初试复试真题资料话可以联系学长哦~ 问:843复试难吗,歧视双非吗? 答:不难中规中矩,不歧视双非,每年有二本上岸的,双非压线上岸的 问:843报考专业如何选择? 答:这个开放的,大家可能根据自己本科专业或者喜好进行选择,学硕毕业要求会难一些,其次招生人数也会少一些
我的随笔
# 西北大学843
刘航宇
1年前
0
2,639
4
2025-01-09
【FPGA】AXI DMA详解
摘要本文详细介绍了AXI DMA的工作原理、ZYNQ中的DMA类型、AXI DMA IP核的配置和使用方法,包括Direct Register模式、Scatter/Gather模式和Cyclic DMA模式,以及AXI DMA的参数和接口分析。关键词:FPGA, AXI DMA, DMA传输, ZYNQ, 数据搬运<DMA简介 ZYNQ DMA简介 三、AXI DMA IP简介 四、AXI DMA参数与接口分析 Xilinx FPGA里面的AXI DMA IP核的简单用法 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 Channel Memory 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接收通道的中断ID #define RX_INTR_ID XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID #define 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; }图片
FPGA&ASIC
# ASIC/FPGA
刘航宇
1年前
1
5,567
10
SoC架构、通信举例、AHB、APB接口
摘要本文介绍了SoC架构,包括处理器内核、存储器、外设等组成部分,并详细阐述了ARM公司推出的AMBA片上总线,如AHB、APB、AXI等,以及它们在CPU与外设通信中的应用。关键词:SoC架构, AHB接口, APB接口, ARM AMBA, AXI总线典型的SoC包括以下部分 CPU与SRAM通信 CPU与I2C接口通信 AHB从接口 AHB主接口 APB桥 APB Slave框图 AXI总线 AXI传输对比 AMBA中AXI AHP 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的SoC 图片 CPU与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比较 图片
IP&SOC设计
# SOC设计
刘航宇
2年前
0
1,437
2
Verilog语言实现读写txt文件方法
摘要本文介绍了Verilog语言中读写txt文件的方法。首先,通过MATLAB生成txt文件,并转换为十六进制格式。然后,在testbench中读取txt文件数据到寄存器,并按地址读取。接着,展示了如何写入txt文件,并提供了示例代码。最后,介绍了使用$display语句进行调试的方法。关键词:Verilog, 文件读写, txt文件, 测试集, 预测结果随着项目难度递增,例如AI芯片设计,不免需要验证Verilog的功能性,那么输入可以是测试集.txt,输出为预测结果/精确率.txt。下面讲解一下Verilog如何实现读写txt文件 一、读txt文件 二、写txt文件 $display语句的使用 demo案例 一、读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 <= {valid_reg[1:0],data_in_valid}; 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图片 图片
FPGA&ASIC
# ASIC/FPGA
刘航宇
2年前
0
1,440
4
1
2
...
9
下一页