小言_互联网的博客

fpga实操训练(ip ram和ip fifo)

313人阅读  评论(0)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

        所有的fpga ip当中,用的最多的ip一般有pll、rom、ram和fifo。前面,我们讨论过了rom,rom相比较ram和fifo而言,多一个mif文件的配置。而ram、fifo则不需要这样的操作。今天可以继续学习下ram和fifo,这样在后面的开发中就会显得游刃有余。

1、ip ram配置

a)创建ip ram的方法

        要创建ip ram,首先需要打开ip catalog,输入ram,选择双端口输入,

         接下来,系统会提示输入文件名,不妨命名为ip_ram.v。完成命名之后,就可以开始ip的配置了。这里面有几个重要的对话框需要注意下,首先是设置好bit宽度和word数量,

        确认是否共享时钟,

         确认是否需要晚一节拍输出数据,

         其他部分的页面采用默认的配置就可以了。

b)准备测试代码

        有了ip ram核之后,下面就是需要把它实例化,并且添加必要的测试代码。内容如下所示,


  
  1. module top(clk, rst);
  2. input clk;
  3. input rst;
  4. wire clk;
  5. wire rst;
  6. reg[ 4: 0] read_address;
  7. reg[ 4: 0] write_address;
  8. reg[ 7: 0] write_data;
  9. reg write_en;
  10. wire[ 7: 0] read_data;
  11. always@(posedge clk or negedge rst)
  12. if(!rst)
  13. read_address <= 5 'd0;
  14. else
  15. read_address <= read_address + 1 'b1;
  16. always@(posedge clk or negedge rst)
  17. if(!rst) begin
  18. write_en <= 1 'b0;
  19. write_address <= 5 'd0;
  20. write_data <= 8 'd0;
  21. end else begin
  22. if(write_address != 5 'd31) begin
  23. write_en <= 1 'b1;
  24. write_address <= write_address + 1 'b1;
  25. write_data <= write_data + 8 'd2 ;
  26. end else begin
  27. write_en <= 1 'b0;
  28. write_address <= 5 'd0;
  29. write_data <= 8 'd0;
  30. end
  31. end
  32. ip_ram ip_ram0(
  33. .clock(clk),
  34. .data(write_data),
  35. .rdaddress(read_address),
  36. .wraddress(write_address),
  37. .wren(write_en),
  38. .q(read_data)
  39. );
  40. endmodule

         测试的代码逻辑并不复杂。首先,在fpga复位之后,先对ram进行赋值操作。等所有的地址都写上数据之后,下面就是不停循环读出这些数据。为了验证这些数据是否真的被写入到ram空间,可以通过SignalTap的方法,通过jtag读一下这些数据,观察之前的写操作究竟有没有生效。

c)准备SignalTap,并且开始测试

        SignalTap的配置方法前面提过多次,这里直接给出配置的截图即可,

         经过输入F6和Esc之后,就可以看到截取的地址和数据了,

         前面编写测试代码的时候,写入的数据都是address*2。假设输入的地址是0,那么读取的数据应该是0;如果输入的地址是1,那么读取的数据应该是2,依次类推。通过截图,我们发现当read_address是0x12h的时候,q需要等一个时钟才能输出0x24h。这说明,ram和rom其实是一样的,两者都是等一个时钟才能输出数据的。

2、ip fifo配置

a)和ram一样,首先还是从ip catalog中寻找fifo,

        同样,这里需要用户自己输入文件名。不妨命名为ip_fifo.v,接着就可以开始配置。配置的内容很多,最关键的有这么几处。首先,输入write bit长度、read bit长度、总长度。这个非常有用,

         确定主要的输出信号,

         确认rdreq的用法,

         完成主要的这些操作之后,再加上自身的默认配置,就可以完成ip fifo的基本设定了。

b)准备测试代码


  
  1. module top(clk, rst);
  2. input clk;
  3. input rst;
  4. wire clk;
  5. wire rst;
  6. reg[ 7: 0] write_data;
  7. wire[ 31: 0] read_data;
  8. wire write_full;
  9. wire read_empty;
  10. reg write_req;
  11. reg read_req;
  12. // write state and next_write_state
  13. reg[ 1: 0] write_state;
  14. reg[ 1: 0] next_write_state;
  15. always@(posedge clk or negedge rst)
  16. if(!rst)
  17. write_state <= 2 'b00;
  18. else
  19. write_state <= next_write_state;
  20. always@(*)
  21. if(!rst)
  22. next_write_state <= 2 'b00;
  23. else begin
  24. case (write_state)
  25. 2 'b00:
  26. if(!write_full)
  27. next_write_state <= 2 'b01;
  28. else
  29. next_write_state <= 2 'b00;
  30. 2 'b01:
  31. if(write_full)
  32. next_write_state <= 2 'b00;
  33. else
  34. next_write_state <= 2 'b01;
  35. default:
  36. next_write_state <= 2 'b00;
  37. endcase
  38. end
  39. always @(posedge clk or negedge rst)
  40. if(!rst)
  41. write_req <= 1 'b0;
  42. else if(next_write_state == 2 'b01)
  43. write_req <= 1 'b1;
  44. else
  45. write_req <= 1 'b0;
  46. always @(posedge clk or negedge rst)
  47. if(!rst)
  48. write_data <= 8 'd0;
  49. else if(next_write_state == 2 'b01)
  50. write_data <= write_data + 1 'b1;
  51. // read_state and next_read_state
  52. reg[ 1: 0] read_state;
  53. reg[ 1: 0] next_read_state;
  54. always@(posedge clk or negedge rst)
  55. if(!rst)
  56. read_state <= 2 'b00;
  57. else
  58. read_state <= next_read_state;
  59. always@(*)
  60. if(!rst)
  61. next_read_state <= 2 'b00;
  62. else begin
  63. case (read_state)
  64. 2 'b00:
  65. if(!read_empty)
  66. next_read_state <= 2 'b01;
  67. else
  68. next_read_state <= 2 'b00;
  69. 2 'b01:
  70. if(read_empty)
  71. next_read_state <= 2 'b00;
  72. else
  73. next_read_state <= 2 'b01;
  74. default:
  75. next_read_state <= 2 'b00;
  76. endcase
  77. end
  78. always @(posedge clk or negedge rst)
  79. if(!rst)
  80. read_req <= 1 'b0;
  81. else if(next_read_state == 2 'b01)
  82. read_req <= 1 'b1;
  83. else
  84. read_req <= 1 'b0;
  85. // invoke ip ip_fifo
  86. wire[ 7: 0] rdusedw;
  87. wire[ 7: 0] wrusedw;
  88. ip_fifo ip_fifo0(
  89. . data(write_data),
  90. . rdclk(clk),
  91. . rdreq(read_req),
  92. . wrclk(clk),
  93. . wrreq(write_req),
  94. . q(read_data),
  95. . rdempty(read_empty),
  96. . rdusedw(rdusedw),
  97. . wrfull(write_full),
  98. . wrusedw(wrusedw));
  99. endmodule

         测试代码的内容不复杂,主要的工作就是把流程切分成write_state和read_state。对于write_state而言,一旦发现fifo不满,就开始写入数据。而对于read_state而言,每当发现fifo非空,就不停读取数据。这样,一写一读,就构成了fifo的基本操作。如果两者速率不匹配,或者字节长度不同,fifo还可以充当临时buffer的角色。当然,为了验证我们的测试是否正确,最终还得借助于SignalTap这个工具。

c)SignalTap配置和测试

        SignalTap中主要检查写入和读取的数据,

        烧入sof文件之后,经过F6和Esc之后,就可以看到相关的波形数据了,

 

补充1:

        之前测试的时候一直没有注意时序约束。事实上,在 fpga开发过程中最好做到0 error,0 warning。这是比较合适的。所以,项目中最好添加必要的时序约束文件,告知quartus软件当前fpga希望以什么样的时钟运行,


  
  1. # Clock constraints
  2. create_clock -name "clk" -period 20.000ns [ get_ports {clk}]
  3. # Automatically constrain PLL and other generated clocks
  4. derive_pll_clocks -create_base_clocks
  5. # Automatically calculate clock uncertainty to jitter and other effects.
  6. derive_clock_uncertainty
  7. # tsu/th constraints
  8. # tco constraints
  9. # tpd constraints

补充2:

        另外,如果引脚比较多,pin bind也是一个体力活。这部分可以借助于tcl脚本来完成。编写完tcl之后,就可以通过"Tools"->"Tcl Scripts"运行脚本即可,


  
  1. package require ::quartus::project
  2. set_location_assignment PIN_E1 - to clk
  3. set_location_assignment PIN_N13 - to rst
  4. set_location_assignment PIN_D9 - to led[ 3]
  5. set_location_assignment PIN_C9 - to led[ 2]
  6. set_location_assignment PIN_F9 - to led[ 1]
  7. set_location_assignment PIN_E10 - to led[ 0]


转载:https://blog.csdn.net/feixiaoxing/article/details/128487210
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场