diff --git a/RomPatcher/IIsiExtraMemory/Makefile b/RomPatcher/IIsiExtraMemory/Makefile
new file mode 100644
index 0000000..75304fe
--- /dev/null
+++ b/RomPatcher/IIsiExtraMemory/Makefile
@@ -0,0 +1,41 @@
+RETRO68=/home/dolbeau/Retro68/build/toolchain
+AS=${RETRO68}/bin/m68k-apple-macos-as
+CC=${RETRO68}/bin/m68k-apple-macos-gcc
+LD=${RETRO68}/bin/m68k-apple-macos-ld
+STRIP=${RETRO68}/bin/m68k-apple-macos-strip
+OBJCOPY=${RETRO68}/bin/m68k-apple-macos-objcopy
+NM=${RETRO68}/bin/m68k-apple-macos-nm
+
+HOSTCC=gcc
+HOSTCFLAGS=-O2
+
+ARCHFLAGS=-march=68020 -mcpu=68020
+CFLAGS=-O2 -mpcrel
+
+GENLINK=../patcher/genlink
+PATCHER=../patcher/patcher
+
+all: IIsi.ROM
+
+show: rompatch.elf
+ $(NM) $< | sort
+
+IIsi.ROM: ../IIsiRemoveChecksumCheck/IIsi.ROM rompatch.raw input.txt
+ /bin/cp ../IIsiRemoveChecksumCheck/IIsi.ROM IIsi.ROM
+ ${PATCHER} -i rompatch.raw -p IIsi.ROM -d input.txt
+
+linker.ld: input.txt rompatch.s
+ ${GENLINK} -d $< >| $@
+ echo $(shell for X in `grep .section rompatch.s | awk '{ print $$2 }' `; do grep -q $$X linker.ld || echo " $$X has no entry in linker.ld" && /bin/false; done)
+
+rompatch.o: rompatch.s
+ ${AS} ${ARCHFLAGS} $< -o $@ -a > rompatch.l
+
+rompatch.elf: linker.ld rompatch.o ${CSRC:.c=.o} # linker script must be first
+ ${LD} -o $@ -T $^
+
+rompatch.raw: rompatch.elf
+ ${OBJCOPY} $^ $@ --input-target=elf32-m68k --output-target=binary
+
+clean:
+ rm -f res.inc ${CSRC_ASM} *.o rompatch.srec rompatch.raw rompatch.dir rompatch.l linker.ld rompatch.elf
diff --git a/RomPatcher/IIsiExtraMemory/input.txt b/RomPatcher/IIsiExtraMemory/input.txt
new file mode 100644
index 0000000..368041f
--- /dev/null
+++ b/RomPatcher/IIsiExtraMemory/input.txt
@@ -0,0 +1,3 @@
+0x00396e, 32, raminfo
+0x003cc4, 316, gary
+0x0419e8, 20, findinfopatch
diff --git a/RomPatcher/IIsiExtraMemory/rompatch.s b/RomPatcher/IIsiExtraMemory/rompatch.s
new file mode 100644
index 0000000..1b6e67d
--- /dev/null
+++ b/RomPatcher/IIsiExtraMemory/rompatch.s
@@ -0,0 +1,51 @@
+ MAPBASE=0x20000000
+ MAPSIZE=0x0f000000
+ MAPEND=MAPBASE+MAPSIZE
+
+/* ************************************************************************ */
+ /* updated table */
+ .section .text.raminfo
+ .long MAPBASE
+ .long MAPEND
+ .long 0x04000000
+ .long 0x08000000
+ .long 0x00000000
+ .long 0x04000000
+ .long 0xFFFFFFFF
+ .long 0xFFFFFFFF
+
+/* ************************************************************************ */
+ .section .text.gary
+fixchunk: /* 316 bytes available */
+ /* recreate the table but with one more chunk, as the original code assumes two chunks and turns them into three */
+
+ move.l %D4,(%A1)+
+ move.l %D5,(%A1)+
+ move.l %D2,(%A1)+
+ move.l %D3,(%A1)+
+
+ add.l %D5,%D4
+ move.l %D4,(%A1)+
+ move.l %D1,(%A1)+
+
+ /* here comes the bonus */
+ move.l #MAPBASE,(%A1)+
+ move.l #MAPSIZE,(%A1)+
+ /* here ends the bonus */
+
+ moveq #-1,%D4
+ move.l %D4,(%A1)+
+ move.l %D4,(%A1)+
+
+ jmp (%pc,returnfindinfopatch)
+
+/* ************************************************************************ */
+ .section .text.findinfopatch
+findinfopatch: /* 20 bytes available */
+ jmp (%pc,fixchunk)
+
+ .section .text.returnfindinfopatch
+returnfindinfopatch:
+ /* */
+
+ .end
diff --git a/RomPatcher/IIsiRemoveChecksumCheck/Makefile b/RomPatcher/IIsiRemoveChecksumCheck/Makefile
new file mode 100644
index 0000000..45475b3
--- /dev/null
+++ b/RomPatcher/IIsiRemoveChecksumCheck/Makefile
@@ -0,0 +1,41 @@
+RETRO68=/home/dolbeau/Retro68/build/toolchain
+AS=${RETRO68}/bin/m68k-apple-macos-as
+CC=${RETRO68}/bin/m68k-apple-macos-gcc
+LD=${RETRO68}/bin/m68k-apple-macos-ld
+STRIP=${RETRO68}/bin/m68k-apple-macos-strip
+OBJCOPY=${RETRO68}/bin/m68k-apple-macos-objcopy
+NM=${RETRO68}/bin/m68k-apple-macos-nm
+
+HOSTCC=gcc
+HOSTCFLAGS=-O2
+
+ARCHFLAGS=-march=68020 -mcpu=68020
+CFLAGS=-O2 -mpcrel
+
+GENLINK=../patcher/genlink
+PATCHER=../patcher/patcher
+
+all: IIsi.ROM
+
+show: rompatch.elf
+ $(NM) $< | sort
+
+IIsi.ROM: ../IIsi.ROM rompatch.raw input.txt
+ /bin/cp ../IIsi.ROM IIsi.ROM
+ ${PATCHER} -i rompatch.raw -p IIsi.ROM -d input.txt
+
+linker.ld: input.txt rompatch.s
+ ${GENLINK} -d $< >| $@
+ echo $(shell for X in `grep .section rompatch.s | awk '{ print $$2 }' `; do grep -q $$X linker.ld || echo " $$X has no entry in linker.ld" && /bin/false; done)
+
+rompatch.o: rompatch.s
+ ${AS} ${ARCHFLAGS} $< -o $@ -a > rompatch.l
+
+rompatch.elf: linker.ld rompatch.o ${CSRC:.c=.o} # linker script must be first
+ ${LD} -o $@ -T $^
+
+rompatch.raw: rompatch.elf
+ ${OBJCOPY} $^ $@ --input-target=elf32-m68k --output-target=binary
+
+clean:
+ rm -f res.inc ${CSRC_ASM} *.o rompatch.srec rompatch.raw rompatch.dir rompatch.l linker.ld rompatch.elf
diff --git a/RomPatcher/IIsiRemoveChecksumCheck/input.txt b/RomPatcher/IIsiRemoveChecksumCheck/input.txt
new file mode 100644
index 0000000..624c175
--- /dev/null
+++ b/RomPatcher/IIsiRemoveChecksumCheck/input.txt
@@ -0,0 +1 @@
+0x0464e2, 10, removecheksumcheck
diff --git a/RomPatcher/IIsiRemoveChecksumCheck/rompatch.s b/RomPatcher/IIsiRemoveChecksumCheck/rompatch.s
new file mode 100644
index 0000000..44149fc
--- /dev/null
+++ b/RomPatcher/IIsiRemoveChecksumCheck/rompatch.s
@@ -0,0 +1,12 @@
+ .section .text.removecheksumcheck
+
+nops:
+ nop
+ nop
+ nop
+ nop
+ nop
+
+ .section .text.returnremovecheksumcheck
+
+ .end
diff --git a/RomPatcher/README.md b/RomPatcher/README.md
new file mode 100644
index 0000000..a5195a2
--- /dev/null
+++ b/RomPatcher/README.md
@@ -0,0 +1,12 @@
+# RomPatcher
+
+This is a small set of tools to help with ROM patching. A single input file made up of lines \
,\,\ describes which area need patching. can be 0 for e.g. calls to exsiting functions.
+
+One tool generates linked file to place things where they need to be in the compiled binary.
+One tool copies the relevant area from the generated binary to the file that needs to be patched.
+
+An assembly source file can then be used to write the patch, placing code in the appropriate sections as per the linker file. They are then patched into the final file.
+
+Example:
+* IIsiRemoveChecksumCheck: replace a few instructions in a Macintosh IIsi ROM file to disable the checksum test (thus allowing further patching)
+* IIsiExtraMemoryi: patch the memory chunk table and some code to enable an extra area of memory in a IIsi ROM, usable with e.g. the IIsiFPGA
diff --git a/RomPatcher/patcher/Makefile b/RomPatcher/patcher/Makefile
new file mode 100644
index 0000000..49dd775
--- /dev/null
+++ b/RomPatcher/patcher/Makefile
@@ -0,0 +1,50 @@
+PATCHERSRC=patcher.c
+PATCHEROBJ=$(PATCHERSRC:.cpp=.o)
+PATCHERDEP=$(PATCHERSRC:.cpp=.d)
+GENLINKSRC=genlink.c
+GENLINKOBJ=$(GENLINKSRC:.cpp=.o)
+GENLINKDEP=$(GENLINKSRC:.cpp=.d)
+OOBJ=parser_par.o parser_lex.o
+LEX=flex
+YACC=bison -d #--report-file=bison.log --report=all
+CC=gcc
+CFLAGS=-O2
+
+all: patcher genlink
+
+%.o: %.c
+ $(CC) $(CFLAGS) $< -c -o $@
+
+patcher: $(PATCHEROBJ) $(OOBJ)
+ $(CC) $(CFLAGS) $^ -o $@
+
+genlink: $(GENLINKOBJ) $(OOBJ)
+ $(CC) $(CFLAGS) $^ -o $@
+
+parser_par.h: parser_par.o
+
+parser_par.o: parser_par.y
+ $(YACC) -o $(<:%.y=%.c) $<
+ $(CC) $(CFLAGS) $(<:%.y=%.c) -c -o $@
+
+parser_lex.o: parser_lex.l parser_par.h
+ $(LEX) -o $(<:%.l=%.c) $<
+ $(CC) $(CFLAGS) $(<:%.l=%.c) -c -o $@
+
+%.d: %.cpp
+ $(CXX) -MM $< -o $@
+
+clean:
+ rm -f $(PATCHEROBJ) patcher
+
+veryclean:
+ rm -f $(PATCHEROBJ) patcher *~ parser_lex.c parser_par.c *.d
+
+ultraclean:
+ rm -f $(PATCHEROBJ) patcher *~ parser_lex.c parser_par.c *.d *.scala
+
+
+## avoid builtin rule for .o
+.SUFFIXES:
+SUFFIXES :=
+%.o:
diff --git a/RomPatcher/patcher/genlink.c b/RomPatcher/patcher/genlink.c
new file mode 100644
index 0000000..68d6a5e
--- /dev/null
+++ b/RomPatcher/patcher/genlink.c
@@ -0,0 +1,76 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "parser.h"
+#include "parser_par.h"
+
+extern FILE *yyin, *yyout;
+extern int yydebug;
+
+
+int main(int argc, char **argv) {
+ char* descfile = NULL;
+ FILE *myfile;
+ int c;
+ azone zones[32];
+ int nz = 0, i;
+
+ while ((c = getopt(argc, argv, "d:")) != -1) {
+ switch(c) {
+ default:
+ fprintf(stderr, "oups : %c\n", c);
+ exit(-1);
+ break;
+
+ case 'd':
+ descfile = strndup(optarg, 512);
+ break;
+ }
+ }
+
+ if (descfile == NULL) {
+ fprintf(stderr, "no desc\n");
+ exit(-4);
+ }
+
+ {
+ myfile = fopen(descfile, "r");
+ if (!myfile) {
+ fprintf(stderr, "no desc file\n");
+ exit(-5);
+ }
+ yyin = myfile;
+ yyparse(zones, &nz);
+
+ fclose(myfile);
+
+ fprintf(stderr, "Found %d entries\n", nz);
+ }
+
+ fprintf(stdout,
+ "OUTPUT_FORMAT(\"elf32-m68k\");\n"
+ "SECTIONS {\n"
+ " .text : {\n");
+
+ for (i = 0 ; i < nz; i++) {
+ fprintf(stdout, " . = 0x%06x;\n", zones[i].address);
+ fprintf(stdout, " *(.text.%s)\n", zones[i].name);
+ if (zones[i].size > 0) {
+ fprintf(stdout, " . = 0x%06x;\n", zones[i].address + zones[i].size);
+ fprintf(stdout, " *(.text.return%s)\n", zones[i].name);
+ }
+ fprintf(stdout, "\n");
+ }
+
+ fprintf(stdout, " }\n}\n");
+
+ return 0;
+}
diff --git a/RomPatcher/patcher/parser.h b/RomPatcher/patcher/parser.h
new file mode 100644
index 0000000..c7f4431
--- /dev/null
+++ b/RomPatcher/patcher/parser.h
@@ -0,0 +1,10 @@
+#ifndef __PATCHER_H__
+#define __PATCHER_H__
+
+typedef struct {
+ unsigned long address;
+ unsigned long size;
+ char* name;
+} azone;
+
+#endif // __PATCHER_H__
diff --git a/RomPatcher/patcher/parser_lex.l b/RomPatcher/patcher/parser_lex.l
new file mode 100644
index 0000000..ed42741
--- /dev/null
+++ b/RomPatcher/patcher/parser_lex.l
@@ -0,0 +1,34 @@
+%{
+/*
+ * Copyright (c) 2023 Romain Dolbeau
+ */
+#include
+#include
+#include
+
+#include "parser.h"
+#include "parser_par.h"
+%}
+
+DIGIT [0-9]
+HEXPREFIX "0x"
+HEXDIGIT [0-9a-fA-F]
+SPACE [ \t]
+FCHARNAME [[:alpha:]]
+CHARNAME [[:alnum:]_]
+
+%%
+
+{HEXPREFIX}{HEXDIGIT}{HEXDIGIT}* { yylval.num = strtol(yytext, NULL, 16); return NUM; }
+
+{DIGIT}{DIGIT}* { yylval.num = strtol(yytext, NULL, 10); return NUM; }
+
+{FCHARNAME}{CHARNAME}* { yylval.string = strdup(yytext); return NAME; }
+
+, { return yytext[0]; }
+
+\n { return yytext[0]; }
+
+{SPACE}+ { }
+
+%%
diff --git a/RomPatcher/patcher/parser_par.y b/RomPatcher/patcher/parser_par.y
new file mode 100644
index 0000000..6f48d28
--- /dev/null
+++ b/RomPatcher/patcher/parser_par.y
@@ -0,0 +1,46 @@
+%{
+/*
+ * Copyright (c) 2023 Romain Dolbeau
+ * MIT License
+ * See the LICENSE file at the top level of this software distribution for details.
+ */
+#include
+#include
+#define YYDEBUG 1
+#include "parser.h"
+%}
+
+%parse-param {azone *zones} {int *nz}
+
+%union
+{
+ unsigned long num;
+ char* string;
+}
+
+%token NUM
+%token NAME
+
+%%
+input: /* empty */ { }
+| zone input { }
+;
+
+zone:
+NUM ',' NUM ',' NAME { /* printf("0x%08lx %ld\n", $1, $3); */ zones[*nz].address = $1; zones[*nz].size = $3; zones[*nz].name = $5; (*nz)++; }
+| '\n'
+;
+%%
+
+int
+yyerror(char *s)
+{
+ fprintf(stderr, "error: %s\n", s);
+ return(0);
+}
+
+int
+yywrap(void)
+{
+ return(-1);
+}
diff --git a/RomPatcher/patcher/patcher.c b/RomPatcher/patcher/patcher.c
new file mode 100644
index 0000000..88af267
--- /dev/null
+++ b/RomPatcher/patcher/patcher.c
@@ -0,0 +1,146 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "parser.h"
+#include "parser_par.h"
+
+extern FILE *yyin, *yyout;
+extern int yydebug;
+
+
+int main(int argc, char **argv) {
+ char* descfile = NULL;
+ char* patchedfile = NULL;
+ char* inputfile = NULL;
+ FILE *myfile;
+ int c;
+ azone zones[32];
+ int nz = 0, i;
+ unsigned long j;
+ char *input;
+ size_t input_size;
+ char *patched;
+ size_t patched_size;
+
+ while ((c = getopt(argc, argv, "d:i:p:")) != -1) {
+ switch(c) {
+ default:
+ fprintf(stderr, "oups : %c\n", c);
+ exit(-1);
+ break;
+
+ case 'd':
+ descfile = strndup(optarg, 512);
+ break;
+
+ case 'i':
+ inputfile = strndup(optarg, 512);
+ break;
+
+ case 'p':
+ patchedfile = strndup(optarg, 512);
+ break;
+ }
+ }
+
+ if (descfile == NULL) {
+ fprintf(stderr, "no desc\n");
+ exit(-4);
+ }
+ if (inputfile == NULL) {
+ fprintf(stderr, "no input\n");
+ exit(-2);
+ }
+ if (patchedfile == NULL) {
+ fprintf(stderr, "nothing to patch\n");
+ exit(-3);
+ }
+
+
+
+ {
+ myfile = fopen(descfile, "r");
+ if (!myfile) {
+ fprintf(stderr, "no desc file\n");
+ exit(-5);
+ }
+ yyin = myfile;
+ yyparse(zones, &nz);
+
+ fclose(myfile);
+
+ fprintf(stdout, "Found %d entries\n", nz);
+ }
+
+ {
+ int fd;
+ fd = open(inputfile, O_RDONLY | O_EXCL);
+ if (fd == -1) {
+ fprintf(stderr, "no input file\n");
+ exit(-6);
+ }
+ struct stat statbuf;
+ int err = fstat(fd, &statbuf);
+ if (err < 0){
+ fprintf(stderr, "no input file stat\n");
+ close(fd);
+ exit(-7);
+ }
+ input_size = statbuf.st_size;
+ input = mmap(NULL, input_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (input == MAP_FAILED){
+ fprintf(stderr, "no input file map (%p for %s %zd: %d / %s)\n", input, inputfile, input_size, errno, strerror(errno));
+ close(fd);
+ exit(-8);
+ }
+ close(fd);
+ }
+ {
+ int fd;
+ fd = open(patchedfile, O_RDWR | O_EXCL);
+ if (fd == -1) {
+ fprintf(stderr, "no patched file\n");
+ exit(-6);
+ }
+ struct stat statbuf;
+ int err = fstat(fd, &statbuf);
+ if (err < 0){
+ fprintf(stderr, "no patched file stat\n");
+ close(fd);
+ exit(-7);
+ }
+
+ patched_size = statbuf.st_size;
+ patched = mmap(NULL, patched_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+ if (patched == MAP_FAILED){
+ fprintf(stderr, "no patched file map\n");
+ close(fd);
+ exit(-8);
+ }
+ close(fd);
+ }
+
+ for (i = 0 ; i < nz; i++) {
+ if (zones[i].size > 0) {
+ fprintf(stdout, "Patching [%08p:%08p[ (%s)\n", (void*)zones[i].address, (void*)(zones[i].address + zones[i].size), zones[i].name);
+ for (j = zones[i].address; j < zones[i].address + zones[i].size; j++) {
+ patched[j] = input[j];
+ }
+ } else {
+ fprintf(stdout, "Not patching [%08p[ (%s)\n", (void*)zones[i].address, zones[i].name);
+ }
+ }
+
+ munmap(input, input_size);
+ munmap(patched, patched_size);
+
+ return 0;
+}
diff --git a/Ziscreen.py b/Ziscreen.py
new file mode 100644
index 0000000..1f30788
--- /dev/null
+++ b/Ziscreen.py
@@ -0,0 +1,152 @@
+from migen import *
+from migen.genlib.fifo import *
+from litex.soc.interconnect.csr import *
+
+class Ziscreen(Module, AutoCSR):
+ def __init__(self, platform, wb, width=1920, height=1080, depth=8, fifo=None):
+
+ default_address = (0x8f800000) // 4
+ last_address = (0x8f800000 + (width-8) + width*1080) // 4
+ address_increment = (width) // 4
+ base_address = Signal(30, reset = default_address)
+ cur_address = Signal(30)
+ rowcnt = Signal(5)
+
+ print(f"TRACE: displaying @ 0x{default_address*4:08x}")
+
+ cur_nib_idx = Signal(3)
+ cur_line_idx = Signal(3)
+
+ wrapped = Signal()
+
+ val = Signal(32)
+
+ cur_nib = Signal(4)
+ self.comb += [
+ Case((cur_nib_idx ^ 1), { ## nibble-swap for proper order
+ x: cur_nib.eq(val[(4*x):(4*x+4)]) for x in range(0, 8)
+ }),
+ ]
+ cur_nib_decoded = Signal(8)
+ self.submodules.fsm = fsm = FSM(reset_state="Reset")
+
+
+ if (True):
+ #saw_ongoing = Signal()
+ #saw_readable= Signal()
+ #self.sync += [
+ # If(~fsm.ongoing("Idle"),
+ # saw_ongoing.eq(1),
+ # ),
+ # If(fifo.readable,
+ # saw_readable.eq(1),
+ # ),
+ #]
+
+ led0 = platform.request("user_led", 0)
+ led1 = platform.request("user_led", 1)
+ led2 = platform.request("user_led", 2)
+ led3 = platform.request("user_led", 3)
+ led4 = platform.request("user_led", 4)
+ led5 = platform.request("user_led", 5)
+ led6 = platform.request("user_led", 6)
+ led7 = platform.request("user_led", 7)
+
+ self.comb += [
+ led0.eq(~fsm.ongoing("Idle")),
+ led1.eq(fifo.readable),
+ led2.eq(0),
+ led3.eq(0),
+
+ #led4.eq(saw_ongoing),
+ #led5.eq(saw_readable),
+ led4.eq(0),
+ led5.eq(wb.ack),
+ led6.eq(wb.stb),
+ led7.eq(wb.cyc),
+ ]
+
+ self.comb += [
+ wb.we.eq(1),
+ wb.sel.eq(0xF),
+ wb.adr.eq(cur_address),
+ ]
+
+ fsm.act("Reset",
+ NextState("Idle"),
+ )
+ fsm.act("Idle",
+ If(base_address >= last_address,
+ NextValue(base_address, default_address),
+ NextValue(wrapped, 1 ^ wrapped),
+ NextValue(rowcnt, 0),
+ ).Elif(rowcnt == 30,
+ NextValue(rowcnt, 0),
+ NextValue(base_address, base_address + 7*address_increment),
+ ).Elif(fifo.readable,
+ NextValue(val, fifo.dout),
+ NextValue(cur_line_idx, 0),
+ NextValue(cur_nib_idx, 0),
+ NextValue(cur_address, base_address),
+ fifo.re.eq(1),
+ NextState("StartWriteLow"),
+ ),
+ )
+ fsm.act("StartWriteLow",
+ wb.cyc.eq(1),
+ wb.stb.eq(1),
+ wb.dat_w.eq(Cat(Replicate(wrapped ^ cur_nib_decoded[7], 8), Replicate(wrapped ^ cur_nib_decoded[6], 8), Replicate(wrapped ^ cur_nib_decoded[5], 8), Replicate(wrapped ^ cur_nib_decoded[4], 8))),
+ If(wb.ack,
+ NextValue(cur_address, cur_address + 1),
+ NextState("StartWriteHigh"),
+ ),
+ )
+ fsm.act("StartWriteHigh",
+ wb.cyc.eq(1),
+ wb.stb.eq(1),
+ wb.dat_w.eq(Cat(Replicate(wrapped ^ cur_nib_decoded[3], 8), Replicate(wrapped ^ cur_nib_decoded[2], 8), Replicate(wrapped ^ cur_nib_decoded[1], 8), Replicate(wrapped ^ cur_nib_decoded[0], 8))),
+ If(wb.ack,
+ If((cur_line_idx == 7) & (cur_nib_idx == 7),
+ NextValue(base_address, base_address + 16 - (address_increment*7) ),
+ NextValue(rowcnt, rowcnt + 1),
+ NextState("Idle"),
+ ).Elif(cur_nib_idx == 7,
+ NextValue(base_address, base_address + address_increment),
+ NextValue(cur_address, base_address + address_increment),
+ NextValue(cur_nib_idx, 0),
+ NextValue(cur_line_idx, cur_line_idx + 1),
+ NextState("StartWriteLow"),
+ ).Else(
+ NextValue(cur_address, cur_address + 1),
+ NextValue(cur_nib_idx, cur_nib_idx + 1),
+ NextState("StartWriteLow"),
+ )
+ ),
+ )
+
+ self.comb += [
+ Case((cur_line_idx), {
+ 0: cur_nib_decoded.eq(0),
+ 7: cur_nib_decoded.eq(0),
+ 1: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x18), 1:cur_nib_decoded.eq(0x10), 2:cur_nib_decoded.eq(0x18), 3:cur_nib_decoded.eq(0x38), 4:cur_nib_decoded.eq(0x20), 5:cur_nib_decoded.eq(0x3c), 6:cur_nib_decoded.eq(0x1c), 7:cur_nib_decoded.eq(0x3c), 8:cur_nib_decoded.eq(0x18), 9:cur_nib_decoded.eq(0x18), 10:cur_nib_decoded.eq(0x18), 11:cur_nib_decoded.eq(0x38), 12:cur_nib_decoded.eq(0x18), 13:cur_nib_decoded.eq(0x38), 14:cur_nib_decoded.eq(0x3c), 15:cur_nib_decoded.eq(0x3c),
+ }),
+ 2: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x24), 1:cur_nib_decoded.eq(0x30), 2:cur_nib_decoded.eq(0x24), 3:cur_nib_decoded.eq(0x04), 4:cur_nib_decoded.eq(0x28), 5:cur_nib_decoded.eq(0x20), 6:cur_nib_decoded.eq(0x20), 7:cur_nib_decoded.eq(0x04), 8:cur_nib_decoded.eq(0x24), 9:cur_nib_decoded.eq(0x24), 10:cur_nib_decoded.eq(0x24), 11:cur_nib_decoded.eq(0x24), 12:cur_nib_decoded.eq(0x24), 13:cur_nib_decoded.eq(0x24), 14:cur_nib_decoded.eq(0x20), 15:cur_nib_decoded.eq(0x20),
+ }),
+ 3: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x2c), 1:cur_nib_decoded.eq(0x10), 2:cur_nib_decoded.eq(0x04), 3:cur_nib_decoded.eq(0x18), 4:cur_nib_decoded.eq(0x28), 5:cur_nib_decoded.eq(0x38), 6:cur_nib_decoded.eq(0x38), 7:cur_nib_decoded.eq(0x08), 8:cur_nib_decoded.eq(0x18), 9:cur_nib_decoded.eq(0x24), 10:cur_nib_decoded.eq(0x24), 11:cur_nib_decoded.eq(0x38), 12:cur_nib_decoded.eq(0x20), 13:cur_nib_decoded.eq(0x24), 14:cur_nib_decoded.eq(0x38), 15:cur_nib_decoded.eq(0x38),
+ }),
+ 4: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x34), 1:cur_nib_decoded.eq(0x10), 2:cur_nib_decoded.eq(0x18), 3:cur_nib_decoded.eq(0x04), 4:cur_nib_decoded.eq(0x3c), 5:cur_nib_decoded.eq(0x04), 6:cur_nib_decoded.eq(0x24), 7:cur_nib_decoded.eq(0x10), 8:cur_nib_decoded.eq(0x24), 9:cur_nib_decoded.eq(0x1c), 10:cur_nib_decoded.eq(0x3c), 11:cur_nib_decoded.eq(0x24), 12:cur_nib_decoded.eq(0x20), 13:cur_nib_decoded.eq(0x24), 14:cur_nib_decoded.eq(0x20), 15:cur_nib_decoded.eq(0x20),
+ }),
+ 5: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x24), 1:cur_nib_decoded.eq(0x10), 2:cur_nib_decoded.eq(0x20), 3:cur_nib_decoded.eq(0x04), 4:cur_nib_decoded.eq(0x08), 5:cur_nib_decoded.eq(0x04), 6:cur_nib_decoded.eq(0x24), 7:cur_nib_decoded.eq(0x10), 8:cur_nib_decoded.eq(0x24), 9:cur_nib_decoded.eq(0x04), 10:cur_nib_decoded.eq(0x24), 11:cur_nib_decoded.eq(0x24), 12:cur_nib_decoded.eq(0x24), 13:cur_nib_decoded.eq(0x24), 14:cur_nib_decoded.eq(0x20), 15:cur_nib_decoded.eq(0x20),
+ }),
+ 6: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x18), 1:cur_nib_decoded.eq(0x38), 2:cur_nib_decoded.eq(0x3c), 3:cur_nib_decoded.eq(0x38), 4:cur_nib_decoded.eq(0x08), 5:cur_nib_decoded.eq(0x38), 6:cur_nib_decoded.eq(0x18), 7:cur_nib_decoded.eq(0x10), 8:cur_nib_decoded.eq(0x18), 9:cur_nib_decoded.eq(0x38), 10:cur_nib_decoded.eq(0x24), 11:cur_nib_decoded.eq(0x38), 12:cur_nib_decoded.eq(0x18), 13:cur_nib_decoded.eq(0x38), 14:cur_nib_decoded.eq(0x3c), 15:cur_nib_decoded.eq(0x20),
+ }),
+ }
+ ),
+ ]
+
diff --git a/Zled.py b/Zled.py
new file mode 100644
index 0000000..3dbfc22
--- /dev/null
+++ b/Zled.py
@@ -0,0 +1,106 @@
+from migen import *
+from migen.genlib.fifo import *
+from litex.soc.interconnect.csr import *
+
+class Zled(Module, AutoCSR):
+ def __init__(self, platform):
+
+ self.data0 = data0 = CSRStorage(32, description = "data 0")
+ self.data1 = data1 = CSRStorage(32, description = "data 1")
+
+ ctr_hundredth = Signal(20)
+ ctr_count = Signal(7)
+
+ leds = platform.request_all("user_led")
+
+ self.submodules.fsm = fsm = FSM(reset_state="Reset")
+
+ self.sync += [
+ If(ctr_hundredth != 0,
+ ctr_hundredth.eq(ctr_hundredth - 1),
+ ).Else(
+ ctr_hundredth.eq(1000000),
+ If(ctr_count != 0,
+ ctr_count.eq(ctr_count - 1),
+ ),
+ )
+ ]
+
+ display_idx = Signal(5, reset = 0x1F)
+ self.comb += [
+ Case(display_idx, {
+ 0x00: [ leds.eq(data0.storage[ 0: 8]), ],
+ 0x01: [ leds.eq(data0.storage[ 8:16]), ],
+ 0x02: [ leds.eq(data0.storage[16:24]), ],
+ 0x03: [ leds.eq(data0.storage[24:32]), ],
+ 0x04: [ leds.eq(data1.storage[ 0: 8]), ],
+ 0x05: [ leds.eq(data1.storage[ 8:16]), ],
+ 0x06: [ leds.eq(data1.storage[16:24]), ],
+ 0x07: [ leds.eq(data1.storage[24:32]), ],
+ 0x08: [ leds.eq(0x00), ],
+ 0x10: [ leds.eq(0x01), ],
+ 0x11: [ leds.eq(0x02), ],
+ 0x12: [ leds.eq(0x04), ],
+ 0x13: [ leds.eq(0x08), ],
+ 0x14: [ leds.eq(0x10), ],
+ 0x15: [ leds.eq(0x20), ],
+ 0x16: [ leds.eq(0x40), ],
+ 0x17: [ leds.eq(0x80), ],
+ 0x18: [ leds.eq(0xFF), ],
+ "default": [ leds.eq(0x00), ],
+ }),
+ ]
+
+ bytenum = Signal(3)
+ flashes = Signal(4)
+
+ fsm.act("Reset",
+ NextValue(ctr_count, 12),
+ NextValue(display_idx, 0x10),
+ NextState("March"),
+ )
+ ###################
+ # must arrive with ctr_count == 12, display_idx == 0x10
+ fsm.act("March",
+ If((ctr_hundredth == 0) & (ctr_count == 0),
+ If(display_idx == 0x17, # finished
+ NextValue(ctr_count, 100),
+ NextValue(display_idx, 0x00),
+ NextValue(bytenum, 0),
+ NextState("Byte"),
+ ).Else(
+ NextValue(display_idx, display_idx + 1),
+ NextValue(ctr_count, 12),
+ )
+ ),
+ )
+ ###################
+ fsm.act("Byte",
+ If((ctr_hundredth == 0) & (ctr_count == 0),
+ If(bytenum == 0x7,
+ NextValue(ctr_count, 12),
+ NextValue(display_idx, 0x10),
+ NextState("March"),
+ ).Else(
+ NextValue(bytenum, bytenum + 1),
+ NextValue(ctr_count, 5),
+ NextValue(display_idx, 0x08),
+ NextValue(flashes, 5),
+ NextState("Flash"),
+ )
+ ),
+ )
+ ###################
+ fsm.act("Flash",
+ If((ctr_hundredth == 0) & (ctr_count == 0),
+ If(flashes == 0, # finished
+ NextValue(ctr_count, 100),
+ NextValue(display_idx, Cat(bytenum, Signal(len(display_idx) - len(bytenum), reset = 0))),
+ NextState("Byte"),
+ ).Else(
+ NextValue(display_idx, display_idx ^ 0x10),
+ NextValue(flashes, flashes - 1),
+ NextValue(ctr_count, 5),
+ )
+ ),
+ )
diff --git a/Zscreen.py b/Zscreen.py
new file mode 100644
index 0000000..894e24f
--- /dev/null
+++ b/Zscreen.py
@@ -0,0 +1,162 @@
+from migen import *
+from migen.genlib.fifo import *
+from litex.soc.interconnect.csr import *
+
+class Zscreen(Module, AutoCSR):
+ def __init__(self, platform, wb, width=1920, height=1080, depth=8):
+
+ self.trace_data = trace_data = CSRStorage(32, description = "trace_data")
+
+ self.submodules.fifo = fifo = SyncFIFOBuffered(width=32,depth=128)
+
+ self.comb += [
+ fifo.din.eq(trace_data.storage),
+ fifo.we.eq(trace_data.re),
+ ]
+
+ #curx = Signal(12, reset = (width-136))
+ #cury = Signal(12, reset = (8))
+
+ default_address = (0x8f800000 + (width-138) + (width*24) ) // 4 # FIXME: for 8 MiB FB only IMPROVEME: assumes 32-bits WB
+ last_address = (0x8f800000 + (width-138) + (width*1072)) // 4 # FIXME: for 8 MiB FB only IMPROVEME: assumes 32-bits WB
+ address_increment = (width) // 4
+ #default_address = (0x8f800000 + width//2 + width*height//2) // 4
+ #last_address = (0x8f800000 + width//2 + width*(height-8)) // 4
+ #address_increment = (width) // 4
+ base_address = Signal(30, reset = default_address) # IMPROVEME: assumes 32-bits WB
+ cur_address = Signal(30)
+
+ print(f"TRACE: displaying @ 0x{default_address*4:08x} with increment {address_increment*4}")
+
+ cur_nib_idx = Signal(3)
+ cur_line_idx = Signal(3)
+
+ val = Signal(32)
+
+ cur_nib = Signal(4)
+ self.comb += [
+ Case((cur_nib_idx ^ 1), { ## nibble-swap for proper order
+ x: cur_nib.eq(val[(4*x):(4*x+4)]) for x in range(0, 8)
+ }),
+ ]
+ cur_nib_decoded = Signal(8)
+ self.submodules.fsm = fsm = FSM(reset_state="Reset")
+
+ saw_ongoing = Signal()
+ saw_re = Signal()
+ saw_readable= Signal()
+ self.sync += [
+ If(~fsm.ongoing("Idle"),
+ saw_ongoing.eq(1),
+ ),
+ If(trace_data.re,
+ saw_re.eq(1),
+ ),
+ If(fifo.readable,
+ saw_readable.eq(1),
+ ),
+ ]
+
+ if (False):
+ led0 = platform.request("user_led", 0)
+ led1 = platform.request("user_led", 1)
+ led2 = platform.request("user_led", 2)
+ led3 = platform.request("user_led", 3)
+ led4 = platform.request("user_led", 4)
+ led5 = platform.request("user_led", 5)
+ led6 = platform.request("user_led", 6)
+ led7 = platform.request("user_led", 7)
+
+ self.comb += [
+ led0.eq(~fsm.ongoing("Idle")),
+ led1.eq(trace_data.re),
+ led2.eq(fifo.readable),
+ led3.eq(0),
+
+ led4.eq(saw_ongoing),
+ led5.eq(saw_re),
+ led6.eq(saw_readable),
+ led7.eq(trace_data.storage[0]),
+ ]
+
+ # set up wishbone
+ self.comb += [
+ wb.we.eq(1),
+ wb.sel.eq(0xF),
+ wb.adr.eq(cur_address),
+ ]
+
+ fsm.act("Reset",
+ NextState("Idle"),
+ )
+ fsm.act("Idle",
+ If(fifo.readable,
+ NextValue(val, fifo.dout),
+ NextValue(cur_line_idx, 0),
+ NextValue(cur_nib_idx, 0),
+ NextValue(cur_address, base_address),
+ fifo.re.eq(1),
+ NextState("StartWriteLow"),
+ ),
+ )
+ fsm.act("StartWriteLow",
+ wb.cyc.eq(1),
+ wb.stb.eq(1),
+ wb.dat_w.eq(Cat(Replicate(cur_nib_decoded[7], 8), Replicate(cur_nib_decoded[6], 8), Replicate(cur_nib_decoded[5], 8), Replicate(cur_nib_decoded[4], 8))),
+ If(wb.ack,
+ NextValue(cur_address, cur_address + 1),
+ NextState("StartWriteHigh"),
+ ),
+ )
+ fsm.act("StartWriteHigh",
+ wb.cyc.eq(1),
+ wb.stb.eq(1),
+ wb.dat_w.eq(Cat(Replicate(cur_nib_decoded[3], 8), Replicate(cur_nib_decoded[2], 8), Replicate(cur_nib_decoded[1], 8), Replicate(cur_nib_decoded[0], 8))),
+ If(wb.ack,
+ If((cur_line_idx == 7) & (cur_nib_idx == 7),
+ #If(base_address == last_address,
+ # NextValue(base_address, default_address),
+ #).Else(
+ NextValue(base_address, base_address + address_increment),
+ #),
+ NextState("Idle"),
+ ).Elif(cur_nib_idx == 7,
+ NextValue(base_address, base_address + address_increment),
+ NextValue(cur_address, base_address + address_increment),
+ NextValue(cur_nib_idx, 0),
+ NextValue(cur_line_idx, cur_line_idx + 1),
+ NextState("StartWriteLow"),
+ ).Else(
+ NextValue(cur_address, cur_address + 1),
+ NextValue(cur_nib_idx, cur_nib_idx + 1),
+ NextState("StartWriteLow"),
+ )
+ ),
+ )
+
+ self.comb += [
+ Case((cur_line_idx), {
+ 0: cur_nib_decoded.eq(0),
+ 7: cur_nib_decoded.eq(0),
+ 1: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x18), 1:cur_nib_decoded.eq(0x10), 2:cur_nib_decoded.eq(0x18), 3:cur_nib_decoded.eq(0x38), 4:cur_nib_decoded.eq(0x20), 5:cur_nib_decoded.eq(0x3c), 6:cur_nib_decoded.eq(0x1c), 7:cur_nib_decoded.eq(0x3c), 8:cur_nib_decoded.eq(0x18), 9:cur_nib_decoded.eq(0x18), 10:cur_nib_decoded.eq(0x18), 11:cur_nib_decoded.eq(0x38), 12:cur_nib_decoded.eq(0x18), 13:cur_nib_decoded.eq(0x38), 14:cur_nib_decoded.eq(0x3c), 15:cur_nib_decoded.eq(0x3c),
+ }),
+ 2: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x24), 1:cur_nib_decoded.eq(0x30), 2:cur_nib_decoded.eq(0x24), 3:cur_nib_decoded.eq(0x04), 4:cur_nib_decoded.eq(0x28), 5:cur_nib_decoded.eq(0x20), 6:cur_nib_decoded.eq(0x20), 7:cur_nib_decoded.eq(0x04), 8:cur_nib_decoded.eq(0x24), 9:cur_nib_decoded.eq(0x24), 10:cur_nib_decoded.eq(0x24), 11:cur_nib_decoded.eq(0x24), 12:cur_nib_decoded.eq(0x24), 13:cur_nib_decoded.eq(0x24), 14:cur_nib_decoded.eq(0x20), 15:cur_nib_decoded.eq(0x20),
+ }),
+ 3: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x2c), 1:cur_nib_decoded.eq(0x10), 2:cur_nib_decoded.eq(0x04), 3:cur_nib_decoded.eq(0x18), 4:cur_nib_decoded.eq(0x28), 5:cur_nib_decoded.eq(0x38), 6:cur_nib_decoded.eq(0x38), 7:cur_nib_decoded.eq(0x08), 8:cur_nib_decoded.eq(0x18), 9:cur_nib_decoded.eq(0x24), 10:cur_nib_decoded.eq(0x24), 11:cur_nib_decoded.eq(0x38), 12:cur_nib_decoded.eq(0x20), 13:cur_nib_decoded.eq(0x24), 14:cur_nib_decoded.eq(0x38), 15:cur_nib_decoded.eq(0x38),
+ }),
+ 4: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x34), 1:cur_nib_decoded.eq(0x10), 2:cur_nib_decoded.eq(0x18), 3:cur_nib_decoded.eq(0x04), 4:cur_nib_decoded.eq(0x3c), 5:cur_nib_decoded.eq(0x04), 6:cur_nib_decoded.eq(0x24), 7:cur_nib_decoded.eq(0x10), 8:cur_nib_decoded.eq(0x24), 9:cur_nib_decoded.eq(0x1c), 10:cur_nib_decoded.eq(0x3c), 11:cur_nib_decoded.eq(0x24), 12:cur_nib_decoded.eq(0x20), 13:cur_nib_decoded.eq(0x24), 14:cur_nib_decoded.eq(0x20), 15:cur_nib_decoded.eq(0x20),
+ }),
+ 5: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x24), 1:cur_nib_decoded.eq(0x10), 2:cur_nib_decoded.eq(0x20), 3:cur_nib_decoded.eq(0x04), 4:cur_nib_decoded.eq(0x08), 5:cur_nib_decoded.eq(0x04), 6:cur_nib_decoded.eq(0x24), 7:cur_nib_decoded.eq(0x10), 8:cur_nib_decoded.eq(0x24), 9:cur_nib_decoded.eq(0x04), 10:cur_nib_decoded.eq(0x24), 11:cur_nib_decoded.eq(0x24), 12:cur_nib_decoded.eq(0x24), 13:cur_nib_decoded.eq(0x24), 14:cur_nib_decoded.eq(0x20), 15:cur_nib_decoded.eq(0x20),
+ }),
+ 6: Case(cur_nib, {
+ 0:cur_nib_decoded.eq(0x18), 1:cur_nib_decoded.eq(0x38), 2:cur_nib_decoded.eq(0x3c), 3:cur_nib_decoded.eq(0x38), 4:cur_nib_decoded.eq(0x08), 5:cur_nib_decoded.eq(0x38), 6:cur_nib_decoded.eq(0x18), 7:cur_nib_decoded.eq(0x10), 8:cur_nib_decoded.eq(0x18), 9:cur_nib_decoded.eq(0x38), 10:cur_nib_decoded.eq(0x24), 11:cur_nib_decoded.eq(0x38), 12:cur_nib_decoded.eq(0x18), 13:cur_nib_decoded.eq(0x38), 14:cur_nib_decoded.eq(0x3c), 15:cur_nib_decoded.eq(0x20),
+ }),
+ }
+ ),
+ ]
+