vhdl_tb 0.4 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
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