3 # Copyright 2008, SoftPLC Corporation http://softplc.com
4 # Dick Hollenbeck dick@softplc.com
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, you may find one here:
19 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 # or you may search the http://www.gnu.org website for the version 2 license,
21 # or you may write to the Free Software Foundation, Inc.,
22 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 # A python program to convert an SVF file to an XSVF file. There is an
26 # option to include comments containing the source file line number from the origin
27 # SVF file before each outputted XSVF statement.
29 # We deviate from the XSVF spec in that we introduce a new command called
30 # XWAITSTATE which directly flows from the SVF RUNTEST command. Unfortunately
31 # XRUNSTATE was ill conceived and is not used here. We also add support for the
32 # three Lattice extensions to SVF: LCOUNT, LDELAY, and LSDR. The xsvf file
33 # generated from this program is suitable for use with the xsvf player in
34 # OpenOCD with my modifications to xsvf.c.
36 # This program is written for python 3.0, and it is not easy to change this
37 # back to 2.x. You may find it easier to use python 3.x even if that means
46 # There are both ---<Lexer>--- and ---<Parser>--- sections to this program
49 if len( sys
.argv
) < 3:
50 print("usage %s <svf_filename> <xsvf_filename>" % sys
.argv
[0])
54 inputFilename
= sys
.argv
[1]
55 outputFilename
= sys
.argv
[2]
57 doCOMMENTs
= True # Save XCOMMENTs in the output xsvf file
58 #doCOMMENTs = False # Save XCOMMENTs in the output xsvf file
60 # pick your file encoding
61 file_encoding
= 'ISO-8859-1'
62 #file_encoding = 'utf-8'
65 xrepeat
= 0 # argument to XREPEAT, gives retry count for masked compares
68 #-----< Lexer >---------------------------------------------------------------
70 StateBin
= (RESET
,IDLE
,
71 DRSELECT
,DRCAPTURE
,DRSHIFT
,DREXIT1
,DRPAUSE
,DREXIT2
,DRUPDATE
,
72 IRSELECT
,IRCAPTURE
,IRSHIFT
,IREXIT1
,IRPAUSE
,IREXIT2
,IRUPDATE
) = range(16)
74 # Any integer index into this tuple will be equal to its corresponding StateBin value
75 StateTxt
= ("RESET","IDLE",
76 "DRSELECT","DRCAPTURE","DRSHIFT","DREXIT1","DRPAUSE","DREXIT2","DRUPDATE",
77 "IRSELECT","IRCAPTURE","IRSHIFT","IREXIT1","IRPAUSE","IREXIT2","IRUPDATE")
80 (XCOMPLETE
,XTDOMASK
,XSIR
,XSDR
,XRUNTEST
,hole0
,hole1
,XREPEAT
,XSDRSIZE
,XSDRTDO
,
81 XSETSDRMASKS
,XSDRINC
,XSDRB
,XSDRC
,XSDRE
,XSDRTDOB
,XSDRTDOC
,
82 XSDRTDOE
,XSTATE
,XENDIR
,XENDDR
,XSIR2
,XCOMMENT
,XWAIT
,XWAITSTATE
,
83 LCOUNT
,LDELAY
,LSDR
,XTRST
) = range(29)
85 #Note: LCOUNT, LDELAY, and LSDR are Lattice extensions to SVF and provide a way to loop back
86 # and check a completion status, essentially waiting on a part until it signals that it is done.
87 # For example below: loop 25 times, each time through the loop do a LDELAY (same as a true RUNTEST)
88 # and exit loop when LSDR compares match.
91 ! Step to DRPAUSE give 5 clocks and wait for 1.00e+000 SEC.
92 LDELAY DRPAUSE 5 TCK 1.00E-003 SEC;
93 ! Test for the completed status. Match means pass.
94 ! Loop back to LDELAY line if not match and loop count less than 25.
99 #XTRST is an opcode Xilinx seemed to have missed and it comes from the SVF TRST statement.
103 def s_ident(scanner
, token
): return ("ident", token
.upper(), LineNumber
)
105 def s_hex(scanner
, token
):
107 LineNumber
= LineNumber
+ token
.count('\n')
108 token
= ''.join(token
.split())
109 return ("hex", token
[1:-1], LineNumber
)
111 def s_int(scanner
, token
): return ("int", int(token
), LineNumber
)
112 def s_float(scanner
, token
): return ("float", float(token
), LineNumber
)
113 #def s_comment(scanner, token): return ("comment", token, LineNumber)
114 def s_semicolon(scanner
, token
): return ("semi", token
, LineNumber
)
116 def s_nl(scanner
,token
):
118 LineNumber
= LineNumber
+ 1
119 #print( 'LineNumber=', LineNumber, file=sys.stderr )
124 scanner
= re
.Scanner([
125 (r
"[a-zA-Z]\w*", s_ident
),
126 # (r"[-+]?[0-9]+[.]?[0-9]*([eE][-+]?[0-9]+)?", s_float),
127 (r
"[-+]?[0-9]+(([.][0-9eE+-]*)|([eE]+[-+]?[0-9]+))", s_float
),
129 (r
"\(([0-9a-fA-F]|\s)*\)", s_hex
),
130 (r
"(!|//).*$", None),
138 # open the file using the given encoding
139 file = open( sys
.argv
[1], encoding
=file_encoding
)
141 # read all svf file input into string "input"
147 # create a list of tuples containing (tokenType, tokenValue, LineNumber)
148 tokens
= scanner
.scan( input )[0]
150 input = None # allow gc to reclaim memory holding file
152 #for tokenType, tokenValue, ln in tokens: print( "line %d: %s" % (ln, tokenType), tokenValue )
155 #-----<parser>-----------------------------------------------------------------
157 tokVal
= tokType
= tokLn
= None
163 Function to read the next token from tup into tokType, tokVal, tokLn (linenumber)
166 global tokType
, tokVal
, tokLn
, tup
167 tokType
, tokVal
, tokLn
= tup
.__next
__()
170 class ParseError(Exception):
171 """A class to hold a parsing error message"""
172 def __init__(self
, linenumber
, token
, message
):
173 self
.linenumber
= linenumber
175 self
.message
= message
178 return "Error in file \'%s\' at line %d near token %s\n %s" % (
179 inputFilename
, self
.linenumber
, repr(self
.token
), self
.message
)
182 class MASKSET(object):
184 Class MASKSET holds a set of bit vectors, all of which are related, will all
185 have the same length, and are associated with one of the seven shiftOps:
186 HIR, HDR, TIR, TDR, SIR, SDR, LSDR. One of these holds a mask, smask, tdi, tdo, and a
189 def __init__(self
, name
):
194 self
.mask
= bytearray()
195 self
.smask
= bytearray()
196 self
.tdi
= bytearray()
197 self
.tdo
= bytearray()
200 def syncLengths( self
, sawTDI
, sawTDO
, sawMASK
, sawSMASK
, newSize
):
202 Set all the lengths equal in the event some of the masks were
203 not seen as part of the last change set.
205 if self
.size
== newSize
:
212 # If an SIR was given without a MASK(), then use a mask of all zeros.
213 # this is not consistent with the SVF spec, but it makes sense because
214 # it would be odd to be testing an instruction register read out of a
215 # tap without giving a mask for it. Also, lattice seems to agree and is
216 # generating SVF files that comply with this philosophy.
217 if self
.name
== 'SIR' and not sawMASK
:
218 self
.mask
= bytearray( newSize
)
220 if newSize
!= len(self
.mask
):
221 self
.mask
= bytearray( newSize
)
222 if self
.name
== 'SDR': # leave mask for HIR,HDR,TIR,TDR,SIR zeros
223 for i
in range( newSize
):
226 if newSize
!= len(self
.tdo
):
227 self
.tdo
= bytearray( newSize
)
229 if newSize
!= len(self
.tdi
):
230 self
.tdi
= bytearray( newSize
)
232 if newSize
!= len(self
.smask
):
233 self
.smask
= bytearray( newSize
)
236 #-----</MASKSET>-----
239 def makeBitArray( hexString
, bitCount
):
241 Converts a packed sequence of hex ascii characters into a bytearray where
242 each element in the array holds exactly one bit. Only "bitCount" bits are
243 scanned and these must be the least significant bits in the hex number. That
244 is, it is legal to have some unused bits in the must significant hex nibble
245 of the input "hexString". The string is scanned starting from the backend,
246 then just before returning we reverse the array. This way the append()
247 method can be used, which I assume is faster than an insert.
252 hexString
= list(hexString
)
259 for mask
in [1,2,4,8]:
263 a
.append( (c
& mask
) != 0 )
265 raise ParseError( tokLn
, hexString
, "Insufficient hex characters for given length of %d" % bitCount
)
271 def makeXSVFbytes( bitarray
):
273 Make a bytearray which is contains the XSVF bits which will be written
274 directly to disk. The number of bytes needed is calculated from the size
275 of the argument bitarray.
277 bitCount
= len(bitarray
)
278 byteCount
= (bitCount
+7)//8
279 ba
= bytearray( byteCount
)
280 firstBit
= (bitCount
% 8) - 1
284 for byteNdx
in range(byteCount
):
297 def writeComment( outputFile
, shiftOp_linenum
, shiftOp
):
299 Write an XCOMMENT record to outputFile
301 comment
= "%s @%d\0" % (shiftOp
, shiftOp_linenum
) # \0 is terminating nul
304 ba
+= comment
.encode()
305 outputFile
.write( ba
)
308 def combineBitVectors( trailer
, meat
, header
):
310 Combine the 3 bit vectors comprizing a transmission. Since the least
311 significant bits are sent first, the header is put onto the list last so
312 they are sent first from that least significant position.
315 ret
.extend( trailer
)
321 def writeRUNTEST( outputFile
, run_state
, end_state
, run_count
, min_time
, tokenTxt
):
323 Write the output for the SVF RUNTEST command.
324 run_count - the number of clocks
325 min_time - the number of seconds
326 tokenTxt - either RUNTEST or LDELAY
328 # convert from secs to usecs
329 min_time
= int( min_time
* 1000000)
331 # the SVF RUNTEST command does NOT map to the XSVF XRUNTEST command. Check the SVF spec, then
332 # read the XSVF command. They are not the same. Use an XSVF XWAITSTATE to
333 # implement the required behavior of the SVF RUNTEST command.
335 writeComment( output
, tokLn
, tokenTxt
)
337 if tokenTxt
== 'RUNTEST':
342 struct
.pack_into(">i", obuf
, 3, run_count
) # big endian 4 byte int to obuf
343 struct
.pack_into(">i", obuf
, 7, min_time
) # big endian 4 byte int to obuf
344 outputFile
.write( obuf
)
349 # LDELAY has no end_state
350 struct
.pack_into(">i", obuf
, 2, run_count
) # big endian 4 byte int to obuf
351 struct
.pack_into(">i", obuf
, 6, min_time
) # big endian 4 byte int to obuf
352 outputFile
.write( obuf
)
355 output
= open( outputFilename
, mode
='wb' )
368 # one of the commands that take the shiftParts after the length, the parse
369 # template for all of these commands is identical
370 shiftOps
= ('SDR', 'SIR', 'LSDR', 'HDR', 'HIR', 'TDR', 'TIR')
372 # the order must correspond to shiftOps, this holds the MASKSETS. 'LSDR' shares sdr with 'SDR'
373 shiftSets
= (sdr
, sir
, sdr
, hdr
, hir
, tdr
, tir
)
375 # what to expect as parameters to a shiftOp, i.e. after a SDR length or SIR length
376 shiftParts
= ('TDI', 'TDO', 'MASK', 'SMASK')
378 # the set of legal states which can trail the RUNTEST command
379 run_state_allowed
= ('IRPAUSE', 'DRPAUSE', 'RESET', 'IDLE')
381 enddr_state_allowed
= ('DRPAUSE', 'IDLE')
382 endir_state_allowed
= ('IRPAUSE', 'IDLE')
384 trst_mode_allowed
= ('ON', 'OFF', 'Z', 'ABSENT')
389 frequency
= 1.00e+006 # HZ;
391 # change detection for xsdrsize and xtdomask
392 xsdrsize
= -1 # the last one sent, send only on change
393 xtdomask
= bytearray() # the last one sent, send only on change
396 # we use a number of single byte writes for the XSVF command below
397 cmdbuf
= bytearray(1)
400 # Save the XREPEAT setting into the file as first thing.
411 expecting_eof
= False
412 # print( tokType, tokVal, tokLn )
414 if tokVal
in shiftOps
:
415 shiftOp_linenum
= tokLn
418 set = shiftSets
[shiftOps
.index(shiftOp
)]
420 # set flags false, if we see one later, set that one true later
421 sawTDI
= sawTDO
= sawMASK
= sawSMASK
= False
425 raise ParseError( tokLn
, tokVal
, "Expecting 'int' giving %s length, got '%s'" % (shiftOp
, tokType
) )
431 if tokVal
not in shiftParts
:
432 raise ParseError( tokLn
, tokVal
, "Expecting TDI, TDO, MASK, SMASK, or ';'")
438 raise ParseError( tokLn
, tokVal
, "Expecting hex bits" )
439 bits
= makeBitArray( tokVal
, length
)
441 if shiftPart
== 'TDI':
445 elif shiftPart
== 'TDO':
449 elif shiftPart
== 'MASK':
453 elif shiftPart
== 'SMASK':
459 set.syncLengths( sawTDI
, sawTDO
, sawMASK
, sawSMASK
, length
)
461 # process all the gathered parameters and generate outputs here
464 writeComment( output
, shiftOp_linenum
, 'SIR' )
466 tdi
= combineBitVectors( tir
.tdi
, sir
.tdi
, hir
.tdi
)
470 struct
.pack_into( ">h", obuf
, 1, len(tdi
) )
476 obuf
= makeXSVFbytes( tdi
)
479 elif shiftOp
== 'SDR':
481 writeComment( output
, shiftOp_linenum
, shiftOp
)
484 # pass a zero filled bit vector for the sdr.mask
485 mask
= combineBitVectors( tdr
.mask
, bytearray(sdr
.size
), hdr
.mask
)
486 tdi
= combineBitVectors( tdr
.tdi
, sdr
.tdi
, hdr
.tdi
)
488 if xsdrsize
!= len(tdi
):
491 output
.write( cmdbuf
)
493 struct
.pack_into( ">i", obuf
, 0, xsdrsize
) # big endian 4 byte int to obuf
499 output
.write( cmdbuf
)
500 obuf
= makeXSVFbytes( mask
)
504 output
.write( cmdbuf
)
505 obuf
= makeXSVFbytes( tdi
)
509 mask
= combineBitVectors( tdr
.mask
, sdr
.mask
, hdr
.mask
)
510 tdi
= combineBitVectors( tdr
.tdi
, sdr
.tdi
, hdr
.tdi
)
511 tdo
= combineBitVectors( tdr
.tdo
, sdr
.tdo
, hdr
.tdo
)
513 if xsdrsize
!= len(tdi
):
516 output
.write( cmdbuf
)
518 struct
.pack_into(">i", obuf
, 0, xsdrsize
) # big endian 4 byte int to obuf
524 output
.write( cmdbuf
)
525 obuf
= makeXSVFbytes( mask
)
529 output
.write( cmdbuf
)
530 obuf
= makeXSVFbytes( tdi
)
532 obuf
= makeXSVFbytes( tdo
)
534 #print( "len(tdo)=", len(tdo), "len(tdr.tdo)=", len(tdr.tdo), "len(sdr.tdo)=", len(sdr.tdo), "len(hdr.tdo)=", len(hdr.tdo) )
536 elif shiftOp
== 'LSDR':
538 writeComment( output
, shiftOp_linenum
, shiftOp
)
540 mask
= combineBitVectors( tdr
.mask
, sdr
.mask
, hdr
.mask
)
541 tdi
= combineBitVectors( tdr
.tdi
, sdr
.tdi
, hdr
.tdi
)
542 tdo
= combineBitVectors( tdr
.tdo
, sdr
.tdo
, hdr
.tdo
)
544 if xsdrsize
!= len(tdi
):
547 output
.write( cmdbuf
)
549 struct
.pack_into(">i", obuf
, 0, xsdrsize
) # big endian 4 byte int to obuf
555 output
.write( cmdbuf
)
556 obuf
= makeXSVFbytes( mask
)
560 output
.write( cmdbuf
)
561 obuf
= makeXSVFbytes( tdi
)
563 obuf
= makeXSVFbytes( tdo
)
565 #print( "len(tdo)=", len(tdo), "len(tdr.tdo)=", len(tdr.tdo), "len(sdr.tdo)=", len(sdr.tdo), "len(hdr.tdo)=", len(hdr.tdo) )
567 elif tokVal
== 'RUNTEST' or tokVal
== 'LDELAY':
568 # e.g. from lattice tools:
569 # "RUNTEST IDLE 5 TCK 1.00E-003 SEC;"
574 max_time
= 600 # ten minutes
575 if tokVal
in run_state_allowed
:
576 run_state
= StateTxt
.index(tokVal
)
577 end_state
= run_state
# bottom of page 17 of SVF spec
579 if tokType
!= 'int' and tokType
!= 'float':
580 raise ParseError( tokLn
, tokVal
, "Expecting 'int' or 'float' after RUNTEST [run_state]")
583 if tokVal
!= 'TCK' and tokVal
!= 'SEC' and tokVal
!= 'SCK':
584 raise ParseError( tokLn
, tokVal
, "Expecting 'TCK' or 'SEC' or 'SCK' after RUNTEST [run_state] (run_count|min_time)")
585 if tokVal
== 'TCK' or tokVal
== 'SCK':
586 run_count
= int( timeval
)
590 if tokType
== 'int' or tokType
== 'float':
594 raise ParseError( tokLn
, tokVal
, "Expecting 'SEC' after RUNTEST [run_state] run_count min_time")
596 if tokVal
== 'MAXIMUM':
598 if tokType
!= 'int' and tokType
!= 'float':
599 raise ParseError( tokLn
, tokVal
, "Expecting 'max_time' after RUNTEST [run_state] min_time SEC MAXIMUM")
603 raise ParseError( tokLn
, tokVal
, "Expecting 'max_time' after RUNTEST [run_state] min_time SEC MAXIMUM max_time")
605 if tokVal
== 'ENDSTATE':
607 if tokVal
not in run_state_allowed
:
608 raise ParseError( tokLn
, tokVal
, "Expecting 'run_state' after RUNTEST .... ENDSTATE")
609 end_state
= StateTxt
.index(tokVal
)
612 raise ParseError( tokLn
, tokVal
, "Expecting ';' after RUNTEST ....")
613 # print( "run_count=", run_count, "min_time=", min_time,
614 # "max_time=", max_time, "run_state=", State[run_state], "end_state=", State[end_state] )
615 writeRUNTEST( output
, run_state
, end_state
, run_count
, min_time
, saveTok
)
617 elif tokVal
== 'LCOUNT':
620 raise ParseError( tokLn
, tokVal
, "Expecting integer 'count' after LCOUNT")
624 raise ParseError( tokLn
, tokVal
, "Expecting ';' after LCOUNT count")
626 writeComment( output
, tokLn
, 'LCOUNT' )
629 struct
.pack_into(">i", obuf
, 1, loopCount
) # big endian 4 byte int to obuf
632 elif tokVal
== 'ENDDR':
634 if tokVal
not in enddr_state_allowed
:
635 raise ParseError( tokLn
, tokVal
, "Expecting 'stable_state' after ENDDR. (one of: DRPAUSE, IDLE)")
636 enddr_state
= StateTxt
.index(tokVal
)
639 raise ParseError( tokLn
, tokVal
, "Expecting ';' after ENDDR stable_state")
641 writeComment( output
, tokLn
, 'ENDDR' )
644 # Page 10 of the March 1999 SVF spec shows that RESET is also allowed here.
645 # Yet the XSVF spec has no provision for that, and uses a non-standard, i.e.
646 # boolean argument to XENDDR which only handles two of the 3 intended states.
647 obuf
[1] = 1 if enddr_state
== DRPAUSE
else 0
650 elif tokVal
== 'ENDIR':
652 if tokVal
not in endir_state_allowed
:
653 raise ParseError( tokLn
, tokVal
, "Expecting 'stable_state' after ENDIR. (one of: IRPAUSE, IDLE)")
654 endir_state
= StateTxt
.index(tokVal
)
657 raise ParseError( tokLn
, tokVal
, "Expecting ';' after ENDIR stable_state")
659 writeComment( output
, tokLn
, 'ENDIR' )
662 # Page 10 of the March 1999 SVF spec shows that RESET is also allowed here.
663 # Yet the XSVF spec has no provision for that, and uses a non-standard, i.e.
664 # boolean argument to XENDDR which only handles two of the 3 intended states.
665 obuf
[1] = 1 if endir_state
== IRPAUSE
else 0
668 elif tokVal
== 'STATE':
672 if tokVal
not in StateTxt
:
673 raise ParseError( tokLn
, tokVal
, "Expecting 'stable_state' after STATE")
674 stable_state
= StateTxt
.index( tokVal
)
676 if doCOMMENTs
and ln
!= -1:
677 writeComment( output
, ln
, 'STATE' )
678 ln
= -1 # save comment only once
682 obuf
[1] = stable_state
686 elif tokVal
== 'FREQUENCY':
689 if tokType
!= 'int' and tokType
!= 'float':
690 raise ParseError( tokLn
, tokVal
, "Expecting 'cycles HZ' after FREQUENCY")
694 raise ParseError( tokLn
, tokVal
, "Expecting 'HZ' after FREQUENCY cycles")
697 raise ParseError( tokLn
, tokVal
, "Expecting ';' after FREQUENCY cycles HZ")
699 elif tokVal
== 'TRST':
701 if tokVal
not in trst_mode_allowed
:
702 raise ParseError( tokLn
, tokVal
, "Expecting 'ON|OFF|Z|ABSENT' after TRST")
706 raise ParseError( tokLn
, tokVal
, "Expecting ';' after TRST trst_mode")
708 writeComment( output
, tokLn
, 'TRST %s' % trst_mode
)
709 obuf
= bytearray( 2 )
711 obuf
[1] = trst_mode_allowed
.index( trst_mode
) # use the index as the binary argument to XTRST opcode
715 raise ParseError( tokLn
, tokVal
, "Unknown token '%s'" % tokVal
)
717 except StopIteration:
718 if not expecting_eof
:
719 print( "Unexpected End of File at line ", tokLn
)
721 except ParseError
as pe
:
725 # print( "closing file" )
726 cmdbuf
[0] = XCOMPLETE
727 output
.write( cmdbuf
)