加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

并口的时序仿真--异步并口读写FPGA学习笔记--8

(2016-12-28 17:14:23)
分类: RAM/ROM/FIFO

2016年12月27日15:29:14  并口的时序仿真--异步并口读写

1、并口经常用于FPGA和cpu芯片通信、配置寄存器、跨芯片数据传输等
2、源同步传输:发送端同时发送时钟和数据给接收端,接收端使用源端发送的时钟采样数据。
3、异步并口通信:
异步并口应用:
--cpu类的芯片(dsp、stm32)与FPGA的数据交互,数据速率一般在100Mbps以内,数据总线不大于16bit
--非cpu类的功能芯片与FPGA通过并口进行数据交互,例如配置寄存器等
并口信号说明:
--chip select片选信号,简写为cs_n,此信号低有效,当被拉低的时候,表示此器件的并口被激活。
--write enable写使能信号,简写为we_n,信号低有效,当别拉低时表示此事地址总线和数据总线的地址数据是写入操作的。
--read enable读使能信号,简写为rd_n,此信号低有效,当别拉低时表示此时地址总线和数据总线的地址和数据是
--Addr[7:0]地址总线,表示读写的地址。
--Data[15:0]数据总线,此总线是双向总线,读操作时数据总线上是读取的数据,写操作时数据总线上是写入的数据。
--写操作时序见图1
--并口不适合高速传输且占用管脚数量很多,优点是简单,逻辑简单
--读操作时序见图2
--读等待(设置为40ns),是为了使地址数据稳定之后才赋值给端口。
4、往寄存器中存储数据,对寄存器寻址
5、cs_n和wr_n是两个握手操作的信号
6、寄存器打拍延时--延时时钟数为n,需要延时的数据为xbit,则使用移位寄存器进行延时的格式为:
reg  [3*n,0]  Data_r;
Data_r <= {Data_r[(n-1)*x,0],Data};--最后三个时钟后Data_r的高n位即为延时后的输出。
例如:
data_r <= {data_r[31:0],data}; //data为16bit,要打三拍,所以总的打拍寄存器为16*3=48bit,前面的为16*2=32bit
addr_r <= {addr_r[15:0],addr}; //addr为8bit,要打三拍,所以总的打拍寄存器为8*3=14bit,前面的为8*2=16bit
7、当task中含有参数的时候,参数需要在task中声明
task write_data(len); //需要在下面声明len
 integer i, len;
 begin
  for(i=0; i
  begin
   ...
  end
  cs_n = 1;
 end
endtask
8、三态门的使用和测试:
--源程序模块的三态门
reg    [2:0] cs_n_r, rd_n_r, wr_n_r;
inout tri  [15:0] data; //双向口数据线
assign data = (cs_n_r[2] == 1'b0 && rd_n_r[2] == 1'b0) ? rdata : 16'hzzzz;

--测试模块的三态门
reg      wr_n;
tri    [15:0] w_data;
assign w_data = (wr_n == 1'b0) ? data_tb : 16'hzzzz;

parall_interf parall_interf_inst( //例化
 .sclk (sclk ),
 .rst_n (rst_n  ),
 .cs_n (cs_n ),
 .rd_n (rd_n ), //读使能
 .wr_n (wr_n ), //写使能
 .addr (addr ), //地址总线  
 
 .data  (w_data )//双向口数据线--双向口w_data为输出的时候,data为输入;相反w_data为输入的时候,data为输入
);


//-------------parall_interf.v--源程序文件代码---------------------------------------------------------

module parall_interf(
 input  wire   sclk,
 input wire   rst_n,
 input wire    cs_n,
 input  wire    rd_n, //读使能
 input  wire    wr_n, //写使能
 input  wire  [7:0] addr, //地址总线  

 inout tri  [15:0] data //双向口数据线
);

//reg  [15:0]  data_buf [7:0]; //可这样定义--8个16bit的寄存器组,但是调用不如分别定义方便
reg   [15:0]   data_0,data_1,data_2,data_3,
      data_4,data_5,data_6,data_7;   
reg   [2:0]  cs_n_r, rd_n_r, wr_n_r;
reg   [47:0]  data_r;
reg   [23:0]  addr_r;

reg   [15:0]  rdata;

//为降低亚稳态出现的概率,把cs_n rd_n wr_n这几个单bit信号打3拍
always @ (posedge sclk or negedge rst_n)
 if(rst_n == 1'b0)
  {cs_n_r, rd_n_r, wr_n_r} <= 9'h1ff;
 else
  {cs_n_r, rd_n_r, wr_n_r} <= {{cs_n_r[1:0],cs_n}, {rd_n_r[1:0],rd_n}, {wr_n_r[1:0],wr_n}};

//数据也进行延时是为了与使能信号进行同步
always @ (posedge sclk or negedge rst_n)
 if(rst_n == 1'b0) begin
  data_r <= 48'd0;
  addr_r <= 24'd0;
 end
 else begin
  data_r <= {data_r[31:0],data}; //data为16bit,要打三拍,所以总的打拍寄存器为16*3=48bit,前面的为16*2=32bit
  addr_r <= {addr_r[15:0],addr}; //addr为8bit,要打三拍,所以总的打拍寄存器为8*3=14bit,前面的为8*2=16bit
 end

//写操作--把数据线上的数据存入相应的寄存器
always @ (posedge sclk or negedge rst_n)
 if(rst_n == 1'b0) begin
  data_0 <= 8'd0;
  data_1 <= 8'd0;
  data_2 <= 8'd0;
  data_3 <= 8'd0;
  data_4 <= 8'd0;
  data_5 <= 8'd0;
  data_6 <= 8'd0;
  data_7 <= 8'd0;
 end
 else if(cs_n_r[2] == 1'b0 && rd_n_r[2] == 1'b1 && wr_n_r[2] == 1'b0) begin
  case (addr_r[23:16])
   8'd0:data_0 <= data_r[47:32];
   8'd1:data_1 <= data_r[47:32];
   8'd2:data_2 <= data_r[47:32];
   8'd3:data_3 <= data_r[47:32];
   8'd4:data_4 <= data_r[47:32];
   8'd5:data_5 <= data_r[47:32];
   8'd6:data_6 <= data_r[47:32];
   8'd7:data_7 <= data_r[47:32];
   
   default : begin
    data_0 <= data_0;
    data_1 <= data_1;
    data_2 <= data_2;
    data_3 <= data_3;
    data_4 <= data_4;
    data_5 <= data_5;
    data_6 <= data_6;
    data_7 <= data_7;
   end
  endcase
 end

//读操作:来地址(cs_n拉低)把数据赋值给rdata,当rd_n为低的时候,将rdata赋值给data--看读操作时序图
always @ (posedge sclk or negedge rst_n)
 if(rst_n == 1'b0)
  rdata <= 8'd0;
 else if(cs_n_r[2] == 1'b0 && wr_n_r[2] == 1'b1)
 begin
  case(addr_r[23:16])
   8'd0:rdata <= data_0;
   8'd1:rdata <= data_1;
   8'd2:rdata <= data_2;
   8'd3:rdata <= data_3;
   8'd4:rdata <= data_4;
   8'd5:rdata <= data_5;
   8'd6:rdata <= data_6;
   8'd7:rdata <= data_7;
   default : rdata <= 16'd0;
  endcase
 end
  
//三态门--当rd_n为低的时候,将rdata赋值给data---读,此时三态门作为输出
assign data = (cs_n_r[2] == 1'b0 && rd_n_r[2] == 1'b0) ? rdata : 16'hzzzz;  //注意,()和{}是不同的,后面的位16'hzzzz,而不是16'd0

 


endmodule

 

//-------------------------------tb_parall_interf.v测试文件代码--------------------------------------------

`timescale 1ns/1ns  //不要忘了
module tb_parall_interf;

parameter   setup_time = 2;
parameter  hold_time = 2;
parameter  data_time = 4; //数据时间
parameter  read_wait = 5; //读数据等待延时

reg    sclk;
reg    rst_n;
reg    cs_n;
reg    rd_n;
reg    wr_n;
reg  [7:0]   addr;
reg  [15:0] data_tb;   

tri  [15:0] w_data; //inout端口的输出只能接wire变量或tri变量

always #10 sclk <= ~sclk;

//测试激励的三态门--FPGA内的三态门使用测试模块的三态门进行测试
assign w_data = (wr_n == 1'b0) ? data_tb : 16'hzzzz; //注意()和{}是有区别的

initial begin
 sclk = 0;
 rst_n = 0;
 #200;
 rst_n = 1;
end          

initial begin
 cs_n = 1;
 rd_n = 1;
 wr_n = 1;
 data_tb = 0;
 addr = 0;
 //@(posedge rst_n);
 #300;
 write_data(8);
 #100;
 read_data(8);
end

//写数据任务--模拟cpu向FPGA发送数据。tasi中嵌套tasi
task write_data(len); //需要在下面声明len
 integer i, len;
 begin
  for(i=0; i
  begin
   @ (posedge sclk);
   cs_n = 0;
   //rd_n = 1;//inout端口为输入口
   data_tb = i[15:0]; //写操作:cpu向FPGA写入地址+数据
   addr = i[7:0];
   setup_dly(); //task嵌套task
   wr_n = 0;
   data_dly();
   wr_n = 1;
   hold_dly();
   //cs_n = 1;//cpu向FPGA连续写数据的时候,cs_n可以不拉高,当写完所有数据之后再将其拉高
  end
  cs_n = 1;
 end
endtask

//读数据任务--模拟FPGA读取cpu
task read_data(len);
 integer i, len;
 begin
  for(i=0; i
  begin
   @ (posedge sclk);
   cs_n = 0;
   addr = i[7:0];
   read_dly(); //task嵌套task
   rd_n = 0;
   data_dly();
   $display("read data addr is %d = %d", i,w_data);
   rd_n = 1;
   //cs_n = 1; //cpu向FPGA连续写数据的时候,cs_n可以不拉高,当写完所有数据之后再将其拉高
  end
  cs_n = 1;
 end
endtask

//练习task使用--基本的延时任务
//延时产生延时setup_time
task setup_dly();
 integer i;
 begin
  for(i = 0; i < setup_time; i = i + 1)
  begin
   @ (posedge sclk);
  end
 end 
endtask

task hold_dly();
 integer i;
 begin
  for(i = 0; i < hold_time; i = i + 1'b1)
  begin
   @ (posedge sclk);
  end
 end
endtask

task read_dly();
 integer i;
 begin
  for(i = 0; i < read_wait; i = i + 1'b1)
  begin
   @ (posedge sclk);
  end
 end
endtask

task data_dly();
 integer i;
 begin
  for(i=0; i
  begin
   @(posedge sclk);
  end
 end
endtask

parall_interf parall_interf_inst(
 .sclk (sclk ),
 .rst_n (rst_n  ),
 .cs_n (cs_n ),
 .rd_n (rd_n ), //读使能
 .wr_n (wr_n ), //写使能
 .addr (addr ), //地址总线  
 
 .data  (w_data )//双向口数据线
);

endmodule

 

//-----------------run.do-文件代码---------------------------------------------------

quit -sim
.main clear

vlib work
vmap work work

vlog ./tb_parall_interf.v
vlog ./../design/parall_interf.v

vsim -voptargs=+acc work.tb_parall_interf

add wave -divider {inst}
add wave tb_parall_interf/parall_interf_inst/*
add wave -divider {tb}
add wave tb_parall_interf/*

run 10us

 

//----------------------相关截图-------------------------------------------

http://s4/mw690/006hC78Ezy77xMxIUUjf3&690

http://s1/mw690/006hC78Ezy77xMxOogwa0&690

http://s4/mw690/006hC78Ezy77xMxSdCXc3&690

http://s10/mw690/006hC78Ezy77xMxWMjf69&690

 

 

 

 

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有