hdl 1.0.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.
- 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
|
+

|
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
|
+
|