本篇文章尝试在FPGA上实现DSMC从设备(目前实现了单次四字节读写功能,连续读写需要稍微修改,然后加上FIFO缓存,由于项目暂时只需要单地址读写便没有做兼容设计。如果有需要的也可以互相交流学习)
由于主机测默认是LocalBus模式配置,这里也先实现LocalBus从机(抓取波形时发现主机默认的读写延时貌似是2clock)
代码和流程参考"创龙科技Tronlong"例程
使用到的FPGA芯片是紫光同创的PG2L25H-6MBG325,主控芯片是瑞芯微RK3576,驱动配置参照例程(我也没看懂)在创龙例程上对接口进行稍微修改
1.模块头
module dsmc_localbus(
//dsmc接口
input wire dsmc_clkp ,
input wire dsmc_clkn ,
input wire dsmc_csn ,
inout wire dsmc_dqs0 ,
inout wire dsmc_dqs1 ,
inout wire [7:0] dsmc_dq0 ,
inout wire [7:0] dsmc_dq1 ,
//数据读写接口
output wire d_clk ,//读写时钟
output wire d_addr ,//读写地址
input wire [31:0] d_r_data ,//主机需要读的数据
output wire [31:0] d_w_data ,//获取到的主机数据
output wire d_w_en ,//主机写使能
output wire d_r_en //主机读使能
);
2.中间信号定义
// 信号定义
wire dsmc_clkf ;//差分模块之后的时钟信号
wire dsmc_csnf ;//片选信号取反作为复位信号
wire [7:0] ds_datal_o ;//DQ0总线输出,发送给上位机
wire [7:0] ds_datal_i ;//DQ0总线输入,来自上位机
wire ds_dqs0_o ;//DQS0总线输出,发送给上位机
wire ds_dqs0_i ;//DQS0总线输入,来自上位机
wire [7:0] ds_datah_o ;//DQ1总线输出,发送给上位机
wire [7:0] ds_datah_i ;//DQ1总线输入,来自上位机
wire ds_dqs1_o ;//DQS1总线输出,发送给上位机
wire ds_dqs1_i ;//DQS1总线输入,来自上位机
reg rw_flag ;//DQ读写三态标志,0=上位机读数据 1=上位机写数据
reg dqs_flag ;//DQS读写三态标志,0=上位机读dqs 1=上位机写dqs
//data寄存器
reg ds_rw_flag ;//读写指令标志,0=上位机读数据 1=上位机写数据
reg [7:0] buf_dar0 ;
reg [7:0] buf_dar1 ;
reg [7:0] buf_dar2 ;
reg [7:0] buf_dar3 ;
reg [7:0] buf_daw0 ;
reg [7:0] buf_daw1 ;
reg [7:0] buf_daw2 ;
reg [7:0] buf_daw3 ;
reg [7:0] ds_clk_cnt ;//上升沿计数器
reg [31:0] data_buf32r ;//读取到的32bit数据缓存
reg [15:0] ds_addr_reg ;//获取到的偏移地址
reg buf_wadd_en ;//主机写使能标志
reg buf_radd_en ;//主机读使能标志
3.网络赋值
// 网络赋值
assign dsmc_csnf = ~dsmc_csn ;//片选低电平有效,这里作为复位要取反
assign d_clk = dsmc_clkf ;
assign d_addr = ds_addr_reg[9:2] ;//地址取4字节时忽略低2位,空间大小1024byet(因为我目前只需要用这么多,实际localbus最大支持32位地址索引)
assign d_w_data = data_buf32r ;
assign d_w_en = (ds_clk_cnt == 8'd5) ? buf_wadd_en : 1'b0 ;//读到第一个数据后就使能一次
assign d_r_en = buf_radd_en ;
// 如果需要连续读写功能,可以在这里修改读写使能标志,然后地址实现自增操作。
4.原语例化
//-------------------------------CLOCK PORT---------------------------------------
// 差分转单端(不知道是否需要)
GTP_INBUFGDS #(.IOSTANDARD("HSTL18D_I"),.TERM_DIFF("ON"))
clk_inbufds (.O(dsmc_clkf),.I(dsmc_clkp),.IB(dsmc_clkn));
//-------------------------------DATA PORT---------------------------------------
// 信号三态转换
// 三态使能,T=1 时 IO 作为输入,T=0 时 IO 作为输出
genvar i ;
generate
for(i=0;i<8;i=i+1)
begin : dsmc_iob
GTP_IOBUF #(.IOSTANDARD("HSTL18_I"),.SLEW_RATE("FAST"),.DRIVE_STRENGTH("8"),.TERM_DDR("ON"))
IOBUF_data_ds0 (.O(ds_datal_i[i]),.IO(dsmc_dq0[i]),.I(ds_datal_o[i]),.T(rw_flag));//t=0 io=i;t=1 o=io
GTP_IOBUF #(.IOSTANDARD("HSTL18_I"),.SLEW_RATE("FAST"),.DRIVE_STRENGTH("8"),.TERM_DDR("ON"))
IOBUF_data_ds1 (.O(ds_datah_i[i]),.IO(dsmc_dq1[i]),.I(ds_datah_o[i]),.T(rw_flag));//t=0 io=i;t=1 o=io
GTP_ODDR_E1 #(.GRS_EN("FALSE"),.ODDR_MODE("SAME_EDGE"),.RS_TYPE("ASYNC_RESET"))
oddr_dq0 (.Q(ds_datal_o[i]),.D0(buf_daw0[i]),.D1(buf_daw1[i]),.CLK(dsmc_clkf),.CE(1),.RS(0));
GTP_ODDR_E1 #(.GRS_EN("FALSE"),.ODDR_MODE("SAME_EDGE"),.RS_TYPE("ASYNC_RESET"))
oddr_dq1 (.Q(ds_datah_o[i]),.D0(buf_daw2[i]),.D1(buf_daw3[i]),.CLK(dsmc_clkf),.CE(1),.RS(0));
end
endgenerate
//-------------------------DQS PORT---------------------------------------------
// DQS信号处理// 作为输入,主机掩码// 作为输出,主机读数据时钟
// 从机在第五个时钟上升沿控制dq和dqs同步输出
GTP_ODDR_E1 #(.GRS_EN("FALSE"),.ODDR_MODE("SAME_EDGE"),.RS_TYPE("ASYNC_RESET"))
oddr_dqs0 (.Q(ds_dqs0_o),.D0(1'b1),.D1(1'b0),.CLK(dsmc_clkf),.CE(~rw_flag),.RS(0));
GTP_ODDR_E1 #(.GRS_EN("FALSE"),.ODDR_MODE("SAME_EDGE"),.RS_TYPE("ASYNC_RESET"))
oddr_dqs1 (.Q(ds_dqs1_o),.D0(1'b1),.D1(1'b0),.CLK(dsmc_clkf),.CE(~rw_flag),.RS(0));
GTP_IOBUF #(.IOSTANDARD("HSTL18_I"),.SLEW_RATE("FAST"),.DRIVE_STRENGTH("8"),.TERM_DDR("ON"))
IOBUF_dqs0 (.O(ds_dqs0_i),.IO(dsmc_dqs0),.I(ds_dqs0_o),.T(dqs_flag));//t=0 io=i;t=1 o=io
GTP_IOBUF #(.IOSTANDARD("HSTL18_I"),.SLEW_RATE("FAST"),.DRIVE_STRENGTH("8"),.TERM_DDR("ON"))
IOBUF_dqs1 (.O(ds_dqs1_i),.IO(dsmc_dqs1),.I(ds_dqs1_o),.T(dqs_flag));//t=0 io=i;t=1 o=io
5.三态使能信号控制
//-------------------------------三态信号处理---------------------------------------
// 当前模式为localbus,非零延时访问,2个clk延时的情况下
// 读模式,从机在片选下降沿拉低dqs,从机在第五个时钟上升沿控制dq和dqs同步输出
// 写模式,从机在片选下降沿拉低dqs,第三个时钟下降沿释放dqs,第五个时钟上升沿监测dqs(可不检测)
always@(posedge dsmc_clkf or negedge dsmc_csnf)begin
if(~dsmc_csnf) rw_flag <= 1'b1 ;//主机未片选时,不使用总线
else begin
if((ds_clk_cnt >= 8'd4) && (ds_rw_flag == 1'b0)) rw_flag <= 1'b0 ;//读模式下,第4个下降沿从机占用总线
else rw_flag <= 1'b1 ;
end
end
always@(posedge dsmc_clkf or negedge dsmc_csnf)begin
if(~dsmc_csnf) dqs_flag <= 1'b1 ;//主机未片选时,不使用总线
else begin
if((ds_clk_cnt >= 8'd3) && (ds_rw_flag == 1'b1)) dqs_flag <= 1'b1 ;// 当写模式下,在第三个下降沿释放总线
else dqs_flag <= 1'b0 ;// 否则片选拉低一直占用
end
end
6.数据字节匹配
// 信号串行解串|解串接收
// 因为数据要在下降沿读取,因此上升沿时的总线数据要提前保存,也就是第0字节和第2字节
always@(posedge dsmc_clkf or negedge dsmc_csnf)begin
if(~dsmc_csnf) buf_dar0 <= 8'd0 ;
else buf_dar0 <= ds_datal_i ;// 0
end
always@(posedge dsmc_clkf or negedge dsmc_csnf)begin
if(~dsmc_csnf) buf_dar2 <= 8'd0 ;
else buf_dar2 <= ds_datah_i ;// 2
end
//信号串行解串|串行发送
always@(posedge dsmc_clkf or negedge dsmc_csnf)begin
if(~dsmc_csnf)begin
buf_daw0 <= 8'd0 ;
buf_daw1 <= 8'd0 ;
buf_daw2 <= 8'd0 ;
buf_daw3 <= 8'd0 ;
end
else begin
if(ds_clk_cnt == 8'd3)begin//第一个数据寄存时机
buf_daw0 <= d_r_data[15:8] ;
buf_daw1 <= d_r_data[7:0] ;
buf_daw2 <= d_r_data[31:24] ;
buf_daw3 <= d_r_data[23:16] ;
end
else begin
buf_daw0 <= buf_daw0;
buf_daw1 <= buf_daw1;
buf_daw2 <= buf_daw2;
buf_daw3 <= buf_daw3;
end
end
end
7.沿计数
//----------------------------------------------------------------------
// 时钟计数,计上升沿
always@(posedge dsmc_clkf or negedge dsmc_csnf)begin
if(~dsmc_csnf) ds_clk_cnt <= 16'd0 ;
else ds_clk_cnt <= ds_clk_cnt + 1'b1 ;
end
8.地址和数据使能
//-------------------------------ADDR REG---------------------------------------
// 指令只在第dq0总线上传递
// 读指令|读写标志位.0=读 1=写
always@(negedge dsmc_clkf or negedge dsmc_csnf)begin
if(~dsmc_csnf) ds_rw_flag <= 1'b1 ;
else begin
if(ds_clk_cnt == 16'd1) ds_rw_flag <= buf_dar0[7] ;//第一个下降沿时,读取寄存器0的最高位
else ds_rw_flag <= ds_rw_flag ;
end
end
// 读指令|读写地址
// 不同模式解析不同,localbus模式下
// 忽略[46:40]目标、突发类型、空间类型、FIFO合并、射频标志、区域划分
// [39:32]一个字节的突发长度
// [31:0]四个字节的起始地址,这里暂时只关注低16位
always@(negedge dsmc_clkf or negedge dsmc_csnf)begin
if(~dsmc_csnf) ds_addr_reg <= 16'd0 ;
else begin
if(ds_clk_cnt == 16'd3) ds_addr_reg <= {buf_dar0,ds_datal_i} ;
else ds_addr_reg <= ds_addr_reg ;
end
end
//-------------------------------DATA REG---------------------------------------
//主机写
always@(negedge dsmc_clkf or negedge dsmc_csnf)begin
if(~dsmc_csnf) buf_wadd_en <= 1'b0 ;
else begin
if(ds_clk_cnt == 8'd5) buf_wadd_en <= ds_rw_flag ;
else buf_wadd_en <= 1'b0 ;
end
end
always@(negedge dsmc_clkf or negedge dsmc_csnf)begin
if(~dsmc_csnf) data_buf32r <= 32'd0 ;
else begin
data_buf32r[15:8] <= buf_dar0 ;//dq0上升沿采样值,1、5
data_buf32r[7:0] <= ds_datal_i ;//dq0下降沿采样值,0、4
data_buf32r[31:24] <= buf_dar2 ;//dq1上升沿采样值,3、7
data_buf32r[23:16] <= ds_datah_i ;//dq1下降沿采样值,2、6
end
end
//主机读
always@(negedge dsmc_clkf or negedge dsmc_csnf)begin
if(~dsmc_csnf) buf_radd_en <= 1'b0 ;
else begin
if((ds_clk_cnt >= 8'd3) && (ds_clk_cnt <= 8'd6)) buf_radd_en <= ~ds_rw_flag;
else buf_radd_en <= 1'b0 ;
end
end
endmodule