Vortex GPGPU的硬件代码分析(Cache篇2)


前言

上一篇的Vortex GPGPU的硬件代码分析(Cache篇1)已经分析了VX_cache.sv代码的一部分,主要包括参数、变量和接口。

这一篇接着分析VX_cache.sv的代码。


一、VX_cache.sv代码部分解读2——buffering/initialize

1.1 core response buffering与VX_elastic_buffer模块解读

    // Core response buffering
    wire [NUM_REQS-1:0]                  core_rsp_valid_s;
    wire [NUM_REQS-1:0][`CS_WORD_WIDTH-1:0] core_rsp_data_s;
    wire [NUM_REQS-1:0][TAG_WIDTH-1:0]   core_rsp_tag_s;
    wire [NUM_REQS-1:0]                  core_rsp_ready_s;

    `RESET_RELAY (core_rsp_reset, reset);

    for (genvar i = 0; i < NUM_REQS; ++i) begin

        VX_elastic_buffer #(
            .DATAW   (`CS_WORD_WIDTH + TAG_WIDTH),
            .SIZE    (CORE_REQ_BUF_ENABLE ? `TO_OUT_BUF_SIZE(CORE_OUT_BUF) : 0),
            .OUT_REG (`TO_OUT_BUF_REG(CORE_OUT_BUF))
        ) core_rsp_buf (
            .clk       (clk),
            .reset     (core_rsp_reset),
            .valid_in  (core_rsp_valid_s[i]),
            .ready_in  (core_rsp_ready_s[i]),
            .data_in   ({core_rsp_data_s[i], core_rsp_tag_s[i]}),
            .data_out  ({core_bus_if[i].rsp_data.data, core_bus_if[i].rsp_data.tag}), 
            .valid_out (core_bus_if[i].rsp_valid),
            .ready_out (core_bus_if[i].rsp_ready)
        );
    end

这里的2个常量定义如下:

// 其中XLEN值为32
parameter WORD_SIZE             = `XLEN/8
`define CS_WORD_WIDTH           (8 * WORD_SIZE) // 值为32

parameter UUID_WIDTH            = 0,
parameter TAG_WIDTH             = UUID_WIDTH + 1, // 值为1

例化模块中的3个parameter如下:

// DATAW为33
.DATAW   (`CS_WORD_WIDTH + TAG_WIDTH),
// CORE_REQ_BUF_ENABLE为1,因此SIZE为2
.SIZE    (CORE_REQ_BUF_ENABLE ? `TO_OUT_BUF_SIZE(CORE_OUT_BUF) : 0),
// OUT_REG为2
.OUT_REG (`TO_OUT_BUF_REG(CORE_OUT_BUF))

// ps:相关变量:
// Core response output register
parameter CORE_OUT_BUF          = 0
localparam CORE_REQ_BUF_ENABLE = (NUM_BANKS != 1) || (NUM_REQS != 1);
// Number of banks
parameter NUM_BANKS             = 1,
// Number of Word requests per cycle
parameter NUM_REQS              = 4,

因此core request buffersize2,也就是允许接受2core request
其中关于TO_OUT_BUF_REGhw/rtl/VX_platform.vh,如下:

// size(x): 0 -> 0, 1 -> 1, 2 -> 2, 3 -> 2, 4-> 2
`define TO_OUT_BUF_SIZE(out_reg)    `MIN(out_reg, 2)

先不管其余变量,先看VX_elastic_buffer模块:

`include "VX_platform.vh"

`TRACING_OFF
module VX_elastic_buffer #(
    parameter DATAW   = 1,
    parameter SIZE    = 1,
    parameter OUT_REG = 0,
    parameter LUTRAM  = 0
) ( 
    input  wire             clk,
    input  wire             reset,

    input  wire             valid_in,
    output wire             ready_in,        
    input  wire [DATAW-1:0] data_in,
    
    output wire [DATAW-1:0] data_out,
    input  wire             ready_out,
    output wire             valid_out
);
    if (SIZE == 0) begin

        `UNUSED_VAR (clk)
        `UNUSED_VAR (reset)

        assign valid_out = valid_in;
        assign data_out  = data_in;
        assign ready_in  = ready_out;

    end else if (SIZE == 1) begin

        VX_pipe_buffer #(
            .DATAW (DATAW)
        ) pipe_buffer (
            .clk       (clk),
            .reset     (reset),
            .valid_in  (valid_in),
            .data_in   (data_in),
            .ready_in  (ready_in),
            .valid_out (valid_out),
            .data_out  (data_out),
            .ready_out (ready_out)
        );

    end else if (SIZE == 2) begin

        VX_skid_buffer #(
            .DATAW   (DATAW),
            .HALF_BW (OUT_REG == 2),
            .OUT_REG (OUT_REG)
        ) skid_buffer (
            .clk       (clk),
            .reset     (reset),
            .valid_in  (valid_in),
            .data_in   (data_in),
            .ready_in  (ready_in),
            .valid_out (valid_out),
            .data_out  (data_out),
            .ready_out (ready_out)
        );
    
    end else begin
        
        wire empty, full;

        wire [DATAW-1:0] data_out_t;
        wire ready_out_t;

        wire push = valid_in && ready_in;
        wire pop = ~empty && ready_out_t;

        VX_fifo_queue #(
            .DATAW   (DATAW),
            .DEPTH   (SIZE),
            .OUT_REG (OUT_REG == 1),
            .LUTRAM  (LUTRAM)
        ) fifo_queue (
            .clk    (clk),
            .reset  (reset),
            .push   (push),
            .pop    (pop),
            .data_in(data_in),
            .data_out(data_out_t),    
            .empty  (empty),
            .full   (full),
            `UNUSED_PIN (alm_empty),
            `UNUSED_PIN (alm_full),
            `UNUSED_PIN (size)
        );

        assign ready_in = ~full;

        VX_elastic_buffer #(
            .DATAW (DATAW),
            .SIZE  (OUT_REG == 2)
        ) out_buf (
            .clk       (clk),
            .reset     (reset),
            .valid_in  (~empty),
            .data_in   (data_out_t),
            .ready_in  (ready_out_t),            
            .valid_out (valid_out),
            .data_out  (data_out),            
            .ready_out (ready_out)
        );

    end

endmodule
`TRACING_ON

其中关于UNUSED_VAR的定义见于hw/rtl/VX_platform.vh,具体如下:

`define UNUSED_VAR(x)   if (1) begin \
                            /* verilator lint_off UNUSED */ \
                            wire [$bits(x)-1:0] __x = x; \
                            /* verilator lint_on UNUSED */ \
                        end

该函数的功能是将x赋给__x,其中$bitsSV内置函数,用于获取变量的位宽。其实就是一个初始化的wire语句。
因此关于SIZE=0的这段条件体内容:

        `UNUSED_VAR (clk)
        `UNUSED_VAR (reset)

        assign valid_out = valid_in;
        assign data_out  = data_in;
        assign ready_in  = ready_out;

其实就是走个过场,表明不接受任何core request。现在size=2,所以关键在接下来两个pipe_bufferskid_buffer

1.1.1 VX_pipe_buffer模块解读

该模块见于hw/rtl/libs/VX_pipe_buffer.sv,代码如下:

`include "VX_platform.vh"

`TRACING_OFF
module VX_pipe_buffer #(
    parameter DATAW    = 1,
    parameter PASSTHRU = 0
) ( 
    input  wire             clk,
    input  wire             reset,
    input  wire             valid_in,
    output wire             ready_in,        
    input  wire [DATAW-1:0] data_in,
    output wire [DATAW-1:0] data_out,
    input  wire             ready_out,
    output wire             valid_out
); 
    if (PASSTHRU != 0) begin
        `UNUSED_VAR (clk)
        `UNUSED_VAR (reset)
        assign ready_in  = ready_out;
        assign valid_out = valid_in;        
        assign data_out  = data_in;
    end else begin
        wire stall = valid_out && ~ready_out;

        VX_pipe_register #(
            .DATAW	(1 + DATAW),
            .RESETW (1)
        ) pipe_register (
            .clk      (clk),
            .reset    (reset),
            .enable	  (~stall),
            .data_in  ({valid_in,  data_in}),
            .data_out ({valid_out, data_out})
        );

        assign ready_in = ~stall;
    end

endmodule
`TRACING_ON

在这里还是能看到几个熟悉的信号的!

input  valid_in
output ready_in
output valid_out
input  ready_out
1.1.1.1 入门级握手及其代码实现——一种简单握手协议的解释

先回顾一个握手协议(该图来自牛客的数据累加输出):
在这里插入图片描述
其中信号的输入输出关系:

input  valid_a
output ready_a
output valid_b
input  ready_b

时序图含有的信息较多,观察时序图需要注意:
1.data_out是在已接收到4个数据后产生输出;
2.在data_out准备好,valid_b拉高时,如果下游的ready_b为低,表示下游此时不能接收本模块的数据,那么,将会拉低ready_a,以反压上游数据输入;
3.当下游ready_b拉高,且valid_b为高,表示模块与下游握手成功,valid_b在下一个时钟周期拉低;
4.当下游ready_b拉高,本来由于之前ready_b为低而反压上游的ready_a立即拉高,开始接收上游数据,注意,此细节也是体现了要求的数据传输无气泡。如果ready_a不是立即拉高,而是在下一个时钟周期拉高,那么本模块将会在下游握手成功后空一个时钟周期,才能开始接收上游数据,这样是不满足要求的。
5.要实现4个输入数据的累加,要用1个寄存器将先到达的数据累加之后进行缓存。当上游握手成功,将输入数据累加进寄存器;当累加完4个输入数据,且下游握手成功,将新的输入数据缓存进寄存器。注意,之所以这样设计,是为了不造成性能损失,而之前的累加结果,已经传给了下游。
6.需要计数器来计数接收到的数据数量,计数器在0-3之间循环。计数器初始值是0,每接收一个数据,计数器加1,当计数器再次循环到0时,表示已经接收到4个数据,可以输出累加结果。
因此其中关于4个信号之间的关系

`timescale 1ns/1ns

module valid_ready(
	input 				clk 		,   
	input 				rst_n		,
	input		[7:0]	data_in		,
	input				valid_a		,
	input	 			ready_b		,
 
 	output		 		ready_a		,
 	output	reg			valid_b		,
	output  reg [9:0] 	data_out
);

reg [1:0] count;

always @(posedge clk or negedge rst_n)
begin
	if (!rst_n) count <= 0;
	else if (valid_a && ready_a) count <= (count==2'd3) ? 0 : count + 1;
	else count <= count;
end

always @(posedge clk or negedge rst_n)
begin
	if (!rst_n) valid_b <= 0;
	else begin
		if (count==2'd3 && valid_a && ready_a) valid_b <= 1; // 注意和ready_b没关系
		else if (valid_b && ready_b) valid_b <= 0; // 这一步代表握手
	end
end

assign ready_a = !valid_b | ready_b; // 注意这个信号和valid_a无关!!!

always @(posedge clk or negedge rst_n)
begin
	if (!rst_n) data_out <= 0;
	else begin
		if (valid_a && ready_a && count==2'd0 && ready_b) data_out <= data_in;
		else if (valid_a && ready_a) data_out <= data_out + data_in;
	end
end

endmodule

所以总结下来就是input ready_b是用于确定下游模块是否(为1表示是,否则否)可以接受输出数据,而input valid_a是表示上游模块是否(为1表示是,否则否)准备好输入数据

那么中间两个输出信号则是为了建立握手,其中output ready_a表示可以接受输入数据传入(但不代表下游模块可以接受,因为还得等到输入数据全部传入完成以后,进入一个停止传入输入数据的状态才行),当且仅当input valid_aoutput ready_a之间均为高时可以传入输入数据

所以有:

1、input valid_a为高时,开始传输数据到内部,但是等到计数器满足条件时,output ready_a被拉低,表明数据已经传入完毕(也就是被ready_b反压的信号);
2、此处关于output ready_a后续被拉高是特殊的,因为题目要求无气泡。当output valid_b和input ready_b都拉高时,表明4个内部数据已经全部处理完毕,且这个过程只需要1拍,那么在处理这些数据的同时其实可以允许输入数据进入内部,所以在input ready_b被拉高的同时,同时拉高output ready_a。

同理,output valid_b表示输入数据传送好了,等待下游模块来取(意味着已经进入了停止传输输入数据的状态,但不意味着下游模块可以取了)。当且仅当,output valid_binput ready_b同时拉高时才可以让下游模块接受已经传入的数据。当然下游模块接受结束以后,output valid_b要被拉低,因为此时内部已经没有传入的数据了

所以有:

1、input valid_a 和 output ready_a 均为高,且计数到一定值,output valid_b被拉高,表明数据已经传入完毕;
2、output valid_b 和 input ready_b 均为高,下一拍output valid_b必须拉低,表明数据已经处理完了。

在基本理解握手协议以后,世界线收束回来!

1.1.1.2 世界线收束——VX_pipe_buffer的核心代码解释

VX_pipe_buffer中的核心代码则是:

        wire stall = valid_out && ~ready_out;

        VX_pipe_register #(
            .DATAW	(1 + DATAW),
            .RESETW (1)
        ) pipe_register (
            .clk      (clk),
            .reset    (reset),
            .enable	  (~stall),
            .data_in  ({valid_in,  data_in}),
            .data_out ({valid_out, data_out})
        );

        assign ready_in = ~stall;

其中stall指的就是output valid_outinput ready_out之间前者拉高后者拉低时的一段时间。也就是下游模块尚未准备好接受数据

其中的:

        wire stall = valid_out && ~ready_out;
        assign ready_in = ~stall;

刚和下图中的valid_bready_bready_a对应!
在这里插入图片描述
按照后面的程序来看,valid_out的数据来源是valid_in
其实后面的程序和上面那个传4个数据类似,但是只传输1个数据,且valid_out信号只是valid_in延迟一拍,仅在~stall阶段传输。

1.1.1.3 VX_pipe_register模块解读与分析握手协议

关于VX_pipe_register见于hw/rtl/libs/VX_pipe_register.sv,代码如下:

`include "VX_platform.vh"

`TRACING_OFF
module VX_pipe_register #( 
    parameter DATAW  = 1, 
    parameter RESETW = 0, 
    parameter DEPTH  = 1
) (
    input wire              clk,
    input wire              reset,
    input wire              enable,
    input wire [DATAW-1:0]  data_in,
    output wire [DATAW-1:0] data_out
);
    if (DEPTH == 0)     // DEPTH 为 0
    begin        
        `UNUSED_VAR (clk)
        `UNUSED_VAR (reset)
        `UNUSED_VAR (enable)
        assign data_out = data_in;  
    end 
    else if (DEPTH == 1)  // DEPTH 为 1
    begin        
        if (RESETW == 0) begin
            `UNUSED_VAR (reset)
            reg [DATAW-1:0] value;

            always @(posedge clk) begin
                if (enable) begin
                    value <= data_in;
                end
            end
            assign data_out = value;
        end 
        
        else if (RESETW == DATAW) begin
            reg [DATAW-1:0] value;

            always @(posedge clk) begin
                if (reset) begin
                    value <= RESETW'(0);
                end else if (enable) begin
                    value <= data_in;
                end
            end
            assign data_out = value;
        end 
        
        else begin  // 是这个
            reg [DATAW-RESETW-1:0] value_d;
            reg [RESETW-1:0]       value_r;

            always @(posedge clk) begin
                if (reset) begin
                    value_r <= RESETW'(0);
                end else if (enable) begin
                    value_r <= data_in[DATAW-1:DATAW-RESETW];
                end
            end

            always @(posedge clk) begin
                if (enable) begin
                    value_d <= data_in[DATAW-RESETW-1:0];
                end
            end        
            assign data_out = {value_r, value_d};
        end
    end 
    
    else begin     // DEPTH ≥ 2
        wire [DEPTH:0][DATAW-1:0] data_delayed;        
        assign data_delayed[0] = data_in;
        for (genvar i = 1; i <= DEPTH; ++i) begin
            VX_pipe_register #(
                .DATAW  (DATAW),
                .RESETW (RESETW)
            ) pipe_reg (
                .clk      (clk),
                .reset    (reset),
                .enable   (enable),
                .data_in  (data_delayed[i-1]),
                .data_out (data_delayed[i])
            );
        end
        assign data_out = data_delayed[DEPTH];
    end

endmodule
`TRACING_ON

满足条件的核心代码是:

            reg [DATAW-RESETW-1:0] value_d;
            reg [RESETW-1:0]       value_r;

            always @(posedge clk) begin
                if (reset) begin
                    value_r <= RESETW'(0);
                end else if (enable) begin
                    value_r <= data_in[DATAW-1:DATAW-RESETW];
                end
            end

            always @(posedge clk) begin
                if (enable) begin
                    value_d <= data_in[DATAW-RESETW-1:0];
                end
            end        
            assign data_out = {value_r, value_d};

按照前面的参数:

value_r <= data_in[DATAW-1:DATAW-RESETW];
//其实就是
value_r <= data_in[W+1-1:W+1-1]; // 也就是最高位的valid_in

value_d <= data_in[DATAW-RESETW-1:0];
//其实就是
value_d <= data_in[W+1-1-1:0]; // 也就是后面拼接的data_in

刚好对应:

.data_in  ({valid_in,  data_in}),
.data_out ({valid_out, data_out})

根据reset最高位的valid_in被拉低,经过stallwire语句后拉低,底下那个enable被拉高,所以允许数据传输:
1、如果此时valid_in为低,那么下一拍valid_out为低,表示接着往内部传输数据。
2、如果此时valid_in为高,那么下一拍valid_out为高,下一拍的stallready_out决定,如果ready_out0表示下游模块没准备好接收数据,此时stall1,出现阻塞,数据不再传入内部,等待ready_out1。如果ready_out1表示下游模块开始接收数据,此时stall0,表示上游模块可以接着往内部送入数据。

下游模块接收数据上游模块送入数据之间不存在气泡

关于为什么又出现VX_pipe_register模块的例化,这个问题放在1.1.4展开。

1.1.2 VX_skid_buffer模块解读

关于VX_skid_buffer模块的代码见于hw/rtl/libs/VX_skid_buffer.sv,代码如下:

`include "VX_platform.vh"

`TRACING_OFF
module VX_skid_buffer #(
    parameter DATAW    = 32,
    parameter PASSTHRU = 0,
    parameter HALF_BW  = 0,
    parameter OUT_REG  = 0
) ( 
    input  wire             clk,
    input  wire             reset,
    
    input  wire             valid_in,
    output wire             ready_in,        
    input  wire [DATAW-1:0] data_in,

    output wire [DATAW-1:0] data_out,
    input  wire             ready_out,
    output wire             valid_out
);
    if (PASSTHRU != 0) begin

        `UNUSED_VAR (clk)
        `UNUSED_VAR (reset)

        assign valid_out = valid_in;
        assign data_out  = data_in;
        assign ready_in  = ready_out;

    end else if (HALF_BW != 0) begin

        VX_toggle_buffer #(
            .DATAW (DATAW)
        ) toggle_buffer (
            .clk       (clk),
            .reset     (reset),
            .valid_in  (valid_in),
            .data_in   (data_in),
            .ready_in  (ready_in),
            .valid_out (valid_out),
            .data_out  (data_out),
            .ready_out (ready_out)
        );

    end else begin

        VX_stream_buffer #(
            .DATAW (DATAW),
            .OUT_REG (OUT_REG)
        ) stream_buffer (
            .clk       (clk),
            .reset     (reset),
            .valid_in  (valid_in),
            .data_in   (data_in),
            .ready_in  (ready_in),
            .valid_out (valid_out),
            .data_out  (data_out),
            .ready_out (ready_out)
        );

    end

endmodule
`TRACING_ON

其中3个parameter的情况如下:

            .DATAW   (DATAW),  
            .HALF_BW (OUT_REG == 2),
            .OUT_REG (OUT_REG)
// 开头分析过:
// DATAW为33
.DATAW   (`CS_WORD_WIDTH + TAG_WIDTH),
// CORE_REQ_BUF_ENABLE为1,因此SIZE为2
.SIZE    (CORE_REQ_BUF_ENABLE ? `TO_OUT_BUF_SIZE(CORE_OUT_BUF) : 0),
// OUT_REG为2
.OUT_REG (`TO_OUT_BUF_REG(CORE_OUT_BUF))

那么满足条件的模块VX_toggle_buffer

1.1.2.1 VX_toggle_buffer模块解读与分析握手协议

VX_toggle_buffer模块见于hw/rtl/libs/VX_toggle_buffer.sv,代码如下:

`include "VX_platform.vh"

`TRACING_OFF
module VX_toggle_buffer #(
   parameter DATAW    = 1,
   parameter PASSTHRU = 0
) ( 
   input  wire             clk,
   input  wire             reset,
   input  wire             valid_in,
   output wire             ready_in,        
   input  wire [DATAW-1:0] data_in,
   output wire [DATAW-1:0] data_out,
   input  wire             ready_out,
   output wire             valid_out
); 
   if (PASSTHRU != 0) begin
       `UNUSED_VAR (clk)
       `UNUSED_VAR (reset)
       assign ready_in  = ready_out;
       assign valid_out = valid_in;        
       assign data_out  = data_in;
   end else begin
       reg [DATAW-1:0] buffer;
       reg has_data;

       always @(posedge clk) begin
           if (reset) begin
               has_data <= 0;
           end else begin
               if (~has_data) begin
                   has_data <= valid_in;
               end else if (ready_out) begin
                   has_data <= 0;
               end 
           end
           if (~has_data) begin
               buffer <= data_in;
           end
       end

       assign ready_in  = ~has_data;
       assign valid_out = has_data;
       assign data_out  = buffer;
   end

endmodule
`TRACING_ON

这里又是一个握手协议
首先在reset为高的情况下,has_data被拉低为0
has_data被拉低为0的情况下,ready_in为高,valid_out为低,这俩信号是合理的,因为ready_in为高表明上游模块正在将输入数据传送到内部,valid_out为低说明此时上游模块的数据还没到送给下游模块的时候。

与此同时,has_data因为是0,所以下一拍data_in会送到内部(也就是buffer),同时has_data的下一拍也被注定了,也就是使用valid_in的当前值,按照握手协议,数据传送只发生在valid_inready_in同时拉高的情况下,因此has_data下一拍只能为高。同时下一拍buffer会把数据交给data_out

如果此刻has_data为高,ready_in被拉低,valid_out被拉高,这是合理的,因为上游模块的输入数据已经进入到buffer内,valid_out被拉高是为了提示下游模块可以准备接收数据了,此时ready_out为1,且在这一拍buffer刚好把数据交给data_out

ready_out被拉高后,has_data会被拉低。又开始重复reset的操作!

顺带解读涉及的另一个模块VX_stream_buffer模块。

1.1.2.2 VX_stream_buffer模块解读与分析握手协议

关于VX_stream_buffer模块见于hw/rtl/libs/VX_stream_buffer.sv,代码如下:

`include "VX_platform.vh"

`TRACING_OFF
module VX_stream_buffer #(
    parameter DATAW    = 1,
	parameter OUT_REG  = 0,
    parameter PASSTHRU = 0
) ( 
    input  wire             clk,
    input  wire             reset,
    input  wire             valid_in,
    output wire             ready_in,        
    input  wire [DATAW-1:0] data_in,
    output wire [DATAW-1:0] data_out,
    input  wire             ready_out,
    output wire             valid_out
); 
    if (PASSTHRU != 0) begin    // 第一层分支
        `UNUSED_VAR (clk)
        `UNUSED_VAR (reset)
        assign ready_in  = ready_out;
        assign valid_out = valid_in;        
        assign data_out  = data_in;
    end else begin
		if (OUT_REG != 0) begin    // 第二层分支

			reg [DATAW-1:0] data_out_r;
			reg [DATAW-1:0] buffer;
			reg             valid_out_r;
			reg             use_buffer;
			
			wire push = valid_in && ready_in;
			wire stall_out = valid_out_r && ~ready_out;
			
			always @(posedge clk) begin
				if (reset) begin
					valid_out_r <= 0; 
					use_buffer  <= 0;
				end else begin
					if (ready_out) begin
						use_buffer <= 0;
					end else if (valid_in && valid_out) begin
						use_buffer <= 1;
					end
					if (~stall_out) begin
						valid_out_r <= valid_in || use_buffer;
					end
				end
			end

			always @(posedge clk) begin
				if (push) begin
					buffer <= data_in;
				end
				if (~stall_out) begin
					data_out_r <= use_buffer ? buffer : data_in;
				end
			end

			assign ready_in  = ~use_buffer;
			assign valid_out = valid_out_r;
			assign data_out  = data_out_r;

		end else begin  // 第三层分支

			reg [1:0][DATAW-1:0] shift_reg;
			reg valid_out_r, ready_in_r, rd_ptr_r;

			wire push = valid_in && ready_in;
			wire pop = valid_out_r && ready_out;

			always @(posedge clk) begin
				if (reset) begin
					valid_out_r <= 0;
					ready_in_r  <= 1;
					rd_ptr_r    <= 1;
				end else begin
					if (push) begin
						if (!pop) begin                            
							ready_in_r  <= rd_ptr_r;
							valid_out_r <= 1;
						end
					end else if (pop) begin
						ready_in_r  <= 1;
						valid_out_r <= rd_ptr_r;
					end
					rd_ptr_r <= rd_ptr_r ^ (push ^ pop);
				end                   
			end

			always @(posedge clk) begin
				if (push) begin
					shift_reg[1] <= shift_reg[0];
					shift_reg[0] <= data_in;
				end
			end

			assign ready_in  = ready_in_r;
			assign valid_out = valid_out_r;
			assign data_out  = shift_reg[rd_ptr_r];
		end
    end

endmodule
`TRACING_ON

这里也有2握手协议,我们分开来讲。按照代码中3个分支,我们只看第2个分支和第3个分支。
先看第2个分支:

			reg [DATAW-1:0] data_out_r;
			reg [DATAW-1:0] buffer;
			reg             valid_out_r;
			reg             use_buffer;
			
			wire push = valid_in && ready_in;
			wire stall_out = valid_out_r && ~ready_out;
			
			always @(posedge clk) begin
				if (reset) begin
					valid_out_r <= 0; 
					use_buffer  <= 0;
				end else begin
					if (ready_out) begin
						use_buffer <= 0;
					end else if (valid_in && valid_out) begin
						use_buffer <= 1;
					end
					if (~stall_out) begin
						valid_out_r <= valid_in || use_buffer;
					end
				end
			end

			always @(posedge clk) begin
				if (push) begin
					buffer <= data_in;
				end
				if (~stall_out) begin
					data_out_r <= use_buffer ? buffer : data_in;
				end
			end

			assign ready_in  = ~use_buffer;
			assign valid_out = valid_out_r;
			assign data_out  = data_out_r;

这一行的wire push = valid_in && ready_in;表示入栈,也就是允许data_in进入buffer

第一拍:在检测reset为高时,valid_out_r下一拍为0,use_buffer下一拍也为0。

// 现在过了一拍
第二拍:valid_out_r此时为0,use_buffer此时为0。此时stall_out为0,valid_out为0,ready_in为1。

下一拍会发生啥?
首先,valid_out_r下一拍为1;
其次,由于valid_in为1,push被拉高为1,那么buffer下一拍被赋予这一拍的data_in
随后,data_out_r下一拍被赋予data_in,因为use_buffer不是1;
之后,use_buffer下一拍保持为0。

//现在又过了一拍
第三拍:valid_out_r为1,data_out_r为上一拍的data_inbuffer为上一拍的data_indata_out为上一拍的data_inuse_buffer0ready_in为1,valid_out为1。stall_out因为valid_out_r为1和~ready_out为1,可以出现1。
第一种可能:push因为valid_in为1和ready_in为1,可以继续为1。
补充——第二种可能:push因为valid_in为0和ready_in为1,为0。

下一拍会发生啥?
首先,use_buffer下一拍为1,valid_out_r保持;
其次,这一拍data_in的数据赋予buffer;(补充第二种可能的结果:buffer保持不变且为第二拍的data_in
随后,data_out_r下一拍被赋予这一拍的data_in,因为use_buffer为0。

//现在又过了一拍
第四拍:valid_out_r为1,data_out_r是上一拍的data_inbuffer为第三拍的data_in(补充第二种可能的结果:buffer保持为第二拍的data_in),data_out为上一拍的data_inuse_buffer1(注意了!),ready_in为0,valid_out为1。push为0(其实很好理解,此刻valid_in不管怎么样,ready_in被拉低,说明输入数据被限制送到内部,ready_in被反压了)!stall_out完全取决于~ready_out了。
假如此时ready_out为1,这说明下游模块准备接收内部数据了,那么stall_out为0。

下一拍会发生啥?
首先,use_buffer下一拍为0,valid_out_r下一拍为1;
其次,下一拍data_out_r是数据来源就是这一拍的buffer
随后,buffer数据保持。

//现在又过了一拍
第五拍:valid_out_r为1,data_out_r就是上一拍的bufferbuffer还是第三拍(补充可能性:第二拍)的data_indata_out就是上一拍的buffer(也就是第二拍或者第三拍的data_in),use_buffer0(注意了!),ready_in为1,valid_out为1。push等待valid_in拉高。接下来的节奏和第三拍一样了。之后不再分析!

上面还是太复杂,还是简化以后看更加明白!

			//reg [DATAW-1:0] data_out_r;
			reg [DATAW-1:0] buffer;
			//reg             valid_out_r;
			//reg             use_buffer;
			
			//wire push = valid_in && ready_in;
			//wire stall_out = valid_out_r && ~ready_out;
			
			always @(posedge clk) begin
				if (reset) begin
					valid_out <= 0; 
					ready_in  <= 1;
				end else begin
					if (ready_out) begin
						ready_in <= 1;
					end else if (valid_in && valid_out) begin
						ready_in <= 0;
					end
					if (~valid_out || ready_out) begin
						valid_out <= valid_in || (~ready_in);
					end
				end
			end

			always @(posedge clk) begin
				if (valid_in && ready_in) begin
					buffer <= data_in;
				end
				if (~valid_out || ready_out) begin
					data_out <= ~ready_in ? buffer : data_in;
				end
			end

			//assign ready_in  = ~use_buffer;
			//assign valid_out = valid_out_r;
			//assign data_out  = data_out_r;

其实实现了从data_inbuffer内,至少隔了2拍(更多拍数取决于ready_out何时为1)后再传送给data_out。但是还是会出现data_in直接到data_out,所以作者的方案分为use_buffer=1use_buffer=0这两种情况!

再来看看第3个分支:

			reg [1:0][DATAW-1:0] shift_reg;
			reg valid_out_r, ready_in_r, rd_ptr_r;

			wire push = valid_in && ready_in;
			//wire pop = valid_out_r && ready_out;
			wire pop = valid_out && ready_out;

			always @(posedge clk) begin
				if (reset) begin
					valid_out <= 0;
					ready_in  <= 1;
					rd_ptr_r    <= 1;
				end else begin
					if (valid_in && ready_in) begin
						if (~valid_out || ~ready_out) begin                            
							ready_in  <= rd_ptr_r;
							valid_out <= 1;
						end
					end 
					
					else if (valid_out && ready_out) begin
						ready_in  <= 1;
						valid_out <= rd_ptr_r;
					end
					
					rd_ptr_r <= rd_ptr_r ^ (push ^ pop);
					
				end                   
			end

			always @(posedge clk) begin
				if (valid_in && ready_in) begin
					shift_reg[1] <= shift_reg[0];
					shift_reg[0] <= data_in;
				end
			end

			//assign ready_in  = ready_in_r;
			//assign valid_out = valid_out_r;
			assign data_out  = shift_reg[rd_ptr_r];
		end
    end

简析一下:

rd_ptr_r <= rd_ptr_r ^ (push ^ pop);

首先,pushpop这两个操作在任何时候最多只能有一个1。

  1. 当push ^ pop = 1时,rd_ptr_r为1,则下一拍为0,否则下一拍为1。换言之,只要push或者pop有一个为1,那么rd_ptr_r将在0和1之间不断跳转。
  2. 当push ^ pop = 0时,rd_ptr_r为1,则下一拍为1,否则下一拍为0。换言之就是push和pop都为0(也就是既不入栈,也不出栈)时,rd_ptr_r保持不变。

那么分析就变得容易了,还是一套握手协议!

1.1.3 VX_fifo_queue模块解读

VX_fifo_queue模块见于hw/rtl/libs/VX_fifo_queue.sv,代码如下:

`include "VX_platform.vh"

`TRACING_OFF
module VX_fifo_queue #(
    parameter DATAW     = 1,
    parameter DEPTH     = 2,
    parameter ALM_FULL  = (DEPTH - 1),
    parameter ALM_EMPTY = 1,
    parameter OUT_REG   = 0,
    parameter LUTRAM    = 1,
    parameter SIZEW     = `CLOG2(DEPTH+1)
) ( 
    input  wire             clk,
    input  wire             reset,    
    input  wire             push,
    input  wire             pop,        
    input  wire [DATAW-1:0] data_in,
    output wire [DATAW-1:0] data_out,
    output wire             empty,      
    output wire             alm_empty,
    output wire             full,            
    output wire             alm_full,
    output wire [SIZEW-1:0] size
); 
    
    localparam ADDRW = `CLOG2(DEPTH);    

    `STATIC_ASSERT(ALM_FULL > 0, ("alm_full must be greater than 0!"))
    `STATIC_ASSERT(ALM_FULL < DEPTH, ("alm_full must be smaller than size!"))
    `STATIC_ASSERT(ALM_EMPTY > 0, ("alm_empty must be greater than 0!"))
    `STATIC_ASSERT(ALM_EMPTY < DEPTH, ("alm_empty must be smaller than size!"))
    `STATIC_ASSERT(`IS_POW2(DEPTH), ("size must be a power of 2!"))
    
    if (DEPTH == 1) begin

        reg [DATAW-1:0] head_r;
        reg size_r;

        always @(posedge clk) begin
            if (reset) begin
                head_r <= '0;
                size_r <= '0;                    
            end else begin
                `ASSERT(~push || ~full, ("runtime error: writing to a full queue"));
                `ASSERT(~pop || ~empty, ("runtime error: reading an empty queue"));
                if (push) begin
                    if (~pop) begin
                        size_r <= 1;
                    end
                end else if (pop) begin
                    size_r <= '0;
                end
                if (push) begin 
                    head_r <= data_in;
                end
            end
        end        

        assign data_out  = head_r;
        assign empty     = (size_r == 0);
        assign alm_empty = 1'b1;
        assign full      = (size_r != 0);
        assign alm_full  = 1'b1;
        assign size      = size_r;

    end else begin
        
        reg empty_r, alm_empty_r;
        reg full_r, alm_full_r;
        reg [ADDRW-1:0] used_r;
        wire [ADDRW-1:0] used_n;

        always @(posedge clk) begin
            if (reset) begin
                empty_r     <= 1;
                alm_empty_r <= 1;    
                full_r      <= 0;        
                alm_full_r  <= 0;
                used_r      <= '0;
            end else begin
                `ASSERT(~(push && ~pop) || ~full, ("runtime error: incrementing full queue"));
                `ASSERT(~(pop && ~push) || ~empty, ("runtime error: decrementing empty queue"));
                if (push) begin
                    if (~pop) begin
                        empty_r <= 0;
                        if (used_r == ADDRW'(ALM_EMPTY))
                            alm_empty_r <= 0;
                        if (used_r == ADDRW'(DEPTH-1))
                            full_r <= 1;
                        if (used_r == ADDRW'(ALM_FULL-1))
                            alm_full_r <= 1;
                    end
                end else if (pop) begin
                    full_r <= 0;
                    if (used_r == ADDRW'(ALM_FULL))
                        alm_full_r <= 0;            
                    if (used_r == ADDRW'(1))
                        empty_r <= 1;
                    if (used_r == ADDRW'(ALM_EMPTY+1))
                        alm_empty_r <= 1;
                end                
                used_r <= used_n;  
            end                   
        end

        if (DEPTH == 2) begin

            assign used_n = used_r ^ (push ^ pop);

            if (0 == OUT_REG) begin 

                reg [1:0][DATAW-1:0] shift_reg;

                always @(posedge clk) begin
                    if (push) begin
                        shift_reg[1] <= shift_reg[0];
                        shift_reg[0] <= data_in;
                    end
                end

                assign data_out = shift_reg[!used_r[0]];    
                
            end else begin

                reg [DATAW-1:0] data_out_r;
                reg [DATAW-1:0] buffer;

                always @(posedge clk) begin
                    if (push) begin
                        buffer <= data_in;
                    end
                    if (push && (empty_r || (used_r && pop))) begin
                        data_out_r <= data_in;
                    end else if (pop) begin
                        data_out_r <= buffer;
                    end
                end

                assign data_out = data_out_r;

            end
        
        end else begin
            
            assign used_n = $signed(used_r) + ADDRW'($signed(2'(push) - 2'(pop)));

            if (0 == OUT_REG) begin          

                reg [ADDRW-1:0] rd_ptr_r;
                reg [ADDRW-1:0] wr_ptr_r;
                
                always @(posedge clk) begin
                    if (reset) begin
                        rd_ptr_r <= '0;
                        wr_ptr_r <= '0;
                    end else begin
                        wr_ptr_r <= wr_ptr_r + ADDRW'(push);
                        rd_ptr_r <= rd_ptr_r + ADDRW'(pop);
                    end               
                end

                VX_dp_ram #(
                    .DATAW  (DATAW),
                    .SIZE   (DEPTH),
                    .LUTRAM (LUTRAM)
                ) dp_ram (
                    .clk(clk),
                    .read  (1'b1),
                    .write (push),                    
                    `UNUSED_PIN (wren),               
                    .waddr (wr_ptr_r),
                    .wdata (data_in),
                    .raddr (rd_ptr_r),
                    .rdata (data_out)
                );

            end else begin

                wire [DATAW-1:0] dout;
                reg [DATAW-1:0] dout_r;
                reg [ADDRW-1:0] wr_ptr_r;
                reg [ADDRW-1:0] rd_ptr_r;
                reg [ADDRW-1:0] rd_ptr_n_r;

                always @(posedge clk) begin
                    if (reset) begin  
                        wr_ptr_r   <= '0;
                        rd_ptr_r   <= '0;
                        rd_ptr_n_r <= 1;
                    end else begin
                        wr_ptr_r <= wr_ptr_r + ADDRW'(push);
                        if (pop) begin
                            rd_ptr_r <= rd_ptr_n_r;                       
                            if (DEPTH > 2) begin    
                                rd_ptr_n_r <= rd_ptr_r + ADDRW'(2);
                            end else begin // (DEPTH == 2);
                                rd_ptr_n_r <= ~rd_ptr_n_r;                            
                            end
                        end
                    end
                end

                wire going_empty;
                if (ALM_EMPTY == 1) begin
                    assign going_empty = alm_empty_r;
                end else begin
                    assign going_empty = (used_r == ADDRW'(1));
                end

                VX_dp_ram #(
                    .DATAW  (DATAW),
                    .SIZE   (DEPTH),
                    .LUTRAM (LUTRAM)
                ) dp_ram (
                    .clk   (clk),
                    .read  (1'b1),
                    .write (push),                    
                    `UNUSED_PIN (wren),               
                    .waddr (wr_ptr_r),
                    .wdata (data_in),
                    .raddr (rd_ptr_n_r),
                    .rdata (dout)
                ); 

                always @(posedge clk) begin
                    if (push && (empty_r || (going_empty && pop))) begin
                        dout_r <= data_in;
                    end else if (pop) begin
                        dout_r <= dout;
                    end
                end

                assign data_out = dout_r;
            end
        end
        
        assign empty     = empty_r;        
        assign alm_empty = alm_empty_r;
        assign full      = full_r;
        assign alm_full  = alm_full_r;
        assign size      = {full_r, used_r};        
    end

endmodule
`TRACING_ON

这段代码只配了一套时钟和复位,因此是同步FIFO

1.1.3.1 该模块的FIFO接口

该模块的FIFO接口如下:

    input  wire             clk,
    input  wire             reset,    
    input  wire             push,
    input  wire             pop,        
    input  wire [DATAW-1:0] data_in,
    output wire [DATAW-1:0] data_out,
    output wire             empty,      
    output wire             alm_empty,
    output wire             full,            
    output wire             alm_full,
    output wire [SIZEW-1:0] size

出现了alm_emptyalm_full,大概就是almost emptyalmost full两个信号!almost_fullalomst_empty信号在FIFO差一个深度就满/空的时候拉高。
一个粗糙的例子:

//增加almost信号判断满
always @(posedge clk or negedge rstn) begin
    if(!rstn)
    full <= 0;
    else if(al_full && wr_en && !rd_en)
    full <= 1;
    //else if(al_full && rd_en && !wr_en)
    else if(al_full && rd_en)
    full <= 0;
end
 
always@(posedge clk or negedge rstn)begin
    if(!rstn)
    empty <= 0;
    else if(al_empty && rd_en && !wr_en)
    empty <= 1;
    //else if(al_empty && wr_en && !rd_en)
    else if(al_empty && wr_en)
    empty <= 0;
end

我们还是先用牛客上的例子来回顾同步FIFO异步FIFO,该例子也就大概能看明白了。也可以补充参考FIFO相关

1.1.3.2 额外延申同步FIFO

首先,对于读写指针而言,通过计数器来确定2个指针之间的距离。

/**********************addr bin gen*************************/
reg 	[ADDR_WIDTH:0]	waddr;
reg 	[ADDR_WIDTH:0]	raddr;

always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		waddr <= 'd0;
	end 
	else if(!wfull && winc)begin
		waddr <= waddr + 1'd1;
	end
end
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		raddr <= 'd0;
	end 
	else if(!rempty && rinc)begin
		raddr <= raddr + 1'd1;
	end
end

随后基于waddrraddr两个计数器来确定空满情况。

/**********************full empty gen*************************/
wire		[ADDR_WIDTH : 0]	fifo_cnt;

assign fifo_cnt = (waddr[ADDR_WIDTH] == raddr[ADDR_WIDTH]) ? (waddr[ADDR_WIDTH:0] - raddr[ADDR_WIDTH:0]) :
                  (DEPTH + waddr[ADDR_WIDTH-1:0] - raddr[ADDR_WIDTH-1:0]);
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		wfull <= 'd0;
		rempty <= 'd0;
	end 
	else if(fifo_cnt == 'd0)begin
		rempty <= 1'd1;
	end
	else if(fifo_cnt == DEPTH)begin
		wfull <= 1'd1;
	end
	else begin
		wfull <= 'd0;
		rempty <= 'd0;
	end 
end

这里的fifo_cnt是因为考虑到计数器相比地址而言,多用了一位。高位用于指示是否出现套圈的情况。
之后就是利用空满信息确定write enableread enable

/**********************RAM*************************/
wire 	wen	;
wire	ren	;
wire 	wren;//high write
assign wen = winc & !wfull;
assign ren = rinc & !rempty;

dual_port_RAM #(.DEPTH(DEPTH),
				.WIDTH(WIDTH)
)dual_port_RAM(
	.wclk (clk),  
	.wenc (wen),  
	.waddr(waddr[ADDR_WIDTH-1:0]),  //深度对2取对数,得到地址的位宽。
	.wdata(wdata),       	//数据写入
	.rclk (clk), 
	.renc (ren), 
	.raddr(raddr[ADDR_WIDTH-1:0]),   //深度对2取对数,得到地址的位宽。
	.rdata(rdata)  		//数据输出
);

关于dual port RAM的verilog:

module dual_port_RAM #(parameter DEPTH = 16,
					   parameter WIDTH = 8)(
	 input wclk
	,input wenc
	,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
	,input [WIDTH-1:0] wdata      	//数据写入
	,input rclk
	,input renc
	,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
	,output reg [WIDTH-1:0] rdata 		//数据输出
);

reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

always @(posedge wclk) begin
	if(wenc)
		RAM_MEM[waddr] <= wdata;
end 

always @(posedge rclk) begin
	if(renc)
		rdata <= RAM_MEM[raddr];
end 

endmodule  

给出完整的同步FIFO的代码:

module sfifo#(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					clk		, 
	input 					rst_n	,
	input 					winc	,
	input 			 		rinc	,
	input 		[WIDTH-1:0]	wdata	,

	output reg				wfull	,
	output reg				rempty	,
	output wire [WIDTH-1:0]	rdata
);

parameter ADDR_WIDTH = $clog2(DEPTH);

/**********************addr bin gen*************************/
reg 	[ADDR_WIDTH:0]	waddr;
reg 	[ADDR_WIDTH:0]	raddr;

always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		waddr <= 'd0;
	end 
	else if(!wfull && winc)begin
		waddr <= waddr + 1'd1;
	end
end
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		raddr <= 'd0;
	end 
	else if(!rempty && rinc)begin
		raddr <= raddr + 1'd1;
	end
end

/**********************full empty gen*************************/
wire		[ADDR_WIDTH : 0]	fifo_cnt;

assign fifo_cnt = (waddr[ADDR_WIDTH] == raddr[ADDR_WIDTH]) ? (waddr[ADDR_WIDTH:0] - raddr[ADDR_WIDTH:0]) :
                  (DEPTH + waddr[ADDR_WIDTH-1:0] - raddr[ADDR_WIDTH-1:0]);
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		wfull <= 'd0;
		rempty <= 'd0;
	end 
	else if(fifo_cnt == 'd0)begin
		rempty <= 1'd1;
	end
	else if(fifo_cnt == DEPTH)begin
		wfull <= 1'd1;
	end
	else begin
		wfull <= 'd0;
		rempty <= 'd0;
	end 
end

/**********************RAM*************************/
wire 	wen	;
wire	ren	;
wire 	wren;//high write
assign wen = winc & !wfull;
assign ren = rinc & !rempty;

dual_port_RAM #(.DEPTH(DEPTH),
				.WIDTH(WIDTH)
)dual_port_RAM(
	.wclk (clk),  
	.wenc (wen),  
	.waddr(waddr[ADDR_WIDTH-1:0]),  //深度对2取对数,得到地址的位宽。
	.wdata(wdata),       	//数据写入
	.rclk (clk), 
	.renc (ren), 
	.raddr(raddr[ADDR_WIDTH-1:0]),   //深度对2取对数,得到地址的位宽。
	.rdata(rdata)  		//数据输出
);
endmodule
1.1.3.3 额外延申异步FIFO

异步FIFO略微麻烦点!但记住下图之后其实处理起来没那么麻烦!
在这里插入图片描述
异步FIFO结构如上图所示:

1.第1部分是双口RAM,用于数据的存储。
2.第2部分是数据写入控制器
3.第3部分是数据读取控制器
4.读指针同步器:使用写时钟的两级触发器采集读指针,输出到数据写入控制器。
5. 写指针同步器:使用读时钟的两级触发器采集写指针,输出到数据读取控制器。

本题解采用的空满判断的方式是用格雷码的比较来产生空满信号
在这里插入图片描述
如上图所示,使用4位格雷码作为深度为8的FIFO的读写指针。将格雷码转换成四位二进制数,使用二进制数低三位作为访问RAM的地址。与同步FIFO类似,当读写指针相等时,得出FIFO为空。
在这里插入图片描述
当写指针比读指针多循环RAM一周时,此时读写指针的最高位和次高位都相反,其余位相同,FIFO为满。
首先还是dual port RAM的verilog:

module dual_port_RAM #(parameter DEPTH = 16,
					   parameter WIDTH = 8)(
	 input wclk
	,input wenc
	,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
	,input [WIDTH-1:0] wdata      	//数据写入
	,input rclk
	,input renc
	,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
	,output reg [WIDTH-1:0] rdata 		//数据输出
);

reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

always @(posedge wclk) begin
	if(wenc)
		RAM_MEM[waddr] <= wdata;
end 

always @(posedge rclk) begin
	if(renc)
		rdata <= RAM_MEM[raddr];
end 

endmodule  

接下来就是异步FIFO

module asyn_fifo#(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					wclk	, 
	input 					rclk	,   
	input 					wrstn	,
	input					rrstn	,
	input 					winc	,
	input 			 		rinc	,
	input 		[WIDTH-1:0]	wdata	,

	output wire				wfull	,
	output wire				rempty	,
	output wire [WIDTH-1:0]	rdata
);

parameter ADDR_WIDTH = $clog2(DEPTH);

/**********************addr bin gen*************************/
reg 	[ADDR_WIDTH:0]	waddr_cnt;
reg 	[ADDR_WIDTH:0]	raddr_cnt;

always @(posedge wclk or negedge wrstn) begin
	if(~wrstn) begin
		waddr_cnt <= 'd0;
	end 
	else if(!wfull && winc)begin
		waddr_cnt <= waddr_cnt + 1'd1;
	end
end
always @(posedge rclk or negedge rrstn) begin
	if(~rrstn) begin
		raddr_cnt <= 'd0;
	end 
	else if(!rempty && rinc)begin
		raddr_cnt <= raddr_cnt + 1'd1;
	end
end

/**********************addr gray gen*************************/
wire 	[ADDR_WIDTH:0]	waddr_gray;
wire 	[ADDR_WIDTH:0]	raddr_gray;
reg 	[ADDR_WIDTH:0]	wptr;
reg 	[ADDR_WIDTH:0]	rptr;
assign waddr_gray = waddr_cnt ^ (waddr_cnt>>1);
assign raddr_gray = raddr_cnt ^ (raddr_cnt>>1);
always @(posedge wclk or negedge wrstn) begin 
	if(~wrstn) begin
		wptr <= 'd0;
	end 
	else begin
		wptr <= waddr_gray;
	end
end
always @(posedge rclk or negedge rrstn) begin 
	if(~rrstn) begin
		rptr <= 'd0;
	end 
	else begin
		rptr <= raddr_gray;
	end
end
/**********************syn addr gray*************************/
reg		[ADDR_WIDTH:0]	wptr_buff;
reg		[ADDR_WIDTH:0]	wptr_syn;
reg		[ADDR_WIDTH:0]	rptr_buff;
reg		[ADDR_WIDTH:0]	rptr_syn;
always @(posedge wclk or negedge wrstn) begin 
	if(~wrstn) begin
		rptr_buff <= 'd0;
		rptr_syn <= 'd0;
	end 
	else begin
		rptr_buff <= rptr;
		rptr_syn <= rptr_buff;
	end
end
always @(posedge rclk or negedge rrstn) begin 
	if(~rrstn) begin
		wptr_buff <= 'd0;
		wptr_syn <= 'd0;
	end 
	else begin
		wptr_buff <= wptr;
		wptr_syn <= wptr_buff;
	end
end
/**********************full empty gen*************************/
assign wfull = (wptr == {~rptr_syn[ADDR_WIDTH:ADDR_WIDTH-1],rptr_syn[ADDR_WIDTH-2:0]});
assign rempty = (rptr == wptr_syn);

/**********************RAM*************************/
wire 	wen	;
wire	ren	;
wire 	wren;//high write
wire [ADDR_WIDTH-1:0]	waddr;
wire [ADDR_WIDTH-1:0]	raddr;
assign wen = winc & !wfull;
assign ren = rinc & !rempty;
assign waddr = waddr_cnt[ADDR_WIDTH-1:0];
assign raddr = raddr_cnt[ADDR_WIDTH-1:0];

dual_port_RAM #(.DEPTH(DEPTH),
				.WIDTH(WIDTH)
)dual_port_RAM(
	.wclk (wclk),  
	.wenc (wen),  
	.waddr(waddr[ADDR_WIDTH-1:0]),  //深度对2取对数,得到地址的位宽。
	.wdata(wdata),       	//数据写入
	.rclk (rclk), 
	.renc (ren), 
	.raddr(raddr[ADDR_WIDTH-1:0]),   //深度对2取对数,得到地址的位宽。
	.rdata(rdata)  		//数据输出
);

endmodule

上方注释比较好,且对异步FIFO做了比较详细的阐述,因此理解难度不大。

1.1.3.4 VX_dp_ram模块讲解

不管是同步FIFO还是异步FIFO,都涉及这么三大部分:

1、读计数器、写计数器;
2、基于以上2个计数器判断空满状态(如果是异步,首先转格雷码,随后需要通过2级触发器组成的同步器来跨时钟域同步格雷码计数结果)
3、将基于2得到的空满信号和其他信号共同例化ram模块

所以回到VX_fifo_queue模块中有VX_dp_ram也挺好理解的!甚至我们可以大胆猜测dp就是dual port。回到该模块的具体代码:

`include "VX_platform.vh"

`TRACING_OFF
module VX_dp_ram #(
    parameter DATAW       = 1,
    parameter SIZE        = 1,
    parameter WRENW       = 1,
    parameter OUT_REG     = 0,
    parameter NO_RWCHECK  = 0,
    parameter LUTRAM      = 0,    
    parameter INIT_ENABLE = 0,
    parameter INIT_FILE   = "",
    parameter [DATAW-1:0] INIT_VALUE = 0,
    parameter ADDRW       = `LOG2UP(SIZE)
) ( 
    input wire               clk,
    input wire               read,
    input wire               write,
    input wire [WRENW-1:0]   wren,
    input wire [ADDRW-1:0]   waddr,        
    input wire [DATAW-1:0]   wdata,
    input wire [ADDRW-1:0]   raddr,
    output wire [DATAW-1:0]  rdata
);
    localparam WSELW = DATAW / WRENW;
    `STATIC_ASSERT((WRENW * WSELW == DATAW), ("invalid parameter"))

`define RAM_INITIALIZATION                         \
    if (INIT_ENABLE != 0) begin                    \
        if (INIT_FILE != "") begin                 \
            initial $readmemh(INIT_FILE, ram);     \
        end else begin                             \
            initial                                \
                for (integer i = 0; i < SIZE; ++i) \
                    ram[i] = INIT_VALUE;           \
        end                                        \
    end
    
    `UNUSED_VAR (read)

`ifdef SYNTHESIS
    if (WRENW > 1) begin
    `ifdef QUARTUS
        if (LUTRAM != 0) begin
            if (OUT_REG != 0) begin        
                reg [DATAW-1:0] rdata_r;
                `USE_FAST_BRAM reg [WRENW-1:0][WSELW-1:0] ram [SIZE-1:0];
                `RAM_INITIALIZATION
                always @(posedge clk) begin
                    if (write) begin
                        for (integer i = 0; i < WRENW; ++i) begin
                            if (wren[i])
                                ram[waddr][i] <= wdata[i * WSELW +: WSELW];
                        end
                    end
                    if (read) begin
                        rdata_r <= ram[raddr];
                    end
                end
                assign rdata = rdata_r;
            end else begin
                `USE_FAST_BRAM reg [WRENW-1:0][WSELW-1:0] ram [SIZE-1:0];
                `RAM_INITIALIZATION
                always @(posedge clk) begin
                    if (write) begin
                        for (integer i = 0; i < WRENW; ++i) begin
                            if (wren[i])
                                ram[waddr][i] <= wdata[i * WSELW +: WSELW];
                        end
                    end
                end
                assign rdata = ram[raddr];
            end
        end else begin
            if (OUT_REG != 0) begin
                reg [DATAW-1:0] rdata_r;
                reg [WRENW-1:0][WSELW-1:0] ram [SIZE-1:0];
                `RAM_INITIALIZATION
                always @(posedge clk) begin
                    if (write) begin
                        for (integer i = 0; i < WRENW; ++i) begin
                            if (wren[i])
                                ram[waddr][i] <= wdata[i * WSELW +: WSELW];
                        end
                    end
                    if (read) begin
                        rdata_r <= ram[raddr];
                    end
                end
                assign rdata = rdata_r;
            end else begin
                if (NO_RWCHECK != 0) begin
                    `NO_RW_RAM_CHECK reg [WRENW-1:0][WSELW-1:0] ram [SIZE-1:0];
                    `RAM_INITIALIZATION
                    always @(posedge clk) begin
                        if (write) begin
                            for (integer i = 0; i < WRENW; ++i) begin
                                if (wren[i])
                                    ram[waddr][i] <= wdata[i * WSELW +: WSELW];
                            end
                        end
                    end
                    assign rdata = ram[raddr];
                end else begin
                    reg [WRENW-1:0][WSELW-1:0] ram [SIZE-1:0];
                    `RAM_INITIALIZATION
                    always @(posedge clk) begin
                        if (write) begin
                            for (integer i = 0; i < WRENW; ++i) begin
                                if (wren[i])
                                    ram[waddr][i] <= wdata[i * WSELW +: WSELW];
                            end
                        end
                    end
                    assign rdata = ram[raddr];
                end
            end
        end
    `else
        // default synthesis
        if (LUTRAM != 0) begin
            `USE_FAST_BRAM reg [DATAW-1:0] ram [SIZE-1:0];
            `RAM_INITIALIZATION
            if (OUT_REG != 0) begin            
                reg [DATAW-1:0] rdata_r;
                always @(posedge clk) begin
                    if (write) begin
                        for (integer i = 0; i < WRENW; ++i) begin
                            if (wren[i])
                                ram[waddr][i * WSELW +: WSELW] <= wdata[i * WSELW +: WSELW];
                        end
                    end
                    if (read) begin
                        rdata_r <= ram[raddr];
                    end
                end
                assign rdata = rdata_r;
            end else begin
                always @(posedge clk) begin
                    if (write) begin
                        for (integer i = 0; i < WRENW; ++i) begin
                            if (wren[i])
                                ram[waddr][i * WSELW +: WSELW] <= wdata[i * WSELW +: WSELW];
                        end
                    end
                end
                assign rdata = ram[raddr];
            end
        end else begin
            if (OUT_REG != 0) begin
                reg [DATAW-1:0] ram [SIZE-1:0];
                reg [DATAW-1:0] rdata_r;
                `RAM_INITIALIZATION
                always @(posedge clk) begin
                    if (write) begin
                        for (integer i = 0; i < WRENW; ++i) begin
                            if (wren[i])
                                ram[waddr][i * WSELW +: WSELW] <= wdata[i * WSELW +: WSELW];
                        end
                    end
                    if (read) begin
                        rdata_r <= ram[raddr];
                    end
                end
                assign rdata = rdata_r;
            end else begin
                if (NO_RWCHECK != 0) begin
                    `NO_RW_RAM_CHECK reg [DATAW-1:0] ram [SIZE-1:0];
                    `RAM_INITIALIZATION
                    always @(posedge clk) begin
                        if (write) begin
                            for (integer i = 0; i < WRENW; ++i) begin
                                if (wren[i])
                                    ram[waddr][i * WSELW +: WSELW] <= wdata[i * WSELW +: WSELW];
                            end
                        end
                    end
                    assign rdata = ram[raddr];
                end else begin
                    reg [DATAW-1:0] ram [SIZE-1:0];
                    `RAM_INITIALIZATION
                    always @(posedge clk) begin
                        if (write) begin
                            for (integer i = 0; i < WRENW; ++i) begin
                                if (wren[i])
                                    ram[waddr][i * WSELW +: WSELW] <= wdata[i * WSELW +: WSELW];
                            end
                        end
                    end
                    assign rdata = ram[raddr];
                end
            end
        end
    `endif
    end else begin
        // (WRENW == 1)
        if (LUTRAM != 0) begin
            `USE_FAST_BRAM reg [DATAW-1:0] ram [SIZE-1:0];
            `RAM_INITIALIZATION
            if (OUT_REG != 0) begin            
                reg [DATAW-1:0] rdata_r;
                always @(posedge clk) begin
                    if (write) begin
                        ram[waddr] <= wdata;
                    end
                    if (read) begin
                        rdata_r <= ram[raddr];
                    end
                end
                assign rdata = rdata_r;
            end else begin
                always @(posedge clk) begin
                    if (write) begin
                        ram[waddr] <= wdata;
                    end
                end
                assign rdata = ram[raddr];
            end
        end else begin
            if (OUT_REG != 0) begin
                reg [DATAW-1:0] ram [SIZE-1:0];
                reg [DATAW-1:0] rdata_r;
                `RAM_INITIALIZATION
                always @(posedge clk) begin
                    if (write) begin
                        ram[waddr] <= wdata;
                    end
                    if (read) begin
                        rdata_r <= ram[raddr];
                    end
                end
                assign rdata = rdata_r;
            end else begin
                if (NO_RWCHECK != 0) begin
                    `NO_RW_RAM_CHECK reg [DATAW-1:0] ram [SIZE-1:0];
                    `RAM_INITIALIZATION
                    always @(posedge clk) begin
                        if (write) begin
                            ram[waddr] <= wdata;
                        end
                    end
                    assign rdata = ram[raddr];
                end else begin
                    reg [DATAW-1:0] ram [SIZE-1:0];
                    `RAM_INITIALIZATION
                    always @(posedge clk) begin
                        if (write) begin
                            ram[waddr] <= wdata;
                        end
                    end
                    assign rdata = ram[raddr];
                end
            end
        end
    end    
`else
    // RAM emulation
    reg [DATAW-1:0] ram [SIZE-1:0];
    `RAM_INITIALIZATION

    wire [DATAW-1:0] ram_n;
    for (genvar i = 0; i < WRENW; ++i) begin
        assign ram_n[i * WSELW +: WSELW] = ((WRENW == 1) | wren[i]) ? wdata[i * WSELW +: WSELW] : ram[waddr][i * WSELW +: WSELW];
    end

    if (OUT_REG != 0) begin        
        reg [DATAW-1:0] rdata_r;        
        always @(posedge clk) begin
            if (write) begin
                ram[waddr] <= ram_n;
            end
            if (read) begin
                rdata_r <= ram[raddr];
            end
        end
        assign rdata = rdata_r;
    end else begin        
        reg [DATAW-1:0] prev_data;
        reg [ADDRW-1:0] prev_waddr;
        reg prev_write;
        always @(posedge clk) begin
            if (write) begin
                ram[waddr] <= ram_n;
            end
            prev_write <= (| wren);
            prev_data  <= ram[waddr];
            prev_waddr <= waddr;
        end            
        if (LUTRAM || !NO_RWCHECK) begin
            `UNUSED_VAR (prev_write)
            `UNUSED_VAR (prev_data)
            `UNUSED_VAR (prev_waddr)
            assign rdata = ram[raddr];
        end else begin
            assign rdata = (prev_write && (prev_waddr == raddr)) ? prev_data : ram[raddr];
        end
    end
`endif

endmodule
`TRACING_ON

这里面出现了很多条件分支:

ifdef SYNTHESIS:这是一个条件编译指令,它指示编译器在进行综合时执行相应的代码路径。在这里,它处理了不同的综合工具(如 Quartus)和一些参数的情况。

if (WRENW > 1):检查写使能端口 WRENW 的宽度是否大于 1。如果是,进入下一级条件。

ifdef QUARTUS:这是对 Quartus 综合工具的特定条件检查。 // 这是基于Vortex GPGPU仿真平台的考量

if (LUTRAM != 0):检查是否使用 LUT RAM。 // 熟悉vivado中会出现LUTRAM,但是不清楚怎么控制使用还是不使用LUTRAM资源

if (OUT_REG != 0):检查是否需要输出寄存器。

else 分支(RAM emulation):如果编译器未定义 SYNTHESIS,则执行 RAM 的仿真模式。

基本功能可以参考我额外提到的同步FIFO异步FIFO中的dual port ram的代码。

1.1.4 为什么又出现一个VX_elastic_buffer模块?

还想再问一个问题,为什么VX_elastic_buffer模块里面可以实例化VX_elastc_buffer?以前我没接触过这个操作!

在Verilog代码中,可以在模块内部实例化同一类型的模块。这种方式被称为递归实例化或者自实例化(self-instantiation)

如果 SIZE == 0,它直接将输入信号连接到输出信号,没有任何缓冲。
如果 SIZE == 1,它实例化了 VX_pipe_buffer。
如果 SIZE == 2,它实例化了 VX_skid_buffer。
对于其他情况,它实现了一个深度为 SIZE 的 FIFO 队列 (VX_fifo_queue),并在 FIFO 的基础上实现了一个更复杂的弹性缓冲器。

1.2 memory request buffering

    // Memory request buffering
    wire                             mem_req_valid_s;
    wire [`CS_MEM_ADDR_WIDTH-1:0]    mem_req_addr_s;
    wire                             mem_req_rw_s;
    wire [LINE_SIZE-1:0]             mem_req_byteen_s;
    wire [`CS_LINE_WIDTH-1:0]        mem_req_data_s;
    wire [MEM_TAG_WIDTH-1:0]         mem_req_tag_s;
    wire                             mem_req_ready_s;

    VX_elastic_buffer #(
        .DATAW   (1 + LINE_SIZE + `CS_MEM_ADDR_WIDTH + `CS_LINE_WIDTH + MEM_TAG_WIDTH),
        .SIZE    (MEM_REQ_BUF_ENABLE ? `TO_OUT_BUF_SIZE(MEM_OUT_BUF) : 0),
        .OUT_REG (`TO_OUT_BUF_REG(MEM_OUT_BUF))
    ) mem_req_buf (
        .clk       (clk),
        .reset     (reset),
        .valid_in  (mem_req_valid_s), 
        .ready_in  (mem_req_ready_s), 
        .data_in   ({mem_req_rw_s, mem_req_byteen_s, mem_req_addr_s, mem_req_data_s, mem_req_tag_s}),
        .data_out  ({mem_bus_if.req_data.rw, mem_bus_if.req_data.byteen, mem_bus_if.req_data.addr, mem_bus_if.req_data.data, mem_bus_if.req_data.tag}), 
        .valid_out (mem_bus_if.req_valid), 
        .ready_out (mem_bus_if.req_ready)
    );
    
    assign mem_bus_if.req_data.atype = '0;

该模块的结构和1.1类似,参考1.1

1.3 memory response buffering

    // Memory response buffering
    wire                         mem_rsp_valid_s;
    wire [`CS_LINE_WIDTH-1:0]    mem_rsp_data_s;
    wire [MEM_TAG_WIDTH-1:0]     mem_rsp_tag_s;
    wire                         mem_rsp_ready_s;
        
    VX_elastic_buffer #(
        .DATAW   (MEM_TAG_WIDTH + `CS_LINE_WIDTH), 
        .SIZE    (MRSQ_SIZE),
        .OUT_REG (MRSQ_SIZE > 2)
    ) mem_rsp_queue (
        .clk        (clk),
        .reset      (reset),
        .valid_in   (mem_bus_if.rsp_valid),
        .ready_in   (mem_bus_if.rsp_ready),
        .data_in    ({mem_bus_if.rsp_data.tag, mem_bus_if.rsp_data.data}), 
        .data_out   ({mem_rsp_tag_s, mem_rsp_data_s}), 
        .valid_out  (mem_rsp_valid_s),
        .ready_out  (mem_rsp_ready_s)
    );

该模块的结构和1.1类似,参考1.1

1.4 cache initialize

    wire [`CS_LINE_SEL_BITS-1:0] init_line_sel;
    wire init_enable;

    // this reset relay is required to sync with bank initialization
    `RESET_RELAY (init_reset, reset);

    VX_cache_init #( 
        .CACHE_SIZE (CACHE_SIZE),
        .LINE_SIZE  (LINE_SIZE), 
        .NUM_BANKS  (NUM_BANKS),
        .NUM_WAYS   (NUM_WAYS)
    ) cache_init (
        .clk       (clk),
        .reset     (init_reset),
        .addr_out  (init_line_sel),
        .valid_out (init_enable)
    );

1.4.1 VX_cache_init模块解读

VX_cache_int模块见于w/rtl/cache/VX_cache_init.sv,其代码如下:

`include "VX_cache_define.vh"

module VX_cache_init #(
    // Size of cache in bytes
    parameter CACHE_SIZE    = 1024, 
    // Size of line inside a bank in bytes
    parameter LINE_SIZE     = 16, 
    // Number of banks
    parameter NUM_BANKS     = 1,
    // Number of associative ways
    parameter NUM_WAYS      = 1
) (
    input  wire clk,
    input  wire reset,    
    output wire [`CS_LINE_SEL_BITS-1:0] addr_out,
    output wire valid_out
);
    reg enabled;
    reg [`CS_LINE_SEL_BITS-1:0] line_ctr;

    always @(posedge clk) begin
        if (reset) begin
            enabled  <= 1;
            line_ctr <= '0;
        end else begin
            if (enabled) begin
                if (line_ctr == ((2 ** `CS_LINE_SEL_BITS)-1)) begin
                    enabled <= 0;
                end
                line_ctr <= line_ctr + `CS_LINE_SEL_BITS'(1);           
            end
        end
    end

    assign addr_out  = line_ctr;
    assign valid_out = enabled;

endmodule

初始化模块的功能还是直观。时钟上升沿触发,当 reset 信号为高时,初始化 enabled 和 line_ctr。如果 enabled 为高,每个时钟周期增加 line_ctr 的值,直到达到最大值 ((2 ** CS_LINE_SEL_BITS)-1`)。当 line_ctr 达到最大值时,将 enabled 置为低,表示初始化完成。

值得一提的是CS_LINE_SEL_BITS(1),其中(1) 的使用方式通常表示一个强制类型转换。举个例子,假设CS_LINE_SEL_BITS的值是4,那么2 ** CS_LINE_SEL_BITS-1将被解析为2 ** 4 - 1,即15。在这种情况下,CS_LINE_SEL_BITS'(1) 实际上是在1的左边加上了4'b,这表示将1转换为一个 4位的二进制数。


总结

本文接着对VX_cache.sv展开介绍,并对涉及的模块做出了解释。

  • 21
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DentionY

谢谢您的支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值