--- /dev/null
+#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;
+}
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
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 -----------------------------------------------------------------------------------------
sdram_data_width = 32,
sdram_spd_data = None,
sdram_verbosity = 0,
+ with_i2c = False,
**kwargs):
platform = Platform()
sys_clk_freq = int(1e6)
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():
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")
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: