Skip to content

Commit

Permalink
[dttool] Move most of the logic out of the templates and into helpers
Browse files Browse the repository at this point in the history
The templates for the various files were extremely difficult to read,
mixing a lot of logic with a lot of formatting. Furthermore, the same
logic was duplicated between files, which could lead to consistency
issues. The new code moves all the logic to python helpers that
create data structures, the template only focus on formatting and
rendering.

Since the dt uses more complex structures than topgen, e.g. nested
structures with nested array mapping, a complete set of new classes
to deal with this complexity.

Signed-off-by: Amaury Pouly <[email protected]>
  • Loading branch information
pamaury committed Jan 27, 2025
1 parent b4f5bdc commit 7466fb1
Show file tree
Hide file tree
Showing 6 changed files with 543 additions and 377 deletions.
125 changes: 15 additions & 110 deletions util/dtgen/dt_api.c.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,75 +5,31 @@
// Device table API auto-generated by `dtgen`
<%
from topgen.lib import Name, is_top_reggen, is_ipgen

module_types = {m["type"] for m in top["module"]}
module_types = sorted(module_types)
top_name = Name(["top", top["name"]])
irq_base_name = top_name + Name(["plic", "irq", "id"])
top_clock_prefix = Name(["dt", "clock"])

def snake_to_constant_name(s):
return Name.from_snake_case(s).as_c_enum()
%>\

%>

#include "dt/dt_api.h"
#include "hw/top_${top["name"]}/sw/autogen/top_${top["name"]}.h"
#include "hw/top_${helper.top["name"]}/sw/autogen/top_${helper.top["name"]}.h"
#include <stdint.h>

<%
dev_prefix = Name(["dt", "instance", "id"])
irq_prefix = Name(["top", top["name"], "plic", "irq", "id"])
none_irq_name = irq_prefix + Name(["None"])
unknown_peripheral_name = dev_prefix + Name(["unknown"])
irq_table = {none_irq_name: unknown_peripheral_name}
if False:
for module_name, irqs in helper.device_irqs.items():
dev_name = Name.from_snake_case(module_name)
module_type = [m for m in top["module"] if m["name"] == module_name]
assert len(module_type) == 1
for irq in irqs:
irq_name = irq_prefix + Name.from_snake_case(irq)
irq_table[irq_name] = dev_prefix + dev_name
print("{} -> {}".format(irq_name, dev_prefix + dev_name))

for intr in top["interrupt"]:
width = int(intr["width"])
for i in range(width):
name = Name.from_snake_case(intr["name"])
if width > 1:
name += Name([str(i)])
module_name = Name.from_snake_case(intr["module_name"])
irq_table[irq_prefix + name] = dev_prefix + module_name
%>\

top_plic_irq_id_name = Name.from_snake_case("top_" + helper.top["name"] + "_plic_irq_id")
top_plic_irq_id_last = top_plic_irq_id_name + Name(["last"])
top_plic_irq_id_count = top_plic_irq_id_name + Name(["count"])
%>
enum {
kDtIrqIdCount = ${str(len(irq_table.keys()))},
${top_plic_irq_id_count.as_c_enum()} = ${top_plic_irq_id_last.as_c_enum()} + 1,
};

static const dt_instance_id_t instance_from_irq[kDtIrqIdCount] = {
% for irq, device_id in irq_table.items():
[${irq.as_c_enum()}] = ${device_id.as_c_enum()},
% endfor
};
static const ${helper.inst_from_irq_map.render_var_def(Name.from_snake_case("instance_from_irq"), helper.inst_from_irq_values)}

dt_instance_id_t dt_plic_id_to_instance_id(dt_plic_irq_id_t irq) {
if (irq < (dt_plic_irq_id_t)kDtIrqIdCount) {
if (irq <= ${top_plic_irq_id_last.as_c_enum()}) {
return instance_from_irq[irq];
}
return kDtInstanceIdUnknown;
}

static const dt_device_type_t device_type[kDtInstanceIdCount] = {
% for module_name in module_types:
<%
modules = [m for m in top["module"] if m["type"] == module_name]
%>\
% for (dev_index, m) in enumerate(modules):
[${snake_to_constant_name("dt_instance_id_" + m["name"])}] = ${snake_to_constant_name("dt_device_type_" + m["type"])},
% endfor
% endfor
};
static const ${helper.dev_type_map.render_var_def(Name.from_snake_case("device_type"), helper.dev_type_values)}

dt_device_type_t dt_device_type(dt_instance_id_t dev) {
if (dev < kDtInstanceIdCount) {
Expand All @@ -82,69 +38,18 @@ dt_device_type_t dt_device_type(dt_instance_id_t dev) {
return kDtDeviceTypeUnknown;
}

<%
# List all muxed pads directly from the top.
pads = {pad["name"]: pad for pad in top['pinout']['pads'] if pad['connection'] == 'muxed'}

# List direct pads from the pinmux to avoid pins which are not relevant.
for pad in top['pinmux']['ios']:
if pad['connection'] == 'muxed':
continue
name = pad['name']
if pad['width'] > 1:
name += str(pad['idx'])
pads[name] = pad
%>\
/**
* Pad description.
*
* A `dt_pad_t` represents a chip's physical pad.
*/
typedef struct dt_pad_desc {
/** Pad type */
dt_pad_type_t type;
/** For `kDtPadTypeMio` pads: MIO out number. This is the index of the MIO_OUTSEL register
* that controls this pad (or the output part of this pad).
*
* For `kDtPadTypeDio`: DIO pad number. This is the index of the various DIO_PAD_* registers
* that control this pad.
*/
uint16_t mio_out_or_direct_pad;
/** For `kDtPadTypeMio` pads: MIO pad number. This is the value to put in the MIO_PERIPH_INSEL
* registers to connect a peripheral to this pad.
*/
uint16_t insel;
} dt_pad_desc_t;
${helper.pad_struct.render_type_def()}

// Pad descriptions.
static const dt_pad_desc_t dt_pad[kDtPadCount] = {
% for (padname, pad) in pads.items():
<%
if pad["connection"] == "muxed":
pad_type = "Mio"
pad_mio_out_or_direct_pad = "0"
pad_insel = "0"
if pad["port_type"] in ["input", "inout"]:
pad_mio_out_or_direct_pad = snake_to_constant_name("top_{}_pinmux_mio_out_{}".format(top["name"], padname))
if pad["port_type"] in ["output", "inout"]:
pad_insel = snake_to_constant_name("top_{}_pinmux_insel_{}".format(top["name"], padname))
elif pad["connection"] == "direct":
pad_type = "Dio"
pad_mio_out_or_direct_pad = snake_to_constant_name("top_{}_direct_pads_{}".format(top["name"], padname))
pad_insel = "0"
else:
assert pad["connection"] == "manual", "unexpected connection type '{}'".format(pad["connection"])
pad_mio_out_or_direct_pad = "0"
pad_insel = "0"
pad_type = "Unspecified"
%>\
[${snake_to_constant_name("dt_pad_" + padname)}] = {
.type = kDtPadType${pad_type},
.mio_out_or_direct_pad = ${pad_mio_out_or_direct_pad},
.insel = ${pad_insel},
},
% endfor
};
dt_pad_array_name = Name.from_snake_case("dt_pad")
%>
// Pad descriptions.
static const ${helper.pad_dt_map.render_var_def(dt_pad_array_name, helper.pad_dt_values)}

<%
invalid_pad_check = "pad < (dt_pad_t)0 || pad >= kDtPadCount"
Expand Down
20 changes: 1 addition & 19 deletions util/dtgen/dt_api.h.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -126,25 +126,7 @@ typedef enum dt_periph_io_dir {
*
* NOTE The fields of this structure are internal, use the dt_periph_io_* functions to access them.
*/
typedef struct dt_periph_io {
struct {
/** Peripheral I/O type */
dt_periph_io_type_t type;
/** Peripheral I/O direction. */
dt_periph_io_dir_t dir;
/** For `kDtPeriphIoTypeMio`: peripheral input number. This is the index of the MIO_PERIPH_INSEL register
* that controls this peripheral I/O.
*
* For `kDtPeriphIoTypeDio`: DIO pad number. This is the index of the various DIO_PAD_* registers
* that control this peripheral I/O.
*/
uint16_t periph_input_or_direct_pad;
/** For `kDtPeriphIoTypeMio`: peripheral output number. This is the value to put in the MIO_OUTSEL registers
* to connect an output to this peripheral I/O.
*/
uint16_t outsel;
} __internal;
} dt_periph_io_t;
${helper.periph_io_struct.render_type_def()}

/** Tie constantly to zero. */
static const dt_pinmux_outsel_t kDtPinmuxOutselConstantZero = k${top_name.as_camel_case()}PinmuxOutselConstantZero;
Expand Down
158 changes: 4 additions & 154 deletions util/dtgen/dt_ip.c.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,173 +6,23 @@
<%
from topgen.lib import Name, is_top_reggen, is_ipgen

top = helper.top
top_name = Name(["top", top["name"]])
irq_base_name = top_name + Name(["plic", "irq", "id"])
top_clock_prefix = Name(["dt", "clock"])

def snake_to_constant_name(s):
return Name.from_snake_case(s).as_c_enum()

module_name = helper.ip.name
%>\


#include "dt/dt_${module_name}.h"

// Device tables for ${module_name}
<%
modules = helper.inst_map.values()
block = helper.ip
reg_count_enum = Name.from_snake_case(
"dt_{}_reg_block_count".format(module_name)
).as_c_enum()
clk_count_enum = Name.from_snake_case(
"dt_{}_clock_count".format(module_name)
).as_c_enum()
irq_count_enum = Name.from_snake_case(
"dt_{}_irq_count".format(module_name)
).as_c_enum()
reg_count = len(block.reg_blocks)
irq_count = 0
for irq in block.interrupts:
irq_count += irq.bits.width()
irqs = {}
for m in modules:
irqs_packed = [irq
for irq in top["interrupt"] if irq["module_name"] == m["name"]]
irqs[m["name"]] = []
for irq in irqs_packed:
irq_name = irq_base_name + Name.from_snake_case(irq["name"])
irq_width = int(irq["width"])
if irq_width > 1:
for i in range(irq_width):
irqs[m["name"]].append(irq_name + Name([str(i)]))
else:
irqs[m["name"]].append(irq_name)
block_clock_prefix = Name.from_snake_case(f"dt_{module_name}_clock")
block_clocks = {}
for clock in block.clocking.items:
if clock.internal or clock.clock == None or clock.clock == "scan_clk_i":
continue
if clock.clock_base_name == "":
block_clock = block_clock_prefix + Name(["clk"])
else:
block_clock = (block_clock_prefix +
Name.from_snake_case(clock.clock_base_name))
block_clocks[clock.clock] = block_clock
clk_count = len(block_clocks.keys())

inouts, inputs, outputs = block.xputs
device_ports = []
for sig in inputs + outputs + inouts:
device_ports.append(sig.name)

pinmux_info = top["pinmux"]
%>\

/**
* Description of instances.
*/
${helper.inst_struct.render()}
${helper.inst_struct.render_type_def()}

<%
dt_array = (helper.ip_name + Name(["desc"])).as_snake_case()
dt_array_name = helper.ip_name + Name(["desc"])
dt_array = dt_array_name.as_snake_case()
%>

static const dt_${module_name}_desc_t ${dt_array}[${(helper.inst_enum.name + Name(["count"])).as_c_enum()}] = {
% for (dev_index, inst_name) in enumerate(helper.inst_map.keys()):
// Properties of ${m["name"]}
[${(helper.inst_enum.name + inst_name).as_c_enum()}] = {
.inst_id = ${snake_to_constant_name("dt_instance_id_" + m["name"])},
.base_addr = {
% for (rb, addr) in m["base_addrs"].items():
<%
# If the register block name is not specified, it will be serialized as "null"
# in the top-generated Hjson file. In this case, use the default node created
# in dt_ip.h.tpl
if rb == "null":
rb = "core"
# Only consider those address spaces that the hart can talk to.
if "hart" not in addr:
continue
%>\
[${snake_to_constant_name(f"dt_{module_name}_reg_block_{rb}")}] = ${addr["hart"]},
% endfor
},
.clock = {
% for port, clock in m["clock_srcs"].items():
% if port in block_clocks:
<%
if type(clock) is str:
clock_id = top_clock_prefix + Name.from_snake_case(clock)
else:
clock_id = top_clock_prefix + Name.from_snake_case(clock["clock"])
block_clock_enum = block_clocks[port].as_c_enum()
%>\
[${block_clock_enum}] = ${clock_id.as_c_enum()},
% endif
% endfor
},
% if len(block.interrupts) > 0:
## It can happen that a block declares some interrupts but the block is not connected to the PLIC.
## For example, on english breakfast, the rv_timer is directly connected to Ibex and not to the PLIC.
## In this case, we set the first_irq to kDtPlicIrqIdNone.
% if len(irqs[m["name"]]) == 0:
.first_irq = kDtPlicIrqIdNone,
% else:
.first_irq = ${irqs[m["name"]][0].as_c_enum()},
% endif
% endif
% if len(device_ports) > 0:
.periph_io = {
% for port in device_ports:
% for conn in [c for c in pinmux_info["ios"] if c["name"] == m["name"] + "_" + port]:
<%
pin_name = port
if conn["type"] == "input":
pin_dir = "kDtPeriphIoDirIn"
elif conn["type"] == "output":
pin_dir = "kDtPeriphIoDirOut"
else:
assert conn["type"] == "inout", "unexpected connection dir '{}'".format(conn["type"])
pin_dir = "kDtPeriphIoDirInout"
if conn["idx"] != -1:
pin_name += str(conn["idx"])
if conn["connection"] == "muxed":
pin_type = "Mio"
pin_periph_input_or_direct_pad = "0"
pin_outsel = "0"
if conn["type"] in ["input", "inout"]:
pin_periph_input_or_direct_pad = snake_to_constant_name("top_{}_pinmux_peripheral_in_{}_{}".format(top["name"], m["name"], pin_name))
if conn["type"] in ["output", "inout"]:
pin_outsel = snake_to_constant_name("top_{}_pinmux_outsel_{}_{}".format(top["name"], m["name"], pin_name))
elif conn["connection"] == "direct":
pin_type = "Dio"
pin_periph_input_or_direct_pad = snake_to_constant_name("top_{}_direct_pads_{}_{}".format(top["name"], m["name"], pin_name))
pin_outsel = "0"
else:
assert conn["connection"] == "manual", "unexpected connection type '{}'".format(conn["connection"])
pin_periph_input_or_direct_pad = "0"
pin_outsel = "0"
pin_type = "Unspecified"
%>\
[${snake_to_constant_name(f"dt_{module_name}_periph_io_{pin_name}")}] = {
.__internal = {
.type = kDtPeriphIoType${pin_type},
.dir = ${pin_dir},
.periph_input_or_direct_pad = ${pin_periph_input_or_direct_pad},
.outsel = ${pin_outsel},
}
},
% endfor
% endfor
},
% endif
},
% endfor
};
static const ${helper.inst_dt_map.render_var_def(dt_array_name, helper.inst_dt_values)}

dt_${module_name}_t dt_${module_name}_from_instance_id(dt_instance_id_t inst_id) {
if (inst_id >= ${helper.first_inst_id.as_c_enum()} && inst_id <= ${helper.last_inst_id.as_c_enum()}) {
Expand Down
4 changes: 2 additions & 2 deletions util/dtgen/dt_ip.h.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#ifndef ${include_guard}
#define ${include_guard}

#include "dt/dt_api.h"
#include "dt_api.h"
#include <stdint.h>

/**
Expand Down Expand Up @@ -58,7 +58,7 @@ ${helper.clock_enum.render()}
/**
* List of peripheral I/O.
*
* peripheral I/O are guaranteed to be numbered consecutively from 0.
* Peripheral I/O are guaranteed to be numbered consecutively from 0.
*/
${helper.periph_io_enum.render()}

Expand Down
Loading

0 comments on commit 7466fb1

Please sign in to comment.