当前位置:首页 > 新闻资讯 > FPGA之家动态 >

FPGA学习笔记一:IIC总线的FPGA实现(Verilog 代码)

时间: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

江秋说:学习一定要按部就班的做好每一步,层次化建模十分的重要。笔者建议看代码的时候用笔画好状态转移图和各个模块的端口,这样的方法同样适用于写程序的时候。画好架构,细致到每个端口及其位宽,具体的连线,按图施工,无论代码是多么的庞大,总能有条理的完成。

看代码的时候有时候会发现:作者某处的代码有些冗余,某些部分可以省略。笔者建议读者按照自己的想法去做,很可能你会发现一修改就会有问题(当初笔者看夏宇闻老师的代码时就是这样的),在此基础上依照波形,仔细想想其中的原因,想明白后便是一个大的提高,若能在原来的基础上做些修改而且能按照自己的设想产生波形图、在开发板上实现预想的现象,这样是最好不过了。

读者对代码有任何疑问都可以留言。


注明:本内容来源网络,不用于商业使用,禁止转载,如有侵权,请来信到邮箱:429562386ⓐqq.com 或联系本站客服处理,感谢配合!

用户登陆

    未注册用户登录后会自动为您创建账号

提交留言