noc网络代码实现systemverilog详细解释

noc网络的部分设计算法


章节在【On-Chip NetworksSecond Edition】书中对应

拓扑:二维拓扑(二维mesh)
路由算法:确定性维序路由算法(每台路由器独立计算)4.3
流控制技术:基于flit流控制(虫洞)5.4
缓冲区反压:基于ON/OFF的缓冲区反压机制5.7.2
round-robin仲裁器6.4.1
可分离的输入优先分配器6.4.3

body/tail flit继承head flit的虚拟通道分配

NOC功耗的主要来源是,缓冲区,交叉开关,节点间链路

noc.sv头文件定义

  • noc.sv
package noc_params;

	localparam MESH_SIZE_X = 5;
	localparam MESH_SIZE_Y = 5;

	localparam DEST_ADDR_SIZE_X = $clog2(MESH_SIZE_X);
	localparam DEST_ADDR_SIZE_Y = $clog2(MESH_SIZE_Y);

	localparam VC_NUM = 2;
	localparam VC_SIZE = $clog2(VC_NUM);

	localparam HEAD_PAYLOAD_SIZE = 16;

	localparam FLIT_DATA_SIZE = DEST_ADDR_SIZE_X+DEST_ADDR_SIZE_Y+HEAD_PAYLOAD_SIZE;

	typedef enum logic [2:0] {LOCAL, NORTH, SOUTH, WEST, EAST} port_t;
	localparam PORT_NUM = 5;
	localparam PORT_SIZE = $clog2(PORT_NUM);

	typedef enum logic [1:0] {HEAD, BODY, TAIL, HEADTAIL} flit_label_t;

	typedef struct packed
	{
		logic [DEST_ADDR_SIZE_X-1 : 0] 	x_dest;
		logic [DEST_ADDR_SIZE_Y-1 : 0] 	y_dest;
		logic [HEAD_PAYLOAD_SIZE-1: 0] 	head_pl;
	} head_data_t;

	typedef struct packed
	{
		flit_label_t			flit_label;
		logic [VC_SIZE-1 : 0] 	vc_id;
		union packed
		{
			head_data_t 		head_data;
			logic [FLIT_DATA_SIZE-1 : 0] bt_pl;
		} data;
	} flit_t;

    typedef struct packed
    {
        flit_label_t flit_label;
        union packed
        {
            head_data_t head_data;
            logic [FLIT_DATA_SIZE-1 : 0] bt_pl;
        } data;
    } flit_novc_t;

endpackage

解释:

noc.sv是一个SystemVerilog包,定义了一些网络芯片(Network on Chip,简称NoC)的参数和数据类型。以下是一些主要的定义:

1. MESH_SIZE_XMESH_SIZE_Y:定义了NoC网格的大小。
2. DEST_ADDR_SIZE_XDEST_ADDR_SIZE_Y:定义了目标地址的大小,这是通过对网格大小进行对数运算得到的。
3. VC_NUMVC_SIZE:定义了虚拟通道(Virtual Channel)的数量和大小。
4. HEAD_PAYLOAD_SIZEFLIT_DATA_SIZE:定义了头部负载和flit数据的大小。
5. port_t:定义了一个枚举类型,表示端口的方向(本地,北,南,西,东)。
6. flit_label_t:定义了一个枚举类型,表示flit的标签(头,体,尾,头尾)。
7. head_data_t:定义了一个打包的结构体,包含目标地址和头部负载。
8. flit_t 和 flit_novc_t:定义了两个打包的结构体,包含flit标签,虚拟通道ID,以及一个联合体,该联合体可以是head_data_t类型的头数据,或者是flit数据。

这些定义主要用于描述NoC的数据传输和路由选择。

疑问

SystemVerilog 中,localparam 关键字用于定义一个本地参数。本地参数是一个常量,其值在定义时就已经确定,并且在整个模块中都不能改变。与 parameter 不同,localparam 不能从模块的外部进行修改,因此它通常用于定义模块内部使用的常量。

在你的代码中,localparam 用于定义两个常量:ON_OFF_LATENCYPOINTER_SIZEON_OFF_LATENCY 是一个常量,其值为 2。这个常量可能用于设置缓冲区的开/关延迟。

POINTER_SIZE 是一个常量,其值为 BUFFER_SIZE 的以2为底的对数,向上取整。这个常量用于确定读写指针的大小。

定义 localparam 的语法如下:
其中,type 是可选的,用于指定参数的数据类型。如果省略,参数的类型默认为整数。name 是参数的名称,value 是参数的值。
$clog2()SystemVerilog 中的一个内置函数,用于计算一个数的以2为底的对数,并向上取整。这个函数常常用于确定一个数可以用多少位二进制数来表示。

例如,$clog2(8) 的结果是 3,因为 8 可以用 3 位二进制数来表示(1000)。而 $clog2(10) 的结果是 4,因为 10 需要 4 位二进制数来表示(1010),即使 23 次方(8)小于 1024 次方(16)大于 10。

在你的代码中,$clog2() 用于确定网格大小、虚拟通道数量和端口数量等参数所需的位数。
SystemVerilog 中,enum 是一个枚举类型,用于定义一组命名的整数常量。枚举类型可以提高代码的可读性和可维护性。

在你的代码中,定义了两个枚举类型:port_t 和 flit_label_t。

port_t 枚举定义了五个方向:LOCAL(本地),NORTH(北),SOUTH(南),WEST(西),EAST(东)。这些方向通常用于描述网络中的路由方向。

flit_label_t 枚举定义了四种 flit 类型:HEAD(头),BODY(体),TAIL(尾),HEADTAIL(头尾)。这些类型用于描述网络中数据包的不同部分。

定义枚举类型的语法如下:
typedef enum [optional_type] {value1, value2, value3, ...} enum_name;
其中,optional_type 是可选的,用于指定枚举值的数据类型。如果省略,枚举值的类型默认为整数。value1, value2, value3, ... 是枚举值的列表,每个枚举值都是一个唯一的标识符。enum_name 是枚举类型的名称。
这段代码定义了一个名为 flit_t 的打包结构体。在 SystemVerilog 中,struct 是一种复合数据类型,可以包含多个不同类型的成员。packed 关键字表示结构体的所有成员都紧密打包在一起,没有任何填充或对齐。

flit_t 结构体包含以下三个成员:

1. flit_label:类型为 flit_label_t 的成员,flit_label_t 是一个之前定义的枚举类型,表示 flit 的类型(头,体,尾,头尾)。

2. vc_id:类型为 logic 的成员,大小为 VC_SIZE 位。logic 是 SystemVerilog 中的一种数据类型,可以表示二进制值。vc_id 表示虚拟通道的 ID3. data:类型为 union packed 的成员。union 是一种特殊的数据类型,可以包含多个不同类型的成员,但是一次只能使用其中一个。data 成员包含两个子成员:head_data 和 bt_pl。head_data 的类型为 head_data_t,是一个之前定义的结构体,包含目标地址和头部负载。bt_pl 的类型为 logic,大小为 FLIT_DATA_SIZE 位,表示 flit 的数据。

这个结构体主要用于表示网络中的 flit 数据,包括 flit 的类型,虚拟通道 ID,以及 flit 的数据。
是的,你的理解是正确的。在 SystemVerilog 中,union 是一种特殊的数据类型,它可以包含多个不同类型的成员,但是一次只能使用其中一个。

在你的代码中,union packed 包含了两个成员:head_data 和 bt_pl。这意味着,你可以在 data 中存储一个 head_data 类型的值,或者一个 bt_pl 类型的值,但是不能同时存储两者。如果你先存储了一个 head_data,然后又存储了一个 bt_pl,那么原来的 head_data 就会被 bt_pl 覆盖。

这种设计通常用于节省存储空间,当你知道一次只会用到其中一个成员时,就可以使用 union。在你的代码中,head_data 和 bt_pl 可能代表了 flit 的不同部分,根据 flit 的类型(由 flit_label 表示),你会选择使用 head_data 还是 bt_pl。

2.1circular_buffer.sv双指针固定长度缓冲区


2.1循环缓冲区循环缓冲区主要负责在中间节点上存储flits,同时将它们路由到目的地。允许简单的读/写操作,空和满的单比特输出表示缓冲区的这两种特殊情况。开/关的单比特输出向连接的上游路由器发出信号,表明缓冲区是否可以接受更多的flits。

import noc_params::*;

module circular_buffer #(
    parameter BUFFER_SIZE = 8
)(
    input flit_novc_t data_i,
    input read_i,
    input write_i,
    input rst,
    input clk,
    output flit_novc_t data_o,
    output logic is_full_o,
    output logic is_empty_o,
    output logic on_off_o
);

    localparam ON_OFF_LATENCY = 2;
    localparam [31:0] POINTER_SIZE = $clog2(BUFFER_SIZE);

    flit_novc_t memory[BUFFER_SIZE-1:0];

    logic [POINTER_SIZE-1:0] read_ptr;
    logic [POINTER_SIZE-1:0] write_ptr;

    logic [POINTER_SIZE-1:0] read_ptr_next;
    logic [POINTER_SIZE-1:0] write_ptr_next;
    logic is_full_next;
    logic is_empty_next;
    logic on_off_next;

    logic [POINTER_SIZE:0] num_flits;
    logic [POINTER_SIZE:0] num_flits_next;
    
    /*
    Sequential logic:
    - reset on the rising edge of the rst input;
    - when the write_i input is asserted on the rising edge of the clock,
      new data is added to the buffer if the buffer is not full
      or a simultaneous read is performed (i.e., the read_i input is asserted).
    */
    always_ff@(posedge clk or posedge rst)
    begin
        if (rst)
        begin
            read_ptr    <= 0;
            write_ptr   <= 0;
            num_flits   <= 0;
            is_full_o   <= 0;
            is_empty_o  <= 1;
            on_off_o    <= 1;  
        end
        else
        begin
            read_ptr    <= read_ptr_next;
            write_ptr   <= write_ptr_next;
            num_flits   <= num_flits_next;
            is_full_o   <= is_full_next;
            is_empty_o  <= is_empty_next;
            on_off_o    <= on_off_next;
            if((~read_i & write_i & ~is_full_o) | (read_i & write_i))
                memory[write_ptr] <= data_i;
        end
    end

    /*
    Combinational logic:
    - the following operations are accepted:
        * read while the buffer is not empty
        * write while the buffer is not full
        * simultaneously read and write while the buffer is not empty
      and, accordingly to the requested operation:
        * full and empty flags are eventually updated
        * read and write pointers are eventually incremented
        * the number of stored flits is updated
    - otherwise, the buffer next status doesn't change
    - additionally, the flit pointed by the read pointer is output
      and the on/off flag for the flow control is updated
    */
    always_comb
    begin
        data_o = memory [read_ptr];
        unique if(read_i & ~write_i & ~is_empty_o)
        begin: read_not_empty
            read_ptr_next = increase_ptr(read_ptr);
            write_ptr_next = write_ptr;
            is_full_next = 0;
            update_empty_on_read();
            num_flits_next = num_flits - 1;
        end
        else if(~read_i & write_i & ~is_full_o)
        begin: write_not_full
            read_ptr_next = read_ptr;
            write_ptr_next = increase_ptr(write_ptr);
            update_full_on_write();
            is_empty_next = 0;
            num_flits_next = num_flits + 1;
        end
        else if(read_i & write_i & ~is_empty_o)
        begin: read_write_not_empty
            read_ptr_next = increase_ptr(read_ptr);
            write_ptr_next = increase_ptr(write_ptr);
            is_full_next = is_full_o;
            is_empty_next = is_empty_o;
            num_flits_next = num_flits;
        end
        else
        begin: do_nothing
            read_ptr_next = read_ptr;
            write_ptr_next = write_ptr;
            is_full_next = is_full_o;
            is_empty_next = is_empty_o;
            num_flits_next = num_flits;
        end
        begin: update_on_off_flag
            unique if(num_flits > num_flits_next & num_flits_next < ON_OFF_LATENCY)
                on_off_next = 1;
            else if(num_flits < num_flits_next & num_flits_next > BUFFER_SIZE - ON_OFF_LATENCY)
                on_off_next = 0;
            else
                on_off_next = on_off_o;
        end
    end

    function logic [POINTER_SIZE-1:0] increase_ptr (input logic [POINTER_SIZE-1:0] ptr);
        if(ptr == BUFFER_SIZE-1)
            increase_ptr = 0;
        else
            increase_ptr = ptr+1;
    endfunction

    function void update_empty_on_read ();
        if(read_ptr_next == write_ptr)
            is_empty_next = 1;
        else
            is_empty_next = 0;
    endfunction

    function void update_full_on_write ();
        if(write_ptr_next == read_ptr)
            is_full_next = 1;
        else
            is_full_next = 0;
    endfunction

endmodule
这段代码定义了一个名为 circular_buffer 的 SystemVerilog 模块,它实现了一个循环缓冲区(也称为环形缓冲区或环形队列)。循环缓冲区是一种数据结构,用于存储和传输数据,特别是在生产者和消费者之间。

以下是这个模块的主要部分:

1. 参数和输入/输出端口:模块有一个参数 BUFFER_SIZE,表示缓冲区的大小。模块的输入包括数据输入 data_i,读取和写入控制信号 read_i 和 write_i,复位信号 rst,以及时钟信号 clk。模块的输出包括数据输出 data_o,以及几个状态标志 is_full_o,is_empty_o,和 on_off_o。

2. 内部变量和数组:模块定义了一个 flit_novc_t 类型的数组 memory,用于存储数据。还定义了几个指针和状态标志,用于跟踪读取和写入的位置,以及缓冲区的状态。

3. 时序逻辑:在 always_ff 块中,模块定义了在时钟上升沿或复位信号上升沿时的行为。如果复位信号被激活,所有状态都会被重置。否则,状态会被更新为下一个状态,如果满足条件,新的数据会被写入缓冲区。

4. 组合逻辑:在 always_comb 块中,模块定义了在任何时候的行为。这包括读取数据,更新指针和状态标志,以及处理读取和写入操作。

5. 函数:模块定义了几个函数,用于增加指针,以及在读取和写入操作后更新空和满状态标志。

这个模块可以用于各种应用,例如网络路由器,流水线处理,硬件队列等。

这段代码定义了一些变量和参数,用于实现循环缓冲区的功能。

1. ON_OFF_LATENCY:这是一个本地参数,用于设置缓冲区的开/关延迟。

2. POINTER_SIZE:这是一个本地参数,其值为BUFFER_SIZE的以2为底的对数,向上取整。这个参数用于确定读写指针的大小。

3. memory:这是一个类型为flit_novc_t的数组,大小为BUFFER_SIZE。这个数组用于存储缓冲区的数据。

4. read_ptr 和 write_ptr:这两个变量是读写指针,用于跟踪当前读取和写入的位置。

5. read_ptr_next 和 write_ptr_next:这两个变量用于存储下一个时钟周期的读写指针的值。

6. is_full_next,is_empty_next 和 on_off_next:这三个变量用于存储下一个时钟周期的缓冲区状态(是否满,是否空,开/关状态)。

7. num_flits 和 num_flits_next:这两个变量用于跟踪当前存储在缓冲区中的flits的数量,以及下一个时钟周期的flits数量。

这些变量和参数都是实现循环缓冲区功能的关键部分。
这段代码是一个 always_comb 块,它描述了循环缓冲区的组合逻辑。这个块会在任何输入变化时立即执行。

1. data_o = memory [read_ptr];:这行代码将当前读指针指向的内存单元的值赋给输出 data_o。

2. unique if(read_i & ~write_i & ~is_empty_o) ... end:这个条件块处理读操作。如果 read_i 为真,write_i 为假,且缓冲区不为空(is_empty_o 为假),则执行读操作。读指针增加,写指针不变,缓冲区不可能满,可能为空,存储的 flits 数量减一。

3. else if(~read_i & write_i & ~is_full_o) ... end:这个条件块处理写操作。如果 read_i 为假,write_i 为真,且缓冲区不满(is_full_o 为假),则执行写操作。读指针不变,写指针增加,缓冲区可能满,不可能为空,存储的 flits 数量加一。

4. else if(read_i & write_i & ~is_empty_o) ... end:这个条件块处理同时读写操作。如果 read_i 和 write_i 都为真,且缓冲区不为空(is_empty_o 为假),则同时执行读写操作。读指针和写指针都增加,缓冲区可能满,可能为空,存储的 flits 数量不变。

5. else ... end:这个条件块处理无操作。如果没有读写操作,或者缓冲区为空或满,那么所有状态都保持不变。

6. begin: update_on_off_flag ... end:这个块更新流控制标志 on_off_next。如果存储的 flits 数量减少并且下一个数量小于开/关延迟,或者存储的 flits 数量增加并且下一个数量大于缓冲区大小减去开/关延迟,那么 on_off_next 为真或假。否则,on_off_next 保持不变。

这段代码定义了三个函数,它们在循环缓冲区的实现中起到关键作用:

1. increase_ptr:这个函数接受一个指针作为输入,返回该指针增加后的值。如果指针已经指向缓冲区的最后一个元素(即,指针的值等于 BUFFER_SIZE-1),那么函数返回 0,表示指针回到缓冲区的开始。否则,函数返回 ptr+1,表示指针向前移动一位。这是因为循环缓冲区是一个环形的数据结构,当指针到达末尾时,下一个位置是缓冲区的开始。

2. update_empty_on_read:这个函数更新 is_empty_next 标志,表示在读操作后缓冲区是否为空。如果下一个读指针的值等于当前的写指针的值,那么缓冲区将为空,因此 is_empty_next 被设置为 1。否则,is_empty_next 被设置为 0。这是因为在循环缓冲区中,如果读指针和写指针指向同一个位置,那么缓冲区就是空的。

3. update_full_on_write:这个函数更新 is_full_next 标志,表示在写操作后缓冲区是否已满。如果下一个写指针的值等于当前的读指针的值,那么缓冲区将被视为已满,因此 is_full_next 被设置为 1。否则,is_full_next 被设置为 0。这是因为在循环缓冲区中,如果写指针前进一位就会碰到读指针,那么缓冲区就是满的。

这些函数在 always_comb 块中被调用,用于更新读写指针和缓冲区的状态。

2.2Input Buffer.sv缓冲区空、虚拟、交换机分配状态


输入缓冲区模块封装一个圆形缓冲区,并用当前数据包的下一跳和内部有限状态机的状态信息包围它,这些状态信息可以是空闲的、虚拟通道分配的或交换机分配的。当缓冲区可以分配给新的传入数据包时,输入缓冲区的内部FSM处于空闲状态,当缓冲区至少接收到数据包的头帧并等待下游路由器分配缓冲区时,则处于虚拟通道分配状态。当flits(直到数据包的尾部)被发送到指定的下游虚拟通道,并与来自相同输入端口和其他输入端口的其他缓冲区争夺对crossbar交换机的访问权时,处于交换机分配状态。如果缓冲区可以分配给一个新包,一个专用的单比特标志就会向连接的上游路由器发出信号。

import noc_params::*;

module input_buffer #(
    parameter BUFFER_SIZE = 8
)(
    input flit_novc_t data_i,
    input read_i,
    input write_i,
    input [VC_SIZE-1:0] vc_new_i,
    input vc_valid_i,
    input port_t out_port_i,
    input rst,
    input clk,
    output flit_t data_o,
    output logic is_full_o,
    output logic is_empty_o,
    output logic on_off_o,
    output port_t out_port_o,
    output logic vc_request_o,
    output logic switch_request_o,
    output logic vc_allocatable_o,
    output logic [VC_SIZE-1:0] downstream_vc_o,
    output logic error_o
);

    enum logic [1:0] {IDLE, VA, SA} ss, ss_next;

    logic [VC_SIZE-1:0] downstream_vc_next;

    logic read_cmd, write_cmd;
    logic end_packet, end_packet_next;
    logic vc_allocatable_next;
    logic error_next;

    flit_novc_t read_flit;

    port_t out_port_next;

    circular_buffer #(
        .BUFFER_SIZE(BUFFER_SIZE)
    )
    circular_buffer (
        .data_i(data_i),
        .read_i(read_cmd),
        .write_i(write_cmd),
        .rst(rst),
        .clk(clk),
        .data_o(read_flit),
        .is_full_o(is_full_o),
        .is_empty_o(is_empty_o),
        .on_off_o(on_off_o)
    );

    /*
    Sequential logic:
    - on the rising edge of the reset input signal, reset the state of the
      finite state machine, the next hop destination and the downstream virtual
      channel identifier;
    - on the rising edge of the clock input signal, update the state,
      the next hop destination and the downstream virtual channel identifier.
    */
    always_ff @(posedge clk, posedge rst)
    begin
        if(rst)
        begin
            ss                  <= IDLE;
            out_port_o          <= LOCAL;
            downstream_vc_o     <= 0;
            end_packet          <= 0;
            vc_allocatable_o    <= 0;
            error_o             <= 0;
        end
        else
        begin
            ss                  <= ss_next;
            out_port_o          <= out_port_next;
            downstream_vc_o     <= downstream_vc_next;
            end_packet          <= end_packet_next;
            vc_allocatable_o    <= vc_allocatable_next;
            error_o             <= error_next;
        end
    end

    /*
    Combinational logic:
    - in Idle state, when the input flit is an Head one, the write command is
      asserted and the buffer is empty, then the next hop destination received
      in input and associated to the flit is stored, and the next state is set
      to be Virtual Channel Allocation;
    - in Virtual Channel Allocation state, when the virtual channel for the
      downstream router is valid, i.e., the corresponding validity signal is
      asserted, then the virtual channel identifier is stored and the next
      state is set to be Switch Allocation;
    - in Switch Allocation state, when the last flit to read is the Tail one
      and the read command is asserted, then the next state is set to be Idle.
    */
    always_comb
    begin
        data_o.flit_label = read_flit.flit_label;
		data_o.vc_id = downstream_vc_o;
		data_o.data = read_flit.data;

        ss_next = ss;
        out_port_next = out_port_o;
        downstream_vc_next = downstream_vc_o;

        read_cmd = 0;
        write_cmd = 0;

        end_packet_next = end_packet;
        error_next = 0;

        vc_request_o = 0;
        switch_request_o = 0;
        vc_allocatable_next = 0;

        unique case(ss)
            IDLE:
            begin
                if((data_i.flit_label == HEAD | data_i.flit_label == HEADTAIL) & write_i & is_empty_o)
                begin
                    ss_next = VA;
                    out_port_next = out_port_i;
                    write_cmd = 1;
                end

                if(vc_valid_i | read_i | ((data_i.flit_label == BODY | data_i.flit_label == TAIL) & write_i) | ~is_empty_o)
                begin
                    error_next = 1;
                end
                if(write_i & data_i.flit_label == HEADTAIL)
                begin
                    end_packet_next = 1;
                end
            end

            VA:
            begin
                if(vc_valid_i)
                begin
                    ss_next = SA;
                    downstream_vc_next = vc_new_i;
                end

                vc_request_o = 1;
                if(write_i & (data_i.flit_label == BODY | data_i.flit_label == TAIL) & ~end_packet)
                begin
                    write_cmd = 1;
                end

                if((write_i & (end_packet | data_i.flit_label == HEAD | data_i.flit_label == HEADTAIL)) | read_i)
                begin
                    error_next = 1;
                end
                if(write_i & data_i.flit_label == TAIL)
                begin
                    end_packet_next = 1;
                end
            end

            SA:
            begin
                if(read_i & (data_o.flit_label == TAIL | data_o.flit_label == HEADTAIL))
                begin
                    ss_next = IDLE;
                    vc_allocatable_next = 1;
                    end_packet_next = 0;
                end

                if(~is_empty_o)
                begin
                    switch_request_o = 1;
                end
                    
                read_cmd = read_i;
                if(write_i & (data_i.flit_label == BODY | data_i.flit_label == TAIL) & ~end_packet)
                begin
                    write_cmd = 1;
                end

                if((write_i & (end_packet | data_i.flit_label == HEAD | data_i.flit_label == HEADTAIL)) | vc_valid_i)
                begin
                    error_next = 1;
                end
                if(write_i & data_i.flit_label == TAIL)
                begin
                    end_packet_next = 1;
                end
            end

            default:
            begin
                ss_next = IDLE;
                vc_allocatable_next = 1;
                error_next = 1;
                end_packet_next = 0;
            end

        endcase
    end

endmodule
这段代码定义了一个名为 input_buffer 的 SystemVerilog 模块,它实现了一个输入缓冲区。输入缓冲区通常用于存储和管理网络中的数据包。

以下是这个模块的主要部分:

1. 参数和输入/输出端口:模块有一个参数 BUFFER_SIZE,表示缓冲区的大小。模块的输入包括数据输入 data_i,读取和写入控制信号 read_i 和 write_i,复位信号 rst,以及时钟信号 clk。模块的输出包括数据输出 data_o,以及几个状态标志 is_full_o,is_empty_o,和 on_off_o。

2. 内部变量和状态机:模块定义了一个状态机,有三个状态:IDLEVAVirtual Channel Allocation),SASwitch Allocation)。还定义了几个内部变量,用于跟踪缓冲区的状态和数据包的信息。

3. 循环缓冲区:模块实例化了一个 circular_buffer 模块,用于存储数据。

4. 时序逻辑:在 always_ff 块中,模块定义了在时钟上升沿或复位信号上升沿时的行为。如果复位信号被激活,所有状态都会被重置。否则,状态会被更新为下一个状态。

5. 组合逻辑:在 always_comb 块中,模块定义了在任何时候的行为。这包括处理读写操作,更新状态机的状态,以及处理错误情况。

这个模块可以用于各种应用,例如网络路由器,流水线处理,硬件队列等。

以下是模块的输入和输出端口:

1. 输入端口:
- data_i:输入的数据,类型为 flit_novc_t。
- read_i:读取控制信号,当为高电平时,表示要从缓冲区读取数据。
- write_i:写入控制信号,当为高电平时,表示要向缓冲区写入数据。
- vc_new_i:新的虚拟通道(Virtual ChannelID,宽度为 VC_SIZE- vc_valid_i:虚拟通道有效信号,当为高电平时,表示 vc_new_i 是有效的。
- out_port_i:输出端口,类型为 port_t,表示数据包的下一跳方向。
- rst:复位信号,当为高电平时,将模块状态重置为初始状态。
- clk:时钟信号,用于同步模块的操作。

2. 输出端口:
- data_o:输出的数据,类型为 flit_t。
- is_full_o:缓冲区满标志,当为高电平时,表示缓冲区已满。
- is_empty_o:缓冲区空标志,当为高电平时,表示缓冲区为空。
- on_off_o:开关标志,用于流控制。
- out_port_o:输出端口,类型为 port_t,表示数据包的下一跳方向。
- vc_request_o:虚拟通道请求信号,当为高电平时,表示请求新的虚拟通道。
- switch_request_o:交换请求信号,当为高电平时,表示请求交换数据包。
- vc_allocatable_o:虚拟通道可分配标志,当为高电平时,表示虚拟通道可以被分配。
- downstream_vc_o:下游虚拟通道ID,宽度为 VC_SIZE- error_o:错误标志,当为高电平时,表示发生错误。
这段代码定义了一些内部变量和一个 circular_buffer 实例:

1. enum logic [1:0] {IDLE, VA, SA} ss, ss_next;:定义了一个枚举类型,表示输入缓冲区的状态机的状态。ss 是当前状态,ss_next 是下一个状态。状态包括:空闲(IDLE),虚拟通道分配(VA),交换分配(SA)。

2. logic [VC_SIZE-1:0] downstream_vc_next;:下一个下游虚拟通道的标识符。

3. logic read_cmd, write_cmd;:读写命令信号,用于控制 circular_buffer 的读写操作。

4. logic end_packet, end_packet_next;:表示当前和下一个数据包是否结束的标志。

5. logic vc_allocatable_next;:下一个虚拟通道是否可分配的标志。

6. logic error_next;:下一个错误标志,如果有错误发生,该标志将被设置为17. flit_novc_t read_flit;:从 circular_buffer 读取的 flit。

8. port_t out_port_next;:下一个输出端口。

9. circular_buffer #(.BUFFER_SIZE(BUFFER_SIZE)) circular_buffer (...);:实例化了一个 circular_buffer 模块,用于存储和管理 flits。这个模块的参数 BUFFER_SIZE 被设置为 input_buffer 的参数 BUFFER_SIZE,输入和输出端口被连接到 input_buffer 的相应端口和内部变量。
这段代码是 input_buffer 模块的组合逻辑部分,它定义了在任何时候的行为。这包括处理读写操作,更新状态机的状态,以及处理错误情况。

1. data_o.flit_label = read_flit.flit_label; data_o.vc_id = downstream_vc_o; data_o.data = read_flit.data;:这三行代码将从循环缓冲区读取的 flit 的标签和数据,以及下游虚拟通道的 ID,赋值给输出数据 data_o。

2. ss_next = ss; out_port_next = out_port_o; downstream_vc_next = downstream_vc_o;:这三行代码将当前的状态,输出端口,和下游虚拟通道的 ID,赋值给它们的下一个值。

3. read_cmd = 0; write_cmd = 0;:这两行代码将读写命令信号重置为 04. end_packet_next = end_packet; error_next = 0;:这两行代码将当前的数据包结束标志赋值给下一个值,并将下一个错误标志重置为 05. vc_request_o = 0; switch_request_o = 0; vc_allocatable_next = 0;:这三行代码将虚拟通道请求信号,交换请求信号,和下一个虚拟通道可分配标志重置为 06. unique case(ss) ... endcase:这是一个 case 语句,用于根据当前状态执行不同的操作。每个状态(IDLEVASA)都有一个对应的代码块,定义了在该状态下的行为。例如,在 IDLE 状态下,如果接收到头部 flit,写入命令被激活,且缓冲区为空,那么状态机将转移到 VA 状态,输出端口将被更新,写入命令将被激活。在 VASA 状态下,也有类似的操作。如果当前状态不是这三个状态中的任何一个,那么将执行 default 代码块,将状态机重置为 IDLE 状态,设置下一个虚拟通道可分配标志为 1,设置下一个错误标志为 1,设置下一个数据包结束标志为 0

2.3rc_unit.svRoute Computation Unit计算下一跳


2.3路由计算单元一个纯粹的组合逻辑模块,由于实现的路由算法只需要当前路由器和目的路由器在二维网格中的位置信息,路由计算单元在头flit到达路由器后立即计算每个数据包的下一跳(因为头flit的头部编码了数据包接收方的位置),并发送其计算结果(即输出端口(数据包必须从该端口流出路由器)到输入缓冲区(其中存储数据包的所有片段)。

import noc_params::*;

module rc_unit #(
    parameter X_CURRENT = 0,
    parameter Y_CURRENT = 0,
    parameter DEST_ADDR_SIZE_X = 4,
    parameter DEST_ADDR_SIZE_Y = 4
)(
    input logic [DEST_ADDR_SIZE_X-1 : 0] x_dest_i,
    input logic [DEST_ADDR_SIZE_Y-1 : 0] y_dest_i,
    output port_t out_port_o
);

    wire signed [DEST_ADDR_SIZE_X-1 : 0] x_offset;
    wire signed [DEST_ADDR_SIZE_Y-1 : 0] y_offset;

    assign x_offset = x_dest_i - X_CURRENT;
    assign y_offset = y_dest_i - Y_CURRENT;

    /*
    Combinational logic:
    - the route computation follows a DOR (Dimension-Order Routing) algorithm,
      with the nodes of the Network-on-Chip arranged in a 2D mesh structure,
      hence with 5 inputs and 5 outputs per node (except for boundary routers),
      i.e., both for input and output:
        * left, right, up and down links to the adjacent nodes
        * one link to the end node
    - the 2D Mesh coordinates scheme is mapped as following:
        * X increasing from Left to Right
        * Y increasing from  Up  to Down
    */
    always_comb
    begin
        unique if (x_offset < 0)
        begin
            out_port_o = WEST;
        end
        else if (x_offset > 0)
        begin
            out_port_o = EAST;
        end
        else if (x_offset == 0 & y_offset < 0)
        begin
            out_port_o = NORTH;
        end
        else if (x_offset == 0 & y_offset > 0)
        begin
            out_port_o = SOUTH;
        end
        else
        begin
            out_port_o = LOCAL;
        end
    end

endmodule
这段代码定义了一个名为 rc_unit 的 SystemVerilog 模块,它实现了一个路由计算单元(Routing Computation Unit)。路由计算单元用于确定数据包在网络中的下一跳方向。

以下是模块的主要部分:

1. 参数和输入/输出端口:模块有四个参数,X_CURRENTY_CURRENT 表示当前节点的坐标,DEST_ADDR_SIZE_XDEST_ADDR_SIZE_Y 表示目标地址的位宽。模块的输入包括目标地址的 XY 坐标 x_dest_i 和 y_dest_i,输出是下一跳的方向 out_port_o。

2. 内部变量:模块定义了两个内部变量 x_offset 和 y_offset,它们表示目标地址和当前地址在 XY 方向上的偏移。

3. 组合逻辑:在 always_comb 块中,模块定义了在任何时候的行为。这包括根据 XY 方向的偏移确定下一跳的方向。如果 X 方向的偏移小于 0,那么下一跳的方向是西(WEST)。如果 X 方向的偏移大于 0,那么下一跳的方向是东(EAST)。如果 X 方向的偏移等于 0,且 Y 方向的偏移小于 0,那么下一跳的方向是北(NORTH)。如果 X 方向的偏移等于 0,且 Y 方向的偏移大于 0,那么下一跳的方向是南(SOUTH)。否则,下一跳的方向是本地(LOCAL)。

这个模块实现了一个简单的维度顺序路由(Dimension-Order Routing)算法,适用于节点排列为 2D 网格结构的网络。

2.4input_port.sv


2.4输入接口输入端口模块包含可配置数量的虚拟通道和路由计算单元;每个输入端口分别连接到所含路由器的虚拟通道分配器、交换分配器和横杆模块。每个时钟周期只有一个flit可以从上游路由器进入输入端口,并存储在与该flit本身正确编码的标识符对应的虚拟通道中;以同样的方式,在每个时钟周期只能从选定的虚拟通道中读取一个flit,并将其发送到crossbar。为了简化与组成路由器的其他模块提供的接口的连接(同时不修改任何功能),输入端口被分组在一个输入块中,每个路由器一个,包含由参数指定的多个端口(例如,在二维网格拓扑情况下为5)。

import noc_params::*;

module input_port #(
    parameter BUFFER_SIZE = 8,
    parameter X_CURRENT = MESH_SIZE_X/2,
    parameter Y_CURRENT = MESH_SIZE_Y/2
)(
    input flit_t data_i,
    input valid_flit_i,
    input rst,
    input clk,
    input [VC_SIZE-1:0] sa_sel_vc_i,
    input [VC_SIZE-1:0] va_new_vc_i [VC_NUM-1:0],
    input [VC_NUM-1:0] va_valid_i,
    input sa_valid_i,
    output flit_t xb_flit_o,
    output logic [VC_NUM-1:0] is_on_off_o,
    output logic [VC_NUM-1:0] is_allocatable_vc_o,
    output logic [VC_NUM-1:0] va_request_o,
    output logic sa_request_o [VC_NUM-1:0],
    output logic [VC_SIZE-1:0] sa_downstream_vc_o [VC_NUM-1:0],
    output port_t [VC_NUM-1:0] out_port_o,
    output logic [VC_NUM-1:0] is_full_o,
    output logic [VC_NUM-1:0] is_empty_o,
    output logic [VC_NUM-1:0] error_o
);

    flit_novc_t data_cmd;
    flit_t [VC_NUM-1:0] data_out;

    port_t out_port_cmd;

    logic [VC_NUM-1:0] read_cmd;
    logic [VC_NUM-1:0] write_cmd;

    genvar vc;
    generate
        for(vc=0; vc<VC_NUM; vc++)
        begin: generate_virtual_channels
            input_buffer #(
                .BUFFER_SIZE(BUFFER_SIZE)
            )
            input_buffer (
                .data_i(data_cmd),
                .read_i(read_cmd[vc]),
                .write_i(write_cmd[vc]),
                .vc_new_i(va_new_vc_i[vc]),
                .vc_valid_i(va_valid_i[vc]),
                .out_port_i(out_port_cmd),
                .rst(rst),
                .clk(clk),
                .data_o(data_out[vc]),
                .is_full_o(is_full_o[vc]),
                .is_empty_o(is_empty_o[vc]),
                .on_off_o(is_on_off_o[vc]),
                .out_port_o(out_port_o[vc]),
                .vc_request_o(va_request_o[vc]),
                .switch_request_o(sa_request_o[vc]),
                .vc_allocatable_o(is_allocatable_vc_o[vc]),
                .downstream_vc_o(sa_downstream_vc_o[vc]),
                .error_o(error_o[vc])
            );
        end
    endgenerate

    rc_unit #(
        .X_CURRENT(X_CURRENT),
        .Y_CURRENT(Y_CURRENT),
        .DEST_ADDR_SIZE_X(DEST_ADDR_SIZE_X),
        .DEST_ADDR_SIZE_Y(DEST_ADDR_SIZE_Y)
    )
    rc_unit (
        .x_dest_i(data_i.data.head_data.x_dest),
        .y_dest_i(data_i.data.head_data.y_dest),
        .out_port_o(out_port_cmd)
    );

    /*
    Combinational logic:
    - if the input flit is valid, assert the write command of the corresponding
      virtual channel buffer where the flit has to be stored;
    - assert the read command of the virtual channel buffer selected by the
      interfaced switch allocator and propagate at the crossbar interface the
      corresponding flit.
    */
    always_comb
    begin
        data_cmd.flit_label = data_i.flit_label;
        data_cmd.data = data_i.data;
        
        write_cmd = {VC_NUM{1'b0}};
        if(valid_flit_i)
            write_cmd[data_i.vc_id] = 1;

        read_cmd = {VC_NUM{1'b0}};
        if(sa_valid_i)
            read_cmd[sa_sel_vc_i] = 1;
        xb_flit_o = data_out[sa_sel_vc_i];
    end

endmodule
X_CURRENT 参数表示当前节点在网络中的 X 坐标。在这个代码中,它被设置为 MESH_SIZE_X/2,这意味着当前节点被默认设置在网络的中心位置。

这只是一个默认值,实际使用时,你可以根据需要设置 X_CURRENT 的值。例如,如果你想将当前节点设置在网络的左上角,你可以将 X_CURRENT 设置为 0。如果你想将当前节点设置在网络的右下角,你可以将 X_CURRENT 设置为 MESH_SIZE_X-1。

请注意,这个设置可能会影响路由算法的行为,因为路由算法通常会根据节点的位置来决定数据包的路由路径。

这段代码定义了一个名为 input_port 的 SystemVerilog 模块,它实现了一个网络节点的输入端口。输入端口用于接收、存储和管理网络中的数据包。

以下是模块的主要部分:

1. 参数和输入/输出端口:模块有三个参数,BUFFER_SIZE 表示每个虚拟通道的缓冲区大小,X_CURRENTY_CURRENT 表示当前节点的坐标。模块的输入包括数据包 data_i,有效数据包标志 valid_flit_i,复位信号 rst,时钟信号 clk,以及一些用于虚拟通道和交换分配的信号。模块的输出包括交叉开关接口的数据包 xb_flit_o,虚拟通道的开关标志 is_on_off_o,虚拟通道可分配标志 is_allocatable_vc_o,虚拟通道请求信号 va_request_o,交换请求信号 sa_request_o,下游虚拟通道 sa_downstream_vc_o,输出端口 out_port_o,以及一些状态标志。

2. 内部变量和实例:模块定义了一些内部变量,用于存储数据包和控制信号。模块实例化了多个 input_buffer 模块和一个 rc_unit 模块。input_buffer 模块用于存储和管理每个虚拟通道的数据包,rc_unit 模块用于计算数据包的下一跳方向。

3. 组合逻辑:在 always_comb 块中,模块定义了在任何时候的行为。这包括处理读写操作,更新数据包的标签和数据,以及处理错误情况。

这个模块可以用于各种应用,例如网络路由器,流水线处理,硬件队列等。
这段代码定义了 input_port 模块的输入和输出端口:

1. 输入端口:
- data_i:输入的数据,类型为 flit_t。
- valid_flit_i:有效数据包标志,当为高电平时,表示 data_i 是有效的。
- rst:复位信号,当为高电平时,将模块状态重置为初始状态。
- clk:时钟信号,用于同步模块的操作。
- sa_sel_vc_i:交换分配器选择的虚拟通道的 ID,宽度为 VC_SIZE- va_new_vc_i:新的虚拟通道的 ID,每个虚拟通道都有一个,宽度为 VC_SIZE- va_valid_i:虚拟通道有效信号,每个虚拟通道都有一个,当为高电平时,表示对应的 va_new_vc_i 是有效的。
- sa_valid_i:交换分配器有效信号,当为高电平时,表示 sa_sel_vc_i 是有效的。

2. 输出端口:
- xb_flit_o:交叉开关接口的数据包,类型为 flit_t。
- is_on_off_o:虚拟通道的开关标志,每个虚拟通道都有一个,当为高电平时,表示对应的虚拟通道是开启的。
- is_allocatable_vc_o:虚拟通道可分配标志,每个虚拟通道都有一个,当为高电平时,表示对应的虚拟通道可以被分配。
- va_request_o:虚拟通道请求信号,每个虚拟通道都有一个,当为高电平时,表示请求新的虚拟通道。
- sa_request_o:交换请求信号,每个虚拟通道都有一个,当为高电平时,表示请求交换数据包。
- sa_downstream_vc_o:下游虚拟通道的 ID,每个虚拟通道都有一个,宽度为 VC_SIZE- out_port_o:输出端口,每个虚拟通道都有一个,类型为 port_t,表示数据包的下一跳方向。
- is_full_o:缓冲区满标志,每个虚拟通道都有一个,当为高电平时,表示对应的缓冲区已满。
- is_empty_o:缓冲区空标志,每个虚拟通道都有一个,当为高电平时,表示对应的缓冲区为空。
- error_o:错误标志,每个虚拟通道都有一个,当为高电平时,表示发生错误。
这段代码是 input_port 模块的主体部分,包括了一些内部变量的定义,input_buffer 和 rc_unit 模块的实例化,以及一个组合逻辑块。

1. 内部变量:data_cmd 是一个没有虚拟通道 ID 的 flit,用于存储要写入 input_buffer 的数据。data_out 是一个 flit 数组,用于存储从每个 input_buffer 读取的数据。out_port_cmd 是一个输出端口,用于存储从 rc_unit 计算出的下一跳方向。read_cmd 和 write_cmd 是两个逻辑信号数组,用于控制每个 input_buffer 的读写操作。

2. input_buffer 实例:使用 generate 和 for 语句生成了 VC_NUM 个 input_buffer 实例。每个实例都有自己的读写命令,新的虚拟通道 ID,虚拟通道有效信号,输出端口,以及一些状态标志。

3. rc_unit 实例:实例化了一个 rc_unit 模块,用于计算数据包的下一跳方向。输入是数据包的目标地址,输出是下一跳的方向。

4. 组合逻辑块:在 always_comb 块中,定义了在任何时候的行为。这包括处理读写操作,更新 data_cmd 的标签和数据,以及更新交叉开关接口的数据包。如果输入 flit 有效,那么将激活对应虚拟通道缓冲区的写命令。如果交换分配器有效,那么将激活对应虚拟通道缓冲区的读命令,并将对应的 flit 传递到交叉开关接口。

2.5crossbar.sv


2.5纵横开关该模块允许从路由器的每个输入端口到输出端口移动flits,实现方式是每个输出最多可以传播一个输入;要在每个输出处传播的输入的选择被委托给一个连接的辅助模块,即开关分配器。

import noc_params::*;

module crossbar #(
)(
    input_block2crossbar.crossbar ib_if,
    switch_allocator2crossbar.crossbar sa_if,
    output flit_t data_o [PORT_NUM-1:0]
);

    /*
    Combinational logic:
    on each output, propagate the corresponding input
    according to the current selection
    */
    always_comb
    begin
        for(int ip = 0; ip < PORT_NUM; ip = ip + 1)
        begin
            data_o[ip] = ib_if.flit[sa_if.input_vc_sel[ip]];
        end
    end

endmodule```

```java
这段代码定义了一个名为 crossbar 的 SystemVerilog 模块,它实现了一个交叉开关(Crossbar Switch)。交叉开关用于在网络节点中连接多个输入端口和输出端口。

以下是模块的主要部分:

1. 输入/输出端口:模块的输入包括 ib_if 和 sa_if,它们分别是输入块到交叉开关的接口和交换分配器到交叉开关的接口。模块的输出是 data_o,它是一个 flit_t 类型的数组,表示从交叉开关出去的数据。

2. 组合逻辑块:在 always_comb 块中,模块定义了在任何时候的行为。这包括根据交换分配器的选择,将对应的输入数据传递到每个输出端口。这是通过一个 for 循环实现的,循环变量 ip 从 0PORT_NUM-1,表示每个输出端口。在每次循环中,将 ib_if.flit[sa_if.input_vc_sel[ip]] 赋值给 data_o[ip],这表示将交换分配器选择的输入数据传递到对应的输出端口。

2.6round_robin_arbiter.sv


2.6轮循仲裁器标准的轮循仲裁器是更复杂的分配器的基本组件,我们将使用它在路由器中解决虚拟通道缓冲区和交叉排交换机的争用问题。这个仲裁器模块可以对共享资源的最大请求数进行参数化,选择循环调度算法是因为它简单、易于实现和无饥饿。

module round_robin_arbiter #(
    parameter AGENTS_NUM = 4
)(
    input rst,
    input clk,
    input [AGENTS_NUM-1:0] requests_i,
    output logic [AGENTS_NUM-1:0] grants_o
);

    localparam [31:0] AGENTS_PTR_SIZE = $clog2(AGENTS_NUM);

    logic [AGENTS_PTR_SIZE-1:0] highest_priority, highest_priority_next;

    /*
    Sequential logic:
    - reset on the rising edge of the rst input;
    - update the agent with the highest priority with
      respect to the Round-Robin arbitration policy.
    */
    always_ff@(posedge clk, posedge rst)
    begin
        if(rst)
        begin
            highest_priority <= 0;
        end
        else
        begin
            highest_priority <= highest_priority_next;
        end
    end

    /*
    Combinational logic:
    - among all the agents requesting for the shared resource,
      grant the first one in ascending order starting
      from the current highest priority agent;
    - set as the next highest priority agent
      the one following the granted agent.
    */

    always_comb
    begin
        grants_o = {AGENTS_NUM{1'b0}};
        highest_priority_next = highest_priority;
        for(int i = 0; i < AGENTS_NUM; i = i + 1)
        begin
            if(requests_i[(highest_priority + i) % AGENTS_NUM])
            begin
                grants_o[(highest_priority + i) % AGENTS_NUM] = 1'b1;
                highest_priority_next = (highest_priority + i + 1) % AGENTS_NUM;
                break;
            end
        end
    end

endmodule
这段代码定义了一个名为 round_robin_arbiter 的 SystemVerilog 模块,它实现了一个循环调度(Round-Robin)仲裁器。仲裁器用于在多个请求中选择一个进行服务。

以下是模块的主要部分:

1. 参数和输入/输出端口:模块有一个参数 AGENTS_NUM,表示代理(请求)的数量。模块的输入包括复位信号 rst,时钟信号 clk,以及一个逻辑信号数组 requests_i,表示每个代理的请求。模块的输出是一个逻辑信号数组 grants_o,表示每个代理的授权。

2. 内部变量:模块定义了两个内部变量 highest_priority 和 highest_priority_next,它们表示当前和下一个最高优先级的代理。

3. 时序逻辑块:在 always_ff @(posedge clk, posedge rst) 块中,定义了在时钟上升沿或复位信号上升沿时的行为。如果复位信号被激活,最高优先级的代理将被重置为 0。否则,最高优先级的代理将被更新为下一个最高优先级的代理。

4. 组合逻辑块:在 always_comb 块中,定义了在任何时候的行为。这包括处理授权操作和更新最高优先级的代理。首先,所有的授权信号都被重置为 0,下一个最高优先级的代理被设置为当前的最高优先级的代理。然后,对于每个代理,如果它的请求信号为高电平,那么它将被授权,下一个最高优先级的代理将被设置为它的下一个代理,然后退出循环。

这个模块实现了一个简单的循环调度算法,适用于公平地在多个请求中选择一个进行服务。
这段代码是 round_robin_arbiter 模块的组合逻辑部分,它定义了在任何时候的行为。这包括处理授权操作和更新最高优先级的代理。

1. grants_o = {AGENTS_NUM{1'b0}};:这行代码将所有的授权信号重置为 0{AGENTS_NUM{1'b0}} 是一个宽度为 AGENTS_NUM 的逻辑向量,所有位都被设置为 02. highest_priority_next = highest_priority;:这行代码将当前的最高优先级的代理赋值给下一个最高优先级的代理。

3. for(int i = 0; i < AGENTS_NUM; i = i + 1):这是一个 for 循环,循环变量 i 从 0AGENTS_NUM-1,表示每个代理。

4. if(requests_i[(highest_priority + i) % AGENTS_NUM]):这是一个 if 语句,条件是当前代理的请求信号是否为高电平。(highest_priority + i) % AGENTS_NUM 是一个表达式,计算出当前代理的 ID5. grants_o[(highest_priority + i) % AGENTS_NUM] = 1'b1;:如果当前代理的请求信号为高电平,那么将对应的授权信号设置为 1,表示授权给当前代理。

6. highest_priority_next = (highest_priority + i + 1) % AGENTS_NUM;:将下一个最高优先级的代理设置为当前代理的下一个代理。

7. break;:退出循环。这意味着一旦找到一个请求信号为高电平的代理,就会授权给它,并更新最高优先级的代理,然后退出循环。

这个模块实现了一个简单的循环调度算法,适用于公平地在多个请求中选择一个进行服务。
是的,这段代码中的 for 循环在 SystemVerilog 中是可以被综合的。在硬件描述语言(HDL)中,可以综合的 for 循环通常用于生成重复的硬件结构,或者在编译时确定的固定次数的迭代。

在这个例子中,for 循环用于遍历所有的代理(agents),并根据循环调度(Round-Robin)策略选择一个进行服务。这个过程在每个时钟周期都会进行,因此可以被综合为硬件。

但是,请注意,不是所有的 for 循环都可以被综合。例如,如果循环的次数在运行时才能确定,或者循环体中包含不能被综合的语句(如延时,等待,或者某些系统任务),那么这样的 for 循环就不能被综合。

2.7separable_input_first_allocator.sv


2.7可分离的输入优先分配器
分配器允许管理多个代理之间的多个共享资源争用;该模块的实现是这样的:首先,在每个输入端口上,在请求分配任何输出端口的虚拟通道之间执行轮询仲裁(实际上,分配器是输入优先的),然后,在每个输出端口上,在请求分配的输入端口之间执行轮询仲裁。这个可分离的输入优先的分配器模块在下面两个分配器单元中使用,因为它封装了所有的分配逻辑和包含的模块。

import noc_params::*;

module separable_input_first_allocator #(
    parameter VC_NUM = 2
)(
    input rst,
    input clk,
    input [PORT_NUM-1:0][VC_NUM-1:0] request_i,
    input port_t [VC_NUM-1:0] out_port_i [PORT_NUM-1:0],
    output logic [PORT_NUM-1:0][VC_NUM-1:0] grant_o
);

    logic [PORT_NUM-1:0][PORT_NUM-1:0] out_request;
    logic [PORT_NUM-1:0][PORT_NUM-1:0] ip_grant;
    logic [PORT_NUM-1:0][VC_NUM-1:0] vc_grant;

    /*
    First stage:
    At each Input Port, Round-Robin arbitration is performed between the
    Virtual Channels requesting for the allocation of any Output Port.
    */
    genvar in_arb;
    generate
        for(in_arb=0; in_arb<PORT_NUM; in_arb++)
        begin: generate_input_round_robin_arbiters
            round_robin_arbiter #(
                .AGENTS_NUM(VC_NUM)
            )
            round_robin_arbiter (
                .rst(rst),
                .clk(clk),
                .requests_i(request_i[in_arb]),
                .grants_o(vc_grant[in_arb])
            );
        end
    endgenerate

    /*
    Second stage:
    At each Output Port, Round-Robin arbitration is performed
    between the Input Ports requesting for its allocation.
    */
    genvar out_arb;
    generate
        for(out_arb=0; out_arb<PORT_NUM; out_arb++)
        begin: generate_output_round_robin_arbiters
            round_robin_arbiter #(
                .AGENTS_NUM(PORT_NUM)
            )
            round_robin_arbiter (
                .rst(rst),
                .clk(clk),
                .requests_i(out_request[out_arb]),
                .grants_o(ip_grant[out_arb])
            );
        end
    endgenerate

    /*
    Combinational logic:
    - compute the request vectors for the second stage arbiters from
      the grants of the first order arbiters; i.e., the Input Ports
      will request the Output Port associated to the Virtual Channel
      winning the first stage arbitration (if there was anyone requesting);
    - compute the output grant matrix from the results of both the first
      and second stage arbiters; i.e., to the VCs winning first stage
      arbitration from Input Ports winning second stage arbitration
      will correspond a 1 in the output grant matrix.
    */
    always_comb
    begin
        out_request = {PORT_NUM*PORT_NUM{1'b0}};
        grant_o= {PORT_NUM*VC_NUM{1'b0}};

        for(int in_port = 0; in_port < PORT_NUM; in_port = in_port + 1)
        begin
            for(int in_vc = 0; in_vc < VC_NUM; in_vc = in_vc + 1)
            begin
                if(vc_grant[in_port][in_vc])
                begin
                    out_request[out_port_i[in_port][in_vc]][in_port] = 1'b1;
                    break;
                end
            end
        end

        for(int out_port = 0; out_port < PORT_NUM; out_port = out_port + 1)
        begin
            for(int in_port = 0; in_port < PORT_NUM; in_port = in_port + 1)
            begin
                for(int in_vc = 0; in_vc < VC_NUM; in_vc = in_vc + 1)
                begin
                    if(ip_grant[out_port][in_port] & vc_grant[in_port][in_vc])
                    begin
                        grant_o[in_port][in_vc] = 1'b1;
                        break;
                    end
                end
            end
        end

    end

endmodule
这段代码定义了一个名为 separable_input_first_allocator 的 SystemVerilog 模块,它实现了一个可分离的输入优先分配器。这种分配器通常用于网络中的路由器或交换机,用于在多个输入端口和输出端口之间进行分配。

以下是模块的主要部分:

1. 参数和输入/输出端口:模块有一个参数 VC_NUM,表示虚拟通道的数量。模块的输入包括复位信号 rst,时钟信号 clk,请求矩阵 request_i,以及每个输入端口的输出端口 out_port_i。模块的输出是授权矩阵 grant_o。

2. 内部变量:模块定义了三个内部变量 out_request,ip_grant 和 vc_grant,它们分别表示输出端口的请求,输入端口的授权,以及虚拟通道的授权。

3. round_robin_arbiter 实例:模块实例化了两组 round_robin_arbiter。第一组仲裁器用于在每个输入端口中,对请求任何输出端口的虚拟通道进行循环调度。第二组仲裁器用于在每个输出端口中,对请求其分配的输入端口进行循环调度。

4. 组合逻辑块:在 always_comb 块中,模块定义了在任何时候的行为。这包括计算第二阶段仲裁器的请求向量,以及计算输出授权矩阵。如果一个虚拟通道在第一阶段获得授权,那么对应的输入端口将请求与该虚拟通道关联的输出端口。如果一个输入端口在第二阶段获得授权,且其虚拟通道在第一阶段也获得授权,那么在输出授权矩阵中对应的位置将被设置为 1。

这个模块实现了一个简单的两阶段分配策略,适用于在多个输入端口和输出端口之间进行公平和高效的分配。
1. 输入端口:
- request_i:这是一个二维数组,表示每个输入端口和虚拟通道的请求。第一维的大小是 PORT_NUM,表示输入端口的数量。第二维的大小是 VC_NUM,表示每个输入端口的虚拟通道的数量。每个元素的值是 01,表示是否有请求。
- out_port_i:这是一个二维数组,表示每个输入端口的输出端口。第一维的大小是 PORT_NUM,表示输入端口的数量。第二维的大小是 VC_NUM,表示每个输入端口的虚拟通道的数量。每个元素的类型是 port_t,表示输出端口的类型。

2. 输出端口:
- grant_o:这是一个二维逻辑数组,表示每个输入端口和虚拟通道的授权。第一维的大小是 PORT_NUM,表示输入端口的数量。第二维的大小是 VC_NUM,表示每个输入端口的虚拟通道的数量。每个元素的值是 01,表示是否授权。

这个模块的功能是在多个输入端口和输出端口之间进行分配。每个输入端口都有多个虚拟通道,每个虚拟通道都可以请求一个输出端口。模块的任务是根据请求,决定哪个虚拟通道可以获得哪个输出端口的授权。

-根据第一阶段仲裁员的授权计算第二阶段仲裁员的请求向量;即,输入端口将请求与赢得第一阶段仲裁的虚拟通道关联的输出端口(如果有人请求);

-根据第一和第二阶段仲裁者的结果计算输出拨款矩阵;也就是说,对于从Input Ports赢得第一阶段仲裁的vc来说,赢得第二阶段仲裁的vc将在输出授权矩阵中对应1
这段代码是 separable_input_first_allocator 模块的一部分,它定义了在任何时候的行为。这包括计算第二阶段仲裁器的请求向量,以及计算输出授权矩阵。

1. out_request = {PORT_NUM*PORT_NUM{1'b0}}; 和 grant_o= {PORT_NUM*VC_NUM{1'b0}};:这两行代码将输出请求矩阵和输出授权矩阵重置为 02. 第一个 for 循环:这个循环遍历每个输入端口和虚拟通道。如果一个虚拟通道在第一阶段获得授权(vc_grant[in_port][in_vc]1),那么对应的输入端口将请求与该虚拟通道关联的输出端口(out_request[out_port_i[in_port][in_vc]][in_port] 被设置为 1)。然后退出虚拟通道的循环。

3. 第二个 for 循环:这个循环遍历每个输出端口,输入端口和虚拟通道。如果一个输入端口在第二阶段获得授权(ip_grant[out_port][in_port]1),且其虚拟通道在第一阶段也获得授权(vc_grant[in_port][in_vc]1),那么在输出授权矩阵中对应的位置将被设置为 1(grant_o[in_port][in_vc] 被设置为 1)。然后退出虚拟通道的循环。

这段代码实现了两阶段分配策略的第二阶段和最后的授权计算。

2.8vc_allocator.sv

2.8虚拟通道分配器该模块生成一个请求矩阵作为所包含的可分离输入优先分配器的输入;该矩阵基于目的下游输入端口的虚拟通道可用性,即该端口是否至少有一个输入缓冲区处于空闲状态。从内部计算的授权矩阵中,虚拟通道分配器模块生成下游路由器上赢得竞争的输入缓冲区的虚拟通道标识符。

import noc_params::*;

module vc_allocator #(
)(
    input rst,
    input clk,
    input [PORT_NUM-1:0][VC_NUM-1:0] idle_downstream_vc_i,
    input_block2vc_allocator.vc_allocator ib_if
);

    logic [PORT_NUM-1:0][VC_NUM-1:0] request_cmd;
    logic [PORT_NUM-1:0][VC_NUM-1:0] grant;

    logic [PORT_NUM-1:0][VC_NUM-1:0] is_available_vc, is_available_vc_next;

    separable_input_first_allocator #(
        .VC_NUM(VC_NUM)
    )
    separable_input_first_allocator (
        .rst(rst),
        .clk(clk),
        .request_i(request_cmd),
        .out_port_i(ib_if.out_port),
        .grant_o(grant)
    );

    /*
    Sequential logic:
    - reset on the rising edge of the rst input;
    - update the availability of downstream Virtual Channels.
    */
    always_ff@(posedge clk, posedge rst)
    begin
        if(rst)
        begin
            is_available_vc <= {PORT_NUM*VC_NUM{1'b1}};
        end
        else
        begin
            is_available_vc <= is_available_vc_next;
        end
    end

    /*
    Combinational logic:
    - compute the request matrix for the internal Separable Input-First
      Allocator, by setting to 1 the upstream Virtual Channels which are
      requesting for the allocation of a downstream Virtual Channel and
      whose associated downstream Input Port has at least one available
      Virtual Channel;
    - compute the outputs of the module from the grants matrix obtained
      from the Separable Input-First allocator and update the next
      value for the availability of downstream Virtual Channels if
      they have just been allocated;
    - update the next value for the availability of downstream Virtual
      Channels after their eventual deallocations.
    */
    always_comb
    begin
        is_available_vc_next = is_available_vc;
        for(int up_port = 0; up_port < PORT_NUM; up_port = up_port + 1)
        begin
            for(int up_vc = 0; up_vc < VC_NUM; up_vc = up_vc + 1)
            begin
                request_cmd[up_port][up_vc] = 1'b0;
                ib_if.vc_valid[up_port][up_vc] = 1'b0;
                ib_if.vc_new[up_port][up_vc] = {VC_SIZE{1'bx}};
            end
        end

        for(int up_port = 0; up_port < PORT_NUM; up_port = up_port + 1)
        begin
            for(int up_vc = 0; up_vc < VC_NUM; up_vc = up_vc + 1)
            begin
                if(ib_if.vc_request[up_port][up_vc] & is_available_vc[ib_if.out_port[up_port][up_vc]])
                begin
                    request_cmd[up_port][up_vc] = 1'b1;
                end
            end
        end

        for(int up_port = 0; up_port < PORT_NUM; up_port = up_port + 1)
        begin
            for(int up_vc = 0; up_vc < VC_NUM; up_vc = up_vc + 1)
            begin
                if(grant[up_port][up_vc])
                begin
                    ib_if.vc_new[up_port][up_vc] = assign_downstream_vc(ib_if.out_port[up_port][up_vc]);
                    ib_if.vc_valid[up_port][up_vc] = 1'b1;
                    is_available_vc_next[ib_if.out_port[up_port][up_vc]][ib_if.vc_new[up_port][up_vc]] = 1'b0;
                end
            end
        end

        for(int down_port = 0; down_port < PORT_NUM; down_port = down_port + 1)
        begin
            for(int down_vc = 0; down_vc < VC_NUM; down_vc = down_vc + 1)
            begin
                if(~is_available_vc[down_port][down_vc] & idle_downstream_vc_i[down_port][down_vc])
                begin
                    is_available_vc_next[down_port][down_vc] = 1'b1;
                end
            end
        end
        
    end

    /*
    Returns the first (starting from 0, without any Round-Robin
    mechanism) Virtual Channel available for allocation from
    the downstream Input Port specified as a parameter.
    */
    function logic [VC_SIZE-1:0] assign_downstream_vc (input port_t port);
        assign_downstream_vc = {VC_SIZE{1'bx}};
        for(int vc = 0; vc < VC_NUM; vc = vc + 1)
        begin
            if(is_available_vc[port][vc])
            begin
                assign_downstream_vc = vc;
                break;
            end
        end
    endfunction

endmodule
这段代码定义了一个名为 vc_allocator 的 SystemVerilog 模块,它实现了一个虚拟通道分配器。这种分配器通常用于网络中的路由器或交换机,用于在多个输入端口和虚拟通道之间进行分配。

以下是模块的主要部分:

1. 参数和输入/输出端口:模块的输入包括复位信号 rst,时钟信号 clk,每个输入端口和虚拟通道的空闲下游虚拟通道 idle_downstream_vc_i,以及输入块到虚拟通道分配器的接口 ib_if。模块没有明确的输出端口,但是通过 ib_if 接口返回结果。

2. 内部变量:模块定义了三个内部变量 request_cmd,grant 和 is_available_vc,它们分别表示请求命令,授权,以及每个输入端口和虚拟通道的可用性。

3. separable_input_first_allocator 实例:模块实例化了一个 separable_input_first_allocator,用于在每个输入端口和虚拟通道之间进行分配。

4. 时序逻辑块:在 always_ff@(posedge clk, posedge rst) 块中,定义了在时钟上升沿或复位信号上升沿时的行为。如果复位信号被激活,所有的虚拟通道将被设置为可用。否则,虚拟通道的可用性将被更新为下一个值。

5. 组合逻辑块:在 always_comb 块中,定义了在任何时候的行为。这包括计算内部分配器的请求矩阵,计算模块的输出,以及更新虚拟通道的可用性。如果一个虚拟通道请求分配,并且其关联的下游输入端口有可用的虚拟通道,那么将设置对应的请求命令。如果一个虚拟通道获得授权,那么将设置对应的输出,并更新虚拟通道的可用性。如果一个虚拟通道被释放,那么将更新其可用性。

6. 函数:模块定义了一个函数 assign_downstream_vc,用于返回指定下游输入端口的第一个可用的虚拟通道。

这个模块实现了一个简单的虚拟通道分配策略,适用于在多个输入端口和虚拟通道之间进行公平和高效的分配。
组合逻辑:

-计算内部可分离输入优先分配器的请求矩阵,通过将请求分配下游虚拟通道的上游虚拟通道设置为1,并且其关联的下游输入端口至少有一个可用的虚拟通道;

-从可分输入优先分配器获得的拨款矩阵中计算模块的输出,并更新下游虚拟通道可用性的下一个值,如果它们刚刚被分配;

-更新下游虚拟通道最终释放后可用性的下一个值。

这段代码是 vc_allocator 模块的一部分,它定义了在任何时候的行为。这包括计算内部分配器的请求矩阵,计算模块的输出,以及更新虚拟通道的可用性。

1. is_available_vc_next = is_available_vc;:这行代码将当前的虚拟通道可用性赋值给下一个虚拟通道可用性。

2. 第一个 for 循环:这个循环遍历每个输入端口和虚拟通道。所有的请求命令,虚拟通道有效性,和新的虚拟通道都被重置。

3. 第二个 for 循环:这个循环遍历每个输入端口和虚拟通道。如果一个虚拟通道请求分配,并且其关联的下游输入端口有可用的虚拟通道,那么将设置对应的请求命令。

4. 第三个 for 循环:这个循环遍历每个输入端口和虚拟通道。如果一个虚拟通道获得授权,那么将设置对应的输出,更新新的虚拟通道,并更新虚拟通道的可用性。

5. 第四个 for 循环:这个循环遍历每个下游输入端口和虚拟通道。如果一个虚拟通道被释放(即,它不再可用,但是下游输入端口有空闲的虚拟通道),那么将更新其可用性。

这段代码实现了虚拟通道分配策略的主要部分,包括计算请求,处理授权,以及更新虚拟通道的可用性。
这段代码定义了一个名为 assign_downstream_vc 的函数,它用于返回指定下游输入端口的第一个可用的虚拟通道。

1. function logic [VC_SIZE-1:0] assign_downstream_vc (input port_t port);:这是函数的声明,函数名为 assign_downstream_vc,输入参数为 port,返回类型为 logic [VC_SIZE-1:0],表示虚拟通道的编号。

2. assign_downstream_vc = {VC_SIZE{1'bx}};:这行代码将函数的返回值初始化为一个宽度为 VC_SIZE 的未定义逻辑向量。

3. for(int vc = 0; vc < VC_NUM; vc = vc + 1):这是一个 for 循环,循环变量 vc 从 0VC_NUM-1,表示每个虚拟通道。

4. if(is_available_vc[port][vc]):这是一个 if 语句,条件是当前虚拟通道是否可用。is_available_vc[port][vc] 是一个逻辑值,表示指定下游输入端口的当前虚拟通道是否可用。

5. assign_downstream_vc = vc;:如果当前虚拟通道可用,那么将函数的返回值设置为当前虚拟通道的编号。

6. break;:退出循环。这意味着一旦找到一个可用的虚拟通道,就会返回它的编号,并退出循环。

这个函数实现了在指定下游输入端口中,查找第一个可用的虚拟通道的功能。

2.9switch_allocator.sv


2.9交换机分配器交换分配器模块通过封装一个可分离的输入优先分配器,从而向交叉发送控制信号,解决了访问交叉开关的输入缓冲区之间的争用问题5Bar模块和输入缓冲区。对共享交叉条交换机的访问取决于先前对资源的授予(使用轮询策略),以及从流控制的角度来看,分配给上游输入缓冲区的下游虚拟通道的可用性。

import noc_params::*;

module switch_allocator #(
)(
    input rst,
    input clk,
    input [PORT_NUM-1:0][VC_NUM-1:0] on_off_i,
    input_block2switch_allocator.switch_allocator ib_if,
    switch_allocator2crossbar.switch_allocator xbar_if,
    output logic [PORT_NUM-1:0] valid_flit_o
);

    logic [PORT_NUM-1:0][VC_NUM-1:0] request_cmd;
    logic [PORT_NUM-1:0][VC_NUM-1:0] grant;

    separable_input_first_allocator #(
        .VC_NUM(VC_NUM)
    )
    separable_input_first_allocator (
        .rst(rst),
        .clk(clk),
        .request_i(request_cmd),
        .out_port_i(ib_if.out_port),
        .grant_o(grant)
    );

    /*
    Combinational logic:
    - compute the request matrix for the internal Separable Input-First
      Allocator, by setting to 1 the upstream Virtual Channels which are
      requesting for the allocation of a downstream Virtual Channel and
      whose associated downstream Virtual Channel is available from
      the on/off flow control point of view;
    - compute the outputs of the module from the grants matrix obtained
      from the Separable Input-First allocator.
    */
    always_comb
    begin
        for(int port = 0; port < PORT_NUM ; port = port + 1)
        begin
            ib_if.valid_sel[port] = 1'b0;
            valid_flit_o[port] = 1'b0;
            ib_if.vc_sel[port] = {VC_SIZE{1'b0}};
            xbar_if.input_vc_sel[port] = {PORT_SIZE{1'b0}};
            request_cmd[port]={VC_NUM{1'b0}};
        end

        for(int up_port = 0; up_port < PORT_NUM; up_port = up_port + 1)
        begin
            for(int up_vc = 0; up_vc < VC_NUM; up_vc = up_vc + 1)
            begin
                if(ib_if.switch_request[up_port][up_vc] & on_off_i[ib_if.out_port[up_port][up_vc]][ib_if.downstream_vc[up_port][up_vc]])
                begin
                    request_cmd[up_port][up_vc] = 1'b1;
                end
            end
        end

        for(int up_port = 0; up_port < PORT_NUM; up_port = up_port + 1)
        begin
            for(int up_vc = 0; up_vc < VC_NUM; up_vc = up_vc + 1)
            begin
                if(grant[up_port][up_vc])
                begin
                    ib_if.vc_sel[up_port] = up_vc;
                    ib_if.valid_sel[up_port] = 1'b1;
                    valid_flit_o[ib_if.out_port[up_port][up_vc]] = 1'b1;
                    xbar_if.input_vc_sel[ib_if.out_port[up_port][up_vc]] = up_port;
                end
            end
        end
        
    end

endmodule
这段代码定义了一个名为 switch_allocator 的 SystemVerilog 模块,它实现了一个交换分配器。这种分配器通常用于网络中的路由器或交换机,用于在多个输入端口和虚拟通道之间进行分配。

以下是模块的主要部分:

1. 参数和输入/输出端口:模块的输入包括复位信号 rst,时钟信号 clk,每个输入端口和虚拟通道的开关信号 on_off_i,输入块到交换分配器的接口 ib_if,交换分配器到交叉开关的接口 xbar_if。模块的输出是每个输入端口的有效数据信号 valid_flit_o。

2. 内部变量:模块定义了两个内部变量 request_cmd 和 grant,它们分别表示请求命令和授权。

3. separable_input_first_allocator 实例:模块实例化了一个 separable_input_first_allocator,用于在每个输入端口和虚拟通道之间进行分配。

4. 组合逻辑块:在 always_comb 块中,定义了在任何时候的行为。这包括计算内部分配器的请求矩阵,计算模块的输出。如果一个虚拟通道请求分配,并且其关联的下游虚拟通道可用,那么将设置对应的请求命令。如果一个虚拟通道获得授权,那么将设置对应的输出。

这个模块实现了一个简单的交换分配策略,适用于在多个输入端口和虚拟通道之间进行公平和高效的分配。

    input_block2switch_allocator.switch_allocator ib_if,
    switch_allocator2crossbar.switch_allocator xbar_if,
这段代码定义了两个输入接口:

1. ib_if:这是一个 input_block2switch_allocator.switch_allocator 类型的接口,用于从输入块到交换分配器的通信。

2. xbar_if:这是一个 switch_allocator2crossbar.switch_allocator 类型的接口,用于从交换分配器到交叉开关的通信。

在 SystemVerilog 中,接口是一种特殊的数据类型,可以包含数据、模块、任务和函数。接口可以用于模块之间的通信,使得模块间的连接更加清晰和模块化。

这种语法是正确的。在定义模块的输入和输出时,可以使用接口类型,而不仅仅是基本的数据类型。这样可以将一组相关的信号和行为打包在一起,使得代码更加清晰和易于管理。

在 SystemVerilog 中,接口(interface)本身并不直接定义为输入或输出,而是包含一组信号,这些信号可以是输入、输出或双向的。接口的输入或输出属性是由它在模块中的使用方式决定的。
组合逻辑:

-通过将上游虚拟通道设置为1,计算内部可分离输入优先分配器的请求矩阵,这些上游虚拟通道请求分配下游虚拟通道,并且从开/关流量控制的角度来看,其关联的下游虚拟通道可用;

-从可分离输入优先分配器获得的授权矩阵中计算模块的输出。
这段代码是 switch_allocator 模块的一部分,它定义了在任何时候的行为。这包括计算内部分配器的请求矩阵,计算模块的输出。

1. 第一个 for 循环:这个循环遍历每个端口。所有的有效选择信号,有效数据信号,虚拟通道选择信号,输入虚拟通道选择信号,和请求命令都被重置。

2. 第二个 for 循环:这个循环遍历每个输入端口和虚拟通道。如果一个虚拟通道请求分配,并且其关联的下游虚拟通道可用,那么将设置对应的请求命令。

3. 第三个 for 循环:这个循环遍历每个输入端口和虚拟通道。如果一个虚拟通道获得授权,那么将设置对应的输出,包括虚拟通道选择信号,有效选择信号,有效数据信号,和输入虚拟通道选择信号。

这段代码实现了交换分配策略的主要部分,包括计算请求,处理授权,以及计算输出。

2.10router.sv


2.10路由器每个路由器模块包含一个可变数量的输入端口、一个交叉条、一个虚拟通道分配器和一个交换机分配器,它们通过相关接口正确地相互连接,如图1所示。在每个端口上有两个接口(一个供路由器作为下行接口,另一个作为上行接口)所述通信的上游)用于将所述路由器连接到另一路由器;每个路由器模块所暴露的接口将在第4节中详细解释,原理图如图2所示。

这段代码定义了一个名为 router 的 SystemVerilog 模块,它实现了一个网络路由器。这个路由器包含一个输入块(input_block)、一个交叉开关(crossbar)、一个交换分配器(switch_allocator)和一个虚拟通道分配器(vc_allocator)。

以下是模块的主要部分:

1. 参数和输入/输出端口:模块的输入包括时钟信号 clk,复位信号 rst,以及五个上游和下游的路由器接口。模块的输出是每个输入端口的错误信号 error_o。

2. 内部变量:模块定义了一些内部变量,包括数据输出、有效数据输出、开关输入、可分配输入、数据输入、有效数据输入、开关输出和可分配输出。

3. 组合逻辑块:在 always_comb 块中,定义了在任何时候的行为。这包括从上游和下游路由器接口读取数据和控制信号,以及向上游和下游路由器接口写入数据和控制信号。

4. 接口实例:模块实例化了四个接口,包括从输入块到交叉开关的接口 ib2xbar_if,从输入块到交换分配器的接口 ib2sa_if,从输入块到虚拟通道分配器的接口 ib2va_if,以及从交换分配器到交叉开关的接口 sa2xbar_if。

5. 子模块实例:模块实例化了一个输入块 input_block,一个交叉开关 crossbar,一个交换分配器 switch_allocator,以及一个虚拟通道分配器 vc_allocator。

这个模块实现了一个网络路由器的主要功能,包括数据的接收、分配、交换和发送。
    flit_t data_out [PORT_NUM-1:0];
    logic  [PORT_NUM-1:0] is_valid_out;
    logic  [PORT_NUM-1:0] [VC_NUM-1:0] is_on_off_in;
    logic  [PORT_NUM-1:0] [VC_NUM-1:0] is_allocatable_in;

    //connections from downstream
    flit_t data_in [PORT_NUM-1:0];
    logic  is_valid_in [PORT_NUM-1:0];
    logic  [VC_NUM-1:0] is_on_off_out [PORT_NUM-1:0];
    logic  [VC_NUM-1:0] is_allocatable_out [PORT_NUM-1:0];
    这两段代码都定义了一些多维逻辑数组,但是数组的维度声明顺序不同。

在 SystemVerilog 中,多维数组可以是打包的(packed)或者是展开的(unpacked)。打包数组的所有维度在内存中是连续的,而展开数组的各个元素可以是任意的数据类型,包括其他的数组。

1. logic [PORT_NUM-1:0] is_valid_out;:这是一个一维打包数组,包含 PORT_NUM 个逻辑值。

2. logic [PORT_NUM-1:0] [VC_NUM-1:0] is_on_off_in;:这是一个二维打包数组,第一维大小为 PORT_NUM,第二维大小为 VC_NUM3. logic is_valid_in [PORT_NUM-1:0];:这是一个一维展开数组,包含 PORT_NUM 个逻辑值。

4. logic [VC_NUM-1:0] is_on_off_out [PORT_NUM-1:0];:这是一个二维数组,第一维(打包维度)大小为 VC_NUM,第二维(展开维度)大小为 PORT_NUM。

所以,logic [PORT_NUM-1:0] [VC_NUM-1:0] is_on_off_in; 和 logic [VC_NUM-1:0] is_on_off_out [PORT_NUM-1:0]; 定义的是同样大小的数组,但是维度的顺序不同。在访问这两个数组时,需要注意维度的顺序。
SystemVerilog 中,接口实例的语法如下:
interface_name instance_name();
其中,interface_name 是接口的名称,instance_name 是你为这个接口实例选择的名称。

例如,在你的代码中:
input_block2crossbar ib2xbar_if();
input_block2switch_allocator ib2sa_if();
input_block2vc_allocator ib2va_if();
switch_allocator2crossbar sa2xbar_if();
这四行代码分别实例化了四个接口:input_block2crossbar,input_block2switch_allocator,input_block2vc_allocator 和 switch_allocator2crossbar。这些接口实例的名称分别是 ib2xbar_if,ib2sa_if,ib2va_if 和 sa2xbar_if。

注意,接口实例化后,可以像使用普通变量一样使用它们。例如,你可以将它们连接到模块的输入/输出端口,或者在代码中引用它们。
这段代码定义了四个接口实例和四个子模块实例:

1. 接口实例:这四个接口实例是用于模块间通信的接口。它们分别是:
- input_block2crossbar ib2xbar_if;:从输入块到交叉开关的接口。
- input_block2switch_allocator ib2sa_if;:从输入块到交换分配器的接口。
- input_block2vc_allocator ib2va_if;:从输入块到虚拟通道分配器的接口。
- switch_allocator2crossbar sa2xbar_if;:从交换分配器到交叉开关的接口。

2. 子模块实例:这四个子模块实例是构成路由器的主要部分。它们分别是:
- input_block:这是一个输入块模块,负责接收和缓存数据。
- crossbar:这是一个交叉开关模块,负责在输入和输出端口之间进行数据交换。
- switch_allocator:这是一个交换分配器模块,负责在输入端口和虚拟通道之间进行分配。
- vc_allocator:这是一个虚拟通道分配器模块,负责在输入端口和虚拟通道之间进行分配。

每个子模块实例都有一些参数和输入/输出端口。参数是在模块实例化时设置的常量值,输入/输出端口是模块与外部世界交互的接口。在这个例子中,每个子模块的输入/输出端口都连接到了一些内部变量或者接口实例。

2.11mesh.sv,node_link.sv,router_link.sv


2.11网格该mesh模块包含连接在具有二维mesh拓扑的直连网络中的可变数量的路由器,该mesh的两个维度都可以定义为该模块的参数;在最后阶段必须设置的网格的另一个参数是输入缓冲区的大小(即,可以存储在每个虚拟通道中的flits的数量)。该模块提供了一个接口,外部节点可以连接到该接口以进行通信通过片上网络互连网络相互连接。

import noc_params::*;

module mesh #(
    parameter BUFFER_SIZE = 8,
    parameter MESH_SIZE_X = 2,
    parameter MESH_SIZE_Y = 3
)(
    input clk,
    input rst,
    output logic [VC_NUM-1:0] error_o [MESH_SIZE_X-1:0][MESH_SIZE_Y-1:0][PORT_NUM-1:0],
    //connections to all local Router interfaces
    output flit_t [MESH_SIZE_X-1:0][MESH_SIZE_Y-1:0] data_o,
    output logic [MESH_SIZE_X-1:0][MESH_SIZE_Y-1:0] is_valid_o,
    input [MESH_SIZE_X-1:0][MESH_SIZE_Y-1:0][VC_NUM-1:0] is_on_off_i,
    input [MESH_SIZE_X-1:0][MESH_SIZE_Y-1:0][VC_NUM-1:0] is_allocatable_i,
    input flit_t [MESH_SIZE_X-1:0][MESH_SIZE_Y-1:0] data_i,
    input [MESH_SIZE_X-1:0][MESH_SIZE_Y-1:0] is_valid_i,
    output logic [MESH_SIZE_X-1:0][MESH_SIZE_Y-1:0][VC_NUM-1:0] is_on_off_o,
    output logic [MESH_SIZE_X-1:0][MESH_SIZE_Y-1:0][VC_NUM-1:0] is_allocatable_o
);

    genvar row, col;
    generate
        for(row=0; row<MESH_SIZE_Y; row++)
        begin: mesh_row
            for(col=0; col<MESH_SIZE_X; col++)
            begin: mesh_col
                //interfaces instantiation
                router2router local_up();
                router2router north_up();
                router2router south_up();
                router2router west_up();
                router2router east_up();
                router2router local_down();
                router2router north_down();
                router2router south_down();
                router2router west_down();
                router2router east_down();
                //router instantiation
                router #(
                    .BUFFER_SIZE(BUFFER_SIZE),
                    .X_CURRENT(col),
                    .Y_CURRENT(row)
                )
                router (
                    .clk(clk),
                    .rst(rst),
                    //upstream interfaces connections 
                    .router_if_local_up(local_up),
                    .router_if_north_up(north_up),
                    .router_if_south_up(south_up),
                    .router_if_west_up(west_up),
                    .router_if_east_up(east_up),
                    //downstream interfaces connections
                    .router_if_local_down(local_down),
                    .router_if_north_down(north_down),
                    .router_if_south_down(south_down),
                    .router_if_west_down(west_down),
                    .router_if_east_down(east_down),
                    .error_o(error_o[col][row])
                );
            end
        end

        for(row=0; row<MESH_SIZE_Y-1; row++)
        begin: vertical_links_row
            for(col=0; col<MESH_SIZE_X; col++)
            begin: vertical_links_col
                router_link link_one (
                    .router_if_up(mesh_row[row].mesh_col[col].south_down),
                    .router_if_down(mesh_row[row+1].mesh_col[col].north_up)
                );

                router_link link_two (
                    .router_if_up(mesh_row[row+1].mesh_col[col].north_down),
                    .router_if_down(mesh_row[row].mesh_col[col].south_up)
                );
                
            end
        end

        for(row=0; row<MESH_SIZE_Y; row++)
        begin: horizontal_links_row
            for(col=0; col<MESH_SIZE_X-1; col++)
            begin: horizontal_links_col
                router_link link_one (
                    .router_if_up(mesh_row[row].mesh_col[col].east_down),
                    .router_if_down(mesh_row[row].mesh_col[col+1].west_up)
                );

                router_link link_two (
                    .router_if_up(mesh_row[row].mesh_col[col+1].west_down),
                    .router_if_down(mesh_row[row].mesh_col[col].east_up)
                );

            end
        end

        for(row=0; row<MESH_SIZE_Y; row++)
        begin: node_connection_row
            for(col=0; col<MESH_SIZE_X; col++)
            begin: node_connection_col
                node_link node_link (
                    .router_if_up(mesh_row[row].mesh_col[col].local_down),
                    .router_if_down(mesh_row[row].mesh_col[col].local_up),
                    .data_i(data_i[col][row]),
                    .is_valid_i(is_valid_i[col][row]),
                    .is_on_off_o(is_on_off_o[col][row]),
                    .is_allocatable_o(is_allocatable_o[col][row]),
                    .data_o(data_o[col][row]),
                    .is_valid_o(is_valid_o[col][row]),
                    .is_on_off_i(is_on_off_i[col][row]),
                    .is_allocatable_i(is_allocatable_i[col][row])
                );
            end
        end

    endgenerate

endmodule
这段代码定义了一个名为 mesh 的 SystemVerilog 模块,它实现了一个网络中的网格拓扑结构。这个网格由多个路由器(router)组成,每个路由器都连接到其相邻的路由器。

以下是模块的主要部分:

1. 参数和输入/输出端口:模块的输入包括时钟信号 clk,复位信号 rst,以及各种数据和控制信号。模块的输出是每个路由器的错误信号 error_o,以及各种数据和控制信号。

2. 生成(generate)语句:生成语句用于创建网格中的路由器和连接。这里有四个生成语句:
- 第一个生成语句创建了 MESH_SIZE_YMESH_SIZE_X 列的路由器。每个路由器都有五个上游接口和五个下游接口。
- 第二个生成语句创建了垂直方向的连接,连接每一行的路由器。
- 第三个生成语句创建了水平方向的连接,连接每一列的路由器。
- 第四个生成语句创建了节点连接,连接每个路由器和其相应的节点。

3. 接口实例:每个路由器都有十个接口实例,包括五个上游接口和五个下游接口。

4. 子模块实例:每个路由器都是一个 router 子模块的实例。每个连接都是一个 router_link 或 node_link 子模块的实例。

这个模块实现了一个网络中的网格拓扑结构,包括路由器的创建和连接,以及数据和控制信号的传递。
module router_link (
    router2router.upstream router_if_up,
    router2router.downstream router_if_down
);

    always_comb
    begin
        router_if_up.data = router_if_down.data;
        router_if_up.is_valid = router_if_down.is_valid;
        router_if_down.is_on_off = router_if_up.is_on_off;
        router_if_down.is_allocatable = router_if_up.is_allocatable;
    end

endmodule
import noc_params::*;

module node_link (
    router2router.upstream router_if_up,
    router2router.downstream router_if_down,
    //upstream connections
    input flit_t data_i,
    input is_valid_i,
    output logic [VC_NUM-1:0] is_on_off_o,
    output logic [VC_NUM-1:0] is_allocatable_o,
    //downstream connections
    output flit_t data_o,
    output logic is_valid_o,
    input [VC_NUM-1:0] is_on_off_i,
    input [VC_NUM-1:0] is_allocatable_i
);

    always_comb
    begin
        router_if_up.data = data_i;
        router_if_up.is_valid = is_valid_i;
        is_on_off_o = router_if_up.is_on_off;
        is_allocatable_o = router_if_up.is_allocatable;
        data_o = router_if_down.data;
        is_valid_o = router_if_down.is_valid;
        router_if_down.is_on_off = is_on_off_i;
        router_if_down.is_allocatable = is_allocatable_i;
    end

endmodule

2.12input_block.sv


这段代码定义了一个名为 input_block 的 SystemVerilog 模块,它实现了一个网络路由器中的输入块。输入块包含多个输入端口,每个输入端口都有一个缓冲区来存储接收到的数据。

以下是模块的主要部分:

  1. 参数和输入/输出端口:模块的输入包括数据输入 data_i,有效数据输入 valid_flit_i,复位信号 rst,时钟信号 clk,以及三个接口 crossbar_if,sa_if,va_if。模块的输出包括开关输出 on_off_o,可分配输出 vc_allocatable_o,以及错误输出 error_o。

  2. 内部变量:模块定义了一些内部变量,包括满状态 is_full,空状态 is_empty,以及输出端口 out_port。

  3. 赋值语句:模块将内部变量 out_port 连接到接口 va_if 和 sa_if 的输出端口。

  4. 生成(generate)语句:生成语句用于创建输入端口。这里有一个生成语句,它创建了 PORT_NUM 个输入端口。每个输入端口都是一个 input_port 子模块的实例,它的输入/输出端口都连接到了一些内部变量或者接口实例。

这个模块实现了一个网络路由器中的输入块的主要功能,包括数据的接收和缓存,以及与交叉开关、交换分配器和虚拟通道分配器的通信。

import noc_params::*;

module input_block #(
    parameter PORT_NUM = 5,
    parameter BUFFER_SIZE = 8,
    parameter X_CURRENT = MESH_SIZE_X/2,
    parameter Y_CURRENT = MESH_SIZE_Y/2
)(
    input flit_t data_i [PORT_NUM-1:0],
    input valid_flit_i [PORT_NUM-1:0],
    input rst,
    input clk,
    input_block2crossbar.input_block crossbar_if,
    input_block2switch_allocator.input_block sa_if,
    input_block2vc_allocator.input_block va_if,
    output logic [VC_NUM-1:0] on_off_o [PORT_NUM-1:0],
    output logic [VC_NUM-1:0] vc_allocatable_o [PORT_NUM-1:0],
    output logic [VC_NUM-1:0] error_o [PORT_NUM-1:0]
);
    
    logic [VC_NUM-1:0] is_full [PORT_NUM-1:0];
    logic [VC_NUM-1:0] is_empty [PORT_NUM-1:0];

    port_t [VC_NUM-1:0] out_port [PORT_NUM-1:0];

    assign va_if.out_port = out_port;
    assign sa_if.out_port = out_port;

    /*
    The Input Block module contains all the PORT_NUM
    Input Ports composing the Router, making it easier
    to connect all of them through one single interface
    per each other module, i.e., the Crossbar, the
    Virtual Channel Allocator and the Switch Allocator.
    */
    genvar ip;
    generate
        for(ip=0; ip<PORT_NUM; ip++)
        begin: generate_input_ports
            input_port #(
                .BUFFER_SIZE(BUFFER_SIZE),
                .X_CURRENT(X_CURRENT),
                .Y_CURRENT(Y_CURRENT)
            )
            input_port (
                .data_i(data_i[ip]),
                .valid_flit_i(valid_flit_i[ip]),
                .rst(rst),
                .clk(clk),
                .sa_sel_vc_i(sa_if.vc_sel[ip]),
                .va_new_vc_i(va_if.vc_new[ip]),
                .va_valid_i(va_if.vc_valid[ip]),
                .sa_valid_i(sa_if.valid_sel[ip]),
                .xb_flit_o(crossbar_if.flit[ip]),
                .is_on_off_o(on_off_o[ip]),
                .is_allocatable_vc_o(vc_allocatable_o[ip]),
                .va_request_o(va_if.vc_request[ip]),
                .sa_request_o(sa_if.switch_request[ip]),
                .sa_downstream_vc_o(sa_if.downstream_vc[ip]),
                .out_port_o(out_port[ip]),
                .is_full_o(is_full[ip]),
                .is_empty_o(is_empty[ip]),
                .error_o(error_o[ip])
            );
        end
    endgenerate

endmodule
这段代码定义了一个名为 input_block 的 SystemVerilog 模块,它实现了一个网络路由器中的输入块。输入块包含多个输入端口,每个输入端口都有一个缓冲区来存储接收到的数据。

以下是模块的主要部分:

1. 参数和输入/输出端口:模块的输入包括数据输入 data_i,有效数据输入 valid_flit_i,复位信号 rst,时钟信号 clk,以及三个接口 crossbar_if,sa_if,va_if。模块的输出包括开关输出 on_off_o,可分配输出 vc_allocatable_o,以及错误输出 error_o。

2. 内部变量:模块定义了一些内部变量,包括满状态 is_full,空状态 is_empty,以及输出端口 out_port。

3. 赋值语句:模块将内部变量 out_port 连接到接口 va_if 和 sa_if 的输出端口。

4. 生成(generate)语句:生成语句用于创建输入端口。这里有一个生成语句,它创建了 PORT_NUM 个输入端口。每个输入端口都是一个 input_port 子模块的实例,它的输入/输出端口都连接到了一些内部变量或者接口实例。

这个模块实现了一个网络路由器中的输入块的主要功能,包括数据的接收和缓存,以及与交叉开关、交换分配器和虚拟通道分配器的通信。

输入块模块包含组成路由器的所有PORT_NUM输入端口,使得通过每个模块(Crossbar, Virtual Channel AllocatorSwitch Allocator)的单个接口连接所有端口变得更加容易。

2.13接口input_block2crossbar.sv

这段代码定义了一个名为 input_block2crossbar 的 SystemVerilog 接口。接口是一种特殊的数据类型,可以包含数据、模块、任务和函数。接口可以用于模块之间的通信,使得模块间的连接更加清晰和模块化。

以下是接口的主要部分:

1. interface input_block2crossbar;:这是接口的声明,接口的名称是 input_block2crossbar。

2. flit_t flit [PORT_NUM-1:0];:这是接口的一个数据成员,名为 flit,类型为 flit_t,是一个大小为 PORT_NUM 的数组。

3. modport input_block (output flit);:这是接口的一个模块端口(modport),名为 input_block。模块端口定义了接口的使用方式。在这个模块端口中,flit 被定义为输出。

4. modport crossbar (input flit);:这是接口的另一个模块端口,名为 crossbar。在这个模块端口中,flit 被定义为输入。

5. endinterface:这是接口的结束。

这个接口定义了一个名为 flit 的数据信号,以及两个模块端口 input_block 和 crossbar。input_block 模块端口将 flit 作为输出,crossbar 模块端口将 flit 作为输入。这意味着,当这个接口被用于连接 input_block 和 crossbar 时,数据将从 input_block 输出到 crossbar。

import noc_params::*;

interface input_block2crossbar;

    flit_t flit [PORT_NUM-1:0];

    modport input_block (
        output flit
    );

    modport crossbar (
        input flit
    );

endinterface

2.14接口input_block2switch_allocator.sv

import noc_params::*;

interface input_block2switch_allocator;

    port_t [VC_NUM-1:0] out_port [PORT_NUM-1:0];
    logic [VC_SIZE-1:0] vc_sel [PORT_NUM-1:0];
    logic valid_sel [PORT_NUM-1:0];
    logic [VC_SIZE-1:0] downstream_vc [PORT_NUM-1:0][VC_NUM-1:0];
    logic switch_request [PORT_NUM-1:0][VC_NUM-1:0];    //from Input Buffer, asserted when in SA state
    
    modport input_block (
        input vc_sel,
        input valid_sel,
        output out_port,
        output downstream_vc,
        output switch_request
    );

    modport switch_allocator (
        output vc_sel,
        output valid_sel,
        input out_port,
        input downstream_vc,
        input switch_request
    );

endinterface

这段代码定义了一个名为 input_block2switch_allocator 的 SystemVerilog 接口。接口是一种特殊的数据类型,可以包含数据、模块、任务和函数。接口可以用于模块之间的通信,使得模块间的连接更加清晰和模块化。

以下是接口的主要部分:

  1. interface input_block2switch_allocator;:这是接口的声明,接口的名称是 input_block2switch_allocator。

  2. port_t [VC_NUM-1:0] out_port [PORT_NUM-1:0];:这是接口的一个数据成员,名为 out_port,类型为 port_t,是一个二维数组。

  3. logic [VC_SIZE-1:0] vc_sel [PORT_NUM-1:0];:这是接口的一个数据成员,名为 vc_sel,类型为 logic,是一个二维数组。

  4. logic valid_sel [PORT_NUM-1:0];:这是接口的一个数据成员,名为 valid_sel,类型为 logic,是一个一维数组。

  5. logic [VC_SIZE-1:0] downstream_vc [PORT_NUM-1:0][VC_NUM-1:0];:这是接口的一个数据成员,名为 downstream_vc,类型为 logic,是一个三维数组。

  6. logic switch_request [PORT_NUM-1:0][VC_NUM-1:0];:这是接口的一个数据成员,名为 switch_request,类型为 logic,是一个二维数组。

  7. modport input_block (input vc_sel, input valid_sel, output out_port, output downstream_vc, output switch_request);:这是接口的一个模块端口(modport),名为 input_block。模块端口定义了接口的使用方式。在这个模块端口中,vc_sel 和 valid_sel 被定义为输入,out_port,downstream_vc 和 switch_request 被定义为输出。

  8. modport switch_allocator (output vc_sel, output valid_sel, input out_port, input downstream_vc, input switch_request);:这是接口的另一个模块端口,名为 switch_allocator。在这个模块端口中,vc_sel 和 valid_sel 被定义为输出,out_port,downstream_vc 和 switch_request 被定义为输入。

  9. endinterface:这是接口的结束。

这个接口定义了一些数据信号和两个模块端口 input_block 和 switch_allocator。这些信号和模块端口可以用于 input_block 和 switch_allocator 之间的通信。

2.15接口input_block2vc_allocator.sv

import noc_params::*;

interface input_block2vc_allocator;

    logic [VC_SIZE-1:0] vc_new [PORT_NUM-1:0] [VC_NUM-1:0];
    logic [VC_NUM-1:0] vc_valid [PORT_NUM-1:0];
    logic [VC_NUM-1:0] vc_request [PORT_NUM-1:0];
    port_t [VC_NUM-1:0] out_port [PORT_NUM-1:0];

    modport input_block (
        input vc_new,
        input vc_valid,
        output vc_request,
        output out_port
    );

    modport vc_allocator (
        output vc_new,
        output vc_valid,
        input vc_request,
        input out_port
    );

endinterface

2.16接口router2router.sv

import noc_params::*;

interface router2router;

    flit_t data;
    logic is_valid;
    logic [VC_NUM-1:0] is_on_off;
    logic [VC_NUM-1:0] is_allocatable;

    modport upstream (
        output data,
        output is_valid,
        input is_on_off,
        input is_allocatable
    );

    modport downstream (
        input data,
        input is_valid,
        output is_on_off,
        output is_allocatable
    );

endinterface

2.18接口switch_allocator2crossbar.sv

import noc_params::*;

interface switch_allocator2crossbar;

    logic [PORT_SIZE-1:0] input_vc_sel [PORT_NUM-1:0];

    modport switch_allocator (
        output input_vc_sel
    );

    modport crossbar (
        input input_vc_sel
    );

endinterface

接口图片

在这里插入图片描述
在这里插入图片描述
router.sv连接使用

import noc_params::*;

module router #(
    parameter BUFFER_SIZE = 8,
    parameter X_CURRENT = MESH_SIZE_X/2,
    parameter Y_CURRENT = MESH_SIZE_Y/2
)( 
    input clk,
    input rst,
    router2router.upstream router_if_local_up,
    router2router.upstream router_if_north_up,
    router2router.upstream router_if_south_up,
    router2router.upstream router_if_west_up,
    router2router.upstream router_if_east_up,
    router2router.downstream router_if_local_down,
    router2router.downstream router_if_north_down,
    router2router.downstream router_if_south_down,
    router2router.downstream router_if_west_down,
    router2router.downstream router_if_east_down,
    output logic [VC_NUM-1:0] error_o [PORT_NUM-1:0]
);

    //connections from upstream
    flit_t data_out [PORT_NUM-1:0];
    logic  [PORT_NUM-1:0] is_valid_out;
    logic  [PORT_NUM-1:0] [VC_NUM-1:0] is_on_off_in;
    logic  [PORT_NUM-1:0] [VC_NUM-1:0] is_allocatable_in;

    //connections from downstream
    flit_t data_in [PORT_NUM-1:0];
    logic  is_valid_in [PORT_NUM-1:0];
    logic  [VC_NUM-1:0] is_on_off_out [PORT_NUM-1:0];
    logic  [VC_NUM-1:0] is_allocatable_out [PORT_NUM-1:0];

    always_comb
    begin
        router_if_local_up.data = data_out[LOCAL];
        router_if_north_up.data = data_out[NORTH];
        router_if_south_up.data = data_out[SOUTH];
        router_if_west_up.data  = data_out[WEST];
        router_if_east_up.data  = data_out[EAST];

        router_if_local_up.is_valid = is_valid_out[LOCAL];
        router_if_north_up.is_valid = is_valid_out[NORTH];
        router_if_south_up.is_valid = is_valid_out[SOUTH];
        router_if_west_up.is_valid  = is_valid_out[WEST];
        router_if_east_up.is_valid  = is_valid_out[EAST];

        is_on_off_in[LOCAL] = router_if_local_up.is_on_off;
        is_on_off_in[NORTH] = router_if_north_up.is_on_off;
        is_on_off_in[SOUTH] = router_if_south_up.is_on_off;
        is_on_off_in[WEST]  = router_if_west_up.is_on_off;
        is_on_off_in[EAST]  = router_if_east_up.is_on_off;

        is_allocatable_in[LOCAL] = router_if_local_up.is_allocatable;
        is_allocatable_in[NORTH] = router_if_north_up.is_allocatable;
        is_allocatable_in[SOUTH] = router_if_south_up.is_allocatable;
        is_allocatable_in[WEST]  = router_if_west_up.is_allocatable;
        is_allocatable_in[EAST]  = router_if_east_up.is_allocatable;

        data_in[LOCAL] = router_if_local_down.data;
        data_in[NORTH] = router_if_north_down.data;
        data_in[SOUTH] = router_if_south_down.data;
        data_in[WEST]  = router_if_west_down.data;
        data_in[EAST]  = router_if_east_down.data;

        is_valid_in[LOCAL] = router_if_local_down.is_valid;
        is_valid_in[NORTH] = router_if_north_down.is_valid;
        is_valid_in[SOUTH] = router_if_south_down.is_valid;
        is_valid_in[WEST]  = router_if_west_down.is_valid;
        is_valid_in[EAST]  = router_if_east_down.is_valid;

        router_if_local_down.is_on_off = is_on_off_out[LOCAL];
        router_if_north_down.is_on_off = is_on_off_out[NORTH];
        router_if_south_down.is_on_off = is_on_off_out[SOUTH];
        router_if_west_down.is_on_off  = is_on_off_out[WEST];
        router_if_east_down.is_on_off  = is_on_off_out[EAST];

        router_if_local_down.is_allocatable = is_allocatable_out[LOCAL];
        router_if_north_down.is_allocatable = is_allocatable_out[NORTH];
        router_if_south_down.is_allocatable = is_allocatable_out[SOUTH];
        router_if_west_down.is_allocatable  = is_allocatable_out[WEST];
        router_if_east_down.is_allocatable  = is_allocatable_out[EAST];

    end

    input_block2crossbar ib2xbar_if();
    input_block2switch_allocator ib2sa_if();
    input_block2vc_allocator ib2va_if();
    switch_allocator2crossbar sa2xbar_if();

    input_block #(
        .BUFFER_SIZE(BUFFER_SIZE),
        .X_CURRENT(X_CURRENT),
        .Y_CURRENT(Y_CURRENT)
    )
    input_block (
        .rst(rst),
        .clk(clk),
        .data_i(data_in),
        .valid_flit_i(is_valid_in),
        .crossbar_if(ib2xbar_if),
        .sa_if(ib2sa_if),
        .va_if(ib2va_if),
        .on_off_o(is_on_off_out),
        .vc_allocatable_o(is_allocatable_out),
        .error_o(error_o)
    );

    crossbar #(
    )
    crossbar (
        .ib_if(ib2xbar_if),
        .sa_if(sa2xbar_if),
        .data_o(data_out)
    );

    switch_allocator #(
    )
    switch_allocator (
        .rst(rst),
        .clk(clk),
        .on_off_i(is_on_off_in),
        .ib_if(ib2sa_if),
        .xbar_if(sa2xbar_if),
        .valid_flit_o(is_valid_out)
    );
    
    vc_allocator #(
    )
    vc_allocator (
        .rst(rst),
        .clk(clk),
        .idle_downstream_vc_i(is_allocatable_in),
        .ib_if(ib2va_if)
    );

endmodule
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
### 回答1: NOC Verilog,简称为NOC Verilog语言,是一种基于硅基微处理器的硬件描述语言,用于描述网络栅格(Network-on-Chip,简称NOC)结构的特性和功能。NOC是一种基于片上网络(Network-on-Chip,简称NOC)的通信架构,用于在片上系统(System-on-Chip,简称SOC)中实现不同功能模块之间的通信和数据传输。 NOC Verilog语言的设计是为了描述和设计NOC的结构和功能,使得硬件工程师可以使用这种语言进行硬件设计和验证。NOC Verilog提供了一种规范和标准的方法来描述NOC的拓扑结构、数据传输和通信协议等方面的信息。 使用NOC Verilog语言可以轻松地建立NOC的模型,并对其进行仿真和验证。通过定义NOC的结构和连接方式,可以方便地确定数据从一个模块到另一个模块的路径,并实现单元之间的通信。 NOC Verilog语言还支持对NOC进行性能分析和优化。在设计NOC时,可以通过修改NOC Verilog代码来调整网络的带宽、时延和吞吐量等性能指标。这样可以根据应用的需求和系统的约束,对NOC进行定制和优化。 总而言之,NOC Verilog是一种用于描述和设计网络栅格结构的硬件描述语言。它提供了一种方便、标准化和规范化的方法,使硬件工程师能够更轻松地设计、验证和优化NOC。 ### 回答2: NOC网络隔离开关(Network on Chip)的缩写,它是一种用于集成电路内部的通信架构NOC的设计是为了解决集成电路内各个模块之间的通信问题,特别是在多处理器系统中。NOC的目标是提供高效的通信通道,减少通信延迟,提高系统性能。 Verilog是一种硬件描述语言,用于描述数字电路的结构和行为。它主要用于设计和仿真集成电路,并且对于设计大型复杂系统非常有用。Verilog的设计可以通过编写代码来定义电路的结构和行为,并使用仿真工具验证电路的功能。 当把NOCVerilog结合起来时,可以使用Verilog来描述和设计NOC结构。通过使用Verilog语言,可以定义NOC的各个模块以及它们之间的通信方式。同时,Verilog还可以用于验证NOC的功能,通过仿真工具来模拟NOC的行为,以确保其在实际应用中的正确性和稳定性。 总而言之,NOC Verilog指的是使用Verilog语言来描述和设计网络隔离开关的结构和行为。通过使用Verilog语言,可以更好地实现NOC的设计、验证和优化,提高集成电路系统的性能和可靠性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

weixin_44781508

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值