diff --git a/.gitignore b/.gitignore index e2b79f2..5a234ce 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ *.asm *.map *.lib +.vscode diff --git a/README.md b/README.md index 8a841d6..e3f1e50 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,20 @@ Handheld PC based on the original 6502 CPU that fits into a pocket. # Simulator -Pocket265 simulator is there to make FW developement easier, but it can be also used to get a feel for the computer usage. I2C memory and UART are not simulated. It uses [simak65](https://github.com/agkaminski/simak65) simulator code. +Pocket265 simulator is there to make FW developement easier, but it can be also used to get a feel for the computer usage. Only UART is not simulated. It uses [simak65](https://github.com/agkaminski/simak65) simulator code. + +## Usage + +./pocket265-sim [OPTIONS] + +where options are: +- -c: path to the computer ROM (obligatory), +- -f: simulated CPU frequency, from 0 (max speed, no throttling) to 20000000 (Hz), +- -r: simulated RAM size in bytes, +- -e: simulated EEPROM file (will be updated after end of the simulation, if write is performed!), +- -h: usage. + +## Builing To run the simulation first build and install [simak65](https://github.com/agkaminski/simak65):
@@ -51,6 +64,8 @@ then build the simulator:
 $ (cd sim && make all)
 
+## Running the simulation + In the `sim/` directory is a script that starts the simulator with memory initialized with Pocket265 firmware - `runsim.sh`. To run the simulator:
 $ cd sim
diff --git a/sim/Makefile b/sim/Makefile
index 8615f22..7628c57 100644
--- a/sim/Makefile
+++ b/sim/Makefile
@@ -3,7 +3,7 @@ CFLAGS := -g -Wall -Werror -Wextra -Wno-type-limits -O2 -ansi -std=gnu99
 DEBUG := -DNDEBUG
 
 OUT = pocket265-sim
-OBJ = pocket265.o
+OBJ = pocket265.o i2c.o
 LIB = simak65
 
 %.o: %.c
diff --git a/sim/i2c.c b/sim/i2c.c
new file mode 100644
index 0000000..ccf5149
--- /dev/null
+++ b/sim/i2c.c
@@ -0,0 +1,146 @@
+/* 24xx I2C memory simulator
+ * Copyright A.K. 2018, 2023
+ */
+
+#include 
+#include 
+#include "i2c.h"
+
+void i2c_step(struct i2c_ctx *ctx, const struct i2c_bus *in, struct i2c_bus *out)
+{
+	*out = ctx->prevout;
+
+	if (in->scl) {
+		if (ctx->previn.sda && !in->sda) {
+			/* Start condition */
+			ctx->state = i2c_dev_addr;
+			ctx->regbit = 0;
+		}
+		else if (!ctx->previn.sda && in->sda) {
+			/* Stop condtion */
+			ctx->state = i2c_idle;
+		}
+	}
+
+	if (in->scl && !ctx->previn.scl) {
+		/* Rising edge */
+		switch (ctx->state) {
+			case i2c_dev_addr:
+				ctx->reg = (ctx->reg << 1) | !!in->sda;
+				if (++ctx->regbit > 7) {
+					ctx->regbit = 0;
+					if (ctx->devaddr == (ctx->reg >> 1)) {
+						if (ctx->reg & 1)
+							ctx->next = i2c_read;
+						else
+							ctx->next = i2c_addr;
+						ctx->state = i2c_ack;
+					}
+					else {
+						ctx->state = i2c_idle;
+					}
+				}
+				break;
+
+			case i2c_addr:
+				ctx->address = (ctx->address << 1) | !!in->sda;
+				++ctx->regbit;
+				if (ctx->regbit == 8) {
+					ctx->state = i2c_ack;
+					ctx->next = i2c_addr;
+				}
+				else if (ctx->regbit > 15) {
+					ctx->regbit = 0;
+					ctx->next = i2c_write;
+					ctx->state = i2c_ack;
+				}
+				break;
+
+			case i2c_write:
+				ctx->reg = (ctx->reg << 1) | !!in->sda;
+				if (++ctx->regbit > 7) {
+					ctx->regbit = 0;
+					ctx->mem[ctx->address % ctx->memsz] = ctx->reg;
+					++ctx->address;
+					ctx->next = i2c_write;
+					ctx->state = i2c_ack;
+				}
+				break;
+
+			case i2c_read:
+				++ctx->regbit;
+				if (ctx->regbit == 9) {
+					if (in->sda) {
+						ctx->state = i2c_idle;
+						ctx->regbit = 0;
+					}
+					else {
+						ctx->regbit = 0;
+						++ctx->address;
+					}
+				}
+				break;
+
+			case i2c_ack:
+				ctx->state = ctx->next;
+				break;
+
+			default:
+				break;
+		}
+	}
+	else if (!in->scl && ctx->previn.scl) {
+		/* Falling ege */
+		switch (ctx->state) {
+			case i2c_ack:
+				out->sda = 0;
+				break;
+
+			case i2c_read:
+				if (ctx->regbit == 0)
+					ctx->reg = ctx->mem[ctx->address % ctx->memsz];
+
+				if (ctx->regbit <= 7) {
+					out->sda = !!(ctx->reg & 0x80);
+					ctx->reg <<= 1;
+				}
+				else {
+					out->sda = 1;
+				}
+				break;
+
+			default:
+				out->sda = 1;
+				break;
+		}
+	}
+
+	ctx->previn = *in;
+	ctx->prevout = *out;
+}
+
+void i2c_destroy(struct i2c_ctx *ctx)
+{
+	free(ctx->mem);
+	ctx->mem = NULL;
+}
+
+int i2c_init(struct i2c_ctx *ctx, size_t memsz, uint8_t devaddr)
+{
+	ctx->previn = (struct i2c_bus){ .sda = 1, .scl = 1 };
+	ctx->prevout = (struct i2c_bus){ .sda = 1, .scl = 1 };
+
+	ctx->state = i2c_idle;
+	ctx->address = 0;
+	ctx->reg = 0;
+	ctx->regbit = 0;
+	ctx->devaddr = devaddr;
+
+	ctx->mem = malloc(memsz);
+	if (ctx->mem == NULL) {
+		return -1;
+	}
+	ctx->memsz = memsz;
+
+	return 0;
+}
diff --git a/sim/i2c.h b/sim/i2c.h
new file mode 100644
index 0000000..51a182f
--- /dev/null
+++ b/sim/i2c.h
@@ -0,0 +1,49 @@
+/* 24xx I2C memory simulator
+ * Copyright A.K. 2018, 2023
+ */
+
+#ifndef I2C_H_
+#define I2C_H_
+
+#include 
+#include 
+
+struct i2c_bus {
+	int sda;
+	int scl;
+};
+
+enum i2c_state {
+	i2c_idle,
+	i2c_dev_addr,
+	i2c_addr,
+	i2c_read,
+	i2c_write,
+	i2c_ack
+};
+
+struct i2c_ctx {
+	struct i2c_bus previn;
+	struct i2c_bus prevout;
+
+	uint8_t *mem;
+	size_t memsz;
+
+	uint16_t address;
+
+	uint8_t devaddr;
+
+	uint8_t reg;
+	uint8_t regbit;
+
+	enum i2c_state state;
+	enum i2c_state next;
+};
+
+void i2c_step(struct i2c_ctx *ctx, const struct i2c_bus *in, struct i2c_bus *out);
+
+void i2c_destroy(struct i2c_ctx *ctx);
+
+int i2c_init(struct i2c_ctx *ctx, size_t memsz, uint8_t devaddr);
+
+#endif
diff --git a/sim/pocket265.c b/sim/pocket265.c
index ac41c6d..3dc0479 100644
--- a/sim/pocket265.c
+++ b/sim/pocket265.c
@@ -17,6 +17,8 @@
 #include 
 #include 
 
+#include "i2c.h"
+
 #define RAM_START 0x0000u
 #define RAM_SIZE  0x2000u
 
@@ -32,16 +34,22 @@
 #define NMI_ACK_ADDR  0xc800u
 #define GPIO_ADDR     0xcc00u
 
+#define EEPROM_SIZE (32 * 1024)
+
+/* Memory */
 static uint8_t *g_ram;
 static size_t g_ramsz = RAM_SIZE;
 static uint8_t g_rom[ROM_SIZE];
 
-static uint8_t g_gpio_out;
+/* I/O */
 static char g_screen[12];
 static int g_screen_update = 1;
 static uint8_t g_key_row[6];
 static int g_nmi = 1;
 static volatile int g_exit = 0;
+static struct i2c_bus i2c_in;
+static struct i2c_bus i2c_out;
+static int g_romwp = 1;
 
 static void pocket265_nmi_ack(void)
 {
@@ -62,6 +70,8 @@ static uint8_t pocket265_keyread(uint16_t addr)
 
 static uint8_t pocket265_ioread(uint16_t addr)
 {
+	uint8_t gpio = 0;
+
 	switch (addr & PERIPH_ADDR_MASK) {
 		case KEYBOARD_ADDR:
 			return pocket265_keyread(addr);
@@ -69,7 +79,10 @@ static uint8_t pocket265_ioread(uint16_t addr)
 			pocket265_nmi_ack();
 			break;
 		case GPIO_ADDR:
-			return g_gpio_out & 0xf;
+			gpio |= !!(i2c_out.sda && i2c_in.sda) << 1; /* SDA */
+			gpio |= !!(i2c_out.scl && i2c_in.scl) << 2; /* SCL */
+			gpio |= !!g_romwp << 3;                     /* ROM_WP */
+			return gpio |= 0xf1;                        /* Unused */
 	}
 
 	return 0xff;
@@ -88,16 +101,13 @@ static void pocket265_iowrite(uint16_t addr, uint8_t byte)
 			pocket265_nmi_ack();
 			break;
 		case GPIO_ADDR:
-			g_gpio_out = byte & 0xf;
+			i2c_in.sda = !!(byte & (1 << 1));
+			i2c_in.scl = !!(byte & (1 << 2));
+			g_romwp = !!(byte & (1 << 3));
 			break;
 	}
 }
 
-static int pocket265_is_rom_wp(void)
-{
-	return (g_gpio_out & (1 << 3)) ? 1 : 0;
-}
-
 static uint8_t pocket265_read(uint16_t addr)
 {
 	if (addr >= RAM_START && addr < (RAM_START + g_ramsz))
@@ -114,7 +124,7 @@ static void pocket265_write(uint16_t addr, uint8_t byte)
 {
 	if (addr >= RAM_START && addr < (RAM_START + g_ramsz))
 		g_ram[addr - RAM_START] = byte;
-	else if (addr >= ROM_START && addr < (ROM_START + ROM_SIZE) && !pocket265_is_rom_wp())
+	else if (addr >= ROM_START && addr < (ROM_START + ROM_SIZE) && !g_romwp)
 		g_rom[addr - ROM_START] = byte;
 	else if (addr >= PERIPH_ADDR_START && addr <= PERIPH_ADDR_END)
 		pocket265_iowrite(addr, byte);
@@ -188,20 +198,20 @@ static useconds_t gettime_us(void)
 
 static void usage(const char *p)
 {
-	printf("usage: %s -c  [-r  ] [-f ]\n", p);
+	printf("usage: %s -c  [-r  ] [ -e  ] [-f ]\n", p);
 }
 
 static void sighandler(int n)
 {
-    (void)n;
+	(void)n;
 
-    g_exit = 1;
+	g_exit = 1;
 }
 
 int main(int argc, char *argv[])
 {
 	int c;
-	FILE *firmware = NULL;
+	FILE *firmware = NULL, *eeprom = NULL;
 	struct simak65_cpustate cpu;
 	const struct simak65_bus ops = { .read = pocket265_read, .write = pocket265_write };
 	unsigned int cycles = 0;
@@ -209,8 +219,9 @@ int main(int argc, char *argv[])
 	useconds_t prev, now, prev_nmi = 0, prev_ui = 0, ns_per_cycle = 0;
 	int ns_error = 0;
 	static struct termios oldt, newt;
+	struct i2c_ctx i2c;
 
-	while ((c = getopt(argc, argv, "c:f:r:h")) != -1) {
+	while ((c = getopt(argc, argv, "c:f:r:e:h")) != -1) {
 		switch (c) {
 			case 'c':
 				if ((firmware = fopen(optarg, "r")) == NULL) {
@@ -233,6 +244,12 @@ int main(int argc, char *argv[])
 					return 1;
 				}
 				break;
+			case 'e':
+				if ((eeprom = fopen(optarg, "r+")) == NULL) {
+					fprintf(stderr, "can't open EEPROM file %s\n", optarg);
+					return 1;
+				}
+				break;
 			case 'h':
 				usage(argv[0]);
 				return 0;
@@ -263,6 +280,14 @@ int main(int argc, char *argv[])
 		return 1;
 	}
 
+	if (i2c_init(&i2c, EEPROM_SIZE, 0x50) < 0) {
+		fprintf(stderr, "i2c init failed");
+		return 1;
+	}
+
+	if (eeprom != NULL)
+		(void)!fread(i2c.mem, 1, i2c.memsz, eeprom);
+
 	signal(SIGINT, sighandler);
 
 	tcgetattr(STDIN_FILENO, &oldt);
@@ -280,6 +305,7 @@ int main(int argc, char *argv[])
 
 	while (!g_exit) {
 		simak65_step(&cpu, &cycles);
+		i2c_step(&i2c, &i2c_in, &i2c_out);
 
 		now = gettime_us();
 
@@ -319,6 +345,14 @@ int main(int argc, char *argv[])
 
 	tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
 
+	if (eeprom != NULL) {
+		rewind(eeprom);
+		(void)!fwrite(i2c.mem, 1, i2c.memsz, eeprom);
+		fflush(eeprom);
+		fclose(eeprom);
+	}
+
+	i2c_destroy(&i2c);
 	free(g_ram);
 
 	return 0;