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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +127 -0
- data/lib/systemrdl/error.rb +55 -0
- data/lib/systemrdl/evaluator/address_allocation.rb +126 -0
- data/lib/systemrdl/evaluator/address_operation.rb +103 -0
- data/lib/systemrdl/evaluator/addrmap.rb +67 -0
- data/lib/systemrdl/evaluator/array_component.rb +42 -0
- data/lib/systemrdl/evaluator/builtin_properties.rb +400 -0
- data/lib/systemrdl/evaluator/common.rb +22 -0
- data/lib/systemrdl/evaluator/component_definition.rb +185 -0
- data/lib/systemrdl/evaluator/component_insts.rb +106 -0
- data/lib/systemrdl/evaluator/container_component.rb +16 -0
- data/lib/systemrdl/evaluator/field.rb +245 -0
- data/lib/systemrdl/evaluator/instance.rb +120 -0
- data/lib/systemrdl/evaluator/list.rb +26 -0
- data/lib/systemrdl/evaluator/literal.rb +173 -0
- data/lib/systemrdl/evaluator/operation.rb +241 -0
- data/lib/systemrdl/evaluator/processor.rb +166 -0
- data/lib/systemrdl/evaluator/property.rb +116 -0
- data/lib/systemrdl/evaluator/property_assignment.rb +220 -0
- data/lib/systemrdl/evaluator/reference.rb +135 -0
- data/lib/systemrdl/evaluator/reg.rb +156 -0
- data/lib/systemrdl/evaluator/regfile.rb +33 -0
- data/lib/systemrdl/evaluator/root.rb +39 -0
- data/lib/systemrdl/evaluator/value.rb +13 -0
- data/lib/systemrdl/evaluator.rb +14 -0
- data/lib/systemrdl/model/instance.rb +194 -0
- data/lib/systemrdl/model/value.rb +54 -0
- data/lib/systemrdl/model.rb +11 -0
- data/lib/systemrdl/parser/generated_parser.rb +2808 -0
- data/lib/systemrdl/parser/node.rb +17 -0
- data/lib/systemrdl/parser/parser.rb +86 -0
- data/lib/systemrdl/parser/scanner.rb +251 -0
- data/lib/systemrdl/parser/systemrdl.y +448 -0
- data/lib/systemrdl/parser/token.rb +58 -0
- data/lib/systemrdl/parser.rb +11 -0
- data/lib/systemrdl/version.rb +5 -0
- data/lib/systemrdl.rb +90 -0
- metadata +96 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SystemRDL
|
|
4
|
+
module Parser
|
|
5
|
+
class Node < AST::Node
|
|
6
|
+
attr_reader :token_range
|
|
7
|
+
|
|
8
|
+
def replace_type(type)
|
|
9
|
+
updated(type, nil, nil)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def replace_token_range(token_range)
|
|
13
|
+
updated(nil, nil, { token_range: token_range })
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SystemRDL
|
|
4
|
+
module Parser
|
|
5
|
+
class Parser < GeneratedParser
|
|
6
|
+
include RaiseParseError
|
|
7
|
+
|
|
8
|
+
def initialize(scanner, debug: false)
|
|
9
|
+
@scanner = scanner
|
|
10
|
+
@yydebug = debug
|
|
11
|
+
super()
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def parse
|
|
15
|
+
do_parse
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def next_token
|
|
21
|
+
@scanner.next_token
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def on_error(_token_id, value, _value_stack)
|
|
25
|
+
parse_error(value)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parse_error(value)
|
|
29
|
+
message = "syntax error on value '#{value.text}' (#{value.kind})"
|
|
30
|
+
raise_parse_error message, value.position
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_list(values, include_separator:)
|
|
34
|
+
if include_separator
|
|
35
|
+
[values[0], *values[1]&.map { |_, value| value }]
|
|
36
|
+
else
|
|
37
|
+
[values[0], *values[1]]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def node(kind, children, values)
|
|
42
|
+
token_range = to_token_range(values)
|
|
43
|
+
Node.new(kind, children, { token_range: token_range })
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def array_node(values)
|
|
47
|
+
values = values.flatten
|
|
48
|
+
children = values.filter_map.with_index { |v, i| i % 3 == 1 ? v : nil }
|
|
49
|
+
node(:array, children, values)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def component_insts_node(type, insts)
|
|
53
|
+
case type&.kind
|
|
54
|
+
when :EXTERNAL then insts.replace_type(:external_component_insts)
|
|
55
|
+
when :INTERNAL then insts.replace_type(:internal_component_insts)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def component_inst_node(values)
|
|
60
|
+
array, range = values[1]
|
|
61
|
+
node(:component_inst, [values[0], array, range, *values[2..]], values)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def uop_node(values)
|
|
65
|
+
node(:unary_operation, values, values)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def bop_node(values)
|
|
69
|
+
node(:binary_operation, [values[1], values[0], values[2]], values)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def to_token_range(values)
|
|
73
|
+
values = values.compact
|
|
74
|
+
head = values.first
|
|
75
|
+
tail = values.last
|
|
76
|
+
if values.size == 1 && head.is_a?(Node)
|
|
77
|
+
head.token_range
|
|
78
|
+
else
|
|
79
|
+
head_token = (head.is_a?(Node) && head.token_range.head) || head
|
|
80
|
+
tail_token = (tail.is_a?(Node) && tail.token_range.tail) || tail
|
|
81
|
+
TokenRange.new(head_token, tail_token)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SystemRDL
|
|
4
|
+
module Parser
|
|
5
|
+
class Scanner
|
|
6
|
+
include RaiseParseError
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def keyword_patterns
|
|
12
|
+
[
|
|
13
|
+
'abstract', 'accesstype', 'addressingtype', 'addrmap', 'alias',
|
|
14
|
+
'all', 'bit', 'boolean', 'bothedge', 'compact',
|
|
15
|
+
'component', 'componentwidth', 'constraint', 'default', 'encode',
|
|
16
|
+
'enum', 'external', 'false', 'field', 'fullalign',
|
|
17
|
+
'hw', 'inside', 'internal', 'level', 'longint',
|
|
18
|
+
'mem', 'na', 'negedge', 'nonsticky', 'number',
|
|
19
|
+
'onreadtype', 'onwritetype', 'posedge', 'property', 'r',
|
|
20
|
+
'rclr', 'ref', 'reg', 'regalign', 'regfile',
|
|
21
|
+
'rset', 'ruser', 'rw', 'rw1', 'signal',
|
|
22
|
+
'string', 'struct', 'sw', 'this', 'true',
|
|
23
|
+
'type', 'unsigned', 'w', 'w1', 'wclr',
|
|
24
|
+
'woclr', 'woset', 'wot', 'wr', 'wset',
|
|
25
|
+
'wuser', 'wzc', 'wzs', 'wzt'
|
|
26
|
+
].to_h { |kw| [kw, kw.upcase.to_sym] }.freeze
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def symbol_patterns
|
|
30
|
+
patterns = [
|
|
31
|
+
'[', ']', '(', ')', '{', '}',
|
|
32
|
+
'!', '&&', '||', '<', '>', '<=', '>=', '==', '!=', '>>', '<<',
|
|
33
|
+
'~', '&', '~&', '|', '~|', '^', '~^', '^~', '*', '/', '%', '+', '-', '**',
|
|
34
|
+
'?', ':', '->', '.', ',', "'", ';', '=', '@', '+=', '%='
|
|
35
|
+
]
|
|
36
|
+
patterns
|
|
37
|
+
.sort_by(&:size)
|
|
38
|
+
.reverse
|
|
39
|
+
.to_h { |pattern| [Regexp.new(Regexp.escape(pattern)), pattern] }
|
|
40
|
+
.freeze
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
WHITE_SPACES = /[ \t\n\r]+/
|
|
45
|
+
|
|
46
|
+
LINE_COMMENT = %r{//.*$}
|
|
47
|
+
BLOCK_COMMENT = %r{/\*(?:(?!\*/).)*\*/}m
|
|
48
|
+
UNTERMINATED_BLOCK_COMMENT = %r{/\*(?:(?!\*/).)*}m
|
|
49
|
+
|
|
50
|
+
KEYWORDS = keyword_patterns
|
|
51
|
+
|
|
52
|
+
SYMBOLS = symbol_patterns
|
|
53
|
+
|
|
54
|
+
VERILOG_BIN_NUMBER = /(\d+)'[bB]([01][01_]*)/
|
|
55
|
+
VERILOG_DEC_NUMBER = /(\d+)'[dD](\d[\d_]*)/
|
|
56
|
+
VERILOG_HEX_NUMBER = /(\d+)'[hH](\h[\h_]*)/
|
|
57
|
+
|
|
58
|
+
DEC_NUMBER = /\d[\d_]*/
|
|
59
|
+
HEX_NUMBER = /0[xX]\h[\h_]*/
|
|
60
|
+
|
|
61
|
+
NUMBERS = {
|
|
62
|
+
VERILOG_HEX_NUMBER => :VERILOG_NUMBER,
|
|
63
|
+
VERILOG_DEC_NUMBER => :VERILOG_NUMBER,
|
|
64
|
+
VERILOG_BIN_NUMBER => :VERILOG_NUMBER,
|
|
65
|
+
HEX_NUMBER => :NUMBER,
|
|
66
|
+
DEC_NUMBER => :NUMBER
|
|
67
|
+
}.freeze
|
|
68
|
+
|
|
69
|
+
def initialize(code, filename, test)
|
|
70
|
+
@ss = StringScanner.new(code)
|
|
71
|
+
@filename = filename
|
|
72
|
+
@line = 1
|
|
73
|
+
@column = 1
|
|
74
|
+
@control_tokens = []
|
|
75
|
+
@control_tokens << create_test_token(test) if test
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def next_token
|
|
79
|
+
if @control_tokens.empty?
|
|
80
|
+
skip_blank
|
|
81
|
+
push_eos_tokens if eos?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
token =
|
|
85
|
+
if @control_tokens.empty?
|
|
86
|
+
scan_next_token
|
|
87
|
+
else
|
|
88
|
+
@control_tokens.shift
|
|
89
|
+
end
|
|
90
|
+
token && [token.kind, token]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def eos?
|
|
96
|
+
@ss.eos?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def skip_blank
|
|
100
|
+
loop do
|
|
101
|
+
next if scan(WHITE_SPACES)
|
|
102
|
+
next if scan(LINE_COMMENT)
|
|
103
|
+
next if scan_block_comment
|
|
104
|
+
|
|
105
|
+
break
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def scan_block_comment
|
|
110
|
+
comment = scan(BLOCK_COMMENT)
|
|
111
|
+
return comment if comment
|
|
112
|
+
|
|
113
|
+
if (_, line, column = scan(UNTERMINATED_BLOCK_COMMENT))
|
|
114
|
+
pos = Position.new(@filename, line, column)
|
|
115
|
+
raise_parse_error 'unterminated block comment', pos
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def push_eos_tokens
|
|
120
|
+
@control_tokens << create_control_token(:EOS)
|
|
121
|
+
@control_tokens << nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def scan(pattern)
|
|
125
|
+
text = @ss.scan(pattern)
|
|
126
|
+
return unless text
|
|
127
|
+
|
|
128
|
+
line = @line
|
|
129
|
+
column = @column
|
|
130
|
+
update_state(text)
|
|
131
|
+
|
|
132
|
+
[text, line, column]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def scan_token(kind, pattern)
|
|
136
|
+
text, line, column = scan(pattern)
|
|
137
|
+
text && create_token(kind, text, line, column)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def peek(pattern)
|
|
141
|
+
@ss.check(pattern)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def peek_char
|
|
145
|
+
peek(/./m)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def advance(text)
|
|
149
|
+
@ss.pos += text.bytesize
|
|
150
|
+
update_state(text)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def update_state(text)
|
|
154
|
+
return if text.empty?
|
|
155
|
+
|
|
156
|
+
n_newline = text.count("\n")
|
|
157
|
+
@line += n_newline
|
|
158
|
+
|
|
159
|
+
@column =
|
|
160
|
+
if text[-1] == "\n"
|
|
161
|
+
1
|
|
162
|
+
elsif n_newline > 0
|
|
163
|
+
last = text.lines.last
|
|
164
|
+
last.length
|
|
165
|
+
else
|
|
166
|
+
@column + text.length
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def create_token(kind, text, line, column)
|
|
171
|
+
position = Position.new(@filename, line, column)
|
|
172
|
+
Token.new(text, kind, position)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def create_control_token(kind)
|
|
176
|
+
create_token(kind, '', @line, @column)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def create_test_token(test)
|
|
180
|
+
kind = :"__test_#{test}__".upcase
|
|
181
|
+
create_control_token(kind)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def current_position
|
|
185
|
+
Position.new(@filename, @line, @column)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def scan_next_token
|
|
189
|
+
token =
|
|
190
|
+
scan_string || scan_number || scan_symbol || scan_word
|
|
191
|
+
return token if token
|
|
192
|
+
|
|
193
|
+
char = peek_char
|
|
194
|
+
raise_parse_error "illegal character `#{char}`", current_position
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def scan_string
|
|
198
|
+
return unless peek(/"/)
|
|
199
|
+
|
|
200
|
+
line = @line
|
|
201
|
+
column = @column
|
|
202
|
+
|
|
203
|
+
buffer = []
|
|
204
|
+
while (char = peek_char)
|
|
205
|
+
advance(char)
|
|
206
|
+
if char == '\\' && ((next_char, _, _) = scan(/"/))
|
|
207
|
+
buffer << next_char
|
|
208
|
+
else
|
|
209
|
+
buffer << char
|
|
210
|
+
break if buffer.size >= 2 && buffer.last == '"'
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
text = buffer.join
|
|
215
|
+
create_token(:STRING, text, line, column)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def scan_number
|
|
219
|
+
NUMBERS.each do |pattern, kind|
|
|
220
|
+
token = scan_token(kind, pattern)
|
|
221
|
+
return token if token
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
nil
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def scan_symbol
|
|
228
|
+
SYMBOLS.each do |pattern, kind|
|
|
229
|
+
token = scan_token(kind, pattern)
|
|
230
|
+
return token if token
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
nil
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def scan_word
|
|
237
|
+
text, line, column = scan(/[_a-zA-Z]\w*/)
|
|
238
|
+
return unless text
|
|
239
|
+
|
|
240
|
+
KEYWORDS.each do |pattern, kind|
|
|
241
|
+
next if text != pattern
|
|
242
|
+
|
|
243
|
+
token = create_token(kind, text, line, column)
|
|
244
|
+
return token
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
create_token(:SIMPLE_ID, text, line, column)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|