前两篇教程利用数码管project介绍了chipscope和各种烧写开发板的方式,这篇开始继续探索开发板,这次关注外置存储器的控制,外置指的是芯片外部,不是开发板外部。板子上的外置存储器有DDR2和SPI flash,本次先写如何读写SPI flash。

FPGA基础入门【8】开发板外部存储器SPI flash访问

  • 开发板中的外置存储器
    • 存储器细节
      • 引脚配置
      • 指令集
      • 控制寄存器
      • 重要指令
  • 逻辑设计
    • 状态机配置
    • 测试读写
    • 代码设计
  • 模拟仿真
    • testbench
    • 库文件调用
    • 仿真脚本
    • 仿真结果
  • 编译实现

开发板中的外置存储器

开发板的具体介绍参考此链接:NEXYS 4 DDR Manual PDF

开发板上有两种外置存储器,一种是DDR2,另一种就是上一篇博文中用来保存mcs文件的SPI flash。DDR2已经比较过时了,只因为NEXYS4板本身就比较老,现在新的板子比较多的使用DDR4。SPI flash容量为16M,开发板的配置文件只需要4M以内,因此开发板实际上还可以利用剩下的近12M空间,由于SPI接口不需要调用IP,我们优先介绍SPI flash。

Nexys 4 DDR开发板比起Nexys 4开发板,将原本的Cellular RAM换成了Micron MT47H64M16HR-25:H DDR2存储器,将容量从16M提升到了128M。我们可以使用Vivado的MIG(Memory Interface Generator)来生成和DDR的接口,这一部分下次再写。

开发板上另外一个存储设备SPI flash序列号是S25FL128S,使用SPI接口和FPGA连接,接口如下

存储器细节

NEXYS 4 DDR开发板上的SPI flash使用的是S25FL128S芯片,它的文档可以在这里下载:S25FL128S Datasheet

引脚配置


从图中可以看出有几个引脚是复用的,这是教程第一次用到FPGA以外的芯片,做点详细的分析。

  • CS#是Chip Select (negative),低电平触发的芯片选择
  • SCK是Serial Clock,串口时钟;SI是Serial Input,串行输入,在双通道或者四通道模式中复用为IO0接口
  • SO是Serial Output,串行输出,在双通道或者四通道模式中复用为IO1接口
  • WP#是Write Protect (negative),低电平触发的写保护,触发时无法配置控制寄存器,在双通道或者四通道模式中复用为IO2接口
  • HOLD#是Hold,低电平触发后可以在不结束CS触发或不停止SCK的情况下暂停串行通信,在双通道或者四通道模式中复用为IO3接口
  • RESET#是低电平触发的复位

这篇教程追求简易实现,暂时不深究如何使用复用接口,只选择初始功能

指令集

不少芯片都用指令访问,具体指令配置要看文档。

指令集有几种不同的形式,不同的时序图,把文档中和指令时序图摘出几个:

  • 独立指令
  • 单字节输入指令
  • 单字节输出指令
  • 寻址单字节输出指令

观察这几种基本指令的规律,可以把它分为几部分

  • 指令instruction都是8位,必然在最先输出
  • 地址address可以是32位或者24位,有些指令不需要地址。理论上来说16MB只需要24位地址,但内部有头地址配置,可以用多枚芯片组成最高4GB,32位地址的存储空间。由于板子上只有一枚,这篇教程只使用24位地址
  • 数据data可以是写入flash的数据,也可以是从flash读出的数据,这个数据以字节为单位,可长可短

因此写指令状态机时需要配置的是:指令本身、是否有地址、地址、读还是写数据、读写数据长度(0表示不需要数据)。这在之后的Verilog code中要用

控制寄存器

S25FL128S的寄存器如下

第一个最重要,其他的看看就好。第一个SR1定义如下

  • 第7位是状态寄存器写锁定,1表示在WP#引脚拉低时将状态寄存器锁定,无法被修改,0表示不锁定
  • 第6位是烧写错误位,1表示烧写有错误
  • 第5位是擦除错误位,1表示擦除有错误
  • 第4到2位是块保护,1表示将相应存储块锁定,不可擦除
  • 第1位是写使能位,只有将这位写到1才能进行烧写擦除操作,否则会被保护,指令写使能WREN和写去能WRDI可以修改这一位
  • 第0位是忙碌位,1表示设备正忙,忽略新进来的指令(除了读取状态,和写暂停等少数指令),0表示设备准备好执行下一个指令

重要指令

  1. 读取芯片型号READ_ID,指令0x90h,地址24’h000000,读取两个byte,分别是厂商ID和型号ID,S25FL128S的厂商ID为01h,型号ID为17h
  2. 读取芯片信息RDID,指令0x9Fh,不需要地址,读取81个byte
  3. 读取状态寄存器1 RDSR1,指令0x05h,不需要地址,可以持续读取,不固定读取长度
  4. 写使能WREN,指令0x06h,无需地址,将写入使能寄存器拉高
  5. 擦除一个sector SE,指令0x20h,需要24位地址,在拉高CS后会将该地址所在的sector都设置成高电平,一个sector有64kB,该命令需要写使能WREN后才有效
  6. 读取数据READ,指令0x03h,需要24位地址,返回第一个byte是相应地址的数据,在拉高停止CS或者停止时钟SCK之前,SO会不停的输出下一个地址的数据,直到最高地址,然后再回到地址0循环往复
  7. 写入数据Page Program(PP),指令0x02h,需要24位地址,之后跟上一个page的数据,在这里是512byte。如果地址最低9位位非0,则在一个page写完后自动写到下一个page,这个命令只有在写使能WREN之后才有效。如果输入的数据不到512byte,则只从起始地址开始输入相应数据。PP命令只能将相应位置的数据从1写成0,而不能将0拉高成1,因此要写入新数据应该先调用擦除命令,比如SE

逻辑设计

状态机配置

状态机,又称作有限状态机finite-state machine (FSM),是FPGA逻辑设计中常见的结构,相比起一般的数字逻辑,它可以比较容易的在FPGA中实现类似软件逻辑。

可以看出SPI flash的配置更偏向软件逻辑,根据不同的instruction依次配置,具体设计如下

测试读写

伪代码设计大致如下:

READ_ID
RDID
RDSR1
RDCR
READ 0x800000 32
WREN
RDSR1
SE 0x800000
RDSR1
READ 0x800000 32
WREN
PP 0x800000 0x5a5a5a5a....
while(RDSR1 return busy) {RDSR1}
READ 0x800000 32

代码设计

module flash(input       clk,input       rst,output reg  led, // used to debug// Ports for SPI Flashoutput reg  cs_n,input       sdi,output reg  sdo,output      wp_n,output      hld_n
);assign wp_n  = 1'b1;
assign hld_n = 1'b1;

顶层设计中,通用部分依旧是时钟、复位和LED测试输出;其他引脚和SPI flash相连

你可能注意到应该如下所示有6个引脚,但我们只写了5个。原因是SCK引脚所在的E9引脚是系统占用的,因为SPI flash可以作为FPGA初始化配置的来源,具体做法可以参考我的上一篇教程

要使用E9引脚,则需要使用一个Xilinx提供的特殊IP STARTUPE2。它能在FPGA初始化完成之后把这个引脚的控制权交给你,具体用法之后会有

parameter IDLE       = 4'b0000;
parameter START      = 4'b0001;
parameter INST_OUT   = 4'b0010;
parameter ADDR1_OUT  = 4'b0011;
parameter ADDR2_OUT  = 4'b0100;
parameter ADDR3_OUT  = 4'b0101;
parameter WRITE_DATA = 4'b0110;
parameter READ_DATA  = 4'b0111;
parameter ENDING     = 4'b1000;reg         sck;
reg  [3:0]  state;
reg  [3:0]  next_state;
(* dont_touch = "true" *)reg  [7:0]   instruction;
(* dont_touch = "true" *)reg  [7:0]   datain_shift;
(* dont_touch = "true" *)reg  [7:0]   datain;
reg  [7:0]  dataout;
reg         sck_en;
reg  [2:0]  sck_en_d;
reg  [2:0]  cs_n_d;(* dont_touch = "true" *)reg  [7:0]  inst_count;
reg         temp;
reg  [3:0]  sdo_count;
reg  [15:0] page_count;
reg  [7:0]  wait_count;
reg  [23:0] addr;
reg         wrh_rdl;  // High indicates write, low indicates read
reg         addr_req;  // Address writing requested
reg  [15:0] wr_cnt;  // Number of bytes to be written
reg  [15:0] rd_cnt;  // Number of bytes to be read

资源定义,parameter是给状态机用的名称。

其中的(* dont_touch = “true” *)是Xilinx定义的资源特质Attribute,在寄存器register定义前加上这个后,编译器就不会把它重新配置来优化布局,这么做是为了方便ChipScope找到相应的寄存器。如果最后逻辑正确,可以将这个Attribute去掉重新编译,对逻辑本身没有影响,仿真也会忽视它。

// State machine
always @(posedge clk or posedge rst) beginif(rst) beginstate <= IDLE;endelse beginstate <= next_state;end
endalways @(posedge clk or posedge rst) beginif(rst) beginnext_state  <= IDLE;sck_en      <= 1'b0;cs_n_d[0]   <= 1'b1;dataout     <= 8'd0;sdo_count   <= 4'd0;sdo         <= 1'b0;datain      <= 8'd0;inst_count  <= 8'd0;temp        <= 1'b0;page_count  <= 16'd0;wait_count  <= 8'd0;endelse begincase(state)IDLE: begin   // IDLE statenext_state <= START;wait_count <= 8'd0;endSTART:begin // enable SCK and CSsck_en <= 1'b1;cs_n_d[0]  <= 1'b0;next_state <= INST_OUT;endINST_OUT:begin    // send out instructionif(sdo_count == 4'd1) begin{sdo, dataout[6:0]} <= instruction;endelse if(sdo_count[0]) begin{sdo, dataout[6:0]} <= {dataout[6:0],1'b0};endif(sdo_count != 4'd15) beginsdo_count <= sdo_count + 4'd1;endelse beginsdo_count  <= 4'd0;next_state <= (addr_req) ?  ADDR1_OUT : ((wrh_rdl) ? ((wr_cnt==16'd0) ? ENDING : WRITE_DATA) : ((rd_cnt==16'd0) ? ENDING : READ_DATA));endendADDR1_OUT:begin  // send out address[23:16]if(sdo_count == 4'd1) begin{sdo, dataout[6:0]} <= addr[23:16];endelse if(sdo_count[0]) begin{sdo, dataout[6:0]} <= {dataout[6:0],1'b0};endif(sdo_count != 4'd15) beginsdo_count <= sdo_count + 4'd1;endelse beginsdo_count  <= 4'd0;next_state <= ADDR2_OUT;endendADDR2_OUT:begin    // send out address[15:8]if(sdo_count == 4'd1) begin{sdo, dataout[6:0]} <= addr[15:8];endelse if(sdo_count[0]) begin{sdo, dataout[6:0]} <= {dataout[6:0],1'b0};endif(sdo_count != 4'd15) beginsdo_count <= sdo_count + 4'd1;endelse beginsdo_count  <= 4'd0;next_state <= ADDR3_OUT;endendADDR3_OUT:begin  // send out address[7:0]if(sdo_count == 4'd1) begin{sdo, dataout[6:0]} <= addr[7:0];endelse if(sdo_count[0]) begin{sdo, dataout[6:0]} <= {dataout[6:0],1'b0};endif(sdo_count != 4'd15) beginsdo_count <= sdo_count + 4'd1;endelse beginsdo_count  <= 4'd0;next_state <= (wrh_rdl) ? ((wr_cnt==16'd0) ? ENDING : WRITE_DATA) : ((rd_cnt==16'd0) ? ENDING : READ_DATA);page_count <= 16'd0;endendWRITE_DATA:begin // send testing data out to flashif(sdo_count == 4'd1) begin{sdo, dataout[6:0]} <= 8'h5A;endelse if(sdo_count[0]) begin{sdo, dataout[6:0]} <= {dataout[6:0],1'b0};endif(sdo_count != 4'd15) beginsdo_count <= sdo_count + 4'd1;endelse beginpage_count <= page_count + 16'd1;sdo_count  <= 4'd0;next_state <= (page_count < (wr_cnt-16'd1)) ? WRITE_DATA : ENDING;endendREAD_DATA:begin // get the first data from flashif(~sdo_count[0]) begindatain_shift <= {datain_shift[6:0],sdi};endif(sdo_count == 4'd1) begindatain <= {datain_shift, sdi};endif(sdo_count != 4'd15) beginsdo_count <= sdo_count + 4'd1;endelse beginpage_count <= page_count + 16'd1;sdo_count  <= 4'd0;next_state <= (page_count < (rd_cnt-16'd1)) ? READ_DATA : ENDING;endendENDING:begin  //disable SCK and CS, wait for 32 clock cyclesif(wait_count != 8'd64) beginwait_count <= wait_count + 8'd1;next_state <= ENDING;endelse beginif(instruction == 8'h05 && datain[0]) begin // If in RDSR1, wait until the process ended{inst_count,temp} <= {inst_count,temp};endelse begin{inst_count,temp} <= {inst_count,temp} + 9'd1;endnext_state <= IDLE;endsck_en <= 1'b0;cs_n_d[0] <= 1'b1;sdo_count <= 4'd0;page_count <= 16'd0;endendcaseend
end

SPI接口逻辑部分,就像前面画的逻辑图一样。在READ_DATA阶段每一个byte的数据进入寄存器datain。

最后在ENDING部分,如果判断是RDSR1读取状态寄存器指令,则会自动识别最后一位数据,判断系统是否正在忙着处理其他指令,等到系统忙完再执行下一个指令,否则就停在RDSR1指令

// SCK generator, 50MHz output
always @(posedge clk) beginsck_en_d <= {sck_en_d[1:0],sck_en};
endalways @(posedge clk or posedge rst) beginif(rst) beginsck <= 1'b0;endelse if(sck_en_d[2] & sck_en) beginsck <= ~sck;endelse beginsck <= 1'b0;end
endalways @(posedge clk or posedge rst) beginif(rst) begin{cs_n,cs_n_d[2:1]} <= 3'h7;endelse begin{cs_n,cs_n_d[2:1]} <= cs_n_d;end
endSTARTUPE2
#(
.PROG_USR("FALSE"),
.SIM_CCLK_FREQ(10.0)
)
STARTUPE2_inst
(.CFGCLK     (),.CFGMCLK    (),.EOS        (),.PREQ       (),.CLK        (1'b0),.GSR        (1'b0),.GTS        (1'b0),.KEYCLEARB  (1'b0),.PACK       (1'b0),.USRCCLKO   (sck),      // First three cycles after config ignored, see AR# 52626.USRCCLKTS  (1'b0),     // 0 to enable CCLK output.USRDONEO   (1'b1),     // Shouldn't matter if tristate is high, but generates a warning if tied low..USRDONETS  (1'b1)      // 1 to tristate DONE output
);

这部分在处理输出时钟信号sck和片选信号cs_n。片选信号比较简单,只要延迟两个时钟后,在需要的时候输出0即可。

对于时钟输出sck,如果仔细阅读文档,可以发现每个指令都有时钟频率限制:

从这里看出只要保证SCK在50MHz及以下就可以让所有指令顺利运行。而查阅NEXYS4的文档,板载时钟的频率是100MHz,因此我们只需要将其二分。加上一个时钟使能信号sck_en,让它只在需要的时候输出即可。

记得前面说过,sck引脚是和芯片初始化共用的,为了能在芯片初始化完成后使用这个引脚,NEXYS4的文档告诉我们使用Xilinx提供的IP STARTUPE2。这个IP的介绍在Xilinx的文档UG953 Vivado 7series libraries的第421页可以找到:


它的简介和引脚介绍告诉我们,这个IP是用来访问特定的引脚的。从网上抄了一个最简单的配置方法,只要把sck连到USRCCLKO上,其他配置连到固定值上即可。

// ROM for instructions
always @(posedge clk) begincase(inst_count)8'd0 : begin instruction <= 8'h90; wrh_rdl <= 1'b0; addr_req <= 1'b1; addr <= 24'h000000; wr_cnt <= 16'd0; rd_cnt <= 16'd2; end  // READ_ID8'd1 : begin instruction <= 8'h9F; wrh_rdl <= 1'b0; addr_req <= 1'b0; addr <= 24'h000000; wr_cnt <= 16'd0; rd_cnt <= 16'd81; end  // RDID8'd2 : begin instruction <= 8'h05; wrh_rdl <= 1'b0; addr_req <= 1'b0; addr <= 24'h000000; wr_cnt <= 16'd0; rd_cnt <= 16'd2; end  // RDSR18'd3 : begin instruction <= 8'h35; wrh_rdl <= 1'b0; addr_req <= 1'b0; addr <= 24'h000000; wr_cnt <= 16'd0; rd_cnt <= 16'd2; end  // RDCR8'd4 : begin instruction <= 8'h03; wrh_rdl <= 1'b0; addr_req <= 1'b1; addr <= 24'h800000; wr_cnt <= 16'd0; rd_cnt <= 16'd32; end  // READ8'd5 : begin instruction <= 8'h06; wrh_rdl <= 1'b0; addr_req <= 1'b0; addr <= 24'h000000; wr_cnt <= 16'd0; rd_cnt <= 16'd0; end  // WREN8'd6 : begin instruction <= 8'h05; wrh_rdl <= 1'b0; addr_req <= 1'b0; addr <= 24'h000000; wr_cnt <= 16'd0; rd_cnt <= 16'd2; end  // RDSR18'd7 : begin instruction <= 8'hd8; wrh_rdl <= 1'b0; addr_req <= 1'b1; addr <= 24'h800000; wr_cnt <= 16'd0; rd_cnt <= 16'd64; end  // SE8'd8 : begin instruction <= 8'h05; wrh_rdl <= 1'b0; addr_req <= 1'b0; addr <= 24'h000000; wr_cnt <= 16'd0; rd_cnt <= 16'd2; end  // RDSR18'd9 : begin instruction <= 8'h03; wrh_rdl <= 1'b0; addr_req <= 1'b1; addr <= 24'h800000; wr_cnt <= 16'd0; rd_cnt <= 16'd32; end  // READ8'd10: begin instruction <= 8'h06; wrh_rdl <= 1'b0; addr_req <= 1'b0; addr <= 24'h000000; wr_cnt <= 16'd0; rd_cnt <= 16'd0; end  // WREN8'd11: begin instruction <= 8'h02; wrh_rdl <= 1'b1; addr_req <= 1'b1; addr <= 24'h800000; wr_cnt <= 16'd32; rd_cnt <= 16'd0; end  // PP8'd12: begin instruction <= 8'h05; wrh_rdl <= 1'b0; addr_req <= 1'b0; addr <= 24'h000000; wr_cnt <= 16'd0; rd_cnt <= 16'd2; end  // RDSR18'd13: begin instruction <= 8'h03; wrh_rdl <= 1'b0; addr_req <= 1'b1; addr <= 24'h800000; wr_cnt <= 16'd0; rd_cnt <= 16'd32; end  // READdefault : begin instruction <= 8'h05; wrh_rdl <= 1'b0; addr_req <= 1'b0; addr <= 24'h000000; wr_cnt <= 16'd0; rd_cnt <= 16'd2; end  // RDSR1endcase
end// Debug LED port
always @(posedge clk or posedge rst) beginif(rst) beginled <= 1'b0;endelse if(instruction == 8'h03) beginled <= (datain == 8'h5a) ? 1'b1 : 1'b0;end
endendmodule

伪代码以ROM的形式存在,现在还只是测试flash,不打算做出一个可以在线控制的模块。在指令流程中定义每一个指令的读写模式,是否需要地址,地址值,读长度和写长度。

有些实际不需要读取数据的指令定义了读长度,因为想让它延迟几个时钟。

最后在读取指令(0x03h)时,如果读到自己写入的数据0x5a,则LED输出为高。看到LED亮起就意味着这个测试完成了。

模拟仿真

testbench

仿真testbench tb_flash.v设计如下:

`timescale 1ns/1nsmodule tb_flash;reg clock;
reg reset;
wire led;
wire cs_n;
reg sdi;
wire sdo;
wire wp_n;
wire hld_n;initial beginclock = 1'b0;reset = 1'b0;sdi = 1'b0;// Reset for 1us#100 reset = 1'b1;#1000reset = 1'b0;
end// Generate 50MHz clock signal
always #10 clock <= ~clock;always @(posedge flash.sck) beginsdi <= ~sdi;
endflash flash(.clk(clock),.rst(reset),.led(led),.cs_n(cs_n),.sdi(sdi),.sdo(sdo),.wp_n(wp_n),.hld_n(hld_n)
);endmodule

在这里不对sdi输入做特殊处理,只是随便输入一个信号,只观察SPI接口操作是否正常。

实际上网上可以下载到S25FL128S的仿真模块,可以进行更接近真实的仿真,但图了个方便没做。

库文件调用

在上面说到SCK端口的位置不是通用IO口,而是FPGA初始配置时会使用的引脚,为了在FPGA初始配置完成之后使用这个引脚,要调用Xilinx的基础模块STARTUPE2。为了使仿真可以进行,需要调用Xilinx的library。实现方式如下:

打开Vivado,无需打开任何project,选择Tools -> Compile Simulation Libraries

如图选择,选择编译好的library存储路径Compiled library location到自己想要的位置,之后选择自己的ModelSim执行文件路径。下方生成相应的命令:

compile_simlib -simulator modelsim -simulator_exec_path {C:/intelFPGA_pro/18.1/modelsim_ase/win32aloem} -family all -language verilog -library unisim -dir {C:/Users/[account]/xilinx_lib} -force

这个命令的含义是调用相应的ModelSim,将Xilinx的code编译到unisim这个library里,如此ModelSim就可以调用它。理解这个操作后,可以尝试另一种做法,那就是找到xilinx相应代码的位置,然后一起编译。

如果是用默认C盘路径安装的Xilinx Vivado,那么你可以在这个路径找到相应的代码:

C:\Xilinx\Vivado\2018.3\data\verilog\src\unisims\STARTUPE2.v

另外,在使用Xilinx的IP时,都需要编译一个叫做glbl.v的文件用来初始化系统引脚。这个文件可以在安装路径找到,然后拷贝到仿真的根目录中:

C:\Xilinx\Vivado\2018.3\data\verilog\src\glbl.v

仿真脚本

写个脚本sim.do放在仿真根目录方便调试:

vlib work
vlog ../src/flash.v ./tb_flash.v ./glbl.v
vsim -L xpm -L secureip -L unisims_ver -L unimacro_ver -L unifast_ver -L simprims_ver work.tb_flash work.glbl -voptargs=+acc +notimingchecks
log -depth 7 /tb_flash/*
do wave.do
run 1ms

查阅ModelSim的指令集可以看到

  • vlib是创建library
  • vlog是编译
  • vsim是仿真,-L是调用库,这里调用的都是Xilinx基础库文件,-voptargs是定义信号检查的量,如果不定义这些,它只会显示少量的信号
  • wave.do在保存好的波形脚本,在第一次仿真时保存一个,之后就能自动帮你囊括想要的信号,以及想要的形式,方便重复调试
  • run 1ms开始跑,以1ms为时长

为了让ModelSim能找到你在上一步编译好的Xilinx库文件,需要在使用仿真脚本前执行下面的命令。具体路径要参考你把Xilinx的库编译到哪:

vmap xpm C:/Users/[account]/xilinx_lib/xpm
vmap secureip C:/Users/[account]/xilinx_lib/secureip
vmap unisims_ver C:/Users/[account]/xilinx_lib/unisims_ver
vmap unimacro_ver C:/Users/[account]/xilinx_lib/unimacro_ver
vmap unifast_ver C:/Users/[account]/xilinx_lib/unifast_ver
vmap simprims_ver C:/Users/[account]/xilinx_lib/simprims_ver

仿真结果


仿真波形里可以看出一步一步执行指令

编译实现

新建一个叫flash的project,初始配置可以参考之前的教程。添加代码文件flash.v。

下一步加入约束constraint文件flash.xdc,同样这是用标准模板取自己需要部分修改出来的(NEXYS 4 DDR Master XDC):

## This file is a general .xdc for the Nexys4 DDR Rev. C
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project## Clock signal
set_property -dict {PACKAGE_PIN E3 IOSTANDARD LVCMOS33} [get_ports clk]
create_clock -period 10.000 -name sys_clk_pin -waveform {0.000 5.000} -add [get_ports clk]##Switchesset_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports rst]## LEDsset_property -dict {PACKAGE_PIN H17 IOSTANDARD LVCMOS33} [get_ports led]##Quad SPI Flashset_property -dict {PACKAGE_PIN K17 IOSTANDARD LVCMOS33} [get_ports sdo]
set_property -dict {PACKAGE_PIN K18 IOSTANDARD LVCMOS33} [get_ports sdi]
set_property -dict {PACKAGE_PIN L14 IOSTANDARD LVCMOS33} [get_ports wp_n]
set_property -dict {PACKAGE_PIN M14 IOSTANDARD LVCMOS33} [get_ports hld_n]
set_property -dict {PACKAGE_PIN L13 IOSTANDARD LVCMOS33} [get_ports cs_n]

到这里可以点击 Run Synthesis做综合,几秒钟完成后用Set Up Debug配置ChipScope:

设置观察长度为8192,因为持续时间会比较长。下面就可以Run Implementation和Generate Bitstream生成配置文件了。

和前面的教程一样,USB线连接NEXYS4板子,开启Hardware Manager,然后auto连接上板子,Program Device烧写进程序,注意Debug probes file有对应的ltx文件,完成后应该看到最低位有绿色LED亮起:

观察ChipScope可以获得更清晰的流程。观察的方式是先设置trigger,最好的trigger是inst_count,配置在其为0x01h时开始捕获,并且在Settings -> Capture Mode Settings -> Trigger position in window中设置为256(或者相对于8192较小的数字),这样捕获出来的波形大部分是在trigger之后的。先把板子上最低位开关拨到0进入reset,再在ChipScope中点击Run,最后拨回开关,这样可以获得Reset之后的波形,如果有什么问题,应该第一个周期就出现了,人工是很难捕捉到的:

第一个READ_ID指令是判断连接是否正确的,从波形图中看出sdo正确输出了0x90h指令和24位地址0x000000h,然后从sdi中
读回的数据是0x17h,是S25FL128S文档中显示的128M芯片传回的正确数据



接下来的指令一切正常,RDID读回81个byte的芯片数据,RDSR1在WREN后变成0x02,SE擦除了地址0x800000h所在sector的所有数据到1,PP又将制定的数据0x5a写入了flash中,不停读取RDSR1直到烧写完成后再读出,确认数据正确最终将LED点亮

FPGA基础入门【8】开发板外部存储器SPI flash访问相关推荐

  1. ADSP-SC589开发板更换SPI FLASH并从flash引导内核与文件系统

    前情提要:目前本人正在用ADSP-SC589开发板做开发,希望实现内核与文件系统都从SPI FLASH启动,但板子上原来的SPI FLASH太小了,于是更换了镁光的MT25QU01GBBB FLASH ...

  2. FPGA基础入门【12】开发板USB鼠标控制

    上一篇教程介绍了NEXYS4 开发板中UART串口通信的使用方式,这一篇介绍USB接口接收鼠标和键盘信号 FPGA基础入门[12]开发板USB鼠标控制 开发板USB芯片 信号时序图 鼠标初始化 逻辑设 ...

  3. FPGA基础入门【1】Vivado官方免费版安装

    本人自本科大二开始接触FPGA相关知识,现已将近五年,从这篇开始将从比较基础的角度讲述如何一步步了解FPGA.我相信动手一步步做下去是从零开始学习知识的最快方法,因此不会从最基础开始讲,而是在碰到相应 ...

  4. FPGA基础入门【3】Blink逻辑及仿真

    从这一篇开始正式介绍FPGA中的硬件逻辑,第一个目标就是从零开始在NEXYS 4开发板上实现闪烁LED. 软件编程中hello world是初学语言中实现的第一个功能,而硬件编程中blink是同等的地 ...

  5. FPGA基础入门【6】ChipScope的使用

    当FPGA设计中复杂度慢慢变高的时候,仿真的手段也要增加,目前我们仿真的手段都是在ModelSim中配置相应的testbench,给模块发送需要的信号.这种软件仿真的方式有几个缺点: 一个是软件仿真速 ...

  6. FPGA基础入门篇(四) 边沿检测电路

    FPGA基础入门篇(四)--边沿检测电路 一.边沿检测 边沿检测,就是检测输入信号,或者FPGA内部逻辑信号的跳变,即上升沿或者下降沿的检测.在检测到所需要的边沿后产生一个高电平的脉冲.这在FPGA电 ...

  7. 【视频回放与课件】零基础入门AI开发

    今天上午,受广州图书馆邀请,在第一讲<零代码上手人工智能>的基础上,以<零基础入门AI开发>为主题,分四步解锁人工智能学习的概念与开发工具,让您在一小时内轻松掌握人工智能开发要 ...

  8. 零基础学习MSP430F552LP开发板,学习前期准备,Code Composer Studio(CCS)软件的安装

    零基础学习MSP430F552LP开发板 一.前言 零基础学习MSP430F552LP开发板,为电子设计竞赛做准备以及学好这一款芯片. 在选择比赛题目时,发现有的题目时规定使用ti的芯片作为控制MCU ...

  9. Linux基础入门--驱动开发--USB

    Linux基础入门--驱动开发--USB 1.基本概念 2.组成结构 2.1 设备描述符 2.2 配置描述符 2.3 接口描述符 2.4 端点描述符 2.5 字符串描述符 3.管道 4.端点分类 4. ...

最新文章

  1. mysql 表字段大小写敏感_各种数据库大小写敏感问题总结
  2. linux查看节点使用进程后退出,Linux通过端口号查看使用进程-结束进程
  3. 在vim粘贴系统剪切板里的内容
  4. 现代软件工程讲义 6 用户调研
  5. JAVA四种遍历Map的方法
  6. rabbitmq topic 收不到数据_RabbitMQ和Kafka到底怎么选?
  7. 小米笔记本安装Win 10历程
  8. pdg file的打开
  9. Android 开发 修改app应用的名字和图标
  10. java jersey,java Jersey
  11. 2018 苹果开发者账号注册、付款流程图解
  12. unison 安装使用
  13. 这片“农场”被他们承包了|第一届中国农业人工智能创新创业大赛初赛回顾
  14. mysql identity属性_Mysql中Identity 详细介绍
  15. 使用selenium自动化操作浏览器
  16. QT5.6 安装 过程,实践经历……
  17. 分类和标注词汇(基于nltk)
  18. 深度学习图像标注软件LabelMe使用方法
  19. Grafana资料搜集
  20. 【操作系统概念-作业1】Introduction

热门文章

  1. 【面向对象课程项目:纸牌】Java实例学习(一):优秀源码的分析
  2. 轻轻松松学习SpringBoot2:第二十六篇: Spring Boot和Redis整合(完整版)
  3. cobol - 字符串操作
  4. c#中while(true)死循环阻塞进程,应该怎么办
  5. 台湾地区信用卡客户贷款违约预测
  6. 5 款最好用的图片无损压缩工具
  7. 怎样从别人的原理图中提取原理图库文件(part)?
  8. McObject的eXtremeDB凭“杰出的结构化数据库”荣获业界倍受推崇的大数据卓越奖
  9. quartz 每月一次_quartz cron表达式 启动时先执行一次 以后每个月执行一次
  10. 一个好的网站设计如何影响内容营销