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

hdl arch writer

parent 779bf68c
module ArGen
module Architecture
class ConfigShuffling
attr_accessor :nb_chunks
def initialize (nb_chunks)
@nb_chunks = [2, nb_chunks.to_i.abs].max
end
def nb_permutations
# nb_chunks!
@nb_chunks.downto(1).inject(:*)
end
def mux_nb_entries
@nb_chunks + 1
end
def mux_config_size
Math.log2(mux_nb_entries).ceil
end
def key_bit_length
@nb_chunks * mux_config_size
end
def default_key
key = 0
(@nb_chunks - 1).downto(0).each do |i|
key = (key << mux_config_size) | i
end
key
end
def self.key_string_to_integer (key_str)
if key_str.length > 2 then
case key_str[0..1]
when '0x'
key = key_str[2..-1].to_i(16)
when '0d'
key = key_str[2..-1].to_i(10)
when '0b'
key = key_str[2..-1].to_i(2)
else
key = Architecture::ConfigShuffling.key_string_to_integer_1(key_str)
end
else
key = Architecture::ConfigShuffling.key_string_to_integer_1(key_str)
end
key
end
def self.key_string_to_integer_1 (key_str)
if (key_str =~ /[^01]/).nil? then
return key_str.to_i(2)
elsif (key_str =~ /[^0-9]/).nil? then
return key_str.to_i(10)
elsif (key_str =~ /[^0-9a-fA-F]/).nil? then
return key_str.to_i(16)
end
raise "The given key contains non-hexadecimal symbols"
end
def read_key (key)
if key.kind_of?(String) then
key = Architecture::ConfigShuffling.key_string_to_integer(key)
elsif not(key.kind_of?(Integer)) then
raise "Expecting the key as an Integer or a String, not a #{key.class}"
end
res = []
mask = 2**mux_config_size - 1
@nb_chunks.times do |i|
res << (key & mask)
key >>= mux_config_size
end
raise "The given key is too big: expecting a #{key_bit_length}-bit key!" if key != 0
res
end
def is_key_valid? (key)
arr = nil
begin
arr = read_key(key)
rescue Exception => e
STDERR.puts e.message
return false
end
res = true
(0...@nb_chunks).each do |i|
if arr[i] > @nb_chunks then
res = false
STDERR.puts "Mux[#{i}] configuration is #{arr[i]}, but cannot be greater than #{@nb_chunks}"
end
end
occurences = (0..@nb_chunks).collect{|i| arr.count(i)}
if occurences[0] < 1 then
res = false
STDERR.puts "The input configuration word is never shuffled into the configuration register"
end
(0..@nb_chunks).each do |i|
if occurences[i] > 1 then
res = false
STDERR.puts "Input #{i} is used more than once"
end
end
if occurences.count(0) != 1 then
res = false
STDERR.puts "One and only one input must be unused"
end
index = -1
@nb_chunks.times do |i|
nindex = arr.index(index + 1)
if nindex.nil? then
STDERR.puts "Chunk chainning is brocken: the output of chunk #{index} is not injected back (there should be a #{index+1})"
res = false
break
end
index = nindex
end
unless res then
STDERR.puts arr.inspect
end
return res
end
def random_key
dflt_key = default_key
keyn = nil
loop do
available = (0...@nb_chunks).to_a
key = Array.new(@nb_chunks){nil}
index = 0
@nb_chunks.times do
picked = available.delete(available[rand(available.length)])
key[picked] = index
index = picked + 1
end
keyn = 0
(@nb_chunks - 1).downto(0).each do |i|
keyn = (keyn << mux_config_size) | key[i]
end
break if keyn != dflt_key
end
keyn
end
class BitstreamSplitPoints
attr_accessor :bitstream_length
attr_accessor :shift_width
attr_accessor :padded_bitstream_length
attr_accessor :bitstream_padding_bits
attr_accessor :chunk_sizes
attr_accessor :chunk_inputs
attr_accessor :chunk_outputs
attr_accessor :chunk_indexes
end
def get_split_points (bitstream_length, shift_width: 32)
bsp = Architecture::ConfigShuffling::BitstreamSplitPoints.new
bsp.bitstream_length = [bitstream_length.to_i, 1].max
bsp.shift_width = [[1, shift_width.to_i].max, bitstream_length].min
bsp.padded_bitstream_length = (bsp.bitstream_length.to_f / bsp.shift_width.to_f).ceil * bsp.shift_width
bsp.bitstream_padding_bits = bsp.padded_bitstream_length - bsp.bitstream_length
main_chunk_length_parallel = bsp.padded_bitstream_length / bsp.shift_width / @nb_chunks
main_chunk_length = main_chunk_length_parallel * bsp.shift_width
bsp.chunk_sizes = Array.new(@nb_chunks){main_chunk_length}
bsp.chunk_sizes[-1] = bsp.padded_bitstream_length - bsp.chunk_sizes[0...-1].inject(&:+)
msb = bsp.padded_bitstream_length - 1
lsb = bsp.padded_bitstream_length - shift_width
bsp.chunk_inputs = bsp.chunk_sizes.collect do |chunk_size|
r = [msb, lsb]
msb -= chunk_size
lsb -= chunk_size
r
end
msb = bsp.padded_bitstream_length - 1 + shift_width
lsb = bsp.padded_bitstream_length
bsp.chunk_outputs = bsp.chunk_sizes.collect do |chunk_size|
msb -= chunk_size
lsb -= chunk_size
[msb, lsb]
end
msb = bsp.padded_bitstream_length - 1
bsp.chunk_indexes = bsp.chunk_sizes.collect do |chunk_size|
r = [msb, msb + 1 - chunk_size]
msb -= chunk_size
r
end
return bsp
end
def keyarr_to_reordering_array (key_arr)
puts "Key indexes: #{key_arr.inspect}"
res = []
index = -1
@nb_chunks.times do |i|
index = key_arr.index(index + 1)
res << index
end
alphabet = ('A'..'Z').to_a
key_score = 0
(1...@nb_chunks).each do |i|
if (res[i] - 1) != res[i-1] then
key_score += 1
end
end
key_score = key_score * 100.0 / (@nb_chunks - 1)
puts "Shuffling: [#{res.collect{|i| alphabet[i]}.join('.')}] (key score: #{key_score.round}%)"
res
end
def shuffle_bitstring (key, bitstring, shift_width: 32)
return nil unless is_key_valid?(key)
key_arr = read_key(key)
shuffling_order = keyarr_to_reordering_array(key_arr)
bsp = get_split_points(bitstring.length, shift_width: shift_width)
rbitstring = (bitstring + '0'*bsp.bitstream_padding_bits).reverse
bitstring_arr = bsp.chunk_indexes.collect do |msb_lsb|
msb = msb_lsb.first
lsb = msb_lsb.last
rbitstring[lsb..msb].reverse
end
shuffled_bitstring = shuffling_order.collect{|index| bitstring_arr[index]}.join
shuffled_bitstring
end
def get_mux_template
str = <<EndOfStr
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity config_shuffling_N#{@nb_chunks}_mux is
port (output : out std_ulogic;
inputs : in std_ulogic_vector(#{mux_nb_entries - 1} downto 0);
config : in std_ulogic_vector(#{mux_config_size - 1} downto 0));
end entity;
architecture arch of config_shuffling_N#{@nb_chunks}_mux is
EndOfStr
input_padding = 2**mux_config_size - mux_nb_entries
str += " signal padded_inputs : std_ulogic_vector(#{2**mux_config_size - 1} downto 0);\n" if input_padding > 0
str += "begin\n"
str += " padded_inputs <= \"#{'0'*input_padding}\" & inputs;\n" if input_padding > 0
str += " output <= #{(input_padding > 0) ? 'padded_':''}inputs(to_integer(unsigned(config)));\n"
str += "end architecture;\n"
str
end
def get_multibit_mux_template (width)
width = [width.to_i, 1].max
str = <<EndOfStr
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity config_shuffling_N#{@nb_chunks}_mux_#{width}bit is
port (output : out std_ulogic_vector(#{width - 1} downto 0);
EndOfStr
(0...mux_nb_entries).each do |i|
str += " input_#{i.to_s.rjust(2, '0')} : in std_ulogic_vector(#{width - 1} downto 0);\n"
end
str += <<EndOfStr
config : in std_ulogic_vector(#{mux_config_size - 1} downto 0));
end entity;
architecture arch of config_shuffling_N#{@nb_chunks}_mux_#{width}bit is
EndOfStr
(0...width).each do |i|
str += " signal mux_#{i.to_s.rjust(2, '0')}_inputs : std_ulogic_vector(#{mux_nb_entries - 1} downto 0);\n"
end
str += "\nbegin\n\n"
(0...width).each do |i|
str += " mux_#{i.to_s.rjust(2, '0')}_inputs <= #{(mux_nb_entries - 1).downto(0).collect{|j| "input_#{j.to_s.rjust(2, '0')}(#{i})"}.join(' & ')};\n"
end
str += "\n"
(0...width).each do |i|
str += " MUX#{i.to_s.rjust(2, '0')}: entity work.config_shuffling_N#{@nb_chunks}_mux\n"
str += " port map (output => output(#{i}),\n"
str += " inputs => mux_#{i.to_s.rjust(2, '0')}_inputs,\n"
str += " config => config);\n\n"
end
str += "end architecture;\n"
str
end
def get_crossbar (width)
width = [width.to_i, 1].max
str = <<EndOfStr
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity config_shuffling_N#{@nb_chunks}_crossbar_#{width}bit is
EndOfStr
str += " port (config : in std_ulogic_vector(#{key_bit_length - 1} downto 0)"
(0...mux_nb_entries).each do |i|
str += ";\n input_#{i.to_s.rjust(2, '0')} : in std_ulogic_vector(#{width - 1} downto 0)"
end
(0...@nb_chunks).each do |i|
str += ";\n output_#{i.to_s.rjust(2, '0')} : out std_ulogic_vector(#{width - 1} downto 0)"
end
str += ");\nend entity;\n\narchitecture arch of config_shuffling_N#{@nb_chunks}_crossbar_#{width}bit is\nbegin\n\n"
(0...@nb_chunks).each do |i|
str += " LMUX#{i.to_s.rjust(2, '0')}: entity work.config_shuffling_N#{@nb_chunks}_mux_#{width}bit\n"
str += " port map (output => output_#{i.to_s.rjust(2, '0')},\n"
(0...mux_nb_entries).each do |j|
str += " input_#{j.to_s.rjust(2, '0')} => input_#{j.to_s.rjust(2, '0')},\n"
end
str += " config => config(#{(i+1)*mux_config_size - 1} downto #{i*mux_config_size}));\n\n"
end
str += "end architecture;\n"
str
end
def get_shuffling_crossbar (width)
width = [width.to_i, 1].max
str = get_mux_template
str += "\n" + '-'*80 + "\n\n"
str += get_multibit_mux_template(width)
str += "\n" + '-'*80 + "\n\n"
str += get_crossbar(width)
str += "\n" + '-'*80 + "\n"
str
end
def self.get_shuffling_crossbar (nb_chunks, shift_width = 32)
cf = Architecture::ConfigShuffling.new(nb_chunks)
return cf.get_shuffling_crossbar(shift_width)
end
def self.default_key (nb_chunks)
cf = Architecture::ConfigShuffling.new(nb_chunks)
return cf.default_key
end
def self.read_key (nb_chunks, key)
cf = Architecture::ConfigShuffling.new(nb_chunks)
return cf.read_key(key)
end
def self.is_key_valid? (nb_chunks, key)
cf = Architecture::ConfigShuffling.new(nb_chunks)
return cf.is_key_valid?(key)
end
def self.random_key (nb_chunks)
cf = Architecture::ConfigShuffling.new(nb_chunks)
return cf.random_key
end
def self.get_split_points (nb_chunks, bitstream_length, shift_width: 32)
cf = Architecture::ConfigShuffling.new(nb_chunks)
return cf.get_split_points(bitstream_length, shift_width: shift_width)
end
def self.shuffle_bitstring (nb_chunks, key, bitstring, shift_width: 32)
cf = Architecture::ConfigShuffling.new(nb_chunks)
return cf.shuffle_bitstring(key, bitstring, shift_width: shift_width)
end
end
end
end
if __FILE__ == $0 then
require 'optparse'
require_relative 'VirtBitgen.rb'
$copyright = "Copyright (C) 2019 ENSTA Bretagne"
$authors = [
["Théotime Bollengier", "<theotime.bollengier@ensta-bretagne.fr>"]
]
$program_name = File.basename(__FILE__, '.*')
$program_version = "1.0"
$description = "This program shuffles an existing overlay bitstream file according to a key."
$description_string = "\nWelcome to #{$program_name} #{$program_version}, a component of the ArGen framework.\n#{$description}\n#{$copyright}\n#{$authors.collect.with_index{|a, i| "#{(i == 0) ? "Author#{($authors.length > 1) ? 's':''}: " : " #{($authors.length > 1) ? ' ':''} "}#{a.first} #{a.last}\n"}.join}"
STDOUT.sync = true
STDERR.sync = true
options = {}
optparse = OptionParser.new do |opts|
# Set a banner, displayed at the top
# of the help screen.
opts.banner = "Usage: #{File.basename($0)} [options] -i <input_bitstream_file> -k <key> -o <output_shuffled_bitstream_file>"
options[:inputFile] = nil
opts.on('-i', '--input FILE', 'Input bitstream file (unciphered)') do |file|
options[:inputFile] = file
end
options[:outputFile] = nil
opts.on('-o', '--output FILE', 'Output bitstream file (ciphered)') do |file|
options[:outputFile] = file
end
options[:nbChunks] = nil
opts.on('-n', '--nb_chunks INTEGER', "Number of configuration chunks on the target overlay implementation") do |n|
options[:nbChunks] = [2, n.to_i].max
end
options[:shiftWidth] = 32
opts.on('-w', '--shift_width INTEGER', "How many configuration bits are shifted per clock cycle in the target overlay implementation (defaults to 32)") do |n|
options[:shiftWidth] = [1, n.to_i].max
end
options[:key] = nil
opts.on('-k', '--key KEY', "Shuffling key, in the form <[0b|0d|0x]key | rand>[/nb_chunks[/shift_width]]") do |s|
options[:key] = s
end
options[:forASIC] = false
opts.on('--SPI_wrapper', 'Target the SPI wrapper instead of the Wishbone IP wrapper') do
options[:forASIC] = true
end
# This displays the help screen, all programs are
# assumed to have this option.
opts.on( '-h', '--help', 'Display this help' ) do
puts opts
puts $description_string
exit
end
end
# Parse the command-line. Remember there are two forms
# of the parse method. The 'parse' method simply parses
# ARGV, while the 'parse!' method parses ARGV and removes
# any options found there, as well as any parameters for
# the options. What's left is the list of files to resize.
begin
optparse.parse!
rescue Exception => e
if e.class == SystemExit then
exit 0
else
abort e.message + "\n" + optparse.to_s
end
end
STDERR.puts "WARNING: Unused commandline arguments: #{ARGV.join(' ')}" unless ARGV.empty?
abort "ERROR: You must specify the input file of the bitstream to cypher.\n" + optparse.to_s if options[:inputFile].nil?
abort "ERROR: You must specify an output file for the cyphered bitstream.\n" + optparse.to_s if options[:outputFile].nil?
abort "ERROR: You must specify a key.\n" + optparse.to_s if options[:key].nil?
key = options[:key].split('/')
options[:nbChunks] = [2, key[1].to_i].max if key.length > 1
options[:shiftWidth] = [1, key[2].to_i].max if key.length > 2
abort "ERROR: You must specify the number of configuration chunks on the target overlay implementation.\n" + optparse.to_s if options[:nbChunks].nil?
abort "ERROR: Bad key format.\n" + optparse.to_s if key.length > 3
key = key.first
cf = Architecture::ConfigShuffling.new(options[:nbChunks])
if key == 'rand' then
key = cf.random_key
puts "Random key: 0x#{key.to_s(16)}/#{options[:nbChunks]}/#{options[:shiftWidth]}"
elsif key == 'default' then
key = cf.default_key
puts "Default key: 0x#{key.to_s(16)}/#{options[:nbChunks]}/#{options[:shiftWidth]}"
else
key = Architecture::ConfigShuffling.key_string_to_integer(key)
puts "Using key: 0x#{key.to_s(16)}/#{options[:nbChunks]}/#{options[:shiftWidth]}"
end
input_content = nil
begin
input_content = File.binread(options[:inputFile])
rescue Exception => e
abort "ERROR: Cannot read file \"#{options[:inputFile]}\":\n#{e.message}\n"
end
if (input_content =~ /[^0-9a-fA-F\s]/).nil? then # This is an ASCII hexadecimal file
puts "Reading file as ASCII hexadecimal"
if options[:forASIC] then
bitstring = input_content.gsub(/[^0-9a-fA-F]/, '').scan(/.{1,8}/).collect{|s| s.to_i(16).to_s(2).rjust(32, '0').scan(/.{1,8}/).collect{|b| b.reverse}.join}.reverse.join
else
bitstring = input_content.gsub(/[^0-9a-fA-F]/, '').scan(/.{1,8}/).collect{|s| s.to_i(16).to_s(2).rjust(32, '0')}.reverse.join
end
else # This is a binary file
puts "Reading file as binnary"
if options[:forASIC] then
bitstring = input_content.unpack('C*').collect{|byte| byte.to_s(2).rjust(8, '0').reverse}.reverse.join
else
bitstring = input_content.unpack('L<*').collect{|w| w.to_s(2).rjust(32, '0')}.reverse.join
end
end
shuffled_bitstring = cf.shuffle_bitstring(key, bitstring, shift_width: options[:shiftWidth])
abort "ERROR: key is not valid" if shuffled_bitstring.nil?
if options[:outputFile] =~ /\.hex/ then
if options[:forASIC] then
Architecture::format_bit_string_to_hexa_bit_string_for_SPI_wrapper(shuffled_bitstring, options[:outputFile])
else
Architecture::format_bit_string_to_hexa_bit_string(shuffled_bitstring, options[:outputFile])
end
#File.write(options[:outputFile], shuffled_bitstring.scan(/.{1,32}/).reverse.collect{|word| word.to_i(2).to_s(16).rjust(8,'0')}.join("\n") + "\n")
puts "Shuffled bitstream written as ASCII hexadecimal in file \"#{options[:outputFile]}\""
else
if options[:forASIC] then
Architecture::format_bit_string_to_binary_bitstream_for_SPI_wrapper(shuffled_bitstring, options[:outputFile])
else
Architecture::format_bit_string_to_binary_bitstream(shuffled_bitstring, options[:outputFile])
end
#File.binwrite(options[:outputFile], shuffled_bitstring.scan(/.{1,32}/).reverse.collect{|word| word.to_i(2)}.pack('L<*'))
puts "Shuffled bitstream written as binnary in file \"#{options[:outputFile]}\""
end
end
require 'erb'
require_relative 'architecture.rb'
require_relative 'config_shuffling.rb'
module ArGen
module Architecture
class Arch
private
def get_BLE_vhdl_template
return File.read(File.expand_path('../../../share/hdl_templates/BLE_template.vhd', __FILE__))
end
def get_CLB_vhdl_template
return File.read(File.expand_path('../../../share/hdl_templates/CLB_template.vhd', __FILE__))
end
def get_matrix_vhdl_template
return File.read(File.expand_path('../../../share/hdl_templates/ARCH_matrix_template.vhd', __FILE__))
end
def get_wrapper_vhdl_template
return File.read(File.expand_path('../../../share/hdl_templates/WRAPPER_template.vhd', __FILE__))
end
def get_IP_vhdl_template
return File.read(File.expand_path('../../../share/hdl_templates/IP_template.vhd', __FILE__))
end
def get_eFPGA_top_level_template
return File.read(File.expand_path('../../../share/hdl_templates/eFPGA_top_level_template.vhd', __FILE__))
end
def get_eFPGA_spi_rxtx_template
return File.read(File.expand_path('../../../share/hdl_templates/eFPGA_spi_rxtx_template.vhd', __FILE__))
end
def get_eFPGA_rst_debounce_template
return File.read(File.expand_path('../../../share/hdl_templates/eFPGA_rst_debounce_template.vhd', __FILE__))
end
def get_eFPGA_clk_gen_template
return File.read(File.expand_path('../../../share/hdl_templates/eFPGA_clk_gen_template.vhd', __FILE__))
end
def get_eFPGA_vFPGA_wrapper_template
return File.read(File.expand_path('../../../share/hdl_templates/eFPGA_vFPGA_wrapper_template.vhd', __FILE__))
end
def get_eFPGA_tristate_IOs_template