litex_sim: add GMII verilator module and add support in litex_sim

Signed-off-by: Leon Schuermann <leon@is.currently.online>
This commit is contained in:
Leon Schuermann 2021-08-08 14:40:37 +02:00
parent 7b533a032d
commit 08a14e8cf1
5 changed files with 608 additions and 5 deletions

View File

@ -1,5 +1,5 @@
include ../variables.mak
MODULES = xgmii_ethernet ethernet serial2console serial2tcp clocker spdeeprom
MODULES = xgmii_ethernet ethernet serial2console serial2tcp clocker spdeeprom gmii_ethernet
.PHONY: $(MODULES)
all: $(MODULES)

View File

@ -0,0 +1,20 @@
include ../../variables.mak
UNAME_S := $(shell uname -s)
include $(SRC_DIR)/modules/rules.mak
CFLAGS += -I$(TAPCFG_DIRECTORY)/src/include
OBJS = $(MOD).o tapcfg.o taplog.o
$(MOD).so: $(OBJS)
ifeq ($(UNAME_S),Darwin)
$(CC) $(LDFLAGS) -o $@ $^
else
$(CC) $(LDFLAGS) -Wl,-soname,$@ -o $@ $^
endif
tapcfg.o: $(TAPCFG_DIRECTORY)/src/lib/tapcfg.c
$(CC) $(CFLAGS) -c -o $@ $<
taplog.o: $(TAPCFG_DIRECTORY)/src/lib/taplog.c
$(CC) $(CFLAGS) -c -o $@ $<

View File

@ -0,0 +1,570 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "error.h"
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>
#include <json-c/json.h>
#include <zlib.h>
#include "tapcfg.h"
#include "modules.h"
// ---------- SETTINGS ---------- //
// Ethernet MTU. Must be >= MIN_ETH_LEN.
#define ETH_LEN 9000
// MAC address for the host's TAP interface
static const char macadr[6] = {0xaa, 0xb6, 0x24, 0x69, 0x77, 0x21};
// Debug (print to stderr) invalid bus states
#define GMII_TX_DEBUG_INVAL_SIGNAL
// Hex-dump transmitted (Sim -> TAP) packets to stderr
//#define GMII_TX_DEBUG
// Hex-dump received (TAP -> Sim) packets to stderr
//#define GMII_RX_DEBUG
// ------------------------------ //
#define MIN_ETH_LEN 60
// RX incoming (TAP -> Sim) Ethernet packet queue structs
typedef struct eth_packet_queue {
// Does not contain the trailing CRC32 checksum
uint8_t data[ETH_LEN];
size_t len;
struct eth_packet_queue *next;
} eth_packet_queue_t;
typedef struct gmii_state {
// ---------- SIMULATION & BUS STATE ----------
// GMII bus signals
//
// Receive (TAP -> SIM)
uint8_t *rx_data_signal;
bool *rx_dv_signal;
bool *rx_er_signal;
// Transmit (Sim -> TAP)
uint8_t *tx_data_signal;
uint8_t *tx_en_signal;
uint8_t *tx_er_signal;
// Collision detection and carrier sensing currently not implemented
// bool *rx_col_signal;
// bool *rx_cs_signal;
// RX clock signal and edge state
bool *rx_clk;
clk_edge_state_t rx_clk_edge;
// TX clock signal and edge state
// current only gigabit clock supported
bool *tx_clk;
clk_edge_state_t tx_clk_edge;
// ---------- GLOBAL STATE --------
tapcfg_t *tapcfg;
int tap_fd;
// ---------- TX (Sim -> TAP) STATE ---------
// Packet currently being transmitted over the GMII bus (Sim -> TAP).
uint8_t current_tx_pkt[ETH_LEN];
size_t current_tx_len;
bool prev_tx_en;
bool current_tx_abrt;
bool current_tx_drop_warning;
uint8_t current_tx_preamble_state;
// ---------- RX (TAP -> Sim) STATE ---------
// Packet currently being received over the GMII bus (TAP -> Sim). Packets
// copied here are already removed from the TAP incoming queue. Fields are
// valid if current_rx_len != 0. This field includes the CRC32 checksum.
uint8_t current_rx_pkt[ETH_LEN + sizeof(uint32_t)];
uint8_t current_rx_preamble_state;
size_t current_rx_len;
size_t current_rx_progress;
// Linked list of pending RX (TAP -> Sim) packets. `tail` is only valid when
// head != NULL.
eth_packet_queue_t *pending_rx_pkt_head;
eth_packet_queue_t *pending_rx_pkt_tail;
struct event *ev;
} gmii_ethernet_state_t;
// Shared libevent state, set on module init
static struct event_base *base = NULL;
/**
* Advance the RX (TAP -> Sim) state machine, producing a new bus snapshot
*
* This method must be called on the rising clock edge. It will produce a GMII
* bus word which needs to be presented to the device.
*
* This function will detect pending RX packets in the queue and remove them
* accordingly. Thus it is important that this function will be called on every
* rising clock edge, regardless of whether a packet is currently being
* transmitted.
*/
static void gmii_ethernet_rx_adv(gmii_ethernet_state_t *s,
uint64_t time_ps) {
// Check whether we are currently transmitting a packet over the GMII
// interface (i.e. whether there are still bytes left in the packet input
// buffer)
if (s->current_rx_len) {
// TODO:
// assert s->current_rx_progress < s->current_rx_len
// There are bytes to send, transfer the preamble, start of frame
// character and data onto the bus.
if (s->current_rx_preamble_state < 8) {
// Transmit 56 bits (7 bytes) of 0x55(preamble), followed by 1 byte
// 0xD5 (start of frame)
switch (s->current_rx_preamble_state) {
case 7:
*s->rx_data_signal = 0xD5;
break;
default:
*s->rx_data_signal = 0x55;
}
*s->rx_dv_signal = true;
*s->rx_er_signal = false;
s->current_rx_preamble_state++;
} else if (s->current_rx_progress < s->current_rx_len) {
*s->rx_data_signal = s->current_rx_pkt[s->current_rx_progress++];
*s->rx_dv_signal = true; // Data on the bus is valid
*s->rx_er_signal = false; // No receive error in this data word
} else {
// Finished transmitting, reset progress and length to zero.
//
// This cannot be combined with the branch above to ensure that we
// have at least one cycle where rx_dv is deasserted.
s->current_rx_preamble_state = 0;
s->current_rx_progress = 0;
s->current_rx_len = 0;
*s->rx_data_signal = 0;
*s->rx_dv_signal = 0;
*s->rx_er_signal = 0;
}
} else {
// No packet to transmit, indicate the bus is idle by deasserting `data
// valid` (`rx_dv_signal`)
*s->rx_data_signal = 0;
*s->rx_dv_signal = false;
*s->rx_er_signal = false;
}
if (!s->current_rx_len) {
// No packet is currently in transit (or one has just completed
// reception). Check if there is an outstanding packet from the TAP
// interface and copy it into the input buffer
if (s->pending_rx_pkt_head) {
eth_packet_queue_t* popped_rx_pkt;
// CRITICAL REGION {
// Advance the pending packets queue, removing the copied
// packet and freeing its allocated memory.
popped_rx_pkt = s->pending_rx_pkt_head;
s->pending_rx_pkt_head = s->pending_rx_pkt_head->next;
// } CRITICAL REGION
// Determine the maximum length to copy. We must not copy
// beyond the length of s->current_rx_pkt and need to
// reserve at least 4 bytes for the CRC32 to be appended.
size_t copy_len =
(popped_rx_pkt->len
<= sizeof(s->current_rx_pkt) - sizeof(uint32_t))
? popped_rx_pkt->len
: sizeof(s->current_rx_pkt) - sizeof(uint32_t);
// Copy the packet into the buffer
memcpy(s->current_rx_pkt, popped_rx_pkt->data, copy_len);
// Calculate the CRC32 checksum and append it to the
// packet data. This uses the original packet's length. If
// the original packet didn't fit into the buffer, the CRC
// is going to be wrong and thus the packet being cut off
// can be detected.
uint32_t crc = crc32(0, popped_rx_pkt->data, popped_rx_pkt->len);
s->current_rx_pkt[copy_len + 3] = (crc >> 24) & 0xFF;
s->current_rx_pkt[copy_len + 2] = (crc >> 16) & 0xFF;
s->current_rx_pkt[copy_len + 1] = (crc >> 8) & 0xFF;
s->current_rx_pkt[copy_len + 0] = (crc >> 0) & 0xFF;
#ifdef GMII_RX_DEBUG
fprintf(stderr, "\n----------------------------------\n"
"Received packet with %ld bytes\n", copy_len);
for (size_t i = 0; i < copy_len; i++) {
fprintf(stderr, "%02x", s->current_rx_pkt[i] & 0xff);
if (i != 0 && (i + 1) % 16 == 0) {
fprintf(stderr, "\n");
} else if (i != 0 && (i + 1) % 8 == 0) {
fprintf(stderr, " ");
}
}
fprintf(stderr, "\n----------------------------------\n");
#endif
// Set the packet length (including CRC32) and thus
// indicate that a packet is ready to be transmitted over
// the GMII interface
s->current_rx_len = copy_len + sizeof(uint32_t);
// Release the packet data memory
free(popped_rx_pkt);
}
}
}
/**
* Advance the TX (Sim -> TAP) state machine based on a GMII bus word
*
* This method must be called on a rising clock edge (when the device has placed
* a new GMII bus word).
*
* This function will detect frames sent by the device and place them on the TAP
* network interface.
*/
static void gmii_ethernet_tx_adv(gmii_ethernet_state_t *s, uint64_t time_ps) {
// Check whether the device is currently transmitting a new packet or
// continuing an old transmission based on the previous tx_en_signal value
if (s->prev_tx_en == false && *s->tx_en_signal == true) {
// New transmission, reset the current packet length and transmission
// abort state
s->current_tx_len = 0;
s->current_tx_preamble_state = 0;
s->current_tx_abrt = false;
s->current_tx_drop_warning = false;
}
if (s->current_tx_abrt) {
// The current transmission has experienced an error. Wait for this
// condition to be reset on the next new initiated transmission.
} else if (*s->tx_en_signal == true && *s->tx_er_signal == true) {
// Error condition on the bus, can't meaningfully handle. Abort
// transmission.
fprintf(stderr, "[gmii_ethernet]: TX error %02x %u %u\n",
*s->tx_data_signal, *s->tx_en_signal, *s->tx_er_signal);
} else if (*s->tx_en_signal == true
&& s->current_tx_len == ETH_LEN
&& !s->current_tx_drop_warning) {
// Data on bus is valid and transmission has not previously experienced
// an error condition, but no space left for the packet. Warn the user
// once per such error in a single transmission.
s->current_tx_drop_warning = true;
fprintf(stderr, "[gmii_ethernet]: TX ETH_LEN reached, "
"dropping frame data. Check the MTU.\n");
} else if (*s->tx_en_signal == true && s->current_tx_len < ETH_LEN) {
// Data valid, transmission has not previously experienced an error
// condition and sufficient space left in the buffer. Expect preamble or
// valid data.
// Read in the preamble. If it doesn't match, report and error and abort
// the current transmission.
bool preamble_error = false;
if (s->current_tx_preamble_state < 7) {
// Expect 56 bits (7 bytes) of 0x55
if (*s->tx_data_signal == 0x55) {
s->current_tx_preamble_state++;
} else {
preamble_error = true;
}
} else if (s->current_tx_preamble_state < 8) {
// Expect 8 bits (1 byte) of 0xD5
if (*s->tx_data_signal == 0xD5) {
s->current_tx_preamble_state++;
} else {
preamble_error = true;
}
} else {
s->current_tx_pkt[s->current_tx_len++] = *s->tx_data_signal;
}
if (preamble_error) {
fprintf(stderr, "[gmii_ethernet]: TX preamble error! %u %02x\n",
s->current_tx_preamble_state, *s->tx_data_signal);
s->current_tx_abrt = true;
}
}
if (s->prev_tx_en == true
&& *s->tx_en_signal == false
&& s->current_tx_len >= 1
&& !s->current_tx_abrt) {
// Falling edge on tx_en, the frame has been transmitted into the
// buffer. Transmit over the TAP interface.
// Length without frame check sequence
size_t pkt_len =
(s->current_tx_len > 3) ? s->current_tx_len - 4 : 0;
#ifdef GMII_TX_DEBUG
fprintf(stderr, "\n----------------------------------\n"
"Transmitted packet with %ld bytes\n", pkt_len);
for (size_t i = 0; i < pkt_len; i++) {
fprintf(stderr, "%02x", s->current_tx_pkt[i] & 0xff);
if (i != 0 && (i + 1) % 16 == 0) {
fprintf(stderr, "\n");
} else if (i != 0 && (i + 1) % 8 == 0) {
fprintf(stderr, " ");
}
}
fprintf(stderr, "\n----------------------------------\n");
#endif
if (s->current_tx_len < 4) {
fprintf(stderr, "[gmii_ethernet]: TX packet too short to contain "
"frame check sequence\n");
} else {
uint32_t crc = crc32(0, s->current_tx_pkt, pkt_len);
if (!((s->current_tx_pkt[pkt_len + 0] == ((crc >> 0) & 0xFF))
&& (s->current_tx_pkt[pkt_len + 1] == ((crc >> 8) & 0xFF))
&& (s->current_tx_pkt[pkt_len + 2] == ((crc >> 16) & 0xFF))
&& (s->current_tx_pkt[pkt_len + 3] == ((crc >> 24) & 0xFF))))
{
fprintf(stderr, "[gmii_ethernet]: TX packet FCS mismatch. "
"Expected: %08x. Actual: %08x.\n", crc,
(uint32_t) s->current_tx_pkt[pkt_len + 0] << 0
| (uint32_t) s->current_tx_pkt[pkt_len + 1] << 8
| (uint32_t) s->current_tx_pkt[pkt_len + 2] << 16
| (uint32_t) s->current_tx_pkt[pkt_len + 3] << 24);
}
}
tapcfg_write(s->tapcfg, s->current_tx_pkt, pkt_len);
}
// Store the previous tx_en_signal for edge detection
s->prev_tx_en = *s->tx_en_signal;
}
static int gmii_ethernet_tick(void *state, uint64_t time_ps) {
gmii_ethernet_state_t *s = (gmii_ethernet_state_t*) state;
if (clk_pos_edge(&s->tx_clk_edge, *s->tx_clk)) {
gmii_ethernet_tx_adv(s, time_ps);
}
if (clk_pos_edge(&s->rx_clk_edge, *s->rx_clk)) {
gmii_ethernet_rx_adv(s, time_ps);
}
return RC_OK;
}
int litex_sim_module_get_args(char *args, char *arg, char **val) {
int ret = RC_OK;
json_object *jsobj = NULL;
json_object *obj = NULL;
char *value = NULL;
int r;
jsobj = json_tokener_parse(args);
if (NULL == jsobj) {
fprintf(stderr, "[gmii_ethernet]: error parsing json arg: %s\n", args);
ret = RC_JSERROR;
goto out;
}
if (!json_object_is_type(jsobj, json_type_object)) {
fprintf(stderr, "[gmii_ethernet]: arg must be type object!: %s\n",
args);
ret = RC_JSERROR;
goto out;
}
obj = NULL;
r = json_object_object_get_ex(jsobj, arg, &obj);
if (!r) {
fprintf(stderr, "[gmii_ethernet]: could not find object: \"%s\" "
"(%s)\n", arg, args);
ret = RC_JSERROR;
goto out;
}
value = strdup(json_object_get_string(obj));
out:
*val = value;
return ret;
}
static int litex_sim_module_pads_get(struct pad_s *pads, char *name,
void **signal) {
int ret = RC_OK;
void *sig = NULL;
int i;
if (!pads || !name || !signal) {
ret=RC_INVARG;
goto out;
}
i = 0;
while (pads[i].name) {
if (!strcmp(pads[i].name, name)) {
sig=(void*)pads[i].signal;
break;
}
i++;
}
out:
*signal=sig;
return ret;
}
void event_handler(int tap_fd, short event, void *arg) {
gmii_ethernet_state_t *s = arg;
// Expect a new TAP packet if the socket has become readable
if (event & EV_READ) {
eth_packet_queue_t *rx_pkt =
malloc(sizeof(eth_packet_queue_t));
// Read the TAP packet into the buffer, extending its length
// to the minimum required Ethernet frame length if necessary.
int read_len = tapcfg_read(s->tapcfg, rx_pkt->data, ETH_LEN);
if (read_len < 0) {
// An error occured while reading from the TAP interface,
// report, free the packet and abort.
fprintf(stderr, "[gmii_ethernet]: TAP read error %d\n", read_len);
free(rx_pkt);
return;
} else if (read_len < MIN_ETH_LEN) {
// To avoid leaking any data, set the packet's contents
// after the proper received length to zero.
memset(&rx_pkt->data[read_len], 0, MIN_ETH_LEN - read_len);
rx_pkt->len = MIN_ETH_LEN;
} else {
// A packet larger than the minimum Ethernet frame length
// has been read.
rx_pkt->len = read_len;
}
// Packet is inserted into the back of the queue, thus no next
// packet.
rx_pkt->next = NULL;
// CRITICAL REGION {
// Append the received packet to the packet queue
if (!s->pending_rx_pkt_head) {
s->pending_rx_pkt_head = rx_pkt;
s->pending_rx_pkt_tail = rx_pkt;
} else {
s->pending_rx_pkt_tail->next = rx_pkt;
s->pending_rx_pkt_tail = rx_pkt;
}
// } CRITICAL REGION
}
}
static int gmii_ethernet_add_pads(void *state, struct pad_list_s *plist) {
int ret = RC_OK;
gmii_ethernet_state_t *s = (gmii_ethernet_state_t*) state;
struct pad_s *pads;
if (!state || !plist) {
ret = RC_INVARG;
goto out;
}
pads = plist->pads;
if (!strcmp(plist->name, "gmii_eth")) {
litex_sim_module_pads_get(pads, "rx_data", (void**) &s->rx_data_signal);
litex_sim_module_pads_get(pads, "rx_dv", (void**) &s->rx_dv_signal);
litex_sim_module_pads_get(pads, "rx_er", (void**) &s->rx_er_signal);
litex_sim_module_pads_get(pads, "tx_data", (void**) &s->tx_data_signal);
litex_sim_module_pads_get(pads, "tx_en", (void**) &s->tx_en_signal);
litex_sim_module_pads_get(pads, "tx_er", (void**) &s->tx_er_signal);
}
if (!strcmp(plist->name, "sys_clk")) {
// TODO: currently the single sys_clk signal is used for both the RX and
// TX GMII clock signals. This should be changed.
litex_sim_module_pads_get(pads, "sys_clk", (void**)&s->rx_clk);
s->tx_clk = s->rx_clk;
}
out:
return ret;
}
static int gmii_ethernet_start(void *b) {
base = (struct event_base *) b;
printf("[gmii_ethernet] loaded (%p)\n", base);
return RC_OK;
}
static int gmii_ethernet_new(void **state, char *args) {
int ret = RC_OK;
char *c_tap = NULL;
char *c_tap_ip = NULL;
gmii_ethernet_state_t *s = NULL;
struct timeval tv = {10, 0};
if (!state) {
ret = RC_INVARG;
goto out;
}
s = (gmii_ethernet_state_t*)malloc(sizeof(gmii_ethernet_state_t));
if (!s) {
ret = RC_NOENMEM;
goto out;
}
memset(s, 0, sizeof(gmii_ethernet_state_t));
ret = litex_sim_module_get_args(args, "interface", &c_tap);
if (ret != RC_OK) {
goto out;
}
ret = litex_sim_module_get_args(args, "ip", &c_tap_ip);
if (ret != RC_OK) {
goto out;
}
s->tapcfg = tapcfg_init();
tapcfg_start(s->tapcfg, c_tap, 0);
s->tap_fd = tapcfg_get_fd(s->tapcfg);
tapcfg_iface_set_hwaddr(s->tapcfg, macadr, 6);
tapcfg_iface_set_ipv4(s->tapcfg, c_tap_ip, 24);
tapcfg_iface_set_status(s->tapcfg, TAPCFG_STATUS_ALL_UP);
free(c_tap);
free(c_tap_ip);
s->ev = event_new(base, s->tap_fd, EV_READ | EV_PERSIST, event_handler, s);
event_add(s->ev, &tv);
out:
*state = (void*) s;
return ret;
}
static struct ext_module_s ext_mod = {
"gmii_ethernet",
gmii_ethernet_start,
gmii_ethernet_new,
gmii_ethernet_add_pads,
NULL,
gmii_ethernet_tick
};
int litex_sim_ext_module_init(int (*register_module)(struct ext_module_s *)) {
int ret = RC_OK;
// Initiate calculation of zlib's CRC32 lookup table such that multithreaded
// calls to crc32() are safe.
get_crc_table();
ret = register_module(&ext_mod);
return ret;
}

View File

@ -235,9 +235,9 @@ class SimVerilatorToolchain:
raise OSError(msg)
_compile_sim(build_name, verbose)
run_as_root = False
if sim_config.has_module("ethernet"):
run_as_root = True
if sim_config.has_module("xgmii_ethernet"):
if sim_config.has_module("ethernet") \
or sim_config.has_module("xgmii_ethernet") \
or sim_config.has_module("gmii_ethernet"):
run_as_root = True
_run_sim(build_name, as_root=run_as_root, interactive=interactive)

View File

@ -30,6 +30,7 @@ from litedram.modules import parse_spd_hexdump
from litedram.phy.model import sdram_module_nphases, get_sdram_phy_settings
from litedram.phy.model import SDRAMPHYModel
from liteeth.phy.gmii import LiteEthPHYGMII
from liteeth.phy.xgmii import LiteEthPHYXGMII
from liteeth.phy.model import LiteEthPHYModel
from liteeth.mac import LiteEthMAC
@ -76,6 +77,14 @@ _io = [
Subsignal("tx_data", Pins(64)),
Subsignal("tx_ctl", Pins(8)),
),
("gmii_eth", 0,
Subsignal("rx_data", Pins(8)),
Subsignal("rx_dv", Pins(1)),
Subsignal("rx_er", Pins(1)),
Subsignal("tx_data", Pins(8)),
Subsignal("tx_en", Pins(1)),
Subsignal("tx_er", Pins(1)),
),
("i2c", 0,
Subsignal("scl", Pins(1)),
Subsignal("sda_out", Pins(1)),
@ -185,6 +194,8 @@ class SimSoC(SoCCore):
self.submodules.ethphy = LiteEthPHYModel(self.platform.request("eth", 0))
elif ethernet_phy_model == "xgmii":
self.submodules.ethphy = LiteEthPHYXGMII(None, self.platform.request("xgmii_eth", 0), model=True)
elif ethernet_phy_model == "gmii":
self.submodules.ethphy = LiteEthPHYGMII(None, self.platform.request("gmii_eth", 0), model=True)
else:
raise ValueError("Unknown Ethernet PHY model:", ethernet_phy_model)
# Ethernet MAC
@ -306,7 +317,7 @@ def sim_args(parser):
parser.add_argument("--sdram-from-spd-dump", default=None, help="Generate SDRAM module based on data from SPD EEPROM dump")
parser.add_argument("--sdram-verbosity", default=0, help="Set SDRAM checker verbosity")
parser.add_argument("--with-ethernet", action="store_true", help="Enable Ethernet support")
parser.add_argument("--ethernet-phy-model", default="sim", help="Ethernet PHY to simulate (sim, xgmii)")
parser.add_argument("--ethernet-phy-model", default="sim", help="Ethernet PHY to simulate (sim, xgmii, gmii)")
parser.add_argument("--with-etherbone", action="store_true", help="Enable Etherbone support")
parser.add_argument("--local-ip", default="192.168.1.50", help="Local IP address of SoC (default=192.168.1.50)")
parser.add_argument("--remote-ip", default="192.168.1.100", help="Remote IP address of TFTP server (default=192.168.1.100)")
@ -360,6 +371,8 @@ def main():
sim_config.add_module("ethernet", "eth", args={"interface": "tap0", "ip": args.remote_ip})
elif args.ethernet_phy_model == "xgmii":
sim_config.add_module("xgmii_ethernet", "xgmii_eth", args={"interface": "tap0", "ip": args.remote_ip})
elif args.ethernet_phy_model == "gmii":
sim_config.add_module("gmii_ethernet", "gmii_eth", args={"interface": "tap0", "ip": args.remote_ip})
else:
raise ValueError("Unknown Ethernet PHY model: " + args.ethernet_phy_model)