diff --git a/axi_intercon_gen.py b/axi_intercon_gen.py new file mode 100644 index 0000000..8c2e6e8 --- /dev/null +++ b/axi_intercon_gen.py @@ -0,0 +1,407 @@ +#!/usr/bin/env python +import math +import sys +from collections import OrderedDict, defaultdict +import yaml + +from verilogwriter import Signal, Wire, Instance, ModulePort, Port, VerilogWriter + +if sys.version[0] == '2': + + math.log2 = lambda x : math.log(x, 2) + +class Widths: + addr = 0 + user = 0 + data = 0 + max_id = 0 + +def axi_signals(w, id_width): + signals = [ + ("awid" , False, id_width), + ("awaddr" , False, w.addr ), + ("awlen" , False, 8 ), + ("awsize" , False, 3 ), + ("awburst" , False, 2 ), + ("awlock" , False, 0 ), + ("awcache" , False, 4 ), + ("awprot" , False, 3 ), + ("awregion", False, 4), + ("awuser" , False, w.user), + ("awqos" , False, 4), + ("awvalid" , False, 0), + ("awready" , True , 0), + + ("arid" , False, id_width), + ("araddr" , False, w.addr), + ("arlen" , False, 8), + ("arsize" , False, 3), + ("arburst" , False, 2), + ("arlock" , False, 0), + ("arcache" , False, 4), + ("arprot" , False, 3), + ("arregion", False, 4), + ("aruser" , False, w.user), + ("arqos" , False, 4), + ("arvalid" , False, 0), + ("arready" , True , 0), + + ("wdata" , False, w.data), + ("wstrb" , False, w.data//8), + ("wlast" , False, 0), + ("wuser" , False, w.user), + ("wvalid", False, 0), + ("wready", True , 0), + + ("bid" , True , id_width), + ("bresp" , True , 2), + ("bvalid", True , 0), + ("buser" , True, w.user), + ("bready", False, 0), + + ("rid" , True , id_width), + ("rdata" , True , w.data), + ("rresp" , True , 2), + ("rlast" , True , 0), + ("ruser" , True, w.user), + ("rvalid", True , 0), + ("rready", False, 0), + ] + return signals + +def module_ports(w, intf, id_width, is_input): + ports = [] + for s in axi_signals(w, id_width): + if s[0].endswith('user') and not w.user: + continue + if s[0].startswith('aw') and intf.read_only: + continue + if s[0].startswith('w') and intf.read_only: + continue + if s[0].startswith('b') and intf.read_only: + continue + prefix = 'o' if is_input == s[1] else 'i' + ports.append(ModulePort("{}_{}_{}".format(prefix, intf.name, s[0]), + 'output' if is_input == s[1] else 'input', + s[2])) + return ports + +def assigns(w, max_idw, masters, slaves): + raw = '\n' + i = 0 + for m in masters: + for s in axi_signals(w, m.idw): + if s[0].endswith('user') and not w.user: + continue + if s[1]: + if m.read_only and (s[0].startswith('aw') or s[0].startswith('w') or s[0].startswith('b')): + continue + src = "slave_{}[{}]".format(s[0], i) + if s[0] in ['bid', 'rid'] and m.idw < max_idw: + src = src+'[{}:0]'.format(m.idw-1) + raw += " assign o_{}_{} = {};\n".format(m.name, s[0], src) + else: + src = "i_{}_{}".format(m.name, s[0]) + if s[0] in ['arid', 'awid'] and m.idw < max_idw: + src = "{"+ str(max_idw-m.idw)+"'d0,"+src+"}" + if m.read_only and (s[0].startswith('aw') or s[0].startswith('w') or s[0].startswith('b')): + if s[0] in ['awid']: + _w = max_idw + else: + _w = max(1,s[2]) + src = "{}'d0".format(_w) + raw += " assign slave_{}[{}] = {};\n".format(s[0], i, src) + raw += " assign connectivity_map[{}] = {}'b{};\n".format(i, len(slaves), '1'*len(slaves)) + i += 1 + + raw += '\n' + + i = 0 + for m in slaves: + for s in axi_signals(w, max_idw): + if s[0].endswith('user') and not w.user: + continue + if s[1]: + raw += " assign master_{}[{}] = i_{}_{};\n".format(s[0], i, m.name, s[0]) + else: + raw += " assign o_{}_{} = master_{}[{}];\n".format(m.name, s[0], s[0], i) + raw += " assign start_addr[0][{}] = 32'h{:08x};\n".format(i, m.offset) + raw += " assign end_addr[0][{}] = 32'h{:08x};\n".format(i, m.offset+m.size-1) + i += 1 + raw += " assign valid_rule[0] = {}'b{};\n".format(len(slaves), '1'*len(slaves)) + return raw + +def instance_ports(w, id_width, masters, slaves): + ports = [Port('clk' , 'clk'), + Port('rst_n', 'rst_n'), + Port('test_en_i', "1'b0"), + Port('slave_awatop_i', "{}'d0".format(len(masters)*6)), + Port('master_awatop_o', '')] + for s in axi_signals(w, id_width): + suffix = 'o' if s[1] else 'i' + name = "slave_{}_{}".format(s[0], suffix) + if s[0].endswith('user') and not w.user: + value = "" if s[1] else "{}'d0".format(len(masters)) + else: + value = "slave_{}".format(s[0]) + ports.append(Port(name, value)) + + for s in axi_signals(w, id_width): + suffix = 'i' if s[1] else 'o' + name = "master_{}_{}".format(s[0], suffix) + if s[0].endswith('user') and not w.user: + value = "{}'d0".format(len(slaves)) if s[1] else "" + else: + value = "master_{}".format(s[0]) + ports.append(Port(name, value)) + + value = '{' + ', '.join(["32'h{start:08x}".format(start=s.offset) for s in slaves]) + '}' + ports.append(Port('cfg_START_ADDR_i', 'start_addr'))#value)) + + value = '{' + ', '.join(["32'h{end:08x}".format(end=s.offset+s.size-1) for s in slaves]) + '}' + ports.append(Port('cfg_END_ADDR_i', 'end_addr'))#value)) + ports.append(Port('cfg_valid_rule_i', "valid_rule")) + ports.append(Port('cfg_connectivity_map_i', "connectivity_map")) + return ports + +def template_ports(w, intf, id_width, is_input): + ports = [] + for s in axi_signals(w, id_width): + if s[0].endswith('user') and not w.user: + continue + if intf.read_only and (s[0].startswith('aw') or s[0].startswith('w') or s[0].startswith('b')): + continue + port_name = "{}_{}".format(intf.name, s[0]) + prefix = 'o' if is_input == s[1] else 'i' + ports.append(Port("{}_{}".format(prefix, port_name), port_name)) + return ports + +def template_wires(w, intf, id_width): + wires = [] + for s in axi_signals(w, id_width): + if s[0].endswith('user') and not w.user: + continue + if intf.read_only and (s[0].startswith('aw') or s[0].startswith('w') or s[0].startswith('b')): + continue + wires.append(Wire("{}_{}".format(intf.name, s[0]), s[2])) + return wires + +class Master: + def __init__(self, name, d=None): + self.name = name + self.slaves = [] + self.idw = 1 + self.read_only = False + if d: + self.load_dict(d) + + def load_dict(self, d): + for key, value in d.items(): + if key == 'slaves': + # Handled in file loading, ignore here + continue + if key == 'id_width': + self.idw = value + elif key == 'read_only': + self.read_only = value + else: + print(key) + raise UnknownPropertyError( + "Unknown property '%s' in master section '%s'" % ( + key, self.name)) + +class Slave: + def __init__(self, name, d=None): + self.name = name + self.masters = [] + self.offset = 0 + self.size = 0 + self.mask = 0 + self.read_only = False + if d: + self.load_dict(d) + + def load_dict(self, d): + for key, value in d.items(): + if key == 'offset': + self.offset = value + elif key == 'size': + self.size = value + self.mask = ~(self.size-1) & 0xffffffff + elif key == 'read_only': + self.read_only = value + else: + raise UnknownPropertyError( + "Unknown property '%s' in slave section '%s'" % ( + key, self.name)) + +class Parameter: + def __init__(self, name, value): + self.name = name + self.value = value + + + +class AxiIntercon: + def __init__(self, name, config_file): + self.verilog_writer = VerilogWriter(name) + self.template_writer = VerilogWriter(name); + self.name = name + d = OrderedDict() + self.slaves = [] + self.masters = [] + import yaml + + def ordered_load(stream, Loader=yaml.Loader, object_pairs_hook=OrderedDict): + class OrderedLoader(Loader): + pass + def construct_mapping(loader, node): + loader.flatten_mapping(node) + return object_pairs_hook(loader.construct_pairs(node)) + OrderedLoader.add_constructor( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, + construct_mapping) + return yaml.load(stream, OrderedLoader) + data = ordered_load(open(config_file)) + + config = data['parameters'] + #files_root = data['files_root'] + self.vlnv = data['vlnv'] + + for k,v in config['masters'].items(): + print("Found master " + k) + self.masters.append(Master(k,v)) + #d[k] = v['slaves'] + for k,v in config['slaves'].items(): + print("Found slave " + k) + self.slaves.append(Slave(k,v)) + + #for master in self.masters: + + #Create master/slave connections + #for master, slaves in d.items(): + # for slave in slaves: + # self.masters[master].slaves += [self.slaves[slave]] + # #self.slaves[slave].masters += [self.masters[master]] + + self.output_file = config.get('output_file', 'axi_intercon.v') + + def _dump(self): + print("*Masters*") + for master in self.masters.values(): + print(master.name) + for slave in master.slaves: + print(' ' + slave.name) + + print("*Slaves*") + for slave in self.slaves.values(): + print(slave.name) + for master in slave.masters: + print(' ' + master.name) + + def write(self): + w = Widths() + w.addr = 32 + w.data = 64 + w.user = 0 + + max_idw = max([m.idw for m in self.masters]) + max_sidw = max_idw + int(math.ceil(math.log2(len(self.masters)))) + file = self.output_file + + _template_ports = [Port('clk' , 'clk'), + Port('rst_n', 'rstn')] + template_parameters = [] + + #Module header + self.verilog_writer.add(ModulePort('clk' , 'input')) + self.verilog_writer.add(ModulePort('rst_n', 'input')) + for master in self.masters: + for port in module_ports(w, master, master.idw, True): + self.verilog_writer.add(port) + for wire in template_wires(w, master, master.idw): + self.template_writer.add(wire) + _template_ports += template_ports(w, master, master.idw, True) + + for slave in self.slaves: + for port in module_ports(w, slave, max_sidw, False): + self.verilog_writer.add(port) + for wire in template_wires(w, slave, max_sidw): + self.template_writer.add(wire) + _template_ports += template_ports(w, slave, max_sidw, False) + + raw = "" + + nm = len(self.masters) + for s in axi_signals(w, max_idw): + if s[0].endswith('user') and not w.user: + continue + raw += " wire [{}:0]".format(nm-1) + if s[2]: + raw += "[{}:0]".format(s[2]-1) + raw += " slave_{};\n".format(s[0]) + ns = len(self.slaves) + for s in axi_signals(w, max_sidw): + if s[0].endswith('user') and not w.user: + continue + raw += " wire [{}:0]".format(ns-1) + if s[2]: + raw += "[{}:0]".format(s[2]-1) + raw += " master_{};\n".format(s[0]) + + raw += """ + wire [0:0][{ns}:0][{aw}:0] start_addr; + wire [0:0][{ns}:0][{aw}:0] end_addr; + wire [0:0][{ns}:0] valid_rule; + wire [{nm}:0][{ns}:0] connectivity_map; + """.format(nm=nm-1,aw=w.addr-1, ns=ns-1) + + raw += assigns(w, max_idw, self.masters, self.slaves) + + self.verilog_writer.raw = raw + parameters = [Parameter('AXI_ADDRESS_W', w.addr), + Parameter('AXI_DATA_W' , w.data), + Parameter('N_MASTER_PORT', len(self.slaves)), + Parameter('N_SLAVE_PORT' , len(self.masters)), + Parameter('AXI_ID_IN' , max_idw), + Parameter('AXI_USER_W' , w.user or 1), + Parameter('N_REGION' , 1), + ] + ports = instance_ports(w, max_idw, self.masters, self.slaves) + + self.verilog_writer.add(Instance('axi_node', + 'axi_node', + parameters, + ports)) + + self.template_writer.add(Instance(self.name, + self.name, + template_parameters, + _template_ports)) + + self.verilog_writer.write(file) + self.template_writer.write(file+'h') + + core_file = self.vlnv.split(':')[2]+'.core' + vlnv = self.vlnv + with open(core_file, 'w') as f: + f.write('CAPI=2:\n') + files = [{file : {'file_type' : 'systemVerilogSource'}}, + {file+'h' : {'is_include_file' : True, + 'file_type' : 'verilogSource'}} + ] + coredata = {'name' : vlnv, + 'targets' : {'default' : {}}, + } + + coredata['filesets'] = {'rtl' : {'files' : files}} + coredata['targets']['default']['filesets'] = ['rtl'] + + f.write(yaml.dump(coredata)) + +if __name__ == "__main__": + name = "axi_intercon" + g = AxiIntercon(name, sys.argv[1]) + print("="*80) + g.write() + diff --git a/axi_node.core b/axi_node.core new file mode 100644 index 0000000..4f71327 --- /dev/null +++ b/axi_node.core @@ -0,0 +1,97 @@ +CAPI=2: + +name : pulp-platform.org::axi_node:1.1.1-r6 + +filesets: + core: + files: + - src/apb_regs_top.sv + - src/axi_address_decoder_AR.sv + - src/axi_address_decoder_AW.sv + - src/axi_address_decoder_BR.sv + - src/axi_address_decoder_BW.sv + - src/axi_address_decoder_DW.sv + - src/axi_AR_allocator.sv + - src/axi_AW_allocator.sv + - src/axi_BR_allocator.sv + - src/axi_BW_allocator.sv + - src/axi_DW_allocator.sv + - src/axi_multiplexer.sv + - src/axi_node_arbiter.sv + - src/axi_node.sv + - src/axi_regs_top.sv + - src/axi_request_block.sv + - src/axi_response_block.sv + file_type : systemVerilogSource + depend : + - ">=pulp-platform.org::common_cells:1.7.5" + #Required since axi_B(R/W)_allocator.sv uses axi_pkg::RESP_DECERR + - ">=pulp-platform.org::axi:0.4.5" + + interface: + files: + - src/axi_node_intf_wrap.sv + - src/axi_node_wrap_with_slices.sv + file_type : systemVerilogSource + +generators: + axi_intercon_gen: + interpreter: python + command: axi_intercon_gen.py + description: Generate a wrapper around PULP AXI Node interconnect + usage: | + axi_intercon_gen wraps AXI Node by expanding arrays of signals into + human-readable buses. Memory map can be set with generator parameters. + It will also generate a verilog include file containing the wire + definitions and module instantiation which can be `included in the + module where the interconnect wrapper is intended to be used. + + Parameters: + masters: A dictionary where each key names a master interface connecting + to the interconnect and the associated value contains + configuration for that interface. + + id_width (int): Width of the id signals for the master + + slaves: A dictionary where each key names a slave interface connecting + to the interconnect and the associated value contains + configuration for that interface. The following configuration + keys are defined + + offset (int): Base address for the slave + size (int): Size of the allocated memory map for the slave + + Example usage: + The following config will generate an interconnect wrapper to which two + AXI4 master interfaces (dma and ibus) with different id widths are + connected, and connects downstream to three AXI4 slaves (rom, gpio, ram) + + soc_intercon: + generator: axi_intercon_gen + parameters: + masters: + dma: + id_width : 1 + ibus: + id_width : 2 + slaves: + ram: + offset : 0 + size: 0x10000000 + gpio: + offset: 0x91000000 + size: 0x1000 + rom: + offset : 0xffff0000 + size : 32768 + +targets: + default: + filesets : [core, interface] + + lint: + default_tool : verilator + filesets : [core] + tools: + verilator: {mode : lint-only} + toplevel: axi_node diff --git a/src/axi_node_arbiter.sv b/src/axi_node_arbiter.sv index 8f72b1c..cf8d86b 100644 --- a/src/axi_node_arbiter.sv +++ b/src/axi_node_arbiter.sv @@ -27,17 +27,14 @@ module axi_node_arbiter #( input logic oup_ready_i ); - typedef struct packed { - logic [AUX_WIDTH-1:0] aux; - logic [ID_WIDTH-1:0] id; - } axi_meta_t; + typedef logic [AUX_WIDTH+ID_WIDTH-1:0] axi_meta_t; axi_meta_t [N_MASTER-1:0] inp_meta; axi_meta_t oup_meta; for (genvar i = 0; i < N_MASTER; i++) begin: gen_inp_meta - assign inp_meta[i].aux = inp_aux_i[i]; - assign inp_meta[i].id = inp_id_i[i]; + assign inp_meta[i][AUX_WIDTH+ID_WIDTH-1:ID_WIDTH] = inp_aux_i[i]; + assign inp_meta[i][ID_WIDTH-1:0] = inp_id_i[i]; end stream_arbiter #( @@ -54,8 +51,8 @@ module axi_node_arbiter #( .oup_ready_i (oup_ready_i) ); - assign oup_id_o = oup_meta.id; - assign oup_aux_o = oup_meta.aux; + assign oup_id_o = oup_meta[ID_WIDTH-1:0]; + assign oup_aux_o = oup_meta[AUX_WIDTH+ID_WIDTH-1:ID_WIDTH]; // pragma translate_off `ifndef VERILATOR diff --git a/src/axi_node_wrap_with_slices.sv b/src/axi_node_wrap_with_slices.sv index 4f1c302..6255e6d 100644 --- a/src/axi_node_wrap_with_slices.sv +++ b/src/axi_node_wrap_with_slices.sv @@ -33,8 +33,8 @@ module axi_node_wrap_with_slices #( input logic clk, input logic rst_n, input logic test_en_i, - AXI_BUS.Slave slave [NB_SLAVE-1:0], - AXI_BUS.Master master [NB_MASTER-1:0], + AXI_BUS slave [NB_SLAVE-1:0], + AXI_BUS master [NB_MASTER-1:0], // Memory map input logic [NB_REGION-1:0][NB_MASTER-1:0][AXI_ADDR_WIDTH-1:0] start_addr_i, input logic [NB_REGION-1:0][NB_MASTER-1:0][AXI_ADDR_WIDTH-1:0] end_addr_i, diff --git a/verilogwriter.py b/verilogwriter.py new file mode 100644 index 0000000..10c3406 --- /dev/null +++ b/verilogwriter.py @@ -0,0 +1,103 @@ +class Signal(object): + def __init__(self, name, width=0, low=0, asc=False, vec=0): + self.name = name + self.width=width + self.low = low + self.asc = asc + + def range(self): + if self.width > 0: + l = self.width+self.low-1 + r = self.low + if self.asc: + return '['+str(r)+':'+str(l)+']' + else: + return '['+str(l)+':'+str(r)+']' + return '' + +class Wire(Signal): + def write(self, width): + return 'wire{range} {name};\n'.format(range=self.range().rjust(width), name=self.name) + +class Port: + def __init__(self, name, value): + self.name = name + self.value = value + +class ModulePort(Signal): + def __init__(self, name, dir, width=0, low=0, asc=False): + super(ModulePort, self).__init__(name, width, low, asc) + self.dir = dir + + def write(self, range_width=0): + return '{dir} wire {range} {name}'.format(dir=self.dir.ljust(6), range=self.range().rjust(range_width), name=self.name) + +class Instance: + def __init__(self, module, name, parameters, ports): + self.module = module + self.name = name + self.parameters = parameters + self.ports = ports + + def write(self): + s = self.module + if self.parameters: + max_len = max([len(p.name) for p in self.parameters]) + s += '\n #(' + s += ',\n '.join(['.' + p.name.ljust(max_len) +' (' + str(p.value) + ')' for p in self.parameters]) + s += ')\n' + s += ' ' + self.name + + if self.ports: + s += '\n (' + max_len = max([len(p.name) for p in self.ports]) + s += ',\n '.join(['.' + p.name.ljust(max_len) +' (' + str(p.value) + ')' for p in self.ports]) + s += ')' + s += ';\n' + return s + +class VerilogWriter: + raw = "" + def __init__(self, name): + self.name = name + self.instances = [] + self.ports = [] + self.wires = [] + + def add(self, obj): + if isinstance(obj, Instance): + self.instances += [obj] + elif isinstance(obj, ModulePort): + self.ports += [obj] + elif isinstance(obj, Wire): + self.wires += [obj] + else: + raise Exception("Invalid type!" + str(obj)) + + def write(self, file=None): + s = ("// THIS FILE IS AUTOGENERATED BY axi_intercon_gen\n" + "// ANY MANUAL CHANGES WILL BE LOST\n") + if self.ports: + s += "`default_nettype none\n" + s += "module {name}\n".format(name=self.name) + max_len = max([len(p.range()) for p in self.ports]) + s += ' (' + s += ',\n '.join([p.write(max_len) for p in self.ports]) + s += ')' + s += ';\n\n' + if self.wires: + max_len = max([len(w.range()) for w in self.wires]) + for w in self.wires: + s += w.write(max_len + 1) + s +='\n' + s += self.raw + for i in self.instances: + s += i.write() + s += '\n' + if self.ports: + s += 'endmodule\n' + if file is None: + return s + else: + f = open(file,'w') + f.write(s)