培训总结

KiCad pcb和原理图设计

  • KiCad,是一款免费开源的PCB设计工具,提供了一个用于原理图输入和PCB布局布线的集成化开发环境,在这个工具中还有用于产生BOM、Gerber文件、对PCB及其上元器件进行3D查看的功能,支持Python脚本定制。
    * KiCad资源
  • 几点心得
    • 少用标签
    • 布局布线的时候首先考虑地线,注意区分数字地和模拟地。
    • 设计的时候一定要规范,严谨

FPGA、Verilog和小脚丫

  • Altera/Intel, Xilinx, Lattice Semi
  • 硬件设计思想
    • 善用IP Core:调用原厂提供的经过验证过的IP内核
    • 硬件设计概念:并行工作、时延
    • 充分仿真:功能仿真、时序仿真、TestBench
  • FPGA设计流程
    • 设计准备:方案论证、系统级设计
    • 设计输入:将所涉及的系统或电路以开发软件要求的某种形式表示出来,并送入计算机的过程。包括原理图输入、硬件描述语言输入、波形输入、IP核、状态机和网表等。
    • 功能仿真:(前仿真?),进行逻辑功能验证。利用波形编辑器和硬件描述语言等建立TestBench,分析仿真报告文件和输出信号波形。没有时延信息,仅验证电路的逻辑功能。
    • 设计处理:编译软件对设计输入文件进行逻辑简化、综合优化和适配,产生编程文件。
      • 语法检查和设计规则检查
      • 逻辑优化和综合:在给定标准元件库和一定设计约束条件下,设计描述从较高层次向较低抽象层次的自动转换和优化过程,有专门的综合器来实现。包括高层次综合(HLS)、门级综合(将高级硬件描述语言转换为门级网表,行为综合、RTL综合)。可综合的硬件描述语言需要满足一定的语法,与综合器和器件相关
      • 适配和分割
      • 布局和布线
      • 生成编程文件
    • 综合后门级功能仿真:前仿真
    • 时序仿真:后仿真,在布局布线后,对系统和各模块进行时序仿真,估计设计的性能,以及检查和消除竞争冒险等。考虑实际器件中的延时问题。
    • 器件编程与测试:
      • 传统调试:示波器、逻辑分析仪等
      • 软件逻辑分析仪:FPGA 片内集成化信号分析工具,利用 FPGA 中未使用的逻辑资源和块存储器,根据用户设定的触发条件,将信号实时的保存到块存储器中,再通过 JTAG 接口传送到计算机,通过计算机的用户界面显示出所采集的时序波形。

  • 三大特点:并行性,时序性,互连性
  • 三种描述方式:数据流描述(assign连续赋值语句),行为级描述(always, initial等块语句),结构化模型(实例化功能模块)
  • 四种逻辑值:低、高、不定(X)和高阻(H)。注:在程序设计时,也要考虑到不定状态和高阻状态。
  • 两种变量
    • 线网数据类型(wire):不具备数据存取功能,表示互连关系。只能在alwags语句块之外,被连续赋值(assign)。
    • 寄存器数据类型(reg):可以存取最后一次的赋值,只能在always和initial语句块中被赋值。
  • 两种参数类型
    • parameter定义的参数:模块内有效,例化时可以通过参数传递更改参数值;
    • localparam定义的参数:不可传递更改。
  • 两种赋值:
    • 阻塞赋值方式(=):块内语句逐条进行赋值,在组合逻辑内使用;
    • 非阻塞赋值方式(⇐):块内赋值语句同时执行,在时序逻辑内使用。
  • 几种常用知识点
    • 连续赋值语句 assign
    • 拼接运算符:{A, B} 将A和B连接起来;{B{A}} 将A重复B次
    • ·define <宏名> <宏文本>
    • ·include “文本名”
    • ·timescale 1ns/100ps :指定仿真时间单位和仿真精度
snippet.verilog
//基本语法结构
module Decode38(A_in, Y_out);   //模块名称(端口列表)
//端口定义申明:
input [2:0] A_in;
output reg [7:0] Y_out;
//内部变量及参数申明
 
//模块功能实现
always@(A_in)
begin
    case(A_in)
        3'b000: Y_out = 8'b1111_1110;
        3'b001: Y_out = 8'b1111_1101;
        3'b010: Y_out = 8'b1111_1011;
        3'b011: Y_out = 8'b1111_0111;
        3'b100: Y_out = 8'b1110_1111;
        3'b101: Y_out = 8'b1101_1111;
        3'b110: Y_out = 8'b1011_1111;
        3'b111: Y_out = 8'b0111_1111;
        default: Y_out = 8'b1111_1111;
    endcase
end
 
endmodule   //模块结束
  • 以Lattice LCMX02-4000HC-4MG132芯片为核心,4320个LUT逻辑资源,36个用户IO,板载编程器,开发工具Lattice Diamond。(开源社区
  • 按键消抖处理:在检测到按键下降沿后,延时20ms,检测到低电平后输出按键按下的脉冲信号。
    • 脉冲边沿检测:利用非阻塞赋值特性,保留两个时钟周期内的按键值
    • 延时:通过计数器计数时钟脉冲实现
    • 巧妙地有效脉冲信号生成方式
snippet.verilog
assign  key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平
 
always @(posedge clk  or  negedge rst)
    begin
        if (!rst) 
        key_sec <= {N{1'b1}};                
    else if (cnt==18'h3ffff)
        key_sec <= key;  
    end
    always @(posedge clk  or  negedge rst)
        begin
            if (!rst)
                key_sec_pre <= {N{1'b1}};
            else                   
                key_sec_pre <= key_sec;             
        end      
    assign  key_pulse = key_sec_pre & (~key_sec);
  • 时钟分频
    • 偶数n分频:计数器对时钟信号上升沿计数到n,计数到 n/2-1 时将信号翻转;
    • 奇数n分频:计数器分别对时钟信号上升沿和下降沿计数到n,计数到 (n-1)/2 时将信号翻转,形成clkp和clkn信号,二者相与输出最后结果。
snippet.verilog
assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p; //对clk进行N分频
  • 状态机
  • 数据传输总线
    • 串行和并行:串行总线由一根数据线每次传输1bit的数据;并行总线由多根数据线同时传输多位数据。
    • 同步和异步:同步通信会在传输数据的同时传递同步的时钟信号,接收端按照时钟信号接收数据;异步通信值传输数据,接收端按照预先规定好的时钟频率接收数据。
    • 单工、全双工和半双工:单工通信仅在一个方向上传输数据,全双工通信使两个设备同时收发数据,半双工通信使设备轮流发送或接收数据。
    • SPI(Serial Peripheral Interface) 同步串行外设总线
      • 四根信号线(SCLK, MOSI, MISO, SS)
        • SCLK:串行时钟
        • MOSI:数据通道,主设备⇒从设备
        • MISO:数据通道,从设备⇒主设备
        • SS:设备选通信号
      • 通信时序
    • I2C(Inter-Integrated Ciruits) 同步串行总线,集成电路之间的连接
      • 两根信号线(SCL、SDA)必须要有上拉电阻
      • 通信时序:按字节交换信息,每个字节后跟一个ACK或NACK
    • UART(Universal Asynchronous Receiver/Transmitter) 异步串行总线
      • 通信时序:起始位、数据位、校验位、停止位、空闲位
    • 几点心得:在verilog设计实现UART功能时,检验起始位下降沿后开启Boud时钟,在Boud时钟中间读取Rx数据,能够保证数据的稳定性;除了UART的基本的并行数据和串行数据相互转化外,可以添加FIFO缓存单元,避免数据丢失;在利用UART通信的时候,可以采用数据帧的格式发送数据,以帧头、数据个数、数据、帧校验和帧尾的格式来传输数据。
  • IP核例化:IP核(Intellectual Property core知识产权核)是一段具有特定电路功能的硬件描述语言程序,该程序与集成电路工艺无关,可以移植到不同的半导体工艺中去生产集成电路芯片。在FPGA的设计中调用IP核可以简化设计流程。

Python基本语法和网络爬虫

  • 数组操作 numpy
  • 数据分析与可视化 pandas, matplotlib.pylot
    • 基于matplotlib的图形可视化python包 seaborn
  • 网络爬虫 requests, BeautifulSoup

机器视觉和神经网络

PYNQ和Vivado

MNIST数据集训练与加速

  • 搭建并训练神经网络
    • 利用tensorflow训练神经网络识别mnist数据集,保存训练参数;利用vivado hls定制卷积和池化ip核;在pynq上复现训练好的神经网络,导入网络参数,利用硬件加速手写数字识别。
    • mnist神经网络的搭建和训练(深入MNIST
      • 开发工具 PyCharm, python 3.7, tensorflow 1.14.0
      • 搭建四层的神经网络:
        • (1, 5, 5, 32)卷积,relu激活,2×2池化;
        • (32, 5, 5, 64)卷积,relu激活,2×2池化;
        • (7X7X64, 1024)全连接层,relu激活,dropout;
        • (1024, 10)全连接输出层,softmax分类;
  • 保存神经网络数据
    • 如何保存训练数据?训练好的网络参数是以浮点数(float)类型存储,而在硬件电路中,浮点数计算较慢,所以把网络参数转化为定点数类型导入网络。设计的硬件ip中,乘法运算采用int16型乘法,所以网络权重参数采用int16存储,但可以配置小数点位数(fractioncnt)来存储小数(= int16 / pow(2, fractioncnt))。将数据以字节的形式存放在bin中。
  • 在pynq的pl中搭建神经网络
    • fpga的配置采用ip设计,主要包括卷积运算ip和池化运算ip。
    • 利用对卷积计算ip和池化运算ip的复用来实现多层卷积神经网络。

  • 在pynq的ps中控制神经网络,能够实现对数字的识别
    • 在jupyter notebook的环境中开发。
    • 主要包括导入比特流文件配置pl部分,读取图片和利用网络识别数字。
  • 参考深入MNIST搭建网络,网络数据按照想要的格式保存下来。
  • Vivado HLS, Vivado
  • 打开Vivado HLS,建立工程poolstream,新建源文件“poolstream.cpp”、“pool_stream.h”和测试文件“testbench.cpp”。
  • 编写基于c++的池化算法:
    • 输入和输出优化:①流的形式 ②输入通道方向按K分块
    • pool1D:按行进行池化
    • pool2D:按列进行池化

snippet.c
#define K 8
typedef ap_int<16>   dtype_dat;
typedef ap_int<16*K> dtype_bus;
typedef struct
{
    dtype_bus data;
    bool last;
}dtype_stream;
snippet.c
void pool_1D(hls::stream<dtype_bus> &in,hls::stream<dtype_bus> &out,int ch_div_K,int height_in,int width_in,int Kx)
参数说明:  
**in** 特征数据流按照[ch_div_K][height_in][width_in]顺序输入;  
**out** 特征数据流按照[ch_div_K][height_in][width_out]顺序输出;  
**Kx** 特征数据流按行进行Kx池化。

snippet.c
void pool_2D(hls::stream<dtype_bus> &in,hls::stream<dtype_bus> &out,int ch_div_K,int height_in,int width_out,int Ky)
参数说明:  
**in** 特征数据流按照[ch_div_K][height_in][width_out]顺序输入;  
**out** 特征数据流按照[ch_div_K][height_out][width_out]顺序输出;  
**Ky** 特征数据流按列进行Ky池化。

  • 优化选项与接口协议设置
    • 对语句块的优化:pipeline与 unroll
    • 对循环块仿真次数的设置:tripcount
    • 对数据存储方式的优化:partition 与 reshape
    • 对数据流(stream)的优化:dataflow
    • 接口协议设置:aphs, maxi, s_axilite, axis等
snippet.c
void pool(hls::stream<dtype_bus> &in,hls::stream<dtype_stream> &out,
int ch_div_K,int height_in,int width_in,
int height_out,int width_out,int Kx,int Ky)
{
    #pragma HLS INTERFACE s_axilite port=return
    #pragma HLS INTERFACE s_axilite port=Ky
    #pragma HLS INTERFACE s_axilite port=width_in
    #pragma HLS INTERFACE s_axilite port=Kx
    #pragma HLS INTERFACE s_axilite port=height_in
    #pragma HLS INTERFACE s_axilite port=height_out
    #pragma HLS INTERFACE s_axilite port=width_out
    #pragma HLS INTERFACE s_axilite port=ch_div_K
 
    #pragma HLS DATAFLOW
    #pragma HLS INTERFACE axis register both port=out
    #pragma HLS INTERFACE axis register both port=in
 
    hls::stream<dtype_bus> stream_tp;
    #pragma HLS STREAM variable=stream_tp depth=8 dim=1
 
    hls::stream<dtype_bus> stream_tp2;
 
    pool_1D(in,stream_tp,ch_div_K,height_in,width_in,Kx);
    pool_2D(stream_tp,stream_tp2,ch_div_K,height_in,width_out,Ky);
    hs2axis(stream_tp2,out,ch_div_K,height_out,width_out);
}
  • C仿真、C综合和 C&RTL联合仿真
    • C仿真是用来检验C++程序的功能,通过编写techbench实现;
    • C综合是根据上述设定的优化选项和接口协议等将C++程序综合成RTL级HDL语言,并生成综合报告,描述综合后的硬件的时序、消耗的逻辑资源和对外接口类型。
    • C&RTL联合仿真是对综合出来的硬件进行仿真,分析仿真波形来修改C++程序或优化选项等。

  • 生成IP核
    • 选择 Export RTL生成ip核,包括硬件描述、HDL程序和C驱动程序等。在Vivado中调用生成的ip核时,需要将生成的文件夹添加到Vivado的ip搜索目录下;在jupyter notebook中配置ip时参考driver目录下的xxx_hw.h文件。

  • 卷积算法
  • Vivado构建pynq的overlay——利用ip配置pynq中的PL部分
    • 搭建如图所示的结构,综合、实现、生成比特流。将生成的.srcs\sources1\bd\design1\hwhandoff\design1.hwh文件和.runs\impl1\design1_wrapper.bit文件找出来并修改为同样的名字。

  • 连上pynq开发板,将hwh文件和bit文件通过本地资源管理器连接pynq上传到板子的文件系统内,并在该目录下新建一个ipynb文件,在该文件中实现相应功能程序。
  • 导入比特流文件,配置pynq上的pl部分。
snippet.python
from pynq import Overlay
from pynq import Xlnk
ol = Overlay("conv.bit")
ol.download()
print(ol.ip_dict.keys())
dma = ol.axi_dma_0
pool = ol.pool_stream_0
conv = ol.Conv_0
xlnk = Xlnk()
  • 导入网络参数
snippet.python
import driver
from MNIST_LARGE_cfg import *
driver.Load_Weight_From_File(W_conv1, "./record/W_conv1.bin")
driver.Load_Weight_From_File(W_fc1, "./record/W_fc1.bin")
driver.Load_Weight_From_File(W_fc2, "./record/W_fc2.bin")
  • 导入数字图片,并使用搭建好的网络识别数字,同时利用内置计时器观察硬件执行时间
snippet.python
start=time.time()
driver.Run_Conv(conv, 1,32, 3,3,  1,1,  1,0,  src_buffer,PTR_IMG,W_conv1,PTR_W_CONV1,h_conv1,PTR_H_CONV1)
driver.Run_Pool(pool,dma, 32,  4,4,  h_conv1,h_pool1)
driver.Run_Conv(conv, 32,256,  7,7,  1,1,  0,0,  h_pool1,PTR_H_POOL1,W_fc1,PTR_W_FC1,h_fc1,PTR_H_FC1)
driver.Run_Conv(conv, 256,10,  1,1,  1,1,  0,0,  h_fc1,PTR_H_FC1,W_fc2,PTR_W_FC2,h_fc2,PTR_H_FC2)
end=time.time()
print("Hardware run time=%s s"%(end-start))
  • 观察最后识别出的数字
snippet.python
max=-32768
num=0
for i in range(10):
    if(h_fc2[i//K][0][0][i%K]>max):
        max=h_fc2[i//K][0][0][i%K]
        num=i;
print("predict num is %d"%num);