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), + }), + } + ), + ] +