peakrdl-busdecoder 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. peakrdl_busdecoder/__peakrdl__.py +3 -1
  2. peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py +28 -3
  3. peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv +7 -0
  4. peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py +28 -3
  5. peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv +7 -0
  6. peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_cpuif.py +31 -6
  7. peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_cpuif_flat.py +2 -2
  8. peakrdl_busdecoder/cpuif/axi4lite/{axi4lite_tmpl.sv → axi4_lite_tmpl.sv} +7 -0
  9. peakrdl_busdecoder/cpuif/base_cpuif.py +24 -5
  10. peakrdl_busdecoder/cpuif/fanin_gen.py +11 -0
  11. peakrdl_busdecoder/cpuif/fanin_intermediate_gen.py +142 -0
  12. peakrdl_busdecoder/cpuif/fanout_gen.py +11 -0
  13. peakrdl_busdecoder/decode_logic_gen.py +16 -0
  14. peakrdl_busdecoder/design_state.py +54 -1
  15. peakrdl_busdecoder/exporter.py +3 -1
  16. peakrdl_busdecoder/listener.py +6 -1
  17. peakrdl_busdecoder/module_tmpl.sv +2 -2
  18. peakrdl_busdecoder/struct_gen.py +25 -14
  19. {peakrdl_busdecoder-0.2.0.dist-info → peakrdl_busdecoder-0.4.0.dist-info}/METADATA +1 -1
  20. {peakrdl_busdecoder-0.2.0.dist-info → peakrdl_busdecoder-0.4.0.dist-info}/RECORD +25 -24
  21. /peakrdl_busdecoder/cpuif/axi4lite/{axi4lite_interface.py → axi4_lite_interface.py} +0 -0
  22. {peakrdl_busdecoder-0.2.0.dist-info → peakrdl_busdecoder-0.4.0.dist-info}/WHEEL +0 -0
  23. {peakrdl_busdecoder-0.2.0.dist-info → peakrdl_busdecoder-0.4.0.dist-info}/entry_points.txt +0 -0
  24. {peakrdl_busdecoder-0.2.0.dist-info → peakrdl_busdecoder-0.4.0.dist-info}/licenses/LICENSE +0 -0
  25. {peakrdl_busdecoder-0.2.0.dist-info → peakrdl_busdecoder-0.4.0.dist-info}/top_level.txt +0 -0
@@ -116,7 +116,9 @@ class Exporter(ExporterSubcommandPlugin):
116
116
  type=int,
117
117
  default=1,
118
118
  help="""Maximum depth for address decoder to descend into nested
119
- addressable components. Default is 1.
119
+ addressable components. Value of 0 decodes all levels (infinite depth).
120
+ Value of 1 decodes only top-level children. Value of 2 decodes top-level
121
+ and one level deeper, etc. Default is 1.
120
122
  """,
121
123
  )
122
124
 
@@ -52,8 +52,16 @@ class APB3Cpuif(BaseCpuif):
52
52
  fanin["cpuif_rd_ack"] = "'0"
53
53
  fanin["cpuif_rd_err"] = "'0"
54
54
  else:
55
- fanin["cpuif_rd_ack"] = self.signal("PREADY", node, "i")
56
- fanin["cpuif_rd_err"] = self.signal("PSLVERR", node, "i")
55
+ # Use intermediate signals for interface arrays to avoid
56
+ # non-constant indexing of interface arrays in procedural blocks
57
+ if self.is_interface and node.is_array and node.array_dimensions:
58
+ # Generate array index string [i0][i1]... for the intermediate signal
59
+ array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
60
+ fanin["cpuif_rd_ack"] = f"{node.inst_name}_fanin_ready{array_idx}"
61
+ fanin["cpuif_rd_err"] = f"{node.inst_name}_fanin_err{array_idx}"
62
+ else:
63
+ fanin["cpuif_rd_ack"] = self.signal("PREADY", node, "i")
64
+ fanin["cpuif_rd_err"] = self.signal("PSLVERR", node, "i")
57
65
 
58
66
  return "\n".join(map(lambda kv: f"{kv[0]} = {kv[1]};", fanin.items()))
59
67
 
@@ -62,6 +70,23 @@ class APB3Cpuif(BaseCpuif):
62
70
  if node is None:
63
71
  fanin["cpuif_rd_data"] = "'0"
64
72
  else:
65
- fanin["cpuif_rd_data"] = self.signal("PRDATA", node, "i")
73
+ # Use intermediate signals for interface arrays to avoid
74
+ # non-constant indexing of interface arrays in procedural blocks
75
+ if self.is_interface and node.is_array and node.array_dimensions:
76
+ # Generate array index string [i0][i1]... for the intermediate signal
77
+ array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
78
+ fanin["cpuif_rd_data"] = f"{node.inst_name}_fanin_data{array_idx}"
79
+ else:
80
+ fanin["cpuif_rd_data"] = self.signal("PRDATA", node, "i")
66
81
 
67
82
  return "\n".join(map(lambda kv: f"{kv[0]} = {kv[1]};", fanin.items()))
83
+
84
+ def fanin_intermediate_assignments(
85
+ self, node: AddressableNode, inst_name: str, array_idx: str, master_prefix: str, indexed_path: str
86
+ ) -> list[str]:
87
+ """Generate intermediate signal assignments for APB3 interface arrays."""
88
+ return [
89
+ f"assign {inst_name}_fanin_ready{array_idx} = {master_prefix}{indexed_path}.PREADY;",
90
+ f"assign {inst_name}_fanin_err{array_idx} = {master_prefix}{indexed_path}.PSLVERR;",
91
+ f"assign {inst_name}_fanin_data{array_idx} = {master_prefix}{indexed_path}.PRDATA;",
92
+ ]
@@ -26,6 +26,13 @@ assign {{cpuif.signal("PSLVERR")}} = cpuif_rd_err | cpuif_rd_sel.cpuif_err | cpu
26
26
  // Fanout CPU Bus interface signals
27
27
  //--------------------------------------------------------------------------
28
28
  {{fanout|walk(cpuif=cpuif)}}
29
+ {%- if cpuif.is_interface %}
30
+
31
+ //--------------------------------------------------------------------------
32
+ // Intermediate signals for interface array fanin
33
+ //--------------------------------------------------------------------------
34
+ {{fanin_intermediate|walk(cpuif=cpuif)}}
35
+ {%- endif %}
29
36
 
30
37
  //--------------------------------------------------------------------------
31
38
  // Fanin CPU Bus interface signals
@@ -55,8 +55,16 @@ class APB4Cpuif(BaseCpuif):
55
55
  fanin["cpuif_rd_ack"] = "'0"
56
56
  fanin["cpuif_rd_err"] = "'0"
57
57
  else:
58
- fanin["cpuif_rd_ack"] = self.signal("PREADY", node, "i")
59
- fanin["cpuif_rd_err"] = self.signal("PSLVERR", node, "i")
58
+ # Use intermediate signals for interface arrays to avoid
59
+ # non-constant indexing of interface arrays in procedural blocks
60
+ if self.is_interface and node.is_array and node.array_dimensions:
61
+ # Generate array index string [i0][i1]... for the intermediate signal
62
+ array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
63
+ fanin["cpuif_rd_ack"] = f"{node.inst_name}_fanin_ready{array_idx}"
64
+ fanin["cpuif_rd_err"] = f"{node.inst_name}_fanin_err{array_idx}"
65
+ else:
66
+ fanin["cpuif_rd_ack"] = self.signal("PREADY", node, "i")
67
+ fanin["cpuif_rd_err"] = self.signal("PSLVERR", node, "i")
60
68
 
61
69
  return "\n".join(map(lambda kv: f"{kv[0]} = {kv[1]};", fanin.items()))
62
70
 
@@ -65,6 +73,23 @@ class APB4Cpuif(BaseCpuif):
65
73
  if node is None:
66
74
  fanin["cpuif_rd_data"] = "'0"
67
75
  else:
68
- fanin["cpuif_rd_data"] = self.signal("PRDATA", node, "i")
76
+ # Use intermediate signals for interface arrays to avoid
77
+ # non-constant indexing of interface arrays in procedural blocks
78
+ if self.is_interface and node.is_array and node.array_dimensions:
79
+ # Generate array index string [i0][i1]... for the intermediate signal
80
+ array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
81
+ fanin["cpuif_rd_data"] = f"{node.inst_name}_fanin_data{array_idx}"
82
+ else:
83
+ fanin["cpuif_rd_data"] = self.signal("PRDATA", node, "i")
69
84
 
70
85
  return "\n".join(map(lambda kv: f"{kv[0]} = {kv[1]};", fanin.items()))
86
+
87
+ def fanin_intermediate_assignments(
88
+ self, node: AddressableNode, inst_name: str, array_idx: str, master_prefix: str, indexed_path: str
89
+ ) -> list[str]:
90
+ """Generate intermediate signal assignments for APB4 interface arrays."""
91
+ return [
92
+ f"assign {inst_name}_fanin_ready{array_idx} = {master_prefix}{indexed_path}.PREADY;",
93
+ f"assign {inst_name}_fanin_err{array_idx} = {master_prefix}{indexed_path}.PSLVERR;",
94
+ f"assign {inst_name}_fanin_data{array_idx} = {master_prefix}{indexed_path}.PRDATA;",
95
+ ]
@@ -29,6 +29,13 @@ assign {{cpuif.signal("PSLVERR")}} = cpuif_rd_err | cpuif_rd_sel.cpuif_err | cpu
29
29
  // Fanout CPU Bus interface signals
30
30
  //--------------------------------------------------------------------------
31
31
  {{fanout|walk(cpuif=cpuif)}}
32
+ {%- if cpuif.is_interface %}
33
+
34
+ //--------------------------------------------------------------------------
35
+ // Intermediate signals for interface array fanin
36
+ //--------------------------------------------------------------------------
37
+ {{fanin_intermediate|walk(cpuif=cpuif)}}
38
+ {%- endif %}
32
39
 
33
40
  //--------------------------------------------------------------------------
34
41
  // Fanin CPU Bus interface signals
@@ -4,14 +4,14 @@ from systemrdl.node import AddressableNode
4
4
 
5
5
  from ...utils import get_indexed_path
6
6
  from ..base_cpuif import BaseCpuif
7
- from .axi4lite_interface import AXI4LiteSVInterface
7
+ from .axi4_lite_interface import AXI4LiteSVInterface
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from ...exporter import BusDecoderExporter
11
11
 
12
12
 
13
13
  class AXI4LiteCpuif(BaseCpuif):
14
- template_path = "axi4lite_tmpl.sv"
14
+ template_path = "axi4_lite_tmpl.sv"
15
15
 
16
16
  def __init__(self, exp: "BusDecoderExporter") -> None:
17
17
  super().__init__(exp)
@@ -68,9 +68,17 @@ class AXI4LiteCpuif(BaseCpuif):
68
68
  fanin["cpuif_rd_ack"] = "'0"
69
69
  fanin["cpuif_rd_err"] = "'0"
70
70
  else:
71
- # Read side: ack comes from RVALID; err if RRESP[1] is set (SLVERR/DECERR)
72
- fanin["cpuif_rd_ack"] = self.signal("RVALID", node, "i")
73
- fanin["cpuif_rd_err"] = f"{self.signal('RRESP', node, 'i')}[1]"
71
+ # Use intermediate signals for interface arrays to avoid
72
+ # non-constant indexing of interface arrays in procedural blocks
73
+ if self.is_interface and node.is_array and node.array_dimensions:
74
+ # Generate array index string [i0][i1]... for the intermediate signal
75
+ array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
76
+ fanin["cpuif_rd_ack"] = f"{node.inst_name}_fanin_ready{array_idx}"
77
+ fanin["cpuif_rd_err"] = f"{node.inst_name}_fanin_err{array_idx}"
78
+ else:
79
+ # Read side: ack comes from RVALID; err if RRESP[1] is set (SLVERR/DECERR)
80
+ fanin["cpuif_rd_ack"] = self.signal("RVALID", node, "i")
81
+ fanin["cpuif_rd_err"] = f"{self.signal('RRESP', node, 'i')}[1]"
74
82
 
75
83
  return "\n".join(f"{lhs} = {rhs};" for lhs, rhs in fanin.items())
76
84
 
@@ -79,6 +87,23 @@ class AXI4LiteCpuif(BaseCpuif):
79
87
  if node is None:
80
88
  fanin["cpuif_rd_data"] = "'0"
81
89
  else:
82
- fanin["cpuif_rd_data"] = self.signal("RDATA", node, "i")
90
+ # Use intermediate signals for interface arrays to avoid
91
+ # non-constant indexing of interface arrays in procedural blocks
92
+ if self.is_interface and node.is_array and node.array_dimensions:
93
+ # Generate array index string [i0][i1]... for the intermediate signal
94
+ array_idx = "".join(f"[i{i}]" for i in range(len(node.array_dimensions)))
95
+ fanin["cpuif_rd_data"] = f"{node.inst_name}_fanin_data{array_idx}"
96
+ else:
97
+ fanin["cpuif_rd_data"] = self.signal("RDATA", node, "i")
83
98
 
84
99
  return "\n".join(f"{lhs} = {rhs};" for lhs, rhs in fanin.items())
100
+
101
+ def fanin_intermediate_assignments(
102
+ self, node: AddressableNode, inst_name: str, array_idx: str, master_prefix: str, indexed_path: str
103
+ ) -> list[str]:
104
+ """Generate intermediate signal assignments for AXI4-Lite interface arrays."""
105
+ return [
106
+ f"assign {inst_name}_fanin_ready{array_idx} = {master_prefix}{indexed_path}.RVALID;",
107
+ f"assign {inst_name}_fanin_err{array_idx} = {master_prefix}{indexed_path}.RRESP[1];",
108
+ f"assign {inst_name}_fanin_data{array_idx} = {master_prefix}{indexed_path}.RDATA;",
109
+ ]
@@ -4,7 +4,7 @@ from systemrdl.node import AddressableNode
4
4
 
5
5
  from ...utils import get_indexed_path
6
6
  from ..base_cpuif import BaseCpuif
7
- from .axi4lite_interface import AXI4LiteFlatInterface
7
+ from .axi4_lite_interface import AXI4LiteFlatInterface
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from ...exporter import BusDecoderExporter
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
13
13
  class AXI4LiteCpuifFlat(BaseCpuif):
14
14
  """Verilator-friendly variant that flattens the AXI4-Lite interface ports."""
15
15
 
16
- template_path = "axi4lite_tmpl.sv"
16
+ template_path = "axi4_lite_tmpl.sv"
17
17
 
18
18
  def __init__(self, exp: "BusDecoderExporter") -> None:
19
19
  super().__init__(exp)
@@ -53,6 +53,13 @@ assign {{cpuif.signal("BRESP")}} = (cpuif_wr_err | cpuif_wr_sel.cpuif_err | cpu
53
53
  // Fanout CPU Bus interface signals
54
54
  //--------------------------------------------------------------------------
55
55
  {{fanout|walk(cpuif=cpuif)}}
56
+ {%- if cpuif.is_interface %}
57
+
58
+ //--------------------------------------------------------------------------
59
+ // Intermediate signals for interface array fanin
60
+ //--------------------------------------------------------------------------
61
+ {{fanin_intermediate|walk(cpuif=cpuif)}}
62
+ {%- endif %}
56
63
 
57
64
  //--------------------------------------------------------------------------
58
65
  // Fanin CPU Bus interface signals
@@ -7,6 +7,7 @@ from systemrdl.node import AddressableNode
7
7
 
8
8
  from ..utils import clog2, get_indexed_path, is_pow2, roundup_pow2
9
9
  from .fanin_gen import FaninGenerator
10
+ from .fanin_intermediate_gen import FaninIntermediateGenerator
10
11
  from .fanout_gen import FanoutGenerator
11
12
 
12
13
  if TYPE_CHECKING:
@@ -24,11 +25,7 @@ class BaseCpuif:
24
25
 
25
26
  @property
26
27
  def addressable_children(self) -> list[AddressableNode]:
27
- return [
28
- child
29
- for child in self.exp.ds.top_node.children(unroll=self.unroll)
30
- if isinstance(child, AddressableNode)
31
- ]
28
+ return self.exp.ds.get_addressable_children_at_depth(unroll=self.unroll)
32
29
 
33
30
  @property
34
31
  def addr_width(self) -> int:
@@ -97,6 +94,7 @@ class BaseCpuif:
97
94
  "ds": self.exp.ds,
98
95
  "fanout": FanoutGenerator,
99
96
  "fanin": FaninGenerator,
97
+ "fanin_intermediate": FaninIntermediateGenerator,
100
98
  }
101
99
 
102
100
  template = jj_env.get_template(self.template_path)
@@ -116,3 +114,24 @@ class BaseCpuif:
116
114
 
117
115
  def readback(self, node: AddressableNode | None = None) -> str:
118
116
  raise NotImplementedError
117
+
118
+ def fanin_intermediate_assignments(
119
+ self, node: AddressableNode, inst_name: str, array_idx: str, master_prefix: str, indexed_path: str
120
+ ) -> list[str]:
121
+ """Generate intermediate signal assignments for interface array fanin.
122
+
123
+ This method should be implemented by cpuif classes that use interfaces.
124
+ It returns a list of assignment strings that copy signals from interface
125
+ arrays to intermediate unpacked arrays using constant (genvar) indexing.
126
+
127
+ Args:
128
+ node: The addressable node
129
+ inst_name: Instance name for the intermediate signals
130
+ array_idx: Array index string (e.g., "[gi0][gi1]")
131
+ master_prefix: Master interface prefix
132
+ indexed_path: Indexed path to the interface element
133
+
134
+ Returns:
135
+ List of assignment strings
136
+ """
137
+ return [] # Default: no intermediate assignments needed
@@ -27,6 +27,17 @@ class FaninGenerator(BusDecoderListener):
27
27
  def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
28
28
  action = super().enter_AddressableComponent(node)
29
29
 
30
+ should_generate = action == WalkerAction.SkipDescendants
31
+ if not should_generate and self._ds.max_decode_depth == 0:
32
+ for child in node.children():
33
+ if isinstance(child, AddressableNode):
34
+ break
35
+ else:
36
+ should_generate = True
37
+
38
+ if not should_generate:
39
+ return action
40
+
30
41
  if node.array_dimensions:
31
42
  for i, dim in enumerate(node.array_dimensions):
32
43
  fb = ForLoopBody(
@@ -0,0 +1,142 @@
1
+ """Generator for intermediate signals needed for interface array fanin.
2
+
3
+ When using SystemVerilog interface arrays, we cannot use variable indices
4
+ in procedural blocks (like always_comb). This generator creates intermediate
5
+ signals that copy from interface arrays using generate loops, which can then
6
+ be safely accessed with variable indices in the fanin logic.
7
+ """
8
+
9
+ from collections import deque
10
+ from typing import TYPE_CHECKING
11
+
12
+ from systemrdl.node import AddressableNode
13
+ from systemrdl.walker import WalkerAction
14
+
15
+ from ..body import Body, ForLoopBody
16
+ from ..design_state import DesignState
17
+ from ..listener import BusDecoderListener
18
+ from ..utils import get_indexed_path
19
+
20
+ if TYPE_CHECKING:
21
+ from .base_cpuif import BaseCpuif
22
+
23
+
24
+ class FaninIntermediateGenerator(BusDecoderListener):
25
+ """Generates intermediate signals for interface array fanin."""
26
+
27
+ def __init__(self, ds: DesignState, cpuif: "BaseCpuif") -> None:
28
+ super().__init__(ds)
29
+ self._cpuif = cpuif
30
+ self._declarations: list[str] = []
31
+ self._stack: deque[Body] = deque()
32
+ self._stack.append(Body())
33
+
34
+ def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
35
+ action = super().enter_AddressableComponent(node)
36
+
37
+ # Only generate intermediates for interface arrays
38
+ # Check if cpuif has is_interface attribute (some implementations don't)
39
+ is_interface = getattr(self._cpuif, "is_interface", False)
40
+ if not is_interface or not node.array_dimensions:
41
+ return action
42
+
43
+ # Generate intermediate signal declarations
44
+ self._generate_intermediate_declarations(node)
45
+
46
+ # Generate assignment logic using generate loops
47
+ if node.array_dimensions:
48
+ for i, dim in enumerate(node.array_dimensions):
49
+ fb = ForLoopBody(
50
+ "genvar",
51
+ f"gi{i}",
52
+ dim,
53
+ )
54
+ self._stack.append(fb)
55
+
56
+ # Generate assignments from interface array to intermediates
57
+ self._stack[-1] += self._generate_intermediate_assignments(node)
58
+
59
+ return action
60
+
61
+ def exit_AddressableComponent(self, node: AddressableNode) -> None:
62
+ is_interface = getattr(self._cpuif, "is_interface", False)
63
+ if is_interface and node.array_dimensions:
64
+ for _ in node.array_dimensions:
65
+ b = self._stack.pop()
66
+ if not b:
67
+ continue
68
+ self._stack[-1] += b
69
+
70
+ super().exit_AddressableComponent(node)
71
+
72
+ def _generate_intermediate_declarations(self, node: AddressableNode) -> None:
73
+ """Generate intermediate signal declarations for a node."""
74
+ inst_name = node.inst_name
75
+
76
+ # Array dimensions should be checked before calling this function
77
+ if not node.array_dimensions:
78
+ return
79
+
80
+ # Calculate total array size
81
+ array_size = 1
82
+ for dim in node.array_dimensions:
83
+ array_size *= dim
84
+
85
+ # Create array dimension string
86
+ array_str = "".join(f"[{dim}]" for dim in node.array_dimensions)
87
+
88
+ # Generate declarations for each fanin signal
89
+ # For APB3/4: PREADY, PSLVERR, PRDATA
90
+ # These are the signals read in fanin
91
+ self._declarations.append(f"logic {inst_name}_fanin_ready{array_str};")
92
+ self._declarations.append(f"logic {inst_name}_fanin_err{array_str};")
93
+ self._declarations.append(
94
+ f"logic [{self._cpuif.data_width - 1}:0] {inst_name}_fanin_data{array_str};"
95
+ )
96
+
97
+ def _generate_intermediate_assignments(self, node: AddressableNode) -> str:
98
+ """Generate assignments from interface array to intermediate signals."""
99
+ inst_name = node.inst_name
100
+ indexed_path = get_indexed_path(node.parent, node, "gi", skip_kw_filter=True)
101
+
102
+ # Get master prefix - use getattr to avoid type errors
103
+ interface = getattr(self._cpuif, "_interface", None)
104
+ if interface is None:
105
+ return ""
106
+ master_prefix = interface.get_master_prefix()
107
+
108
+ # Array dimensions should be checked before calling this function
109
+ if not node.array_dimensions:
110
+ return ""
111
+
112
+ # Create indexed signal names for left-hand side
113
+ array_idx = "".join(f"[gi{i}]" for i in range(len(node.array_dimensions)))
114
+
115
+ # Delegate to cpuif to get the appropriate assignments for this interface type
116
+ assignments = self._cpuif.fanin_intermediate_assignments(
117
+ node, inst_name, array_idx, master_prefix, indexed_path
118
+ )
119
+
120
+ return "\n".join(assignments)
121
+
122
+ def get_declarations(self) -> str:
123
+ """Get all intermediate signal declarations."""
124
+ if not self._declarations:
125
+ return ""
126
+ return "\n".join(self._declarations)
127
+
128
+ def __str__(self) -> str:
129
+ """Get all intermediate signal declarations and assignments."""
130
+ if not self._declarations:
131
+ return ""
132
+
133
+ # Output declarations first
134
+ output = "\n".join(self._declarations)
135
+ output += "\n\n"
136
+
137
+ # Then output assignments
138
+ body_str = "\n".join(map(str, self._stack))
139
+ if body_str and body_str.strip():
140
+ output += body_str
141
+
142
+ return output
@@ -23,6 +23,17 @@ class FanoutGenerator(BusDecoderListener):
23
23
  def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
24
24
  action = super().enter_AddressableComponent(node)
25
25
 
26
+ should_generate = action == WalkerAction.SkipDescendants
27
+ if not should_generate and self._ds.max_decode_depth == 0:
28
+ for child in node.children():
29
+ if isinstance(child, AddressableNode):
30
+ break
31
+ else:
32
+ should_generate = True
33
+
34
+ if not should_generate:
35
+ return action
36
+
26
37
  if node.array_dimensions:
27
38
  for i, dim in enumerate(node.array_dimensions):
28
39
  fb = ForLoopBody(
@@ -85,6 +85,20 @@ class DecodeLogicGenerator(BusDecoderListener):
85
85
  def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
86
86
  action = super().enter_AddressableComponent(node)
87
87
 
88
+ should_decode = action == WalkerAction.SkipDescendants
89
+
90
+ if not should_decode and self._ds.max_decode_depth == 0:
91
+ # When decoding all levels, treat leaf registers as decode boundary
92
+ for child in node.children():
93
+ if isinstance(child, AddressableNode):
94
+ break
95
+ else:
96
+ should_decode = True
97
+
98
+ # Only generate select logic if we're at the decode boundary
99
+ if not should_decode:
100
+ return action
101
+
88
102
  conditions: list[str] = []
89
103
  conditions.extend(self.cpuif_addr_predicate(node))
90
104
  conditions.extend(self.cpuif_prot_predicate(node))
@@ -146,6 +160,8 @@ class DecodeLogicGenerator(BusDecoderListener):
146
160
  def __str__(self) -> str:
147
161
  body = self._decode_stack[-1]
148
162
  if isinstance(body, IfBody):
163
+ if len(body) == 0:
164
+ return f"{self._flavor.cpuif_select}.cpuif_err = 1'b1;"
149
165
  with body.cm(...) as b:
150
166
  b += f"{self._flavor.cpuif_select}.cpuif_err = 1'b1;"
151
167
 
@@ -1,6 +1,6 @@
1
1
  from typing import TypedDict
2
2
 
3
- from systemrdl.node import AddrmapNode
3
+ from systemrdl.node import AddressableNode, AddrmapNode
4
4
  from systemrdl.rdltypes.user_enum import UserEnum
5
5
 
6
6
  from .design_scanner import DesignScanner
@@ -72,3 +72,56 @@ class DesignState:
72
72
  if user_addr_width < self.addr_width:
73
73
  msg.fatal(f"User-specified address width shall be greater than or equal to {self.addr_width}.")
74
74
  self.addr_width = user_addr_width
75
+
76
+ def get_addressable_children_at_depth(self, unroll: bool = False) -> list[AddressableNode]:
77
+ """
78
+ Get addressable children at the decode boundary based on max_decode_depth.
79
+
80
+ max_decode_depth semantics:
81
+ - 0: decode all levels (return leaf registers)
82
+ - 1: decode only top level (return children at depth 1)
83
+ - 2: decode top + 1 level (return children at depth 2)
84
+ - N: decode down to depth N (return children at depth N)
85
+
86
+ Args:
87
+ unroll: Whether to unroll arrayed nodes
88
+
89
+ Returns:
90
+ List of addressable nodes at the decode boundary
91
+ """
92
+ from systemrdl.node import RegNode
93
+
94
+ def collect_nodes(node: AddressableNode, current_depth: int) -> list[AddressableNode]:
95
+ """Recursively collect nodes at the decode boundary."""
96
+ result: list[AddressableNode] = []
97
+
98
+ # For depth 0, collect all leaf registers
99
+ if self.max_decode_depth == 0:
100
+ # If this is a register, it's a leaf
101
+ if isinstance(node, RegNode):
102
+ result.append(node)
103
+ else:
104
+ # Recurse into children
105
+ for child in node.children(unroll=unroll):
106
+ if isinstance(child, AddressableNode):
107
+ result.extend(collect_nodes(child, current_depth + 1))
108
+ else:
109
+ # For depth N, collect children at depth N
110
+ if current_depth == self.max_decode_depth:
111
+ # We're at the decode boundary - return this node
112
+ result.append(node)
113
+ elif current_depth < self.max_decode_depth:
114
+ # We haven't reached the boundary yet - recurse
115
+ for child in node.children(unroll=unroll):
116
+ if isinstance(child, AddressableNode):
117
+ result.extend(collect_nodes(child, current_depth + 1))
118
+
119
+ return result
120
+
121
+ # Start collecting from top node's children
122
+ nodes: list[AddressableNode] = []
123
+ for child in self.top_node.children(unroll=unroll):
124
+ if isinstance(child, AddressableNode):
125
+ nodes.extend(collect_nodes(child, 1))
126
+
127
+ return nodes
@@ -89,7 +89,9 @@ class BusDecoderExporter:
89
89
  interface. By default, arrayed nodes are kept as arrays.
90
90
  max_decode_depth: int
91
91
  Maximum depth for address decoder to descend into nested addressable
92
- components. By default, the decoder descends 1 level deep.
92
+ components. A value of 0 decodes all levels (infinite depth). A value
93
+ of 1 decodes only top-level children. A value of 2 decodes top-level
94
+ and one level deeper, etc. By default, the decoder descends 1 level deep.
93
95
  """
94
96
  # If it is the root node, skip to top addrmap
95
97
  if isinstance(node, RootNode):
@@ -15,7 +15,12 @@ class BusDecoderListener(RDLListener):
15
15
  def should_skip_node(self, node: AddressableNode) -> bool:
16
16
  """Check if this node should be skipped (not decoded)."""
17
17
  # Check if current depth exceeds max depth
18
- if self._depth > self._ds.max_decode_depth:
18
+ # max_decode_depth semantics:
19
+ # - 0 means decode all levels (infinite)
20
+ # - 1 means decode only top level (depth 0)
21
+ # - 2 means decode top + 1 level (depth 0 and 1)
22
+ # - N means decode down to depth N-1
23
+ if self._ds.max_decode_depth > 0 and self._depth >= self._ds.max_decode_depth:
19
24
  return True
20
25
 
21
26
  # Check if this node only contains external addressable children
@@ -51,7 +51,7 @@ module {{ds.module_name}}
51
51
  //--------------------------------------------------------------------------
52
52
  always_comb begin
53
53
  // Default all write select signals to 0
54
- cpuif_wr_sel = '0;
54
+ cpuif_wr_sel = '{default: '0};
55
55
 
56
56
  if (cpuif_req && cpuif_wr_en) begin
57
57
  // A write request is pending
@@ -66,7 +66,7 @@ module {{ds.module_name}}
66
66
  //--------------------------------------------------------------------------
67
67
  always_comb begin
68
68
  // Default all read select signals to 0
69
- cpuif_rd_sel = '0;
69
+ cpuif_rd_sel = '{default: '0};
70
70
 
71
71
  if (cpuif_req && cpuif_rd_en) begin
72
72
  // A read request is pending
@@ -3,7 +3,7 @@ from collections import deque
3
3
  from systemrdl.node import AddressableNode
4
4
  from systemrdl.walker import WalkerAction
5
5
 
6
- from .body import Body, StructBody
6
+ from .body import StructBody
7
7
  from .design_state import DesignState
8
8
  from .identifier_filter import kw_filter as kwf
9
9
  from .listener import BusDecoderListener
@@ -16,42 +16,53 @@ class StructGenerator(BusDecoderListener):
16
16
  ) -> None:
17
17
  super().__init__(ds)
18
18
 
19
- self._stack: deque[Body] = deque()
20
- self._stack.append(StructBody("cpuif_sel_t", True, True))
19
+ self._stack: list[StructBody] = [StructBody("cpuif_sel_t", True, False)]
20
+ self._struct_defs: list[StructBody] = []
21
+ self._created_struct_stack: deque[bool] = deque() # Track if we created a struct for each node
21
22
 
22
23
  def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
23
24
  action = super().enter_AddressableComponent(node)
24
25
 
25
- self._skip = False
26
- if action == WalkerAction.SkipDescendants:
27
- self._skip = True
26
+ skip = action == WalkerAction.SkipDescendants
28
27
 
29
- if node.children():
28
+ # Only create nested struct if we're not skipping and node has addressable children
29
+ has_addressable_children = any(isinstance(child, AddressableNode) for child in node.children())
30
+ if has_addressable_children and not skip:
30
31
  # Push new body onto stack
31
- body = StructBody(f"cpuif_sel_{node.inst_name}_t", True, True)
32
+ body = StructBody(f"cpuif_sel_{node.inst_name}_t", True, False)
32
33
  self._stack.append(body)
34
+ self._created_struct_stack.append(True)
35
+ else:
36
+ self._created_struct_stack.append(False)
33
37
 
34
38
  return action
35
39
 
36
40
  def exit_AddressableComponent(self, node: AddressableNode) -> None:
37
41
  type = "logic"
38
42
 
39
- if node.children():
43
+ # Pop the created_struct flag
44
+ created_struct = self._created_struct_stack.pop()
45
+
46
+ # Only pop struct body if we created one
47
+ if created_struct:
40
48
  body = self._stack.pop()
41
- if body and isinstance(body, StructBody) and not self._skip:
42
- self._stack.appendleft(body)
49
+ if body:
50
+ self._struct_defs.append(body)
43
51
  type = body.name
44
52
 
45
53
  name = kwf(node.inst_name)
46
54
 
47
55
  if node.array_dimensions:
48
56
  for dim in node.array_dimensions:
49
- name = f"[{dim - 1}:0]{name}"
57
+ name = f"{name}[{dim}]"
50
58
 
51
59
  self._stack[-1] += f"{type} {name};"
52
60
 
53
61
  super().exit_AddressableComponent(node)
54
62
 
55
63
  def __str__(self) -> str:
56
- self._stack[-1] += "logic cpuif_err;"
57
- return "\n".join(map(str, self._stack))
64
+ if "logic cpuif_err;" not in self._stack[-1].lines:
65
+ self._stack[-1] += "logic cpuif_err;"
66
+ bodies = [str(body) for body in self._struct_defs]
67
+ bodies.append(str(self._stack[-1]))
68
+ return "\n".join(bodies)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: peakrdl-busdecoder
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Generate a SystemVerilog bus decoder from SystemRDL for splitting CPU interfaces to multiple sub-address spaces
5
5
  Author: Alex Mykyta
6
6
  License: LGPLv3
@@ -1,15 +1,15 @@
1
1
  peakrdl_busdecoder/__init__.py,sha256=zKSEa8EqCrZUtGoHCuZ0uNQVQMV2isVn47CgsDj5K48,75
2
- peakrdl_busdecoder/__peakrdl__.py,sha256=phRHXzr6_7zIpgQtwg4-aY4N_Da4PSuDXNRZUgG_eN0,4429
3
- peakrdl_busdecoder/decode_logic_gen.py,sha256=9ub2Vmx_vwONaUT9gCgLe6AixS0MTe7_q5foRBLdhG4,5256
2
+ peakrdl_busdecoder/__peakrdl__.py,sha256=76kLBbSWVlKkCI5ESiID1aWVcQHr4sK32ujP5kna8xU,4601
3
+ peakrdl_busdecoder/decode_logic_gen.py,sha256=MdZbOiW2YNudhD-Msl_5wgGhd3mMaBNhTpUl-bilgHs,5873
4
4
  peakrdl_busdecoder/design_scanner.py,sha256=syD6IXAJWYdpq2PQPvUGflPjhR_pO35R46Ld5K5XZHQ,1562
5
- peakrdl_busdecoder/design_state.py,sha256=xwos_gw9qoV2OpuAXP95Hn_dFyV5cFdROfVGS6OB7Z0,2617
6
- peakrdl_busdecoder/exporter.py,sha256=2Id5riWIR-AwvIU8fzjFC0NbIEyYzXk1YS6gXvEXMuA,5178
5
+ peakrdl_busdecoder/design_state.py,sha256=_zqgn2dQH2ozK63NwqRvPV7Bty7G-BAESu3yux2cJmI,4926
6
+ peakrdl_busdecoder/exporter.py,sha256=ZkGBeK-c733a3Xyc2ZeiGbWgBnDDW_s9nc64QiEf6no,5356
7
7
  peakrdl_busdecoder/identifier_filter.py,sha256=poSIS1BMscORxOuDX_nXqvdNzClhKASdAVv9aDM_aqA,3972
8
- peakrdl_busdecoder/listener.py,sha256=y62gXl_SOg_TGLYytw9xO5tRNe_kgv7dKbET_RlBpnQ,2233
9
- peakrdl_busdecoder/module_tmpl.sv,sha256=OoscLFeh6FwChaZOFEmJXbz0OjRklifNE5jpAs2ZqN0,2853
8
+ peakrdl_busdecoder/listener.py,sha256=WZ2fma6oSpGGZwuP7Zcv1futWBalaeZeVZD7DX1KQz4,2509
9
+ peakrdl_busdecoder/module_tmpl.sv,sha256=Sjo05-g_oP48x0jCdEyOvpBjwYV3wiACESgaC7viN00,2877
10
10
  peakrdl_busdecoder/package_tmpl.sv,sha256=E0_jYmXx1nEMb78WApbCvMWYlrAuNfSWB7Jw23UkBYU,852
11
11
  peakrdl_busdecoder/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- peakrdl_busdecoder/struct_gen.py,sha256=6SIx7Eds2xrcpAqDBLSbNhy21sTdqMrIHT0I8QZe-FU,1690
12
+ peakrdl_busdecoder/struct_gen.py,sha256=UGNdGg-S6u2JRoDExP6Mt5h3bu01fP-gQ15FcwD9gNw,2334
13
13
  peakrdl_busdecoder/sv_int.py,sha256=W0utmnR4XCuj3FVb9OGmRZ7SwlgEyO-XghMfd1I3iGA,872
14
14
  peakrdl_busdecoder/utils.py,sha256=Kdcee6bhubI5XVX20JQ3wo0qYJTknqrv4l8kxhSd7wI,2219
15
15
  peakrdl_busdecoder/validate_design.py,sha256=91yuWEzj0aAZ0eqzF1TF4N17yqG-D5oK5yYxDINlPms,8857
@@ -20,29 +20,30 @@ peakrdl_busdecoder/body/for_loop_body.py,sha256=lJYfDiB7MF9_mfbGEApoVHRViBCqpGRs
20
20
  peakrdl_busdecoder/body/if_body.py,sha256=S4oVS7yz04R1i-23W-aWpVcVZa5dW-H0FL_KnMy-Wsc,3261
21
21
  peakrdl_busdecoder/body/struct_body.py,sha256=74ItYoYD-GqEidxc-ncV5y8fzGoM47NI4GnnweclmS8,624
22
22
  peakrdl_busdecoder/cpuif/__init__.py,sha256=T8YrELGuOBIPgj1e86tNJ5tSCf7fXpPadqa71v5MEx8,59
23
- peakrdl_busdecoder/cpuif/base_cpuif.py,sha256=QJ8F8VMZN57rNnLY8QHDmnPLaXR-CyCx2cNligPT5wU,4100
24
- peakrdl_busdecoder/cpuif/fanin_gen.py,sha256=K2fZbxGx4um05LG18eDRPqXBp7-wBXwPmddOTKbe_NI,2053
25
- peakrdl_busdecoder/cpuif/fanout_gen.py,sha256=mENigVzZ7edw3DMVkoXzyFQObV_r81hO4UP677IDlYk,1478
23
+ peakrdl_busdecoder/cpuif/base_cpuif.py,sha256=tSqBeV_eG5zvBaLltLY8qmJBhwuCym3rs_q4mIJT6HI,5058
24
+ peakrdl_busdecoder/cpuif/fanin_gen.py,sha256=ioCxjj3V3X8rHssqwuI4p_U-LKpBalATW_Qshtt8j9U,2425
25
+ peakrdl_busdecoder/cpuif/fanin_intermediate_gen.py,sha256=hfvMyyx5ajEzvop3dzV37dT6Jn9yjORFnUkMAT1034A,5271
26
+ peakrdl_busdecoder/cpuif/fanout_gen.py,sha256=0-6eMzYbf25csHaRUJCsiyFz7lxS2Mdu0bXNm6e6cFI,1850
26
27
  peakrdl_busdecoder/cpuif/interface.py,sha256=tFObXG18ashLrXSWn5O1ZwA-VNg9CL6w9XWi3qKd1sY,6148
27
28
  peakrdl_busdecoder/cpuif/apb3/__init__.py,sha256=Uq82IJHzlITUvjTuETvPpSzvLEYoairzzPKfPz7kuC4,119
28
- peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py,sha256=oskculfTBlXJWvJzcC0LeS5OH_s0GfOc0q7iuEvGzkw,2584
29
+ peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py,sha256=iQDpHpv9HQQiBm9zHlUuSCaUysraFvHA25kt2XlpKmM,4248
29
30
  peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py,sha256=5_li9azFbFc0q9CLpqQNPvbcALsNlOBwV9ROHZuZ81c,2374
30
31
  peakrdl_busdecoder/cpuif/apb3/apb3_interface.py,sha256=hU0GUCZGYnvskmZU6NznMGgRJFliD360WgwhsQh9rqY,2147
31
- peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv,sha256=DCiBUQEEZ5zAo71Wra9IiyHazLk98SugLF2MdjxNYWU,1679
32
+ peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv,sha256=6et25KbiNkZBD1hxjSx0XZFUP9NnvVpixMSGtwJuwdA,1967
32
33
  peakrdl_busdecoder/cpuif/apb4/__init__.py,sha256=k4JCbIrKGT8hiRvWJDcqc5xx7j9i_xYgpXU70sNaLsc,119
33
- peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py,sha256=2XklIUPYuBatWonL8Clni_hm8bq8pzHXWuyASBwd2EY,2793
34
+ peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py,sha256=SnQ1wERBkk1yc4ZiE4z__Sp2441ljFJ7b7mxGPKoiFQ,4457
34
35
  peakrdl_busdecoder/cpuif/apb4/apb4_cpuif_flat.py,sha256=JcQ7TfW2bWA9g_uCpsmEUNQfRikx5ZImhf2VSFGIdmw,2504
35
36
  peakrdl_busdecoder/cpuif/apb4/apb4_interface.py,sha256=Ln5jIBhCHbIYS5sxR5AjFVvcMYD_rw71VF6KqyyNUkg,2449
36
- peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv,sha256=0Ql3ri9VVSJP9ODFpsparhUeKvWwjQgZDn0zQqmad7Q,2022
37
+ peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv,sha256=jjzIbiNvsDeMQWOIo08TpeKbSP3NILQkP3gWdlcX9Ak,2310
37
38
  peakrdl_busdecoder/cpuif/axi4lite/__init__.py,sha256=5XuWfPK2jDzr6egKUDJFr8l3k3lW-feLIh-lN7Mo8Ks,145
38
- peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_cpuif.py,sha256=QxgAKKN8tJ6gphWy2_ztfTEO92YDMXBgBQGEDfFbNJk,3326
39
- peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_cpuif_flat.py,sha256=M9gR3aNuN3VE32iwBMVQuvBRxQ1g4zGW_YlT6Q9QDNw,3418
40
- peakrdl_busdecoder/cpuif/axi4lite/axi4lite_interface.py,sha256=eBs3RLGGvvQen4C6MR9RUZIHuTQEidYHG4NGxlYY1Bc,3702
41
- peakrdl_busdecoder/cpuif/axi4lite/axi4lite_tmpl.sv,sha256=Uz30kPye9wiAn1UIR9oNpahyn5KPrrAjbCI8_8-gxK8,3063
39
+ peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_cpuif.py,sha256=_DpGXEFs77AFQAAspsGZxIxQuhehjPPFgL_9hLfuQeQ,5001
40
+ peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_cpuif_flat.py,sha256=M9f6-2XloH1UCwHctolO4Um8BfAYiivWVM5PmAfksjY,3420
41
+ peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_interface.py,sha256=eBs3RLGGvvQen4C6MR9RUZIHuTQEidYHG4NGxlYY1Bc,3702
42
+ peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_tmpl.sv,sha256=ketPrq715LSEs8Ar9ecXgnSe2AUAdQ70oNVmDbXc2Tc,3351
42
43
  peakrdl_busdecoder/udps/__init__.py,sha256=gPc74OMVWTIr5vgtFArzbhEyi1OYjllR3ZFwHJ8APaY,106
43
- peakrdl_busdecoder-0.2.0.dist-info/licenses/LICENSE,sha256=eAMIGRcnsTDZVr4qelHkJ49Rd_IiDY4_MVHU7N0UWSw,7646
44
- peakrdl_busdecoder-0.2.0.dist-info/METADATA,sha256=xGwY74YR6s52NNmhUpNxQJ8MeK5p8UGG2PMUYDixAXg,2560
45
- peakrdl_busdecoder-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
46
- peakrdl_busdecoder-0.2.0.dist-info/entry_points.txt,sha256=7Xzgt-C2F4cQu1kRLpZa0MbXSFFMC1SWEDnZkY0GH7s,73
47
- peakrdl_busdecoder-0.2.0.dist-info/top_level.txt,sha256=ZIYuTsl8cYby4g8tNR_JGzbYYTrG9mqYLSBqnY1Gpmk,19
48
- peakrdl_busdecoder-0.2.0.dist-info/RECORD,,
44
+ peakrdl_busdecoder-0.4.0.dist-info/licenses/LICENSE,sha256=eAMIGRcnsTDZVr4qelHkJ49Rd_IiDY4_MVHU7N0UWSw,7646
45
+ peakrdl_busdecoder-0.4.0.dist-info/METADATA,sha256=uGopMHm1NkeK7SFCfOHcvahKg3HcWiKY59S7ixTqBVE,2560
46
+ peakrdl_busdecoder-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ peakrdl_busdecoder-0.4.0.dist-info/entry_points.txt,sha256=7Xzgt-C2F4cQu1kRLpZa0MbXSFFMC1SWEDnZkY0GH7s,73
48
+ peakrdl_busdecoder-0.4.0.dist-info/top_level.txt,sha256=ZIYuTsl8cYby4g8tNR_JGzbYYTrG9mqYLSBqnY1Gpmk,19
49
+ peakrdl_busdecoder-0.4.0.dist-info/RECORD,,