vhdl_tb 0.4 → 0.6.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63557b1b90136b3f8306bd68a2107cda107e930a
4
- data.tar.gz: e6acdf6957129cb2f774a1c638a121e3799f4f51
3
+ metadata.gz: 99115f46e648d9a330a16e6998507fe532bbaac7
4
+ data.tar.gz: 4a7ca042f8bbe236adc0cec08c0ba820e7d26c89
5
5
  SHA512:
6
- metadata.gz: e0c7d92ccc71af281e69e78583a30e07831836bc41d8678e8ecc2b2dccf8e0f1eb0c790127b4aa0b9ac37f367e686975f23d2b206ec47d42f95e845ed0baf603
7
- data.tar.gz: 5f9f354c7866312c98072a400ec02a5d24b1fee52669ec9ffd928d7c940f223220ee6c1f6b33854066fcffb0386b1e9cb7171b75066e4d11a635c67e545b4870
6
+ metadata.gz: ccce1103568033abcdaa16ac33666e4b73c82f007550607c5b79564cb64ef24dae46d3f258963a3a5aa34263a9180298875f49539a6ba507025c70aad5fd1a94
7
+ data.tar.gz: 5d1f555f4908071b4360c906209dfa90ae2d99ef0d7f5b2dd22788780331a0be2b53db73e76aad1ff39f5a56e364da6d3f78dfeb59f4fb170e9ffb85da6d06e4
data/bin/tbgen CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../lib/vhdl_tb'
4
- generator=VhdlTb.new
5
- generator.analyze_options(ARGV)
6
- generator.generate
3
+ require_relative '../lib/compiler'
4
+ compiler=VHDL_TB::Compiler.new
5
+ compiler.analyze_options(ARGV)
6
+ compiler.generate
data/bin/vhdl_tb CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../lib/vhdl_tb'
4
- generator=VhdlTb.new
3
+ require_relative '../lib/compiler'
4
+ generator=VHDL_TB::Compiler.new
5
5
  generator.analyze_options(ARGV)
6
6
  generator.generate
data/lib/ast.rb ADDED
@@ -0,0 +1,26 @@
1
+ module VHDL_TB
2
+ Root=Struct.new(:design_units)
3
+ Entity=Struct.new(:name,:generics,:ports)
4
+ Generic=Struct.new(:name,:type,:init)
5
+ Input=Struct.new(:name,:type)
6
+ Output=Struct.new(:name,:type)
7
+ Architecture=Struct.new(:name,:entity)
8
+
9
+ Identifier=Struct.new(:tok) do
10
+ def to_s
11
+ "#{self.tok}"
12
+ end
13
+ end
14
+
15
+ IntLit=Struct.new(:tok) do
16
+ def to_s
17
+ "#{self.tok}"
18
+ end
19
+ end
20
+
21
+ VectorType=Struct.new(:name,:lhs,:dir,:rhs) do
22
+ def to_s
23
+ "#{self.name}(#{self.lhs} #{self.dir} #{self.rhs})"
24
+ end
25
+ end
26
+ end
data/lib/compiler.rb ADDED
@@ -0,0 +1,127 @@
1
+ require 'erb'
2
+ require 'pp'
3
+ require 'optparse'
4
+
5
+ require_relative 'ast'
6
+ require_relative 'parser'
7
+
8
+ module VHDL_TB
9
+
10
+ TestBench=Struct.new(:name)
11
+
12
+ class Compiler
13
+
14
+ VERSION = "0.6.3"
15
+
16
+ def initialize
17
+ #puts __dir__
18
+ banner
19
+ @engine=ERB.new(IO.read "#{__dir__}/template.tb.vhd")
20
+ @parser=Parser.new
21
+ end
22
+
23
+ def banner
24
+ puts "-- "+"="*65
25
+ puts "-- VHDL testbench generator -- Jean-Christophe Le Lann 2017-2018"
26
+ puts "-- "+"="*65
27
+
28
+ end
29
+
30
+ def analyze_options args
31
+ args << "-h" if args.empty?
32
+
33
+ opt_parser = OptionParser.new do |opts|
34
+ opts.banner = "Usage: vhdl_tb (or tbgen) <filename>"
35
+
36
+ opts.on("-v", "--version", "Prints version") do |n|
37
+ puts VERSION
38
+ abort
39
+ end
40
+
41
+ opts.on("-h", "--help", "Prints this help") do
42
+ puts "Generates testbench in VHDL, from a given file containing an Entity-Architecture couple."
43
+ puts
44
+ puts opts
45
+ abort
46
+ end
47
+ end
48
+
49
+ begin
50
+ opt_parser.parse!(args)
51
+ @args=args
52
+
53
+ rescue Exception => e
54
+ puts e
55
+ #puts e.backtrace
56
+ exit
57
+ end
58
+ end
59
+
60
+ def generate entity_filename=@args.first
61
+ @symtable=[]
62
+ @symtable << "clk"
63
+ @symtable << "reset_n"
64
+ @symtable << "sreset"
65
+ begin
66
+ analyze(entity_filename)
67
+ tb_txt=@engine.result(binding)
68
+ tb_filename="#{@tb.name}.vhd"
69
+ File.open(tb_filename,'w'){|f| f.puts tb_txt}
70
+ puts "testbench generated : #{tb_filename}"
71
+ rescue Exception => e
72
+ abort
73
+ end
74
+ end
75
+
76
+ def analyze entity_filename
77
+ puts "analyzing VHDL file : #{entity_filename}"
78
+
79
+ root=Parser.new.parse entity_filename
80
+
81
+ @entity=root.design_units.find{|du| du.class==Entity}
82
+ puts "entity found : #{@entity.name} (#{@entity.ports.size} ports)"
83
+
84
+ @arch=root.design_units.find{|du| du.is_a? Architecture}
85
+ check
86
+ # prepare ERB through instance variables
87
+ @max_length=@entity.ports.map{|p| p.name.val.size}.max
88
+ @ports=@entity.ports.map{|p| " "*10+"#{e=p.name.val.ljust(@max_length)} => #{e}"}.join(",\n")
89
+ if gs=(@entity.generics)
90
+ generics=gs.map{|g| " "*10+"#{g.name} => #{g.init}"}
91
+ @generics="generic map(\n#{generics.join(",\n")})"
92
+ end
93
+ @tb=TestBench.new(@entity.name.val+"_tb")
94
+ end
95
+
96
+ def check
97
+ print "checking".ljust(20)
98
+ errors =[]
99
+
100
+ if @entity.ports.size==0
101
+ errors << "0 ports found for #{@entity.name}"
102
+ end
103
+
104
+ if @arch.entity.val!=@entity.name.val
105
+ errors << "wrong entity-architecture pair : entity is -->#{@entity.name}<-- vs arch #{@arch.name} of -->#{@arch.entity.name}<--"
106
+ end
107
+
108
+ if errors.any?
109
+ puts ": nok"
110
+ puts "\nchecks failed due to the following errors :"
111
+ errors.each{|e| puts "- ERROR : #{e}"}
112
+ raise
113
+ else
114
+ puts ": ok"
115
+ end
116
+ end
117
+
118
+
119
+
120
+ end
121
+ end
122
+
123
+ if $PROGRAM_NAME == __FILE__
124
+ raise "you need to provide a VHDL file that contains the entity to test" if ARGV.size==0
125
+ filename=ARGV[0]
126
+ VHDL_TB.Compiler.new.generate(filename)
127
+ end
@@ -0,0 +1,62 @@
1
+ require 'strscan'
2
+
3
+ require_relative 'token'
4
+
5
+ class GenericLexer
6
+
7
+ def initialize
8
+ @rules = []
9
+ @rules << [:newline,/[\n]/]
10
+ end
11
+
12
+ def ignore pattern
13
+ @rules << [:skip,pattern]
14
+ end
15
+
16
+ def keyword str
17
+ @rules.unshift [str.to_sym,/#{str}\b/]
18
+ end
19
+
20
+ def token hash
21
+ token,pattern=*hash.to_a.flatten
22
+ @rules << [token, pattern]
23
+ end
24
+
25
+ def open code
26
+ @ssc = StringScanner.new code
27
+ @line=0
28
+ end
29
+
30
+ def next_token
31
+ return [nil,nil,nil] if @ssc.empty?
32
+ tok = get_token
33
+ return (tok.is_a? :skip) ? next_token : tok
34
+ end
35
+
36
+ def get_token
37
+ linecol=position()
38
+ @rules.each do |rule, regexp|
39
+ val = @ssc.scan(regexp)
40
+ return Token.new([rule, val, linecol]) if val
41
+ end
42
+ raise "lexing error line #{linecol.first} around : ...'#{@ssc.peek(5)}'... "
43
+ end
44
+
45
+ def position
46
+ if @ssc.bol?
47
+ @line+=1
48
+ @old_pos=@ssc.pos
49
+ end
50
+ [@line,@ssc.pos-@old_pos+1]
51
+ end
52
+
53
+ def tokenize code
54
+ open(code)
55
+ tokens=[]
56
+ tokens << next_token() while not @ssc.eos?
57
+ # while not @ssc.eos?
58
+ # tokens << (p next_token)
59
+ # end
60
+ tokens
61
+ end
62
+ end
@@ -0,0 +1,25 @@
1
+ class GenericParser
2
+
3
+ def acceptIt
4
+ tok=tokens.shift
5
+ puts "consuming #{tok.val} (#{tok.kind})" if @verbose
6
+ tok
7
+ end
8
+
9
+ def showNext k=1
10
+ tokens[k-1]
11
+ end
12
+
13
+ def expect kind
14
+ if (actual=showNext.kind)!=kind
15
+ abort "ERROR at #{showNext.pos}. Expecting #{kind}. Got #{actual}"
16
+ else
17
+ return acceptIt()
18
+ end
19
+ end
20
+
21
+ def more?
22
+ !tokens.empty?
23
+ end
24
+
25
+ end
data/lib/lexer.rb ADDED
@@ -0,0 +1,31 @@
1
+ require_relative 'generic_lexer'
2
+
3
+ class Lexer < GenericLexer
4
+ def initialize
5
+ super
6
+ ignore /\s+/
7
+ #....keywords
8
+ token :entity => /entity/
9
+ token :is => /is/
10
+ token :generic => /generic/
11
+ token :port => /port/
12
+ token :in => /in/
13
+ token :to => /to/
14
+ token :downto => /downto/
15
+ token :out => /out/
16
+ token :end => /end/
17
+ token :comment => /--(.*)$/
18
+ token :vassign => /\:\=/
19
+ token :lparen => /\(/
20
+ token :rparen => /\)/
21
+ token :semicolon=> /\;/
22
+ token :colon => /\:/
23
+ token :dot => /\./
24
+ token :comma => /\,/
25
+ token :architecture => /architecture/
26
+ token :of => /of/
27
+ token :id => /[a-zA-Z_][a-zA-Z0-9_]*/i
28
+ token :int_lit => /[0-9]+/
29
+
30
+ end
31
+ end
data/lib/parser.rb ADDED
@@ -0,0 +1,183 @@
1
+ require_relative 'generic_parser'
2
+ require_relative 'ast'
3
+ require_relative 'lexer'
4
+
5
+ require 'pp'
6
+
7
+ module VHDL_TB
8
+
9
+ class Parser < GenericParser
10
+ attr_accessor :lexer,:tokens
11
+ attr_accessor :basename,:filename
12
+
13
+ def initialize
14
+ @lexer=Lexer.new
15
+ end
16
+
17
+ def parse filename
18
+ str=IO.read(filename)
19
+ begin
20
+ @tokens=lexer.tokenize(str)
21
+ @tokens=tokens.select{|t| t.class==Token} # filters [nil,nil,nil]
22
+ @tokens=tokens.reject{|tok| tok.is_a? [:comment,:newline]}
23
+ rescue Exception=>e
24
+ puts e
25
+ puts "an error occured during LEXICAL analysis. Sorry. Aborting."
26
+ raise
27
+ end
28
+ root=Root.new([])
29
+ begin
30
+ consume_to :entity
31
+ root.design_units << entity=parse_entity
32
+
33
+ consume_to :architecture
34
+ root.design_units << archi=parse_architecture
35
+
36
+ rescue Exception => e
37
+ #puts e.backtrace
38
+ puts e
39
+ puts "an error occured during SYNTACTIC analysis (around line #{showNext.pos.first}). Sorry. Aborting."
40
+ raise
41
+ end
42
+ return root
43
+ end
44
+
45
+ def consume_to token_kind
46
+ while showNext && showNext.kind!=token_kind
47
+ acceptIt
48
+ end
49
+ if showNext.nil?
50
+ raise "cannot find token '#{token_kind}'"
51
+ end
52
+ end
53
+
54
+ def parse_entity
55
+ entity=Entity.new(nil,[],[])
56
+ expect :entity
57
+ entity.name=expect :id
58
+ expect :is
59
+ if showNext.is_a? :generic
60
+ entity.generics=parse_generics
61
+ end
62
+ if showNext.is_a? :port
63
+ entity.ports=parse_ports
64
+ end
65
+ expect :end
66
+ return entity
67
+ end
68
+
69
+ def parse_generics
70
+ generics=[]
71
+ expect :generic
72
+ expect :lparen
73
+ while showNext.is_not_a? :rparen
74
+ generics << parse_generic
75
+ if showNext.is_a? :semicolon
76
+ acceptIt
77
+ end
78
+ end
79
+ expect :rparen
80
+ expect :semicolon
81
+ generics.flatten!
82
+ generics
83
+ end
84
+
85
+ def parse_generic
86
+ ids=[]
87
+ ids << expect(:id)
88
+ while showNext.is_a? :comma
89
+ acceptIt
90
+ ids << expect(:id)
91
+ end
92
+ expect :colon
93
+ type=parse_type
94
+ if showNext.is_a? :vassign
95
+ acceptIt
96
+ expr=parse_expression
97
+ end
98
+ ids.map{|id| Generic.new(id,type,expr)}
99
+ end
100
+
101
+ def parse_ports
102
+ ports=[]
103
+ expect :port
104
+ expect :lparen
105
+ while showNext.is_not_a? :rparen
106
+ ports << parse_io
107
+ if showNext.is_a? :semicolon
108
+ acceptIt
109
+ end
110
+ end
111
+ expect :rparen
112
+ expect :semicolon
113
+ ports.flatten!
114
+ ports
115
+ end
116
+
117
+ def parse_io
118
+ ids=[]
119
+ ids << expect(:id)
120
+ while showNext.is_a? :comma
121
+ acceptIt
122
+ ids << expect(:id)
123
+ end
124
+ expect :colon
125
+ if showNext.is_a? [:in,:out]
126
+ dir=acceptIt
127
+ dir=dir.kind
128
+ end
129
+ type=parse_type
130
+ ids.map{|id| dir==:in ? Input.new(id,type) : Output.new(id,type)}
131
+ end
132
+
133
+ def parse_type
134
+ type=Identifier.new
135
+ type.tok=expect(:id)
136
+ if showNext.is_a? :lparen
137
+ acceptIt
138
+ name=type.tok
139
+ type=VectorType.new
140
+ type.name=name
141
+ type.lhs=parse_expression
142
+ if showNext.is_a? [:downto,:to]
143
+ type.dir=acceptIt
144
+ end
145
+ type.rhs=parse_expression
146
+ expect :rparen
147
+ end
148
+ type
149
+ end
150
+
151
+ def parse_expression
152
+ parse_term
153
+ end
154
+
155
+ def parse_term
156
+ if showNext.is_a? [:int_lit,:id]
157
+ case showNext.kind
158
+ when :int_lit
159
+ return IntLit.new(acceptIt)
160
+ when :id
161
+ return Identifier.new(acceptIt)
162
+ else
163
+ puts "cannot parse term"
164
+ end
165
+ end
166
+ end
167
+
168
+ def parse_architecture
169
+ archi=Architecture.new
170
+ expect :architecture
171
+ archi.name=expect(:id)
172
+ expect :of
173
+ archi.entity=expect(:id)
174
+ archi
175
+ end
176
+ end
177
+ end
178
+
179
+ if $PROGRAM_NAME==__FILE__
180
+ filename=ARGV.first
181
+ parser=VHDL_TB::Parser.new
182
+ parser.parse filename
183
+ end
data/lib/template.tb.vhd CHANGED
@@ -27,7 +27,7 @@ architecture bhv of <%=@tb.name%> is
27
27
  end procedure;
28
28
 
29
29
  <%=@entity.ports.collect do |port|
30
- " signal #{port.name.rjust(@max_length)} : #{port.type}" if not @symtable.include?(port.name)
30
+ " signal #{port.name.val.ljust(@max_length)} : #{port.type}" if not @symtable.include?(port.name)
31
31
  end.compact.join(";\n")%>;
32
32
 
33
33
  begin
@@ -42,8 +42,10 @@ begin
42
42
  -- Design Under Test
43
43
  --------------------------------------------------------------------
44
44
  dut : entity work.<%=@entity.name%>(<%=@arch.name%>)
45
- port map (
46
- <%=@entity.ports.collect{|port| "\t #{port.name.ljust(@max_length)} => #{port.name}"}.join(",\n")%>);
45
+ <%=@generics%>
46
+ port map (
47
+ <%=@ports%>
48
+ );
47
49
 
48
50
  --------------------------------------------------------------------
49
51
  -- sequential stimuli
data/lib/token.rb ADDED
@@ -0,0 +1,54 @@
1
+ class Token
2
+ attr_accessor :kind,:val,:pos
3
+ def initialize tab
4
+ @kind,@val,@pos=*tab
5
+ end
6
+
7
+ def is_a? kind
8
+ case kind
9
+ when Symbol
10
+ return @kind==kind
11
+ when Array
12
+ for sym in kind
13
+ return true if @kind==sym
14
+ end
15
+ return false
16
+ else
17
+ raise "wrong type during lookahead"
18
+ end
19
+ end
20
+
21
+ def is_not_a? kind
22
+ case kind
23
+ when Symbol
24
+ return @kind!=kind
25
+ when Array
26
+ ret=true
27
+ for sym in kind
28
+ ret=false if @kind==sym
29
+ end
30
+ return ret
31
+ else
32
+ raise "wrong type during lookahead"
33
+ end
34
+ end
35
+
36
+ # def accept visitor
37
+ # end
38
+
39
+ def self.create str
40
+ Token.new [:id,str,[0,0]]
41
+ end
42
+
43
+ def to_s
44
+ val
45
+ end
46
+
47
+ def clone
48
+ Token.new([@kind,@val,@pos])
49
+ end
50
+ end
51
+
52
+ ONE = Token.new [:int_lit,'1',['na','na']]
53
+ ZERO = Token.new [:int_lit,'0',['na','na']]
54
+ DUMMY= Token.new [:id ,'' ,['na','na']]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vhdl_tb
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.4'
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean-Christophe Le Lann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-24 00:00:00.000000000 Z
11
+ date: 2018-04-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A simple testbench generator for VHDL
14
14
  email: jean-christophe.le_lann@ensta-bretagne.fr
@@ -20,8 +20,14 @@ extra_rdoc_files: []
20
20
  files:
21
21
  - bin/tbgen
22
22
  - bin/vhdl_tb
23
+ - lib/ast.rb
24
+ - lib/compiler.rb
25
+ - lib/generic_lexer.rb
26
+ - lib/generic_parser.rb
27
+ - lib/lexer.rb
28
+ - lib/parser.rb
23
29
  - lib/template.tb.vhd
24
- - lib/vhdl_tb.rb
30
+ - lib/token.rb
25
31
  - tests/circuit.vhd
26
32
  homepage: http://rubygems.org/gems/vhdl_tb
27
33
  licenses:
data/lib/vhdl_tb.rb DELETED
@@ -1,105 +0,0 @@
1
- require 'erb'
2
- require 'pp'
3
- require 'optparse'
4
-
5
- Entity = Struct.new("Entity",:name,:ports)
6
- Architecture = Struct.new("Architecture",:name,:entity)
7
- Port = Struct.new("Port",:name,:dir,:type)
8
- Testbench = Struct.new("Testbench",:name)
9
-
10
- class VhdlTb
11
-
12
- VERSION = "0.4"
13
-
14
- def initialize
15
- #puts __dir__
16
- banner
17
- @engine=ERB.new(IO.read "#{__dir__}/template.tb.vhd")
18
- end
19
-
20
- def banner
21
- puts "-- "+"="*60
22
- puts "-- VHDL testbench generator -- Jean-Christophe Le Lann 2017"
23
- puts "-- "+"="*60
24
-
25
- end
26
-
27
- def analyze_options args
28
- args << "-h" if args.empty?
29
-
30
- opt_parser = OptionParser.new do |opts|
31
- opts.banner = "Usage: vhdl_tb (or tbgen) <filename>"
32
-
33
- opts.on("-v", "--version", "Prints version") do |n|
34
- puts VERSION
35
- abort
36
- end
37
-
38
- opts.on("-h", "--help", "Prints this help") do
39
- puts "Generates testbench in VHDL, from a given file containing an Entity-Architecture couple."
40
- puts
41
- puts opts
42
- abort
43
- end
44
- end
45
-
46
- begin
47
- opt_parser.parse!(args)
48
- @args=args
49
-
50
- rescue Exception => e
51
- puts e
52
- #puts e.backtrace
53
- exit
54
- end
55
- end
56
-
57
- def generate entity_filename=@args.first
58
- @symtable=[]
59
- @symtable << "clk"
60
- @symtable << "reset_n"
61
- @symtable << "sreset"
62
- analyze(entity_filename)
63
- tb_txt=@engine.result(binding)
64
- tb_filename="#{@tb.name}.vhd"
65
- File.open(tb_filename,'w'){|f| f.puts tb_txt}
66
- puts "testbench generated : #{tb_filename}"
67
- end
68
-
69
- def analyze entity_filename
70
- puts "analyzing VHDL file : #{entity_filename}"
71
- code=IO.read(entity_filename)
72
- regexp_entity=/entity\s+(\w+)\s+is\s+(generic\(.*\)\;\s+)?port\s*\(\s*(.*)\)\s*\;\s*end\s+/im
73
- entity_matched = regexp_entity.match(code)
74
- name,generics,iotext=*entity_matched.captures
75
-
76
- ioregexp=/(\w+)\s*:\s*(\w+)\s+(.*)\s*;?/ix
77
- iotab= iotext.scan(ioregexp)
78
- io= to_port(iotab)
79
- puts "entity found : #{name} (#{io.size} ports)"
80
- @entity=Entity.new(name,io)
81
-
82
- regexp_arch=/architecture\s+(\w+)\s+of\s+(\w+)/im
83
- arch_matched = regexp_arch.match(code)
84
- arch_name,entity_name=*arch_matched.captures
85
- @arch=Architecture.new(arch_name,entity_name)
86
- @tb=Testbench.new(@entity.name+"_tb")
87
- end
88
-
89
- def to_port iotab
90
- @max_length=0
91
- iotab.collect do |io|
92
- name,dir,type=io
93
- type=type.split(";")[0] #suppress comments
94
- @max_length=name.size if name.size>@max_length
95
- Port.new(name,dir,type)
96
- end
97
- end
98
-
99
- end
100
-
101
- if $PROGRAM_NAME == __FILE__
102
- raise "you need to provide a VHDL file that contains the entity to test" if ARGV.size==0
103
- filename=ARGV[0]
104
- VhdlTb.new.generate(filename)
105
- end