当我们学习过以太网的相关知识,学习了以太网模块IP核在FPGA 中的应用, 就可以将以太网IP 封装成RISCV cpu 所需要的IP 模块。 重新封装的模块将对之前的以太网模块进行改造。 主要是去掉UDP层, IP 层, 这些层的功能之后会在软件层面实现(软件层面的协议栈不光实现UDP,IP, 还有其他的协议需要支持),所以重新封装的模块只要保留mac 层就可以了, 同时设计好和RISCV cup 的总线接口。
改造后的以太网模块框图:

图1
其中eth_core.v 内部框图为图2所示:

图2
cpu 端的接口:
接收端:设计以太网cpu 端的接口为 双端口, (IP_RX_DRAM) ,双端口的深度为4096 bytes ,分为乒乓buffer。由于接口为32bit, 所以每个buffer的深度为512。
发送端:设计为双端口(MAC_TX_DRAM),双端口的深度为4096 bytes ,分为乒乓buffer。 接口为32bit, 每个buffer的深度为512。
接收,发送的双端口 控制字节定义:cpu 端的 bit[31] 为控制字, bit[27:16] 为接收到的以太网数据的字节(bytes)数量(length)。
mac_rx_web: cpu 写信号, 当得到完整的数据包后, 清除当前buffer时,使用。
mac_rx_addrb: cpu 地址总线, 用于指示读写双端口地址
mac_rx_doutb[31:0]: 双端口中的读数据通道, 连接到cpu 的 读数据总线上。
mac_tx_wea: cpu 写信号, 用于将软件的数据写入到双端口中。
mac_tx_addra: cpu 地址总线,指示双端口的地址。
mac_tx_dina[31:0] 写入双端口的 cpu 数据写 通道
mac_tx_douta[31:0] 读双端口到 cpu 读数据通道。
mdio 控制接口:
phy_addr[4:0] : 设置以太网phy mdio 的物理地址
smi_data[24:0]: 一组mdio 命令接口, 包括 start of frame + 读/写 OpCode + mdio register addr + write_data[15:0]。 (如果是读命令, write_data[15:0] 的内容不会被fpga 使用) {st_op, phy_reg, phy_reg_val_w} <= smi_data;
smi_en: 命令有效信号, 一个时钟宽度。
smi_dout[15:0] : 读命令发送后, 当smi_done == 1 时,得到的从phy 芯片返回的 寄存器 内容。
smi_done: 表示当前的命令操作结束。
smi_err: 当前的命令发生错误。
CPU寄存器接口:
图3
在cpu 设计中,我们将:
以太网的寄存器组分配物理地址:0xd000_0000 — 0xd000_7ff 空间。
将以太网发送buffer 分配空间为:0xd000_8000 — 0xd000_8fff
将以太网接收buffer 分配空间为:0xd000_c000 — 0xd000_cfff
0xd000_0000 ETH版本寄存器 R :
指示当前以太网IP 核的版本号。 只读寄存器。
0xD000_0004 ETH控制寄存器 R/W:
控制寄存器,用于启动以太网IP核,是否硬件添加padding, 是否计算CRC等。
0xD000_0008 ETH状态寄存器 R:
以太网状态寄存器,指示接收到的包是否有错误,以太网phy 是否有中断,当前以太网phy状态等。
0xD000_0014 Eth mac 寄存器 h R/W:
0xD000_0018 Eth mac 寄存器 l R/W:
以太网MAC地址寄存器
0xD000_001c Eth ip寄存器 R/W:
以太网IP 地址寄存器,表示当前设备的IP地址。
0xD000_0020 Eth mdio寄存器 R/W:
SMI (mdio)控制寄存器
0xD000_0024 Eth mask寄存器 R/W:
0xD000_0028 Eth IRQ enable R/W:
0xD000_002c Eth IRQ STAT R:
以太网中断寄存器, 中断屏蔽寄存器,中断状态寄存器。 指示以太网mac 层的接收错误; 以太网phy 相关中断;smi 错误等。
寄存器的具体信息,请参照RISC-V Address Map 3.09版
CPU接口verilog 代码:
`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, //以太网发送时钟
output E_TXEN, //发送使能
output [3:0] E_TXD, //发送数据
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[i] <= 0;
else
begin
if( rib_shk && i_rib_srd && reg_bank_cs && reg_w_addr == 12'h02c) ETH_IRQ_REG[i] <= 0;
else if(ETH_IRQ_ENA[i] & ETH_IRQ_MASK[i] & eth_irq_stat[i])
ETH_IRQ_REG[i] <= 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
关于RISCV 以太网框架部分的详细信息,请参照视频讲解。