在FPGA模块开发过程中,有时会出现多个模块向同一个模块发起申请请求,并接收返回结果的情况。例如,模块MEM内部为片上存储器,保存有特定的配置数据。若只有一个模块A进行读取查询,则模块A发出查询请求和查询内容给MEM,MEM接收到读请求和读地址之后,进行查询并把结果返回给模块A即可。若有多个模块向MEM进行读取请求,则必须要进行数据调度,防止将结果返回给错误的模块。
对于FPGA内部请求调度,本文介绍以下三种方法,可以进行使用。
1、使用总线调度器
若发起请求的数据格式遵循总线协议,如AXI4-stream,avalon-stream等格式,则可以直接使用总线调度器进行调度。当前主流标准总线协议(如 Avalon、AMBA 等),大多配套有开源调度器代码,在GitHub、Gitee等开源平台可便捷获取。这些代码经社区长期验证与迭代,可靠性较高,无需开发者从零开发调度逻辑,直接调用即可实现总线请求的高效调度,大幅降低开发难度、缩短项目周期,同时减少自研调度器可能出现的漏洞风险,为总线相关系统开发提供了稳定支撑。
例如,对于axi4-stream格式的数据,可以找到开源的axis demux模块,实现将任意路数的axis多转一,并保持每一路valid和ready的正确性。
/*
* AXI input
*/
input wire [DATA_WIDTH-1:0] s_axis_tdata,
input wire [KEEP_WIDTH-1:0] s_axis_tkeep,
input wire s_axis_tvalid,
output wire s_axis_tready,
input wire s_axis_tlast,
input wire [ID_WIDTH-1:0] s_axis_tid,
input wire [S_DEST_WIDTH-1:0] s_axis_tdest,
input wire [USER_WIDTH-1:0] s_axis_tuser,
/*
* AXI outputs
*/
output wire [M_COUNT*DATA_WIDTH-1:0] m_axis_tdata,
output wire [M_COUNT*KEEP_WIDTH-1:0] m_axis_tkeep,
output wire [M_COUNT-1:0] m_axis_tvalid,
input wire [M_COUNT-1:0] m_axis_tready,
output wire [M_COUNT-1:0] m_axis_tlast,
output wire [M_COUNT*ID_WIDTH-1:0] m_axis_tid,
output wire [M_COUNT*M_DEST_WIDTH-1:0] m_axis_tdest,
output wire [M_COUNT*USER_WIDTH-1:0] m_axis_tuser,
如果本身发起请求的接口是native接口,不遵守总线协议,也可以自行将接口封装成AXI4-stream等格式,再调用开源模块。这样开发者只需要关注将单个模块的发起请求、请求数据、返回结果封装到axis总线的valid和data信号中,而不需要再考虑多个模块的请求调度。
2.自行采用FIFO进行调度
例如,对于2个模块的查询请求和查询结果,可以采用FIFO将多路输入的缓存暂存住,然后状态机轮询两个FIFO将数据调出,并额外采用一个顺序FIFO进行记录。当查询结果返回后,根据顺序FIFO中记录的顺序返回给对应模块。
图中,紫色部分的线表明两个模块的req和req info信号,分别送入FIFO中,并要在调度出去的时候记录被调用者的ID在order fifo。使用状态机轮询读取两个FIFO并送出。绿色部分的线表明rsp返回,返回逻辑读取order fifo读到当前的返回属于哪一个模块并对应返回。
自行采用FIFO进行调度,实现简单,可以任意增加减少路数,并完全避免对外部代码的使用。但是使用多个FIFO会增加资源占用率,提高模块间数据传输的延迟。
3、采用开源的rr调度模块
目前也存在直接只提供调度功能的rr调度开源模块,例如采用tm_rr_sched模块,调用模块时提供rr client的数量,给出上一个被调度的client的id和当前所有client的请求情况,则模块会给出下一个要调度的client id。
接口 | 描述 |
---|---|
last[N-1:0] | 输入,上一个被调度的client id拉高 |
request[N-1:0] | 输入,当前每个client的请求状态 |
next[N-1:0] | 输出,下一个要被调度的client id拉高 |
使用该开源模块可以方便简捷地得到下一个需要调度的模块ID,但具体将该模块的请求和数据送出去,还需要开发者自己实现。