Merge pull request #543 from antmicro/jboc/eeprom-sim

litex/build/sim: add module for simulating SPD EEPROM
This commit is contained in:
enjoy-digital 2020-05-28 16:46:34 +02:00 committed by GitHub
commit 62d939e85f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 477 additions and 2 deletions

View File

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

View File

@ -0,0 +1,2 @@
include ../../variables.mak
include $(SRC_DIR)/modules/rules.mak

View File

@ -0,0 +1,428 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "error.h"
#include "modules.h"
/*
* This is a simulation of SPD EEPROM I2C slave.
* It only supports basic read/write commands.
* Although it has been written with SPD EEPROM chips in mind, it should be
* compatible with some other EEPROM chips that use single byte addressing.
*
* Some details can be controlled using defines/environmental variables:
* #define SPD_EEPROM_ADDR 7bit address of I2C slave
* #define DEBUG_SPD_EEPROM print debug messages
* env SPD_EEPROM_FILE load memory contents from file
*/
#define SPD_EEPROM_ADDR 0b000
#ifdef DEBUG_SPD_EEPROM
#define DBG(...) do{ fprintf(stderr, __VA_ARGS__); } while(0)
#else
#define DBG(...) do{ } while (0)
#endif
// state of the serial-to-parallel FSM
enum SerialState {
IDLE,
WRITE, // slave writing a byte to master
READ, // slave reading a byte from master
RACK_0, // slave starts sending ACK
RACK_1, // slave finishes sending ACK
WACK, // slave reads ACK
};
// state of the transaction FSM
enum TransactionState {
DEV_ADDR, // reading slave device address
WRITE_ADDR, // master writes address
WRITE_DATA, // master writes data
READ_DATA, // master reads data
};
// module state
struct session_s {
// DUT pads (need separate SDA io/out as Verilator does not support tristate pins)
char *sys_clk;
char *sda_in;
char *sda_out;
char *scl;
// SPD EEPROM memory contents
unsigned char mem[256];
// state machine
enum TransactionState state_transaction;
enum SerialState state_serial;
unsigned int byte_in;
unsigned int byte_out;
unsigned int bit_counter;
unsigned int devaddr;
unsigned int addr;
};
// Module interface
static int spdeeprom_start();
static int spdeeprom_new(void **sess, char *args);
static int spdeeprom_add_pads(void *sess, struct pad_list_s *plist);
static int spdeeprom_tick(void *sess);
// EEPROM simulation
static void fsm_tick(struct session_s *s);
static enum SerialState state_serial_next(struct session_s *s);
// Helper functions
static void spdeeprom_from_file(struct session_s *s, FILE *file);
static int litex_sim_module_pads_get(struct pad_s *pads, char *name, void **signal);
/*** Module interface *****************************************************************************/
static struct ext_module_s ext_mod = {
"spdeeprom",
spdeeprom_start,
spdeeprom_new,
spdeeprom_add_pads,
NULL,
spdeeprom_tick
};
int litex_sim_ext_module_init(int (*register_module)(struct ext_module_s *))
{
int ret = RC_OK;
ret = register_module(&ext_mod);
return ret;
}
static int spdeeprom_start()
{
printf("[spdeeprom] loaded (addr = 0x%01x)\n", SPD_EEPROM_ADDR);
return RC_OK;
}
static int spdeeprom_new(void **sess, char *args)
{
int ret=RC_OK;
int i;
char *spd_filename;
FILE *spd_file;
struct session_s *s=NULL;
if(!sess) {
ret = RC_INVARG;
goto out;
}
s = (struct session_s*) malloc(sizeof(struct session_s));
if(!s) {
ret=RC_NOENMEM;
goto out;
}
memset(s, 0, sizeof(struct session_s));
spd_filename = getenv("SPD_EEPROM_FILE");
if (spd_filename != NULL) {
spd_file = fopen(spd_filename, "r");
}
if (spd_filename != NULL && spd_file != NULL) {
DBG("[spdeeprom] loading EEPROM contents from file: %s\n", spd_filename);
spdeeprom_from_file(s, spd_file);
fclose(spd_file);
} else { // fill in the memory with some data
for (i = 0; i < sizeof(s->mem) / sizeof(s->mem[0]); ++i) {
s->mem[i] = i & 0xff;
}
}
out:
*sess = (void*) s;
return ret;
}
static int spdeeprom_add_pads(void *sess, struct pad_list_s *plist)
{
int ret = RC_OK;
struct session_s *s = (struct session_s*) sess;
struct pad_s *pads;
if(!sess || !plist) {
ret = RC_INVARG;
goto out;
}
pads = plist->pads;
if(!strcmp(plist->name, "i2c")) {
litex_sim_module_pads_get(pads, "sda_in", (void**) &s->sda_in);
litex_sim_module_pads_get(pads, "sda_out", (void**) &s->sda_out);
litex_sim_module_pads_get(pads, "scl", (void**) &s->scl);
}
if(!strcmp(plist->name, "sys_clk"))
litex_sim_module_pads_get(pads, "sys_clk", (void**) &s->sys_clk);
out:
return ret;
}
static int spdeeprom_tick(void *sess)
{
struct session_s *s = (struct session_s*) sess;
if (s->sda_in == 0 || s->sda_out == 0 || s->scl == 0) {
return RC_OK;
}
if(*s->sys_clk == 0) {
return RC_OK;
}
fsm_tick(s);
return RC_OK;
}
/*** Simulation ***********************************************************************************/
#ifdef DEBUG_SPD_EEPROM
static inline const char *state_serial_str(enum SerialState s)
{
switch (s) {
case IDLE: return "IDLE";
case WRITE: return "WRITE";
case READ: return "READ";
case RACK_0: return "RACK_0";
case RACK_1: return "RACK_1";
case WACK: return "WACK";
default: return "_";
}
}
static inline const char *state_transaction_str(enum TransactionState s)
{
switch (s) {
case DEV_ADDR: return "DEV_ADDR";
case WRITE_ADDR: return "WRITE_ADDR";
case WRITE_DATA: return "WRITE_DATA";
case READ_DATA: return "READ_DATA";
default: return "_";
}
}
#endif
static void fsm_tick(struct session_s *s)
{
static int sda_last = 1;
static int scl_last = 1;
enum SerialState last_state_serial;
int sda_rising_edge;
int sda_falling_edge;
int start_cond;
int stop_cond;
int scl_rising;
int scl_falling;
sda_rising_edge = !sda_last && *s->sda_out;
sda_falling_edge = sda_last && !*s->sda_out;
start_cond = sda_falling_edge && *s->scl;
stop_cond = sda_rising_edge && *s->scl;
scl_rising = !scl_last && *s->scl;
scl_falling = scl_last && !*s->scl;
sda_last = *s->sda_out;
scl_last = *s->scl;
if (start_cond) {
DBG("[spdeeprom] START condition\n");
s->state_serial = READ;
s->state_transaction = DEV_ADDR;
s->bit_counter = 0;
}
if (stop_cond) {
DBG("[spdeeprom] STOP condition\n");
s->state_serial = IDLE;
s->state_transaction = DEV_ADDR;
}
last_state_serial = s->state_serial;
switch (s->state_serial) {
case IDLE:
*s->sda_in = 1;
break;
case READ:
if (s->bit_counter == 0) {
s->byte_in = 0;
}
if (scl_rising) {
s->byte_in <<= 1;
s->byte_in |= *s->sda_out & 1;
s->bit_counter++;
}
if (s->bit_counter >= 8) {
s->bit_counter = 0;
s->state_serial = RACK_0;
}
break;
case WRITE:
if (scl_rising) {
*s->sda_in = (s->byte_out & (1 << 7)) != 0;
s->byte_out <<= 1;
s->bit_counter++;
}
if (s->bit_counter >= 8) {
s->bit_counter = 0;
s->state_serial = WACK;
}
break;
case RACK_0: // first falling edge
if (scl_falling) {
*s->sda_in = 0;
s->state_serial = RACK_1;
}
break;
case RACK_1: // second falling edge
if (scl_falling) {
*s->sda_in = 1;
s->state_serial = state_serial_next(s);
}
break;
case WACK:
if (scl_rising) {
if ((*s->sda_out) != 0) {
DBG("[spdeeprom] No ACK from master!\n");
}
s->state_serial = state_serial_next(s);
}
break;
default: DBG("[spdeeprom] unknown state_serial\n"); break;
}
if (s->state_serial != last_state_serial) {
DBG("[spdeeprom] state_serial: %s -> %s\n",
state_serial_str(last_state_serial), state_serial_str(s->state_serial));
}
}
static enum SerialState state_serial_next(struct session_s *s)
{
enum TransactionState state_transaction_last = s->state_transaction;
enum SerialState state_serial = IDLE;
switch (s->state_transaction) {
case DEV_ADDR:
if (s->state_serial != RACK_1) {
DBG("[spdeeprom] ERROR: DEV_ADDR during WACK\n");
}
s->devaddr = s->byte_in;
if (((s->devaddr & 0b1110) >> 1) != SPD_EEPROM_ADDR) {
DBG("[spdeeprom] ERROR: read wrong address\n");
state_serial = IDLE;
} else {
DBG("[spdeeprom] devaddr = 0x%02x\n", s->devaddr);
if ((s->devaddr & 1) != 0) { // read command
DBG("[spdeeprom] registered READ cmd\n");
s->state_transaction = READ_DATA;
s->byte_out = s->mem[s->addr++];
s->addr %= sizeof(s->mem);
state_serial = WRITE;
} else { // write command
DBG("[spdeeprom] registered WRITE cmd\n");
s->state_transaction = WRITE_ADDR;
state_serial = READ;
}
}
break;
case WRITE_ADDR:
if (s->state_serial != RACK_1) {
DBG("[spdeeprom] ERROR: WRITE_ADDR during WACK\n");
}
s->addr = s->byte_in;
s->state_transaction = WRITE_DATA;
DBG("[spdeeprom] addr = 0x%02x\n", s->addr);
state_serial = READ;
break;
case WRITE_DATA:
if (s->state_serial != RACK_1) {
DBG("[spdeeprom] ERROR: WRITE_DATA during WACK\n");
}
s->mem[s->addr++] = s->byte_in;
s->addr %= sizeof(s->mem);
s->state_transaction = WRITE_DATA;
DBG("[spdeeprom] wdata = 0x%02x\n", s->byte_in);
state_serial = READ;
break;
case READ_DATA:
if (s->state_serial != WACK) {
DBG("[spdeeprom] ERROR: READ_DATA during RACK\n");
}
s->state_transaction = READ_DATA;
s->byte_out = s->mem[s->addr++];
DBG("[spdeeprom] rdata = 0x%02x\n", s->byte_out);
state_serial = WRITE;
break;
default:
DBG("[spdeeprom] ERROR: wrong state_transaction!\n");
break;
}
if (state_serial == IDLE) {
DBG("[spdeeprom] ERROR: unhandled state_serial_next\n");
}
if (state_transaction_last != s->state_transaction) {
DBG("[spdeeprom] state_transaction: %s -> %s\n", state_transaction_str(state_transaction_last), state_transaction_str(s->state_transaction));
}
return state_serial;
}
/*** Helper functions *****************************************************************************/
static void spdeeprom_from_file(struct session_s *s, FILE *file)
{
size_t bufsize = 0;
ssize_t n_read;
char *line = NULL;
char *c;
unsigned int byte;
int i;
for (i = 0; i < sizeof(s->mem) / sizeof(s->mem[0]); ++i) {
if ((n_read = getline(&line, &bufsize, file)) < 0) {
break;
}
byte = strtoul(line, &c, 0);
if (c == line) {
DBG("[spdeeprom] Incorrect value at line %d\n", i);
} else {
s->mem[i] = byte;
}
}
if (line != NULL)
free(line);
}
static int litex_sim_module_pads_get(struct pad_s *pads, char *name, void **signal)
{
int ret;
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;
}

View File

@ -30,8 +30,9 @@ class I2CMaster(Module, AutoCSR):
CSRField("sda", size=1, offset=0)], CSRField("sda", size=1, offset=0)],
name="r") name="r")
# # # self.connect(pads)
def connect(self, pads):
_sda_w = Signal() _sda_w = Signal()
_sda_oe = Signal() _sda_oe = Signal()
_sda_r = Signal() _sda_r = Signal()
@ -44,6 +45,32 @@ class I2CMaster(Module, AutoCSR):
self.specials += Tristate(pads.sda, _sda_w, _sda_oe, _sda_r) self.specials += Tristate(pads.sda, _sda_w, _sda_oe, _sda_r)
class I2CMasterSim(I2CMaster):
"""I2C Master Bit-Banging for Verilator simulation
Uses separate pads for SDA IN/OUT as Verilator does not support tristate pins well.
"""
pads_layout = [("scl", 1), ("sda_in", 1), ("sda_out", 1)]
def connect(self, pads):
_sda_w = Signal()
_sda_oe = Signal()
_sda_r = Signal()
_sda_in = Signal()
self.comb += [
pads.scl.eq(self._w.fields.scl),
_sda_oe.eq( self._w.fields.oe),
_sda_w.eq( self._w.fields.sda),
If(_sda_oe,
pads.sda_out.eq(_sda_w),
self._r.fields.sda.eq(_sda_w),
).Else(
pads.sda_out.eq(1),
self._r.fields.sda.eq(pads.sda_in),
)
]
# SPI Master Bit-Banging --------------------------------------------------------------------------- # SPI Master Bit-Banging ---------------------------------------------------------------------------
class SPIMaster(Module, AutoCSR): class SPIMaster(Module, AutoCSR):

View File

@ -18,6 +18,7 @@ from litex.soc.integration.soc_core import *
from litex.soc.integration.soc_sdram import * from litex.soc.integration.soc_sdram import *
from litex.soc.integration.builder import * from litex.soc.integration.builder import *
from litex.soc.integration.soc import * from litex.soc.integration.soc import *
from litex.soc.cores.bitbang import *
from litedram import modules as litedram_modules from litedram import modules as litedram_modules
from litedram.modules import parse_spd_hexdump from litedram.modules import parse_spd_hexdump
@ -63,6 +64,11 @@ _io = [
Subsignal("sink_ready", Pins(1)), Subsignal("sink_ready", Pins(1)),
Subsignal("sink_data", Pins(8)), Subsignal("sink_data", Pins(8)),
), ),
("i2c", 0,
Subsignal("scl", Pins(1)),
Subsignal("sda_out", Pins(1)),
Subsignal("sda_in", Pins(1)),
),
] ]
# Platform ----------------------------------------------------------------------------------------- # Platform -----------------------------------------------------------------------------------------
@ -170,6 +176,7 @@ class SimSoC(SoCCore):
sdram_data_width = 32, sdram_data_width = 32,
sdram_spd_data = None, sdram_spd_data = None,
sdram_verbosity = 0, sdram_verbosity = 0,
with_i2c = False,
**kwargs): **kwargs):
platform = Platform() platform = Platform()
sys_clk_freq = int(1e6) sys_clk_freq = int(1e6)
@ -293,6 +300,12 @@ class SimSoC(SoCCore):
csr_csv = "analyzer.csv") csr_csv = "analyzer.csv")
self.add_csr("analyzer") self.add_csr("analyzer")
# I2C --------------------------------------------------------------------------------------
if with_i2c:
pads = platform.request("i2c", 0)
self.submodules.i2c = I2CMasterSim(pads)
self.add_csr("i2c")
# Build -------------------------------------------------------------------------------------------- # Build --------------------------------------------------------------------------------------------
def main(): def main():
@ -313,6 +326,7 @@ def main():
parser.add_argument("--local-ip", default="192.168.1.50", help="Local IP address of SoC (default=192.168.1.50)") 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)") parser.add_argument("--remote-ip", default="192.168.1.100", help="Remote IP address of TFTP server (default=192.168.1.100)")
parser.add_argument("--with-analyzer", action="store_true", help="Enable Analyzer support") parser.add_argument("--with-analyzer", action="store_true", help="Enable Analyzer support")
parser.add_argument("--with-i2c", action="store_true", help="Enable I2C support")
parser.add_argument("--trace", action="store_true", help="Enable Tracing") parser.add_argument("--trace", action="store_true", help="Enable Tracing")
parser.add_argument("--trace-fst", action="store_true", help="Enable FST tracing (default=VCD)") parser.add_argument("--trace-fst", action="store_true", help="Enable FST tracing (default=VCD)")
parser.add_argument("--trace-start", default=0, help="Cycle to start tracing") parser.add_argument("--trace-start", default=0, help="Cycle to start tracing")
@ -352,12 +366,16 @@ def main():
if args.with_ethernet or args.with_etherbone: if args.with_ethernet or args.with_etherbone:
sim_config.add_module("ethernet", "eth", args={"interface": "tap0", "ip": args.remote_ip}) sim_config.add_module("ethernet", "eth", args={"interface": "tap0", "ip": args.remote_ip})
if args.with_i2c:
sim_config.add_module("spdeeprom", "i2c")
# SoC ------------------------------------------------------------------------------------------ # SoC ------------------------------------------------------------------------------------------
soc = SimSoC( soc = SimSoC(
with_sdram = args.with_sdram, with_sdram = args.with_sdram,
with_ethernet = args.with_ethernet, with_ethernet = args.with_ethernet,
with_etherbone = args.with_etherbone, with_etherbone = args.with_etherbone,
with_analyzer = args.with_analyzer, with_analyzer = args.with_analyzer,
with_i2c = args.with_i2c,
sdram_init = [] if args.sdram_init is None else get_mem_data(args.sdram_init, cpu_endianness), sdram_init = [] if args.sdram_init is None else get_mem_data(args.sdram_init, cpu_endianness),
**soc_kwargs) **soc_kwargs)
if args.ram_init is not None: if args.ram_init is not None: