` 本帖最后由 jf_azsd 于 2021-5-14 08:47 编辑
此次要做的是给一款怀旧文曲星复刻配件让其能够刷自定义固件. 它的系统固件是一个独立的ROM 小板. 该ROM 是真的掩膜ROM, 不是现在电子设备的flash 那种. 主控是一个spdc1016的soc, 指令集为6502,有23位并行地址线和一个8位AD复用线. 年轻的我曾经以为这八根是数据,mc1/mc0传递地址.(因为跟norflash的数据引脚并联) 后来有了网上泄露的busflash资料和逻辑分析仪,差不多搞明白了这个协议的时序. 首先是BROM小板的引脚定义.
这上面有三个并联的芯片,每个4Mx8bit,地址宽度是16,用mapper来完成对不同数据的访问. 主控spdc1016 使用普通指令例如LDA/STA 访问00..3F 是读写寄存器, 有gpio, timer,mapper 不同用途. 主机通过安排bank/vol/roa 位, 可以将4000..FFFF 的空间范围映射到不同的器件存储单元. 这个mapper主机和存储芯片是同步切换的,代码对主机内的寄存器写入时候,adbus上也会有写入时序,让busrom能够切换自己的映射. 这三块busrom(B1/B2/B3),B1在4000..BFFF响应所有vol的0..7Fbank和在C000..DFFF响应所有bio***ank, E000..FFFF响应固定映射.而B2/B3在4000..BFFF响应vol0/vol1的80..FF bank. bank[7:0]位于寄存器地址00[7:0],vol位于0D[1:0],roa位于0A[7],bios bank位于0A[3:0].在verilog中写入这些地址时候存储到reg里, 然后在主机读取到原本ROM空间时候, 动态生成24bit的物理地址. 下面再说mc1/mc0信号, 其中mc1高区间表示ad总线走的地址, 低区间ad总线走数据. mc0在地址阶段指示传输的是AH还是AL, 在数据阶段指示的是读操作还是写操作. cpu的访问因为mc1相对cpu时钟存在相位差,可以看作单周期的,也就是在同一个cpu时钟周期内, 给出地址并要读到数据,不能拖到下一周期(因为总线可能马上得输出地址).
cpuclk, mc1/mc0/ad在理想情况下的一次读取时序大概如上. 频率为3.6864M, 因为要使用qspi flash来存储固件, 数据和指令的消耗成为很大的瓶颈. cpu冷复位的时候,因为cpu复位后默认为振荡器 4分频.总线速度将是1/4,通过代码执行时候设置寄存器,分频数可以在1-64之间切换. 经过挑选,W25Q128JM的IM系列可以在133Mhz下用22/14时钟完成一个字节的读取.flash上电后首次读取必须跑一次完整22时钟, 此后可以省略命令字节,只需14时钟. 为了尽量更早的发出指令/地址, 我要将SCK往MC0下降沿对齐. 因为高云没有类似的时钟原语,只得用266M的时钟做对齐和反转,来生成一个133M的时钟, 该新时钟和MC0下降沿的距离不超过3.75ns. 因为下降沿后最多15ns(实测10-12ns)AL就到达, 而mapper产生的24bit地址, AL固定放在低8bit,前16bit用不到AL. qspi模式下每时钟发送4bit,所以前面16bit发送需要4时钟, 也就是这30~33.75ns时间内都用不到AL.如果是首次读取的话,发CMD的8个时钟更用不到AL的值. 因此我的优化就是,一旦捕获到MC0下降就可以开始进行spi操作, 等到发送低8bit时候分别为第12/4时钟 , 此时AD的时间段都位于AL区间(图中绿色方框).
在模拟器中验证了波形,没问题.初始MC为AH状态, AD输出FFFC, 在MC0下降沿后保持了12ns,MC0上升晚于MC0下降10ns. 这两个参数前者抓波形得来,后者来自数据手册最大值. 由spiclk2x生成spi外部时钟和逻辑时钟.外部时钟来自于逻辑时钟的反向和spiss#信号的组合. 这样我可以在逻辑时钟上升沿设置发送数据,而在设备看来上升沿在我数据到达半个时钟后,正好位于setup+hold的中间. 通过寄存器打拍,在spiclk2x时钟检测MC0/MC1下降沿. MC0下降沿(实际略迟于输入)开始传输, 并且延迟后抓取AD的值. 其实没有必要, 可以在发送时候直接从总线采集到qspi输出寄存器. MC1下降沿启动两个计数器,一个在10ns后根据MC0决定是写入还是读取,如果是写入就尝试更新mapper.adzcnt延时到MC1上升前20ns,此时如果确定是读取,那么自己从flash中读到的数据就复制到AD总线, 并且设置iobuf为输出, 以便主机读取. 主机在读取阶段AD 总线是类似弱推挽的buskeeper 电路, 在MC1 下降沿过后35ns, 设备可以开始向AD 总线输出电平, 主机将至少将电平保持到AD 变为输出AH 为止. SPR1024A 文档中的参数是设备在MC1 上升沿后至少15ns 内还保持输出. 实际应该可以只输出很短的adbusz 负区间, 让主机自行保持直到主机开始刷AH 到总线上. 模拟很好可在云源软件里面综合遇到问题,spiclk2x时钟到不了266,增加合适约束后.时序错误.于是退求其次, 用133的主时钟, 丢弃了按照半时钟对齐的特性,再将一些组合逻辑改为时序的以后,spiclk的fMax达到了179M,可以通过了. 另外提一嘴,高云自己的gowinsynthesis综合能达到的fMax明显低于SynplifyPro的. 模拟波形图中可以看出,主机时钟比设备看到的时钟”提前”半个时钟, 本来设备会在dummyclock最后一个下降沿准备数据,对应的就是clk_spi_free的19/20的上升沿. 我们要在半个时钟后的下降沿读取.但加上时钟到管脚,再加上设备响应的延迟,要放在20/21时钟的上升沿读.单边沿也有助于时序的优化.
结合spiflash的时序和实验结果,实际时钟21可以不输出给器件,只要SS#留的足够, D3..D0会保持输出直到SS#跳变. 如果给了时钟21实际会在SS#跳变前多读出下一个字节的前半部分. 接上开启了QE并写入了ROM的w25q125jvpim,需要注意要修改核心板下方的0欧姆电阻从左侧挪到右侧把vccio从2.5跳到3.3v. 上机测试, 不通过, 因为高云的GAO 分析仪无法在266M 下工作, 只能从其他开发板抓波形, 经过不断地抓取, 偶然发现上电时候, 总线有可能出现531k 左右的主频, 并且此时AH 的” 保持时间” 也拉长到了47.25ns, 同样MC1 下降沿到MC0 上升也有38.5ns, 超过了文档给出的-20~10ns 限制. 我在之前抓取复位时候总线信号时候,多次统计会发现主频是921k左右而AH的保持时间和全速下一样都是10/12ns左右. 怀疑是成功充当了一段时间ROM后, 主机降频导致verilog逻辑失败. 对于这样可变速度的主机,因为响应速度的限制,并不能测量主机总线周期来调节flash放慢时钟倍率. 而在不知道总线速度几何的时候,比如14时钟的读取流程中,sck第四时钟(30ns)时候要往spiio输出低8bit值了, 此时总线还没给出AL. 同样的问题还有AL保持时间, 如果MC1过后, 也要一百多ns主机才释放AD的输出, 那么我预设的112ns延时计数器结束后可能主机仍在输出AL,导致电平冲突.但这个释放时间不好观察,要等待整个移植稳定后,在ROM写一段小代码在默认4分频下复制到sram执行, 切换主机的分频系数,同时不断地读已知的ROM地址, 而在verilog里面只输出7.5或者15ns的回应, 提前到什么时刻会导致逻辑分析仪上AD数据重新被刷回AL,就说明此刻主机端还是保持时间.不过我认为,如果sunplus本身做的busrom芯片, 不能根据时钟高低来调节输出的时间点,那我也不用假设AL保持会拉长. 为了适应AL晚到达的总线bug,可以给flash提供不连续的SCK,类似于软件gpio模拟spi时候的操作. 通过增加一个waital的脉冲, 在此期间停止发送地址的计数,同时修改spiout的逻辑连线如下 wire [3:0] quadaddr = spiaddrcnt >2?offset[23:20]:(spiaddrcnt == 2?adin[7:4]:(spiaddrcnt ==1?adin[3:0]:4'b1110)); // M[5..4]=10 assign spiout = (state == SENDCMD)?{3'b000,spicmd[7]}:(state == SENDADDR)?quadaddr:4'b0000; 这样可以在需要等待稳定可靠的AL到来的期间, 让spisck停止输出, 并且adin直接跨接到spiout,这样计数器恢复走数的时候,实际接下来设备看到的两个上升沿对应的是adin的高四和低四位,此项兼容搞定. (AL保持可能实测只有10ns, 因此可能只有adin[7:4]输出稳定, adin[3:0]要修改为addr[3:0])
然后再来看另一个bug,就是531k总线频率adzcnt产生的延时(112.5ns)不够, 此时可能AD总线还在保持AL,主机还在输出的而我也对AD进行输出, 会电平冲突(短路). 我后来想了下是有机会估测出MC1的下降区间对应的时间的,大概等于AH区间+AL区间, AL区间可能因为接下来是写入而延后到MC1上升时候翻转, 所以计算的是MC0下降到MC1下降的时间. 我们把原本的”绝对时间”换为相对时间, 原本的AL保持时间35ns相对于AL区间68ns大概是一半多一点,而原本的设备是选择在MC1上升前20~35ns开始输出数据的.也就是如果主机按照分频来拉长adbus时间参数, 0.52~1.47/1.7的时间段内, 主机应该处于buskeeper状态. 目前推测主机在1/2和1/4系数按照固定时延来,只有更低的1/8直至1/64时候才有所拉长,而似乎不是等比例拉长的.按照固定时延的算法,在主机拉长后就有冲突危险,反之不会冲突. 所以我可以采用如下策略,在al时间统计是1倍频的时候, 使用手工安排的112.5ns(小于临界值116ns),而1/2~1/64等倍频使用AL区间时间, 一定是夹在AL保持结束和MC1上升沿之前开始设置数据之间的. 其实112.5ns如果计算上打拍采样带来的延迟,相对实际的MC1下降沿是112.5~120ns,也有可能出现主机侧设置时间小于20,实际设置时间因为cpu相对mc1有提前可能更短,有采集不到的危险.但再提前输出做不到,因为在总线满速情况,这是flash刚好返回第一个字节数据的时间点. 所以比较廉价的w25q128JVPIQ,因为每次都要发22时钟, 很容易在满速下出现问题. 一个办法就是, 在FPGA启动后, 立刻对flash 进行一次含cmd 的读取并手动重启主机, 此后由于flash 都可以用14 时钟返回数据, 在最快的1 分频就可以提前7.5 或者15ns 输出到ad 总线, 避开误差. 如果假定fpga启动=设备冷启动, 则第一次22时钟的读取一定发生在4分频下, 那么我一样可以在1分频的时候提前输出. 经过修改的模拟波形如下:
可以看出AL改在了MC1下降后才从adbus转发给spi. 而在总线4分频, 慢速读取时候也会选在Data区间中心时间点输出,避开由于过慢可能出现的电平冲突. 至此这个简单的工程算是比较稳定了,接下来还要不断地重启看看哪里出错.我还打了machxo2版本的转接板子,这次开发其实是在vivado,diamond, 云源三个软件之间倒来倒去. 模拟上vivado比diamond操作方便, 不是很习惯modelsim的界面. 内置信号探头上,ILA完胜GAO.云源软件使用中唯一优势就是自带的综合极快吧,但综合出来的时序不如SynplifyPro.
附表:开发板引脚分配 信号 | | | CS | | | SCK | | | IO0 | | | IO1 | | | IO2 | | | IO3 | | |
|
|
| MC1 | | | MC0 | | | AD0 | | | AD1 | | | AD2 | | | AD3 | | | AD4 | | | AD5 | | | AD6 | | | AD7 | | |
附图:GAO 在synplifypro 下加入后会严重降低fMax, 通过将spiflash 逻辑单独剥离综合,fMax 能到133 了, 但GAO 模块中仍存在时序问题.
复位的vectorFFFC/FFFD是映射到偏移007FFC/007FFD的, 大致上能看到flash往spiIO输出了存储的值F4. GAO达不到两倍采样,266时钟fMax为150, 此处是133M采样看不了sck时钟, 会显示为持续高电平.
若改用GowinSynthesis,加入GAO也影响fMax,GAO用双倍时钟266.6的时候fMax只有146M, 因此抓的波形不可靠,但在循环里面可以挑选到相对错漏少一些的. |