Menu Close

Ethernet module (IP core) RISCV interface package

When we have learned the relevant knowledge of Ethernet and the application of the Ethernet module IP core in the FPGA, we can encapsulate the Ethernet IP into the IP module required by the RISCV cpu. The repackaged module will retrofit the previous Ethernet module. It is mainly to remove the UDP layer and the IP layer. The functions of these layers will be implemented at the software level later (the protocol stack at the software level not only implements UDP, IP, but also other protocols that need to be supported), so the repackaged module only needs to keep the mac layer. Okay, at the same time design the bus interface with RISCV cup.

The modified Ethernet module block diagram:

ethernet verilog code structure

figure 1

The internal block diagram of eth_core.v is shown in Figure 2:

ethernet verilog module flow chart

figure 2

The interface on the cpu side:

Receiver: The interface on the Ethernet CPU side is designed as a dual-port (IP_RX_DRAM), the depth of the dual-port is 4096 bytes, and it is divided into ping-pong buffers. Since the interface is 32bit, the depth of each buffer is 512.

Transmitter: designed as dual-port (MAC_TX_DRAM), the depth of dual-port is 4096 bytes, divided into a ping-pong buffer. The interface is 32bit, and the depth of each buffer is 512.

Receive and send dual-port control byte definition: bit[31] on the CPU side is the control word, bit[27:16] is the number of bytes (bytes) of the received Ethernet data (length).

mac_rx_web: cpu write signal, used when clearing the current buffer after getting a complete data packet.

mac_rx_addrb: cpu address bus, used to indicate read and write dual-port addresses

mac_rx_doutb[31:0]: The read data channel in the dual-port, connected to the read data bus of the CPU .

 

mac_tx_wea: cpu write signal, used to write software data into dual ports.

mac_tx_addra: cpu address bus, indicating the address of the dual-port.

mac_tx_dina[31:0] Write to dual port cpu data write channel

mac_tx_douta[31:0] Read dual port to cpu read data channel.

 

mdio control interface:

phy_addr[4:0] : Set the physical address of Ethernet phy mdio

smi_data[24:0]: A set of mdio command interfaces, including start of frame + read/write OpCode + mdio register addr + write_data[15:0]. (If it is a read command, the content of write_data[15:0] will not be used by fpga) {st_op, phy_reg, phy_reg_val_w} <= smi_data;

smi_en: command valid signal, one clock width.

smi_dout[15:0] : After the read command is sent, when smi_done == 1, the contents of the register returned from the phy chip are obtained.

smi_done: Indicates the end of the current command operation.

smi_err: An error occurred with the current command.

 

CPU register interface:

 

image 3

In cpu design, we will:

The Ethernet register group allocates physical addresses: 0xd000_0000 — 0xd000_7ff space.

Allocate the Ethernet sending buffer as: 0xd000_8000 — 0xd000_8fff

Allocate the Ethernet receive buffer as: 0xd000_c000 — 0xd000_cfff

 

0xd000_0000 ETH version register R:

Indicates the version number of the current Ethernet IP core. Read-only register.

0xD000_0004 ETH control register R/W:

Control register, used to start the Ethernet IP core, whether to add padding in hardware, whether to calculate CRC, etc.

0xD000_0008 ETH status register R:

Ethernet status register, indicating whether the received packet has errors, whether the Ethernet phy is interrupted, the current Ethernet phy status, etc.

0xD000_0014 Eth mac register h R/W:

0xD000_0018 Eth mac register l R/W:

Ethernet MAC Address Register

0xD000_001c Eth ip register R/W:

Ethernet IP address register, indicating the IP address of the current device.

0xD000_0020 Eth mdio register R/W:

SMI (mdio) Control Register

0xD000_0024 Eth mask register R/W:

0xD000_0028 Eth IRQ enable R/W:

0xD000_002c Eth IRQ STAT R:

Ethernet Interrupt Register, Interrupt Mask Register, Interrupt Status Register. Indicates Ethernet mac layer receiving errors; Ethernet phy related interrupts; smi errors, etc.

For the specific information of the register, please refer to RISC-V Address Map version 3.09

 

CPU interface verilog code:

`timescale 1ns / 1ps 
//////////////////////////////////////////////////////////////////////////////////
// Company:     Fraser Innavotion Inc
// Engineer:    William Gou
//
// Create Date: 09/25/2018 09:28:18 AM
// Design Name:
// Module Name: RIB_fii_ETH
// Project Name: RISC-V SOC RTL
// Target Devices: FII-PE7030, FII-PRX100T, FII-BM7100, FII-PRA040, FII-PRA006
// Tool Versions: vivado 18.1/18.2
// Description:
//
// Dependencies:
// this is a simple version of risc-v integer version,aim to show up basic concept of RTL
// Revision:1.0
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module RIB_fii_ETH
#(
    parameter [ 31: 0 ] DEV_BASEADDR   = 32'hD000_0000,

    parameter PERI_DEEP     = 4,    //memory data width
    parameter PERI_W        = 32,   //memory data width
    parameter PERI_MSK_W    = 4,    //memory data mask width
    parameter PERI_ADDR_W   = 32    //memory address width
)
(
    input  clk,
    
    input  [31:0] i_rib_saddr,
    input  [31:0] i_rib_sdin,
    output [31:0] o_rib_sdout,
    input  i_rib_svalid,
    output [1:0] o_rib_sready,
    input  [3:0]  i_rib_swe,
    input  i_rib_srd,
    input  i_rib_sop,

    input        E_RXC,
    input        E_RXDV,
    input  [3:0] E_RXD,

    output       E_TXC,         // Ethernet transmit clock
    output       E_TXEN,        // transmit enable
    output [3:0] E_TXD,         // send data 
    
    output       e_mdc,
    input        e_mdio_i,
    output       e_mdio_o,
    output       e_mdio_t,

    input        ETH_IRQN,
(* mark_debug = "true" *)    output       eth_irq,
    input        rst_n
);

wire [ PERI_W - 1 : 0 ] i_PERI_din = i_rib_sdin;
reg  [ PERI_W - 1 : 0 ] o_PERI_dout;
assign    o_rib_sdout = o_PERI_dout;

wire [ PERI_ADDR_W - 1 : 0 ] i_addr = i_rib_saddr;
wire [ PERI_MSK_W - 1: 0 ] i_wem = i_rib_swe;

wire i_cs = (i_rib_saddr[31:16] == DEV_BASEADDR[31:16]) ? 1'b1 : 1'b0;
wire reg_bank_cs = i_cs & (i_rib_saddr[15:14] == 2'b00) ; // register bank

wire tx_bank_cs  = i_cs & (i_rib_saddr[15:14] == 2'b10) ; // tx buffer bank
wire rx_bank_cs  = i_cs & (i_rib_saddr[15:14] == 2'b11) ; // rx buffer bank

reg eth_valid = 0;
always @( posedge clk )
if(!rst_n) eth_valid <= 0;
else if(i_cs & i_rib_sop) eth_valid <= i_rib_svalid;
else eth_valid <= 0;

assign o_rib_sready = {1'b0, eth_valid};
wire rib_shk = i_rib_svalid & o_rib_sready[0];
wire i_we = (|i_wem) ? 1'b1 : 1'b0;

//===============================================================================
wire [ PERI_MSK_W - 1: 0 ] wen = ( { PERI_MSK_W{i_cs} } & i_wem );
wire ren = i_cs & ( ~i_we );

//===============================================================================
wire [ 11: 0 ] reg_w_addr = i_addr[ 11: 0 ];
wire [ 7: 0 ]  ETH_VER = 8'h01;
reg  [ 31: 0 ] ETH_CTRL = 0;
//wire [ 31: 0 ] ETH_STAT;
wire [2:0] eth_speed;
wire [7:0] mac_error;

reg  [ 15: 0 ] ETH_MAC_H = 16'h66_72;
reg  [ 31: 0 ] ETH_MAC_L = 32'h61_73_65_72;
reg  [ 31: 0 ] ETH_IP = 32'hc0_a8_00_3c;
reg  [ 31: 0 ] ETH_MDIO = 32'h8000_0000;

always @( posedge clk )
if(!rst_n) ETH_CTRL <= 0;
else
begin
    if( reg_bank_cs && reg_w_addr == 12'h004)  // eth control reg
    begin
        if( wen[0] ) ETH_CTRL[07:00] <= i_PERI_din[07:00];
        if( wen[1] ) ETH_CTRL[15:08] <= i_PERI_din[15:08];
        if( wen[2] ) ETH_CTRL[23:16] <= i_PERI_din[23:16];
        if( wen[3] ) ETH_CTRL[31:24] <= i_PERI_din[31:24];
    end
end

always @( posedge clk )
if(!rst_n) ETH_MAC_H <= 16'h66_72;
else
begin
    if( reg_bank_cs && reg_w_addr == 12'h014) // mac high 16bit reg
    begin
        if( wen[0] ) ETH_MAC_H[07:00] <= i_PERI_din[07:00];
        if( wen[1] ) ETH_MAC_H[15:08] <= i_PERI_din[15:08];
    end
end

always @( posedge clk )
if(!rst_n) ETH_MAC_L <= 32'h61_73_65_72;
else
begin
    if( reg_bank_cs && reg_w_addr == 12'h018) // mac low 32bit reg
    begin
        if( wen[0] ) ETH_MAC_L[07:00] <= i_PERI_din[07:00];
        if( wen[1] ) ETH_MAC_L[15:08] <= i_PERI_din[15:08];
        if( wen[2] ) ETH_MAC_L[23:16] <= i_PERI_din[23:16];
        if( wen[3] ) ETH_MAC_L[31:24] <= i_PERI_din[31:24];
    end
end

always @( posedge clk )
if(!rst_n) ETH_IP <= 32'hc0_a8_00_3c;
else
begin
    if( reg_bank_cs && reg_w_addr == 12'h01c) // eth ip 32bit reg
    begin
        if( wen[0] ) ETH_IP[07:00] <= i_PERI_din[07:00];
        if( wen[1] ) ETH_IP[15:08] <= i_PERI_din[15:08];
        if( wen[2] ) ETH_IP[23:16] <= i_PERI_din[23:16];
        if( wen[3] ) ETH_IP[31:24] <= i_PERI_din[31:24];
    end
end


always @( posedge clk )
if(!rst_n) ETH_MDIO <= 32'h8000_0000;
else
begin
    ETH_MDIO[28] <= 0;
    if( rib_shk && reg_bank_cs && reg_w_addr == 12'h020) // eth mdio reg
    begin
        if( wen[0] ) ETH_MDIO[07:00] <= i_PERI_din[07:00];
        if( wen[1] ) ETH_MDIO[15:08] <= i_PERI_din[15:08];
        if( wen[2] ) ETH_MDIO[23:16] <= i_PERI_din[23:16];
        if( wen[3] ) ETH_MDIO[31:24] <= i_PERI_din[31:24];
    end
end

reg  [ 31: 0 ] ETH_IRQ_MASK = 0;
always @( posedge clk )
if(!rst_n) ETH_IRQ_MASK <= 0;
else
begin
    if( reg_bank_cs && reg_w_addr == 12'h024) // eth mask reg
    begin
        if( wen[0] ) ETH_IRQ_MASK[07:00] <= i_PERI_din[07:00];
        if( wen[1] ) ETH_IRQ_MASK[15:08] <= i_PERI_din[15:08];
        if( wen[2] ) ETH_IRQ_MASK[23:16] <= i_PERI_din[23:16];
        if( wen[3] ) ETH_IRQ_MASK[31:24] <= i_PERI_din[31:24];
    end
end



reg  [ 31: 0 ] ETH_IRQ_ENA = 0;
always @( posedge clk )
if(!rst_n) ETH_IRQ_ENA <= 0;
else
begin
    if( reg_bank_cs && reg_w_addr == 12'h028) // eth irq reg
    begin
        if( wen[0] ) ETH_IRQ_ENA[07:00] <= i_PERI_din[07:00];
        if( wen[1] ) ETH_IRQ_ENA[15:08] <= i_PERI_din[15:08];
        if( wen[2] ) ETH_IRQ_ENA[23:16] <= i_PERI_din[23:16];
        if( wen[3] ) ETH_IRQ_ENA[31:24] <= i_PERI_din[31:24];
    end
end

  // eth control reg
wire [ 31: 0 ] eth_irq_stat = {0, smi_err, 6'b00_0000, ~ETH_IRQN, mac_error};
(* mark_debug = "true" *)reg  [ 31: 0 ] ETH_IRQ_REG = 0;
genvar i;
generate 
for (i = 0; i < 32; i = i + 1)
begin : eth_irq_rr
    always @( posedge clk )
    if(!rst_n) ETH_IRQ_REG <= 0;
    else
    begin
        if( rib_shk && i_rib_srd && reg_bank_cs && reg_w_addr == 12'h02c) ETH_IRQ_REG <= 0;
        else if(ETH_IRQ_ENA & ETH_IRQ_MASK & eth_irq_stat)
            ETH_IRQ_REG <= 1;
    end
end
endgenerate

assign eth_irq = |(ETH_IRQ_REG);
//===============================================================================
localparam CPU_BUF_SIZE              = 9;
localparam CPU_IDX_BIT               = 1;   

localparam MAIN_CLK                  = 50;
localparam PHY_SPEED                 = "100M";  // "AUTO", "1G", "100M", "10M"
//===============================================================================
wire        smi_done;
wire [15:0] smi_dout;
wire        smi_err;
wire        smi_en = ETH_MDIO[28];

wire  [3:0] mac_rx_web = {4{rx_bank_cs}} & wen;
wire  [CPU_BUF_SIZE + CPU_IDX_BIT - 1:0] mac_rx_addrb = i_addr[31:2];
wire  [31:0] mac_rx_doutb;

wire  [3:0] mac_tx_wea = {4{tx_bank_cs}} & wen;
wire  [CPU_BUF_SIZE + CPU_IDX_BIT - 1:0] mac_tx_addra = i_addr[31:2];
wire  [31:0] mac_tx_dina = i_rib_sdin;
wire  [31:0] mac_tx_douta;
    
ETH_CORE #
(
    .CPU_BUF_SIZE       (CPU_BUF_SIZE),
    .CPU_IDX_BIT        (CPU_IDX_BIT),

    .MAIN_CLK           (MAIN_CLK),
    .PHY_SPEED          (PHY_SPEED)  // "AUTO", "1G", "100M", "10M"
)
ETH_CORE_inst
(
    .MAC_TX_CRC_EN  (ETH_CTRL[1]),
    .MAC_TX_PADDING (ETH_CTRL[2]),
    .mac_error      (mac_error[7:0]),

    .E_RXC          (E_RXC),
    .E_RXDV         (E_RXDV),
    .E_RXD          (E_RXD),

    .E_TXC          (E_TXC),
    .E_TXEN         (E_TXEN),
    .E_TXD          (E_TXD),

    .sys_clk        (clk),
    .eth_en         (ETH_CTRL[0]),

    .e_mdc          (e_mdc),
    .e_mdio_i       (e_mdio_i),
    .e_mdio_o       (e_mdio_o),
    .e_mdio_t       (e_mdio_t),

    .phy_addr       (ETH_MDIO[25:21]),
    .smi_data       ({2'b01, ETH_MDIO[27:26], ETH_MDIO[20:0]}),
    .smi_en         (smi_en),
    .smi_dout       (smi_dout),
    .smi_done       (smi_done),
    .smi_err        (smi_err),
    
    .eth_speed      (eth_speed),

    .mac_rx_web     (mac_rx_web),
    .mac_rx_addrb   (mac_rx_addrb),
    .mac_rx_doutb   (mac_rx_doutb),

    .mac_tx_wea     (mac_tx_wea),
    .mac_tx_addra   (mac_tx_addra),
    .mac_tx_dina    (mac_tx_dina),
    .mac_tx_douta   (mac_tx_douta),

    .reset          (!rst_n | !ETH_CTRL[0])
);

//===============================================================================
always @ (*)
begin
    casex ({rx_bank_cs, tx_bank_cs, reg_bank_cs, reg_w_addr[11:0]})
    15'h1_000: o_PERI_dout = {24'h00_0000, ETH_VER};
    15'h1_004: o_PERI_dout = ETH_CTRL;
    15'h1_008: o_PERI_dout = {0, eth_speed, smi_err, 6'b00_0000, ~ETH_IRQN, mac_error};
    15'h1_014: o_PERI_dout = {16'h0000, ETH_MAC_H};
    15'h1_018: o_PERI_dout = ETH_MAC_L;
    15'h1_01c: o_PERI_dout = ETH_IP;
    15'h1_020: o_PERI_dout = {smi_done, smi_err, 1'b0, ETH_MDIO[28:16],smi_dout[15:0]};
    15'h1_024: o_PERI_dout = ETH_IRQ_MASK;
    15'h1_028: o_PERI_dout = ETH_IRQ_ENA;
    15'h1_02c: o_PERI_dout = ETH_IRQ_REG;
    
    15'h2_xxx: o_PERI_dout = mac_tx_douta >> {i_addr[1:0], 3'b000};  // tx buffer data
    15'h4_xxx: o_PERI_dout = mac_rx_doutb >> {i_addr[1:0], 3'b000};  // rx buffer data
    default:  o_PERI_dout = 0;
    endcase
end


//===============================================================================

endmodule

 

For more information on the RISCV Ethernet framework part, please refer to the video explanation.

Posted in FPGA, RISC-V, RISC-V Textbook, Textbook and Training Project

Related Articles

Leave a Reply

Your email address will not be published.

Leave the field below empty!