diff options
author | bryan newbold <bnewbold@leaflabs.com> | 2014-02-26 13:16:42 -0500 |
---|---|---|
committer | bryan newbold <bnewbold@leaflabs.com> | 2014-02-26 13:16:42 -0500 |
commit | 500cbce69dee527babd6a9472643ebde5b7adb1e (patch) | |
tree | 4a642c5d1965a931083e5fdf7cc31be138ca64e1 | |
parent | 0530e8994ea274e7fdf12267b4e7aeba32e97a4a (diff) | |
download | axi-lite-gen-500cbce69dee527babd6a9472643ebde5b7adb1e.tar.gz axi-lite-gen-500cbce69dee527babd6a9472643ebde5b7adb1e.zip |
commit WIP to date
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | example.csv | 17 | ||||
-rw-r--r-- | output/.stub | 0 | ||||
-rw-r--r-- | pack_array.v | 16 | ||||
-rwxr-xr-x | parse.py | 280 | ||||
-rw-r--r-- | syntax.rst | 38 | ||||
-rw-r--r-- | templates/minimal.html.tmpl | 41 | ||||
-rw-r--r-- | templates/minimal.rst.tmpl | 23 |
8 files changed, 416 insertions, 0 deletions
@@ -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 %} |