hdl 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +127 -0
- data/lib/hdl/base.rb +24 -0
- data/lib/hdl/chip/schema_chip/evaluator.rb +141 -0
- data/lib/hdl/chip/schema_chip.rb +74 -0
- data/lib/hdl/chip/table_chip.rb +39 -0
- data/lib/hdl/chip.rb +41 -0
- data/lib/hdl/dependency.rb +70 -0
- data/lib/hdl/loader.rb +58 -0
- data/lib/hdl/parser/grammar/hdl.treetop +17 -0
- data/lib/hdl/parser/grammar/pins.treetop +23 -0
- data/lib/hdl/parser/grammar/schema.treetop +49 -0
- data/lib/hdl/parser/grammar/table.treetop +42 -0
- data/lib/hdl/parser/grammar/vars.treetop +33 -0
- data/lib/hdl/parser/tree_walker.rb +13 -0
- data/lib/hdl/parser/validator/input_validator.rb +13 -0
- data/lib/hdl/parser/validator/schema_validator.rb +44 -0
- data/lib/hdl/parser/validator/table_validator.rb +42 -0
- data/lib/hdl/parser/validator.rb +28 -0
- data/lib/hdl/parser.rb +17 -0
- data/lib/hdl.rb +21 -0
- metadata +127 -0
data/README.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
## HDL
|
2
|
+
|
3
|
+
A parser and emulator for a minimalist [hardware description language](http://en.wikipedia.org/wiki/Hardware_description_language).
|
4
|
+
|
5
|
+
## Chips
|
6
|
+
|
7
|
+
Here's an example definition of a chip called 'and.hdl':
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# and.hdl
|
11
|
+
inputs a, b
|
12
|
+
outputs out
|
13
|
+
|
14
|
+
nand(a=a, b=b, out=x)
|
15
|
+
nand(a=x, b=x, out=out)
|
16
|
+
```
|
17
|
+
|
18
|
+
The equivalent circuit diagram is:
|
19
|
+
|
20
|
+
!['and' gate from 'nand'](http://upload.wikimedia.org/wikipedia/commons/1/16/AND_from_NAND.svg)
|
21
|
+
|
22
|
+
## Tables
|
23
|
+
|
24
|
+
The above chip references another chip called 'nand'.
|
25
|
+
|
26
|
+
Here's its definition, as a truth table:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
# nand.hdl
|
30
|
+
inputs a, b
|
31
|
+
outputs out
|
32
|
+
|
33
|
+
| a | b | out |
|
34
|
+
| 0 | 0 | 1 |
|
35
|
+
| 0 | 1 | 1 |
|
36
|
+
| 1 | 0 | 1 |
|
37
|
+
| 1 | 1 | 0 |
|
38
|
+
```
|
39
|
+
|
40
|
+
If you'd prefer, you can use 'T' and 'F'.
|
41
|
+
|
42
|
+
## Ruby
|
43
|
+
|
44
|
+
Now that we've satisfied the 'nand' dependency, we can write some Ruby:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
require "hdl"
|
48
|
+
|
49
|
+
chip = HDL.load("and")
|
50
|
+
chip.evaluate(a: true, b: false)
|
51
|
+
#=> { out: false }
|
52
|
+
```
|
53
|
+
|
54
|
+
The 'nand' chip is automatically loaded when it is referenced.
|
55
|
+
|
56
|
+
## Path
|
57
|
+
|
58
|
+
By default, chips in the current directory will be discovered.
|
59
|
+
|
60
|
+
You can expand this search by adding to your path:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
HDL.path << "chips"
|
64
|
+
```
|
65
|
+
|
66
|
+
## Parsing
|
67
|
+
|
68
|
+
If you'd rather parse your chips directly in Ruby, you can do so with:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
chip = HDL.parse(name, definition)
|
72
|
+
```
|
73
|
+
|
74
|
+
This might be useful if you're storing definitions in a database.
|
75
|
+
|
76
|
+
## Miscellaneous
|
77
|
+
|
78
|
+
Here are some useful methods for querying properties of chips:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
chip.name
|
82
|
+
#=> "and"
|
83
|
+
|
84
|
+
chip.path
|
85
|
+
#=> "./and.hdl"
|
86
|
+
|
87
|
+
chip.inputs
|
88
|
+
#=> [:a, :b]
|
89
|
+
|
90
|
+
chip.outputs
|
91
|
+
#=> [:out]
|
92
|
+
|
93
|
+
chip.internal
|
94
|
+
#=> [:x]
|
95
|
+
|
96
|
+
chip.components
|
97
|
+
#=> { #<HDL::Chip nand> => 2 }
|
98
|
+
|
99
|
+
chip.primitive? # Does this chip contain a truth table?
|
100
|
+
#=> false
|
101
|
+
|
102
|
+
chip.primitives
|
103
|
+
#=> [#<HDL::Chip nand>]
|
104
|
+
|
105
|
+
chip.dependents # Chips that this chip uses.
|
106
|
+
#=> [#<HDL::Chip nand>]
|
107
|
+
|
108
|
+
chip.dependees # Chips that use this chip.
|
109
|
+
#=> []
|
110
|
+
```
|
111
|
+
|
112
|
+
## Contribution
|
113
|
+
|
114
|
+
I'm not an electronics engineer. If you are, you could probably build in all kinds of cool features and optimisations.
|
115
|
+
|
116
|
+
Here a few features that might be in the pipeline:
|
117
|
+
|
118
|
+
* Let me build a CNF expression for a chip
|
119
|
+
* Query method for circuit fan-outs for chips
|
120
|
+
* Evaluate and return outputs with internal pins
|
121
|
+
* Support for buses, mostly for convenience
|
122
|
+
* Pushing stricter validations to parse time
|
123
|
+
* Cleaner decoupling between the parser and evaluator
|
124
|
+
|
125
|
+
If you'd like to build any of these features, I'd be super grateful. Send me a pull request, or open an issue.
|
126
|
+
|
127
|
+
You should follow me on [twitter](http://twitter.com/cpatuzzo).
|
data/lib/hdl/base.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module HDL
|
2
|
+
class << self
|
3
|
+
|
4
|
+
def version
|
5
|
+
"1.0.0"
|
6
|
+
end
|
7
|
+
|
8
|
+
def path
|
9
|
+
Loader.path
|
10
|
+
end
|
11
|
+
|
12
|
+
def load(name)
|
13
|
+
Loader.load(name, :force => true)
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse(name, definition)
|
17
|
+
Loader.load(name,
|
18
|
+
:force => true,
|
19
|
+
:definition => definition
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
class HDL::SchemaChip::Evaluator
|
2
|
+
|
3
|
+
def initialize(chip, schema)
|
4
|
+
@chip = chip
|
5
|
+
@schema = schema
|
6
|
+
@parts = partition_io
|
7
|
+
@order = evaluation_order(@parts, chip.inputs)
|
8
|
+
|
9
|
+
check_multi_internal!
|
10
|
+
end
|
11
|
+
|
12
|
+
def evaluate(pins)
|
13
|
+
results = @order.inject(pins) do |known_pins, i|
|
14
|
+
known_pins.merge(evaluate_line(@parts[i], known_pins))
|
15
|
+
end
|
16
|
+
|
17
|
+
filter_outputs(results)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def evaluate_line(line, pins)
|
22
|
+
chip, inputs, outputs = line
|
23
|
+
|
24
|
+
chip_in = connect_inputs(inputs, pins)
|
25
|
+
chip_out = chip.evaluate(chip_in)
|
26
|
+
|
27
|
+
connect_outputs(outputs, chip_out)
|
28
|
+
end
|
29
|
+
|
30
|
+
def connect_inputs(inputs, pins)
|
31
|
+
is_bool = lambda { |x| [true, false].include?(x) }
|
32
|
+
|
33
|
+
inputs.inject({}) do |hash, (dest, source)|
|
34
|
+
value = is_bool[source] ? source : pins[source]
|
35
|
+
hash.merge(dest => value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def connect_outputs(outputs, pins)
|
40
|
+
outputs.inject({}) do |hash, (dest, source)|
|
41
|
+
hash.merge(source => pins[dest])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def filter_outputs(pins)
|
46
|
+
f = pins.select { |k, _| @chip.outputs.include?(k) }
|
47
|
+
Hash[f]
|
48
|
+
end
|
49
|
+
|
50
|
+
def evaluation_order(schema_lines, known_pins, first_call = true)
|
51
|
+
schema_lines = schema_lines.each_with_index if first_call
|
52
|
+
|
53
|
+
known, unknown = schema_lines.partition do |line, _|
|
54
|
+
all_inputs_known?(line, known_pins)
|
55
|
+
end
|
56
|
+
|
57
|
+
known_outputs = known.map do |line, _|
|
58
|
+
outputs_for_line(line)
|
59
|
+
end.flatten
|
60
|
+
|
61
|
+
check_for_new_information!(known_outputs, unknown)
|
62
|
+
|
63
|
+
if unknown.any?
|
64
|
+
# Recursive case.
|
65
|
+
next_pins = known_pins + known_outputs
|
66
|
+
tail = evaluation_order(unknown, next_pins, false)
|
67
|
+
else
|
68
|
+
# Base case.
|
69
|
+
tail = []
|
70
|
+
end
|
71
|
+
|
72
|
+
known_indexes = known.map(&:last)
|
73
|
+
known_indexes + tail
|
74
|
+
end
|
75
|
+
|
76
|
+
def all_inputs_known?(line, known_pins)
|
77
|
+
_, inputs, _ = line
|
78
|
+
inputs.values.all? do |pin|
|
79
|
+
(known_pins + [true, false]).include?(pin)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def partition_io
|
84
|
+
@schema.map do |line|
|
85
|
+
chip_name = line.keys.first
|
86
|
+
wiring = line.values.first
|
87
|
+
dest_pins = wiring.keys
|
88
|
+
source_pins = wiring.values
|
89
|
+
chip = fetch_chip(chip_name)
|
90
|
+
|
91
|
+
inputs, outputs = wiring.partition do |dest, source|
|
92
|
+
chip.inputs.include?(dest)
|
93
|
+
end
|
94
|
+
|
95
|
+
[chip, Hash[inputs], Hash[outputs]]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def outputs_for_line(line)
|
100
|
+
line.last.values
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_for_new_information!(information, unknown)
|
104
|
+
if information.empty?
|
105
|
+
p = unknown.map do |(chip, _, _), exp|
|
106
|
+
{ (exp + 1) => chip.name }.inspect
|
107
|
+
end.join(", ")
|
108
|
+
|
109
|
+
if unknown.size == 1
|
110
|
+
err = "Unknown internal pin for expression: #{p}"
|
111
|
+
else
|
112
|
+
err = "Unknowable internal pins for expressions: #{p}"
|
113
|
+
end
|
114
|
+
|
115
|
+
raise CircularError, err
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# We can't check this at parse time because we
|
120
|
+
# don't know which are getters/setters.
|
121
|
+
def check_multi_internal!
|
122
|
+
set_pins = @parts.map do |_, _, outputs|
|
123
|
+
outputs.values
|
124
|
+
end.flatten
|
125
|
+
|
126
|
+
repetitions = set_pins.select { |p| set_pins.count(p) > 1 }.uniq
|
127
|
+
if repetitions.size == 1
|
128
|
+
err = "The internal pin `#{repetitions.first}' is set multiple times"
|
129
|
+
raise ParseError, err
|
130
|
+
elsif repetitions.size > 1
|
131
|
+
list = repetitions.join(', ')
|
132
|
+
err = "These internal pins are set multiple times: #{list}"
|
133
|
+
raise ParseError, err
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def fetch_chip(name)
|
138
|
+
HDL::Loader.load(name)
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class HDL::SchemaChip < HDL::Chip
|
2
|
+
|
3
|
+
def initialize(name, path, data)
|
4
|
+
super
|
5
|
+
@schema = data[:schema]
|
6
|
+
create_dependencies
|
7
|
+
load_dependencies(:dependents)
|
8
|
+
evaluate_some_expression
|
9
|
+
end
|
10
|
+
|
11
|
+
def internal
|
12
|
+
wiring_values - inputs - outputs - [true, false]
|
13
|
+
end
|
14
|
+
|
15
|
+
def components
|
16
|
+
chips = component_names.map do |name|
|
17
|
+
HDL::Loader.load(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
freq = frequencies(chips)
|
21
|
+
hashes = [freq] + chips.map(&:components)
|
22
|
+
|
23
|
+
hashes.inject({}) do |acc, hash|
|
24
|
+
acc.merge(hash) { |_, a, b| a + b }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def primitive?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def evaluate(pins = {})
|
33
|
+
check_pins!(pins)
|
34
|
+
evaluator.evaluate(pins)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def create_dependencies
|
39
|
+
component_names.each do |dep|
|
40
|
+
HDL::Dependency.create(name, dep.to_s)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def component_names
|
45
|
+
@schema.map(&:keys).flatten
|
46
|
+
end
|
47
|
+
|
48
|
+
def wiring
|
49
|
+
@schema.map(&:values).flatten
|
50
|
+
end
|
51
|
+
|
52
|
+
def wiring_values
|
53
|
+
wiring.map(&:values).flatten.uniq
|
54
|
+
end
|
55
|
+
|
56
|
+
def evaluator
|
57
|
+
@evaluator ||= Evaluator.new(self, @schema)
|
58
|
+
end
|
59
|
+
|
60
|
+
def frequencies(array)
|
61
|
+
array.inject(Hash.new(0)) do |hash, element|
|
62
|
+
hash[element] += 1
|
63
|
+
hash
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check upfront if there are circular problems
|
68
|
+
# by evaluating something on the chip.
|
69
|
+
def evaluate_some_expression
|
70
|
+
pins = Hash[inputs.zip(inputs.map { true })]
|
71
|
+
evaluate(pins)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class HDL::TableChip < HDL::Chip
|
2
|
+
|
3
|
+
def initialize(name, path, data)
|
4
|
+
super
|
5
|
+
@table = data[:table]
|
6
|
+
end
|
7
|
+
|
8
|
+
def internal
|
9
|
+
[]
|
10
|
+
end
|
11
|
+
|
12
|
+
def components
|
13
|
+
{}
|
14
|
+
end
|
15
|
+
|
16
|
+
def primitive?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def evaluate(pins = {})
|
21
|
+
check_pins!(pins)
|
22
|
+
|
23
|
+
row = find_row(pins)
|
24
|
+
select_outputs(row)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def find_row(pins)
|
29
|
+
@table.detect do |row|
|
30
|
+
inputs.all? { |i| row[i] == pins[i] }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def select_outputs(row)
|
35
|
+
filtered = row.select { |p, _| outputs.include?(p) }
|
36
|
+
Hash[filtered]
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/hdl/chip.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
class HDL::Chip
|
2
|
+
|
3
|
+
attr_reader :name, :path, :inputs, :outputs
|
4
|
+
|
5
|
+
def initialize(name, path, data)
|
6
|
+
@name = name.to_s
|
7
|
+
@path = path
|
8
|
+
@inputs = data[:inputs]
|
9
|
+
@outputs = data[:outputs]
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
"#<HDL::Chip #{name}>"
|
14
|
+
end
|
15
|
+
|
16
|
+
def primitives
|
17
|
+
dependents.select { |d| d.primitive? }
|
18
|
+
end
|
19
|
+
|
20
|
+
def dependents
|
21
|
+
load_dependencies(:dependents)
|
22
|
+
end
|
23
|
+
|
24
|
+
def dependees
|
25
|
+
load_dependencies(:dependees)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def load_dependencies(type)
|
30
|
+
deps = HDL::Dependency.send("#{type}_for", name)
|
31
|
+
deps.map { |d| HDL::Loader.load(d) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_pins!(pins)
|
35
|
+
unless pins.keys.to_set == inputs.to_set
|
36
|
+
err = "Expecting inputs #{inputs.inspect}, given #{pins.keys.inspect}"
|
37
|
+
raise ArgumentError, err
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class HDL::Dependency
|
2
|
+
class << self
|
3
|
+
|
4
|
+
# dependee depends on dependent
|
5
|
+
|
6
|
+
def create(dependee, dependent)
|
7
|
+
check_for_cycles!(dependee, dependent)
|
8
|
+
set << { dependee => dependent }
|
9
|
+
end
|
10
|
+
|
11
|
+
def all
|
12
|
+
set
|
13
|
+
end
|
14
|
+
|
15
|
+
def where(filter = {})
|
16
|
+
scope = all
|
17
|
+
|
18
|
+
if (f = filter[:dependee])
|
19
|
+
scope = scope.select { |h| h.keys.first == f }
|
20
|
+
end
|
21
|
+
|
22
|
+
if (f = filter[:dependent])
|
23
|
+
scope = scope.select { |h| h.values.first == f }
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
scope
|
28
|
+
end
|
29
|
+
|
30
|
+
# What does the dependee depend on?
|
31
|
+
def dependents_for(dependee)
|
32
|
+
recursive_ancestors_for(dependee, :dependee, :values)
|
33
|
+
end
|
34
|
+
|
35
|
+
# What depends on the dependent?
|
36
|
+
def dependees_for(dependent)
|
37
|
+
recursive_ancestors_for(dependent, :dependent, :keys)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def set
|
42
|
+
@dependencies ||= Set.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def check_for_cycles!(dependee, dependent)
|
46
|
+
if dependee == dependent
|
47
|
+
err = "'#{dependee}` cannot depend on itself"
|
48
|
+
elsif dependees_for(dependee).include?(dependent)
|
49
|
+
err = "'#{dependee}` and '#{dependent}` depend on each other"
|
50
|
+
end
|
51
|
+
|
52
|
+
raise CircularError, err if err
|
53
|
+
end
|
54
|
+
|
55
|
+
def recursive_ancestors_for(object, type, side_of_hash)
|
56
|
+
hashes = where(type => object)
|
57
|
+
children = hashes.map(&side_of_hash).flatten
|
58
|
+
|
59
|
+
ancestors = children.to_set
|
60
|
+
children.each do |c|
|
61
|
+
ancestors += recursive_ancestors_for(
|
62
|
+
c, type, side_of_hash
|
63
|
+
)
|
64
|
+
end
|
65
|
+
ancestors.to_a
|
66
|
+
end
|
67
|
+
|
68
|
+
class ::CircularError < StandardError; end
|
69
|
+
end
|
70
|
+
end
|
data/lib/hdl/loader.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
class HDL::Loader
|
2
|
+
class << self
|
3
|
+
|
4
|
+
def path
|
5
|
+
@path ||= ["."]
|
6
|
+
end
|
7
|
+
attr_writer :path
|
8
|
+
|
9
|
+
def load(name, options = {})
|
10
|
+
memoize(name, options[:force]) do
|
11
|
+
name = name.to_s
|
12
|
+
|
13
|
+
if (d = options[:definition])
|
14
|
+
raw = d
|
15
|
+
path = nil
|
16
|
+
else
|
17
|
+
file = file_from_path(name)
|
18
|
+
raw = file.read
|
19
|
+
path = file.path
|
20
|
+
end
|
21
|
+
|
22
|
+
data = HDL::Parser.parse(raw)
|
23
|
+
|
24
|
+
if data[:table]
|
25
|
+
klass = HDL::TableChip
|
26
|
+
else
|
27
|
+
klass = HDL::SchemaChip
|
28
|
+
end
|
29
|
+
|
30
|
+
klass.new(name, path, data)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def memoize(key, override, &block)
|
36
|
+
@memo ||= {}
|
37
|
+
|
38
|
+
if @memo.has_key?(key) && !override
|
39
|
+
@memo[key]
|
40
|
+
else
|
41
|
+
@memo[key] = yield
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def file_from_path(name)
|
46
|
+
path.each do |p|
|
47
|
+
q = File.join(p, name) + ".hdl"
|
48
|
+
return File.new(q) if File.exists?(q)
|
49
|
+
end
|
50
|
+
|
51
|
+
err = "Could not locate chip definition for `#{name}'"
|
52
|
+
raise FileNotFound, err
|
53
|
+
end
|
54
|
+
|
55
|
+
class ::FileNotFound < StandardError; end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
grammar HDL
|
2
|
+
include Pins
|
3
|
+
include Schema
|
4
|
+
include Table
|
5
|
+
|
6
|
+
rule hdl
|
7
|
+
ws? pins ws body:(schema / table) ws? {
|
8
|
+
def structured
|
9
|
+
{
|
10
|
+
:inputs => pins.input_array,
|
11
|
+
:outputs => pins.output_array,
|
12
|
+
body.key => body.to_array
|
13
|
+
}
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
grammar Pins
|
2
|
+
include Vars
|
3
|
+
|
4
|
+
rule pins
|
5
|
+
inputs ws outputs {
|
6
|
+
def input_array
|
7
|
+
inputs.vars.to_array
|
8
|
+
end
|
9
|
+
|
10
|
+
def output_array
|
11
|
+
outputs.vars.to_array
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
rule inputs
|
17
|
+
"inputs" ws vars
|
18
|
+
end
|
19
|
+
|
20
|
+
rule outputs
|
21
|
+
"outputs" ws vars
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
grammar Schema
|
2
|
+
include Vars
|
3
|
+
|
4
|
+
rule schema
|
5
|
+
(ws? chip)+ {
|
6
|
+
def to_array
|
7
|
+
names = TreeWalker.walk(self, :name)
|
8
|
+
hashes = TreeWalker.walk(self, :to_hash)
|
9
|
+
|
10
|
+
names.zip(hashes).map { |n, h| { n => h } }
|
11
|
+
end
|
12
|
+
|
13
|
+
def key
|
14
|
+
:schema
|
15
|
+
end
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
rule chip
|
20
|
+
var "(" arguments ")" {
|
21
|
+
def name
|
22
|
+
var.to_sym
|
23
|
+
end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
rule arguments
|
28
|
+
argument ("," arguments)* ws? {
|
29
|
+
def to_hash
|
30
|
+
lefts = TreeWalker.walk(self, :left)
|
31
|
+
rights = TreeWalker.walk(self, :right)
|
32
|
+
|
33
|
+
Hash[lefts.zip(rights)]
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
rule argument
|
39
|
+
ws? l:var ws? "=" ws? r:(var / boolean) {
|
40
|
+
def left
|
41
|
+
l.to_sym
|
42
|
+
end
|
43
|
+
|
44
|
+
def right
|
45
|
+
r.respond_to?(:to_sym) ? r.to_sym : r.to_bool
|
46
|
+
end
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
grammar Table
|
2
|
+
include Vars
|
3
|
+
|
4
|
+
rule table
|
5
|
+
header rows:(ws? row)+ {
|
6
|
+
def to_array
|
7
|
+
headers = header.header_cells.to_array
|
8
|
+
data = TreeWalker.walk(rows, :to_array)
|
9
|
+
|
10
|
+
data.map { |d| Hash[headers.zip(d)] }
|
11
|
+
end
|
12
|
+
|
13
|
+
def key
|
14
|
+
:table
|
15
|
+
end
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
rule header
|
20
|
+
"|" header_cells "|"
|
21
|
+
end
|
22
|
+
|
23
|
+
rule row
|
24
|
+
"|" row_cells "|"
|
25
|
+
end
|
26
|
+
|
27
|
+
rule header_cells
|
28
|
+
ws? var ws? ("|" header_cells)* {
|
29
|
+
def to_array
|
30
|
+
TreeWalker.walk(self, :to_sym)
|
31
|
+
end
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
rule row_cells
|
36
|
+
ws? boolean ws? ("|" row_cells)* {
|
37
|
+
def to_array
|
38
|
+
TreeWalker.walk(self, :to_bool)
|
39
|
+
end
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
grammar Vars
|
2
|
+
rule vars
|
3
|
+
var ("," ws? vars)* {
|
4
|
+
def to_array
|
5
|
+
TreeWalker.walk(self, :to_sym)
|
6
|
+
end
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
rule var
|
11
|
+
[a-z] [a-z0-9_]* {
|
12
|
+
def to_sym
|
13
|
+
text_value.to_sym
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
rule boolean
|
19
|
+
[01TF] {
|
20
|
+
def to_bool
|
21
|
+
["1", "T"].include?(text_value)
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
rule ws
|
27
|
+
([ \t\r\n] / comment)+
|
28
|
+
end
|
29
|
+
|
30
|
+
rule comment
|
31
|
+
"#" (!"\n" .)* "\n"
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class TreeWalker
|
2
|
+
def self.walk(element, method)
|
3
|
+
array = []
|
4
|
+
|
5
|
+
if element.respond_to?(method)
|
6
|
+
array << element.send(method)
|
7
|
+
elsif !element.terminal?
|
8
|
+
array += element.elements.map { |e| walk(e, method) }.flatten(1)
|
9
|
+
end
|
10
|
+
|
11
|
+
array
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class HDL::Parser::Validator::InputValidator < HDL::Parser::Validator
|
2
|
+
|
3
|
+
def validate!
|
4
|
+
intersection = @hash[:inputs] & @hash[:outputs]
|
5
|
+
if intersection.size == 1
|
6
|
+
raise "`#{intersection.first}' is both an input and an output"
|
7
|
+
elsif intersection.size > 1
|
8
|
+
pins = intersection.map { |p| "`#{p}'" }.join(", ")
|
9
|
+
raise "These pins are both inputs and outputs: #{pins}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class HDL::Parser::Validator::SchemaValidator < HDL::Parser::Validator
|
2
|
+
|
3
|
+
def validate!
|
4
|
+
inputs!
|
5
|
+
outputs!
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
def inputs!
|
10
|
+
inputs = @hash[:inputs]
|
11
|
+
disconnected_inputs = inputs - connected_pins
|
12
|
+
disconnected_pins_error(disconnected_inputs, "input")
|
13
|
+
end
|
14
|
+
|
15
|
+
def outputs!
|
16
|
+
outputs = @hash[:outputs]
|
17
|
+
disconnected_outputs = outputs - connected_pins
|
18
|
+
disconnected_pins_error(disconnected_outputs, "output")
|
19
|
+
|
20
|
+
repetitions = outputs.select { |o| connected_pins.count(o) > 1 }
|
21
|
+
if repetitions.size == 1
|
22
|
+
raise "The output `#{repetitions.first}' is connected multiple times"
|
23
|
+
elsif repetitions.size > 1
|
24
|
+
list = repetitions.join(', ')
|
25
|
+
raise "These outputs are connected multiple times: #{list}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def connected_pins
|
30
|
+
schema = @hash[:schema]
|
31
|
+
wiring = schema.map(&:values).flatten
|
32
|
+
wiring.map(&:values).flatten
|
33
|
+
end
|
34
|
+
|
35
|
+
def disconnected_pins_error(disconnected_pins, type)
|
36
|
+
if disconnected_pins.size == 1
|
37
|
+
raise "The #{type} `#{disconnected_pins.first}' is not connected"
|
38
|
+
elsif disconnected_pins.size > 1
|
39
|
+
list = disconnected_pins.join(", ")
|
40
|
+
raise "The following #{type}s are not connected: #{list}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class HDL::Parser::Validator::TableValidator < HDL::Parser::Validator
|
2
|
+
|
3
|
+
def validate!
|
4
|
+
headers!
|
5
|
+
combinations!
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
def headers!
|
10
|
+
external_pins = @hash[:inputs] + @hash[:outputs]
|
11
|
+
table_headers = @hash[:table].first.keys
|
12
|
+
|
13
|
+
unless external_pins.to_set == table_headers.to_set
|
14
|
+
raise "expecting table headers #{external_pins.join(",")}, got #{table_headers.join(",")}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def combinations!
|
19
|
+
inputs = @hash[:inputs]
|
20
|
+
|
21
|
+
combi = @hash[:table].inject([]) do |acc, row|
|
22
|
+
only_inputs = row.select { |k, _| inputs.include?(k) }
|
23
|
+
acc + [Hash[only_inputs]]
|
24
|
+
end
|
25
|
+
|
26
|
+
repetitions = combi.select { |c| combi.count(c) > 1 }.uniq
|
27
|
+
if repetitions.size == 1
|
28
|
+
raise "The row for #{repetitions.first.inspect} appears multiple times"
|
29
|
+
elsif repetitions.size > 1
|
30
|
+
list = repetitions.map(&:inspect).join(', ')
|
31
|
+
raise "These rows appear multiple times: #{list}"
|
32
|
+
end
|
33
|
+
|
34
|
+
required_size = 2 ** inputs.size
|
35
|
+
given_size = combi.size
|
36
|
+
unless given_size == required_size
|
37
|
+
rows = given_size > 1 ? "rows were" : "row was"
|
38
|
+
raise "#{given_size} #{rows} given, but #{required_size} rows are required"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class HDL::Parser::Validator
|
2
|
+
|
3
|
+
def self.validate!(hash)
|
4
|
+
new(hash).validate!
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(hash)
|
8
|
+
@hash = hash
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate!
|
12
|
+
InputValidator.validate!(@hash)
|
13
|
+
|
14
|
+
if @hash[:schema]
|
15
|
+
validator = SchemaValidator
|
16
|
+
else
|
17
|
+
validator = TableValidator
|
18
|
+
end
|
19
|
+
|
20
|
+
validator.validate!(@hash)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def raise(string)
|
25
|
+
super(ParseError.new(string))
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/hdl/parser.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class HDL::Parser
|
2
|
+
def self.parse(definition)
|
3
|
+
parser = HDLParser.new
|
4
|
+
|
5
|
+
ast = parser.parse(definition)
|
6
|
+
if ast.nil?
|
7
|
+
raise ParseError, parser.failure_reason
|
8
|
+
end
|
9
|
+
|
10
|
+
structured_data = ast.structured
|
11
|
+
Validator.validate!(structured_data)
|
12
|
+
|
13
|
+
structured_data
|
14
|
+
end
|
15
|
+
|
16
|
+
class ::ParseError < StandardError; end
|
17
|
+
end
|
data/lib/hdl.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "set"
|
2
|
+
require "polyglot"
|
3
|
+
require "treetop"
|
4
|
+
require "hdl/base"
|
5
|
+
require "hdl/loader"
|
6
|
+
require "hdl/dependency"
|
7
|
+
require "hdl/chip"
|
8
|
+
require "hdl/chip/schema_chip"
|
9
|
+
require "hdl/chip/schema_chip/evaluator"
|
10
|
+
require "hdl/chip/table_chip"
|
11
|
+
require "hdl/parser"
|
12
|
+
require "hdl/parser/validator"
|
13
|
+
require "hdl/parser/validator/input_validator"
|
14
|
+
require "hdl/parser/validator/schema_validator"
|
15
|
+
require "hdl/parser/validator/table_validator"
|
16
|
+
require "hdl/parser/tree_walker.rb"
|
17
|
+
require "hdl/parser/grammar/vars"
|
18
|
+
require "hdl/parser/grammar/pins"
|
19
|
+
require "hdl/parser/grammar/table"
|
20
|
+
require "hdl/parser/grammar/schema"
|
21
|
+
require "hdl/parser/grammar/hdl"
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hdl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Chris Patuzzo
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-06-02 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: treetop
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: polyglot
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rspec
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
description: A parser and emulator for a minimalist hardware description language.
|
64
|
+
email: chris@patuzzo.co.uk
|
65
|
+
executables: []
|
66
|
+
|
67
|
+
extensions: []
|
68
|
+
|
69
|
+
extra_rdoc_files: []
|
70
|
+
|
71
|
+
files:
|
72
|
+
- README.md
|
73
|
+
- lib/hdl.rb
|
74
|
+
- lib/hdl/chip.rb
|
75
|
+
- lib/hdl/chip/schema_chip/evaluator.rb
|
76
|
+
- lib/hdl/chip/schema_chip.rb
|
77
|
+
- lib/hdl/chip/table_chip.rb
|
78
|
+
- lib/hdl/base.rb
|
79
|
+
- lib/hdl/dependency.rb
|
80
|
+
- lib/hdl/parser/validator.rb
|
81
|
+
- lib/hdl/parser/tree_walker.rb
|
82
|
+
- lib/hdl/parser/grammar/pins.treetop
|
83
|
+
- lib/hdl/parser/grammar/hdl.treetop
|
84
|
+
- lib/hdl/parser/grammar/schema.treetop
|
85
|
+
- lib/hdl/parser/grammar/table.treetop
|
86
|
+
- lib/hdl/parser/grammar/vars.treetop
|
87
|
+
- lib/hdl/parser/validator/table_validator.rb
|
88
|
+
- lib/hdl/parser/validator/input_validator.rb
|
89
|
+
- lib/hdl/parser/validator/schema_validator.rb
|
90
|
+
- lib/hdl/loader.rb
|
91
|
+
- lib/hdl/parser.rb
|
92
|
+
has_rdoc: true
|
93
|
+
homepage: https://github.com/cpatuzzo/hdl
|
94
|
+
licenses: []
|
95
|
+
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
hash: 3
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
version: "0"
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
hash: 3
|
116
|
+
segments:
|
117
|
+
- 0
|
118
|
+
version: "0"
|
119
|
+
requirements: []
|
120
|
+
|
121
|
+
rubyforge_project:
|
122
|
+
rubygems_version: 1.6.2
|
123
|
+
signing_key:
|
124
|
+
specification_version: 3
|
125
|
+
summary: HDL
|
126
|
+
test_files: []
|
127
|
+
|