Commit 5f86876f authored by Théotime BOLLENGIER's avatar Théotime BOLLENGIER
Browse files

bitgen, STA, P&R viewer

parent 9d978be0
require_relative 'architecture.rb'
require_relative 'bitstream_extractor.rb'
require_relative 'par_loader.rb'
#require_relative 'VirtSta.rb'
module ArGen
module Architecture
class Arch
def extract_bitstream_wrapped_for_SPI_wrapper(ioLocationReportFile: options[:ioloc])
vFpgaCoreInputNames = Array.new(@vFpgaNbIoPins)
vFpgaCoreOutputNames = Array.new(@vFpgaNbIoPins)
update_IOs_position()
## Input pads ##
@ios.select{|io| io.vFpgaInputName != nil and io.outputPin.used and io.vFpgaInputName != '_clk_'}.each do |io|
vFpgaCoreInputNames[io.vFpgaIoPinNumber] = io.vFpgaInputName
end
## Output pads ##
@ios.select{|io| io.vFpgaOutputName != nil and io.inputPin.used}.each do |io|
vFpgaCoreOutputNames[io.vFpgaIoPinNumber] = io.vFpgaOutputName
end
output_state_arr = Array.new(@vFpgaNbIoPins){'0'}
iolocationreportOutFile = File.open(ioLocationReportFile, 'w') if ioLocationReportFile
maxSigNameLen = ([vFpgaCoreInputNames, vFpgaCoreOutputNames].flatten.reject{|e| e.nil?}.collect{|s| s.length} + [11]).max if ioLocationReportFile
iolocationreportOutFile.puts "┏━━━━━━━━━━━┳━━━━━┳#{'━'*(2+maxSigNameLen)}┓" if ioLocationReportFile
iolocationreportOutFile.puts "┃ Direction ┃ PIN ┃#{'Signal Name'.center(2+maxSigNameLen)}┃" if ioLocationReportFile
iolocationreportOutFile.puts "┡━━━━━━━━━━━╇━━━━━╇#{'━'*(2+maxSigNameLen)}┩" if ioLocationReportFile
pins = []
@vFpgaNbIoPins.times do |i|
next unless vFpgaCoreInputNames[i] or vFpgaCoreOutputNames[i]
el = {input: vFpgaCoreInputNames[i], output: vFpgaCoreOutputNames[i], dir: (vFpgaCoreOutputNames[i] ? 'Output ' : 'Input Z'), number: i}
pins << el
end
pins.each_with_index do |el, i|
output_state_arr[el[:number]] = '1' if el[:output]
if ioLocationReportFile then
iolocationreportOutFile.puts "│ Output │ #{el[:number].to_s.rjust(3)}#{el[:output].ljust(maxSigNameLen)} │" if el[:output]
iolocationreportOutFile.puts "│ Input Z │ #{el[:number].to_s.rjust(3)}#{el[:input].ljust(maxSigNameLen)} │" if el[:input] and not(el[:output])
iolocationreportOutFile.puts "│ Input │ │ #{el[:input].ljust(maxSigNameLen)} │" if el[:input] and el[:output]
iolocationreportOutFile.puts "├───────────┼─────┼#{'─'*(2+[11, maxSigNameLen].max)}┤" if i < (pins.length - 1)
end
STDERR.puts "WARNING: Physical pin #{el[:number]} is used at the same time as an input and an output.\n Note that the pin state is NOT high impedence." if el[:input] and el[:output]
end
iolocationreportOutFile.puts "└───────────┴─────┴#{'─'*(2+[11, maxSigNameLen].max)}┘" if ioLocationReportFile
iolocationreportOutFile.close if ioLocationReportFile
puts "IO location report written to file \"#{ioLocationReportFile}\"" if ioLocationReportFile
outmap = output_state_arr.reverse.join
nVTPR = (get_longest_path_length_fast(quiet: true) - 1).to_s(2).rjust(8, '0')
return nVTPR + outmap + extract_bitstream()
end # Architecture::Arch#extract_bitstream_wrapped_for_SPI_wrapper
def extract_bitstream_wrapped (constraintFile: nil, ioLocationReportFile: nil, withReorderingConfig: true)
vFpgaCoreInputNames = Array.new(@vFpgaNbIoPins){nil}
vFpgaCoreOutputNames = Array.new(@vFpgaNbIoPins){nil}
update_IOs_position()
## Input pads ##
@ios.select{|io| io.vFpgaInputName != nil and io.outputPin.used}.each do |io|
vFpgaCoreInputNames[io.vFpgaIoPinNumber] = io.vFpgaInputName
end
## Output pads ##
@ios.select{|io| io.vFpgaOutputName != nil and io.inputPin.used}.each do |io|
vFpgaCoreOutputNames[io.vFpgaIoPinNumber] = io.vFpgaOutputName
end
vFpgaWrappedInputNames = Array.new(@vFpgaNbIoPins)
vFpgaWrappedOutputNames = Array.new(@vFpgaNbIoPins)
if constraintFile then
begin
content = File.read(constraintFile).gsub(/#.*?$/,'').gsub(/^\s+/,'').gsub(/\n+/, "\n").gsub(/\A\n+/,'')
source = :fromFile
rescue Exception => e
STDERR.puts "No IO constraint file found"
source = :noConstraints
end
else
puts "No IO location constraint file was provided"
end
if source == :fromFile then
content.lines.each do |line|
match = line.match(/^\s*(\w+)\s*(\[\s*(\d+)\s*(:\s*(\d+)\s*)?\])?\s+(\d+)\s*(:\s*(\d+)\s*)?$/)
abort "ERROR: Parsing error in file \"#{constraintFile}\":\n \"#{line.chomp}\"" if match.nil?
sigName = match[1]
if match[2] then
fromMsbBit = match[3].to_i
if match[4] then
fromLsbBit = match[5].to_i
else
fromLsbBit = fromMsbBit
end
else
fromMsbBit = nil
fromLsbBit = nil
end
toMsbBit = match[6].to_i
if match[7] then
toLsbBit = match[8].to_i
else
toLsbBit = toMsbBit
end
if toLsbBit > toMsbBit then
tmp = toLsbBit
toLsbBit = toMsbBit
toMsbBit = tmp
end
abort "ERROR: In file \"#{constraintFile}\": index cannot be negative\n \"#{line.chomp}\"" if toLsbBit < 0
abort "ERROR: In file \"#{constraintFile}\": IO constraint location not in the architecture (#{@vFpgaNbIoPins} inputs and #{@vFpgaNbIoPins} outputs)\n \"#{line.chomp}\"" if toLsbBit >= @vFpgaNbIoPins
if fromMsbBit.nil? then
fromMsbBit = toMsbBit - toLsbBit
end
if fromLsbBit.nil? then
fromLsbBit = 0
end
if fromLsbBit > fromMsbBit then
tmp = fromLsbBit
fromLsbBit = fromMsbBit
fromMsbBit = tmp
end
abort "ERROR: In file \"#{constraintFile}\": index cannot be negative\n \"#{line.chomp}\"" if fromLsbBit < 0
if (fromMsbBit - fromLsbBit) != (toMsbBit - toLsbBit) then
abort "ERROR: In file \"#{constraintFile}\": bitwidths mismatch (#{fromMsbBit + 1 - fromLsbBit} and #{toMsbBit + 1 - toLsbBit})\n \"#{line.chomp}\""
end
if vFpgaCoreInputNames.reject{|el| el.nil?}.collect{|name| name.gsub(/\[\s*\d+\s*\]/,'')}.include?(sigName) then
arr = vFpgaWrappedInputNames
coreArr = vFpgaCoreInputNames
direction = :input
elsif vFpgaCoreOutputNames.reject{|el| el.nil?}.collect{|name| name.gsub(/\[\s*\d+\s*\]/,'')}.include?(sigName) then
arr = vFpgaWrappedOutputNames
direction = :output
coreArr = vFpgaCoreOutputNames
else
abort "ERROR: In file \"#{constraintFile}\": cannot find signal \"#{sigName}\" in the placed and routed netlist.\n \"#{line.chomp}\""
end
toBit = toLsbBit
fromLsbBit.upto(fromMsbBit) do |fromBit|
putSig = sigName + '[' + fromBit.to_s + ']'
unless arr[toBit].nil? then
abort "ERROR: In file \"#{constraintFile}\": cannot place #{(direction == :input) ? 'input' : 'output'} \"#{putSig}\" at position #{toBit}, because it is already occupied by signal \"#{arr[toBit]}\".\n \"#{line.chomp}\""
end
arr[toBit] = putSig
unless coreArr.include?(putSig) then
abort "ERROR: In file \"#{constraintFile}\": cannot find #{(direction == :input) ? 'input' : 'output'} \"#{putSig}\" in the placed and routed netlist.\n \"#{line.chomp}\""
end
toBit += 1
end
end
vFpgaCoreInputNames.each do |name|
abort "ERROR: Input \"#{name}\" from the placed and routed netlist is not found in the IO constraint file \"#{constraintFile}\"." unless vFpgaWrappedInputNames.include?(name)
end
vFpgaCoreOutputNames.each do |name|
abort "ERROR: Output \"#{name}\" from the placed and routed netlist is not found in the IO constraint file \"#{constraintFile}\"." unless vFpgaWrappedOutputNames.include?(name)
end
else
vFpgaCoreInputNames.each_with_index do |name, pos|
vFpgaWrappedInputNames[pos] = name
end
vFpgaCoreOutputNames.each_with_index do |name, pos|
vFpgaWrappedOutputNames[pos] = name
end
end
if ioLocationReportFile then
iolocationreportOutFile = File.open(ioLocationReportFile, 'w')
iolocationreportOutFile.puts "IO mapping:"
vFpgaWrappedInputNames.each_with_index do |name, pos|
next if name.nil?
iolocationreportOutFile.puts "Input #{pos.to_s.rjust(3)} -> #{name}"
end
vFpgaWrappedOutputNames.each_with_index do |name, pos|
next if name.nil?
iolocationreportOutFile.puts "Output #{pos.to_s.rjust(3)} -> #{name}"
end
iolocationreportOutFile.close
puts "IO location report written to file \"#{ioLocationReportFile}\""
end
wrappedIoReorderingConfigForOneIoSize = Math.log2(@vFpgaNbIoPins + 1).ceil
wrappedIoReorderingConfigSize = wrappedIoReorderingConfigForOneIoSize * @vFpgaNbIoPins * 2
wrappedIoReorderingConfig = '1' * wrappedIoReorderingConfigSize
posStart = -wrappedIoReorderingConfigForOneIoSize
vFpgaWrappedOutputNames.reverse.each_with_index do |name, i|
posStart += wrappedIoReorderingConfigForOneIoSize
next if name.nil?
corePos = vFpgaCoreOutputNames.index(name)
posEnd = posStart + wrappedIoReorderingConfigForOneIoSize
wrappedIoReorderingConfig[posStart...posEnd] = corePos.to_s(2).rjust(wrappedIoReorderingConfigForOneIoSize, '0')
end
vFpgaCoreInputNames.reverse.each_with_index do |name, i|
posStart += wrappedIoReorderingConfigForOneIoSize
next if name.nil?
corePos = vFpgaWrappedInputNames.index(name)
posEnd = posStart + wrappedIoReorderingConfigForOneIoSize
wrappedIoReorderingConfig[posStart...posEnd] = corePos.to_s(2).rjust(wrappedIoReorderingConfigForOneIoSize, '0')
end
if withReorderingConfig then
return extract_bitstream() + wrappedIoReorderingConfig
else
return extract_bitstream()
end
end # extract_bitstream_wrapped
end # Arch
def self.format_bit_string_to_binary_bitstream (bitString, outputFileName)
bitString = bitString.gsub(/#.*?$/,'').gsub(/[^(0|1)]/,'')
blength = bitString.length
totalLength = 32*((blength/32.0).ceil)
padding = totalLength - blength
paddedBitString = bitString + '0'*padding
array = paddedBitString.scan(/.{1,32}/).reverse
#binString = array.collect{|word| word.to_i(2)}.pack('L>*')
binString = array.collect{|word| word.to_i(2)}.pack('L<*')
File.binwrite(outputFileName, binString)
end
def self.format_bit_string_to_hexa_bit_string (bitString, outputFileName)
bitString = bitString.gsub(/#.*?$/,'').gsub(/[^(0|1)]/,'')
blength = bitString.length
totalLength = 32*((blength/32.0).ceil)
padding = totalLength - blength
paddedBitString = bitString + '0'*padding
array = paddedBitString.scan(/.{1,32}/).reverse
hexString = array.collect{|word| word.to_i(2).to_s(16).rjust(8,'0')}.join("\n") + "\n"
File.write(outputFileName, hexString)
end
def self.format_bit_string_to_binary_bitstream_for_SPI_wrapper (bitString, outputFileName)
bitString = bitString.gsub(/#.*?$/,'').gsub(/[^(0|1)]/,'')
blength = bitString.length
totalLength = 32*((blength/32.0).ceil)
padding = totalLength - blength
paddedBitString = bitString + '0'*padding
array = paddedBitString.scan(/.{1,8}/).reverse
binString = array.collect{|byte| byte.reverse.to_i(2)}.pack('C*')
File.binwrite(outputFileName, binString)
end
def self.format_bit_string_to_hexa_bit_string_for_SPI_wrapper (bitString, outputFileName)
bitString = bitString.gsub(/#.*?$/,'').gsub(/[^(0|1)]/,'')
blength = bitString.length
totalLength = 32*((blength/32.0).ceil)
padding = totalLength - blength
paddedBitString = bitString + '0'*padding
array = paddedBitString.scan(/.{1,8}/).collect{|s| s.reverse}.reverse.join.scan(/.{1,32}/).collect{|ws| [ws[24..31], ws[16..23], ws[8..15], ws[0..7]].join}
hexString = array.collect{|word| word.to_i(2).to_s(16).rjust(8,'0')}.join("\n") + "\n"
File.write(outputFileName, hexString)
end
# def self.read_bit_string_as_binary_bitstream_for_SPI_wrapper (inputFileName)
# File.binread(inputFileName).unpack('C*').collect{|byte| byte.to_s(2).rjust(8, '0').reverse}.reverse.join
# end
end # Architecture
end
require_relative 'architecture.rb'
require_relative 'timing_analysis.rb'
#require_relative 'graphics/place_and_route_viewer.rb'
module ArGen
module NetGraph
class Graph
attr_accessor :nodes
attr_accessor :nets
def initialize
@nodes = []
@nets = []
end
end #/ Graph
class Node
attr_reader :type # :pinput :poutput :combBLE : seqBLE
attr_reader :archElement
attr_reader :archInputs
attr_reader :archOutput
attr_reader :name
attr_accessor :inputs
attr_accessor :outputs
attr_accessor :maxCombinatorialVtprPathLengthFromThisNode
def initialize (type, inputs, output, archElement)
@type = type
@archElement = archElement
@archInputs = inputs
@archOutput = output
@inputs = []
@outputs = []
@maxCombinatorialVtprPathLengthFromThisNode = nil
@name = "#{case @type when :pinput then 'Primary INPUT' when :poutput then 'Primary OUTPUT' when :combBLE then 'Combinatorial BLE' when :seqBLE then 'Sequential BLE' else 'Smurh' end} #{@archElement.locX},#{@archElement.locY}:#{@archElement.index}"
end
def to_s
return @name
end
end #/ Node
class Net
attr_reader :startNode
attr_reader :endNode
attr_reader :archElements
attr_reader :name
attr_reader :vtprLength
attr_accessor :maxCombinatorialVtprPathLengthFromThisNet
def initialize (startNode, endNode, archElements, vtprLength)
@startNode = startNode
@endNode = endNode
@archElements = archElements
@vtprLength = vtprLength
@maxCombinatorialVtprPathLengthFromThisNet = nil
elementsNetNames = @archElements.collect{|el| el.netName}.reject{|el| el.nil?}
netNamesOccurences = Hash.new(0)
elementsNetNames.each{|netname| netNamesOccurences[netname] += 1}
maxOccurences = 0
netNamesOccurences.each do |netname, nbOccurences|
if nbOccurences > maxOccurences then
@name = netname
maxOccurences = nbOccurences
end
end
end
def to_s
return "Net #{@name} (#{@vtprLength})"
end
end #/ Net
end #/ NetGraph
module Architecture
class Arch
def get_longest_path_length_fast (quiet: false, draw_opts: {})
unless @withVTPR then
STDERR.puts "The architecture do not have Virtual Time Propagation Registers, cannot perform abstract timing analysis"
return nil
end
puts "Analysing combinatorial path lengths..." unless quiet
graph = build_graph(quiet: quiet)
maxPathLength = get_max_combinatorial_path_VTPR_length(graph, quiet: quiet)
puts "Critical path: #{maxPathLength} VTPRs" unless quiet
if draw_opts and draw_opts[:image] then
compress = not(not(draw_opts[:image] =~ /z$/i))
basename = File.basename(draw_opts[:image], '.*')
mamatch = draw_opts[:image].match(/^(.+)#{basename}/)
basename = mamatch[1] + basename if mamatch
extname = File.extname(draw_opts[:image])
inverseColor = not(not(draw_opts[:negate]))
drawPinNumbers = not(draw_opts[:nonum])
max_width = draw_opts[:width]
max_height = draw_opts[:height]
longestPaths = get_longest_paths(graph, quiet: quiet)
puts "#{longestPaths.length} path#{(longestPaths.length > 1) ? 's are' : ' is'} #{maxPathLength} long" unless quiet
longestPathsNumPad = longestPaths.length.to_s.length
longestPaths.each_with_index do |netPath,i|
longestPaths.each do |papath|
papath.each{|net| tag_path(net.archElements, nil)}
end
archelemarray = []
netPath.each{|net| archelemarray += net.archElements}
archelemarray.reverse!
tag_path_with_color_gradient(archelemarray, inverseColor: inverseColor)
#tag_path(path, 'red')
write_architecture_used_resources(
fileName: basename + ((longestPaths.length > 1) ? "_#{i.to_s.rjust(longestPathsNumPad, '0')}" : '') + extname,
compress: compress,
inverseColor: inverseColor,
drawPinNumbers: drawPinNumbers,
max_width: max_width,
max_height: max_height,
quiet: quiet
)
end
end
return maxPathLength
end #/ get_longest_path_length_fast
private
def build_graph (quiet: false)
graph = NetGraph::Graph.new
puts " Building placed and routed netlist graph..." unless quiet
build_nodes(graph, quiet: quiet)
build_nets(graph, quiet: quiet)
return graph
end #/ build_graph
def build_nodes (graph, quiet: false)
### Find all nodes ###
puts " Finding all nodes..." unless quiet
@ios.each do |io|
graph.nodes << NetGraph::Node.new(:poutput, [io.inputPin], nil, io.inputPin) if io.inputPin.used
graph.nodes << NetGraph::Node.new(:pinput, [], io.outputPin, io.outputPin) if io.outputPin.used
end
@clbs.each do |clb|
next unless clb.used
clb.bles.each do |ble|
next unless ble.used
graph.nodes << NetGraph::Node.new(ble.registerUsed ? :seqBLE : :combBLE, ble.lut.inputs.select{|ipin| ipin.used}, ble.outputs[0], ble)
end
end
puts " #{graph.nodes.length} nodes found (#{graph.nodes.select{|node| node.type == :pinput}.length} primary inputs, #{graph.nodes.select{|node| node.type == :poutput}.length} primary outputs, #{graph.nodes.select{|node| node.type == :combBLE}.length} combinatorial BLEs, #{graph.nodes.select{|node| node.type == :seqBLE}.length} sequential BLEs)" unless quiet
end #/ build_nodes
def build_nets (graph, quiet: false)
### Build all nets, starting from endpoints wich are primary outputs and BLE inputs ###
puts " Following point to point nets..." unless quiet
nets = []
graph.nodes.each do |node|
node.archInputs.each do |ipin|
archElements = [ipin]
while follow_net(archElements) do end
endNode = node
startNode = graph.nodes.find{|node| node.archOutput == archElements[-1]}
raise "ERROR: Cannot find start node related to the net!" if startNode.nil?
vtprLength = get_path_VTPR_length(archElements)
newNet = NetGraph::Net.new(startNode, endNode, archElements.reverse, vtprLength)
graph.nets << newNet
endNode.inputs << newNet
startNode.outputs << newNet
#puts "#{startNode} -> #{newNet} -> #{endNode}"
end
end
puts " #{graph.nets.length} point to point nets found" unless quiet
maxlen = graph.nets.collect{|net| net.vtprLength}.max
minlen = graph.nets.collect{|net| net.vtprLength}.min
puts " Maximum net vtpr length is #{maxlen}" unless quiet
puts " Minimum net vtpr length is #{minlen}" unless quiet
end #/ build_nets
def follow_net (archElements)
endPoint = archElements[-1]
if endPoint.kind_of?(Architecture::OutputPin) then
if endPoint.parentElement.kind_of?(Architecture::IO) then
return false
elsif endPoint.parentElement.kind_of?(Architecture::CLB) then
endPoint = endPoint.relatedPin
archElements << endPoint
return true
elsif endPoint.parentElement.kind_of?(Architecture::BLE) then
return false
else
raise "What is this endpoint (OutputPin) with parent element #{endPoint.parentElement.class.name} ?"
end
elsif endPoint.kind_of?(Architecture::Track) then
archElements << endPoint.driverMux.inputs[endPoint.driverMux.routedInputIndex]
return true
elsif endPoint.kind_of?(Architecture::InputPin) then
if endPoint.driverMux.routedInputIndex.nil? then
raise "Cannot follow path" if endPoint.relatedPin.nil?
endPoint = endPoint.relatedPin
raise "Cannot follow path" if endPoint.driverMux.routedInputIndex.nil?
end
archElements << endPoint.driverMux.inputs[endPoint.driverMux.routedInputIndex]
return true
else
raise "What is this endpoint ? (#{endPoint.class.name})"
end
end #/ follow_net
def get_max_combinatorial_path_VTPR_length (graph, quiet: false)
puts " Following combinatorial path from the graph..." unless quiet
theMax = 0
graph.nodes.select{|node| node.type == :pinput or node.type == :seqBLE}.each do |startingNode|
follow_combinatorial_path(graph, startingNode)
theMax = startingNode.maxCombinatorialVtprPathLengthFromThisNode if startingNode.maxCombinatorialVtprPathLengthFromThisNode > theMax
end
return theMax
end #/ get_max_combinatorial_path_VTPR_length
def follow_combinatorial_path (graph, node)
return unless node.maxCombinatorialVtprPathLengthFromThisNode.nil?
theMax = 0
node.outputs.each do |net|
if net.maxCombinatorialVtprPathLengthFromThisNet.nil? then
if net.endNode.type == :seqBLE then
net.maxCombinatorialVtprPathLengthFromThisNet = net.vtprLength
else
follow_combinatorial_path(graph, net.endNode)
net.maxCombinatorialVtprPathLengthFromThisNet = net.vtprLength + net.endNode.maxCombinatorialVtprPathLengthFromThisNode
end
end
theMax = net.maxCombinatorialVtprPathLengthFromThisNet if net.maxCombinatorialVtprPathLengthFromThisNet > theMax
end
node.maxCombinatorialVtprPathLengthFromThisNode = theMax
end #/ follow_combinatorial_path
def get_longest_paths (graph, quiet: false)
longestPaths = []
maxPathLength = get_max_combinatorial_path_VTPR_length(graph, quiet: quiet)
graph.nodes.select{|node| node.type == :pinput or node.type == :seqBLE}.each do |startingNode|
if startingNode.maxCombinatorialVtprPathLengthFromThisNode == maxPathLength then
get_longest_paths_from_node(graph, startingNode, [], longestPaths)
end
end
return longestPaths
end #/ get_longest_paths
def get_longest_paths_from_node (graph, startingNode, pathBegining, paths)
if startingNode.type == :poutput or (startingNode.type == :seqBLE and not(pathBegining.empty?)) then
paths << pathBegining
return
end
### find the output net (from this node) that lead to the longest path ###
longestNetLengthFromThisNode = 0
startingNode.outputs.each do |net|
longestNetLengthFromThisNode = net.maxCombinatorialVtprPathLengthFromThisNet if net.maxCombinatorialVtprPathLengthFromThisNet > longestNetLengthFromThisNode
end
longestNets = startingNode.outputs.select{|net| net.maxCombinatorialVtprPathLengthFromThisNet == longestNetLengthFromThisNode}
if longestNets.length == 1 then
pathBegining << longestNets[0]
get_longest_paths_from_node(graph, longestNets[0].endNode, pathBegining, paths)
else
longestNets.each do |net|
newPathBegining = pathBegining.collect{|nt| nt}