时间:2024-08-08 来源:网络搜集 关于我们 0
FPGA学习笔记一:I2c
下面的I2c程序笔者写了5天左右,相信大家在写的过程中肯定也会有各种各样的问题,有任何疑问都可以给我留言,在实际的操作中才能真正的学习,不断的遇到问题,不断解决。
目的:
1.学会看仿真波形,根据波形图一步一步的向上推倒,总能找到问题所在。
2.学习正统的代码风格,这样的代码风格是业界较为优秀的。 3.尽量自己解决问题,看不同的地方,有疑问的地方可以隔离开来做仿真。
I2c的通信是非常常见的一种通信协议,学过单片机的读者必然不会陌生,官方I2c的介绍百度一下一堆,过程就是看懂datasheet,之后按照协议写代码(大的电子网站很容易就能找到芯片的数据手册)。此处使用的EEPROM是24L64,与传统的24C02大体上一样,只是寻址空间多了些而已。
功能要求:随意按下矩阵键盘上的一个键,控制模块将ROM中的值取出并且写到EEPROM中,再随意按下一个键,读出EEPROM中的数存放到RAM中并在数码管上显示出在ROM中读取的数。(可以使用IP直接生成ROM,RAM和PLL);
提示:键盘扫描部分和数码管显示部分可以共同使用一个1ms的时钟,而EEPROM手册中对器件有时钟限制,笔者使用的24L64最大时钟频率不超过400KHZ(用PLL直接产生一个500KHZ的时钟,再用这个clk的下降沿产生scl的时钟。数码管部分:只显示有数字的数码管,高位为0时不显示);
在附件中我会附上夏宇闻老师的代码:据夏老师介绍这是他17、18年前写的代码,对应的芯片是24C02。这三个代码有两个是不可综合模块,直接用Qutues综合是不能成功的,可以直接使用Modelism看仿真的波形。我们的目的就是写出EEPROM_WR这个控制模块,夏老师的Signal和EEPROM模块就是为了验证EEPROM_WR这个模块写的。
笔者写EEPROM_WR的时候,主要就是参考夏老师的源代码,当然夏老师代码中的逻辑架构都是没有问题的,直接copy却是不可以用的。
附件:
/****************************************************************************
模块名称:Top 文件名:top.v
模块功能:用于把产生测试信号的模块(Signal)与设计的具体模块(EEPROM_WR)
以及EEPROM虚拟模块连接起来的模块,用于全面测试。
模块说明:本模块为行为模块,不可综合为门级网表。但其中EEPROM_WR可以综合
为门级网表,所以可以对所设计的EEPROM读写器进行门级后仿真。
****************************************************************************/
`include "./Signal.v"
`include "./EEPROM.v"
`include "./EEPROM_WR.v" //可以用EEPROM_WR模块相应的Verilog门级网表来替换
`timescale 1ns/1ns
module Top;
wire RESET;
wire CLK;
wire RD,WR;
wire ACK;
wire[10:0] ADDR;
wire[7:0] DATA;
wire SCL;
wire SDA;
Signal signal(.RESET(RESET),.CLK(CLK),.RD(RD),
.WR(WR),.ADDR(ADDR),.ACK(ACK),.DATA(DATA));
EEPROM_WR eeprom_wr(.RESET(RESET),.SDA(SDA),.SCL(SCL),.ACK(ACK),
.CLK(CLK),.WR(WR),.RD(RD),.ADDR(ADDR),.DATA(DATA));
EEPROM eeprom(.sda(SDA),.scl(SCL));
endmodule
//------------ top.v 文件的结束---------------
/****************************************************************************
模块名称:Signal 文件名:signal.v
模块功能:用于产生测试信号,对所设计的EEPROM_WR模块进行测试。Signal模块
能对被测试模块产生的ack信号产生响应,发出模仿MCU的数据、地址信号
和读/写信号。被测试的模块在接收到信号后会发出写/读EEPROM虚拟模块
的信号。
模块说明:本模块为行为模块,不可综合为门级网表。而且本模块为教学目的做了许
多简化,功能不完整,不能用做商业目的。
****************************************************************************/
//信号源模型:
`timescale 1ns/1ns
`define timeslice 200
`define testnumber 8hff
module Signal(RESET,CLK,RD,WR,ADDR,ACK,DATA);
output RESET; //复位信号
output CLK; //时钟信号
output RD,WR; //读写信号
output[10:0] ADDR; //11位地址信号
input ACK; //读写周期的应答信号
inout[7:0] DATA; //数据线
reg RESET;
reg CLK;
reg RD,WR;
reg W_R; //低位:写操作;高位:读操作
reg[10:0] ADDR;
reg[7:0] data_to_eeprom;
reg[10:0] addr_mem[0:255];
reg[7:0] data_mem[0:255];
reg[7:0] ROM[0:2047];
integer i,j,k;
integer OUTFILE, AddrFILE, DataFILE;
assign DATA = (W_R) ? 8bz : data_to_eeprom ;
//-----------生成测试用随机地址和数据存入文件---------------
initial
begin
AddrFILE = $fopen("./addrrandom.dat"); // 打开一个名为addrrandom.dat的文件备用
DataFILE = $fopen("./datarandom.dat"); // 打开一个名为datarandom.dat的文件备用
for(k = 0; k <= `testnumber; k = k+1)
begin
$fdisplay(AddrFILE," %0h ",{$random}%(`testnumber+1b1)); // 生成测试用随机地址数据文件
$fdisplay(DataFILE," %0h ", {$random}%(`testnumber+1b1)); // 生成测试用随机数据文件
end
$fclose(AddrFILE ); // 关闭名为addrrandom.dat的文件备用
$fclose(DataFILE); // 关闭名为datarandom.dat的文件备用
end
//---------打开准备记录传送数据的文件,并把随机地址和数据从文件中取出放入储存器--------
initial
begin
OUTFILE = $fopen("./eeprom.dat"); // 打开一个名为eeprom.dat的文件备用
$readmemh("./addrrandom.dat",addr_mem); // 把地址数据存入地址存储器
$readmemh("./datarandom.dat",data_mem); // 把准备写入EEPROM的数据存入数据存储器
end
//------------------------------------时钟信号输入
------------------------------always #(`timeslice/2)
CLK = ~CLK;
//----------------------------------- 读写信号输入
------------------------------initial
begin
RESET = 1;
i = 0;
j =0;
W_R = 0;
CLK = 0;
RD = 0;
WR = 0;
#1000 ;
RESET = 0;
repeat(`testnumber) //连续写`testnumber次数据,调试成功后可以增加到全部地址覆盖测试
begin
# (5 * `timeslice);
WR = 1;
# (`timeslice);
WR = 0;
@ (posedge ACK); //EEPROM_WR转换模块请求写数据
end
#(10 * `timeslice);
W_R = 1; //开始读操作
repeat(`testnumber) //连续读`testnumber次数据
begin
# (5 * `timeslice);
RD = 1;
# ( `timeslice );
RD = 0;
@ (posedge ACK); //EEPROM_WR转换模块请求读数据
end
end
//-----------------------------------------写操作
-----------------------------initial
begin
$display("writing-----writing-----writing-----writing");
# (2*`timeslice);
for(i=0;i<= `testnumber;i=i+1)
begin
ADDR = addr_mem[i]; //输出写操作的地址
data_to_eeprom = data_mem[i]; //输出需要转换的平行数据
$fdisplay(OUTFILE,"@%0h %0h",ADDR, data_to_eeprom);
//把输出的地址和数据记录在已经打开的eeprom.dat文件中
@(posedge ACK) ; //EEPROM_WR转换模块请求写数据
end
end
//----------------------------------------读操作
----------------------------initial
@(posedge W_R)
begin
ADDR = addr_mem[0];
$fclose (OUTFILE); //关闭已经打开的eeprom.dat文件
$readmemh("./eeprom.dat",ROM); //把数据文件的数据读到ROM中
$display("Begin READING-----READING-----READING-----READING");
for(j = 0; j <= `testnumber; j = j+1)
begin
ADDR = addr_mem[j];
@(posedge ACK);
if(DATA == ROM[ADDR]) //比较并显示发送的数据和接收到的数据是否一致
$display("DATA %0h == ROM[%0h]---READ RIGHT",DATA,ADDR);
else
$display("DATA %0h != ROM[%0h]---READ WRONG",DATA,ADDR);
end
end
//控制仿真的时间
initial
begin
#( `timeslice * 10 * 3 * 5 *`testnumber);
$display ("Test is stopped after checking writing and reading 255 times in random addr and data");
$stop;
end
endmodule
/****************************************************************************
模块名称:EEPROM_WR 文件名:eeprom_wr.v
模块功能:EEPROM读写器,可以根据MCU的并行数据、地址线和读/写的控制
线对EEPROM (AT24C02/4/8/16)的行为模块进行随机的读写操作。
而且本模块为教学目的做了许多简化,只能做随机的读写操作,功能
不完整,不能用做商业目的。
模块说明:本模块为可综合模块可综合为门级网表。
****************************************************************************/
`timescale 1ns/1ns
module EEPROM_WR(SDA,SCL,ACK,RESET,CLK,WR,RD,ADDR,DATA);
output SCL; //串行时钟线
output ACK; //读写一个周期的应答信号
input RESET; //复位信号
input CLK; //时钟信号输入
input WR,RD; //读写信号
input[10:0] ADDR; //地址线
inout SDA; //串行数据线
inout[7:0] DATA; //并行数据线
reg ACK;
reg SCL;
reg WF,RF; //读写操作标志
reg FF; //标志寄存器
reg [1:0] head_buf; //启动信号寄存器
reg[1:0] stop_buf; //停止信号寄存器
reg [7:0] sh8out_buf; //EEPROM写寄存器
reg [8:0] sh8out_state; //EEPROM 写状态寄存器
reg [9:0] sh8in_state; //EEPROM 读状态寄存器
reg [2:0] head_state; //启动状态寄存器
reg [2:0] stop_state; //停止状态寄存器
reg [10:0] main_state; //主状态寄存器
reg [7:0] data_from_rm; //EEPROM读寄存器
reg link_sda; //SDA 数据输入EEPROM开关
reg link_read; //EEPROM读操作开关
reg link_head; //启动信号开关
reg link_write; //EEPROM写操作开关
reg link_stop; //停止信号开关
wire sda1,sda2,sda3,sda4;
//--------------串行数据在开关的控制下有次序的输出或输入-------------------
assign sda1 = (link_head) ? head_buf[1] : 1b0;
assign sda2 = (link_write) ? sh8out_buf[7] : 1b0;
assign sda3 = (link_stop) ? stop_buf[1] : 1b0;
assign sda4 = (sda1 | sda2 | sda3);
assign SDA = (link_sda) ? sda4 : 1bz;
assign DATA = (link_read) ? data_from_rm : 8hzz;
//--------------------------------主状态机状态定义
------------------------------------------parameter
Idle = 11b00000000001,
Ready = 11b00000000010,
Write_start = 11b00000000100,
Ctrl_write = 11b00000001000,
Addr_write = 11b00000010000,
Data_write = 11b00000100000,
Read_start = 11b00001000000,
Ctrl_read = 11b00010000000,
Data_read = 11b00100000000,
Stop = 11b01000000000,
Ackn = 11b10000000000,
//-------------------------并行数据串行输出状态
-----------------------------sh8out_bit7 = 9b000000001,
sh8out_bit6 = 9b000000010,
sh8out_bit5 = 9b000000100,
sh8out_bit4 = 9b000001000,
sh8out_bit3 = 9b000010000,
sh8out_bit2 = 9b000100000,
sh8out_bit1 = 9b001000000,
sh8out_bit0 = 9b010000000,
sh8out_end = 9b100000000;
//--------------------------串行数据并行输出状态
----------------------------parameter sh8in_begin = 10b0000000001,
sh8in_bit7 = 10b0000000010,
sh8in_bit6 = 10b0000000100,
sh8in_bit5 = 10b0000001000,
sh8in_bit4 = 10b0000010000,
sh8in_bit3 = 10b0000100000,
sh8in_bit2 = 10b0001000000,
sh8in_bit1 = 10b0010000000,
sh8in_bit0 = 10b0100000000,
sh8in_end = 10b1000000000,
//---------------------------------启动状态
----------------------------------head_begin = 3b001,
head_bit = 3b010,
head_end = 3b100,
//---------------------------------停止状态
----------------------------------stop_begin = 3b001,
stop_bit = 3b010,
stop_end = 3b100;
parameter YES = 1,
NO = 0;
//-------------产生串行时钟scl,为输入时钟的二分频-------------------
always @(negedge CLK)
if(RESET)
SCL <= 0;
else
SCL <= ~SCL;
//-----------------------------主状态机程序
----------------------------------always @ (posedge CLK)
if(RESET)
begin
link_read <= NO;
link_write <= NO;
link_head <= NO;
link_stop <= NO;
link_sda <= NO;
ACK <= 0;
RF <= 0;
WF <= 0;
FF <= 0;
main_state <= Idle;
end
else
begin
casex(main_state)
Idle:
begin
link_read <= NO;
link_write <= NO;
link_head <= NO;
link_stop <= NO;
link_sda <= NO;
if(WR)
begin
WF <= 1;
main_state <= Ready ;
end
else if(RD)
begin
RF <= 1;
main_state <= Ready ;
end
else
begin
WF <= 0;
RF <= 0;
main_state <= Idle;
end
end
Ready:
begin
link_read <= NO;
link_write <= NO;
link_stop <= NO;
link_head <= YES;
link_sda <= YES;
head_buf[1:0] <= 2b10;
stop_buf[1:0] <= 2b01;
head_state <= head_begin;
FF <= 0;
ACK <= 0;
main_state <= Write_start;
end
Write_start:
if(FF == 0)
shift_head;
else
begin
sh8out_buf[7:0] <= {1b1,1b0,1b1,1b0,ADDR[10:8],1b0};
link_head <= NO;
link_write <= YES;
FF <= 0;
sh8out_state <= sh8out_bit6;
main_state <= Ctrl_write;
end
Ctrl_write:
if(FF ==0)
shift8_out;
else
begin
sh8out_state <= sh8out_bit7;
sh8out_buf[7:0] <= ADDR[7:0];
FF <= 0;
main_state <= Addr_write;
end
Addr_write:
if(FF == 0)
shift8_out;
else
begin
FF <= 0;
if(WF)
begin
sh8out_state <= sh8out_bit7;
sh8out_buf[7:0] <= DATA;
main_state <= Data_write;
end
if(RF)
begin
head_buf <= 2b10;
head_state <= head_begin;
main_state <= Read_start;
end
end
Data_write:
if(FF == 0)
shift8_out;
else
begin
stop_state <= stop_begin;
main_state <= Stop;
link_write <= NO;
FF <= 0;
end
Read_start:
if(FF == 0)
shift_head;
else
begin
sh8out_buf <= {1b1,1b0,1b1,1b0,ADDR[10:8],1b1};
link_head <= NO;
link_sda <= YES;
link_write <= YES;
FF <= 0;
sh8out_state <= sh8out_bit6;
main_state <= Ctrl_read;
end
Ctrl_read:
if(FF == 0)
shift8_out;
else
begin
link_sda <= NO;
link_write <= NO;
FF <= 0;
sh8in_state <= sh8in_begin;
main_state <= Data_read;
end
Data_read:
if(FF == 0)
shift8in;
else
begin
link_stop <= YES;
link_sda <= YES;
stop_state <= stop_bit;
FF <= 0;
main_state <= Stop;
end
Stop:
if(FF == 0)
shift_stop;
else
begin
ACK <= 1;
FF <= 0;
main_state <= Ackn;
end
Ackn:
begin
ACK <= 0;
WF <= 0;
RF <= 0;
main_state <= Idle;
end
default: main_state <= Idle;
endcase
end
//------------------------串行数据转换为并行数据任务
----------------------------------task shift8in;
begin
casex(sh8in_state)
sh8in_begin:
sh8in_state <= sh8in_bit7;
sh8in_bit7: if(SCL)
begin
data_from_rm[7] <= SDA;
sh8in_state <= sh8in_bit6;
end
else
sh8in_state <= sh8in_bit7;
sh8in_bit6: if(SCL)
begin
data_from_rm[6] <= SDA;
sh8in_state <= sh8in_bit5;
end
else
sh8in_state <= sh8in_bit6;
sh8in_bit5: if(SCL)
begin
data_from_rm[5] <= SDA;
sh8in_state <= sh8in_bit4;
end
else
sh8in_state <= sh8in_bit5;
sh8in_bit4: if(SCL)
begin
data_from_rm[4] <= SDA;
sh8in_state <= sh8in_bit3;
end
else
sh8in_state <= sh8in_bit4;
sh8in_bit3: if(SCL)
begin
data_from_rm[3] <= SDA;
sh8in_state <= sh8in_bit2;
end
else
sh8in_state <= sh8in_bit3;
sh8in_bit2: if(SCL)
begin
data_from_rm[2] <= SDA;
sh8in_state <= sh8in_bit1;
end
else
sh8in_state <= sh8in_bit2;
sh8in_bit1: if(SCL)
begin
data_from_rm[1] <= SDA;
sh8in_state <= sh8in_bit0;
end
else
sh8in_state <= sh8in_bit1;
sh8in_bit0: if(SCL)
begin
data_from_rm[0] <= SDA;
sh8in_state <= sh8in_end;
end
else
sh8in_state <= sh8in_bit0;
sh8in_end: if(SCL)
begin
link_read <= YES;
FF <= 1;
sh8in_state <= sh8in_bit7;
end
else
sh8in_state <= sh8in_end;
default: begin
link_read <= NO;
sh8in_state <= sh8in_bit7;
end
endcase
end
endtask
//------------------------------ 并行数据转换为串行数据任务
---------------------------task shift8_out;
begin
casex(sh8out_state)
sh8out_bit7:
if(!SCL)
begin
link_sda <= YES;
link_write <= YES;
sh8out_state <= sh8out_bit6;
end
else
sh8out_state <= sh8out_bit7;
sh8out_bit6:
if(!SCL)
begin
link_sda <= YES;
link_write <= YES;
sh8out_state <= sh8out_bit5;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit6;
sh8out_bit5:
if(!SCL)
begin
sh8out_state <= sh8out_bit4;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit5;
sh8out_bit4:
if(!SCL)
begin
sh8out_state <= sh8out_bit3;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit4;
sh8out_bit3:
if(!SCL)
begin
sh8out_state <= sh8out_bit2;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit3;
sh8out_bit2:
if(!SCL)
begin
sh8out_state <= sh8out_bit1;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit2;
sh8out_bit1:
if(!SCL)
begin
sh8out_state <= sh8out_bit0;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit1;
sh8out_bit0:
if(!SCL)
begin
sh8out_state <= sh8out_end;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit0;
sh8out_end:
if(!SCL)
begin
link_sda <= NO;
link_write <= NO;
FF <= 1;
end
else
sh8out_state <= sh8out_end;
endcase
end
endtask
//--------------------------- 输出启动信号任务
---------------------------------task shift_head;
begin
casex(head_state)
head_begin:
if(!SCL)
begin
link_write <= NO;
link_sda <= YES;
link_head <= YES;
head_state <= head_bit;
end
else
head_state <= head_begin;
head_bit:
if(SCL)
begin
FF <= 1;
head_buf <= head_buf<<1;
head_state <= head_end;
end
else
head_state <= head_bit;
head_end:
if(!SCL)
begin
link_head <= NO;
link_write <= YES;
end
else
head_state <= head_end;
endcase
end
endtask
//--------------------------- 输出停止信号任务
--------------------------------------task shift_stop;
begin
casex(stop_state)
stop_begin: if(!SCL)
begin
link_sda <= YES;
link_write <= NO;
link_stop <= YES;
stop_state <= stop_bit;
end
else
stop_state <= stop_begin;
stop_bit: if(SCL)
begin
stop_buf <= stop_buf<<1;
stop_state <= stop_end;
end
else
stop_state<= stop_bit;
stop_end: if(!SCL)
begin
link_head <= NO;
link_stop <= NO;
link_sda <= NO;
FF <= 1;
end
else
stop_state <= stop_end;
endcase
end
endtask
endmodule
//--------------------------- eeprom_wr.v 文件结束
---------------------------/****************************************************************************
模块名称:EEPROM 文件名:eeprom.v
模块功能:用于模拟真实的EEPROM(AT24C02/4/8/16) 的随机读写的功能。对于符合
AT24C02/4/8/16 要求的scl和sda 随机读/写信号能根据I2C协议,分析
其含义并进行相应的读/写操作。
模块说明:本模块为行为模块,不可综合为门级网表。而且本模块为教学目的做了许
多简化,功能不完整,不能用做商业目的。
****************************************************************************/
`timescale 1ns/1ns
`define timeslice1 100
module EEPROM(scl, sda);
input scl; //串行时钟线
inout sda; //串行数据线
reg out_flag; //SDA数据输出的控制信号
reg[7:0] memory[2047:0];
reg[10:0] address;
reg[7:0] memory_buf;
reg[7:0] sda_buf; //SDA 数据输出寄存器
reg[7:0] shift; //SDA 数据输入寄存器
reg[7:0] addr_byte; //EEPROM 存储单元地址寄存器
reg[7:0] ctrl_byte; //控制字寄存器
reg[1:0] State; //状态寄存器
integer i;
//--------------------------------------------------------------
parameter r7= 8b10101111,w7= 8b10101110, //main7
r6= 8b10101101,w6= 8b10101100, //main6
r5= 8b10101011,w5= 8b10101010, //main5
r4= 8b10101001,w4= 8b10101000, //main4
r3= 8b10100111,w3= 8b10100110, //main3
r2= 8b10100101,w2= 8b10100100, //main2
r1= 8b10100011,w1= 8b10100010, //main1
r0= 8b10100001,w0= 8b10100000; //main0
//--------------------------------------------------------------
assign sda = (out_flag == 1) ? sda_buf[7] : 1bz;
//----------寄存器和存储器初始化---------
initial
begin
addr_byte = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
State = 2b00;
memory_buf = 0;
address = 0;
shift = 0;
for(i=0;i<=2047;i=i+1)
memory[i]=0;
end
//------------ 启动信号
-----------------------------always @ (negedge sda)
if(scl == 1 )
begin
State = State + 1;
if(State == 2b11)
disable write_to_eeprm;
end
//------------ 主状态机
--------------------------always @(posedge sda)
if (scl == 1 ) //停止操作
stop_W_R;
else
begin
casex(State)
2b01: // 做RTL仿真时,处此应该是2b10,
// 做布局布线后仿真时也应该是2’b10
// 但Modelsim和quartusII版本必须是6.1b和6.1以上
begin
read_in;
if(ctrl_byte==w7||ctrl_byte==w6||ctrl_byte==w5
||ctrl_byte==w4||ctrl_byte==w3||ctrl_byte==w2
||ctrl_byte==w1||ctrl_byte==w0)
begin
State = 2b10;
write_to_eeprm; //写操作
end
else
State = 2b00;
end
2b11:
read_from_eeprm; //读操作
default:
State=2b00;
endcase
end //主状态机结束
//------------- 操作停止
------------------------------task stop_W_R;
begin
State =2b00; //状态返回为初始状态
addr_byte = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
end
endtask
//------------- 读进控制字和存储单元地址 ------------------------
task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte);
end
endtask
//------------EEPROM 的写操作
---------------------------------------task write_to_eeprm;
begin
shift_in(memory_buf);
address = {ctrl_byte[3:1],addr_byte};
memory[address] = memory_buf;
$display("eeprm----memory[%0h]=%0h",address,memory[address]);
State =2b00; //回到0状态
end
endtask
//-----------EEPROM 的读操作
----------------------------------------task read_from_eeprm;
begin
shift_in(ctrl_byte);
if(ctrl_byte==r7||ctrl_byte==r6||ctrl_byte==r5||ctrl_byte==r4
||ctrl_byte==r3||ctrl_byte==r2||ctrl_byte==r1||ctrl_byte==r0)
begin
address = {ctrl_byte[3:1],addr_byte};
sda_buf = memory[address];
shift_out;
State= 2b00;
end
end
endtask
//-----SDA 数据线上的数据存入寄存器,数据在SCL的高电平有效-------------
task shift_in;
output [7:0] shift;
begin
@ (posedge scl) shift[7] = sda;
@ (posedge scl) shift[6] = sda;
@ (posedge scl) shift[5] = sda;
@ (posedge scl) shift[4] = sda;
@ (posedge scl) shift[3] = sda;
@ (posedge scl) shift[2] = sda;
@ (posedge scl) shift[1] = sda;
@ (posedge scl) shift[0] = sda;
@ (negedge scl)
begin
#`timeslice1 ;
out_flag = 1; //应答信号输出
sda_buf = 0;
end
@(negedge scl)
#`timeslice1 out_flag = 0;
end
endtask
//----EEPROM 存储器中的数据通过SDA 数据线输出,数据在SCL 低电平时变化
task shift_out;
begin
out_flag = 1;
for(i=6;i>=0;i=i-1)
begin
@ (negedge scl);
#`timeslice1;
sda_buf = sda_buf<<1;
end
@(negedge scl) #`timeslice1 sda_buf[7] = 1; //非应答信号输出
@(negedge scl) #`timeslice1 out_flag = 0;
end
endtask
endmodule
//-------------------------------eeprom.v 文件结束----------------
以上就是夏老师的源代码,夏老师的不可综合模块可谓一绝,恐怕我大中国能达到这样水平的没有几人。
下面就是笔者完成的I2c的代码,在开发板上验证无误。测试模块可能有些难懂,但最起码要回写一些简单的测试模块,没有测试根本就找不到错误。
最顶层的模块,建立层次化建模的思想
//功能实现:按键一次表示把ROM中的数据写入eeprom,第二次按键表示把EEPROM中的数据读出来并且写入RAM同时在数码管上显示出来 按下复位键后和重新开始一样
// the combitination contain the all module
module version_1(clk, rst_n, key_in, key_scan, sel, seg, scl, sda);
input clk,rst_n;
input [3:0] key_in;
output [3:0] key_scan;
output [2:0] sel;
output [7:0] seg;
inout sda;
output scl;
wire WR,RD;
wire [7:0] addr_reg;
wire [7:0] q;
// 下面的复位使得按下复位键后 再软件延时一会儿再才真正的复位 问题在于同步复位(把后面的改成异步复位就搞定了)
reg [20:0] count;
wire pll_rst_n ;
always @ (posedge clk)
begin
if(!rst_n)
begin
count <= 0;
end
else if (count == 20h3ffff) begin
count <= 20h3ffff ;
end
else
begin
count <= count + 1;
end
end
assign pll_rst_n = (count == 20h3ffff) ? 1b1 : 1b0 ;
wire clk_out,clk_key;
top_pll_divider top_pll_divider(.clk(clk), .rst_n(rst_n), .clk_out(clk_out), .clk_key(clk_key));
top_keyscan_addrgen_rom top_keyscan_addrgen_rom(.clk_key(clk_key), .clk_out(clk_out), .rst_n(pll_rst_n), .key_in(key_in), .key_scan(key_scan), .WR(WR), .RD(RD), .addr_reg(addr_reg), .q(q));
top_eepwr_ram_BinBCD_display top_eepwr_ram_BinBCD_display(.clk_out(clk_out), .clk_key(clk_key), .rst_n(pll_rst_n), .addr_reg(addr_reg), .WR(WR), .RD(RD), .data_from_rom(q), .scl(scl), .sda(sda), .sel(sel), .seg(seg));
//----------------------------------------------------
//-------------下板的时候用上面的代码,仿真的时候用下面的代码(因为下面的仿真全部用50M的时钟,可以加快仿真,效果很明显)
-------------------------------------//----------------------------------------------------
// top_keyscan_addrgen_rom top_keyscan_addrgen_rom(.clk_key(clk), .clk_out(clk), .rst_n(rst_n), .key_in(key_in), .key_scan(key_scan), .WR(WR), .RD(RD), .addr_reg(addr_reg), .q(q));
// top_eepwr_ram_BinBCD_display top_eepwr_ram_BinBCD_display(.clk_out(clk), .clk_key(clk), .rst_n(rst_n), .addr_reg(addr_reg), .WR(WR), .RD(RD), .data_from_rom(q), .scl(scl), .sda(sda), .sel(sel), .seg(seg));
Endmodule
第一层:此顶层包括锁相环和分频器
// a top file: contain the pll and divider
module top_pll_divider(clk, rst_n, clk_out, clk_key);
input clk,rst_n;
output clk_out,clk_key;
divider divider(.clk(clk), .rst_n(rst_n), .clk_key(clk_key));
my_pll my_pll(.inclk0(clk), .c0(clk_out));
endmodule
下面是分频模块
// the module can genenrate a clk for the keyscan.v and display.v
module divider(clk, rst_n, clk_key);
input clk,rst_n;
output reg clk_key;
reg [20:0] count;
always @ (posedge clk)
begin
if(!rst_n)
begin
clk_key <= 1;
count <= 0;
end
else
begin
count <= count + 1;
clk_key <= count [13];
end
end
endmodule
锁相环直接调用IP核即可
第二个顶层模块:包括键盘扫描,地址发生器,ROM
下面是地址发生器:作用是为ROM提供了地址
// the module is used to generate the address
module addr_generate(clk, rst_n, real_number, addr_reg, RD, WR);
input clk;
input rst_n;
input [4:0] real_number;
output [7:0] addr_reg;
output reg WR,RD;
reg [7:0] count,count_reg;
reg flag;
reg [1:0] flag_state;
assign addr_reg = count_reg;
// the fllow alwayss function is generate a pulse about the real_number is coming
always @ (posedge clk)
begin
if(!rst_n)
begin
flag <= 0;
flag_state <= 0;
end
else if(real_number!=17)
case(flag_state)
0 : begin flag <= 1; flag_state <= 1; end
1 : begin flag <= 0; flag_state <= 1; end
default : begin flag <= 0; flag_state <= 0; end
endcase
else
begin
flag <= 0;
flag_state <= 0;
end
end
// press the key one time, the count will increse 1. when the number of press the keys time is odd number,
// the add_reg will increse 1,otherwise the address invariant(bu bian) at the same time read the eeprom
always @ (posedge clk)
begin
if(!rst_n)
begin
count <= 0;
count_reg <= 0;
WR <= 0;
RD <= 0;
end
else if(flag)
begin
count <= count + 1;
if(count%2==0)
begin
count_reg <= count_reg + 1;
WR <= 1;
RD <= 0;
end
else
begin
count_reg <= count_reg;
WR <= 0;
RD <= 1;
end
end
else count_reg <= count_reg;
end
////
// always @ (posedge clk)
// begin
//
// end
//
// always @ (posedge clk)
// begin
// if(!rst_n)
// begin
// count <= 0;
// WR <= 0;
// RD <= 0;
// end
// else
// case(real_number)
// 0 : begin WR <= 0; RD <= 1; count <= 0; end //after the key 15 have pressed , pressed the other key can read the data from the eeprom
// 1 : begin WR <= 0; RD <= 1; count <= 1; end
// 2 : begin WR <= 0; RD <= 1; count <= 2; end
// 3 : begin WR <= 0; RD <= 1; count <= 3; end
// 4 : begin WR <= 0; RD <= 1; count <= 4; end
// 5 : begin WR <= 0; RD <= 1; count <= 5; end
// 6 : begin WR <= 0; RD <= 1; count <= 6; end
// 7 : begin WR <= 0; RD <= 1; count <= 7; end
// 8 : begin WR <= 0; RD <= 1; count <= 8; end
// 9 : begin WR <= 0; RD <= 1; count <= 9; end
// 10 : begin WR <= 0; RD <= 1; count <= 10; end
// 11 : begin WR <= 0; RD <= 1; count <= 11; end
// 12 : begin WR <= 0; RD <= 1; count <= 12; end
// 13 : begin WR <= 0; RD <= 1; count <= 13; end
// 14 : begin WR <= 0; RD <= 1; count <= 14; end
// 15 : begin // at first : press the key 15, the adddress and data enter the eeprom in order automatically
// WR <= 1;
// RD <= 0;
// count <= 0;
// if(ack)
// count <= count + 1;
// else
// count <= count;
// end
// default : begin WR <= 0; RD <= 1; count <= 0; end
// endcase
// end
Endmodule
下面是键盘扫描部分:十分的精妙,仔细品读。
//键盘按下几就在数码管上显示几 注意:ROW是输入(因为有上拉电阻) COL才是输出
module keyscan(clk_slow, rst_n, key_in, key_scan, real_number);
input clk_slow;
input rst_n;
input [3:0] key_in;
output reg [4:0] real_number; //因为17表示的数码管灭,所以[3:0]共16个状态(0~15)差一个数
output [3:0] key_scan; //对应的端口是COL 是FPGA芯片的输出
reg any_key_pressed; //是否有按键按下的标志位
reg [3:0] scan_code;
reg [2:0] scan_state;
// wire [3:0] judge; //通过key_in和key_scan的按位与来判断是哪个键按下
reg [4:0] number_out;
reg [4:0] number_reg,number_reg1;
reg [4:0] state; //此状态机用来松手的消抖
reg [2:0] four_state;
localparam ok = 1b1,
no_ok = 1b0;
//产生一个标志信号来指示是否有按键按下,有按下时的开始进行扫描(如果一上电就开始扫描会比较费电)
always @ (posedge clk_slow or negedge rst_n)
begin
if(!rst_n)
any_key_pressed <= no_ok;
else
if(!(&key_in))
begin
any_key_pressed <= ok; //刚按下的时候any_key_pressed为1 同时执行scan_state ,一个时钟后有3/4的概率由key_scan使得key_in为1
four_state <= 0; //之后就跳到48行之后的循环了
end
else
if(any_key_pressed)
case (four_state) //此处十分精辟 四个状态使得key_scan把所有的组合都输出一遍 之后any_key_pressed为0 不再扫描 直到下次的key_in到来
0 : begin any_key_pressed <= ok; four_state <= 1; end
1 : begin any_key_pressed <= ok; four_state <= 2; end
2 : begin any_key_pressed <= ok; four_state <= 3; end
3 : begin any_key_pressed <= ok; four_state <= 4; end
4 : begin any_key_pressed <= no_ok; four_state <= 0; end
default : begin any_key_pressed <= no_ok; four_state <= 0; end
endcase
end
//使得COL端口产生扫描信号
always @ (posedge clk_slow or negedge rst_n)
begin
if(!rst_n)
begin
scan_code <= 0;
scan_state <= 0;
end
else
if(any_key_pressed)
case(scan_state)
0 : begin scan_code <= 4b1110; scan_state <= 1; end
1 : begin scan_code <= 4b1101; scan_state <= 2; end
2 : begin scan_code <= 4b1011; scan_state <= 3; end
3 : begin scan_code <= 4b0111; scan_state <= 4; end
4 : begin scan_code <= 4b0000; scan_state <= 0; end
default :begin scan_state <= 0; scan_code <= 0; end
endcase
else begin scan_code <= 0; scan_state <= 0; end
end
assign key_scan = scan_code;
// 根据key_in和scan_code相与的结果judge来判断输出:number_out之所以不直接输出real_number是因为要进行一个松手时的去抖
// assign judge = key_in & scan_code;
always @ (posedge clk_slow or negedge rst_n) //组合逻辑也行,只是用时序逻辑能让输出加一个寄存器
begin
if(!rst_n)
number_out <= 17;
else
// case(key_in)
// 4b1110 : case(judge)
// 4b1110 : number_out <= 0;
// 4b1100 : number_out <= 1;
// 4b1010 : number_out <= 2;
// 4b0110 : number_out <= 3;
// endcase
// 4b1101 : case(judge)
// 4b1100 : number_out <= 4;
// 4b1101 : number_out <= 5;
// 4b1001 : number_out <= 6;
// 4b0101 : number_out <= 7;
// endcase
// 4b1011 : case(judge)
// 4b1010 : number_out <= 8;
// 4b1001 : number_out <= 9;
// 4b1011 : number_out <= 10;
// 4b0011 : number_out <= 11;
// endcase
// 4b0111 : case(judge)
// 4b0110 : number_out <= 12;
// 4b0101 : number_out <= 13;
// 4b0011 : number_out <= 14;
// 4b0111 : number_out <= 15;
// endcase
// default: number_out <= 5d16;
// endcase
case({scan_code,key_in}) //夏老师的程序段
8b0111_1110: number_out <= 5d3;
8b1011_1110: number_out <= 5d2;
8b1101_1110: number_out <= 5d1;
8b1110_1110: number_out <= 5d0;
8b0111_1101: number_out <= 5d7;
8b1011_1101: number_out <= 5d6;
8b1101_1101: number_out <= 5d5;
8b1110_1101: number_out <= 5d4;
8b0111_1011: number_out <= 5d11;
8b1011_1011: number_out <= 5d10;
8b1101_1011: number_out <= 5d9;
8b1110_1011: number_out <= 5d8;
8b0111_0111: number_out <= 5d15;
8b1011_0111: number_out <= 5d14;
8b1101_0111: number_out <= 5d13;
8b1110_0111: number_out <= 5d12;
default: number_out <= 5d17;
endcase
end
///根据仿真可知出来的数值必须用一个寄存器number_reg1来存储0~15之间的数,否则此数取不出来
always @ (posedge clk_slow)
begin
if(!rst_n)
number_reg <= 0;
else if(number_out>=0 && number_out<=15)
number_reg <= number_out;
else if(any_key_pressed==no_ok)
number_reg <= 17;
else
number_reg <= number_reg;
end
//此处再进行去抖 都是用FSM延时来实现的(检测时间应该大于16ms)
always @ (posedge clk_slow or negedge rst_n)
begin
if(!rst_n)
begin
real_number <= 17;
state <= 0;
end
else if(any_key_pressed)
case(state) //*******按下的时候不可能再按另一个,所以在FSM中不会正在状态中跳转而产生新数据把number更新掉
0 : begin number_reg1 <= number_reg; state <= 1; end
1 : if(number_reg == number_reg1) state <= 2; else state <= 0;
2 : if(number_reg == number_reg1) state <= 3; else state <= 0;
3 : if(number_reg == number_reg1) state <= 4; else state <= 0;
4 : if(number_reg == number_reg1) state <= 5; else state <= 0;
5 : if(number_reg == number_reg1) state <= 6; else state <= 0;
6 : if(number_reg == number_reg1) state <= 7; else state <= 0;
7 : if(number_reg == number_reg1) state <= 8; else state <= 0;
8 : if(number_reg == number_reg1) state <= 9; else state <= 0;
9 : if(number_reg == number_reg1) state <= 10; else state <= 0;
10 : if(number_reg == number_reg1) state <= 11; else state <= 0;
11 : if(number_reg == number_reg1) state <= 12; else state <= 0;
12 : if(number_reg == number_reg1) state <= 13; else state <= 0;
13 : if(number_reg == number_reg1) state <= 14; else state <= 0;
14 : if(number_reg == number_reg1) state <= 15; else state <= 0;
15 : if(number_reg == number_reg1)
begin
real_number <= number_reg;
state <= 0;
end
default : state <= 0;
endcase
else
begin
real_number <= 17;
state <= 0;
end
end
endmodule
ROM模块也是直接调用IP,单口
第三层:包括EEPROM_WR控制、RAM和二进制转BCD码以及 数码管显示部分
// the top.v module contain eeprom.v\ my_ram\ Binary_to_BCDa\ diaplay.v
module top_eepwr_ram_BinBCD_display(clk_out, clk_key, rst_n, addr_reg, WR, RD, data_from_rom, scl, sda, sel, seg);
input clk_out,rst_n;
input clk_key;
input [7:0] addr_reg;
input WR,RD;
input [7:0] data_from_rom;
output scl,sda;
output [2:0] sel;
output [7:0] seg;
wire [7:0] data_to_ram_reg;
wire [7:0] q;
wire [23:0] BCDa;
// in the eeprom_wr, ack_to_rom is ignored
eeprom_wr eeprom_wr(.clk(clk_out), .rst_n(rst_n), .addr({5b00000,addr_reg}), .WR(WR), .RD(RD), .data_from_rom(data_from_rom), .scl(scl), .sda(sda), .data_to_ram_reg(data_to_ram_reg));
my_ram my_ram (.address(addr_reg), .clock(clk_out), .data(data_to_ram_reg), .wren(1), .q(q));
Binary_to_BCD Binary_to_BCD(.clk(clk_out), .rst_n(rst_n), .Binary_a({12d0,q}), .BCDa(BCDa));
display display(.clk(clk_key), .rst_n(rst_n), .BCDa(BCDa), .sel(sel), .seg(seg));
endmodule
下面是EEPROM_WR部分:作用就是产生协议上一样的SDA信号,据夏老师说,十几年前他写的时候用了一个月的时间才写出来,望读者在看懂的基础上再写一遍,原来的思路也是可以的。
//eeproms control
module eeprom_wr(clk, rst_n, addr, WR, RD, data_from_rom, scl, sda, data_to_ram_reg, ack_to_rom);
input clk,rst_n;
input [12:0] addr;
input WR,RD;
input [7:0] data_from_rom;
output reg scl;
inout sda;
output reg [7:0] data_to_ram_reg;
output reg ack_to_rom; // if the data from rom have disposed(chu li guo de), the signal will hold on hign level a period of clk
wire [7:0] data_to_ram;
reg [1:0] head_start; // control the start signal
reg [7:0] write_data; // write the address or data
reg [1:0] end_stop;
wire sda1,sda2,sda3,sda4;
reg [7:0] data_from_eeprom;
reg WF,RF,FF; // express the write_flag\read_flag\
// FF : it is mean: 1. a flag to generate start\stop\write_bit\read_bit signal 2.genenrate the acknowledge from EEPROM_WR to eeprom when write to slave
// --------under the line is the control signal , it is belong to FSM ---------------
reg link_start;
reg link_write;
reg link_stop;
reg link_sda;
reg link_read;
reg [11:0] main_state;
reg [1:0] write_start_state; //register of start signal
reg [1:0] write_stop_state; // it is belong to task
reg [8:0] write_bit_state;
reg [8:0] read_bit_state;
//-----------combination logic: provide the sda rely on the scl and the control signal-------------------
assign sda1 = (link_start)? head_start[1] : 1b0;
assign sda2 = (link_write)? write_data[7] : 1b0;
assign sda3 = (link_stop) ? end_stop[1] : 1b0;
assign sda4 = (sda1 || sda2 || sda3);
assign sda = (link_sda) ? sda4 : 1bz;
assign data_to_ram = (link_read) ? data_from_eeprom : 8hzz;
//----------------------------------------------------------------------------------
//-------------generate the scl--------------------------------------
always @ (negedge clk) begin //because in the FSM ,alwayss clk is posedge. to void in the posedge(scl snd clk) generate competion risk(jing zheng mao xian)
if(!rst_n)
scl <= 0;
else
scl <= ~scl;
end
//----------Main FSMs statement----------------------------------------------------
localparam
because_ack = 12b0000_0000_0000,
idle = 12b0000_0000_0001,
prepare_read = 12b0000_0000_0010,
write_device_start = 12b0000_0000_0100,
write_device_addr = 12b0000_0000_1000,
write_HighBit_addr = 12b0000_0001_0000,
write_LowBit_addr = 12b0000_0010_0000,
write_data_state = 12b0000_0100_0000,
read_device_start = 12b0000_1000_0000,
read_device_addr = 12b0001_0000_0000,
read_data = 12b0010_0000_0000,
main_write_stop = 12b0100_0000_0000,
rom_ack = 12b1000_0000_0000;
//-----------tasks statement-------------------------------------------------
//--------------task : write_start----------
localparam start_state1 = 0,
start_state2 = 1,
start_state3 = 2;
//--------------task : write_stop---------
localparam stop_state1 = 0,
stop_state2 = 1,
stop_state3 = 2;
//--------------task : write_bit------------
localparam state_bit7 = 9b 000_000_001,
state_bit6 = 9b 000_000_010,
state_bit5 = 9b 000_000_100,
state_bit4 = 9b 000_001_000,
state_bit3 = 9b 000_010_000,
state_bit2 = 9b 000_100_000,
state_bit1 = 9b 001_000_000,
state_bit0 = 9b 010_000_000,
state_end = 9b 100_000_000;
//----------------- task : read_bit---------
localparam read_bit7 = 9b 000_000_001,
read_bit6 = 9b 000_000_010,
read_bit5 = 9b 000_000_100,
read_bit4 = 9b 000_001_000,
read_bit3 = 9b 000_010_000,
read_bit2 = 9b 000_100_000,
read_bit1 = 9b 001_000_000,
read_bit0 = 9b 010_000_000,
read_end = 9b 100_000_000;
//--------------------------------------------------------------------
always @ (posedge clk)
begin
if(!rst_n)
begin
link_start <= 0;
link_write <= 0;
link_stop <= 0;
link_read <= 0;
link_sda <= 0;
WF <= 0;
RF <= 0;
FF <= 0;
ack_to_rom <= 1;
main_state <= because_ack;
end
else
case(main_state)
because_ack :
begin
ack_to_rom <= 0; //because the ack_to_rams sequental dosent matching with address or data_from_rom
main_state <= idle;
end
idle :
begin
link_start <= 0;
link_write <= 0;
link_stop <= 0;
link_read <= 0;
link_sda <= 0;
WF <= 0;
RF <= 0;
FF <= 0;
main_state <= prepare_read;
end
prepare_read :
begin
// ack_to_rom <= 0;
head_start <= 2b10;
end_stop <= 2b01;
write_start_state <= start_state1;
if(WR) begin WF <= 1; main_state <= write_device_start; end
else if(RD) begin RF <= 1; main_state <= write_device_start; end
else begin WF <= 0; RF <= 0; main_state <= prepare_read; end
end
write_device_start :
begin
if(FF==0)
write_start; // it is a task : generate a start signal
else
begin
write_data <= 8ha0; // its device address 8b1010_000_0(the last bit("0") is represent write)
write_bit_state <= state_bit6; //when it come to task start run from the first statement
main_state <= write_device_addr;
link_write <= 1;
link_sda <= 1;
FF <= 0;
end
end
write_device_addr :
begin
if(FF==0)
write_bit; // itis a task : write the device address into sda (bit by bit)
else
begin
write_data <= {1b0,1b0,1b0,addr[12:8]}; // in the 24LC64 ,former three bits (qian san ge zi jie) dont care
write_bit_state <= state_bit7;
FF <= 0;
main_state <= write_HighBit_addr;
end
end
write_HighBit_addr :
begin
if(FF==0)
write_bit; // write the storage(cun chu qi) address[12:8]
else
begin
write_data <= addr[7:0];
write_bit_state <= state_bit7;
FF <= 0;
main_state <= write_LowBit_addr;
end
end
write_LowBit_addr :
begin
if(FF==0)
write_bit; // write the storage address[7:0]
else
begin
FF <= 0;
if(WF)
begin
write_bit_state <= state_bit7;
write_data <= data_from_rom;
main_state <= write_data_state;
end
else
if(RF)
begin
write_start_state <= start_state1;
main_state <= read_device_start;
head_start <= 2b10;
end
else
main_state <= write_LowBit_addr;
end
end
write_data_state :
begin
if(FF==0)
write_bit;
else
begin
write_stop_state <= stop_state1;
FF <= 0;
main_state <= main_write_stop;
end
end
read_device_start :
begin
if(FF==0)
write_start;
else
begin
FF <= 0;
link_write <= 1;
link_start <= 0;
write_data <= 8ha1; //represent the 8b1010_0001 the last bit meaning read
write_bit_state <= state_bit6;
main_state <= read_device_addr;
end
end
read_device_addr :
begin
if(FF==0)
write_bit;
else
begin
link_sda <= 0;
// link_write <= 0;
//link_read <= 1; if the sentence is effected, the oscillogram(bo xing tu) data_to_ram[6~0 timing] will be Z state
read_bit_state <= read_bit7;
FF <= 0;
main_state <= read_data;
end
end
read_data :
begin
if(FF==0)
read_bit;
else
begin
write_stop_state <= stop_state1;
FF <= 0;
main_state <= main_write_stop;
end
end
main_write_stop :
begin
if(FF==0)
write_stop;
else
begin
ack_to_rom <= 1;
FF = 0;
main_state <= rom_ack;
end
end
rom_ack :
begin
ack_to_rom <= 0;
WF <= 0;
RF <= 0;
main_state <= idle;
end
default : main_state <= idle;
endcase
end
//--------------------under the line is task----------------------------------------------------------
//---------------task:write_sart----------
task write_start;
begin
case(write_start_state)
start_state1 :
begin
if(scl==0)
begin
link_start <= 1;
link_sda <= 1;
write_start_state <= start_state2;
end
else
write_start_state <= start_state1;
end
start_state2 :
begin
if(scl==1)
begin
FF <= 1; // the FFs meaning is very important : Here it is no acknowledge
head_start = head_start << 1;
write_start_state <= start_state3;
end
else
write_start_state <= start_state2;
end
start_state3 :
begin
if(scl==0)
begin
link_sda <= 0; // because of the FF=1, so the FF=1 just keep a period . when the next clk posedge( link_sda=link_start=0 ,jump out the task)
link_start <= 0;
end
else
write_start_state <= start_state3;
end
default : write_start_state <= start_state1;
endcase
end
endtask
//---------------task:write_stop----------
task write_stop;
begin
case(write_stop_state)
stop_state1 :
begin
if(scl==0)
begin
link_stop <= 1;
link_sda <= 1;
write_stop_state <= stop_state2;
end
else
write_stop_state <= stop_state1;
end
stop_state2 :
begin
if(scl==1)
begin
end_stop = end_stop << 1;
write_stop_state <= stop_state3;
end
else
write_stop_state <= stop_state2;
end
stop_state3 :
begin
if(scl==0)
begin
link_sda <= 0;
FF <= 1;
link_stop <= 0;
end
else
write_stop_state <= stop_state3;
end
default : write_stop_state <= stop_state1;
endcase
end
endtask
//----------task : write_bit---------------
task write_bit;
begin
case(write_bit_state)
state_bit7 :
begin
if(scl==0)
begin
link_write <= 1;
link_sda <= 1;
write_bit_state <= state_bit6;
end
else
write_bit_state <= state_bit7;
end
state_bit6 :
begin
if(scl==0)
begin
write_data <= write_data << 1;
write_bit_state <= state_bit5;
end
else
write_bit_state <= state_bit6;
end
state_bit5 :
begin
if(scl==0)
begin
write_data <= write_data << 1;
write_bit_state <= state_bit4;
end
else
write_bit_state <= state_bit5;
end
state_bit4 :
begin
if(scl==0)
begin
write_data <= write_data << 1;
write_bit_state <= state_bit3;
end
else
write_bit_state <= state_bit4;
end
state_bit3 :
begin
if(scl==0)
begin
write_data <= write_data << 1;
write_bit_state <= state_bit2;
end
else
write_bit_state <= state_bit3;
end
state_bit2 :
begin
if(scl==0)
begin
write_data <= write_data << 1;
write_bit_state <= state_bit1;
end
else
write_bit_state <= state_bit2;
end
state_bit1 :
begin
if(scl==0)
begin
write_data <= write_data << 1;
write_bit_state <= state_bit0;
end
else
write_bit_state <= state_bit1;
end
state_bit0 :
begin
if(scl==0)
begin
write_data <= write_data << 1;
write_bit_state <= state_end;
end
else
write_bit_state <= state_bit0;
end
state_end :
begin
if(scl==0)
begin
FF <= 1; // Here it is generate a acknowledge(the ack can hold on a clk period)
link_write <= 0;
link_sda <= 0;
end
else
write_bit_state <= state_end;
end
default : write_bit_state <= state_bit7;
endcase
end
endtask
//----------task : read_bit---------------------------
task read_bit;
begin
case(read_bit_state)
read_bit7 :
begin
if(scl)
begin
link_sda <= 0;
data_from_eeprom[7] <= sda;
read_bit_state <= read_bit6;
end
else
read_bit_state <= read_bit7;
end
read_bit6 :
begin
if(scl)
begin
data_from_eeprom[6] <= sda;
read_bit_state <= read_bit5;
end
else
read_bit_state <= read_bit6;
end
read_bit5 :
begin
if(scl)
begin
data_from_eeprom[5] <= sda;
read_bit_state <= read_bit4;
end
else
read_bit_state <= read_bit5;
end
read_bit4 :
begin
if(scl)
begin
data_from_eeprom[4] <= sda;
read_bit_state <= read_bit3;
end
else
read_bit_state <= read_bit4;
end
read_bit3 :
begin
if(scl)
begin
data_from_eeprom[3] <= sda;
read_bit_state <= read_bit2;
end
else
read_bit_state <= read_bit3;
end
read_bit2 :
begin
if(scl)
begin
data_from_eeprom[2] <= sda;
read_bit_state <= read_bit1;
end
else
read_bit_state <= read_bit2;
end
read_bit1 :
begin
if(scl)
begin
data_from_eeprom[1] <= sda;
read_bit_state <= read_bit0;
end
else
read_bit_state <= read_bit1;
end
read_bit0 :
begin
if(scl)
begin
data_from_eeprom[0] <= sda;
read_bit_state <= read_end;
end
else
read_bit_state <= read_bit0;
end
read_end :
begin
if(scl)
begin
link_read <= 1;
FF <= 1;
end
else
read_bit_state <= read_end;
end
default : begin
link_read <= 0;
read_bit_state <= read_bit7;
end
endcase
end
endtask
//----------under the lines always function: catch the signal data_to_ram-----------------
always @ (posedge clk)
begin
if(!rst_n)
data_to_ram_reg <= 0;
else
if(RD)
begin
if(data_to_ram >= 0)
data_to_ram_reg <= data_to_ram;
else
data_to_ram_reg <= data_to_ram_reg;
end
else
data_to_ram_reg <= data_to_ram_reg;
end
endmodule
单口RAM直接调用
下面是二进制转BCD部分:这样的算法来自国外教材,与传统的取余方法相比会节省很多的资源
//大四加三的二进制转BCD的算法
module Binary_to_BCD(clk, rst_n, Binary_a, BCDa);
input clk,rst_n;
input [19:0] Binary_a; //因为24bitBCD码的最大十进制表示值为999999,用20位的二进制数表示已够
output reg [23:0] BCDa;
reg [19:0] data_reg,data_reg1;
reg [3:0] w1,w2,w3,w4,w5,w6;
reg [4:0] q; //表示移位的次数,二十位的二进制数需要移位20次
reg [1:0] state;
always @ (posedge clk)
begin
if(!rst_n)
begin
BCDa <= 0;
data_reg <= 0;
data_reg1 <= 0;
w1<=0;w2<=0;w3<=0;
w4<=0;w5<=0;w6<=0;
q <= 0;
state <= 0;
end
else
case(state)
0 : begin
data_reg <= Binary_a;
data_reg1 <= Binary_a;
w1<=0;w2<=0;w3<=0;
w4<=0;w5<=0;w6<=0;
q<=0;
state <= 1;
end
1 : begin
q <= q + 1;
data_reg <= (data_reg << 1);
w1 <= {w1[2:0],data_reg[19]};
w2 <= {w2[2:0],w1[3]}; w3 <= {w3[2:0],w2[3]};
w4 <= {w4[2:0],w3[3]}; w5 <= {w5[2:0],w4[3]};
w6 <= {w6[2:0],w5[3]};
if(q==19)
state <= 3;
else
state <= 2;
end
2 : begin
if(w1 > 4) w1 = w1 + 3;
else w1 = w1; if(w2>4) w2 = w2 + 3;
else w2 = w2;if(w3>4) w3 = w3 + 3;
else w3 = w3;if(w4>4) w4 = w4 + 3;
else w4 = w4;if(w5>4) w5 = w5 + 3;
else w5 = w5;if(w6>4) w6 = w6 + 3;
else w6 = w6;state <= 1;
end
3 : begin
BCDa <= {w6,w5,w4,w3,w2,w1};
if(data_reg1!=Binary_a)
state <= 0;
else
state <= 3;
end
default : state <= 0;
endcase
end
endmodule
下面是数码管显示部分
//把移位后的数字在数码管显示
//在该模块中实现了高位不是0的时候数码管上不显示0这个数字
module display(clk, rst_n, BCDa, sel, seg);
input clk;
input rst_n;
input [23:0] BCDa;
output reg [2:0] sel;
output reg [7:0] seg;
localparam seg_display0 = 8hc0, //表示0~f的十六进制数
seg_display1 = 8hf9,
seg_display2 = 8ha4,
seg_display3 = 8hb0,
seg_display4 = 8h99,
seg_display5 = 8h92,
seg_display6 = 8h82,
seg_display7 = 8hf8,
seg_display8 = 8h80,
seg_display9 = 8h98,
seg_displayA = 8h88,
seg_displayB = 8h83,
seg_displayC = 8hc6,
seg_displayD = 8ha1,
seg_displayE = 8h86,
seg_displayF = 8h8e,
seg_displayNO = 8hff;
wire [23:0] data;
reg [3:0] segdata;
assign data = BCDa;
//------------------------------------------------------------
//循环一直产生sel
always @ (posedge clk, negedge rst_n)
begin
if(!rst_n)
begin
sel <= 0;
end
else
if(sel>=5)
sel <= 0;
else
sel <= sel + 1;
end
//在sel为不同的位数时只管当前对应的segdata即可 该位亮的条件只需当前位或者其高位不为零就显示 否则就不显示
//-------------------------------------------------------------------
always @ (*)
begin
if(!rst_n)
segdata = 15; //复位的作用是让seg显示seg_displayNO
else
begin
case(sel)
0 : begin
if(data[23:20])
segdata = data[23:20];
else
segdata = 15; //最高位为零的时候不显示数码(只要不是0~9就能达到效果),竟然忘记其[3:0]三位最大值是15,写成17时segdata=1
end
1 : begin
if(data[19:16]!=0 || data[23:20]!=0)
segdata = data[19:16];
else
segdata = 15;
end
2 : begin
if(data[15:12]!=0 || data[23:16]!=0)
segdata <= data[15:12];
else
segdata = 15;
end
3 : begin
if(data[11:8]!=0 || data[23:12]!=0)
segdata = data[11:8];
else
segdata = 15;
end
4 : begin
if(data[7:4]!=0 || data[23:8]!=0)
segdata = data[7:4];
else
segdata = 15;
end
5 : begin
if(data[3:0]!=0 || data[23:4]!=0)
segdata = data[3:0];
else
segdata = 15;
end
default : segdata = 15;
endcase
end
end
//-------------------------------------------------------------------
//不同的数值将数码管上的十六进制数赋给segdata
always @ (*)
begin
if(!rst_n)
seg = seg_displayNO;
else
case(segdata)
0 : seg = seg_display0;
1 : seg = seg_display1;
2 : seg = seg_display2;
3 : seg = seg_display3;
4 : seg = seg_display4;
5 : seg = seg_display5;
6 : seg = seg_display6;
7 : seg = seg_display7;
8 : seg = seg_display8;
9 : seg = seg_display9;
10: seg = seg_displayA;
11: seg = seg_displayB;
12: seg = seg_displayC;
13: seg = seg_displayD;
default : seg = seg_displayNO;
endcase
end
endmodule
江秋说:学习一定要按部就班的做好每一步,层次化建模十分的重要。笔者建议看代码的时候用笔画好状态转移图和各个模块的端口,这样的方法同样适用于写程序的时候。画好架构,细致到每个端口及其位宽,具体的连线,按图施工,无论代码是多么的庞大,总能有条理的完成。
看代码的时候有时候会发现:作者某处的代码有些冗余,某些部分可以省略。笔者建议读者按照自己的想法去做,很可能你会发现一修改就会有问题(当初笔者看夏宇闻老师的代码时就是这样的),在此基础上依照波形,仔细想想其中的原因,想明白后便是一个大的提高,若能在原来的基础上做些修改而且能按照自己的设想产生波形图、在开发板上实现预想的现象,这样是最好不过了。
读者对代码有任何疑问都可以留言。