0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

浅谈Verilog复杂时序逻辑电路设计实践

TLOc_gh_3394704 来源:OpenFPGA 作者:碎碎思 2021-08-10 16:33 次阅读

笔试时也很常见。

[例1] 一个简单的状态机设计--序列检测

序列检测器是时序数字电路设计中经典的教学范例,下面我们将用Verilog HDL语言来描述、仿真、并实现它。

序列检测器的逻辑功能描述:

序列检测指的就是将一个指定的序列从数字码流中识别出来。本例中,我们将设计一个“10010”序列的检测器。设X为数字码流输入,Z为检出标记输出,高电平表示“发现指定序列”,低电平表示“没有发现指定序列”。考虑码流为“ 110010010000100101…”

时钟2-6,码流X中出现指定序列“10010”,对应输出Z在第6个时钟变为高电平――“1”,表示“发现指定序列”。同样地,在时钟13-17码流,X中再次出现指定序列“10010”,Z输出“1”。注意,在时钟5-9还有一次检出,但它是与第一次检出的序列重叠的,即前者的前面两位同时也是后者的最后两位。

根据以上逻辑功能描述,我们可以分析得出状态转换图如下:

19d3911c-f7b2-11eb-9bcf-12bb97331649.png

其中状态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码流的产生,我们采用了移位寄存器的方式,以方便更改测试数据。仿真结果如下图所示:

1a0c803a-f7b2-11eb-9bcf-12bb97331649.png

从波形中,我们可以看到程序代码正确地完成了所要设计的逻辑功能。另外,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 双向二线制串行总线协议定义如下:

只有在总线处于“非忙”状态时,数据传输才能被初始化。在数据传输期间,只要时钟线为高电平,数据线都必须保持稳定,否则数据线上的任何变化都被当作“启动”或“停止”信号。

①总线非忙状态(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 线上输出。读操作有三种:读当前地址存储单元的数据、读指定地址存储单元的数据、读连续存储单元的数据。在这里只介绍读指定地址存储单元数据的操作。

4.EEPROM的Verilog HDL 程序要设计一个串行EEPROM读写器件,不仅要编写EEPROM读写器件的可综合Verilog HDl的代码,而且要编写相应的测试代码以及EERPOM的行为模型。

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的读写操作。

电路最终用同步有限状态机(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有些延迟外,信号的逻辑关系与前仿真一致:

说明:

以上编程、仿真、综合在PC WINDOWS NT 4.0操作系统、Synplify 、Actel Designer 、Altera Maxplus9.3及ModelSim Verilog环境下通过前后仿真,也在Unix Cadence Verilog-XL上通过前、后仿真(可综合到各种FPGA和ASIC工艺)。

编辑:jq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Verilog
    +关注

    关注

    28

    文章

    1351

    浏览量

    110070
  • HDL
    HDL
    +关注

    关注

    8

    文章

    327

    浏览量

    47373
  • 读写器
    +关注

    关注

    3

    文章

    656

    浏览量

    38842

原文标题:Verilog复杂时序逻辑电路设计实践

文章出处:【微信号:gh_339470469b7d,微信公众号:FPGA与数据通信】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    时序逻辑电路设计

    时序逻辑电路设计6.1 基本D触发器的设计6.2 JK触发器6.3 带异步复位/置位端的使能T触发器6.4 基本计数器的设计6.5 同步清零的计数器6.6 同步清零的可逆计数器6.7 同步预置数的计数器
    发表于 03-20 10:04

    浅谈Verilog复杂时序逻辑电路设计实践

    笔试时也很常见。[例1] 一个简单的状态机设计--序列检测器序列检测器是时序数字电路设计中经典的教学范例,下面我们将用Verilog HDL语言来描述、仿真、并实现它。序列检测器的逻辑功能描述:序列
    发表于 08-18 07:00

    时序逻辑电路设计

    时序逻辑电路的输出不但和当前输入有关,还与系统的原先状态有关,即时序电路的当前输出由输入变量与电路原先的状态共同决定。为达到这一目的,时序
    发表于 03-18 22:13 71次下载

    同步时序逻辑电路

    同步时序逻辑电路:本章系统的讲授同步时序逻辑电路的工作原理、分析方法和设计方法。从同步时序逻辑电路
    发表于 09-01 09:06 0次下载

    异步时序逻辑电路

    异步时序逻辑电路:本章主要从同步时序逻辑电路与异步时序逻辑电路状态改变方式不同的特殊性出发, 系
    发表于 09-01 09:12 0次下载

    时序逻辑电路

    数字逻辑电路逻辑功能和电路组成的特点可分为组合逻辑电路时序逻辑电路两大类。
    发表于 08-10 11:51 39次下载

    Verilog HDL语言实现时序逻辑电路

    Verilog HDL语言实现时序逻辑电路Verilog HDL语言中,时序逻辑电路使用a
    发表于 02-08 11:46 4704次阅读

    数字逻辑电路设计实践

    数字逻辑电路设计实践_电工电子实验中心实验报告。
    发表于 10-29 16:25 0次下载

    时序逻辑电路分析有几个步骤(同步时序逻辑电路的分析方法)

    分析时序逻辑电路也就是找出该时序逻辑电路逻辑功能,即找出时序
    发表于 01-30 18:55 12.7w次阅读
    <b class='flag-5'>时序</b><b class='flag-5'>逻辑电路</b>分析有几个步骤(同步<b class='flag-5'>时序</b><b class='flag-5'>逻辑电路</b>的分析方法)

    如何使用Verilog-HDL做CPLD设计的时序逻辑电路的实现

    本文档的主要内容详细介绍的是如何使用Verilog-HDL做CPLD设计的时序逻辑电路的实现。
    发表于 12-12 16:25 10次下载
    如何使用<b class='flag-5'>Verilog</b>-HDL做CPLD设计的<b class='flag-5'>时序</b><b class='flag-5'>逻辑电路</b>的实现

    时序逻辑电路设计

    数字电路根据逻辑功能的不同特点,可以分成两大类,一类叫组合逻辑电路(简称组合电路),另一类叫做时序逻辑电
    发表于 05-16 18:32 8415次阅读
    <b class='flag-5'>时序</b><b class='flag-5'>逻辑电路设计</b>

    Verilog教程之Verilog HDL数字逻辑电路设计方法

    在现阶段,作为设计人员熟练掌握 Verilog HDL程序设计的多样性和可综合性,是至关重要的。作为数字集成电路的基础,基本数字逻辑电路的设计是进行复杂
    发表于 12-09 11:24 36次下载
    <b class='flag-5'>Verilog</b>教程之<b class='flag-5'>Verilog</b> HDL数字<b class='flag-5'>逻辑电路设计</b>方法

    Verilog复杂时序逻辑电路设计实践

    笔试时也很常见。[例1] 一个简单的状态机设计--序列检测器序列检测器是时序数字电路设计中经典的教学范例,下面我们将用Verilog HDL语言来描述、仿真、并实现它。序列检测器的逻辑功能...
    发表于 12-17 18:28 16次下载
    <b class='flag-5'>Verilog</b><b class='flag-5'>复杂</b><b class='flag-5'>时序</b><b class='flag-5'>逻辑电路设计</b><b class='flag-5'>实践</b>

    时序逻辑电路设计之同步计数器

    时序电路的考察主要涉及分析与设计两个部分,上文介绍了时序逻辑电路的一些分析方法,重点介绍了同步时序电路分析的步骤与注意事项。 本文就时序
    的头像 发表于 05-22 17:01 3323次阅读
    <b class='flag-5'>时序</b><b class='flag-5'>逻辑电路设计</b>之同步计数器

    时序逻辑电路的功能表示方法有哪些

    时序逻辑电路是数字电路中的一种重要类型,其特点是电路的输出不仅取决于当前的输入,还取决于电路的状态。时序
    的头像 发表于 08-28 11:41 574次阅读