litex/build/sim: add module for simulating SPD EEPROM
authorJędrzej Boczar <jboczar@antmicro.com>
Thu, 28 May 2020 10:10:25 +0000 (12:10 +0200)
committerJędrzej Boczar <jboczar@antmicro.com>
Thu, 28 May 2020 10:10:25 +0000 (12:10 +0200)
litex/build/sim/core/modules/Makefile
litex/build/sim/core/modules/spdeeprom/Makefile [new file with mode: 0644]
litex/build/sim/core/modules/spdeeprom/spdeeprom.c [new file with mode: 0644]
litex/soc/cores/bitbang.py
litex/tools/litex_sim.py

index c7a35784acf737fa14441b13372d7118531219e0..6782a192a17a5e5a03d1d98df346003aa6bffb66 100644 (file)
@@ -1,5 +1,5 @@
 include ../variables.mak
-MODULES = xgmii_ethernet ethernet serial2console serial2tcp clocker
+MODULES = xgmii_ethernet ethernet serial2console serial2tcp clocker spdeeprom
 
 .PHONY: $(MODULES)
 all: $(MODULES)
diff --git a/litex/build/sim/core/modules/spdeeprom/Makefile b/litex/build/sim/core/modules/spdeeprom/Makefile
new file mode 100644 (file)
index 0000000..0c69b5b
--- /dev/null
@@ -0,0 +1,2 @@
+include ../../variables.mak
+include $(SRC_DIR)/modules/rules.mak
diff --git a/litex/build/sim/core/modules/spdeeprom/spdeeprom.c b/litex/build/sim/core/modules/spdeeprom/spdeeprom.c
new file mode 100644 (file)
index 0000000..db5d85f
--- /dev/null
@@ -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;
+}
index d2f62fa886feba76e36a3ebe844310d2577d7cbc..a54c45605d48abed4ef966b145120d57a47e9ed4 100644 (file)
@@ -30,8 +30,9 @@ class I2CMaster(Module, AutoCSR):
             CSRField("sda", size=1, offset=0)],
             name="r")
 
-        # # #
+        self.connect(pads)
 
+    def connect(self, pads):
         _sda_w  = Signal()
         _sda_oe = Signal()
         _sda_r  = Signal()
@@ -44,6 +45,32 @@ class I2CMaster(Module, AutoCSR):
         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 ---------------------------------------------------------------------------
 
 class SPIMaster(Module, AutoCSR):
index a96a17321314e6ee25e0a86cc337e9c6dbb04cb4..23fb6b71f3e17d9953aa020bf11b461917c7dc42 100755 (executable)
@@ -18,6 +18,7 @@ from litex.soc.integration.soc_core import *
 from litex.soc.integration.soc_sdram import *
 from litex.soc.integration.builder import *
 from litex.soc.integration.soc import *
+from litex.soc.cores.bitbang import *
 
 from litedram import modules as litedram_modules
 from litedram.modules import parse_spd_hexdump
@@ -63,6 +64,11 @@ _io = [
         Subsignal("sink_ready",   Pins(1)),
         Subsignal("sink_data",    Pins(8)),
     ),
+    ("i2c", 0,
+        Subsignal("scl",     Pins(1)),
+        Subsignal("sda_out", Pins(1)),
+        Subsignal("sda_in",  Pins(1)),
+    ),
 ]
 
 # Platform -----------------------------------------------------------------------------------------
@@ -170,6 +176,7 @@ class SimSoC(SoCCore):
         sdram_data_width      = 32,
         sdram_spd_data        = None,
         sdram_verbosity       = 0,
+        with_i2c              = False,
         **kwargs):
         platform     = Platform()
         sys_clk_freq = int(1e6)
@@ -293,6 +300,12 @@ class SimSoC(SoCCore):
                 csr_csv      = "analyzer.csv")
             self.add_csr("analyzer")
 
+        # I2C --------------------------------------------------------------------------------------
+        if with_i2c:
+            pads = platform.request("i2c", 0)
+            self.submodules.i2c = I2CMasterSim(pads)
+            self.add_csr("i2c")
+
 # Build --------------------------------------------------------------------------------------------
 
 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("--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-i2c",             action="store_true",     help="Enable I2C support")
     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-start",          default=0,               help="Cycle to start tracing")
@@ -352,12 +366,16 @@ def main():
     if args.with_ethernet or args.with_etherbone:
         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 = SimSoC(
         with_sdram     = args.with_sdram,
         with_ethernet  = args.with_ethernet,
         with_etherbone = args.with_etherbone,
         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),
         **soc_kwargs)
     if args.ram_init is not None: