01.基础学习
01.基础学习
基础语法、概念
若干教程
https://www.zhihu.com/people/jaspersi-lan/posts
概念
EEPROM
EEPROM(Electrically Erasable Progammable Read Only Memory, E2PROM)是指带电可擦可编程只读存储器,是一种常用的非易失性存储器(掉电数据不丢失)。
一般而言,对于存储类型的芯片,我们比较关注其存储容量。所用的 AT24C64 存储容量为 64Kbit,内部分成 256 页,每页 32 字节,共有 8192 个字节,且其读写操作都是以字节为基本单位。可以把 AT24C64 看作一本书,那么这本书有 256 页,每页有 32 行,每行有 8 个字,总共有 256328=65536个字,对应着 AT24C64 的 64*1024=65536 个 bit。
协议
URAT
UART: Universal Asynchronous Receiver/Transmitter,即通用异步收发器,串口是串行接口的简称,两者组合起来即通用异步串行通信接口。
应用场景:长距离、低速率。
串行通信方式:
同步:通信双方在同一时钟的控制下同步传输数据
- I2C,SPI:需建立同步
异步:具有不规则数据段传送特性的串行数据传输
- UART:任意时刻发送,需要数据的起始位和停止位

- 电磁辐射、不一致的波特率或者长距离传输都可能改变数据位。
- 为正确通信,收发双方约定相同设置,数据位可以选择为5、6、7、8位。
在信息传输通道中,携带数据信息的信号单元叫做码元(因为串口是1bit进行传输的,所以码元即代表一个二进制数),每秒通过信号传输的码元数称为码元的传输速率,简称波特率,用符号"Baud"表示,单位为“波特每秒”(Bps)。
比特率(bps)=波特率 x 单个调制状态对应的二进制位数
在设置好数据格式及传输速率之后, UART 负责完成数据的串并转换, 而信号的传输则由外部驱动电路实现。
电信号的传输过程有着不同的电平标准和接口规范, 针对异步串行通信的接口标准有 RS232(DB9接口)、 RS422、RS485等,它们定义了接口不同的电气特性,如 RS-232 是单端输入输出,而 RS-422/485 为差分输入输出等。

假设系统时钟为50MHZ,波特率设置为115200Bps,即发送一个bit(Baud)的时间为1/115200秒,
则对应为
对于115200Baud,即需要一个至少为9位的波特率计数器来计数。在计数到一般时取采集数据。
结果并行输出,需要一个四位的计数器
对串口输入数据需要进行打拍以消除亚稳态
亚稳态是由于违背了触发器的建立和保持时间产生的。寄存器需要满足一定的建立时间和保持时间,而异步电路没有办法保证者两点。
下图中寄存器B采样可能采样到寄存器A输出的任意状态,包括Q的信号跳变沿。
- 单bit信号:直接多级寄存器同步法,
- FPGA中直接使用2~3级寄存器进行同步处理。
- 多bit信号:异步FIFO或者使用多次握手同步。在握手协议中,异步的REQ/ACK也需要使用单bit同步计数,异步FIFO亦然。
- 单bit信号:直接多级寄存器同步法,
串口回环测试中,尽管串口发送数据是接收数据的反过程,理论上传输的时间是一致的,单考虑到模块内计算波特率会有较小的偏差,且串口对端的通信设备收发数据的波特率同样可能会出现较小的偏差,因此未却表回环测试的正确,串口发送模块的停止可略微提前结束。
note:较小偏差的波特率在串口通信是是允许的,同样可以保证数据可靠稳定的传输。
RS-485
单端传输:在发送和接收过程中,用一路信号线对地线的电压值来表示逻辑“0”和“1”。
差分传输:使用两个信号线来传输一路信号,这两根信号线上传输的信号幅值相等,极性相反,用其差值来表示逻辑”0“和”1“。
在传输过程中,当信号线上叠加了频率、幅值和相位都相同的干扰信号时(共模干扰),对于单端传输而言,由于地线电位为0,则传输的信号就包含了干扰信号;而在差分传输方式下,干扰可以通过两个信号线上电压的差值抵消,相当于抑制了共模干扰。
I2C
I2C 即 Inter-Integrated Circuit(集成电路总线),是由 Philips 半导体公司(现在的 NXP 半导体公司)在八十年代初设计出来的一种简单、双向、二线制总线标准。
1.物理层
多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。
由数据线SDA和时钟线SCL构成通信线路,即可用于发送数据,也可接收数据。
系统仅有一个主机,其他器件都是具有I2C总线的外围从机。
于主从工作方式中,主机启动数据的发送(发出启动信号)并产生时钟信号,数据发送完成后,发射出停止信号。
2.协议层

如上图:
- 状态①-总线空闲状态
- 状态②-起始信号:如果主机想传输数据,即需在SCL为高电平时,将SDA拉低,产生一个起始信号。
- 状态③-数据传输状态:主机可以向从机写数据,也可以读取从机输出的数据,数据的传输由双向数据线(SDA)完成。
- 状态④-停止状态:当数据传输完毕,主机只需产生一个停止信号,高速从机数据传输结束**,停止信号的产生是SCL为高电平时,SDA从低电平跳变到高电平**,从机检测到停止信号后,停止接收数据,且I2C总线跳转回总线空闲状态。

如上图:
- 在串行时钟线SCL为低电平状态,SDA允许改变传输的数据位(1为高电平,0为低电平)
- 在SCL为高电平时,SDA要求保持稳定,如此持续8个时钟周期,第8个时钟周期末,主机释放SDA以使从机应答
- 在第9个时钟周期,SCL为高电平时,
- 从机将SDA拉低以应答;
- 若从机未被检测到为低电平,视为非应答,表明本次数据传输失败。
- 第9个时钟周期末,从机释放SDA以使主机继续传输数据
- 主机发送停止信号,此次传输结束。
整个过程,数据以8bit一字节为单位串行发出,最先发出的是字节的最高位。

3.I2C器件地址(多从机)
每个I2C器件都有一个器件地址,有些I2C器件的器件地址是固定的,而有些I2C器件的器件地址是由一个固定部分和一个可编程的部分构成。
器件地址的可编程部分能最大数量的使这些器件连接到 I2C 总线上,例如 E2PROM 器件,为了增加系统的 E2PROM 容量,可能需要多个 E2PROM。

对于 AT24C64 而言,其器件地址为 1010 加 3 位的可编程地址, 3 位可编程地址由器件上的 3 个管脚A2、 A1、 A0(见上图)的硬件连接决定。当硬件电路上分别将这三个管脚连接到 GND 或 VCC 时,就可以设置不同的可编程地址。
进行数据传输时,主机首先向总线上发出开始信号,对应开始位 S,然后按照从高到低的位序发送器件地址,一般为 7bit,第 8bit 位为读写控制位 R/W,该位为 0 时表示主机对从机进行写操作,当该位为 1 时表示主机对从机进行读操作,然后接收从机响应。对于 AT24C64 来说,其传输器件地址格式如下图所示。


4. 读写时序及注意事项

写时序
当存储器(寄存器)地址超过一个字节能表示的最大数量时,则使用两个字节表示
所有 I2C 设备均支持单字节数据写入操作,但只有部分 I2C 设备支持页写操作,对于AT24C64 的页写,是不能发送超过一页的单元容量的数据的,而 AT24C64 的一页的单元容量为 32Byte,当写完一页的最后一个单元时,地址指针指向该页的开头,如果再写入数据,就会覆盖该页的起始数据。

两者的区别在于,一个字节的数据写入完成后,主机是发送停止信号,单次写;还是继续下一个字节数据的写入
读时序
在发送控制命令时,如果读写控制位 R/W 位为“1”即读命令,主机就处于接收数据的状态,从机从该地址单元输出数据。读数据有三种方式:当前地址读、随机读和连续读。
当前地址读:是指在一次读或写操作后发起读操作。 由于 I2C 器件在读写操作后,其内部的地址指针自动加一,因此当前地址读可以读取下一个字地址的数据。
当前地址读 随机读:
I2C_random_read **注意1:**数据接收完成后,主机产生一个时钟的高电平无应答信号
**注意2:**随机地址读在发送完器件地址和字地址后,竟然又发送起始信号和器件地址,而且第一次发送器件地址时后面的读写控制位为“ 0”,也就是写命令,第二次发送器件地址时后面的读写控制位为“1”,也就是读。
**原因:**需要使从机内的存储单元地址指针指向我们想要读取的存储单元地址处,所以首先发送了一次Dummy Write 也就是虚写操作,只所以称为虚写,是因为我们并不是真的要写数据,而是通过这种虚写操作使地址指针指向虚写操作中字地址的位置,等从机应答后,就可以以当前地址读的方式读数据。
3**.连续读:** 当前地址读和随机读都是一次读取一个字节,连续读是将当前地址读或随机读的主机非应答改成应答,表示继续读取数据 。
- 当前地址连续读
连续读 - 随机地址开始+连续读
SPI
参阅文章:https://blog.csdn.net/weixin_46022434/article/details/105624672
Flash读写测试实验
基本IP核使用
RAM
Random Access Memory,即 随机存取存储器,简称随机存储器。

- 静态RAM只有有供电,其保存数据便不会丢失;动态RAM在供电情况下,还需要根据其要求的时间来对存储的数据进行刷新,才能保证存储的数据不会丢失。
"可以说,查找表、寄存器、组合逻辑和静态RAM构成了整个数字电路体系"
只读存储器是非易失性的存储器,使用率较高为EEPROM,其特点为容量相对较小,存储的一般为器件的配置参数信息
USB 2.0芯片一般会配一个E2PROM 来存储相关的固件信息
单端口RAM
- REGCEA 输出寄存器使能信号:当其为低电平时,读端口保持最后一次输出的数据
- Vivado 软件自带的 Block Memory Generator IP 核(缩写为 BMG, 中文名为块 RAM 生成器)
- Xilinx 7 系列器件内部的 BRAM 全部是真双端口 RAM,但是通过BMG IP 核,我们还可以将其配置为伪双端口 RAM 或者单端口 RAM
具体配置使用中的注意事项:
Operating Mode: RAM 运作模式。
该选项决定了我们在进行写操作期间, DOUT(读数据线)上的数据变化, 有三种模式可选,分别为:
Write First(写优先模式): 当我们在对某个地址进行写操作时,则会将写入的数据传递到读数据线上。
写优先模式时序图 理解点:ENA、ENA、Posedge CLKA
Read First(读优先模式): 当我们在对某个地址进行写操作时,则会将上次写入该地址的数据递到读数据线上。
No Change(不变模式):在该模式下,进行写操作时,读数据线上的数据保持不变。
进行写操作时,读数据线(DOUT)上的数据在写优先模式和读优先模式下会随着地址的变化而输出相应的数据,在不变模式下则会保持最后一次读操作输出的数据
“Port A Optional Output Register” 用于为 RAM 的输出端添加寄存器。其作用是提高BRAM的运行频率和改善时序,当然为此付出的代价就是每勾选一个寄存器,输出就会延迟一拍。
添加寄存器虽然会引入一个时钟周期的延迟,但这种延迟通常是可以接受的,因为它带来的运行频率提升和时序改善对系统性能的整体提升更加显著。在高性能设计中,时序收敛和高频率运行是首要考虑的目标。
提高运行频率:
1.减少组合路径延迟
在没有寄存器的情况下,RAM的输出信号会直接传递到后续逻辑电路。这意味着从RAM读取数据到数据到达下一级电路的整个路径都是组合逻辑,这条路径的延迟将限制电路的最高运行频率
添加寄存器后,数据在从RAM读取后首先被锁存在寄存器中。这样,RAM读取到寄存器之间的路径成为关键路径,通常比未经寄存器的路径要短得多。这有助于提高时钟频率。
2.时钟域跨越(Clock Domain Crossing)
在需要跨越时钟域的设计中,使用寄存器可以缓解跨域同步问题,减少时钟域之间的延迟和不稳定性,从而提高整个系统的运行频率。
改善时序:
时序收敛:
- 在复杂的FPGA设计中,时序收敛(Timing Closure)是一个关键问题。添加寄存器能够打断长的组合路径,使得每个时钟周期内的逻辑运算变得更容易满足时序约束。
- 在时序分析中,添加寄存器可以减少关键路径的组合逻辑延迟,提升时序裕量(Timing Margin),从而使设计更容易收敛。
数据稳定性:
- 寄存器可以确保数据在下一个时钟周期内稳定不变,避免因组合逻辑延迟引起的数据抖动(Glitches)和竞争冒险(Race Conditions),改善时序的稳定性。
双端口RAM
例程与区别
单端口RAM (Single-Port RAM)
单端口RAM只有一个读/写端口,每个时钟周期内只能执行一次读或写操作。
module single_port_ram #(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 8
) (
input clk,
input we,
input [ADDR_WIDTH-1:0] addr,
input [DATA_WIDTH-1:0] din,
output reg [DATA_WIDTH-1:0] dout
);
reg [DATA_WIDTH-1:0] ram [2**ADDR_WIDTH-1:0];
always @(posedge clk) begin
if (we) begin
ram[addr] <= din;
end
dout <= ram[addr];
end
endmodule
伪双端口RAM (Pseudo Dual-Port RAM)
伪双端口RAM利用单端口RAM的读写特性,通过时间分片的方法实现类似双端口的功能。这种RAM有两个端口(一个读端口和一个写端口),但实际上在一个时钟周期内分别进行读和写操作(互斥)。
module pseudo_dual_port_ram #(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 8
) (
input clk,
input we,
input [ADDR_WIDTH-1:0] wr_addr,
input [DATA_WIDTH-1:0] din,
input [ADDR_WIDTH-1:0] rd_addr,
output reg [DATA_WIDTH-1:0] dout
);
reg [DATA_WIDTH-1:0] ram [2**ADDR_WIDTH-1:0];
always @(posedge clk) begin
if (we) begin
ram[wr_addr] <= din;
end
dout <= ram[rd_addr];
end
endmodule
真双端口RAM (True Dual-Port RAM)
真双端口RAM具有两个完全独立的读写端口,可以在同一个时钟周期内同时进行两个读操作、两个写操作,或一个读操作和一个写操作。
module true_dual_port_ram #(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 8
) (
input clk,
input we_a,
input [ADDR_WIDTH-1:0] addr_a,
input [DATA_WIDTH-1:0] din_a,
output reg [DATA_WIDTH-1:0] dout_a,
input we_b,
input [ADDR_WIDTH-1:0] addr_b,
input [DATA_WIDTH-1:0] din_b,
output reg [DATA_WIDTH-1:0] dout_b
);
reg [DATA_WIDTH-1:0] ram [2**ADDR_WIDTH-1:0];
always @(posedge clk) begin
if (we_a) begin
ram[addr_a] <= din_a;
end
dout_a <= ram[addr_a];
if (we_b) begin
ram[addr_b] <= din_b;
end
dout_b <= ram[addr_b];
end
endmodule
注意问题
在伪双端口模式下我们需要避免读写冲突;在真双端口模式下我们需要避免读写冲突和写写冲突。
读写冲突
读写冲突发生在同一个时钟周期内尝试同时读取和写入同一地址。由于伪双端口RAM在实际硬件中只有一个端口,因此这种冲突会导致不可预测的行为。
避免方法:
- 软件控制: 在编写控制逻辑时,确保在同一个时钟周期内不会对同一个地址进行同时读写操作。例如,使用控制信号和状态机来协调读写操作。
- 优先级控制: 如果确实需要在同一个时钟周期内访问同一个地址,可以设定优先级,例如优先执行写操作,读取上一个周期的写入数据。
// 单端口RAM
always @(posedge clk) begin
if (we && (wr_addr == rd_addr)) begin
// 写操作优先
ram[wr_addr] <= din;
dout <= din;
end else begin
if (we) begin
ram[wr_addr] <= din;
end
dout <= ram[rd_addr];
end
end
双端口RAM

写写冲突
写写冲突发生在同一个时钟周期内,对同一个地址进行两次写操作。由于两个写操作同时发生,可能会导致数据不一致。
避免方法:
- 软件控制: 确保在同一个时钟周期内,不会对同一个地址进行两次写操作。
- 写仲裁机制: 在设计中引入仲裁机制,当两个端口同时对同一地址进行写操作时,根据优先级或其他规则决定哪个写操作被执行。
always @(posedge clk) begin
if (we_a && we_b && (addr_a == addr_b)) begin
// 写写冲突,优先级控制或仲裁机制
// 这里假设写A优先
ram[addr_a] <= din_a;
end else begin
if (we_a) begin
ram[addr_a] <= din_a;
end
if (we_b) begin
ram[addr_b] <= din_b;
end
end
dout_a <= ram[addr_a];
dout_b <= ram[addr_b];
end