[例1] 一个简单的状态机设计--序列检测器
序列检测器是时序数字电路设计中经典的教学范例,下面我们将用Verilog HDL语言来描述、仿真、并实现它。
序列检测器的逻辑功能描述:
序列检测指的就是将一个指定的序列从数字码流中识别出来。本例中,我们将设计一个“10010”序列的检测器。设X为数字码流输入,Z为检出标记输出,高电平表示“发现指定序列”,低电平表示“没有发现指定序列”。考虑码流为“ 110010010000100101…” 则有下表:
在时钟2-6,码流X中出现指定序列“10010”,对应输出Z在第6个时钟变为高电平――“1”,表示“发现指定序列”。同样地,在时钟13-17码流,X中再次出现指定序列“10010”,Z输出“1”。注意,在时钟5-9还有一次检出,但它是与第一次检出的序列重叠的,即前者的前面两位同时也是后者的最后两位。
根据以上逻辑功能描述,我们可以分析得出状态转换图如下:
其中状态A-E表示5比特序列“10010”按顺序正确地出现在码流中。考虑到序列重叠的可能,转换图中还有状态F、G。另外、电路的初始状态设为IDLE。
进一步,我们得出Verilog HDL代码。
//文件:sequence.v
module seqdet( x, z, clk, rst);
input x,clk, rst;
output z;
reg [2:0] state;//状态寄存器
wire z;
parameter IDLE= 'd0, A='d1, B='d2,
C='d3, D='d4,
E='d5, F='d6,
G='d7;
assign z=(state==D && x==0) ? 1 :0;
always @(posedge clk or negedge rst)
if(!rst)
begin
state<=IDLE;
end
else
casex( state)
IDLE: if(x==1)
begin
state<=A;
end
A: if (x==0)
begin
state<=B;
end
B: if (x==0)
begin
state<=C;
end
else
begin
state<=F;
end
C: if(x==1)
begin
state<=D;
end
else
begin
state<=G;
end
D: if(x==0)
begin
state<=E;
end
else
begin
state<=A;
end
E: if(x==0)
begin
state<=C;
end
else
begin
state<=A;
end
F: if(x==1)
begin
state<=A;
end
else
begin
state<=B;
end
G: if(x==1)
begin
state<=F;
end
default: state<=IDLE;
endcase
endmodule
为了验证其正确性,我们接着编写测试用代码。
//文件:sequence.tf
`timescale 1ns/1ns
module t;
reg clk, rst;
reg [23:0] data;
wire z,x;
assign x=data[23];
initial
begin
clk<=0;
rst<=1;
#2 rst<=0;
#30 rst<=1; //复位信号
data='b1100_1001_0000_1001_0100; //码流数据
end
always #10 clk=~clk; //时钟信号
always @ (posedge clk) // 移位输出码流
data={data[22:0],data[23]};
seqdet m ( .x(x), .z(z), .clk(clk), .rst(rst)); //调用序列检测器模块
// Enter fixture code here
endmodule // t
其中、X码流的产生,我们采用了移位寄存器的方式,以方便更改测试数据。仿真结果如下图所示:
从波形中,我们可以看到程序代码正确地完成了所要设计的逻辑功能。另外,sequence.v的编写,采用了可综合的Verilog HDL 风格,它可以通过综合器的综合最终实现到FPGA中。
说明:以上编程、仿真、综合和后仿真在PC WINDOWS NT 4.0操作系统及QuickLogic SPDE环境下通过。
[例2]EEPROM读写器件的设计
下面我们将介绍一个经过实际运行验证并可综合到各种FPGA和ASIC工艺的串行EEPROM读写器件的设计过程。列出了所有有关的Verilog HDL程序。这个器件能把并行数据和地址信号转变为串行EEPROM能识别的串行码并把数据写入相应的地址,或根据并行的地址信号从EEPROM相应的地址读取数据并把相应的串行码转换成并行的数据放到并行地址总线上。当然还需要有相应的读信号或写信号和应答信号配合才能完成以上的操作。
1. 二线制I2C CMOS 串行EEPROM的简单介绍
二线制I2C CMOS 串行EEPROM AT24C02/4/8/16 是一种采用CMOS 工艺制成的串行可用电擦除可编程只读存储器。串行EEPROM 一般具有两种写入方式,一种是字节写入方式,还有另一种页写入方式,允许在一个写周期内同时对一个字节到一页的若干字节进行编程写入,一页的大小取决于芯片内页寄存器的大小,不同公司的同一种型号存储器的内页寄存器可能是不一样的。为了程序的简单起见,在这里只编写串行 EEPROM 的一个字节的写入和读出方式的Verilog HDL 的行为模型代码,串行EEPROM读写器的Verilog HDL模型也只是字节读写方式的可综合模型,对于页写入和读出方式,读者可以参考有关书籍,改写串行EEPROM 的行为模型和串行EEPROM读写器的可综合模型。
2. I2C (Inter Integrated Circuit)总线特征介绍
I2C 双向二线制串行总线协议定义如下:
只有在总线处于“非忙”状态时,数据传输才能被初始化。在数据传输期间,只要时钟线为高电平,数据线都必须保持稳定,否则数据线上的任何变化都被当作“启动”或“停止”信号。图 1 是被定义的总线状态。
① 总线非忙状态(A 段)
数据线SDA 和 时钟线 SCL 都保持高电平。
② 启动数据传输(B 段)
当时钟线(SCL)为高电平状态时,数据线(SDA)由高电平变为低电平的下降沿被认为是“启动”信号。只有出现“启动”信号后,其它的命令才有效。
③ 停止数据传输(C 段)
当时钟线(SCL)为高电平状态时,数据线(SDA)由低电平变为高电平的上升沿被认为是“停止”信号。随着“停在”信号出现,所有的外部操作都结束。
④ 数据有效(D 段)
在出现“启动”信号以后,在时钟线(SCL)为高电平状态时数据线是稳定的,这时数据线的状态就要传送的数据。数据线(SDA)上的数据的改变必须在时钟线为低电平期间完成,每位数据占用一个时钟脉冲。每个数传输都是由“启动”信号开始,结束于“停止”信号。
⑤ 应答信号
每个正在接收数据的EEPROM 在接到一个字节的数据后,通常需要发出一个应答信号。而每个正在发送数据的EEPROM 在发出一个字节的数据后,通常需要接收一个应答信号。EEPROM 读写控制器必须产生一个与这个应答位相联系的额外的时钟脉冲。在EEPROM 的读操作中,EEPROM 读写控制器对EEPROM 完成的最后一个字节不产生应答位,但是应该给EEPROM 一个结束信号。
3. 二线制I2C CMOS 串行EEPROM读写操作
1) EEPROM 的写操作(字节编程方式)
所谓EEPROM的写操作(字节编程方式)就是通过读写控制器把一个字节数据发送到EEPROM 中指定地址的存储单元。其过程如下:EEPROM 读写控制器发出“启动”信号后,紧跟着送4位I2C总线器件特征编码1010 和3 位EEPROM 芯片地址/页地址XXX 以及写状态的R/W 位(=0),到总线上。这一字节表示在接收到被寻址的EEPROM 产生的一个应答位后,读写控制器将跟着发送1个字节的EEPROM 存储单元地址和要写入的1个字节数据。EEPROM 在接收到存储单元地址后又一次产生应答位以后,读写控制器才发送数据字节,并把数据写入被寻址的存储单元。EEPROM 再一次发出应答信号,读写控制器收到此应答信号后,便产生“停止”信号。字节写入帧格式如图2所示:
2) 二线制I2C CMOS 串行EEPROM 的读操作
所谓EEPROM的读操作即通过读写控制器读取 EEPROM 中指定地址的存储单元中的一个字节数据。串行EEPROM 的读操作分两步进行:读写器首先发送一个“启动”信号和控制字节(包括页面地址和写控制位)到EEPROM,再通过写操作设置EEPROM 存储单元地址(注意:虽然这是读操作,但需要先写入地址指针的值),在此期间EEPROM 会产生必要的应答位。接着读写器重新发送另一个“启动”信号和控制字节(包括页面地址和读控制位R/W = 1),EEPROM收到后发出应答信号,然后,要寻址存储单元的数据就从SDA 线上输出。读操作有三种:读当前地址存储单元的数据、读指定地址存储单元的数据、读连续存储单元的数据。在这里只介绍读指定地址存储单元数据的操作。读指定地址存储单元数据的帧格式如图3:
4. EEPROM的Verilog HDL 程序
要设计一个串行EEPROM读写器件,不仅要编写EEPROM读写器件的可综合Verilog HDl的代码,而且要编写相应的测试代码以及EERPOM的行为模型。EEPROM的读写电路及其测试电路如图4。
1) EEPROM的行为模型
为了设计这样一个电路我们首先要设计一个EEPROM的Verilog HDL模型,而设计这样一个模型我们需要仔细地阅读和分析EEPROM器件的说明书,因为EEPROM不是我们要设计的对象,而是我们验证设计对象所需要的器件,所以只需设计一个EEPROM的行为模型,而不需要可综合风格的模型,这就大大简化了设计过程。下面的Verilog HDL程序就是这个EEPROM(AT24C02/4/8/16) 能完成一 个字节数据读写的部分行为模型,请读者查阅AT24C02/4/8/16说明书,对照下面的Verilog HDL程序理解设计的要点。因为这一程序是我们自己编写的有不完善之处敬请指正。
这里只对在操作中用到的信号线进行模拟,对于没有用到的信号线就略去了。对EEPROM用于基本总线操作的引脚SCL和SDA说明如下:SCL,串行时钟端,这个信号用于对输入和输出数据的同步,写入串行EEPROM的数据用其上升沿同步,输出数据用其下降沿同步;SDA,串行数据(/地址)输入/输出端。
`timescale 1ns/1ns
`define timeslice 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= 8'b10101111,w7= 8'b10101110, //main7
r6= 8'b10101101,w6= 8'b10101100, //main6
r5= 8'b10101011,w5= 8'b10101010, //main5
r4= 8'b10101001,w4= 8'b10101000, //main4
r3= 8'b10100111,w3= 8'b10100110, //main3
r2= 8'b10100101,w2= 8'b10100100, //main2
r1= 8'b10100011,w1= 8'b10100010, //main1
r0= 8'b10100001,w0= 8'b10100000; //main0
//--------------------------------------------------------------
assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz;
//―――――――寄存器和存储器初始化―――――――――――――――
initial
begin
addr_byte = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
State = 2'b00;
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 == 2'b11)
disable write_to_eeprm;
end
//------------ 主状态机 --------------------------
always @(posedge sda)
if (scl == 1 ) //停止操作
stop_W_R;
else
begin
casex(State)
2'b01:
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 = 2'b10;
write_to_eeprm; //写操作
end
else
State = 2'b00;
end
2'b11:
read_from_eeprm; //读操作
default:
State=2'b00;
endcase
end
//------------- 操作停止------------------------------
task stop_W_R;
begin
State =2'b00; //状态返回为初始状态
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 =2'b00; //回到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= 2'b00;
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
#`timeslice ;
out_flag = 1; //应答信号输出
sda_buf = 0;
end
@(negedge scl)
#`timeslice 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);
#`timeslice;
sda_buf = sda_buf<<1;
end
@(negedge scl) #`timeslice sda_buf[7] = 1; //非应答信号输出
@(negedge scl) #`timeslice out_flag = 0;
end
endtask
endmodule
2 ) EEPROM读写器的可综合的Verilog HDL模型
下面的程序是一个串行EEPROM读写器的可综合的Verilog HDL模型,它接收来自信号源模型产生的读信号、写信号、并行地址信号、并行数据信号,并把它们转换为相应的串行信号发送到串行EEPROM(AT24C02/4/8/16) 的行为模型中去;它还发送应答信号 (ACK)到信号源模型,以便让信号源来调节发送或接收数据的速度以配合EEPROM模型的接收(写)和发送(读)数据。因为它是我们的设计对象,所以它不但要仿真正确无误,还需要可综合。
这个程序基本上由两部分组成:开关组合电路和控制时序电路,见图5。开关电路在控制时序电路的控制下按照设计的要求有节奏的打开或闭合,这样SDA可以按I2C 数据总线的格式输出或输入,SDA和SCL一起完成EEPROM的读写操作。
EEPROM读写器的结构
电路最终用同步有限状态机(FSM)的设计方法实现。程序实则上是一个嵌套的状态机,由主状态机和从状态机通过由控制线启动的总线在不同的输入信号的情况下构成不同功能的较复杂的有限状态机,这个有限状态机只有唯一的驱动时钟CLK。根据串行EEPROM的读写操作时序可知,用5个状态时钟可以完成写操作,用7个状态时钟可以完成读操作,由于读写操作的状态中有几个状态是一致的,用一个嵌套的状态机即可。状态转移如图6,程序由一个读写大任务和若干个较小的任务所组成,其状态机采用独热编码,若需改变状态编码,只需改变程序中的parameter定义即可。读者可以通过模仿这一程序来编写较复杂的可综合Verilog HDL模块程序。这个设计已通过后仿真,并可在FPGA上实现布局布线。
读写操作状态转移
`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] : 1'b0;
assign sda2 = (link_write) ? sh8out_buf[7] : 1'b0;
assign sda3 = (link_stop) ? stop_buf[1] : 1'b0;
assign sda4 = (sda1 | sda2 | sda3);
assign SDA = (link_sda) ? sda4 : 1'bz;
assign DATA = (link_read) ? data_from_rm : 8'hzz;
//--------------------------------主状态机状态------------------------------------------
parameter
Idle = 11'b00000000001,
Ready = 11'b00000000010,
Write_start = 11'b00000000100,
Ctrl_write = 11'b00000001000,
Addr_write = 11'b00000010000,
Data_write = 11'b00000100000,
Read_start = 11'b00001000000,
Ctrl_read = 11'b00010000000,
Data_read = 11'b00100000000,
Stop = 11'b01000000000,
Ackn = 11'b10000000000,
//-------------------------并行数据串行输出状态-----------------------------
sh8out_bit7 = 9'b000000001,
sh8out_bit6 = 9'b000000010,
sh8out_bit5 = 9'b000000100,
sh8out_bit4 = 9'b000001000,
sh8out_bit3 = 9'b000010000,
sh8out_bit2 = 9'b000100000,
sh8out_bit1 = 9'b001000000,
sh8out_bit0 = 9'b010000000,
sh8out_end = 9'b100000000;
//--------------------------串行数据并行输出状态----------------------------
parameter sh8in_begin = 10'b0000000001,
sh8in_bit7 = 10'b0000000010,
sh8in_bit6 = 10'b0000000100,
sh8in_bit5 = 10'b0000001000,
sh8in_bit4 = 10'b0000010000,
sh8in_bit3 = 10'b0000100000,
sh8in_bit2 = 10'b0001000000,
sh8in_bit1 = 10'b0010000000,
sh8in_bit0 = 10'b0100000000,
sh8in_end = 10'b1000000000,
//---------------------------------启动状态----------------------------------
head_begin = 3'b001,
head_bit = 3'b010,
head_end = 3'b100,
//---------------------------------停止状态----------------------------------
stop_begin = 3'b001,
stop_bit = 3'b010,
stop_end = 3'b100;
parameter YES = 1,
NO = 0;
//-------------产生串行时钟,为输入时钟的二分频-------------------
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] <= 2'b10;
stop_buf[1:0] <= 2'b01;
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] <= {1'b1,1'b0,1'b1,1'b0,ADDR[10:8],1'b0};
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 <= 2'b10;
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 <= {1'b1,1'b0,1'b1,1'b0,ADDR[10:8],1'b1};
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
程序最终通过Synplify器的综合,并在Actel 3200DX 系列的FPGA上实现布局布线,通过布线后仿真 。
3 ) EEPROM的信号源模块和顶层模块
完成串行EEPROM读写器件的设计后,我们还需要做的重要一步是EEPROM读写器件的仿真。仿真可以分为前仿真和后仿真,前仿真是Verilog HDL的功能仿真,后仿真是Verilog HDL 代码经过综合、布局布线后的时序仿真。为此,我们还要编写了用于EEPROM读写器件的仿真测试的信号源程序。这个信号源能产生相应的读信号、写信号、并行地址信号、并行数据信号,并能接收串行EEPROM读写器件的应答信号 (ACK),来调节发送或接收数据的速度。在这个程序中,我们为了保证串行EEPROM读写器件的正确性,可以进行完整的测试,写操作时输入的地址信号和数据信号的数据通过系统命令readmemh和$fopen等系统命令读者可以参考Verilog HDL的语法部分。最后我们把信号源、EEPROM和EEPROM读写器用顶层模块连接在一起。在下面的程序就是这个信号源的Verilog HDL模型和顶层模块。
信号源模型:
`timescale 1ns/1ns
`define timeslice 200
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[1:2048];
integer i,j;
integer OUTFILE;
assign DATA = (W_R) ? 8'bz : data_to_eeprom ;
//------------------------------------时钟信号输入------------------------------
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(15) //连续写15次数据
begin
#(5*`timeslice);
WR = 1;
#(`timeslice);
WR = 0;
@ (posedge ACK);
end
#(10*`timeslice);
W_R = 1; //开始读操作
repeat(15) //连续读15次数据
begin
#(5*`timeslice);
RD = 1;
#(`timeslice);
RD = 0;
@ (posedge ACK);
end
end
//-----------------------------------------写操作-----------------------------
initial
begin
$display("writing-----writing-----writing-----writing");
# (2*`timeslice);
for(i=0;i<=15;i=i+1)
begin
ADDR = addr_mem[i];
data_to_eeprom = data_mem[i];
$fdisplay(OUTFILE,"@%0h %0h",ADDR, data_to_eeprom);
@(posedge ACK) ;
end
end
//----------------------------------------读操作----------------------------
initial
@(posedge W_R)
begin
ADDR = addr_mem[0];
$fclose(OUTFILE);
$readmemh("./eeprom.dat",ROM);
$display("Begin READING-----READING-----READING-----READING");
for(j = 0; j <= 15; 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
OUTFILE = $fopen("./eeprom.dat");
$readmemh("./addr.dat",addr_mem); //地址数据存入地址存储器
$readmemh("./data.dat",data_mem); //写入EEPROM的数据存入数据存储器
end
endmodule
顶层模块:
`include “./Signal.v”
`include “./EEPROM.v”
`include “./EEPROM_WR.v”
`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
通过前后仿真可以验证程序的正确性。这里给出的是EEPROM读写时序的前仿真波形。后仿真波形除SCL和SDA与CLK有些延迟外,信号的逻辑关系与前仿真一致:
EEPROM 的写时序
EEPROM 的读时序
说明:
以上编程、仿真、综合在PC WINDOWS NT 4.0操作系统、Synplify 、Actel Designer 、Altera Maxplus9.3及ModelSim Verilog环境下通过前后仿真,也在Unix Cadence Verilog-XL上通过前、后仿真(可综合到各种FPGA和ASIC工艺)。
原出处:OpenFPGA
作者:碎碎思
相关文章推荐
更多FPGA技术干货请关注IC设计技术专栏。