实验目的
通过FPGA实现对EEPROM(型号为24LC04B)的单字节写、单字节读、连续写入和连续读出
理论原理
一、IIC
1. 背景
IIC(Inter-Integrated Circuit) 是IIC Bus的简称,中文名为内置集成电路总线,是一种串行通信总线,使用多主从架构,I2C通信方式为同步通信,通信方向为半双工,数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。
2. 物理结构
IIC有两条线,分别是数据线SDA和时钟线SCL,所有数据都是通过SDA线传递的,SCL线并不传输数据,它只是提供时钟周期。IIC通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连到总线的任何器件输出的高电平都将使总线的信号变低,即各器件的SDA及SCL都是“线与”的关系,每个接到IIC总线器件都有唯一的地址。
线与:将多个门电路的输出端接在一起,以实现逻辑“与”的效果,要求每个门电路必须是集电极开路门(OC门)或源极开路门(OD门),这是因为只有当任意一个开关对地导通时,这根线就一定是低电平,从而实现了线与的功能。
3. 数据传输
3.1 空闲状态
I2C 总线总线的 SDA 和 SCL 两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
3.2 数据有效性
IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。在SCL高电平期间的SDA变换被视为起始/停止信号。
3.3 起始/停止信号
SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。
起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用的状态;在终止信号产生后,总线就处于空闲状态。
3.4 字节传送与应答
在数据传送时,每个字节必须保证是8位字节长度,首先传送最高位(MSB),每个传送的字节后面必须跟随一位应答位(即一帧数据共有九位)。
数据应答(ACK):主机发完8bit数据后就不再驱动总线了(SDA引脚变输入),而SDA和SCL硬件设计时都有上拉电阻,所以这时候SDA变成高电平。那么在第8个数据位,如果外接IIC设备能收到信号的话接着在第9个周期把SDA拉低,那么处理器检测到SDA拉低就能知道外接IIC设备数据已经收到。
二、EEPROM(24LC04B)
本实验选用Microchip Technology Inc生产的型号为24LC04B的EEPROM,以下是对数据手册的相关摘取。
1. 概述
24LC04B是一种4kbit(2*256*8)容量的电可擦除PROM,该设备有两个256 x 8bit的存储块,具有2线串行接口(兼容IIC),支持最多写入16字节数据的页写功能, 兼容100kHz和400kHz的时钟速率,有16字节的页面写入缓冲区。
2. 交流特性
24LC04B的时钟频率最大支持400kHz,高/低电平最小持续时间为600/1300ns,SDA输出有效延时最大为900ns,一次传输完毕后会有至少1300ns的总线释放时间,一次写循环时间为5ms。
以上特性在使用仿真模型时需要注意,博主在第一次仿真时未注意到以上特性debug用了很久/(ㄒoㄒ)/~~
3. 器件寻址
控制字节是在启动条件之后从主设备接收的第一个字节,对24LC04B来说,前4bit为1010,剩下两位don’t care(本实验仿真模型使用的为00),然后的B0位由主设备用来选择要访问存储器的两个256字块中的哪一个(本实验仿真模型使用的为0)。最后一位定义了要执行的操作,当设置为“1”时,将选择读取操作;当设置为“0”时,将选择写入操作。
当主机发送起始信号后,24LC04B监视SDA总线,检查正在传输的设备类型标识符,并且在接收到“1010000”代码后,根据R/W位的状态选择读取或写入操作。
4. 写操作
4.1 字节写
字节写步骤如下:
先发送起始信号,接着发送control byte(本实验为1010_000),r/w标志位为0,然后接收从机应答的ACK信号,发送字地址(block里的寄存器地址),接收从机应答,发送将要写入的数据,接收从机应答,最后发送停止信号。
4.2 页写
页写步骤如下:
先发送起始信号,接着发送control byte(本实验为1010_000),r/w标志位为0,然后接收从机应答的ACK信号,发送字地址(block里的寄存器地址),接收从机应答,发送将要写入的数据n,接收从机应答,发送将要写入的数据n+1,接收从机应答,重复上述操作,直到发完最后一个数据主机发送停止位表明页写结束。
需要注意的是,页写操作最多只能写入16个有效数据字节,这些数据字节临时存储在片上页面缓冲区中,一旦主设备发送了停止条件,就会写入存储器。收到每个字后,低阶地址指针位在内部递增“1”。若传输超过16个字节,则地址计数器将翻转,并且先前接收的数据将被覆盖。
5. 读操作
5.1 当前地址读
当前地址读步骤如下:
先发送起始信号,接着发送control byte(本实验为1010_000),r/w标志位为1,接收从机应答ACK,接收读到的1字节数据,主机发送应答NACK,最后发送停止信号。
5.2 随机读
随机地址读步骤如下:
先发送起始信号,接着发送control byte(本实验为1010_000),r/w标志位为0,然后接收从机应答的ACK信号,发送字地址(block里的寄存器地址),接收从机应答,发送起始信号,发送control byte(本实验为1010_000),r/w标志位为1,接收读到的1字节数据,主机发送应答信号NACK,最后发送停止信号。
5.3 顺序读
顺序读步骤如下:
先发送起始信号,接着发送control byte(本实验为1010_000),r/w标志位为0,然后接收从机应答的ACK信号,发送字地址(block里的寄存器地址),接收从机应答,发送起始信号,发送control byte(本实验为1010_000),r/w标志位为1,接收读到数据n,主机发送应答信号ACK,接收读到的数据n+1,主机发送应答信号ACK,重复上述操作,读到想读取的最后一字节后,主机发送应答信号NACK,最后发送停止信号。
硬件原理
本实验采用FPGA芯片型号为EP4CE6F17C8,与EEPROM连接图如下:
模块设计
主模块
其中uart_rx_byte模块为串口接收模块,接收上位机发送的一个字节数据保存到d_out;uart_tx_byte为串口发送模块,接收i2c_ctrl模块传递过来的数据d_in发送给上位机。key模块为按键消抖模块,本实验用到了三个按键key2、key3、key4。i2c_ctrl为整个设计控制模块,其作用是给i2c_driver模块发送起始命令start、cmd(指示i2c_driver模块将要进行的不同写/读操作)、将要写入的数据wr_data;还有接收i2c_driver传回来的从EEPROM读到的数据及完成信号。i2c_driver 模块是底层驱动EEPROM模块,作用是将上层控制模块与EEPROM器件的数据以IIC协议进行交互。
/* ================================================ *\
Filename : i2c_eeprom.v
Author : Strauss
@Time : 2023-11-26 13:23:52
Revision & Function: 1.0
\* ================================================ */
module i2c_eeprom(clk, rst_n, key2, key3, key4, scl, sda, rxd, txd);
input clk, rst_n, key2, key3, key4, rxd;
output scl, txd;
inout sda;
//Parameter Declarations
//Internal wire/reg declarations
wire done;
wire [7:0]rd_data;
wire data_vld;
wire [2:0]cmd;
wire start;
wire [7:0]wr_data;
wire op_done;
wire [7:0]d_out;
wire rx_done;
wire [7:0]d_in;
wire wr_done, rd_done, tx_done;
wire [2:0]key_in = {key4, key3, key2};
wire [2:0]key_out;
wire tx_loading;
//Module instantiations , self-build module
i2c_ctrl i2c_ctrl_inst(.clk(clk), .rst_n(rst_n), .key2(key_out[0]), .key3(key_out[1]), .key4(key_out[2]),
.d_out(d_out), .done(done), .rx_done(rx_done), .wr_done(wr_done), .rd_done(rd_done), .d_in(d_in), .rd_data(rd_data),
.data_vld(data_vld), .cmd(cmd), .start(start), .wr_data(wr_data), .tx_loading(tx_loading));
i2c_driver i2c_driver_inst(.clk(clk), .rst_n(rst_n), .cmd(cmd), .start(start), .wr_data(wr_data), .rd_data(rd_data),
.data_vld(data_vld), .done(done), .scl(scl), .sda(sda));
uart_rx_byte uart_rx_byte_inst(.clk(clk), .rst_n(rst_n), .rxd(rxd), .dout(d_out), .rx_done(rx_done));
uart_tx_byte uart_tx_byte_inst(.clk(clk), .rst_n(rst_n), .d_in(d_in), .start(rd_done), .txd(txd), .tx_done(tx_done), .tx_loading(tx_loading));
key key_inst(.clk(clk), .rst_n(rst_n), .key_in(key_in), .key_out(key_out));
//Logic Description
endmodule
驱动模块
驱动模块为FPGA与EEPROM进行数据交互的底层模块,其作用是将上层需要写入的数据或从EEPROM读出的数据以IIC协议进行交互,输入输出信号含义如下(忽略clk、rst_n):
信号名称 | 信号含义 |
cmd | 控制i2c_driver进行一次读/写操作的类型 |
start | 控制i2c_driver开始工作的信号 |
wr_data | 将要写入EEPROM的数据 |
done | i2c_driver完成一次操作的结束信号 |
rd_data | i2c_driver完成一次读操作读到的1字节数据 |
data_vld | i2c_driver完成一次读操作的标志信号 |
scl | IIC的时钟线 |
sda | IIC的数据线 |
由EEPROM手册可知,若想实现单字节写、页写、随机读、连续读,可视为对EEPROM进行以下n次(n取决于操作的类型以及需要写/读字节的数量)操作,用cmd来表示这五种情况:
例如要进行单字节写操作,则上层控制模块应发cmd为0-1-2,(d7-d0由上层模块控制);要进行随机读操作,则上层控制模块应发cmd为0-1-0-3;剩余两种操作类似。
可画出i2c_driver模块内的状态图:
根据状态图写出代码:
/* ================================================ *\
Filename : i2c_driver.v
Author : Strauss
@Time : 2023-11-28 09:00:27
Revision & Function: 1.0
iic传输速率:200Kbit/s
\* ================================================ */
module i2c_driver(clk, rst_n, cmd, start, wr_data, rd_data, data_vld, done, scl, sda);
input clk, rst_n;
input [2:0]cmd;//写:cmd==00||cmd==01cmd==10对应三种形式的写操作 读:cmd==11读一个字节 cmd==100顺序读
input start;
input [7:0]wr_data;
output [7:0]rd_data;
output data_vld, done;//data_vld表示读出的数据rd_data有效;done表示一次驱动模块与外设的数据交互完成
output scl;
//三态门的相关定义
reg sda_out_en_dly;
inout sda;
wire sda_in;
reg sda_out;
reg sda_out_en;
assign sda = sda_out_en_dly?sda_out:'bz;
assign sda_in = sda;
//Parameter Declarations
parameter SCL_PER = 250;//scl周期为5us
parameter SCL_PER_HALF = 250/2;
parameter SCL_PER_LOW_HALF = 250/2/2;
parameter SCL_PER_HIGH_HALF = 250/2+250/4;
//Internal wire/reg declarations
reg [7:0]r_data_tmp;
//Module instantiations , self-build module
//Logic Description
localparam //独热码定义状态参数
IDLE = "IDLE",
START = "START",
WR = "WR",
RD = "RD",
ACK = "ACK",
NACK = "NACK",
STOP = "STOP";
reg [39:00] state_c, state_n; //状态变量
//状态转移条件声明
wire idle2start;
wire idle2wr;
wire idle2rd;
wire start2wr;
wire wr2ack;
wire rd2nack;
wire ack2stop;
wire ack2idle;
wire nack2stop;
wire stop2idle;
wire rd2ack;
//scl计数器
reg [7:0] cnt_scl ; //Counter
wire add_cnt_scl ; //Counter Enable
wire end_cnt_scl ; //Counter Reset
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_scl <= 'd0;
else if(add_cnt_scl )begin
if(end_cnt_scl )begin
cnt_scl <= 'd0;
end
else begin
cnt_scl <= cnt_scl + 1'b1;
end
end
else begin
cnt_scl <= cnt_scl ;
end
end
assign add_cnt_scl = state_c != IDLE;
assign end_cnt_scl = add_cnt_scl && cnt_scl >= SCL_PER-1;
//WR/RD的bit计数
reg [3:0] cnt_bit ; //Counter
wire add_cnt_bit ; //Counter Enable
wire end_cnt_bit ; //Counter Reset
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_bit <= 'd0;
else if(add_cnt_bit )begin
if(end_cnt_bit )begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 1'b1;
end
end
else begin
cnt_bit <= cnt_bit ;
end
end
assign add_cnt_bit = (state_c == WR || state_c == RD)&&end_cnt_scl;
assign end_cnt_bit = add_cnt_bit && cnt_bit >= 7;
//scl控制
// always@(posedge clk or negedge rst_n)
// begin
// if(!rst_n)
// scl <= 1;
// else if(add_cnt_scl)
// begin
// if(cnt_scl < SCL_PER_HALF-1)
// scl <= 0;
// else
// scl <= 1;
// end
// else
// scl <= 1;
// end
assign scl = (add_cnt_scl && cnt_scl <= SCL_PER_HALF-1)?0:1;
//读出数据
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
r_data_tmp <= 0;
end
else if(state_c == RD&&cnt_scl == SCL_PER_HIGH_HALF-1)begin
r_data_tmp[7-cnt_bit] <= sda_in;
end
end //always end
assign rd_data = data_vld ? r_data_tmp : rd_data;
assign data_vld = (stop2idle && cmd == 3) || (ack2idle && cmd==4);
assign done = stop2idle || ack2idle;
//第一段, 状态空间切换
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段, 状态转移条件描述
always @(*) begin
case (state_c)
IDLE: begin
if (idle2start) begin
state_n = START;
end
else if (idle2wr) begin
state_n = WR;
end
else if (idle2rd) begin
state_n = RD;
end
else begin
state_n = IDLE;
end
end
START: begin
if (start2wr) begin
state_n = WR;
end
else begin
state_n = START;
end
end
WR: begin
if (wr2ack) begin
state_n = ACK;
end
else begin
state_n = WR;
end
end
RD: begin
if (rd2nack) begin
state_n = NACK;
end
else if(rd2ack)
state_n = ACK;
else begin
state_n = RD;
end
end
ACK: begin
if (ack2stop) begin
state_n = STOP;
end
else if (ack2idle) begin
state_n = IDLE;
end
else begin
state_n = ACK;
end
end
NACK: begin
if (nack2stop) begin
state_n = STOP;
end
else begin
state_n = NACK;
end
end
STOP: begin
if (stop2idle) begin
state_n = IDLE;
end
else begin
state_n = STOP;
end
end
default:state_n = IDLE;
endcase
end
//状态转移条件描述
assign idle2start = state_c == IDLE && (start&&!cmd);
assign idle2wr = state_c == IDLE && (start&&cmd[0]^cmd[1]);
assign idle2rd = state_c == IDLE && ((start&&cmd==3)||cmd==4);
assign start2wr = state_c == START && (end_cnt_scl);
assign wr2ack = state_c == WR && (end_cnt_bit);
assign rd2ack = state_c == RD && (cmd==4&&end_cnt_bit);
assign rd2nack = state_c == RD && (cmd==3&&end_cnt_bit);
assign ack2stop = state_c == ACK && (cmd==2&&end_cnt_scl);
assign ack2idle = state_c == ACK && (cmd!=2&&end_cnt_scl);
assign nack2stop = state_c == NACK && (end_cnt_scl);
assign stop2idle = state_c == STOP && (end_cnt_scl);
//sda_out控制
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_out <= 1;
end
else begin
case(state_c)
IDLE:
// if(cmd != 4)
sda_out <= 1;
START:
if(cnt_scl >= SCL_PER_HIGH_HALF-1)
sda_out <= 0;
WR:
if(cnt_scl >= SCL_PER_LOW_HALF-1)
sda_out <= wr_data[7-cnt_bit];
// else
// sda_out <= 1;
RD:
sda_out <= 0;
ACK:
begin
if(cnt_scl >= SCL_PER_LOW_HALF-1)
sda_out <= 0;
end
NACK:
if(cnt_scl >= SCL_PER_LOW_HALF-1)
sda_out <= 1;
STOP:
if(cnt_scl == SCL_PER_LOW_HALF-1)
sda_out <= 0;
else if(cnt_scl >= SCL_PER_HIGH_HALF-1)
sda_out <= 1;
default:sda_out <= 1;
endcase
end //always end
end
//sda_en控制
//assign sda_out_en = (state_c == START || state_c == WR || state_c == NACK || state_c == STOP|| (state_c==ACK&&cmd==4))?1:0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_out_en <= 0;
end
else if(state_c == START || state_c == WR || state_c == NACK || state_c == STOP || (state_c==ACK&&cmd==4))begin
sda_out_en <= 1;
end
else begin
sda_out_en <= 0;
end
end //always end
always@(posedge clk)
sda_out_en_dly <= sda_out_en;
endmodule
控制模块
控制模块作用为将FPGA外界传入的写/读请求信号转化成具体的传输给下层的命令和数据,本实验写入的地址和页写数据由i2c_ctrl模块内部产生,单字节写的数据由上层串口模块发送过来。
本实验控制模块用到了两个FIFO,由于作者时间有限,这两个FIFO只用于单字节的读/写操作的缓存,所以有一点鸡肋,但是是老师要求的,所以就加上了,两个FIFO对本实验的影响不大。
输入输出信号含义如下(忽略clk、rst_n):
信号名称 | 信号含义 |
dout | 串口传入的一字节接收数据 |
rx_done | 串口数据传输完成的标志信号 |
wr_done | 一次写操作完成 |
d_in | 串口传出的一字节发送数据 |
rd_done | 一次读操作完成 |
key2/key3/key4 | 按键控制,用于读/写请求 |
start | 控制i2c_driver模块的开始 |
wr_data | 将要写入EEPROM的数据 |
done | i2c_driver完成一次操作的结束信号 |
rd_data | i2c_driver完成一次读操作读到的1字节数据 |
data_vld | i2c_driver完成一次读操作的标志信号 |
cmd | 控制i2c_driver进行一次读/写操作的类型 |
由于本实验的目标是对EEPROM进行四种操作:单字节写/读、页写和顺序读,每一种操作i2c_ctrl模块需往下发送n段的指令和数据(单字节写时n为3,单字节读时n为4,页写和顺序读取决于需进行写/读的字节数),设计状态机时可对每种操作设置两个状态:请求状态和等待操作完成状态,其中请求状态的作用是发送每一段操作的起始信号、命令种类和需要写入的数据(读的话可以不关心),等待状态下等待i2c_driver模块的一段读/写操作完成,如果是整个命令的最后一段操作则返回到IDLE态,若不是返回到i2c_ctrl状态继续发送起始信号、命令种类和写入数据。状态机设计图如下:
其中页写操作博主设置的是写12个数据bb ba b9...b0;顺序读操作设置的是读10个数据。页写操作中cnt_page==14的原因是写入数据前有两段写control byte 和写word address的操作;顺序读操作中cnt_seq==13的原因是读有效数据前还需写入control byte、word address、control byte。
由上可写出i2c_ctrl模块的代码:
/* ================================================ *\
Filename : i2c_ctrl.v
Author : Strauss
@Time : 2023-11-28 20:22:17
Revision & Function: 1.0
\* ================================================ */
module i2c_ctrl(clk, rst_n, key2, key3, key4, d_out, done, rx_done, wr_done, rd_done, d_in, rd_data, data_vld, cmd, start, wr_data, tx_loading);
input clk, rst_n, key2, key3, key4, data_vld;
input [7:0]d_out;
input done;
input rx_done;
input [7:0]rd_data;
input tx_loading;
output [7:0]d_in;
output reg [2:0]cmd;
output reg start;
output reg [7:0]wr_data;
output wr_done, rd_done;
//Parameter Declarations
//Internal wire/reg declarations
reg [2:0]cnt_alt;//计数写/读操作的当前段数
wire [7:0]q_fifo;
reg [3:0]cnt_page;
wire seq_rd_en;
reg [3:0]cnt_seq;
// wire wr_done, rd_done;
wire rd_fifo_empty;
//Module instantiations , self-build module
wr_fifo wr_fifo_inst (
.clock ( clk ),
.data ( d_out ),
.rdreq ( key2 ),
.wrreq ( rx_done ),
.empty ( ),
.full ( ),
.q ( q_fifo ),
.usedw ( )
);
rd_fifo rd_fifo_inst (
.clock ( clk ),
.data ( rd_data ),
.rdreq ( key3 ),
.wrreq ( data_vld ),
.empty ( rd_fifo_empty ),
.full ( ),
.q ( d_in ),
.usedw ( )
);
//Logic Description
localparam //独热码定义状态参数
IDLE = "IDLE", //状态
WR_REG = "WR_REG", //状态
WR_WAIT = "WR_WAIT", //状态
RD_REG = "RD_REG", //状态
RD_WAIT = "RD_WAIT", //状态
PAGE_WR_REG = "PAGE_WR_REG",
PAGE_WR_WAIT = "PAGE_WR_WAIT",
SEQ_RD_REG = "SEQ_RD_REG",
SEQ_RD_WAIT = "SEQ_RD_WAIT";
reg [95:00] state_c, state_n; //状态变量声明
//状态转移条件声明
wire idle2wr_reg;
wire idle2rd_reg;
wire wr_reg2wr_wait;
wire wr_wait2wr_reg;
wire wr_wait2idle;
wire rd_reg2rd_wait;
wire rd_wait2rd_reg;
wire rd_wait2idle;
wire idle2page_wr_reg;
wire page_wr_reg2page_wr_wait;
wire page_wr_wait2page_wr_reg;
wire page_wr_wait2idle;
wire idle2seq_rd_reg;
wire seq_rd_reg2seq_rd_wait;
wire seq_rd_wait2seq_rd_reg;
wire seq_rd_wait2idle;
//第一段, 状态空间切换
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段, 状态转移条件描述
always @(*) begin
case (state_c)
IDLE: begin
if (idle2wr_reg) begin
state_n = WR_REG;
end
else if (idle2rd_reg) begin
state_n = RD_REG;
end
else if(idle2page_wr_reg)
state_n = PAGE_WR_REG;
else if(idle2seq_rd_reg)
state_n = SEQ_RD_REG;
else begin
state_n = IDLE;
end
end
WR_REG: begin
if (wr_reg2wr_wait) begin
state_n = WR_WAIT;
end
else begin
state_n = WR_REG;
end
end
WR_WAIT: begin
if (wr_wait2wr_reg) begin
state_n = WR_REG;
end
else if (wr_wait2idle) begin
state_n = IDLE;
end
else begin
state_n = WR_WAIT;
end
end
RD_REG: begin
if (rd_reg2rd_wait) begin
state_n = RD_WAIT;
end
else begin
state_n = RD_REG;
end
end
RD_WAIT: begin
if (rd_wait2rd_reg) begin
state_n = RD_REG;
end
else if (rd_wait2idle) begin
state_n = IDLE;
end
else begin
state_n = RD_WAIT;
end
end
PAGE_WR_REG:
begin
if(page_wr_reg2page_wr_wait)
state_n = PAGE_WR_WAIT;
else
state_n = PAGE_WR_REG;
end
PAGE_WR_WAIT:
begin
if(page_wr_wait2page_wr_reg)
state_n = PAGE_WR_REG;
else if(page_wr_wait2idle)
state_n = IDLE;
else
state_n = PAGE_WR_WAIT;
end
SEQ_RD_REG:
begin
if(seq_rd_reg2seq_rd_wait)
state_n = SEQ_RD_WAIT;
else
state_n = SEQ_RD_REG;
end
SEQ_RD_WAIT:
begin
if(seq_rd_wait2seq_rd_reg)
state_n = SEQ_RD_REG;
else if(seq_rd_wait2idle)
state_n = IDLE;
end
default:state_n = IDLE;
endcase
end
//状态转移条件描述
assign idle2wr_reg = state_c == IDLE && (key2); //
assign idle2rd_reg = state_c == IDLE && (0); //
assign wr_reg2wr_wait = state_c == WR_REG && (1); //
assign wr_wait2wr_reg = state_c == WR_WAIT && (done && cnt_alt < 3); //
assign wr_wait2idle = state_c == WR_WAIT && (done && cnt_alt == 3); //
assign rd_reg2rd_wait = state_c == RD_REG && (1); //
assign rd_wait2rd_reg = state_c == RD_WAIT && (done && cnt_alt < 4); //
assign rd_wait2idle = state_c == RD_WAIT && (done && cnt_alt == 4); //
assign idle2page_wr_reg = state_c == IDLE && (key4);
assign page_wr_reg2page_wr_wait = state_c == PAGE_WR_REG && (1);
assign page_wr_wait2page_wr_reg = state_c == PAGE_WR_WAIT && (done && cnt_page < 14);
assign page_wr_wait2idle = state_c == PAGE_WR_WAIT && (done && cnt_page == 14);
assign idle2seq_rd_reg = state_c == IDLE && key3;
assign seq_rd_reg2seq_rd_wait = state_c == SEQ_RD_REG && 1;
assign seq_rd_wait2seq_rd_reg = state_c == SEQ_RD_WAIT && (done && cnt_seq < 13);
assign seq_rd_wait2idle = state_c == SEQ_RD_WAIT && (done && cnt_seq == 13);
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_alt <= 0;
end
else if(state_c == IDLE)
cnt_alt <= 0;
else if(wr_reg2wr_wait || rd_reg2rd_wait)begin
cnt_alt <= cnt_alt + 1'd1;
end
end //always end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_page <= 0;
end
else if(state_c == IDLE)begin
cnt_page <= 0;
end
else if(page_wr_reg2page_wr_wait)begin
cnt_page <= cnt_page + 1'd1;
end
end //always end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_seq <= 0;
end
else if(state_c == IDLE)begin
cnt_seq <= 0;
end
else if(seq_rd_reg2seq_rd_wait)begin
cnt_seq <= cnt_seq + 1'd1;
end
end //always end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cmd <= 0;
wr_data <= 0;
start <= 0;
end
else
begin
case(state_c)
IDLE:
begin
cmd <= 0;
wr_data <= 0;
start <= 0;
end
WR_REG:
begin
case(cnt_alt)
0:
begin
cmd <= 0;
wr_data <= 8'ha0;
start <= 1;
end
1:
begin
cmd <= 1;
wr_data <= 8'hf0;
start <= 1;
end
2:
begin
cmd <= 2;
wr_data <= q_fifo;
start <= 1;
end
default:;
endcase
end
WR_WAIT:
begin
start <= 0;
end
RD_REG:
begin
case(cnt_alt)
0:
begin
cmd <= 0;
wr_data <= 8'ha0;
start <= 1;
end
1:
begin
cmd <= 1;
wr_data <= 8'h02;
start <= 1;
end
2:
begin
cmd <= 0;
wr_data <= 8'ha1;
start <= 1;
end
3:
begin
cmd <= 3;
start <= 1;
end
default:;
endcase
end
RD_WAIT:
begin
start <= 0;
end
PAGE_WR_REG:
begin
case(cnt_page)
0:
begin
cmd <= 0;
wr_data <= 8'ha0;
start <= 1;
end
1:
begin
cmd <= 1;
wr_data <= 8'h02;
start <= 1;
end
2:
begin
cmd <= 1;
wr_data <= 8'hbb;
start <= 1;
end
3:
begin
cmd <= 1;
wr_data <= 8'hba;
start <= 1;
end
4:
begin
cmd <= 1;
wr_data <= 8'hb9;
start <= 1;
end
5:
begin
cmd <= 1;
wr_data <= 8'hb8;
start <= 1;
end
6:
begin
cmd <= 1;
wr_data <= 8'hb7;
start <= 1;
end
7:
begin
cmd <= 1;
wr_data <= 8'hb6;
start <= 1;
end
8:
begin
cmd <= 1;
wr_data <= 8'hb5;
start <= 1;
end
9:
begin
cmd <= 1;
wr_data <= 8'hb4;
start <= 1;
end
10:
begin
cmd <= 1;
wr_data <= 8'hb3;
start <= 1;
end
11:
begin
cmd <= 1;
wr_data <= 8'hb2;
start <= 1;
end
12:
begin
cmd <= 1;
wr_data <= 8'hb1;
start <= 1;
end
13:
begin
cmd <= 2;
wr_data <= 8'hb0;
start <= 1;
end
default:;
endcase
end
PAGE_WR_WAIT:
start = 0;
SEQ_RD_REG:
begin
case(cnt_seq)
0:
begin
cmd <= 0;
wr_data <= 8'ha0;
start <= 1;
end
1:
begin
cmd <= 1;
wr_data <= 8'h02;
start <= 1;
end
2:
begin
cmd <= 0;
wr_data <= 8'ha1;
start <= 1;
end
3:
begin
cmd <= 4;
start <= 1;
end
4:
begin
cmd <= 4;
start <= 1;
end
5:
begin
cmd <= 4;
start <= 1;
end
6:
begin
cmd <= 4;
start <= 1;
end
7:
begin
cmd <= 4;
start <= 1;
end
8:
begin
cmd <= 4;
start <= 1;
end
9:
begin
cmd <= 4;
start <= 1;
end
10:
begin
cmd <= 4;
start <= 1;
end
11:
begin
cmd <= 4;
start <= 1;
end
12:
begin
cmd <= 3;
start <= 1;
end
default:;
endcase
end
SEQ_RD_WAIT:
start <= 0;
default:;
endcase
end
end //always end
assign wr_done = wr_wait2idle || page_wr_wait2idle;
assign rd_done = rd_wait2idle || seq_rd_wait2idle || (seq_rd_wait2seq_rd_reg&&cnt_seq>3);
endmodule
需要注意的是,由于博主使用的开发板上只有四个按键,其中key1用于复位,key2用于写FIFO的读出使能,实现单字节写、单字节读、页写、顺序读四种操作请求只能对key3、key4进行时分复用,被注意以下转移条件需要不同请求下改变(进行相应操作时把&&后面的值换成key,不用的时候写0):
assign idle2wr_reg = state_c == IDLE && (key2); //单字节写
assign idle2rd_reg = state_c == IDLE && (0); //单字节读
assign idle2page_wr_reg = state_c == IDLE && (key4);//页写
assign idle2seq_rd_reg = state_c == IDLE && key3;//顺序读
串口模块
串口接收和发送模块比较简单,这里不再分析,就是选用115200的波特率,实现PC端和FPGA一个字节数据的交互:
/* ================================================ *\
Filename : uart_rx_byte.v
Author : Strauss
@Time : 2023-11-09 13:57:09
Revision & Function: 1.0
\* ================================================ */
module uart_rx_byte (clk, rst_n, rxd, dout, rx_done);
input clk, rst_n, rxd;
output reg [7:0]dout;
output reg rx_done;
//Parameter Declarations
parameter SYS_CLK = 50_000_000;
parameter SECOND = 49_999_999;
parameter BPS_CLK = 115200;
//Internal wire/reg declarations
reg [19:0]cnt_bps;
reg [3:0]cnt_bit;
reg [9:0]tmp_data;
reg rx_en;
reg rxd_syn1;
reg rxd_syn2;
reg [1:0]rxd_r;
wire [12:0]MAX_BPS;
//Logic Description
assign MAX_BPS = SYS_CLK/BPS_CLK;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rxd_r <= 2'b11;
end
else begin
rxd_r <= {rxd_r[0], rxd};
end
end //always end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_en <= 0;
end
else if(!rxd_r[1])begin
rx_en <= 1'd1;
end
else if(rx_done)
rx_en <= 0;
else begin
rx_en <= rx_en;
end
end //always end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bps <= 0;
end
else if(rx_en)
begin
if(cnt_bps >= MAX_BPS-1)
cnt_bps <= 0;
else
cnt_bps <= cnt_bps + 1'd1;
end
else
cnt_bps <= 0;
end //always end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(cnt_bps >= MAX_BPS-1)
begin
if(cnt_bit == 9)
cnt_bit <= 0;
else
cnt_bit <= cnt_bit + 1'd1;
end
else begin
cnt_bit <= cnt_bit;
end
end //always end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tmp_data <= 0;
end
else if(rx_en && cnt_bps == MAX_BPS/2)
tmp_data[cnt_bit] <= rxd_r[1];
else
tmp_data <= tmp_data;
end //always end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout <= 0;
rx_done <= 0;
end
else if((cnt_bit == 9) && (cnt_bps >= MAX_BPS - 1) && (!tmp_data[0]) && (tmp_data[9]))begin
dout <= tmp_data[8:1];
rx_done <= 1;
end
else begin
dout = dout;
rx_done = 0;
end
end //always end
endmodule
module uart_tx_byte(clk, rst_n, d_in, start, txd, tx_done, tx_loading);
input clk, rst_n;
input [7:0]d_in;
input start;
output reg txd, tx_done, tx_loading;
//Parameter Declarations
parameter SYS_CLK = 50_000_000;
parameter BPS_CLK = 115200;
//Internal wire/reg declarations
reg [19:0]cnt_bps;
reg [3:0]cnt_bit;
wire [12:0]MAX_BPS;
//Module instantiations , self-build module
//Logic Description
assign MAX_BPS = SYS_CLK/BPS_CLK;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bps <= 0;
end
else if(tx_loading)
begin
if(cnt_bps >= MAX_BPS)
cnt_bps <= 0;
else
cnt_bps <= cnt_bps + 1'd1;
end
else
cnt_bps <= 0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_loading <= 0;
end
else if(start)
tx_loading <= 1'd1;
else if(cnt_bit == 9&& cnt_bps >= MAX_BPS)begin
tx_loading <= 0;
end
else
tx_loading <= tx_loading;
end //always end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(cnt_bps >= MAX_BPS)begin
if(cnt_bit == 9)
cnt_bit <= 0;
else
cnt_bit <= cnt_bit + 1'd1;
end
else begin
cnt_bit <= cnt_bit;
end
end //always end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
txd <= 1'd1;
end
else if(tx_loading)
begin
case(cnt_bit)
0:txd <= 0;
1:txd <= d_in[0];
2:txd <= d_in[1];
3:txd <= d_in[2];
4:txd <= d_in[3];
5:txd <= d_in[4];
6:txd <= d_in[5];
7:txd <= d_in[6];
8:txd <= d_in[7];
9:txd <= 1;
default: txd <= 1;
endcase
end
else
txd <= 1'b1;
end //always end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_done <= 0;
else if(cnt_bit == 9 && cnt_bps >= MAX_BPS)
tx_done <= 1'd1;
else
tx_done <=0;
end //always end
endmodule
按键消抖模块
/* ================================================ *\
Filename : key.v
Author : Strauss
@Time : 2023-12-04 21:09:36
Revision & Function: 1.0
\* ================================================ */
module key(clk, rst_n, key_in, key_out);
input clk, rst_n;
input [3-1:0]key_in;
output reg [3-1:0]key_out;
//Parameter Declarations
parameter width = 3;
parameter SECOND = 50_000_000;
//Internal wire/reg declarations
reg [width-1:0]key_syn1;
reg [width-1:0]key_syn2;
wire [width-1:0]key_pos;
wire [width-1:0]key_neg;
localparam //独热码定义状态参数
IDLE = "IDLE",
PFIL = "PFIL",
LOW = "LOW",
RFIL = "RFIL";
reg [31:0] current_state, next_state; //状态变量声明
//跳转条件定义
wire ILDE2PFIL;
wire PFIL2IDLE;
wire PFIL2LOW;
wire LOW2RFIL;
wire RFIL2IDLE;
//Module instantiations , self-build module
//Logic Description
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_syn1 <= {width{1'd1}};
key_syn2 <= {width{1'd1}};
end
else begin
key_syn1 <= key_in;
key_syn2 <= key_syn1;
end
end
assign key_pos = key_syn1 & ~key_syn2;
assign key_neg = ~key_syn1 & key_syn2;
reg [19:0] cnt_fil ; //Counter
wire add_cnt_fil ; //Counter Enable
wire end_cnt_fil ; //Counter Reset
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_fil <= 'd0;
else if(add_cnt_fil)begin
if(end_cnt_fil)begin
cnt_fil <= 'd0;
end
else begin
cnt_fil <= cnt_fil + 1'b1;
end
end
else begin
cnt_fil <= cnt_fil;
end
end
assign add_cnt_fil = current_state==PFIL||current_state==RFIL;
assign end_cnt_fil = add_cnt_fil && cnt_fil >= SECOND/50-1;
//三段式状态机
//状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
current_state <= IDLE;
end
else begin
current_state <= next_state;
end
end //always end
//组合逻辑定义状态转移
always@(*)begin
case(current_state)
IDLE :
if(ILDE2PFIL)
next_state = PFIL;
else
next_state = current_state;
PFIL :
if(PFIL2IDLE)
next_state = IDLE;
else if(PFIL2LOW)
next_state = LOW;
else
next_state = current_state;
LOW :
if(LOW2RFIL)
next_state = RFIL;
else
next_state = current_state;
RFIL :
if(RFIL2IDLE)
next_state = IDLE;
else
next_state = current_state;
default: begin
next_state = IDLE;
end
endcase
end //always end
//状态转移条件
assign ILDE2PFIL = current_state == IDLE && (key_neg);//
assign PFIL2IDLE = current_state == PFIL && (key_pos);//
assign PFIL2LOW = current_state == PFIL && (end_cnt_fil);//
assign LOW2RFIL = current_state == LOW && (key_pos);//
assign RFIL2IDLE = current_state == RFIL && (end_cnt_fil);//
//第三段,状态机输出
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
key_out <= {width{1'd0}};
else if(PFIL2LOW)
key_out <= ~key_syn2;
else
key_out <= 0;
end
endmodule