听说你还在搞什么原创,搞来搞去也就那样?
自辞职读研后,已有一年多未踏足服务器FPGA固件开发行业了,本着怀旧心理,回忆下曾经的自己。本博客将介绍SGPIO总线的原理和如何使用FPGA实现其编码和解码。
本文将介绍多组SGPIO总线输入到FPGA后,经过FPGA解析并重组成一组新的SGPIO总线发生至从机(LED或其他I/O口)。
1.SGPIO原理
串行通用输入输出(SGPIO)是一种串行化通用IO信号的方法。通常用于电子设备或嵌入式系统中,尤其是在需要通过串行接口控制或监控多个I/O引脚的场景下。
其SGPIO总线一般包括四条线:时钟CLK、数据输入DI、数据输出DO和使能LOAD。
其中:详细介绍请参考SFF-8485数据手册。
在服务器系统中,BMC或CPU等主设备常使用SGPIO总线和CPLD/FPGA执行数据的交互。如:PCH或CPU使用SGPIO控制硬盘背板上HDD的状态LED,需要使用FPGA解析CPU发送的SGPIO总线数据,进而控制HDD背板上的LED。常见的SGPIO控制硬盘LED的拓补图如下图所示。
基本知识就不赘述了,凡尔赛一下:很简单的。
那么问题来了,如何使用FPGA准确的解析多组SGPIO总线上的数据并重组成一组新的SGPIO总线呢?且看本集后续。多组SGPIO输入FPGA后重组成一组SGPIO的系统框图如下图所示。
若实现上述框图的FPGA逻辑,首先需要无误解析SGPIO总线的数据,那么FPGA的采样时钟必须满足奈奎斯特采样定理。即FPGA的采样时钟频率需要大于二倍的SGPIO时钟频率,才能保证FPGA解析SGPIO的数据不会出错。由SFF-8485数据手册可详细的得到SGPIO时钟频率的范围(32Hz~100KHz)。当然在编写FPGA固件时,其信号线DO/DI的建立时间和保持时间也必须符合SGPIO的规范。SGPIO通信时的波形图如下:
LOAD置1后,LOAD、DO和DI上的位流会在时钟CLK上重启。比特流应至少包含4个驱动器的信息。比特流不必每次都是相同的长度,并且可以在从驱动器3位开始的任何位位置结束。通俗来讲,就是3个bit(0,1,2)位表示一组设备的数据信息。SGPIO 规范要求目标在 CLK、LOAD和 DO为高电平 64 ms 时关闭设备的所有指示器。
根据波形示意图可以得知DI/DO是在CLK的上升沿接收/下降沿发送数据。那么,开始搓代码呗!
2.解码
若想获取SGPIO上的数据,在此之前需要先对输入的SGPIO总线进行滤波处理,其目的是去除PCB走线或者线材引入的毛刺,这些毛刺对FPGA有很大的负面影响。其滤波固件本文不做阐述和编写。
前戏结束,开始正题。
2.1 检测SGPIO总线状态
由于SGPIO非活动状态下的CLK处于高电平状态(应该是高阻抗,其内部上拉电阻拉至高电平),先检测其状态并输出控制使能信号,保证编码逻辑块输出的I/O电平符合SGPIO规范。
always@(posedge i_sys_clk or negedge i_sys_rst_n)begin
if(!i_sys_rst_n)
sgpio_bit_cnt <= 24'h000000;
else if(i_sample_clk)
sgpio_bit_cnt <= {sgpio_bit_cnt[22:0] , sgpio_clk};
else
sgpio_bit_cnt <= sgpio_bit_cnt;
end
assign sgpio_act_flg = (sgpio_bit_cnt == 24'hffffff || sgpio_bit_cnt == 24'h000000) ? 1'b0 : 1'b1;
2.2 捕获LOAD信号
LOAD从高电平变为低电平在变为高电平,其时间跨度表示SGPIO数据的一个周期,可以结合SGPIO的CLK频率得到SGPIO总线的位宽。
LOAD边沿检测逻辑块:
always@(posedge i_sys_clk or negedge i_sys_rst_n) begin
if(!i_sys_rst_n)begin
sgpio_load_d <= 1'b1;
sgpio_load_q <= 1'b1;
end
else begin
sgpio_load_d <= sgpio_load;
sgpio_load_q <= sgpio_load_d;
end
end
assign sgpio_load_pose = (sgpio_load_d && ~sgpio_load_q);
assign sgpio_load_nege = (sgpio_load_q && ~sgpio_load_d);
获取SGPIO位宽逻辑块(没啥用!)
always@(negedge w_sgpio_clk or negedge i_sys_rst_n)begin
if(!i_sys_rst_n || w_sgpio_load)
sgpio_bitw_d <= {24'h0000000};
else if(!w_sgpio_load)
sgpio_bitw_d <= sgpio_bitw_d + 1'b1;
else
sgpio_bitw_d <= sgpio_bitw_d;
end
2.3 采样并获取DI的数据
结合 CLK和LOAD捕获并解析DI上的数据信息。高位在前,低位在后。解码后的数据传送至FIFO中。
always@(negedge w_sgpio_clk or negedge i_sys_rst_n) begin
if(!i_sys_rst_n)begin
receive_data_d <= {SGPIO_WIDTH{1'b0}};
receive_data_q <= {SGPIO_WIDTH{1'b0}};
end
else begin
if(!w_sgpio_load) begin
receive_data_d <= {sgpio_data_in , receive_data_d[SGPIO_WIDTH - 1'b1 : 1]};
end
else begin
receive_data_q <= {sgpio_data_in , receive_data_d[SGPIO_WIDTH - 1'b1 : 1]};
end
end
end
assign o_data_out = sgpio_act_flg ? receive_data_q : {SGPIO_WIDTH{1'b0}};
3.编码
3.1 生成SGPIO的时钟CLK
其实在频率需要自己设定,但需要满足SGPIO规范的范围(32Hz~100KHz)同样可以使用FPGA的IP核生成所需的时钟(CLK IP + PLL IP)。
always @(posedge isys_clk or negedge isys_rst_n)begin
if (!isys_rst_n || state == IDLE) begin
sgpio_clk_out <= 1'b1;
clk_div_count <= {(CNT_SIZE + 1'b1){1'b0}};
end
else begin
if (clk_div_count == (SGPIO_CLK_DIV - 1'b1)) begin
sgpio_clk_out <= ~sgpio_clk_out ;
clk_div_count <= {(CNT_SIZE + 1'b1){1'b0}};
end
else begin
sgpio_clk_out <= sgpio_clk_out;
clk_div_count <= clk_div_count + 1'b1;
end
end
end
assign osgpio_clk_out = sgpio_clk_out ;
3.2 LOAD逻辑块
always@(posedge osgpio_clk_out or negedge isys_rst_n)begin
if(!isys_rst_n || state == IDLE || (state == CYCLE_END && orcrtl_flg == 1'b0) ) begin
sgpio_load_count <= {(SGPIO_CYCLE_BIT + 1'b1){1'b0}};
end
else if(sgpio_load_count == (SGPIO_BIT_WIDTH - 1'b1))begin
sgpio_load_count <= {(SGPIO_CYCLE_BIT + 1'b1){1'b0}};
end
else begin
sgpio_load_count <= sgpio_load_count + 1'b1;
end
end
always@(posedge isys_clk or negedge isys_rst_n)begin
if(!isys_rst_n || state == IDLE || state == CYCLE_STOP) begin
sgpio_load_n <= 1'b1;
end
else if(sgpio_load_count == 8'd23)begin
sgpio_load_n <= 1'b1;
end
else begin
sgpio_load_n <= 1'b0;
end
end
3.3 SGPIO状态机
always@(posedge isys_clk or negedge isys_rst_n)begin
if(!isys_rst_n)
state <= IDLE;
else
state <= next_state;
end
always@(*)begin
case(state)
IDLE : begin
if(iwr_enable == 1'b1)
next_state <= CYCLE_START;
else
next_state <= IDLE;
end
CYCLE_START : begin
if(sgpio_load_count == (SGPIO_BIT_WIDTH - 1'b1))
next_state <= CYCLE_VALID;
else
next_state <= CYCLE_START;
end
CYCLE_VALID : begin
if(iwcrtl_flg == 1'b1)
next_state <= CYCLE_VALID;
else
next_state <= CYCLE_END;
end
CYCLE_END : begin
if(orcrtl_flg == 1'b1)
next_state <= CYCLE_END;
else
next_state <= CYCLE_STOP;
end
CYCLE_STOP : begin
if(sgpio_load_count == (SGPIO_BIT_WIDTH - 1'b1))
next_state <= IDLE;
else
next_state <= CYCLE_STOP;
end
default : begin
next_state <= IDLE;
end
endcase
end
always@(posedge isys_clk or negedge isys_rst_n)begin
if(!isys_rst_n || state == IDLE)
orcrtl_flg <= 1'b0;
else if(state == CYCLE_END || state == CYCLE_STOP)begin
if(sgpio_load_count == (SGPIO_BIT_WIDTH - 1'b1))
orcrtl_flg <= 1'b0;
else
orcrtl_flg <= 1'b1;
end
else
orcrtl_flg <= 1'b1;
end
3.4 DO输出数据
always@(negedge osgpio_clk_out or negedge isys_rst_n)begin
if(!isys_rst_n) begin
data_out1 <= {SGPIO_BIT_WIDTH{1'b0}};
data_out2 <= {SGPIO_BIT_WIDTH{1'b0}};
end
else if(!osgpio_load_n)begin
data_out1 <= {data_out1[SGPIO_BIT_WIDTH - 2 : 0] , sgpio_data_in};
end
else begin
data_out2 <= {data_out1[SGPIO_BIT_WIDTH - 1 : 1] , sgpio_data_in};
end
end
4.FIFO
测试时使用的是ALTERA的IP核,这里不做阐述。很简单的啦!
5.仿真验证
比较懒,不想写testbench文件。
6.测试的波形(未发送数据)
7.参考资料
- 脑袋瓜
- Intel FPGA逻辑设计指南
- SGPIO Spec