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

vpr P&R reader

parent 216b42b9
# ArGen
The ArGen FPGA overlay framework
\ No newline at end of file
The ArGen FPGA overlay framework
```bash
git diff master conso -- file
```
- lib/argen/architecture.rb
require 'blifutils'
require_relative 'architecture.rb'
module BlifUtils
class Netlist
class IO
attr_accessor :parIsUsed
end
class Component
attr_accessor :parIsUsed
end
end
end
module ArGen
module Architecture
class Arch
def read_netlist_file (fileName)
puts "Reading netlist..."
netlist = BlifUtils::read(fileName, quiet: true)
abort "ERROR: Netlist \"#{fileName}\" has no model" if netlist.length < 1
STDERR.puts "WARNING: Netlist \"#{fileName}\" has more than one model (#{netlist.length})" if netlist.length > 1
model = netlist.first_model
@parModelName = model.name
abort "ERROR: Model \"#{model.name}\" from file \"#{fileName}\" contains sub-circuits" unless model.is_self_contained?
model.inputs.each{|inp| inp.parIsUsed = false}
model.outputs.each{|outp| outp.parIsUsed = false}
model.components.each{|comp| comp.parIsUsed = false}
model.components.select{|comp| comp.isGate?}.select{|gate| gate.is_buffer?}.each{|buffer| buffer.output.name = buffer.inputs[0].name}
@clbs.each do |clb|
clb.bles.each do |ble|
next unless ble.used
if ble.lut.netName == :wire then
update_LUT_as_wire(ble.lut)
else
update_LUT(ble.lut, model)
end
if ble.registerUsed then
update_latch(ble, model)
end
end
end
## Check that all components from the blif model are used ##
model.components.each do |comp|
STDERR.puts "WARNING: #{comp} from model \"#{model.name}\" is not used in the placement and routing" unless comp.parIsUsed or (comp.isGate? and comp.is_buffer?)
end
@parNetlist = netlist
end
private
def update_LUT (lut, model)
#print "LUT "
lgate = find_matching_blif_model_names(lut, model)
lgate.parIsUsed = true
#puts ".names #{lut.inputs.collect{|ipin| ipin.netName}.reject{|name| name.length == 0}.join(' ')} #{lut.netName}"
table = lgate.get_LookUpTable()
namesAndInedexes = [] # [[iname, fpga_index, blif_index], ... ]
lut.inputs.each_with_index do |ipin, i|
next unless ipin.used
iname = ipin.netName
abort "ERROR: A LUT input pin has no net name but it is used" if iname.nil? or iname.length == 0
fpga_index = i
blif_index = lgate.inputs.collect{|inp| inp.name}.index(iname)
abort "ERROR: Cannot find input \"#{iname}\" in #{lgate}" if blif_index.nil?
namesAndInedexes << [ipin.netName, fpga_index, blif_index]
#puts namesAndInedexes[-1].reverse.inspect
end
(0...lut.content.length).each do |i|
blifTableIndex = 0
namesAndInedexes.each do |nfibi|
if (i & (1 << nfibi[1])) != 0 then
blifTableIndex |= (1 << nfibi[2])
end
end
#puts "#{i} -> #{blifTableIndex}"
if table[blifTableIndex] == 1 then
lut.content[i] = 1
else
lut.content[i] = 0
end
end
#puts table.inspect
#puts lut.content.inspect
end
def update_latch (ble, model)
#print "REG "
reg = model.components.select{|comp| comp.isLatch? and comp.input.name == ble.registerInputNetName and comp.output.name == ble.registerOutputNetName}
abort "ERROR: Found #{reg.length} .latches corresponding to: (input: \"#{ble.registerInputNetName}\", output: \"#{ble.registerOutputNetName}\") in model \"#{model.name}\"" if reg.length != 1
reg = reg[0]
abort "ERROR: #{reg.to_s} is sensitive on \"#{reg.ctrlType}\", but we support only \"re\"" if reg.ctrlType != :re
unless reg.ctrlSig.nil? then
if reg.ctrlSig.kind_of?(String) then
ble.registerCtrlSig = reg.ctrlSig
else
ble.registerCtrlSig = reg.ctrlSig.name
end
end
ble.registerInitValue = (reg.initValue == 1) ? 1 : 0
reg.parIsUsed = true
#puts ".latch #{ble.registerInputNetName} #{ble.registerOutputNetName} re #{ble.registerCtrlSig} #{ble.registerInitValue}"
end
def update_LUT_as_wire (lut)
#print "WIRE "
input = lut.inputs.select{|ipin| ipin.used}
abort "ERROR: LUT used as wire do not have only one input" if input.length != 1
inputIndex = lut.inputs.index(input[0])
mask = 1 << inputIndex
(0...lut.content.length).each do |i|
if (i & mask) != 0 then
lut.content[i] = 1
else
lut.content[i] = 0
end
end
#puts lut.content.inspect
end
def find_matching_blif_model_names (lut, model)
logicGates = model.components.select{|comp| comp.isGate?}
lutOutputName = lut.netName
lutInputNames = lut.inputs.collect{|ipin| ipin.netName}.reject{|name| name.nil? or name.length == 0}
matchingGates = []
logicGates.each do |lgate|
next if lgate.output.name != lutOutputName
lgateInputNames = lgate.inputs.collect{|inp| inp.name}.sort{|na, nb| na <=> nb}
next if lgateInputNames.length != lutInputNames.length
itsthewritelut = true
lutInputNames.sort{|na, nb| na <=> nb}.each_with_index do |lin, i|
unless lgateInputNames[i] == lin then
itsthewritelut = false
break
end
end
next unless itsthewritelut
matchingGates << lgate
end
abort "ERROR: Found #{matchingGates.length} .names corresponding to LUT \"#{lutOutputName}\" in model \"#{model.name}\"" if matchingGates.length != 1
return matchingGates[0]
end
end
end
end
require 'rexml/document'
require_relative 'arch_reader.rb'
require_relative 'architecture.rb'
module ArGen
module Architecture
class Arch
def read_VPR_packing_file (fileName)
abort "ERROR: Yout must read the placement file before reading the packing file" if @placement.nil?
puts "Reading packing..."
begin
str = File.read(fileName)
rescue Exception => e
abort "ERROR: Cannot read file \"#{fileName}\":\n #{e.message}"
end
doc = REXML::Document.new(str)
root = doc.root
rootInputs = root.elements['inputs'].text.split(/\s+/).delete_if{|inp| inp.length == 0}
rootOutputs = root.elements['outputs'].text.split(/\s+/).delete_if{|oup| oup.length == 0}
if root.elements['clocks'].text.nil? then
rootClocks = []
else
rootClocks = root.elements['clocks'].text.split(/\s+/).delete_if{|oup| oup.length == 0}
end
# puts ' Root '.center(80, '-')
# puts 'Inputs: ' + rootInputs.join(', ')
# puts 'Outputs: ' + rootOutputs.join(', ')
# puts 'Clocks: ' + rootClocks.join(', ')
root.elements.each('block') do |clbPack|
#next unless clbPack.attributes['mode'] == 'clb'
next unless @archParams[:CLBs].keys.include?(clbPack.attributes['mode'])
next if clbPack.attributes['name'] == 'open' ###???
clbName = clbPack.attributes['name']
clbPlacement = @placement.placements.find{|pl| pl[:blockName] == clbName}
abort "ERROR: Cannot find CLB block \"#{clbName}\" from packing file \"#{fileName}\" in the placement file \"#{@placement.placementFileName}\"" if clbPlacement.nil?
clbLocX = clbPlacement[:x]
clbLocY = clbPlacement[:y]
#################################################################
#puts " CLB #{clbName} (#{clbLocX},#{clbLocY}) ".center(80, '-')
#################################################################
tile = @grid[clbLocX][clbLocY]
abort "ERROR: Architecture mismatch: CLB \"#{clbName}\" is placed on a periphery tile" if tile.class != Architecture::CoreTile
clb = tile.clb
read_vpr_CLB_packing(clb, clbPack)
end
root.elements.each('block') do |ioPack|
next unless ioPack.attributes['mode'] == 'io'
ioName = ioPack.attributes['name']
next if ioName == 'open'
ioPlacement = @placement.placements.find{|pl| pl[:blockName] == ioName}
abort "ERROR: Cannot find IO block \"#{ioName}\" from packing file \"#{fileName}\" in the placement file \"#{@placement.placementFileName}\"" if ioPlacement.nil?
iobLocX = ioPlacement[:x]
iobLocY = ioPlacement[:y]
tile = @grid[iobLocX][iobLocY]
abort "ERROR: Architecture mismatch: IO \"#{ioName}\" is placed on at position (#{iobLocX},#{iobLocY})" if tile.class == Architecture::CoreTile or tile.class == Architecture::CornerTile or tile.class == Architecture::CornerBottomLeftTile
iob = tile.ioBlock
subblk = ioPlacement[:subblk]
abort "ERROR: Architecture mismatch: IO \"#{ioName}\" placed at position (#{iobLocX},#{iobLocY}) with sub-block number #{subblk} on an IO block which has only #{iob.ios.length} IOs" if subblk >= iob.ios.length
io = iob.ios[subblk]
## vFPGA outpad (input pin from the inside) ##
inputs = []
#####################################################################################################
#ioPack.elements['inputs'].elements.each('port') do |port|
# next unless port.attributes['name'] == 'outpad'
# inputs += port.text.split(/\s+/).reject{|el| el.length == 0 or el == 'open'}
#end
#####################################################################################################
#ioPack.elements.each('block') do |block|
# next unless block.attributes['instance'] == 'outpad[0]'
# next if block.elements['inputs'].nil?
# block.elements['inputs'].elements.each('port') do |port|
# next unless port.attributes['name'] == 'outpad'
# inputs += port.text.split(/\s+/).reject{|el| el.length == 0 or el == 'open'}
# end
# #outputs << block.attributes['name']
#end
#####################################################################################################
ioPack.elements.each('block') do |block|
next unless block.attributes['instance'] == 'outpad[0]'
next if block.elements['inputs'].nil?
inputs << block.attributes['name'].gsub('out:','')
end
#####################################################################################################
abort "ERROR: More than one input for IO \"#{ioName}\"" if inputs.length > 1
io.vFpgaOutputName = inputs[0] if inputs.length == 1
## vFPGA inpad (output pin from the inside) ##
outputs = []
ioPack.elements.each('block') do |block|
next unless block.attributes['instance'] == 'inpad[0]'
next if block.elements['outputs'].nil?
block.elements['outputs'].elements.each('port') do |port|
next unless port.attributes['name'] == 'inpad'
outputs += port.text.split(/\s+/).reject{|el| el.length == 0 or el == 'open'}
end
#outputs << block.attributes['name']
end
abort "ERROR: More than one output for IO \"#{ioName}\"" if outputs.length > 1
io.vFpgaInputName = outputs[0] if outputs.length == 1
end
#puts '-'*80
end
private
def read_vpr_CLB_packing (clb, xmlClb)
packingToRoutingLut = Array.new(clb.inputs.length)
## CLB inputs ##
xmlClb.elements['inputs'].elements.each('port') do |port|
next unless port.attributes['name'] == 'I'
port.text.split(/\s+/).delete_if{|el| el.length == 0}.each_with_index do |netName, i|
#puts "#{i}: #{netName}"
next if netName == 'open'
## Find the real clb input that have this net name ##
ind = clb.inputs.index{|ipin| ipin.netName == netName}
abort "ERROR: cannot find CLB input with net name \"#{netName}\" at location (#{clb.locX},#{clb.locY})" if ind.nil?
packingToRoutingLut[i] = ind
#clb.inputs[i].used = true
clb.crossbar.inputs[ind].used = true
clb.crossbar.inputs[ind].netName = netName
#clb.crossbar.inputs[ind].relatedPin = clb.inputs[i]
end
end
###############################################################
#puts 'packingToRoutingLut ' + packingToRoutingLut.inspect
###############################################################
packingBleOutputsNetNames = Array.new(clb.bles.length)
## Get BLE output names according to the packing ##
xmlClb.elements.each('block') do |xmlBle|
next if xmlBle.attributes['name'] == 'open' ###???
bleNum = xmlBle.attributes['instance'].match(/.*?\[(\d+)\]/)[1].to_i
abort "ERROR: Architecture mismatch: trying to reach BLE[#{bleNum}] at location (#{clb.locX},#{clb.locY})" if bleNum >= clb.bles.length
## BLE outputs, see if the register is used ##
lutOutputNetName = ''
regOutputNetName = ''
xmlBle.elements.each('block') do |xmlBlock|
if xmlBlock.attributes['instance'][0..2] == 'lut' then
lutOutputNetName = xmlBlock.attributes['name']
elsif xmlBlock.attributes['instance'][0..2] == 'ff[' then
regOutputNetName = xmlBlock.attributes['name']
else
raise "WTF? (#{xmlBlock.attributes['instance']})"
end
end
if regOutputNetName == 'open' then
packingBleOutputsNetNames[bleNum] = lutOutputNetName
#puts "Lut is used, name is \"#{packingBleOutputsNetNames[bleNum]}\""
else
packingBleOutputsNetNames[bleNum] = regOutputNetName
#puts "Reg is used, name is \"#{packingBleOutputsNetNames[bleNum]}\""
end
end
###############################################################
#puts 'packingBleOutputsNetNames ' + packingBleOutputsNetNames.inspect
###############################################################
packingToRoutingBle = Array.new(clb.bles.length)
## CLB outputs ##
xmlClb.elements['outputs'].elements.each('port') do |port|
next unless port.attributes['name'] == 'O'
port.text.split(/\s+/).delete_if{|el| el.length == 0}.each_with_index do |netName, i|
#puts "#{i}: #{netName}"
next if netName == 'open'
## Find the real CLB output that have this net name ##
ind = clb.outputs.index{|opin| opin.netName == packingBleOutputsNetNames[i]}
abort "ERROR: cannot find CLB output with net name \"#{packingBleOutputsNetNames[i]}\" at location (#{clb.locX},#{clb.locY})" if ind.nil?
packingToRoutingBle[i] = ind
end
end
###############################################################
#puts 'packingToRoutingBle ' + packingToRoutingBle.inspect
###############################################################
## Now packingToRoutingBle contains at index i the real BLE index at witch the packed BLE i is expected to be
## to match the routing (one BLE can output to only one CLB output, but the vpr architecture says that CLB outputs are equivalent to ease the routing,
## that is why we need to rearange BLEs).
## However not all BLE are outputed on the CLB outputs, so if a packed BLE entry is nil, we fill it with a remaining real BLE entry
#puts '-'*80
#puts packingToRoutingBle.inspect
xmlClb.elements.each('block') do |xmlBle|
next if xmlBle.attributes['name'] == 'open' ###???
bleNum = xmlBle.attributes['instance'].match(/.*?\[(\d+)\]/)[1].to_i
###############################################################
#next if packingToRoutingBle.include?(bleNum)
#firstNilPos = packingToRoutingBle.index(nil)
#packingToRoutingBle[firstNilPos] = bleNum
###############################################################
next unless packingToRoutingBle[bleNum].nil?
firstRealNotYetUsedBle = (0...clb.nbBLEs).find{|i| not(packingToRoutingBle.include?(i))}
packingToRoutingBle[bleNum] = firstRealNotYetUsedBle
###############################################################
end
###############################################################
#puts 'packingToRoutingBle ' + packingToRoutingBle.inspect
###############################################################
#puts packingToRoutingBle.inspect
#puts '-'*80
## BLEs ##
xmlClb.elements.each('block') do |xmlBle|
next if xmlBle.attributes['name'] == 'open' ###???
bleNum = xmlBle.attributes['instance'].match(/.*?\[(\d+)\]/)[1].to_i
#####
puts xmlBle.attributes['instance'] if bleNum.nil?
if packingToRoutingBle[bleNum].nil? then
puts xmlBle.attributes['instance']
puts bleNum.to_s
puts packingToRoutingBle.inspect
end
#####
ble = clb.bles[packingToRoutingBle[bleNum]]
ble.used = true
ble.outputs[0].netName = packingBleOutputsNetNames[bleNum]
## BLE inputs ##
#if xmlBle.elements['inputs'].nil? then
# abort "name: #{xmlBle.attributes['name']}, instance: #{xmlBle.attributes['instance']}, mode: #{xmlBle.attributes['mode']}"
#end
xmlBle.elements['inputs'].elements.each('port') do |port|
next unless port.attributes['name'] == 'in'
port.text.split(/\s+/).delete_if{|el| el.length == 0}.each_with_index do |netName, i|
next if netName == 'open'
ipin = ble.inputs[i]
ipin.used = true
ipin.netName = netName#.split('->')[0]
end
end
## BLE outputs ##
xmlBle.elements['outputs'].elements.each('port') do |port|
next unless port.attributes['name'] == 'out'
port.text.split(/\s+/).delete_if{|el| el.length == 0}.each_with_index do |netName, i|
next if netName == 'open'
#opin = clb.outputs[i]
#opin.used = true
#opin.netName = netName#.split('->')[0]
if netName[0..2] == 'ff[' then
ble.registerUsed = true
elsif netName[0..2] == 'lut' then
ble.registerUsed = false
else
raise "WTF? (#{netName})"
end
end
end
## Find configurations for the crossbar multiplexers (ble ipins) ##
ble.inputs.each do |ipin|
next unless ipin.used
#if ipin.netName[0..2] == 'clb' then # It's a CLB input
truc = ipin.netName.scan(/(\w+)\./)[0]
truc = truc[0] unless truc.nil?
#puts truc
if @archParams[:CLBs].keys.include?(truc) then # It's a CLB input
crossbarInputIndex = ipin.netName.match(/\w+\.I\[(\d+)\]->crossbar/)[1].to_i
ipin.driverMux.routedInputIndex = packingToRoutingLut[crossbarInputIndex]
elsif ipin.netName[0..2] == 'ble' then # It's a BLE output
crossbarInputIndex = packingToRoutingBle[ipin.netName.match(/ble\d+\[(\d+)\]\.out\[0\]->crossbar/)[1].to_i] + clb.inputs.length
#crossbarInputIndex = ipin.netName.match(/ble\d+\[(\d+)\]\.out\[0\]->crossbar/)[1].to_i + clb.inputs.length
ipin.driverMux.routedInputIndex = crossbarInputIndex
ipin.driverMux.inputs[crossbarInputIndex].used = true
else
raise "WTF? (#{ipin.netName})"
end
end
## Find LUTs output net name ##
netName = xmlBle.elements['block'].attributes['name']
modeName = xmlBle.elements['block'].attributes['mode']
if netName != 'open' and modeName[0..2] == 'lut' then
ble.lut.netName = netName
elsif netName == 'open' and modeName == 'wire' then
ble.lut.netName = :wire
else
raise "WTF? (LUT net name: \"#{netName}\", mode: \"#{modeName}\")"
end
end
## Trace back LUTs input net names ##
clb.bles.each do |ble|
next unless ble.used
ble.inputs.each do |ipin|
next unless ipin.used
ipin.netName = ipin.driverMux.inputs[ipin.driverMux.routedInputIndex].netName
end
if ble.registerUsed then
if ble.lut.netName == :wire then
unless ble.registerUsed then
STDERR.puts "Real wire found!"
next
end
bleInputsForRegister = ble.inputs.collect{|ipin| ipin.netName}.reject{|name| name.length == 0}
abort "ERROR: BLE used with register only do not have only one input" if bleInputsForRegister.length != 1
ble.registerInputNetName = bleInputsForRegister[0]
ble.registerOutputNetName = ble.outputs[0].netName
else
ble.registerInputNetName = ble.lut.netName
ble.registerOutputNetName = ble.outputs[0].netName
end
end
end
end
end
end
end
require_relative 'arch_reader.rb'
require_relative 'architecture.rb'
module ArGen
module Architecture
class Placement
attr_reader :placementFileName
attr_reader :netlistFileName
attr_reader :archiFileName
attr_reader :layoutWidth, :layoutHeight
attr_reader :placements # [{blockName:, x:, y: , subblk:}, ... ]
def initialize (fileName)
@placementFileName = fileName
begin
str = File.read(fileName).gsub(/#.*?$/,'')
rescue Exception => e
abort "ERROR: Cannot read file \"#{fileName}\":\n #{e.message}"
end
headerMatch = str.match(/Netlist\s+file:\s+(.*?)\s+Architecture file:\s+(.*?)\s+Array\s+size:\s+(\d+)\s+x\s+(\d+)\s+logic\s+blocks\s+(.*)/m)
if headerMatch.nil? then
abort "ERROR: The VPR placement file \"#{fileName}\" does not follow the expected format (see regular expression in file #{$0})"
end
@netlistFileName = headerMatch[1]
@archiFileName = headerMatch[2]
@layoutWidth = headerMatch[3].to_i
@layoutHeight = headerMatch[4].to_i
#puts "Netlist: #{@netlistFileName}\nArchitecture: #{@archiFileName}\nLayout: #{@layoutWidth}x#{@layoutHeight}\nPlacement:"
@placements = headerMatch[5].lines.collect do |placementLine|
placementMatch = placementLine.match(/(.*?)\s+(\d+)\s+(\d+)\s+(\d+)\s*/)
if placementMatch.nil? then
abort "ERROR: The VPR placement file \"#{fileName}\" does not follow the expected format (see regular expression in file #{$0})"
end
{blockName: placementMatch[1], x: placementMatch[2].to_i, y: placementMatch[3].to_i, subblk: placementMatch[4].to_i}
end
#@placements.each{|pl| puts "#{pl[:blockName]}\t(#{pl[:x]},#{pl[:y]})\t#{pl[:subblk]}"}
end
def [](key)
return @placements[key]
end
def length
return @placements.length
end
end
class Arch
attr_accessor :placement
def read_VPR_placement_file (fileName)
puts "Reading placement..."
@placement = Architecture::Placement.new(fileName)
if @placement.layoutWidth + 2 != @gridWidth or @placement.layoutHeight + 2 != @gridHeight then
abort "ERROR: Architecture mismatch: architecture \"#{@name}\" has a #{@gridWidth-2}x#{@gridWidth-2} layout\n whereas architecture \"#{@placement.archiFileName}\" used for the placement file \"#{fileName}\" has a #{@placement.layoutWidth}x#{@placement.layoutHeight} layout"
end
@placement.placements.each do |pl|
tile = @grid[pl[:x]][pl[:y]]
if tile.class == Architecture::CornerTile or tile.class == Architecture::CornerBottomLeftTile then
abort "ERROR: Architecture mismatch: a CLB or an IO is placed on a corner tile"
elsif tile.class == Architecture::CoreTile then