diff options
author | bryan newbold <bnewbold@leaflabs.com> | 2014-02-26 19:42:51 -0500 |
---|---|---|
committer | bryan newbold <bnewbold@leaflabs.com> | 2014-02-26 19:42:51 -0500 |
commit | 6cc0f5ff9e4de203572ef6a66df82aca97bb6544 (patch) | |
tree | 398f13f7a2d3a35af8a91f497c6dc96f88b7adcd | |
parent | 37e46745e248b0136b16aa40bed9ea20df16769d (diff) | |
download | axi-lite-gen-6cc0f5ff9e4de203572ef6a66df82aca97bb6544.tar.gz axi-lite-gen-6cc0f5ff9e4de203572ef6a66df82aca97bb6544.zip |
commit day's work
-rwxr-xr-x | parse.py | 381 | ||||
-rw-r--r-- | templates/headers.h.tmpl | 20 | ||||
-rw-r--r-- | templates/minimal.html.tmpl | 20 | ||||
-rw-r--r-- | templates/minimal.rst.tmpl | 9 | ||||
-rw-r--r-- | templates/partial_axi_lite_slave.v.tmpl | 141 | ||||
-rw-r--r-- | templates/stub.v.tmpl | 45 |
6 files changed, 467 insertions, 149 deletions
@@ -17,8 +17,13 @@ import os import jinja2 -AXI_ADDR_BITS = 16 -WORD_BITS = 32 +AXI_DATA_WIDTH = 32 +AXI_ADDR_WIDTH = 16 +AXI_ADDR_MSB = AXI_ADDR_WIDTH-1 +AXI_ADDR_LSB = 2 + +required_fields = ('word_index', 'bits', 'mode', 'section', 'slug', + 'default', 'description') def parse_slug(s): pre = s.split('[')[0] @@ -32,6 +37,7 @@ def parse_slug(s): assert(post >= 0) return (pre, post) + def str2val(s, bits): """ Strip '_' characters (eg, 0x1111_2222). @@ -50,6 +56,7 @@ def str2val(s, bits): assert(v >= 0 and v < 2**bits) return v + class Value(): index = None bits = None @@ -61,8 +68,9 @@ class Value(): description = None mode = None addr = None + signed = False - def offset(self, offset): + def set_offset(self, offset): self.addr = offset + (4 * self.index) def addr_pp(self): @@ -73,7 +81,7 @@ class Value(): # TODO: input validation/transforms self.index = int(word_index) assert(self.index >= 0) - assert(self.index <= (2**AXI_ADDR_BITS - 1)) + assert(self.index <= (2**AXI_ADDR_WIDTH - 1)) if bits in [None, '']: raise ValueError("Bits not defined") @@ -101,18 +109,69 @@ class Value(): def __str__(self): return "<Value: %s>" % str(self.__dict__) + def hdlwidth(self): + if self.bits == 1: + return "[0]" + else: + return "[%d:0] " % (self.bits - 1) + + def pphdlwidth(self): + if self.bits == 1: + return "" + else: + return "[%d:0] " % (self.bits - 1) + + def ppdefault(self): + return "%d'h%X" % (self.bits, self.default) + + def word_list(self): + l = [] + b = self.bits + bottom = 0 + a = self.index + span = None + while b > 0: + if b < 32: + if (self.bits == 1): + span = "" + else: + span = "[%d:%d]" % (bottom+b-1, bottom) + l.append((a, "{%d'd0, %s%s}" % (32-b, self.slug, span), span)) + else: + if (self.bits == 1): + span = "" + else: + span = "[%d:%d]" % (bottom+31, bottom) + l.append((a, "%s%s" % (self.slug, span), span)) + a += 1 + b -= 32 + bottom += 32 + return l + + def ctype(self): + if self.bits <= 32: + return self.signed and "int32_t" or "uint32_t" + elif self.bits <= 64: + return self.signed and "int64_t" or "uint64_t" + else: + raise ValueError("Can't represent %d bits in C... ?" % self.bits) + + class Register(Value): read = False write = False + class Parameter(Value): - pass + def ppslug(self): + return self.slug.upper() + def check_overlaps(l): rangelist = [] for val in l: # TODO: also handle larger ranges - this = (val.index, val.index + ((val.bits-1)/WORD_BITS)) + this = (val.index, val.index + ((val.bits-1)/AXI_DATA_WIDTH)) inserted = False for i in range(len(rangelist)): that = rangelist[i] @@ -127,7 +186,12 @@ def check_overlaps(l): if not inserted: rangelist.append(this) + def check_names(l): + """ + Checks that all section+slug combinations are unique (no duplicates) + 'l' should be the set of all values, in any order. + """ names = [] n = None for val in l: @@ -139,142 +203,185 @@ def check_names(l): 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)) +def check_gaps(l): + """ + Checks for gaps between memory map locations within a section. + Assumes 'l' is a list of values in a section, already sorted by index. + """ + n = None + for v in l: + if n is not None: + if v.index != n: + raise Exception("Gap between values! Oh no! At: %s.%s (n=%d)" + % (v.section, v.slug, n)) + n = v.index + 1 + (v.bits-1)/32 - 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") +def error(s="unspecified"): + sys.stderr.write(str(s) + '\n') + sys.exit(-1) -#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") +def parse(): + print("------- START READ") + f = open('example.csv', 'r') + reader = csv.DictReader(f) -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") + registers = [] + parameters = [] + mode = None -print("------- DONE!") + 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'].lower() + try: + if mode == 'p': + p = Parameter(**line) + parameters.append(p) + elif mode in ['r', 'w', 'rw', 'wr']: + r = Register(**line) + r.read = 'r' in mode + r.write = 'w' in mode + 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.set_offset(offset) + for p in parameters: + p.set_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) + check_gaps(sections[key]) + + print("------- END READ") + return registers, parameters, sections + + +def output(registers, parameters, sections): + settings = { + 'stub_axi_nets': True, + 'stub_nets': True, + } + 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, + AXI_DATA_WIDTH=AXI_DATA_WIDTH, + AXI_ADDR_WIDTH=AXI_ADDR_WIDTH, + AXI_ADDR_MSB=AXI_ADDR_MSB, + AXI_ADDR_LSB=AXI_ADDR_LSB, + settings=settings) + + def guess_autoescape(template_name): + """Only auto-escape HTML documents""" + if template_name is None: + return False + if 'html' in template_name.lower(): + return True + else: + return False + + # TODO: + # jinja2.ChoiceLoader + # jinja2.PackageLoader + env = jinja2.Environment(loader=jinja2.FileSystemLoader('templates'), + lstrip_blocks=True, + trim_blocks=True, + autoescape=guess_autoescape, + extensions=['jinja2.ext.autoescape']) + #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 + """ + t = env.get_template('partial_axi_lite_slave.v.tmpl') + out_f = open('output/axi_lite_slave_%s.v' % context['name'], 'w') + out_f.write(t.render(**context)) + out_f.close() + + t = env.get_template('stub.v.tmpl') + out_f = open('output/%s_stub.v' % context['name'], 'w') + out_f.write(t.render(**context)) + out_f.close() + print("------- END HDL") + + #print("------- START C_HEADER") + """ + just structs for parameters/registers + """ + t = env.get_template('headers.h.tmpl') + out_f = open('output/%s_headers.h' % context['name'], 'w') + out_f.write(t.render(**context)) + out_f.close() + #print("------- END C_HEADER") + + print("------- START HTML") + t = env.get_template('minimal.html.tmpl') + out_f = open('output/%s.html' % context['name'], '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/%s.rst' % context['name'], 'w') + out_f.write(t.render(**context)) + out_f.close() + print("------- END RST") + + print("------- DONE!") + +def main(): + r, p, s = parse() + output(r,p,s) + +if __name__=="__main__": + main() diff --git a/templates/headers.h.tmpl b/templates/headers.h.tmpl new file mode 100644 index 0000000..ad6a0f7 --- /dev/null +++ b/templates/headers.h.tmpl @@ -0,0 +1,20 @@ +#ifndef {{ name.upper() }}_MAP_H +#define {{ name.upper() }}_MAP_H + +/* {{ name }} Memory Map Structs */ + +/* WARNING: Currently assumes perfect packing */ + +{% for sec_name, values in sections.iteritems() %} +typedef struct { +{% for val in values %} + {{ val.ctype() }} {{ val.slug }}; +{% endfor %} +} {{ name }}_{{ sec_name }}_map; +#define {{ name }}_{{ sec_name }}_offset {{ values[0].addr_pp() }} +/* Usage (?) + * void *uint32_t {{ name }}_{{ sec_name }}_map... + */ +{% endfor %} + +#endif diff --git a/templates/minimal.html.tmpl b/templates/minimal.html.tmpl index 98042ee..21530b1 100644 --- a/templates/minimal.html.tmpl +++ b/templates/minimal.html.tmpl @@ -24,17 +24,17 @@ Last updated [{{ now }}] by {{ whoami }} {% 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 + <tr><th>Memory Address</th> + <th>Bits</th> + <th>Mode</th> + <th>Shortname</th> + <th>What</th></tr> {% for val in sec_values %} - <tr><td>{{ val.addr_pp() }} - <td>{{ val.bits }} - <td>{{ val.mode }} - <td>{{ val.slug }} - <td>{{ val.description }} + <tr><td><pre>{{ val.addr_pp() }}</pre></td> + <td><pre>{{ val.bits }}</pre></td> + <td><pre>{{ val.mode }}</pre></td> + <td>{{ val.slug }}</td> + <td>{{ val.description }}</td></tr> {% endfor %} </table>{% endfor %} diff --git a/templates/minimal.rst.tmpl b/templates/minimal.rst.tmpl index 0ad74df..167aa14 100644 --- a/templates/minimal.rst.tmpl +++ b/templates/minimal.rst.tmpl @@ -3,6 +3,7 @@ ========================================================================= {% for sec_name, sec_values in sections.iteritems() %} + {{sec_name}} --------------------------------------------------------- @@ -14,10 +15,14 @@ - Bits - Mode - Shortname - - What {% for val in sec_values %} + - What + {% for val in sec_values %} * - ``{{ val.addr_pp() }}`` - {{ val.bits }} - ``{{ val.mode }}`` - {{ val.slug }} - - {{ val.description }} {% endfor %} + - {{ val.description }} + {% endfor %} + {% endfor %} + diff --git a/templates/partial_axi_lite_slave.v.tmpl b/templates/partial_axi_lite_slave.v.tmpl new file mode 100644 index 0000000..042dc11 --- /dev/null +++ b/templates/partial_axi_lite_slave.v.tmpl @@ -0,0 +1,141 @@ + +// Generate-time parameters: +// AXI_DATA_WIDTH = {{AXI_DATA_WIDTH}} +// AXI_ADDR_WIDTH = {{AXI_ADDR_WIDTH}} +// AXI_ADDR_MSB = {{AXI_ADDR_MSB}} +// AXI_ADDR_LSB = {{AXI_ADDR_LSB}} + +module axi_lite_slave_{{name}} ( + //// AXI I/O Signals + input wire axi_anreset, + input wire [{{AXI_ADDR_WIDTH-1}}:0] axi_awaddr, + input wire axi_awvalid, + output wire s_axi_awready, + input wire [{{AXI_DATA_WIDTH-1}}:0] s_axi_wdata, + input wire [{{AXI_DATA_WIDTH/8-1}}:0] s_axi_wstrb, + input wire s_axi_wvalid, + output wire s_axi_wready, + output wire [1:0] s_axi_bresp, + output wire s_axi_bvalid, + input wire s_axi_bready, + input wire [{{AXI_ADDR_WIDTH-1}}:0] s_axi_araddr, + input wire s_axi_arvalid, + output wire s_axi_arready, + output wire [{{AXI_DATA_WIDTH-1}}:0] s_axi_rdata, + output wire [1:0] s_axi_rresp, + output wire s_axi_rvalid, + input wire s_axi_rready, +{% if registers|length > 0 %} + //// {{ name }} register values +{% for reg in registers %} + {%+ if reg.write %}output reg{% else %}input wire{% endif %} {{ reg.pphdlwidth() }}{{ reg.slug }}, +{% endfor %} +{% endif %} + // axi_clock is last to ensure no trailing comma + input wire axi_clock +); + +{% if parameters|length > 0 %}//// Static Memory Map Values{% endif %} + +{% for param in parameters %} +parameter {{ param.ppslug() }} = {{ param.ppdefault() }}; +{% endfor %} + +//// Register Default Parameters +{% for reg in registers %} +parameter DEFAULT_{{ reg.slug.upper() }} = {{ reg.ppdefault() }}; +{% endfor %} + +//// Memory Mapped Register Initialization +initial begin +{% for reg in registers %} +{% if reg.write %} + {{ reg.slug }} = DEFAULT_{{ reg.slug.upper() }}; +{% endif %} +{% endfor %} +end + +//// AXI Internals +// TODO: +wire slv_reg_wren; +wire slv_reg_rden; +reg [{{AXI_DATA_WIDTH-1}}:0] reg_data_out; +reg [{{AXI_DATA_WIDTH-1}}:0] axi_wdata; +reg [{{AXI_DATA_WIDTH-1}}:0] axi_rdata; +reg [{{AXI_DATA_WIDTH-1}}:0] axi_araddr; + +{# this would be for duplicated sections +genvar k; +generate +for (k = 1; k < 8; k = k + 1) begin: kblock + initial begin + afg_wavetable_select[k] <= 0; + end +end +endgenerate +#} + +// This block handles writes +always @(posedge axi_clock) begin + if (axi_anreset == 1'b0) begin +{% for reg in registers %} +{% if reg.write %} + {{ reg.slug }} <= DEFAULT_{{ reg.slug.upper() }}; +{% endif %} +{% endfor %} + end else begin + if (slv_reg_wren) begin + casex (axi_awaddr[{{AXI_ADDR_MSB-1}}:{{AXI_ADDR_LSB}}]) +{% for reg in registers %} +{% if reg.write %} +{% for word in reg.word_list() %} + 14'd{{ word[0] }}: begin + {{ reg.slug }}{{ word[2] }} <= axi_wdata{{ word[2] }}; + end +{% endfor %} +{% endif %} +{% endfor %} + default: begin + // pass + end + endcase + end else begin + // TODO: doorbells + end + end +end + +// This block handles reads +always @(posedge axi_clock) begin + if (axi_anreset == 1'b0) begin + reg_data_out <= 0; + end else if (slv_reg_rden) begin + // Read address mux + casex ( axi_araddr[{{AXI_ADDR_MSB-1}}:{{AXI_ADDR_LSB}}] ) +{% for reg in registers %} +{% if reg.read %} +{% for word in reg.word_list() %} + 14'd{{ word[0] }}: begin + reg_data_out[31:0] <= {{ word[1] }}; + end +{% endfor %} +{% endif %} +{% endfor %} + default: begin + // pass + end + endcase + end +end + +{# this would be for MEMORY blocks +always @(posedge axi_clock) begin + if (slv_reg_wren) begin + memory_addr <= axi_awaddr[13:2]; + end else begin + wavetables_addr <= axi_araddr[13:2]; + end +end +#} + +endmodule diff --git a/templates/stub.v.tmpl b/templates/stub.v.tmpl new file mode 100644 index 0000000..9363240 --- /dev/null +++ b/templates/stub.v.tmpl @@ -0,0 +1,45 @@ +{% if settings.stub_nets %} + {% for reg in registers %} + {% if reg.write %} + wire {{ reg.pphdlwidth() }}{{ reg.slug }}; + {% else %} + reg {{ reg.pphdlwidth() }}{{ reg.slug }} = {{ reg.ppdefault() }}; + {% endif %} + {% endfor %} + {%+ if parameters|length > 0 %}//// Static Memory Map Values{% endif %} + {% for param in parameters %} + parameter {{ param.ppslug() }} = {{ param.ppdefault() }}; + {% endfor %} +{% endif %} + axi_lite_slave_afg {% if parameters|length > 0 %}#( +{%+ for param in parameters %} + .{{ param.ppslug() }}({{ param.ppslug() if settings.stub_axi_nets }}){% if not loop.last %},{% endif %} + +{% endfor %} + ){%endif%} axi_lite_slave_afg_i ( + //// AXI I/O Signals + // NB: axi_clock comes at end + .axi_anreset({% if settings.stub_axi_nets %}axi_aresetn{% endif %}), + .axi_araddr({% if settings.stub_axi_nets %}axi_slave1_araddr[15:0]{% endif %}), + .axi_arready({% if settings.stub_axi_nets %}axi_slave1_arready{% endif %}), + .axi_arvalid({% if settings.stub_axi_nets %}axi_slave1_arvalid{% endif %}), + .axi_awaddr({% if settings.stub_axi_nets %}axi_slave1_awaddr[15:0]{% endif %}), + .axi_awready({% if settings.stub_axi_nets %}axi_slave1_awready{% endif %}), + .axi_awvalid({% if settings.stub_axi_nets %}axi_slave1_awvalid{% endif %}), + .axi_bready({% if settings.stub_axi_nets %}axi_slave1_bready{% endif %}), + .axi_bresp({% if settings.stub_axi_nets %}axi_slave1_bresp{% endif %}), + .axi_bvalid({% if settings.stub_axi_nets %}axi_slave1_bvalid{% endif %}), + .axi_rdata({% if settings.stub_axi_nets %}axi_slave1_rdata{% endif %}), + .axi_rready({% if settings.stub_axi_nets %}axi_slave1_rready{% endif %}), + .axi_rresp({% if settings.stub_axi_nets %}axi_slave1_rresp{% endif %}), + .axi_rvalid({% if settings.stub_axi_nets %}axi_slave1_rvalid{% endif %}), + .axi_wdata({% if settings.stub_axi_nets %}axi_slave1_wdata{% endif %}), + .axi_wready({% if settings.stub_axi_nets %}axi_slave1_wready{% endif %}), + .axi_wstrb({% if settings.stub_axi_nets %}axi_slave1_wstrb{% endif %}), + .axi_wvalid({% if settings.stub_axi_nets %}axi_slave1_wvalid{% endif %}), + //// Memory Map + {% for reg in registers %} + .{{ reg.slug }}({{ reg.slug if settings.stub_nets }}), + {% endfor %} + .axi_clock({% if settings.stub_axi_nets %}axi_aclk{% endif %}) // axi_clock is last to ensure no trailing comma + ); |