STARTUPE2 在spi flash 上的使用
7 series fpga CCLK 使用
当我们使用spartan 7 fpga 时, 需要fpga 逻辑对spi flash 进行读写操作, 就会使用fpga 指定的pin。包括bank0 当中的CCLK, BANK14 中的 D[00], DIN/D[01], FCS_B, D[02],D[03] 等。
在之前的fpga 中(spartan 3, spartan 6), CCLK 启动时,有FPGA自主控制,产生时钟(不论fpga 是否被编程过)当从flash 中读取需要配置的数据后, CCLK pin 可以当中普通的gpio ,有用户编程使用。在7 series fpga 中, 由于CCLK 是特定的pin, 不能被用户当作普通的gpio 使用, 也不能设置约束文件。(例如xdc)
如图所示:

或者

但在很多开发板中, 我们即需要使用CCLK 用来启动fpga (从spi flash 中读取数据来配置fpga )又希望在fpga 启动后, 我们依然可以操作CCLK pin ,为用户自己定义的spi controller来对spi flash 进行读写。

在我们的prx100t开发板中,不但连接了7 series bank 0 当中的CCLK,同时又有一个gpio pin 和spi flash 相连。

这样,就可以避免使用STARTUPE2原语配置spi flash。原因, 启动时,fpga 自主从CCLK 输出时钟, 驱动spi flash。 在启动后, 如果用户需要使用spi flash ,可以驱动gpio_pin 来实现。(这时,CCLK 处于高组态)。
如果使用的其他的开发板,或者硬件设计没有预留gpio 连接spi flash,那么,我们必须是由STARTUPE2 原语控制CCLK。在阅读UG470 文档 (7 Series FPGA Configuration User Guide) 中有关 CCLK的使用时,发现CCLK 是Dedicated pin , 而不是Multi-function pin。 也就是说CCLK 不能被fpga 内部逻辑直接使用,也不能在xdc 文件中对CCLK 进行约束。需要使用STARTUPE2 这个原语来实现。即fpga 内部逻辑可以控制STARTUPE2 原语, 而STARTUPE2 原语可以驱动CCLK。

STARTUPE2 原语使用
在spartan 6 fpga 中CCLK 是一个multi-function pin 。 可以被fpga 内部逻辑直接驱动。在7 series fpga 中CCLK pin 是Dedicated pin,不能被fpga 直接驱动,所以需要使用STARTUPE2原语间接驱动CCLK。
通过分析STARTUPE2的原语使用,我们发现在系统启动后需要3个USRCCLKO 的时钟后,才能正常的使用CCLK

为此, 我们设计了一个简单的仿真程序,测试STARTUPE2 的相关功能。
STARTUPE2 仿真工程:
首先, 我们模拟一个基本的spi flash 的操作过程 , fpga 正常驱动spi_cs, spi_clk, spi_mosi 等。状态机 0 直接跳转到状态机6。 spi_clk 用来驱动STARTUPE2 primitive 中的 USRCCLKO pin。
工程代码: (vivado)
`timescale 1ns / 1ps
module TOP#
(
parameter SIM_CCLK_FREQ = 0.0
)
(
input OSC_20M,
// output FLASH_CLK,
output FLASH_CS,
output FLASH_MOSI,
input FLASH_MISO,
input reset
);
//=======================================================================================
wire FLASH_CLK;
STARTUPE2 #(
.PROG_USR("FALSE"), // Activate program event security feature. Requires encrypted bitstreams.
.SIM_CCLK_FREQ(SIM_CCLK_FREQ) // Set the Configuration Clock Frequency(ns) for simulation.
)
STARTUPE2_inst (
.CFGCLK(), // 1-bit output: Configuration main clock output
.CFGMCLK(), // 1-bit output: Configuration internal oscillator clock output
.EOS(), // 1-bit output: Active high output signal indicating the End Of Startup.
.PREQ(), // 1-bit output: PROGRAM request to fabric output
.CLK(0), // 1-bit input: User start-up clock input
.GSR(0), // 1-bit input: Global Set/Reset input (GSR cannot be used for the port name)
.GTS(0), // 1-bit input: Global 3-state input (GTS cannot be used for the port name)
.KEYCLEARB(1), // 1-bit input: Clear AES Decrypter Key input from Battery-Backed RAM (BBRAM)
.PACK(1), // 1-bit input: PROGRAM acknowledge input
.USRCCLKO(FLASH_CLK), // 1-bit input: User CCLK input
// For Zynq-7000 devices, this input must be tied to GND
.USRCCLKTS(0), // 1-bit input: User CCLK 3-state enable input
// For Zynq-7000 devices, this input must be tied to VCC
.USRDONEO(1), // 1-bit input: User DONE pin output control
.USRDONETS(1) // 1-bit input: User DONE 3-state enable output
);
//=======================================================================================
reg [7:0] spi_cmd = 8'h39; // test data
reg spi_clk = 0;
reg spi_mosi = 0;
reg spi_cs = 1;
wire spi_miso;
reg [4:0] spi_cnt = 0;
reg [3:0] spi_st = 0;
always @ (posedge OSC_20M or posedge reset)
if(reset)
begin
spi_cnt <= 0;
spi_cs <= 1;
spi_st <= 0;
end
else case (spi_st)
0:
begin
spi_cs <= 1;
spi_clk <= 0;
spi_st <= 1; // add 3 usrcclko for startupe2 primitive
// spi_st <= 6; // normal spi operation including drive spi_clk, spi_cs, spi_mosi
end
1: // add 3 usrcclko for startupe2 primitive
begin
spi_clk <= 1;
spi_st <= 2;
end
2:
begin
spi_clk <= 0;
spi_st <= 3;
end
3:
begin
spi_clk <= 1;
spi_st <= 4;
end
4:
begin
spi_clk <= 0;
spi_st <= 5;
end
5:
begin
spi_clk <= 1;
spi_st <= 6;
end
6: // siumlate a spi flash operation , drive spi_clk, spi_cs, spi_mosi
begin
spi_cs <= 1;
spi_clk <= 0;
spi_cnt <= spi_cnt + 1;
if(&spi_cnt)
begin
spi_cnt <= 7;
spi_st <= 7;
end
end
7:
begin
spi_cs <= 0;
spi_mosi <= spi_cmd[spi_cnt];
spi_st <= 8;
end
8:
begin
spi_clk <= 1;
if(spi_cnt == 0) spi_st <= 10;
else
begin
spi_cnt <= spi_cnt - 1;
spi_st <= 9;
end
end
9:
begin
spi_clk <= 0;
spi_mosi <= spi_cmd[spi_cnt];
spi_st <= 8;
end
10:
begin
spi_clk <= 0;
spi_st <= 11;
end
11:
begin
spi_cs <= 1;
spi_cnt <= 0;
spi_st <= 6;
end
default: spi_st <= 0;
endcase
//=======================================================================================
//=======================================================================================
assign FLASH_CLK = spi_clk;
assign FLASH_CS = spi_cs;
assign FLASH_MOSI = spi_mosi;
assign spi_miso = FLASH_MISO;
endmodule
仿真文件:
`timescale 1ns / 1ps
module tb_sim(
);
reg clk = 0;
always clk = #25 ~clk;
reg reset = 1;
initial
begin
reset = 1;
# 100;
reset = 0;
#10000;
$stop;
end
wire FLASH_CS;
wire FLASH_MOSI;
reg FLASH_MISO = 0;
TOP # (.SIM_CCLK_FREQ(10.0))
top_inst
(
.OSC_20M (clk),
.FLASH_CS (FLASH_CS),
.FLASH_MOSI (FLASH_MOSI),
.FLASH_MISO (FLASH_MISO),
.reset (reset)
);
//==================================================================
endmodule
仿真波形:

在第一次输出spi_clk 时, 我们发现在真实的CCLK 上缺少2 个时钟。
解决方案:
根据UG470 文档的介绍, 只要spi 使用前, 在USRCCLKO 上增加3个clk 后, USRCCLKO 就可以正常的输出到CCLK 上了。
修改状态机:
0:
begin
spi_cs <= 1;
spi_clk <= 0;
spi_st <= 1; // add 3 usrcclko for startupe2 primitive
// spi_st <= 6; // normal spi operation including drive spi_clk, spi_cs, spi_mosi
end
仿真波形:

结论:
如果使用STARTUPE2 来驱动CCLK pin , 就要在spi controller 使用之前, 增加3个USRCCLKO 时钟,之后, STARTUPE2 primitive 就可以正常使用了。
参考文档:
https://www.xilinx.com/content/dam/xilinx/support/documentation/user_guides/ug470_7Series_Config.pdf