feat: publish FreeRTOS C FC05 card
This commit is contained in:
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
FPGA Scripts
|
||||
============
|
||||
|
||||
A loose collection of scripts I've collected while playing with FPGAs at home. Simulation, synthesis, so forth. All of them are terrible, some of them work.
|
||||
|
||||
The most useful one is `listfiles` which provides a simple way of describing source-level dependencies and producing file lists for e.g. a synthesis tool.
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Based on Commander Keen compresssion algorithm (a variant of LZSS)
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
def get_symbol(data, window):
|
||||
it = iter(data)
|
||||
needle = bytearray([next(it)])
|
||||
try:
|
||||
while window.find(needle) >= 0:
|
||||
needle.append(next(it))
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
matchlen = len(needle) - 1
|
||||
if matchlen <= 1:
|
||||
return (1, (False, needle[0]))
|
||||
else:
|
||||
matchpos = len(window) - 1 - window.find(needle[:-1])
|
||||
return (matchlen, (True, matchpos, matchlen - 1))
|
||||
|
||||
def iter_symbols(data, windowsize):
|
||||
data = data[:]
|
||||
window = bytearray()
|
||||
|
||||
while True:
|
||||
consumed, symbol = get_symbol(data, window)
|
||||
yield symbol
|
||||
del window[:max(0, len(window) + consumed - windowsize)]
|
||||
window.extend(data[:consumed])
|
||||
del data[:consumed]
|
||||
|
||||
def bitmap(bits):
|
||||
bm = 0
|
||||
for i, bit in enumerate(bits):
|
||||
bm = bm | (bool(bit) << i)
|
||||
return bm
|
||||
|
||||
def iter_bytes(data, windowsize):
|
||||
syms = iter(iter_symbols(data, windowsize))
|
||||
holding = []
|
||||
eof = False
|
||||
while not eof:
|
||||
try:
|
||||
holding.append(next(syms))
|
||||
except StopIteration:
|
||||
eof = True
|
||||
if eof and len(holding) or len(holding) >= 8:
|
||||
yield bitmap(sym[0] for sym in holding)
|
||||
for sym in holding:
|
||||
yield from sym[1:]
|
||||
holding.clear()
|
||||
|
||||
def compress(data, windowsize):
|
||||
return bytearray(iter_bytes(data, windowsize))
|
||||
|
||||
def decompress(cdata, windowsize):
|
||||
out = bytearray()
|
||||
window = bytearray()
|
||||
symbolcount = 0
|
||||
it = iter(cdata)
|
||||
while True:
|
||||
if symbolcount == 0:
|
||||
try:
|
||||
bitmap = next(it)
|
||||
except StopIteration:
|
||||
break
|
||||
symbolcount = 7
|
||||
else:
|
||||
symbolcount -= 1
|
||||
if bitmap & 1:
|
||||
start = len(window) - 1 - next(it)
|
||||
count = next(it) + 1
|
||||
new = window[start:start + count]
|
||||
out.extend(new)
|
||||
window.extend(new)
|
||||
else:
|
||||
try:
|
||||
lit = next(it)
|
||||
except StopIteration:
|
||||
break
|
||||
window.append(lit)
|
||||
out.append(lit)
|
||||
del window[:max(0, len(window) - windowsize)]
|
||||
bitmap = bitmap >> 1
|
||||
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("input", help="Input file name")
|
||||
parser.add_argument("output", help="Output file name")
|
||||
parser.add_argument("-d", "--decompress", action="store_true", help="Decompress input (default is to compress)")
|
||||
parser.add_argument("-w", "--window", help="Compression window size")
|
||||
args = parser.parse_args()
|
||||
if args.input == "-":
|
||||
ifile = sys.stdin
|
||||
else:
|
||||
ifile = open(args.input, "rb")
|
||||
if args.output == "-":
|
||||
ofile = sys.stdout
|
||||
else:
|
||||
ofile = open(args.output, "wb")
|
||||
if args.window is None:
|
||||
args.window = 256
|
||||
|
||||
ibuf = bytearray(ifile.read())
|
||||
ifile.close()
|
||||
|
||||
if args.decompress:
|
||||
obuf = decompress(ibuf, args.window)
|
||||
else:
|
||||
obuf = compress(ibuf, args.window)
|
||||
|
||||
ofile.write(obuf)
|
||||
ofile.close()
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
# Turn KiCad netlist into a PCF file for icestorm
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import shlex
|
||||
from collections import OrderedDict
|
||||
|
||||
def sanitize_net_name(s):
|
||||
s = s.replace("~", "")
|
||||
s = re.sub(r"(\d+)$", r"[\1]", s)
|
||||
return s
|
||||
|
||||
def natural_key(s):
|
||||
return tuple(int(x) if x.isdigit() else x for x in re.split(r"(\d+)", s))
|
||||
|
||||
def extract_nets(fh, ref, filters):
|
||||
nets = []
|
||||
|
||||
name = None
|
||||
namevalid = False
|
||||
|
||||
for l in fh:
|
||||
m = re.match(r"^\s*\(net \([^\)]*\) \(name ([^\)]+)\)", l)
|
||||
if m:
|
||||
name = m.group(1).strip().strip("/").lower()
|
||||
namevalid = not any(name.startswith(s) for s in [
|
||||
"\"net", "+", "-", "gnd", "vcc", "vdd", "vss", *filters
|
||||
])
|
||||
elif namevalid:
|
||||
m = re.match(r"^\s*\(node \(ref ([^\)]+)\) \(pin ([^\)]+)\)", l)
|
||||
if m and m.group(1) == ref:
|
||||
nets.append((name, m.group(2)))
|
||||
return nets
|
||||
|
||||
def align_buses(nets):
|
||||
buses = OrderedDict()
|
||||
onets = []
|
||||
# First figure out what is/isn't a bus
|
||||
for net in nets:
|
||||
if "[" in net[0]:
|
||||
busname = net[0].split("[")[0]
|
||||
if busname not in buses:
|
||||
buses[busname] = []
|
||||
buses[busname].append(net)
|
||||
else:
|
||||
onets.append(net)
|
||||
# Then right-justify the buses
|
||||
for busname, bus in buses.items():
|
||||
indices = list(int(net[0].split('[')[1].strip(']')) for net in bus)
|
||||
lsb = min(indices)
|
||||
for index, net in zip(indices, bus):
|
||||
onets.append(("{}[{}]".format(busname, index - lsb), net[1]))
|
||||
return onets
|
||||
|
||||
|
||||
def write_pcf(fh, nets):
|
||||
fh.write("# Generated with extract_ref_nets\n\n")
|
||||
for name, loc in sorted(nets, key = lambda x: natural_key(x[0])):
|
||||
fh.write("set_io {:<20} {}\n".format(name, loc))
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("netfile", help="Path to KiCad .net file")
|
||||
parser.add_argument("ref", help="Component reference whose named nets will be extracted")
|
||||
parser.add_argument("--output", "-o", help="Output file name")
|
||||
parser.add_argument("--filters", "-f", help="-f \"abc xyz\": filter out nets beginning with abc or xyz")
|
||||
args = parser.parse_args()
|
||||
opath = "chip.pcf" if args.output is None else args.output
|
||||
filters = [] if args.filters is None else shlex.split(args.filters)
|
||||
with open(args.netfile) as ifile:
|
||||
nets = extract_nets(ifile, args.ref, filters)
|
||||
nets = map(lambda n: (sanitize_net_name(n[0]), n[1]), nets)
|
||||
nets = align_buses(nets)
|
||||
with open(opath, "w") as ofile:
|
||||
write_pcf(ofile, nets)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
# Must define:
|
||||
# DOTF: .f file containing root of file list
|
||||
# TOP: name of top-level module
|
||||
|
||||
SRCS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flat $(DOTF))
|
||||
INCDIRS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flati $(DOTF))
|
||||
|
||||
YOSYS=yosys
|
||||
YOSYS_SMTBMC=$(YOSYS)-smtbmc
|
||||
|
||||
DEPTH?=20
|
||||
COVER_APPEND?=3
|
||||
YOSYS_SMT_SOLVER?=z3
|
||||
|
||||
DEFINES?=
|
||||
|
||||
PREP_CMD =read_verilog -formal
|
||||
PREP_CMD+=$(addprefix -I,$(INCDIRS))
|
||||
PREP_CMD+=$(addprefix -D,$(DEFINES) )
|
||||
PREP_CMD+= $(SRCS);
|
||||
PREP_CMD+=prep -top $(TOP); async2sync; dffunmap; write_smt2 -wires $(TOP).smt2
|
||||
|
||||
BMC_ARGS=-s $(YOSYS_SMT_SOLVER) --dump-vcd $(TOP).vcd -t $(DEPTH)
|
||||
IND_ARGS=-i $(BMC_ARGS)
|
||||
COV_ARGS = -c $(BMC_ARGS) --append $(COVER_APPEND)
|
||||
.PHONY: prove prep bmc induct clean
|
||||
|
||||
prove: bmc induct
|
||||
|
||||
prep:
|
||||
$(YOSYS) -p "$(PREP_CMD)" > prep.log
|
||||
|
||||
bmc: prep
|
||||
$(YOSYS_SMTBMC) $(BMC_ARGS) $(TOP).smt2 | tee bmc.log
|
||||
|
||||
induct: prep
|
||||
$(YOSYS_SMTBMC) $(IND_ARGS) $(TOP).smt2 | tee induct.log
|
||||
|
||||
cover: prep
|
||||
$(YOSYS_SMTBMC) $(COV_ARGS) $(TOP).smt2 | tee cover.log
|
||||
|
||||
clean::
|
||||
rm -f $(TOP).vcd $(TOP).smt2 srcs.mk prep.log bmc.log induct.log cover.log
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
# Navigate up directories until we find a file called "Default.wcfg"
|
||||
# Stop if we get to root
|
||||
|
||||
set cfgdir [file normalize .];
|
||||
while {"$cfgdir" != "/"} {
|
||||
if [file exists $cfgdir/Default.wcfg] {
|
||||
wcfg open $cfgdir/Default.wcfg
|
||||
break
|
||||
}
|
||||
set cfgdir [file normalize $cfgdir/..]
|
||||
}
|
||||
|
||||
run 100us;
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import shlex
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import re
|
||||
|
||||
help_str = """
|
||||
|
||||
Tool for building file lists, into formats required by various tools.
|
||||
".f" files contain four types of command:
|
||||
|
||||
file (filename)
|
||||
adds a file to the list
|
||||
include (dir)
|
||||
add a directory to include path, if output format supports this
|
||||
wildcard (.extension) (dir)
|
||||
add all files with a given extension in a given directory
|
||||
list (filename)
|
||||
recurse on another filelist file
|
||||
|
||||
The idea is that each component in a project has a
|
||||
.f file which lists all of the Verilog (e.g.) files
|
||||
for that component. Higher-level .f files will hierarchically
|
||||
include the lower-level ones.
|
||||
|
||||
In this way, you can build large flat file lists to pass into the
|
||||
various tools, but never have to *write* large flat file lists.
|
||||
|
||||
It also makes it easier to specify parts of your design hierarchy
|
||||
for a given tool. For example, if you just want to synthesise your
|
||||
CPU, you can run "listfiles cpu.f -f flat"
|
||||
"""
|
||||
|
||||
def wildcard(dir, extension):
|
||||
prev_dir = os.getcwd()
|
||||
os.chdir(dir)
|
||||
files = [os.path.abspath(f) for f in os.listdir() if os.path.splitext(f)[-1] == extension]
|
||||
os.chdir(prev_dir)
|
||||
return files
|
||||
|
||||
def read_filelist(fname):
|
||||
files = []
|
||||
includes = []
|
||||
f = open(fname)
|
||||
prev_dir = os.getcwd()
|
||||
os.chdir(os.path.dirname(os.path.abspath(fname)))
|
||||
for l in f.readlines():
|
||||
l = l.split("#")[0].strip()
|
||||
if l == "":
|
||||
continue
|
||||
words = shlex.split(l)
|
||||
if words[0] == "file":
|
||||
assert(len(words) == 2)
|
||||
files.append(os.path.abspath(os.path.expandvars(words[1])))
|
||||
elif words[0] == "include":
|
||||
assert(len(words) == 2)
|
||||
includes.append(os.path.abspath(os.path.expandvars(words[1])))
|
||||
elif words[0] == "wildcard":
|
||||
assert(len(words) == 3)
|
||||
files.extend(wildcard(os.path.expandvars(words[2]), words[1]))
|
||||
elif words[0] == "list":
|
||||
assert(len(words) == 2)
|
||||
newfiles, newincludes = read_filelist(os.path.expandvars(words[1]))
|
||||
files.extend(newfiles)
|
||||
includes.extend(newincludes)
|
||||
else:
|
||||
raise Exception("In filelist {}: Invalid command \"{}\"".format(fname, words[0]))
|
||||
os.chdir(prev_dir)
|
||||
return (files, includes)
|
||||
|
||||
formats = {
|
||||
"isim": lambda f, i: "verilog work {} {}\n".format(" ".join(f), " ".join("-i " + inc for inc in i)),
|
||||
"flat": lambda f, i: " ".join(f) + "\n",
|
||||
"flati": lambda f, i: " ".join(i) + "\n",
|
||||
"make": lambda f, i: "SRCS={}\nINCDIRS={}\n".format(" ".join(f), " ".join(i))
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.epilog = help_str
|
||||
parser.add_argument("src", help="File list source file")
|
||||
parser.add_argument("--format", "-f", help="Format to generate output in. Allowed: isim, make, flat (default)")
|
||||
parser.add_argument("--relative", "-r", action="store_true", help="Use relative paths in output file")
|
||||
parser.add_argument("--relativeto", help="Use relative paths, relative to some specified path")
|
||||
parser.add_argument("--output", "-o", help="Output file name")
|
||||
args = parser.parse_args()
|
||||
if args.format is None:
|
||||
args.format = "flat"
|
||||
files, includes = read_filelist(args.src)
|
||||
# Uniquify whilst preserving order
|
||||
func = lambda l: list(dict.fromkeys(l))
|
||||
if args.relative:
|
||||
func = lambda l, func=func: [os.path.relpath(f) for f in func(l)]
|
||||
elif args.relativeto:
|
||||
func = lambda l, func=func: [os.path.relpath(f, args.relativeto) for f in func(l)]
|
||||
|
||||
files, includes = map(func, (files, includes))
|
||||
if args.format not in formats:
|
||||
sys.exit("Unknown format: " + args.format)
|
||||
ofile = sys.stdout if args.output is None else open(args.output, "w")
|
||||
ofile.write(formats[args.format](files, includes))
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import struct
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("ifile")
|
||||
parser.add_argument("ofile")
|
||||
args = parser.parse_args()
|
||||
data = open(args.ifile, "rb").read()
|
||||
with open(args.ofile, "wb") as ofile:
|
||||
ofile.write("RISCBoy".encode() + bytes(1))
|
||||
ofile.write(struct.pack("<L", len(data)))
|
||||
ofile.write(data)
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Prepend a length and CRC32 checksum to a binary file so that it can be sent
|
||||
# to the DOOMSoC UART bootloader. Both values are unsigned 32-bit
|
||||
# little-endian. CRC32 is calculated with standard parameters (input and
|
||||
# output reflected, seed all-ones, final XOR all-ones).
|
||||
|
||||
import argparse
|
||||
import binascii
|
||||
import struct
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("ifile")
|
||||
parser.add_argument("ofile")
|
||||
args = parser.parse_args()
|
||||
|
||||
data = open(args.ifile, "rb").read()
|
||||
|
||||
with open(args.ofile, "wb") as ofile:
|
||||
ofile.write(struct.pack("<L", len(data)))
|
||||
ofile.write(struct.pack("<L", binascii.crc32(data)))
|
||||
ofile.write(data)
|
||||
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from PIL import Image
|
||||
import argparse
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
FORMATS = ["argb1555", "rgab5515", "bgar5515", "rgb565", "argb1232", "ragb2132", "rgb332", "r2", "r1", "p8", "p4", "p2", "p1"]
|
||||
|
||||
def bytes_from_bitstream_le(bitstream):
|
||||
accum = 0
|
||||
accum_size = 0
|
||||
while True:
|
||||
while accum_size < 8:
|
||||
try:
|
||||
nbits, newdata = next(bitstream)
|
||||
except StopIteration:
|
||||
return
|
||||
accum = accum | (newdata << accum_size)
|
||||
accum_size += nbits
|
||||
while accum_size >= 8:
|
||||
yield accum & 0xff
|
||||
accum = accum >> 8
|
||||
accum_size -= 8
|
||||
|
||||
class BinHeader:
|
||||
def __init__(self, filename, arrayname=None):
|
||||
if arrayname is None:
|
||||
arrayname = filename.split(".")[0]
|
||||
self.f = open(filename, "w")
|
||||
self.out_count = 0
|
||||
self.f.write(
|
||||
"#ifndef _IMG_ASSET_SECTION\n" \
|
||||
"#define _IMG_ASSET_SECTION \".data\"\n" \
|
||||
"#endif\n\n" \
|
||||
f"static const char __attribute__((aligned(16), section(_IMG_ASSET_SECTION \".{arrayname}\"))) {arrayname}[] = {{\n\t"
|
||||
)
|
||||
|
||||
def write(self, bs):
|
||||
for b in bs:
|
||||
self.f.write("0x{:02x}".format(b) + (",\n\t" if self.out_count % 16 == 15 else ", "))
|
||||
self.out_count += 1
|
||||
|
||||
def close(self):
|
||||
self.f.write("\n};\n")
|
||||
self.f.close()
|
||||
|
||||
# Fixed dither -- note every number 0...15 appears once (thanks Graham)
|
||||
dither_pattern_4x4 = [
|
||||
[0 , 8 , 2 , 10],
|
||||
[12 , 4 , 14 , 6 ],
|
||||
[3 , 11 , 1 , 9 ],
|
||||
[15 , 7 , 13 , 5 ],
|
||||
]
|
||||
|
||||
def format_channel(data, msb, lsb, dither=False, dithercoord=None):
|
||||
# Assume data to be 8 bits
|
||||
out_width = msb - lsb + 1
|
||||
assert(out_width <= 8)
|
||||
if dither:
|
||||
ditherval = dither_pattern_4x4[dithercoord[1] % 4][dithercoord[0] % 4]
|
||||
shamt = (8 - out_width) - 4
|
||||
if shamt >= 0:
|
||||
data += ditherval << shamt
|
||||
else:
|
||||
data += ditherval >> -shamt
|
||||
data = min(data, 0xff)
|
||||
return (data >> (8 - out_width)) << lsb
|
||||
|
||||
def format_rgb_pixel(pix, fmt, dither=False, dithercoord=None):
|
||||
accum = 0
|
||||
for p, f in zip(pix, fmt):
|
||||
accum |= format_channel(p, f[0], f[1], dither, dithercoord)
|
||||
if len(pix) == len(fmt) - 1:
|
||||
accum |= format_channel(0xff, fmt[-1][0], fmt[-1][1])
|
||||
return accum
|
||||
|
||||
# TODO would be kind of nice to generate these based on format string but I don't need that yet
|
||||
rgb_formats = {
|
||||
"argb1555": (16, ((14, 10), (9, 5), (4, 0), (15, 15))),
|
||||
"rgab5515": (16, ((15, 11), (10, 6), (4, 0), (5, 5))),
|
||||
"bgar5515": (16, ((4, 0), (10, 6), (15, 11), (5, 5))),
|
||||
"rgb565" : (16, ((15, 11), (10, 5), (4, 0))),
|
||||
"argb1232": (8, ((6, 5), (4, 2), (1, 0), (7, 7))),
|
||||
"ragb2132": (8, ((7, 6), (4, 2), (1, 0), (5, 5))),
|
||||
"rgb332" : (8, ((7, 5), (4, 2), (1, 0))),
|
||||
"r2" : (2, ((1, 0), (-1, 0), (-1, 0))),
|
||||
"r1" : (1, ((0, 0), (-1, 0), (-1, 0))),
|
||||
}
|
||||
|
||||
def format_pixel(format, src_has_transparency, pixel, dither=False, dithercoord=None):
|
||||
assert(format in FORMATS)
|
||||
if format in rgb_formats:
|
||||
return (rgb_formats[format][0], format_rgb_pixel(pixel, rgb_formats[format][1], dither, dithercoord))
|
||||
elif format in ["p8", "p4", "p2", "p1"]:
|
||||
size = int(format[1:])
|
||||
return (size, (pixel + src_has_transparency) & ((1 << size) - 1))
|
||||
else:
|
||||
raise Exception()
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("input", help="Input file name")
|
||||
parser.add_argument("output", help="Output file name")
|
||||
parser.add_argument("--tilesize", "-t", help="Tile size (pixels), default 8",
|
||||
default="8", choices=[str(2 ** i) for i in range(3, 11)])
|
||||
parser.add_argument("--single", "-s", action="store_true",
|
||||
help="The input consists of a single image of arbitrary width/height, rather than a tileset")
|
||||
parser.add_argument("--format", "-f", help="Output pixel format, default argb1555",
|
||||
default="argb1555", choices=FORMATS)
|
||||
parser.add_argument("--dither", "-d", action="store_true",
|
||||
help="Apply a simple fixed dither pattern when packing RGB files")
|
||||
parser.add_argument("--metadata", "-m", action="store_true",
|
||||
help="Write out opacity metadata at end of file for faster alpha blit (must be used with --single)")
|
||||
args = parser.parse_args()
|
||||
img = Image.open(args.input)
|
||||
if args.single:
|
||||
tsize_x = img.width
|
||||
tsize_y = img.height
|
||||
else:
|
||||
tsize_x = int(args.tilesize)
|
||||
tsize_y = tsize_x
|
||||
if args.metadata and not args.single:
|
||||
sys.exit("--metadata must be used with --single")
|
||||
|
||||
format_is_paletted = args.format.startswith("p")
|
||||
image_is_transparent = img.mode == "RGBA" and img.getextrema()[3][0] < 255
|
||||
if args.metadata and not image_is_transparent:
|
||||
sys.exit("Can't write opacity metadata for a non-transparent image")
|
||||
|
||||
friendly_out_name = os.path.basename(args.input).split(".")[0]
|
||||
|
||||
if format_is_paletted:
|
||||
ncolours_max = 1 << int(args.format[1:])
|
||||
ncolours_actual = min(ncolours_max, len(img.getcolors()))
|
||||
pimg = img.quantize(ncolours_max)
|
||||
palette = pimg.getpalette()
|
||||
# TODO haven't found a sane way to make PIL map transparency to palette
|
||||
if image_is_transparent:
|
||||
for x in range(img.width):
|
||||
for y in range(img.height):
|
||||
if not (img.getpixel((x, y))[3] & 0x80):
|
||||
pimg.putpixel((x, y), 255)
|
||||
|
||||
if args.output.endswith(".h"):
|
||||
pfile = BinHeader(args.output + ".pal", arrayname=friendly_out_name + "_pal")
|
||||
else:
|
||||
pfile = open(args.output + ".pal", "wb")
|
||||
if image_is_transparent:
|
||||
pfile.write(bytes(2))
|
||||
pfile.write(bytes(bytes_from_bitstream_le(
|
||||
format_pixel("argb1555", False, palette[i:i+3]) for i in range(0, ncolours_actual * 3, 3)
|
||||
)))
|
||||
if ncolours_actual < ncolours_max:
|
||||
pfile.write(bytes(2 * (ncolours_max - ncolours_actual)))
|
||||
pfile.close()
|
||||
|
||||
img = pimg
|
||||
|
||||
if args.output.endswith(".h"):
|
||||
ofile = BinHeader(args.output, arrayname=friendly_out_name)
|
||||
else:
|
||||
ofile = open(args.output, "wb")
|
||||
|
||||
for y in range(0, img.height - (tsize_y - 1), tsize_y):
|
||||
for x in range(0, img.width - (tsize_x - 1), tsize_x):
|
||||
tile = img.crop((x, y, x + tsize_x, y + tsize_y))
|
||||
ofile.write(bytes(bytes_from_bitstream_le(
|
||||
format_pixel(args.format, image_is_transparent, tile.getpixel((i, j)), args.dither, dithercoord=(i, j)) for j in range(tsize_y) for i in range(tsize_x)
|
||||
)))
|
||||
if args.metadata:
|
||||
assert(tsize_x * tsize_y % 4 == 0)
|
||||
for y in range(0, tsize_y):
|
||||
opacity = list(img.getpixel((x, y))[3] >= 128 for x in range(tsize_x))
|
||||
try:
|
||||
first_transparent = opacity.index(True)
|
||||
last_transparent = tsize_x - 1 - list(reversed(opacity)).index(True)
|
||||
continuous_span = all(opacity[first_transparent:last_transparent + 1])
|
||||
ofile.write(struct.pack("<L", (last_transparent & 0xffff) | ((first_transparent & 0x7fff) << 16) | ((continuous_span & 1) << 31)))
|
||||
except ValueError:
|
||||
# Completely transparent row
|
||||
ofile.write(struct.pack("<L", 0))
|
||||
|
||||
ofile.close()
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Raspberry Pi iceprog (piceprog)
|
||||
# Tool for programming iCE40 FPGAs with Pi GPIOs
|
||||
|
||||
import sys
|
||||
import RPi.GPIO as gpio
|
||||
from time import sleep
|
||||
from math import ceil
|
||||
|
||||
fpga_sclk = 19
|
||||
fpga_sdi = 26
|
||||
fpga_ss = 13
|
||||
fpga_done = 6
|
||||
fpga_rst = 5
|
||||
|
||||
gpio.setmode(gpio.BCM)
|
||||
gpio.setwarnings(False)
|
||||
|
||||
for pin in [fpga_sclk, fpga_sdi, fpga_ss, fpga_rst]:
|
||||
gpio.setup(pin, gpio.OUT)
|
||||
|
||||
gpio.setup(fpga_done, gpio.IN)
|
||||
|
||||
def prog(fname):
|
||||
bitstream = list(open(fname, "rb").read())
|
||||
print("{} bytes.".format(len(bitstream)))
|
||||
bits = []
|
||||
for byte in bitstream:
|
||||
for i in range(8):
|
||||
bits.append(byte >> 7)
|
||||
byte = (byte << 1) & 0xff
|
||||
bits.extend(49 * [0]) # at least 49 dummy cycles required at end.
|
||||
print("Starting")
|
||||
gpio.output(fpga_rst, 0)
|
||||
gpio.output(fpga_ss, 0)
|
||||
gpio.output(fpga_sclk, 1) # CPOL = 1 (clock idle high)
|
||||
gpio.output(fpga_sdi, 0)
|
||||
sleep(0.001)
|
||||
gpio.output(fpga_rst, 1)
|
||||
sleep(0.001)
|
||||
gpio.output(fpga_ss, 1)
|
||||
for i in range(8):
|
||||
gpio.output(fpga_sclk, 0)
|
||||
gpio.output(fpga_sclk, 1)
|
||||
# CPHA = 1 (data captured on trailing edge of clock pulse)
|
||||
gpio.output(fpga_ss, 0)
|
||||
for bit in bits:
|
||||
gpio.output(fpga_sdi, bit)
|
||||
gpio.output(fpga_sclk, 0)
|
||||
gpio.output(fpga_sclk, 1)
|
||||
if gpio.input(fpga_done):
|
||||
print("CDONE high, yay!")
|
||||
else:
|
||||
print("CDONE not high, something may have gone wrong")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
exit("Usage: piceprog (file.bin)")
|
||||
prog(sys.argv[1])
|
||||
+526
@@ -0,0 +1,526 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Regblock generation script
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
import yaml
|
||||
from collections import OrderedDict
|
||||
from math import log2, ceil
|
||||
|
||||
# Regblock data structures and parser/loader
|
||||
|
||||
class RegBlock:
|
||||
bus_types = ["apb"]
|
||||
field_properties = ["name", "b", "access", "info", "rst", "concat"]
|
||||
def __init__(self, name, w_data, w_addr, bus, info):
|
||||
self.name = name
|
||||
self.w_data = w_data
|
||||
self.w_addr = w_addr
|
||||
if bus not in self.bus_types:
|
||||
raise Exception("Unknown bus type: {}".format(bus))
|
||||
self.bus = bus
|
||||
self.info = info
|
||||
self.regs = []
|
||||
|
||||
def add(self, reg):
|
||||
self.regs.append(reg)
|
||||
|
||||
@staticmethod
|
||||
def load(file):
|
||||
y = yaml.load(file.read(), Loader=yaml.FullLoader)
|
||||
if "bus" not in y:
|
||||
sys.exit("Must specify bus type with \"bus: x\"")
|
||||
if "data" not in y:
|
||||
sys.exit("Must specify data width with \"data: x\"")
|
||||
if "addr" not in y:
|
||||
sys.exit("Must specify address width with \"addr: x\"")
|
||||
if "name" not in y:
|
||||
sys.exit("Must specify regblock name with \"name: x\"")
|
||||
rb = RegBlock(y["name"], y["data"], y["addr"], y["bus"], y["info"] if "info" in y else None)
|
||||
param_dict = {"W_DATA": y["data"]}
|
||||
if ("params" in y):
|
||||
param_dict = {**param_dict, **y["params"]}
|
||||
param_resolve = lambda x: x if type(x) is int else int(eval(x, {**param_dict}))
|
||||
# Expand any generate blocks (one level only)
|
||||
reglist = []
|
||||
for i, rspec in enumerate(y["regs"]):
|
||||
if "generate" not in rspec:
|
||||
reglist.append(rspec)
|
||||
continue
|
||||
yaml_lines = []
|
||||
emit = lambda x: yaml_lines.append(str(x))
|
||||
exec(rspec["generate"], {"_": emit, **param_dict}, dict())
|
||||
newregs = yaml.load("\n".join(yaml_lines), Loader=yaml.FullLoader)
|
||||
if newregs is not None:
|
||||
reglist.extend(newregs)
|
||||
# Then process the expanded reglist
|
||||
for rspec in reglist:
|
||||
reg = Register(rspec["name"], rb.w_data, rspec["info"] if "info" in rspec else None, rspec["wstb"] if "wstb" in rspec else None)
|
||||
rb.add(reg)
|
||||
for fspec in rspec["bits"]:
|
||||
for key in fspec:
|
||||
if key not in RegBlock.field_properties:
|
||||
raise Exception("'{}' is not a valid property for a field.".format(key))
|
||||
bitrange = fspec["b"]
|
||||
if type(bitrange) is int:
|
||||
bitrange = [bitrange, bitrange]
|
||||
reg.add(Field(
|
||||
fspec["name"] if "name" in fspec else "",
|
||||
param_resolve(bitrange[0]),
|
||||
param_resolve(bitrange[1]),
|
||||
fspec["access"],
|
||||
fspec["rst"] if "rst" in fspec else 0,
|
||||
fspec["info"] if "info" in fspec else None,
|
||||
fspec["concat"] if "concat" in fspec else None
|
||||
))
|
||||
return rb
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.pre(self)
|
||||
for reg in self.regs:
|
||||
reg.accept(visitor)
|
||||
visitor.post(self)
|
||||
|
||||
class Register:
|
||||
def __init__(self, name, width, info, wstrobe=None):
|
||||
assert(width > 0)
|
||||
self.name = name
|
||||
self.width = width
|
||||
self.info = info
|
||||
self.occupancy = [None] * width
|
||||
self.fields = OrderedDict()
|
||||
self.wstrobe = wstrobe
|
||||
|
||||
def add(self, field):
|
||||
if field.lsb >= self.width or field.msb >= self.width or field.lsb < 0 or field.msb < 0:
|
||||
raise Exception("Field {} extends outside of register {}".format(field.name, self.name))
|
||||
if field.name in self.fields:
|
||||
raise Exception("{} already has a field called \"{}\"".format(self.name, field.name))
|
||||
for i in range(field.lsb, field.msb + 1):
|
||||
if self.occupancy[i] is not None:
|
||||
raise Exception("Field {} overlaps {} in register {}".format(field.name, self.occupancy[i], self.name))
|
||||
self.occupancy[i] = field.name
|
||||
self.fields[field.name] = field
|
||||
field.parent = self
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.pre(self)
|
||||
for field in self.fields.values():
|
||||
field.accept(visitor)
|
||||
visitor.post(self)
|
||||
|
||||
|
||||
class Field:
|
||||
access_types = ["ro", "rov", "wo", "rw", "rf", "wf", "rwf", "sc", "w1c"]
|
||||
def __init__(self, name, msb, lsb, access, resetval=0, info="", concat=None, parent=None):
|
||||
if lsb > msb:
|
||||
raise Exception("Field width must be >= 0 in field {}".format(name))
|
||||
if access not in self.access_types:
|
||||
raise Exception("Unknown access type: {}. Recognised types: {}".format(access, ", ".join(self.access_types)))
|
||||
if access in ["sc", "w1c"] and msb != lsb:
|
||||
raise Exception("Field width must be 1 for access type '{}'".format(access))
|
||||
self.name = name
|
||||
self.msb = msb
|
||||
self.lsb = lsb
|
||||
self.access = access
|
||||
self.resetval = resetval
|
||||
self.info = info
|
||||
self.concat = concat
|
||||
self.parent = parent
|
||||
|
||||
@property
|
||||
def width_decl(self):
|
||||
if self.msb == self.lsb:
|
||||
return ""
|
||||
else:
|
||||
return "[{}:0]".format(self.msb - self.lsb)
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self.msb - self.lsb + 1
|
||||
|
||||
@property
|
||||
def fullname(self):
|
||||
if self.name == "":
|
||||
return self.parent.name
|
||||
else:
|
||||
return "{}_{}".format(self.parent.name, self.name)
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.pre(self)
|
||||
|
||||
# Base class for various useful lumps of functionality
|
||||
# which are applied as traversals on a regblock description tree
|
||||
class RegBlockVisitor:
|
||||
def pre(self, x):
|
||||
if type(x) is RegBlock:
|
||||
self.pre_regblock(x)
|
||||
elif type(x) is Register:
|
||||
self.pre_register(x)
|
||||
elif type(x) is Field:
|
||||
self.pre_field(x)
|
||||
else:
|
||||
raise TypeError()
|
||||
|
||||
def post(self, x):
|
||||
if type(x) is RegBlock:
|
||||
self.post_regblock(x)
|
||||
elif type(x) is Register:
|
||||
self.post_register(x)
|
||||
elif type(x) is Field:
|
||||
self.post_field(x)
|
||||
else:
|
||||
raise TypeError()
|
||||
|
||||
def pre_regblock(self, rb):
|
||||
raise NotImplementedError()
|
||||
|
||||
def pre_register(self, r):
|
||||
raise NotImplementedError()
|
||||
|
||||
def pre_field(self, f):
|
||||
raise NotImplementedError()
|
||||
|
||||
def post_regblock(self, rb):
|
||||
raise NotImplementedError()
|
||||
|
||||
def post_register(self, r):
|
||||
raise NotImplementedError()
|
||||
|
||||
def post_field(self, f):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
# Verilog generation
|
||||
|
||||
def width2str(w):
|
||||
return "[{}:0]".format(w - 1)
|
||||
|
||||
def c_block_comment(lines, width=80, align="<"):
|
||||
blines = []
|
||||
blines.append("/" + (width - 1) * "*")
|
||||
line_fmt = "* {:" + align + str(width - 4) + "} *"
|
||||
for l in lines:
|
||||
blines.append(line_fmt.format(l.strip()))
|
||||
blines.append((width - 1) * "*" + "/")
|
||||
return blines
|
||||
|
||||
def c_line_comment(line):
|
||||
if not hasattr(c_line_comment, "wrap"):
|
||||
c_line_comment.wrap = textwrap.TextWrapper(width=80, initial_indent="// ", subsequent_indent="// ")
|
||||
return "\n".join(c_line_comment.wrap.wrap(line.strip()))
|
||||
|
||||
class Verilog:
|
||||
def __init__(self):
|
||||
self.header = []
|
||||
self.ports = []
|
||||
self.decls = []
|
||||
self.logic_comb = []
|
||||
self.logic_rst = []
|
||||
self.logic_clk = []
|
||||
|
||||
def __add__(self, other):
|
||||
new = Verilog()
|
||||
new.header.extend(self.header)
|
||||
for n, s, o in zip(
|
||||
[new.ports, new.decls, new.logic_comb, new.logic_rst, new.logic_clk],
|
||||
[self.ports, self.decls, self.logic_comb, self.logic_rst, self.logic_clk],
|
||||
[other.ports, other.decls, other.logic_comb, other.logic_rst, other.logic_clk]):
|
||||
n.extend(s)
|
||||
n.extend(o)
|
||||
return new
|
||||
|
||||
def __str__(self):
|
||||
strs = []
|
||||
strs.extend(self.header)
|
||||
strs.extend("\t" + s + ("" if s == "" or s.startswith("//") else ",") for s in self.ports[:-1])
|
||||
if len(self.ports) > 0:
|
||||
strs.append("\t" + self.ports[-1])
|
||||
strs.append(");")
|
||||
strs.append("")
|
||||
strs.extend(self.decls);
|
||||
strs.append("")
|
||||
strs.append("always @ (*) begin")
|
||||
strs.extend("\t" + s for s in self.logic_comb)
|
||||
strs.append("end")
|
||||
strs.append("")
|
||||
strs.append("always @ (posedge clk or negedge rst_n) begin")
|
||||
strs.append("\tif (!rst_n) begin")
|
||||
strs.extend("\t\t" + s for s in self.logic_rst)
|
||||
strs.append("\tend else begin")
|
||||
strs.extend("\t\t" + s for s in self.logic_clk)
|
||||
strs.append("\tend")
|
||||
strs.append("end")
|
||||
strs.append("")
|
||||
strs.append("endmodule\n")
|
||||
return "\n".join(strs)
|
||||
|
||||
|
||||
class VerilogWriter(RegBlockVisitor):
|
||||
def __init__(self):
|
||||
self.v = Verilog()
|
||||
self.regname = None
|
||||
self.concat = OrderedDict()
|
||||
self.wstrobe = OrderedDict()
|
||||
|
||||
def pre_regblock(self, rb):
|
||||
v = self.v
|
||||
v.header.extend(c_block_comment(["AUTOGENERATED BY REGBLOCK", "Do not edit manually.",
|
||||
"Edit the source file (or regblock utility) and regenerate."], align = "^"))
|
||||
v.header.append("")
|
||||
v.header.append(c_line_comment("{:<20} : {}".format("Block name", rb.name)))
|
||||
v.header.append(c_line_comment("{:<20} : {}".format("Bus type", rb.bus)))
|
||||
v.header.append(c_line_comment("{:<20} : {}".format("Bus data width", rb.w_data)))
|
||||
v.header.append(c_line_comment("{:<20} : {}".format("Bus address width", rb.w_addr)))
|
||||
v.header.append("")
|
||||
if rb.info is not None:
|
||||
v.header.append(c_line_comment(rb.info))
|
||||
v.header.append("")
|
||||
v.header.append("module {}_regs (".format(rb.name))
|
||||
v.ports.extend(["input wire clk", "input wire rst_n",])
|
||||
addr_mask = (1 << 2 + ceil(log2(len(rb.regs)))) - 1
|
||||
addr_mask = addr_mask & (-1 << ceil(log2(rb.w_data / 8)))
|
||||
if rb.bus == "apb":
|
||||
v.ports.append("")
|
||||
v.ports.append("// APB Port")
|
||||
v.ports.append("input wire apbs_psel")
|
||||
v.ports.append("input wire apbs_penable")
|
||||
v.ports.append("input wire apbs_pwrite")
|
||||
v.ports.append("input wire {} apbs_paddr".format(width2str(rb.w_addr)))
|
||||
v.ports.append("input wire {} apbs_pwdata".format(width2str(rb.w_data)))
|
||||
v.ports.append("output wire {} apbs_prdata".format(width2str(rb.w_data)))
|
||||
v.ports.append("output wire apbs_pready")
|
||||
v.ports.append("output wire apbs_pslverr")
|
||||
v.decls.append("// APB adapter")
|
||||
v.decls.append("wire {} wdata = apbs_pwdata;".format(width2str(rb.w_data)))
|
||||
v.decls.append("reg {} rdata;".format(width2str(rb.w_data)))
|
||||
v.decls.append("wire wen = apbs_psel && apbs_penable && apbs_pwrite;")
|
||||
v.decls.append("wire ren = apbs_psel && apbs_penable && !apbs_pwrite;")
|
||||
v.decls.append("wire {} addr = apbs_paddr & {}'h{:x};".format(width2str(rb.w_addr), rb.w_addr, addr_mask))
|
||||
v.decls.append("assign apbs_prdata = rdata;")
|
||||
v.decls.append("assign apbs_pready = 1'b1;")
|
||||
v.decls.append("assign apbs_pslverr = 1'b0;")
|
||||
v.decls.append("")
|
||||
v.ports.append("")
|
||||
v.ports.append("// Register interfaces")
|
||||
for i, reg in enumerate(rb.regs):
|
||||
v.decls.append("localparam ADDR_{} = {};".format(reg.name.upper(), i * 4))
|
||||
v.decls.append("")
|
||||
for reg in rb.regs:
|
||||
v.decls.append("wire __{}_wen = wen && addr == ADDR_{};".format(reg.name, reg.name.upper()))
|
||||
v.decls.append("wire __{}_ren = ren && addr == ADDR_{};".format(reg.name, reg.name.upper()))
|
||||
v.logic_comb.append("case (addr)")
|
||||
for reg in rb.regs:
|
||||
v.logic_comb.append("\tADDR_{}: rdata = __{}_rdata;".format(reg.name.upper(), reg.name))
|
||||
v.logic_comb.append("\tdefault: rdata = {}'h0;".format(rb.w_data))
|
||||
v.logic_comb.append("endcase")
|
||||
|
||||
def post_regblock(self, rb):
|
||||
for name, conns in self.concat.items():
|
||||
concat_name = "concat_{}_o".format(name)
|
||||
width = sum(f[0] for f in conns)
|
||||
self.v.ports.append("output wire [{}:0] {}".format(width - 1, concat_name))
|
||||
self.v.decls.append("assign {} = {{{}}};".format(concat_name, ", ".join(f[1] for f in reversed(conns))))
|
||||
max_strobe_index = OrderedDict()
|
||||
for name, conns in self.wstrobe.items():
|
||||
self.v.logic_rst.append("wstrobe_{} <= 1'b0;".format(name))
|
||||
self.v.logic_clk.append("wstrobe_{} <= {};".format(name, " || ".join(
|
||||
"__{}_wen".format(conn) for conn in conns)))
|
||||
if name.endswith("]"):
|
||||
shortname = name.split("[")[0]
|
||||
idx = int(name.split("[")[-1][:-1])
|
||||
if shortname in max_strobe_index:
|
||||
max_strobe_index[shortname] = max(max_strobe_index[shortname], idx)
|
||||
else:
|
||||
max_strobe_index[shortname] = idx
|
||||
else:
|
||||
self.v.ports.append("output reg wstrobe_{}".format(name))
|
||||
for name, max_idx in max_strobe_index.items():
|
||||
self.v.ports.append("output reg [{}:0] wstrobe_{}".format(max_idx, name))
|
||||
|
||||
def pre_register(self, reg):
|
||||
v = self.v
|
||||
self.regname = reg.name
|
||||
v.decls.append("")
|
||||
rdata_conns = []
|
||||
empty_count = 0
|
||||
last_occupant = None
|
||||
for occupant in reversed(reg.occupancy):
|
||||
if occupant is None:
|
||||
empty_count += 1
|
||||
elif occupant != last_occupant:
|
||||
if empty_count > 0:
|
||||
rdata_conns.append("{}'h0".format(empty_count))
|
||||
empty_count = 0
|
||||
rdata_conns.append("{}_rdata".format(reg.fields[occupant].fullname))
|
||||
last_occupant = occupant
|
||||
if empty_count > 0:
|
||||
rdata_conns.append("{}'h0".format(empty_count))
|
||||
for field in reg.fields.values():
|
||||
lsb = reg.occupancy.index(field.name)
|
||||
msb = lsb - 1 + reg.occupancy.count(field.name)
|
||||
index = "[{}]".format(lsb) if msb == lsb else "[{}:{}]".format(msb, lsb)
|
||||
v.decls.append("wire {} {}_wdata = wdata{};".format(field.width_decl, field.fullname, index))
|
||||
v.decls.append("wire {} {}_rdata;".format(field.width_decl, field.fullname))
|
||||
v.decls.append("wire {} __{}_rdata = {{{}}};".format(width2str(reg.width), reg.name, ", ".join(rdata_conns)))
|
||||
|
||||
if reg.wstrobe is not None:
|
||||
if not reg.wstrobe in self.wstrobe:
|
||||
self.wstrobe[reg.wstrobe] = []
|
||||
self.wstrobe[reg.wstrobe].append(reg.name)
|
||||
|
||||
def post_register(self, reg):
|
||||
pass
|
||||
|
||||
def pre_field(self, f):
|
||||
v = self.v
|
||||
rname = self.regname
|
||||
fname = f.fullname
|
||||
if f.access in ["rov", "rf", "rwf", "w1c"]:
|
||||
v.ports.append("input wire {} {}_i".format(f.width_decl, fname))
|
||||
if f.access in ["rov", "rf", "rwf"]:
|
||||
v.decls.append("assign {}_rdata = {}_i;".format(fname, fname))
|
||||
if f.access in ["wo", "rw", "wf", "rwf", "sc", "w1c"]:
|
||||
v.ports.append("output reg {} {}_o".format(f.width_decl, fname))
|
||||
if f.access in ["wf", "rwf"]:
|
||||
v.ports.append("output reg {}_wen".format(fname))
|
||||
v.logic_comb.append("{}_wen = __{}_wen;".format(fname, rname))
|
||||
v.logic_comb.append("{}_o = {}_wdata;".format(fname, fname))
|
||||
if f.access in ["rf", "rwf"]:
|
||||
v.ports.append("output reg {}_ren".format(fname))
|
||||
v.logic_comb.append("{}_ren = __{}_ren;".format(fname, rname))
|
||||
if f.access in ["rw"]:
|
||||
v.decls.append("assign {}_rdata = {}_o;".format(fname, fname))
|
||||
if f.access in ["rw", "wo"]:
|
||||
v.logic_rst.append("{}_o <= {}'h{:x};".format(fname, f.width, f.resetval))
|
||||
v.logic_clk.append("if (__{}_wen)".format(rname))
|
||||
v.logic_clk.append("\t{}_o <= {}_wdata;".format(fname, fname))
|
||||
if f.access in ["w1c"]:
|
||||
v.logic_rst.append("{} <= {}'h{:x};".format(fname, f.width, f.resetval))
|
||||
v.decls.append("reg {} {};".format(f.width_decl, fname))
|
||||
v.decls.append("assign {}_rdata = {};".format(fname, fname))
|
||||
v.logic_clk.append("{0} <= ({0} && !(__{1}_wen && {0}_wdata)) || {0}_i;".format(fname, rname))
|
||||
v.logic_comb.append("{0}_o = {0};".format(fname))
|
||||
if f.access in ["sc"]:
|
||||
v.logic_comb.append("{0}_o = {0}_wdata & {{{1}{{__{2}_wen}}}};".format(fname, f.width, rname))
|
||||
if f.access in ["ro", "wo", "wf", "sc"]:
|
||||
v.decls.append("assign {}_rdata = {}'h{:x};".format(fname, f.width, f.resetval))
|
||||
|
||||
if f.concat is not None:
|
||||
if not f.access in ["wo", "rw", "wf", "rwf", "sc"]:
|
||||
raise Exception("concat specified for port with no regblock output")
|
||||
if not f.concat in self.concat:
|
||||
self.concat[f.concat] = []
|
||||
self.concat[f.concat].append((f.width, "{}_o".format(fname)))
|
||||
|
||||
|
||||
# C header generation
|
||||
|
||||
class HeaderWriter(RegBlockVisitor):
|
||||
def __init__(self):
|
||||
self.lines = []
|
||||
self.blockname = None
|
||||
self.regname = None
|
||||
|
||||
def __str__(self):
|
||||
return "".join(l + "\n" for l in self.lines)
|
||||
|
||||
def pre_regblock(self, rb):
|
||||
lines = self.lines
|
||||
self.blockname = rb.name
|
||||
lines.extend(c_block_comment(["AUTOGENERATED BY REGBLOCK", "Do not edit manually.",
|
||||
"Edit the source file (or regblock utility) and regenerate."], align = "^"))
|
||||
lines.append("")
|
||||
lines.append("#ifndef _{}_REGS_H_".format(rb.name.upper()))
|
||||
lines.append("#define _{}_REGS_H_".format(rb.name.upper()))
|
||||
lines.append("")
|
||||
lines.append(c_line_comment("{:<20} : {}".format("Block name", rb.name)))
|
||||
lines.append(c_line_comment("{:<20} : {}".format("Bus type", rb.bus)))
|
||||
lines.append(c_line_comment("{:<20} : {}".format("Bus data width", rb.w_data)))
|
||||
lines.append(c_line_comment("{:<20} : {}".format("Bus address width", rb.w_addr)))
|
||||
lines.append("")
|
||||
if rb.info is not None:
|
||||
lines.append(c_line_comment(rb.info))
|
||||
lines.append("")
|
||||
for i, reg in enumerate(rb.regs):
|
||||
lines.append("#define {}_{}_OFFS {}".format(rb.name.upper(), reg.name.upper(), i * 4))
|
||||
|
||||
def post_regblock(self, rb):
|
||||
self.lines.append("")
|
||||
self.lines.append("#endif // _{}_REGS_H_".format(rb.name.upper()))
|
||||
|
||||
def pre_register(self, reg):
|
||||
lines = self.lines
|
||||
lines.append("")
|
||||
lines.extend(c_block_comment([reg.name.upper()], align = "^"))
|
||||
lines.append("")
|
||||
if reg.info is not None:
|
||||
lines.append(c_line_comment(reg.info))
|
||||
lines.append("")
|
||||
self.regname = reg.name
|
||||
|
||||
def post_register(self, reg):
|
||||
pass
|
||||
|
||||
def pre_field(self, f):
|
||||
fname = f.fullname.upper()
|
||||
lines = self.lines
|
||||
lines.append(c_line_comment("Field: {} Access: {}".format(fname, f.access.upper())))
|
||||
fname = self.blockname.upper() + "_" + fname
|
||||
if f.info is not None:
|
||||
lines.append(c_line_comment(f.info))
|
||||
lines.append("#define {}_LSB {}".format(fname, f.lsb))
|
||||
lines.append("#define {}_BITS {}".format(fname, f.width))
|
||||
mask = 0
|
||||
for i in range(32):
|
||||
if i in range(f.lsb, f.msb + 1):
|
||||
mask = mask | (1 << i)
|
||||
lines.append("#define {}_MASK {:#x}".format(fname, mask))
|
||||
|
||||
def change_ext(fname, ext):
|
||||
return ".".join(fname.split(".")[0:-1] + [ext.strip(".")])
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("src", help="Source file to read from (pass - for stdin)")
|
||||
parser.add_argument("--verilog", "-v", help="Verilog file to write to (pass - for stdout)")
|
||||
parser.add_argument("--cheader", "-c", help="C header file to write to (pass - for stdout)")
|
||||
parser.add_argument("--all", "-a", action="store_true", help="Generate all output types, with default filenames")
|
||||
args = parser.parse_args()
|
||||
sfile = None
|
||||
vfile = None
|
||||
hfile = None
|
||||
if args.src == "-" and args.all:
|
||||
exit("Cannot use --all with stdin input")
|
||||
if args.src == "-":
|
||||
sfile = sys.stdin
|
||||
else:
|
||||
sfile = open(args.src)
|
||||
rb = RegBlock.load(sfile)
|
||||
|
||||
if args.verilog is not None or args.all:
|
||||
if args.verilog == "-":
|
||||
vfile = sys.stdout
|
||||
elif args.verilog is None:
|
||||
vfile = open(change_ext(args.src, ".v"), "w")
|
||||
else:
|
||||
vfile = open(args.verilog, "w")
|
||||
if args.cheader is not None or args.all:
|
||||
if (args.cheader == "-"):
|
||||
hfile = sys.stdout
|
||||
elif args.verilog is None:
|
||||
hfile = open(change_ext(args.src, ".h"), "w")
|
||||
else:
|
||||
hfile = open(args.cheader, "w")
|
||||
|
||||
if vfile is not None:
|
||||
vw = VerilogWriter()
|
||||
rb.accept(vw)
|
||||
vfile.write(str(vw.v))
|
||||
if hfile is not None:
|
||||
hw = HeaderWriter()
|
||||
rb.accept(hw)
|
||||
hfile.write(str(hw))
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
TOP ?= tb
|
||||
DOTF ?= $(TOP).f
|
||||
SIMNAME?=simulation
|
||||
|
||||
SIM_VARS = PLATFORM=lin64 LD_LIBRARY_PATH=$XILINX/lib/$PLATFORM
|
||||
FUSE ?= $(SIM_VARS) fuse
|
||||
SIMSCRIPT ?= $(SCRIPTS)/sim_run.tcl
|
||||
GUISCRIPT ?= $(SCRIPTS)/gui_run.tcl
|
||||
|
||||
# Kill implicit rules
|
||||
.SUFFIXES:
|
||||
.IMPLICIT:
|
||||
|
||||
sim: build
|
||||
(cd sim; $(SIM_VARS) ./$(SIMNAME) -tclbatch $(SIMSCRIPT))
|
||||
|
||||
gui: build
|
||||
(cd sim; $(SIM_VARS) ./$(SIMNAME) -gui -tclbatch $(GUISCRIPT))
|
||||
|
||||
build:
|
||||
mkdir -p sim
|
||||
$(SCRIPTS)/listfiles --relativeto sim -f isim $(DOTF) -o sim/sim.prj
|
||||
(cd sim; $(FUSE) -d SIM -prj sim.prj $(TOP) -o $(SIMNAME))
|
||||
|
||||
clean::
|
||||
rm -rf sim
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
run 1s;
|
||||
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
SRCS ?= $(wildcard *.c) $(wildcard *.S)
|
||||
APPNAME ?= test
|
||||
|
||||
OBJS = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SRCS)))
|
||||
|
||||
CROSS_PREFIX=riscv32-unknown-elf-
|
||||
CC=$(CROSS_PREFIX)gcc
|
||||
LD=$(CROSS_PREFIX)gcc
|
||||
OBJCOPY=$(CROSS_PREFIX)objcopy
|
||||
OBJDUMP=$(CROSS_PREFIX)objdump
|
||||
|
||||
MARCH?=rv32ic
|
||||
LDSCRIPT?=memmap.ld
|
||||
override CCFLAGS+=-c -march=$(MARCH) $(addprefix -I ,$(INCDIRS))
|
||||
override CCFLAGS+=-Wall -Wextra -Wno-parentheses
|
||||
override LDFLAGS+=-march=$(MARCH) -T $(LDSCRIPT)
|
||||
|
||||
# Override to -D to get all sections
|
||||
DISASSEMBLE?=-d
|
||||
|
||||
.SUFFIXES:
|
||||
.SECONDARY:
|
||||
.PHONY: all clean
|
||||
all: compile
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CCFLAGS) $< -o $@
|
||||
|
||||
%.o: %.S
|
||||
$(CC) $(CCFLAGS) $< -o $@
|
||||
|
||||
$(APPNAME).elf: $(OBJS)
|
||||
$(LD) $(LDFLAGS) $(OBJS) $(addprefix -l,$(LIBS)) -o $(APPNAME).elf
|
||||
|
||||
%.bin: %.elf
|
||||
$(OBJCOPY) -O binary $< $@
|
||||
|
||||
%8.hex: %.elf
|
||||
$(OBJCOPY) -O verilog $< $@
|
||||
|
||||
%32.hex: %8.hex
|
||||
$(SCRIPTS)/vhexwidth -w 32 $< -o $@
|
||||
|
||||
$(APPNAME).dis: $(APPNAME).elf
|
||||
@echo ">>>>>>>>> Memory map:" > $(APPNAME).dis
|
||||
$(OBJDUMP) -h $(APPNAME).elf >> $(APPNAME).dis
|
||||
@echo >> $(APPNAME).dis
|
||||
@echo ">>>>>>>>> Disassembly:" >> $(APPNAME).dis
|
||||
$(OBJDUMP) $(DISASSEMBLE) $(APPNAME).elf >> $(APPNAME).dis
|
||||
|
||||
|
||||
compile:: $(APPNAME)32.hex $(APPNAME).dis $(APPNAME).bin
|
||||
|
||||
clean::
|
||||
rm -f $(APPNAME).elf $(APPNAME)32.hex $(APPNAME)8.hex $(APPNAME).dis $(APPNAME).bin $(OBJS)
|
||||
Vendored
+82
@@ -0,0 +1,82 @@
|
||||
YOSYS=yosys
|
||||
NEXTPNR=nextpnr-ecp5
|
||||
TRELLIS?=/usr/share/trellis
|
||||
|
||||
CHIPNAME?=chip
|
||||
DEVICE?=um5g-85k
|
||||
PACKAGE?=CABGA381
|
||||
DEVICE_IDCODE?=0x41113043
|
||||
|
||||
DEFINES+=FPGA FPGA_ECP5
|
||||
|
||||
SYNTH_OPT?=
|
||||
PNR_OPT?=
|
||||
|
||||
SYNTH_CMD=read_verilog $(addprefix -I,$(INCDIRS)) $(addprefix -D,$(DEFINES)) $(SRCS);
|
||||
ifneq (,$(TOP))
|
||||
SYNTH_CMD+=hierarchy -top $(TOP);
|
||||
endif
|
||||
SYNTH_CMD+=synth_ecp5 $(SYNTH_OPT) -json $(CHIPNAME).json
|
||||
|
||||
# Kill implicit rules
|
||||
.SUFFIXES:
|
||||
.IMPLICIT:
|
||||
|
||||
.PHONY: all romfiles synth clean program dump
|
||||
|
||||
all: bit
|
||||
|
||||
romfiles::
|
||||
synth: romfiles $(CHIPNAME).json
|
||||
dump: romfiles
|
||||
pnr: synth $(CHIPNAME).config
|
||||
bit: pnr $(CHIPNAME).bit $(CHIPNAME).svf
|
||||
|
||||
SRCS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flat $(DOTF))
|
||||
INCDIRS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flati $(DOTF))
|
||||
|
||||
dump:
|
||||
$(YOSYS) -p "$(SYNTH_CMD); write_verilog $(CHIPNAME)_synth.v"
|
||||
|
||||
$(CHIPNAME).json: $(SRCS)
|
||||
@echo ">>> Synth"
|
||||
@echo
|
||||
$(YOSYS) -p "$(SYNTH_CMD)" > synth.log
|
||||
tail -n 35 synth.log
|
||||
|
||||
|
||||
$(CHIPNAME).config: $(CHIPNAME).json $(CHIPNAME).lpf
|
||||
@echo ">>> Place and Route"
|
||||
@echo
|
||||
$(NEXTPNR) -r --placer sa --$(DEVICE) --package $(PACKAGE) --lpf $(CHIPNAME).lpf --json $(CHIPNAME).json --textcfg $@ $(PNR_OPT) --quiet --log pnr.log
|
||||
@grep "Info: Max frequency for clock " pnr.log | tail -n 1
|
||||
|
||||
$(CHIPNAME).bit: $(CHIPNAME).config
|
||||
@echo ">>> Generate Bitstream"
|
||||
@echo
|
||||
ecppack --compress --svf $(CHIPNAME).svf --idcode $(DEVICE_IDCODE) $< $@
|
||||
|
||||
$(CHIPNAME).svf: $(CHIPNAME).bit
|
||||
|
||||
clean::
|
||||
rm -f $(CHIPNAME).json $(CHIPNAME).asc $(CHIPNAME).bit $(CHIPNAME)_synth.v
|
||||
rm -f synth.log pnr.log
|
||||
|
||||
# Code for trying n different pnr seeds and reporting results
|
||||
|
||||
PNR_N_TRIES := 100
|
||||
PNR_TRY_LIST := $(shell seq $(PNR_N_TRIES))
|
||||
|
||||
pnr_sweep: $(addprefix pnr_try,$(PNR_TRY_LIST))
|
||||
|
||||
define make-sweep-target
|
||||
pnr_try$1: synth
|
||||
@echo ">>> Starting sweep $1"
|
||||
$(NEXTPNR) --seed $1 --placer sa --$(DEVICE) --package $(PACKAGE) --lpf $(CHIPNAME).lpf --json $(CHIPNAME).json --textcfg pnr_try$1.config $(PNR_OPT) --quiet --log pnr$1.log
|
||||
@grep "Info: Max frequency for clock " pnr$1.log | tail -n 1
|
||||
endef
|
||||
|
||||
$(foreach try,$(PNR_TRY_LIST),$(eval $(call make-sweep-target,$(try))))
|
||||
|
||||
clean::
|
||||
rm -f pnr_try*.asc pnr*.log
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
YOSYS=yosys
|
||||
NEXTPNR=nextpnr-ice40
|
||||
CHIPNAME?=chip
|
||||
DEVICE?=hx8k
|
||||
PACKAGE?=bg121
|
||||
|
||||
DEFINES+=FPGA FPGA_ICE40
|
||||
|
||||
PRE_SYNTH_CMD?=
|
||||
SYNTH_OPT?=
|
||||
PNR_OPT?=
|
||||
|
||||
SYNTH_CMD=read_verilog $(addprefix -I,$(INCDIRS)) $(addprefix -D,$(DEFINES)) $(SRCS);
|
||||
ifneq (,$(TOP))
|
||||
SYNTH_CMD+=hierarchy -top $(TOP);
|
||||
endif
|
||||
SYNTH_CMD+=$(PRE_SYNTH_CMD)
|
||||
SYNTH_CMD+=synth_ice40 $(SYNTH_OPT); write_json $(CHIPNAME).json
|
||||
|
||||
# Kill implicit rules
|
||||
.SUFFIXES:
|
||||
.IMPLICIT:
|
||||
|
||||
.PHONY: all romfiles synth clean program dump
|
||||
|
||||
all: bit
|
||||
|
||||
romfiles::
|
||||
synth: romfiles $(CHIPNAME).json
|
||||
dump: romfiles
|
||||
pnr: synth $(CHIPNAME).asc
|
||||
bit: pnr $(CHIPNAME).bin
|
||||
|
||||
SRCS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flat $(DOTF))
|
||||
INCDIRS=$(shell PROJ_ROOT=$(PROJ_ROOT) HDL=$(HDL) $(SCRIPTS)/listfiles --relative -f flati $(DOTF))
|
||||
|
||||
dump:
|
||||
$(YOSYS) -p "$(SYNTH_CMD); write_verilog $(CHIPNAME)_synth.v"
|
||||
|
||||
$(CHIPNAME).json: $(SRCS)
|
||||
@echo ">>> Synth"
|
||||
@echo
|
||||
$(YOSYS) -p "$(SYNTH_CMD)" > synth.log
|
||||
tail -n 35 synth.log
|
||||
|
||||
|
||||
$(CHIPNAME).asc: $(CHIPNAME).json $(CHIPNAME).pcf
|
||||
@echo ">>> Place and Route"
|
||||
@echo
|
||||
$(NEXTPNR) -r --$(DEVICE) --package $(PACKAGE) --pcf $(CHIPNAME).pcf --json $(CHIPNAME).json --asc $(CHIPNAME).asc $(PNR_OPT) --quiet --log pnr.log
|
||||
@grep "Info: Max frequency for clock " pnr.log | tail -n 1
|
||||
|
||||
$(CHIPNAME).bin: $(CHIPNAME).asc
|
||||
@echo ">>> Generate Bitstream"
|
||||
@echo
|
||||
icepack -s $(CHIPNAME).asc $(CHIPNAME).bin
|
||||
|
||||
clean::
|
||||
rm -f $(CHIPNAME).json $(CHIPNAME).asc $(CHIPNAME).bin $(CHIPNAME)_synth.v
|
||||
rm -f synth.log pnr.log
|
||||
|
||||
# Code for trying n different pnr seeds and reporting results
|
||||
|
||||
PNR_N_TRIES := 100
|
||||
PNR_TRY_LIST := $(shell seq $(PNR_N_TRIES))
|
||||
|
||||
pnr_sweep: $(addprefix pnr_try,$(PNR_TRY_LIST))
|
||||
|
||||
define make-sweep-target
|
||||
pnr_try$1: synth
|
||||
@echo ">>> Starting sweep $1"
|
||||
-$(NEXTPNR) --seed $1 --$(DEVICE) --package $(PACKAGE) --pcf $(CHIPNAME).pcf --json $(CHIPNAME).json --asc pnr_try$1.asc $(PNR_OPT) --quiet --log pnr$1.log
|
||||
@grep "Info: Max frequency for clock " pnr$1.log | tail -n 1
|
||||
endef
|
||||
|
||||
$(foreach try,$(PNR_TRY_LIST),$(eval $(call make-sweep-target,$(try))))
|
||||
|
||||
clean::
|
||||
rm -f pnr_try*.asc pnr*.log
|
||||
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Simple utility for programming SPI flash via a UART shell in the bootloader.
|
||||
# The bootloader possesses a 1-page buffer which we can write to, read from and
|
||||
# checksum.
|
||||
# It also possesses commands for transfers between this buffer and the SPI flash,
|
||||
# flash erasure, and launching the 2nd stage boot code.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
import sys
|
||||
import time
|
||||
|
||||
PAGESIZE = 2 ** 8
|
||||
SECTORSIZE = 2 ** 12
|
||||
BLOCKSIZE = 2 ** 16
|
||||
|
||||
SRAM_LOAD_ADDR = 0x2 << 28
|
||||
SRAM_EXEC_ADDR = SRAM_LOAD_ADDR + 0xc0
|
||||
|
||||
class ProtocolError(Exception):
|
||||
pass
|
||||
|
||||
class CMD:
|
||||
NOP = '\n'.encode()
|
||||
WRITE_BUF = 'w'.encode()
|
||||
READ_BUF = 'r'.encode()
|
||||
GET_CHECKSUM = 'c'.encode()
|
||||
SET_ADDR = 'a'.encode()
|
||||
WRITE_FLASH = 'W'.encode()
|
||||
READ_FLASH = 'R'.encode()
|
||||
ERASE_SECTOR = 'E'.encode()
|
||||
ERASE_BLOCK = 'B'.encode()
|
||||
BOOT_2ND = '2'.encode()
|
||||
LOAD_MEM = 'l'.encode()
|
||||
EXEC_MEM = 'x'.encode()
|
||||
ACK = ':'.encode()
|
||||
|
||||
def reset_shell(port):
|
||||
while True:
|
||||
port.write(CMD.NOP * (PAGESIZE + 1))
|
||||
port.flushOutput()
|
||||
time.sleep(0.02)
|
||||
port.flushInput()
|
||||
port.write(CMD.NOP)
|
||||
time.sleep(0.02)
|
||||
if port.readable() and port.read_all().endswith(CMD.ACK):
|
||||
break
|
||||
|
||||
def check_ack(port):
|
||||
resp = port.read()
|
||||
if len(resp) == 0 or resp != CMD.ACK:
|
||||
print("Got '{!r}'".format(resp))
|
||||
raise ProtocolError()
|
||||
|
||||
def write_buf(port, data, check=True):
|
||||
assert(len(data) == 256)
|
||||
port.write(CMD.WRITE_BUF)
|
||||
port.write(data)
|
||||
if check:
|
||||
check_ack(port)
|
||||
# TODO checksum
|
||||
|
||||
def read_buf(port, pipelined=False):
|
||||
if not pipelined:
|
||||
port.write(CMD.READ_BUF)
|
||||
resp = port.read(PAGESIZE)
|
||||
if len(resp) != PAGESIZE:
|
||||
raise ProtocolError()
|
||||
return resp
|
||||
# TODO checksum
|
||||
|
||||
def set_addr(port, addr):
|
||||
addr_b = bytes([
|
||||
(addr >> 16) & 0xff,
|
||||
(addr >> 8) & 0xff,
|
||||
(addr >> 0) & 0xff
|
||||
])
|
||||
port.write(CMD.SET_ADDR + addr_b)
|
||||
echo = port.read(3)
|
||||
if echo != addr_b:
|
||||
raise ProtocolError()
|
||||
check_ack(port)
|
||||
|
||||
def progress(header, frac, width=40):
|
||||
n = int(width * frac)
|
||||
sys.stdout.write("\r" + header + "▕" + "▒" * n + " " * (width - n) + "▏")
|
||||
|
||||
def read_flash(port, addr, size):
|
||||
set_addr(port, addr)
|
||||
data = bytes()
|
||||
addr_range = range(addr, addr + size, PAGESIZE)
|
||||
for a in addr_range:
|
||||
port.write(CMD.READ_FLASH)
|
||||
port.write(CMD.READ_BUF) # Should have space for 2 cmds in FIFO. Hoist this one up from read_buf()
|
||||
progress("Read: ", (a - addr) / size)
|
||||
check_ack(port)
|
||||
data += read_buf(port, pipelined=True)
|
||||
progress("Read: ", 1)
|
||||
if len(data) > size:
|
||||
data = data[:size] # ouch
|
||||
return data
|
||||
|
||||
def write_flash(port, addr, data):
|
||||
assert(addr % PAGESIZE == 0)
|
||||
if len(data) % PAGESIZE != 0:
|
||||
data += bytes(PAGESIZE - len(data) % PAGESIZE)
|
||||
set_addr(port, addr)
|
||||
for i in range(len(data) // PAGESIZE):
|
||||
write_buf(port, data[i * PAGESIZE : (i + 1) * PAGESIZE], check=False)
|
||||
port.write(CMD.WRITE_FLASH)
|
||||
progress("Write: ", i / (len(data) // PAGESIZE))
|
||||
check_ack(port)
|
||||
check_ack(port)
|
||||
progress("Write: ", 1)
|
||||
|
||||
def erase_flash(port, addr, size):
|
||||
end = addr + size
|
||||
if end % SECTORSIZE != 0:
|
||||
end += SECTORSIZE - end % SECTORSIZE
|
||||
start = addr - addr % SECTORSIZE
|
||||
set_addr(port, start)
|
||||
a = start
|
||||
while a < end:
|
||||
progress("Erase: ", (a - start) / (end - start))
|
||||
if end - a >= BLOCKSIZE and a % BLOCKSIZE == 0:
|
||||
port.write(CMD.ERASE_BLOCK)
|
||||
a += BLOCKSIZE
|
||||
else:
|
||||
port.write(CMD.ERASE_SECTOR)
|
||||
a += SECTORSIZE
|
||||
check_ack(port)
|
||||
progress("Erase: ", 1)
|
||||
|
||||
def run_2nd_stage(port):
|
||||
set_addr(port, 0x123456)
|
||||
port.write(CMD.BOOT_2ND)
|
||||
check_ack(port)
|
||||
|
||||
def load_mem(port, data):
|
||||
set_addr(port, 0xb00000 | SRAM_LOAD_ADDR)
|
||||
port.write(CMD.LOAD_MEM + bytes([
|
||||
(len(data) >> 16) & 0xff,
|
||||
(len(data) >> 8) & 0xff,
|
||||
(len(data) >> 0) & 0xff
|
||||
]))
|
||||
check_ack(port)
|
||||
port.write(data) # yup
|
||||
check_ack(port)
|
||||
|
||||
def exec_mem(port):
|
||||
set_addr(port, 0xb00000 | SRAM_EXEC_ADDR)
|
||||
port.write(CMD.EXEC_MEM)
|
||||
check_ack(port)
|
||||
|
||||
def any_int(x):
|
||||
return int(x, 0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("file", nargs="?", help="Filename to read from or dump to.")
|
||||
parser.add_argument("--uart", "-u", help="Path to UART device (e.g. /dev/ttyUSB0)")
|
||||
parser.add_argument("--baud", "-b", help="Baud rate for UART. Default 1 Mbaud")
|
||||
parser.add_argument("--write", "-w", action="store_true",
|
||||
help="Write to flash (default is read)")
|
||||
parser.add_argument("--verify", "-v", action="store_true",
|
||||
help="Verify contents after programming (use with --write)")
|
||||
parser.add_argument("--run", "-r", action="store_true",
|
||||
help="Run code from flash after programming")
|
||||
parser.add_argument("--execute", "-x", action="store_true",
|
||||
help="Load code directly into SRAM and execute it. Not to be combined with -w,-v,-r")
|
||||
parser.add_argument("--start", "-s", type=any_int, help="Base address to start read/write")
|
||||
parser.add_argument("--len", "-l", type=any_int, help="Number of bytes to read, or override file length for write.")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Do some simple parameter checking to make it harder to accidentally trash your device
|
||||
if args.write and args.file is None:
|
||||
sys.exit("Must specify filename for write")
|
||||
if args.start is None:
|
||||
args.start = 0
|
||||
if args.uart is None:
|
||||
args.uart = sorted(p.device for p in serial.tools.list_ports.comports())[-1]
|
||||
if args.baud is None:
|
||||
args.baud = 1000000
|
||||
if args.verify and not args.write:
|
||||
sys.exit("Verify is only valid for a write operation")
|
||||
if args.run and not args.write:
|
||||
sys.exit("Run is only valid for a write operation")
|
||||
if args.write and (args.start % PAGESIZE != 0):
|
||||
sys.exit("Writes must be aligned on a {}-byte boundary.".format(PAGESIZE))
|
||||
if args.execute and (args.write or args.verify or args.run or args.start or args.len):
|
||||
sys.exit("--execute is not compatible with flash-related arguments")
|
||||
if args.write or args.execute:
|
||||
try:
|
||||
filesize = os.stat(args.file).st_size
|
||||
if args.len is None:
|
||||
args.len = filesize
|
||||
except:
|
||||
sys.exit("Could not open file '{}'".format(args.file))
|
||||
else:
|
||||
if args.len is None:
|
||||
args.len = PAGESIZE
|
||||
|
||||
# Don't want to overwrite their image if they forget -w
|
||||
if not (args.write or args.execute) and args.file and os.path.exists(args.file):
|
||||
resp = ""
|
||||
while not resp in ["y", "n"]:
|
||||
resp = input("File '{}' exists. Overwrite? (y/n) ".format(args.file))
|
||||
resp = resp.lower().strip()
|
||||
if resp == "n":
|
||||
sys.exit(0)
|
||||
|
||||
# Summarise what we're about to do
|
||||
if args.execute:
|
||||
print("Loading {} bytes to SRAM and running".format(args.len))
|
||||
else:
|
||||
print("{} {} bytes {} {}, starting at address 0x{:06x}".format(
|
||||
"Writing" + (" and verifying" if args.verify else "") if args.write else "Reading",
|
||||
args.len,
|
||||
"from" if args.write else "to",
|
||||
"stdout" if args.file is None else "file " + args.file,
|
||||
args.start,
|
||||
", and verifying" if args.verify else ""
|
||||
))
|
||||
|
||||
# And then do it
|
||||
# need to allow for page erase, upward of 60 ms. (Uh, actually it seems to be much longer)
|
||||
port = serial.Serial(args.uart, args.baud, timeout=1)
|
||||
print("Waiting for bootloader on {}...".format(port.name))
|
||||
reset_shell(port)
|
||||
print("")
|
||||
|
||||
if args.execute:
|
||||
print("Loading...")
|
||||
load_mem(port, open(args.file, "rb").read())
|
||||
print("Load ok, running...")
|
||||
exec_mem(port)
|
||||
elif args.write:
|
||||
data = open(args.file, "rb").read()
|
||||
if len(data) > args.len:
|
||||
data = data[:args.len]
|
||||
else:
|
||||
data = data + bytes(args.len - len(data)) # zero padding
|
||||
|
||||
start = time.time()
|
||||
erase_flash(port, args.start, args.len)
|
||||
print(" Took {:.1f} s\n".format(time.time() - start))
|
||||
start = time.time()
|
||||
write_flash(port, args.start, data)
|
||||
print(" Took {:.1f} s\n".format(time.time() - start))
|
||||
if args.verify:
|
||||
start = time.time()
|
||||
readback = read_flash(port, args.start, args.len)
|
||||
print(" Took {:.1f} s".format(time.time() - start))
|
||||
if readback != data:
|
||||
sys.exit("Verification failed.") # TODO: better info
|
||||
if args.run:
|
||||
print("Launching flash second stage")
|
||||
run_2nd_stage(port)
|
||||
print("Done")
|
||||
else:
|
||||
start = time.time()
|
||||
data = read_flash(port, args.start, args.len)
|
||||
print(" Took {:.1f} s\nDone".format(time.time() - start))
|
||||
if args.file:
|
||||
open(args.file, "wb").write(data)
|
||||
else:
|
||||
for i, byte in enumerate(data):
|
||||
if i % 8 == 0:
|
||||
sys.stdout.write("{:06x}: ".format(args.start + i))
|
||||
sys.stdout.write("{:02x}".format(byte))
|
||||
sys.stdout.write("\n" if i % 8 == 7 else " ")
|
||||
sys.stdout.write("\n")
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
from PIL import Image
|
||||
from collections import OrderedDict
|
||||
|
||||
__doc__ = """Utility for stripping non-unique tiles from a tileset image.
|
||||
Outputs the uniquified image, and an index map file from original tile indices
|
||||
to new tile indices."""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("input", help="Input file name")
|
||||
parser.add_argument("output", help="Output file name")
|
||||
parser.add_argument("--tilesize", "-t", help="Tile size (pixels), default 8",
|
||||
default="8", choices=["8", "16"])
|
||||
parser.epilog = __doc__
|
||||
args = parser.parse_args()
|
||||
|
||||
img = Image.open(args.input)
|
||||
tilesize = int(args.tilesize)
|
||||
|
||||
tile_first_seen = {}
|
||||
index_mapping = []
|
||||
output_images = []
|
||||
|
||||
src_index = 0
|
||||
for y in range(0, img.height - (tilesize - 1), tilesize):
|
||||
for x in range(0, img.width - (tilesize - 1), tilesize):
|
||||
tile = img.crop((x, y, x + tilesize, y + tilesize))
|
||||
tiledata = tuple(tile.getdata())
|
||||
if tiledata in tile_first_seen:
|
||||
index_mapping.append((src_index, tile_first_seen[tiledata]))
|
||||
else:
|
||||
tile_first_seen[tiledata] = len(output_images)
|
||||
index_mapping.append((src_index, len(output_images)))
|
||||
output_images.append(tile)
|
||||
src_index += 1
|
||||
|
||||
print("Found {} unique tile images".format(len(output_images)))
|
||||
|
||||
oimg = Image.new("RGBA", (tilesize * len(output_images), tilesize))
|
||||
for i, tile in enumerate(output_images):
|
||||
oimg.paste(tile, (i * tilesize, 0))
|
||||
|
||||
with open(args.output, "wb") as ofile:
|
||||
oimg.save(ofile)
|
||||
with open(args.output + ".index_map", "w") as mapfile:
|
||||
for old, new in index_mapping:
|
||||
mapfile.write("{}, {}\n".format(old, new))
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Tool for converting width of verilog hex files
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import re
|
||||
|
||||
# Assume little-endian, and that widths are multiples of 4
|
||||
|
||||
def width_convert(ilines, width, base):
|
||||
olines = []
|
||||
pad_fmt = "{:>0" + str(width // 4) + "}"
|
||||
accum = ""
|
||||
for l in ilines:
|
||||
if l.startswith("@"):
|
||||
if len(accum):
|
||||
olines.append(pad_fmt.format(accum))
|
||||
accum = ""
|
||||
# TODO: address scaling wrong if input not byte-sized:
|
||||
olines.append("@{:x}".format((int(l[1:], 16) - base) // (width // 8)))
|
||||
continue
|
||||
for num in re.findall(r"[0-9a-fA-F]+", l):
|
||||
accum = num + accum
|
||||
while len(accum) >= width // 4:
|
||||
olines.append(accum[len(accum) - width // 4 :])
|
||||
accum = accum[:len(accum) - width // 4]
|
||||
if len(accum):
|
||||
olines.append(pad_fmt.format(accum))
|
||||
return olines
|
||||
|
||||
def parseint(arg, name, default):
|
||||
if arg is None:
|
||||
arg = default
|
||||
try:
|
||||
if type(arg) is str and arg.startswith("0x"):
|
||||
arg = int(arg, 16)
|
||||
else:
|
||||
arg = int(arg)
|
||||
except ValueError:
|
||||
sys.exit("Invalid value for {}: {}".format(name, arg))
|
||||
return arg
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("input", help="Input file name")
|
||||
parser.add_argument("--width", "-w", help="Output hex width (default 32)")
|
||||
parser.add_argument("--base", "-b", help="Base address for @ commands (subtracted)")
|
||||
parser.add_argument("--output", "-o", help="Output file name")
|
||||
args = parser.parse_args()
|
||||
if args.output is None:
|
||||
ofile = sys.stdout
|
||||
else:
|
||||
ofile = open(args.output, "w")
|
||||
if args.input is None:
|
||||
ifile = sys.stdin
|
||||
else:
|
||||
ifile = open(args.input)
|
||||
width = parseint(args.width, "width", 32)
|
||||
base = parseint(args.base, "base", 0)
|
||||
olines = width_convert(ifile, width, base)
|
||||
for l in olines:
|
||||
ofile.write(l + "\n")
|
||||
Reference in New Issue
Block a user