systemrdl 0.1.0

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +127 -0
  4. data/lib/systemrdl/error.rb +55 -0
  5. data/lib/systemrdl/evaluator/address_allocation.rb +126 -0
  6. data/lib/systemrdl/evaluator/address_operation.rb +103 -0
  7. data/lib/systemrdl/evaluator/addrmap.rb +67 -0
  8. data/lib/systemrdl/evaluator/array_component.rb +42 -0
  9. data/lib/systemrdl/evaluator/builtin_properties.rb +400 -0
  10. data/lib/systemrdl/evaluator/common.rb +22 -0
  11. data/lib/systemrdl/evaluator/component_definition.rb +185 -0
  12. data/lib/systemrdl/evaluator/component_insts.rb +106 -0
  13. data/lib/systemrdl/evaluator/container_component.rb +16 -0
  14. data/lib/systemrdl/evaluator/field.rb +245 -0
  15. data/lib/systemrdl/evaluator/instance.rb +120 -0
  16. data/lib/systemrdl/evaluator/list.rb +26 -0
  17. data/lib/systemrdl/evaluator/literal.rb +173 -0
  18. data/lib/systemrdl/evaluator/operation.rb +241 -0
  19. data/lib/systemrdl/evaluator/processor.rb +166 -0
  20. data/lib/systemrdl/evaluator/property.rb +116 -0
  21. data/lib/systemrdl/evaluator/property_assignment.rb +220 -0
  22. data/lib/systemrdl/evaluator/reference.rb +135 -0
  23. data/lib/systemrdl/evaluator/reg.rb +156 -0
  24. data/lib/systemrdl/evaluator/regfile.rb +33 -0
  25. data/lib/systemrdl/evaluator/root.rb +39 -0
  26. data/lib/systemrdl/evaluator/value.rb +13 -0
  27. data/lib/systemrdl/evaluator.rb +14 -0
  28. data/lib/systemrdl/model/instance.rb +194 -0
  29. data/lib/systemrdl/model/value.rb +54 -0
  30. data/lib/systemrdl/model.rb +11 -0
  31. data/lib/systemrdl/parser/generated_parser.rb +2808 -0
  32. data/lib/systemrdl/parser/node.rb +17 -0
  33. data/lib/systemrdl/parser/parser.rb +86 -0
  34. data/lib/systemrdl/parser/scanner.rb +251 -0
  35. data/lib/systemrdl/parser/systemrdl.y +448 -0
  36. data/lib/systemrdl/parser/token.rb +58 -0
  37. data/lib/systemrdl/parser.rb +11 -0
  38. data/lib/systemrdl/version.rb +5 -0
  39. data/lib/systemrdl.rb +90 -0
  40. metadata +96 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ef8e253b49ef4971248d9e4ad7b1938ff69952613de40fb985e993653ebd37f2
4
+ data.tar.gz: f7c11c7334b79e9c93e81a5b2ccc211d15c3e7991ca514385f3525deb9316bd8
5
+ SHA512:
6
+ metadata.gz: 60382cdccb4df5c4d9df37a988e0d62d0986c7d2bb7de98add2f706f93349f57e4b0783f2f1660ccb039c7f463c3cfb03b439c2d5429a535063f8344743a152f
7
+ data.tar.gz: 766461e671109a8813e5a39a4f5a4cdd640ebdc2c32de76a31969404317a709aa7a52fbc580ec42229317ecae4fe98f1c7c9ac326c686df70eb4cf42e6146acc
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Taichi Ishitani
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ [![Gem Version](https://badge.fury.io/rb/systemrdl.svg)](https://badge.fury.io/rb/systemrdl)
2
+ [![Regression](https://github.com/taichi-ishitani/systemrdl/actions/workflows/regression.yml/badge.svg?branch=master)](https://github.com/taichi-ishitani/systemrdl/actions/workflows/regression.yml)
3
+
4
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/A0A231E3I)
5
+
6
+ # SystemRDL
7
+
8
+ A SystemRDL 2.0 front-end for Ruby.
9
+ It parses SystemRDL source, elaborates the described register model, and exposes it as plain Ruby objects so that back-end tools can generate RTL, documentation, software headers, and so on.
10
+
11
+ SystemRDL is a register description language standardized by [Accellera](https://www.accellera.org/activities/working-groups/systemrdl).
12
+ This gem targets the [SystemRDL 2.0 specification](https://www.accellera.org/images/downloads/standards/systemrdl/SystemRDL_2.0_Jan2018.pdf).
13
+
14
+ ## Installation
15
+
16
+ Install the gem and add it to the application's Gemfile by executing:
17
+
18
+ ```bash
19
+ bundle add systemrdl
20
+ ```
21
+
22
+ If bundler is not being used to manage dependencies, install the gem by executing:
23
+
24
+ ```bash
25
+ gem install systemrdl
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### Compiling SystemRDL
31
+
32
+ Use the methods below to compile SystemRDL into an elaborated model.
33
+ Each method returns a list of the top-level address maps.
34
+
35
+ * Compile SystemRDL read from the given file path(s)
36
+ * `SystemRDL.compile`
37
+ * Compile SystemRDL read from the given input stream(s)
38
+ * `SystemRDL.compile_streams`
39
+ * Compile the given SystemRDL source string
40
+ * `SystemRDL.compile_source`
41
+
42
+ `SystemRDL.compile` and `SystemRDL.compile_streams` accept multiple inputs.
43
+ The inputs are compiled in the given order and share their component definitions, so a component defined in an earlier input can be instantiated in a later one.
44
+ Note that this sharing is one-directional; an earlier input cannot refer to a component defined in a later one.
45
+
46
+ ```ruby
47
+ require 'systemrdl'
48
+
49
+ # Compile files
50
+ address_maps = SystemRDL.compile('gpio.rdl', 'pwm.rdl')
51
+
52
+ # Compile a source string
53
+ address_maps = SystemRDL.compile_source(<<~'RDL')
54
+ addrmap my_map {
55
+ reg {
56
+ field { sw = rw; hw = r; reset = 0x0; } value[32];
57
+ } data;
58
+ };
59
+ RDL
60
+ ```
61
+
62
+ ### Accessing the model
63
+
64
+ An elaborated model is a tree of address maps, registers, and fields.
65
+ Each element carries the properties assigned to it in the source.
66
+
67
+ ```ruby
68
+ require 'systemrdl'
69
+
70
+ address_map = SystemRDL.compile_source(<<~'RDL').first
71
+ addrmap gpio {
72
+ name = "GPIO";
73
+ desc = "Simple general purpose I/O controller";
74
+
75
+ default sw = rw;
76
+ default hw = r;
77
+
78
+ reg {
79
+ name = "Port Direction";
80
+ field { desc = "0: input, 1: output"; reset = 0x0; } dir[32];
81
+ } direction;
82
+
83
+ reg {
84
+ name = "Input Data";
85
+ field { sw = r; hw = w; } value[32];
86
+ } data_in;
87
+ };
88
+ RDL
89
+
90
+ # GPIO: Simple general purpose I/O controller
91
+ puts "#{address_map.display_name}: #{address_map.desc}"
92
+
93
+ address_map.regs.each do |reg|
94
+ # direction @ 0x0
95
+ # data_in @ 0x4
96
+ puts "#{reg.name} @ 0x#{reg.address.to_s(16)}"
97
+
98
+ reg.fields.each do |field|
99
+ # dir [31:0] sw=rw hw=r
100
+ # value [31:0] sw=r hw=w
101
+ puts " #{field.name} [#{field.msb}:#{field.lsb}] sw=#{field.sw} hw=#{field.hw}"
102
+ end
103
+ end
104
+ ```
105
+
106
+ ## Scope
107
+
108
+ This gem currently supports only the basic syntax needed to describe register maps that consist of address maps, registers, and fields.
109
+ More advanced constructs of the SystemRDL language are not supported yet.
110
+
111
+ ## Design notes
112
+
113
+ The design decisions behind this implementation, along with their rationale, are documented under the [`notes`](notes) directory.
114
+ These notes focus on parts of the SystemRDL specification that are open to interpretation or left unspecified, and explain how this implementation resolves them.
115
+
116
+ ## Contributing
117
+
118
+ Bug reports and pull requests are welcome on GitHub at https://github.com/taichi-ishitani/systemrdl.
119
+
120
+ * [Issue Tracker](https://github.com/taichi-ishitani/systemrdl/issues)
121
+ * [Pull Request](https://github.com/taichi-ishitani/systemrdl/pulls)
122
+ * [Discussion](https://github.com/taichi-ishitani/systemrdl/discussions)
123
+
124
+ ## Copyright & License
125
+
126
+ Copyright &copy; 2026 Taichi Ishitani.
127
+ SystemRDL is licensed under the terms of the [MIT License](https://opensource.org/licenses/MIT), see [LICENSE.txt](LICENSE.txt) for further details.
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SystemRDL
4
+ class Error < StandardError
5
+ def initialize(message)
6
+ super
7
+ @error_message = message
8
+ end
9
+
10
+ attr_reader :error_message
11
+ end
12
+
13
+ class ParseError < Error
14
+ def initialize(message, position)
15
+ super(message)
16
+ @position = position
17
+ end
18
+
19
+ attr_reader :position
20
+
21
+ def to_s
22
+ (@position && "#{super} -- #{@position}") || super
23
+ end
24
+ end
25
+
26
+ module RaiseParseError
27
+ private
28
+
29
+ def raise_parse_error(message, position)
30
+ raise ParseError.new(message, position)
31
+ end
32
+ end
33
+
34
+ class EvaluationError < Error
35
+ def initialize(message, *token_ranges)
36
+ super(message)
37
+ @token_ranges = token_ranges.compact
38
+ end
39
+
40
+ attr_reader :token_ranges
41
+
42
+ def to_s
43
+ position = token_ranges.first&.head&.position
44
+ (position && "#{super} -- #{position}") || super
45
+ end
46
+ end
47
+
48
+ module RaiseEvaluationError
49
+ private
50
+
51
+ def raise_evaluation_error(message, *token_ranges)
52
+ raise EvaluationError.new(message, *token_ranges)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SystemRDL
4
+ module Evaluator
5
+ module AddressAllocation
6
+ private
7
+
8
+ def allocate_addresses(instance)
9
+ address = 0
10
+ instance.instances.each do |child_inst|
11
+ # For now, support reg only
12
+ next unless child_inst.reg?
13
+
14
+ assign_address(address, instance, child_inst)
15
+ address = calc_next_address(child_inst)
16
+ end
17
+ end
18
+
19
+ def assign_address(current_address, inst, child_inst)
20
+ if child_inst.first_element?
21
+ child_inst.address ||= begin
22
+ alignment = calc_alignment(inst, child_inst)
23
+ address = roundup(current_address, alignment)
24
+ Value.new(address, :bit, 64, nil)
25
+ end
26
+ else
27
+ # Array non-head elements carry a copy of the head's @ address at this point;
28
+ # overwrite with the stride-accumulated address for this element.
29
+ child_inst.address = Value.new(current_address, :bit, 64, nil)
30
+ end
31
+ end
32
+
33
+ def calc_alignment(inst, child_inst)
34
+ inst_alignment = child_inst.alignment&.value
35
+ return inst_alignment if inst_alignment
36
+
37
+ alignments = [
38
+ inst.property_value(:alignment)&.value || 0,
39
+ child_inst.accesswidth / 8,
40
+ alignment_by_addressing(inst, child_inst)
41
+ ]
42
+ alignments.max
43
+ end
44
+
45
+ def alignment_by_addressing(inst, child_inst)
46
+ addressing = inst.property_value(:addressing).value
47
+ case addressing
48
+ when :compact then child_inst.accesswidth / 8
49
+ when :regalign then aligned_size(child_inst)
50
+ when :fullalign then calc_fullalign_alignment(child_inst)
51
+ end
52
+ end
53
+
54
+ def calc_fullalign_alignment(child_inst)
55
+ return aligned_size(child_inst) unless child_inst.array?
56
+
57
+ array_size = calc_occupied_size(child_inst)
58
+ if power_of_2?(array_size, 1)
59
+ array_size
60
+ else
61
+ 2**array_size.bit_length
62
+ end
63
+ end
64
+
65
+ def calc_next_address(child_inst)
66
+ delta =
67
+ if child_inst.last_element?
68
+ child_inst.size
69
+ else
70
+ element_stride(child_inst)
71
+ end
72
+ child_inst.address.value + delta
73
+ end
74
+
75
+ def check_overlapping_regs(instance)
76
+ # For now, support reg only but not regfile
77
+ regs = instance.instances.select { |reg| reg.reg? && reg.first_element? }
78
+ regs.combination(2).each do |(reg_a, reg_b)|
79
+ next unless overlapping_pair?(reg_a, reg_b)
80
+
81
+ message = 'overlapping regs not allowed'
82
+ raise_evaluation_error message, reg_a.token_range, reg_b.token_range
83
+ end
84
+ end
85
+
86
+ def overlapping_pair?(reg_a, reg_b)
87
+ range_a = calc_address_range(reg_a)
88
+ range_b = calc_address_range(reg_b)
89
+ return false unless range_a.include?(range_b.begin) || range_b.include?(range_a.begin)
90
+
91
+ r_a = reg_a.sw_readable?
92
+ w_a = reg_a.sw_writable?
93
+ r_b = reg_b.sw_readable?
94
+ w_b = reg_b.sw_writable?
95
+ (r_a && r_b) || (w_a && w_b)
96
+ end
97
+
98
+ def calc_address_range(child_inst)
99
+ base = child_inst.address.value
100
+ size = calc_occupied_size(child_inst)
101
+ base...(base + size)
102
+ end
103
+
104
+ def roundup(dividend, divisor)
105
+ divisor * dividend.ceildiv(divisor)
106
+ end
107
+
108
+ def aligned_size(child_inst)
109
+ roundup(child_inst.size, child_inst.accesswidth / 8)
110
+ end
111
+
112
+ def calc_occupied_size(child_inst)
113
+ if child_inst.array?
114
+ n_elements = child_inst.array_info.n_elements
115
+ ((n_elements - 1) * element_stride(child_inst)) + child_inst.size
116
+ else
117
+ child_inst.size
118
+ end
119
+ end
120
+
121
+ def element_stride(child_inst)
122
+ child_inst.stride&.value || aligned_size(child_inst)
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SystemRDL
4
+ module Evaluator
5
+ module AddressOperation
6
+ private
7
+
8
+ def apply_explicit_address(instance, inst_values)
9
+ address = inst_values[:address_assignment]
10
+ return unless address
11
+
12
+ instance.address = address
13
+ end
14
+
15
+ def apply_stride(instance, inst_values)
16
+ stride = inst_values[:address_stride]
17
+ return unless stride
18
+
19
+ unless instance.array?
20
+ message = "address stride not allowed for scalar #{layer} instance"
21
+ raise_evaluation_error message, stride.token_range
22
+ end
23
+
24
+ instance.stride = stride
25
+ end
26
+
27
+ def apply_alignment(instance, inst_values)
28
+ alignment = inst_values[:address_alignment]
29
+ return unless alignment
30
+
31
+ instance.alignment = alignment
32
+ end
33
+
34
+ def apply_address_operations(instance, inst_values)
35
+ apply_explicit_address(instance, inst_values)
36
+ apply_stride(instance, inst_values)
37
+ apply_alignment(instance, inst_values)
38
+
39
+ return unless instance.address && instance.alignment
40
+
41
+ message = '@ and %= address operations are mutually exclusive'
42
+ raise_evaluation_error message, instance.address.token_range, instance.alignment.token_range
43
+ end
44
+
45
+ def check_range(inst_values)
46
+ range = inst_values[:range]
47
+ return unless range
48
+
49
+ message = "range not allowed for #{layer} instance"
50
+ raise_evaluation_error message, range.token_range
51
+ end
52
+
53
+ def check_accesswidth_boundary(instance, label, value)
54
+ return unless value
55
+
56
+ accesswidth = instance.accesswidth
57
+ return if (value.value % (accesswidth / 8)) == 0
58
+
59
+ message =
60
+ "#{label} not aligned to accesswidth: " \
61
+ "#{label} 0x#{value.value.to_s(16)} accesswidth #{accesswidth}"
62
+ raise_evaluation_error message, value.token_range
63
+ end
64
+
65
+ def check_address(instance)
66
+ check_accesswidth_boundary(instance, :address, instance.address)
67
+ end
68
+
69
+ def check_stride(instance)
70
+ stride = instance.stride
71
+ return unless stride
72
+
73
+ check_accesswidth_boundary(instance, :stride, stride)
74
+
75
+ size = instance.size
76
+ return if stride.value >= size
77
+
78
+ message =
79
+ "stride less than #{instance.layer} size: " \
80
+ "stride 0x#{stride.value.to_s(16)} #{instance.layer} size #{size}"
81
+ raise_evaluation_error message, stride.token_range
82
+ end
83
+
84
+ def check_alignment(instance)
85
+ alignment = instance.alignment
86
+ return unless alignment
87
+
88
+ check_accesswidth_boundary(instance, :alignment, alignment)
89
+
90
+ return if alignment.value > 0
91
+
92
+ message = 'alignment must be positive'
93
+ raise_evaluation_error message, alignment.token_range
94
+ end
95
+
96
+ def check_address_operations(instance)
97
+ check_address(instance)
98
+ check_stride(instance)
99
+ check_alignment(instance)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SystemRDL
4
+ module Evaluator
5
+ class AddrMapDefinition < ComponentDefinition
6
+ include AddressAllocation
7
+ include ContainerComponent
8
+
9
+ def evaluate(instance, **optargs)
10
+ if instance.root?
11
+ create_instance(instance, @id, nil, nil, nil, **optargs)
12
+ else
13
+ super
14
+ end
15
+ end
16
+
17
+ def validate(instance)
18
+ check_structural_component_instances(instance)
19
+ check_alignment(instance)
20
+ check_endianness_exclusivity(instance)
21
+ check_rsvdset_exclusivity(instance)
22
+ check_bit_ordering_exclusivity(instance)
23
+ end
24
+
25
+ def finalize(instance)
26
+ allocate_addresses(instance)
27
+ check_overlapping_regs(instance)
28
+ end
29
+
30
+ def layer
31
+ :addrmap
32
+ end
33
+
34
+ private
35
+
36
+ def instance_class
37
+ AddrMapInstance
38
+ end
39
+
40
+ def check_alignment(instance)
41
+ check_power_of_2(instance, :alignment, 1)
42
+ end
43
+
44
+ def check_endianness_exclusivity(instance)
45
+ check_property_exclusivity(instance, [:bigendian, :littleendian])
46
+ end
47
+
48
+ def check_rsvdset_exclusivity(instance)
49
+ check_property_exclusivity(instance, [:rsvdset, :rsvdsetX])
50
+ end
51
+
52
+ def check_bit_ordering_exclusivity(instance)
53
+ check_property_exclusivity(instance, [:msb0, :lsb0])
54
+ end
55
+ end
56
+
57
+ class AddrMapInstance < Instance
58
+ def definable?(definition)
59
+ definition.layer in :addrmap | :regfile | :reg | :field
60
+ end
61
+
62
+ def instantiable?(definition)
63
+ definition.layer in :addrmap | :regfile | :reg
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SystemRDL
4
+ module Evaluator
5
+ ArrayInfo = Data.define(:id, :indices, :sizes, :first, :last) do
6
+ def n_elements
7
+ sizes.inject(:*)
8
+ end
9
+ end
10
+
11
+ module ArrayComponent
12
+ private
13
+
14
+ def eval_array(inst_values)
15
+ array = inst_values[:array]
16
+ return super unless array
17
+
18
+ sizes = array.elements.map do |size|
19
+ next size.value if size.value > 0
20
+
21
+ message = 'array size must be positive'
22
+ raise_evaluation_error message, size.token_range
23
+ end
24
+
25
+ index_list =
26
+ sizes
27
+ .map { |size| size.times.to_a }
28
+ .then { |list| list[0].product(*list[1..]) }
29
+ id = array_id
30
+ index_list.each.with_index(1) do |indices, i|
31
+ info = ArrayInfo.new(id, indices, sizes, i == 1, i == index_list.size)
32
+ yield(info)
33
+ end
34
+ end
35
+
36
+ def array_id
37
+ @array_id ||= - 1
38
+ @array_id += 1
39
+ end
40
+ end
41
+ end
42
+ end