aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbryan newbold <bnewbold@leaflabs.com>2014-02-26 13:16:42 -0500
committerbryan newbold <bnewbold@leaflabs.com>2014-02-26 13:16:42 -0500
commit500cbce69dee527babd6a9472643ebde5b7adb1e (patch)
tree4a642c5d1965a931083e5fdf7cc31be138ca64e1
parent0530e8994ea274e7fdf12267b4e7aeba32e97a4a (diff)
downloadaxi-lite-gen-500cbce69dee527babd6a9472643ebde5b7adb1e.tar.gz
axi-lite-gen-500cbce69dee527babd6a9472643ebde5b7adb1e.zip
commit WIP to date
-rw-r--r--.gitignore1
-rw-r--r--example.csv17
-rw-r--r--output/.stub0
-rw-r--r--pack_array.v16
-rwxr-xr-xparse.py280
-rw-r--r--syntax.rst38
-rw-r--r--templates/minimal.html.tmpl41
-rw-r--r--templates/minimal.rst.tmpl23
8 files changed, 416 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index f466019..bce1b0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
.*
*.tmp
*.old
+output/
diff --git a/example.csv b/example.csv
new file mode 100644
index 0000000..24445bf
--- /dev/null
+++ b/example.csv
@@ -0,0 +1,17 @@
+word_index,bits,mode,section,slug,default,description
+0,32,p,meta,magic,0x0002_1EAF,Core-Specific Magic Number (0x0002_1EAF)
+1,32,p,meta,version,0,Core version number
+2,32,p,meta,feature_flags,0,Feature flags (or zero)
+3,32,p,meta,git_hash,0,"HDL Git commit hash stub, or 0 (32bits)"
+4,64,p,meta,build_time,0,UNIX time of build
+1024,1,rw,general,enable,,
+1025,1,rw,general,output_en,,
+1026,1,b,general,ring,,Doorbell
+1027,16,rw,general,ring_count,,Count of doorbell rings
+1028,16,r,general,ring_counta,,Count of doorbell rings
+1029,16,r,general,ring_countb,,Count of doorbell rings
+,,m,general,,,
+,32,p,general,PARAM,100,
+,20,rw,general,,,
+,,brw,general,secret,,Value with a doorbell
+4096,18,rwm,table,row[256],,
diff --git a/output/.stub b/output/.stub
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/output/.stub
diff --git a/pack_array.v b/pack_array.v
new file mode 100644
index 0000000..26e6049
--- /dev/null
+++ b/pack_array.v
@@ -0,0 +1,16 @@
+
+`define PACK_ARRAY(PK_WIDTH,PK_LEN,PK_SRC,PK_DEST) genvar pk_idx; generate for (pk_idx=0; pk_idx<(PK_LEN); pk_idx=pk_idx+1) begin; assign PK_DEST[((PK_WIDTH)*pk_idx+((PK_WIDTH)-1)):((PK_WIDTH)*pk_idx)] = PK_SRC[pk_idx][((PK_WIDTH)-1):0]; end; endgenerate
+
+`define UNPACK_ARRAY(PK_WIDTH,PK_LEN,PK_DEST,PK_SRC) genvar unpk_idx; generate for (unpk_idx=0; unpk_idx<(PK_LEN); unpk_idx=unpk_idx+1) begin; assign PK_DEST[unpk_idx][((PK_WIDTH)-1):0] = PK_SRC[((PK_WIDTH)*unpk_idx+(PK_WIDTH-1)):((PK_WIDTH)*unpk_idx)]; end; endgenerate
+
+
+module example (
+ input [63:0] pack_4_16_in,
+ output [31:0] pack_16_2_out
+ );
+
+wire [3:0] in [0:15];
+`UNPACK_ARRAY(4,16,in,pack_4_16_in)
+
+wire [15:0] out [0:1];
+`PACK_ARRAY(16,2,in,pack_16_2_out)
diff --git a/parse.py b/parse.py
new file mode 100755
index 0000000..c404d97
--- /dev/null
+++ b/parse.py
@@ -0,0 +1,280 @@
+#!/usr/bin/env python
+"""
+
+TODO:
+- values should only parse as section if XYZ???
+- whitespace control
+ http://jinja.pocoo.org/docs/templates/#whitespace-control
+- disable HTML safety for non-html documents?
+"""
+
+from __future__ import print_function
+
+import sys
+import csv
+import time
+import os
+
+import jinja2
+
+AXI_ADDR_BITS = 16
+WORD_BITS = 32
+
+def parse_slug(s):
+ pre = s.split('[')[0]
+ post = None
+ assert(pre == filter(lambda x: x.isalnum() or x is '_', pre))
+ assert(len(pre) >= 1 and pre[0] != '_')
+ if '[' in s:
+ assert(s.count('[') == 1)
+ assert(s.count(']') == 1 and s[-1] == ']')
+ post = int(s.split('[')[1][:-1])
+ assert(post >= 0)
+ return (pre, post)
+
+def str2val(s, bits):
+ """
+ Strip '_' characters (eg, 0x1111_2222).
+ Allow 0x and 0b prefixes.
+ """
+ v = None
+ s = s.lower().replace('_', '')
+ if s.count("'") is 1:
+ raise NotImplementedError("Can't handle verilog-style constants yet")
+ if s.startswith('0b'):
+ v = int(s[2:], 2)
+ if s.startswith('0x'):
+ v = int(s[2:], 16)
+ else: # fallback
+ v = int(s)
+ assert(v >= 0 and v < 2**bits)
+ return v
+
+class Value():
+ index = None
+ bits = None
+ section = None
+ section_index = None
+ slug = None
+ slug_index = None
+ default = None
+ description = None
+ mode = None
+ addr = None
+
+ def offset(self, offset):
+ self.addr = offset + (4 * self.index)
+
+ def addr_pp(self):
+ return "0x%08X" % self.addr
+
+ def __init__(self, word_index=None, bits=None, section=None, slug=None,
+ default=None, description=None, mode=None):
+ # TODO: input validation/transforms
+ self.index = int(word_index)
+ assert(self.index >= 0)
+ assert(self.index <= (2**AXI_ADDR_BITS - 1))
+
+ if bits in [None, '']:
+ raise ValueError("Bits not defined")
+ self.bits = str2val(bits, 9)
+ assert(self.bits >= 1)
+ assert(self.bits <= 128)
+
+ if section is None:
+ (self.section, self.section_index) = ('top_level', None)
+ else:
+ (self.section, self.section_index) = parse_slug(section)
+
+ if slug is None:
+ (self.slug, self.slug_index) = (None, None)
+ else:
+ (self.slug, self.slug_index) = parse_slug(slug)
+
+ if default not in [None, '']:
+ self.default = str2val(default, self.bits)
+ else:
+ self.default = 0
+ self.description = description
+ self.mode = mode
+
+ def __str__(self):
+ return "<Value: %s>" % str(self.__dict__)
+
+class Register(Value):
+ read = False
+ write = False
+
+class Parameter(Value):
+ pass
+
+def check_overlaps(l):
+ rangelist = []
+ for val in l:
+ # TODO: also handle larger ranges
+ this = (val.index, val.index + ((val.bits-1)/WORD_BITS))
+ inserted = False
+ for i in range(len(rangelist)):
+ that = rangelist[i]
+ if ((that[0] <= this[0] <= that[1])
+ or (that[0] <= this[1] <= that[1])):
+ raise ValueError("Overlapping memory ranges: %s and %s" %
+ (this, that))
+ if this[0] < that[0]:
+ rangelist.insert(i, this)
+ inserted = True
+ break
+ if not inserted:
+ rangelist.append(this)
+
+def check_names(l):
+ names = []
+ n = None
+ for val in l:
+ if val.section:
+ n = "%s.%s" % (val.section, val.slug)
+ else:
+ n = val.slug
+ if n in names:
+ raise ValueError("Dupliate name: %s" % n)
+ names.append(n)
+
+def error(s="unspecified"):
+ sys.stderr.write(str(s) + '\n')
+ sys.exit(-1)
+
+class Repeated():
+ section = None
+
+ def __init__(self, word_index, slug):
+ self.index = int(word_index)
+ assert(self.index >= 0)
+ assert(self.index <= (2**AXI_ADDR_BITS - 1))
+
+ if self.section is '':
+ self.section = ''
+ self.section_index = None
+ else:
+ (self.section, self.section_index) = parse_slug(section)
+
+
+req = ('word_index', 'bits', 'mode', 'section', 'slug', 'default',
+ 'description')
+
+print("------- START READ")
+f = open('example.csv', 'r')
+reader = csv.DictReader(f)
+
+registers = []
+parameters = []
+mode = None
+
+for line in reader:
+ if reader.line_num is 0:
+ # validate fields just once
+ for field in req:
+ if not field in reader.fields:
+ error("Missing column: %s" % field)
+
+ # skip lines w/o
+ if line['word_index'] in [None, '']:
+ print("Skipping line %d (no index)" % reader.line_num)
+ continue
+
+ mode = line['mode']
+ try:
+ if mode.lower() == 'p':
+ p = Parameter(**line)
+ parameters.append(p)
+ elif mode.lower() == 'r':
+ r = Register(**line)
+ r.read = True
+ registers.append(r)
+ else:
+ #error("Unknown mode: %s" % mode)
+ print("Skipping line %d (unknown mode %s)" % (reader.line_num,
+ mode))
+ pass
+ except (AttributeError, TypeError, ValueError), e:
+ error("Syntax error parsing line %d: %s" % (reader.line_num, e))
+ sys.stdout.write(".")
+print('')
+f.close()
+
+print("Registers:\t%d" % len(registers))
+print("Parameters:\t%d" % len(parameters))
+
+offset = 0x0
+for r in registers:
+ r.offset(offset)
+for p in parameters:
+ p.offset(offset)
+
+check_overlaps(registers + parameters)
+check_names(registers + parameters)
+sections = {}
+for val in (registers + parameters):
+ if not val.section in sections.keys():
+ sections[val.section] = []
+ sections[val.section].append(val)
+
+for key, sec in sections.iteritems():
+ sections[key] = sorted(sec, key=lambda x: x.index)
+
+print("------- END READ")
+
+# TODO: process into sections; sort; apply offsets
+
+context = dict(registers=registers,
+ parameters=parameters,
+ name="example",
+ now=time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()),
+ attribution="Generated by AXI-Lite Generator",
+ whoami=os.getenv('USER'),
+ sections=sections)
+
+# TODO:
+# jinja2.ChoiceLoader
+# jinja2.PackageLoader
+env = jinja2.Environment(loader=jinja2.FileSystemLoader('templates'))
+#print("------- START PYTHON")
+"""
+params: single helper to dump them all
+registers:
+ helper get/set by string (eg, get("meta.magic"))
+ module cmd to dump them all
+ module+slug cmd to get/set
+ <section>.<slug> getter/setter functions
+"""
+#print("------- END PYTHON")
+
+#print("------- START HDL")
+"""
+wrapper stub also.
+params: passed all around
+registers: just one place
+"""
+#print("------- END HDL")
+
+#print("------- START C_HEADER")
+"""
+just structs for parameters/registers
+"""
+#print("------- END C_HEADER")
+
+print("------- START HTML")
+t = env.get_template('minimal.html.tmpl')
+out_f = open('output/example.html', 'w')
+out_f.write(t.render(**context))
+out_f.close()
+print("------- END HTML")
+
+print("------- START RST")
+t = env.get_template('minimal.rst.tmpl')
+out_f = open('output/example.rst', 'w')
+out_f.write(t.render(**context))
+out_f.close()
+print("------- END RST")
+
+print("------- DONE!")
+
diff --git a/syntax.rst b/syntax.rst
new file mode 100644
index 0000000..27ab499
--- /dev/null
+++ b/syntax.rst
@@ -0,0 +1,38 @@
+
+Repetition
+-----------------------
+Repeated sections use array ([n]) syntax for the section name. The [0] section
+should be complete, all the other repetitions should only have the word_index
+set.
+
+UNIMPLEMENTED: repeated sections could be packed/unpacked automatically (see
+pack_array for macros)
+
+HDL: Variables and Storage
+-------------------------------------
+Read-only nets are implemented
+
+Mode Flags
+----------------------
+
+**r: Read**
+
+**w: Write**
+
+**p: Parameter**
+ A special read-only mode for compile-time constants. The slug name is
+ capitalized for HDL but lower case in other contexts.
+
+**b: Doorbell**
+ UNIMPLEMENTED.
+ A new net with "_trig" suffix is created which is triggered on every write
+ transaction.
+
+**m: Memory Block**
+ UNIMPLEMENTED.
+ Uses an address mask to read/write, eg, a BRAM or regfile instead of
+ exposing many individual registers.
+
+**f: FIFO**
+ UNIMPLEMENTED.
+ Sets up a read or write FIFO.
diff --git a/templates/minimal.html.tmpl b/templates/minimal.html.tmpl
new file mode 100644
index 0000000..98042ee
--- /dev/null
+++ b/templates/minimal.html.tmpl
@@ -0,0 +1,41 @@
+<html>
+<head><title>{{ name }} Memory Map Documentation</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
+<style type="text/css">
+.error {padding: 15px; background-color: yellow; border: 4px solid orange; color:red;}
+table {border: 1px solid black; border-collapse: collapse; border-spacing:2px;}
+th, td {border-width: 1px; border-style: inset; border-color: gray; padding: 2px;}
+dt {font-weight: bold;}
+.code {color: white; background-color: black; border: 3px gray solid;
+ width: 640px; padding: 3px; margin-left: 10px;}
+</style>
+</head>
+<body style="margin: 25px; font-family: helvetica;">
+<a name="top"><h1 style="border-bottom: 2px solid; margin-bottom: 2px;">
+{{ name }} Memory Map Documentation</h1></a>
+<i style="font-size:smaller;">
+{% for section in sections.keys() %}
+<a href="#{{section}}">{{section}}</a> -
+{% endfor %}
+Last updated [{{ now }}] by {{ whoami }}
+</i><br>
+<p>
+
+{% for sec_name, sec_values in sections.iteritems() %}
+<a name="{{sec_name}}"><h2>{{ sec_name }}</h2></a>
+<table>
+ <tr><th>Memory Address
+ <th>Bits
+ <th>Mode
+ <th>Shortname
+ <th>What
+{% for val in sec_values %}
+ <tr><td>{{ val.addr_pp() }}
+ <td>{{ val.bits }}
+ <td>{{ val.mode }}
+ <td>{{ val.slug }}
+ <td>{{ val.description }}
+{% endfor %}
+</table>{% endfor %}
+
+</body></html>
diff --git a/templates/minimal.rst.tmpl b/templates/minimal.rst.tmpl
new file mode 100644
index 0000000..0ad74df
--- /dev/null
+++ b/templates/minimal.rst.tmpl
@@ -0,0 +1,23 @@
+=========================================================================
+{{name}} Memory Map
+=========================================================================
+
+{% for sec_name, sec_values in sections.iteritems() %}
+{{sec_name}}
+---------------------------------------------------------
+
+.. list-table::
+ :widths: 10 5 5 15 55
+ :header-rows: 1
+
+ * - Address
+ - Bits
+ - Mode
+ - Shortname
+ - What {% for val in sec_values %}
+ * - ``{{ val.addr_pp() }}``
+ - {{ val.bits }}
+ - ``{{ val.mode }}``
+ - {{ val.slug }}
+ - {{ val.description }} {% endfor %}
+{% endfor %}