最近太热了,实在无心看书。阵列书丢一边看不进去,还买了几本统计信号的甚至都没开始看(笑),躺在床上玩玩手机摆烂,看到某黄色APP上有老板卖拆机的板子,价格美丽,美中不足的是没有资料。大致跟老板确认了一下板子成色、来源就拍下来了,昨天正好到手里,今天寻思看看有没有可以折腾的可能。由于缺少相关资料,别说原理图了,连个管脚约束都没有,老老实实跑个逆向吧。
其实买的时候很担心这个芯片是带锁的,但是我几乎没用听过有谁给Cyclone iv的芯片写fuse,这里小赌一把,焊了个牛角座浅试一把发现可以正常使用,赌博成功。由于我没有大功率的低压电源,这里从另一个FPGA板子上取电,测试发现FPGA功耗非常高,猜测应该把Transceiver全部用起来了,估计资源也起来了大半。
板载资源介绍
板子正面的主要器件已经框出来了。
红色框的是FPGA芯片,intel家里的,型号是EP4CGX110DF27I7。这个芯片资源可以,有109k个LE,5Mbit的memory bits,带8个锁相环。这个芯片带了8对收发器可以完成一些高速设计,不过我暂时应该用不到。其实这几年A家的芯片用的比较多,资料相当完善,但是I家的似乎没有那么丰富。
橙色框的是美光的DDR SDRAM,这是一个第二代SDRAM,2.5V电源供电。而绿色框的芯片是LP2998,一个比较常见的VTT芯片,这个VTT芯片也可以为DDR3提供VTT电源。
一般来说VTT芯片需要比较快速的瞬态响应以满足数据传输中电荷的Charge/Discharge,关于DDR的端接技术和相关设计可能以后会写点博文吧。目前我用的比较多的型号是TPS51200,这个VTT芯片在很多设计中都能看到。
这张图是板上的46V32M16的内部框图,这是一个512Mbit的芯片,有4个bank,每个bank是8192x512的,对应行地址线是13条,列地址线10条(COL0是实现双边沿的),这个非常关键,后面使用DDR控制器的时候需要设置。这个板子上一共只有1个DDR非常遗憾,如果多点就好了。这个板子做做简单的图像处理,跑跑RISCV还是不成问题的。话说micron的手册写的真非常好,图也好看…在Cyclone iv FPGA中,只有位于TOP和BOTTOM BANK的DQ可以跑到150兆,用右侧的BANK只能跑到133兆,好在这个板子的DDR挂在BOTTOM。
黄色框是一个LDO,这个LDO为FPGA内部模拟部分(PLL)供电,他的PSRR比较高可以提供比较干净的供电。蓝色框是一个128Bit的NOR FLASH,等效于EPCQ128,用于固化设计。草绿色的芯片是一个看门狗加上一个EEPROM,这个芯片可以提供上电复位和电源监控功能。
逆向
观察板子发现,这个板子一共有4个连接器,每个连接器都是50x2P的,也就是拉了400个脚出来,全部测一遍工作量实在有些大。不过观察板子设计不难发现,设计者引出的全部都是差分对,如果我们一次能测得4个差分对,那么每跑一次就能得到8个pin。这里我采用的方法是让每个差分对的n输出一个特定频率的信号,利用频率分辨测得的4个管脚,这里手头刚还有一个之前做的选频表,这个东西本质是利用LTC1043制作的锁相放大器,刚好派上用场了。
不得不说这个板子设计的还是比较到位的,一共8层,应该用的是S-G-S-V-G-V-S-G-S层叠,没用用特殊工艺(盲孔,盘中孔之类的)。FPGA的去耦相当到位用的0402,小体积的电容有更小的ESL,谐振点更高,能提供更好的去耦效果。这里去耦电容的大小一般设置有2种方法,第一种是经验法,为20~50倍的Cpd,这Cpd是器件的等效电容(含负载),第二种是PDN目标阻抗匹配法,需要使用电源完整性仿真工具做优化。bulk电容考虑到几乎要将全部管脚都扇出,所以放置距离较远。右侧是DDR,VTT的bulk太豪华了,有点过度设计,去耦同样也比较到位,
这里有一个问题是如何确定时钟脚,观察晶振的位置在芯片上侧,凑巧的是在TOP SIDE只有唯一的两个时钟脚,于是成功迈出第一步。这里GXB_RX是瞎猜的,因为没用手段去测试它,只能大致确定差分对的位置,不过我应该用不到。经过3H的奋战,到凌晨基本已经走完了大半。
测试
为了验证DDR的正确性,这里跑一个喜闻乐见的NIOS。
这里最关键的是DDR Controller的配置。
时钟给了150兆。
这里没有46V32M16的模板,倒是有个16M16的,依葫芦画瓢改改列地址。
module NIOS_DDR1(
input clk ,
output [1:0] pio ,
inout sdram_ck, sdram_ck_n,
output sdram_cs, sdram_cke, sdram_ras, sdram_cas, sdram_we,
output [12:0] sdram_addr,
inout [15:0] sdram_dq,
inout [1:0] sdram_dqs,
output [1:0] sdram_dm,
output [1:0] sdram_ba
);
wire nrst;
nios u0 (
.reset_reset_n ( nrst ),
.clk_clk ( clk ),
.pio_export ( pio ),
.sdram_sysclk_clk ( ),
.sdram_ex_local_refresh_ack ( ),
.sdram_ex_local_init_done ( ),
.sdram_ex_reset_phy_clk_n ( ),
.adram_mem_mem_clk ( sdram_ck ),
.adram_mem_mem_clk_n ( sdram_ck_n ),
.adram_mem_mem_cs_n ( sdram_cs ),
.adram_mem_mem_cke ( sdram_cke ),
.adram_mem_mem_addr ( sdram_addr ),
.adram_mem_mem_ba ( sdram_ba ),
.adram_mem_mem_ras_n ( sdram_ras ),
.adram_mem_mem_cas_n ( sdram_cas ),
.adram_mem_mem_we_n ( sdram_we ),
.adram_mem_mem_dq ( sdram_dq ),
.adram_mem_mem_dqs ( sdram_dqs ),
.adram_mem_mem_dm ( sdram_dm )
);
assign nrst = (cnt == 4'b1110) ? 1'b0 : 1'b1;
reg [3:0] cnt = 4'd0;
always @(posedge clk) begin
if(cnt != 4'b1111)
cnt <= cnt + 1'b1;
else
cnt <= cnt + 1'b0;
end
endmodule
简单例化一下,然后写了一个小灯程序。
#include <io.h>
#include <unistd.h>
#include <system.h>
int main()
{
IOWR(PIO_0_BASE,1,0x0003);
while(1)
{
IOWR(PIO_0_BASE,0, 0x0003);
usleep(500000);
IOWR(PIO_0_BASE,0,0x0000);
usleep(500000);
}
return 0;
}
行云流水,一把跑通,香槟庆祝。目前没用想到特别有价值的应用,可能以后会画一个底板,或者给有缘人吧。
板子后面有个彩蛋,一看英语课就在睡觉!Supply?Suplly!